enrecipes/app/components/Settings/Database.vue
2021-04-19 00:04:17 +05:30

455 lines
14 KiB
Vue

<template>
<Page @loaded="onPageLoad" actionBarHidden="true">
<GridLayout rows="*, auto" columns="auto, *">
<ListView
colSpan="2"
rowSpan="2"
class="options-list"
for="item in items"
>
<v-template if="$index == 0">
<Label class="pageTitle" :text="'db' | L" />
</v-template>
<v-template if="$index == 4">
<StackLayout class="listSpace"> </StackLayout>
</v-template>
<v-template>
<GridLayout
columns="auto, *"
class="option"
@touch="touch($event, item.action)"
>
<Label class="ico" :text="icon[item.icon]" />
<StackLayout col="1" verticalAlignment="center">
<Label :text="item.title | L" class="info" />
<Label v-if="item.subTitle" :text="item.subTitle" class="sub" />
</StackLayout>
</GridLayout>
</v-template>
</ListView>
<GridLayout
v-show="!toast && !progress"
row="1"
class="appbar"
rows="*"
columns="auto, *"
>
<Button class="ico" :text="icon.back" @tap="$navigateBack()" />
</GridLayout>
<GridLayout
v-show="toast"
row="1"
colSpan="2"
class="appbar snackBar"
columns="*"
@tap="toast = null"
>
<FlexboxLayout minHeight="48" alignItems="center">
<Label class="title msg" :text="toast" />
</FlexboxLayout>
</GridLayout>
<GridLayout
v-show="progress"
row="1"
colSpan="2"
class="appbar snackBar"
columns="auto, *"
>
<ActivityIndicator :busy="progress ? true : false" />
<Label col="1" class="title" :text="progress" textWrap="true" />
</GridLayout>
</GridLayout>
</Page>
</template>
<script>
import {
ApplicationSettings,
path,
knownFolders,
File,
Folder,
Observable,
Application,
} from "@nativescript/core";
import { localize } from "@nativescript/localize";
import ConfirmDialog from "../modal/ConfirmDialog.vue";
import { mapState, mapActions } from "vuex";
import * as utils from "~/shared/utils";
export default {
data() {
return {
backupFolder: null,
progress: null,
toast: null,
};
},
computed: {
...mapState([
"icon",
"recipes",
"cuisines",
"categories",
"yieldUnits",
"units",
"mealPlans",
"importSummary",
]),
items() {
return [
{},
{
icon: "folder",
title: "buFol",
subTitle: this.backupFolder,
action: this.setBackupFolder,
},
{
icon: "exp",
title: "expBu",
subTitle: localize("buInfo"),
action: this.exportCheck,
},
{
icon: "imp",
title: "impBu",
subTitle: localize("impInfo"),
action: this.openZipFile,
},
{},
];
},
},
methods: {
...mapActions([
"importListItemsAction",
"importRecipesAction",
"importMealPlansAction",
"unlinkBrokenImages",
"clearImportSummary",
]),
onPageLoad(args) {
const page = args.object;
page.bindingContext = new Observable();
const ContentResolver = Application.android.nativeApp.getContentResolver();
this.backupFolder = ApplicationSettings.getString("backupFolder");
if (
!this.backupFolder ||
ContentResolver.getPersistedUriPermissions().isEmpty()
) {
this.backupFolder = null;
}
},
// BACKUP FOLDER PICKER
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();
ApplicationSettings.setString("backupFolder", this.backupFolder);
// PERSIST PERMISSIONS
ContentResolver.takePersistableUriPermission(uri, FLAGS);
if (startExport && this.backupFolder) {
this.exportBackup();
}
}
});
},
// EXPORT HANDLERS
exportCheck() {
const ContentResolver = Application.android.nativeApp.getContentResolver();
if (!this.recipes.length) {
this.toast = localize("aFBu");
utils.timer(5, (val) => {
if (!val) this.toast = val;
});
} else {
if (
!this.backupFolder ||
ContentResolver.getPersistedUriPermissions().isEmpty()
) {
this.setBackupFolder(true);
} else this.exportBackup();
}
},
exportBackup() {
this.progress = localize("expip");
this.exportFiles("create");
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);
let filename = `${localize("EnRecipes")}_${formattedDate}.zip`;
let fromPath = path.join(knownFolders.documents().path, "EnRecipes");
utils.Zip.zip(fromPath, this.backupFolder, filename)
.then((res) => res && this.showExportSummary(filename))
.catch((err) => {
console.log("Backup error: ", err);
this.progress = null;
this.setBackupFolder(true);
});
},
exportFiles(option) {
const folder = path.join(knownFolders.documents().path, "EnRecipes");
const EnRecipesFile = File.fromPath(path.join(folder, "recipes.json"));
let userCuisinesFile,
userCategoriesFile,
userYieldUnitsFile,
userUnitsFile,
mealPlansFile;
if (this.cuisines.length)
userCuisinesFile = File.fromPath(
path.join(folder, "userCuisines.json")
);
if (this.categories.length)
userCategoriesFile = File.fromPath(
path.join(folder, "userCategories.json")
);
if (this.yieldUnits.length)
userYieldUnitsFile = File.fromPath(
path.join(folder, "userYieldUnits.json")
);
if (this.units.length)
userUnitsFile = File.fromPath(path.join(folder, "userUnits.json"));
if (this.mealPlans.length)
mealPlansFile = File.fromPath(path.join(folder, "mealPlans.json"));
switch (option) {
case "create":
this.writeDataToFile(EnRecipesFile, this.recipes);
this.cuisines.length &&
this.writeDataToFile(userCuisinesFile, this.cuisines);
this.categories.length &&
this.writeDataToFile(userCategoriesFile, this.categories);
this.yieldUnits.length &&
this.writeDataToFile(userYieldUnitsFile, this.yieldUnits);
this.units.length && this.writeDataToFile(userUnitsFile, this.units);
this.mealPlans.length &&
this.writeDataToFile(mealPlansFile, this.mealPlans);
break;
case "delete":
EnRecipesFile.remove();
this.cuisines.length && userCuisinesFile.remove();
this.categories.length && userCategoriesFile.remove();
this.yieldUnits.length && userYieldUnitsFile.remove();
this.units.length && userUnitsFile.remove();
this.mealPlans.length && mealPlansFile.remove();
break;
}
},
writeDataToFile(file, data) {
file.writeText(JSON.stringify(data));
},
showExportSummary(filename) {
this.progress = null;
let description = localize("buto", `"${filename}"`);
this.$showModal(ConfirmDialog, {
props: {
title: "expSuc",
description,
okButtonText: "OK",
},
});
},
// IMPORT HANDLERS
openZipFile() {
utils.getBackupFile().then((uri) => {
if (uri) {
let dest = knownFolders.temp().path;
utils.Zip.unzip(uri, dest)
.then((res) => res && this.validateZipContent(res, uri))
.catch(() => this.failedImport(localize("buInc")));
}
});
},
validateZipContent(extractedFolderPath, uri) {
this.progress = localize("impip");
let cacheFolderPath = extractedFolderPath + "/EnRecipes";
const EnRecipesFilePath = cacheFolderPath + "/recipes.json";
const ImagesFolderPath = cacheFolderPath + "/Images";
const userCuisinesFilePath = cacheFolderPath + "/userCuisines.json";
const userCategoriesFilePath = cacheFolderPath + "/userCategories.json";
const userYieldUnitsFilePath = cacheFolderPath + "/userYieldUnits.json";
const userUnitsFilePath = cacheFolderPath + "/userUnits.json";
const mealPlansFilePath = cacheFolderPath + "/mealPlans.json";
if (Folder.exists(cacheFolderPath)) {
this.isFileDataValid([
{
path: EnRecipesFilePath,
db: "EnRecipesDB",
file: "recipes.json",
},
{
path: userCuisinesFilePath,
db: "userCuisinesDB",
file: "userCuisines.json",
},
{
path: userCategoriesFilePath,
db: "userCategoriesDB",
file: "userCategories.json",
},
{
path: userYieldUnitsFilePath,
db: "userYieldUnitsDB",
file: "userYieldUnits.json",
},
{
path: userUnitsFilePath,
db: "userUnitsDB",
file: "userUnits.json",
},
{
path: mealPlansFilePath,
db: "mealPlansDB",
file: "mealPlans.json",
},
]);
const timer = setInterval(() => {
if (!this.progress) clearInterval(timer);
if (this.progress && this.importSummary.found) {
Folder.exists(ImagesFolderPath)
? this.importImages(uri)
: this.showImportSummary();
}
}, 100);
} else this.failedImport(localize("buInc"));
},
isFileDataValid(file) {
const files = file.filter((e) => File.exists(e.path));
if (files.length) {
let isValid = files.map((e) => false);
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)) {
files.forEach((file) => {
File.fromPath(file.path)
.readText()
.then((data) => {
this.importDataToDB(JSON.parse(data), file.db);
});
});
}
});
});
} else this.failedImport(localize("buEmp"));
},
failedImport(description) {
this.progress = null;
knownFolders.temp().clear();
this.$showModal(ConfirmDialog, {
props: {
title: "impFail",
description,
okButtonText: "OK",
},
});
},
hasValidJSON(data) {
try {
JSON.parse(data) && Array.isArray(JSON.parse(data));
} catch (e) {
return false;
}
return true;
},
importDataToDB(data, db) {
switch (db) {
case "EnRecipesDB":
this.importRecipesAction(data);
break;
case "userCuisinesDB":
this.importListItemsAction({
data,
listName: "cuisines",
});
break;
case "userCategoriesDB":
this.importListItemsAction({
data,
listName: "categories",
});
break;
case "userYieldUnitsDB":
this.importListItemsAction({
data,
listName: "yieldUnits",
});
break;
case "userUnitsDB":
this.importListItemsAction({
data,
listName: "units",
});
break;
case "mealPlansDB":
this.importMealPlansAction(data);
break;
}
},
importImages(uri) {
let destPath = knownFolders.documents().path;
Folder.fromPath(destPath);
utils.Zip.unzip(uri, destPath).then((res) => {
if (res) {
this.showImportSummary();
this.unlinkBrokenImages();
}
});
},
showImportSummary() {
this.progress = null;
knownFolders.temp().clear();
this.exportFiles("delete");
let { found, imported, updated } = this.importSummary;
let exists = Math.abs(found - imported - updated) + updated;
let importedNote = `\n${localize("recI")} ${imported}`;
let existsNote = `\n${localize("recE")} ${exists}`;
let updatedNote = `\n${localize("recU")} ${updated}`;
this.$showModal(ConfirmDialog, {
props: {
title: "impSuc",
description: `${found} ${localize(
"recF"
)}\n${importedNote}${existsNote}${updatedNote}`,
okButtonText: "OK",
},
}).then(() => this.clearImportSummary());
},
// HELPERS
touch({ object, action }, method) {
object.className = action.match(/down|move/) ? "option fade" : "option";
if (action == "up") method();
},
},
};
</script>