From dc0b8deccf40be747fc2b34d7c836e4808146c51 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Thu, 19 Nov 2020 18:31:28 -0600 Subject: [PATCH] Export backups daily Fixes #178 --- .../isoron/uhabits/database/AutoBackupTest.kt | 81 +++++++++++++++++++ .../habits/list/ListHabitsActivity.kt | 7 ++ .../org/isoron/uhabits/database/AutoBackup.kt | 61 ++++++++++++++ .../isoron/uhabits/utils/DatabaseUtils.java | 3 + 4 files changed, 152 insertions(+) create mode 100644 android/uhabits-android/src/androidTest/java/org/isoron/uhabits/database/AutoBackupTest.kt create mode 100644 android/uhabits-android/src/main/java/org/isoron/uhabits/database/AutoBackup.kt diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/database/AutoBackupTest.kt b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/database/AutoBackupTest.kt new file mode 100644 index 000000000..cbc25be08 --- /dev/null +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/database/AutoBackupTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016-2020 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker 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. + * + * Loop Habit Tracker 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 this program. If not, see . + */ + +package org.isoron.uhabits.database + +import org.isoron.androidbase.* +import org.isoron.uhabits.* +import org.isoron.uhabits.core.utils.* +import org.junit.* +import java.io.* + +class AutoBackupTest : BaseAndroidTest() { + @Test + fun testRun() { + DateUtils.setFixedLocalTime(40 * DateUtils.DAY_LENGTH) + val basedir = AndroidDirFinder(targetContext).getFilesDir("Backups")!! + createTestFiles(basedir, 30) + + val autoBackup = AutoBackup(targetContext) + autoBackup.run(keep=5) + + for (k in 1..25) assertDoesNotExist("${basedir.path}/test-$k.txt") + for (k in 26..30) assertExists("${basedir.path}/test-$k.txt") + assertExists("${basedir.path}/Loop Habits Backup 1970-02-10 000000.db") + } + + @Test + fun testRunWithEmptyDir() { + val basedir = AndroidDirFinder(targetContext).getFilesDir("Backups")!! + removeAllFiles(basedir) + basedir.delete() + + // Should not crash + val autoBackup = AutoBackup(targetContext) + autoBackup.run() + } + + private fun assertExists(path: String) { + assertTrue("File $path should exist", File(path).exists()) + } + + private fun assertDoesNotExist(path: String) { + assertFalse("File $path should not exist", File(path).exists()) + } + + private fun createTestFiles(basedir: File, nfiles: Int) { + removeAllFiles(basedir) + for (k in 1..nfiles) { + touch("${basedir.path}/test-$k.txt", DateUtils.DAY_LENGTH * k) + } + } + + private fun touch(path: String, time: Long) { + val file = File(path) + FileOutputStream(file).close() + file.setLastModified(time) + } + + private fun removeAllFiles(dir: File) { + dir.list().forEach { path -> + val file = File("${dir.path}/$path") + assertTrue(file.delete()) + } + } +} \ No newline at end of file diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt index 1b8a92822..227aeb674 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.kt @@ -24,12 +24,15 @@ import org.isoron.uhabits.* import org.isoron.uhabits.activities.* import org.isoron.uhabits.activities.habits.list.views.* import org.isoron.uhabits.core.preferences.* +import org.isoron.uhabits.core.tasks.* import org.isoron.uhabits.core.ui.ThemeSwitcher.* import org.isoron.uhabits.core.utils.* +import org.isoron.uhabits.database.* class ListHabitsActivity : HabitsActivity() { var pureBlack: Boolean = false + lateinit var taskRunner: TaskRunner lateinit var adapter: HabitCardListAdapter lateinit var rootView: ListHabitsRootView lateinit var screen: ListHabitsScreen @@ -44,6 +47,7 @@ class ListHabitsActivity : HabitsActivity() { rootView = component.listHabitsRootView screen = component.listHabitsScreen adapter = component.habitCardListAdapter + taskRunner = appComponent.taskRunner setScreen(screen) component.listHabitsBehavior.onStartup() @@ -62,6 +66,9 @@ class ListHabitsActivity : HabitsActivity() { screen.onAttached() rootView.postInvalidate() midnightTimer.onResume() + taskRunner.run { + AutoBackup(this@ListHabitsActivity).run() + } if (prefs.theme == THEME_DARK && prefs.isPureBlackEnabled != pureBlack) { restartWithFade(ListHabitsActivity::class.java) diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/database/AutoBackup.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/database/AutoBackup.kt new file mode 100644 index 000000000..853965775 --- /dev/null +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/database/AutoBackup.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016-2020 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker 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. + * + * Loop Habit Tracker 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 this program. If not, see . + */ + +package org.isoron.uhabits.database + +import android.content.* +import android.util.* +import org.isoron.androidbase.* +import org.isoron.uhabits.core.utils.* +import org.isoron.uhabits.utils.* +import java.io.* + +class AutoBackup(private val context: Context) { + + private val basedir = AndroidDirFinder(context).getFilesDir("Backups")!! + + fun run(keep: Int = 5) { + val files = listBackupFiles() + var newestTimestamp = 0L; + if (files.isNotEmpty()) { + newestTimestamp = files.last().lastModified() + } + val todayTimestamp = DateUtils.getStartOfToday() + removeOldest(files, keep) + if (todayTimestamp - newestTimestamp > DateUtils.DAY_LENGTH) { + DatabaseUtils.saveDatabaseCopy(context, basedir) + } + } + + private fun removeOldest(files: ArrayList, keep: Int) { + files.sortBy { -it.lastModified() } + for (k in keep until files.size) { + Log.i("AutoBackup", "Removing ${files[k]}") + files[k].delete() + } + } + + private fun listBackupFiles(): ArrayList { + val files = ArrayList() + for (path in basedir.list()!!) { + files.add(File("${basedir.path}/$path")) + } + return files + } +} \ No newline at end of file diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java index 1108ad3d2..3ae414822 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java @@ -21,6 +21,7 @@ package org.isoron.uhabits.utils; import android.content.*; import android.database.sqlite.*; +import android.util.*; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -28,6 +29,7 @@ import androidx.annotation.Nullable; import org.isoron.androidbase.utils.*; import org.isoron.uhabits.*; import org.isoron.uhabits.core.*; +import org.isoron.uhabits.core.Config; import org.isoron.uhabits.core.utils.*; import java.io.*; @@ -97,6 +99,7 @@ public abstract class DatabaseUtils String date = dateFormat.format(DateUtils.getLocalTime()); String format = "%s/Loop Habits Backup %s.db"; String filename = String.format(format, dir.getAbsolutePath(), date); + Log.i("DatabaseUtils", "Writing: " + filename); File db = getDatabaseFile(context); File dbCopy = new File(filename);