Coverage for /home/agp/Documents/me/code/brotation/brotation/brotation.py: 97%

# Coverage for /home/agp/Documents/me/code/brotation/brotation/brotation.py : 97%

## 73 statements 71 run 2 missing 0 excluded

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

3 Rotation Policy

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

5 Rotation 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

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

13

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

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

16 and hence all files correspond with the last update.

17

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

19 behind 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

49 1. get day, week, month numbers

50 2. if month goes to 1, then last month became last year.

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

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

53

54# steps:

55

56 1. create the dictionary of existing rotate files.

57 2. compte the current day.

58 3. chech above rules in order.

59 4. try to get the last

60

61

62 d{n} --> d{}

63

64"""

65

66 import re

67 import os

68 import tarfile

69 from subprocess import Popen, PIPE

70

71

72 from gutools.tools import expandpath, soft_update, fileiter

73 from gutools.ushift import Rotator, FQItem

74

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

76# logger

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

78 from gutools.loggers import logger, \

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

80 log = logger(__name__)

81

82

83 rotate_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

86 def 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

94 def 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

112 class 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