Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15437360
D21097.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
22 KB
Referenced Files
None
Subscribers
None
D21097.id.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -190,6 +190,10 @@
'ArcanistFeatureWorkflow' => 'workflow/ArcanistFeatureWorkflow.php',
'ArcanistFileConfigurationSource' => 'config/source/ArcanistFileConfigurationSource.php',
'ArcanistFileDataRef' => 'upload/ArcanistFileDataRef.php',
+ 'ArcanistFileRef' => 'ref/file/ArcanistFileRef.php',
+ 'ArcanistFileSymbolHardpointQuery' => 'ref/file/ArcanistFileSymbolHardpointQuery.php',
+ 'ArcanistFileSymbolRef' => 'ref/file/ArcanistFileSymbolRef.php',
+ 'ArcanistFileSymbolRefInspector' => 'ref/file/ArcanistFileSymbolRefInspector.php',
'ArcanistFileUploader' => 'upload/ArcanistFileUploader.php',
'ArcanistFilenameLinter' => 'lint/linter/ArcanistFilenameLinter.php',
'ArcanistFilenameLinterTestCase' => 'lint/linter/__tests__/ArcanistFilenameLinterTestCase.php',
@@ -1149,7 +1153,7 @@
),
'ArcanistDoubleQuoteXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistDoubleQuoteXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
- 'ArcanistDownloadWorkflow' => 'ArcanistWorkflow',
+ 'ArcanistDownloadWorkflow' => 'ArcanistArcWorkflow',
'ArcanistDuplicateKeysInArrayXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistDuplicateKeysInArrayXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistDuplicateSwitchCaseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@@ -1173,6 +1177,13 @@
'ArcanistFeatureWorkflow' => 'ArcanistFeatureBaseWorkflow',
'ArcanistFileConfigurationSource' => 'ArcanistFilesystemConfigurationSource',
'ArcanistFileDataRef' => 'Phobject',
+ 'ArcanistFileRef' => array(
+ 'ArcanistRef',
+ 'ArcanistDisplayRefInterface',
+ ),
+ 'ArcanistFileSymbolHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
+ 'ArcanistFileSymbolRef' => 'ArcanistSymbolRef',
+ 'ArcanistFileSymbolRefInspector' => 'ArcanistRefInspector',
'ArcanistFileUploader' => 'Phobject',
'ArcanistFilenameLinter' => 'ArcanistLinter',
'ArcanistFilenameLinterTestCase' => 'ArcanistLinterTestCase',
diff --git a/src/ref/file/ArcanistFileRef.php b/src/ref/file/ArcanistFileRef.php
new file mode 100644
--- /dev/null
+++ b/src/ref/file/ArcanistFileRef.php
@@ -0,0 +1,52 @@
+<?php
+
+final class ArcanistFileRef
+ extends ArcanistRef
+ implements
+ ArcanistDisplayRefInterface {
+
+ private $parameters;
+
+ public function getRefDisplayName() {
+ return pht('File "%s"', $this->getMonogram());
+ }
+
+ public static function newFromConduit(array $parameters) {
+ $ref = new self();
+ $ref->parameters = $parameters;
+ return $ref;
+ }
+
+ public function getID() {
+ return idx($this->parameters, 'id');
+ }
+
+ public function getPHID() {
+ return idx($this->parameters, 'phid');
+ }
+
+ public function getName() {
+ return idxv($this->parameters, array('fields', 'name'));
+ }
+
+ public function getDataURI() {
+ return idxv($this->parameters, array('fields', 'dataURI'));
+ }
+
+ public function getSize() {
+ return idxv($this->parameters, array('fields', 'size'));
+ }
+
+ public function getMonogram() {
+ return 'F'.$this->getID();
+ }
+
+ public function getDisplayRefObjectName() {
+ return $this->getMonogram();
+ }
+
+ public function getDisplayRefTitle() {
+ return $this->getName();
+ }
+
+}
diff --git a/src/ref/file/ArcanistFileSymbolHardpointQuery.php b/src/ref/file/ArcanistFileSymbolHardpointQuery.php
new file mode 100644
--- /dev/null
+++ b/src/ref/file/ArcanistFileSymbolHardpointQuery.php
@@ -0,0 +1,90 @@
+<?php
+
+final class ArcanistFileSymbolHardpointQuery
+ extends ArcanistRuntimeHardpointQuery {
+
+ public function getHardpoints() {
+ return array(
+ ArcanistFileSymbolRef::HARDPOINT_OBJECT,
+ );
+ }
+
+ protected function canLoadRef(ArcanistRef $ref) {
+ return ($ref instanceof ArcanistFileSymbolRef);
+ }
+
+ public function loadHardpoint(array $refs, $hardpoint) {
+ $id_map = array();
+ $phid_map = array();
+
+ foreach ($refs as $key => $ref) {
+ switch ($ref->getSymbolType()) {
+ case ArcanistFileSymbolRef::TYPE_ID:
+ $id_map[$key] = $ref->getSymbol();
+ break;
+ case ArcanistFileSymbolRef::TYPE_PHID:
+ $phid_map[$key] = $ref->getSymbol();
+ break;
+ }
+ }
+
+ $futures = array();
+
+ if ($id_map) {
+ $id_future = $this->newConduitSearch(
+ 'file.search',
+ array(
+ 'ids' => array_values(array_fuse($id_map)),
+ ));
+
+ $futures[] = $id_future;
+ } else {
+ $id_future = null;
+ }
+
+ if ($phid_map) {
+ $phid_future = $this->newConduitSearch(
+ 'file.search',
+ array(
+ 'phids' => array_values(array_fuse($phid_map)),
+ ));
+
+ $futures[] = $phid_future;
+ } else {
+ $phid_future = null;
+ }
+
+ yield $this->yieldFutures($futures);
+
+ $result_map = array();
+
+ if ($id_future) {
+ $id_results = $id_future->resolve();
+ $id_results = ipull($id_results, null, 'id');
+
+ foreach ($id_map as $key => $id) {
+ $result_map[$key] = idx($id_results, $id);
+ }
+ }
+
+ if ($phid_future) {
+ $phid_results = $phid_future->resolve();
+ $phid_results = ipull($phid_results, null, 'phid');
+
+ foreach ($phid_map as $key => $phid) {
+ $result_map[$key] = idx($phid_results, $phid);
+ }
+ }
+
+ foreach ($result_map as $key => $raw_result) {
+ if ($raw_result === null) {
+ continue;
+ }
+
+ $result_map[$key] = ArcanistFileRef::newFromConduit($raw_result);
+ }
+
+ yield $this->yieldMap($result_map);
+ }
+
+}
diff --git a/src/ref/file/ArcanistFileSymbolRef.php b/src/ref/file/ArcanistFileSymbolRef.php
new file mode 100644
--- /dev/null
+++ b/src/ref/file/ArcanistFileSymbolRef.php
@@ -0,0 +1,47 @@
+<?php
+
+final class ArcanistFileSymbolRef
+ extends ArcanistSymbolRef {
+
+ private $type;
+
+ const TYPE_ID = 'id';
+ const TYPE_PHID = 'phid';
+
+ public function getRefDisplayName() {
+ return pht('File Symbol "%s"', $this->getSymbol());
+ }
+
+ protected function newCacheKeyParts() {
+ return array(
+ sprintf('type(%s)', $this->type),
+ );
+ }
+
+ public function getSymbolType() {
+ return $this->type;
+ }
+
+ protected function resolveSymbol($symbol) {
+ $matches = null;
+
+ $is_id = preg_match('/^[Ff]?([1-9]\d*)\z/', $symbol, $matches);
+ if ($is_id) {
+ $this->type = self::TYPE_ID;
+ return (int)$matches[1];
+ }
+
+ $is_phid = preg_match('/^PHID-FILE-\S+\z/', $symbol, $matches);
+ if ($is_phid) {
+ $this->type = self::TYPE_PHID;
+ return $matches[0];
+ }
+
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'The format of file symbol "%s" is unrecognized. Expected a '.
+ 'monogram like "F123", or an ID like "123", or a file PHID.',
+ $symbol));
+ }
+
+}
diff --git a/src/ref/file/ArcanistFileSymbolRefInspector.php b/src/ref/file/ArcanistFileSymbolRefInspector.php
new file mode 100644
--- /dev/null
+++ b/src/ref/file/ArcanistFileSymbolRefInspector.php
@@ -0,0 +1,22 @@
+<?php
+
+final class ArcanistFileSymbolRefInspector
+ extends ArcanistRefInspector {
+
+ public function getInspectFunctionName() {
+ return 'file';
+ }
+
+ public function newInspectRef(array $argv) {
+ if (count($argv) !== 1) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Expected exactly one argument to "file(...)" with a '.
+ 'file symbol.'));
+ }
+
+ return id(new ArcanistFileSymbolRef())
+ ->setSymbol($argv[0]);
+ }
+
+}
diff --git a/src/ref/symbol/ArcanistSymbolEngine.php b/src/ref/symbol/ArcanistSymbolEngine.php
--- a/src/ref/symbol/ArcanistSymbolEngine.php
+++ b/src/ref/symbol/ArcanistSymbolEngine.php
@@ -48,6 +48,17 @@
$symbols);
}
+ public function loadFileForSymbol($symbol) {
+ $refs = $this->loadFilesForSymbols(array($symbol));
+ return head($refs)->getObject();
+ }
+
+ public function loadFilesForSymbols(array $symbols) {
+ return $this->loadRefsForSymbols(
+ new ArcanistFileSymbolRef(),
+ $symbols);
+ }
+
public function loadRefsForSymbols(
ArcanistSymbolRef $template,
array $symbols) {
diff --git a/src/runtime/ArcanistRuntime.php b/src/runtime/ArcanistRuntime.php
--- a/src/runtime/ArcanistRuntime.php
+++ b/src/runtime/ArcanistRuntime.php
@@ -113,7 +113,7 @@
$workflows = $this->newWorkflows($toolset);
$this->workflows = $workflows;
- $conduit_engine = $this->newConduitEngine($config);
+ $conduit_engine = $this->newConduitEngine($config, $args);
$this->conduitEngine = $conduit_engine;
$phutil_workflows = array();
@@ -698,16 +698,34 @@
return last($this->stack);
}
- private function newConduitEngine(ArcanistConfigurationSourceList $config) {
+ private function newConduitEngine(
+ ArcanistConfigurationSourceList $config,
+ PhutilArgumentParser $args) {
+
+ try {
+ $force_uri = $args->getArg('conduit-uri');
+ } catch (PhutilArgumentSpecificationException $ex) {
+ $force_uri = null;
+ }
+
+ try {
+ $force_token = $args->getArg('conduit-token');
+ } catch (PhutilArgumentSpecificationException $ex) {
+ $force_token = null;
+ }
- $conduit_uri = $config->getConfig('phabricator.uri');
- if ($conduit_uri === null) {
- // For now, read this older config from raw storage. There is currently
- // no definition of this option in the "toolsets" config list, and it
- // would be nice to get rid of it.
- $default_list = $config->getStorageValueList('default');
- if ($default_list) {
- $conduit_uri = last($default_list)->getValue();
+ if ($force_uri !== null) {
+ $conduit_uri = $force_uri;
+ } else {
+ $conduit_uri = $config->getConfig('phabricator.uri');
+ if ($conduit_uri === null) {
+ // For now, read this older config from raw storage. There is currently
+ // no definition of this option in the "toolsets" config list, and it
+ // would be nice to get rid of it.
+ $default_list = $config->getStorageValueList('default');
+ if ($default_list) {
+ $conduit_uri = last($default_list)->getValue();
+ }
}
}
@@ -731,16 +749,19 @@
// TODO: This isn't using "getConfig()" because we aren't defining a
// real config entry for the moment.
- $hosts = array();
+ if ($force_token !== null) {
+ $conduit_token = $force_token;
+ } else {
+ $hosts = array();
- $hosts_list = $config->getStorageValueList('hosts');
- foreach ($hosts_list as $hosts_config) {
- $hosts += $hosts_config->getValue();
- }
+ $hosts_list = $config->getStorageValueList('hosts');
+ foreach ($hosts_list as $hosts_config) {
+ $hosts += $hosts_config->getValue();
+ }
- $host_config = idx($hosts, $conduit_uri, array());
- $user_name = idx($host_config, 'user');
- $conduit_token = idx($host_config, 'token');
+ $host_config = idx($hosts, $conduit_uri, array());
+ $conduit_token = idx($host_config, 'token');
+ }
if ($conduit_token !== null) {
$engine->setConduitToken($conduit_token);
diff --git a/src/workflow/ArcanistDownloadWorkflow.php b/src/workflow/ArcanistDownloadWorkflow.php
--- a/src/workflow/ArcanistDownloadWorkflow.php
+++ b/src/workflow/ArcanistDownloadWorkflow.php
@@ -1,53 +1,31 @@
<?php
-/**
- * Download a file from Phabricator.
- */
-final class ArcanistDownloadWorkflow extends ArcanistWorkflow {
-
- private $id;
- private $saveAs;
- private $show;
+final class ArcanistDownloadWorkflow
+ extends ArcanistArcWorkflow {
public function getWorkflowName() {
return 'download';
}
- public function getCommandSynopses() {
- return phutil_console_format(<<<EOTEXT
- **download** __file__ [--as __name__] [--show]
+ public function getWorkflowInformation() {
+ $help = pht(<<<EOTEXT
+Download a file to local disk.
EOTEXT
);
- }
- public function getCommandHelp() {
- return phutil_console_format(<<<EOTEXT
- Supports: filesystems
- Download a file to local disk, e.g.:
-
- $ arc download F33 # Download file 'F33'
-EOTEXT
- );
+ return $this->newWorkflowInformation()
+ ->setSynopsis(pht('Download a file to local disk.'))
+ ->addExample(pht('**download** [__options__] -- __file__'))
+ ->setHelp($help);
}
- public function getArguments() {
+ public function getWorkflowArguments() {
return array(
- 'show' => array(
- 'conflicts' => array(
- 'as' => pht(
- 'Use %s to direct the file to stdout, or %s to direct '.
- 'it to a named location.',
- '--show',
- '--as'),
- ),
- 'help' => pht('Write file to stdout instead of to disk.'),
- ),
- 'as' => array(
- 'param' => 'name',
- 'help' => pht(
- 'Save the file with a specific name rather than the default.'),
- ),
- '*' => 'argv',
+ $this->newWorkflowArgument('as')
+ ->setParameter('path')
+ ->setHelp(pht('Save the file to a specific location.')),
+ $this->newWorkflowArgument('argv')
+ ->setWildcard(true),
);
}
@@ -72,207 +50,143 @@
$this->show = $this->getArgument('show');
}
- public function requiresAuthentication() {
- return true;
- }
- public function run() {
- $conduit = $this->getConduit();
+ public function runWorkflow() {
+ $file_symbols = $this->getArgument('argv');
- $id = $this->id;
- $display_name = 'F'.$id;
- $is_show = $this->show;
- $save_as = $this->saveAs;
- $path = null;
-
- try {
- $file = $conduit->callMethodSynchronous(
- 'file.search',
- array(
- 'constraints' => array(
- 'ids' => array($id),
- ),
- ));
-
- $data = $file['data'];
- if (!$data) {
- throw new ArcanistUsageException(
- pht(
- 'File "%s" is not a valid file, or not visible.',
- $display_name));
- }
-
- $file = head($data);
- $data_uri = idxv($file, array('fields', 'dataURI'));
+ if (!$file_symbols) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Specify a file to download, like "F123".'));
+ }
- if ($data_uri === null) {
- throw new ArcanistUsageException(
- pht(
- 'File "%s" can not be downloaded.',
- $display_name));
- }
+ if (count($file_symbols) > 1) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Specify exactly one file to download.'));
+ }
- if ($is_show) {
- // Skip all the file path stuff if we're just going to echo the
- // file contents.
- } else {
- if ($save_as !== null) {
- $path = Filesystem::resolvePath($save_as);
+ $file_symbol = head($file_symbols);
- $try_unique = false;
- } else {
- $path = idxv($file, array('fields', 'name'), $display_name);
- $path = basename($path);
- $path = Filesystem::resolvePath($path);
+ $symbols = $this->getSymbolEngine();
+ $file_ref = $symbols->loadFileForSymbol($file_symbol);
+ if (!$file_ref) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'File "%s" does not exist, or you do not have permission to '.
+ 'view it.',
+ $file_symbol));
+ }
- $try_unique = true;
- }
+ $is_stdout = false;
+ $path = null;
- if ($try_unique) {
- $path = Filesystem::writeUniqueFile($path, '');
- } else {
- if (Filesystem::pathExists($path)) {
- throw new ArcanistUsageException(
- pht(
- 'File "%s" already exists.',
- $save_as));
- }
-
- Filesystem::writeFile($path, '');
- }
+ $save_as = $this->getArgument('as');
+ if ($save_as === '-') {
+ $is_stdout = true;
+ } else if ($save_as === null) {
+ $path = $file_ref->getName();
+ $path = basename($path);
+ $path = Filesystem::resolvePath($path);
- $display_path = Filesystem::readablePath($path);
- }
+ $try_unique = true;
+ } else {
+ $path = Filesystem::resolvePath($save_as);
- $size = idxv($file, array('fields', 'size'), 0);
+ $try_unique = false;
+ }
- if ($is_show) {
- $file_handle = null;
+ $file_handle = null;
+ if (!$is_stdout) {
+ if ($try_unique) {
+ $path = Filesystem::writeUniqueFile($path, '');
+ Filesystem::remove($path);
} else {
- $file_handle = fopen($path, 'ab+');
- if ($file_handle === false) {
- throw new Exception(
+ if (Filesystem::pathExists($path)) {
+ throw new PhutilArgumentUsageException(
pht(
- 'Failed to open file "%s" for writing.',
+ 'File "%s" already exists.',
$path));
}
-
- $this->writeInfo(
- pht('DATA'),
- pht(
- 'Downloading "%s" (%s byte(s))...',
- $display_name,
- new PhutilNumber($size)));
}
+ }
- $future = new HTTPSFuture($data_uri);
-
- // For small files, don't bother drawing a progress bar.
- $minimum_bar_bytes = (1024 * 1024 * 4);
+ $display_path = Filesystem::readablePath($path);
- if ($is_show || ($size < $minimum_bar_bytes)) {
- $bar = null;
- } else {
- $bar = id(new PhutilConsoleProgressBar())
- ->setTotal($size);
- }
+ $display_name = $file_ref->getName();
+ if (!strlen($display_name)) {
+ $display_name = $file_ref->getMonogram();
+ }
- // TODO: We should stream responses to disk, but cURL gives us the raw
- // HTTP response data and BaseHTTPFuture can not currently parse it in
- // a stream-oriented way. Until this is resolved, buffer the file data
- // in memory and write it to disk in one shot.
-
- list($status, $data) = $future->resolve();
- if ($status->getStatusCode() !== 200) {
- throw new Exception(
- pht(
- 'Got HTTP %d status response, expected HTTP 200.',
- $status->getStatusCode()));
- }
+ $expected_bytes = $file_ref->getSize();
+ $log = $this->getLogEngine();
+
+ if (!$is_stdout) {
+ $log->writeStatus(
+ pht('DATA'),
+ pht(
+ 'Downloading "%s" (%s byte(s)) to "%s"...',
+ $display_name,
+ new PhutilNumber($expected_bytes),
+ $display_path));
+ }
- if (strlen($data)) {
- if ($is_show) {
- echo $data;
- } else {
- $ok = fwrite($file_handle, $data);
- if ($ok === false) {
- throw new Exception(
- pht(
- 'Failed to write file data to "%s".',
- $path));
- }
- }
- }
+ $data_uri = $file_ref->getDataURI();
+ $future = new HTTPSFuture($data_uri);
- if ($bar) {
- $bar->update(strlen($data));
- }
+ if (!$is_stdout) {
+ // For small files, don't bother drawing a progress bar.
+ $minimum_bar_bytes = (1024 * 1024 * 4);
+ if ($expected_bytes > $minimum_bar_bytes) {
+ $progress = id(new PhutilConsoleProgressSink())
+ ->setTotalWork($expected_bytes);
- if ($bar) {
- $bar->done();
+ $future->setProgressSink($progress);
}
- if ($file_handle) {
- $ok = fclose($file_handle);
- if ($ok === false) {
- throw new Exception(
- pht(
- 'Failed to close file handle for "%s".',
- $path));
- }
- }
+ // Compute a timeout based on the expected filesize.
+ $transfer_rate = 32 * 1024;
+ $timeout = (int)(120 + ($expected_bytes / $transfer_rate));
- if (!$is_show) {
- $this->writeOkay(
- pht('DONE'),
- pht(
- 'Saved "%s" as "%s".',
- $display_name,
- $display_path));
- }
+ $future
+ ->setTimeout($timeout)
+ ->setDownloadPath($path);
+ }
- return 0;
+ try {
+ list($data) = $future->resolvex();
} catch (Exception $ex) {
-
- // If we created an empty file, clean it up.
- if (!$is_show) {
- if ($path !== null) {
- Filesystem::remove($path);
- }
- }
-
- // If we fail for any reason, fall back to the older mechanism using
- // "file.info" and "file.download".
+ Filesystem::removePath($path);
+ throw $ex;
}
- $this->writeStatusMessage(pht('Getting file information...')."\n");
- $info = $conduit->callMethodSynchronous(
- 'file.info',
- array(
- 'id' => $this->id,
- ));
-
- $desc = pht('(%s bytes)', new PhutilNumber($info['byteSize']));
- if ($info['name']) {
- $desc = "'".$info['name']."' ".$desc;
+ if ($is_stdout) {
+ $file_bytes = strlen($data);
+ } else {
+ // TODO: This has various potential problems with clearstatcache() and
+ // 32-bit systems, but just ignore them for now.
+ $file_bytes = filesize($path);
}
- $this->writeStatusMessage(pht('Downloading file %s...', $desc)."\n");
- $data = $conduit->callMethodSynchronous(
- 'file.download',
- array(
- 'phid' => $info['phid'],
- ));
-
- $data = base64_decode($data);
+ if ($file_bytes !== $expected_bytes) {
+ throw new Exception(
+ pht(
+ 'Downloaded file size (%s bytes) does not match expected '.
+ 'file size (%s bytes). This download may be incomplete or '.
+ 'corrupt.',
+ new PhutilNumber($file_bytes),
+ new PhutilNumber($expected_bytes)));
+ }
- if ($this->show) {
+ if ($is_stdout) {
echo $data;
} else {
- $path = Filesystem::writeUniqueFile(
- nonempty($this->saveAs, $info['name'], 'file'),
- $data);
- $this->writeStatusMessage(pht("Saved file as '%s'.", $path)."\n");
+ $log->writeStatus(
+ pht('DONE'),
+ pht(
+ 'Saved "%s" as "%s".',
+ $display_name,
+ $display_path));
}
return 0;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Mar 26, 7:00 PM (5 d, 22 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7722097
Default Alt Text
D21097.id.diff (22 KB)
Attached To
Mode
D21097: Upgrade "arc download" to Toolsets
Attached
Detach File
Event Timeline
Log In to Comment