From 728526517c349983faa854aeccbd2c3e7476c5ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar=20Garc=C3=ADa=20Amor?= Date: Mon, 5 Jun 2017 18:41:54 +0200 Subject: [PATCH 1/6] Initial setup as Python project --- fileteasend/__init__.py | 0 filetea.py => fileteasend/app.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 fileteasend/__init__.py rename filetea.py => fileteasend/app.py (100%) diff --git a/fileteasend/__init__.py b/fileteasend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/filetea.py b/fileteasend/app.py similarity index 100% rename from filetea.py rename to fileteasend/app.py From ee45aef1be39589be47fa996a24e4e59b0041cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar=20Garc=C3=ADa=20Amor?= Date: Mon, 5 Jun 2017 18:46:09 +0200 Subject: [PATCH 2/6] Several improvements and code fix - Better README.md. - Added setup.py for an easy installation in a virtualenv. - Updated for use python-magic pip version. - Fixed a print typo. - Fixed a small issue with Python3 (startswith expect binary). - Now server sends a `y` instead a `w` to start file transfer. --- README.md | 17 +++++++++++++++-- filetea.py | 10 ++++++++++ fileteasend/app.py | 15 ++++++--------- setup.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 11 deletions(-) create mode 100755 filetea.py create mode 100644 setup.py diff --git a/README.md b/README.md index 50be76d..452e6a9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,17 @@ -FileTeaSend -=========== +# FileTeaSend Easy file transfer using FileTea service + +## Installation + +```bash +git clone https://github.com/brechin/FileTeaSend.git +virtualenv ./fileteavenv +source ./fileteavenv/bin/activate +cd FileTeaSend +python setup.py install +``` + +## Run + +Simply run `filetea` command with file that you want to share. diff --git a/filetea.py b/filetea.py new file mode 100755 index 0000000..2d98256 --- /dev/null +++ b/filetea.py @@ -0,0 +1,10 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Distributed under terms of the GNU GPLv3 license. + +from fileteasend.app import main + +if __name__ == "__main__": + main() diff --git a/fileteasend/app.py b/fileteasend/app.py index 8bbdb32..2a00e5b 100644 --- a/fileteasend/app.py +++ b/fileteasend/app.py @@ -34,13 +34,10 @@ def send_file(session_obj, file_name_to_send, token): return resp -def register_file(session_obj, file_to_send): +def register_file(session_obj, my_uuid, file_to_send): """Register file with endpoint, returns url to use to download the file""" logger = logging.getLogger('register_file') - ms = magic.open(magic.MIME_TYPE) - ms.load() - mime_type = ms.file(file_to_send) - ms.close() + mime_type = magic.from_file(file_to_send, mime=True) send_url = '%stransport/lp/send?%s' % (__FILETEA_URL, my_uuid) send_data = 'X{"method":"addFileSources","params":[["%s","%s",%d]],"id":"1"}' % ( os.path.basename(file_to_send), mime_type, os.path.getsize(file_to_send)) @@ -56,7 +53,7 @@ def register_file(session_obj, file_to_send): file_download_url = '%s%s' % (__FILETEA_URL, send_response_json['result'][0][0]) return file_download_url -if __name__ == '__main__': +def main(): base_logger = logging.getLogger('filetea') session = requests.session() @@ -72,9 +69,9 @@ def register_file(session_obj, file_to_send): # Get a peer-id registered with endpoint my_uuid = get_peer_id(session) # Register specified file with endpoint, which returns the URL to download the file - url = register_file(session, file_to_send) + url = register_file(session, my_uuid, file_to_send) base_logger.info('URL for file %s: %s' % (file_to_send, url)) - print('URL: %s', url) + print('URL: %s' % url) while True: # Try recv, might 404 @@ -84,7 +81,7 @@ def register_file(session_obj, file_to_send): base_logger.debug(recv_response.content) recv_return_data = recv_response.content - if recv_return_data.startswith('w'): + if recv_return_data.startswith(b'y'): base_logger.debug('Got file transfer request') recv_return_data = recv_return_data[1:] diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a1843c8 --- /dev/null +++ b/setup.py @@ -0,0 +1,45 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Copyright © 2016 Óscar García Amor +# +# Distributed under terms of the GNU GPLv3 license. + +import os +from setuptools import setup + +# Utility function to read the README file. +# Used for the long_description. It's nice, because now 1) we have a top level +# README file and 2) it's easier to type in the README file than to put a raw +# string in below ... +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() + +setup( + name = "fileteasend", + version = "1.0", + author = "Jason Brechin", + author_email = "brechinj+github@gmail.com", + description = ("Easy file transfer using the FileTea service"), + license = "GPLv3", + keywords = "file web transfer easy FileTea", + url = "https://github.com/brechin/FileTeaSend", + packages=['fileteasend'], + long_description=read('README.md'), + entry_points={ + 'console_scripts': [ + 'filetea = fileteasend.app:main' + ] + }, + install_requires = [ + "requests>=2.13.0", + "python-magic>=0.4.0" + ], + classifiers=[ + "Development Status :: 3 - Alpha", + "Topic :: Utilities", + "License :: OSI Approved :: GNU General Public License (GPLv3)", + 'Programming Language :: Python :: 3', + ], +) From 3faa21d62080dddb7a40a99a5c4f9a4f1a179375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar=20Garc=C3=ADa=20Amor?= Date: Mon, 5 Jun 2017 19:05:49 +0200 Subject: [PATCH 3/6] Add more info about log to README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 452e6a9..f0c348d 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,8 @@ python setup.py install ## Run Simply run `filetea` command with file that you want to share. + +If you want to reduce verbose output, edit `fileteasend/app.py` and change +the line 12 `logging.basicConfig(level=logging.DEBUG)` to +`logging.basicConfig(level=logging.INFO)` and rerun the setup install. Set +to `logging.basicConfig(level=logging.ERROR)` for no log output. From 4c1a776fd650d3302d5ac2e2245269bb788bdf7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar=20Garc=C3=ADa=20Amor?= Date: Wed, 7 Jun 2017 16:46:05 +0200 Subject: [PATCH 4/6] Code refactoring - Implement client as class. - Use argparse for command-line options. - Now can specify the FileTea server via env var or argument. - Better detection of file transfer requests. --- README.md | 14 ++- fileteasend/app.py | 240 +++++++++++++++++++++++++++++---------------- 2 files changed, 167 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index f0c348d..e1e6391 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,13 @@ python setup.py install ## Run -Simply run `filetea` command with file that you want to share. +Simply run `filetea` command with `-h` or `--help` to read the help of +command. It's self explanatory. -If you want to reduce verbose output, edit `fileteasend/app.py` and change -the line 12 `logging.basicConfig(level=logging.DEBUG)` to -`logging.basicConfig(level=logging.INFO)` and rerun the setup install. Set -to `logging.basicConfig(level=logging.ERROR)` for no log output. +## Options and arguments + +You can specify FileTea server with `FILETEAURL` environment variable or +with `-l` or `--url` arguments. + +The `-v` argument enable the verbose mode, you can repeat it to get more +verbose output. diff --git a/fileteasend/app.py b/fileteasend/app.py index 2a00e5b..fc56e44 100644 --- a/fileteasend/app.py +++ b/fileteasend/app.py @@ -1,93 +1,169 @@ -#!/usr/bin/env python +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# vim:fenc=utf-8 +# +# Distributed under terms of the GNU GPLv3 license. + from __future__ import print_function +try: + from urllib.parse import urljoin, urlparse +except ImportError: + from urlparse import urljoin, urlparse +import argparse +import json import logging -import sys import os -import json +import signal +import sys -import requests import magic +import requests -__FILETEA_URL = 'https://filetea.me/' -logging.basicConfig(level=logging.DEBUG) - - -def get_peer_id(session_obj): - """Perform handshake with endpoint to get a usable peer-id""" - logger = logging.getLogger('get_peer_id') - handshake_text = '{"mechanisms":["websocket","long-polling"],"url":"/transport/"}' - handshake = session_obj.post('%stransport/handshake' % __FILETEA_URL, data=handshake_text, - headers={'Content-Type': 'text/plain'}) - logger.debug(handshake.headers) - logger.debug(handshake.content) - return handshake.json()['peer-id'] - - -def send_file(session_obj, file_name_to_send, token): - """Send specified file to endpoint""" - logger = logging.getLogger('send_file') - logger.debug('Sending %s to %s' % (file_name_to_send, token)) - logger.info('Sending file %s' % file_name_to_send) - with open(file_name_to_send, 'rb') as f: - resp = session_obj.put('%s%s' % (__FILETEA_URL, token), f) - logger.debug(resp) - return resp - - -def register_file(session_obj, my_uuid, file_to_send): - """Register file with endpoint, returns url to use to download the file""" - logger = logging.getLogger('register_file') - mime_type = magic.from_file(file_to_send, mime=True) - send_url = '%stransport/lp/send?%s' % (__FILETEA_URL, my_uuid) - send_data = 'X{"method":"addFileSources","params":[["%s","%s",%d]],"id":"1"}' % ( - os.path.basename(file_to_send), mime_type, os.path.getsize(file_to_send)) - logger.info('Sending %s' % send_data) - sent_response = session_obj.post(send_url, data=send_data, headers={'Content-Type': 'text/plain'}) - logger.debug(sent_response.headers) - logger.debug(sent_response.text) - send_return_data = sent_response.text - assert send_return_data.startswith('@') - send_return_data = send_return_data[1:] - send_response_json = json.loads(send_return_data) - logger.debug('Decoded JSON: %s' % send_response_json) - file_download_url = '%s%s' % (__FILETEA_URL, send_response_json['result'][0][0]) - return file_download_url - -def main(): - base_logger = logging.getLogger('filetea') - session = requests.session() +class FileTeaClient(object): + """Base client class for FileTea access""" + def __init__(self, url=None): + logger = logging.getLogger('filetea.FileTeaClient') + # Set URL to default if not URL is passed + self.url = 'https://filetea.me/' if url == None else url + logger.debug('Using FileTea server {}'.format(self.url)) + self.session = requests.session() + + def get_peer_id(self): + """ + Perform handshake with endpoint to get a usable peer-id + + :return: peer-id + :rtype: string + """ + logger = logging.getLogger('filetea.FileTeaClient.get_peer_id') + handshake_headers = {'Content-Type': 'text/plain'} + handshake_text = '{"mechanisms":["websocket","long-polling"],"url":"/transport/"}' + url = urljoin(self.url, 'transport/handshake') + handshake = self.session.post(url, data=handshake_text, headers=handshake_headers) + logger.debug(handshake.headers) + logger.debug(handshake.content) + return handshake.json()['peer-id'] + + def register_file(self, peer_id, file_to_send): + """ + Register file with endpoint and returns url to use to download the file + + :return: url + :rtype: string + """ + logger = logging.getLogger('filetea.FileTeaClient.register_file') + file_name = os.path.basename(file_to_send) + file_size = os.path.getsize(file_to_send) + mime_type = magic.from_file(file_to_send, mime=True) + send_headers = {'Content-Type': 'text/plain'} + send_url = urljoin(self.url, 'transport/lp/send') + send_data = { + "method": "addFileSources", + "params": [[ + file_name, + mime_type, + file_size + ]], + "id": "1" + } + send_data = 'X{}'.format(json.dumps(send_data)) + logger.info('Sending: {}'.format(send_data)) + send_response = self.session.post(send_url, data=send_data, params=peer_id, headers=send_headers) + logger.debug(send_response.headers) + logger.debug(send_response.text) + send_response_text = send_response.text + assert send_response_text.startswith('@') + send_response_text = send_response_text[1:] + send_response_json = json.loads(send_response_text) + logger.debug('Decoded JSON response: {}'.format(send_response_json)) + file_download_url = urljoin(self.url, send_response_json['result'][0][0]) + return file_download_url + + def send_file(self, file_to_send, token): + """ + Send specified file to endpoint + """ + logger = logging.getLogger('filetea.FileTeaClient.send_file') + logger.info('Sending file {} to {}'.format(file_to_send, token)) + send_url = urljoin(self.url, token) + with open(file_to_send, 'rb') as f: + send_response = self.session.put(send_url, f) + logger.debug(send_response) + + def receive_response(self, peer_id, file_to_send): + """ + Wait for server response to send file + """ + logger = logging.getLogger('filetea.FileTeaClient.receive_response') + receive_url = urljoin(self.url, 'transport/lp/receive') + receive_response = self.session.get(receive_url, params=peer_id) + receive_response_text = receive_response.text + logger.debug(receive_response_text) + try: + receive_response_text = receive_response_text[1:] + receive_response_json = json.loads(receive_response_text) + logger.debug('Decoded JSON response: {}'.format(receive_response_json)) + if receive_response_json['method'] == 'fileTransferNew': + token = receive_response_json['params'][-1] + logger.debug('Got a file transfer request from token {}'.format(token)) + print('File transfer request received from token {}'.format(token)) + self.send_file(file_to_send, token) + else: + logger.info('Not a file transfer request') + except Exception as e: + logger.info('Not a file transfer request: {}'.format(e)) + pass + +def exit(signal, frame): + sys.exit(0) + +def run(args): + logger = logging.getLogger('filetea') + # Set FileTea server URL + server_url = args.url if args.url != None else os.getenv('FILETEAURL', 'https://filetea.me/') + if not urlparse(server_url).netloc: + sys.exit('No schema supplied on URL.') + + file_tea_client = FileTeaClient(server_url) + # Get a peer-id registered with endpoint + peer_id = file_tea_client.get_peer_id() + # Register specified file with endpoint, which returns the URL to download the file + file_url = file_tea_client.register_file(peer_id, args.file) + logger.info('URL for file {}: {}'.format(args.file, file_url)) + print('URL: {}'.format(file_url)) + print('Press CTRL+C to stop sharing...') - if len(sys.argv) < 2: - sys.exit('Need to specify file to share') + while True: + # Main loop + logger.info('Waiting for a server response') + file_tea_client.receive_response(peer_id, args.file) - file_to_send = sys.argv[1] - if not os.path.exists(file_to_send): +def main(): + signal.signal(signal.SIGINT, exit) + parser = argparse.ArgumentParser(description='Easy file transfer using the FileTea service') + parser.add_argument('--url', '-l', help='filetea server url (also FILETEAURL)') + parser.add_argument('--verbose', '-v', action='count', default=0, help='be verbose (vv to increase verbosity)') + parser.add_argument('file', help='file to send') + parser.set_defaults(func=run) + args = parser.parse_args() + + # Set loglevel equivalents + log_levels = { + 0: logging.ERROR, + 1: logging.INFO, + 2: logging.DEBUG + } + + # Maximum loglevel is 2 if user sends more vvv we ignore it + args.verbose = 2 if args.verbose >= 2 else args.verbose + logging.basicConfig(level=log_levels[args.verbose]) + + # Check if file exists and can be read + if not os.path.exists(args.file): sys.exit('File does not exist.') - if not os.path.isfile(file_to_send): + if not os.path.isfile(args.file): sys.exit('Path is not a file.') + if not os.access(args.file, os.R_OK): + sys.exit('File can not be read.') - # Get a peer-id registered with endpoint - my_uuid = get_peer_id(session) - # Register specified file with endpoint, which returns the URL to download the file - url = register_file(session, my_uuid, file_to_send) - base_logger.info('URL for file %s: %s' % (file_to_send, url)) - print('URL: %s' % url) - - while True: - # Try recv, might 404 - base_logger.info('Waiting to send recv') - recv_url = '%stransport/lp/receive?%s' % (__FILETEA_URL, my_uuid) - recv_response = session.get(recv_url) - base_logger.debug(recv_response.content) - - recv_return_data = recv_response.content - if recv_return_data.startswith(b'y'): - base_logger.debug('Got file transfer request') - recv_return_data = recv_return_data[1:] - - r = json.loads(recv_return_data) - - if r['method'] == "fileTransferNew": - send_file(session, file_to_send, r['params'][-1]) - else: - base_logger.debug('Not a file transfer') + args.func(args) From 58f88c157af347033c4cd5658071c7d1f2f653d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar=20Garc=C3=ADa=20Amor?= Date: Wed, 7 Jun 2017 17:33:48 +0200 Subject: [PATCH 5/6] Added CONTRIBUTORS and style fixed --- CONTRIBUTORS.md | 5 +++++ fileteasend/app.py | 9 +++------ setup.py | 2 -- 3 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 CONTRIBUTORS.md diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..25df619 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,5 @@ +# CONTRIBUTORS + +Alphabetically-ordered list of the people who contributed to FileTeaSend: + +- Óscar García Amor (@ogarcia) diff --git a/fileteasend/app.py b/fileteasend/app.py index fc56e44..5e8405e 100644 --- a/fileteasend/app.py +++ b/fileteasend/app.py @@ -1,4 +1,3 @@ -#! /usr/bin/env python # -*- coding: utf-8 -*- # vim:fenc=utf-8 # @@ -24,7 +23,7 @@ class FileTeaClient(object): def __init__(self, url=None): logger = logging.getLogger('filetea.FileTeaClient') # Set URL to default if not URL is passed - self.url = 'https://filetea.me/' if url == None else url + self.url = 'https://filetea.me/' if url is None else url logger.debug('Using FileTea server {}'.format(self.url)) self.session = requests.session() @@ -64,8 +63,7 @@ def register_file(self, peer_id, file_to_send): mime_type, file_size ]], - "id": "1" - } + "id": "1" } send_data = 'X{}'.format(json.dumps(send_data)) logger.info('Sending: {}'.format(send_data)) send_response = self.session.post(send_url, data=send_data, params=peer_id, headers=send_headers) @@ -151,8 +149,7 @@ def main(): log_levels = { 0: logging.ERROR, 1: logging.INFO, - 2: logging.DEBUG - } + 2: logging.DEBUG } # Maximum loglevel is 2 if user sends more vvv we ignore it args.verbose = 2 if args.verbose >= 2 else args.verbose diff --git a/setup.py b/setup.py index a1843c8..f88e1e6 100644 --- a/setup.py +++ b/setup.py @@ -2,8 +2,6 @@ # -*- coding: utf-8 -*- # vim:fenc=utf-8 # -# Copyright © 2016 Óscar García Amor -# # Distributed under terms of the GNU GPLv3 license. import os From 6c431ffc32d34bbee0b93649e79820efeba64e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar=20Garc=C3=ADa=20Amor?= Date: Wed, 7 Jun 2017 18:52:59 +0200 Subject: [PATCH 6/6] Fixed to use any version of magic --- fileteasend/app.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fileteasend/app.py b/fileteasend/app.py index 5e8405e..4d81aa1 100644 --- a/fileteasend/app.py +++ b/fileteasend/app.py @@ -53,7 +53,13 @@ def register_file(self, peer_id, file_to_send): logger = logging.getLogger('filetea.FileTeaClient.register_file') file_name = os.path.basename(file_to_send) file_size = os.path.getsize(file_to_send) - mime_type = magic.from_file(file_to_send, mime=True) + if hasattr(magic, 'open'): # use magic-file-extensions + ms = magic.open(magic.MIME_TYPE) + ms.load() + mime_type = ms.file(file_to_send) + ms.close() + else: # use python-magic + mime_type = magic.from_file(file_to_send, mime=True) send_headers = {'Content-Type': 'text/plain'} send_url = urljoin(self.url, 'transport/lp/send') send_data = {