[feat] local backup functionality

This commit is contained in:
kwaroran
2023-12-02 23:20:06 +09:00
parent eaf07f75ef
commit 4f58a12b46
4 changed files with 263 additions and 40 deletions

View File

@@ -20,8 +20,9 @@
"@mlc-ai/web-tokenizers": "^0.1.2", "@mlc-ai/web-tokenizers": "^0.1.2",
"@smithy/protocol-http": "^3.0.10", "@smithy/protocol-http": "^3.0.10",
"@smithy/signature-v4": "^2.0.16", "@smithy/signature-v4": "^2.0.16",
"@tauri-apps/api": "1.4.0", "@tauri-apps/api": "1.5.1",
"@types/marked": "^5.0.2", "@types/marked": "^5.0.2",
"@types/streamsaver": "^2.0.4",
"@xenova/transformers": "^2.9.0", "@xenova/transformers": "^2.9.0",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
@@ -54,6 +55,7 @@
"rollup": "^3.29.4", "rollup": "^3.29.4",
"showdown": "^2.1.0", "showdown": "^2.1.0",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"streamsaver": "^2.0.6",
"three": "^0.154.0", "three": "^0.154.0",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"uuid": "^9.0.1", "uuid": "^9.0.1",
@@ -64,7 +66,7 @@
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.5.3", "@sveltejs/vite-plugin-svelte": "^2.5.3",
"@tailwindcss/typography": "^0.5.10", "@tailwindcss/typography": "^0.5.10",
"@tauri-apps/cli": "1.4.0", "@tauri-apps/cli": "1.5.7",
"@tsconfig/svelte": "^3.0.0", "@tsconfig/svelte": "^3.0.0",
"@types/blueimp-md5": "^2.18.2", "@types/blueimp-md5": "^2.18.2",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",

90
pnpm-lock.yaml generated
View File

@@ -24,11 +24,14 @@ dependencies:
specifier: ^2.0.16 specifier: ^2.0.16
version: 2.0.16 version: 2.0.16
'@tauri-apps/api': '@tauri-apps/api':
specifier: 1.4.0 specifier: 1.5.1
version: 1.4.0 version: 1.5.1
'@types/marked': '@types/marked':
specifier: ^5.0.2 specifier: ^5.0.2
version: 5.0.2 version: 5.0.2
'@types/streamsaver':
specifier: ^2.0.4
version: 2.0.4
'@xenova/transformers': '@xenova/transformers':
specifier: ^2.9.0 specifier: ^2.9.0
version: 2.9.0 version: 2.9.0
@@ -125,6 +128,9 @@ dependencies:
sortablejs: sortablejs:
specifier: ^1.15.0 specifier: ^1.15.0
version: 1.15.0 version: 1.15.0
streamsaver:
specifier: ^2.0.6
version: 2.0.6
three: three:
specifier: ^0.154.0 specifier: ^0.154.0
version: 0.154.0 version: 0.154.0
@@ -152,8 +158,8 @@ devDependencies:
specifier: ^0.5.10 specifier: ^0.5.10
version: 0.5.10(tailwindcss@3.3.5) version: 0.5.10(tailwindcss@3.3.5)
'@tauri-apps/cli': '@tauri-apps/cli':
specifier: 1.4.0 specifier: 1.5.7
version: 1.4.0 version: 1.5.7
'@tsconfig/svelte': '@tsconfig/svelte':
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.0 version: 3.0.0
@@ -954,13 +960,13 @@ packages:
tailwindcss: 3.3.5 tailwindcss: 3.3.5
dev: true dev: true
/@tauri-apps/api@1.4.0: /@tauri-apps/api@1.5.1:
resolution: {integrity: sha512-Jd6HPoTM1PZSFIzq7FB8VmMu3qSSyo/3lSwLpoapW+lQ41CL5Dow2KryLg+gyazA/58DRWI9vu/XpEeHK4uMdw==} resolution: {integrity: sha512-6unsZDOdlXTmauU3NhWhn+Cx0rODV+rvNvTdvolE5Kls5ybA6cqndQENDt1+FS0tF7ozCP66jwWoH6a5h90BrA==}
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
dev: false dev: false
/@tauri-apps/cli-darwin-arm64@1.4.0: /@tauri-apps/cli-darwin-arm64@1.5.7:
resolution: {integrity: sha512-nA/ml0SfUt6/CYLVbHmT500Y+ijqsuv5+s9EBnVXYSLVg9kbPUZJJHluEYK+xKuOj6xzyuT/+rZFMRapmJD3jQ==} resolution: {integrity: sha512-eUpOUhs2IOpKaLa6RyGupP2owDLfd0q2FR/AILzryjtBtKJJRDQQvuotf+LcbEce2Nc2AHeYJIqYAsB4sw9K+g==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
@@ -968,8 +974,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-darwin-x64@1.4.0: /@tauri-apps/cli-darwin-x64@1.5.7:
resolution: {integrity: sha512-ov/F6Zr+dg9B0PtRu65stFo2G0ow2TUlneqYYrkj+vA3n+moWDHfVty0raDjMLQbQt3rv3uayFMXGPMgble9OA==} resolution: {integrity: sha512-zfumTv1xUuR+RB1pzhRy+51tB6cm8I76g0xUBaXOfEdOJ9FqW5GW2jdnEUbpNuU65qJ1lB8LVWHKGrSWWKazew==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
@@ -977,8 +983,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-linux-arm-gnueabihf@1.4.0: /@tauri-apps/cli-linux-arm-gnueabihf@1.5.7:
resolution: {integrity: sha512-zwjbiMncycXDV7doovymyKD7sCg53ouAmfgpUqEBOTY3vgBi9TwijyPhJOqoG5vUVWhouNBC08akGmE4dja15g==} resolution: {integrity: sha512-JngWNqS06bMND9PhiPWp0e+yknJJuSozsSbo+iMzHoJNRauBZCUx+HnUcygUR66Cy6qM4eJvLXtsRG7ApxvWmg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
@@ -986,8 +992,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-linux-arm64-gnu@1.4.0: /@tauri-apps/cli-linux-arm64-gnu@1.5.7:
resolution: {integrity: sha512-5MCBcziqXC72mMXnkZU68mutXIR6zavDxopArE2gQtK841IlE06bIgtLi0kUUhlFJk2nhPRgiDgdLbrPlyt7fw==} resolution: {integrity: sha512-WyIYP9BskgBGq+kf4cLAyru8ArrxGH2eMYGBJvuNEuSaqBhbV0i1uUxvyWdazllZLAEz1WvSocUmSwLknr1+sQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@@ -995,8 +1001,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-linux-arm64-musl@1.4.0: /@tauri-apps/cli-linux-arm64-musl@1.5.7:
resolution: {integrity: sha512-7J3pRB6n6uNYgIfCeKt2Oz8J7oSaz2s8GGFRRH2HPxuTHrBNCinzVYm68UhVpJrL3bnGkU0ziVZLsW/iaOGfUg==} resolution: {integrity: sha512-OrDpihQP2MB0JY1a/wP9wsl9dDjFDpVEZOQxt4hU+UVGRCZQok7ghPBg4+Xpd1CkNkcCCuIeY8VxRvwLXpnIzg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@@ -1004,8 +1010,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-linux-x64-gnu@1.4.0: /@tauri-apps/cli-linux-x64-gnu@1.5.7:
resolution: {integrity: sha512-Zh5gfAJxOv5AVWxcwuueaQ2vIAhlg0d6nZui6nMyfIJ8dbf3aZQ5ZzP38sYow5h/fbvgL+3GSQxZRBIa3c2E1w==} resolution: {integrity: sha512-4T7FAYVk76rZi8VkuLpiKUAqaSxlva86C1fHm/RtmoTKwZEV+MI3vIMoVg+AwhyWIy9PS55C75nF7+OwbnFnvQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@@ -1013,8 +1019,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-linux-x64-musl@1.4.0: /@tauri-apps/cli-linux-x64-musl@1.5.7:
resolution: {integrity: sha512-OLAYoICU3FaYiTdBsI+lQTKnDHeMmFMXIApN0M+xGiOkoIOQcV9CConMPjgmJQ867+NHRNgUGlvBEAh9CiJodQ==} resolution: {integrity: sha512-LL9aMK601BmQjAUDcKWtt5KvAM0xXi0iJpOjoUD3LPfr5dLvBMTflVHQDAEtuZexLQyqpU09+60781PrI/FCTw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@@ -1022,8 +1028,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-win32-arm64-msvc@1.4.0: /@tauri-apps/cli-win32-arm64-msvc@1.5.7:
resolution: {integrity: sha512-gZ05GENFbI6CB5MlOUsLlU0kZ9UtHn9riYtSXKT6MYs8HSPRffPHaHSL0WxsJweWh9nR5Hgh/TUU8uW3sYCzCg==} resolution: {integrity: sha512-TmAdM6GVkfir3AUFsDV2gyc25kIbJeAnwT72OnmJGAECHs/t/GLP9IkFLLVcFKsiosRf8BXhVyQ84NYkSWo14w==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
@@ -1031,8 +1037,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-win32-ia32-msvc@1.4.0: /@tauri-apps/cli-win32-ia32-msvc@1.5.7:
resolution: {integrity: sha512-JsetT/lTx/Zq98eo8T5CiRyF1nKeX04RO8JlJrI3ZOYsZpp/A5RJvMd/szQ17iOzwiHdge+tx7k2jHysR6oBlQ==} resolution: {integrity: sha512-bqWfxwCfLmrfZy69sEU19KHm5TFEaMb8KIekd4aRq/kyOlrjKLdZxN1PyNRP8zpJA1lTiRHzfUDfhpmnZH/skg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
@@ -1040,8 +1046,8 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli-win32-x64-msvc@1.4.0: /@tauri-apps/cli-win32-x64-msvc@1.5.7:
resolution: {integrity: sha512-z8Olcnwp5aYhzqUAarFjqF+oELCjuYWnB2HAJHlfsYNfDCAORY5kct3Fklz8PSsubC3U2EugWn8n42DwnThurg==} resolution: {integrity: sha512-OxLHVBNdzyQ//xT3kwjQFnJTn/N5zta/9fofAkXfnL7vqmVn6s/RY1LDa3sxCHlRaKw0n3ShpygRbM9M8+sO9w==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@@ -1049,21 +1055,21 @@ packages:
dev: true dev: true
optional: true optional: true
/@tauri-apps/cli@1.4.0: /@tauri-apps/cli@1.5.7:
resolution: {integrity: sha512-VXYr2i2iVFl98etQSQsqLzXgX96bnWiNZd1YADgatqwy/qecbd6Kl5ZAPB5R4ynsgE8A1gU7Fbzh7dCEQYFfmA==} resolution: {integrity: sha512-z7nXLpDAYfQqR5pYhQlWOr88DgPq1AfQyxHhGiakiVgWlaG0ikEfQxop2txrd52H0TRADG0JHR9vFrVFPv4hVQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
hasBin: true hasBin: true
optionalDependencies: optionalDependencies:
'@tauri-apps/cli-darwin-arm64': 1.4.0 '@tauri-apps/cli-darwin-arm64': 1.5.7
'@tauri-apps/cli-darwin-x64': 1.4.0 '@tauri-apps/cli-darwin-x64': 1.5.7
'@tauri-apps/cli-linux-arm-gnueabihf': 1.4.0 '@tauri-apps/cli-linux-arm-gnueabihf': 1.5.7
'@tauri-apps/cli-linux-arm64-gnu': 1.4.0 '@tauri-apps/cli-linux-arm64-gnu': 1.5.7
'@tauri-apps/cli-linux-arm64-musl': 1.4.0 '@tauri-apps/cli-linux-arm64-musl': 1.5.7
'@tauri-apps/cli-linux-x64-gnu': 1.4.0 '@tauri-apps/cli-linux-x64-gnu': 1.5.7
'@tauri-apps/cli-linux-x64-musl': 1.4.0 '@tauri-apps/cli-linux-x64-musl': 1.5.7
'@tauri-apps/cli-win32-arm64-msvc': 1.4.0 '@tauri-apps/cli-win32-arm64-msvc': 1.5.7
'@tauri-apps/cli-win32-ia32-msvc': 1.4.0 '@tauri-apps/cli-win32-ia32-msvc': 1.5.7
'@tauri-apps/cli-win32-x64-msvc': 1.4.0 '@tauri-apps/cli-win32-x64-msvc': 1.5.7
dev: true dev: true
/@tootallnate/once@2.0.0: /@tootallnate/once@2.0.0:
@@ -1156,6 +1162,10 @@ packages:
resolution: {integrity: sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==} resolution: {integrity: sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==}
dev: true dev: true
/@types/streamsaver@2.0.4:
resolution: {integrity: sha512-XxpGYIaBP+2NgZ5+4YeG7hI3wYAyOX8QB92xlPpNvStIAvlniml1th+D0bes1lUZz52IWkPlXMf88wy8NzFkbA==}
dev: false
/@types/three@0.154.0: /@types/three@0.154.0:
resolution: {integrity: sha512-IioqpGhch6FdLDh4zazRn3rXHj6Vn2nVOziJdXVbJFi9CaI65LtP9qqUtpzbsHK2Ezlox8NtsLNHSw3AQzucjA==} resolution: {integrity: sha512-IioqpGhch6FdLDh4zazRn3rXHj6Vn2nVOziJdXVbJFi9CaI65LtP9qqUtpzbsHK2Ezlox8NtsLNHSw3AQzucjA==}
dependencies: dependencies:
@@ -3362,6 +3372,10 @@ packages:
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
dev: false dev: false
/streamsaver@2.0.6:
resolution: {integrity: sha512-LK4e7TfCV8HzuM0PKXuVUfKyCB1FtT9L0EGxsFk5Up8njj0bXK8pJM9+Wq2Nya7/jslmCQwRK39LFm55h7NBTw==}
dev: false
/streamx@2.15.5: /streamx@2.15.5:
resolution: {integrity: sha512-9thPGMkKC2GctCzyCUjME3yR03x2xNo0GPKGkRw2UMYN+gqWa9uqpyNWhmsNCutU5zHmkUum0LsCRQTXUgUCAg==} resolution: {integrity: sha512-9thPGMkKC2GctCzyCUjME3yR03x2xNo0GPKGkRw2UMYN+gqWa9uqpyNWhmsNCutU5zHmkUum0LsCRQTXUgUCAg==}
dependencies: dependencies:

View File

@@ -8,6 +8,7 @@
import { forageStorage, isNodeServer, isTauri } from "src/ts/storage/globalApi"; import { forageStorage, isNodeServer, isTauri } from "src/ts/storage/globalApi";
import { unMigrationAccount } from "src/ts/storage/accountStorage"; import { unMigrationAccount } from "src/ts/storage/accountStorage";
import { checkDriver } from "src/ts/drive/drive"; import { checkDriver } from "src/ts/drive/drive";
import { LoadLocalBackup, SaveLocalBackup } from "src/ts/drive/backuplocal";
let openIframe = false let openIframe = false
let openIframeURL = '' let openIframeURL = ''
let popup:Window = null let popup:Window = null
@@ -36,6 +37,25 @@
}}></svelte:window> }}></svelte:window>
<h2 class="mb-2 text-2xl font-bold mt-2">{language.account} & {language.files}</h2> <h2 class="mb-2 text-2xl font-bold mt-2">{language.account} & {language.files}</h2>
<button
on:click={async () => {
if(await alertConfirm(language.backupConfirm)){
SaveLocalBackup()
}
}}
class="drop-shadow-lg p-3 border-borderc border-solid mt-2 flex justify-center items-center ml-2 mr-2 border-1 hover:bg-selected text-sm">
{language.savebackup} (Local)
</button>
<button
on:click={async () => {
if((await alertConfirm(language.backupLoadConfirm)) && (await alertConfirm(language.backupLoadConfirm2))){
LoadLocalBackup()
}
}}
class="drop-shadow-lg p-3 border-borderc border-solid mt-2 flex justify-center items-center ml-2 mr-2 border-1 hover:bg-selected text-sm mb-4">
{language.loadbackup} (Local)
</button>
<button <button
on:click={async () => { on:click={async () => {

187
src/ts/drive/backuplocal.ts Normal file
View File

@@ -0,0 +1,187 @@
import { BaseDirectory, readBinaryFile, readDir, writeBinaryFile } from "@tauri-apps/api/fs";
import { alertError, alertNormal, alertStore, alertWait } from "../alert";
import { forageStorage, isTauri } from "../storage/globalApi";
import { decodeRisuSave, encodeRisuSave } from "../storage/risuSave";
import { get } from "svelte/store";
import { DataBase } from "../storage/database";
import { save } from "@tauri-apps/api/dialog";
import { relaunch } from "@tauri-apps/api/process";
import { sleep } from "../util";
class TauriWriter{
path: string
firstWrite: boolean = true
constructor(path: string){
this.path = path
}
async write(data:Uint8Array) {
await writeBinaryFile(this.path, data, {
append: !this.firstWrite
})
this.firstWrite = false
}
async close(){
// do nothing
}
}
function getBasename(data:string){
const baseNameRegex = /\\/g
const splited = data.replace(baseNameRegex, '/').split('/')
const lasts = splited[splited.length-1]
return lasts
}
class LocalWriter{
writableStream: WritableStream
writer: WritableStreamDefaultWriter|TauriWriter
async init() {
if(isTauri){
const filePath = await save({
filters: [{
name: 'Binary',
extensions: ['bin']
}]
});
if(!filePath){
return false
}
this.writer = new TauriWriter(filePath)
return true
}
const streamSaver = await import('streamsaver')
this.writableStream = streamSaver.createWriteStream('risu-backup.bin')
this.writer = this.writableStream.getWriter()
return true
}
async write(name:string,data: Uint8Array){
const encodedName = new TextEncoder().encode(getBasename(name))
const nameLength = new Uint32Array([encodedName.byteLength])
await this.writer.write(new Uint8Array(nameLength.buffer))
await this.writer.write(encodedName)
const dataLength = new Uint32Array([data.byteLength])
await this.writer.write(new Uint8Array(dataLength.buffer))
await this.writer.write(data)
}
async close(){
await this.writer.close()
}
}
export async function SaveLocalBackup(){
alertWait("Saving local backup...")
const writer = new LocalWriter()
const r = await writer.init()
if(!r){
alertError('Failed')
return
}
if(isTauri){
const assets = await readDir('assets', {dir: BaseDirectory.AppData})
let i = 0;
for(let asset of assets){
i += 1;
alertWait(`Saving local Backup... (${i} / ${assets.length})`)
const key = asset.name
if(!key || !key.endsWith('.png')){
continue
}
await writer.write(key, await readBinaryFile(asset.path))
}
}
else{
const keys = await forageStorage.keys()
for(let i=0;i<keys.length;i++){
alertWait(`Saving local Backup... (${i} / ${keys.length})`)
const key = keys[i]
if(!key || !key.endsWith('.png')){
continue
}
await writer.write(key, await forageStorage.getItem(key))
}
}
const dbData = encodeRisuSave(get(DataBase), 'compression')
alertWait(`Saving local Backup... (Saving database)`)
await writer.write('database.risudat', dbData)
alertNormal('Success')
}
export async function LoadLocalBackup(){
//select file
try {
const input = document.createElement('input')
input.type = 'file'
input.accept = '.bin'
input.onchange = async () => {
if(!input.files || input.files.length === 0){
input.remove()
return
}
const file = input.files[0]
const reader = new FileReader()
input.remove()
reader.onload = async () => {
const buffer = reader.result as ArrayBuffer
const bufferLength = buffer.byteLength
for(let i=0;i<bufferLength;){
const progress = (i / bufferLength * 100).toFixed(2)
alertWait(`Loading local Backup... (${progress}%)`)
const nameLength = new Uint32Array(buffer.slice(i, i+4))[0]
i += 4
const name = new TextDecoder().decode(new Uint8Array(buffer.slice(i, i+nameLength)))
i += nameLength
const dataLength = new Uint32Array(buffer.slice(i, i+4))[0]
i += 4
const data = new Uint8Array(buffer.slice(i, i+dataLength))
i += dataLength
if(name === 'database.risudat'){
const db = new Uint8Array(data)
const dbData = await decodeRisuSave(db)
DataBase.set(dbData)
if(isTauri){
await writeBinaryFile('database/database.bin', dbData, {dir: BaseDirectory.AppData})
relaunch()
alertStore.set({
type: "wait",
msg: "Success, Refreshing your app."
})
}
else{
await forageStorage.setItem('database/database.bin', dbData)
location.search = ''
alertStore.set({
type: "wait",
msg: "Success, Refreshing your app."
})
}
continue
}
if(isTauri){
await writeBinaryFile(`assets/` + name, data ,{dir: BaseDirectory.AppData})
}
else{
await forageStorage.setItem('assets/' + name, data)
}
await sleep(10)
}
}
reader.readAsArrayBuffer(file)
}
input.click()
} catch (error) {
console.error(error)
alertError('Failed, Is file corrupted?')
}
}