array( 'help' => pht('Output upload information in JSON format.'), ), 'temporary' => array( 'help' => pht( 'Mark the file as temporary. Temporary files will be deleted '. 'automatically after 24 hours.'), ), '*' => 'paths', ); } protected function didParseArguments() { if (!$this->getArgument('paths')) { throw new ArcanistUsageException( pht('Specify one or more files to upload.')); } $this->paths = $this->getArgument('paths'); $this->json = $this->getArgument('json'); } public function requiresAuthentication() { return true; } public function run() { $is_temporary = $this->getArgument('temporary'); $conduit = $this->getConduit(); $results = array(); $uploader = id(new ArcanistFileUploader()) ->setConduitClient($conduit); foreach ($this->paths as $key => $path) { $file = id(new ArcanistFileDataRef()) ->setName(basename($path)) ->setPath($path); if ($is_temporary) { $expires_at = time() + phutil_units('24 hours in seconds'); $file->setDeleteAfterEpoch($expires_at); } $uploader->addFile($file); } $files = $uploader->uploadFiles(); $results = array(); foreach ($files as $file) { // TODO: This could be handled more gracefully; just preserving behavior // until we introduce `file.query` and modernize this. if ($file->getErrors()) { throw new Exception(implode("\n", $file->getErrors())); } $phid = $file->getPHID(); $name = $file->getName(); $info = $conduit->callMethodSynchronous( 'file.info', array( 'phid' => $phid, )); $results[$path] = $info; if (!$this->json) { $id = $info['id']; echo " F{$id} {$name}: ".$info['uri']."\n\n"; } } if ($this->json) { echo json_encode($results)."\n"; } else { $this->writeStatus(pht('Done.')); } return 0; } private function writeStatus($line) { $this->writeStatusMessage($line."\n"); } private function uploadChunks($file_phid, $path) { $conduit = $this->getConduit(); $f = @fopen($path, 'rb'); if (!$f) { throw new Exception(pht('Unable to open file "%s"', $path)); } $this->writeStatus(pht('Beginning chunked upload of large file...')); $chunks = $conduit->callMethodSynchronous( 'file.querychunks', array( 'filePHID' => $file_phid, )); $remaining = array(); foreach ($chunks as $chunk) { if (!$chunk['complete']) { $remaining[] = $chunk; } } $done = (count($chunks) - count($remaining)); if ($done) { $this->writeStatus( pht( 'Resuming upload (%s of %s chunks remain).', phutil_count($remaining), phutil_count($chunks))); } else { $this->writeStatus( pht( 'Uploading chunks (%s chunks to upload).', phutil_count($remaining))); } $progress = new PhutilConsoleProgressBar(); $progress->setTotal(count($chunks)); for ($ii = 0; $ii < $done; $ii++) { $progress->update(1); } $progress->draw(); // TODO: We could do these in parallel to improve upload performance. foreach ($remaining as $chunk) { $offset = $chunk['byteStart']; $ok = fseek($f, $offset); if ($ok !== 0) { throw new Exception( pht( 'Failed to %s!', 'fseek()')); } $data = fread($f, $chunk['byteEnd'] - $chunk['byteStart']); if ($data === false) { throw new Exception( pht( 'Failed to %s!', 'fread()')); } $conduit->callMethodSynchronous( 'file.uploadchunk', array( 'filePHID' => $file_phid, 'byteStart' => $offset, 'dataEncoding' => 'base64', 'data' => base64_encode($data), )); $progress->update(1); } } }