You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

backend.py 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import os.path
  2. import sys
  3. import shutil
  4. from datetime import datetime, timedelta
  5. import socketserver
  6. import socket # for the exceptions
  7. from libkiara import ed2khash, database, anidb, AbandonShip
  8. config = {}
  9. # Define a dump object to pass around.
  10. class KiaraFile(object):
  11. def __init__(self, name):
  12. self.file = open(name, 'rb')
  13. self.file_name = name
  14. self.name = os.path.basename(name)
  15. self.size = os.path.getsize(name)
  16. self.dirty = False # Should this be saved.
  17. self.updated = None
  18. self.in_anidb = True
  19. self.hash = None
  20. self.watched = False
  21. self.fid = None
  22. self.mylist_id = None
  23. self.aid = None
  24. self.crc32 = None
  25. self.type = None
  26. self.anime_total_eps = None
  27. self.anime_name = None
  28. self.anime_type = None
  29. self.ep_no = None
  30. self.group_name = None
  31. self.added = False
  32. def misses_info(self):
  33. return (
  34. self.fid == None or
  35. self.mylist_id == None or
  36. self.aid == None or
  37. self.crc32 == None or
  38. self.file_type == None or
  39. self.anime_total_eps == None or
  40. self.anime_name == None or
  41. self.anime_type == None or
  42. self.ep_no == None or
  43. self.group_name == None)
  44. def is_movie(self):
  45. return (
  46. self.anime_type == 'Movie' or
  47. self.anime_type == 'OVA' and self.anime_total_eps == 1 or
  48. self.anime_type == 'Web' and self.anime_total_eps == 1)
  49. def __str__(self):
  50. parts = [self.name]
  51. if self.hash:
  52. parts.append(self.hash)
  53. if self.dirty:
  54. parts.append('(unsaved)')
  55. return ' '.join(parts)
  56. def makedirs(path):
  57. parts = os.path.abspath(path).split(os.path.sep)
  58. path = '/'
  59. while parts:
  60. path = os.path.join(path, parts.pop(0))
  61. if not os.path.exists(path):
  62. os.makedirs(path)
  63. def rmdirp(path):
  64. while path:
  65. if os.listdir(path) == []:
  66. yield ['status', 'removing_empty_dir', path]
  67. os.rmdir(path)
  68. path = os.path.dirname(path)
  69. else:
  70. return
  71. def pad(length, num):
  72. try:
  73. int(num)
  74. return "0" * max(0, (length - len(num))) + num
  75. except ValueError:
  76. return num
  77. class Handler(socketserver.BaseRequestHandler):
  78. def __init__(self, *args, **kwargs):
  79. self.queued_messages = []
  80. return super().__init__(*args, **kwargs)
  81. def reply(self, message, catch_fails=True):
  82. if type(message) == tuple:
  83. message = list(message)
  84. if type(message) == list:
  85. message = '\n'.join(message)
  86. self.write(message + '\n', catch_fails)
  87. def write(self, message, catch_fails=True):
  88. try:
  89. self.request.send(bytes(message+'\n', 'UTF-8'))
  90. except socket.error:
  91. if catch_fails:
  92. self.queued_messages.append(message)
  93. def handle(self):
  94. while self.queued_messages:
  95. self.reply(self.queued_messages.pop(0), False)
  96. data = self.request.recv(1024).strip().decode('UTF-8')
  97. act, file_name = data.split(' ', 1)
  98. if act == '-':
  99. # Non-file related commands
  100. if file_name == 'ping':
  101. if anidb.ping(self):
  102. self.reply(['success', 'anidb_ping_ok'])
  103. else:
  104. self.reply(['error', 'anidb_ping_error'])
  105. if file_name == 'dups':
  106. dups = False
  107. for line in database.find_duplicates():
  108. dups = True
  109. self.reply(line)
  110. if not dups:
  111. self.reply(['success', 'dups_none'])
  112. if file_name.startswith('forget'):
  113. for fid in file_name.split(' ')[1:]:
  114. for line in database.forget(int(fid)):
  115. self.reply(line)
  116. if file_name == 'kill':
  117. self.reply(['status', 'backend_shutting_down'])
  118. self.shutdown()
  119. else:
  120. try:
  121. # File related commands
  122. file = KiaraFile(file_name)
  123. # Load file info.
  124. database.load(file)
  125. if not file.hash:
  126. self.reply(['status', 'hashing_file', file.name])
  127. file.hash = ed2khash.hash(file.file)
  128. database.load(file)
  129. if file.misses_info() or not file.updated or \
  130. 'u' in act and \
  131. file.updated < datetime.now() - timedelta(days=7):
  132. anidb.load_info(file, self)
  133. if not file.fid:
  134. self.reply(['error', 'anidb_file_unknown'])
  135. else:
  136. if (not file.added) and 'a' in act:
  137. self.reply(['status', 'anidb_adding_file',
  138. file.anime_name, str(file.ep_no)])
  139. anidb.add(file, self)
  140. if not file.watched and 'w' in act:
  141. self.reply(['status', 'anidb_marking_watched',
  142. file.anime_name, str(file.ep_no)])
  143. anidb.watch(file, self)
  144. if 'o' in act:
  145. anime_name = file.anime_name.replace('/', '_')
  146. dir = os.path.join(os.path.expanduser((
  147. config['basepath_movie']
  148. if file.is_movie()
  149. else config['basepath_series'])), anime_name)
  150. self.reply(['debug', 'file_type_location',
  151. file.anime_type, dir])
  152. makedirs(os.path.normpath(dir))
  153. new_name = None
  154. if file.anime_total_eps == "1":
  155. new_name = "[%s] %s [%s]%s" % (
  156. file.group_name, anime_name, file.crc32,
  157. os.path.splitext(file_name)[1])
  158. else:
  159. new_name = "[%s] %s - %s [%s]%s" % (
  160. file.group_name, anime_name,
  161. pad(
  162. len(str(file.anime_total_eps)),
  163. str(file.ep_no)),
  164. file.crc32, os.path.splitext(file_name)[1])
  165. new_path = os.path.join(dir, new_name)
  166. if file_name == new_path:
  167. self.reply(['status', 'file_already_organized',
  168. new_name])
  169. else:
  170. if os.path.isfile(new_path) and not 'x' in act:
  171. self.reply(['error', 'file_exists', new_path])
  172. else:
  173. if 'c' in act:
  174. shutil.copyfile(file_name, new_path)
  175. self.reply(['success', 'file_copied',
  176. file_name, new_path])
  177. else:
  178. shutil.move(file_name, new_path)
  179. self.reply(['success', 'file_moved',
  180. file_name, new_path])
  181. file.name = new_name
  182. file.dirty = True
  183. for r in rmdirp(os.path.dirname(file_name)):
  184. self.reply(r)
  185. database.save(file)
  186. except SystemExit as e:
  187. self.request.sendall(bytes('---end---', 'UTF-8'))
  188. self.request.close()
  189. sys.exit(status)
  190. except AbandonShip:
  191. self.reply(['error', 'abandon_ship'])
  192. # Ignore the actual error, the connection will be closed now
  193. self.request.sendall(bytes('---end---', 'UTF-8'))
  194. def serve(cfg):
  195. global config
  196. config = cfg
  197. anidb.config = config
  198. database.connect(os.path.expanduser(config['database']), config['user'])
  199. try:
  200. os.remove(os.path.expanduser(config['session']))
  201. except: pass
  202. run = [True]
  203. def killer(r):
  204. r[0] = False
  205. class ActualHandler(Handler):
  206. def __init__(self, *args, **kwargs):
  207. self.shutdown = lambda: killer(run)
  208. return super().__init__(*args, **kwargs)
  209. server = socketserver.UnixStreamServer(
  210. os.path.expanduser(config['session']), ActualHandler)
  211. while run[0]:
  212. server.handle_request()