__main__.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import os
  2. import os.path
  3. import sys
  4. import math
  5. import time
  6. import atexit
  7. import struct
  8. import argparse
  9. from pathlib import Path
  10. from . import log
  11. from . import yafn
  12. from pyvis.network import Network
  13. class Progress:
  14. def __init__(self, max):
  15. self.max = max
  16. self.CHARS = ('/', '-', '\\', '|')
  17. self.index = 0
  18. self.length = 0
  19. self.ready = 0
  20. self._display(0)
  21. def _render(self):
  22. text = f'{self.ready}/{self.max} piece{"s" if self.ready != 1 else ""} ready ... {self.CHARS[self.index]}\n'
  23. self.index += 1
  24. if self.index == len(self.CHARS):
  25. self.index = 0
  26. self.length = len(text)
  27. return text
  28. def _display(self, ready):
  29. text = self._render()
  30. sys.stdout.write(text)
  31. sys.stdout.flush()
  32. def update(self, ready):
  33. self.ready += ready
  34. sys.stdout.write("\033[F")
  35. sys.stdout.write("\033[K")
  36. sys.stdout.write(self._render())
  37. sys.stdout.flush()
  38. class Metafile:
  39. HEADER = b'\x80YAFN-METAFILE\x00\x00'
  40. HEADER_LEN = len(HEADER)
  41. def __init__(self, filename, size, pieces):
  42. self.filename = filename
  43. self.size = size
  44. self.pieces = pieces
  45. def save(self, path):
  46. with open(path, 'wb') as f:
  47. f.write(Metafile.HEADER)
  48. filename = self.filename.encode('UTF-8')
  49. filename_len = len(filename)
  50. f.write(struct.pack('!I', filename_len))
  51. f.write(filename)
  52. f.write(struct.pack('!L', self.size))
  53. pieces_count = len(self.pieces)
  54. f.write(struct.pack('!L', pieces_count))
  55. for hash in self.pieces:
  56. f.write(hash)
  57. @staticmethod
  58. def load(path):
  59. with open(path, 'rb') as f:
  60. header = f.read(Metafile.HEADER_LEN)
  61. if header != Metafile.HEADER:
  62. raise ValueError
  63. filename_len = f.read(4)
  64. filename_len = struct.unpack('!I', filename_len)[0]
  65. filename = f.read(filename_len)
  66. filename = filename.decode('UTF-8')
  67. filename = Path(filename).name
  68. size = f.read(4)
  69. size = struct.unpack('!I', size)[0]
  70. pieces_count = f.read(4)
  71. pieces_count = struct.unpack('!L', pieces_count)[0]
  72. if pieces_count < 1:
  73. raise ValueError
  74. pieces = []
  75. while pieces_count > 0:
  76. piece_hash = f.read(76)
  77. if len(piece_hash) != 76:
  78. raise ValueError
  79. pieces.append(piece_hash)
  80. pieces_count -= 1
  81. return Metafile(filename, size, pieces)
  82. COLORS = [
  83. (0, 255, 0),
  84. (255, 255, 0),
  85. (255, 0, 0)
  86. ]
  87. def pick_color(index):
  88. index = min(index, 10) * 0.1
  89. n3 = 0
  90. if index <= 0:
  91. n1 = 0
  92. n2 = 0
  93. elif index >= 1:
  94. n1 = len(COLORS) - 1
  95. n2 = len(COLORS) - 1
  96. else:
  97. index = index * (len(COLORS) - 1)
  98. n1 = math.floor(index)
  99. n2 = n1 + 1
  100. n3 = index - n1
  101. color = (
  102. (COLORS[n2][0] - COLORS[n1][0]) * n3 + COLORS[n1][0],
  103. (COLORS[n2][1] - COLORS[n1][1]) * n3 + COLORS[n1][1],
  104. (COLORS[n2][2] - COLORS[n1][2]) * n3 + COLORS[n1][2]
  105. )
  106. return f'#{int(color[0]):02x}{int(color[1]):02x}{int(color[2]):02x}'
  107. def distance(graph, start, end):
  108. visited = []
  109. queue = [[start]]
  110. while queue:
  111. path = queue.pop(0)
  112. node = path[-1]
  113. if node not in visited:
  114. near = graph[node]
  115. for near_node in near:
  116. if near_node == end:
  117. return len(path)
  118. queue.append(path + [near_node])
  119. visited.append(node)
  120. return 10
  121. def build_graph(graph, map, first=False):
  122. uid = yafn.encode_uid(map.uid)
  123. graph.add_node(
  124. uid,
  125. label=f'{uid[:6]}{" (this peer)" if first else ""}',
  126. title=uid
  127. )
  128. for submap in map.submaps:
  129. subuid = build_graph(graph, submap)
  130. graph.add_edge(uid, subuid)
  131. return uid
  132. parser = argparse.ArgumentParser()
  133. parser.add_argument(
  134. '-S', '--start',
  135. help='Start up a peer.',
  136. action='store_true'
  137. )
  138. parser.add_argument(
  139. '-a', '--address',
  140. help='Set a custom external address.',
  141. type=str
  142. )
  143. parser.add_argument(
  144. '-c', '--crawl',
  145. help='Create a map of the network.',
  146. action='store_true'
  147. )
  148. parser.add_argument(
  149. '-s', '--share',
  150. help='Share a file to the network',
  151. action='append',
  152. type=str,
  153. metavar='PATH'
  154. )
  155. parser.add_argument(
  156. '-q', '--query',
  157. help='Query a file from the network',
  158. action='append',
  159. type=str,
  160. metavar='METAFILE'
  161. )
  162. args = parser.parse_args()
  163. if args.crawl or args.share or args.query:
  164. interface = yafn.Interface.connect()
  165. atexit.register(interface.close)
  166. if args.crawl:
  167. log.info(f'Building a map of the network...')
  168. map = interface.crawl()
  169. if not map:
  170. log.fatal('Failed to crawl the network.')
  171. network = Network(
  172. height='100%',
  173. width='100%',
  174. bgcolor='#222222',
  175. font_color='white'
  176. )
  177. first_uid = build_graph(network, map, first=True)
  178. adj_list = network.get_adj_list()
  179. for node in network.nodes:
  180. node['value'] = min(max(len(adj_list[node['id']]), 1), 10)
  181. node['color'] = pick_color(distance(adj_list, first_uid, node['id']))
  182. filename = f'map_{int(time.time())}.html'
  183. network.save_graph(filename)
  184. log.info(f'Network map is saved as \'{filename}\'.')
  185. if args.share:
  186. for path in args.share:
  187. if not os.path.isfile(path):
  188. log.fatal(f'Not a valid file: \'{path}\'.')
  189. log.info(f'Share \'{path}\'.')
  190. with open(path, 'rb') as f:
  191. progress = Progress('_')
  192. pieces = []
  193. size = 0
  194. while True:
  195. piece = f.read(yafn.Piece.PIECE_SIZE)
  196. if not piece:
  197. break
  198. hash = interface.save(piece)
  199. if not hash:
  200. log.error('Failed to save a piece.')
  201. continue
  202. pieces.append(hash)
  203. progress.update(1)
  204. size += len(piece)
  205. filename = os.path.basename(path)
  206. metafile_name = f'{filename}.ynmf'
  207. metafile = Metafile(
  208. filename,
  209. size,
  210. pieces
  211. )
  212. try:
  213. metafile.save(metafile_name)
  214. except:
  215. log.fatal('Failed to save the metafile.')
  216. log.info(f'Metafile is saved as \'{metafile_name}\'.')
  217. log.info('Announcing the neighbour peers...')
  218. if interface.announce():
  219. log.info('Done.')
  220. else:
  221. log.error('Announcing failed.')
  222. if args.query:
  223. for path in args.query:
  224. try:
  225. metafile = Metafile.load(path)
  226. except:
  227. log.fatal(f'Cannot open metafile: \'{path}\'.')
  228. log.info(f'Query \'{metafile.filename}\'.')
  229. with open(metafile.filename, 'wb') as f:
  230. progress = Progress(len(metafile.pieces))
  231. for hash in metafile.pieces:
  232. piece = interface.query(hash)
  233. if not piece:
  234. log.error(f'Piece {hash[:32].hex()} is not available.')
  235. continue
  236. f.write(piece)
  237. progress.update(1)
  238. log.info(f'File is saved as \'{metafile.filename}\'.')
  239. if args.start:
  240. peer = yafn.Peer()
  241. peer.start(remote_addr=args.address)