enrecipes/app/components/settings/Database.vue

457 lines
13 KiB
Vue
Raw Normal View History

2021-03-21 17:02:04 +00:00
<template>
2021-04-01 10:55:35 +00:00
<Page @loaded="onPageLoad" actionBarHidden="true">
<GridLayout rows="*, auto" columns="auto, *">
2021-05-25 14:32:53 +00:00
<OptionsList title="db" :items="items" />
2021-04-07 17:18:38 +00:00
<GridLayout
2021-04-12 18:09:48 +00:00
v-show="!toast && !progress"
2021-04-07 17:18:38 +00:00
row="1"
class="appbar"
rows="*"
columns="auto, *"
>
<Button class="ico" :text="icon.back" @tap="$navigateBack()" />
</GridLayout>
2021-05-25 14:32:53 +00:00
<Toast :toast="toast" :action="hideToast" />
2021-04-07 17:18:38 +00:00
<GridLayout
2021-04-12 18:09:48 +00:00
v-show="progress"
2021-04-07 17:18:38 +00:00
row="1"
colSpan="2"
class="appbar snackBar"
2021-04-12 18:09:48 +00:00
columns="auto, *"
2021-04-07 17:18:38 +00:00
>
2021-04-12 18:09:48 +00:00
<ActivityIndicator :busy="progress ? true : false" />
<Label col="1" class="title" :text="progress" textWrap="true" />
2021-03-21 17:02:04 +00:00
</GridLayout>
2021-04-01 10:55:35 +00:00
</GridLayout>
2021-03-21 17:02:04 +00:00
</Page>
</template>
<script>
import {
ApplicationSettings,
path,
knownFolders,
2021-05-22 08:56:31 +00:00
AndroidApplication,
2021-03-21 17:02:04 +00:00
File,
Folder,
Observable,
2021-04-07 17:18:38 +00:00
Application,
2021-05-22 08:56:31 +00:00
getFileAccess,
2021-03-21 17:02:04 +00:00
} from "@nativescript/core";
import { localize } from "@nativescript/localize";
2021-05-25 14:32:53 +00:00
import Confirm from "../modals/Confirm";
import OptionsList from "../sub/OptionsList";
import Toast from "../sub/Toast";
2021-03-21 17:02:04 +00:00
import { mapState, mapActions } from "vuex";
2021-05-22 08:56:31 +00:00
import { openOrCreate } from "@akylas/nativescript-sqlite";
2021-03-21 17:02:04 +00:00
import * as utils from "~/shared/utils";
export default {
2021-05-25 14:32:53 +00:00
components: { OptionsList, Toast },
2021-03-21 17:02:04 +00:00
data() {
return {
2021-04-07 17:18:38 +00:00
backupFolder: null,
2021-04-12 18:09:48 +00:00
progress: null,
2021-04-07 17:18:38 +00:00
toast: null,
2021-03-21 17:02:04 +00:00
};
},
computed: {
...mapState([
"icon",
"recipes",
"cuisines",
"categories",
"yieldUnits",
"units",
"mealPlans",
"importSummary",
]),
2021-04-01 10:55:35 +00:00
items() {
return [
{},
2021-04-07 17:18:38 +00:00
{
2021-05-25 14:32:53 +00:00
type: "list",
2021-04-07 17:18:38 +00:00
icon: "folder",
2021-04-15 19:01:35 +00:00
title: "buFol",
2021-04-07 17:18:38 +00:00
subTitle: this.backupFolder,
action: this.setBackupFolder,
},
2021-04-01 10:55:35 +00:00
{
2021-05-25 14:32:53 +00:00
type: "list",
2021-04-01 10:55:35 +00:00
icon: "exp",
title: "expBu",
2021-04-07 17:18:38 +00:00
subTitle: localize("buInfo"),
2021-04-01 10:55:35 +00:00
action: this.exportCheck,
},
{
2021-05-25 14:32:53 +00:00
type: "list",
2021-04-01 10:55:35 +00:00
icon: "imp",
title: "impBu",
2021-04-07 17:18:38 +00:00
subTitle: localize("impInfo"),
action: this.openZipFile,
2021-04-01 10:55:35 +00:00
},
{},
];
},
2021-03-21 17:02:04 +00:00
},
methods: {
...mapActions([
2021-05-22 08:56:31 +00:00
"importListItems",
"importRecipesFromJSON",
"importRecipesFromDB",
"importMealPlansFromJSON",
"importMealPlansFromDB",
2021-03-21 17:02:04 +00:00
"unlinkBrokenImages",
2021-04-12 18:09:48 +00:00
"clearImportSummary",
2021-03-21 17:02:04 +00:00
]),
2021-05-25 14:32:53 +00:00
onPageLoad({ object }) {
object.bindingContext = new Observable();
2021-04-12 18:09:48 +00:00
const ContentResolver = Application.android.nativeApp.getContentResolver();
this.backupFolder = ApplicationSettings.getString("backupFolder");
if (
!this.backupFolder ||
ContentResolver.getPersistedUriPermissions().isEmpty()
) {
this.backupFolder = null;
}
2021-04-07 17:18:38 +00:00
},
// BACKUP FOLDER PICKER
2021-04-12 18:09:48 +00:00
setBackupFolder(startExport) {
const ContentResolver = Application.android.nativeApp.getContentResolver();
const FLAGS =
android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION |
android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
utils.getBackupFolder().then((uri) => {
if (uri != null) {
if (this.backupFolder)
ContentResolver.releasePersistableUriPermission(
new android.net.Uri.parse(this.backupFolder),
FLAGS
);
this.backupFolder = uri.toString();
2021-04-07 17:18:38 +00:00
ApplicationSettings.setString("backupFolder", this.backupFolder);
2021-04-12 18:09:48 +00:00
// PERSIST PERMISSIONS
ContentResolver.takePersistableUriPermission(uri, FLAGS);
if (startExport && this.backupFolder) {
this.exportBackup();
}
2021-04-07 17:18:38 +00:00
}
});
2021-03-21 17:02:04 +00:00
},
2021-04-12 18:09:48 +00:00
2021-03-21 17:02:04 +00:00
// EXPORT HANDLERS
exportCheck() {
2021-04-12 18:09:48 +00:00
const ContentResolver = Application.android.nativeApp.getContentResolver();
2021-03-21 17:02:04 +00:00
if (!this.recipes.length) {
2021-04-07 17:18:38 +00:00
this.toast = localize("aFBu");
utils.timer(5, (val) => {
if (!val) this.toast = val;
});
2021-03-21 17:02:04 +00:00
} else {
2021-04-12 18:09:48 +00:00
if (
!this.backupFolder ||
ContentResolver.getPersistedUriPermissions().isEmpty()
) {
this.setBackupFolder(true);
} else this.exportBackup();
2021-03-21 17:02:04 +00:00
}
},
exportBackup() {
2021-04-12 18:09:48 +00:00
this.progress = localize("expip");
2021-05-22 08:56:31 +00:00
this.hijackBackEvent();
2021-03-21 17:02:04 +00:00
let date = new Date();
let formattedDate =
date.getFullYear() +
"-" +
("0" + (date.getMonth() + 1)).slice(-2) +
"-" +
("0" + date.getDate()).slice(-2) +
"_" +
("0" + date.getHours()).slice(-2) +
("0" + date.getMinutes()).slice(-2) +
("0" + date.getSeconds()).slice(-2);
2021-04-07 17:18:38 +00:00
2021-05-22 08:56:31 +00:00
// Copy db file to EnRecipes folder
utils.copyDBToExport();
2021-04-18 13:28:07 +00:00
let filename = `${localize("EnRecipes")}_${formattedDate}.zip`;
2021-03-21 17:02:04 +00:00
let fromPath = path.join(knownFolders.documents().path, "EnRecipes");
2021-04-12 18:09:48 +00:00
utils.Zip.zip(fromPath, this.backupFolder, filename)
2021-04-18 18:34:17 +00:00
.then((res) => res && this.showExportSummary(filename))
2021-04-18 13:28:07 +00:00
.catch((err) => {
console.log("Backup error: ", err);
this.progress = null;
2021-05-22 08:56:31 +00:00
this.releaseBackEvent();
2021-04-18 13:28:07 +00:00
this.setBackupFolder(true);
});
2021-03-21 17:02:04 +00:00
},
2021-04-12 18:09:48 +00:00
showExportSummary(filename) {
2021-05-22 08:56:31 +00:00
// delete copied db file
getFileAccess().deleteFile(
path.join(
knownFolders.documents().getFolder("EnRecipes").path,
"EnRecipes.db"
)
);
2021-04-12 18:09:48 +00:00
this.progress = null;
2021-05-22 08:56:31 +00:00
this.releaseBackEvent();
2021-04-18 14:42:14 +00:00
let description = localize("buto", `"${filename}"`);
2021-05-25 14:32:53 +00:00
this.$showModal(Confirm, {
2021-04-12 18:09:48 +00:00
props: {
title: "expSuc",
2021-04-18 13:28:07 +00:00
description,
2021-04-12 18:09:48 +00:00
okButtonText: "OK",
},
});
2021-03-21 17:02:04 +00:00
},
2021-04-07 17:18:38 +00:00
2021-04-12 18:09:48 +00:00
// IMPORT HANDLERS
openZipFile() {
2021-04-07 17:18:38 +00:00
utils.getBackupFile().then((uri) => {
2021-04-14 09:27:40 +00:00
if (uri) {
2021-05-25 14:32:53 +00:00
knownFolders.temp().clear();
2021-04-18 13:28:07 +00:00
let dest = knownFolders.temp().path;
2021-04-14 09:27:40 +00:00
utils.Zip.unzip(uri, dest)
2021-04-18 18:34:17 +00:00
.then((res) => res && this.validateZipContent(res, uri))
2021-04-14 09:27:40 +00:00
.catch(() => this.failedImport(localize("buInc")));
}
2021-04-07 17:18:38 +00:00
});
2021-03-21 17:02:04 +00:00
},
2021-05-22 08:56:31 +00:00
validateZipContent(dest, uri) {
2021-04-12 18:09:48 +00:00
this.progress = localize("impip");
2021-05-22 08:56:31 +00:00
this.hijackBackEvent();
let cache = dest + "/EnRecipes";
const recipesDB = cache + "/EnRecipes.db";
const recipes = cache + "/recipes.json";
const images = cache + "/Images";
const userCuisines = cache + "/userCuisines.json";
const userCategories = cache + "/userCategories.json";
const userYieldUnits = cache + "/userYieldUnits.json";
const userUnits = cache + "/userUnits.json";
const mealPlans = cache + "/mealPlans.json";
let vm = this;
// IMPORT IMAGES FINALLY
function importImages() {
2021-04-12 18:09:48 +00:00
const timer = setInterval(() => {
2021-05-22 08:56:31 +00:00
if (!vm.progress) clearInterval(timer);
if (vm.progress && vm.importSummary.found) {
Folder.exists(images)
? vm.importImages(uri)
: vm.showImportSummary();
2021-04-12 18:09:48 +00:00
}
}, 100);
2021-05-22 08:56:31 +00:00
}
if (Folder.exists(cache)) {
if (File.exists(recipesDB)) {
// IMPORT FROM DB FILE
this.extractData(recipesDB);
importImages();
} else if (File.exists(recipes)) {
// IMPORT FROM JSON FILES
this.isFileDataValid([
{
path: recipes,
db: "recipes",
file: "recipes.json",
},
{
path: userCuisines,
db: "userCuisines",
file: "userCuisines.json",
},
{
path: userCategories,
db: "userCategories",
file: "userCategories.json",
},
{
path: userYieldUnits,
db: "userYieldUnits",
file: "userYieldUnits.json",
},
{
path: userUnits,
db: "userUnits",
file: "userUnits.json",
},
{
path: mealPlans,
db: "mealPlans",
file: "mealPlans.json",
},
]);
importImages();
} else this.failedImport(localize("buEmp"));
2021-04-18 18:34:17 +00:00
} else this.failedImport(localize("buInc"));
2021-03-21 17:02:04 +00:00
},
isFileDataValid(file) {
const files = file.filter((e) => File.exists(e.path));
if (files.length) {
2021-05-22 08:56:31 +00:00
let isValid = files.map(() => false);
2021-03-21 17:02:04 +00:00
files.forEach((file, i) => {
File.fromPath(file.path)
.readText()
.then((data) => {
isValid[i] = this.hasValidJSON(data);
if (!isValid[i]) {
this.failedImport(
`${localize("buMod")}\n\n${localize("invFile")}: ${file.file}`
);
return 0;
}
if (isValid.every((e) => e === true)) {
2021-04-12 18:09:48 +00:00
files.forEach((file) => {
2021-03-21 17:02:04 +00:00
File.fromPath(file.path)
.readText()
.then((data) => {
2021-05-22 08:56:31 +00:00
this.importData(JSON.parse(data), file.db);
2021-03-21 17:02:04 +00:00
});
});
}
});
});
2021-04-18 18:34:17 +00:00
} else this.failedImport(localize("buEmp"));
2021-03-21 17:02:04 +00:00
},
failedImport(description) {
2021-04-18 18:34:17 +00:00
this.progress = null;
2021-05-22 08:56:31 +00:00
this.releaseBackEvent();
2021-04-18 18:34:17 +00:00
knownFolders.temp().clear();
2021-05-25 14:32:53 +00:00
this.$showModal(Confirm, {
2021-03-21 17:02:04 +00:00
props: {
title: "impFail",
description,
okButtonText: "OK",
},
});
},
2021-04-12 18:09:48 +00:00
hasValidJSON(data) {
try {
JSON.parse(data) && Array.isArray(JSON.parse(data));
} catch (e) {
return false;
}
return true;
},
2021-05-22 08:56:31 +00:00
extractData(recipesDB) {
const db = openOrCreate(recipesDB);
// Import recipes
2021-05-25 14:32:53 +00:00
db.select("SELECT * FROM recipes").then((res) => {
2021-05-22 08:56:31 +00:00
this.importRecipesFromDB(res);
});
// Import listitems
db.select(
`SELECT cuisines, categories, yieldUnits, units FROM lists`
).then((res) =>
Object.keys(res[0]).forEach((listName) =>
this.importListItems({
data: JSON.parse(res[0][listName]),
listName,
})
)
);
// Import mealPlans
db.select(`SELECT * FROM mealPlans`).then((res) =>
this.importMealPlansFromDB(res)
);
},
importData(data, db) {
2021-04-12 18:09:48 +00:00
switch (db) {
2021-05-22 08:56:31 +00:00
case "recipes":
this.importRecipesFromJSON(data);
2021-04-12 18:09:48 +00:00
break;
2021-05-22 08:56:31 +00:00
case "userCuisines":
this.importListItems({
2021-04-12 18:09:48 +00:00
data,
listName: "cuisines",
});
break;
2021-05-22 08:56:31 +00:00
case "userCategories":
this.importListItems({
2021-04-12 18:09:48 +00:00
data,
listName: "categories",
});
break;
2021-05-22 08:56:31 +00:00
case "userYieldUnits":
this.importListItems({
2021-04-12 18:09:48 +00:00
data,
listName: "yieldUnits",
});
break;
2021-05-22 08:56:31 +00:00
case "userUnits":
this.importListItems({
2021-04-12 18:09:48 +00:00
data,
listName: "units",
});
break;
2021-05-22 08:56:31 +00:00
case "mealPlans":
this.importMealPlansFromJSON(data);
2021-04-12 18:09:48 +00:00
break;
}
},
2021-04-18 13:28:07 +00:00
importImages(uri) {
2021-04-12 18:09:48 +00:00
let destPath = knownFolders.documents().path;
Folder.fromPath(destPath);
utils.Zip.unzip(uri, destPath).then((res) => {
if (res) {
2021-05-25 14:32:53 +00:00
// delete unzipped data files
2021-05-22 08:56:31 +00:00
Folder.fromPath(path.join(destPath, "EnRecipes"))
.getEntities()
.then((entities) => {
entities.forEach((entity) => {
if (/.json|.db/.test(entity._extension))
File.fromPath(entity._path).remove();
});
});
2021-04-12 18:09:48 +00:00
this.showImportSummary();
this.unlinkBrokenImages();
2021-03-21 17:02:04 +00:00
}
});
},
showImportSummary() {
2021-04-18 18:34:17 +00:00
this.progress = null;
2021-05-22 08:56:31 +00:00
this.releaseBackEvent();
2021-03-21 17:02:04 +00:00
let { found, imported, updated } = this.importSummary;
let exists = Math.abs(found - imported - updated) + updated;
2021-04-15 19:01:35 +00:00
let importedNote = `\n${localize("recI")} ${imported}`;
let existsNote = `\n${localize("recE")} ${exists}`;
let updatedNote = `\n${localize("recU")} ${updated}`;
2021-05-25 14:32:53 +00:00
this.$showModal(Confirm, {
2021-03-21 17:02:04 +00:00
props: {
title: "impSuc",
description: `${found} ${localize(
"recF"
2021-04-15 19:01:35 +00:00
)}\n${importedNote}${existsNote}${updatedNote}`,
2021-03-21 17:02:04 +00:00
okButtonText: "OK",
},
2021-04-12 18:09:48 +00:00
}).then(() => this.clearImportSummary());
2021-03-21 17:02:04 +00:00
},
2021-04-12 18:09:48 +00:00
2021-05-22 08:56:31 +00:00
// NAVIGATION HANDLERS
hijackBackEvent() {
AndroidApplication.on(
AndroidApplication.activityBackPressedEvent,
this.backEvent
);
},
releaseBackEvent() {
AndroidApplication.off(
AndroidApplication.activityBackPressedEvent,
this.backEvent
);
},
backEvent(args) {
args.cancel = true;
},
2021-04-01 10:55:35 +00:00
// HELPERS
2021-05-25 14:32:53 +00:00
hideToast() {
this.toast = null;
2021-04-01 10:55:35 +00:00
},
2021-03-21 17:02:04 +00:00
},
};
</script>