Changeset View
Changeset View
Standalone View
Standalone View
src/workflow/ArcanistUploadWorkflow.php
<?php | <?php | ||||
/** | final class ArcanistUploadWorkflow | ||||
* Upload a file to Phabricator. | extends ArcanistArcWorkflow { | ||||
*/ | |||||
final class ArcanistUploadWorkflow extends ArcanistWorkflow { | |||||
private $paths; | |||||
private $json; | |||||
public function getWorkflowName() { | public function getWorkflowName() { | ||||
return 'upload'; | return 'upload'; | ||||
} | } | ||||
public function getCommandSynopses() { | public function getWorkflowInformation() { | ||||
return phutil_console_format(<<<EOTEXT | $help = pht(<<<EOTEXT | ||||
**upload** __file__ [__file__ ...] [--json] | Upload one or more files from local disk to Phabricator. | ||||
EOTEXT | EOTEXT | ||||
); | ); | ||||
} | |||||
public function getCommandHelp() { | return $this->newWorkflowInformation() | ||||
return phutil_console_format(<<<EOTEXT | ->setSynopsis(pht('Upload files.')) | ||||
Supports: filesystems | ->addExample(pht('**upload** [__options__] -- __file__ [__file__ ...]')) | ||||
Upload a file from local disk. | ->setHelp($help); | ||||
EOTEXT | |||||
); | |||||
} | } | ||||
public function getArguments() { | public function getWorkflowArguments() { | ||||
return array( | return array( | ||||
'json' => array( | $this->newWorkflowArgument('json') | ||||
'help' => pht('Output upload information in JSON format.'), | ->setHelp(pht('Output upload information in JSON format.')), | ||||
), | $this->newWorkflowArgument('temporary') | ||||
'temporary' => array( | ->setHelp( | ||||
'help' => pht( | pht( | ||||
'Mark the file as temporary. Temporary files will be deleted '. | 'Mark the file as temporary. Temporary files will be '. | ||||
'automatically after 24 hours.'), | 'deleted after 24 hours.')), | ||||
), | $this->newWorkflowArgument('paths') | ||||
'*' => 'paths', | ->setWildcard(true) | ||||
->setIsPathArgument(true), | |||||
); | ); | ||||
} | } | ||||
protected function didParseArguments() { | public function runWorkflow() { | ||||
if (!$this->getArgument('paths')) { | if (!$this->getArgument('paths')) { | ||||
throw new ArcanistUsageException( | throw new PhutilArgumentUsageException( | ||||
pht('Specify one or more files to upload.')); | pht('Specify one or more paths to files you want 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'); | $is_temporary = $this->getArgument('temporary'); | ||||
$is_json = $this->getArgument('json'); | |||||
$paths = $this->getArgument('paths'); | |||||
$conduit = $this->getConduit(); | $conduit = $this->getConduitEngine(); | ||||
$results = array(); | $results = array(); | ||||
$uploader = id(new ArcanistFileUploader()) | $uploader = id(new ArcanistFileUploader()) | ||||
->setConduitClient($conduit); | ->setConduitEngine($conduit); | ||||
foreach ($this->paths as $key => $path) { | foreach ($paths as $key => $path) { | ||||
$file = id(new ArcanistFileDataRef()) | $file = id(new ArcanistFileDataRef()) | ||||
->setName(basename($path)) | ->setName(basename($path)) | ||||
->setPath($path); | ->setPath($path); | ||||
if ($is_temporary) { | if ($is_temporary) { | ||||
$expires_at = time() + phutil_units('24 hours in seconds'); | $expires_at = time() + phutil_units('24 hours in seconds'); | ||||
$file->setDeleteAfterEpoch($expires_at); | $file->setDeleteAfterEpoch($expires_at); | ||||
} | } | ||||
$uploader->addFile($file); | $uploader->addFile($file); | ||||
} | } | ||||
$files = $uploader->uploadFiles(); | $files = $uploader->uploadFiles(); | ||||
$results = array(); | $results = array(); | ||||
foreach ($files as $file) { | foreach ($files as $file) { | ||||
// TODO: This could be handled more gracefully; just preserving behavior | // TODO: This could be handled more gracefully; just preserving behavior | ||||
// until we introduce `file.query` and modernize this. | // until we introduce `file.query` and modernize this. | ||||
if ($file->getErrors()) { | if ($file->getErrors()) { | ||||
throw new Exception(implode("\n", $file->getErrors())); | throw new Exception(implode("\n", $file->getErrors())); | ||||
} | } | ||||
$phid = $file->getPHID(); | $phid = $file->getPHID(); | ||||
$name = $file->getName(); | $name = $file->getName(); | ||||
$info = $conduit->callMethodSynchronous( | $info = $conduit->resolveCall( | ||||
'file.info', | 'file.info', | ||||
array( | array( | ||||
'phid' => $phid, | 'phid' => $phid, | ||||
)); | )); | ||||
$results[$path] = $info; | $results[$path] = $info; | ||||
if (!$this->json) { | if (!$is_json) { | ||||
$id = $info['id']; | $id = $info['id']; | ||||
echo " F{$id} {$name}: ".$info['uri']."\n\n"; | echo " F{$id} {$name}: ".$info['uri']."\n\n"; | ||||
} | } | ||||
} | } | ||||
if ($this->json) { | if ($is_json) { | ||||
echo json_encode($results)."\n"; | $output = id(new PhutilJSON())->encodeFormatted($results); | ||||
echo $output; | |||||
} else { | } else { | ||||
$this->writeStatus(pht('Done.')); | $this->writeStatus(pht('Done.')); | ||||
} | } | ||||
return 0; | return 0; | ||||
} | } | ||||
private function writeStatus($line) { | private function writeStatus($line) { | ||||
$this->writeStatusMessage($line."\n"); | $this->writeStatusMessage($line."\n"); | ||||
} | } | ||||
private function uploadChunks($file_phid, $path) { | private function uploadChunks($file_phid, $path) { | ||||
$conduit = $this->getConduit(); | $conduit = $this->getConduit(); | ||||
$f = @fopen($path, 'rb'); | $f = @fopen($path, 'rb'); | ||||
if (!$f) { | if (!$f) { | ||||
throw new Exception(pht('Unable to open file "%s"', $path)); | throw new Exception(pht('Unable to open file "%s"', $path)); | ||||
} | } | ||||
$this->writeStatus(pht('Beginning chunked upload of large file...')); | $this->writeStatus(pht('Beginning chunked upload of large file...')); | ||||
$chunks = $conduit->callMethodSynchronous( | $chunks = $conduit->resolveCall( | ||||
'file.querychunks', | 'file.querychunks', | ||||
array( | array( | ||||
'filePHID' => $file_phid, | 'filePHID' => $file_phid, | ||||
)); | )); | ||||
$remaining = array(); | $remaining = array(); | ||||
foreach ($chunks as $chunk) { | foreach ($chunks as $chunk) { | ||||
if (!$chunk['complete']) { | if (!$chunk['complete']) { | ||||
Show All 40 Lines | foreach ($remaining as $chunk) { | ||||
$data = fread($f, $chunk['byteEnd'] - $chunk['byteStart']); | $data = fread($f, $chunk['byteEnd'] - $chunk['byteStart']); | ||||
if ($data === false) { | if ($data === false) { | ||||
throw new Exception( | throw new Exception( | ||||
pht( | pht( | ||||
'Failed to %s!', | 'Failed to %s!', | ||||
'fread()')); | 'fread()')); | ||||
} | } | ||||
$conduit->callMethodSynchronous( | $conduit->resolveCall( | ||||
'file.uploadchunk', | 'file.uploadchunk', | ||||
array( | array( | ||||
'filePHID' => $file_phid, | 'filePHID' => $file_phid, | ||||
'byteStart' => $offset, | 'byteStart' => $offset, | ||||
'dataEncoding' => 'base64', | 'dataEncoding' => 'base64', | ||||
'data' => base64_encode($data), | 'data' => base64_encode($data), | ||||
)); | )); | ||||
$progress->update(1); | $progress->update(1); | ||||
} | } | ||||
} | } | ||||
} | } |