diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 3f29825bb4..c67aba4ea3 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -50,3 +50,4 @@ FairEmail uses parts or all of: * [Liberation Sans Narrow font](https://github.com/liberationfonts/liberation-sans-narrow). Copyright (C) 1989, 1991 Free Software Foundation, Inc. [GNU General Public License version 2 with exceptions](https://fedoraproject.org/wiki/Licensing/LiberationFontLicense). * [Prism](https://github.com/PrismJS/prism). Copyright (c) 2012 Lea Verou. [MIT LICENSE](https://github.com/PrismJS/prism/blob/master/LICENSE). * [Jayway JsonPath](https://github.com/json-path/JsonPath). Copyright 2011 the original author or authors. [Apache License 2.0](https://github.com/json-path/JsonPath/blob/master/LICENSE). +* [tinylog 2](https://github.com/tinylog-org/tinylog). Copyright 2016-2023 Martin Winandy. [Apache License 2.0](https://github.com/tinylog-org/tinylog/blob/v2.7/license.txt). diff --git a/app/build.gradle b/app/build.gradle index 5471b30359..265858b609 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -545,6 +545,7 @@ dependencies { def ipaddress_version = "5.4.0" def canary_version = "2.12" def ws_version = "2.14" + def tinylog_version = "2.6.2" // https://mvnrepository.com/artifact/com.android.tools/desugar_jdk_libs?repo=google coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' @@ -790,4 +791,9 @@ dependencies { // https://github.com/TakahikoKawasaki/nv-websocket-client implementation "com.neovisionaries:nv-websocket-client:$ws_version" + + // https://tinylog.org/v2/download/ + // https://mvnrepository.com/artifact/org.tinylog + implementation "org.tinylog:tinylog-api:$tinylog_version" + implementation "org.tinylog:tinylog-impl:$tinylog_version" } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index e057d4a2d4..b1a4226b20 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -150,4 +150,14 @@ #ShortcutBadger -keep class me.leolin.shortcutbadger.** {*;} --keepnames class me.leolin.shortcutbadger.** {*;} \ No newline at end of file +-keepnames class me.leolin.shortcutbadger.** {*;} + +#https://tinylog.org/v2/configuration/#proguard +-keepnames interface org.tinylog.** +-keepnames class * implements org.tinylog.** +-keepclassmembers class * implements org.tinylog.** { (...); } + +-dontwarn dalvik.system.VMStack +-dontwarn java.lang.** +-dontwarn javax.naming.** +-dontwarn sun.reflect.Reflection diff --git a/app/src/main/assets/ATTRIBUTION.md b/app/src/main/assets/ATTRIBUTION.md index 3f29825bb4..c67aba4ea3 100644 --- a/app/src/main/assets/ATTRIBUTION.md +++ b/app/src/main/assets/ATTRIBUTION.md @@ -50,3 +50,4 @@ FairEmail uses parts or all of: * [Liberation Sans Narrow font](https://github.com/liberationfonts/liberation-sans-narrow). Copyright (C) 1989, 1991 Free Software Foundation, Inc. [GNU General Public License version 2 with exceptions](https://fedoraproject.org/wiki/Licensing/LiberationFontLicense). * [Prism](https://github.com/PrismJS/prism). Copyright (c) 2012 Lea Verou. [MIT LICENSE](https://github.com/PrismJS/prism/blob/master/LICENSE). * [Jayway JsonPath](https://github.com/json-path/JsonPath). Copyright 2011 the original author or authors. [Apache License 2.0](https://github.com/json-path/JsonPath/blob/master/LICENSE). +* [tinylog 2](https://github.com/tinylog-org/tinylog). Copyright 2016-2023 Martin Winandy. [Apache License 2.0](https://github.com/tinylog-org/tinylog/blob/v2.7/license.txt). diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index cfcb637465..30493e1022 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -1628,8 +1628,6 @@ public class AdapterMessage extends RecyclerView.Adapter(BREADCRUMBS_SIZE); - boolean trace = (debug || log || Log.isDebugLogLevel()); + boolean trace = (debug || log); isession.setDebug(trace); if (trace) diff --git a/app/src/main/java/eu/faircode/email/EntityAttachment.java b/app/src/main/java/eu/faircode/email/EntityAttachment.java index 6bc3f43803..29832c2a9e 100644 --- a/app/src/main/java/eu/faircode/email/EntityAttachment.java +++ b/app/src/main/java/eu/faircode/email/EntityAttachment.java @@ -375,18 +375,41 @@ public class EntityAttachment { File file = getFile(context); File zip = new File(file.getAbsolutePath() + ".zip"); - try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { - try (ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zip)))) { - out.setMethod(ZipOutputStream.DEFLATED); - out.setLevel(Deflater.BEST_COMPRESSION); - ZipEntry entry = new ZipEntry(name); - out.putNextEntry(entry); + try (ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zip)))) { + out.setMethod(ZipOutputStream.DEFLATED); + out.setLevel(Deflater.BEST_COMPRESSION); + ZipEntry entry = new ZipEntry(name); + out.putNextEntry(entry); + try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { Helper.copy(in, out); } } DB db = DB.getInstance(context); db.attachment().setName(id, name + ".zip", "application/zip", zip.length()); + db.attachment().setDownloaded(id, zip.length()); + Helper.secureDelete(file); + } + + void zip(Context context, File[] files) throws IOException { + File file = getFile(context); + File zip = new File(file.getAbsolutePath() + ".zip"); + + try (ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zip)))) { + out.setMethod(ZipOutputStream.DEFLATED); + out.setLevel(Deflater.BEST_COMPRESSION); + for (File f : files) { + ZipEntry entry = new ZipEntry(f.getName()); + out.putNextEntry(entry); + try (InputStream in = new BufferedInputStream(new FileInputStream(f))) { + Helper.copy(in, out); + } + } + } + + DB db = DB.getInstance(context); + db.attachment().setName(id, name + ".zip", "application/zip", zip.length()); + db.attachment().setDownloaded(id, zip.length()); Helper.secureDelete(file); } diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java b/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java index 4a2947934e..b436af84aa 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java @@ -892,7 +892,6 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc if (checked) prefs.edit() .putLong("protocol_since", new Date().getTime()) - .putInt("log_level", android.util.Log.INFO) .apply(); else EntityLog.clear(compoundButton.getContext()); @@ -901,8 +900,9 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc swLogInfo.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { - prefs.edit().putInt("log_level", checked ? android.util.Log.INFO : android.util.Log.WARN).apply(); + public void onCheckedChanged(CompoundButton v, boolean checked) { + prefs.edit().putInt("log_level", checked ? android.util.Log.INFO : android.util.Log.WARN).commit(); + ApplicationEx.restart(v.getContext(), "log_level"); } }); diff --git a/app/src/main/java/eu/faircode/email/Log.java b/app/src/main/java/eu/faircode/email/Log.java index 415ecdcaf9..c246132f80 100644 --- a/app/src/main/java/eu/faircode/email/Log.java +++ b/app/src/main/java/eu/faircode/email/Log.java @@ -195,7 +195,6 @@ import javax.net.ssl.X509TrustManager; public class Log { private static Context ctx; - private static int level = android.util.Log.INFO; private static final long MAX_LOG_SIZE = 8 * 1024 * 1024L; private static final int MAX_CRASH_REPORTS = (BuildConfig.TEST_RELEASE ? 50 : 5); private static final long MIN_FILE_SIZE = 1024 * 1024L; @@ -244,52 +243,27 @@ public class Log { public static native long[] jni_safe_runtime_stats(); - public static void setLevel(Context context) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean debug = prefs.getBoolean("debug", false); - if (debug) - level = android.util.Log.DEBUG; - else { - int def = (BuildConfig.DEBUG ? android.util.Log.INFO : android.util.Log.WARN); - level = prefs.getInt("log_level", def); - } - jni_safe_log(android.util.Log.DEBUG, TAG, "Log level=" + level); - } - - public static boolean isDebugLogLevel() { - return (level <= android.util.Log.INFO); - } - public static int d(String msg) { - if (level <= android.util.Log.DEBUG) - return jni_safe_log(android.util.Log.DEBUG, TAG, msg); - else - return 0; + return d(TAG, msg); } public static int d(String tag, String msg) { - if (level <= android.util.Log.DEBUG) - return jni_safe_log(android.util.Log.DEBUG, tag, msg); - else - return 0; + org.tinylog.Logger.tag(tag).debug(msg); + return 0; } public static int i(String msg) { - if (level <= android.util.Log.INFO || BuildConfig.DEBUG || BuildConfig.TEST_RELEASE) - return jni_safe_log(android.util.Log.INFO, TAG, msg); - else - return 0; + return i(TAG, msg); } public static int i(String tag, String msg) { - if (level <= android.util.Log.INFO || BuildConfig.DEBUG || BuildConfig.TEST_RELEASE) - return jni_safe_log(android.util.Log.INFO, TAG, msg); - else - return 0; + org.tinylog.Logger.tag(tag).info(msg); + return 0; } public static int w(String msg) { - return jni_safe_log(android.util.Log.WARN, TAG, msg); + org.tinylog.Logger.tag(TAG).warn(msg); + return 0; } public static int e(String msg) { @@ -307,11 +281,14 @@ public class Log { } catch (Throwable ex) { Log.i(ex); } - return jni_safe_log(android.util.Log.ERROR, TAG, msg); + + org.tinylog.Logger.tag(TAG).error(msg); + return 0; } public static int i(Throwable ex) { - return jni_safe_log(android.util.Log.INFO, TAG, getDescription(ex)); + org.tinylog.Logger.tag(TAG).info(ex); + return 0; } public static int w(Throwable ex) { @@ -330,7 +307,9 @@ public class Log { } catch (Throwable ex1) { Log.i(ex1); } - return jni_safe_log(android.util.Log.WARN, TAG, getDescription(ex)); + + org.tinylog.Logger.tag(TAG).warn(ex); + return 0; } public static int e(Throwable ex) { @@ -349,11 +328,14 @@ public class Log { } catch (Throwable ex1) { Log.i(ex1); } - return jni_safe_log(android.util.Log.ERROR, TAG, getDescription(ex)); + + org.tinylog.Logger.tag(TAG).error(ex); + return 0; } public static int i(String prefix, Throwable ex) { - return jni_safe_log(android.util.Log.INFO, TAG, prefix + " " + getDescription(ex)); + org.tinylog.Logger.tag(TAG).info(ex, prefix); + return 0; } public static int w(String prefix, Throwable ex) { @@ -369,7 +351,9 @@ public class Log { } catch (Throwable ex1) { Log.i(ex1); } - return jni_safe_log(android.util.Log.WARN, TAG, prefix + " " + getDescription(ex)); + + org.tinylog.Logger.tag(TAG).warn(ex, prefix); + return 0; } public static int e(String prefix, Throwable ex) { @@ -385,24 +369,21 @@ public class Log { } catch (Throwable ex1) { Log.i(ex1); } - return jni_safe_log(android.util.Log.ERROR, TAG, prefix + " " + getDescription(ex)); + + org.tinylog.Logger.tag(TAG).error(ex, prefix); + return 0; } public static void persist(String message) { if (ctx == null) - Log.e(message); + org.tinylog.Logger.tag(TAG).error(message); else EntityLog.log(ctx, message); } - private static String getDescription(Throwable ex) { - ThrowableWrapper t = new ThrowableWrapper(ex); - return t.toSafeString() + "\n" + t.getSafeStackTraceString(); - } - public static void persist(EntityLog.Type type, String message) { if (ctx == null) - Log.e(message); + org.tinylog.Logger.tag(TAG).error(type.name() + " " + message); else EntityLog.log(ctx, type, message); } @@ -470,7 +451,6 @@ public class Log { static void setup(Context context) { ctx = context; - setLevel(context); setupBugsnag(context); } @@ -2197,8 +2177,8 @@ public class Log { } } - sb.append(String.format("Log main: %b protocol: %b debug: %b build: %b test: %b\r\n", - main_log, protocol, Log.isDebugLogLevel(), BuildConfig.DEBUG, BuildConfig.TEST_RELEASE)); + sb.append(String.format("Log main: %b protocol: %b build: %b test: %b\r\n", + main_log, protocol, BuildConfig.DEBUG, BuildConfig.TEST_RELEASE)); int[] contacts = ContactInfo.getStats(); sb.append(String.format("Contact lookup: %d cached: %d\r\n", @@ -3153,6 +3133,8 @@ public class Log { attachment.progress = 0; attachment.id = db.attachment().insertAttachment(attachment); + attachment.zip(context, TinyLogConfigurationLoader.getFiles(context)); +/* // https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html#java ProcessBuilder pb = new ProcessBuilder("/system/bin/logcat", "-d", @@ -3182,6 +3164,7 @@ public class Log { if (proc != null) proc.destroy(); } +*/ } catch (Throwable ex) { Log.e(ex); } diff --git a/app/src/main/java/eu/faircode/email/TinyLogConfigurationLoader.java b/app/src/main/java/eu/faircode/email/TinyLogConfigurationLoader.java new file mode 100644 index 0000000000..d21a38773f --- /dev/null +++ b/app/src/main/java/eu/faircode/email/TinyLogConfigurationLoader.java @@ -0,0 +1,91 @@ +package eu.faircode.email; + +/* + This file is part of FairEmail. + + FairEmail is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + FairEmail is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with FairEmail. If not, see . + + Copyright 2018-2023 by Marcel Bokhorst (M66B) +*/ + +import android.content.Context; +import android.content.SharedPreferences; + +import androidx.preference.PreferenceManager; + +import org.tinylog.Level; +import org.tinylog.configuration.PropertiesConfigurationLoader; + +import java.io.File; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Properties; + +// https://tinylog.org/v2/configuration/ +// https://github.com/tinylog-org/tinylog-android-example/blob/v2/app/src/main/resources/tinylog.properties + +public class TinyLogConfigurationLoader extends PropertiesConfigurationLoader { + private static Level level = Level.TRACE; + + @Override + public Properties load() { + Properties props = super.load(); + props.setProperty("level", level.name()); + return props; + } + + static void setup(Context context) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + boolean debug = prefs.getBoolean("debug", false); // Changing requires force stop + + if (debug) + level = Level.DEBUG; + else { + int def = (BuildConfig.DEBUG || BuildConfig.TEST_RELEASE ? android.util.Log.INFO : android.util.Log.WARN); + int _level = prefs.getInt("log_level", def); + if (_level == android.util.Log.VERBOSE) + level = Level.TRACE; + else if (_level == android.util.Log.DEBUG) + level = Level.DEBUG; + else if (_level == android.util.Log.INFO) + level = Level.INFO; + else if (_level == android.util.Log.WARN) + level = Level.WARN; + else if (_level == android.util.Log.ERROR) + level = Level.ERROR; + } + + System.setProperty("tinylog.configurationloader", + TinyLogConfigurationLoader.class.getName()); + + System.setProperty("tinylog.directory", + new File(context.getFilesDir(), "logs").getAbsolutePath()); + } + + static File[] getFiles(Context context) { + File[] files = new File(context.getFilesDir(), "logs").listFiles(); + + if (files == null) + return new File[0]; + + Arrays.sort(files, new Comparator() { + @Override + public int compare(File f1, File f2) { + return f1.getName().compareTo(f2.getName()); + } + }); + + return files; + } +} diff --git a/app/src/main/res/layout/fragment_options_misc.xml b/app/src/main/res/layout/fragment_options_misc.xml index 179ffb05d5..e1e314bb73 100644 --- a/app/src/main/res/layout/fragment_options_misc.xml +++ b/app/src/main/res/layout/fragment_options_misc.xml @@ -784,6 +784,19 @@ app:layout_constraintTop_toBottomOf="@id/tvProtocolHint" app:switchPadding="12dp" /> + +