From 76fad1db9bf3d812333ca9f65b1ed87c8549cab7 Mon Sep 17 00:00:00 2001 From: Oleg Broytman Date: Mon, 6 Jun 2016 22:42:13 +0300 Subject: [PATCH] Allow to set download format --- docs-ru/command_line.rst | 28 ++++++++++++- docs/command_line.rst | 27 +++++++++++- m_librarian/config.py | 4 +- m_librarian/download.py | 89 ++++++++++++++++++++++++++++++++++++++-- sample/m_librarian.conf | 15 +++++++ scripts/ml-search.py | 3 +- tests/test_format.py | 18 ++++++++ 7 files changed, 172 insertions(+), 12 deletions(-) create mode 100755 tests/test_format.py diff --git a/docs-ru/command_line.rst b/docs-ru/command_line.rst index 4179558..872ef5c 100644 --- a/docs-ru/command_line.rst +++ b/docs-ru/command_line.rst @@ -116,8 +116,8 @@ ml-search.py При использовании опции `-v` также выводится id из БД. -Поиск книг -^^^^^^^^^^ +Поиск и загрузка книг +^^^^^^^^^^^^^^^^^^^^^ Использование:: @@ -170,6 +170,30 @@ ml-search.py использовать эту команду — совместно с опцией `--id`. Файл сохраняется в текущую директорию с тем именем, под которым он храниться в библиотеке. +Ключ `format` в секции `[download]` файла конфигурации m_librarian.conf + +| [download] +| format = %a/%s/%n %t + +позволяет управлять именами директорий и именем файла, куда m_Librarian +будет сохранять файлы. Формат по умолчанию `%f`, т.е. просто имя файла. +Другие доступные спецификаторы:: + + %a - автор (один из, если их несколько) + %e - расширение имени файла + %f - имя файла + %G - жанр (один из, если их несколько), имя из БД + %g - жанр (один из, если их несколько), название + %l - язык + %n - номер в серии (или 0) + %s - серия + %t - название + +Формат не должен заканчиваться на разделитель директорий (`/` или `\\`). +Если спецификатор `%e` (расширение) не найден в формате, он добавляется +в конец с точкой в качестве разделителя. Т.о. формат `%f` эквивалентен +формату `%f.%e`. + Поиск расширений ^^^^^^^^^^^^^^^^ diff --git a/docs/command_line.rst b/docs/command_line.rst index f9911a8..ef27723 100644 --- a/docs/command_line.rst +++ b/docs/command_line.rst @@ -113,8 +113,8 @@ name starts with "mack", case insensitive. With one option `-v` it also prints database id. -Book search -^^^^^^^^^^^ +Book searching and downloading +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Usage:: @@ -167,6 +167,29 @@ file. The option allows to download exactly one book. The simplest way to use it is via option `--id`. The file is downloaded into the current directory with the name from the library. +Configuration key + +| [download] +| format = %a/%s/%n %t + +allows to set format for the download file pathname. Default format is +`%f`, i.e. just filename. Other format specifiers are:: + + %a - author (one of if many) + %e - file extension + %f - file name in archive + %G - genre (one of if many), name + %g - genre (one of if many), title + %l - language + %n - series number (or 0) + %s - series + %t - title + +Format must not end in directory separator (`/` or `\\`). If specifier +`%e` (extension) is not found in the format it is appended +unconditionally with a dot. That is, format `%f` is equivalent to +`%f.%e`. + Extension search ^^^^^^^^^^^^^^^^ diff --git a/m_librarian/config.py b/m_librarian/config.py index d896bf5..ca7eadd 100755 --- a/m_librarian/config.py +++ b/m_librarian/config.py @@ -1,7 +1,7 @@ #! /usr/bin/env python import os -from ConfigParser import SafeConfigParser +from ConfigParser import RawConfigParser __all__ = ['get_config'] @@ -43,7 +43,7 @@ def get_config(config_filename=None): if _ml_config is None: if config_filename is None: config_filename = find_config_file() - _ml_config = SafeConfigParser() + _ml_config = RawConfigParser() _ml_config.read(config_filename) return _ml_config diff --git a/m_librarian/download.py b/m_librarian/download.py index 361f7aa..a66e692 100755 --- a/m_librarian/download.py +++ b/m_librarian/download.py @@ -9,22 +9,103 @@ from .config import get_config __all__ = ['download'] +_format = '%f' +_compile_format = True +_compiled_format = '%(file)s' + + +def _do_compile_format(): + global _format, _compile_format, _compiled_format + if _compile_format: + _compile_format = False + try: + _format = get_config().get('download', 'format') + except: + return + got_percent = False + compiled = [] + for c in _format: + if c == '%': + if got_percent: + got_percent = False + compiled.append('%') + else: + got_percent = True + else: + if got_percent: + got_percent = False + if c == 'a': + new_format = u'%(author)s' + elif c == 'e': + new_format = u'%(extension)s' + elif c == 'f': + new_format = u'%(file)s' + elif c == 'G': + new_format = u'%(gname)s' + elif c == 'g': + new_format = u'%(gtitle)s' + elif c == 'l': + new_format = u'%(language)s' + elif c == 'n': + new_format = u'%(ser_no)d' + elif c == 's': + new_format = u'%(series)s' + elif c == 't': + new_format = u'%(title)s' + else: + raise ValueError('Bad format specifier "%%%c"' % c) + compiled.append(new_format) + else: + compiled.append(c) + _compiled_format = ''.join(compiled) + + _library_path = None -def download(archive, filename, date, path=None): +def download(book, path=None): if path is None: global _library_path if _library_path is None: _library_path = get_config().get('library', 'path') path = _library_path - zf = ZipFile(os.path.join(path, archive), 'r') - infile = zf.open(filename) + global _compiled_format + _do_compile_format() + if _compiled_format[-1] in ('\0', '\\', '/'): + raise ValueError('Bad format: "%s"' % _compiled_format) + bdict = {} + bdict['author'] = book.authors[0].fullname + bdict['extension'] = book.extension.name + bdict['file'] = book.file + genre = book.genres[0] + bdict['gname'] = genre.name + bdict['gtitle'] = genre.title + bdict['language'] = book.language.name + bdict['ser_no'] = book.ser_no or 0 + bdict['series'] = book.series + bdict['title'] = book.title + if '%(extension)s' not in _compiled_format: + _compiled_format += '.%(extension)s' + filename = _compiled_format % bdict + try: + os.makedirs(os.path.dirname(filename)) + except OSError: + pass # Already exists + zf = ZipFile(os.path.join(path, book.archive), 'r') + infile = zf.open('%s.%s' % (book.file, book.extension.name)) outfile = open(filename, 'wb') copyfileobj(infile, outfile) outfile.close() infile.close() zf.close() - dt = mktime(date.timetuple()) + dt = mktime(book.date.timetuple()) os.utime(filename, (dt, dt)) + + +def test(): + _do_compile_format() + print _compiled_format + +if __name__ == '__main__': + test() diff --git a/sample/m_librarian.conf b/sample/m_librarian.conf index e3863aa..2ed0f8a 100644 --- a/sample/m_librarian.conf +++ b/sample/m_librarian.conf @@ -11,3 +11,18 @@ [library] path = /var/lib/LRE_Flibusta + +[download] +# Download formats: +# %a - author (one of) +# %e - file extension +# %f - file name in archive +# %G - genre (one of), name +# %g - genre (one of), title +# %l - language +# %n - series number (or 0) +# %s - series +# %t - title +# Examples: +# format = %f (this is the default) +# format = %a/%s/%n %t diff --git a/scripts/ml-search.py b/scripts/ml-search.py index 0d995e5..d19c5b4 100755 --- a/scripts/ml-search.py +++ b/scripts/ml-search.py @@ -144,8 +144,7 @@ def _search_books(case_sensitive, search_type, args): "(found %d).\n" % count) sys.exit(1) book = books[0] - download(book.archive, '%s.%s' % (book.file, book.extension.name), - book.date, args.path) + download(book, args.path) return count = 0 for book in books: diff --git a/tests/test_format.py b/tests/test_format.py new file mode 100755 index 0000000..9d52fb9 --- /dev/null +++ b/tests/test_format.py @@ -0,0 +1,18 @@ +#! /usr/bin/env python + + +import unittest +from tests import main +from m_librarian import config, download + + +class TestFormat(unittest.TestCase): + def test_compile_format(self): + config.get_config().set('download', 'format', '%a/%s/%n %t') + download._do_compile_format() + self.assertEqual(download._compiled_format, + u'%(author)s/%(series)s/%(ser_no)d %(title)s') + + +if __name__ == "__main__": + main() -- 2.39.2