Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1"""Main module. 

2 

3Rotation Policy 

4================ 

5Rotation policy is also supported: 

6 

7- 7-daily (mon-sun) : e.g. flowmonitor.git.d6.7z 

8- 1-per-week of the month: e.g. flowmonitor.git.w0.7z 

9- 1 per month: e.g. flowmonitor.git.m9.7z 

10- 1 per year: e.g. flowmonitor.git.y17.7z 

11 

12So we need 7 + 5 + 12 = 24 files to cover a whole year. 

13 

14In each backup the dairy backup is generated and then is cloned 

15modifying the rotation names, so the compressed file only is sent once 

16and hence all files correspond with the last update. 

17 

18As soon a new day comes, some of the previous backups files will be left 

19behind creating the rotation sequence. 

20 

21'womanly_speech.d2.xz', 

22'womanly_speech.d5.xz', 

23'womanly_speech.m01.xz', 

24'womanly_speech.m11.7z', 

25'womanly_speech.m11.xz', 

26'womanly_speech.m12.7z', 

27'womanly_speech.m12.xz', 

28'womanly_speech.m3.7z', 

29'womanly_speech.m5.7z', 

30'womanly_speech.m6.xz', 

31'womanly_speech.m8.xz', 

32'womanly_speech.w0.xz', 

33'womanly_speech.w13.xz', 

34'womanly_speech.w17.7z', 

35'womanly_speech.w17.xz', 

36'womanly_speech.w2.7z', 

37'womanly_speech.w26.7z', 

38'womanly_speech.w39.7z', 

39'womanly_speech.w9.7z', 

40'womanly_speech.y11.7z', 

41'womanly_speech.y13.xz', 

42'womanly_speech.y14.7z', 

43'womanly_speech.y15.xz', 

44'womanly_speech.y17.7z', 

45'womanly_speech.y20.xz' 

46 

47# rules: 

48 

491. get day, week, month numbers 

502. if month goes to 1, then last month became last year. 

513. if week goes back to 0, then last week became last month. 

524. if days goes back to 0, then last day became last week. 

53 

54# steps: 

55 

561. create the dictionary of existing rotate files. 

572. compte the current day. 

583. chech above rules in order. 

594. try to get the last  

60 

61 

62d{n} --> d{} 

63  

64""" 

65 

66import re 

67import os 

68import tarfile 

69from subprocess import Popen, PIPE 

70 

71 

72from gutools.tools import expandpath, soft_update, fileiter 

73from gutools.ushift import Rotator, FQItem 

74 

75# -------------------------------------------------- 

76# logger 

77# -------------------------------------------------- 

78from gutools.loggers import logger, \ 

79 trace, exception, debug, info, warn, error 

80log = logger(__name__) 

81 

82 

83rotate_match = re.compile(r'(?P<key>.+?)\.(?P<rot>(d[0-6]|w[0-9]|w[0-4][0-9]|w5[1-3]|\d|m[0-9]|m1[0-2]?|m0[0-9]|y\d{2}))\.(?P<ext>7z|xz)$', re.DOTALL).match 

84 

85 

86def extract_info(path): 

87 """*Extract rotate info from path.* 

88 """ 

89 m = rotate_match(path) 

90 if m: 

91 return FQItem(m.groupdict()) 

92 

93 

94def parse_config_file(path): 

95 config = dict() 

96 path = expandpath(path) 

97 if os.path.exists(path): 

98 reg = re.compile(r'^(?P<key>\w+)\s*="?(?P<value>.*?)"?$') 

99 reg2 = re.compile(r'([^\s]+)\s*') 

100 for line in open(path, 'rt'): 

101 m = reg.match(line) 

102 if m: 

103 key, value = m.groups() 

104 value2 = [x.group() for x in reg2.finditer(value)] 

105 if len(value2) > 1: 

106 value = value2 

107 config[key] = value 

108 

109 return config 

110 

111 

112class BackupRotator(Rotator): 

113 RANGES = [ 

114 ('d', 0, 6), 

115 ('w', 0, 4), 

116 ('m', 1, 12), 

117 ('y', 10, None), 

118 ] 

119 

120 FLAGS = { 

121 'xz': ['cJf', '--threads=0'], 

122 'gz': ['cxf'], 

123 } 

124 """A class ... 

125  

126 """ 

127 

128 def __init__(self, root, config=None, config_file=None, *args, **kw): 

129 super().__init__(self.RANGES) 

130 self.root = root 

131 self.config_file = config_file or '~/.config/backups.conf' 

132 self.config = config 

133 debug(f"root: {root}") 

134 

135 def _parse(self, element): 

136 item = extract_info(element) 

137 if item: 

138 item['rot'], value = item['rot'][0], int(item['rot'][1:]) 

139 item[item['rot']] = value 

140 return item 

141 

142 def _rebuild(self, item): 

143 """*Reconstruct an element from parsed data.*""" 

144 return "{key}.{rot}{value}.{ext}".format(**item) 

145 # return f"{item['rot']}{item[item['rot']]}" + "[{d}.{w}.{m}.{y}]".format(**item) 

146 

147 def apply(self, items): 

148 """*xxx* 

149  

150 - load config if not already downloaded. 

151 - xax 

152  

153  

154 """ 

155 if self.config is None: 

156 self.config = parse_config_file(self.config_file) 

157 

158 root = '/tmp/kk' 

159 root = '~/Documents/me/code/gutools' 

160 where = '/tmp/' 

161 

162 firstone = None 

163 root = expandpath(root) 

164 for path in items: 

165 if not firstone: 

166 firstone = path 

167 output = os.path.join(where, repr(path)) 

168 self.compress(output, root) 

169 foo = 1 

170 foo = 1 

171 

172 def compress(self, output, root, cwd='/'): 

173 """*Compress folder using tar and xz utitlities.*  

174 - 'output': the compressed tar file name. 

175 - 'root': folder to compress. 

176 - 'cwd' is a relative path from where the files would be included 

177  

178 The approach is to use system call directly for faster execution. 

179 Using tarfile library implies python intervention, that will be 

180 slower than system call, specially running in a raspberry.  

181 """ 

182 ext = os.path.splitext(output)[-1][1:] 

183 # with tarfile.open(output, f"w:{ext}") as tar: 

184 #tar.add(root, arcname=os.path.basename(root)) 

185 

186 root = root.lstrip(cwd) 

187 name = root.replace('/', '.') 

188 cmd = ['tar'] 

189 cmd.extend(self.FLAGS[ext]) 

190 cmd.extend([output, root]) 

191 with Popen(cmd, stdout=PIPE, cwd=cwd) as proc: 

192 print(proc.stdout.read()) 

193 foo = 1 

194 foo = 1 

195 

196 def load_config(self, config): 

197 """ 

198  

199 """ 

200 

201 foo = 1 

202 

203