def get_download_url(conf, fileid): """the api to download just redirects to the real url to download :conf: configuration object :fileid: fileid to download :returns: the download url """ LOG.debug( 'trying to dereference download url for file id: %s', str(fileid)) try: conn = httplib.HTTPSConnection('api.put.io') url = API_URL + \ '/files/%s/download?oauth_token=' + conf.get('oauthtoken') url = url % fileid conn.request("GET", url, None, {'User-Agent': USER_AGENT}) response = conn.getresponse() if response.status == 302: return response.getheader('Location') else: LOG.error( 'putio api returned status %d for download: %s', response.status, url) return None except (httplib.HTTPException, IOError, OSError): LOG.error( 'error dereferencing download url for file id: %s', str(fileid), exc_info=True) return None
def make_api_request(conf, resource, params, compress=True): """makes an http call to put.io api :conf: configuration object :resource: the REST resource in the api :params: a dictionary of url parameters key-val :returns: raw response from http response or None if failed """ params['oauth_token'] = conf.get('oauthtoken') url = API_URL + resource + '?' + urlencode(params) LOG.debug('making http request: %s', url) req = urllib2.Request(url) req.add_header('User-Agent', USER_AGENT) req.add_header('Accept', 'application/json') if compress: req.add_header('Accept-Encoding', 'gzip;q=1.0,deflate;q=0.5,*;q=0') try: response = urllib2.urlopen(req) if response.getcode() == 200: content = response.read() try: if compress: try: inflated = gzip.GzipFile( fileobj=StringIO.StringIO(content)) return json.loads(inflated.read()) except IOError: LOG.error( 'request failed due to IO error, content', exc_info=True) return json.loads(content) else: return json.loads(content) except ValueError: LOG.error( 'cant parse api response: %s', content, exc_info=True) return None elif response.getcode() == 302: LOG.debug('got redirect: %s', str(response.info())) return response.info() else: LOG.error('request failed %s status: %s', url, response.getcode()) except urllib2.HTTPError as exc: LOG.error( 'request failed %s status: %s message: %s', url, exc.code, exc.reason) return None
def start_download(filesize, download_url, targetfile, conf): """downloads the file""" suspend_until_can_store_file(filesize, targetfile) bps = conf.get('bytes_per_second') connections = conf.get('conn_per_downloads') print '\nStarting download :%s' % download_url LOG.info('starting download :%s into %s', download_url, targetfile) cmd = 'axel -o %s -n %d -a -s %d %s' % (targetfile, connections, bps, download_url) LOG.debug('running axel: %s', cmd) axel = subprocess.Popen([ 'axel', '-o', targetfile, '-a', '-s', str(bps), '-n', str(connections), download_url ]) currsize = os.path.getsize(targetfile) if os.path.exists(targetfile) else 0 pollinterval = 5 time.sleep(pollinterval) remaining_attempts = 3 while axel.poll() is None: time.sleep(pollinterval) progress = os.path.getsize(targetfile) - currsize currsize = currsize + progress if progress == 0: LOG.warn('seems like axel isnt effective in the last %d seconds', pollinterval) if remaining_attempts == 0: LOG.error('axel seems totally stuck, aborting') axel.kill() return remaining_attempts = remaining_attempts - 1 pollinterval = pollinterval * 2 returncode = axel.poll() if returncode != 0: print '\n[E] Download %s failed!' % download_url LOG.error('download %s failed with code: %d', download_url, returncode) return if os.path.exists(targetfile): if os.path.getsize(targetfile) != filesize: LOG.info('detected partial download %s due to file size', targetfile) try: os.remove(targetfile) except (OSError, IOError): print '\n[E] Cant remove bad download %s' % targetfile LOG.error('cant remove bad download %s', targetfile, exc_info=True)
def __create_local_dirs(root, dirtree, files_to_dirs): """creates the local dir tree :conf: configuration object :dirtree: the tree fetched from the putio account :files_to_dirs: a mapping of file data to the dir the file should be downloaded to :returns: None """ todelete = os.listdir(root) for name, remoteitem in dirtree.iteritems(): if name in todelete: todelete.remove(name) if remoteitem is None: print ('Skipping dir %s because no data for it', name) LOG.error('skipping dir %s because no data for it', name) continue target = os.path.join(root, name) if remoteitem.isdir(): LOG.debug('inspecting dir: %s', name) # this is a directory if os.path.exists(target) and not os.path.isdir(target): LOG.warn( "remote dir and local file conflict" + "removing local file: %s", target) os.remove(target) if not os.path.exists(target): LOG.debug('creating dir: %s', target) os.makedirs(target) if remoteitem.dirtree: __create_local_dirs(target, remoteitem.dirtree, files_to_dirs) elif os.path.exists(target): todelete.append(name) else: LOG.debug('inspecting file: %s', name) # this is a normal file exists = os.path.exists(target) if exists and os.path.getsize(target) != remoteitem.size: LOG.warn('file size != from whats on putio: %s', target) todelete.append(name) files_to_dirs[remoteitem] = target elif not exists: LOG.debug( 'file will be downloaded: %s -> %s', remoteitem, target) files_to_dirs[remoteitem] = target sync_utils.delete_files(root, todelete)
def __create_local_dirs(root, dirtree, files_to_dirs): """creates the local dir tree :conf: configuration object :dirtree: the tree fetched from the putio account :files_to_dirs: a mapping of file data to the dir the file should be downloaded to :returns: None """ todelete = os.listdir(root) for name, remoteitem in dirtree.iteritems(): if name in todelete: todelete.remove(name) if remoteitem is None: print('Skipping dir %s because no data for it', name) LOG.error('skipping dir %s because no data for it', name) continue target = os.path.join(root, name) if remoteitem.isdir(): LOG.debug('inspecting dir: %s', name) # this is a directory if os.path.exists(target) and not os.path.isdir(target): LOG.warn( "remote dir and local file conflict" + "removing local file: %s", target) os.remove(target) if not os.path.exists(target): LOG.debug('creating dir: %s', target) os.makedirs(target) if remoteitem.dirtree: __create_local_dirs(target, remoteitem.dirtree, files_to_dirs) elif os.path.exists(target): todelete.append(name) else: LOG.debug('inspecting file: %s', name) # this is a normal file exists = os.path.exists(target) if exists and os.path.getsize(target) != remoteitem.size: LOG.warn('file size != from whats on putio: %s', target) todelete.append(name) files_to_dirs[remoteitem] = target elif not exists: LOG.debug('file will be downloaded: %s -> %s', remoteitem, target) files_to_dirs[remoteitem] = target sync_utils.delete_files(root, todelete)
def __get_putio_files(conf, parent_id=0, tree=None, root=None): """fetches the file list from put.io account :conf: configuration object :parent_id: the from which to start on the account :returns: a dict of dicts representing the account directory tree """ if not tree: tree = {} if not root: root = '/' data = putio_api.getfiles(conf, parent_id) putio_api.ensure_valid_oauth_token(data) freshtree = {} if data: LOG.debug('got data for file id: %d', parent_id) for remotefile in data.get('files'): filename = remotefile.get('name') filetype = remotefile.get('file_type') fileid = remotefile.get('id') filesize = remotefile.get('size') abspath = root + '/' + filename skip = False for exclude in EXCLUDE_LIST: skip = abspath == exclude or re.search(exclude, abspath) if skip: LOG.info('skipping because exclude rule match (%s ~ %s)', exclude, abspath) break if skip: continue if filetype == PUTIO_DIR_FTP: cached = tree.get(filename, None) cached_filesize = cached.size if cached else 0 if cached: freshtree[filename] = cached if filesize != cached_filesize: subtree = cached.dirtree if cached else {} subtree = __get_putio_files(conf, fileid, subtree, abspath) freshtree[filename] = RemoteItem(filename, filesize, fileid, subtree) LOG.debug('mapped directory: %s', freshtree[filename]) else: filedata = RemoteItem(filename, filesize, fileid, None) LOG.debug('mapped file: %s', filedata) freshtree[filename] = filedata tree = freshtree return tree
def __get_putio_files(conf, parent_id=0, tree=None, root=None): """fetches the file list from put.io account :conf: configuration object :parent_id: the from which to start on the account :returns: a dict of dicts representing the account directory tree """ if not tree: tree = {} if not root: root = '/' data = putio_api.getfiles(conf, parent_id) putio_api.ensure_valid_oauth_token(data) freshtree = {} if data: LOG.debug('got data for file id: %d', parent_id) for remotefile in data.get('files'): filename = remotefile.get('name') filetype = remotefile.get('file_type') fileid = remotefile.get('id') filesize = remotefile.get('size') abspath = root + '/' + filename skip = False for exclude in EXCLUDE_LIST: skip = abspath == exclude or re.search(exclude, abspath) if skip: LOG.info('skipping because exclude rule match (%s ~ %s)', exclude, abspath) break if skip: continue if filetype == PUTIO_DIR_FTP: cached = tree.get(filename, None) cached_filesize = cached.size if cached else 0 if cached: freshtree[filename] = cached if filesize != cached_filesize: subtree = cached.dirtree if cached else {} subtree = __get_putio_files(conf, fileid, subtree, abspath) freshtree[filename] = RemoteItem( filename, filesize, fileid, subtree) LOG.debug('mapped directory: %s', freshtree[filename]) else: filedata = RemoteItem(filename, filesize, fileid, None) LOG.debug('mapped file: %s', filedata) freshtree[filename] = filedata tree = freshtree return tree
def suspend_sync(): """simple sleep helper""" sleep_seconds = 60 * 1 LOG.debug('sleeping for %d seconds', sleep_seconds) time.sleep(sleep_seconds)
def start_download(filesize, download_url, targetfile, conf): """downloads the file""" suspend_until_can_store_file(filesize, targetfile) bps = conf.get('bytes_per_second') connections = conf.get('conn_per_downloads') print '\nStarting download :%s' % download_url LOG.info('starting download :%s into %s', download_url, targetfile) cmd = 'axel -o %s -n %d -a -s %d %s' % (targetfile, connections, bps, download_url) LOG.debug('running axel: %s', cmd) axel = subprocess.Popen( ['axel', '-o', targetfile, '-a', '-s', str(bps), '-n', str(connections), download_url]) currsize = os.path.getsize(targetfile) if os.path.exists(targetfile) else 0 pollinterval = 5 time.sleep(pollinterval) remaining_attempts = 3 while axel.poll() is None: time.sleep(pollinterval) progress = os.path.getsize(targetfile) - currsize currsize = currsize + progress if progress == 0: LOG.warn('seems like axel isnt effective in the last %d seconds', pollinterval) if remaining_attempts == 0: LOG.error('axel seems totally stuck, aborting') axel.kill() return remaining_attempts = remaining_attempts - 1 pollinterval = pollinterval * 2 returncode = axel.poll() if returncode != 0: print '\n[E] Download %s failed!' % download_url LOG.error( 'download %s failed with code: %d', download_url, returncode) return if os.path.exists(targetfile): if os.path.getsize(targetfile) != filesize: LOG.info( 'detected partial download %s due to file size', targetfile) try: os.remove(targetfile) except (OSError, IOError): print '\n[E] Cant remove bad download %s' % targetfile LOG.error( 'cant remove bad download %s', targetfile, exc_info=True)