/*
- * Copyright 2020-2023 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2020-2024 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
*
import android.app.Activity
import android.content.Context
import android.net.Uri
-import android.os.Build
import android.provider.OpenableColumns
import android.util.Base64
import android.webkit.CookieManager
import java.net.HttpURLConnection
import java.net.URL
import java.text.NumberFormat
+import java.util.Date
class SaveUrlCoroutine {
fun save(context: Context, activity: Activity, urlString: String, fileUri: Uri, userAgent: String, cookiesEnabled: Boolean) {
+ // Create a canceled boolean.
+ var canceled = false
+
// Use a coroutine to save the URL.
CoroutineScope(Dispatchers.Main).launch {
- // Create a file name string.
- val fileNameString: String
-
- // Query the exact file name if the API >= 26.
- if (Build.VERSION.SDK_INT >= 26) {
- // Get a cursor from the content resolver.
- val contentResolverCursor = activity.contentResolver.query(fileUri, null, null, null)!!
+ // Get a cursor from the content resolver.
+ val contentResolverCursor = activity.contentResolver.query(fileUri, null, null, null)!!
- // Move to the first row.
- contentResolverCursor.moveToFirst()
+ // Move to the first row.
+ contentResolverCursor.moveToFirst()
- // Get the file name from the cursor.
- fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
+ // Get the file name from the cursor.
+ val fileNameString = contentResolverCursor.getString(contentResolverCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
- // Close the cursor.
- contentResolverCursor.close()
- } else {
- // Use the file URI last path segment as the file name string.
- fileNameString = fileUri.lastPathSegment!!
- }
+ // Close the cursor.
+ contentResolverCursor.close()
// Get a handle for the no swipe view pager.
val webViewViewPager2 = activity.findViewById<ViewPager2>(R.id.webview_viewpager2)
// Create a saving file snackbar.
val savingFileSnackbar = Snackbar.make(webViewViewPager2, activity.getString(R.string.saving_file, 0, fileNameString), Snackbar.LENGTH_INDEFINITE)
+ .setAction(R.string.cancel) { canceled = true }
// Display the saving file snackbar.
savingFileSnackbar.show()
val inputStream: InputStream = BufferedInputStream(httpUrlConnection.inputStream)
// Initialize the conversion buffer byte array.
- // This is set to a megabyte so that frequent updating of the snackbar doesn't freeze the interface on download. <https://redmine.stoutner.com/issues/709>
- val conversionBufferByteArray = ByteArray(1048576)
+ // This is set to a 50,000 bytes so that frequent updating of the snackbar doesn't freeze the interface on download, although `inputStream.read` currently uses 8,000 as an upper limit.
+ // <https://redmine.stoutner.com/issues/709>
+ val conversionBufferByteArray = ByteArray(50_000)
- // Initialize the downloaded kilobytes counter.
- var downloadedKilobytesCounter: Long = 0
+ // Initialize the longs.
+ var downloadedBytesCounterLong: Long = 0
+ var lastSnackbarUpdateLong: Long = 0
// Define the buffer length variable.
var bufferLength: Int
// Attempt to read data from the input stream and store it in the output stream. Also store the amount of data read in the buffer length variable.
- while (inputStream.read(conversionBufferByteArray).also { bufferLength = it } > 0) { // Proceed while the amount of data stored in the buffer in > 0.
+ while ((inputStream.read(conversionBufferByteArray).also { bufferLength = it } > 0) && !canceled) { // Proceed while the amount of data stored in the buffer in > 0.
// Write the contents of the conversion buffer to the file output stream.
outputStream.write(conversionBufferByteArray, 0, bufferLength)
- // Update the downloaded kilobytes counter.
- downloadedKilobytesCounter += bufferLength
+ // Update the downloaded bytes counter.
+ downloadedBytesCounterLong += bufferLength
// Format the number of bytes downloaded.
- val formattedNumberOfBytesDownloadedString = NumberFormat.getInstance().format(downloadedKilobytesCounter)
-
- // Update the UI.
- withContext(Dispatchers.Main) {
- // Check to see if the file size is known.
- if (fileSize == -1L) { // The size of the download file is not known.
- // Update the snackbar.
- savingFileSnackbar.setText(activity.getString(R.string.saving_file_progress, formattedNumberOfBytesDownloadedString, fileNameString))
- } else { // The size of the download file is known.
- // Calculate the download percentage.
- val downloadPercentage = downloadedKilobytesCounter * 100 / fileSize
-
- // Update the snackbar.
- savingFileSnackbar.setText(activity.getString(R.string.saving_file_percentage_progress, downloadPercentage, formattedNumberOfBytesDownloadedString, formattedFileSize,
- fileNameString)
- )
+ val formattedNumberOfBytesDownloadedString = NumberFormat.getInstance().format(downloadedBytesCounterLong)
+
+ // Get the current time.
+ val currentTimeLong = Date().time
+
+ // Update the snackbar if more than 100 milliseconds have passed since the last update.
+ // Updating the snackbar is so resource intensive that it will throttle the download if it is done too frequently.
+ if (currentTimeLong - lastSnackbarUpdateLong > 100) {
+ // Store the update time.
+ lastSnackbarUpdateLong = currentTimeLong
+
+ // Update the UI.
+ withContext(Dispatchers.Main) {
+ // Check to see if the file size is known.
+ if (fileSize == -1L) { // The size of the download file is not known.
+ // Update the snackbar.
+ savingFileSnackbar.setText(activity.getString(R.string.saving_file_progress, formattedNumberOfBytesDownloadedString, fileNameString))
+ } else { // The size of the download file is known.
+ // Calculate the download percentage.
+ val downloadPercentage = downloadedBytesCounterLong * 100 / fileSize
+
+ // Update the snackbar.
+ savingFileSnackbar.setText(activity.getString(R.string.saving_file_percentage_progress, downloadPercentage, formattedNumberOfBytesDownloadedString, formattedFileSize,
+ fileNameString)
+ )
+ }
}
}
}
// Dismiss the saving file snackbar.
savingFileSnackbar.dismiss()
- // Display the file saved snackbar.
- Snackbar.make(webViewViewPager2, activity.getString(R.string.saved, fileNameString), Snackbar.LENGTH_LONG).show()
+ // Display a final disposition snackbar.
+ if (canceled)
+ // Display the download cancelled snackbar.
+ Snackbar.make(webViewViewPager2, activity.getString(R.string.download_cancelled), Snackbar.LENGTH_SHORT).show()
+ else
+ // Display the file saved snackbar.
+ Snackbar.make(webViewViewPager2, activity.getString(R.string.saved, fileNameString), Snackbar.LENGTH_LONG).show()
}
} catch (exception: Exception) {
// Update the UI.
}
}
}
-}
\ No newline at end of file
+}