execute file fallback on file thread

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/persistance/FileBackend.java                   | 18 
src/main/java/eu/siacs/conversations/services/AttachFileToConversationRunnable.java | 99 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java            |  2 
3 files changed, 78 insertions(+), 41 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/persistance/FileBackend.java 🔗

@@ -268,7 +268,8 @@ public class FileBackend {
         return inSampleSize;
     }
 
-    private static Dimensions getVideoDimensions(Context context, Uri uri) throws NotAVideoFile, IOException {
+    private static Dimensions getVideoDimensions(Context context, Uri uri)
+            throws NotAVideoFile, IOException {
         MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
         try {
             mediaMetadataRetriever.setDataSource(context, uri);
@@ -663,16 +664,14 @@ public class FileBackend {
         }
     }
 
-    private void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
+    private void copyFileToPrivateStorage(final File file, final Uri uri) throws FileCopyException {
         final var parentDirectory = file.getParentFile();
         if (parentDirectory != null && parentDirectory.mkdirs()) {
-            Log.d(Config.LOGTAG,"created directory "+parentDirectory.getAbsolutePath());
+            Log.d(Config.LOGTAG, "created directory " + parentDirectory.getAbsolutePath());
         }
         try {
             if (file.createNewFile()) {
-                Log.d(
-                        Config.LOGTAG,
-                        "copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath());
+                Log.d(Config.LOGTAG, "created empty file " + file.getAbsolutePath());
             }
         } catch (final IOException e) {
             throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
@@ -708,9 +707,10 @@ public class FileBackend {
         }
     }
 
-    public void copyFileToPrivateStorage(Message message, Uri uri, String type)
+    public void copyFileToPrivateStorage(final Message message, final Uri uri, final String type)
             throws FileCopyException {
-        final String mime = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
+        final String mime =
+                MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
         Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime=" + mime + ")");
         String extension = MimeUtils.guessExtensionFromMimeType(mime);
         if (extension == null) {
@@ -1500,7 +1500,7 @@ public class FileBackend {
         return calcSampleSize(options, size);
     }
 
-    public void updateFileParams(Message message) {
+    public void updateFileParams(final Message message) {
         updateFileParams(message, null);
     }
 

src/main/java/eu/siacs/conversations/services/AttachFileToConversationRunnable.java 🔗

@@ -11,12 +11,6 @@ import androidx.annotation.NonNull;
 import com.otaliastudios.transcoder.Transcoder;
 import com.otaliastudios.transcoder.TranscoderListener;
 
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.Objects;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.crypto.PgpEngine;
@@ -27,6 +21,12 @@ import eu.siacs.conversations.ui.UiCallback;
 import eu.siacs.conversations.utils.MimeUtils;
 import eu.siacs.conversations.utils.TranscoderStrategies;
 
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
 public class AttachFileToConversationRunnable implements Runnable, TranscoderListener {
 
     private final XmppConnectionService mXmppConnectionService;
@@ -38,16 +38,26 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
     private final long originalFileSize;
     private int currentProgress = -1;
 
-    AttachFileToConversationRunnable(XmppConnectionService xmppConnectionService, Uri uri, String type, Message message, UiCallback<Message> callback) {
+    AttachFileToConversationRunnable(
+            XmppConnectionService xmppConnectionService,
+            Uri uri,
+            String type,
+            Message message,
+            UiCallback<Message> callback) {
         this.uri = uri;
         this.type = type;
         this.mXmppConnectionService = xmppConnectionService;
         this.message = message;
         this.callback = callback;
-        final String mimeType = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
-        final int autoAcceptFileSize = mXmppConnectionService.getResources().getInteger(R.integer.auto_accept_filesize);
+        final String mimeType =
+                MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
+        final int autoAcceptFileSize =
+                mXmppConnectionService.getResources().getInteger(R.integer.auto_accept_filesize);
         this.originalFileSize = FileBackend.getFileSize(mXmppConnectionService, uri);
-        this.isVideoMessage = (mimeType != null && mimeType.startsWith("video/")) && originalFileSize > autoAcceptFileSize && !"uncompressed".equals(getVideoCompression());
+        this.isVideoMessage =
+                (mimeType != null && mimeType.startsWith("video/"))
+                        && originalFileSize > autoAcceptFileSize
+                        && !"uncompressed".equals(getVideoCompression());
     }
 
     boolean isVideoMessage() {
@@ -67,7 +77,9 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
             }
         } else {
             try {
-                mXmppConnectionService.getFileBackend().copyFileToPrivateStorage(message, uri, type);
+                mXmppConnectionService
+                        .getFileBackend()
+                        .copyFileToPrivateStorage(message, uri, type);
                 mXmppConnectionService.getFileBackend().updateFileParams(message);
                 if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
                     final PgpEngine pgpEngine = mXmppConnectionService.getPgpEngine();
@@ -80,16 +92,26 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
                     mXmppConnectionService.sendMessage(message);
                     callback.success(message);
                 }
-            } catch (FileBackend.FileCopyException e) {
+            } catch (final FileBackend.FileCopyException e) {
                 callback.error(e.getResId(), message);
             }
         }
     }
 
+    private void fallbackToProcessAsFile() {
+        final var file = mXmppConnectionService.getFileBackend().getFile(message);
+        if (file.exists() && file.delete()) {
+            Log.d(Config.LOGTAG, "deleted preexisting file " + file.getAbsolutePath());
+        }
+        XmppConnectionService.FILE_ATTACHMENT_EXECUTOR.execute(this::processAsFile);
+    }
+
     private void processAsVideo() throws FileNotFoundException {
         Log.d(Config.LOGTAG, "processing file as video");
         mXmppConnectionService.startOngoingVideoTranscodingForegroundNotification();
-        mXmppConnectionService.getFileBackend().setupRelativeFilePath(message, String.format("%s.%s", message.getUuid(), "mp4"));
+        mXmppConnectionService
+                .getFileBackend()
+                .setupRelativeFilePath(message, String.format("%s.%s", message.getUuid(), "mp4"));
         final DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
         if (Objects.requireNonNull(file.getParentFile()).mkdirs()) {
             Log.d(Config.LOGTAG, "created parent directory for video file");
@@ -99,16 +121,23 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
 
         final Future<Void> future;
         try {
-            future = Transcoder.into(file.getAbsolutePath()).
-                addDataSource(mXmppConnectionService, uri)
-                .setVideoTrackStrategy(highQuality ? TranscoderStrategies.VIDEO_720P : TranscoderStrategies.VIDEO_360P)
-                .setAudioTrackStrategy(highQuality ? TranscoderStrategies.AUDIO_HQ : TranscoderStrategies.AUDIO_MQ)
-                .setListener(this)
-                .transcode();
+            future =
+                    Transcoder.into(file.getAbsolutePath())
+                            .addDataSource(mXmppConnectionService, uri)
+                            .setVideoTrackStrategy(
+                                    highQuality
+                                            ? TranscoderStrategies.VIDEO_720P
+                                            : TranscoderStrategies.VIDEO_360P)
+                            .setAudioTrackStrategy(
+                                    highQuality
+                                            ? TranscoderStrategies.AUDIO_HQ
+                                            : TranscoderStrategies.AUDIO_MQ)
+                            .setListener(this)
+                            .transcode();
         } catch (final RuntimeException e) {
             // transcode can already throw if there is an invalid file format or a platform bug
             mXmppConnectionService.stopOngoingVideoTranscodingForegroundNotification();
-            processAsFile();
+            fallbackToProcessAsFile();
             return;
         }
         try {
@@ -118,9 +147,9 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
         } catch (final ExecutionException e) {
             if (e.getCause() instanceof Error) {
                 mXmppConnectionService.stopOngoingVideoTranscodingForegroundNotification();
-                processAsFile();
+                fallbackToProcessAsFile();
             } else {
-                Log.d(Config.LOGTAG, "ignoring execution exception. Should get handled by onTranscodeFiled() instead", e);
+                Log.d(Config.LOGTAG, "ignoring execution exception. Handled by onTranscodeFiled()");
             }
         }
     }
@@ -130,7 +159,9 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
         final int p = (int) Math.round(progress * 100);
         if (p > currentProgress) {
             currentProgress = p;
-            mXmppConnectionService.getNotificationService().updateFileAddingNotification(p, message);
+            mXmppConnectionService
+                    .getNotificationService()
+                    .updateFileAddingNotification(p, message);
         }
     }
 
@@ -139,11 +170,15 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
         mXmppConnectionService.stopOngoingVideoTranscodingForegroundNotification();
         final File file = mXmppConnectionService.getFileBackend().getFile(message);
         long convertedFileSize = mXmppConnectionService.getFileBackend().getFile(message).getSize();
-        Log.d(Config.LOGTAG, "originalFileSize=" + originalFileSize + " convertedFileSize=" + convertedFileSize);
+        Log.d(
+                Config.LOGTAG,
+                "originalFileSize=" + originalFileSize + " convertedFileSize=" + convertedFileSize);
         if (originalFileSize != 0 && convertedFileSize >= originalFileSize) {
             if (file.delete()) {
-                Log.d(Config.LOGTAG, "original file size was smaller. deleting and processing as file");
-                processAsFile();
+                Log.d(
+                        Config.LOGTAG,
+                        "original file size was smaller. deleting and processing as file");
+                fallbackToProcessAsFile();
                 return;
             } else {
                 Log.d(Config.LOGTAG, "unable to delete converted file");
@@ -161,14 +196,14 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
     @Override
     public void onTranscodeCanceled() {
         mXmppConnectionService.stopOngoingVideoTranscodingForegroundNotification();
-        processAsFile();
+        fallbackToProcessAsFile();
     }
 
     @Override
     public void onTranscodeFailed(@NonNull final Throwable exception) {
         mXmppConnectionService.stopOngoingVideoTranscodingForegroundNotification();
         Log.d(Config.LOGTAG, "video transcoding failed", exception);
-        processAsFile();
+        fallbackToProcessAsFile();
     }
 
     @Override
@@ -176,7 +211,7 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
         if (this.isVideoMessage()) {
             try {
                 processAsVideo();
-            } catch (FileNotFoundException e) {
+            } catch (final FileNotFoundException e) {
                 processAsFile();
             }
         } else {
@@ -189,7 +224,9 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
     }
 
     public static String getVideoCompression(final Context context) {
-        final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
-        return preferences.getString("video_compression", context.getResources().getString(R.string.video_compression));
+        final SharedPreferences preferences =
+                PreferenceManager.getDefaultSharedPreferences(context);
+        return preferences.getString(
+                "video_compression", context.getResources().getString(R.string.video_compression));
     }
 }

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -205,7 +205,7 @@ public class XmppConnectionService extends Service {
 
     public final CountDownLatch restoredFromDatabaseLatch = new CountDownLatch(1);
     private final static Executor FILE_OBSERVER_EXECUTOR = Executors.newSingleThreadExecutor();
-    private final static Executor FILE_ATTACHMENT_EXECUTOR = Executors.newSingleThreadExecutor();
+    public final static Executor FILE_ATTACHMENT_EXECUTOR = Executors.newSingleThreadExecutor();
 
     private final ScheduledExecutorService internalPingExecutor = Executors.newSingleThreadScheduledExecutor();
     private final static SerialSingleThreadExecutor VIDEO_COMPRESSION_EXECUTOR = new SerialSingleThreadExecutor("VideoCompression");