蓝屏后文件读取失败
曾有惨痛的经历:工作的过程中电脑蓝屏了,重启之后之前编辑的主文件损坏、无法打开,一整个下午的东西全部无法找回了。
当时用的软件不是像Word/Powerpoint那样、缓存目录一般可以找到临时文件;我遇到的情况是,不完整的内容保存和覆盖了原始文件、导致文件结构已经损坏,而磁盘再无其他版本的备份,因此只能重新编写。
为了避免这样的事情,工作的时候实时保存工作目录是非常必要的。因此这里使用python写了一个自动备份的脚本。
不同于软件中的”自动保存“,该程序按照给定的时间间隔、把指定工作目录的文件复制到新的备份目录中去,并且维护保持多个不同时间的拷贝,也支持永久备份选项。
自动备份工作目录
整个程序在名为Backup.py
的文件中。
首先引入需要的模块。click
用于设定command line中的选项设定;time
和datetime
设定备份时候的时间戳以及计时;shutil
中的copyfile
负责复制文件,如果需要保持文件的metadata、可以用shutil.copy2
替换copyfile
。
import click
import os
import time
import datetime
from shutil import copy, copyfile
LOG = print
定义好复制整个目录的函数。这里简便期间使用递归啥的,也没仔细地debug, 但是能用就行:
def copy_a_folder(src, dest):
"""
copy files from src to dest.
Invokers shall make sure the src folder exists.
"""
names = os.listdir(src)
if not os.path.exists(dest):
os.mkdir(dest)
for name in names:
src_new = src + "\\" + name
dest_new = dest + "\\" + name
if os.path.isdir(src_new):
if not os.path.exists(dest_new):
os.mkdir(dest_new)
copy_a_folder(src_new, dest_new) # REG
else:
copyfile(src_new, dest_new)
除此之外,为了防止人为失误、待备份地目录文件夹太大了,我们至少给出一个磁盘空间的计算:
def size_of_folder(src, maxdepth=None) -> float:
"""
return the size of a target folder in bytes.
src: the folder
maxdepth: int | None, maximal depth inside the folder.
"""
total = 0.0
maxdepth = -1 if maxdepth is None else maxdepth
if maxdepth >= 0:
if os.path.isfile(src):
total += os.path.getsize(src)
else:
with os.scandir(src) as it:
depth = maxdepth - 1
for entry in it:
if entry.is_file():
total += entry.stat().st_size
else:
total += size_of_folder(entry.path, depth)
#
#
#
return total
同样用了递归,代码逻辑不是最好的,但是能用。
接下来就是起主要作用的函数了。
我们使用了@click.XXX
来提供命令行选项。
我们实现的功能流程大概是这样的:
- 首先,用户通过命令行的参数指定备份的源路径、备份路径,即时备份的时间间隔和队列总数目,永久备份时间间隔等
- 然后,程序计算出需要备份的源目录所需磁盘空间,让用户选择是否继续
- 然后备份开始:程序每分钟活动一次,判断该时刻是否需要备份。
- 最后,用户
Ctrl+C
时结束。
代码如下:
@click.command()
@click.option("--src", type=str, help="source folder: your working folder. Default './'.")
@click.option("--dest", type=str, help="the backup main folder. Default 'E://Backup'.")
@click.option("--i", type=int, help='the time interval for instant backup in minutes. Default 5.')
@click.option("--n", type=int, help='the size of instant backup queue. Bigger than this value, folders will be covered. Default 10.')
@click.option("--t", type=float, help='time in hour for hard backup. Default 6.')
def startbackup(src, dest, i, n, t) -> None:
"""
the main function for backup
"""
if src is None:
src = "./"
if dest is None:
dest = "E://Backup"
if i is None:
i = 5
if n is None:
n = 10
if t is None:
t = 6.0
LOG("Configs: \t\n",
"src: ", src,
"\t\ni:", i, " n:", n, " t:", t, "\t\n")
if not os.path.exists(src):
LOG("Error src folder: not a folder or not exists.")
exit(-1)
size_in_bytes = size_of_folder(src, 10)
LOG("src info: %.2fMB size in depth %d\t\n"%(size_in_bytes/1024.**2, 10))
LOG("Press `c` to continue, or `q` to quit: ")
c = input()
if c.lower().startswith('q'):
exit(0)
elif c.lower().startswith('c'):
pass
else:
exit(0) # other than `c`, also exit.
LOG("# -------------------------------------- #\t\n")
LOG(
"[BACKUP STARTS] ",
"src: %s"%src,
"dest: %s"%dest,
"Mini-Interval: %d minutes."%i,
"Hard-Interval: %.2f hours."%t,
"Mini-Cache Size: %d."%n,
"",
"Press Ctrl+C to stop.",
"="*30,
sep="\t\n"
)
tstart = time.time()
minute = 60.0
hour = 60.0 * 60.0 # [sec]
dests = [os.path.join(dest, "B%d"%k) for k in range(1, n+1)]
k = 0
miniintervalstart = tstart
hardbackstart = tstart
FIRST = True
while True:
end = time.time()
if end - miniintervalstart >= i * minute or FIRST:
# mini backup
now = datetime.datetime.now()
tmp = now.strftime("%Y-%m-%d%H:%M:%S:")
desti = dests[k]
LOG(tmp, "MINI BACKUP TO ", desti)
copy_a_folder(src, desti)
k = (k + 1) % n # next backup folder.
miniintervalstart = end
if end - hardbackstart >= t * hour or FIRST:
# hard backup
now = datetime.datetime.now()
foldername = 'BACK_' + now.strftime("%Y-%m-%d-%H%M%S")
desti = os.path.join(dest, foldername)
tmp = now.strftime("%Y-%m-%d%H:%M:%S:")
LOG(tmp, "HARD BACKUP TO ", desti)
copy_a_folder(src, desti)
hardbackstart = end
FIRST = False
time.sleep(60)
最后,需要使用__main__
的环境调用:
if __name__ == "__main__":
startbackup()
应用时需要安装python3环境。
比如我在D盘有个目录需要备份,结构如下:
D:\I_am_a_working_folder
│ F0.txt
│
├─tempFolder1
│ │ F0.txt
│ │
│ └─subFolder1
│ f11.txt
│
└─tempFolder2
F2.txt
F3.txt
然后,上述python代码放到了这个路径:C:\Backup\Backup.py
想要放置备份的主目录是E:\Backup,并且已在E盘建立了该目录。命令行输入python
后看到python能用(即python路径加入了环境变量,否则可以使用python.exe的全路径代替)。
接下来,自动备份启动。注意这是一行指令,确认好空格、半角引号。然后回车:
PS C:\Windows\system32> python -u C:\Backup\Backup.py --src "D:\I_am_a_working_folder" --dest "E:\Backup" --i 5 --n 10 --t 4
Configs:
src: D:\I_am_a_working_folder
i: 5 n: 10 t: 4.0
src info: 0.05MB size in depth 10
Press `c` to continue, or `q` to quit:
此时Configs后面给出了当前的备份设定:
src: 源目录。
i: 5 每隔5分钟复制一份即时备份?
n: 10 即时备份的总的备份数目为10。当超出的时候、最旧的目录将会被覆盖掉。
t: 4.0 每隔4.0个小时,保存一份永久备份。这些文件将会被永久储存在dest的目录内的子目录中、并以时间戳标识(如果未来不需要,则应择机手动删除)。
然后src info后面表示了源目录的文件总大小,(这里的目录层次最多是10,一般够了,如果需要改程序即可QAQ)。 如果这个size太大,一定要仔细考虑是否真的要这么备份!
输入c
回车,即可开启自动备份。然后你可以随意编辑工作目录D:\I_am_a_working_folder
内部的内容了。但不要关闭这个命令终端!
c
# -------------------------------------- #
[BACKUP STARTS]
src: D:\I_am_a_working_folder
dest: E:\Backup
Mini-Interval: 5 minutes.
Hard-Interval: 4.00 hours.
Mini-Cache Size: 10.
Press Ctrl+C to stop.
==============================
2024-02-2502:06:25: MINI BACKUP TO E:\Backup\B1
2024-02-2502:06:25: HARD BACKUP TO E:\Backup\BACK_2024-02-25-020625
2024-02-2502:11:25: MINI BACKUP TO E:\Backup\B2
...
备份过程中,会不断记录备份行为。
要想结束这个备份过程有几种可选方式:
- 在这个终端窗口,英文输入状态下按
Ctrl+C
即可中断执行、停止备份,然后关闭窗口即可。 - 直接关闭终端窗口
打开dest的目录(E:\Backup),可以找到备份:
B1, B2, B3, … 是临时备份文件,由–i 和–n指定备份测略。
BACK_2024-02-25-020625等为永久备份,由–t指定间隔小时数目(支持小数)。
这样,妈妈再也不用担心工作文件突然死机丢掉啦~
====
环境: Windows, python3.8