implement backup and restore draft 1

+ add extra fields for backup in favorites and scrobble data
- not yet for the playlist tracks
This commit is contained in:
cwilvx
2024-08-17 12:19:24 +03:00
parent ca31054f48
commit 7852be5e3f
8 changed files with 133 additions and 21 deletions
+2
View File
@@ -31,6 +31,7 @@ from app.api import (
getall,
auth,
stream,
backup_and_restore
)
# TODO: Move this description to a separate file
@@ -107,6 +108,7 @@ def create_api():
app.register_api(settings.api)
app.register_api(colors.api)
app.register_api(lyrics.api)
app.register_api(backup_and_restore.api)
# Plugins
app.register_api(plugins.api)
+78
View File
@@ -0,0 +1,78 @@
from dataclasses import asdict
import json
from pathlib import Path
import shutil
from time import time
from flask_openapi3 import Tag
from flask_openapi3 import APIBlueprint
from app.api.auth import admin_required
from app.db.userdata import FavoritesTable, PlaylistTable, ScrobbleTable
from app.settings import Paths
bp_tag = Tag(name="Backup and Restore", description="Backup and Restore")
api = APIBlueprint("backup_and_restore", __name__, url_prefix="/", abp_tags=[bp_tag])
@api.post("/backup")
@admin_required()
def backup():
"""
Create a backup file of your favorites, playlists and scrobble data.
"""
backup_dir = Path(Paths.get_app_dir()) / "backup"
backup_dir.mkdir(parents=True, exist_ok=True)
backup_name = f"backup.{int(time())}"
backup_file = backup_dir / f"{backup_name}.json"
# INFO: Image folder for playlist images
img_folder = backup_dir / "images" / backup_name
img_folder.mkdir(parents=True, exist_ok=True)
favorites = FavoritesTable.get_all()
favorites = [asdict(entry) for entry in favorites]
scrobbles = ScrobbleTable.get_all(start=0)
scrobbles = [asdict(entry) for entry in scrobbles]
# SECTION: Playlists
playlists = PlaylistTable.get_all()
playlist_dicts = []
for entry in playlists:
playlist = asdict(entry)
for key in ["_last_updated", "has_image", "images", "duration", "count"]:
del playlist[key]
playlist_dicts.append(playlist)
# copy images
if playlist["thumb"]:
img_path = Path(Paths.get_playlist_img_path()) / playlist["thumb"]
shutil.copy(img_path, img_folder / playlist["thumb"])
# !SECTION
data = {
"favorites": favorites,
"scrobbles": scrobbles,
"playlists": playlist_dicts,
}
with open(backup_file, "w") as f:
json.dump(data, f, indent=4)
return {
"msg": "Backup created",
"data_path": str(backup_file),
"images_path": str(img_folder),
}, 200
@api.post("/restore")
@admin_required()
def restore():
"""
Restore your favorites, playlists and scrobble data from a backup file.
"""
return {"msg": "Restore"}
+5 -1
View File
@@ -7,6 +7,7 @@ from pydantic import BaseModel, Field
from app.api.apischemas import GenericLimitSchema
from app.db.libdata import TrackTable
from app.db.userdata import FavoritesTable
from app.lib.extras import get_extra_info
from app.models import FavType
from app.settings import Defaults
@@ -71,9 +72,12 @@ def toggle_favorite(body: FavoritesAddBody):
"""
Adds a favorite to the database.
"""
extra = get_extra_info(body.hash, body.type)
try:
FavoritesTable.insert_item({"hash": body.hash, "type": body.type})
FavoritesTable.insert_item(
{"hash": body.hash, "type": body.type, "extra": extra}
)
except:
return {"msg": "Failed! An error occured"}, 500
+4 -1
View File
@@ -4,6 +4,7 @@ from pydantic import Field
from app.api.apischemas import TrackHashSchema
from app.db.userdata import ScrobbleTable
from app.lib.extras import get_extra_info
from app.settings import Defaults
from app.store.albums import AlbumStore
from app.store.artists import ArtistStore
@@ -39,7 +40,9 @@ def log_track(body: LogTrackBody):
if trackentry is None:
return {"msg": "Track not found."}, 404
ScrobbleTable.add(dict(body))
scrobble_data = dict(body)
scrobble_data["extra"] = get_extra_info(body.trackhash, "track")
ScrobbleTable.add(scrobble_data)
# Update play data on the in-memory stores
track = trackentry.tracks[0]
+4 -17
View File
@@ -247,23 +247,11 @@ def send_file_as_chunks(filepath: str) -> Response:
while remaining_bytes > 0 or retry_count < max_retries:
if retry_count == max_retries:
print("💚 sending final chunk! ...")
return (
file.read(os.path.getsize(filepath) - file.tell()),
file.tell(),
True,
)
print("\n\n")
print(f"file: {filepath}")
print(f"start: {start}")
print(f"end: {end}")
print(f"filesize: {os.path.getsize(filepath)}")
print(f"⭐ (O) Remaining bytes: {remaining_bytes}")
print(f"⭐ Remaining bytes: {remaining_bytes}")
print(f"⭐ Cursor position: {file.tell()}")
# Read the chunk size or all the remaining bytes
print(f"💚 remaining_bytes: {remaining_bytes}")
print(f"💚 retry_count: {retry_count}")
pos = file.tell()
chunk = file.read(os.path.getsize(filepath) - pos)
return chunk, pos, True
if remaining_bytes < chunk_size:
time.sleep(0.25)
@@ -303,7 +291,6 @@ def send_file_as_chunks(filepath: str) -> Response:
f"bytes {start}-{position}/{os.path.getsize(filepath) + bytes_to_add}",
)
response.headers.add("Accept-Ranges", "bytes")
response.headers.add("Content-Length", str(len(data or [])))
return response