main.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. #!/usr/bin/env python3
  2. # -*- coding: UTF-8 -*-
  3. import logging
  4. import os
  5. import re
  6. import sys
  7. from gettext import gettext as _
  8. import tornado.httpserver
  9. import tornado.ioloop
  10. from social_tornado.models import init_social
  11. from sqlalchemy import create_engine
  12. from sqlalchemy.orm import scoped_session, sessionmaker
  13. from tornado import web
  14. from tornado.options import define, options
  15. from webserver import loader, models, social_routes, handlers
  16. CONF = loader.get_settings()
  17. define("host", default="", type=str, help=_("The host address on which to listen"))
  18. define("port", default=8080, type=int, help=_("The port on which to listen."))
  19. define("path-calibre", default="/usr/lib/calibre", type=str, help=_("Path to calibre package."))
  20. define("path-resources", default="/usr/share/calibre", type=str, help=_("Path to calibre resources."))
  21. define("path-plugins", default="/usr/lib/calibre/calibre/plugins", type=str, help=_("Path to calibre plugins."))
  22. define("path-bin", default="/usr/bin", type=str, help=_("Path to calibre binary programs."))
  23. define("with-library", default=CONF["with_library"], type=str, help=_("Path to the library folder"))
  24. define("syncdb", default=False, type=bool, help=_("Create all tables"))
  25. define("update-config", default=False, type=bool, help=_("update config when system upgrade"))
  26. def init_calibre():
  27. path = options.path_calibre
  28. if path not in sys.path:
  29. sys.path.insert(0, path)
  30. sys.resources_location = options.path_resources
  31. sys.extensions_location = options.path_plugins
  32. sys.executables_location = options.path_bin
  33. try:
  34. import calibre # noqa: F401
  35. except Exception as e:
  36. import logging
  37. import traceback
  38. logging.error(traceback.format_exc())
  39. raise ImportError(_("Can not import calibre. Please set the corrent options.\n%s" % e))
  40. if not options.with_library:
  41. sys.stderr.write(
  42. _(
  43. "No saved library path. Use the --with-library option"
  44. " to specify the path to the library you want to use."
  45. )
  46. )
  47. sys.stderr.write("\n")
  48. sys.exit(2)
  49. def safe_filename(filename):
  50. return re.sub(r"[\/\\\:\*\?\"\<\>\|]", "_", filename) # 替换为下划线
  51. # the codes is from calibre source code. just change 'ascii_filename' to 'safe_filename'
  52. def utf8_construct_path_name(book_id, title, author):
  53. from calibre.db.backend import DB, WINDOWS_RESERVED_NAMES
  54. book_id = " (%d)" % book_id
  55. lm = DB.PATH_LIMIT - (len(book_id) // 2) - 2
  56. lm = lm // 4 # UTF8 is 1~4 char
  57. author = safe_filename(author)[:lm]
  58. title = safe_filename(title.lstrip())[:lm].rstrip()
  59. if not title:
  60. title = "Unknown"[:lm]
  61. try:
  62. while author[-1] in (" ", "."):
  63. author = author[:-1]
  64. except IndexError:
  65. author = ""
  66. if not author:
  67. author = safe_filename(_("Unknown"))
  68. if author.upper() in WINDOWS_RESERVED_NAMES:
  69. author += "w"
  70. return "%s/%s%s" % (author, title, book_id)
  71. def utf8_construct_file_name(book_id, title, author, extlen):
  72. from calibre.db.backend import DB
  73. extlen = max(extlen, 14) # 14 accounts for ORIGINAL_EPUB
  74. lm = (DB.PATH_LIMIT - extlen - 2) // 2
  75. lm = lm // 4 # UTF8 is 1~4 char
  76. if lm < 5:
  77. raise ValueError("Extension length too long: %d" % extlen)
  78. author = safe_filename(author)[:lm]
  79. title = safe_filename(title.lstrip())[:lm].rstrip()
  80. if not title:
  81. title = "Unknown"[:lm]
  82. name = title + " - " + author
  83. while name.endswith("."):
  84. name = name[:-1]
  85. if not name:
  86. name = safe_filename(_("Unknown"))
  87. return name
  88. def bind_utf8_book_names(cache):
  89. cache.backend.construct_path_name = utf8_construct_path_name
  90. cache.backend.construct_file_name = utf8_construct_file_name
  91. return
  92. def bind_topdir_book_names(cache):
  93. old_construct_path_name = cache.backend.construct_path_name
  94. def new_construct_path_name(*args, **kwargs):
  95. s = old_construct_path_name(*args, **kwargs)
  96. ns = s[0] + "/" + s
  97. logging.debug("new str = %s" % ns)
  98. return ns
  99. cache.backend.construct_path_name = new_construct_path_name
  100. return
  101. def make_app():
  102. auth_db_path = CONF["user_database"]
  103. logging.info("Init library with [%s]" % options.with_library)
  104. logging.info("Init AuthDB with [%s]" % auth_db_path)
  105. logging.info("Init Static with [%s]" % CONF["resource_path"])
  106. logging.info("Init HTML with [%s]" % CONF["html_path"])
  107. logging.info("Init Nuxtjs with [%s]" % CONF["nuxt_env_path"])
  108. if options.update_config:
  109. logging.info("updating configs ...")
  110. # 触发一次空白配置更新
  111. from webserver.handlers.admin import SettingsSaverLogic
  112. logic = SettingsSaverLogic()
  113. logic.update_nuxtjs_env()
  114. logging.info("done")
  115. sys.exit(0)
  116. # build sql session factory
  117. engine = create_engine(auth_db_path, **CONF["db_engine_args"])
  118. ScopedSession = scoped_session(sessionmaker(bind=engine, autoflush=True, autocommit=False))
  119. models.bind_session(ScopedSession)
  120. init_social(models.Base, ScopedSession, CONF)
  121. if options.syncdb:
  122. models.user_syncdb(engine)
  123. logging.info("Create tables into DB")
  124. sys.exit(0)
  125. init_calibre()
  126. from calibre.db.legacy import LibraryDatabase
  127. from calibre.utils.date import fromtimestamp
  128. book_db = LibraryDatabase(os.path.expanduser(options.with_library))
  129. cache = book_db.new_api
  130. # hook 1: 按字母作为第一级目录,解决书库子目录太多的问题
  131. if CONF["BOOK_NAMES_FORMAT"].lower() == "utf8":
  132. bind_utf8_book_names(cache)
  133. else:
  134. bind_topdir_book_names(cache)
  135. # hook 2: don't force GUI
  136. from calibre import gui2
  137. old_must_use_qt = gui2.must_use_qt
  138. def new_must_use_qt(headless=True):
  139. try:
  140. old_must_use_qt(headless)
  141. except:
  142. pass
  143. gui2.must_use_qt = new_must_use_qt
  144. path = CONF["resource_path"] + "/calibre/default_cover.jpg"
  145. with open(path, "rb") as cover_file:
  146. default_cover = cover_file.read()
  147. app_settings = dict(CONF)
  148. app_settings.update(
  149. {
  150. "legacy": book_db,
  151. "cache": cache,
  152. "ScopedSession": ScopedSession,
  153. "build_time": fromtimestamp(os.stat(path).st_mtime),
  154. "default_cover": default_cover,
  155. }
  156. )
  157. logging.info("Now, Running...")
  158. app = web.Application(social_routes.SOCIAL_AUTH_ROUTES + handlers.routes(), **app_settings)
  159. app._engine = engine
  160. return app
  161. def get_upload_size():
  162. n = 1
  163. s = CONF["MAX_UPLOAD_SIZE"].lower().strip()
  164. if s.endswith("k") or s.endswith("kb"):
  165. n = 1024
  166. s = s.split("k")[0]
  167. elif s.endswith("m") or s.endswith("mb"):
  168. n = 1024 * 1024
  169. s = s.split("m")[0]
  170. elif s.endswith("g") or s.endswith("gb"):
  171. n = 1024 * 1024 * 1024
  172. s = s.split("g")[0]
  173. s = s.strip()
  174. return int(s) * n
  175. def main():
  176. tornado.options.parse_command_line()
  177. app = make_app()
  178. http_server = tornado.httpserver.HTTPServer(app, xheaders=True, max_buffer_size=get_upload_size())
  179. http_server.listen(options.port, options.host)
  180. tornado.ioloop.IOLoop.instance().start()
  181. from flask.ext.sqlalchemy import _EngineDebuggingSignalEvents
  182. _EngineDebuggingSignalEvents(app._engine, app.import_name).register()
  183. if __name__ == "__main__":
  184. sys.path.append(os.path.dirname(__file__))
  185. sys.exit(main())