[feat] local backup functionality
This commit is contained in:
187
src/ts/drive/backuplocal.ts
Normal file
187
src/ts/drive/backuplocal.ts
Normal 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?')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user