Repository navigation
Expand file tree
/
Copy pathbackup-rotate.py
More file actions
179 lines (143 loc) · 6.74 KB
/
Copy pathbackup-rotate.py
File metadata and controls
179 lines (143 loc) · 6.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/env python3
import os
import stat
import datetime
import shutil
import getopt
import sys
from dateutil import relativedelta as rd
# default configuration
daily_params = {'type': 'daily', 'cnt': 7, 'arch_dir': 'daily'}
weekly_params = {'type': 'weekly', 'cnt': 4, 'arch_dir': 'weekly'}
monthly_params = {'type': 'monthly', 'cnt': 12, 'arch_dir': 'monthly'}
yearly_params = {'type': 'yearly', 'cnt': 5, 'arch_dir': 'yearly'}
archive_path = os.getcwd()
log_path = os.getcwd()
rotate_steps = []
def print_help():
print('backup-rotate (c) 2015-2021, Stanislav V. Emets\n'
'Usage:\n'
' backup-rotate [-a <path>| --archive=<path>]\n'
' -h | --help - print this help\n'
' -l <path> | --log=<path> - path to log file, default value: current directory\n'
' -a <path> | --archive=<path> - path to archive directory, default value: current directory\n'
' -d <number> | --daily=<number> - how many keep daily copies, default value 7\n'
' -w <number> | --weekly - how many keep weekly copies, default value 4\n'
' -m <number> | --monthly=<number> - how many keep monthly copies, default value 12\n'
' -y <number> | --yearly=<number> - how many keep yearly copies, default value 5\n')
def parse_args():
global daily_params, weekly_params, monthly_params, yearly_params, archive_path, log_path
status = True
try:
opts, args = getopt.getopt(sys.argv[1:], "hl:a:d:w:m:y:",
["help", "log=", "archive=", "daily=", "weekly=", "monthly=", "yearly=", ])
except getopt.GetoptError as err:
status = False
for opt, arg in opts:
if opt in ('-h', '--help'):
print_help()
status = False
elif opt in ('-l', '--log'):
log_path = arg
elif opt in ('-a', '--archive'):
archive_path = arg
elif opt in ('-d', '--daily'):
daily_params['cnt'] = int(arg)
elif opt in ('-w', '--weekly'):
weekly_params['cnt'] = int(arg)
elif opt in ('-m', '--monthly'):
monthly_params['cnt'] = int(arg)
elif opt in ('-y', '--yearly'):
yearly_params['cnt'] = int(arg)
return status
def get_file_list(step, dir_path):
archive_file_list = {}
cur_dir = os.path.join(dir_path, step['arch_dir'])
file_list = os.listdir(cur_dir)
for file_name in file_list:
cur_file = os.path.join(cur_dir, file_name)
file_attrib = os.stat(cur_file)
if stat.S_ISREG(file_attrib.st_mode):
archive_file_list[file_attrib.st_mtime] = cur_file
return archive_file_list
def rotate_dir(step, next_step, dates, files):
if len(next_step) > 0:
for file_date in dates:
# находим самый свежий файл в next_step
next_step_files = get_file_list(next_step, archive_path)
if len(next_step_files) > 0:
next_step_dates = sorted(next_step_files)
next_step_last_date = datetime.datetime.fromtimestamp(next_step_dates[len(next_step_dates) - 1])
step_file_date = datetime.datetime.fromtimestamp(file_date)
# к дате добавляем интервал в зависимости от типа следующего шага
if next_step['type'] == 'weekly':
next_file_date = next_step_last_date + rd.relativedelta(weeks=+1)
elif next_step['type'] == 'monthly':
next_file_date = next_step_last_date + rd.relativedelta(months=+1)
elif next_step['type'] == 'yearly':
next_file_date = next_step_last_date + rd.relativedelta(years=+1)
# если дата текущего файла больше или равна получившейся, то файл перемещаем в next_step, иначе удаляем
if step_file_date >= next_file_date:
# переносим в следующий шаг
new_file_name = os.path.split(files[file_date])
new_file_name = os.path.join(archive_path, next_step['arch_dir'], new_file_name[1])
shutil.move(files[file_date], new_file_name)
else:
# удаляем
#os.remove(os.path.join(archive_path, step['arch_dir'], files[file_date]))
os.remove(files[file_date])
else: # ничего нет пока в следующем шаге, просто перемещаем
new_file_name = os.path.split(files[file_date])
new_file_name = os.path.join(archive_path, next_step['arch_dir'], new_file_name[1])
shutil.move(files[file_date], new_file_name)
else: # следующего шага нет, просто удаляем лишние файлы
for file_date in dates:
new_file_name = os.path.split(files[file_date])
new_file_name = os.path.join(archive_path, next_step['arch_dir'], new_file_name[1])
shutil.move(files[file_date], new_file_name)
def main():
global rotate_steps
has_errors = False
if not parse_args():
print_help()
sys.exit(1)
if daily_params['cnt'] > 0:
rotate_steps.append(daily_params)
if weekly_params['cnt'] > 0:
rotate_steps.append(weekly_params)
if monthly_params['cnt'] > 0:
rotate_steps.append(monthly_params)
if yearly_params['cnt'] > 0:
rotate_steps.append(yearly_params)
# проверим соответствие заданного каталога требуемой структуре
dir_list = os.listdir(archive_path)
if len(dir_list) == 0:
print_help()
sys.exit(2)
else:
for cur_step in rotate_steps:
try:
dir_list.index(cur_step['arch_dir'])
except ValueError:
has_errors = True
if has_errors:
print_help()
sys.exit(3)
# начинаем ротацию архива
next_step_index = 1
for cur_step in rotate_steps:
archive_files = get_file_list(cur_step, archive_path)
archive_dates = sorted(archive_files)
rotate_dates = []
if len(archive_dates) > cur_step['cnt']:
rotate_dates = archive_dates[0:len(archive_dates) - cur_step['cnt']]
if next_step_index > 0:
next_step = rotate_steps[next_step_index]
else:
next_step = {}
rotate_dir(cur_step, next_step, rotate_dates, archive_files)
next_step_index += 1
if next_step_index > (len(rotate_steps) - 1):
next_step_index = 0
if __name__ == '__main__':
main()