File indexing completed on 2024-12-22 04:41:41

0001 /*
0002  * SPDX-FileCopyrightText: 2019 Erik Duisters <e.duisters1@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 package org.kde.kdeconnect.Plugins.SharePlugin;
0008 
0009 import android.os.Handler;
0010 import android.os.Looper;
0011 
0012 import androidx.annotation.GuardedBy;
0013 import androidx.annotation.NonNull;
0014 
0015 import org.kde.kdeconnect.Device;
0016 import org.kde.kdeconnect.NetworkPacket;
0017 import org.kde.kdeconnect.async.BackgroundJob;
0018 import org.kde.kdeconnect_tp.R;
0019 
0020 import java.util.ArrayList;
0021 import java.util.List;
0022 
0023 /**
0024  * A type of {@link BackgroundJob} that sends Files to another device.
0025  *
0026  * <p>
0027  *     We represent the individual upload requests as {@link NetworkPacket}s.
0028  * </p>
0029  * <p>
0030  *     Each packet should have a 'filename' property and a payload. If the payload is
0031  *     missing, we'll just send an empty file. You can add new packets anytime via
0032  *     {@link #addNetworkPacket(NetworkPacket)}.
0033  * </p>
0034  * <p>
0035  *     The I/O-part of this file sending is handled by
0036  *     {@link Device#sendPacketBlocking(NetworkPacket, Device.SendPacketStatusCallback)}.
0037  * </p>
0038  *
0039  * @see CompositeReceiveFileJob
0040  * @see SendPacketStatusCallback
0041  */
0042 public class CompositeUploadFileJob extends BackgroundJob<Device, Void> {
0043     private boolean isRunning;
0044     private final Handler handler;
0045     private String currentFileName;
0046     private int currentFileNum;
0047     private boolean updatePacketPending;
0048     private long totalSend;
0049     private int prevProgressPercentage;
0050     private final UploadNotification uploadNotification;
0051 
0052     private final Object lock;                              //Use to protect concurrent access to the variables below
0053     @GuardedBy("lock")
0054     private final List<NetworkPacket> networkPacketList;
0055     private NetworkPacket currentNetworkPacket;
0056     private final Device.SendPacketStatusCallback sendPacketStatusCallback;
0057     @GuardedBy("lock")
0058     private int totalNumFiles;
0059     @GuardedBy("lock")
0060     private long totalPayloadSize;
0061 
0062     CompositeUploadFileJob(@NonNull Device device, @NonNull Callback<Void> callback) {
0063         super(device, callback);
0064 
0065         isRunning = false;
0066         handler = new Handler(Looper.getMainLooper());
0067         currentFileNum = 0;
0068         currentFileName = "";
0069         updatePacketPending = false;
0070 
0071         lock = new Object();
0072         networkPacketList = new ArrayList<>();
0073         totalNumFiles = 0;
0074         totalPayloadSize = 0;
0075         totalSend = 0;
0076         prevProgressPercentage = 0;
0077         uploadNotification = new UploadNotification(getDevice(), getId());
0078 
0079         sendPacketStatusCallback = new SendPacketStatusCallback();
0080     }
0081 
0082     private Device getDevice() { return requestInfo; }
0083 
0084     @Override
0085     public void run() {
0086         boolean done;
0087 
0088         isRunning = true;
0089 
0090         synchronized (lock) {
0091             done = networkPacketList.isEmpty();
0092         }
0093 
0094         try {
0095             while (!done && !canceled) {
0096                 synchronized (lock) {
0097                     currentNetworkPacket = networkPacketList.remove(0);
0098                 }
0099 
0100                 currentFileName = currentNetworkPacket.getString("filename");
0101                 currentFileNum++;
0102 
0103                 setProgress(prevProgressPercentage);
0104 
0105                 addTotalsToNetworkPacket(currentNetworkPacket);
0106 
0107                 // We set sendPayloadFromSameThread to true so this call blocks until the payload
0108                 // has been received by the other end,  so payloads are sent one by one.
0109                 if (!getDevice().sendPacketBlocking(currentNetworkPacket, sendPacketStatusCallback, true)) {
0110                     throw new RuntimeException("Sending packet failed");
0111                 }
0112 
0113                 synchronized (lock) {
0114                     done = networkPacketList.isEmpty();
0115                 }
0116             }
0117 
0118             if (canceled) {
0119                 uploadNotification.cancel();
0120             } else {
0121                 uploadNotification.setFinished(getDevice().getContext().getResources().getQuantityString(R.plurals.sent_files_title, currentFileNum, getDevice().getName(), currentFileNum));
0122                 uploadNotification.show();
0123 
0124                 reportResult(null);
0125             }
0126         } catch (RuntimeException e) {
0127             int failedFiles;
0128             synchronized (lock) {
0129                 failedFiles = (totalNumFiles - currentFileNum + 1);
0130                 uploadNotification.setFailed(getDevice().getContext().getResources()
0131                         .getQuantityString(R.plurals.send_files_fail_title, failedFiles, getDevice().getName(),
0132                                 failedFiles, totalNumFiles));
0133             }
0134 
0135             uploadNotification.show();
0136             reportError(e);
0137         } finally {
0138             isRunning = false;
0139 
0140             for (NetworkPacket networkPacket : networkPacketList) {
0141                 networkPacket.getPayload().close();
0142             }
0143             networkPacketList.clear();
0144         }
0145     }
0146 
0147     private void addTotalsToNetworkPacket(NetworkPacket networkPacket) {
0148         synchronized (lock) {
0149             networkPacket.set(SharePlugin.KEY_NUMBER_OF_FILES, totalNumFiles);
0150             networkPacket.set(SharePlugin.KEY_TOTAL_PAYLOAD_SIZE, totalPayloadSize);
0151         }
0152     }
0153 
0154     private void setProgress(int progress) {
0155         synchronized (lock) {
0156             uploadNotification.setProgress(progress, getDevice().getContext().getResources()
0157                     .getQuantityString(R.plurals.outgoing_files_text, totalNumFiles, currentFileName, currentFileNum, totalNumFiles));
0158         }
0159         uploadNotification.show();
0160     }
0161 
0162     void addNetworkPacket(@NonNull NetworkPacket networkPacket) {
0163         synchronized (lock) {
0164             networkPacketList.add(networkPacket);
0165 
0166             totalNumFiles++;
0167 
0168             if (networkPacket.getPayloadSize() >= 0) {
0169                 totalPayloadSize += networkPacket.getPayloadSize();
0170             }
0171 
0172             uploadNotification.setTitle(getDevice().getContext().getResources()
0173                     .getQuantityString(R.plurals.outgoing_file_title, totalNumFiles, totalNumFiles, getDevice().getName()));
0174 
0175             //Give SharePlugin some time to add more NetworkPackets
0176             if (isRunning && !updatePacketPending) {
0177                 updatePacketPending = true;
0178                 handler.post(this::sendUpdatePacket);
0179             }
0180         }
0181     }
0182 
0183     /**
0184      * Use this to send metadata ahead of all the other {@link #networkPacketList packets}.
0185      */
0186     private void sendUpdatePacket() {
0187         NetworkPacket np = new NetworkPacket(SharePlugin.PACKET_TYPE_SHARE_REQUEST_UPDATE);
0188 
0189         synchronized (lock) {
0190             np.set("numberOfFiles", totalNumFiles);
0191             np.set("totalPayloadSize", totalPayloadSize);
0192             updatePacketPending = false;
0193         }
0194 
0195         getDevice().sendPacket(np);
0196     }
0197 
0198     @Override
0199     public void cancel() {
0200         super.cancel();
0201 
0202         currentNetworkPacket.cancel();
0203     }
0204 
0205     private class SendPacketStatusCallback extends Device.SendPacketStatusCallback {
0206         @Override
0207         public void onPayloadProgressChanged(int percent) {
0208             float send = totalSend + (currentNetworkPacket.getPayloadSize() * ((float)percent / 100));
0209             int progress = (int)((send * 100) / totalPayloadSize);
0210 
0211             if (progress != prevProgressPercentage) {
0212                 setProgress(progress);
0213                 prevProgressPercentage = progress;
0214             }
0215         }
0216 
0217         @Override
0218         public void onSuccess() {
0219             if (currentNetworkPacket.getPayloadSize() == 0) {
0220                 synchronized (lock) {
0221                     if (networkPacketList.isEmpty()) {
0222                         setProgress(100);
0223                     }
0224                 }
0225             }
0226 
0227             totalSend += currentNetworkPacket.getPayloadSize();
0228         }
0229 
0230         @Override
0231         public void onFailure(Throwable e) {
0232             // Handled in the run() function when sendPacketBlocking returns false
0233         }
0234     }
0235 }