Source code for benten_client.rest_download

# coding:utf-8
"""
   REST API for download

   Copyright (C) 2020 JASRI All Rights Reserved.
"""

import codecs

import os
import sys
import json
import requests
import zipfile

if sys.version_info.major <=2:
    import urlparse      # python2
else:
    import urllib.parse as urlparse  # python3
    

from . import config
from . import util

[docs]class Main(): def __init__(self, parent): self.__parent = parent self.__endpoint = parent.endpoint(config.download_path) self.queue = Queue(self) self.file = File(self) self.metalink = Metalink(self)
[docs] def endpoint(self, path): return self.__parent.endpoint(path)
[docs] def access_token(self): return self.__parent.access_token()
[docs] def post(self, **v): """ REST API: ZIPダウンロード命令実行 (非同期処理) * Endpoint: [agent-url]/benten/v1/download * Method: POST * Response: JSON * Authorization: 要 :param \**v: See below. :Keyword Arguments: * *register_name_list* (``list(str)`` [Option]) : 登録名リスト * *file_list* (``list(file)`` [Option]) : ディレクトリ及びファイルリスト * ディレクトリとファイルの区別なし * *flag_recursive* (``int`` [Option]) : ディレクトリ指定時に下位ディレクトリを含めてダウンロードするかどうかを示すフラグ * デフォルト値は0, 1の時に有効になる * *flag_own* (``int`` [Option]) : 自身の所属のデータのみダウンロードするかどうかのフラグ * 0 (default, disable) or 1 (enable) * *mode* (``str`` [Option]) : ZIP圧縮のモード (normal, stored, deflated) * normal(default)の場合 file capttionが binaryを除いてZIP圧縮を行う。 * stored の場合、 ZIP無圧縮 * deflatedの場合、 ZIP圧縮 * *timeout* (``int``) : タイムアウト値 (秒単位) * 最大値は 30 * 負の値の際は30がセットさあれる (注) register_name_list または file_list のいずれかの入力要 :return: A dict of mapping keys. :Keyword: * *status* (``str``) : ダウンロード実行状況 * 未完の場合は PENDING 、処理が開始した場合は STARTED、処理が失敗した場合は FAILURE、処理が終了し成功した場合は SUCCESS * *uuid* (``str``) : ダウンロードを行うまたは状況確認の際に必要となるid値 * *error* (``dict``) : エラー情報 (エラー発生時のみ付加) """ vdata = {} for key in ["register_name_list", "file_list", "flag_recursive", "flag_own", "mode", "timeout"]: if key in v: vdata[key] = v[key] ret = requests.post(self.__endpoint, vdata, verify=False, headers=util.headers_authorization(self.__parent.access_token())) return util.json_response(ret)
[docs]class Queue(): def __init__(self, parent): self.__parent = parent self.__endpoint = parent.endpoint(config.download_queue_path)
[docs] def get(self, v): """ REST API: ZIPダウンロード命令のqueue確認 * Endpoint: [agent-url]/benten/v1/download/queue/<uuid> * Method: GET * Response: JSON * Authorization: 不要 :param \**v: 指定なし :return: A dict of mapping keys. :Keyword: * *status* (``str``) : ダウンロード実行状況 * 未完の場合は PENDING 、処理が開始した場合は STARTED、処理が失敗した場合は FAILURE、処理が終了し成功した場合は SUCCESS * *uuid* (``str``) : ダウンロードを行うまたは状況確認の際に必要となるid値 * *error* (``dict``) : エラー情報 (エラー発生時のみ付加) """ endpoint = "%s/%s" % (self.__endpoint, v) ret = requests.get(endpoint, verify=False) return util.json_response(ret)
[docs]class File(): def __init__(self, parent): self.__parent = parent self.__endpoint = parent.endpoint(config.download_file_path)
[docs] def get(self, v, out_directory=None, debug=True, unzip=False,): """ REST API: ZIPファイルダウンロード実行 * Endpoint: [agent-url]/benten/v1/download/file/<uuid> * Method: GET * Response: HTTP (JSON if error occured) * Authorization: 必要 :param \**v: 指定なし :param debug: True の時に debug print される (Default値はTrue) :param unzip: Trueの際は、ダウンロード後にzipファイルを解凍する :return: なし (注) Rest APIの返答においてContent-Disposition の header に記載されたzipファイル名で ファイルコンテンツのダウンロードを行う。 """ endpoint = "%s/%s" % (self.__endpoint, v) ret = requests.get(endpoint, verify=False, headers=util.headers_authorization(self.__parent.access_token()),stream=True) if ret.status_code != 200: message = "HTTP status: " + str(ret.status_code) raise util.Error(message, domain=util.error_domain( __file__, sys._getframe())) content_type = ret.headers["content-type"] try: content_length = int(ret.headers.get("Content-Length")) except: content_length = None if "json" in content_type: return util.json_response(ret) if "zip" not in content_type: message = "Content-Type: " + content_type raise util.Error(message, domain=util.error_domain( __file__, sys._getframe())) try: content_disposition = ret.headers["Content-Disposition"] filename = content_disposition.split("filename=")[1].strip() if out_directory is not None: filename = "{}/{}".format(out_directory, filename) util.makedirs(out_directory) except: raise util.Error("File cannot be opened") stored_bytes_written = 0 threshold_size = 1048576 * 100 # 100 Mbyte with open(filename, "wb") as file: bytes_written = 0 flag_monitor = False if debug and content_length is not None and content_length > threshold_size: flag_monitor = True for chunk in ret.iter_content(chunk_size=1024): file.write(chunk) if flag_monitor: bytes_written += 1024 diff = bytes_written - stored_bytes_written if diff > threshold_size: stored_bytes_written = bytes_written total = int(content_length/1048576) rate = int(100.*float(bytes_written)/float(content_length)) util.log("[download_monitor] total={}MB, {}% downloaded".format( total, rate), flush=True) util.log("==> downloaded with file = {}".format(filename), flush=True) if unzip: if out_directory is None: out_directory = "." util.log("==> unzip file under {}".format(out_directory)) zip_file = zipfile.ZipFile(filename) zip_file.extractall(out_directory) zip_file.close() if os.path.exists(filename): util.log("==> remove file = {}".format(filename)) os.remove(filename)
[docs] def post(self, v, out_directory=None, debug=True, flag_path=False, flag_subdirectory=False, disable_hash=False): """ REST API: 単一ファイルダウンロード実行 * Endpoint: [agent-url]/benten/v1/download/file * Method: POST * Response: HTTP (JSON when error occured) * Authorization: 要 :param \**v: See below. :Keyword Arguments: * *file* (``str`` ) : ファイル名 :param debug: True の時に debug print される (Default値はTrue) :param out_directory: ダウンロードファイルを出力するディレクトリ (flag_pathがTrueの時に有効になる) :param flag_path: Trueの時に out_directory 以下にダウンロードダウンロードファイルを出力する :param flag_subdirectory: Trueの時、ファイルは full pathでなく、登録ディレクトリ以下のsub directoryで保存する :param disable_hash Trueの時、ダウンロード後のハッシュチェックを省略する :return: なし (注) Rest APIの返答においてContent-Disposition の header に記載されたファイル名で ファイルコンテンツのダウンロードを行う。 """ endpoint = "%s/%s" % (self.__endpoint, v) ret = requests.post(self.__endpoint, v, verify=False, headers=util.headers_authorization(self.__parent.access_token()),stream=True) if ret.status_code != 200: message = "HTTP status: " + str(ret.status_code) raise util.Error(message, domain=util.error_domain( __file__, sys._getframe())) content_type = ret.headers["content-type"] try: content_length = int(ret.headers.get("Content-Length")) except: content_length = None if "json" in content_type: return util.json_response(ret) if "application/octet-stream" not in content_type: message = "Content-Type: " + content_type raise util.Error(message, domain=util.error_domain( __file__, sys._getframe())) try: content_length = int(ret.headers.get("Content-Length")) except: content_length = None file_hash = ret.cookies.get("file_hash") if file_hash is not None: file_hash = file_hash.strip('"') file_time = ret.cookies.get("file_time") if file_time is not None: file_time = file_time.strip('"') file_subdirectory = ret.cookies.get("file_subdirectory") if file_subdirectory is not None: file_subdirectory = file_subdirectory.strip('"') try: content_disposition = ret.headers["Content-Disposition"] filename = content_disposition.split("filename=")[1].strip() filename = urlparse.unquote(filename) except: raise util.Error("File cannot be opened") file_path = filename if file_subdirectory in ["", None]: flag_subdirectory = False # ,.. define dirname/file_path to output file dirname = None file_basename = os.path.basename(filename) file_dirname = os.path.dirname(filename) if flag_path in [False, None]: if out_directory in [False, None]: if flag_subdirectory in [False, None]: dirname = None else: dirname = file_subdirectory else: if flag_subdirectory in [False, None]: dirname = out_directory else: dirname = "{}/{}".format(out_directory, file_subdirectory) else: if out_directory in [False, None]: dirname = ".{}".format(file_dirname) else: dirname = "{}{}".format(out_directory,file_dirname) if dirname is None: file_path = file_basename else: file_path = "{}/{}".format(dirname, file_basename) if dirname is not None: util.makedirs(dirname) stored_bytes_written = 0 threshold_size = 1048576 * 100 # 100 Mbyte with open(file_path, "wb") as file: bytes_written = 0 flag_monitor = False if debug and content_length is not None and content_length > threshold_size: flag_monitor = True for chunk in ret.iter_content(chunk_size=1024): file.write(chunk) if flag_monitor: bytes_written += 1024 diff = bytes_written - stored_bytes_written if diff > threshold_size: stored_bytes_written = bytes_written total = int(content_length/1048576) rate = int(100.*(float(bytes_written)/float(content_length))) util.log("[download_monitor] total={}MB, {}% downloaded".format( total, rate), flush=True) util.log("==> downloaded with file = {}".format(file_path), flush=True) if file_time: mtime = util.mktime(file_time) atime = mtime # os.utime(file_path, times=(atime, mtime)) # <- not work with python2.7 os.utime(file_path, (atime, mtime)) if file_hash and disable_hash in [None, False, 0]: if debug: util.log("==> check consitency with hash = {}".format(file_hash), flush=True) hash_check = util.checksum(file_path) if hash_check != file_hash: try: os.remove(file_path) except: pass raise util.Error("download again (inconsistent hash in download file)")