#!/usr/bin/env python from requests_toolbelt import ( MultipartEncoder, MultipartEncoderMonitor ) from progressbar import ( Bar, ETA, FileTransferSpeed, Percentage, ProgressBar, Widget ) import os, sys, time, math, json, mimetypes, requests # a copy of https://github.com/WoLpH/python-progressbar/blob/master/progressbar/widgets.py#L123 # remove this once the widgets.py bugs are fixed class AdaptiveETA(ETA): TIME_SENSITIVE = True def __init__(self, num_samples=10, **kwargs): ETA.__init__(self, **kwargs) self.num_samples = num_samples self.samples = [] self.sample_vals = [] self.last_sample_val = None def _eta(self, pbar): samples = self.samples sample_vals = self.sample_vals if pbar.currval != self.last_sample_val: self.last_sample_val = pbar.currval samples.append(pbar.seconds_elapsed) sample_vals.append(pbar.currval) if len(samples) > self.num_samples: samples.pop(0) sample_vals.pop(0) if len(samples) <= 1: return ETA._eta(self, pbar) todo = pbar.maxval - pbar.currval items = sample_vals[-1] - sample_vals[0] duration = float(samples[-1] - samples[0]) if (items == 0): return ETA._eta(self, pbar) per_item = duration / items return todo * per_item # a copy of https://github.com/WoLpH/python-progressbar/blob/master/progressbar/widgets.py#L192 # remove this once the widgets.py bugs are fixed class AdaptiveTransferSpeed(FileTransferSpeed): def __init__(self, num_samples=10): FileTransferSpeed.__init__(self) self.num_samples = num_samples self.samples = [] self.sample_vals = [] self.last_sample_val = None def _speed(self, pbar): samples = self.samples sample_vals = self.sample_vals if pbar.currval != self.last_sample_val: self.last_sample_val = pbar.currval samples.append(pbar.seconds_elapsed) sample_vals.append(pbar.currval) if len(samples) > self.num_samples: samples.pop(0) sample_vals.pop(0) if len(samples) <= 1: return FileTransferSpeed._speed(self, pbar) items = sample_vals[-1] - sample_vals[0] duration = float(samples[-1] - samples[0]) speed = items / duration if (speed <= 0): return FileTransferSpeed._speed(self, pbar) power = int(math.log(speed, 1000)) scaled = speed / 1000. ** power return scaled, power class FilesCounter(Widget): def __init__(self, sep=' of '): self.sep = sep def update(self, progressBar): return '(%d%s%d)' % (progressBar.fileNumber, self.sep, progressBar.totalNumberOfFiles) JBI_URL = "https://www.justbeamit.com" PING_URL = "http://www.justbeamit.com/ping?server_root=1" ACTIVE_BACKEND = None WIDGETS = [ ' ', Percentage(), ' ', FilesCounter(), ' ', Bar(marker='=', left='[', right=']'), ' ', AdaptiveETA(), ' ', AdaptiveTransferSpeed() ] def main(): if len(sys.argv) == 0 or sys.argv[1] == "-h" or sys.argv[1] == "--help": exit("usage: beam [path-to-file(s) ...]") filePaths = getFilePaths() # call /ping pingForActiveBackend() # call /token token = getToken(filePaths) print("please provide this link to the recipient:\n") print(" " + JBI_URL + "/" + token + "\n") # call /wait wait(token) # call /upload transfer(token, filePaths) def getFilePaths(): filePaths = [] for index in range(1, len(sys.argv)): filePath = sys.argv[index] if (os.path.exists(filePath) and os.path.isfile(filePath)): filePaths.append(filePath) else: print("please specify a valid file (and not a directory):") print(filePath) exit() return filePaths def pingForActiveBackend(): try: # send GET request response = requests.get(PING_URL) # make sure the request is ok if response.status_code == requests.codes.ok: responseJson = response.json() global ACTIVE_BACKEND ACTIVE_BACKEND = responseJson["serverRoot"] else: response.raise_for_status() except Exception as e: print(e.__class__.__name__) print(e) exit() def getToken(filePaths): tokenUrl = ACTIVE_BACKEND + "/token" # gather JSON data for request files = [] for filePath in filePaths: fileName = os.path.basename(filePath) fileSize = os.path.getsize(filePath) fileExtensionType = os.path.splitext(filePath)[1][1:] # remove the '.' from the extension fileData = { "fileName": fileName, "fileSize": fileSize, "fileExtensionType": fileExtensionType } files.append(fileData) # end for loop requestData = { "type": "CLI", "files": json.dumps(files) } try: # send POST request response = requests.post(tokenUrl, data=requestData) # make sure the request is ok if response.status_code == requests.codes.ok: responseJson = response.json() return responseJson["token"] else: response.raise_for_status() except Exception as e: print(e.__class__.__name__) print(e) exit() def wait(token): waitUrl = ACTIVE_BACKEND + "/wait" urlParams = { "type": "CLI", "token": token } try: # send GET request response = requests.get(waitUrl, params=urlParams) # make sure the request is ok if response.status_code == requests.codes.ok: responseJson = response.json() if responseJson.has_key("validToken") and responseJson["validToken"] != True: exit("something went wrong. please try again.") else: response.raise_for_status() except Exception as e: print(e.__class__.__name__) print(e) exit() def transfer(token, filePaths): print("recipient has connected! starting transfer...") uploadUrl = ACTIVE_BACKEND + "/upload" try: index = 0 ProgressBar.totalNumberOfFiles = len(filePaths) for filePath in filePaths: # make the console look pretty sys.stdout.write("\n") print(" " + filePath) # the callback function invoked by the monitor def updateProgress(monitor): theProgressBar.update(monitor.bytes_read) # setup the multi-part encoder & its monitor fileMPE = getMultipartEncoder(filePath) monitor = MultipartEncoderMonitor(fileMPE, updateProgress) # setup the progress bar ProgressBar.fileNumber = index + 1 # to avoid showing (0 of 3) # since the progress bar will be updated by the multi-part encoder, we can't set 'maxval' # to be the file's size since the encoder adds extra bytes to account for the header theProgressBar = ProgressBar( maxval = len(fileMPE), widgets = WIDGETS, ) theProgressBar.start() urlParams = { "type": "CLI", "token": token, "index": index } requests.post( uploadUrl, data=monitor, params=urlParams, headers={'Content-Type': monitor.content_type} ) theProgressBar.finish() index += 1 # end for loop except Exception as e: print(e.__class__.__name__) print(e) exit() finally: sys.stdout.write("\n") def getMultipartEncoder(filePath): return MultipartEncoder({ "file": ( os.path.basename(filePath), open(filePath, "r"), getMimeTypeForFile(filePath) ) }) def getMimeTypeForFile(file): return mimetypes.guess_type(file)[0] or 'application/octet-stream' if __name__ == "__main__": main()