1# -*- coding: UTF-8 -*- 2 3__revision__ = '$Id$' 4 5# Copyright (c) 2005-2009 Vasco Nunes, Piotr Ożarowski 6# 7# This program is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation; either version 2 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU Library General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program; if not, write to the Free Software 19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 20 21# You may use and distribute this software under the terms of the 22# GNU General Public License, version 2 or later 23 24from gi.repository import Gdk 25from gi.repository import GdkPixbuf 26import gzip 27import html.entities 28import logging 29import os 30import re 31import string 32import sys 33import webbrowser 34from io import StringIO 35import platform 36 37try: 38 from gi.repository import Gtk 39 import db 40except: 41 Gtk = None 42 pass 43 44mac = False 45 46if os.name in ('mac') or \ 47 (hasattr(os, 'uname') and os.uname()[0] == 'Darwin'): 48 from . import macutils 49 mac = True 50 51log = logging.getLogger("Griffith") 52 53ENTITY = re.compile(r'\&.\w*?\;') 54 55 56def remove_accents(txt, encoding='iso-8859-1'): 57 d = {192: 'A', 193: 'A', 194: 'A', 195: 'A', 196: 'A', 197: 'A', 58 199: 'C', 200: 'E', 201: 'E', 202: 'E', 203: 'E', 204: 'I', 59 205: 'I', 206: 'I', 207: 'I', 209: 'N', 210: 'O', 211: 'O', 60 212: 'O', 213: 'O', 214: 'O', 216: 'O', 217: 'U', 218: 'U', 61 219: 'U', 220: 'U', 221: 'Y', 224: 'a', 225: 'a', 226: 'a', 62 227: 'a', 228: 'a', 229: 'a', 231: 'c', 232: 'e', 233: 'e', 63 234: 'e', 235: 'e', 236: 'i', 237: 'i', 238: 'i', 239: 'i', 64 241: 'n', 242: 'o', 243: 'o', 244: 'o', 245: 'o', 246: 'o', 65 248: 'o', 249: 'u', 250: 'u', 251: 'u', 252: 'u', 253: 'y', 66 255: 'y'} 67 return txt.translate(d) 68 69 70def is_number(x): 71 return isinstance(x, int) 72 73 74def find_next_available(gsql): 75 """ 76 finds next available movie number. 77 This is the first empty position. 78 If none is empty then increments the last position. 79 """ 80 first = 0 81 82 movies = gsql.session.query(db.Movie.number).order_by(db.Movie.number.asc()).all() 83 for movie in movies: 84 second = int(movie.number) 85 if second is None: 86 second = 0 87 if second > first + 1: 88 break 89 first = second 90 91 if first is None: 92 return 1 93 else: 94 number = first + 1 95 return number 96 97 98def trim(text, key1, key2): 99 p1 = text.find(key1) 100 if p1 == -1: 101 return '' 102 else: 103 p1 = p1 + len(key1) 104 p2 = text[p1:].find(key2) 105 if p2 == -1: 106 return "" 107 else: 108 p2 = p1 + p2 109 return text[p1:p2] 110 111def rtrim(text, key1, key2): 112 p1 = text.rfind(key2) 113 if p1 == -1: 114 return '' 115 p2 = text[:p1].rfind(key1) 116 if p2 == -1: 117 return "" 118 else: 119 p2 = p2 + len(key1) 120 return text[p2:p1] 121 122def regextrim(text, key1, key2): 123 obj = re.search(key1, text) 124 if obj is None: 125 return '' 126 else: 127 p1 = obj.end() 128 obj = re.search(key2, text[p1:]) 129 if obj is None: 130 return '' 131 else: 132 p2 = p1 + obj.start() 133 return text[p1:p2] 134 135 136def after(text, key): 137 p1 = text.find(key) 138 return text[p1 + len(key):] 139 140 141def before(text, key): 142 p1 = text.find(key) 143 return text[:p1] 144 145 146def gescape(text): 147 text = text.replace("'", "''") 148 text = text.replace("--", "-") 149 return text 150 151 152def progress(blocks, size_block, size): 153 transfered = blocks * size_block 154 if size > 0 and transfered > size: 155 transfered = size 156 elif size < 0: 157 size = "?" 158 print(transfered, '/', size, 'bytes') 159 160# functions to handle comboboxentry stuff 161 162 163def set_model_from_list(cb, items): 164 """Setup a ComboBox or ComboBoxEntry based on a list of strings.""" 165 model = Gtk.ListStore(str) 166 for i in items: 167 model.append([i]) 168 cb.set_model(model) 169 if type(cb) == Gtk.ComboBoxEntry: 170 cb.set_text_column(0) 171 elif type(cb) == Gtk.ComboBox: 172 cell = Gtk.CellRendererText() 173 cb.pack_start(cell, True) 174 cb.add_attribute(cell, 'text', 0) 175 176 177def on_combo_box_entry_changed(widget): 178 model = widget.get_model() 179 m_iter = widget.get_active_iter() 180 if m_iter: 181 value = model.get_value(m_iter, 0) 182 #if type(value) is str: 183 # value = value.decode('utf-8') 184 return value 185 else: 186 return 0 187 188 189def on_combo_box_entry_changed_name(widget): 190 return widget.get_active_text() 191 192 193def convert_entities(text): 194 195 def conv(ents): 196 entities = html.entities.entitydefs 197 ents = ents.group(0) 198 ent_code = entities.get(ents[1:-1], None) 199 if ent_code: 200 ents = ent_code 201 202 # check if it still needs conversion 203 if not ENTITY.search(ents): 204 return ents 205 206 if ents[1] == '#': 207 code = ents[2:-1] 208 base = 10 209 if code[0] == 'x': 210 code = code[1:] 211 base = 16 212 return chr(int(code, base)) 213 else: 214 return 215 216 in_entity = ENTITY.search(text) 217 if not in_entity: 218 return text 219 else: 220 ctext = in_entity.re.sub(conv, text) 221 return ctext 222 223 224def strip_tags(text): 225 if text is None: 226 return '' 227 finished = 0 228 while not finished: 229 finished = 1 230 # check if there is an open tag left 231 start = text.find('<') 232 if start >= 0: 233 # if there is, check if the tag gets closed 234 stop = text[start:].find(">") 235 if stop >= 0: 236 # if it does, strip it, and continue loop 237 text = text[:start] + text[start + stop + 1:] 238 finished = 0 239 return text 240 241 242def clean(text): 243 t = strip_tags(text) 244 t = t.replace(' ', ' ') 245 t = t.replace('"', '') 246 t = t.replace(' ', ' ') 247 return t.strip() 248 249 250def gdecode(txt, encode): 251 try: 252 return txt.decode(encode) 253 except: 254 return txt 255 256# Messages 257 258 259def error(msg, parent=None): 260 dialog = Gtk.MessageDialog(parent, 261 Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, 262 Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, msg) 263 dialog.set_skip_taskbar_hint(False) 264 dialog.run() 265 dialog.destroy() 266 267 268def urllib_error(msg, parent=None): 269 dialog = Gtk.MessageDialog(parent, 270 Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, 271 Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, msg) 272 dialog.set_skip_taskbar_hint(False) 273 dialog.run() 274 dialog.destroy() 275 276 277def warning(msg, parent=None): 278 if mac: 279 macutils.createAlert(msg) 280 else: 281 dialog = Gtk.MessageDialog(parent, 282 Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, 283 Gtk.MessageType.WARNING, Gtk.ButtonsType.OK, msg) 284 dialog.set_skip_taskbar_hint(False) 285 dialog.run() 286 dialog.destroy() 287 288def info(msg, parent=None): 289 if mac: 290 macutils.createAlert(msg) 291 else: 292 dialog = Gtk.MessageDialog(parent, 293 Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, 294 Gtk.MessageType.INFO, Gtk.ButtonsType.OK, msg) 295 dialog.set_skip_taskbar_hint(False) 296 dialog.run() 297 dialog.destroy() 298 299def question(msg, window=None): 300 if mac: 301 response = macutils.question(msg) 302 return response 303 else: 304 dialog = Gtk.MessageDialog(window, 305 Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, 306 Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, msg) 307 dialog.add_buttons(Gtk.STOCK_YES, Gtk.ResponseType.YES, 308 Gtk.STOCK_NO, Gtk.ResponseType.NO) 309 dialog.set_default_response(Gtk.ResponseType.NO) 310 dialog.set_skip_taskbar_hint(False) 311 response = dialog.run() 312 dialog.destroy() 313 return response in (Gtk.ResponseType.OK, Gtk.ResponseType.YES) 314 315def popup_message(message): 316 """shows popup message while executing decorated function""" 317 def wrap(f): 318 def wrapped_f(*args, **kwargs): 319 if Gtk: 320 window = Gtk.Window() 321 window.set_title('Griffith info') 322 window.set_position(Gtk.WindowPosition.CENTER) 323 window.set_keep_above(True) 324 window.stick() 325 window.set_default_size(200, 50) 326 label = Gtk.Label() 327 label.set_markup("""<big><b>Griffith:</b> 328%s</big>""" % message) 329 window.add(label) 330 window.set_modal(True) 331 window.set_type_hint(Gdk.WindowTypeHint.DIALOG) 332 window.show_all() 333 while Gtk.events_pending(): # give GTK some time for updates 334 Gtk.main_iteration() 335 else: 336 print(message, end=' ') 337 res = f(*args, **kwargs) 338 if Gtk: 339 window.destroy() 340 else: 341 print(' [done]') 342 return res 343 return wrapped_f 344 return wrap 345 346 347def file_chooser(title, action=None, buttons=None, name='', folder=os.path.expanduser('~'), picture=False, backup=False): 348 if mac: 349 if "SAVE" in str(action): 350 if backup: 351 status, filename, path = macutils.saveDialog(['zip']) 352 else: 353 status, filename, path = macutils.saveDialog() 354 else: 355 status, filename, path = macutils.openDialog(['zip']) 356 if status: 357 if filename.lower().endswith('.zip'): 358 pass 359 else: 360 filename = filename+".zip" 361 return filename, path 362 else: 363 return False 364 else: 365 dialog = Gtk.FileChooserDialog(title=title, action=action, buttons=buttons) 366 dialog.set_default_response(Gtk.ResponseType.OK) 367 if name: 368 dialog.set_current_name(name) 369 if folder: 370 dialog.set_current_folder(folder) 371 mfilter = Gtk.FileFilter() 372 if picture: 373 preview = Gtk.Image() 374 dialog.set_preview_widget(preview) 375 dialog.connect("update-preview", update_preview_cb, preview) 376 mfilter.set_name(_("Images")) 377 mfilter.add_mime_type("image/png") 378 mfilter.add_mime_type("image/jpeg") 379 mfilter.add_mime_type("image/gif") 380 mfilter.add_pattern("*.[pP][nN][gG]") 381 mfilter.add_pattern("*.[jJ][pP][eE]?[gG]") 382 mfilter.add_pattern("*.[gG][iI][fF]") 383 mfilter.add_pattern("*.[tT][iI][fF]{1,2}") 384 mfilter.add_pattern("*.[xX][pP][mM]") 385 dialog.add_filter(mfilter) 386 elif backup: 387 mfilter.set_name(_('backups')) 388 mfilter.add_pattern('*.[zZ][iI][pP]') 389 mfilter.add_pattern('*.[gG][rR][iI]') 390 mfilter.add_pattern('*.[dD][bB]') 391 dialog.add_filter(mfilter) 392 mfilter = Gtk.FileFilter() 393 mfilter.set_name(_("All files")) 394 mfilter.add_pattern("*") 395 dialog.add_filter(mfilter) 396 397 398 response = dialog.run() 399 if response == Gtk.ResponseType.OK: 400 filename = dialog.get_filename() 401 elif response == Gtk.ResponseType.CANCEL: 402 filename = None 403 else: 404 return False 405 path = dialog.get_current_folder() 406 dialog.destroy() 407 return filename, path 408 409 410def update_preview_cb(file_chooser, preview): 411 filename = file_chooser.get_preview_filename() 412 try: 413 pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filename, 128, 128) 414 preview.set_from_pixbuf(pixbuf) 415 have_preview = True 416 except: 417 have_preview = False 418 file_chooser.set_preview_widget_active(have_preview) 419 return 420 421 422def run_browser(url): 423 webbrowser.register('open', webbrowser.GenericBrowser("open '%s'")) 424 webbrowser._tryorder.append('open') 425 webbrowser.open(url) 426 427 428def read_plugins(prefix, directory): 429 """returns available plugins""" 430 431 import glob 432 return glob.glob("%s/%s*.py" % (directory, prefix)) 433 434 435def findKey(val, dict): 436 for key, value in list(dict.items()): 437 if value == val: 438 return key 439 return None 440 441 442def garbage(handler): 443 pass 444 445 446def clean_posters_dir(self): 447 posters_dir = self.locations['posters'] 448 counter = 0 449 450 for dirpath, dirnames, filenames in os.walk(posters_dir): 451 for name in filenames: 452 filepath = os.path.join(dirpath, name) 453 if name.endswith('_m.jpg') or name.endswith('_s.jpg'): 454 # it's safe to remove all thumbs, they'll be regenerated later 455 os.unlink(filepath) 456 else: 457 poster_md5 = md5sum(file(filepath, 'rb')) 458 # lets check if this poster is orphan 459 used = self.db.session.query(db.Poster).filter(db.Poster.md5sum == poster_md5).count() 460 if not used: 461 counter += 1 462 os.unlink(filepath) 463 464 if counter: 465 print("%d orphan files cleaned." % counter) 466 else: 467 print("No orphan files found.") 468 469 470def decompress(data): 471 try: 472 compressedStream = StringIO(data) 473 gzipper = gzip.GzipFile(fileobj=compressedStream) 474 data = gzipper.read() 475 except Exception as e: 476 log.debug("Cannot decompress data: ", e) 477 pass 478 return data 479 480 481def get_dependencies(): 482 depend = [] 483 484 # Python version 485 if sys.version_info[:2] < (2, 5): 486 depend.append({'module': 'python', 487 'version': '-' + '.'.join(map(str, sys.version_info)), 488 'module_req': '2.5', 489 'url': 'http://www.python.org/', 490 'debian': 'python', 491 'debian_req': '2.5'}) 492 # TODO: 'fedora', 'suse', etc. 493 """ 494 try: 495 from gi.repository import Gtk 496 version = '.'.join([str(i) for i in Gtk.pygtk_version]) 497 if Gtk.pygtk_version <= (2, 6, 0): 498 version = '-%s' % version 499 except: 500 version = False 501 depend.append({'module': 'gtk', 502 'version': version, 503 'module_req': '2.6', 504 'url': 'http://www.pyGtk.org/', 505 'debian': 'python-gtk2', 506 'debian_req': '2.8.6-1'}) 507 # TODO: 'fedora', 'suse', etc. 508 509 try: 510 import Gtk.glade 511 # (version == Gtk.pygtk_version) 512 except: 513 version = False 514 depend.append({'module': 'Gtk.glade', 515 'version': version, 516 'module_req': '2.6', 517 'url': 'http://www.pyGtk.org/', 518 'debian': 'python-glade2', 519 'debian_req': '2.8.6-1'}) 520 """ 521 try: 522 import sqlalchemy 523 if list(map(int, sqlalchemy.__version__[:3].split('.'))) < [0, 5]: 524 version = "-%s" % sqlalchemy.__version__ 525 else: 526 version = sqlalchemy.__version__ 527 except: 528 version = False 529 depend.append({'module': 'sqlalchemy', 530 'version': version, 531 'module_req': '0.5rc3', 532 'url': 'http://www.sqlalchemy.org/', 533 'debian': 'python-sqlalchemy', 534 'debian_req': '0.5~rc3'}) 535 try: 536 import sqlite3 537 version = sqlite3.version 538 sqliteversion = sqlite3.sqlite_version 539 except ImportError: 540 version = False 541 if version is False: 542 try: 543 import pysqlite2.dbapi2 544 version = pysqlite2.dbapi2.version 545 sqliteversion = pysqlite2.dbapi2.sqlite_version 546 except: 547 version = False 548 depend.append({'module': 'pysqlite2', 549 'version': version + ' (sqlite-lib ' + sqliteversion + ')', 550 'url': 'http://pypi.python.org/pypi/pysqlite/', 551 'debian': 'python-pysqlite2', 552 'debian_req': '2.3.0-1'}) 553 else: 554 depend.append({'module': 'sqlite3', 555 'version': version + ' (sqlite-lib ' + sqliteversion + ')', 556 'url': 'http://www.python.org', 557 'debian': 'python', 558 'debian_req': '2.5'}) 559 try: 560 import reportlab 561 version = reportlab.Version 562 except: 563 version = False 564 depend.append({'module': 'reportlab', 565 'version': version, 566 'url': 'http://www.reportlab.org/', 567 'debian': 'python-reportlab', 568 'debian_req': '1.20debian-6'}) 569 try: 570 import PIL 571 version = True 572 except: 573 version = False 574 depend.append({'module': 'PIL', 575 'version': version, 576 'url': 'http://www.pythonware.com/products/pil/', 577 'debian': 'python-imaging', 578 'debian_req': '1.1.5-6'}) 579 580 # extra dependencies: 581 optional = [] 582 583 try: 584 import tmdbsimple 585 version = True 586 except: 587 version = False 588 optional.append({'module': 'tmdbsimple', 589 'version': version}) 590 591 try: 592 import psycopg2 593 version = psycopg2.__version__ 594 except: 595 version = False 596 optional.append({'module': 'psycopg2', 597 'version': version, 598 'url': 'http://initd.org/psycopg/', 599 'debian': 'python-psycopg2', 600 'debian_req': '1.1.21-6'}) 601 try: 602 import pymysql 603 pymysql.install_as_MySQLdb() 604 import MySQLdb 605 version = '.'.join([str(i) for i in MySQLdb.version_info]) 606 except: 607 version = False 608 optional.append({'module': 'MySQLdb', 609 'version': version, 610 'url': 'http://sourceforge.net/projects/mysql-python', 611 'debian': 'python-mysqldb', 612 'debian_req': '1.2.1-p2-2'}) 613 try: 614 import chardet 615 version = chardet.__version__ 616 except: 617 version = False 618 optional.append({'module': 'chardet', 619 'version': version, 620 'url': 'http://chardet.feedparser.org/', 621 'debian': 'python-chardet'}) 622 try: 623 import sqlite 624 version = sqlite.version 625 except: 626 version = False 627 optional.append({'module': 'sqlite', 628 'version': version, 629 'url': 'http://initd.org/tracker/pysqlite', 630 'debian': 'python-sqlite'}) 631 try: 632 import lxml 633 version = True 634 except ImportError: 635 version = False 636 optional.append({'module': 'lxml', 637 'version': version, 638 'url': 'http://lxml.de/', 639 'debian': 'python-lxml'}) 640 return depend, optional 641 642 643def html_encode(s): 644 if not isinstance(s, str): 645 s = str(s) 646 s = s.replace('&', '&') 647 s = s.replace('<', '<') 648 s = s.replace('>', '>') 649 s = s.replace('"', '"') 650 return s 651 652 653def digits_only(s, maximum=None): 654 if s is None: 655 return 0 656 match = re.compile(r"\d+") 657 a = match.findall(str(s)) 658 if len(a): 659 s = int(a[0]) 660 else: 661 s = 0 662 if maximum is None: 663 return s 664 else: 665 if s > maximum: 666 return maximum 667 else: 668 return s 669 670 671def copytree(src, dst, symlinks=False): 672 """Recursively copy a directory tree using copy2(). 673 674 This is shutil's copytree modified version 675 """ 676 from shutil import copy2 677 names = os.listdir(src) 678 if not os.path.isdir(dst): 679 os.mkdir(dst) 680 errors = [] 681 for name in names: 682 srcname = os.path.join(src, name) 683 dstname = os.path.join(dst, name) 684 try: 685 if symlinks and os.path.islink(srcname): 686 linkto = os.readlink(srcname) 687 os.symlink(linkto, dstname) 688 elif os.path.isdir(srcname): 689 copytree(srcname, dstname, symlinks) 690 else: 691 copy2(srcname, dstname) 692 except (IOError, os.error) as why: 693 errors.append((srcname, dstname, why)) 694 # catch the Error from the recursive copytree so that we can 695 # continue with other files 696 except EnvironmentError as err: 697 errors.extend(err.args[0]) 698 if errors: 699 raise EnvironmentError(errors) 700 701 702def is_windows_system(): 703 if os.name == 'nt' or os.name.startswith('win'): # win32, win64 704 return True 705 return False 706 707 708def md5sum(fobj): 709 """Returns an md5 hash for an object with read() method.""" 710 try: 711 import hashlib 712 m = hashlib.md5() 713 except ImportError: 714 import md5 715 m = md5.new() 716 if hasattr(fobj, 'read'): 717 while True: 718 d = fobj.read(8096) 719 if not d: 720 break 721 m.update(d) 722 else: 723 m.update(fobj) 724 return str(m.hexdigest()) 725 726 727def create_image_cache(md5sum, gsql): 728 poster = gsql.session.query(db.Poster).filter_by(md5sum=md5sum).first() 729 if not poster: 730 log.warn("poster not available: %s", md5sum) 731 return False 732 if not poster.data: 733 log.warn("poster data not available: %s", md5sum) 734 return False 735 736 fn_big = os.path.join(gsql.data_dir, 'posters', md5sum + '.jpg') 737 fn_medium = os.path.join(gsql.data_dir, 'posters', md5sum + '_m.jpg') 738 fn_small = os.path.join(gsql.data_dir, 'posters', md5sum + '_s.jpg') 739 740 if not os.path.isfile(fn_big): 741 f = open(fn_big, 'wb') 742 f.write(poster.data) 743 f.close() 744 745 image = Gtk.Image() 746 image.set_from_file(fn_big) 747 748 if not os.path.isfile(fn_medium): 749 pixbuf = image.get_pixbuf() 750 pixbuf = pixbuf.scale_simple(100, 140, GdkPixbuf.InterpType.BILINEAR) 751 pixbuf.savev(fn_medium, 'jpeg', ['quality'], ['70']) 752 753 if not os.path.isfile(fn_small): 754 pixbuf = image.get_pixbuf() 755 pixbuf = pixbuf.scale_simple(30, 40, GdkPixbuf.InterpType.BILINEAR) 756 pixbuf.savev(fn_small, 'jpeg', ['quality'], ['70']) 757 758 return True 759 760 761def create_imagefile(destdir, md5sum, gsql, destfilename=None): 762 poster = gsql.session.query(db.Poster).filter_by(md5sum=md5sum).first() 763 if not poster: 764 log.warn("poster not available: %s", md5sum) 765 return False 766 if not poster.data: 767 log.warn("poster data not available: %s", md5sum) 768 return False 769 770 if destfilename: 771 fulldestpath = os.path.join(destdir, destfilename + '.jpg') 772 else: 773 fulldestpath = os.path.join(destdir, md5sum + '.jpg') 774 775 f = open(fulldestpath, 'wb') 776 try: 777 f.write(poster.data) 778 finally: 779 f.close() 780 781 return True 782 783 784def get_image_fname(md5sum, gsql, size=None): 785 """size: s - small; m - medium, b or None - big""" 786 if size not in (None, 's', 'm', 'b'): 787 raise TypeError("wrong size: %s" % size) 788 if not md5sum: 789 raise TypeError("md5sum not set") 790 791 if not size or size == 'b': 792 size = '' 793 else: 794 size = "_%s" % size 795 796 file_name = os.path.join(gsql.data_dir, 'posters', md5sum + size + '.jpg') 797 798 if not os.path.isfile(file_name) and not create_image_cache(md5sum, gsql): 799 log.warn("Can't create image file %s for md5sum %s" % (file_name, md5sum)) 800 return False 801 return file_name 802 803 804def get_defaultimage_fname(self): 805 return os.path.join(self.locations['images'], 'default.png') 806 807 808def get_defaultthumbnail_fname(self): 809 return os.path.join(self.locations['images'], 'default_thumbnail.png') 810 811def get_filesystem_pagesize(path): 812 pagesize = 1024 813 # retrieve filesystem page size for optimizing filesystem based database systems like sqlite 814 try: 815 if is_windows_system(): 816 pagesize = 4096 # almost the best for standard windows systems 817 818 # try to find the perfect value from the filesystem 819 import ctypes 820 821 drive = os.path.splitdrive(path) 822 sectorsPerCluster = ctypes.c_ulonglong(0) 823 bytesPerSector = ctypes.c_ulonglong(0) 824 rootPathName = ctypes.c_wchar_p(str(drive[0])) 825 826 ctypes.windll.kernel32.GetDiskFreeSpaceW(rootPathName, 827 ctypes.pointer(sectorsPerCluster), 828 ctypes.pointer(bytesPerSector), 829 None, 830 None, 831 ) 832 pagesize = sectorsPerCluster.value * bytesPerSector.value 833 else: 834 if not os.path.isfile(path): 835 return pagesize 836 # I could not try it out on non-windows platforms 837 # if it doesn't work the default page size is returned 838 from os import statvfs 839 stats = statvfs(path) 840 pagesize = stats.f_bsize 841 except: 842 log.error('') 843 844 # adjust page size 845 if pagesize > 32768: 846 pagesize = 32768 847 if pagesize < 1024: 848 pagesize = 1024 849 850 return pagesize 851