1
2
3
4
5 __doc__ = """GNUmed internetworking tools."""
6
7
8 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
9 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
10
11
12 import sys
13 import os.path
14 import logging
15 import urllib.request
16 import urllib.error
17 import zipfile
18 import webbrowser
19 import io
20
21
22
23 if __name__ == '__main__':
24 sys.path.insert(0, '../../')
25 from Gnumed.pycommon import gmLog2
26 from Gnumed.pycommon import gmTools
27 from Gnumed.pycommon import gmShellAPI
28 from Gnumed.pycommon import gmCfg2
29 from Gnumed.pycommon import gmMimeLib
30
31
32 _log = logging.getLogger('gm.net')
33
34
35
36
38
39
40 try:
41 webbrowser.open(url, new = new, autoraise = autoraise, **kwargs)
42 except (webbrowser.Error, OSError, UnicodeEncodeError):
43 _log.exception('error calling browser with url=%s', url)
44 return False
45 return True
46
62
63
64
67
69
70 _log.debug('downloading data pack from: %s', pack_url)
71 dp_fname = download_file(pack_url, filename = filename, suffix = 'zip')
72 _log.debug('downloading MD5 from: %s', md5_url)
73 md5_fname = download_file(md5_url, filename = dp_fname + '.md5')
74
75 md5_file = io.open(md5_fname, mode = 'rt', encoding = 'utf8')
76 md5_expected = md5_file.readline().strip('\n')
77 md5_file.close()
78 _log.debug('expected MD5: %s', md5_expected)
79 md5_calculated = gmTools.file2md5(dp_fname, return_hex = True)
80 _log.debug('calculated MD5: %s', md5_calculated)
81
82 if md5_calculated != md5_expected:
83 _log.error('mismatch of expected vs calculated MD5: [%s] vs [%s]', md5_expected, md5_calculated)
84 return (False, (md5_expected, md5_calculated))
85
86 return True, dp_fname
87
89
90 unzip_dir = os.path.splitext(filename)[0]
91 _log.debug('unzipping data pack into [%s]', unzip_dir)
92 gmTools.mkdir(unzip_dir)
93 try:
94 data_pack = zipfile.ZipFile(filename, 'r')
95 except (zipfile.BadZipfile):
96 _log.exception('cannot unzip data pack [%s]', filename)
97 gmLog2.log_stack_trace()
98 return None
99
100 data_pack.extractall(unzip_dir)
101
102 return unzip_dir
103
104
106 from Gnumed.pycommon import gmPsql
107 psql = gmPsql.Psql(conn)
108 sql_script = os.path.join(data_pack['unzip_dir'], 'install-data-pack.sql')
109 if psql.run(sql_script) == 0:
110 curs = conn.cursor()
111 curs.execute('select gm.log_script_insertion(%(name)s, %(ver)s)', {'name': data_pack['pack_url'], 'ver': 'current'})
112 curs.close()
113 conn.commit()
114 return True
115
116 _log.error('error installing data pack: %s', data_pack)
117 return False
118
120
121 if target_dir is None:
122 target_dir = gmTools.get_unique_filename(prefix = 'gm-dl-')
123
124 _log.debug('downloading [%s]', url)
125 _log.debug('unpacking into [%s]', target_dir)
126
127 gmTools.mkdir(directory = target_dir)
128
129
130
131 paths = gmTools.gmPaths()
132 local_script = os.path.join(paths.local_base_dir, '..', 'external-tools', 'gm-download_data')
133
134 candidates = ['gm-download_data', 'gm-download_data.bat', local_script, 'gm-download_data.bat']
135 args = ' %s %s' % (url, target_dir)
136
137 success = gmShellAPI.run_first_available_in_shell (
138 binaries = candidates,
139 args = args,
140 blocking = True,
141 run_last_one_anyway = True
142 )
143
144 if success:
145 return True, target_dir
146
147 _log.error('download failed')
148 return False, None
149
150
151
153 """
154 0: left == right
155 -1: left < right
156 1: left > right
157 """
158 if left_version == right_version:
159 _log.debug('same version: [%s] = [%s]', left_version, right_version)
160 return 0
161
162 left_parts = left_version.split('.')
163 right_parts = right_version.split('.')
164
165 tmp, left_major = gmTools.input2decimal('%s.%s' % (left_parts[0], left_parts[1]))
166 tmp, right_major = gmTools.input2decimal('%s.%s' % (right_parts[0], right_parts[1]))
167
168 if left_major < right_major:
169 _log.debug('left version [%s] < right version [%s]: major part', left_version, right_version)
170 return -1
171
172 if left_major > right_major:
173 _log.debug('left version [%s] > right version [%s]: major part', left_version, right_version)
174 return 1
175
176 tmp, left_part3 = gmTools.input2decimal(left_parts[2].replace('rc', '0.'))
177 tmp, right_part3 = gmTools.input2decimal(right_parts[2].replace('rc', '0.'))
178
179 if left_part3 < right_part3:
180 _log.debug('left version [%s] < right version [%s]: minor part', left_version, right_version)
181 return -1
182
183 if left_part3 > right_part3:
184 _log.debug('left version [%s] > right version [%s]: minor part', left_version, right_version)
185 return 1
186
187 return 0
188
189 -def check_for_update(url=None, current_branch=None, current_version=None, consider_latest_branch=False):
190 """Check for new releases at <url>.
191
192 Returns (bool, text).
193 True: new release available
194 False: up to date
195 None: don't know
196 """
197 if current_version == 'GIT HEAD':
198 _log.debug('GIT HEAD always up to date')
199 return (False, None)
200
201 try:
202 remote_file = urllib.request.urlopen(url)
203 except (urllib.error.URLError, ValueError, OSError, IOError):
204
205 _log.exception("cannot retrieve version file from [%s]", url)
206 return (None, _('Cannot retrieve version information from:\n\n%s') % url)
207
208 _log.debug('retrieving version information from [%s]', url)
209
210 cfg = gmCfg2.gmCfgData()
211 try:
212
213 cfg.add_stream_source(source = 'gm-versions', stream = remote_file, encoding = u'utf8')
214 except (UnicodeDecodeError):
215 remote_file.close()
216 _log.exception("cannot read version file from [%s]", url)
217 return (None, _('Cannot read version information from:\n\n%s') % url)
218
219 remote_file.close()
220
221 latest_branch = cfg.get('latest branch', 'branch', source_order = [('gm-versions', 'return')])
222 latest_release_on_latest_branch = cfg.get('branch %s' % latest_branch, 'latest release', source_order = [('gm-versions', 'return')])
223 latest_release_on_current_branch = cfg.get('branch %s' % current_branch, 'latest release', source_order = [('gm-versions', 'return')])
224
225 cfg.remove_source('gm-versions')
226
227 _log.info('current release: %s', current_version)
228 _log.info('current branch: %s', current_branch)
229 _log.info('latest release on current branch: %s', latest_release_on_current_branch)
230 _log.info('latest branch: %s', latest_branch)
231 _log.info('latest release on latest branch: %s', latest_release_on_latest_branch)
232
233
234 no_release_information_available = (
235 (
236 (latest_release_on_current_branch is None) and
237 (latest_release_on_latest_branch is None)
238 ) or (
239 not consider_latest_branch and
240 (latest_release_on_current_branch is None)
241 )
242 )
243 if no_release_information_available:
244 _log.warning('no release information available')
245 msg = _('There is no version information available from:\n\n%s') % url
246 return (None, msg)
247
248
249 if consider_latest_branch:
250 _log.debug('latest branch taken into account')
251 if latest_release_on_latest_branch is None:
252 if compare_versions(latest_release_on_current_branch, current_version) in [-1, 0]:
253 _log.debug('up to date: current version >= latest version on current branch and no latest branch available')
254 return (False, None)
255 else:
256 if compare_versions(latest_release_on_latest_branch, current_version) in [-1, 0]:
257 _log.debug('up to date: current version >= latest version on latest branch')
258 return (False, None)
259 else:
260 _log.debug('latest branch not taken into account')
261 if compare_versions(latest_release_on_current_branch, current_version) in [-1, 0]:
262 _log.debug('up to date: current version >= latest version on current branch')
263 return (False, None)
264
265 new_release_on_current_branch_available = (
266 (latest_release_on_current_branch is not None) and
267 (compare_versions(latest_release_on_current_branch, current_version) == 1)
268 )
269 _log.info('%snew release on current branch available', gmTools.bool2str(new_release_on_current_branch_available, '', 'no '))
270
271 new_release_on_latest_branch_available = (
272 (latest_branch is not None)
273 and
274 (
275 (latest_branch > current_branch) or (
276 (latest_branch == current_branch) and
277 (compare_versions(latest_release_on_latest_branch, current_version) == 1)
278 )
279 )
280 )
281 _log.info('%snew release on latest branch available', gmTools.bool2str(new_release_on_latest_branch_available, '', 'no '))
282
283 if not (new_release_on_current_branch_available or new_release_on_latest_branch_available):
284 _log.debug('up to date: no new releases available')
285 return (False, None)
286
287
288 msg = _('A new version of GNUmed is available.\n\n')
289 msg += _(' Your current version: "%s"\n') % current_version
290 if consider_latest_branch:
291 if new_release_on_current_branch_available:
292 msg += '\n'
293 msg += _(' New version: "%s"') % latest_release_on_current_branch
294 msg += '\n'
295 msg += _(' - bug fixes only\n')
296 msg += _(' - database fixups may be needed\n')
297 if new_release_on_latest_branch_available:
298 if current_branch != latest_branch:
299 msg += '\n'
300 msg += _(' New version: "%s"') % latest_release_on_latest_branch
301 msg += '\n'
302 msg += _(' - bug fixes and new features\n')
303 msg += _(' - database upgrade required\n')
304 else:
305 msg += '\n'
306 msg += _(' New version: "%s"') % latest_release_on_current_branch
307 msg += '\n'
308 msg += _(' - bug fixes only\n')
309 msg += _(' - database fixups may be needed\n')
310
311 msg += '\n\n'
312 msg += _(
313 'Note, however, that this version may not yet\n'
314 'be available *pre-packaged* for your system.'
315 )
316
317 msg += '\n\n'
318 msg += _('Details are found on <http://wiki.gnumed.de>.\n')
319 msg += '\n'
320 msg += _('Version information loaded from:\n\n %s') % url
321
322 return (True, msg)
323
324
325
326
327 default_mail_sender = 'gnumed@gmx.net'
328 default_mail_receiver = 'gnumed-devel@gnu.org'
329 default_mail_server = 'mail.gmx.net'
330
331
332
333 from email.mime.text import MIMEText
334 from email.mime.multipart import MIMEMultipart
335 from email.mime.image import MIMEImage
336 from email.mime.audio import MIMEAudio
337 from email.mime.application import MIMEApplication
338
339 -def compose_email(sender=None, receiver=None, message=None, subject=None, files2attach=None):
340
341
342
343
344 if message is None:
345 raise ValueError('<message> is None, cannot compose email')
346
347 message = message.lstrip().lstrip('\r\n').lstrip()
348
349 if sender is None:
350 sender = default_mail_sender
351
352 if receiver is None:
353 receiver = [default_mail_receiver]
354
355 if subject is None:
356 subject = 'compose_email() test'
357
358 if files2attach is None:
359 email = MIMEText(message, 'plain', 'utf8')
360 else:
361 email = MIMEMultipart()
362 email.attach(MIMEText(message, 'plain', 'utf8'))
363
364 email['From'] = sender
365 email['To'] = ', '.join(receiver)
366 email['Subject'] = subject
367
368 if files2attach is None:
369 return email
370
371 for file2attach in files2attach:
372 filename = file2attach[0]
373 try:
374 mimetype = file2attach[1]
375 except IndexError:
376 mimetype = gmMimeLib.guess_mimetype(filename = filename)
377
378 if mimetype.startswith('text/'):
379 txt = io.open(filename, mode = 'rt', encoding = 'utf8')
380 attachment = MIMEText(txt.read(), 'plain', 'utf8')
381 txt.close()
382
383 elif mimetype.startswith('image/'):
384 img = io.open(filename, mode = 'rb')
385 attachment = MIMEImage(img.read())
386 img.close()
387
388 elif mimetype.startswith('audio/'):
389 song = io.open(filename, mode = 'rb')
390 attachment = MIMEAudio(song.read())
391 song.close()
392
393 else:
394 _log.debug('attaching [%s] with type [%s]', filename, mimetype)
395 mime_subtype = mimetype.split('/', 1)[1]
396 data = io.open(filename, mode = 'rb')
397 attachment = MIMEApplication(data.read(), mime_subtype)
398 data.close()
399
400 try:
401 attachment.replace_header('Content-Disposition', 'attachment; filename="%s"' % gmTools.fname_from_path(filename))
402 except KeyError:
403 attachment.add_header('Content-Disposition', 'attachment; filename="%s"' % gmTools.fname_from_path(filename))
404 email.attach(attachment)
405
406 return email
407
408
409 -def send_email(sender=None, receiver=None, email=None, server=None, auth=None, debug=False):
410
411 if email is None:
412 raise ValueError('<email> is None, cannot send')
413
414 if sender is None:
415 sender = default_mail_sender
416
417 if receiver is None:
418 receiver = [default_mail_receiver]
419
420 if server is None:
421 server = default_mail_server
422
423 import smtplib
424 failed = False
425 refused = []
426 try:
427 session = smtplib.SMTP(server)
428 session.set_debuglevel(debug)
429 try:
430 session.starttls()
431 except smtplib.SMTPException:
432 _log.error('cannot enable TLS on [%s]', server)
433 session.ehlo()
434 if auth is not None:
435 session.login(auth['user'], auth['password'])
436 refused = session.sendmail(sender, receiver, email.as_string())
437 session.quit()
438 except smtplib.SMTPException:
439 failed = True
440 _log.exception('cannot send email')
441 gmLog2.log_stack_trace()
442
443 if len(refused) > 0:
444 _log.error("refused recipients: %s" % refused)
445
446 if failed:
447 return False
448
449 return True
450
451
452 -def compose_and_send_email(sender=None, receiver=None, message=None, server=None, auth=None, debug=False, subject=None, attachments=None):
453 email = compose_email (
454 sender = sender,
455 receiver = receiver,
456 message = message,
457 subject = subject,
458 files2attach = attachments
459 )
460 return send_email (
461 sender = sender,
462 receiver = receiver,
463 email = email,
464 server = server,
465 auth = auth,
466 debug = debug
467 )
468
469
470
471
472 if __name__ == '__main__':
473
474 if len(sys.argv) < 2:
475 sys.exit()
476
477 if sys.argv[1] != 'test':
478 sys.exit()
479
480
482 email = compose_email (
483 message = 'compose_email() test: üü ßß',
484 files2attach = [[sys.argv[2]]]
485 )
486 print(email.as_string())
487 return email
488
489
491 email = compose_email (
492 message = 'compose_email() test: üü ßß',
493 files2attach = [[sys.argv[2]]]
494 )
495 print(send_email (
496
497 email = email,
498
499 auth = {'user': default_mail_sender, 'password': 'gnumed-at-gmx-net'},
500 debug = True
501 ))
502
503
505
506 test_data = [
507 ('http://www.gnumed.de/downloads/gnumed-versions.txt', None, None, False),
508 ('file:///home/ncq/gm-versions.txt', None, None, False),
509 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', False),
510 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.1', True),
511 ('file:///home/ncq/gm-versions.txt', '0.2', '0.2.8.5', True)
512 ]
513
514 for test in test_data:
515 print("arguments:", test)
516 found, msg = check_for_update(test[0], test[1], test[2], test[3])
517 print(msg)
518
519 return
520
522
523
524 url = 'gmTools.py'
525 dl_name = download_data_pack(url)
526 print(url, "->", dl_name)
527 unzip_dir = unzip_data_pack(dl_name)
528 print("unzipped into", unzip_dir)
529
530
535
536
537
538
539 test_send_email()
540
541
542
543
544