diff --git a/app/src/main/java/eu/faircode/email/FragmentRules.java b/app/src/main/java/eu/faircode/email/FragmentRules.java index 91e1edd187..7425c03ea0 100644 --- a/app/src/main/java/eu/faircode/email/FragmentRules.java +++ b/app/src/main/java/eu/faircode/email/FragmentRules.java @@ -19,9 +19,12 @@ package eu.faircode.email; Copyright 2018-2020 by Marcel Bokhorst (M66B) */ +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.AssetFileDescriptor; +import android.net.Uri; import android.os.Bundle; import android.text.TextUtils; import android.view.LayoutInflater; @@ -30,6 +33,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -45,9 +49,18 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.floatingactionbutton.FloatingActionButton; +import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.List; import static android.app.Activity.RESULT_OK; @@ -69,8 +82,10 @@ public class FragmentRules extends FragmentBase { private String searching = null; private AdapterRule adapter; - static final int REQUEST_MOVE = 1; - private static final int REQUEST_CLEAR = 2; + private static final int REQUEST_EXPORT = 1; + private static final int REQUEST_IMPORT = 2; + static final int REQUEST_MOVE = 3; + private static final int REQUEST_CLEAR = 4; @Override public void onCreate(Bundle savedInstanceState) { @@ -180,6 +195,14 @@ public class FragmentRules extends FragmentBase { try { switch (requestCode) { + case REQUEST_EXPORT: + if (resultCode == RESULT_OK && data != null) + onExport(data); + break; + case REQUEST_IMPORT: + if (resultCode == RESULT_OK && data != null) + onImport(data); + break; case REQUEST_MOVE: if (resultCode == RESULT_OK && data != null) onMove(data.getBundleExtra("args")); @@ -235,6 +258,12 @@ public class FragmentRules extends FragmentBase { @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { + case R.id.menu_export: + onMenuExport(); + return true; + case R.id.menu_import: + onMenuImport(); + return true; case R.id.menu_delete_all: onMenuDelete(true); return true; @@ -246,6 +275,23 @@ public class FragmentRules extends FragmentBase { } } + private void onMenuExport() { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + intent.putExtra(Intent.EXTRA_TITLE, "fairemail_" + + new SimpleDateFormat("yyyyMMdd").format(new Date().getTime()) + ".rules"); + Helper.openAdvanced(intent); + startActivityForResult(intent, REQUEST_EXPORT); + } + + private void onMenuImport() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + startActivityForResult(intent, REQUEST_IMPORT); + } + private void onMenuDelete(boolean all) { Bundle aargs = new Bundle(); aargs.putString("question", getString(all @@ -260,6 +306,129 @@ public class FragmentRules extends FragmentBase { ask.show(getParentFragmentManager(), "rules:clear"); } + private void onExport(Intent data) { + Bundle args = new Bundle(); + args.putLong("folder", folder); + args.putParcelable("uri", data.getData()); + + new SimpleTask() { + @Override + protected void onPreExecute(Bundle args) { + ToastEx.makeText(getContext(), R.string.title_executing, Toast.LENGTH_LONG).show(); + } + + @Override + protected Void onExecute(Context context, Bundle args) throws Throwable { + long fid = args.getLong("folder"); + Uri uri = args.getParcelable("uri"); + + if (!"content".equals(uri.getScheme())) { + Log.w("Export uri=" + uri); + throw new IllegalArgumentException(context.getString(R.string.title_no_stream)); + } + + DB db = DB.getInstance(context); + JSONArray jrules = new JSONArray(); + for (EntityRule rule : db.rule().getRules(fid)) + jrules.put(rule.toJSON()); + + ContentResolver resolver = context.getContentResolver(); + try (OutputStream os = resolver.openOutputStream(uri)) { + Log.i("Writing URI=" + uri); + os.write(jrules.toString(2).getBytes()); + } + + return null; + } + + @Override + protected void onExecuted(Bundle args, Void data) { + ToastEx.makeText(getContext(), R.string.title_completed, Toast.LENGTH_LONG).show(); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + if (ex instanceof IllegalArgumentException || + ex instanceof FileNotFoundException) + ToastEx.makeText(getContext(), ex.getMessage(), Toast.LENGTH_LONG).show(); + else + Log.unexpectedError(getParentFragmentManager(), ex); + } + }.execute(this, args, "rules:export"); + } + + private void onImport(Intent data) { + Bundle args = new Bundle(); + args.putLong("folder", folder); + args.putParcelable("uri", data.getData()); + + new SimpleTask() { + @Override + protected void onPreExecute(Bundle args) { + ToastEx.makeText(getContext(), R.string.title_executing, Toast.LENGTH_LONG).show(); + } + + @Override + protected Void onExecute(Context context, Bundle args) throws Throwable { + long fid = args.getLong("folder"); + Uri uri = args.getParcelable("uri"); + + if (!"content".equals(uri.getScheme())) { + Log.w("Import uri=" + uri); + throw new IllegalArgumentException(context.getString(R.string.title_no_stream)); + } + + StringBuilder data = new StringBuilder(); + + Log.i("Reading URI=" + uri); + ContentResolver resolver = context.getContentResolver(); + AssetFileDescriptor descriptor = resolver.openTypedAssetFileDescriptor(uri, "*/*", null); + try (InputStream is = descriptor.createInputStream()) { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + String line; + while ((line = reader.readLine()) != null) + data.append(line); + } + + JSONArray jrules = new JSONArray(data.toString()); + + DB db = DB.getInstance(context); + try { + db.beginTransaction(); + + for (int i = 0; i < jrules.length(); i++) { + JSONObject jrule = jrules.getJSONObject(i); + EntityRule rule = EntityRule.fromJSON(jrule); + rule.folder = fid; + rule.applied = 0; + rule.id = db.rule().insertRule(rule); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + return null; + } + + @Override + protected void onExecuted(Bundle args, Void data) { + ToastEx.makeText(getContext(), R.string.title_completed, Toast.LENGTH_LONG).show(); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + if (ex instanceof IllegalArgumentException || + ex instanceof FileNotFoundException || + ex instanceof JSONException) + ToastEx.makeText(getContext(), ex.getMessage(), Toast.LENGTH_LONG).show(); + else + Log.unexpectedError(getParentFragmentManager(), ex); + } + }.execute(this, args, "rules:import"); + } + private void onMove(Bundle args) { new SimpleTask() { @Override diff --git a/app/src/main/res/menu/menu_rules.xml b/app/src/main/res/menu/menu_rules.xml index 56631cfc81..b42212d8dd 100644 --- a/app/src/main/res/menu/menu_rules.xml +++ b/app/src/main/res/menu/menu_rules.xml @@ -8,6 +8,16 @@ app:actionViewClass="androidx.appcompat.widget.SearchView" app:showAsAction="collapseActionView|always" /> + + + + Auto scroll Clear Search name or condition + Export rules + Import rules Delete all rules Delete spam rules Delete all rules?