add leveldb support
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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=?;',
|
||||||
|
|||||||
@@ -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}')
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user