blob: 5516bb0198e3c351c8b53972c389d5e5fa9f59c1 [file] [log] [blame]
Max Rasskazovb07aacb2015-05-29 19:54:52 +03001#-*- coding: utf-8 -*-
2
Max Rasskazov2f93dd22015-06-04 14:20:44 +03003import os
Max Rasskazovb07aacb2015-05-29 19:54:52 +03004import re
Max Rasskazov2f93dd22015-06-04 14:20:44 +03005
Max Rasskazovb1b09a62015-06-03 15:21:35 +03006import utils
Max Rasskazovb07aacb2015-05-29 19:54:52 +03007
Max Rasskazovb1b09a62015-06-03 15:21:35 +03008
9logger = utils.logger.getChild('RsyncUrl')
Max Rasskazovb07aacb2015-05-29 19:54:52 +030010
11
12class RsyncUrl(object):
13
14 def __init__(self, remote_url):
15
Max Rasskazovd1200f32015-06-17 01:00:09 +030016 if not remote_url:
17 msg = 'Specified rsync url == "{}"'.format(remote_url)
18 logger.error(msg)
19 raise Exception(msg)
20
Max Rasskazov64f77bf2015-06-02 15:22:48 +030021 self._url = remote_url
Max Rasskazovb07aacb2015-05-29 19:54:52 +030022 self._url_type = False
23
24 self.regexps = {
25 # ssh: [USER@]HOST:SRC
26 'ssh': re.compile(
27 r'^'
28 r'(?P<user>[-\w]+@)?'
29 r'(?P<host>[-\.\w]+){1}'
30 r':'
Max Rasskazov95e001b2015-05-30 23:53:22 +030031 r'(?P<path>(~{0,1}[\w/-]*)){1}'
Max Rasskazovb07aacb2015-05-29 19:54:52 +030032 r'$'
33 ),
34 # rsync: [USER@]HOST::SRC
35 'rsync1': re.compile(
36 r'^'
37 r'(?P<user>[-\w]+@)?'
38 r'(?P<host>[-\.\w]+){1}'
39 r'::'
Max Rasskazov64f77bf2015-06-02 15:22:48 +030040 r'(?P<module>[\w-]+){1}'
41 r'(?P<path>[\w/-]*)?'
Max Rasskazovb07aacb2015-05-29 19:54:52 +030042 r'$'
43 ),
44 # rsync://[USER@]HOST[:PORT]/SRC
45 'rsync2': re.compile(
46 r'^rsync://'
47 r'(?P<user>[-\w]+@)?'
48 r'(?P<host>[-\.\w]+){1}'
49 r'(?P<port>:[\d]+)?'
Max Rasskazov64f77bf2015-06-02 15:22:48 +030050 r'(?P<module>/[\w-]*)?'
51 r'(?P<path>[\w/-]*)?'
Max Rasskazovb07aacb2015-05-29 19:54:52 +030052 r'$'
53 ),
54 # local/path/to/directory
55 'path': re.compile(
56 r'^'
Max Rasskazov95e001b2015-05-30 23:53:22 +030057 r'(?P<path>(~{0,1}[\w/-]+)){1}'
Max Rasskazovb07aacb2015-05-29 19:54:52 +030058 r'$'
59 ),
60 }
61
Max Rasskazov64f77bf2015-06-02 15:22:48 +030062 self._match = self._get_matching_regexp()
Max Rasskazovb07aacb2015-05-29 19:54:52 +030063 if self.match is None:
Max Rasskazov64f77bf2015-06-02 15:22:48 +030064 self.user, self.host, self.module, self.port, self.path = \
65 None, None, None, None, None
Max Rasskazovd1200f32015-06-17 01:00:09 +030066 self._root = self.url_dir()
Max Rasskazovb07aacb2015-05-29 19:54:52 +030067 else:
68 self._parse_rsync_url(self.match)
69
70 def _get_matching_regexp(self):
71 regexps = self._get_all_matching_regexps()
72 regexps_len = len(regexps)
73 #if regexps_len > 1:
74 # raise Exception('Rsync location {} matches with {} regexps {}'
75 # ''.format(self.url, len(regexps), str(regexps)))
76 # TODO: Possible may be better remove this raise and keep
77 # only warning with request to fail bug. rsync will parse this
78 # remote later
79 if regexps_len != 1:
80 logger.warn('Rsync location "{}" matches with {} regexps: {}.'
81 'Please fail a bug on {} if it is wrong.'
82 ''.format(self.url, len(regexps), str(regexps), '...'))
83 if regexps_len == 0:
84 self._url_type = None
85 return None
86 else:
87 return regexps[0]
88
89 def _get_all_matching_regexps(self):
90 regexps = list()
91 for url_type, regexp in self.regexps.items():
92 match = regexp.match(self.url)
93 if match is not None:
94 if self.url_type is False:
95 self._url_type = url_type
96 regexps.append(regexp)
Max Rasskazovb07aacb2015-05-29 19:54:52 +030097 return regexps
98
99 def _parse_rsync_url(self, regexp):
100 # parse remote url
101
Max Rasskazov64f77bf2015-06-02 15:22:48 +0300102 for match in re.finditer(regexp, self._url):
Max Rasskazovb07aacb2015-05-29 19:54:52 +0300103
104 self.path = match.group('path')
Max Rasskazov64f77bf2015-06-02 15:22:48 +0300105 if self.path is None:
106 self.path = ''
Max Rasskazovb07aacb2015-05-29 19:54:52 +0300107
108 try:
109 self.host = match.group('host')
110 except IndexError:
111 self.host = None
112
113 try:
114 self.user = match.group('user')
115 except IndexError:
116 self.user = None
117 else:
118 if self.user is not None:
119 self.user = self.user.strip('@')
120
121 try:
122 self.port = match.group('port')
123 except IndexError:
124 self.port = None
125 else:
126 if self.port is not None:
127 self.port = int(self.port.strip(':'))
128
Max Rasskazov64f77bf2015-06-02 15:22:48 +0300129 try:
130 self.module = match.group('module')
131 except IndexError:
132 self.module = None
133 else:
134 if self.module is not None:
135 self.module = self.module.strip('/')
136 if not self.module:
137 self.module = None
138
Max Rasskazovd1200f32015-06-17 01:00:09 +0300139 if self.url_type == 'ssh':
140 if self.path == '':
141 self.path = '~'
142
143 if self.path.startswith('/'):
144 self._rootpath = '/'
145 else:
146 self._rootpath = '~/'
147
148 self._netloc = '{}:'.format(self.url.split(':')[0])
149 self._root = '{}{}'.format(self._netloc, self._rootpath)
150 self._url = '{}{}'.format(self._netloc, self.path)
151
152 elif self.url_type.startswith('rsync'):
153 if self.path == '':
154 self.path = '/'
155 self._rootpath = '/'
156
157 if self.url_type == 'rsync1':
158 root_parts = ['{}::'.format(self.url.split('::')[0])]
159 if self.module is not None:
160 root_parts.append('{}'.format(self.module))
161
162 elif self.url_type == 'rsync2':
163 root_parts = ['rsync://']
164 if self.user is not None:
165 root_parts.append('{}@'.format(self.user))
166 root_parts.append('{}'.format(self.host))
167 if self.port is not None:
168 root_parts.append(':{}'.format(self.port))
169 if self.module is not None:
170 root_parts.append('/{}'.format(self.module))
171
172 self._netloc = ''.join(root_parts)
173 if self.module is not None:
174 root_parts.append('{}'.format(self._rootpath))
175 self._root = ''.join(root_parts)
176 self._url = '{}{}'.format(self._netloc, self.path)
177
178 elif self.url_type == 'path':
179 if self.path == '':
180 self.path = '.'
181 self._rootpath = self.a_dir(self.path)
182 self._netloc = None
183 self._root = self._rootpath
184 self._url = '{}'.format(self.path)
185
186 else:
187 self._netloc = None
188 self._root = self._url
189 self._url = '{}'.format(self._rootpath)
190
191
Max Rasskazov64f77bf2015-06-02 15:22:48 +0300192 @property
193 def match(self):
194 return self._match
195
Max Rasskazovb07aacb2015-05-29 19:54:52 +0300196 @property
197 def url_type(self):
198 return self._url_type
Max Rasskazov64f77bf2015-06-02 15:22:48 +0300199
200 @property
201 def is_valid(self):
202 if self.match is None:
203 return False
204 if self.path in (None, False):
205 return False
206 if self.url_type != 'path':
207 if self.host in ('', None, False):
208 return False
209 if self.url_type.startswith('rsync'):
210 if self.module is None:
211 return False
212 return True
213
Max Rasskazovba34b142015-06-04 17:12:27 +0300214 def _fn_join(self, *parts):
215 ''' Joins filenames with ignoring empty parts (None, '', etc)'''
216 parts = [_ for _ in parts if _]
Max Rasskazov2f93dd22015-06-04 14:20:44 +0300217
Max Rasskazovba34b142015-06-04 17:12:27 +0300218 if len(parts) > 0:
219 if parts[-1].endswith(os.path.sep):
220 isdir = True
221 else:
222 isdir = False
223 first, parts = parts[0], parts[1:]
224 else:
225 return ''
226
227 if first is None:
228 first = ''
229 if len(first) > 1:
230 while first.endswith(os.path.sep):
231 first = first[:-1]
232
233 subs = os.path.sep.join([_ for _ in parts if _]).split(os.path.sep)
Max Rasskazov2f93dd22015-06-04 14:20:44 +0300234 subs = [_ for _ in subs if _]
235
Max Rasskazovba34b142015-06-04 17:12:27 +0300236 result = re.sub(r'^//', r'/', os.path.sep.join([first, ] + subs))
Max Rasskazov2f93dd22015-06-04 14:20:44 +0300237 result = re.sub(r'([^:])//', r'\1/', result)
Max Rasskazovba34b142015-06-04 17:12:27 +0300238 if not result.endswith(os.path.sep) and isdir:
239 result += os.path.sep
Max Rasskazov2f93dd22015-06-04 14:20:44 +0300240 return result
241
Max Rasskazov64f77bf2015-06-02 15:22:48 +0300242 @property
243 def url(self):
244 return self._url
Max Rasskazov2f93dd22015-06-04 14:20:44 +0300245
Max Rasskazovd1200f32015-06-17 01:00:09 +0300246 @property
247 def root(self):
248 return self._root
249
250 def join(self, *parts):
251 return self._fn_join(*parts)
252
Max Rasskazovba34b142015-06-04 17:12:27 +0300253 def urljoin(self, *parts):
Max Rasskazovd1200f32015-06-17 01:00:09 +0300254 return self.join(self.url, *parts)
Max Rasskazovba34b142015-06-04 17:12:27 +0300255
Max Rasskazov46cc0732015-06-05 19:23:24 +0300256 def a_dir(self, *path):
Max Rasskazovba34b142015-06-04 17:12:27 +0300257 result = self._fn_join(*path)
Max Rasskazov2f93dd22015-06-04 14:20:44 +0300258 if not result.endswith('/'):
259 result += '/'
260 return result
261
Max Rasskazovbe9a2772015-06-05 20:15:11 +0300262 def url_dir(self, *path):
Max Rasskazov46cc0732015-06-05 19:23:24 +0300263 return self.a_dir(self.url, *path)
Max Rasskazovba34b142015-06-04 17:12:27 +0300264
Max Rasskazov46cc0732015-06-05 19:23:24 +0300265 def a_file(self, *path):
Max Rasskazovba34b142015-06-04 17:12:27 +0300266 result = self._fn_join(*path)
267 if len(result) > 1:
268 while result.endswith(os.path.sep):
269 result = result[:-1]
270 return result
271
Max Rasskazovbe9a2772015-06-05 20:15:11 +0300272 def url_file(self, *path):
Max Rasskazov46cc0732015-06-05 19:23:24 +0300273 return self.a_file(self.url, *path)