mirror of
https://we.phorge.it/source/arcanist.git
synced 2025-01-08 13:51:01 +01:00
2db40f9953
Summary: Depends on D14070. Test Plan: Eyeball it. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D14072
198 lines
4.6 KiB
PHP
198 lines
4.6 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Upload a file to Phabricator.
|
|
*/
|
|
final class ArcanistUploadWorkflow extends ArcanistWorkflow {
|
|
|
|
private $paths;
|
|
private $json;
|
|
|
|
public function getWorkflowName() {
|
|
return 'upload';
|
|
}
|
|
|
|
public function getCommandSynopses() {
|
|
return phutil_console_format(<<<EOTEXT
|
|
**upload** __file__ [__file__ ...] [--json]
|
|
EOTEXT
|
|
);
|
|
}
|
|
|
|
public function getCommandHelp() {
|
|
return phutil_console_format(<<<EOTEXT
|
|
Supports: filesystems
|
|
Upload a file from local disk.
|
|
EOTEXT
|
|
);
|
|
}
|
|
|
|
public function getArguments() {
|
|
return array(
|
|
'json' => 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);
|
|
}
|
|
}
|
|
|
|
}
|