Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 24 additions & 26 deletions client/bush/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,21 @@

class BushFile():

def __init__(self, tag, name, date=0, compressed=None, url=None, **kwargs):
def __init__(self, tag, name, date=0, url=None, base=None, **kwargs):
if name.endswith('.tar.gz'):
name = name[:-7]

if compressed is None:
compressed = name.endswith('.tar.gz')
if base is not None:
url = urllib.parse.urljoin(base, url)

self.tag = tag
self.compressed = compressed
self.name = name[:-7] if self.compressed else name
self.name = name
self.date = arrow.get(date)
self.url = url

def __repr__(self):
return "BushFile(tag=%s, name=%s, date=%s, compressed=%s)" % (
self.tag, self.name, self.data, self.compressed)
return "BushFile(tag=%s, name=%s, date=%s, url=%s)" % (
self.tag, self.name, self.data, self.url)

def output(self, file=sys.stdout, align=0, extended=False):
if not extended:
Expand All @@ -54,28 +55,30 @@ def output(self, file=sys.stdout, align=0, extended=False):
class BushAPI():

def __init__(self, base, username=None, password=None):
self.base = base
self.base_url = base
self.base = urllib.parse.urlparse(self.base_url)

self.requests = requests.session()

self.requests.headers.update({
'User-Agent': 'bush.py.%s' % bush.meta.__version__,
})

scheme = urllib.parse.urlparse(self.base)[0]
if (username or password) and scheme != 'https':
if (username or password) and self.base.scheme != 'https':
if not self.confirmation("Sending credentials over %r is insecure."
% scheme, level=EXTREME):
% self.base.scheme, level=EXTREME):
raise KeyboardInterrupt()
self.requests.auth = (username, password)


def confirmation(self, msg, level):
# Don't confirm anything by default!
if level > INFO:
raise RuntimeError(msg)

def url(self, url):
return urllib.parse.urljoin(self.base, url)
def url(self, path, *args):
args = (urllib.parse.quote(arg, safe='') for arg in args)
return urllib.parse.urljoin(self.base_url, path.format(*args))

def tag_for_path(self, filepath):
basename = os.path.basename(filepath)
Expand Down Expand Up @@ -124,10 +127,10 @@ def check_target(self, dest, fdest, isdir=False, placeholder=True):
return True

def list(self):
r = self.requests.get(self.url("index.php?request=list"))
r = self.requests.get(self.url("/files/"))
self.assert_response(r)
return [BushFile(url=self.getddl(f['tag']), **f)
for f in json.loads(r.text)]
return [BushFile(k, base=self.base_url, **v)
for k, v in r.json().items()]

def upload(self, filepath, tag=None, callback=None):

Expand All @@ -142,7 +145,6 @@ def upload(self, filepath, tag=None, callback=None):
raise ValueError("Must specify tag for multifile.")

tag = tag or self.tag_for_path(filepaths[0])

tmp = tempfile.TemporaryFile()
tar = tarfile.open("bush_upload.tar.gz", "w:gz", fileobj=tmp)

Expand All @@ -158,7 +160,6 @@ def upload(self, filepath, tag=None, callback=None):

filename = "%s.tar.gz" % ", ".join(basenames)
encoder = MultipartEncoder(fields={
'tag': tag,
'file': (filename, tmp, 'application/octet-stream')
})

Expand All @@ -172,8 +173,7 @@ def _callback(monitor):
_callback = None

monitor = MultipartEncoderMonitor(encoder, _callback)

r = self.requests.post(self.url('index.php?request=upload'), data=monitor,
r = self.requests.put(self.url('/files/{}', tag), data=monitor,
headers={'Content-Type': monitor.content_type})

if _callback:
Expand All @@ -187,12 +187,11 @@ def _callback(monitor):

def getddl(self, tag):
tag = urllib.parse.quote(tag)
return self.url("index.php?request=get&tag=%s" % tag)
return self.url("/files/{}", tag)

def download(self, tag, dest, callback=None, chunksz=8192):

r = self.requests.get(self.url("index.php?request=get"),
params={"tag": tag}, stream=True)
r = self.requests.get(self.url("/files/{}", tag), stream=True)

self.assert_response(r)

Expand Down Expand Up @@ -301,16 +300,15 @@ def check_member(member):

def delete(self, tag):

r = self.requests.get(self.url("index.php?request=delete"),
params={"tag": tag})
r = self.requests.delete(self.url("/files/{}", tag))

self.assert_response(r)
data = r.json()
self.assert_status(data)

def reset(self):

r = self.requests.get(self.url("index.php?request=reset"))
r = self.requests.delete(self.url("/files/"))

self.assert_response(r)

Expand Down
52 changes: 34 additions & 18 deletions client/bush/cli.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import os
import sys
import time
import pprint
import argparse
import progressbar
import datetime
import operator
import contextlib

from distutils import util

import arrow
import progressbar

import bush.api
import bush.config


class ShowProgress():

def __init__(self, total):
Expand Down Expand Up @@ -49,15 +46,24 @@ def confirmation(self, msg, level):

return status

@contextlib.contextmanager
def user_friendly_errors(exceptions=Exception, debug=False):
try:
yield
except KeyboardInterrupt:
print('interrupted', file=sys.stderr)
except () if debug else exceptions as e:
exit(e)


def do_list(api, args):
def do_list(api, args, config):
files = api.list()
maxlen = max(len(f.tag) for f in files) if files else 0
for f in files:
f.output(align=maxlen, extended=args.exact)


def do_wait(api, args):
def do_wait(api, args, config):

latest = None
update = arrow.now() - datetime.timedelta(seconds=args.age)
Expand All @@ -78,7 +84,7 @@ def do_wait(api, args):
api.download(latest.tag, args.dest, callback=ShowProgress)


def do_upload(api, args):
def do_upload(api, args, config):

if args.tag is not None:
tag = args.tag
Expand All @@ -94,18 +100,24 @@ def do_upload(api, args):
api.upload(args.file, tag=tag, callback=ShowProgress)


def do_download(api, args):
def do_download(api, args, config):
api.download(args.tag, args.dest, callback=ShowProgress)


def do_delete(api, args):
def do_delete(api, args, config):
api.delete(args.tag)


def do_reset(api, args):
def do_reset(api, args, config):
api.reset()


def do_serve(api, args, config):
from bush import server
server.config_datadir(args.datadir or config.get('datadir', './data/'))
server.app.run(host=api.base.hostname, port=api.base.port, debug=args.debug)


def main():

parser = argparse.ArgumentParser(description="Simplistic file sharing. ",
Expand Down Expand Up @@ -149,6 +161,11 @@ def main():
sub = subs.add_parser('reset', help="delete all files")
sub.set_defaults(callback=do_reset)

sub = subs.add_parser('serve', help="act as a bush server")
sub.set_defaults(callback=do_serve)
sub.add_argument('-d', '--datadir',
help="path do the directory used for storing files")

parser.add_argument('-u', '--url', help="API endpoint")
parser.add_argument('-U', '--username', help="API username")
parser.add_argument('-P', '--password', help="API password")
Expand All @@ -161,6 +178,9 @@ def main():

config = bush.config.load_config(args.config)

if args.callback is do_serve and args.url is None:
args.url = 'local'

servers = config.get('servers') or [{}]

for server in servers:
Expand All @@ -182,11 +202,7 @@ def main():

username = args.username or server.get('username')
password = args.password or server.get('password')
api = UIAPI(url, username=username, password=password)

try:
api = UIAPI(url, username=username, password=password)
args.callback(api, args)
except KeyboardInterrupt:
print('interrupted') # Canceled by user :(
except Exception if not args.debug else () as e:
exit(e)
with user_friendly_errors(debug=args.debug):
args.callback(api, args, server)
88 changes: 88 additions & 0 deletions client/bush/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import os
import uuid
import os.path

import arrow

from flask import Flask, request, redirect, send_file
from flask_restful import Resource, Api, abort
from flask.ext import shelve

app = Flask(__name__)

def config_datadir(datadir):
app.config.update(
UPLOAD_FOLDER=os.path.realpath(datadir),
SHELVE_FILENAME=os.path.realpath(os.path.join(datadir, 'files.db'))
)

config_datadir('./data/')

api = Api(app)
shelve.init_app(app)

class FileList(Resource):

def get(self):
db = shelve.get_shelve('c')
return {k: {'name': v['name'],
'date': v['date'].isoformat(),
'url': api.url_for(File, tag=k)}
for k, v in db.items()}

def delete(self):
db = shelve.get_shelve('c')
for f in db.values():
try:
os.remove(f['path'])
except FileNotFoundError:
pass
db.clear()
return {}


class File(Resource):

def get(self, tag):
db = shelve.get_shelve('c')

try:
f = db[tag]
except KeyError:
abort(404)

return send_file(f['path'], as_attachment=True, attachment_filename=f['name'])

def put(self, tag):
path = "%s/%s" % (app.config['UPLOAD_FOLDER'], uuid.uuid4())

f = request.files['file']
f.save(path)

db = shelve.get_shelve('c')
db[tag] = {
'name': f.filename,
'path': path,
'date': arrow.now()
}

return {}, 201

def delete(self, tag):
db = shelve.get_shelve('c')

try:
f = db.pop(tag)
except KeyError:
abort(404)

try:
os.remove(f['path'])
except FileNotFoundError:
pass

return {}, 200


api.add_resource(FileList, '/files/')
api.add_resource(File, '/files/<string:tag>')
3 changes: 3 additions & 0 deletions client/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ pyyaml
appdirs
progressbar2
requests-toolbelt
flask
flask-restful
flask-shelve
2 changes: 1 addition & 1 deletion client/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

if sys.version_info < (3, 3, 0):
from datetime import datetime
sys.stdout.write("It's %d. This requires Python > 3.3.\n"
sys.stdout.write("It's %d. This requires Python >= 3.3.\n"
% datetime.now().year)
sys.exit(1)

Expand Down
23 changes: 0 additions & 23 deletions server/README.md

This file was deleted.

Empty file removed server/data/files.sqlite
Empty file.
Loading