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 }