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('&nbsp;', ' ')
245    t = t.replace('&#34;', '')
246    t = t.replace('&#160;', ' ')
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('&', '&amp;')
647    s = s.replace('<', '&lt;')
648    s = s.replace('>', '&gt;')
649    s = s.replace('"', '&quot;')
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