def loadlibgen(csvname): ''' Загружает в память csv-файл с базой Library Genesis и возвращает словарь, где ключ — md5-хеш файла (строка в нижнем регистре), а значение — кортеж (путь к файлу, размер файла) ''' try: with open(csvname) as csvfile: # инициализируем прогрессбар pbar = ProgressBar(maxval=len(open(csvname).readlines())) csvfile.seek(0) data = csv.reader(csvfile, delimiter=',', quotechar='"') # определяем, есть ли в файле заголовок # формат csv: путь, размер, md5 try: # пытаемся преобразовать размер к целому числа int(second(data.next())) except ValueError: # нашли заголовок, стоим уже на второй строке pass else: # заголовка нет, идём обратно csvfile.seek(0) print(u'Загружаем в память базу Library Genesis...') # заполняем словарь library = {} values = ((os.path.normpath(first(entry)), int(second(entry)), third(entry)) for entry in data) for name, size, md5 in values: library[md5.lower()] = (name, size) # если выводить прогресс на каждом шаге, получается очень медленно # поэтому будем обновляться каждый CSV_LOAD_DISPLAY_RATE'ый шаг if data.line_num % CSV_LOAD_DISPLAY_RATE == 0: pbar.set(data.line_num) pbar.finish() except Exception as e: raise FatalError(u'Не удалось загрузить CSV-файл: {0!s}'.format(e)) return library
def load(self, pbar_enabled): fobj = open(self.filename) pbar = ProgressBar(maxval=len(fobj.readlines()), enabled=pbar_enabled) fobj.seek(0) reader = csv.DictReader(fobj, fieldnames=self.fieldnames) # skip header header = reader.next() for fieldname in header: if fieldname != header[fieldname].lower(): # header not found fobj.seek(0) break library = {} for entry in reader: library[entry['md5'].lower()] = (entry['filename'], int(entry['filesize'])) if reader.line_num % PROGRESSBAR_UPDATE_INTERVAL == 0: pbar.set(reader.line_num) pbar.finish() fobj.close() return library
def main(): global config, log oparser = optparse.OptionParser( usage='%prog [options] <source> <destination>', version=APP_VERSION_STRING, prog=APP_SHORT_NAME) oparser.add_option('-n', '--dry-run', action='store_true', dest='dry_run', default=False, help="don't perform write actions, just simulate") oparser.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False, help="show operations log") oparser.add_option('', '--no-progressbar', action='store_false', dest='pbar', default=True, help="don't show progress bar") optgroup = optparse.OptionGroup(oparser, 'File handling options') optgroup.add_option('-m', '--method', dest='method', default=M_COPY, help='file processing method ({0})'.format('|'.join( config.methods))) optgroup.add_option('-r', '--remove-empty', action='store_true', dest='remove_empty', default=False, help='remove empty directories') optgroup.add_option('', '--remove-duplicates', action='store_true', dest='remove_duplicates', default=False, help='remove files that already exist in repository') oparser.add_option_group(optgroup) optgroup = optparse.OptionGroup(oparser, 'CSV options') optgroup.add_option('', '--csv', dest='csv', metavar='FILENAME', default='libgen.csv', help='path to csv (%default)') oparser.add_option_group(optgroup) optgroup = optparse.OptionGroup(oparser, "DB connection options") optgroup.add_option('', '--db-host', default='localhost', help='DB host (%default)') optgroup.add_option('', '--db-name', default='bookwarrior', help='DB name (%default)') optgroup.add_option('', '--db-user', help='DB user') optgroup.add_option('', '--db-passwd', metavar='PASSWD', default='', help='DB password (empty)') oparser.add_option_group(optgroup) (options, args) = oparser.parse_args() if len(args) != 2: oparser.error('Wrong number of arguments') if options.method not in config.methods: oparser.error(u'Unknown file processing method "{0}"'.format( options.method)) if config.methods[options.method] is None: return error(config.get_error_message(options.method)) config.src, config.dst = (os.path.abspath(arg).decode(config.encoding) for arg in args) if not os.path.isdir(config.src): return error(u'Directory {0} not found'.format(config.src)) if not os.path.isdir(config.dst): return error(u'Directory {0} not found'.format(config.dst)) if not os.access(config.src, os.R_OK): return error(u'Not enough rights for reading from %s' % config.src) if ((options.remove_empty or options.remove_duplicates or options.method == M_MOVE) and not os.access(config.src, os.W_OK)): return error(u'Not enough rights for writing to %s' % config.src) if not os.access(config.dst, os.W_OK): return error(u'Not enough rights for writing to %s' % config.dst) # проверим, поддерживает ли файловая система создание ссылок # в Windows мягкие и жёсткие ссылки можно создавать только на NTFS # (жёсткие — только в пределах одного диска) if config.windows and options.method in (M_SYMLINK, M_HARDLINK): message = config.checkfs(options.method) if message: return error(message) if options.db_user: worker = loader.DBLoader(options.db_host, options.db_name, options.db_user, options.db_passwd) else: if not os.path.isfile(options.csv): return error(u'File {0} not found'.format(options.csv)) worker = loader.CSVLoader(options.csv) print('Loading Library Genesis...') library = worker.load(options.pbar) library_filesizes = set(value[1] for value in library.values()) print('{0} books loaded'.format(len(library))) print('Analyzing total size of files for processing...', end=' ') src_size = dirsize(config.src) print(bytes_to_human(src_size)) print('Scanning...') processed, added, duplicate = ProgressCounter(), ProgressCounter( ), ProgressCounter() pbar = ProgressBar(maxval=src_size, displaysize=True, enabled=options.pbar) log.set_pbar(pbar) delta = src_size / CHECK_PROGRESS_DIVIDER for path, dirs, files in os.walk(config.src): for file in files: fullpath = os.path.join(path, file) filesize = os.path.getsize(fullpath) # если в базе есть файл такого размера if filesize in library_filesizes: md5 = md5hash(fullpath) # и совпал по хешу if md5 in library: # то обрабатываем его already_in_repo = process(fullpath, library[md5][0], options) if already_in_repo: duplicate.add(filesize) else: added.add(filesize) processed.add(filesize) # будем обновлять, только если накопилось достаточно файлов if processed.size - pbar.curval >= delta: pbar.set(processed.size) if not options.dry_run and options.remove_empty and dirsize(path) == 0: shutil.rmtree(path) pbar.finish() log.unset_pbar() print('Processed: {0} ({1})'.format(processed.count, bytes_to_human(processed.size))) print('Added to repository ({0}): {1} ({2})'.format( config.method_descriptions[options.method], added.count, bytes_to_human(added.size))) print('Duplicates {0}: {1} ({2})'.format( 'removed' if options.remove_duplicates else 'found', duplicate.count, bytes_to_human(duplicate.size))) return 0
def __init__(self, cmdo): self.pbar = ProgressBar(BAR_COLOR, block=BAR_CHAR_FULL, empty=BAR_CHAR_EMPTY, width=BAR_WIDTH) self.cmdo = cmdo self.tmp = {} self.tmp['speed'] = ['', '']
class DDOutput: def __init__(self, cmdo): self.pbar = ProgressBar(BAR_COLOR, block=BAR_CHAR_FULL, empty=BAR_CHAR_EMPTY, width=BAR_WIDTH) self.cmdo = cmdo self.tmp = {} self.tmp['speed'] = ['', ''] """ Update progress """ def put(self, **kwargs): if kwargs.get('f') and kwargs.get('state') == 'ft_finished': self.tmp['speed'] = kwargs.get('f').speed if self.cmdo.quiet: pass elif self.cmdo.verbose: self.put_verbose(*kwargs.values()) else: if self.cmdo.detailed: self.put_bar_extended(*kwargs.values()) else: self.put_bar(*kwargs.values()) """ Print -v(erbose) """ def put_verbose(self, state, task, instance, counter): print '{c}/{ca} {state} \'{from_path}\' \'{to_path}\' {speed}'.format( c=counter+1, ca=task.count(), state=state, from_path=instance.from_path, to_path=instance.to_path, speed=''.join(self.tmp['speed']) ) """ Print bar """ def put_bar(self, state, task, instance, counter): counter = counter+(state == 'ft_finished' and 1 or 0) percent = int(float(counter)/task.count()*100) self.pbar.render(percent, '100%\n[{c}/{ca}] {state} {f}'.format( c=counter, ca=task.count(), f=instance.file(), state=(state == 'ft_finished' and 'Finished' or 'Copying') )) """ Print -d(etailed) bar """ def put_bar_extended(self, state, task, instance, counter): counter = counter+(state == 'ft_finished' and 1 or 0) percent = int(float(counter)/task.count()*100) self.pbar.render(percent, '100%\n[{c}/{ca}] {speed} {state} {basepath}/{f}'.format( c=counter, ca=task.count(), f=instance.file(), state=(state == 'ft_finished' and 'Finished' or 'Copying'), basepath=instance.base_path, speed=''.join(self.tmp['speed']), )) """ Called when task finished, sends notify """ def finished(self, task): if not self.cmdo.quiet: os.system('notify-send %s' % quote('ddcp finished task (%s)' % task.count()))
def main(argv): # инициализируем парсер опций командной строки parser_usage = u'%prog [опции] <источник> <приёмник>' parser = OptionParser(parser_usage, version=APP_VERSION_STRING, prog=APP_SHORT_NAME, formatter=OptionFormatter(APP_VERSION, APP_LONG_NAME), add_help_option=False) parser.disable_interspersed_args() def usage(): # временное решение (пока нету gettext) print(parser.format_help()[:-1].replace('Options', u'Опции')) return 0 parser.add_option('-h', '--help', action='store_true', dest='help', default=False, help=u'показать это сообщение и выйти') parser.add_option('-c', '--csv', dest='filename', default='libgen.csv', help=u'путь к CSV-файлу') parser.add_option('-m', '--method', dest='method', default=M_COPY, help=u'метод обработки файлов (' + u'|'.join(config.methods.keys()) + ')') parser.add_option('-r', '--remove-empty', action='store_true', dest='remove_empty', default=False, help=u'удалять пустые обработанные каталоги') try: (options, args) = parser.parse_args() except OptionError as e: return error(e.msg) if empty(args) or options.help: return usage() if len(args) != 2: return error(u'Количество аргументов не равно двум (источник и приёмник)') if options.method not in config.methods: return error(u'Неверный аргумент у опции --method') if config.methods[options.method] is None: return error(config.method_errors[options.method]) # источник и приёмник config.source = os.path.abspath(first(args)).decode(config.encoding) config.dest = os.path.abspath(second(args)).decode(config.encoding) # проверим, поддерживает ли файловая система создание ссылок # в Windows мягкие и жёсткие ссылки можно создавать только на NTFS # (жёсткие — только в пределах одного диска) if config.windows and options.method in (M_SYMLINK, M_HARDLINK): try: config.checkfs(options.method) except FatalError as e: return error(e.msg) # проверяем, все ли пути существуют if not os.path.isfile(options.filename): return error(u'CSV-файл {0} не найден'.format(options.filename)) if not os.path.isdir(config.source): return error(u'Директория {0} не найдена'.format(config.source)) if not os.path.isdir(config.dest): return error(u'Директория {0} не найдена'.format(config.dest)) if not os.access(config.source, os.R_OK): return error(u'Недостаточно привилегий для чтения из ' + config.source) if (options.method == M_MOVE or options.remove_empty) and not os.access(config.source, os.W_OK): return error(u'Недостаточно привилегий для записи в ' + config.source) if not os.access(config.dest, os.W_OK): return error(u'Недостаточно привилегий для записи в ' + config.dest) try: # загружаем базу library = loadlibgen(options.filename) # library[md5] == (filename, size) libsizes = set(second(value) for value in library.values()) print(u'Оцениваем общий размер анализируемых файлов...') source_size = dirsize(config.source) print(bytes_to_human(source_size)) def process(source, dest): ''' Обрабатываем (копируем, перемещаем и т.д.) файл ''' errmsg = u'Ошибка при обработке файла {0}'.format(source) + u': {0!s}' try: source = os.path.join(config.source, source) dest = os.path.join(config.dest, dest) # если такой директории ещё нет, то создадим if not os.path.isdir(os.path.dirname(dest)): os.makedirs(os.path.dirname(dest)) duplicate = os.path.isfile(dest) if not duplicate: try: config.methods[options.method](source, dest) except OSError as e: raise FatalError(errmsg.format(e)) except IOError as e: raise FatalError(errmsg.format(e)) return duplicate def remove_if_empty(path): if options.remove_empty and dirsize(path) == 0: shutil.rmtree(path) print(u'Обрабатываем...') class ProgressCounter(object): def __init__(self): self.count, self.size = 0, 0 processed, added, duplicate = ProgressCounter(), ProgressCounter(), ProgressCounter() # инициализируем индикатор прогресса pbar = ProgressBar(maxval=source_size, displaysize=True, width=40) delta = source_size / CHECK_PROGRESS_DIVIDER for path, dirs, files in os.walk(config.source): for file in files: fullpath = os.path.join(path, file) filesize = os.path.getsize(fullpath) # если в базе есть файл такого размера if filesize in libsizes: md5 = md5hash(fullpath) # и совпал по хешу if md5 in library: # то обрабатываем его isduplicate = process(fullpath, first(library[md5])) if isduplicate: duplicate.count += 1 duplicate.size += filesize else: added.count += 1 added.size += filesize processed.count += 1 processed.size += filesize # будем обновлять, только если накопилось достаточно файлов if processed.size - pbar.curval >= delta: pbar.set(processed.size) remove_if_empty(path) remove_if_empty(config.source) pbar.finish() except FatalError as e: return error(e.msg) print(u'Обработано: {0} ({1})'.format(processed.count, bytes_to_human(processed.size))) print(u'Добавлено в репозиторий ({0}): {1} ({2})'.format( config.method_descriptions[options.method], added.count, bytes_to_human(added.size))) print(u'Пропущено дублей при добавлении: {0} ({1})'.format(duplicate.count, bytes_to_human(duplicate.size))) return 0
def main(argv): if len(argv) > 3: return error('ljrss.py <lj-username> <password> [filename]') config.user, config.password = argv[0], argv[1] config.filename = argv[2] if len(argv) == 3 else 'lj_mutual.xml' print('Getting the list of mutual friends...') try: livejournal = lj.LJServer('lj.py; [email protected]', 'Python-PyLJ/0.0.1') livejournal.login(config.user, config.password) def getusernames(response): return (item['username'] for item in response) friendof = getusernames(livejournal.friendof()['friendofs']) friends = getusernames(livejournal.getfriends()['friends']) mutualfriends = list(set(friendof) & set(friends)) except lj.LJException as e: return error(e) print('User {0} has {1} mutual friends'.format(config.user, len(mutualfriends))) browser = mechanize.Browser() def getrssurl(url): browser.open(FMF_URL) browser.select_form(nr=FMF_FORM_ID) browser['url'] = url browser['user'] = config.user browser['pass'] = config.password response = browser.submit() url_re = r'\<p id="viewrss"\>\<a href="(.*)" onclick.*\<\/p\>' return re.search(url_re, response.read()).group(1) data = ((username, getrssurl(LJ_RSS_URL.format(username))) for username in mutualfriends) document = xml.dom.minidom.Document() opml = document.createElement('opml') opml.setAttribute('version', '1.1') document.appendChild(opml) body = document.createElement('body') opml.appendChild(body) folder = document.createElement('outline') folder.setAttribute('text', 'lj_mutual') body.appendChild(folder) print('Working with FreeMyFeed...') progressbar = ProgressBar(maxval=len(mutualfriends)) for username, url in data: entry = document.createElement('outline') entry.setAttribute('title', username) entry.setAttribute('text', username) entry.setAttribute('xmlUrl', url) folder.appendChild(entry) progressbar.update(1) progressbar.finish() print('Creating OPML-file...') with open(config.filename, 'w') as opmlfile: opmlfile.write(document.toprettyxml(indent=' ' * 2)) return 0
def main(): global config, log oparser = optparse.OptionParser( usage='%prog [options] <source> <destination>', version=APP_VERSION_STRING, prog=APP_SHORT_NAME) oparser.add_option('-n', '--dry-run', action='store_true', dest='dry_run', default=False, help="don't perform write actions, just simulate") oparser.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False, help="show operations log") oparser.add_option('', '--no-progressbar', action='store_false', dest='pbar', default=True, help="don't show progress bar") optgroup = optparse.OptionGroup(oparser, 'File handling options') optgroup.add_option('-m', '--method', dest='method', default=M_COPY, help='file processing method ({0})'.format('|'.join(config.methods))) optgroup.add_option('-r', '--remove-empty', action='store_true', dest='remove_empty', default=False, help='remove empty directories') optgroup.add_option('', '--remove-duplicates', action='store_true', dest='remove_duplicates', default=False, help='remove files that already exist in repository') oparser.add_option_group(optgroup) optgroup = optparse.OptionGroup(oparser, 'CSV options') optgroup.add_option('', '--csv', dest='csv', metavar='FILENAME', default='libgen.csv', help='path to csv (%default)') oparser.add_option_group(optgroup) optgroup = optparse.OptionGroup(oparser, "DB connection options") optgroup.add_option('', '--db-host', default='localhost', help='DB host (%default)') optgroup.add_option('', '--db-name', default='bookwarrior', help='DB name (%default)') optgroup.add_option('', '--db-user', help='DB user') optgroup.add_option('', '--db-passwd', metavar='PASSWD', default='', help='DB password (empty)') oparser.add_option_group(optgroup) (options, args) = oparser.parse_args() if len(args) != 2: oparser.error('Wrong number of arguments') if options.method not in config.methods: oparser.error(u'Unknown file processing method "{0}"'.format(options.method)) if config.methods[options.method] is None: return error(config.get_error_message(options.method)) config.src, config.dst = (os.path.abspath(arg).decode(config.encoding) for arg in args) if not os.path.isdir(config.src): return error(u'Directory {0} not found'.format(config.src)) if not os.path.isdir(config.dst): return error(u'Directory {0} not found'.format(config.dst)) if not os.access(config.src, os.R_OK): return error(u'Not enough rights for reading from %s' % config.src) if ((options.remove_empty or options.remove_duplicates or options.method == M_MOVE) and not os.access(config.src, os.W_OK) ): return error(u'Not enough rights for writing to %s' % config.src) if not os.access(config.dst, os.W_OK): return error(u'Not enough rights for writing to %s' % config.dst) # проверим, поддерживает ли файловая система создание ссылок # в Windows мягкие и жёсткие ссылки можно создавать только на NTFS # (жёсткие — только в пределах одного диска) if config.windows and options.method in (M_SYMLINK, M_HARDLINK): message = config.checkfs(options.method) if message: return error(message) if options.db_user: worker = loader.DBLoader(options.db_host, options.db_name, options.db_user, options.db_passwd) else: if not os.path.isfile(options.csv): return error(u'File {0} not found'.format(options.csv)) worker = loader.CSVLoader(options.csv) print('Loading Library Genesis...') library = worker.load(options.pbar) library_filesizes = set(value[1] for value in library.values()) print('{0} books loaded'.format(len(library))) print('Analyzing total size of files for processing...', end=' ') src_size = dirsize(config.src) print(bytes_to_human(src_size)) print('Scanning...') processed, added, duplicate = ProgressCounter(), ProgressCounter(), ProgressCounter() pbar = ProgressBar(maxval=src_size, displaysize=True, enabled=options.pbar) log.set_pbar(pbar) delta = src_size / CHECK_PROGRESS_DIVIDER for path, dirs, files in os.walk(config.src): for file in files: fullpath = os.path.join(path, file) filesize = os.path.getsize(fullpath) # если в базе есть файл такого размера if filesize in library_filesizes: md5 = md5hash(fullpath) # и совпал по хешу if md5 in library: # то обрабатываем его already_in_repo = process(fullpath, library[md5][0], options) if already_in_repo: duplicate.add(filesize) else: added.add(filesize) processed.add(filesize) # будем обновлять, только если накопилось достаточно файлов if processed.size - pbar.curval >= delta: pbar.set(processed.size) if not options.dry_run and options.remove_empty and dirsize(path) == 0: shutil.rmtree(path) pbar.finish() log.unset_pbar() print('Processed: {0} ({1})'.format( processed.count, bytes_to_human(processed.size))) print('Added to repository ({0}): {1} ({2})'.format( config.method_descriptions[options.method], added.count, bytes_to_human(added.size))) print('Duplicates {0}: {1} ({2})'.format( 'removed' if options.remove_duplicates else 'found', duplicate.count, bytes_to_human(duplicate.size))) return 0