<commit_before>package ee.ria.DigiDoc.android.crypto.create;

import android.app.Application;
import android.content.ContentResolver;

import com.google.common.collect.ImmutableList;

import java.io.File;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

import ee.ria.DigiDoc.android.model.idcard.IdCardDataResponse;
import ee.ria.DigiDoc.android.model.idcard.IdCardService;
import ee.ria.DigiDoc.android.utils.files.FileStream;
import ee.ria.DigiDoc.android.utils.files.FileSystem;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.android.utils.navigator.Transaction;
import ee.ria.DigiDoc.common.Certificate;
import ee.ria.DigiDoc.crypto.CryptoContainer;
import ee.ria.DigiDoc.crypto.Pin1InvalidException;
import ee.ria.DigiDoc.crypto.RecipientRepository;
import ee.ria.DigiDoc.idcard.Token;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.ObservableTransformer;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;

import static android.app.Activity.RESULT_OK;
import static ee.ria.DigiDoc.android.Constants.RC_CRYPTO_CREATE_DATA_FILE_ADD;
import static ee.ria.DigiDoc.android.Constants.RC_CRYPTO_CREATE_INITIAL;
import static ee.ria.DigiDoc.android.utils.Immutables.with;
import static ee.ria.DigiDoc.android.utils.Immutables.without;
import static ee.ria.DigiDoc.android.utils.IntentUtils.createGetContentIntent;
import static ee.ria.DigiDoc.android.utils.IntentUtils.createSendIntent;
import static ee.ria.DigiDoc.android.utils.IntentUtils.createViewIntent;
import static ee.ria.DigiDoc.android.utils.IntentUtils.parseGetContentIntent;
import static ee.ria.DigiDoc.crypto.CryptoContainer.createContainerFileName;
import static ee.ria.DigiDoc.crypto.CryptoContainer.isContainerFileName;
import static ee.ria.DigiDoc.sign.SignedContainer.mimeType;

/**
 * Error when opening crypto container: show error message and close on dialog cancel?
 * Do not create file when encrypt is not successful
 * Check for all cases when application is terminated
 */
final class Processor implements ObservableTransformer<Intent, Result> {

    private final ContentResolver contentResolver;
    private final FileSystem fileSystem;

    private final ObservableTransformer<Intent.InitialIntent, Result.InitialResult> initial;

    private final ObservableTransformer<Intent.UpButtonClickIntent, Result> upButtonClick;

    private final ObservableTransformer<Intent.DataFilesAddIntent,
                                        Result.DataFilesAddResult> dataFilesAdd;

    private final ObservableTransformer<Intent.DataFileRemoveIntent,
                                        Result.DataFileRemoveResult> dataFileRemove;

    private final ObservableTransformer<Intent.DataFileViewIntent, Result> dataFileView;

    private final ObservableTransformer<Intent.RecipientsAddButtonClickIntent,
                                        Result.RecipientsAddButtonClickResult>
            recipientsAddButtonClick;

    private final ObservableTransformer<Intent.RecipientsScreenUpButtonClickIntent,
                                        Result> recipientsScreenUpButtonClick;

    private final ObservableTransformer<Intent.RecipientsSearchIntent,
            Result.RecipientsSearchResult> recipientsSearch;

    private final ObservableTransformer<Intent.RecipientAddIntent,
            Result.RecipientAddResult> recipientAdd;

    private final ObservableTransformer<Intent.RecipientRemoveIntent,
                                        Result.RecipientRemoveResult> recipientRemove;

    private final ObservableTransformer<Intent.EncryptIntent, Result.EncryptResult> encrypt;

    private final ObservableTransformer<Intent.DecryptionIntent,
                                        Result.DecryptionResult> decryption;

    private final ObservableTransformer<Intent.DecryptIntent, Result.DecryptResult> decrypt;

    private final ObservableTransformer<Intent.SendIntent, Result> send;

    @Inject Processor(Navigator navigator, RecipientRepository recipientRepository,
                      ContentResolver contentResolver, FileSystem fileSystem,
                      Application application, IdCardService idCardService) {
        this.contentResolver = contentResolver;
        this.fileSystem = fileSystem;

        initial = upstream -> upstream.switchMap(intent -> {
            File containerFile = intent.containerFile();
            android.content.Intent androidIntent = intent.intent();
            if (containerFile != null) {
                return Observable
                        .fromCallable(() -> CryptoContainer.open(containerFile))
                        .map(Result.InitialResult::success)
                        .onErrorReturn(Result.InitialResult::failure)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .startWith(Result.InitialResult.activity());
            } else if (androidIntent != null) {
                return parseIntent(androidIntent);
            } else {
                navigator.execute(Transaction.activityForResult(RC_CRYPTO_CREATE_INITIAL,
                        createGetContentIntent(), null));
                return navigator.activityResults()
                        .filter(activityResult ->
                                activityResult.requestCode() == RC_CRYPTO_CREATE_INITIAL)
                        .switchMap(activityResult -> {
                            android.content.Intent data = activityResult.data();
                            if (activityResult.resultCode() == RESULT_OK && data != null) {
                                return parseIntent(data);
                            } else {
                                navigator.onBackPressed();
                                return Observable.just(Result.InitialResult.clear());
                            }
                        });
            }
        });

        upButtonClick = upstream -> upstream.switchMap(intent -> {
            navigator.execute(Transaction.pop());
            return Observable.empty();
        });

        dataFilesAdd = upstream -> upstream.switchMap(intent -> {
            ImmutableList<File> dataFiles = intent.dataFiles();
            if (dataFiles == null) {
                return Observable.just(Result.DataFilesAddResult.clear());
            }
            navigator.execute(Transaction.activityForResult(RC_CRYPTO_CREATE_DATA_FILE_ADD,
                    createGetContentIntent(), null));
            return navigator.activityResults()
                    .filter(activityResult ->
                            activityResult.requestCode() == RC_CRYPTO_CREATE_DATA_FILE_ADD)
                    .switchMap(activityResult -> {
                        android.content.Intent data = activityResult.data();
                        if (activityResult.resultCode() == RESULT_OK && data != null) {
                            return Observable
                                    .fromCallable(() -> {
                                        ImmutableList<FileStream> fileStreams =
                                                parseGetContentIntent(contentResolver, data);
                                        ImmutableList.Builder<File> builder =
                                                ImmutableList.<File>builder().addAll(dataFiles);
                                        for (FileStream fileStream : fileStreams) {
                                            File dataFile = fileSystem.cache(fileStream);
                                            if (dataFiles.contains(dataFile)) {
                                                throw new IllegalArgumentException(
                                                        "File already exists in the container");
                                            }
                                            builder.add(dataFile);
                                        }
                                        return builder.build();
                                    })
                                    .map(Result.DataFilesAddResult::success)
                                    .onErrorReturn(Result.DataFilesAddResult::failure)
                                    .subscribeOn(Schedulers.io())
                                    .observeOn(AndroidSchedulers.mainThread())
                                    .startWith(Result.DataFilesAddResult.activity());
                        } else {
                            return Observable.just(Result.DataFilesAddResult.clear());
                        }
                    });
        });

        dataFileRemove = upstream -> upstream.switchMap(intent ->
                Observable
                        .fromCallable(() -> {
                            try {
                                //noinspection ResultOfMethodCallIgnored
                                intent.dataFile().delete();
                            } catch (Exception e) {
                                // ignore because it's a cache file and is deleted anyway
                            }
                            return Result.DataFileRemoveResult.create(
                                    without(intent.dataFiles(), intent.dataFile()));
                        })
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread()));

        dataFileView = upstream -> upstream.switchMap(intent -> {
            File file = intent.dataFile();
            navigator.execute(Transaction
                    .activity(createViewIntent(application, file, mimeType(file)), null));
            return Observable.empty();
        });

        recipientsAddButtonClick = upstream -> upstream.switchMap(intent -> {
            navigator.execute(Transaction.push(
                    CryptoRecipientsScreen.create(intent.cryptoCreateScreenId())));
            return Observable.just(Result.RecipientsAddButtonClickResult.create());
        });

        recipientsScreenUpButtonClick = upstream -> upstream.switchMap(intent -> {<commit_msg>Handle signed containers in crypto containers<commit_after>package ee.ria.DigiDoc.android.crypto.create;

import android.app.Application;
import android.content.ContentResolver;

import com.google.common.collect.ImmutableList;

import java.io.File;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

import ee.ria.DigiDoc.android.model.idcard.IdCardDataResponse;
import ee.ria.DigiDoc.android.model.idcard.IdCardService;
import ee.ria.DigiDoc.android.signature.update.SignatureUpdateScreen;
import ee.ria.DigiDoc.android.utils.files.FileStream;
import ee.ria.DigiDoc.android.utils.files.FileSystem;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.android.utils.navigator.Transaction;
import ee.ria.DigiDoc.common.Certificate;
import ee.ria.DigiDoc.crypto.CryptoContainer;
import ee.ria.DigiDoc.crypto.Pin1InvalidException;
import ee.ria.DigiDoc.crypto.RecipientRepository;
import ee.ria.DigiDoc.idcard.Token;
import ee.ria.DigiDoc.sign.SignedContainer;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.ObservableTransformer;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;

import static android.app.Activity.RESULT_OK;
import static ee.ria.DigiDoc.android.Constants.RC_CRYPTO_CREATE_DATA_FILE_ADD;
import static ee.ria.DigiDoc.android.Constants.RC_CRYPTO_CREATE_INITIAL;
import static ee.ria.DigiDoc.android.utils.Immutables.with;
import static ee.ria.DigiDoc.android.utils.Immutables.without;
import static ee.ria.DigiDoc.android.utils.IntentUtils.createGetContentIntent;
import static ee.ria.DigiDoc.android.utils.IntentUtils.createSendIntent;
import static ee.ria.DigiDoc.android.utils.IntentUtils.createViewIntent;
import static ee.ria.DigiDoc.android.utils.IntentUtils.parseGetContentIntent;
import static ee.ria.DigiDoc.crypto.CryptoContainer.createContainerFileName;
import static ee.ria.DigiDoc.crypto.CryptoContainer.isContainerFileName;
import static ee.ria.DigiDoc.sign.SignedContainer.mimeType;

/**
 * Error when opening crypto container: show error message and close on dialog cancel?
 * Do not create file when encrypt is not successful
 * Check for all cases when application is terminated
 */
final class Processor implements ObservableTransformer<Intent, Result> {

    private final ContentResolver contentResolver;
    private final FileSystem fileSystem;

    private final ObservableTransformer<Intent.InitialIntent, Result.InitialResult> initial;

    private final ObservableTransformer<Intent.UpButtonClickIntent, Result> upButtonClick;

    private final ObservableTransformer<Intent.DataFilesAddIntent,
                                        Result.DataFilesAddResult> dataFilesAdd;

    private final ObservableTransformer<Intent.DataFileRemoveIntent,
                                        Result.DataFileRemoveResult> dataFileRemove;

    private final ObservableTransformer<Intent.DataFileViewIntent, Result> dataFileView;

    private final ObservableTransformer<Intent.RecipientsAddButtonClickIntent,
                                        Result.RecipientsAddButtonClickResult>
            recipientsAddButtonClick;

    private final ObservableTransformer<Intent.RecipientsScreenUpButtonClickIntent,
                                        Result> recipientsScreenUpButtonClick;

    private final ObservableTransformer<Intent.RecipientsSearchIntent,
            Result.RecipientsSearchResult> recipientsSearch;

    private final ObservableTransformer<Intent.RecipientAddIntent,
            Result.RecipientAddResult> recipientAdd;

    private final ObservableTransformer<Intent.RecipientRemoveIntent,
                                        Result.RecipientRemoveResult> recipientRemove;

    private final ObservableTransformer<Intent.EncryptIntent, Result.EncryptResult> encrypt;

    private final ObservableTransformer<Intent.DecryptionIntent,
                                        Result.DecryptionResult> decryption;

    private final ObservableTransformer<Intent.DecryptIntent, Result.DecryptResult> decrypt;

    private final ObservableTransformer<Intent.SendIntent, Result> send;

    @Inject Processor(Navigator navigator, RecipientRepository recipientRepository,
                      ContentResolver contentResolver, FileSystem fileSystem,
                      Application application, IdCardService idCardService) {
        this.contentResolver = contentResolver;
        this.fileSystem = fileSystem;

        initial = upstream -> upstream.switchMap(intent -> {
            File containerFile = intent.containerFile();
            android.content.Intent androidIntent = intent.intent();
            if (containerFile != null) {
                return Observable
                        .fromCallable(() -> CryptoContainer.open(containerFile))
                        .map(Result.InitialResult::success)
                        .onErrorReturn(Result.InitialResult::failure)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .startWith(Result.InitialResult.activity());
            } else if (androidIntent != null) {
                return parseIntent(androidIntent);
            } else {
                navigator.execute(Transaction.activityForResult(RC_CRYPTO_CREATE_INITIAL,
                        createGetContentIntent(), null));
                return navigator.activityResults()
                        .filter(activityResult ->
                                activityResult.requestCode() == RC_CRYPTO_CREATE_INITIAL)
                        .switchMap(activityResult -> {
                            android.content.Intent data = activityResult.data();
                            if (activityResult.resultCode() == RESULT_OK && data != null) {
                                return parseIntent(data);
                            } else {
                                navigator.onBackPressed();
                                return Observable.just(Result.InitialResult.clear());
                            }
                        });
            }
        });

        upButtonClick = upstream -> upstream.switchMap(intent -> {
            navigator.execute(Transaction.pop());
            return Observable.empty();
        });

        dataFilesAdd = upstream -> upstream.switchMap(intent -> {
            ImmutableList<File> dataFiles = intent.dataFiles();
            if (dataFiles == null) {
                return Observable.just(Result.DataFilesAddResult.clear());
            }
            navigator.execute(Transaction.activityForResult(RC_CRYPTO_CREATE_DATA_FILE_ADD,
                    createGetContentIntent(), null));
            return navigator.activityResults()
                    .filter(activityResult ->
                            activityResult.requestCode() == RC_CRYPTO_CREATE_DATA_FILE_ADD)
                    .switchMap(activityResult -> {
                        android.content.Intent data = activityResult.data();
                        if (activityResult.resultCode() == RESULT_OK && data != null) {
                            return Observable
                                    .fromCallable(() -> {
                                        ImmutableList<FileStream> fileStreams =
                                                parseGetContentIntent(contentResolver, data);
                                        ImmutableList.Builder<File> builder =
                                                ImmutableList.<File>builder().addAll(dataFiles);
                                        for (FileStream fileStream : fileStreams) {
                                            File dataFile = fileSystem.cache(fileStream);
                                            if (dataFiles.contains(dataFile)) {
                                                throw new IllegalArgumentException(
                                                        "File already exists in the container");
                                            }
                                            builder.add(dataFile);
                                        }
                                        return builder.build();
                                    })
                                    .map(Result.DataFilesAddResult::success)
                                    .onErrorReturn(Result.DataFilesAddResult::failure)
                                    .subscribeOn(Schedulers.io())
                                    .observeOn(AndroidSchedulers.mainThread())
                                    .startWith(Result.DataFilesAddResult.activity());
                        } else {
                            return Observable.just(Result.DataFilesAddResult.clear());
                        }
                    });
        });

        dataFileRemove = upstream -> upstream.switchMap(intent ->
                Observable
                        .fromCallable(() -> {
                            try {
                                //noinspection ResultOfMethodCallIgnored
                                intent.dataFile().delete();
                            } catch (Exception e) {
                                // ignore because it's a cache file and is deleted anyway
                            }
                            return Result.DataFileRemoveResult.create(
                                    without(intent.dataFiles(), intent.dataFile()));
                        })
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread()));

        dataFileView = upstream -> upstream.switchMap(intent -> {
            return Observable
                    .fromCallable(() -> {
                        File file = intent.dataFile();
                        if (SignedContainer.isContainer(file)) {
                            return Transaction.push(
                                    SignatureUpdateScreen.create(true, true, file, false, false));
                        } else {
                            return Transaction.activity(
                                    createViewIntent(application, file, mimeType(file)), null);
                        }
                    })
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .flatMap(transaction -> {
                        navigator.execute(transaction);
                        return Observable.empty();
                    });
        });

        recipientsAddButtonClick = upstream -> upstream.switchMap(intent -> {
            navigator.execute(Transaction.push(
                    CryptoRecipientsScreen.create(intent.cryptoCreateScreenId())));
            return Observable.just(Result.RecipientsAddButtonClickResult.create());
        });

        recipientsScreenUpButtonClick = upstream -> upstream.switchMap(intent -> {
{'fraction_non_alphanumeric': 0.07811764705882353, 'fraction_numerical': 9.411764705882353e-05, 'mean_word_length': 1.7994994071927282, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 85, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 76, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3523718', 'n_tokens_mistral': 4835, 'n_tokens_neox': 4596, 'n_words': 807}