@@ -51,6 +51,7 @@ import java.io.File
5151import java.io.IOException
5252import java.io.InputStream
5353import java.io.InputStreamReader
54+ import java.lang.Thread.sleep
5455import java.net.Socket
5556import javax.inject.Inject
5657import javax.net.ssl.SSLServerSocket
@@ -109,6 +110,22 @@ class FileTransferService : Service() {
109110 }
110111 }
111112
113+ ACTION_SEND_BULK_FILES -> {
114+ val fileUris = intent.getParcelableArrayListExtra<Uri >(EXTRA_FILE_URIS ) ? : run {
115+ Log .e(TAG , " No URIs provided in intent" )
116+ return START_NOT_STICKY
117+ }
118+ serviceScope.launch {
119+ try {
120+ Log .d(TAG , " Starting bulk file transfer process for ${fileUris.size} files" )
121+ sendBulkFiles(fileUris)
122+ } catch (e: Exception ) {
123+ Log .e(TAG , " Bulk file transfer failed" , e)
124+ updateNotificationForError(e.message ? : " Bulk transfer failed" )
125+ }
126+ }
127+ }
128+
112129 ACTION_RECEIVE_FILE -> {
113130 val fileTransfer = intent.getParcelableExtra<FileTransfer >(EXTRA_FILE_TRANSFER )
114131 val bulkTransfer = intent.getParcelableExtra<BulkFileTransfer >(EXTRA_BULK_TRANSFER )
@@ -143,62 +160,109 @@ class FileTransferService : Service() {
143160 }
144161
145162 private suspend fun sendFile (fileUri : Uri ) {
146- val fileName = getFileMetadata(this , fileUri).fileName
147- setupNotification(TransferType .Sending (fileName))
148- try {
149- val password = generateRandomPassword()
150- val serverInfo = initializeServer(password) ? : return
151- fileInputStream = this .contentResolver.openInputStream(fileUri)
152- val fileMetadata = getFileMetadata(this , fileUri)
163+ val fileMetadata = getFileMetadata(this , fileUri)
164+ setupNotification(TransferType .Sending (fileMetadata.fileName))
165+
166+ val password = generateRandomPassword()
167+ val serverInfo = initializeServer(password) ? : return
168+ networkManager.sendMessage(FileTransfer (serverInfo, fileMetadata))
169+
170+ sendFileInternal(
171+ fileUris = listOf (fileUri),
172+ filesMetadata = listOf (fileMetadata),
173+ password = password,
174+ isBulk = false
175+ )
176+ }
153177
154- val message = FileTransfer (serverInfo, fileMetadata)
155- networkManager.sendMessage(message)
178+ private suspend fun sendBulkFiles (fileUris : List <Uri >) {
179+ val filesMetadata = fileUris.map { getFileMetadata(this , it) }
180+ setupNotification(TransferType .Sending (" ${filesMetadata.size} files" ))
181+
182+ val password = generateRandomPassword()
183+ val serverInfo = initializeServer(password) ? : return
184+ networkManager.sendMessage(BulkFileTransfer (serverInfo, filesMetadata))
185+
186+ sendFileInternal(
187+ fileUris = fileUris,
188+ filesMetadata = filesMetadata,
189+ password = password,
190+ isBulk = true
191+ )
192+ }
156193
194+ private suspend fun sendFileInternal (
195+ fileUris : List <Uri >,
196+ filesMetadata : List <FileMetadata >,
197+ password : String ,
198+ isBulk : Boolean
199+ ) {
200+ try {
157201 withContext(Dispatchers .IO ) {
158202 socket = serverSocket?.accept() as ? SSLSocket
159203 ? : throw IOException (" Failed to accept SSL connection" )
160204
161205 val outputStream = socket!! .getOutputStream()
162206 val inputStream = socket!! .getInputStream()
163-
164207 val reader = BufferedReader (InputStreamReader (inputStream, Charsets .UTF_8 ))
165208
166209 withTimeout(5000 ) {
167210 if (reader.readLine() != password) throw IOException (" Invalid password" )
168211 }
169212
170- val buffer = ByteArray (DEFAULT_BUFFER_SIZE )
171- var bytesRead: Int
213+ val totalSize = filesMetadata.sumOf { it.fileSize }
172214 var totalBytesTransferred = 0L
173215
174- try {
175- // Send the file data
216+ fileUris.forEachIndexed { index, fileUri ->
217+ val fileMetadata = filesMetadata[index]
218+
219+ if (isBulk) {
220+ setupNotification(TransferType .Sending (" ${fileMetadata.fileName} (${index + 1 } /${fileUris.size} )" ))
221+ }
222+ fileInputStream = contentResolver.openInputStream(fileUri)
223+ val buffer = ByteArray (DEFAULT_BUFFER_SIZE )
224+ var bytesRead: Int
225+ outputStream.flush()
226+
176227 while (fileInputStream?.read(buffer).also { bytesRead = it ? : - 1 } != - 1 ) {
177228 outputStream.write(buffer, 0 , bytesRead)
178229 outputStream.flush()
179230
180231 totalBytesTransferred + = bytesRead
181- updateTransferProgress(totalBytesTransferred, fileMetadata.fileSize )
232+ updateTransferProgress(totalBytesTransferred, totalSize )
182233 }
183-
184- // Signal end of file stream
185234 outputStream.flush()
186- socket?.shutdownOutput()
235+ fileInputStream?.close()
236+ fileInputStream = null
187237
188238 withTimeout(5000 ) {
189- if (reader.readLine() == " Complete" ) {
190- delay(1000 )
191- withContext(Dispatchers .Main ) {
192- showCompletedNotification()
193- }
194- } else {
195- throw IOException (" Invalid confirmation received" )
239+ val message = reader.readLine()
240+ if (message != FILE_TRANSFER_COMPLETION )
241+ throw IOException (" Invalid bulk transfer confirmation received: '$message '" )
242+ }
243+ sleep(150 )
244+ }
245+
246+ // Signal end of transfer
247+ outputStream.flush()
248+
249+ withTimeout(5000 ) {
250+ val finalMessage = reader.readLine()
251+ if (finalMessage == FILE_TRANSFER_COMPLETION ) {
252+ delay(100 )
253+ withContext(Dispatchers .Main ) {
254+ showBulkTransferCompletedNotification(fileUris.size)
196255 }
256+ } else {
257+ throw IOException (" Invalid bulk transfer confirmation received" )
197258 }
198- } catch (e: Exception ) {
199- Log .e(TAG , " Error during file transfer" , e)
200- throw IOException (" File transfer interrupted" , e)
201259 }
260+
261+ // Shutdown output after receiving final confirmation
262+ socket?.shutdownOutput()
263+ }
264+ withContext(Dispatchers .Main ) {
265+ showCompletedNotification()
202266 }
203267 } catch (e: Exception ) {
204268 Log .e(TAG , " File transfer failed" , e)
@@ -216,8 +280,6 @@ class FileTransferService : Service() {
216280 try {
217281 serverSocket = ipAddress?.let { socketFactory.tcpServerSocket(port, it) } ? : return null
218282 Log .d(TAG , " Server socket created on ${ipAddress} :${port} , waiting for client to connect" )
219-
220-
221283 return ServerInfo (ipAddress, port, password)
222284 } catch (e: Exception ) {
223285 Log .w(TAG , " Failed to create server socket on port $port , trying next port" , e)
@@ -247,13 +309,13 @@ class FileTransferService : Service() {
247309 readChannel = readChannel,
248310 metadata = fileTransfer.fileMetadata
249311 )
250- writeChannel.writeStringUtf8(" Success " )
312+ writeChannel.writeStringUtf8(FILE_TRANSFER_COMPLETION )
251313 writeChannel.flush()
252314
253315 showCompletedNotification(fileUri = fileUri, mimeType = fileTransfer.fileMetadata.mimeType)
254316
255317 if (preferencesRepository.readImageClipboardSettings().first()) {
256- val clipboardManager = this .getSystemService(Context . CLIPBOARD_SERVICE ) as ClipboardManager
318+ val clipboardManager = this .getSystemService(CLIPBOARD_SERVICE ) as ClipboardManager
257319 clipboardManager.setPrimaryClip(ClipData .newUri(contentResolver, " received file" , fileUri))
258320 }
259321 } finally {
@@ -294,7 +356,7 @@ class FileTransferService : Service() {
294356 totalReceived = totalReceived,
295357 totalSize = totalSize
296358 )
297- writeChannel.writeStringUtf8(" Success " )
359+ writeChannel.writeStringUtf8(FILE_TRANSFER_COMPLETION )
298360 writeChannel.flush()
299361 totalReceived + = metadata.fileSize
300362
@@ -584,10 +646,13 @@ class FileTransferService : Service() {
584646 companion object {
585647
586648 const val ACTION_SEND_FILE = " sefirah.network.action.SEND_FILE"
649+ const val ACTION_SEND_BULK_FILES = " sefirah.network.action.SEND_BULK_FILES"
587650 const val ACTION_RECEIVE_FILE = " sefirah.network.action.RECEIVE_FILE"
588651 const val EXTRA_FILE_TRANSFER = " sefirah.network.extra.FILE_TRANSFER"
589652 const val EXTRA_BULK_TRANSFER = " sefirah.network.extra.BULK_TRANSFER"
653+ const val EXTRA_FILE_URIS = " sefirah.network.extra.FILE_URIS"
590654 const val ACTION_CANCEL_TRANSFER = " sefirah.network.action.CANCEL_TRANSFER"
655+ const val FILE_TRANSFER_COMPLETION = " Complete"
591656
592657 private const val TAG = " FileTransferService"
593658
@@ -597,3 +662,4 @@ class FileTransferService : Service() {
597662 private const val CANCEL_REQUEST_CODE = 100
598663 }
599664}
665+
0 commit comments