2019-01-28 21:28:30 +08:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
2020-08-28 06:34:07 +08:00
|
|
|
import logging
|
2019-01-28 21:28:30 +08:00
|
|
|
import os
|
|
|
|
|
from sqlite3 import dbapi2 as sqlite
|
2020-08-28 06:34:07 +08:00
|
|
|
import shutil
|
2020-08-27 00:27:05 +08:00
|
|
|
import sys
|
2020-08-28 06:34:07 +08:00
|
|
|
from webob.exc import HTTPBadRequest
|
2019-01-28 21:28:30 +08:00
|
|
|
|
2020-08-28 06:34:07 +08:00
|
|
|
from anki.db import DB
|
|
|
|
|
from anki.collection import Collection
|
2019-01-28 21:28:30 +08:00
|
|
|
|
2020-08-28 06:34:07 +08:00
|
|
|
logger = logging.getLogger("ankisyncd.media")
|
|
|
|
|
logger.setLevel(1)
|
|
|
|
|
|
2020-09-03 00:43:15 +08:00
|
|
|
class FullSyncManager:
|
2020-08-28 06:34:07 +08:00
|
|
|
def test_db(self, db: DB):
|
|
|
|
|
"""
|
|
|
|
|
:param anki.db.DB db: the database uploaded from the client.
|
|
|
|
|
"""
|
|
|
|
|
if db.scalar("pragma integrity_check") != "ok":
|
|
|
|
|
raise HTTPBadRequest(
|
|
|
|
|
"Integrity check failed for uploaded collection database file."
|
|
|
|
|
)
|
|
|
|
|
|
2022-02-07 14:21:51 +08:00
|
|
|
def upload(self, col: Collection, data: bytes, session) -> str:
|
2020-08-28 06:34:07 +08:00
|
|
|
"""
|
|
|
|
|
Uploads a sqlite database from the client to the sync server.
|
|
|
|
|
|
|
|
|
|
:param anki.collection.Collectio col:
|
|
|
|
|
:param bytes data: The binary sqlite database from the client.
|
|
|
|
|
:param .sync_app.SyncUserSession session: The current session.
|
|
|
|
|
"""
|
2019-01-28 21:28:30 +08:00
|
|
|
# Verify integrity of the received database file before replacing our
|
|
|
|
|
# existing db.
|
|
|
|
|
temp_db_path = session.get_collection_path() + ".tmp"
|
|
|
|
|
with open(temp_db_path, 'wb') as f:
|
|
|
|
|
f.write(data)
|
|
|
|
|
|
|
|
|
|
try:
|
2020-08-28 06:34:07 +08:00
|
|
|
with DB(temp_db_path) as test_db:
|
|
|
|
|
self.test_db(test_db)
|
2019-01-28 21:28:30 +08:00
|
|
|
except sqlite.Error as e:
|
|
|
|
|
raise HTTPBadRequest("Uploaded collection database file is "
|
|
|
|
|
"corrupt.")
|
|
|
|
|
|
|
|
|
|
# Overwrite existing db.
|
|
|
|
|
col.close()
|
|
|
|
|
try:
|
2020-08-28 06:34:07 +08:00
|
|
|
shutil.copyfile(temp_db_path, session.get_collection_path())
|
2019-01-28 21:28:30 +08:00
|
|
|
finally:
|
|
|
|
|
col.reopen()
|
2020-08-27 03:06:57 +08:00
|
|
|
# Reopen the media database
|
|
|
|
|
col.media.connect()
|
2019-01-28 21:28:30 +08:00
|
|
|
|
|
|
|
|
return "OK"
|
|
|
|
|
|
2020-08-28 06:34:07 +08:00
|
|
|
def download(self, col: Collection, session) -> bytes:
|
|
|
|
|
"""Download the binary database.
|
2019-01-28 21:28:30 +08:00
|
|
|
|
2020-08-28 06:34:07 +08:00
|
|
|
Performs a downgrade to database schema 11 before sending the database
|
|
|
|
|
to the client.
|
|
|
|
|
|
|
|
|
|
:param anki.collection.Collection col:
|
|
|
|
|
:param .sync_app.SyncUserSession session:
|
|
|
|
|
|
|
|
|
|
:return bytes: the binary sqlite3 database
|
|
|
|
|
"""
|
|
|
|
|
col.close(downgrade=True)
|
|
|
|
|
db_path = session.get_collection_path()
|
2019-01-28 21:28:30 +08:00
|
|
|
try:
|
2020-08-28 06:34:07 +08:00
|
|
|
with open(db_path, 'rb') as tmp:
|
|
|
|
|
data = tmp.read()
|
2019-01-28 21:28:30 +08:00
|
|
|
finally:
|
|
|
|
|
col.reopen()
|
2020-08-28 06:34:07 +08:00
|
|
|
# Reopen the media database
|
|
|
|
|
col.media.connect()
|
|
|
|
|
|
2019-01-28 21:28:30 +08:00
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_full_sync_manager(config):
|
|
|
|
|
if "full_sync_manager" in config and config["full_sync_manager"]: # load from config
|
|
|
|
|
import importlib
|
|
|
|
|
import inspect
|
|
|
|
|
module_name, class_name = config['full_sync_manager'].rsplit('.', 1)
|
|
|
|
|
module = importlib.import_module(module_name.strip())
|
|
|
|
|
class_ = getattr(module, class_name.strip())
|
|
|
|
|
|
|
|
|
|
if not FullSyncManager in inspect.getmro(class_):
|
|
|
|
|
raise TypeError('''"full_sync_manager" found in the conf file but it doesn''t
|
|
|
|
|
inherit from FullSyncManager''')
|
|
|
|
|
return class_(config)
|
|
|
|
|
else:
|
|
|
|
|
return FullSyncManager()
|