Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b8a4d9884 | |||
| 4e5342fc83 | |||
| 75e6d4bd39 | |||
| 4e816cbf61 | |||
| b5bc5cbf39 | |||
| aabe935933 | |||
| b1016b029e | |||
| a45215497f | |||
| 6290fc1dae | |||
| 4ff3b8a414 | |||
| d4dedcb316 | |||
| 7d82acb488 | |||
| 48c2295a49 | |||
| ea645a8ec9 | |||
| a779ca4b8d |
24
build_exe.py
Normal file
24
build_exe.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from version import version, dversion
|
||||||
|
from py2exe import freeze
|
||||||
|
|
||||||
|
freeze(
|
||||||
|
console=[{
|
||||||
|
'script': "game_backuper/__main__.py",
|
||||||
|
"dest_base": 'game-backuper',
|
||||||
|
'version_info': {
|
||||||
|
'version': version,
|
||||||
|
'product_name': 'game-backuper',
|
||||||
|
'product_version': dversion,
|
||||||
|
'company_name': 'lifegpc',
|
||||||
|
'description': 'A game backuper',
|
||||||
|
'copyright': 'Copyright (C) 2021-2024 lifegpc'
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
options={
|
||||||
|
"optimize": 2,
|
||||||
|
"compressed": 1,
|
||||||
|
"excludes": ["doctest", "pydoc", "unittest"],
|
||||||
|
"includes": ["cryptography.utils", "_cffi_backend", "sqlite3.dump"]
|
||||||
|
},
|
||||||
|
zipfile=None,
|
||||||
|
)
|
||||||
@@ -8,7 +8,10 @@ compress_method: "bzip2" # Optional. Default value: null. Supported value: "bzi
|
|||||||
compress_level: 6
|
compress_level: 6
|
||||||
encrypt_db: false # Optional. Default value: false. Encrypt the database. Warning: The default python sqlite library don't support encrypt, it just ignore encrypt phases.
|
encrypt_db: false # Optional. Default value: false. Encrypt the database. Warning: The default python sqlite library don't support encrypt, it just ignore encrypt phases.
|
||||||
db_password: "Password" # Specify the password of the encryped database.
|
db_password: "Password" # Specify the password of the encryped database.
|
||||||
|
db_path: /path/to/db/path # Optional. Default value: $dest/data.db. The path to the database.
|
||||||
encrypt_files: false # Optional. Default value: false. Encrypt backup files. The key information will stored in database.
|
encrypt_files: false # Optional. Default value: false. Encrypt backup files. The key information will stored in database.
|
||||||
|
protect_filename: false # Optional. Default value: false. Use id in database as file name. Only works when encrypt_files is true.
|
||||||
|
unpin_file: false # Optional. Default value: false. Notifiy sync provider to dehydrate file data.
|
||||||
programs:
|
programs:
|
||||||
- name: Your program name # This name is used to identify different application.
|
- name: Your program name # This name is used to identify different application.
|
||||||
base: /path/to/save/path # Must be absoulte path.
|
base: /path/to/save/path # Must be absoulte path.
|
||||||
@@ -18,6 +21,8 @@ programs:
|
|||||||
compress_method: null # Optional.
|
compress_method: null # Optional.
|
||||||
compress_level: null # Optional.
|
compress_level: null # Optional.
|
||||||
encrypt_files: false # Optional
|
encrypt_files: false # Optional
|
||||||
|
protect_filename: false # Optional
|
||||||
|
unpin_file: false # Optional.
|
||||||
files:
|
files:
|
||||||
- BGI.gdb # path to a file/folder. All subfolders will include if it is a folder. Must be relative path.
|
- BGI.gdb # path to a file/folder. All subfolders will include if it is a folder. Must be relative path.
|
||||||
- type: path
|
- type: path
|
||||||
@@ -29,6 +34,8 @@ programs:
|
|||||||
compress_method: null # Optional.
|
compress_method: null # Optional.
|
||||||
compress_level: null # Optional.
|
compress_level: null # Optional.
|
||||||
encrypt_files: false # Optional.
|
encrypt_files: false # Optional.
|
||||||
|
protect_filename: false # Optional
|
||||||
|
unpin_file: false # Optional.
|
||||||
excludes: # Optional. Exculde some files. Only effected when path is a folder.
|
excludes: # Optional. Exculde some files. Only effected when path is a folder.
|
||||||
- data.db # Releative path
|
- data.db # Releative path
|
||||||
- /path/to/data.db # Absolute path
|
- /path/to/data.db # Absolute path
|
||||||
@@ -51,5 +58,7 @@ programs:
|
|||||||
compress_method: null # Optional.
|
compress_method: null # Optional.
|
||||||
compress_level: null # Optional.
|
compress_level: null # Optional.
|
||||||
encrypt_files: false # Optional.
|
encrypt_files: false # Optional.
|
||||||
|
protect_filename: false # Optional
|
||||||
|
unpin_file: false # Optional.
|
||||||
domains: # optional. Just backup minor domains in localstorage database. Only chromium is tested.
|
domains: # optional. Just backup minor domains in localstorage database. Only chromium is tested.
|
||||||
- some domain
|
- some domain
|
||||||
|
|||||||
@@ -1,9 +1,23 @@
|
|||||||
__version__ = "1.0.0"
|
__version__ = "1.0.0"
|
||||||
|
import sys
|
||||||
|
from platform import system
|
||||||
|
if system() == 'Windows' and sys.version_info.minor > 7:
|
||||||
|
from os import add_dll_directory, environ, getcwd
|
||||||
|
from os.path import dirname, isdir
|
||||||
|
add_dll_directory(dirname(sys.executable))
|
||||||
|
add_dll_directory(getcwd())
|
||||||
|
for i in environ['PATH'].split(";"):
|
||||||
|
if isdir(i):
|
||||||
|
add_dll_directory(i)
|
||||||
|
for i in sys.path:
|
||||||
|
if isdir(i):
|
||||||
|
add_dll_directory(i)
|
||||||
|
|
||||||
|
|
||||||
from game_backuper.main import main
|
from game_backuper.main import main
|
||||||
|
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
import sys
|
|
||||||
try:
|
try:
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -8,13 +8,15 @@ 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, isdir
|
from os.path import exists, join, isdir
|
||||||
from os import mkdir, remove, close
|
from os import remove, close
|
||||||
|
from shutil import move
|
||||||
from game_backuper.file import new_file, copy_file, File, mkdir_for_file
|
from game_backuper.file import new_file, copy_file, File, mkdir_for_file
|
||||||
from game_backuper.filetype import FileType
|
from game_backuper.filetype import FileType
|
||||||
from game_backuper.restorer import RestoreTask
|
from game_backuper.restorer import RestoreTask
|
||||||
from game_backuper.file import remove_compress_files, remove_unencryped_files
|
from game_backuper.file import remove_compress_files, remove_unencryped_files
|
||||||
from game_backuper.compress import compress
|
from game_backuper.compress import compress
|
||||||
from game_backuper.enc import encrypt_file
|
from game_backuper.enc import encrypt_file
|
||||||
|
from game_backuper.file import unpin_file_if_needed
|
||||||
from tempfile import mkstemp
|
from tempfile import mkstemp
|
||||||
|
|
||||||
|
|
||||||
@@ -30,8 +32,7 @@ class BackupTask(Thread):
|
|||||||
prog = self.prog.name
|
prog = self.prog.name
|
||||||
bp = join(self.cfg.dest, prog)
|
bp = join(self.cfg.dest, prog)
|
||||||
ebp = join(self.cfg.dest, '.encrypt', prog)
|
ebp = join(self.cfg.dest, '.encrypt', prog)
|
||||||
if not exists(bp):
|
ebpi = join(self.cfg.dest, '.encrypt', '.id')
|
||||||
mkdir(bp)
|
|
||||||
fl = self.db.get_file_list(prog)
|
fl = self.db.get_file_list(prog)
|
||||||
for f in self.prog.files:
|
for f in self.prog.files:
|
||||||
if isinstance(f, ConfigNormalFile):
|
if isinstance(f, ConfigNormalFile):
|
||||||
@@ -45,26 +46,48 @@ class BackupTask(Thread):
|
|||||||
continue
|
continue
|
||||||
de = join(ebp if f.encrypt_files else bp, f[0])
|
de = join(ebp if f.encrypt_files else bp, f[0])
|
||||||
if ori is not None:
|
if ori is not None:
|
||||||
|
de2 = join(ebpi if f.encrypt_files else bp, str(ori.id)) # noqa: E501
|
||||||
|
if f.protect_filename:
|
||||||
|
if not exists(de2) and exists(de):
|
||||||
|
mkdir_for_file(de2)
|
||||||
|
move(de, de2)
|
||||||
|
print(f'{prog}: Renamed {de} -> {de2}.')
|
||||||
|
de = de2
|
||||||
|
else:
|
||||||
|
if not exists(de) and exists(de2):
|
||||||
|
mkdir_for_file(de)
|
||||||
|
move(de2, de)
|
||||||
|
print(f'{prog}: Renamed {de2} -> {de}.')
|
||||||
if ori.size == nf.size and ori.hash == nf.hash:
|
if ori.size == nf.size and ori.hash == nf.hash:
|
||||||
if c is None:
|
if c is None:
|
||||||
if exists(de) and not f.encrypt_files:
|
if exists(de) and not f.encrypt_files:
|
||||||
print(f'{prog}: Skip {f[0]}.')
|
print(f'{prog}: Skip {f[0]}.')
|
||||||
remove_compress_files(de, prog, f.name)
|
remove_compress_files(de, prog, f.name)
|
||||||
self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501
|
self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501
|
||||||
|
self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501
|
||||||
|
if f.unpin_file:
|
||||||
|
unpin_file_if_needed(de)
|
||||||
continue
|
continue
|
||||||
elif exists(de) and f.encrypt_files and not ori.compressed: # noqa: E501
|
elif exists(de) and f.encrypt_files and not ori.compressed: # noqa: E501
|
||||||
print(f'{prog}: Skip {f[0]}.')
|
print(f'{prog}: Skip {f[0]}.')
|
||||||
remove_unencryped_files(join(bp, f[0]), prog, f.name) # noqa: E501
|
remove_unencryped_files(join(bp, f[0]), prog, f.name) # noqa: E501
|
||||||
|
if f.unpin_file:
|
||||||
|
unpin_file_if_needed(de)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
if not f.encrypt_files and exists(de + c.ext):
|
if not f.encrypt_files and exists(de + c.ext):
|
||||||
print(f'{prog}: Skip {f.name}.')
|
print(f'{prog}: Skip {f.name}.')
|
||||||
remove_compress_files(de, prog, f.name, c.ext) # noqa: E501
|
remove_compress_files(de, prog, f.name, c.ext) # noqa: E501
|
||||||
self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501
|
self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501
|
||||||
|
self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501
|
||||||
|
if f.unpin_file:
|
||||||
|
unpin_file_if_needed(de + c.ext)
|
||||||
continue
|
continue
|
||||||
elif f.encrypt_files and ori.compressed_type == c.method: # noqa: E501
|
elif f.encrypt_files and ori.compressed_type == c.method: # noqa: E501
|
||||||
print(f'{prog}: Skip {f.name}.')
|
print(f'{prog}: Skip {f.name}.')
|
||||||
remove_unencryped_files(join(bp, f.name), prog, f.name) # noqa: E501
|
remove_unencryped_files(join(bp, f.name), prog, f.name) # noqa: E501
|
||||||
|
if f.unpin_file:
|
||||||
|
unpin_file_if_needed(de)
|
||||||
continue
|
continue
|
||||||
stats = None
|
stats = None
|
||||||
if f.encrypt_files:
|
if f.encrypt_files:
|
||||||
@@ -74,12 +97,20 @@ class BackupTask(Thread):
|
|||||||
copy_file(f[1], de, f[0], prog)
|
copy_file(f[1], de, f[0], prog)
|
||||||
remove_compress_files(de, prog, f.name)
|
remove_compress_files(de, prog, f.name)
|
||||||
self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501
|
self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501
|
||||||
|
self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501
|
||||||
else:
|
else:
|
||||||
compress(f[1], de, c, f.name, prog)
|
compress(f[1], de, c, f.name, prog)
|
||||||
self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501
|
self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501
|
||||||
|
self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501
|
||||||
self.db.set_file(ori.id, nf.size, nf.hash)
|
self.db.set_file(ori.id, nf.size, nf.hash)
|
||||||
self.db.set_file_encrypt_information(ori.id, stats)
|
self.db.set_file_encrypt_information(ori.id, stats)
|
||||||
|
if f.unpin_file:
|
||||||
|
unpin_file_if_needed(de)
|
||||||
else:
|
else:
|
||||||
|
if f.protect_filename:
|
||||||
|
self.db.add_file(nf, False)
|
||||||
|
tmpori = self.db.get_file(prog, f[0])
|
||||||
|
de = join(ebpi if f.encrypt_files else bp, str(tmpori.id)) # noqa: E501
|
||||||
if f.encrypt_files:
|
if f.encrypt_files:
|
||||||
s = encrypt_file(f[1], de, nf, f.name, prog, c)
|
s = encrypt_file(f[1], de, nf, f.name, prog, c)
|
||||||
nf = File.from_encrypt_stats(s, nf)
|
nf = File.from_encrypt_stats(s, nf)
|
||||||
@@ -91,7 +122,12 @@ class BackupTask(Thread):
|
|||||||
else:
|
else:
|
||||||
compress(f[1], de, c, f.name, prog)
|
compress(f[1], de, c, f.name, prog)
|
||||||
self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501
|
self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501
|
||||||
self.db.add_file(nf)
|
if f.protect_filename:
|
||||||
|
self.db.set_file_encrypt_information(tmpori.id, s)
|
||||||
|
else:
|
||||||
|
self.db.add_file(nf)
|
||||||
|
if f.unpin_file:
|
||||||
|
unpin_file_if_needed(de)
|
||||||
elif isinstance(f, ConfigLeveldb):
|
elif isinstance(f, ConfigLeveldb):
|
||||||
from game_backuper.leveldb import have_leveldb
|
from game_backuper.leveldb import have_leveldb
|
||||||
if not have_leveldb:
|
if not have_leveldb:
|
||||||
@@ -111,12 +147,25 @@ class BackupTask(Thread):
|
|||||||
c = f.compress_config
|
c = f.compress_config
|
||||||
de = join(ebp if f.encrypt_files else bp, f.name + ".db")
|
de = join(ebp if f.encrypt_files else bp, f.name + ".db")
|
||||||
if ori is not None:
|
if ori is not None:
|
||||||
|
de2 = join(ebpi if f.encrypt_files else bp, str(ori.id)) # noqa: E501
|
||||||
|
if f.protect_filename:
|
||||||
|
if not exists(de2) and exists(de):
|
||||||
|
mkdir_for_file(de2)
|
||||||
|
move(de, de2)
|
||||||
|
print(f'{prog}: Renamed {de} -> {de2}.')
|
||||||
|
de = de2
|
||||||
|
else:
|
||||||
|
if not exists(de) and exists(de2):
|
||||||
|
mkdir_for_file(de)
|
||||||
|
move(de2, de)
|
||||||
|
print(f'{prog}: Renamed {de2} -> {de}.')
|
||||||
if ori.type is None or ori.type != FileType.LEVELDB:
|
if ori.type is None or ori.type != FileType.LEVELDB:
|
||||||
pp = join(bp, ori.file)
|
pp = join(bp, ori.file)
|
||||||
if exists(pp):
|
if exists(pp):
|
||||||
remove(pp)
|
remove(pp)
|
||||||
remove_compress_files(pp, prog, f.name)
|
remove_compress_files(pp, prog, f.name)
|
||||||
self.remove_encrypted_file(join(ebp, ori.file), prog, f.name, ori) # noqa: E501
|
self.remove_encrypted_file(join(ebp, ori.file), prog, f.name, ori) # noqa: E501
|
||||||
|
self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501
|
||||||
self.db.remove_file(ori)
|
self.db.remove_file(ori)
|
||||||
ori = None
|
ori = None
|
||||||
if ori is not None:
|
if ori is not None:
|
||||||
@@ -126,28 +175,46 @@ class BackupTask(Thread):
|
|||||||
print(f'{prog}: Skip {f[0]}.')
|
print(f'{prog}: Skip {f[0]}.')
|
||||||
remove_compress_files(de, prog, f.name)
|
remove_compress_files(de, prog, f.name)
|
||||||
self.remove_encrypted_file(join(ebp, f[0] + '.db'), prog, f.name, ori) # noqa: E501
|
self.remove_encrypted_file(join(ebp, f[0] + '.db'), prog, f.name, ori) # noqa: E501
|
||||||
|
self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501
|
||||||
|
if f.unpin_file:
|
||||||
|
unpin_file_if_needed(de)
|
||||||
continue
|
continue
|
||||||
elif exists(de) and f.encrypt_files and not ori.compressed: # noqa: E501
|
elif exists(de) and f.encrypt_files and not ori.compressed: # noqa: E501
|
||||||
print(f'{prog}: Skip {f[0]}.')
|
print(f'{prog}: Skip {f[0]}.')
|
||||||
remove_unencryped_files(join(bp, f[0] + '.db'), prog, f.name) # noqa: E501
|
remove_unencryped_files(join(bp, f[0] + '.db'), prog, f.name) # noqa: E501
|
||||||
|
if f.unpin_file:
|
||||||
|
unpin_file_if_needed(de)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
if not f.encrypt_files and exists(de + c.ext):
|
if not f.encrypt_files and exists(de + c.ext):
|
||||||
print(f'{prog}: Skip {f.name}.')
|
print(f'{prog}: Skip {f.name}.')
|
||||||
remove_compress_files(de, prog, f.name, c.ext)
|
remove_compress_files(de, prog, f.name, c.ext)
|
||||||
self.remove_encrypted_file(join(ebp, f[0] + '.db'), prog, f.name, ori) # noqa: E501
|
self.remove_encrypted_file(join(ebp, f[0] + '.db'), prog, f.name, ori) # noqa: E501
|
||||||
|
self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501
|
||||||
|
if f.unpin_file:
|
||||||
|
unpin_file_if_needed(de + c.ext)
|
||||||
continue
|
continue
|
||||||
elif f.encrypt_files and ori.compressed_type == c.method: # noqa: E501
|
elif f.encrypt_files and ori.compressed_type == c.method: # noqa: E501
|
||||||
print(f'{prog}: Skip {f.name}.')
|
print(f'{prog}: Skip {f.name}.')
|
||||||
remove_unencryped_files(join(bp, f.name + '.db'), prog, f.name) # noqa: E501
|
remove_unencryped_files(join(bp, f.name + '.db'), prog, f.name) # noqa: E501
|
||||||
|
if f.unpin_file:
|
||||||
|
unpin_file_if_needed(de)
|
||||||
continue
|
continue
|
||||||
|
if f.protect_filename:
|
||||||
|
ori = self.db.get_file(prog, f[0])
|
||||||
|
if ori is None:
|
||||||
|
nf = File(None, f.name, 0, prog, None, FileType.LEVELDB, None, None, None, None, None) # noqa: E501
|
||||||
|
self.db.add_file(nf, False)
|
||||||
|
ori = self.db.get_file(prog, f[0])
|
||||||
|
de = join(ebpi if f.encrypt_files else bp, str(ori.id)) # noqa: E501
|
||||||
mkdir_for_file(de)
|
mkdir_for_file(de)
|
||||||
st = None
|
st = None
|
||||||
if c is None:
|
if c is None and not f.encrypt_files:
|
||||||
leveldb_to_sqlite(f.full_path, de, ent)
|
leveldb_to_sqlite(f.full_path, de, ent)
|
||||||
print(f'{prog}: Covert leveldb done. {f.full_path}({f.name}) -> {de}') # noqa: E501
|
print(f'{prog}: Covert leveldb done. {f.full_path}({f.name}) -> {de}') # noqa: E501
|
||||||
remove_compress_files(de, prog, f.name)
|
remove_compress_files(de, prog, f.name)
|
||||||
self.remove_encrypted_file(join(ebp, f[0] + '.db'), prog, f.name, ori) # noqa: E501
|
self.remove_encrypted_file(join(ebp, f[0] + '.db'), prog, f.name, ori) # noqa: E501
|
||||||
|
self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501
|
||||||
else:
|
else:
|
||||||
tmp = mkstemp()
|
tmp = mkstemp()
|
||||||
close(tmp[0])
|
close(tmp[0])
|
||||||
@@ -160,6 +227,7 @@ class BackupTask(Thread):
|
|||||||
else:
|
else:
|
||||||
compress(tmp, de, c, f.name, prog)
|
compress(tmp, de, c, f.name, prog)
|
||||||
self.remove_encrypted_file(join(ebp, f[0] + '.db'), prog, f.name, ori) # noqa: E501
|
self.remove_encrypted_file(join(ebp, f[0] + '.db'), prog, f.name, ori) # noqa: E501
|
||||||
|
self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501
|
||||||
remove(tmp)
|
remove(tmp)
|
||||||
print(f'{prog}: Removed tempfile {tmp}')
|
print(f'{prog}: Removed tempfile {tmp}')
|
||||||
if ori is None:
|
if ori is None:
|
||||||
@@ -171,6 +239,8 @@ class BackupTask(Thread):
|
|||||||
else:
|
else:
|
||||||
self.db.set_file(ori.id, stats.size, stats.hash)
|
self.db.set_file(ori.id, stats.size, stats.hash)
|
||||||
self.db.set_file_encrypt_information(ori.id, st)
|
self.db.set_file_encrypt_information(ori.id, st)
|
||||||
|
if f.unpin_file:
|
||||||
|
unpin_file_if_needed(de)
|
||||||
for fn in fl:
|
for fn in fl:
|
||||||
f = self.db.get_file(prog, fn)
|
f = self.db.get_file(prog, fn)
|
||||||
if f.type is None:
|
if f.type is None:
|
||||||
@@ -180,6 +250,7 @@ class BackupTask(Thread):
|
|||||||
print(f'{prog}: Remove {de}({fn})')
|
print(f'{prog}: Remove {de}({fn})')
|
||||||
remove_compress_files(de, prog, fn)
|
remove_compress_files(de, prog, fn)
|
||||||
self.remove_encrypted_file(join(ebp, fn), prog, fn, f)
|
self.remove_encrypted_file(join(ebp, fn), prog, fn, f)
|
||||||
|
self.remove_encrypted_file(join(ebpi, str(f.id)), prog, fn, f)
|
||||||
self.db.remove_file(f)
|
self.db.remove_file(f)
|
||||||
if f.type == FileType.LEVELDB:
|
if f.type == FileType.LEVELDB:
|
||||||
de = join(bp, fn + '.db')
|
de = join(bp, fn + '.db')
|
||||||
@@ -188,6 +259,7 @@ class BackupTask(Thread):
|
|||||||
print(f'{prog}: Remove {de}({fn})')
|
print(f'{prog}: Remove {de}({fn})')
|
||||||
remove_compress_files(de, prog, fn + '.db')
|
remove_compress_files(de, prog, fn + '.db')
|
||||||
self.remove_encrypted_file(join(ebp, fn + '.db'), prog, fn, f)
|
self.remove_encrypted_file(join(ebp, fn + '.db'), prog, fn, f)
|
||||||
|
self.remove_encrypted_file(join(ebpi, str(f.id)), prog, fn, ori) # noqa: E501
|
||||||
self.db.remove_file(f)
|
self.db.remove_file(f)
|
||||||
|
|
||||||
def remove_encrypted_file(self, loc: str, prog: str, name: str, f: File):
|
def remove_encrypted_file(self, loc: str, prog: str, name: str, f: File):
|
||||||
|
|||||||
@@ -1,15 +1,69 @@
|
|||||||
from ctypes import HRESULT, byref, c_uint, windll
|
from ctypes import HRESULT, POINTER, Structure, Union, byref, c_uint, sizeof, windll # noqa: E501
|
||||||
from ctypes.wintypes import (
|
from ctypes.wintypes import BYTE, DWORD, HANDLE, LARGE_INTEGER, LPCWSTR, LPVOID, PDWORD, PHANDLE, ULONG, WPARAM # noqa: E501
|
||||||
DWORD, HANDLE, LARGE_INTEGER, LPCWSTR, LPVOID, PHANDLE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
ULONG_PTR = WPARAM
|
||||||
|
PVOID = LPVOID
|
||||||
dll = windll.CldApi
|
dll = windll.CldApi
|
||||||
|
CF_HYDRATE_FLAG_NONE = 0
|
||||||
|
CF_IN_SYNC_STATE_NOT_IN_SYNC = 0
|
||||||
|
CF_IN_SYNC_STATE_IN_SYNC = 1
|
||||||
CF_OPEN_FILE_FLAG_NONE = 0
|
CF_OPEN_FILE_FLAG_NONE = 0
|
||||||
CF_OPEN_FILE_FLAG_EXCLUSIVE = 1
|
CF_OPEN_FILE_FLAG_EXCLUSIVE = 1
|
||||||
CF_OPEN_FILE_FLAG_WRITE_ACCESS = 2
|
CF_OPEN_FILE_FLAG_WRITE_ACCESS = 2
|
||||||
CF_OPEN_FILE_FLAG_DELETE_ACCESS = 3
|
CF_OPEN_FILE_FLAG_DELETE_ACCESS = 3
|
||||||
CF_OPEN_FILE_FLAG_FOREGROUND = 4
|
CF_OPEN_FILE_FLAG_FOREGROUND = 4
|
||||||
|
CF_PIN_STATE_UNSPECIFIED = 0
|
||||||
|
CF_PIN_STATE_PINNED = 1
|
||||||
|
CF_PIN_STATE_UNPINNED = 2
|
||||||
|
CF_PIN_STATE_EXCLUDED = 3
|
||||||
|
CF_PIN_STATE_INHERIT = 4
|
||||||
|
CF_PLACEHOLDER_INFO_BASIC = 0
|
||||||
|
CF_PLACEHOLDER_INFO_STANDARD = 1
|
||||||
|
CF_SET_PIN_FLAG_NONE = 0x00000000
|
||||||
|
CF_SET_PIN_FLAG_RECURSE = 0x00000001
|
||||||
|
CF_SET_PIN_FLAG_RECURSE_ONLY = 0x00000002
|
||||||
|
CF_SET_PIN_FLAG_RECURSE_STOP_ON_ERROR = 0x00000004
|
||||||
|
|
||||||
|
|
||||||
|
class CF_PLACEHOLDER_BASIC_INFO(Structure):
|
||||||
|
_fields_ = [("PinState", c_uint),
|
||||||
|
("InSyncState", c_uint),
|
||||||
|
("FileId", LARGE_INTEGER),
|
||||||
|
("SyncRootFileId", LARGE_INTEGER),
|
||||||
|
("FileIdentityLength", ULONG),
|
||||||
|
("FileIdentity", BYTE)]
|
||||||
|
|
||||||
|
|
||||||
|
class CF_PLACEHOLDER_STANDARD_INFO(Structure):
|
||||||
|
_fields_ = [("OnDiskDataSize", LARGE_INTEGER),
|
||||||
|
("ValidatedDataSize", LARGE_INTEGER),
|
||||||
|
("ModifiedDataSize", LARGE_INTEGER),
|
||||||
|
("PropertiesSize", LARGE_INTEGER),
|
||||||
|
("PinState", c_uint),
|
||||||
|
("InSyncState", c_uint),
|
||||||
|
("FileId", LARGE_INTEGER),
|
||||||
|
("SyncRootFileId", LARGE_INTEGER),
|
||||||
|
("FileIdentityLength", ULONG),
|
||||||
|
("FileIdentity", BYTE)]
|
||||||
|
|
||||||
|
|
||||||
|
class DUMMYSTRUCTNAME(Structure):
|
||||||
|
_fields_ = [("Offset", DWORD), ("OffsetHigh", DWORD)]
|
||||||
|
|
||||||
|
|
||||||
|
class DUMMYUNIONNAME(Union):
|
||||||
|
_fields_ = [("DUMMYSTRUCTNAME", DUMMYSTRUCTNAME), ("Pointer", PVOID)]
|
||||||
|
|
||||||
|
|
||||||
|
class OVERLAPPED(Structure):
|
||||||
|
_fields_ = [("Internal", ULONG_PTR),
|
||||||
|
("InternalHigh", ULONG_PTR),
|
||||||
|
("DUMMYUNIONNAME", DUMMYUNIONNAME),
|
||||||
|
("hEvent", HANDLE)]
|
||||||
|
|
||||||
|
|
||||||
|
LPOVERLAPPED = POINTER(OVERLAPPED)
|
||||||
CfOpenFileWithOplock = dll.CfOpenFileWithOplock
|
CfOpenFileWithOplock = dll.CfOpenFileWithOplock
|
||||||
CfOpenFileWithOplock.argtypes = [LPCWSTR, c_uint, PHANDLE]
|
CfOpenFileWithOplock.argtypes = [LPCWSTR, c_uint, PHANDLE]
|
||||||
CfOpenFileWithOplock.restype = HRESULT
|
CfOpenFileWithOplock.restype = HRESULT
|
||||||
@@ -18,7 +72,14 @@ CfHydratePlaceholder.argtypes = [HANDLE, LARGE_INTEGER, LARGE_INTEGER, c_uint, L
|
|||||||
CfHydratePlaceholder.restype = HRESULT
|
CfHydratePlaceholder.restype = HRESULT
|
||||||
CfCloseHandle = dll.CfCloseHandle
|
CfCloseHandle = dll.CfCloseHandle
|
||||||
CfCloseHandle.argtypes = [HANDLE]
|
CfCloseHandle.argtypes = [HANDLE]
|
||||||
|
CfGetPlaceholderInfo = dll.CfGetPlaceholderInfo
|
||||||
|
CfGetPlaceholderInfo.argtypes = [HANDLE, c_uint, PVOID, DWORD, PDWORD] # noqa: E501
|
||||||
|
CfGetPlaceholderInfo.restype = HRESULT
|
||||||
|
CfSetPinState = dll.CfSetPinState
|
||||||
|
CfSetPinState.argtypes = [HANDLE, c_uint, c_uint, LPOVERLAPPED]
|
||||||
|
CfSetPinState.restype = HRESULT
|
||||||
ERROR_INVALID_FUNCTION = 1
|
ERROR_INVALID_FUNCTION = 1
|
||||||
|
ERROR_MORE_DATA = 234
|
||||||
GetLastError = windll.Kernel32.GetLastError
|
GetLastError = windll.Kernel32.GetLastError
|
||||||
GetLastError.restype = DWORD
|
GetLastError.restype = DWORD
|
||||||
|
|
||||||
@@ -28,6 +89,41 @@ def hydrate_file(s: str):
|
|||||||
try:
|
try:
|
||||||
CfOpenFileWithOplock(s, CF_OPEN_FILE_FLAG_NONE, byref(h))
|
CfOpenFileWithOplock(s, CF_OPEN_FILE_FLAG_NONE, byref(h))
|
||||||
CfHydratePlaceholder(h, 0, -1, 0, LPVOID())
|
CfHydratePlaceholder(h, 0, -1, 0, LPVOID())
|
||||||
|
except OSError as e:
|
||||||
|
if GetLastError() != ERROR_INVALID_FUNCTION:
|
||||||
|
# File is not cloud file
|
||||||
|
if e.winerror != -2147024520:
|
||||||
|
CfCloseHandle(h)
|
||||||
|
raise e
|
||||||
|
CfCloseHandle(h)
|
||||||
|
|
||||||
|
|
||||||
|
def get_info(s: str, standard: bool = False):
|
||||||
|
h = HANDLE()
|
||||||
|
i = CF_PLACEHOLDER_STANDARD_INFO() if standard else CF_PLACEHOLDER_BASIC_INFO() # noqa: E501
|
||||||
|
t = CF_PLACEHOLDER_INFO_STANDARD if standard else CF_PLACEHOLDER_INFO_BASIC
|
||||||
|
le = DWORD()
|
||||||
|
si = DWORD(sizeof(i))
|
||||||
|
try:
|
||||||
|
CfOpenFileWithOplock(s, CF_OPEN_FILE_FLAG_FOREGROUND, byref(h))
|
||||||
|
CfGetPlaceholderInfo(h, t, byref(i), si, byref(le))
|
||||||
|
except OSError as e:
|
||||||
|
ee = GetLastError()
|
||||||
|
if ee == ERROR_MORE_DATA:
|
||||||
|
CfCloseHandle(h)
|
||||||
|
return i
|
||||||
|
if ee != ERROR_INVALID_FUNCTION:
|
||||||
|
CfCloseHandle(h)
|
||||||
|
raise e
|
||||||
|
CfCloseHandle(h)
|
||||||
|
return i
|
||||||
|
|
||||||
|
|
||||||
|
def unpin_file(s: str):
|
||||||
|
h = HANDLE()
|
||||||
|
try:
|
||||||
|
CfOpenFileWithOplock(s, CF_OPEN_FILE_FLAG_FOREGROUND, byref(h))
|
||||||
|
CfSetPinState(h, CF_PIN_STATE_UNPINNED, CF_SET_PIN_FLAG_NONE, None)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if GetLastError() != ERROR_INVALID_FUNCTION:
|
if GetLastError() != ERROR_INVALID_FUNCTION:
|
||||||
CfCloseHandle(h)
|
CfCloseHandle(h)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class OptAction(IntEnum):
|
|||||||
RESTORE = 1
|
RESTORE = 1
|
||||||
LIST = 2
|
LIST = 2
|
||||||
LIST_LEVELDB_KEY = 3
|
LIST_LEVELDB_KEY = 3
|
||||||
|
VERSION = 4
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_str(v: str) -> IntEnum:
|
def from_str(v: str) -> IntEnum:
|
||||||
@@ -28,6 +29,8 @@ class OptAction(IntEnum):
|
|||||||
return OptAction.LIST
|
return OptAction.LIST
|
||||||
elif t == 'list_leveldb_key':
|
elif t == 'list_leveldb_key':
|
||||||
return OptAction.LIST_LEVELDB_KEY
|
return OptAction.LIST_LEVELDB_KEY
|
||||||
|
elif t == "version":
|
||||||
|
return OptAction.VERSION
|
||||||
else:
|
else:
|
||||||
raise TypeError('Must be str.')
|
raise TypeError('Must be str.')
|
||||||
|
|
||||||
@@ -59,7 +62,7 @@ class Opts:
|
|||||||
re = OptAction.from_str(cm[0])
|
re = OptAction.from_str(cm[0])
|
||||||
if re is not None:
|
if re is not None:
|
||||||
self.action = re
|
self.action = re
|
||||||
if re == OptAction.LIST:
|
if re == OptAction.LIST or re == OptAction.VERSION:
|
||||||
return
|
return
|
||||||
elif re == OptAction.LIST_LEVELDB_KEY:
|
elif re == OptAction.LIST_LEVELDB_KEY:
|
||||||
if len(cm) == 1:
|
if len(cm) == 1:
|
||||||
@@ -79,6 +82,7 @@ class Opts:
|
|||||||
print('''game-backuper [options] [backup|restore] [<game names> [...]]
|
print('''game-backuper [options] [backup|restore] [<game names> [...]]
|
||||||
game-backuper [options] list
|
game-backuper [options] list
|
||||||
game-backuper [options] list_leveldb_key [<db_path> [...]]
|
game-backuper [options] list_leveldb_key [<db_path> [...]]
|
||||||
|
game-backuper version Print library support message.
|
||||||
Options:
|
Options:
|
||||||
-h, --help Print help message.
|
-h, --help Print help message.
|
||||||
-c, --config <path> Set config file.
|
-c, --config <path> Set config file.
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ if have_gzip:
|
|||||||
self._f = None
|
self._f = None
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
if self._f is None:
|
||||||
|
return
|
||||||
self._f.close()
|
self._f.close()
|
||||||
|
|
||||||
def compress(self, data: bytes) -> bytes:
|
def compress(self, data: bytes) -> bytes:
|
||||||
@@ -90,6 +92,8 @@ if have_gzip:
|
|||||||
self._f = None
|
self._f = None
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
if self._f is None:
|
||||||
|
return
|
||||||
self._f.close()
|
self._f.close()
|
||||||
|
|
||||||
def read(self, len: int) -> bytes:
|
def read(self, len: int) -> bytes:
|
||||||
|
|||||||
@@ -21,9 +21,14 @@ class BasicOption:
|
|||||||
_enable_pcre2 = None
|
_enable_pcre2 = None
|
||||||
_encrypt_files = None
|
_encrypt_files = None
|
||||||
_compress_config = None
|
_compress_config = None
|
||||||
|
_disable_compress = False
|
||||||
|
_protect_filename = None
|
||||||
|
_unpin_file = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def compress_config(self) -> CompressConfig:
|
def compress_config(self) -> CompressConfig:
|
||||||
|
if self._disable_compress:
|
||||||
|
return None
|
||||||
if self._compress_config is not None:
|
if self._compress_config is not None:
|
||||||
return self._compress_config
|
return self._compress_config
|
||||||
prog = getattr(self, "_prog", None)
|
prog = getattr(self, "_prog", None)
|
||||||
@@ -64,6 +69,20 @@ class BasicOption:
|
|||||||
return cfg._encrypt_files
|
return cfg._encrypt_files
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def protect_filename(self) -> bool:
|
||||||
|
if self._protect_filename is not None:
|
||||||
|
return self._protect_filename and self.encrypt_files
|
||||||
|
prog = getattr(self, "_prog", None)
|
||||||
|
if prog is not None:
|
||||||
|
if prog._protect_filename is not None:
|
||||||
|
return prog._protect_filename and self.encrypt_files
|
||||||
|
cfg = getattr(self, "_cfg", None)
|
||||||
|
if cfg is not None:
|
||||||
|
if cfg._protect_filename is not None:
|
||||||
|
return cfg._protect_filename and self.encrypt_files
|
||||||
|
return False
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def remove_old_files(self) -> bool:
|
def remove_old_files(self) -> bool:
|
||||||
if self._remove_old_files is not None:
|
if self._remove_old_files is not None:
|
||||||
@@ -78,11 +97,27 @@ class BasicOption:
|
|||||||
return cfg._remove_old_files
|
return cfg._remove_old_files
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def unpin_file(self) -> bool:
|
||||||
|
if self._unpin_file is not None:
|
||||||
|
return self._unpin_file
|
||||||
|
prog = getattr(self, "_prog", None)
|
||||||
|
if prog is not None:
|
||||||
|
if prog._unpin_file is not None:
|
||||||
|
return prog._unpin_file
|
||||||
|
cfg = getattr(self, "_cfg", None)
|
||||||
|
if cfg is not None:
|
||||||
|
if cfg._unpin_file is not None:
|
||||||
|
return cfg._unpin_file
|
||||||
|
return False
|
||||||
|
|
||||||
def parse_all(self, data=None):
|
def parse_all(self, data=None):
|
||||||
self.parse_compress_config(data)
|
self.parse_compress_config(data)
|
||||||
self.parse_remove_old_files(data)
|
self.parse_remove_old_files(data)
|
||||||
self.parse_enable_pcre2(data)
|
self.parse_enable_pcre2(data)
|
||||||
self.parse_encrypt_files(data)
|
self.parse_encrypt_files(data)
|
||||||
|
self.parse_protect_filename(data)
|
||||||
|
self.parse_unpin_file(data)
|
||||||
|
|
||||||
def parse_compress_config(self, data=None):
|
def parse_compress_config(self, data=None):
|
||||||
if data is None:
|
if data is None:
|
||||||
@@ -93,6 +128,8 @@ class BasicOption:
|
|||||||
self._compress_config = CompressConfig(v, data.get("compress_level")) # noqa: E501
|
self._compress_config = CompressConfig(v, data.get("compress_level")) # noqa: E501
|
||||||
elif v is not None:
|
elif v is not None:
|
||||||
raise TypeError('compress_method option should be str or None.') # noqa: E501
|
raise TypeError('compress_method option should be str or None.') # noqa: E501
|
||||||
|
else:
|
||||||
|
self._disable_compress = True
|
||||||
|
|
||||||
def parse_enable_pcre2(self, data=None):
|
def parse_enable_pcre2(self, data=None):
|
||||||
if data is None:
|
if data is None:
|
||||||
@@ -116,6 +153,17 @@ class BasicOption:
|
|||||||
raise TypeError('encrypt_files option must be a boolean.')
|
raise TypeError('encrypt_files option must be a boolean.')
|
||||||
del v
|
del v
|
||||||
|
|
||||||
|
def parse_protect_filename(self, data=None):
|
||||||
|
if data is None:
|
||||||
|
data = getattr(self, 'data')
|
||||||
|
if 'protect_filename' in data:
|
||||||
|
v = data['protect_filename']
|
||||||
|
if isinstance(v, bool):
|
||||||
|
self._protect_filename = v
|
||||||
|
else:
|
||||||
|
raise TypeError('protect_filename option must be a boolean.')
|
||||||
|
del v
|
||||||
|
|
||||||
def parse_remove_old_files(self, data=None):
|
def parse_remove_old_files(self, data=None):
|
||||||
if data is None:
|
if data is None:
|
||||||
data = getattr(self, 'data')
|
data = getattr(self, 'data')
|
||||||
@@ -127,6 +175,17 @@ class BasicOption:
|
|||||||
raise TypeError('remove_old_files option must be a boolean.')
|
raise TypeError('remove_old_files option must be a boolean.')
|
||||||
del v
|
del v
|
||||||
|
|
||||||
|
def parse_unpin_file(self, data=None):
|
||||||
|
if data is None:
|
||||||
|
data = getattr(self, 'data')
|
||||||
|
if 'unpin_file' in data:
|
||||||
|
v = data['unpin_file']
|
||||||
|
if isinstance(v, bool):
|
||||||
|
self._unpin_file = v
|
||||||
|
else:
|
||||||
|
raise TypeError('unpin_file option must be a boolean.')
|
||||||
|
del v
|
||||||
|
|
||||||
|
|
||||||
class NFBasicOption:
|
class NFBasicOption:
|
||||||
"""Basic options which is included in config, program."""
|
"""Basic options which is included in config, program."""
|
||||||
@@ -438,7 +497,14 @@ class Program(BasicOption, NFBasicOption):
|
|||||||
continue
|
continue
|
||||||
return r
|
return r
|
||||||
elif t == 'leveldb':
|
elif t == 'leveldb':
|
||||||
if relpath(i['name'], name) == '.':
|
if isabs(i['path']):
|
||||||
|
n = i['name']
|
||||||
|
else:
|
||||||
|
n = i['path']
|
||||||
|
if 'name' in i and isinstance(i['name'], str):
|
||||||
|
if i['name'] != '':
|
||||||
|
n = i['name']
|
||||||
|
if relpath(n, name) == '.':
|
||||||
return ConfigOLeveldb(i, self._cfg, self)
|
return ConfigOLeveldb(i, self._cfg, self)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
@@ -453,6 +519,7 @@ class Config(BasicOption, NFBasicOption):
|
|||||||
dest = ''
|
dest = ''
|
||||||
encrypt_db = False
|
encrypt_db = False
|
||||||
db_password = None
|
db_password = None
|
||||||
|
db_path = None
|
||||||
progs = []
|
progs = []
|
||||||
progs_name = []
|
progs_name = []
|
||||||
|
|
||||||
@@ -476,6 +543,10 @@ class Config(BasicOption, NFBasicOption):
|
|||||||
if not isinstance(t['db_password'], str):
|
if not isinstance(t['db_password'], str):
|
||||||
raise ValueError('db_password should be a string.')
|
raise ValueError('db_password should be a string.')
|
||||||
self.db_password = t['db_password']
|
self.db_password = t['db_password']
|
||||||
|
if 'db_path' in t:
|
||||||
|
if not isinstance(t['db_path'], str):
|
||||||
|
raise ValueError('db_path should be a string.')
|
||||||
|
self.db_path = t['db_path']
|
||||||
if 'programs' not in t:
|
if 'programs' not in t:
|
||||||
raise ValueError("No programs found.")
|
raise ValueError("No programs found.")
|
||||||
self.parse_all(t)
|
self.parse_all(t)
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ def getpass(prompt, cfg: Config) -> str:
|
|||||||
|
|
||||||
class Db:
|
class Db:
|
||||||
VERSION = [1, 0, 0, 2]
|
VERSION = [1, 0, 0, 2]
|
||||||
|
fn = None
|
||||||
|
|
||||||
def __check_database(self) -> bool:
|
def __check_database(self) -> bool:
|
||||||
self.__updateExistsTable()
|
self.__updateExistsTable()
|
||||||
@@ -85,9 +86,10 @@ class Db:
|
|||||||
def __init__(self, config: Config, opts: Opts):
|
def __init__(self, config: Config, opts: Opts):
|
||||||
self._cfg = config
|
self._cfg = config
|
||||||
self._opt = opts
|
self._opt = opts
|
||||||
fn = join(config.dest, "data.db")
|
self.fn = config.db_path if config.db_path else join(
|
||||||
hydrate_file_if_needed(fn)
|
config.dest, "data.db")
|
||||||
self.db = connect(fn, check_same_thread=False)
|
hydrate_file_if_needed(self.fn)
|
||||||
|
self.db = connect(self.fn, check_same_thread=False)
|
||||||
if config.encrypt_db:
|
if config.encrypt_db:
|
||||||
passpharse = getpass('Please input the password of the database:', config) # noqa: E501
|
passpharse = getpass('Please input the password of the database:', config) # noqa: E501
|
||||||
if not self.encrypted:
|
if not self.encrypted:
|
||||||
@@ -100,8 +102,8 @@ class Db:
|
|||||||
db.execute(q)
|
db.execute(q)
|
||||||
self.db.close()
|
self.db.close()
|
||||||
db.close()
|
db.close()
|
||||||
move(tfn, fn)
|
move(tfn, self.fn)
|
||||||
self.db = connect(fn, check_same_thread=False)
|
self.db = connect(self.fn, check_same_thread=False)
|
||||||
elif opts.change_key:
|
elif opts.change_key:
|
||||||
self.__set_encrypt_key(passpharse)
|
self.__set_encrypt_key(passpharse)
|
||||||
passpharse = getpass('Please input new password of the database:', config) # noqa: E501
|
passpharse = getpass('Please input new password of the database:', config) # noqa: E501
|
||||||
@@ -114,8 +116,8 @@ class Db:
|
|||||||
db.execute(q)
|
db.execute(q)
|
||||||
self.db.close()
|
self.db.close()
|
||||||
db.close()
|
db.close()
|
||||||
move(tfn, fn)
|
move(tfn, self.fn)
|
||||||
self.db = connect(fn, check_same_thread=False)
|
self.db = connect(self.fn, check_same_thread=False)
|
||||||
self.__set_encrypt_key(passpharse)
|
self.__set_encrypt_key(passpharse)
|
||||||
else:
|
else:
|
||||||
if self.encrypted:
|
if self.encrypted:
|
||||||
@@ -129,8 +131,8 @@ class Db:
|
|||||||
db.execute(q)
|
db.execute(q)
|
||||||
self.db.close()
|
self.db.close()
|
||||||
db.close()
|
db.close()
|
||||||
move(tfn, fn)
|
move(tfn, self.fn)
|
||||||
self.db = connect(fn, check_same_thread=False)
|
self.db = connect(self.fn, check_same_thread=False)
|
||||||
if opts.optimize_db:
|
if opts.optimize_db:
|
||||||
self.db.execute('VACUUM;')
|
self.db.execute('VACUUM;')
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
@@ -171,7 +173,7 @@ class Db:
|
|||||||
tuple(self.VERSION))
|
tuple(self.VERSION))
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
def add_file(self, f: File):
|
def add_file(self, f: File, commited: bool = True):
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self.db.execute('INSERT INTO files (file, size, program, hash) VALUES (?, ?, ?, ?);', # noqa: E501
|
self.db.execute('INSERT INTO files (file, size, program, hash) VALUES (?, ?, ?, ?);', # noqa: E501
|
||||||
(f.file, f.size, f.program, f.hash))
|
(f.file, f.size, f.program, f.hash))
|
||||||
@@ -188,12 +190,13 @@ class Db:
|
|||||||
(f.program, f.file))
|
(f.program, f.file))
|
||||||
for i in cur:
|
for i in cur:
|
||||||
self.db.execute('INSERT INTO encrypted_files VALUES (?, ?, ?, ?, ?, ?);', (i[0], f.key, f.iv, f.crc32, f.x_compress_type, f.compressed_size)) # noqa: E501
|
self.db.execute('INSERT INTO encrypted_files VALUES (?, ?, ?, ?, ?, ?);', (i[0], f.key, f.iv, f.crc32, f.x_compress_type, f.compressed_size)) # noqa: E501
|
||||||
self.db.commit()
|
if commited:
|
||||||
|
self.db.commit()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def encrypted(self):
|
def encrypted(self):
|
||||||
try:
|
try:
|
||||||
con = connect(join(self._cfg.dest, 'data.db'))
|
con = connect(self.fn)
|
||||||
con.execute('SELECT count(*) FROM sqlite_master;')
|
con.execute('SELECT count(*) FROM sqlite_master;')
|
||||||
con.close()
|
con.close()
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from game_backuper.filetype import FileType
|
|||||||
from platform import system
|
from platform import system
|
||||||
if system() == "Windows":
|
if system() == "Windows":
|
||||||
try:
|
try:
|
||||||
from game_backuper.cfapi import hydrate_file
|
from game_backuper.cfapi import hydrate_file, unpin_file
|
||||||
have_cfapi = True
|
have_cfapi = True
|
||||||
except Exception:
|
except Exception:
|
||||||
have_cfapi = False
|
have_cfapi = False
|
||||||
@@ -150,3 +150,10 @@ def remove_unencryped_files(loc: str, prog: str, name: str):
|
|||||||
remove(loc)
|
remove(loc)
|
||||||
print(f'{prog}: Removed {loc}({name})')
|
print(f'{prog}: Removed {loc}({name})')
|
||||||
remove_compress_files(loc, prog, name)
|
remove_compress_files(loc, prog, name)
|
||||||
|
|
||||||
|
|
||||||
|
def unpin_file_if_needed(fn: str):
|
||||||
|
if not have_cfapi:
|
||||||
|
return
|
||||||
|
if exists(fn):
|
||||||
|
unpin_file(fn)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from game_backuper.config import Config
|
from game_backuper.config import Config
|
||||||
from game_backuper.cml import Opts
|
from game_backuper.cml import Opts, OptAction
|
||||||
from game_backuper.db import Db
|
from game_backuper.db import Db
|
||||||
from game_backuper.backuper import Backuper
|
from game_backuper.backuper import Backuper
|
||||||
from os import makedirs
|
from os import makedirs
|
||||||
@@ -11,6 +11,28 @@ def main(cm=None):
|
|||||||
import sys
|
import sys
|
||||||
cm = sys.argv[1:]
|
cm = sys.argv[1:]
|
||||||
cml = Opts(cm)
|
cml = Opts(cm)
|
||||||
|
if cml.action == OptAction.VERSION:
|
||||||
|
from game_backuper.compress import (
|
||||||
|
have_brotli,
|
||||||
|
have_bz2,
|
||||||
|
have_gzip,
|
||||||
|
have_lzip,
|
||||||
|
have_lzma,
|
||||||
|
have_snappy,
|
||||||
|
have_zstd,
|
||||||
|
)
|
||||||
|
from game_backuper.leveldb import have_leveldb
|
||||||
|
from game_backuper.regexp import have_pcre2
|
||||||
|
print("Brotli support:", have_brotli)
|
||||||
|
print("BZip2 support:", have_bz2)
|
||||||
|
print("GZip support:", have_gzip)
|
||||||
|
print("LZip support:", have_lzip)
|
||||||
|
print("LZMA support:", have_lzma)
|
||||||
|
print("Snappy support:", have_snappy)
|
||||||
|
print("ZSTD support:", have_zstd)
|
||||||
|
print("LevelDB support:", have_leveldb)
|
||||||
|
print("PCRE2 support:", have_pcre2)
|
||||||
|
return 0
|
||||||
cfg = Config(cml.config_file)
|
cfg = Config(cml.config_file)
|
||||||
if not exists(cfg.dest):
|
if not exists(cfg.dest):
|
||||||
makedirs(cfg.dest)
|
makedirs(cfg.dest)
|
||||||
|
|||||||
@@ -48,4 +48,4 @@ def wildcards_to_regex(s: str, **k):
|
|||||||
s = s.replace(i, f"\\{i}")
|
s = s.replace(i, f"\\{i}")
|
||||||
s = s.replace("*", ".*")
|
s = s.replace("*", ".*")
|
||||||
s = s.replace("?", ".")
|
s = s.replace("?", ".")
|
||||||
return Regex(s, **k)
|
return Regex(f"^{s}$", **k)
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ class RestoreTask(Thread):
|
|||||||
nam = r.real_name
|
nam = r.real_name
|
||||||
if f.encrypted:
|
if f.encrypted:
|
||||||
src = join(self.cfg.dest, '.encrypt', prog, fn)
|
src = join(self.cfg.dest, '.encrypt', prog, fn)
|
||||||
|
if not exists(src):
|
||||||
|
src = join(self.cfg.dest, '.encrypt', '.id', str(f.id)) # noqa: E501
|
||||||
else:
|
else:
|
||||||
src = join(self.cfg.dest, prog, fn)
|
src = join(self.cfg.dest, prog, fn)
|
||||||
c = r.compress_config
|
c = r.compress_config
|
||||||
@@ -62,6 +64,7 @@ class RestoreTask(Thread):
|
|||||||
if tf.size == f.size and tf.hash == f.hash:
|
if tf.size == f.size and tf.hash == f.hash:
|
||||||
print(f'{prog}: Skip {fn}')
|
print(f'{prog}: Skip {fn}')
|
||||||
continue
|
continue
|
||||||
|
mkdir_for_file(dest)
|
||||||
if f.encrypted:
|
if f.encrypted:
|
||||||
decrypt_file(src, dest, f, fn, prog, CompressConfig(f.compressed_type.to_str()) if f.compressed else None) # noqa: E501
|
decrypt_file(src, dest, f, fn, prog, CompressConfig(f.compressed_type.to_str()) if f.compressed else None) # noqa: E501
|
||||||
elif c is None:
|
elif c is None:
|
||||||
@@ -78,6 +81,8 @@ class RestoreTask(Thread):
|
|||||||
nam = r.real_name
|
nam = r.real_name
|
||||||
if f.encrypted:
|
if f.encrypted:
|
||||||
src = join(self.cfg.dest, '.encrypt', prog, fn + '.db')
|
src = join(self.cfg.dest, '.encrypt', prog, fn + '.db')
|
||||||
|
if not exists(src):
|
||||||
|
src = join(self.cfg.dest, '.encrypt', '.id', str(f.id)) # noqa: E501
|
||||||
else:
|
else:
|
||||||
src = join(self.cfg.dest, prog, fn + '.db')
|
src = join(self.cfg.dest, prog, fn + '.db')
|
||||||
c = r.compress_config
|
c = r.compress_config
|
||||||
|
|||||||
54
setup.py
54
setup.py
@@ -1,7 +1,7 @@
|
|||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
import sys
|
import sys
|
||||||
from version import version, dversion
|
from version import version
|
||||||
from setuptools import Extension
|
from setuptools import setup, Extension
|
||||||
try:
|
try:
|
||||||
from Cython.Build import cythonize
|
from Cython.Build import cythonize
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -18,44 +18,6 @@ if '--without-zstd' in sys.argv:
|
|||||||
else:
|
else:
|
||||||
ext_modules.append(Extension("game_backuper._zstd", ["game_backuper/_zstd.pyx"], libraries=["zstd"]))
|
ext_modules.append(Extension("game_backuper._zstd", ["game_backuper/_zstd.pyx"], libraries=["zstd"]))
|
||||||
|
|
||||||
if "py2exe" in sys.argv:
|
|
||||||
from distutils.core import setup
|
|
||||||
import py2exe
|
|
||||||
params = {
|
|
||||||
"console": [{
|
|
||||||
'script': "game_backuper/__main__.py",
|
|
||||||
"dest_base": 'game-backuper',
|
|
||||||
'version': version,
|
|
||||||
'product_name': 'game-backuper',
|
|
||||||
'product_version': dversion,
|
|
||||||
'company_name': 'lifegpc',
|
|
||||||
'description': 'A game backuper',
|
|
||||||
}],
|
|
||||||
"options": {
|
|
||||||
"py2exe": {
|
|
||||||
"optimize": 2,
|
|
||||||
"compressed": 1,
|
|
||||||
"excludes": ["pydoc", "unittest"],
|
|
||||||
"includes": ["cryptography.utils", "_cffi_backend", "sqlite3.dump"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"zipfile": None,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
from setuptools import setup
|
|
||||||
params = {
|
|
||||||
"install_requires": ["pyyaml"],
|
|
||||||
'entry_points': {
|
|
||||||
'console_scripts': ['game-backuper = game_backuper:start']
|
|
||||||
},
|
|
||||||
"extras_require": {
|
|
||||||
"leveldb": "plyvel",
|
|
||||||
"lzip": "lzip",
|
|
||||||
"snappy": "python-snappy",
|
|
||||||
"brotli": "brotli",
|
|
||||||
},
|
|
||||||
"python_requires": ">=3.6"
|
|
||||||
}
|
|
||||||
setup(
|
setup(
|
||||||
name="game-backuper",
|
name="game-backuper",
|
||||||
version=version,
|
version=version,
|
||||||
@@ -72,5 +34,15 @@ setup(
|
|||||||
keywords="backup",
|
keywords="backup",
|
||||||
packages=["game_backuper"],
|
packages=["game_backuper"],
|
||||||
ext_modules=cythonize(ext_modules, compiler_directives={'language_level': "3"}),
|
ext_modules=cythonize(ext_modules, compiler_directives={'language_level': "3"}),
|
||||||
**params
|
install_requires=["pyyaml"],
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': ['game-backuper = game_backuper:start']
|
||||||
|
},
|
||||||
|
extras_require={
|
||||||
|
"leveldb": "plyvel",
|
||||||
|
"lzip": "lzip",
|
||||||
|
"snappy": "python-snappy",
|
||||||
|
"brotli": "brotli",
|
||||||
|
},
|
||||||
|
python_requires=">=3.6"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user