Search in sources :

Example 1 with NotificationController

use of org.thoughtcrime.securesms.service.NotificationController in project Signal-Android by WhisperSystems.

the class AttachmentUploadJob method onRun.

@Override
public void onRun() throws Exception {
    if (!Recipient.self().isRegistered()) {
        throw new NotPushRegisteredException();
    }
    Data inputData = getInputData();
    ResumableUploadSpec resumableUploadSpec;
    if (forceV2) {
        Log.d(TAG, "Forcing utilization of V2");
        resumableUploadSpec = null;
    } else if (inputData != null && inputData.hasString(ResumableUploadSpecJob.KEY_RESUME_SPEC)) {
        Log.d(TAG, "Using attachments V3");
        resumableUploadSpec = ResumableUploadSpec.deserialize(inputData.getString(ResumableUploadSpecJob.KEY_RESUME_SPEC));
    } else {
        Log.d(TAG, "Using attachments V2");
        resumableUploadSpec = null;
    }
    SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender();
    AttachmentDatabase database = SignalDatabase.attachments();
    DatabaseAttachment databaseAttachment = database.getAttachment(attachmentId);
    if (databaseAttachment == null) {
        throw new InvalidAttachmentException("Cannot find the specified attachment.");
    }
    long timeSinceUpload = System.currentTimeMillis() - databaseAttachment.getUploadTimestamp();
    if (timeSinceUpload < UPLOAD_REUSE_THRESHOLD && !TextUtils.isEmpty(databaseAttachment.getLocation())) {
        Log.i(TAG, "We can re-use an already-uploaded file. It was uploaded " + timeSinceUpload + " ms ago. Skipping.");
        return;
    } else if (databaseAttachment.getUploadTimestamp() > 0) {
        Log.i(TAG, "This file was previously-uploaded, but too long ago to be re-used. Age: " + timeSinceUpload + " ms");
    }
    Log.i(TAG, "Uploading attachment for message " + databaseAttachment.getMmsId() + " with ID " + databaseAttachment.getAttachmentId());
    try (NotificationController notification = getNotificationForAttachment(databaseAttachment)) {
        SignalServiceAttachment localAttachment = getAttachmentFor(databaseAttachment, notification, resumableUploadSpec);
        SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream());
        Attachment attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.getFastPreflightId()).get();
        database.updateAttachmentAfterUpload(databaseAttachment.getAttachmentId(), attachment, remoteAttachment.getUploadTimestamp());
    } catch (NonSuccessfulResumableUploadResponseCodeException e) {
        if (e.getCode() == 400) {
            Log.w(TAG, "Failed to upload due to a 400 when getting resumable upload information. Downgrading to attachments v2", e);
            forceV2 = true;
        }
    }
}
Also used : NotPushRegisteredException(org.thoughtcrime.securesms.net.NotPushRegisteredException) DatabaseAttachment(org.thoughtcrime.securesms.attachments.DatabaseAttachment) SignalServiceMessageSender(org.whispersystems.signalservice.api.SignalServiceMessageSender) SignalServiceAttachmentPointer(org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer) Data(org.thoughtcrime.securesms.jobmanager.Data) PointerAttachment(org.thoughtcrime.securesms.attachments.PointerAttachment) DatabaseAttachment(org.thoughtcrime.securesms.attachments.DatabaseAttachment) SignalServiceAttachment(org.whispersystems.signalservice.api.messages.SignalServiceAttachment) Attachment(org.thoughtcrime.securesms.attachments.Attachment) AttachmentDatabase(org.thoughtcrime.securesms.database.AttachmentDatabase) NonSuccessfulResumableUploadResponseCodeException(org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResumableUploadResponseCodeException) SignalServiceAttachment(org.whispersystems.signalservice.api.messages.SignalServiceAttachment) ResumableUploadSpec(org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec) NotificationController(org.thoughtcrime.securesms.service.NotificationController)

Example 2 with NotificationController

use of org.thoughtcrime.securesms.service.NotificationController in project Signal-Android by WhisperSystems.

the class LocalBackupJob method onRun.

@Override
public void onRun() throws NoExternalStorageException, IOException {
    Log.i(TAG, "Executing backup job...");
    BackupFileIOError.clearNotification(context);
    if (!Permissions.hasAll(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
        throw new IOException("No external storage permission!");
    }
    ProgressUpdater updater = new ProgressUpdater();
    try (NotificationController notification = GenericForegroundService.startForegroundTask(context, context.getString(R.string.LocalBackupJob_creating_signal_backup), NotificationChannels.BACKUPS, R.drawable.ic_signal_backup)) {
        updater.setNotification(notification);
        EventBus.getDefault().register(updater);
        notification.setIndeterminateProgress();
        String backupPassword = BackupPassphrase.get(context);
        File backupDirectory = StorageUtil.getOrCreateBackupDirectory();
        String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US).format(new Date());
        String fileName = String.format("signal-%s.backup", timestamp);
        File backupFile = new File(backupDirectory, fileName);
        deleteOldTemporaryBackups(backupDirectory);
        if (backupFile.exists()) {
            throw new IOException("Backup file already exists?");
        }
        if (backupPassword == null) {
            throw new IOException("Backup password is null");
        }
        File tempFile = File.createTempFile(TEMP_BACKUP_FILE_PREFIX, TEMP_BACKUP_FILE_SUFFIX, backupDirectory);
        try {
            FullBackupExporter.export(context, AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(), SignalDatabase.getBackupDatabase(), tempFile, backupPassword, this::isCanceled);
            if (!tempFile.renameTo(backupFile)) {
                Log.w(TAG, "Failed to rename temp file");
                throw new IOException("Renaming temporary backup file failed!");
            }
        } catch (FullBackupExporter.BackupCanceledException e) {
            Log.w(TAG, "Backup cancelled");
            throw e;
        } catch (IOException e) {
            BackupFileIOError.postNotificationForException(context, e, getRunAttempt());
            throw e;
        } finally {
            if (tempFile.exists()) {
                if (tempFile.delete()) {
                    Log.w(TAG, "Backup failed. Deleted temp file");
                } else {
                    Log.w(TAG, "Backup failed. Failed to delete temp file " + tempFile);
                }
            }
        }
        BackupUtil.deleteOldBackups();
    } finally {
        EventBus.getDefault().unregister(updater);
        updater.setNotification(null);
    }
}
Also used : IOException(java.io.IOException) File(java.io.File) SimpleDateFormat(java.text.SimpleDateFormat) NotificationController(org.thoughtcrime.securesms.service.NotificationController) Date(java.util.Date) FullBackupExporter(org.thoughtcrime.securesms.backup.FullBackupExporter)

Example 3 with NotificationController

use of org.thoughtcrime.securesms.service.NotificationController in project Signal-Android by WhisperSystems.

the class LocalBackupJobApi29 method onRun.

@Override
public void onRun() throws IOException {
    Log.i(TAG, "Executing backup job...");
    BackupFileIOError.clearNotification(context);
    if (!BackupUtil.isUserSelectionRequired(context)) {
        throw new IOException("Wrong backup job!");
    }
    Uri backupDirectoryUri = SignalStore.settings().getSignalBackupDirectory();
    if (backupDirectoryUri == null || backupDirectoryUri.getPath() == null) {
        throw new IOException("Backup Directory has not been selected!");
    }
    ProgressUpdater updater = new ProgressUpdater();
    try (NotificationController notification = GenericForegroundService.startForegroundTask(context, context.getString(R.string.LocalBackupJob_creating_signal_backup), NotificationChannels.BACKUPS, R.drawable.ic_signal_backup)) {
        updater.setNotification(notification);
        EventBus.getDefault().register(updater);
        notification.setIndeterminateProgress();
        String backupPassword = BackupPassphrase.get(context);
        DocumentFile backupDirectory = DocumentFile.fromTreeUri(context, backupDirectoryUri);
        String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US).format(new Date());
        String fileName = String.format("signal-%s.backup", timestamp);
        if (backupDirectory == null || !backupDirectory.canWrite()) {
            BackupFileIOError.ACCESS_ERROR.postNotification(context);
            throw new IOException("Cannot write to backup directory location.");
        }
        deleteOldTemporaryBackups(backupDirectory);
        if (backupDirectory.findFile(fileName) != null) {
            throw new IOException("Backup file already exists!");
        }
        String temporaryName = String.format(Locale.US, "%s%s%s", TEMP_BACKUP_FILE_PREFIX, UUID.randomUUID(), TEMP_BACKUP_FILE_SUFFIX);
        DocumentFile temporaryFile = backupDirectory.createFile("application/octet-stream", temporaryName);
        if (temporaryFile == null) {
            throw new IOException("Failed to create temporary backup file.");
        }
        if (backupPassword == null) {
            throw new IOException("Backup password is null");
        }
        try {
            FullBackupExporter.export(context, AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(), SignalDatabase.getBackupDatabase(), temporaryFile, backupPassword, this::isCanceled);
            if (!temporaryFile.renameTo(fileName)) {
                Log.w(TAG, "Failed to rename temp file");
                throw new IOException("Renaming temporary backup file failed!");
            }
        } catch (FullBackupExporter.BackupCanceledException e) {
            Log.w(TAG, "Backup cancelled");
            throw e;
        } catch (IOException e) {
            Log.w(TAG, "Error during backup!", e);
            BackupFileIOError.postNotificationForException(context, e, getRunAttempt());
            throw e;
        } finally {
            DocumentFile fileToCleanUp = backupDirectory.findFile(temporaryName);
            if (fileToCleanUp != null) {
                if (fileToCleanUp.delete()) {
                    Log.w(TAG, "Backup failed. Deleted temp file");
                } else {
                    Log.w(TAG, "Backup failed. Failed to delete temp file " + temporaryName);
                }
            }
        }
        BackupUtil.deleteOldBackups();
    } finally {
        EventBus.getDefault().unregister(updater);
        updater.setNotification(null);
    }
}
Also used : DocumentFile(androidx.documentfile.provider.DocumentFile) IOException(java.io.IOException) Uri(android.net.Uri) SimpleDateFormat(java.text.SimpleDateFormat) NotificationController(org.thoughtcrime.securesms.service.NotificationController) Date(java.util.Date) FullBackupExporter(org.thoughtcrime.securesms.backup.FullBackupExporter)

Example 4 with NotificationController

use of org.thoughtcrime.securesms.service.NotificationController in project Signal-Android by WhisperSystems.

the class AttachmentCompressionJob method transcodeVideoIfNeededToDatabase.

@NonNull
private static DatabaseAttachment transcodeVideoIfNeededToDatabase(@NonNull Context context, @NonNull AttachmentDatabase attachmentDatabase, @NonNull DatabaseAttachment attachment, @NonNull MediaConstraints constraints, @NonNull EventBus eventBus, @NonNull TranscoderCancelationSignal cancelationSignal) throws UndeliverableMessageException {
    AttachmentDatabase.TransformProperties transformProperties = attachment.getTransformProperties();
    boolean allowSkipOnFailure = false;
    if (!MediaConstraints.isVideoTranscodeAvailable()) {
        if (transformProperties.isVideoEdited()) {
            throw new UndeliverableMessageException("Video edited, but transcode is not available");
        }
        return attachment;
    }
    try (NotificationController notification = GenericForegroundService.startForegroundTask(context, context.getString(R.string.AttachmentUploadJob_compressing_video_start))) {
        notification.setIndeterminateProgress();
        try (MediaDataSource dataSource = attachmentDatabase.mediaDataSourceFor(attachment.getAttachmentId())) {
            if (dataSource == null) {
                throw new UndeliverableMessageException("Cannot get media data source for attachment.");
            }
            allowSkipOnFailure = !transformProperties.isVideoEdited();
            TranscoderOptions options = null;
            if (transformProperties.isVideoTrim()) {
                options = new TranscoderOptions(transformProperties.getVideoTrimStartTimeUs(), transformProperties.getVideoTrimEndTimeUs());
            }
            if (FeatureFlags.useStreamingVideoMuxer() || !MemoryFileDescriptor.supported()) {
                StreamingTranscoder transcoder = new StreamingTranscoder(dataSource, options, constraints.getCompressedVideoMaxSize(context));
                if (transcoder.isTranscodeRequired()) {
                    Log.i(TAG, "Compressing with streaming muxer");
                    AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
                    File file = SignalDatabase.attachments().newFile();
                    file.deleteOnExit();
                    try {
                        try (OutputStream outputStream = ModernEncryptingPartOutputStream.createFor(attachmentSecret, file, true).second) {
                            transcoder.transcode(percent -> {
                                notification.setProgress(100, percent);
                                eventBus.postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.COMPRESSION, 100, percent));
                            }, outputStream, cancelationSignal);
                        }
                        MediaStream mediaStream = new MediaStream(ModernDecryptingPartInputStream.createFor(attachmentSecret, file, 0), MimeTypes.VIDEO_MP4, 0, 0);
                        attachmentDatabase.updateAttachmentData(attachment, mediaStream, transformProperties.isVideoEdited());
                    } finally {
                        if (!file.delete()) {
                            Log.w(TAG, "Failed to delete temp file");
                        }
                    }
                    attachmentDatabase.markAttachmentAsTransformed(attachment.getAttachmentId());
                    return Objects.requireNonNull(attachmentDatabase.getAttachment(attachment.getAttachmentId()));
                } else {
                    Log.i(TAG, "Transcode was not required");
                }
            } else {
                try (InMemoryTranscoder transcoder = new InMemoryTranscoder(context, dataSource, options, constraints.getCompressedVideoMaxSize(context))) {
                    if (transcoder.isTranscodeRequired()) {
                        Log.i(TAG, "Compressing with android in-memory muxer");
                        MediaStream mediaStream = transcoder.transcode(percent -> {
                            notification.setProgress(100, percent);
                            eventBus.postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.COMPRESSION, 100, percent));
                        }, cancelationSignal);
                        attachmentDatabase.updateAttachmentData(attachment, mediaStream, transformProperties.isVideoEdited());
                        attachmentDatabase.markAttachmentAsTransformed(attachment.getAttachmentId());
                        return Objects.requireNonNull(attachmentDatabase.getAttachment(attachment.getAttachmentId()));
                    } else {
                        Log.i(TAG, "Transcode was not required (in-memory transcoder)");
                    }
                }
            }
        }
    } catch (VideoSourceException | EncodingException | MemoryFileException e) {
        if (attachment.getSize() > constraints.getVideoMaxSize(context)) {
            throw new UndeliverableMessageException("Duration not found, attachment too large to skip transcode", e);
        } else {
            if (allowSkipOnFailure) {
                Log.w(TAG, "Problem with video source, but video small enough to skip transcode", e);
            } else {
                throw new UndeliverableMessageException("Failed to transcode and cannot skip due to editing", e);
            }
        }
    } catch (IOException | MmsException e) {
        throw new UndeliverableMessageException("Failed to transcode", e);
    }
    return attachment;
}
Also used : VideoSourceException(org.thoughtcrime.securesms.video.VideoSourceException) MemoryFileException(org.thoughtcrime.securesms.util.MemoryFileDescriptor.MemoryFileException) EncodingException(org.thoughtcrime.securesms.video.videoconverter.EncodingException) OutputStream(java.io.OutputStream) ModernEncryptingPartOutputStream(org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream) PartProgressEvent(org.thoughtcrime.securesms.events.PartProgressEvent) IOException(java.io.IOException) AttachmentDatabase(org.thoughtcrime.securesms.database.AttachmentDatabase) TranscoderOptions(org.thoughtcrime.securesms.video.TranscoderOptions) MmsException(org.thoughtcrime.securesms.mms.MmsException) InMemoryTranscoder(org.thoughtcrime.securesms.video.InMemoryTranscoder) MediaStream(org.thoughtcrime.securesms.mms.MediaStream) MediaDataSource(android.media.MediaDataSource) UndeliverableMessageException(org.thoughtcrime.securesms.transport.UndeliverableMessageException) StreamingTranscoder(org.thoughtcrime.securesms.video.StreamingTranscoder) AttachmentSecret(org.thoughtcrime.securesms.crypto.AttachmentSecret) File(java.io.File) NotificationController(org.thoughtcrime.securesms.service.NotificationController) NonNull(androidx.annotation.NonNull)

Aggregations

NotificationController (org.thoughtcrime.securesms.service.NotificationController)4 IOException (java.io.IOException)3 File (java.io.File)2 SimpleDateFormat (java.text.SimpleDateFormat)2 Date (java.util.Date)2 FullBackupExporter (org.thoughtcrime.securesms.backup.FullBackupExporter)2 AttachmentDatabase (org.thoughtcrime.securesms.database.AttachmentDatabase)2 MediaDataSource (android.media.MediaDataSource)1 Uri (android.net.Uri)1 NonNull (androidx.annotation.NonNull)1 DocumentFile (androidx.documentfile.provider.DocumentFile)1 OutputStream (java.io.OutputStream)1 Attachment (org.thoughtcrime.securesms.attachments.Attachment)1 DatabaseAttachment (org.thoughtcrime.securesms.attachments.DatabaseAttachment)1 PointerAttachment (org.thoughtcrime.securesms.attachments.PointerAttachment)1 AttachmentSecret (org.thoughtcrime.securesms.crypto.AttachmentSecret)1 ModernEncryptingPartOutputStream (org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream)1 PartProgressEvent (org.thoughtcrime.securesms.events.PartProgressEvent)1 Data (org.thoughtcrime.securesms.jobmanager.Data)1 MediaStream (org.thoughtcrime.securesms.mms.MediaStream)1