From 48c6edfba35858ded1740882b735cb022c284e79 Mon Sep 17 00:00:00 2001 From: mg4gh Date: Thu, 16 Jan 2025 19:50:06 +0100 Subject: [PATCH] improve gpx backup --- .../mgmap/activity/mgmap/MGMapActivity.java | 6 +- .../features/tilestore/MGTileStoreDB.java | 3 +- .../mgmap/application/MGMapApplication.java | 2 +- .../mg/mgmap/application/util/ExtrasUtil.java | 11 +- .../application/util/PersistenceManager.java | 138 ++++++-- .../mg/mgmap/generic/util/BackupUtil.java | 306 ++++++++++++------ .../mg/mgmap/generic/util/basic/IOUtil.java | 14 + mgmap/src/main/res/values/strings.xml | 1 + 8 files changed, 340 insertions(+), 141 deletions(-) diff --git a/mgmap/src/main/java/mg/mgmap/activity/mgmap/MGMapActivity.java b/mgmap/src/main/java/mg/mgmap/activity/mgmap/MGMapActivity.java index 1c1f25be..c897da43 100644 --- a/mgmap/src/main/java/mg/mgmap/activity/mgmap/MGMapActivity.java +++ b/mgmap/src/main/java/mg/mgmap/activity/mgmap/MGMapActivity.java @@ -152,6 +152,8 @@ public class MGMapActivity extends MapViewerBase implements XmlRenderThemeMenuCa private MapDataStoreUtil mapDataStoreUtil = null; private GGraphTileFactory gGraphTileFactory = null; private final Runnable ttUploadGpxTrigger = () -> prefCache.get(R.string.preferences_sftp_uploadGpxTrigger, false).toggle(); + private final Runnable ttCheckFullBackup = () -> + BackupUtil.checkFullBackup(MGMapActivity.this, application.getPersistenceManager(), prefCache.get(R.string.preferences_last_full_backup_time, 0L)); private final PropertyChangeListener prefGpsObserver = (e) -> triggerTrackLoggerService(); private Pref prefTracksVisible; private List recreatePreferences; @@ -256,7 +258,7 @@ protected void onCreate(Bundle savedInstanceState) { application.prefGps.addObserver(prefGpsObserver); application.prefGps.onChange(); prefCache.get(R.string.preferences_sftp_uploadGpxTrigger, false).addObserver((e) -> { - FeatureService.getTimer().postDelayed(()->BackupUtil.trigger(MGMapActivity.this, application.getPersistenceManager()), 10); + FeatureService.getTimer().postDelayed(()->BackupUtil.trigger(MGMapActivity.this, application.getPersistenceManager(), true, null), 10); FeatureService.getTimer().postDelayed(()->new GpxSyncUtil().trySynchronisation(application), 1000); }); prefTracksVisible = prefCache.get(R.string.preferences_tracks_visible, true); @@ -308,6 +310,7 @@ protected void onResume() { application.lastPositionsObservable.changed(); FeatureService.getTimer().postDelayed(ttUploadGpxTrigger, 25*1000); + FeatureService.getTimer().postDelayed(ttCheckFullBackup, 100*1000); application.finishAlarm(); // just in case there is one } @@ -337,6 +340,7 @@ protected void onPause() { } } FeatureService.getTimer().removeCallbacks(ttUploadGpxTrigger); + FeatureService.getTimer().removeCallbacks(ttCheckFullBackup); super.onPause(); } diff --git a/mgmap/src/main/java/mg/mgmap/activity/mgmap/features/tilestore/MGTileStoreDB.java b/mgmap/src/main/java/mg/mgmap/activity/mgmap/features/tilestore/MGTileStoreDB.java index 48c2cd15..f425ed28 100644 --- a/mgmap/src/main/java/mg/mgmap/activity/mgmap/features/tilestore/MGTileStoreDB.java +++ b/mgmap/src/main/java/mg/mgmap/activity/mgmap/features/tilestore/MGTileStoreDB.java @@ -28,7 +28,6 @@ import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.lang.invoke.MethodHandles; @@ -83,7 +82,7 @@ public MGTileStoreDB(File storeDir, AssetManager am, GraphicFactory graphicFacto } else if (storeRW == null){ // store found, but is not writeable - create a clone that is writeable mgLog.i(prefix+"store found ("+store.getName()+"), but is not writeable - create a clone that is writeable"); storeRW = new File(store.getAbsolutePath().replaceFirst("\\.mbtiles", "_rw.mbtiles")); - IOUtil.copyStreams(new FileInputStream(store), new FileOutputStream(storeRW)); + IOUtil.copyFile(store, storeRW); } else { mgLog.i(prefix+"store found with read/write access found: "+storeRW.getName()); } diff --git a/mgmap/src/main/java/mg/mgmap/application/MGMapApplication.java b/mgmap/src/main/java/mg/mgmap/application/MGMapApplication.java index e9593f9d..bdfc61a2 100644 --- a/mgmap/src/main/java/mg/mgmap/application/MGMapApplication.java +++ b/mgmap/src/main/java/mg/mgmap/application/MGMapApplication.java @@ -204,7 +204,7 @@ void _init(BaseConfig baseConfig){ mgLog.i("init finished!"); }).start(); - BackupUtil.restore2(this, persistenceManager); + BackupUtil.restore2(this, persistenceManager, true); // initialize MetaData (as used from AvailableTrackLogs service and statistic) new Thread(() -> { diff --git a/mgmap/src/main/java/mg/mgmap/application/util/ExtrasUtil.java b/mgmap/src/main/java/mg/mgmap/application/util/ExtrasUtil.java index 7a35cabc..f432b6ae 100644 --- a/mgmap/src/main/java/mg/mgmap/application/util/ExtrasUtil.java +++ b/mgmap/src/main/java/mg/mgmap/application/util/ExtrasUtil.java @@ -67,13 +67,14 @@ public static ArrayList checkCreateMeta(MGMapApplication mgMapApplicatio } } catch (Exception e){ mgLog.e(e); } } -// mgLog.d(String.format(Locale.ENGLISH, "load metaData trackLog=%s currentRun=%s app.currentRun=%s",name, currentRun, mgMapApplication.currentRun )); - if (mgMapApplication.currentRun != currentRun) break; // check correct run immediately before adding to metaData - mgMapApplication.addMetaDataTrackLog(trackLog); + if (trackLog != null){ // might be null, if gpx is corrupted + if (mgMapApplication.currentRun != currentRun) break; // check correct run immediately before adding to metaData + mgMapApplication.addMetaDataTrackLog(trackLog); + } } - mgLog.d(String.format(Locale.ENGLISH,"checkCreateMeta summary gpxNames=%d cntMetaLoaded=%d cntMetaCreated=%d",gpxNames.size(),cntMetaLoaded,cntMetaCreated)); + mgLog.i(String.format(Locale.ENGLISH,"checkCreateMeta summary gpxNames=%d cntMetaLoaded=%d cntMetaCreated=%d",gpxNames.size(),cntMetaLoaded,cntMetaCreated)); for (String name : metaNames){ - mgLog.i("Delete meta file for "+name); + mgLog.d("Delete meta file for "+name); persistenceManager.deleteTrack(name); } mgLog.i("checkCreateMeta finished - currentRun="+currentRun); diff --git a/mgmap/src/main/java/mg/mgmap/application/util/PersistenceManager.java b/mgmap/src/main/java/mg/mgmap/application/util/PersistenceManager.java index 14d5638b..e7396683 100644 --- a/mgmap/src/main/java/mg/mgmap/application/util/PersistenceManager.java +++ b/mgmap/src/main/java/mg/mgmap/application/util/PersistenceManager.java @@ -30,7 +30,9 @@ import java.lang.invoke.MethodHandles; import java.nio.file.Files; import java.util.ArrayList; +import java.util.List; import java.util.Properties; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -46,6 +48,101 @@ public class PersistenceManager { private static final MGLog mgLog = new MGLog(MethodHandles.lookup().lookupClass().getName()); + public static final String SUFFIX_GPX = ".gpx"; + public static final String SUFFIX_META = ".meta"; + + + private static ArrayList getFilesRecursive(File dir, String nameEndsWith, ArrayList matchedList){ + File[] entries = dir.listFiles(); + if (entries != null){ + for (File entry : entries) { + if (entry.isDirectory()) { + getFilesRecursive(entry, nameEndsWith, matchedList); + } else { + if (entry.getName().endsWith(nameEndsWith)) { + matchedList.add(entry); + } + } + } + } + return matchedList; + } + + public static ArrayList getFilesRecursive(File dir, String suffix){ + return getFilesRecursive(dir, suffix, new ArrayList<>()); + } + + public static List getNamesRecursive(File dir, String suffix){ + return getFilesRecursive(dir, suffix, new ArrayList<>()) + .stream().map(f->f.getAbsolutePath() + .substring(dir.getAbsolutePath().length() + File.separator.length(), f.getAbsolutePath().length()-suffix.length()) ) + .collect(Collectors.toList()); + } + + public static boolean forceDelete(File file){ + File[] contents = file.listFiles(); + if (contents != null) { + for (File f : contents) { + if (! Files.isSymbolicLink(f.toPath())) { + forceDelete(f); + } + } + } + return file.delete(); + } + + public static void mergeDir(File mergeFrom, File mergeTo){ // recursive merge dir + assert mergeFrom.exists(); + assert mergeTo.exists(); + File[] fromArray = mergeFrom.listFiles(); + if (fromArray != null){ + for (File file : fromArray){ + if (Files.isSymbolicLink(file.toPath())) continue; + File toFile = new File(mergeTo, file.getName()); + + if (toFile.exists() && (file.isFile() != toFile.isFile())){ + mgLog.e("cannot merge "+file.getAbsolutePath()+" with type "+(file.isFile()?"file":"directory")+" to "+toFile.getAbsolutePath()+" with type "+(toFile.isFile()?"file":"directory")); + continue; + } + if (file.isFile()){ + if (toFile.exists()){ + if ( file.lastModified() > toFile.lastModified() ){ + if (!toFile.delete()) mgLog.e("failed to delete "+toFile.getAbsolutePath()); + if (!file.renameTo(toFile)) mgLog.e("failed to rename from "+file.getAbsolutePath()+" to "+toFile.getAbsolutePath()); + } + } else { + if (!file.renameTo(toFile)) mgLog.e("failed to rename from "+file.getAbsolutePath()+" to "+toFile.getAbsolutePath()); + } + } else { // file is directory + if (!toFile.exists()){ + if (!toFile.mkdir()) mgLog.e("failed to create "+toFile.getAbsolutePath()); + } + mergeDir(file, toFile); + } + } // for (File file : fromArray){ + } // if (fromArray != null){ + } + + public static boolean checkFilesOlderThan(File dir, long timestamp){ + File[] entries = dir.listFiles(); + if (entries != null){ + for (File entry : entries) { + if (entry.isDirectory()) { + checkFilesOlderThan(entry, timestamp); + } else { + if (entry.lastModified() >= timestamp) { + return false; + } + } + } + } + return true; + } + + + + + private final MGMapApplication application; private final Context context; @@ -263,12 +360,12 @@ private void checkCreatePath(File file){ } public boolean existsGpx(String filename) { - File file = getAbsoluteFile(trackGpxDir, filename, ".gpx"); + File file = getAbsoluteFile(trackGpxDir, filename, SUFFIX_GPX); return file.exists(); } public boolean isGpxOlderThanMeta(String filename){ - File gpxFile = getAbsoluteFile(trackGpxDir, filename, ".gpx"); - File metaFile = getAbsoluteFile(trackMetaDir, filename, ".meta"); + File gpxFile = getAbsoluteFile(trackGpxDir, filename, SUFFIX_GPX); + File metaFile = getAbsoluteFile(trackMetaDir, filename, SUFFIX_META); assert gpxFile.exists(); if (metaFile.exists()){ return gpxFile.lastModified() <= metaFile.lastModified(); @@ -278,7 +375,7 @@ public boolean isGpxOlderThanMeta(String filename){ } public PrintWriter openGpxOutput(String filename) { try { - File file = getAbsoluteFile(trackGpxDir, filename, ".gpx"); + File file = getAbsoluteFile(trackGpxDir, filename, SUFFIX_GPX); checkCreatePath(file); return new PrintWriter(file); } catch (FileNotFoundException e) { @@ -288,7 +385,7 @@ public PrintWriter openGpxOutput(String filename) { } public InputStream openGpxInput(String filename) { try { - File file = getAbsoluteFile(trackGpxDir, filename, ".gpx"); + File file = getAbsoluteFile(trackGpxDir, filename, SUFFIX_GPX); return new FileInputStream(file); } catch (FileNotFoundException e) { mgLog.e(e); @@ -297,7 +394,7 @@ public InputStream openGpxInput(String filename) { } public Uri getGpxUri(String filename) { try { - File file = getAbsoluteFile(trackGpxDir, filename, ".gpx"); + File file = getAbsoluteFile(trackGpxDir, filename, SUFFIX_GPX); return FileProvider.getUriForFile(context, context.getPackageName() + ".provider", file); } catch (Exception e) { mgLog.e(e); @@ -307,7 +404,7 @@ public Uri getGpxUri(String filename) { public FileOutputStream openMetaOutput(String filename) { try { - File file = getAbsoluteFile(trackMetaDir, filename, ".meta"); + File file = getAbsoluteFile(trackMetaDir, filename, SUFFIX_META); checkCreatePath(file); return new FileOutputStream(file); } catch (FileNotFoundException e) { @@ -318,7 +415,7 @@ public FileOutputStream openMetaOutput(String filename) { public FileInputStream openMetaInput(String filename) { try { - File file = getAbsoluteFile(trackMetaDir, filename, ".meta"); + File file = getAbsoluteFile(trackMetaDir, filename, SUFFIX_META); return new FileInputStream(file); } catch (FileNotFoundException e) { mgLog.e(e); @@ -326,29 +423,12 @@ public FileInputStream openMetaInput(String filename) { return null; } - /** Retruns a list of relative path name, but without extension */ - public ArrayList getNames(File baseDir, File dir, String endsWith, ArrayList matchedList) { - File[] entries = dir.listFiles(); - if (entries != null){ - for (File entry : entries) { - if (entry.isDirectory()) { - getNames(baseDir, entry, endsWith, matchedList); - } else { - if (entry.getName().endsWith(endsWith)) { - String sPath = entry.getAbsolutePath(); - matchedList.add(sPath.substring((baseDir.getAbsolutePath()+File.pathSeparator ).length(), sPath.length() - endsWith.length())); - } - } - } - } - return matchedList; - } - public ArrayList getGpxNames() { - return getNames(trackGpxDir, trackGpxDir, ".gpx", new ArrayList<>()); + public List getGpxNames() { + return getNamesRecursive(trackGpxDir, SUFFIX_GPX); } - public ArrayList getMetaNames() { - return getNames(trackMetaDir, trackMetaDir, ".meta", new ArrayList<>()); + public List getMetaNames() { + return getNamesRecursive(trackMetaDir, SUFFIX_META); } public File getThemesDir(){ diff --git a/mgmap/src/main/java/mg/mgmap/generic/util/BackupUtil.java b/mgmap/src/main/java/mg/mgmap/generic/util/BackupUtil.java index 3ec0a301..e5ad97b0 100644 --- a/mgmap/src/main/java/mg/mgmap/generic/util/BackupUtil.java +++ b/mgmap/src/main/java/mg/mgmap/generic/util/BackupUtil.java @@ -1,8 +1,14 @@ package mg.mgmap.generic.util; +import android.app.Activity; +import android.content.ClipData; import android.content.Context; +import android.content.Intent; +import android.net.Uri; import android.os.SystemClock; +import androidx.core.content.FileProvider; + import net.lingala.zip4j.ZipFile; import net.lingala.zip4j.model.ZipParameters; import net.lingala.zip4j.model.enums.AesKeyStrength; @@ -10,18 +16,19 @@ import net.lingala.zip4j.progress.ProgressMonitor; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; +import mg.mgmap.R; import mg.mgmap.application.MGMapApplication; import mg.mgmap.application.util.PersistenceManager; import mg.mgmap.generic.util.basic.IOUtil; import mg.mgmap.generic.util.basic.MGLog; +import mg.mgmap.generic.view.DialogView; public class BackupUtil { @@ -29,93 +36,130 @@ public class BackupUtil { private static final String clazz = MethodHandles.lookup().lookupClass().getName(); private static final MGLog mgLog = new MGLog(clazz); private static final String PW = "mfoidfbmiofUUdCos"; - private static final String BACKUP_FILE_NAME1 = "inner_backup.zip"; - private static final String BACKUP_FILE_NAME2 = "backup.zip"; private static final AtomicBoolean inProgress = new AtomicBoolean(false); - public static ZipParameters getZipParameters(){ - ZipParameters zipParameters = new ZipParameters(); + private static String getBackupFileName(boolean latest){ + return "backup_"+(latest?"latest":"full")+".zip"; + } + + public static void checkFullBackup(Context context, PersistenceManager persistenceManager, Pref prefLastFullBackupTime){ + long days = (System.currentTimeMillis() - prefLastFullBackupTime.getValue()) / (1000L*60*60*24L); + boolean timeTrigger = ( days >= 90L ); // 90 days + boolean trackNumberTrigger = false; + ArrayList files = PersistenceManager.getFilesRecursive(persistenceManager.getTrackGpxDir(),PersistenceManager.SUFFIX_GPX); + int cntNewFiles = 0; + for (File f : files){ + if (f.isFile() && (f.lastModified() > prefLastFullBackupTime.getValue())) cntNewFiles++; + if (cntNewFiles > 300){ + trackNumberTrigger = true; + break; + } + } + mgLog.d("checkFullBackup timeTrigger="+timeTrigger+" days="+days+" trackNumberTrigger="+trackNumberTrigger+" cntNewFiles="+cntNewFiles); + if (timeTrigger || trackNumberTrigger){ + BackupUtil.trigger(context, persistenceManager, false, prefLastFullBackupTime); + } + } + + public static ZipParameters addZipParametersForEncryption(ZipParameters zipParameters){ zipParameters.setEncryptFiles(true); zipParameters.setEncryptionMethod(EncryptionMethod.AES); // Below line is optional. AES 256 is used by default. You can override it to use AES 128. AES 192 is supported only for extracting. zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256); return zipParameters; } - public static void trigger(Context context, PersistenceManager persistenceManager){ - mgLog.d("trigger"); + public static void trigger(Context context, PersistenceManager persistenceManager, boolean latest, Pref prefLastFullBackupTime){ + String backupFileName = getBackupFileName(latest); + mgLog.d("trigger prepare backup"); if (inProgress.compareAndSet(false, true)){ BgJob bgJob = new BgJob(){ @Override - protected void doJob() throws Exception { + protected void doJob() { try { - boolean success = false; - mgLog.d("trigger backup started"); - File filesDir = context.getFilesDir(); - File backupDir = new File(filesDir, "backup"); - if (!backupDir.exists()){ - boolean res = backupDir.mkdir(); - mgLog.d(backupDir.getAbsolutePath()+" created - res="+res); + mgLog.d("prepare backup - job started for "+backupFileName); + File dataBackupDir = new File(context.getFilesDir(), "backup"); + if (dataBackupDir.exists()){ // make sure that there is no other stuff which might influence the backup + if (!PersistenceManager.forceDelete(dataBackupDir)) mgLog.e("delete failed for dataBackupDir "+dataBackupDir.getAbsolutePath()); + } + if (!dataBackupDir.exists()){ + if (!dataBackupDir.mkdir()) mgLog.e( "creation of data backup dir failed: "+dataBackupDir.getAbsolutePath()); + } + + File backupFile = new File(persistenceManager.getBackupDir(), backupFileName); + if (backupFile.exists()){ + if (!backupFile.delete()) mgLog.e("delete failed for old backupFile: "+backupFile.getAbsolutePath()); } - File backupTempFile1 = new File(persistenceManager.getBackupDir(), BACKUP_FILE_NAME1); - if (backupTempFile1.exists()){ - boolean res = backupTempFile1.delete(); - mgLog.d("delete old backupTempFile1 ("+backupTempFile1.getAbsolutePath()+") - res="+res); + File backupFileTemp = new File(persistenceManager.getBackupDir(), backupFileName+".temp"); + if (backupFileTemp.exists()){ + if (!backupFileTemp.delete()) mgLog.e("delete failed for old backupFile: "+backupFileTemp.getAbsolutePath()); } - File backupFile = new File(backupDir, BACKUP_FILE_NAME2); mgLog.d(clazz+PW); - try (ZipFile zipFile = new ZipFile( backupTempFile1)){ + boolean success = false; + try (ZipFile zipFile = new ZipFile( backupFileTemp )){ + ArrayList files = PersistenceManager.getFilesRecursive(persistenceManager.getTrackGpxDir(),PersistenceManager.SUFFIX_GPX); + if (files.isEmpty()) return; + files.sort( (f1,f2)-> -(Long.compare(f1.lastModified(),f2.lastModified())) ); + File last = (latest && (files.size() > 365))?files.get(365):files.get(files.size()-1); + ZipParameters zipParameters = new ZipParameters(); + zipParameters.setExcludeFileFilter(file -> file.isDirectory()? PersistenceManager.checkFilesOlderThan(file,last.lastModified()):file.lastModified() < last.lastModified()); zipFile.setRunInThread(true); ProgressMonitor progressMonitor = zipFile.getProgressMonitor(); + zipFile.addFolder(persistenceManager.getTrackGpxDir(), zipParameters); - ArrayList files = new ArrayList<>(Arrays.asList(persistenceManager.getTrackGpxDir().listFiles())); - files.sort( (f1,f2)-> -(Long.compare(f1.lastModified(),f2.lastModified())) ); - while (files.size() > 365) files.remove(files.size()-1); - zipFile.addFiles(files); -// zipFile.addFolder(persistenceManager.getTrackGpxDir()); while (!progressMonitor.getState().equals(ProgressMonitor.State.READY) || (progressMonitor.getResult() == null)) { - mgLog.d("progress: "+progressMonitor.getState()+" "+progressMonitor.getWorkCompleted()+" "+progressMonitor.getTotalWork()+" "+progressMonitor.getPercentDone()+" "+progressMonitor.getFileName()); - setProgress(progressMonitor.getPercentDone()/2); // this task is just the first half + mgLog.d("compress progress: "+progressMonitor.getState()+" "+progressMonitor.getWorkCompleted()+" "+progressMonitor.getTotalWork()+" "+progressMonitor.getPercentDone()); + setProgress( progressMonitor.getPercentDone() / 2 ); SystemClock.sleep(1000); } + mgLog.d("compress progress: "+progressMonitor.getState()+" "+progressMonitor.getWorkCompleted()+" "+progressMonitor.getTotalWork()+" "+progressMonitor.getPercentDone()+" result="+progressMonitor.getResult()); if (progressMonitor.getResult().equals(ProgressMonitor.Result.SUCCESS)){ - mgLog.d("trigger backup compress: success"); + mgLog.d("prepare backup - create zip successful for "+backupFileName); success = true; } } + if (success){ - File backupTempFile2 = new File(persistenceManager.getBackupDir(), BACKUP_FILE_NAME2); - if (backupTempFile2.exists()){ - boolean res = backupTempFile2.delete(); - mgLog.d("delete old backupTempFile2 ("+backupTempFile2.getAbsolutePath()+") - res="+res); - } - try (ZipFile zipFile = new ZipFile( backupTempFile2, (clazz+PW).toCharArray() )) { + try (ZipFile zipFile = new ZipFile( backupFile, (clazz+PW).toCharArray() )){ + ZipParameters zipParameters = addZipParametersForEncryption( new ZipParameters() ); zipFile.setRunInThread(true); ProgressMonitor progressMonitor = zipFile.getProgressMonitor(); - zipFile.addFile(backupTempFile1, getZipParameters()); + zipFile.addFile(backupFileTemp, zipParameters); + while (!progressMonitor.getState().equals(ProgressMonitor.State.READY) || (progressMonitor.getResult() == null)) { - mgLog.d("progress: "+progressMonitor.getState()+" "+progressMonitor.getWorkCompleted()+" "+progressMonitor.getTotalWork()+" "+progressMonitor.getPercentDone()+" "+progressMonitor.getFileName()); - setProgress(50+progressMonitor.getPercentDone()/2); // second half of job + mgLog.d("encrypt progress: "+progressMonitor.getState()+" "+progressMonitor.getWorkCompleted()+" "+progressMonitor.getTotalWork()+" "+progressMonitor.getPercentDone()); + setProgress( 50 + progressMonitor.getPercentDone()/2 ); SystemClock.sleep(1000); } + mgLog.d("encrypt progress: "+progressMonitor.getState()+" "+progressMonitor.getWorkCompleted()+" "+progressMonitor.getTotalWork()+" "+progressMonitor.getPercentDone()+" result="+progressMonitor.getResult()); if (progressMonitor.getResult().equals(ProgressMonitor.Result.SUCCESS)){ - mgLog.d("trigger backup encryption: success"); - IOUtil.copyStreams(new FileInputStream(backupTempFile2), new FileOutputStream(backupFile)); + if (latest){ + mgLog.d("prepare backup - copy to dataBackupDir "+backupFileName+" to "+dataBackupDir.getAbsolutePath()); + IOUtil.copyFile( backupFile, new File(dataBackupDir, backupFileName) ); + } else { // full backup + triggerFullBackup(context, backupFile, prefLastFullBackupTime); + } } } } - mgLog.d("trigger backup finished"); + if (backupFileTemp.exists()){ + if (!backupFileTemp.delete()) mgLog.e("delete failed for old backupFile: "+backupFileTemp.getAbsolutePath()); + } + + mgLog.d("prepare backup - job finished for "+backupFileName); + } catch (Exception e){ + mgLog.e(e); } finally { inProgress.set(false); } } }; - bgJob.setText("Prepare backup of gpx."); + bgJob.setText("Prepare backup of gpx for "+backupFileName); bgJob.setMax(100); MGMapApplication.getByContext(context).addBgJob(bgJob); } else { - mgLog.d("trigger failed - in Progress"); + mgLog.d("trigger prepare backup failed - other backup operation in Progress"); } } @@ -123,80 +167,136 @@ protected void doJob() throws Exception { * Restore from /data folder to restore folder */ public static void restore(Context context, PersistenceManager persistenceManager){ + File dataRestoreMarker = new File(context.getFilesDir(), "dataRestoreMarker"); try { - mgLog.d("restore backup - check started"); - File filesDir = context.getFilesDir(); - File backupDir = new File(filesDir, "backup"); - File backupFile = new File(backupDir, BACKUP_FILE_NAME2); - File backupTempFile2 = new File(persistenceManager.getRestoreDir(), BACKUP_FILE_NAME2); - if (!backupTempFile2.exists()){ - new FileOutputStream(backupTempFile2).close(); // touch restoreFile - if (backupDir.exists() && backupFile.exists()){ - mgLog.d("restore backup - copy backup file."); - IOUtil.copyStreams(new FileInputStream(backupFile), new FileOutputStream(backupTempFile2)); - mgLog.d("restore backup - copy backup file finished."); - new FileOutputStream(new File(persistenceManager.getRestoreDir(), "restore.job")).close(); + String backupFileName = getBackupFileName(true); + mgLog.d("restore backup - check started for "+backupFileName); + File dataBackupDir = new File(context.getFilesDir(), "backup"); + File dataBackupFile = new File(dataBackupDir, backupFileName); + mgLog.d("restore backup - dataBackupFile="+dataBackupFile.getAbsolutePath()+" dataBackupDir="+dataBackupDir.exists()+" dataBackupFile+"+dataBackupFile.exists()+" dataRestoreMarker="+dataRestoreMarker.exists()); + if (!dataRestoreMarker.exists()){ + File backupFile = new File(persistenceManager.getRestoreDir(), backupFileName); + if (dataBackupDir.exists() && dataBackupFile.exists()){ + mgLog.d("restore backup - copy started for "+backupFile.getAbsolutePath()); + IOUtil.copyFile( dataBackupFile, backupFile); + mgLog.d("restore backup - copy finished for "+backupFile.getAbsolutePath()); } } - mgLog.d("restore backup - check finished."); + mgLog.d("restore backup - check finished for "+backupFileName); } catch (Exception e) { mgLog.e(e); + } finally { + try { + new FileOutputStream(dataRestoreMarker).close(); // touch restore marker + } catch (IOException e) { mgLog.e(e); } } + } - public static void restore2(Context context, PersistenceManager persistenceManager){ - File restoreJob = new File(persistenceManager.getRestoreDir(), "restore.job"); - if (restoreJob.exists()){ - BgJob bgJob = new BgJob(){ - @Override - protected void doJob() { - boolean success = false; - try { - mgLog.d("restore backup - job started"); - File backupTempFile2 = new File(persistenceManager.getRestoreDir(), BACKUP_FILE_NAME2); - try (ZipFile zipFile = new ZipFile( backupTempFile2, (clazz+PW).toCharArray() )){ - zipFile.setRunInThread(true); - ProgressMonitor progressMonitor = zipFile.getProgressMonitor(); - zipFile.extractAll(persistenceManager.getRestoreDir().getAbsolutePath()); - while (!progressMonitor.getState().equals(ProgressMonitor.State.READY) || (progressMonitor.getResult() == null)) { - mgLog.d("progress: "+progressMonitor.getState()+" "+progressMonitor.getWorkCompleted()+" "+progressMonitor.getTotalWork()+" "+progressMonitor.getPercentDone()+" "+progressMonitor.getFileName()); - setProgress(progressMonitor.getPercentDone()/2); // this task is just the first half - SystemClock.sleep(1000); - } - mgLog.d("trigger backup decryption: "+progressMonitor.getResult()); - if (progressMonitor.getResult().equals(ProgressMonitor.Result.SUCCESS)){ - success = true; - } - } - if (success){ - File backupTempFile1 = new File(persistenceManager.getRestoreDir(), BACKUP_FILE_NAME1); - try (ZipFile zipFile = new ZipFile( backupTempFile1 )){ - zipFile.setRunInThread(true); - ProgressMonitor progressMonitor = zipFile.getProgressMonitor(); - zipFile.extractAll(persistenceManager.getTrackGpxDir().getAbsolutePath().replace("/gpx", "")); - while (!progressMonitor.getState().equals(ProgressMonitor.State.READY) || (progressMonitor.getResult() == null)) { - mgLog.d("progress: "+progressMonitor.getState()+" "+progressMonitor.getWorkCompleted()+" "+progressMonitor.getTotalWork()+" "+progressMonitor.getPercentDone()+" "+progressMonitor.getFileName()); - setProgress(50+progressMonitor.getPercentDone()/2); // this task is just the first half - SystemClock.sleep(1000); + public static void restore2(Context context, PersistenceManager persistenceManager, boolean latest){ + String backupFileName = getBackupFileName(latest); + File backupFile = new File(persistenceManager.getRestoreDir(), backupFileName); + if (backupFile.exists()){ + if (inProgress.compareAndSet(false, true)) { + BgJob bgJob = new BgJob(){ + @Override + protected void doJob() { + try { + mgLog.d("restore backup - job started"); + if (backupFile.length() > 0){ + boolean success = false; + + File backupFileTemp = new File(persistenceManager.getRestoreDir(), backupFileName+".temp"); + if (backupFileTemp.exists()){ + if (!backupFileTemp.delete()) mgLog.e("delete failed for old backupFile: "+backupFileTemp.getAbsolutePath()); + } + try (ZipFile zipFile = new ZipFile( backupFile, (clazz+PW).toCharArray() )){ + zipFile.setRunInThread(true); + + ProgressMonitor progressMonitor = zipFile.getProgressMonitor(); + zipFile.extractAll(persistenceManager.getRestoreDir().getAbsolutePath()); + while (!progressMonitor.getState().equals(ProgressMonitor.State.READY) || (progressMonitor.getResult() == null)) { + mgLog.d("decryp progress: "+progressMonitor.getState()+" "+progressMonitor.getWorkCompleted()+" "+progressMonitor.getTotalWork()+" "+progressMonitor.getPercentDone()+" "+progressMonitor.getFileName()); + setProgress(progressMonitor.getPercentDone()); + SystemClock.sleep(1000); + } + mgLog.d("decryp progress: "+progressMonitor.getState()+" "+progressMonitor.getWorkCompleted()+" "+progressMonitor.getTotalWork()+" "+progressMonitor.getPercentDone()+" "+progressMonitor.getFileName()+" result="+progressMonitor.getResult()); + if (progressMonitor.getResult().equals(ProgressMonitor.Result.SUCCESS)){ + mgLog.d("restore backup - uncompress zip successful for "+backupFileName); + success = true; + } + } // try (ZipFile zipFile =... + + if (success){ + File restoreGpxDir = new File(persistenceManager.getRestoreDir(), "gpx"); + if (!PersistenceManager.forceDelete(restoreGpxDir)) mgLog.e("delete failed for restoreGpxDir "+restoreGpxDir.getAbsolutePath()); + try (ZipFile zipFile = new ZipFile( backupFileTemp )){ + zipFile.setRunInThread(false); + ProgressMonitor progressMonitor = zipFile.getProgressMonitor(); + zipFile.extractAll(persistenceManager.getRestoreDir().getAbsolutePath()); + while (!progressMonitor.getState().equals(ProgressMonitor.State.READY) || (progressMonitor.getResult() == null)) { + mgLog.d("decompress progress: "+progressMonitor.getState()+" "+progressMonitor.getWorkCompleted()+" "+progressMonitor.getTotalWork()+" "+progressMonitor.getPercentDone()+" "+progressMonitor.getFileName()); + setProgress(100); + SystemClock.sleep(100); + } + mgLog.d("decompress progress: "+progressMonitor.getState()+" "+progressMonitor.getWorkCompleted()+" "+progressMonitor.getTotalWork()+" "+progressMonitor.getPercentDone()+" "+progressMonitor.getFileName()+" result="+progressMonitor.getResult()); + if (progressMonitor.getResult().equals(ProgressMonitor.Result.SUCCESS)){ + PersistenceManager.mergeDir(restoreGpxDir, persistenceManager.getTrackGpxDir()); + } + } // try (ZipFile zipFile =... + if (!PersistenceManager.forceDelete(restoreGpxDir)) mgLog.e("delete failed for restoreGpxDir "+restoreGpxDir.getAbsolutePath()); + } + if (backupFileTemp.exists()){ + if (!backupFileTemp.delete()) mgLog.e("delete failed for old backupFile: "+backupFileTemp.getAbsolutePath()); } - mgLog.d("trigger backup uncompress: "+progressMonitor.getResult()); - } + } + } catch (IOException e) { + mgLog.e(e); + } finally { + inProgress.set(false); + File renameTo = new File(persistenceManager.getRestoreDir(), backupFileName +".done"); + if (renameTo.exists()){ + if (!renameTo.delete()) mgLog.e("delete failed for: "+renameTo.getAbsolutePath()); + } + if (!backupFile.renameTo(renameTo)) mgLog.e("rename failed from "+backupFile.getAbsolutePath()+" to "+renameTo.getAbsolutePath() ) ; } - } catch (IOException e) { - mgLog.e(e); - } finally { - boolean res = restoreJob.delete(); - mgLog.d("restore.job deleted - res="+res); } + }; + bgJob.setText("Restore backup of gpx."); + bgJob.setMax(100); + MGMapApplication.getByContext(context).addBgJob(bgJob); + } else { + mgLog.d("restore backup failed - other backup operation in Progress"); + } + } - } - }; - bgJob.setText("Restore backup of gpx."); - bgJob.setMax(100); - MGMapApplication.getByContext(context).addBgJob(bgJob); + } + + private static void triggerFullBackup(Context context, File fullBackupFile, Pref prefLastFullBackupTime){ + if (context instanceof Activity activity){ + String fileSize = String.format(Locale.ENGLISH,"%f.1MB", fullBackupFile.length()/(1024*1024f)); + DialogView dialogView = activity.findViewById(R.id.dialog_parent); + dialogView.lock(() -> dialogView + .setTitle("Full GPX Backup") + .setMessage("It's time to save the full backup of gpx tracks ("+fileSize+") (recommended to your google drive). \n\nIf you want to restore them later, just copy this file via the Android share mechanism back to the MGMapViewer/backup/restore/ folder and restart the app.") + .setPositive("OK", evt -> triggerFullBackupShare(context, fullBackupFile, prefLastFullBackupTime)) + .setNegative("Cancel", null) + .show()); + } else { + triggerFullBackupShare(context, fullBackupFile, prefLastFullBackupTime); } + } + private static void triggerFullBackupShare(Context context, File fullBackupFile, Pref prefLastFullBackupTime){ + Intent sendIntent = new Intent(Intent.ACTION_SEND); + Uri uri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", fullBackupFile); + sendIntent.putExtra(Intent.EXTRA_STREAM, uri); + sendIntent.setClipData(ClipData.newRawUri("", uri)); + sendIntent.setType("*/zip"); + sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + context.startActivity(Intent.createChooser(sendIntent, "Share full backup ...")); + prefLastFullBackupTime.setValue(System.currentTimeMillis()); } } diff --git a/mgmap/src/main/java/mg/mgmap/generic/util/basic/IOUtil.java b/mgmap/src/main/java/mg/mgmap/generic/util/basic/IOUtil.java index 52ae741d..74784c6d 100644 --- a/mgmap/src/main/java/mg/mgmap/generic/util/basic/IOUtil.java +++ b/mgmap/src/main/java/mg/mgmap/generic/util/basic/IOUtil.java @@ -1,11 +1,17 @@ package mg.mgmap.generic.util.basic; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.invoke.MethodHandles; public class IOUtil { + private static final MGLog mgLog = new MGLog(MethodHandles.lookup().lookupClass().getName()); + public static void copyStreams(InputStream is, OutputStream os) throws IOException { try (is; os){ byte[] buffer = new byte[8 * 1024]; @@ -17,4 +23,12 @@ public static void copyStreams(InputStream is, OutputStream os) throws IOExcepti } } } + + public static void copyFile(File a, File b){ + try { + copyStreams(new FileInputStream(a), new FileOutputStream(b)); + } catch (IOException e) { + mgLog.e(e); + } + } } diff --git a/mgmap/src/main/res/values/strings.xml b/mgmap/src/main/res/values/strings.xml index 2231d93b..03a7a070 100644 --- a/mgmap/src/main/res/values/strings.xml +++ b/mgmap/src/main/res/values/strings.xml @@ -136,6 +136,7 @@ mg.mgmap.args Sftp.uploadGpxTrigger + Pref.prefLastFullBackupTime prefTracksVisible prefMapsforgeNumRenderThreads