add leveldb support

This commit is contained in:
2021-09-06 14:14:28 +08:00
parent c96d31b30a
commit d0257d6de7
4 changed files with 96 additions and 11 deletions

View File

@@ -8,11 +8,9 @@ from game_backuper.config import (
from game_backuper.cml import Opts, OptAction from game_backuper.cml import Opts, OptAction
from threading import Thread from threading import Thread
from os.path import exists, join from os.path import exists, join
from os import mkdir from os import mkdir, remove
from game_backuper.file import new_file, copy_file from game_backuper.file import new_file, copy_file, File, mkdir_for_file
from game_backuper.leveldb import have_leveldb from game_backuper.filetype import FileType
if have_leveldb:
from game_backuper.leveldb import list_leveldb_entries
class BackupTask(Thread): class BackupTask(Thread):
@@ -46,9 +44,38 @@ class BackupTask(Thread):
copy_file(f[1], de, f[0], prog) copy_file(f[1], de, f[0], prog)
self.db.add_file(nf) self.db.add_file(nf)
elif isinstance(f, ConfigLeveldb): elif isinstance(f, ConfigLeveldb):
from game_backuper.leveldb import have_leveldb
if not have_leveldb: if not have_leveldb:
raise ValueError('Leveldb is not supported.') raise ValueError('Leveldb is not supported.')
print(list_leveldb_entries(f.full_path, f.domains)) from game_backuper.leveldb import (
list_leveldb_entries,
leveldb_stats,
leveldb_to_sqlite,
)
ent = list_leveldb_entries(f.full_path, f.domains)
stats = leveldb_stats(f.full_path, ent)
ori = self.db.get_file(prog, f.name)
de = join(bp, f.name + ".db")
if ori is not None:
if ori.size == stats.size and ori.hash == stats.hash:
continue
if ori.type is None or ori.type != FileType.LEVELDB:
pp = join(bp, ori.file)
if exists(pp):
remove(pp)
self.db.remove_file(ori)
ori = None
if exists(de):
remove(de)
mkdir_for_file(de)
leveldb_to_sqlite(f.full_path, de, ent)
print(f'{prog}: Covert leveldb done. {f.full_path}({f.name}) -> {de}') # noqa: E501
if ori is None:
nf = File(None, f.name, stats.size, prog, stats.hash,
FileType.LEVELDB)
self.db.add_file(nf)
else:
self.db.set_file(ori.id, stats.size, stats.hash)
class Backuper: class Backuper:

View File

@@ -1,6 +1,6 @@
from sqlite3 import connect from sqlite3 import connect
from os.path import join from os.path import join
from typing import List from typing import List, Union
from threading import Lock from threading import Lock
from game_backuper.file import File from game_backuper.file import File
from game_backuper.filetype import FileType from game_backuper.filetype import FileType
@@ -107,10 +107,25 @@ class Db:
'SELECT files.*, filetype.type FROM files LEFT JOIN filetype ON files.id=filetype.id WHERE program=? AND file=?;', # noqa: E501 'SELECT files.*, filetype.type FROM files LEFT JOIN filetype ON files.id=filetype.id WHERE program=? AND file=?;', # noqa: E501
(prog, file)) (prog, file))
for i in cur: for i in cur:
if i[5] is not None:
i[5] = FileType(i[5])
return File(*i) return File(*i)
def remove_file(self, id: Union[int, File]):
with self._lock:
ft = None
if isinstance(id, File):
iid = id.id
ft = id.type
else:
cur = self.db.execute('SELECT type FROM filetype WHERE id=?;',
(id,))
for i in cur:
ft = FileType(i[0])
iid = id
self.db.execute('DELETE FROM files WHERE id=?;', (iid,))
if ft is not None:
self.db.execute('DELETE FROM filetype WHERE id=?;', (iid,))
self.db.commit()
def set_file(self, id: int, size: int, hash: str): def set_file(self, id: int, size: int, hash: str):
with self._lock: with self._lock:
self.db.execute('UPDATE files SET size=?, hash=? WHERE id=?;', self.db.execute('UPDATE files SET size=?, hash=? WHERE id=?;',

View File

@@ -9,10 +9,14 @@ from game_backuper.filetype import FileType
File = namedtuple('File', ['id', 'file', 'size', 'program', 'hash', 'type']) File = namedtuple('File', ['id', 'file', 'size', 'program', 'hash', 'type'])
def copy_file(loc: str, dest: str, name: str, prog: str): def mkdir_for_file(p: str):
d = dirname(abspath(dest)) d = dirname(abspath(p))
if not exists(d): if not exists(d):
makedirs(d) makedirs(d)
def copy_file(loc: str, dest: str, name: str, prog: str):
mkdir_for_file(dest)
r = copy2(loc, dest) r = copy2(loc, dest)
print(f'{prog}: Copyed {loc}({name}) -> {r}') print(f'{prog}: Copyed {loc}({name}) -> {r}')

View File

@@ -2,11 +2,22 @@ try:
from plyvel import DB from plyvel import DB
have_leveldb = True have_leveldb = True
from typing import List from typing import List
from hashlib import sha512
from base64 import b85encode
from collections import namedtuple
from sqlite3 import connect
except ImportError: except ImportError:
have_leveldb = False have_leveldb = False
if have_leveldb: if have_leveldb:
LeveldbStats = namedtuple('LeveldbStats', ['hash', 'size'])
MAP_TABLE = '''CREATE TABLE map (
key TEXT,
value TEXT,
PRIMARY KEY(key)
)'''
def list_leveldb_entries(db: str, dms: List[bytes] = None): def list_leveldb_entries(db: str, dms: List[bytes] = None):
d = DB(db) d = DB(db)
r = [] r = []
@@ -28,3 +39,31 @@ if have_leveldb:
r.append(i) r.append(i)
r.sort() r.sort()
return r return r
def leveldb_stats(db: str, entries: List[bytes]) -> LeveldbStats:
d = DB(db)
h = sha512()
le = 0
for e in entries:
v = d.get(e)
if v is not None:
h.update(e)
h.update(v)
le += len(e) + len(v)
d.close()
return LeveldbStats(b85encode(h.digest()).decode(), le)
def leveldb_to_sqlite(db: str, dest: str, entries: List[bytes]):
d = DB(db)
s = connect(dest)
s.text_factory = bytes
s.execute(MAP_TABLE)
for e in entries:
v = d.get(e)
if v is not None:
s.execute('INSERT INTO map VALUES (?, ?);', (e, v))
s.commit()
s.execute('VACUUM;')
s.commit()
d.close()
s.close()