Skip to content

Commit

Permalink
v1.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
zyxkad committed May 7, 2022
1 parent 93f0a79 commit b732c9d
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 68 deletions.
7 changes: 7 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@

# Smart Backup

### Feture

- Support *Full Backup* / *Incremental Backup* / *Differential Backup
- Support **timed backup**
- Support **auto clean** out of ranged backups

### Notes

- The backup created with `!!smb make(full)` is **not going to** *auto clean*
- **Don't** change/remove files in `./smt_backup`, if you really need, please remove the **whole folder**
- If you remove `./smt_backup` when *the plugin is running*, that probably will cause some *unknown error*
8 changes: 8 additions & 0 deletions README_zh.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

# Smart Backup

### 特性

- 一个集 *全盘备份* / *增量备份* / *差异备份* 为一体的插件
- 支持**定时备份**
- 支持备份清理功能

### 注意

- 使用`!!smb make(full)`创建的备份**不会***自动清理*
- **不要**手动清理/修改`./smt_backup`内部文件, 如果需要, 请将**整个文件夹**都删除
- *插件运行时*若删除`./smt_backup`可能会导致一些*未知错误*
4 changes: 2 additions & 2 deletions mcdreforged.plugin.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"id": "smart_backup",
"version": "1.3.0",
"version": "1.4.0",
"name": "SmartBackup",
"description": {
"en_us": "A Minecraft Backup Plugin",
"zh_cn": "一个依赖MCDR的备份插件"
"zh_cn": "一个多功能Minecraft存档备份插件"
},
"author": "zyxkad",
"link": "https://github.com/kmcsr/smart_backup_mcdr",
Expand Down
32 changes: 22 additions & 10 deletions smart_backup/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def _timed_make_backup():
global backup_timer
backup_timer = None
source = GL.SERVER_INS.get_plugin_command_source()
make_backup(source, time.strftime('SMB timed backup: %Y-%m-%d %H:%M:%S', time.localtime()))
make_backup(source, time.strftime('SMB timed backup: %Y-%m-%d %H:%M:%S', time.localtime()), timed=True)

@GL.on_load_call
def on_load(server: MCDR.PluginServerInterface):
Expand All @@ -65,16 +65,18 @@ def clean_backup():
start_time = time.time()
before_size = get_total_size(GL.Config.backup_path)
while len(GL.Manager.index.fulln) > GL.Config.full_backup_limit:
bid = GL.Manager.index.fulln[GL.Config.get_next_clean_index()]
bid = GL.Manager.index.pop_outdated()
if bid is None:
break
bk = GL.Manager.load(bid)
broadcast_message('Removing backup {id}:{comment}({date})...'.format(id=bk.id, comment=bk.comment, date=bk.strftime))
broadcast_message('Outdated backup {id}:{comment}({date}).'.format(id=bk.id, comment=bk.comment, date=bk.strftime))
bk.remove()
used_time = time.time() - start_time
free_size = before_size - get_total_size(GL.Config.backup_path)
broadcast_message('Backup cleaned up, use {0:.2f} sec, free up disk {1}'.format(used_time, format_size(free_size)))

@new_job('make backup')
def make_backup(source: MCDR.CommandSource, comment: str, mode: BackupMode = None, clean: bool = True):
def make_backup(source: MCDR.CommandSource, comment: str, mode: BackupMode = None, *, timed: bool = False, clean: bool = True):
cancel_backup_timer()
server = source.get_server()
broadcast_message('Making backup "{}"'.format(comment))
Expand All @@ -99,20 +101,30 @@ def c():
elif mode == BackupMode.DIFFERENTIAL:
GL.Config.cache['incremental_count'] = 0

backup = GL.Manager.create(mode, comment,
outdate: int
if timed:
outdate = GL.Config.get_next_protect_time()
outdate = int(time.time() // 60 + outdate) if outdate > 0 else 0
else:
outdate = 1
backup = GL.Manager.create(mode, comment, outdate,
source.get_server().get_mcdr_config()['working_directory'], GL.Config.backup_needs, GL.Config.backup_ignores, saved=False)
send_message(source, 'Saving backup "{}"'.format(comment), log=True)
backup.save()
send_message(source, 'Saved backup "{}"'.format(comment), log=True)
for _ in map(server.execute, GL.Config.after_backup): pass
if GL.SERVER_INS.is_server_running:
for _ in map(server.execute, GL.Config.after_backup): pass
used_time = time.time() - start_time
broadcast_message('Backup finished, use {:.2f} sec'.format(used_time))
broadcast_message('Backup finished, use {0:.2f} sec, {1:}'.format(used_time,
format_size(get_total_size(os.path.join(GL.Config.backup_path, backup.id)))))
_flush_backup_timer()
if clean and mode == BackupMode.FULL and GL.Config.full_backup_limit > 0 and len(GL.Manager.index.fulln) > GL.Config.full_backup_limit:
broadcast_message('Backup out of range, automagically cleaning backups...')
next_job_call(clean_backup)
swap_job_call(clean_backup)

if len(GL.Config.start_backup_trigger_info) > 0:
if not GL.SERVER_INS.is_server_running:
c()
elif len(GL.Config.start_backup_trigger_info) > 0:
ping_job()
global game_saved_callback
game_saved_callback = new_thread(after_job_wrapper(c))
Expand All @@ -135,7 +147,7 @@ def restore_backup(source: MCDR.CommandSource, bid: str):
broadcast_message('Stopping the server')
server.stop()
server.wait_for_start()
make_backup(source, f'Server before restore({bid}) backup', mode=BackupMode.FULL, clean=False)
swap_job_call(make_backup, source, f'Server before restore({bid}) backup', mode=BackupMode.FULL, clean=False)
log_info('Restoring...')
bk.restore(server.get_mcdr_config()['working_directory'], GL.Config.backup_needs, GL.Config.backup_ignores)
log_info('Starting the server')
Expand Down
56 changes: 29 additions & 27 deletions smart_backup/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@
Prefix = '!!smb'

HelpMessage = '''
{0} help 显示帮助信息
{0} status 显示插件状态
{0} list [<limit> = 10] 列出<limit>条备份
{0} query <id> 查询备份详细信息
{0} make [<comment> = 'None'] 创建新备份(差异/全盘)
{0} makefull [<comment> = 'None'] 创建全盘备份
{0} remove <id> [<force> = false] 删除指定备份(及其子备份)
{0} restore <id> [<force> = false] 回档至指定备份
{0} confirm 确认操作
{0} abort 取消操作
{0} reload 重新加载配置文件
{0} save 保存配置文件
{0} help :Show this help message
{0} status :Show the plugin status
{0} list [<limit> = 10] :List up to <limit> backups
{0} query <id> :Query full information of backup
{0} make [<comment> = 'None'] :Create new backup
{0} makefull [<comment> = 'None'] :Create new full backup
{0} remove <id> [<force> = false] :Remove backup
{0} restore <id> [<force> = false] :Restore backup
{0} confirm :Confirm operation
{0} abort :Cancel operation
{0} reload :Reload config file
{0} save :Save config file
'''.strip().format(Prefix)

def register(server: MCDR.PluginServerInterface):
Expand Down Expand Up @@ -71,22 +71,24 @@ def command_status(source: MCDR.CommandSource):
' Size: ' + format_size(bs),
' Count: ' + str(len(GL.Manager.listID())),
join_rtext('Timed backup:', MCDR.RText('disabled' if api.backup_timer is None else 'enabled', color=MCDR.RColor.yellow)),
join_rtext('Latest backup:', lc)
join_rtext('Last backup:', lc)
)

@new_thread
def command_list_backup(source: MCDR.CommandSource, limit: int):
bks = GL.Manager.list(limit)
lines = [MCDR.RTextList(b.z_index * '|',
new_command('{0} restore {1}'.format(Prefix, b.id), b.id).h(join_rtext(
'ID: ' + b.id,
'Comment: ' + b.comment,
'Date: ' + b.strftime,
'Size: ' + format_size(get_total_size(os.path.join(GL.Config.backup_path, b.id))),
sep='\n')),
': ' + b.comment)
for b in bks]
send_block_message(source, 'Last {} backups:'.format(len(bks)), *lines)
send_message(source, GL.BIG_BLOCK_BEFOR)
send_message(source, 'Last backups (up to {} lines):'.format(limit))
for b in bks:
send_message(source, MCDR.RTextList(b.z_index * '|',
new_command('{0} restore {1}'.format(Prefix, b.id), b.id).h(join_rtext(
'ID: ' + b.id,
'Comment: ' + b.comment,
'Date: ' + b.strftime,
'Size: ' + format_size(get_total_size(os.path.join(GL.Config.backup_path, b.id))),
sep='\n')),
': ' + b.comment))
send_message(source, GL.BIG_BLOCK_AFTER)

@new_thread
def command_query_backup(source: MCDR.CommandSource, bid: str):
Expand Down Expand Up @@ -119,7 +121,7 @@ def command_makefull(source: MCDR.CommandSource, comment: str):
@new_job('restore')
def command_restore(source: MCDR.CommandSource, bid: str, force: bool = False):
if force:
next_job_call(api.restore_backup, source, bid)
swap_job_call(api.restore_backup, source, bid)
return

if not bid.startswith('0x'):
Expand Down Expand Up @@ -150,7 +152,7 @@ def ab():
return
timeout -= 1
confirm_map.pop(None, None)
next_job_call(api.restore_backup, source, bid)
swap_job_call(api.restore_backup, source, bid)

ping_job()
register_confirm(source.player if source.is_player else '',
Expand All @@ -168,7 +170,7 @@ def ab():
@new_job('remove')
def command_remove(source: MCDR.CommandSource, bid: str, force: bool = False):
if force:
next_job_call(api.remove_backup, source, bid)
swap_job_call(api.remove_backup, source, bid)
return

if not bid.startswith('0x'):
Expand All @@ -182,7 +184,7 @@ def command_remove(source: MCDR.CommandSource, bid: str, force: bool = False):

ping_job()
register_confirm(source.player if source.is_player else '',
lambda: next_job_call(api.remove_backup, source, bid),
after_job_wrapper(lambda: swap_job_call(api.remove_backup, source, bid)),
after_job_wrapper(lambda: send_message(source, 'Canceled removing')), timeout=15)
send_message(source, MCDR.RText('Are you sure to remove "{}" and child backup for it?'.format(bk.comment)).
h('id: ' + bk.id,
Expand Down
36 changes: 26 additions & 10 deletions smart_backup/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,20 @@
BIG_BLOCK_AFTER = ':::: {0} v{1} ============'

class SMBConfig(MCDR.Serializable):
incremental_backup_limit: int = 12
differential_backup_limit: int = 8
incremental_backup_limit: int = 2
differential_backup_limit: int = 4
full_backup_limit: int = 8
full_backup_clean_indexs: List[int] = [1, 2, 2, 3, 3, 3, 0]
backup_interval: int = 60 * 60 * 1 # 1 hours
full_backup_protect_times: List[int] = [ # unit minute
60 * 24 * 15, # 15 days
60 * 24 * 7, # 1 week
60 * 24, # 1 day
60 * 24 * 3,
60 * 24,
0, # 0 sec (non-protection)
60 * 24 * 3,
60 * 24,
]
backup_interval: int = 60 * 60 * 1 # 1 hour
restore_timeout: int = 30
backup_path: str = './smt_backups'
overwrite_path: str = './smt_backup_overwrite'
Expand Down Expand Up @@ -66,19 +75,26 @@ def cache(self, cache):
assert isinstance(cache, dict)
self._cache = cache

def get_next_clean_index(self):
if 'clean_II' not in self.cache or self.cache['clean_II'] >= len(self.full_backup_clean_indexs):
self.cache['clean_II'] = 0
ind: int = self.full_backup_clean_indexs[self.cache['clean_II']]
self.cache['clean_II'] = (self.cache['clean_II'] + 1) % len(self.full_backup_clean_indexs)
return ind
def get_next_protect_time(self):
pti: int = self.cache['protect_time_ind'] + 1 if 'protect_time_ind' in self.cache else 0
if pti > len(self.full_backup_protect_times):
if pti < self.full_backup_limit:
return 0
pti = 0
self.cache['protect_time_ind'] = pti
return self.full_backup_protect_times[pti] * 60

def _fix(self):
pass # TODO

@classmethod
def load(cls, source: MCDR.CommandSource = None):
global Config, Manager
cache: dict = {}
oldConfig: SMBConfig = Config
Config = SERVER_INS.load_config_simple(target_class=cls, source_to_reply=source)
Config._fix()

cf: str = os.path.join(Config.backup_path, 'cache.json')
if os.path.exists(cf):
with open(cf, 'r') as fd:
Expand Down
Loading

0 comments on commit b732c9d

Please sign in to comment.