.gitignore000064400000000065144761607150006550 0ustar00/vendor/ /tests/ /test/ composer.lock test/config.phpcomposer.json000064400000000707144761607150007305 0ustar00{ "name": "boru/backblaze", "type": "library", "autoload": { "psr-4": { "boru\\backblaze\\": "src/" } }, "authors": [ { "name": "Daniel Hayes", "email": "dhayes@boruapps.com" } ], "require": { "boru/dhutils": "*" }, "repositories": [ { "type": "composer", "url": "https://satis.boruapps.com" } ] } instructions-composer.txt000064400000000311144761607150011704 0ustar00{ "require": { "boru/dhcli": "dev-master" }, "repositories": [ { "type": "composer", "url": "https://satis.boruapps.com" } ] }src/Account.php000064400000012041144761607150007451 0ustar00cacheRemoveOnClose && is_object($this->cacheFile)) { $this->cacheFile->delete(); } } public function __construct($keyId,$applicationKey,$options=[]) { $this->setupDebug(); $this->setKeyId($keyId); $this->setApplicationKey($applicationKey); $this->initCache(dhGlobal::getDot($options,"cacheFile","")); $this->cacheRemoveOnClose = dhGlobal::getVal($options,"cacheRemoveOnClose",$this->cacheRemoveOnClose); $this->accountInfo = isset($options["accountInfo"]) ? $options["accountInfo"] : []; if(empty($this->accountInfo)) { $this->loadCache(); } if(($this->get("accountId",false) === false || $this->get("authorizationToken",false) === false) && !is_null($this->getKeyId()) && !is_null($this->getApplicationKey())) { //$this->authorize(); } else { $this->_trace("Loaded accountInfo from cache ".$this->cacheFile->path()); } $this->checkAuthorized(true); } public function checkAuthorized($writeCache=false) { if($this->get("accountId",false) === false || $this->get("authorizationToken",false) === false) { $this->authorized = false; } else { $this->authorized = true; if($writeCache) { $this->writeCache(); } } return $this->authorized; } public function get($item=null,$default=false) { $this->_trace("funcStart",__METHOD__); if(is_null($this->accountInfo)) { $this->_trace("funcEnd",__METHOD__); return $default; } if(is_null($item)) { $this->_trace("funcEnd",__METHOD__); return $this->accountInfo; } elseif(!isset($this->accountInfo[$item])) { $this->_trace("funcEnd",__METHOD__); return $default; } $this->_trace("funcEnd",__METHOD__); return $this->accountInfo[$item]; } public function set($dotKey,$value=null) { dhGlobal::dotAssign($this->accountInfo,$dotKey,$value); return $this; } protected function initCache($cacheFilePath="") { $cleanupTime = "+60"; if(empty($cacheFilePath)) { $this->cacheRemoveOnClose=true; $cacheFilePath = ".bbcache_".uniqid(); $cleanupTime = "+5"; } $dir = new Directory(["scan"=>false]); $files = $dir->find(["mmin"=>$cleanupTime,"name"=>'".bbcache*"']); if(!empty($files) && is_array($files)) { foreach($files as $file) { $file->delete(); } } $this->cacheFile = new File([ "path"=>$cacheFilePath, "readMeta"=>true, "create"=>true, "overwrite"=>false, "maxAge"=>60*60*3, //3 hours max life ]); return $this; } public function loadCache() { $cached = $this->cacheFile->content(); if(!empty($cached)) { $j = json_decode($cached,true); if(is_array($j)) { $this->accountInfo = $j; } } return $this; } public function writeCache() { $this->cacheFile->write(json_encode($this->accountInfo)); return $this; } /** * Get the value of keyId */ public function getKeyId() { return $this->keyId; } /** * Set the value of keyId * * @return self */ public function setKeyId($keyId) { $this->keyId = $keyId; return $this; } /** * Get the value of applicationKey */ public function getApplicationKey() { return $this->applicationKey; } /** * Set the value of applicationKey * * @return self */ public function setApplicationKey($applicationKey) { $this->applicationKey = $applicationKey; return $this; } /** * Get the value of accountInfo * * @return mixed */ public function getAccountInfo() { return $this->accountInfo; } /** * Set the value of accountInfo * * @param mixed $accountInfo * @return self */ public function setAccountInfo($accountInfo) { $this->accountInfo = $accountInfo; return $this; } }src/Browser.php000064400000012570144761607150007507 0ustar00client = $clientOrOptions; } elseif(is_array($clientOrOptions)) { $this->client = Client::fromAuthArray($clientOrOptions); } if(is_null($this->client)) { throw new \Exception(__CLASS__." - failed constructor, client or auth array invalid"); } $this->filesOp = new Files($this->client); $this->bucketId = $bucketId; } /** * * @param mixed $path * @param bool $useCache (true) * @param int $maxFileCount (1000) * @return Listing */ public function contents($path=null,$useCache=true,$maxFileCount=1000) { $path = $this->path($path); $listing = null; if(isset($this->cache[$path]) && $useCache) { $listing = $this->cache[$path]; } if(!isset($this->cache[$path]) || !$useCache) { $options = [ "bucketId"=>$this->bucketId, "maxFileCount"=>$maxFileCount, "delimiter"=>$this->delimiter, "arrayFormat"=>"name", "prefix"=>$this->path($path) ]; $files = $this->filesOp->listFiles($options); if(!empty($files)) { $listing = Listing::fromBucketFiles($files,$this); if($useCache) { $this->cache[$path] = $listing; } } } if(is_null($listing)) { $listing = new Listing([],[]); } return $listing; } public function downloadByFileName($fileName,$headersOnly=false,$async=false) { return $this->client->files()->downloadByName($fileName,$headersOnly,$async); } public function downloadByFileId($fileId,$headersOnly=false,$async=false) { return $this->client->files()->downloadById($fileId,$headersOnly,$async); } /** * * @param string $fileName * @param int $limit * @return File[] */ public function getFileVersionsByName($fileName,$limit=100) { $versions = $this->client()->files()->versions([ "bucketId" => $this->bucketId, "maxFileCount"=>$limit, "delimiter"=>"/", "fileName"=>$fileName, "prefix"=>$fileName, //"maxIterations"=>5, //"filesPerRequest"=>1000 ]); if(!empty($versions)) { $output = []; foreach($versions as $bucketFile) { $output[] = static::fileOrFolder($bucketFile,$this); } return $output; } return []; } /** * Returns the delimiter-safe current path, optionally changing $this->path before returning it. * @param string|null $path if null, returns $this->path, otherwise sets $this->path and then returns it * @return string $this->path */ public function path($path=null) { if(is_null($path)) { return $this->getPath(); } else { $this->setPath($path); return $this->getPath(); } } private function pathWithDelimiter($path="") { if(empty($path) || $path == $this->delimiter) { return ""; } if(substr($path,-(strlen($this->delimiter))) != $this->delimiter) { $path.=$this->delimiter; } return $path; } /** * Get the value of path * @return mixed */ public function getPath() { return $this->pathWithDelimiter($this->path); } /** * Set the value of path * @param mixed $path * @return self */ public function setPath($path) { $this->path = $path; return $this; } /** * Get the value of delimiter * @return mixed */ public function getDelimiter() { return $this->delimiter; } /** * Set the value of delimiter * @param mixed $delimiter * @return self */ public function setDelimiter($delimiter) { $this->delimiter = $delimiter; return $this; } public function client() { return $this->client; } public function getClient() { return $this->client; } /** * * @param BucketFile $bucketFile * @param Browser|null $browser * @return Folder|File */ public static function fileOrFolder(BucketFile $bucketFile,$browser=null) { if($bucketFile->get("action") == "folder") { return new Folder($bucketFile); } else { return new File($bucketFile); } } /** * Get the value of bucketId * @return mixed */ public function getBucketId() { return $this->bucketId; } }src/Buckets.php000064400000010154144761607150007460 0ustar00client = $clientOrOptions; } elseif(is_array($clientOrOptions)) { $this->client = Client::fromAuthArray($clientOrOptions); } if(is_null($this->client)) { throw new \Exception(__CLASS__." - failed constructor, client or auth array invalid"); } } /** * list * * @param bool $force * @return \boru\backblaze\parts\Bucket[] */ public function listBuckets($force=false) { $this->_trace("funcStart",__METHOD__); if(!$force && (time() - $this->_bucketCacheLastList) < $this->bucketCacheTime && !empty($this->_bucketCache)) { $this->_trace("funcEnd (cache-hit)",__METHOD__); $this->_trace("listBuckets (cache) successful",count($this->_bucketCache),"returned"); return $this->_bucketCache; } $buckets = []; $params = [ "accountId"=>$this->client->getAccountId(), ]; $request = $this->client->request("post","/b2_list_buckets",$params); if(($response = $request->jsonParseSend()) !== false) { if(isset($response["buckets"])) { foreach($response["buckets"] as $bucket) { $buckets[] = new Bucket($bucket); } } } $this->_trace("listBuckets successful",count($buckets),"returned"); $this->_trace("funcEnd",__METHOD__); $this->_bucketCache = $buckets; $this->_bucketCacheLastList = time(); return $buckets; } public function create($bucketName,$type=Bucket::T_PRIVATE) { $this->_trace("funcStart",__METHOD__); $params = [ "bucketType"=>$type, "bucketName"=>$bucketName, "accountId"=>$this->client->getAccountId() ]; $request = $this->client->request("post","/b2_create_bucket",$params); if(($response = $request->jsonParseSend()) !== false) { $this->_trace("createBucket successful",$response["bucketName"]); $this->_trace("funcEnd",__METHOD__); $bucket = new Bucket($response); $this->_bucketCache[$bucket->get("bucketId")] = $bucket; return $bucket; } $this->_trace("funcEnd",__METHOD__); return false; } public function getBy($option="bucketName",$value) { $this->_trace("funcStart",__METHOD__); $buckets = $this->listBuckets(); foreach($buckets as $bucket) { if($bucket->get($option) == $value) { $this->_trace("funcEnd",__METHOD__); return $bucket; } } $this->_trace("funcEnd",__METHOD__); return false; } public function fromOptions($options=[],$justId=false) { $this->_trace("funcStart",__METHOD__); if(isset($options["bucket"])) { $this->_trace("funcEnd",__METHOD__); return $options["bucket"]; } elseif(isset($options["bucketId"])) { $this->_trace("funcEnd",__METHOD__); return $this->getBy("bucketId",$options["bucketId"]); } elseif(isset($options["bucketName"])) { $this->_trace("funcEnd",__METHOD__); return $this->getBy("bucketName",$options["bucketName"]); } $this->_trace("funcEnd",__METHOD__); return false; } }src/Client.php000064400000022324144761607150007300 0ustar00setupDebug(); $this->authArray["keyId"] = $keyId; $this->authArray["applicationKey"] = $applicationKey; $this->authArray["options"] = $options; $this->http = new Http($keyId,$applicationKey,$options); $account = $this->http->getAccount(); if(!$account->checkAuthorized()) { throw new \Exception("Authorization Failure"); } } public function dropHttp() { $this->http = null; } public function resetHttp() { $this->http = new Http($this->authArray["keyId"],$this->authArray["applicationKey"],$this->authArray["options"]); } /** @return Files */ public function files() { if(is_null($this->fileOperations)) { $this->fileOperations = new Files($this); } return $this->fileOperations; } public function upload() { if(is_null($this->uploadOperations)) { $this->uploadOperations = new Upload($this); } return $this->uploadOperations; } public function buckets() { if(is_null($this->bucketOperations)) { $this->bucketOperations = new Buckets($this); } return $this->bucketOperations; } public function getAccount($item=null,$default=false) { $this->_trace("funcStart",__METHOD__); if(is_null($this->accountInfo)) { $this->_trace("funcEnd",__METHOD__); return $default; } if(is_null($item)) { $this->_trace("funcEnd",__METHOD__); return $this->accountInfo; } elseif(!isset($this->accountInfo[$item])) { $this->_trace("funcEnd",__METHOD__); return $default; } $this->_trace("funcEnd",__METHOD__); return $this->accountInfo[$item]; } /** * * @param mixed $method * @param mixed $url * @param mixed $data * @param mixed $auth * @param mixed $onSuccess * @param mixed $onError * @return Request */ public function request($method=null,$url=null,$data=null,$auth=true,$onSuccess=null,$onError=null) { if(is_null($this->http)) { $this->resetHttp(); } return $this->http->request($method,$url,$data,$auth,$onSuccess,$onError); } public function accountSet($dotKey,$value=null,$writeCache=false) { if(is_null($this->http)) { $this->resetHttp(); } $this->http->accountSet($dotKey,$value,$writeCache); return $this; } public function getAccountId() { if(is_null($this->http)) { $this->resetHttp(); } return $this->http->getAccount()->get("accountId",false); } public function getDownloadUrl() { if(is_null($this->http)) { $this->resetHttp(); } return $this->http->getAccount()->get("downloadUrl",false); } public function getUploadAuth() { if(is_null($this->http)) { $this->resetHttp(); } return $this->http->getAccount()->get("uploadAuth",false); } /** * Get the value of keyId */ public function getKeyId() { if(is_null($this->http)) { $this->resetHttp(); } return $this->http->getAccount()->getKeyId(); } /** * Set the value of keyId * * @return self */ public function setKeyId($keyId) { if(is_null($this->http)) { $this->resetHttp(); } $this->http->getAccount()->setKeyId($keyId); return $this; } /** * Get the value of applicationKey */ public function getApplicationKey() { if(is_null($this->http)) { $this->resetHttp(); } return $this->http->getAccount()->getApplicationKey(); } /** * Set the value of applicationKey * * @return self */ public function setApplicationKey($applicationKey) { if(is_null($this->http)) { $this->resetHttp(); } $this->http->getAccount()->setApplicationKey($applicationKey); return $this; } /** * Get the value of queue * @return mixed */ public function getQueue() { return $this->queue; } /** * Set the value of queue * @param mixed $queue * @return self */ public function setQueue($queue) { $this->queue = $queue; return $this; } private function pathFromFileOrPath($fileOrPath) { if(is_object($fileOrPath) && method_exists($fileOrPath,"path")) { $filePath = $fileOrPath->path(); } elseif(is_object($fileOrPath) || is_array($fileOrPath)) { return false; } else { $filePath = $fileOrPath; } return $filePath; } public function queueVerifyById($sourceFileOrPath,$fileId,$extraArgs=[],$bootstrap=null,$run=true) { if(($filePath = $this->pathFromFileOrPath($sourceFileOrPath)) === false) { return false; } $args = [ "sourceFile"=>$filePath, "fileId"=>$fileId ]; if(!is_null($extraArgs) && !empty($extraArgs)) { foreach($extraArgs as $k=>$v) { $args[$k] = $v; } } return $this->asyncWorker("verifyUpload",$args,$bootstrap,$run); } public function queueUpload($bucketOrId,$fileOrPath,$destFilePath,$extraArgs=[],$bootstrap=null,$run=true) { if(is_object($bucketOrId) && $bucketOrId instanceof Bucket) { $bucketId = $bucketOrId->get("bucketId"); } elseif(is_object($bucketOrId) || is_array($bucketOrId)) { return false; } else { $bucketId = $bucketOrId; } if(($filePath = $this->pathFromFileOrPath($fileOrPath)) === false) { return false; } $args = [ "bucketId"=>$bucketId, "sourceFile"=>$filePath, "destFile"=>$destFilePath ]; if(!is_null($extraArgs) && !empty($extraArgs)) { foreach($extraArgs as $k=>$v) { $args[$k] = $v; } } return $this->asyncWorker("upload",$args,$bootstrap,$run); } public function asyncWorker($methodName,$args=[],$bootstrap=null,$run=true) { $appKey = $this->getApplicationKey(); $keyId = $this->getKeyId(); $config = [ "keyId"=>$keyId, "appKey"=>$appKey ]; $work = new Work(["\\".AsyncWorker::getClassCallable(),$methodName],["args"=>[$config,$args],"asJson"=>false]); if($run) { dhGlobal::asyncWork($work,$bootstrap); } return $work; } public static function fromAuthArray($array,$throwOnError=true) { if(($keyId = dhGlobal::getVal($array,"keyId",false)) === false) { if($throwOnError) { throw new \Exception("Client::fromAuthArray() -- missing keyId"); } return false; } if(($applicationKey = dhGlobal::getVal($array,"applicationKey",false)) === false) { if(($applicationKey = dhGlobal::getVal($array,"appKey",false)) === false) { if($throwOnError) { throw new \Exception("Client::fromAuthArray() -- missing applicationKey"); } return false; } } if(($options = dhGlobal::getVal($array,"options",false)) === false) { $options = []; } if(!is_array($options)) { $options = []; } return new self($keyId,$applicationKey,$options); } } src/Files.php000064400000024062144761607150007125 0ustar00client = $clientOrOptions; } elseif(is_array($clientOrOptions)) { $this->client = Client::fromAuthArray($clientOrOptions); } if(is_null($this->client)) { throw new \Exception(__CLASS__." - failed constructor, client or auth array invalid"); } } /** * * @param mixed $fileId * @param bool $async * @return string|\React\Promise\ExtendedPromiseInterface * @throws InvalidArgumentException * @throws Exception * @throws UnexpectedValueException */ public function downloadById($fileId,$headersOnly=false,$async=false) { $request = $this->client->request("get","/b2_download_file_by_id?fileId=$fileId",[]); if($headersOnly) { $request->header("Range","bytes=0-1"); } if($async) { $deferred = new Deferred(); $request->sendAsync()->then(function($response) use (&$deferred) { $deferred->resolve($response); },function($e) use (&$deferred) { $deferred->reject($e); }); return $deferred->promise(); } else { return $request->send(); } //return } /** @return \boru\dhutils\http\Response|\React\Promise\ExtendedPromiseInterface */ public function downloadByName($fileName,$headersOnly=false,$async=false) { $url = $this->client->getDownloadUrl(); if(!empty($url)) { $url.="/file"; $url.="/".$fileName; $request = $this->client->request("get",$url); if($headersOnly) { $request->header("Range","bytes=0-1"); } if($async) { $deferred = new Deferred(); $request->sendAsync()->then(function($response) use (&$deferred) { $deferred->resolve($response); },function($e) use (&$deferred) { $deferred->reject($e); }); return $deferred->promise(); } return $request->send(); } return false; } public function getById($fileId) { $this->_trace("funcStart",__METHOD__); $params = ['fileId' => $fileId]; $request = $this->client->request("post","/b2_get_file_info",$params); if(($response = $request->jsonParseSend()) !== false) { $this->_trace("funcEnd",__METHOD__); return new BucketFile($response); } } public function getByName($bucket,$fileName,$dir=false) { $this->_trace("funcStart",__METHOD__); $prefix = $fileName; if($dir) { if(substr($prefix,-1) != "/") { $fileName.="/"; } else { $prefix = substr($prefix,0,-1); } } /* $pathWithSlash(filename) = $path(prefix); if(substr($path,-1) != "/") { $pathWithSlash.="/"; } else { $path = substr($path,0,-1); }*/ $options = [ "maxFileCount"=>1, "prefix"=>$prefix, "fileName"=>$fileName, "delimiter"=>"/" ]; if($dir) { $options["includeDirs"] = true; } if(is_object($bucket)) { $options["bucket"] = $bucket; } else { $options["bucketId"] = $bucket; } $files = $this->listFiles($options); //$bucket,1,$fileName,null,$fileName); //print_r($files); foreach($files as $file) { if($file->get("fileName") == $fileName) { $this->_trace("funcEnd",__METHOD__); return $file; } } $this->_trace("funcEnd",__METHOD__); return false; } /** * * @param array $options * @return false|BucketFile[] */ public function listFiles($options=[],$versions=false) { /** * $options = [ * "bucket" / "bucketId" / "bucketName", * "fileName" -- to get a file by filename * "maxFileCount" (defaults to 1), * "prefix" -- prefix to search for * "delimiter" -- delimiter to use * "startFileName" -- filename offset to start listing at * "includeDirs" -- defaults to false * ] */ if(($bucketId = dhGlobal::getDot($options,"bucketId",false))!==false) { } elseif(($bucket = $this->client->buckets()->fromOptions($options)) !== false) { $bucketId = $bucket->get("bucketId"); } $fileName = dhGlobal::getDot($options,"fileName",null); $maxFileCount = dhGlobal::getDot($options,"maxFileCount",1); $prefix = dhGlobal::getDot($options,"prefix",null); $delimiter = dhGlobal::getDot($options,"delimiter",null); $startFileName = dhGlobal::getDot($options,"startFileName",null); if($versions) { $maxIterations = dhGlobal::getDot($options,"maxIterations", $versions ? 5 : 3); $filesPerRequest = dhGlobal::getDot($options,"filesPerRequest",1000); $options["isVersionsRequest"] = $versions; } else { $maxIterations = dhGlobal::getDot($options,"maxIterations", 3); $filesPerRequest = dhGlobal::getDot($options,"filesPerRequest",is_null($fileName) ? 1000 : 1); } $this->_trace("funcStart",__METHOD__); $files = []; if(!is_null($fileName)) { $nextFile = $fileName; } else { $nextFile = is_null($startFileName) ? '' : $startFileName; } $continue = true; $iterations = 0; while($continue) { $iterations++; $params = $this->makeListParams($bucketId,$nextFile,$filesPerRequest,$prefix,$delimiter); if($versions) { $request = $this->client->request("post","/b2_list_file_versions",$params); } else { $request = $this->client->request("post","/b2_list_file_names",$params); } if(($response = $request->jsonParseSend()) !== false && isset($response["files"])) { $continue = $this->parseListResponseFile($files,$response,$maxFileCount,$delimiter,$fileName,$options); if(is_null($response['nextFileName'])) { $continue = false; break; } else { $nextFile = $response["nextFileName"]; } } else { return false; } if($maxIterations>0 && $iterations>=$maxIterations) { break; } } $this->_trace("funcEnd",__METHOD__); return $files; } private function parseListResponseFile(&$files,$response,$maxFileCount,$delimiter="",$fileName=null,$options=[]) { $includeDirs = dhGlobal::getDot($options,"includeDirs",false); $isVersionRequest = dhGlobal::getDot($options,"isVersionsRequest",false); foreach($response["files"] as $file) { if(count($files) >= $maxFileCount) { return false; } $isDir = substr($file['fileName'],-1) == "/" ? true : false; if($isDir && $delimiter != "/" && !$includeDirs) { continue; } if(!is_null($fileName)) { if($file["fileName"] == $fileName) { $this->listFilesAddToArray($files,new BucketFile($file),$options); if(!$isVersionRequest) { return false; } } elseif($includeDirs && substr($file['fileName'],-1) == "/" && substr($file['fileName'],0,-1) == $fileName) { $this->listFilesAddToArray($files,new BucketFile($file),$options); if(!$isVersionRequest) { return false; } } } else { $this->listFilesAddToArray($files,new BucketFile($file),$options); } } return true; } protected function listFilesAddToArray(&$array,$bucketFile,$options) { $arrayFormat = dhGlobal::getDot($options,"arrayFormat",null); if(is_null($arrayFormat) || $arrayFormat == "default" || empty($arrayFormat)) { $array[] = $bucketFile; return; } if($arrayFormat == "name") { $array[$bucketFile->name()] = $bucketFile; } if($arrayFormat == "fullName") { $array[$bucketFile->fileName()] = $bucketFile; } } /** @return BucketFile[] */ public function versions($options=[]) { return $this->listFiles($options,true); } private function makeListParams($bucketId,$nextFile,$filesPerRequest,$prefix,$delimiter) { $params = [ "bucketId"=>$bucketId, "startFileName"=>$nextFile, "maxFileCount"=>$filesPerRequest, ]; if(!is_null($prefix)) { $params["prefix"] = $prefix; } if(!is_null($delimiter)) { $params["delimiter"] = $delimiter; } return $params; } }src/Path.php000064400000022333144761607150006756 0ustar00setupDebug(); if(isset($options["localDirectory"]) && !empty($options["localDirectory"])) { $this->localDir = new Directory([ "path"=>$options["localDirectory"], "scan"=>true, "recursive"=>true ]); } elseif(isset($options["localDir"]) && !empty($options["localDir"])) { $this->localDir = new Directory([ "path"=>$options["localDir"], "scan"=>true, "recursive"=>true ]); } if(is_null($this->localDir)) { throw new PathException(__METHOD__,"local directory not specified in options (localDir or localDirectory)",-1); } $this->bucketPath = dhGlobal::getDot($options,"bucketPath",null); if(is_null($this->bucketPath)) { throw new PathException(__METHOD__,"remote bucketPath not specified in options (bucketPath)",-1); } if(substr($this->bucketPath,-1) != "/") { $this->bucketPath.="/"; } if(substr($this->bucketPath,0,1) == "/") { if($this->bucketPath == "/") { $this->bucketPath = ""; } else { $this->bucketPath = substr($this->bucketPath,1); } } $this->setClientFromOptions($options); $this->setBucketFromOptions($options); } public function getStats() { $stats = [ "directories"=>count($this->directories), "files"=>0, "size"=>0, "changed"=>[ "files"=>0, "size"=>0, ], "uploaded"=>[ "files"=>0, "size"=>0 ] ]; foreach($this->files as $file) { $stats["files"]++; $stats["size"]+=$file->size(); $changed = $file->get("changed",false); $uploaded = $file->get("uploaded",false); if($changed) { $stats["changed"]["files"]++; $stats["changed"]["size"]+=$file->size(); } if($uploaded) { $stats["uploaded"]["files"]++; $stats["uploaded"]["size"]+=$file->size(); } } return $stats; } public function files() { return $this->files; } public function dirs() { return $this->dirs; } public function scan(callable $fileComplete=null,callable $directoryComplete=null) { $this->_info("scan started"); $this->syncDirectory($this->localDir,true,false,$fileComplete,$directoryComplete); } public function sync($forceFullSync=false,callable $fileComplete=null,callable $directoryComplete=null) { $this->_info("sync started"); $this->syncDirectory($this->localDir,false,$forceFullSync,$fileComplete,$directoryComplete); } protected function syncDirectory($Directory,$scanOnly=false,$forceFullSync=false,callable $fileComplete=null,callable $directoryComplete=null) { //Get backblaze files and info for the parent directory.. $bdir = $this->bucketPath.substr($Directory->path(),1)."/"; $files = $Directory->files(); $dirs = $Directory->dirs(); $fileCount = is_array($files) ? count($files) : 0; $dirCount = is_array($dirs) ? count($dirs) : 0; $bbFiles = $this->client->listFiles([ "bucket"=>$this->bucket, "maxFileCount"=>1000, "delimiter"=>"/", "prefix"=>$bdir, "arrayFormat"=>"name" ]); $bbFileCount = is_array($bbFiles) ? count($bbFiles) : 0; $this->_info("Starting ".$bdir." - Files:".$fileCount." Subdirs:".$dirCount." BackBlaze:".$bbFileCount." "); $filesProcessed = []; if(!empty($files) && is_array($files)) { $count=0; foreach($files as $file) { $this->files[$file->path()] = $file; $fRef = &$this->files[$file->path()]; $count++; $name = $file->name(); $bbFile = isset($bbFiles[$name]) ? $bbFiles[$name] : false; $fRef->set("backblazeFile",$bbFile); $changed = true; if(!$forceFullSync) { if($bbFile !== false) { if(strlen($file->sha1())>10) { if($file->sha1() == $bbFile->sha1()) { //$this->_debug("sha match on",$file->path()); $changed = false; } } } } $uploaded = false; if($changed) { $this->changed[] = &$this->files[$file->path()]; if(!$scanOnly) { try { $this->client->upload([ "bucket"=>$this->bucket, "file"=>$file ]); $uploaded = true; $this->uploaded[] = &$this->files[$file->path()]; } catch (\Exception $e) { } } } $fRef->set("changed",$changed); $fRef->set("uploaded",$uploaded); $filesProcessed[] = &$this->files[$file->path()]; if(is_callable($fileComplete)) { $fileComplete([ "sync"=>$this, "file"=>$fRef, "num"=>$count-1, "fileCount"=>$fileCount, "bbFileCount"=>$bbFileCount ]); } else { $this->_trace("Processed ".$fRef->path()." - ".( $changed ? ($uploaded ? "Uploaded" : "Changed") : "Skipped" ) ); } } $file = null; $bbFile = null; unset($fRef); } if(is_callable($directoryComplete)) { $directoryComplete([ "sync"=>$this, "directory"=>$Directory, "processed"=>$filesProcessed, "fileCount"=>$fileCount, "bbFileCount"=>$bbFileCount ]); } $Directory->set("filesProcessed",$filesProcessed); $Directory->set("fileCount",$fileCount); $Directory->set("bbFileCount",$bbFileCount); $this->directories[$Directory->path()] = $Directory; $this->_info("complete with ".$Directory->path()." .. processed ".count($filesProcessed)." files"); unset($filesProcessed); if(!empty($dirs) && is_array($dirs)) { foreach($dirs as $dir) { $this->syncDirectory($dir,$scanOnly,$forceFullSync,$fileComplete,$directoryComplete); } } } public function setClientFromOptions($options=[]) { if(isset($options["client"])) { $this->client = $options["client"]; return $this->client; } $keyId = dhGlobal::getDot($options,"keyId",null); $applicationKey = dhGlobal::getDot($options,"applicationKey",null); $accountInfo = dhGlobal::getDot($options,"accountInfo",[]); $options["accountInfo"] = $accountInfo; $cacheFile = dhGlobal::getDot($options,"cacheFile",false); if($cacheFile !== false) { $options["cacheFile"] = $cacheFile; } $this->client = new Client($keyId,$applicationKey,$options); return $this->client; } public function setBucketFromOptions($options=[]) { $this->_trace("funcStart",__METHOD__); if(isset($options["bucket"])) { $this->bucket = $options["bucket"]; } elseif(isset($options["bucketId"])) { $this->bucket = $this->client->getBucketBy("bucketId",$options["bucketId"]); } elseif(isset($options["bucketName"])) { $this->bucket = $this->client->getBucketBy("bucketName",$options["bucketName"]); } $this->_trace("funcEnd",__METHOD__); if(is_null($this->bucket)) { return false; } return $this->bucket; } } class PathException extends BackBlazeBaseException { protected $debug_name = "backblaze-path"; }src/Upload.php000064400000065532144761607150007316 0ustar00debug_name."-t","trace","TRACE"); //_trace() dhGlobal::addLogLevel($this->debug_name."-d","debug","DEBUG"); //_debug() dhGlobal::addLogLevel($this->debug_name."-i","info"," INFO"); //_info() dhGlobal::addLogLevel($this->debug_name."-w","warn"," WARN"); //_warn() dhGlobal::addLogLevel($this->debug_name."-e","error","ERROR"); //_error() } protected function _trace(...$args) { if($this->debugDoTrace) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-t"); return call_user_func_array([$this,"log"],$args); } return; } protected function _debug(...$args) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-d"); return call_user_func_array([$this,"log"],$args); } protected function _info(...$args) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-i"); return call_user_func_array([$this,"log"],$args); } protected function _warn(...$args) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-w"); return call_user_func_array([$this,"log"],$args); } protected function _error(...$args) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-e"); return call_user_func_array([$this,"log"],$args); } public function log(...$args) { return dhGlobal::log(...$args); } protected $_bucketCacheLastList = 0; protected $_bucketCache = []; protected $debug_name = "backblaze"; protected $debug_prefix = "BBUPLOAD"; public $debugDoTrace = false; protected $async = true; public $uploadNormalLimit = 3000000000; //3gb public $multiPartSize = 100000000; //100mb //public $uploadNormalLimit = 100000000; //100mb //public $multiPartSize = 20000000; //20MB protected $bucketId; protected $fileName; protected $contentType; protected $fileSize; protected $fileHash; protected $modifiedTime; protected $content; protected $meta; protected $partPieces = []; /** @var Client */ protected $client; public function __construct($clientOrOptions) { if(is_object($clientOrOptions) && $clientOrOptions instanceof Client) { $this->client = $clientOrOptions; } elseif(is_array($clientOrOptions)) { $this->client = Client::fromAuthArray($clientOrOptions); } if(is_null($this->client)) { throw new \Exception(__CLASS__." - failed constructor, client or auth array invalid"); } } /** * * @param string|Bucket $bucket * @param bool $cache * @return \React\Promise\Promise * @throws ClientException * @throws ClientException */ public function getUploadUrlAuth($bucket,$cache=false) { $this->_trace("funcStart",__METHOD__); $deferred = new \React\Promise\Deferred(); if(is_object($bucket)) { $params = ['bucketId' => $bucket->get("bucketId")]; } else { $params = ['bucketId' => $bucket]; } $response = false; if($cache) { $response = $this->client->getUploadAuth(); } if($response !== false && $cache) { $deferred->resolve($response); } else { $request = $this->client->request("post","/b2_get_upload_url",$params); $request->sendAsync(function($response) use ($deferred,$cache) { $json = $response->body(true); if(is_array($json)) { $this->client->accountSet("uploadAuth",$json,$cache); } $this->_trace("funcEnd",__METHOD__,__LINE__); $deferred->resolve($json); },function($e) use ($deferred,$cache) { $this->_trace("funcEnd with error",__METHOD__,__LINE__); $deferred->reject($e); //throw new \Exception("b2_get_upload_url failed"); }); } return $deferred->promise(); } /** * * @param array $options * @return false|BucketFile|PromiseInterface * @throws InvalidArgumentException * @throws Exception * @throws UnexpectedValueException * @throws ClientException */ public function upload($options=[]) { $this->_trace("funcStart",__METHOD__); $bucketId = false; if(($bucketId = dhGlobal::getDot($options,"bucketId",false))!==false) { } elseif(($bucket = $this->client->buckets()->fromOptions($options)) !== false) { $bucketId = $bucket->get("bucketId"); } else { throw new ClientException(__METHOD__,"No bucket option provided (bucket, bucketId, bucketName)"); } $this->bucketId = $bucketId; $this->async=true; if(($async=dhGlobal::getVal($options,"async",false)) !== false) { $this->async = true; } $timeout = dhGlobal::getVal($options,"timeout",true); $uploadNormalLimit = dhGlobal::getVal($options,"uploadNormalLimit",$this->uploadNormalLimit); $multiPartSize = dhGlobal::getVal($options,"multiPartSize",$this->multiPartSize); $file = dhGlobal::getDot($options,"file",null); if(!is_null($file) && $file instanceof File) { $prefix = dhGlobal::trimString("/",dhGlobal::getDot($options,"prefix",""),dhGlobal::TRIM_BOTH); if(($fileName = dhGlobal::getVal($options,"fileName",false)) !== false) { $this->fileName = $fileName; } else { $this->fileName = dhGlobal::trimString("/",$prefix.$file->path(),dhGlobal::TRIM_START); } $this->modifiedTime = $file->mtime("millis"); $this->fileSize = $file->size(); $this->fileHash = $file->sha1(); $this->contentType = $file->mimeType(); $this->content = $file->content(); $file=null; } else { $this->modifiedTime = dhGlobal::getDot($options,"modifiedTimeMillis",false); if(!$this->modifiedTime) { $mt = dhGlobal::getDot($options,"modifiedTime",false); if(is_object($mt)) { $this->modifiedTime = $mt->format("U")*1000; } elseif(is_numeric($mt)) { $this->modifiedTime = $mt * 1000; } else { try { $mt = new \DateTime($mt); $this->modifiedTime = $mt->format("U")*1000; } catch (\Exception $e) { } } } if(!$this->modifiedTime) { $this->modifiedTime = floor(microtime(true)*1000); } //size, //hash $fileInfo = dhGlobal::getFileHashAndSize($options["content"]); $this->fileSize = $fileInfo["size"]; $this->fileHash = $fileInfo["hash"]; $this->fileName = $options["fileName"]; $this->contentType = dhGlobal::getDot($options,"contentType","b2/x-auto"); $this->content = &$options["content"]; } $this->meta = dhGlobal::getDot($options,"meta",[]); if($this->fileSize <= $uploadNormalLimit) { $return = $this->uploadNormal($timeout); } else { if($this->async) { $return = $this->uploadMultiPartAsync($timeout,$multiPartSize); } else { $return = $this->uploadMultiPart($timeout,$multiPartSize); } } $this->_trace("funcEnd",__METHOD__); if($return instanceof PromiseInterface) { $return->then(function($resp) { }); } return $return; } /** * * @return PromiseInterface * @throws ClientException */ protected function uploadNormal($timeout=true) { $this->_trace("funcStart",__METHOD__); if($this->async) { $cache = false; } else { $cache = true; } $uDeferred = new \React\Promise\Deferred(); $this->getUploadUrlAuth($this->bucketId,false)->then(function($value) use (&$uDeferred,$timeout) { $request = $this->client->request("post",$value["uploadUrl"]); $request->authToken($value["authorizationToken"]) ->header("Content-Type",$this->contentType) ->header("Content-Length",$this->fileSize) ->header("X-Bz-File-Name",$this->fileName) ->header("X-Bz-Content-Sha1",$this->fileHash) ->header("X-Bz-Info-src_last_modified_millis",$this->modifiedTime) ->rawBody($this->content); if(!empty($this->meta)) { foreach($this->meta as $k=>$v) { $request->header("X-Bz-Info-".$k,$v); } } if($this->async) { $request->async(true); $request->sendAsync(function($response) use ($request,&$uDeferred,$timeout) { $this->_trace("funcEnd",__METHOD__); $this->handleNormalReponse($request,$response,$uDeferred,$timeout); },function($e) use ($request,&$uDeferred) { $this->_trace("funcEnd",__METHOD__); if(!is_null($uDeferred)) { $uDeferred->reject($request->getMethod()." Request to ".$request->getUrl()." failed with exception ".$e->getCode()." - ".$e->getMessage()); } else { throw new ClientException(__METHOD__,$request->getMethod()." Request to ".$request->getUrl()." failed with exception ".$e->getCode()." - ".$e->getMessage()); } },$timeout); } else { $response = $request->send(); $this->handleNormalReponse($request,$response,$uDeferred,$timeout); $this->_trace("funcEnd",__METHOD__); } },function($e) use (&$uDeferred) { $this->_trace("funcEnd",__METHOD__); if(!is_null($uDeferred)) { $uDeferred->reject($e); } else { throw new \Exception($e->getMessage(),$e->getCode()); } }); //if(!$this->async) { // return \React\Async\await($uDeferred->promise()); //} return $uDeferred->promise(); } protected function handleNormalReponse($request,$response,$deferred=null,$timeout=true) { $this->_trace("funcStart",__METHOD__); $json = $response->body(true); if(is_array($json)) { $bucketFile = new BucketFile($json); $bucketFile->set("sourceSha1",$this->fileHash); dhGlobal::debug("uploaded ".$bucketFile->fileName(),$bucketFile->fileId()); dhGlobal::increment("bb.async.completed"); if(!is_null($deferred)) { $deferred->resolve($bucketFile); } $this->_trace("funcEnd",__METHOD__); return $bucketFile; } else { if(is_object($response)) { if($response->getStatusCode() == 401 && $response->getReasonPhrase() == "unauthorized") { $this->_trace("funcEnd",__METHOD__); if(!is_null($deferred)) { $deferred->reject("401-unauthorized returned from BackBlaze. Account credentials are not valid to perform this action"); } else { throw new ClientException(__METHOD__,"401-unauthorized returned from BackBlaze. Account credentials are not valid to perform this action",-1); } } elseif($response->getStatusCode() == 401 || $response->getStatusCode() == 400) { if($this->uploadTries >= 5) { $this->_trace("funcEnd",__METHOD__); if(!is_null($deferred)) { $deferred->reject("upload re-try limit (5) hit"); } else { throw new ClientException(__METHOD__,"upload re-try limit (5) hit"); } } $this->uploadTries++; $this->accountInfo["uploadAuth"] = null; $retryDelay = $this->uploadDelay * 1000; $this->uploadDelay *= $this->uploadBackoff; usleep($retryDelay); $this->_trace("funcEnd",__METHOD__); return $this->uploadNormal($timeout); } else { $this->_trace("funcEnd",__METHOD__); if(!is_null($deferred)) { $deferred->reject($request->getMethod()." Request to ".$request->getUrl()." failed with status code ".$response->getStatusCode()." - ".$response->getReasonPhrase().PHP_EOL.PHP_EOL.$response->body().PHP_EOL); } else { throw new ClientException(__METHOD__,$request->getMethod()." Request to ".$request->getUrl()." failed with status code ".$response->getStatusCode()." - ".$response->getReasonPhrase().PHP_EOL.PHP_EOL.$response->body().PHP_EOL,$response->getStatusCode()); } } } else { if(is_array($response)) { $response = json_encode($response); } $this->_trace("funcEnd",__METHOD__); if(!is_null($deferred)) { $deferred->reject($request->getMethod()." Request to ".$request->getUrl()." failed with non-object: ".$response); } else { throw new \Exception("HTTP Response to ".$request->getMethod()." Request to ".$request->getUrl()." is not an object",-1); } } } } /** * We still return a promiss, even tho it's synchronous * @param bool $timeout * @return false|BucketFile */ protected function uploadMultiPart($timeout=true,$multiPartSize=null) { if(is_null($multiPartSize)) { $multiPartSize = $this->multiPartSize; } $uDeferred = new \React\Promise\Deferred(); $this->_trace("funcStart",__METHOD__); $params = [ 'bucketId' => $this->bucketId, 'fileName' => $this->fileName, 'contentType' => $this->contentType, 'fileInfo' => [ "large_file_sha1" => $this->fileHash, "src_last_modified_millis"=> $this->modifiedTime ] ]; if(!empty($meta)) { $params["fileInfo"] = $meta; } $request = $this->client->request("post","/b2_start_large_file",$params); if(($response = $request->jsonParseSend()) !== false) { //print_r($response); $newFileId = $response['fileId']; } else { $uDeferred->reject($response); return $uDeferred->promise(); } $parts = []; $partsTotal = ceil($this->fileSize/$multiPartSize); for($i=0;$i<$partsTotal;$i++) { dhGlobal::outLine("part $i"); $sizeSent = $i * $multiPartSize; $remainingSize = $this->fileSize - $sizeSent; $size = $remainingSize > $multiPartSize ? $multiPartSize : $remainingSize; $partInfo = dhGlobal::getFileHashAndSize($this->content,$sizeSent,$size); $partSize = $partInfo["size"]; $partHash = $partInfo["hash"]; $parts[] = $partHash; $request = $this->client->request("post","/b2_get_upload_part_url",["fileId"=>$newFileId]); if(($response = $request->jsonParseSend()) !== false) { print_r($response); $request = $request = $this->client->request("post",$response["uploadUrl"]) ->authToken($response["authorizationToken"]) ->header("X-Bz-Part-Number",$i+1) ->header("Content-Length",$partSize) ->header("X-Bz-Content-Sha1",$partHash) ->rawBody(dhGlobal::getPartOfFile($this->content,$sizeSent,$size)); $response = $request->send(); } } $request = $this->client->request("post","/b2_finish_large_file",["fileId"=>$newFileId,"partSha1Array"=>$parts]); $this->_trace("funcEnd",__METHOD__); if(($response = $request->jsonParseSend()) !== false) { $bf = new BucketFile($response); $bf->set("sourceSha1",$this->fileHash); $uDeferred->resolve($bf); //return new BucketFile($response); }else { $uDeferred->reject(""); } $this->_trace("funcEnd",__METHOD__); return $uDeferred->promise(); } /** * Returns a promise, but uses many internal promises * @param bool $timeout * @return false|BucketFile */ protected function uploadMultiPartAsync($timeout=true,$multiPartSize=null) { if(is_null($multiPartSize)) { $multiPartSize = $this->multiPartSize; } $uDeferred = new \React\Promise\Deferred(); $this->_trace("funcStart",__METHOD__); $params = [ 'bucketId' => $this->bucketId, 'fileName' => $this->fileName, 'contentType' => $this->contentType, 'fileInfo' => [ "large_file_sha1" => $this->fileHash, "src_last_modified_millis"=> (string) $this->modifiedTime ] ]; if(!empty($meta)) { $params["fileInfo"] = $meta; } //This call we do synchronously $request = $this->client->request("post","/b2_start_large_file",$params); if(($response = $request->jsonParseSend()) !== false && isset($response["fileId"])) { $newFileId = $response['fileId']; } else { $uDeferred->reject($response); unset($request); return $uDeferred->promise(); } unset($response); unset($request); $parts = []; $partsTotal = ceil($this->fileSize/$multiPartSize); $pendingParts = []; $results = []; for($i=0;$i<$partsTotal;$i++) { $partNumber=$i+1; $pendingParts[$partNumber] = null; $results[$partNumber] = null; } $partsDeferred = new Deferred(); for($i=0;$i<$partsTotal;$i++) { //dhGlobal::outLine("part $i"); $partNumber=$i+1; $sizeSent = $i * $multiPartSize; $remainingSize = $this->fileSize - $sizeSent; $size = $remainingSize > $multiPartSize ? $multiPartSize : $remainingSize; $partInfo = dhGlobal::getFileHashAndSize($this->content,$sizeSent,$size); $partSize = $partInfo["size"]; $partHash = $partInfo["hash"]; $parts[] = $partHash; $this->partPieces[$partNumber] = dhGlobal::getPartOfFile($this->content,$sizeSent,$size); $this->sendPart($newFileId,$partNumber,$partSize,$partHash,$timeout,$uDeferred,$pendingParts,$partsDeferred,$sizeSent,$size,0); } $this->content=null; $partsDeferred->promise()->then(function() use($parts,$newFileId,&$uDeferred,$pendingParts) { $this->_trace("upload - all parts done"); //dhGlobal::outLine("parts all done"); $request = $this->client->request("post","/b2_finish_large_file",["fileId"=>$newFileId,"partSha1Array"=>$parts]); $this->_trace("funcEnd",__METHOD__); if(($response = $request->jsonParseSend()) !== false) { $this->content=null; $bf = new BucketFile($response); $bf->set("sourceSha1",$this->fileHash); $uDeferred->resolve($bf); //return new BucketFile($response); }else { $this->content=null; $uDeferred->reject($pendingParts); } unset($response); unset($request); }); //Resolves with BucketFile //Rejects with array of results from each of the part uploads //May refactor this down into a special thrown Exception/wrapper class return $uDeferred->promise(); } private function sendPart($newFileId,$partNumber,$partSize,$partHash,$timeout,&$uDeferred,&$pendingParts,&$partsDeferred,$sizeSent,$size,$tries=0) { $tries++; $partUrlRequest = $this->client->request("post","/b2_get_upload_part_url",["fileId"=>$newFileId]); $partUrlRequest->async(true); //dhGlobal::outLine("($partNumber) sending to ",$partUrlRequest->getUrl()); $partUrlRequest->sendAsync(function($response) use(&$partsDeferred,$partUrlRequest,&$uDeferred,&$pendingParts,$timeout,$partNumber,$partSize,$partHash,$sizeSent,$size) { $this->handlePartUrlRequest($response,$partsDeferred,$partUrlRequest,$uDeferred,$pendingParts,$timeout,$partNumber,$partSize,$partHash,$sizeSent,$size); },function($e) use (&$partsDeferred,$partUrlRequest,&$uDeferred,&$pendingParts,$partNumber,$tries,$timeout,$partSize,$partHash,$sizeSent,$size,$newFileId) { if($tries<3) { $this->sendPart($newFileId,$partNumber,$partSize,$partHash,$timeout,$uDeferred,$pendingParts,$partsDeferred,$sizeSent,$size,$tries=0); } else { $this->_trace("upload - some kind of general error with $partNumber...",$partUrlRequest->getMethod()." Request to ".$partUrlRequest->getUrl()." failed with exception ".$e->getCode()." - ".$e->getMessage()); $pendingParts[$partNumber] = $partUrlRequest->getMethod()." Request to ".$partUrlRequest->getUrl()." failed with exception ".$e->getCode()." - ".$e->getMessage(); if($this->isPartArrayComplete($pendingParts)) { $partsDeferred->resolve(true); } } }); } private function handlePartUrlRequest($response,&$partsDeferred,$partUrlRequest,&$uDeferred,&$pendingParts,$timeout,$partNumber,$partSize,$partHash,$sizeSent,$size) { $body = $response->body(true); if(is_array($body)) { $this->_trace("upload - part $partNumber prepped"); //dhGlobal::outLine("got a response with $partNumber, proceeding to upload"); $uploadRequest = $this->client->request("post",$body["uploadUrl"]) ->authToken($body["authorizationToken"]) ->header("X-Bz-Part-Number",$partNumber) ->header("Content-Length",$partSize) ->header("X-Bz-Content-Sha1",$partHash) ->rawBody($this->partPieces[$partNumber]); $uploadRequest->async(true); //dhGlobal::outLine("($partNumber) sending to ",$uploadRequest->getUrl()); $uploadRequest->sendAsync(function($response) use (&$partsDeferred,&$pendingParts,$partNumber,&$uploadRequest) { $this->_trace("upload - part $partNumber done"); //dhGlobal::outLine("part $partNumber is complete"); $pendingParts[$partNumber] = true; unset($this->partPieces[$partNumber]); $uploadRequest=null; if($this->isPartArrayComplete($pendingParts)) { $this->partPieces = null; $partsDeferred->resolve(true); } },function($e) use (&$partsDeferred,$uploadRequest,&$pendingParts,$partNumber,&$uploadRequest) { $this->_trace("upload - part $partNumber failed"); //dhGlobal::outLine("part $partNumber failed"); $pendingParts[$partNumber] = $uploadRequest->getMethod()." Request to ".$uploadRequest->getUrl()." failed with exception ".$e->getCode()." - ".$e->getMessage(); unset($this->partPieces[$partNumber]); $uploadRequest=null; if($this->isPartArrayComplete($pendingParts)) { $partsDeferred->resolve(true); } },$timeout); } else { $this->_trace("upload - part $partNumber errored"); //dhGlobal::outLine("got an err response with $partNumber"); $pendingParts[$partNumber] = $response->body(); $this->partPieces = []; if($this->isPartArrayComplete($pendingParts)) { $partsDeferred->resolve(true); } } } private function isPartArrayComplete($partArray) { foreach($partArray as $pn=>$re) { if(is_null($re)) { return false; } } return true; } protected function handleMultiResponse() { } /** * Get the value of uploadNormalLimit * @return mixed */ public function getUploadNormalLimit() { return $this->uploadNormalLimit; } /** * Set the value of uploadNormalLimit * @param mixed $uploadNormalLimit * @return self */ public function setUploadNormalLimit($uploadNormalLimit) { $this->uploadNormalLimit = $uploadNormalLimit; return $this; } /** * Get the value of multiPartSize * @return mixed */ public function getMultiPartSize() { return $this->multiPartSize; } /** * Set the value of multiPartSize * @param mixed $multiPartSize * @return self */ public function setMultiPartSize($multiPartSize) { $this->multiPartSize = $multiPartSize; return $this; } }src/UploadBetter.php000064400000054225144761607150010461 0ustar00client = $clientOrOptions; } elseif(is_array($clientOrOptions)) { $this->client = Client::fromAuthArray($clientOrOptions); } if(is_null($this->client)) { throw new \Exception(__CLASS__." - failed constructor, client or auth array invalid"); } } private function cleanupOnDone() { if($this->stream instanceof ReadableResourceStream) { $this->stream->close(); } if(!is_null($this->rawStream) && is_resource($this->rawStream)) { fclose($this->rawStream); } $this->file = null; $this->stream = null; $this->rawStream = null; gc_collect_cycles(); } public function handleUploadComplete($bucketFile) { $this->finalized = true; $this->cleanupOnDone(); $bucketFile->set("sourceSha1",$this->fileHash); $this->_trace("resolved",__METHOD__,__LINE__); if(!is_null($this->multipartDeferred)) { $this->multipartDeferred->reject('finalized'); } if($bucketFile->sha1() == $this->fileHash && !empty($bucketFile->fileId())) { $this->uploadDeferred->resolve($bucketFile); } else { $this->uploadDeferred->reject("sha1 mismatch"); } } public function handleUploadFailed($reason) { $this->finalized = true; $this->cleanupOnDone(); $this->uploadDeferred->reject($reason); if(!is_null($this->multipartDeferred)) { $this->multipartDeferred->reject('finalized'); } } public function upload($options=[]) { $this->_trace("funcStart",__METHOD__,__LINE__); $this->uploadDeferred = new Deferred(); $this->bucketId = $this->getBucketIdFromOptions($options); $this->uploadNormalLimit = dhGlobal::getVal($options, "uploadNormalLimit", $this->uploadNormalLimit); $this->multiPartSize = dhGlobal::getVal($options, "multiPartSize", $this->multiPartSize); $this->numParts = dhGlobal::getVal($options, "numParts", $this->numParts); $this->timeout = dhGlobal::getVal($options, "timeout", true); $this->prefix = dhGlobal::getDot($options, "prefix", ""); $this->file = dhGlobal::getVal($options, "file", null); $this->fileName = dhGlobal::getVal($options, "fileName", false); $this->modifiedTimeMillies = dhGlobal::getVal($options, "modifiedTimeMillis", false); $this->modifiedTime = dhGlobal::getVal($options, "modifiedTime", false); $this->contentType = dhGlobal::getVal($options, "contentType", "b2/x-auto"); $this->content = dhGlobal::getVal($options, "content", null); if(($this->stream = $this->prepareUpload($options)) !== false && !is_null($this->stream)) { if($this->fileSize<=$this->uploadNormalLimit) { //uploadNormal; $this->_trace("fileSize = ",$this->fileSize,"..","using uploadNormal."); $this->uploadNormal(); } else { //uploadMulti; $this->_trace("fileSize = ",$this->fileSize,"..","using uploadMulti."); $this->uploadMulti(); } } else { $this->handleUploadFailed("poop"); } $this->_trace("funcEnd",__METHOD__,__LINE__); return $this->uploadDeferred->promise(); } private function uploadNormal() { $this->_trace("funcStart",__METHOD__,__LINE__); $this->getUploadUrlAuth()->then(function($authUrlResponse) { $request = $this->createNormalUploadRequest($authUrlResponse); $request->sendAsync(function($response) use ($request) { if(($json = $response->body(true)) && is_array($json)) { $this->handleUploadComplete(new BucketFile($json)); } else { $this->handleNormalUploadError($response,$request); } }, function($e) use ($request) { $this->_trace("rejected",__METHOD__,__LINE__); $this->handleUploadFailed($this->makeRejectMessageFromRequest($e,$request)); },$this->timeout); }); $this->_trace("funcEnd",__METHOD__,__LINE__); } private function uploadMulti() { $this->_trace("funcStart",__METHOD__,__LINE__); if(($this->largeFileId = $this->startLargeFile()) === false) { $this->handleUploadFailed("Unable to get largeFileId"); return; } $this->fileParts = $this->createMultiParts(); if(empty($this->fileParts)) { $this->handleUploadFailed("empty parts"); return; } $this->uploadMultiParts()->then(function() { $this->finishLargeFile(); },function($e) { $this->_trace("errorHandled",__METHOD__,__LINE__); }); } private function finishLargeFile() { $parts = []; foreach($this->fileParts as $i=>$uploadPart) { $parts[] = $uploadPart->hash(); $this->fileParts[$i] = null; unset($this->fileParts[$i]); } $request = $this->client->request("post","/b2_finish_large_file",["fileId"=>$this->largeFileId,"partSha1Array"=>$parts]); if(($response = $request->jsonParseSend()) !== false) { $this->handleUploadComplete(new BucketFile($response)); } else { $this->handleUploadFailed("failed to finish large file"); } } private function uploadMultiParts() { $this->_trace("funcStart",__METHOD__,__LINE__); $this->multipartDeferred = new Deferred(); foreach($this->fileParts as $uploadPart) { $this->sendMultiPart($uploadPart); } Loop::addPeriodicTimer(0.1,function($timer) { foreach($this->fileParts as $i=>$uploadPart) { if(!$uploadPart->done()) { return; } $uploadPart->set("content",""); } Loop::cancelTimer($timer); $this->multipartDeferred->resolve(true); $this->_trace("funcEnd",__METHOD__,__LINE__); }); return $this->multipartDeferred->promise(); } private function sendMultiPart(UploadPart $uploadPart) { $this->_trace("funcStart",__METHOD__,__LINE__); $uploadPart->incAttempts(); $uploadPart->started(true); $uploadPart->done(false); $this->getUploadPartUrl(function($uploadInfo) use ($uploadPart) { $this->_trace("funcEnd",__METHOD__,__LINE__); $this->uploadMultiPartPart($uploadInfo,$uploadPart); },function($error,$request) use($uploadPart) { $this->_trace("funcEnd",__METHOD__,__LINE__); if(!$this->finalized) { if($uploadPart->attempts()<3) { $this->sendMultiPart($uploadPart); } else { //failed! $this->handleUploadFailed("failed 3 times on sendMultiPart(".$uploadPart->number().")"); } } }); } private function uploadMultiPartPart($uploadInfo,UploadPart $uploadPart) { $this->_trace("funcStart",__METHOD__,__LINE__); $request = $this->client->request("post",$uploadInfo["uploadUrl"]) ->authToken($uploadInfo["authorizationToken"]) ->header("X-Bz-Part-Number",$uploadPart->number()) ->header("Content-Length",$uploadPart->size()) ->header("X-Bz-Content-Sha1",$uploadPart->hash()); $request->rawBody($uploadPart->content()); $request->async(true); $request->sendAsync(function($response) use ($uploadPart) { $uploadPart->done(true); $uploadPart->started(false); $uploadPart->content(" "); $this->_trace("funcEnd",__METHOD__,__LINE__); },function($error) use ($uploadPart) { $this->_trace("funcEnd",__METHOD__,__LINE__); if(!$this->finalized) { if($uploadPart->attempts()<3) { $this->sendMultiPart($uploadPart); } else { //failed! $this->handleUploadFailed("failed 3 times on uploadMultiPartPart(".$uploadPart->number().")"); } } },$this->timeout); } private function createMultiParts() { $this->_trace("funcStart",__METHOD__,__LINE__); $totalParts = ceil($this->fileSize/$this->multiPartSize); $sizeUsed=0; $i=0; $fileParts = []; while($this->fileSize > $sizeUsed) { $partNumber = $i+1; $sizeUsed = $i * $this->multiPartSize; $remainingSize = $this->fileSize - $sizeUsed; $partSize = $remainingSize > $this->multiPartSize ? $this->multiPartSize : $remainingSize; $partInfo = dhGlobal::getfileHashAndSize($this->rawStream,$sizeUsed,$partSize); $fileParts[$i] = new UploadPart($partNumber,$partSize,$partInfo["hash"],$sizeUsed); $fileParts[$i]->content(stream_get_contents($this->rawStream,$partSize,$sizeUsed)); $i++; $sizeUsed+=$partSize; } $this->_trace("funcEnd",__METHOD__,__LINE__); return $fileParts; } private function handleNormalUploadError($response,$request) { $this->_trace("funcStart",__METHOD__,__LINE__); if(is_object($response)) { if($response->getStatusCode() == 401 && $response->getReasonPhrase() == "unauthorized") { $this->_trace("rejected",__METHOD__,__LINE__); $this->handleUploadFailed("401-unauthorized returned from BackBlaze. Account credentials are not valid to perform this action"); } elseif($response->getStatusCode() == 401 || $response->getStatusCode() == 400) { //retry with backoff if(!$this->retriesWithBackoff($this->retryCounts["normal"],function() { $this->uploadNormal(); })) { $this->_trace("rejected",__METHOD__,__LINE__); $this->handleUploadFailed("upload re-try limit (".$this->maxRetries.") hit"); } return; } else { //request failed.. $this->_trace("rejected",__METHOD__,__LINE__); $this->handleUploadFailed($this->makeRejectMessageFromRequest($response,$request)); } } else { $this->_trace("rejected",__METHOD__,__LINE__); $this->handleUploadFailed($this->makeRejectMessageFromRequest($response,$request)); } } private function retriesWithBackoff(&$retries=0,$callable) { $this->_trace("funcStart",__METHOD__,__LINE__); if($retries>=$this->maxRetries) { return false; } else { $retries++; $delay = $this->retryDelay * (pow($this->retryBackoffPercent,$retries)); usleep($delay); $callable(); return true; } } private function prepareUpload() { $this->_trace("funcStart",__METHOD__,__LINE__); $stream = false; $this->prefix = dhGlobal::trimString("/",$this->prefix,dhGlobal::TRIM_BOTH); if(!is_null($this->file) && $this->file instanceof File) { if(is_null($this->fileName)) { $this->fileName = dhGlobal::ltrimString("/",$this->prefix.$this->file->path()); } $this->modifiedTimeMillis = $this->file->mtime("millis"); $this->fileSize = $this->file->size(); $this->fileHash = $this->file->sha1(); $this->contentType = $this->file->mimeType(); $this->rawStream = fopen($this->file->path(),"r"); $stream = new ReadableResourceStream($this->rawStream); } elseif(is_resource($this->file) && ($fstat = fstat($this->file)) !== false) { $info = dhGlobal::getFileHashAndSize($this->file); $this->fileSize = $info["size"]; $this->fileHash = $info["hash"]; $this->modifiedTime = $fstat["mtime"]; if(($mimeType = mime_content_type($this->file)) !== false) { $this->contentType = $mimeType; } $this->rawStream = &$this->file; $stream = new ReadableResourceStream($this->file); } else { $stream = &$this->content; $fileInfo = dhGlobal::getFileHashAndSize($stream); $this->fileSize = $fileInfo["size"]; $this->fileHash = $fileInfo["hash"]; } if(!is_null($this->numParts) && $this->numParts>0) { $maxParts = max(5,floor(($this->fileSize/$this->uploadNormalLimit) * $this->numParts)); $this->multiPartSize = min(max(5000000,floor($this->fileSize/$maxParts)),$this->uploadNormalLimit); } $this->modifiedTimeOverride(); $this->_trace("funcEnd",__METHOD__,__LINE__); return $stream; } public function startLargeFile() { $this->_trace("funcStart",__METHOD__,__LINE__); $params = [ 'bucketId' => $this->bucketId, 'fileName' => $this->fileName, 'contentType' => $this->contentType, 'fileInfo' => [ "large_file_sha1" => $this->fileHash, "src_last_modified_millis"=> (string) $this->modifiedTimeMillis ] ]; if(!empty($this->meta)) { foreach($this->meta as $k=>$v) { $params["fileInfo"][$k] = $v; } } $request = $this->client->request("post","/b2_start_large_file",$params); if(($response = $request->jsonParseSend()) !== false && isset($response["fileId"])) { unset($request); return $response['fileId']; } else { unset($request); unset($response); return false; } } private function getUploadPartUrl($onSuccess,$onError) { $partUrlRequest = $this->client->request("post","/b2_get_upload_part_url",["fileId"=>$this->largeFileId]); $partUrlRequest->async(true); $partUrlRequest->sendAsync(function($response) use($onSuccess,$onError,$partUrlRequest) { $body = $response->body(true); if(is_array($body)) { $onSuccess($body); } else { $onError($response->body,$partUrlRequest); } },function($e) use ($onError,$partUrlRequest) { $onError($e,$partUrlRequest); }); } /** * * @param string|Bucket $bucket * @param bool $cache * @return \React\Promise\Promise * @throws ClientException * @throws ClientException */ private function getUploadUrlAuth($cache=false) { $this->_trace("funcStart",__METHOD__,__LINE__); $deferred = new Deferred(); if($cache && ($response = $this->client->getUploadAuth()) !== false) { $deferred->resolve($response); } else { $params = ["bucketId"=>$this->bucketId]; $request = $this->client->request("post","/b2_get_upload_url",$params); $request->sendAsync(function($response) use ($deferred,$cache) { $json = $response->body(true); if(is_array($json)) { $this->client->accountSet("uploadAuth",$json,$cache); } $this->_trace("resolved",__METHOD__,__LINE__); $deferred->resolve($json); },function($e) use ($deferred,$cache) { $this->_trace("rejected",__METHOD__,__LINE__); $deferred->reject($e); }); } $this->_trace("funcEnd",__METHOD__,__LINE__); return $deferred->promise(); } private function getBucketIdFromOptions($options=[],$exception=true) { $this->_trace("funcStart",__METHOD__,__LINE__); $bucketId = false; if(($bucketId = dhGlobal::getDot($options,"bucketId",false))!==false) { } elseif(($bucket = $this->client->buckets()->fromOptions($options)) !== false) { $bucketId = $bucket->get("bucketId"); } else { if($exception) { throw new ClientException(__METHOD__,"No bucket option provided (bucket, bucketId, bucketName)"); } } $this->_trace("funcEnd",__METHOD__,__LINE__); return $bucketId; } private function modifiedTimeOverride() { $this->_trace("funcStart",__METHOD__,__LINE__); if(!is_null($this->modifiedTime) && is_null($this->modifiedTimeMillis)) { if(is_object($this->modifiedTime)) { $this->modifiedTimeMillis = $this->modifiedTime->format("U")*1000; } elseif(is_numeric($this->modifiedTime)) { $this->modifiedTimeMillis = $this->modifiedTime*1000; } else { try { $mt = new \DateTime($this->modifiedTime); $this->modifiedTimeMillis = $mt->format("U")*1000; } catch (\Exception $e) { } } } if(is_null($this->modifiedTimeMillis)) { $this->modifiedTimeMillis = floor(microtime(true) * 1000); } $this->_trace("funcEnd",__METHOD__,__LINE__); } private function makeRejectMessageFromRequest($response,$request) { $this->_trace("funcStart",__METHOD__,__LINE__); if(is_object($response) && method_exists($response,"getMessage")) { return $request->getMethod()." Request to ".$request->getUrl()." failed with exception ".$response->getCode()." - ".$response->getMessage(); } else { if(is_array($response)) { $response = json_encode($response); } return $request->getMethod()." Request to ".$request->getUrl()." failed with non-object: ".$response; } } private function createNormalUploadRequest($authUrlResponse) { $this->_trace("funcStart",__METHOD__,__LINE__); $uploadUrl = $authUrlResponse["uploadUrl"]; $authToken = $authUrlResponse["authorizationToken"]; $request = $this->client->request("post",$uploadUrl) ->authToken($authToken) ->header("Content-Type",$this->contentType) ->header("Content-Length",$this->fileSize) ->header("X-Bz-File-Name",$this->fileName) ->header("X-Bz-Content-Sha1",$this->fileHash) ->header("X-Bz-Info-src_last_modified_millis",$this->modifiedTimeMillis) ->rawBody($this->stream) ->async(true); if(!empty($this->meta)) { foreach($this->meta as $k=>$v) { $request->header("X-Bz-Info-".$k,$v); } } //print_r($request); $this->_trace("funcEnd",__METHOD__,__LINE__); return $request; } private $lastMemUsed = 0; protected function _trace(...$args) { $diff = memory_get_usage() - $this->lastMemUsed; $this->lastMemUsed = memory_get_usage(); if($this->debugDoTrace) { array_unshift($args,"[MemUsed:".dhGlobal::formatBytes(memory_get_usage())." / Diff:".dhGlobal::formatBytes($diff)."] "); array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-t"); return call_user_func_array([$this,"log"],$args); } return; } protected function _debug(...$args) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-d"); return call_user_func_array([$this,"log"],$args); } protected function _info(...$args) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-i"); return call_user_func_array([$this,"log"],$args); } protected function _warn(...$args) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-w"); return call_user_func_array([$this,"log"],$args); } protected function _error(...$args) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-e"); return call_user_func_array([$this,"log"],$args); } public function log(...$args) { return dhGlobal::log(...$args); } }src/UploadTool.php000064400000005063144761607150010145 0ustar00client = new Client($keyId,$applicationKey,$accountInfo); $this->fileList = dhGlobal::getDot($options,"fileList",[]); $dir = dhGlobal::getDot($options,"dir",false); if($dir !== false) { $this->initDir($dir); } } public function initDir($initDir,$seperator=DIRECTORY_SEPARATOR) { $directories = []; if(is_array($initDir)) { $directories = $initDir; } else { $directories = [$initDir]; } foreach($directories as $directory) { $this->directories[] = new Directory($directory,true,true); } return $this->fileList; } protected function _trace(...$args) { if($this->debugDoTrace) { array_unshift($args,"UploadTool -"); array_unshift($args,"blaze-t"); return call_user_func_array([$this,"log"],$args); } return; } protected function _debug(...$args) { array_unshift($args,"UploadTool -"); array_unshift($args,"blaze-d"); return $this->log($args); } protected function _info(...$args) { array_unshift($args,"UploadTool -"); array_unshift($args,"blaze-i"); return call_user_func_array([$this,"log"],$args); } protected function _warn(...$args) { array_unshift($args,"UploadTool -"); array_unshift($args,"blaze-w"); return call_user_func_array([$this,"log"],$args); } protected function _error(...$args) { array_unshift($args,"UploadTool -"); array_unshift($args,"blaze-e"); return call_user_func_array([$this,"log"],$args); } public function log(...$args) { return dhGlobal::log(...$args); } }src/async/AsyncWorker.php000064400000026507144761607150011455 0ustar00static::$cacheFile,"cacheRemoveOnClose"=>true]); return ["success"=>true,"method"=>"init"]; } public static function getTimeoutByFilesize($fileSize) { if(!is_null($fileSize)) { if($fileSize<500000) { //500k $timeOutSeconds = 30; } elseif($fileSize<5000000) { //5mb $timeOutSeconds = 60; } elseif($fileSize<10000000) { //10mb $timeOutSeconds = 120; } else { $timeOutSeconds = 300; } return $timeOutSeconds; } return false; } public static function verifyUpload($config=[],$options=[]) { if(!is_array($config) || !is_array($options)) { throw new \Exception(__CLASS__."::verifyUpload - requires 2 args, arg1 is the config array, arg2 is the verifyInfo array",1); } if(($sourceFile = dhGlobal::getval($options,"sourceFile",false)) === false) { throw new \Exception(__CLASS__."::upload - missing required option: sourceFile",2); } if(($destFile = dhGlobal::getval($options,"destFile",false)) === false) { if(($fileId = dhGlobal::getVal($options,"fileId",false)) === false) { throw new \Exception(__CLASS__."::upload - missing required option: destFile or fileId",2); } else { } } if(($file = dhGlobal::fileIfExists($sourceFile)) === false) { throw new \Exception(__CLASS__."::upload - sourceFile not a valid file",3); } $timeout = dhGlobal::getVal($options,"timeout",true); if(is_null(static::$client)) { static::init($config); } $customId = dhGlobal::getVal($options,"customId",null); if($destFile !== false) { $destFileOrId = $destFile; /** @var \boru\dhutils\http\Response */ $response = static::$client->files()->downloadByName($destFile,true); } else { $destFileOrId = $fileId; /** @var \boru\dhutils\http\Response */ $response = static::$client->files()->downloadById($fileId,true); } $destSha1 = $response->header("X-Bz-Content-Sha1")[0]; if($destSha1 == "none" && is_array($response->header("x-bz-info-large_file_sha1"))) { $destSha1 = $response->header("x-bz-info-large_file_sha1")[0]; } //x-bz-info-large_file_sha1 $sourceSha1 = $file->sha1(); $uploadDeferred = new Deferred(); $uploadDeferred->resolve(AsyncWorker::verifyResponse($customId,$sourceFile,$destFileOrId,$sourceSha1,$destSha1)); return $uploadDeferred->promise(); } public static function upload($config=[],$options=[]) { if(!is_array($config) || !is_array($options)) { throw new \Exception(__CLASS__."::upload - requires 2 args, arg1 is the config array, arg2 is the uploadInfo array",1); } if(($bucketId = dhGlobal::getVal($options,"bucketId",false)) === false) { throw new \Exception(__CLASS__."::upload - missing required option: bucketId",2); } if(($sourceFile = dhGlobal::getval($options,"sourceFile",false)) === false) { throw new \Exception(__CLASS__."::upload - missing required option: sourceFile",2); } if(($destFile = dhGlobal::getval($options,"destFile",false)) === false) { throw new \Exception(__CLASS__."::upload - missing required option: destFile",2); } if(($file = dhGlobal::fileIfExists($sourceFile)) === false) { throw new \Exception(__CLASS__."::upload - sourceFile not a valid file",3); } $timeout = dhGlobal::getVal($options,"timeout",true); if(is_null(static::$client)) { static::init($config); } static::$client->upload()->debugDoTrace=dhGlobal::getval($options,"trace",false); $uploadNormalLimit = dhGlobal::getVal($options,"uploadNormalLimit",false); $multiPartSize = dhGlobal::getVal($options,"multiPartSize",false); //if set, we take the (filesize / uploadNormalLimit) * $partsPerSize as the number of parts, and build //the multiPartSize off of that. if(($partsPerSize = dhGlobal::getVal($options,"partsPerSize",false)) !== false) { $maxParts = floor(($file->size()/$uploadNormalLimit) * $partsPerSize); $multiPartSize = min(max(5000000,floor($file->size()/$maxParts)),$uploadNormalLimit); } $customId = dhGlobal::getVal($options,"customId",null); $uploadOptions = []; $uploadOptions["bucketId"] = $bucketId; $uploadOptions["file"] = $file; $uploadOptions["fileName"] = $destFile; $uploadOptions["async"] = true; $uploadOptions["timeout"] = $timeout; if($uploadNormalLimit !== false) { $uploadOptions["uploadNormalLimit"] = $uploadNormalLimit; } if($multiPartSize !== false) { $uploadOptions["multiPartSize"] = $multiPartSize; } $promise = static::$client->upload()->upload($uploadOptions); $uploadDeferred = new Deferred(); $promise->then(function($bucketFileResponse) use (&$uploadDeferred,$sourceFile,$destFile,$customId,&$file) { $result = AsyncWorker::uploadResponse($customId,$sourceFile,$destFile,$bucketFileResponse,null,$file); $file=null; $uploadDeferred->resolve($result); return $result; },function($e) use (&$uploadDeferred,$sourceFile,$destFile,$customId,&$file) { $result = AsyncWorker::uploadResponse($customId,$sourceFile,$destFile,null,$e,$file); $file=null; $uploadDeferred->resolve($result); }); $file=null; return $uploadDeferred->promise(); } public static function getClassCallable() { return __CLASS__; } private static function verifyResponse($customId,$sourceFile,$destFileOrId,$sourceSha1,$destSha1) { $resultOutput = [ "customId"=>$customId, "success"=>false, "source"=>$sourceFile, "dest"=>$destFileOrId, "sourceSha1"=>$sourceSha1, "destSha1"=>$destSha1, "errorCode"=>false, "error"=>null, ]; try { $compare = static::compareSha1($sourceSha1,$destSha1); $resultOutput["success"]=true; } catch (UploadException $e) { $resultOutput["success"]=false; $resultOutput["errorCode"] = $e->getType(); $resultOutput["error"] = $e->getMessage(); } return $resultOutput; } private static function uploadResponse($customId,$sourceFilePath,$destPath,$bucketFileResponse=false,$errorResponse=false,&$sourceFile=null) { $resultOutput = [ "customId"=>$customId, "success"=>false, "source"=>$sourceFilePath, "dest"=>$destPath, "errorCode"=>false, "error"=>null, "isBucketFile"=>false, "uploadResponse"=>null, ]; if(is_object($errorResponse) && method_exists($errorResponse,"getMessage")) { $resultOutput["error"] = $errorResponse->getMessage(); } elseif(!is_object($errorResponse)) { $resultOutput["error"] = $errorResponse; } else { $resultOutput["error"] = "unknown"; } if($bucketFileResponse instanceof BucketFile) { $resultOutput["isBucketFile"] = true; $resultOutput["uploadResponse"] = $bucketFileResponse->get(); if(!is_null($bucketFileResponse->fileId()) && !empty($bucketFileResponse->fileId())) { $resultOutput["success"]=true; } else { if($bucketFileResponse->get("sourceSha1",false) !== false) { try { $compare = static::compareSha1($bucketFileResponse->get("sourceSha1",false),$bucketFileResponse->sha1()); $resultOutput["success"]=false; } catch (UploadException $e) { $resultOutput["success"]=false; $resultOutput["errorCode"] = $e->getType(); $resultOutput["error"] = $e->getMessage(); } } else { if(is_null($sourceFile)) { $sourceFile = new File(["path"=>$sourceFilePath]); } try { $compare = static::compareSourceToBucketFile($sourceFile,$bucketFileResponse); $resultOutput["success"]=false; } catch (UploadException $e) { $resultOutput["success"]=false; $resultOutput["errorCode"] = $e->getType(); $resultOutput["error"] = $e->getMessage(); } $sourceFile=null; } } }elseif($bucketFileResponse === false || is_null($bucketFileResponse)) { $resultOutput["success"] = false; $resultOutput["isBucketFile"] = false; $resultOutput["uploadResponse"] = false; $resultOutput["errorCode"] = Client::ERR_BLANK_RESPONSE; } else { $resultOutput["success"] = false; $resultOutput["isBucketFile"] = false; $resultOutput["uploadResponse"] = $bucketFileResponse; $resultOutput["errorCode"] = Client::ERR_NOT_BUCKETFILE; } $sourceFile=null; static::$client= null; return $resultOutput; } private static function compareSourceToBucketFile(File $sourceFile,BucketFile $bucketFile) { return static::compareSha1($sourceFile->sha1(),$bucketFile->sha1()); } private static function compareSha1($sourceSha1,$destSha1) { if(is_null($sourceSha1) || is_null($destSha1) || empty($sourceSha1) || empty($destSha1)) { throw new UploadException(Client::ERR_UPLOAD_SHA1_MISMATCH,"Destination sha1 of $destSha1 != Source sha1 of $sourceSha1"); } if($destSha1 != $sourceSha1) { throw new UploadException(Client::ERR_UPLOAD_SHA1_MISMATCH,"Destination sha1 of $destSha1 != Source sha1 of $sourceSha1"); } return true; } }src/browser/File.php000064400000006306144761607150010426 0ustar00browser = $browser; } $this->bucketFile = $bucketFile; } public function download($headersOnly=false,$async=false) { if(is_null($this->browser)) { throw new \Exception("Cannot get download File without a browser attached"); } return $this->browser->downloadByFileId($this->bucketFile->fileId()); } public function get($dotKey=null,$default=null) { return $this->bucketFile->get($dotKey,$default); } public function modified($format=null) { return $this->bucketFile->modified($format); } public function uploaded($format=null) { return $this->bucketFile->uploaded($format); } public function name() { return $this->bucketFile->name(); } public function path() { return $this->bucketFile->path(); } public function fileName() { return $this->bucketFile->fileName(); } public function fileId() { return $this->bucketFile->fileId(); } public function fileInfo() { return $this->bucketFile->fileInfo(); } public function sha1() { return $this->bucketFile->sha1(); } public function contentType() { return $this->bucketFile->contentType(); } public function action() { return $this->bucketFile->action(); } public function size() { return $this->bucketFile->size(); } public function accountId() { return $this->bucketFile->accountId(); } public function bucketId() { return $this->bucketFile->bucketId(); } public function fileRetention() { return $this->bucketFile->fileRetention(); } /** * @param int $limit * @return File[] */ public function versions($limit=100) { return $this->browser->getFileVersionsByName($this->fileName($limit)); } /** * Set the value of browser * @param Browser $browser * @return self */ public function setBrowser(Browser $browser) { $this->browser = $browser; return $this; } /** * Get the value of bucketFile * @return mixed */ public function getBucketFile() { return $this->bucketFile; } /** * Set the value of bucketFile * @param mixed $bucketFile * @return self */ public function setBucketFile($bucketFile) { $this->bucketFile = $bucketFile; return $this; } public static function fromFileName($fileName,Browser $browser) { return new self($browser->client()->files()->getByName($browser->getBucketId(),$fileName,false),$browser); } }src/browser/Folder.php000064400000006646144761607150010771 0ustar00browser = $browser; } if(is_object($bucketFileOrPathString) && method_exists($bucketFileOrPathString,"path")) { $this->setBucketFile($bucketFileOrPathString); } elseif(is_object($bucketFileOrPathString) || is_array($bucketFileOrPathString)) { throw new \Exception(__CLASS__." requires either a BucketFile or a path string as a constructor"); } else { $this->setPath($bucketFileOrPathString); } } public function path() { return $this->getPath(); } public function name() { return $this->getName(); } public function bucketId() { return $this->getBucketId(); } public function contents($useCache=true) { if(is_null($this->browser)) { throw new \Exception("Cannot get Folder contents without a browser attached"); } if(!is_null($this->listing) && $useCache) { return $this->listing; } $listing = $this->browser->contents($this->path); if($useCache) { $this->listing = $listing; } return $listing; } /** * Set the value of browser * @param Browser $browser * @return self */ public function setBrowser(Browser $browser) { $this->browser = $browser; return $this; } /** * Set the value of bucketFile * @param mixed $bucketFile * @return self */ public function setBucketFile($bucketFile) { $this->bucketFile = $bucketFile; $this->setPath($this->bucketFile->path()); $this->setName($this->bucketFile->name()); $this->setBucketId($this->bucketFile->get("bucketId")); return $this; } /** * Get the value of path * @return mixed */ public function getPath() { return $this->path; } /** * Set the value of path * @param mixed $path * @return self */ public function setPath($path) { $this->path = $path; return $this; } /** * Get the value of name * @return mixed */ public function getName() { return $this->name; } /** * Set the value of name * @param mixed $name * @return self */ public function setName($name) { $this->name = $name; return $this; } /** * Get the value of bucketId * @return mixed */ public function getBucketId() { return $this->bucketId; } /** * Set the value of bucketId * @param mixed $bucketId * @return self */ public function setBucketId($bucketId) { $this->bucketId = $bucketId; return $this; } }src/browser/Listing.php000064400000004212144761607150011152 0ustar00files = is_array($files) ? $files : []; $this->folders = is_array($folders) ? $folders : []; $this->fileCount = count($this->files); $this->folderCount = count($this->folders); } public function isEmpty() { if($this->fileCount>0 || $this->folderCount>0) { return false; } return true; } /** @return File[] */ public function files() { return $this->files; } /** @return Folder[] */ public function folders() { return $this->folders; } /** @return int */ public function fileCount() { return $this->fileCount; } /** @return int */ public function folderCount() { return $this->folderCount; } /** * @param mixed $name * @return File|false */ public function file($name) { return isset($this->files[$name]) ? $this->files[$name] : false; } /** * @param mixed $name * @return Folder|false */ public function folder($name) { return isset($this->folders[$name]) ? $this->folders[$name] : false; } /** * * @param BucketFile[] $array * @param Browser|null $browser * @return self */ public static function fromBucketFiles($array=[],$browser=null) { $files = []; $folders = []; foreach($array as $bucketFile) { $obj = Browser::fileOrFolder($bucketFile,$browser); if($bucketFile->get("action") == "folder") { $folders[$bucketFile->name()] = $obj; } else { $files[$bucketFile->name()] = $obj; } } return new self($files,$folders); } }src/exceptions/BaseException.php000064400000000635144761607150012775 0ustar00debug_name."-e",$method." ".$code." ".print_r($message,true)); } parent::__construct($message,$code); } }src/exceptions/ClientException.php000064400000000204144761607150013331 0ustar00account = new \boru\backblaze\Account($keyId,$applicationKey,$options); if(!$this->account->checkAuthorized()) { $this->authorize(); } } public function apiUrl($uri) { if(substr($uri,0,4) != "http") { if(substr($uri,0,1) != "/") { $uri = "/".$uri; } $url = $this->account->get("apiUrl",$this->apiUrl); $url .= $this->apiVersion.$uri; } else { $url = $uri; } return $url; } public function authorize() { $this->_trace("funcStart",__METHOD__); $request = $this->request("get","/b2_authorize_account"); $response = $this->asJson($request->send()); if(is_array($response)) { $this->account->setAccountInfo($response); $this->account->checkAuthorized(true); } else { dhGlobal::error($response->body()); $this->_trace("funcEnd",__METHOD__); return false; } $this->_trace("authorize successful"); $this->_trace("funcEnd",__METHOD__); return true; } public function asJson($response) { if($response instanceof \boru\dhutils\http\Response) { return $response->body(true); } return $response; } /** * * @param mixed $method * @param mixed $url * @param mixed $data * @param bool $auth * @param mixed $onSuccess * @param mixed $onError * @return Request */ public function request($method,$url,$data=null,$auth=true,$onSuccess=null,$onError=null) { $url = $this->apiUrl($url); if($auth) { if(($authToken = $this->account->get("authorizationToken",null)) !== null) { $auth = $authToken; } else { $auth = [$this->account->getKeyId(),$this->account->getApplicationKey()]; } } else { $auth = null; } return new Request($method,$url,$data,$auth,$onSuccess,$onError); } /** * Get the value of account * * @return \boru\backblaze\Account */ public function getAccount() { return $this->account; } public function accountSet($dotKey,$value=null,$writeCache=false) { $this->account->set($dotKey,$value); if($writeCache) { $this->account->writeCache(); } return $this; } public function accountGet($item=null,$default=false) { return $this->account->get($item,$default); } }src/lib/Request.php000064400000015001144761607150010252 0ustar00setMethod($method); } if(!is_null($url)) { $this->setUrl($url); } if(!is_null($auth)) { if(is_array($auth)) { $this->authBasic($auth); } else { $this->authToken($auth); } } if(!is_null($data)) { $this->json($data); } $this->onSuccess = function(\boru\dhutils\http\Response $response) { return $response; }; if(!is_null($onSuccess)) { $this->async = true; $this->onSuccess = $onSuccess; } $this->onError = function($e) { return $e; }; if(!is_null($onError)) { $this->onError = $onError; } } public function async($async=true) { $this->async = $async; return $this; } public function rawBody($body) { $this->bodyRaw = $body; return $this; } public function json($body=[]) { $this->bodyJson = $body; return $this; } public function form($body=[]) { $this->bodyForm = $body; return $this; } public function query($body=[]) { $this->bodyQuery = $body; return $this; } public function header($key,$value=null) { $this->headers[$key]=$value; return $this; } public function send() { if(!empty($this->bodyQuery)) { if(strpos($this->url,"?") === false) { $this->url.="?"; } else { $this->url.="&"; } $this->url .= http_build_query($this->bodyQuery); } $client = new \boru\dhutils\dhHttp(); $req = $client->request($this->method,$this->url); if(!empty($this->bodyForm)) { $req->form($this->bodyForm); } if(!empty($this->bodyJson)) { $req->json($this->bodyJson); } if(!empty($this->bodyRaw)) { $req->body($this->bodyRaw); } if(!empty($this->headers)) { foreach($this->headers as $key=>$value) { $req->header($key,$value); } } return $req->send(); } /** * * @param callable|null $onSuccess * @param callabel|null $onError * @return \React\Promise\ExtendedPromiseInterface * @throws InvalidArgumentException * @throws Exception * @throws Throwable * @throws UnexpectedValueException */ public function sendAsync($onSuccess=null,$onError=null,$timeout=true) { if(is_null($onSuccess)) { $onSuccess = $this->onSuccess; } if(is_null($onError)) { $onError = $this->onError; } if(!empty($this->bodyQuery)) { if(strpos($this->url,"?") === false) { $this->url.="?"; } else { $this->url.="&"; } $this->url .= http_build_query($this->bodyQuery); } $options = ["success"=>$onSuccess,"error"=>$onError,"async"=>$this->async,"timeout"=>$timeout]; $client = \boru\dhutils\dAsyncHttp::client($options); if(!empty($this->bodyForm)) { $client->form($this->bodyForm); } if(!empty($this->bodyJson)) { $client->json($this->bodyJson); } if(!empty($this->bodyRaw)) { $client->raw($this->bodyRaw); } if(!empty($this->headers)) { foreach($this->headers as $key=>$value) { $client->header($key,$value); } } $client->url($this->url); return $client->send($this->method); } public function jsonParseSend($async=false) { $response = $this->send(); if($response instanceof Response) { $json = $response->body(true); if(is_array($json)) { return $json; } } else { if(method_exists($response,"getMessage")) { echo $response->getMessage()."\n"; } } return false; } public function authBasic($userPass=[]) { $this->headers["Authorization"] = "Basic ".base64_encode(implode(":",$userPass)); return $this; } public function authToken($token) { $this->headers["Authorization"] = $token; return $this; } /** * Get the value of method * * @return mixed */ public function getMethod() { return $this->method; } /** * Set the value of method * * @param mixed $method * @return self */ public function setMethod($method) { $this->method = $method; return $this; } /** * Get the value of url * * @return mixed */ public function getUrl() { return $this->url; } /** * Set the value of url * * @param mixed $url * @return self */ public function setUrl($url) { $this->url = $url; return $this; } /** * Get the value of onSuccess * * @return mixed */ public function getOnSuccess() { return $this->onSuccess; } /** * Set the value of onSuccess * * @param mixed $onSuccess * @return self */ public function setOnSuccess($onSuccess) { $this->onSuccess = $onSuccess; return $this; } /** * Get the value of onError * * @return mixed */ public function getOnError() { return $this->onError; } /** * Set the value of onError * * @param mixed $onError * @return self */ public function setOnError($onError) { $this->onError = $onError; return $this; } }src/parts/Bucket.php000064400000001024144761607150010422 0ustar00"", "bucketName"=>"", "bucketType"=>"", "bucketInfo"=>"", "corsRules"=>"", "fileLockConfiguration"=>"", "defaultServerSideEncryption"=>"", "lifecycleRules"=>"", "revision"=>"", "options"=>"", ]; }src/parts/BucketFile.php000064400000005347144761607150011236 0ustar00"", "action"=>"", "bucketId"=>"", "contentLength"=>"", "contentMd5"=>"", "contentSha1"=>"", "contentType"=>"", "fileId"=>"", "fileInfo"=>"", "fileName"=>"", "fileRetention"=>"", "legalHold"=>"", "serverSideEncryption"=>"", "uploadTimestamp"=>"", "name"=>"", "path"=>"", "isError"=>false, "errorMessage"=>"", ]; public function __construct($arr=null,$separator=".") { parent::__construct($arr,$separator); $fileName = $this->get("fileName",null); if(!is_null($fileName)) { $parts = explode("/",$fileName); $name = array_pop($parts); if(empty($name)) { $name = array_pop($parts)."/"; } $path = implode("/",$parts)."/"; $this->set("name",$name); $this->set("path",$path); } } public function modified($format=null) { return $this->fromMillisToFormat($this->get("fileInfo.src_last_modified_millis",false),$format); } public function uploaded($format=null) { return $this->fromMillisToFormat($this->get("uploadTimestamp",false),$format); } public function name() { return $this->get("name",null); } public function path() { return $this->get("path",null); } public function fileName() { return $this->get("fileName",null); } public function fileId() { return $this->get("fileId",null); } public function fileInfo() { return $this->get("fileInfo",null); } public function sha1() { $sha1 = $this->get("contentSha1",null); return $sha1 !== "none" ? $sha1 : $this->get("fileInfo.large_file_sha1", null); } public function contentType() { return $this->get("contentType",null); } public function action() { return $this->get("action",null); } public function size() { return $this->get("contentLength"); } public function accountId() { return $this->get("accountId",null); } public function bucketId() { return $this->get("bucketId",null); } public function fileRetention() { return $this->get("fileRetention",null); } private function fromMillisToFormat($millis,$format=null) { if($millis !== false) { $timestamp = $millis/1000; $dt = new \DateTime(date("Y-m-d H:i:s",$timestamp)); if(is_null($format)) { return $dt; } return $dt->format($format); } return false; } }src/parts/UploadException.php000064400000000546144761607150012320 0ustar00type = $type; parent::__construct($message, $code, $previous); } public function getType() { return $this->type; } }src/parts/UploadPart.php000064400000007632144761607150011273 0ustar00null, "size"=>0, "hash"=>"", "done"=>false, "started"=>false, "offset"=>0, "attempts"=>0, "content"=>"", ]; public function __construct($number,$size,$hash,$offset=0) { $this->number($number); $this->size($size); $this->hash($hash); $this->offset($offset); } /** * Set or Get the value of number * @param mixed $number * @param mixed $default * @return mixed */ public function number($number=null,$default=null) { if(is_null($number)) return $this->get("number",$default); $this->set("number",$number); return $this; } /** * Set or Get the value of size * @param mixed $size * @param mixed $default * @return mixed */ public function size($size=null,$default=null) { if(is_null($size)) return $this->get("size",$default); $this->set("size",$size); return $this; } /** * Set or Get the value of hash * @param mixed $hash * @param mixed $default * @return mixed */ public function hash($hash=null,$default=null) { if(is_null($hash)) return $this->get("hash",$default); $this->set("hash",$hash); return $this; } /** * Set or Get the value of done * @param mixed $done * @param mixed $default * @return mixed */ public function done($done=null,$default=null) { if(is_null($done)) return $this->get("done",$default); $this->set("done",$done); return $this; } /** * Set or Get the value of started * @param mixed $started * @param mixed $default * @return mixed */ public function started($started=null,$default=null) { if(is_null($started)) return $this->get("started",$default); $this->set("started",$started); return $this; } /** * Set or Get the value of offset * @param mixed $offset * @param mixed $default * @return mixed */ public function offset($offset=null,$default=null) { if(is_null($offset)) return $this->get("offset",$default); $this->set("offset",$offset); return $this; } /** * Set or Get the value of attempts * @param mixed $attempts * @param mixed $default * @return mixed */ public function attempts($attempts=null,$default=null) { if(is_null($attempts)) return $this->get("attempts",$default); $this->set("attempts",$attempts); return $this; } public function incAttempts() { $attempts = $this->attempts(null,0); $attempts++; $this->attempts($attempts); return $this; } /** * Set or Get the value of content * @param mixed $content * @param mixed $default * @return mixed */ public function content($content=null,$default=null) { if(is_null($content)) return $this->get("content",$default); $this->set("content",$content); return $this; } /** * Get the value for $key. if $key is null, the whole array is returned. * @param null|mixed $key if null, returns the entire array. Otherwise, a dot-separated key to find in the configuration array * @param mixed $default default return value if $key isn't found/set * @return mixed */ public function get($key=null,$default=null) { if(is_null($key)) { return $this->data; } return dhGlobal::getVal($this->data,$key,$default); } /** * Set the value of $key * @param mixed $key * @param mixed $value * @return $this */ public function set($key,$value=null) { dhGlobal::dotAssign($this->data,$key,$value); return $this; } }src/parts/traits/TraitLog.php000064400000003545144761607150012252 0ustar00debug_name."-t","trace","TRACE"); //_trace() dhGlobal::addLogLevel($this->debug_name."-d","debug","DEBUG"); //_debug() dhGlobal::addLogLevel($this->debug_name."-i","info"," INFO"); //_info() dhGlobal::addLogLevel($this->debug_name."-w","warn"," WARN"); //_warn() dhGlobal::addLogLevel($this->debug_name."-e","error","ERROR"); //_error() } protected function _trace(...$args) { if($this->debugDoTrace) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-t"); return call_user_func_array([$this,"log"],$args); } return; } protected function _debug(...$args) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-d"); return call_user_func_array([$this,"log"],$args); } protected function _info(...$args) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-i"); return call_user_func_array([$this,"log"],$args); } protected function _warn(...$args) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-w"); return call_user_func_array([$this,"log"],$args); } protected function _error(...$args) { array_unshift($args,$this->debug_prefix." - "); array_unshift($args,$this->debug_name."-e"); return call_user_func_array([$this,"log"],$args); } public function log(...$args) { return dhGlobal::log(...$args); } }test/basic.php000064400000001556144761607150007337 0ustar00listBuckets(); $bucket = $client->getBucketBy("bucketName","dhtest"); $options = [ "bucket"=>$bucket ]; $files = $client->listFiles($options); foreach($files as $file) { $modified = $file->modified(); echo $file->get("fileId")." - ".$file->get("fileName")."\t".$file->modified("Y-m-d H:i:s")."\n"; } return; //print_r($client->createBucket("dhtest")); //works $bucket = $client->getBucketBy("bucketName","dhtest"); $options = [ "bucket"=>$bucket, "fileName"=>"testfile.txt", "content"=>file_get_contents("testfile.txt"), ]; $client->upload($options);test/init.php000064400000000334144761607150007212 0ustar00$keyId,"appKey"=>$appKey]; }tests000064400000000000144761607150005632 0ustar00