diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/LegacyBackupImport.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/LegacyBackupImport.kt
index 05e1fed..dfff751 100644
--- a/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/LegacyBackupImport.kt
+++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/backup/legacy/LegacyBackupImport.kt
@@ -192,7 +192,6 @@ object LegacyBackupImport : LegacyBackupBase() {
it[status] = fetchedManga.status
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url!!.isNotEmpty())
it[MangaTable.thumbnail_url] = fetchedManga.thumbnail_url
-
}
}
diff --git a/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt b/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt
index 7f034de..899c38a 100644
--- a/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt
+++ b/server/src/main/kotlin/ir/armor/tachidesk/server/JavalinSetup.kt
@@ -40,6 +40,8 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.future.future
import mu.KotlinLogging
import java.io.IOException
+import java.text.SimpleDateFormat
+import java.util.Date
import java.util.concurrent.CompletableFuture
/*
@@ -326,7 +328,7 @@ object JavalinSetup {
ctx.json(getCategoryMangaList(categoryId))
}
- // expects a Tachiyomi legacy backup json to be uploaded
+ // expects a Tachiyomi legacy backup json in the body
app.post("/api/v1/backup/legacy/import") { ctx ->
ctx.result(
future {
@@ -335,7 +337,16 @@ object JavalinSetup {
)
}
- // returns a Tachiyomi legacy backup json created from the current database
+ // expects a Tachiyomi legacy backup json as a file upload, the file must be named "backup.json"
+ app.post("/api/v1/backup/legacy/import/file") { ctx ->
+ ctx.result(
+ future {
+ restoreLegacyBackup(ctx.uploadedFile("backup.json")!!.content)
+ }
+ )
+ }
+
+ // returns a Tachiyomi legacy backup json created from the current database as a json body
app.get("/api/v1/backup/legacy/export") { ctx ->
ctx.contentType("application/json")
ctx.result(
@@ -352,5 +363,27 @@ object JavalinSetup {
}
)
}
+
+ // returns a Tachiyomi legacy backup json created from the current database as a file
+ app.get("/api/v1/backup/legacy/export/file") { ctx ->
+ ctx.contentType("application/json")
+ val sdf = SimpleDateFormat("yyyy-MM-dd_HH-mm")
+ val currentDate = sdf.format(Date())
+
+ ctx.header("Content-Disposition", "attachment; filename=\"tachidesk_$currentDate.json\"")
+ ctx.result(
+ future {
+ createLegacyBackup(
+ BackupFlags(
+ includeManga = true,
+ includeCategories = true,
+ includeChapters = true,
+ includeTracking = true,
+ includeHistory = true,
+ )
+ )
+ }
+ )
+ }
}
}
diff --git a/webUI/react/package.json b/webUI/react/package.json
index c5e198a..58ae588 100644
--- a/webUI/react/package.json
+++ b/webUI/react/package.json
@@ -10,6 +10,7 @@
"@testing-library/user-event": "^12.1.10",
"@types/react-lazyload": "^3.1.0",
"axios": "^0.21.1",
+ "file-selector": "^0.2.4",
"fontsource-roboto": "^4.0.0",
"react": "^17.0.1",
"react-beautiful-dnd": "^13.0.0",
diff --git a/webUI/react/src/App.tsx b/webUI/react/src/App.tsx
index 3cbaa05..0855514 100644
--- a/webUI/react/src/App.tsx
+++ b/webUI/react/src/App.tsx
@@ -25,6 +25,7 @@ import DarkTheme from './context/DarkTheme';
import Library from './screens/Library';
import Settings from './screens/Settings';
import Categories from './screens/settings/Categories';
+import Backup from './screens/settings/Backup';
import useLocalStorage from './util/useLocalStorage';
export default function App() {
@@ -103,6 +104,9 @@ export default function App() {
+
+
+
diff --git a/webUI/react/src/screens/Settings.tsx b/webUI/react/src/screens/Settings.tsx
index d1b6d67..0d02bd6 100644
--- a/webUI/react/src/screens/Settings.tsx
+++ b/webUI/react/src/screens/Settings.tsx
@@ -16,15 +16,11 @@ import {
DialogContentText, IconButton, ListItemSecondaryAction, Switch, TextField,
ListItemIcon, ListItemText,
} from '@material-ui/core';
-import ListItem, { ListItemProps } from '@material-ui/core/ListItem';
+import ListItem from '@material-ui/core/ListItem';
import NavbarContext from '../context/NavbarContext';
import DarkTheme from '../context/DarkTheme';
import useLocalStorage from '../util/useLocalStorage';
-
-function ListItemLink(props: ListItemProps<'a', { button?: true }>) {
- // eslint-disable-next-line react/jsx-props-no-spreading
- return ;
-}
+import ListItemLink from '../util/ListItemLink';
export default function Settings() {
const { setTitle, setAction } = useContext(NavbarContext);
@@ -58,6 +54,12 @@ export default function Settings() {
+
+
+
+
+
+
diff --git a/webUI/react/src/screens/settings/Backup.tsx b/webUI/react/src/screens/settings/Backup.tsx
new file mode 100644
index 0000000..ade387f
--- /dev/null
+++ b/webUI/react/src/screens/settings/Backup.tsx
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) Contributors to the Suwayomi project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+import React, { useContext, useEffect } from 'react';
+import { ListItemIcon } from '@material-ui/core';
+import List from '@material-ui/core/List';
+import InboxIcon from '@material-ui/icons/Inbox';
+import ListItem from '@material-ui/core/ListItem';
+import ListItemText from '@material-ui/core/ListItemText';
+import { fromEvent } from 'file-selector';
+import ListItemLink from '../../util/ListItemLink';
+import NavbarContext from '../../context/NavbarContext';
+import client from '../../util/client';
+
+export default function Backup() {
+ const { setTitle, setAction } = useContext(NavbarContext);
+ useEffect(() => { setTitle('Backup'); setAction(<>>); }, []);
+
+ const { baseURL } = client.defaults;
+
+ const submitBackup = (file: File) => {
+ file.text()
+ .then(
+ (fileContent: string) => {
+ client.post('/api/v1/backup/legacy/import',
+ fileContent, { headers: { 'Content-Type': 'application/json' } });
+ },
+ );
+ };
+
+ const dropHandler = async (e: Event) => {
+ e.preventDefault();
+ const files = await fromEvent(e);
+
+ submitBackup(files[0] as File);
+ };
+
+ const dragOverHandler = (e: Event) => {
+ e.preventDefault();
+ };
+
+ useEffect(() => {
+ document.addEventListener('drop', dropHandler);
+ document.addEventListener('dragover', dragOverHandler);
+
+ const input = document.getElementById('backup-file');
+ input?.addEventListener('change', async (evt) => {
+ const files = await fromEvent(evt);
+ submitBackup(files[0] as File);
+ });
+
+ return () => {
+ document.removeEventListener('drop', dropHandler);
+ document.removeEventListener('dragover', dragOverHandler);
+ };
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+ document.getElementById('backup-file')?.click()}>
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/webUI/react/src/util/ListItemLink.tsx b/webUI/react/src/util/ListItemLink.tsx
new file mode 100644
index 0000000..fc1b6b9
--- /dev/null
+++ b/webUI/react/src/util/ListItemLink.tsx
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) Contributors to the Suwayomi project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+import React from 'react';
+import ListItem, { ListItemProps } from '@material-ui/core/ListItem';
+
+export default function ListItemLink(props: ListItemProps<'a', { button?: true }>) {
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ return ;
+}
diff --git a/webUI/react/yarn.lock b/webUI/react/yarn.lock
index c0e01d0..941059c 100644
--- a/webUI/react/yarn.lock
+++ b/webUI/react/yarn.lock
@@ -5136,6 +5136,13 @@ file-loader@6.1.1:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
+file-selector@^0.2.4:
+ version "0.2.4"
+ resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.2.4.tgz#7b98286f9dbb9925f420130ea5ed0a69238d4d80"
+ integrity sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==
+ dependencies:
+ tslib "^2.0.3"
+
file-uri-to-path@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"