#!/usr/bin/python3 -u # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # # Copyright (C) 2014-2015, 2017 Bashton Ltd # Copyright (C) 2017 JaquerEspeis # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # IPFS transport for apt. # This is based on apt-transport-s3: # https://github.com/BashtonLtd/apt-transport-s3 import hashlib import os import sys import ipfsapi class IPFS_method(): __eof = False def __init__(self): self.send_capabilities() def send_capabilities(self): self._send(100, { 'Version': '1.1', 'Single-Instance': 'true'}) def send_status(self, headers): self._send(102, headers) def send_uri_start(self, headers): self._send(200, headers) def send_uri_done(self, headers): self._send(201, headers) def send_uri_failure(self, headers): self._send(400, headers) def _send(self, code, headers): message = APTMessage(code, headers) sys.stdout.write(message.encode()) def run(self): """Loop through requests on stdin""" while True: message = self._read_message() if message is None: return 0 if message['number'] == 600: uri = message['URI'][0] filename = message['Filename'][0] try: self.fetch(uri, filename) except Exception as e: self.send_uri_failure({ 'URI': uri, 'Message': e.__class__.__name__ + ": " + str(e)}) else: return 100 def _read_message(self): """Read an apt message. Apt uses for communication with its methods the text protocol similar to http. This function parses the protocol messages from stdin. """ if self.__eof: return None result = {} line = sys.stdin.readline() while line == '\n': line = sys.stdin.readline() if not line: self.__eof = True return None s = line.split(" ", 1) result['number'] = int(s[0]) result['text'] = s[1].strip() while not self.__eof: line = sys.stdin.readline() if not line: self.__eof = True return result if line == '\n': return result (item, value) = line.split(":", 1) if not result.get(item): result[item] = [] result[item].append(value.strip()) return result def fetch(self, uri, filename): ipfs_file_path = uri.replace('ipfs:', '', 1) self.send_status({'URI': uri, 'Message': 'Waiting for stats'}) ipfs = ipfsapi.connect('127.0.0.1', 5001) stat = ipfs.object_stat(ipfs_file_path) self.send_uri_start({ 'URI': uri, # FIXME We can't get the real size without downloading the file. # https://github.com/ipfs/go-ipfs/issues/2071 # --elopio - 20171203 'Size': stat['CumulativeSize']}) # XXX IPFS downloads the file to the current directory. # --elopio - 20171203 cwd = os.getcwd() os.chdir(os.path.dirname(filename)) try: ipfs.get(ipfs_file_path) os.rename( os.path.basename(ipfs_file_path), filename) finally: os.chdir(cwd) hash_md5 = hashlib.md5() hash_sha256 = hashlib.sha256() hash_sha512 = hashlib.sha512() with open(filename, 'rb') as fetched_file: for chunk in iter(lambda: fetched_file.read(4096), b''): hash_md5.update(chunk) hash_sha256.update(chunk) hash_sha512.update(chunk) self.send_uri_done({ 'URI': uri, 'Filename': filename, 'Size': os.stat(filename).st_size, 'MD5-Hash': hash_md5.hexdigest(), 'MD5Sum-Hash': hash_md5.hexdigest(), 'SHA256-Hash': hash_sha256.hexdigest(), 'SHA512-Hash': hash_sha512.hexdigest()}) class APTMessage(): _MESSAGE_CODES = { 100: 'Capabilities', 102: 'Status', 200: 'URI Start', 201: 'URI Done', 400: 'URI Failure', 600: 'URI Acquire', 601: 'Configuration' } def __init__(self, code, headers): self._code = code self._headers = headers def encode(self): result = '{0} {1}\n'.format( self._code, self._MESSAGE_CODES[self._code]) for header_key in self._headers: if self._headers[header_key] is not None: result += '{0}: {1}\n'.format( header_key, self._headers[header_key]) return result + '\n' if __name__ == '__main__': try: method = IPFS_method() exitcode = method.run() sys.exit(exitcode) except KeyboardInterrupt: pass