=> 4f55dd4a701ff18885c34b4d0939ea2bdd602dce
[1mdiff --git a/res/about/android-version.gmi b/res/about/android-version.gmi[m [1mindex 642dd64a..a5fa91d4 100644[m [1m--- a/res/about/android-version.gmi[m [1m+++ b/res/about/android-version.gmi[m [36m@@ -6,6 +6,11 @@[m ```[m # Release notes[m [m [32m+[m[32m## 1.14 (Beta 11)[m [32m+[m[32m* Try to keep user data safe in unusual situations. Bookmarks and identities are saved via a system API, while also ensuring that data written to storage is not truncated. NOTE: Uninstalling the app will still delete everything, because user data is only stored locally.[m [32m+[m[32m* User data files are now stored in external storage, if available.[m [32m+[m[32m* Fixed determining actual name of files opened via the system file picker. This was sometimes preventing importing user data ZIP archives, for instance.[m [32m+[m ## 1.14 (Beta 10)[m * Fixed tab with pinned identity not closing if it's the last tab that's being closed.[m * Fixed hang when setting a folder's parent to itself in the Edit Folder dialog.[m [1mdiff --git a/src/android.c b/src/android.c[m [1mindex 7c00df8b..d753d4bb 100644[m [1m--- a/src/android.c[m [1m+++ b/src/android.c[m [36m@@ -22,6 +22,7 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m [m #include "android.h"[m #include "app.h"[m [32m+[m[32m#include "export.h"[m #include "resources.h"[m #include "ui/command.h"[m #include "ui/metrics.h"[m [36m@@ -29,6 +30,7 @@[m [mSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */[m #include "ui/window.h"[m [m #include[m [32m+[m[32m#include [m #include [m #include [m #include [m [36m@@ -225,6 +227,27 @@[m [mint preferredHeight_SystemTextInput(const iSystemTextInput *d) {[m return d->numLines * lineHeight_Text(d->font);[m }[m [m [32m+[m[32m/*----------------------------------------------------------------------------------------------*/[m [32m+[m [32m+[m[32mstatic int userBackupTimer_;[m [32m+[m [32m+[m[32mstatic uint32_t backupUserData_Android_(uint32_t interval, void *data) {[m [32m+[m[32m userBackupTimer_ = 0;[m [32m+[m[32m iUnused(interval, data);[m [32m+[m[32m /* This runs in a background thread. We don't want to block the UI thread for saving. */[m [32m+[m[32m iExport *backup = new_Export();[m [32m+[m[32m generatePartial_Export(backup, bookmarks_ExportFlag | identitiesAndTrust_ExportFlag);[m [32m+[m[32m iBuffer *buf = new_Buffer();[m [32m+[m[32m openEmpty_Buffer(buf);[m [32m+[m[32m serialize_Archive(archive_Export(backup), stream_Buffer(buf));[m [32m+[m[32m delete_Export(backup);[m [32m+[m[32m iString *enc = base64Encode_Block(data_Buffer(buf));[m [32m+[m[32m iRelease(buf);[m [32m+[m[32m javaCommand_Android("backup.save data:%s", cstr_String(enc));[m [32m+[m[32m delete_String(enc);[m [32m+[m[32m return 0;[m [32m+[m[32m}[m [32m+[m iBool handleCommand_Android(const char *cmd) {[m if (equal_Command(cmd, "android.input.changed")) {[m const int id = argLabel_Command(cmd, "id");[m [36m@@ -283,5 +306,29 @@[m [miBool handleCommand_Android(const char *cmd) {[m }[m return iTrue;[m }[m [32m+[m[32m else if (equal_Command(cmd, "bookmarks.changed") ||[m [32m+[m[32m equal_Command(cmd, "idents.changed") ||[m [32m+[m[32m equal_Command(cmd, "backup.now")) {[m [32m+[m[32m SDL_RemoveTimer(userBackupTimer_);[m [32m+[m[32m userBackupTimer_ = SDL_AddTimer(1000, backupUserData_Android_, NULL);[m [32m+[m[32m return iFalse;[m [32m+[m[32m }[m [32m+[m[32m else if (equal_Command(cmd, "backup.found")) {[m [32m+[m[32m iString *data = suffix_Command(cmd, "data");[m [32m+[m[32m iBlock *decoded = base64Decode_Block(utf8_String(data));[m [32m+[m[32m delete_String(data);[m [32m+[m[32m iArchive *archive = new_Archive();[m [32m+[m[32m if (openData_Archive(archive, decoded)) {[m [32m+[m[32m iExport *backup = new_Export();[m [32m+[m[32m if (load_Export(backup, archive)) {[m [32m+[m[32m import_Export(backup, ifMissing_ImportMethod, all_ImportMethod,[m [32m+[m[32m none_ImportMethod, none_ImportMethod, none_ImportMethod);[m [32m+[m[32m }[m [32m+[m[32m delete_Export(backup);[m [32m+[m[32m }[m [32m+[m[32m iRelease(archive);[m [32m+[m[32m delete_Block(decoded);[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m return iFalse;[m }[m [1mdiff --git a/src/app.c b/src/app.c[m [1mindex 331f84d9..de7e8f66 100644[m [1m--- a/src/app.c[m [1m+++ b/src/app.c[m [36m@@ -371,10 +371,10 @@[m [mstatic const char *dataDir_App_(void) {[m d->didCheckDataPathOption = iTrue;[m const iCommandLineArg *arg;[m if ((arg = iClob(checkArgumentValues_CommandLine([m [31m- &d->args, userDataDir_CommandLineOption, 1))) != NULL) {[m [32m+[m[32m &d->args, userDataDir_CommandLineOption, 1))) != NULL) {[m iString *execDir = concatCStr_Path(d->execPath, "..");[m d->overrideDataPath = concat_Path(execDir, value_CommandLineArg(arg, 0));[m [31m- delete_String(execDir); [m [32m+[m[32m delete_String(execDir);[m }[m }[m if (d->overrideDataPath) {[m [36m@@ -392,6 +392,11 @@[m [mstatic const char *dataDir_App_(void) {[m if (fileExistsCStr_FileInfo(userDir)) {[m return userDir;[m }[m [32m+[m[32m#endif[m [32m+[m[32m#if defined (iPlatformAndroid)[m [32m+[m[32m if (SDL_AndroidGetExternalStorageState() & SDL_ANDROID_EXTERNAL_STORAGE_WRITE) {[m [32m+[m[32m return SDL_AndroidGetExternalStoragePath();[m [32m+[m[32m }[m #endif[m if (defaultDataDir_App_) {[m return defaultDataDir_App_;[m [36m@@ -399,6 +404,59 @@[m [mstatic const char *dataDir_App_(void) {[m return SDL_GetPrefPath("Jaakko Keränen", "fi.skyjake.lagrange");[m }[m [m [32m+[m[32m#if defined (iPlatformAndroid)[m [32m+[m[32mstatic void copyFile_(const char *srcPath, const char *dstPath) {[m [32m+[m[32m if (fileExistsCStr_FileInfo(srcPath)) {[m [32m+[m[32m iFile *src = newCStr_File(srcPath);[m [32m+[m[32m iFile *dst = newCStr_File(dstPath);[m [32m+[m[32m if (open_File(src, readOnly_FileMode) && open_File(dst, writeOnly_FileMode)) {[m [32m+[m[32m iBlock *data = readAll_File(src);[m [32m+[m[32m write_File(dst, data);[m [32m+[m[32m delete_Block(data);[m [32m+[m[32m }[m [32m+[m[32m iRelease(dst);[m [32m+[m[32m iRelease(src);[m [32m+[m[32m }[m [32m+[m[32m}[m [32m+[m [32m+[m[32mstatic void migrateInternalUserDirToExternalStorage_App_(iApp *d) {[m [32m+[m[32m if (!(SDL_AndroidGetExternalStorageState() & SDL_ANDROID_EXTERNAL_STORAGE_WRITE)) {[m [32m+[m[32m /* As a fallback, user data will be stored in internal storage instead. */[m [32m+[m[32m return;[m [32m+[m[32m }[m [32m+[m[32m /* This is the app-specific "files" directory in internal storage. */[m [32m+[m[32m const char *intDataDir = SDL_GetPrefPath("Jaakko Keränen", "fi.skyjake.lagrange");[m [32m+[m[32m const char *extDataDir = SDL_AndroidGetExternalStoragePath();[m [32m+[m[32m const char *names[] = {[m [32m+[m[32m "bookmarks.ini",[m [32m+[m[32m "prefs.cfg",[m [32m+[m[32m "state.lgr",[m [32m+[m[32m "idents.lgr",[m [32m+[m[32m "trusted.2.txt",[m [32m+[m[32m "visited.2.txt",[m [32m+[m[32m };[m [32m+[m[32m makeDirs_Path(collectNewCStr_String(extDataDir));[m [32m+[m[32m iForIndices(i, names) {[m [32m+[m[32m const char *src = concatPath_CStr(intDataDir, names[i]);[m [32m+[m[32m copyFile_(src, concatPath_CStr(extDataDir, names[i]));[m [32m+[m[32m remove(src);[m [32m+[m[32m }[m [32m+[m[32m /* Copy identities as well. */[m [32m+[m[32m const char *srcIdents = concatPath_CStr(intDataDir, "idents");[m [32m+[m[32m if (fileExistsCStr_FileInfo(srcIdents)) {[m [32m+[m[32m const char *dstIdents = concatPath_CStr(extDataDir, "idents");[m [32m+[m[32m makeDirs_Path(collectNewCStr_String(dstIdents));[m [32m+[m[32m iForEach(DirFileInfo, entry, iClob(newCStr_DirFileInfo(srcIdents))) {[m [32m+[m[32m const iRangecc name = baseName_Path(path_FileInfo(entry.value));[m [32m+[m[32m const char *src = cstr_String(path_FileInfo(entry.value));[m [32m+[m[32m copyFile_(src, concatPath_CStr(dstIdents, cstr_Rangecc(name)));[m [32m+[m[32m remove(src);[m [32m+[m[32m }[m [32m+[m[32m rmdir_Path(collectNewCStr_String(srcIdents));[m [32m+[m[32m }[m [32m+[m[32m}[m [32m+[m[32m#endif[m [32m+[m static const char *downloadDir_App_(void) {[m #if defined (iPlatformAndroidMobile)[m const char *dir = concatPath_CStr(SDL_AndroidGetExternalStoragePath(), "Downloads");[m [36m@@ -997,6 +1055,10 @@[m [mstatic void dumpRequestFinished_App_(void *obj, iGmRequest *req) {[m [m static void init_App_(iApp *d, int argc, char **argv) {[m iBool doDump = iFalse;[m [32m+[m[32m#if defined (iPlatformAndroid)[m [32m+[m[32m /* Internal storage may be limited in size. */[m [32m+[m[32m migrateInternalUserDirToExternalStorage_App_(d);[m [32m+[m[32m#endif[m #if defined (iPlatformLinux) && !defined (iPlatformAndroid) && !defined (iPlatformTerminal)[m d->isRunningUnderWindowSystem = !iCmpStr(SDL_GetCurrentVideoDriver(), "x11") ||[m !iCmpStr(SDL_GetCurrentVideoDriver(), "wayland");[m [36m@@ -1309,6 +1371,10 @@[m [mstatic void init_App_(iApp *d, int argc, char **argv) {[m /* HACK: Force a resize so widgets update their state. */[m resize_MainWindow(d->window, -1, -1);[m }[m [32m+[m[32m#if defined (iPlatformAndroid)[m [32m+[m[32m /* See if there is something to import from backup. */[m [32m+[m[32m javaCommand_Android("backup.load");[m [32m+[m[32m#endif[m }[m [m static void deinit_App(iApp *d) {[m [36m@@ -1688,6 +1754,11 @@[m [mvoid processEvents_App(enum iAppEventMode eventMode) {[m case SDL_APP_WILLENTERBACKGROUND:[m #if defined (iPlatformAppleMobile)[m updateNowPlayingInfo_iOS();[m [32m+[m[32m#endif[m [32m+[m[32m#if defined (iPlatformAndroidMobile)[m [32m+[m[32m postCommand_App("backup.now"); /* ensure there's a copy of user data */[m [32m+[m[32m saveIdentities_GmCerts(certs_App());[m [32m+[m[32m save_Bookmarks(d->bookmarks, dataDir_App_());[m #endif[m setFreezeDraw_MainWindow(d->window, iTrue);[m savePrefs_App_(d);[m [36m@@ -2260,10 +2331,12 @@[m [mvoid postCommand_Root(iRoot *d, const char *command) {[m SDL_PushEvent(&ev);[m iWindow *win = d ? d->window : NULL;[m #if defined (iPlatformAndroid)[m [31m- SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s[command] {%d} %s",[m [31m- app_.isLoadingPrefs ? "[Prefs] " : "",[m [31m- (d == NULL || win == NULL ? 0 : d == win->roots[0] ? 1 : 2),[m [31m- command);[m [32m+[m[32m if (!startsWith_CStr(command, "backup.")) {[m [32m+[m[32m SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s[command] {%d} %s",[m [32m+[m[32m app_.isLoadingPrefs ? "[Prefs] " : "",[m [32m+[m[32m (d == NULL || win == NULL ? 0 : d == win->roots[0] ? 1 : 2),[m [32m+[m[32m command);[m [32m+[m[32m }[m #else[m if (app_.commandEcho) {[m const int windowIndex =[m
text/gemini; charset=utf-8
This content has been proxied by September (ba2dc).