Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F89215
D7739.id17525.diff
All Users
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
15 KB
Referenced Files
None
Subscribers
None
D7739.id17525.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
@@ -207,6 +207,9 @@
'ConduitAPI_phpast_Method' => 'applications/phpast/conduit/ConduitAPI_phpast_Method.php',
'ConduitAPI_phpast_getast_Method' => 'applications/phpast/conduit/ConduitAPI_phpast_getast_Method.php',
'ConduitAPI_phpast_version_Method' => 'applications/phpast/conduit/ConduitAPI_phpast_version_Method.php',
+ 'ConduitAPI_phragment_Method' => 'applications/phragment/conduit/ConduitAPI_phragment_Method.php',
+ 'ConduitAPI_phragment_getpatch_Method' => 'applications/phragment/conduit/ConduitAPI_phragment_getpatch_Method.php',
+ 'ConduitAPI_phragment_queryfragments_Method' => 'applications/phragment/conduit/ConduitAPI_phragment_queryfragments_Method.php',
'ConduitAPI_phriction_Method' => 'applications/phriction/conduit/ConduitAPI_phriction_Method.php',
'ConduitAPI_phriction_edit_Method' => 'applications/phriction/conduit/ConduitAPI_phriction_edit_Method.php',
'ConduitAPI_phriction_history_Method' => 'applications/phriction/conduit/ConduitAPI_phriction_history_Method.php',
@@ -2587,6 +2590,9 @@
'ConduitAPI_phpast_Method' => 'ConduitAPIMethod',
'ConduitAPI_phpast_getast_Method' => 'ConduitAPI_phpast_Method',
'ConduitAPI_phpast_version_Method' => 'ConduitAPI_phpast_Method',
+ 'ConduitAPI_phragment_Method' => 'ConduitAPIMethod',
+ 'ConduitAPI_phragment_getpatch_Method' => 'ConduitAPI_phragment_Method',
+ 'ConduitAPI_phragment_queryfragments_Method' => 'ConduitAPI_phragment_Method',
'ConduitAPI_phriction_Method' => 'ConduitAPIMethod',
'ConduitAPI_phriction_edit_Method' => 'ConduitAPI_phriction_Method',
'ConduitAPI_phriction_history_Method' => 'ConduitAPI_phriction_Method',
diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php
--- a/src/applications/files/query/PhabricatorFileQuery.php
+++ b/src/applications/files/query/PhabricatorFileQuery.php
@@ -13,6 +13,7 @@
private $transforms;
private $dateCreatedAfter;
private $dateCreatedBefore;
+ private $contentHashes;
public function withIDs(array $ids) {
$this->ids = $ids;
@@ -39,6 +40,11 @@
return $this;
}
+ public function withContentHashes(array $content_hashes) {
+ $this->contentHashes = $content_hashes;
+ return $this;
+ }
+
/**
* Select files which are transformations of some other file. For example,
* you can use this query to find previously generated thumbnails of an image
@@ -228,6 +234,13 @@
$this->dateCreatedBefore);
}
+ if ($this->contentHashes) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'f.contentHash IN (%Ls)',
+ $this->contentHashes);
+ }
+
return $this->formatWhereClause($where);
}
diff --git a/src/applications/phragment/conduit/ConduitAPI_phragment_Method.php b/src/applications/phragment/conduit/ConduitAPI_phragment_Method.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phragment/conduit/ConduitAPI_phragment_Method.php
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @group conduit
+ */
+abstract class ConduitAPI_phragment_Method extends ConduitAPIMethod {
+
+ public function getApplication() {
+ return PhabricatorApplication::getByClass(
+ 'PhabricatorApplicationPhragment');
+ }
+
+}
diff --git a/src/applications/phragment/conduit/ConduitAPI_phragment_getpatch_Method.php b/src/applications/phragment/conduit/ConduitAPI_phragment_getpatch_Method.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phragment/conduit/ConduitAPI_phragment_getpatch_Method.php
@@ -0,0 +1,185 @@
+<?php
+
+/**
+ * @group conduit
+ */
+final class ConduitAPI_phragment_getpatch_Method
+ extends ConduitAPI_phragment_Method {
+
+ public function getMethodStatus() {
+ return self::METHOD_STATUS_UNSTABLE;
+ }
+
+ public function getMethodDescription() {
+ return pht("Retrieve the patches to apply for a given set of files.");
+ }
+
+ public function defineParamTypes() {
+ return array(
+ 'path' => 'required string',
+ 'state' => 'required dict<string, string>',
+ );
+ }
+
+ public function defineReturnType() {
+ return 'nonempty dict';
+ }
+
+ public function defineErrorTypes() {
+ return array(
+ 'ERR_BAD_FRAGMENT' => 'No such fragment exists',
+ );
+ }
+
+ protected function execute(ConduitAPIRequest $request) {
+ $path = $request->getValue('path');
+ $state = $request->getValue('state');
+ // The state is an array mapping file paths to hashes.
+
+ $patches = array();
+
+ // We need to get all of the mappings (like phragment.getstate) first
+ // so that we can detect deletions and creations of files.
+ $fragment = id(new PhragmentFragmentQuery())
+ ->setViewer($request->getUser())
+ ->withPaths(array($path))
+ ->executeOne();
+ if ($fragment === null) {
+ throw new ConduitException('ERR_BAD_FRAGMENT');
+ }
+
+ $mappings = $fragment->getFragmentMappings(
+ $request->getUser(),
+ $fragment->getPath());
+
+ $file_phids = mpull(mpull($mappings, 'getLatestVersion'), 'getFilePHID');
+ $files = id(new PhabricatorFileQuery())
+ ->setViewer($request->getUser())
+ ->withPHIDs($file_phids)
+ ->execute();
+ $files = mpull($files, null, 'getPHID');
+
+ // Scan all of the files that the caller currently has and iterate
+ // over that.
+ foreach ($state as $path => $hash) {
+ // If $mappings[$path] exists, then the user has the file and it's
+ // also a fragment.
+ if (array_key_exists($path, $mappings)) {
+ $file_phid = $mappings[$path]->getLatestVersion()->getFilePHID();
+ if ($file_phid !== null) {
+ // If the file PHID is present, then we need to check the
+ // hashes to see if they are the same.
+ $hash_caller = strtolower($state[$path]);
+ $hash_current = $files[$file_phid]->getContentHash();
+ if ($hash_caller === $hash_current) {
+ // The user's version is identical to our version, so
+ // there is no update needed.
+ } else {
+ // The hash differs, and the user needs to update.
+ $patches[] = array(
+ 'path' => $path,
+ 'file_old' => null,
+ 'file_new' => $files[$file_phid],
+ 'hash_old' => $hash_caller,
+ 'hash_new' => $hash_current,
+ 'patch_uri' => null);
+ }
+ } else {
+ // We have a record of this as a file, but there is no file
+ // attached to the latest version, so we consider this to be
+ // a deletion.
+ $patches[] = array(
+ 'path' => $path,
+ 'file_old' => null,
+ 'file_new' => null,
+ 'hash_old' => $hash_caller,
+ 'hash_new' => PhragmentPatchUtil::EMPTY_HASH,
+ 'patch_uri' => null);
+ }
+ } else {
+ // If $mappings[$path] does not exist, then the user has a file,
+ // and we have absolutely no record of it what-so-ever (we haven't
+ // even recorded a deletion). Assuming most applications will store
+ // some form of data near their own files, this is probably a data
+ // file relevant for the application that is not versioned, so we
+ // don't tell the client to do anything with it.
+ }
+ }
+
+ // Check the remaining files that we know about but the caller has
+ // not reported.
+ foreach ($mappings as $path => $child) {
+ if (array_key_exists($path, $state)) {
+ // We have already evaluated this above.
+ } else {
+ $file_phid = $mappings[$path]->getLatestVersion()->getFilePHID();
+ if ($file_phid !== null) {
+ // If the file PHID is present, then this is a new file that
+ // we know about, but the caller does not. We need to tell
+ // the caller to create the file.
+ $hash_current = $files[$file_phid]->getContentHash();
+ $patches[] = array(
+ 'path' => $path,
+ 'file_old' => null,
+ 'file_new' => $files[$file_phid],
+ 'hash_old' => PhragmentPatchUtil::EMPTY_HASH,
+ 'hash_new' => $hash_current,
+ 'patch_uri' => null);
+ } else {
+ // We have a record of deleting this file, and the caller hasn't
+ // reported it, so they've probably deleted it in a previous
+ // update.
+ }
+ }
+ }
+
+ // Before we can calculate patches, we need to resolve the old versions
+ // of files so we can draw diffs on them.
+ $hashes = array();
+ foreach ($patches as $patch) {
+ if ($patch["hash_old"] !== PhragmentPatchUtil::EMPTY_HASH) {
+ $hashes[] = $patch["hash_old"];
+ }
+ }
+ $old_files = array();
+ if (count($hashes) !== 0) {
+ $old_files = id(new PhabricatorFileQuery())
+ ->setViewer($request->getUser())
+ ->withContentHashes($hashes)
+ ->execute();
+ }
+ $old_files = mpull($old_files, null, 'getContentHash');
+ foreach ($patches as $key => $patch) {
+ if ($patch["hash_old"] !== PhragmentPatchUtil::EMPTY_HASH) {
+ if (array_key_exists($patch['hash_old'], $old_files)) {
+ $patches[$key]['file_old'] = $old_files[$patch['hash_old']];
+ } else {
+ // We either can't see or can't read the old file.
+ $patches[$key]['hash_old'] = PhragmentPatchUtil::EMPTY_HASH;
+ $patches[$key]['file_old'] = null;
+ }
+ }
+ }
+
+ // Now run through all of the patch entries, calculate the patches
+ // and return the results.
+ foreach ($patches as $key => $patch) {
+ $data = PhragmentPatchUtil::calculatePatch(
+ $patches[$key]['file_old'],
+ $patches[$key]['file_new']);
+ unset($patches[$key]['file_old']);
+ unset($patches[$key]['file_new']);
+
+ $file = PhabricatorFile::buildFromFileDataOrHash(
+ $data,
+ array(
+ 'name' => 'patch.dmp',
+ 'ttl' => time() + 60 * 60 * 24,
+ ));
+ $patches[$key]['patch_uri'] = $file->getDownloadURI();
+ }
+
+ return $patches;
+ }
+
+}
diff --git a/src/applications/phragment/conduit/ConduitAPI_phragment_queryfragments_Method.php b/src/applications/phragment/conduit/ConduitAPI_phragment_queryfragments_Method.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phragment/conduit/ConduitAPI_phragment_queryfragments_Method.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @group conduit
+ */
+final class ConduitAPI_phragment_queryfragments_Method
+ extends ConduitAPI_phragment_Method {
+
+ public function getMethodStatus() {
+ return self::METHOD_STATUS_UNSTABLE;
+ }
+
+ public function getMethodDescription() {
+ return pht("Query fragments based on their paths.");
+ }
+
+ public function defineParamTypes() {
+ return array(
+ 'paths' => 'required list<string>',
+ );
+ }
+
+ public function defineReturnType() {
+ return 'nonempty dict';
+ }
+
+ public function defineErrorTypes() {
+ return array(
+ 'ERR_BAD_FRAGMENT' => 'No such fragment exists',
+ );
+ }
+
+ protected function execute(ConduitAPIRequest $request) {
+ $paths = $request->getValue('paths');
+
+ $fragments = id(new PhragmentFragmentQuery())
+ ->setViewer($request->getUser())
+ ->withPaths($paths)
+ ->execute();
+ $fragments = mpull($fragments, null, 'getPath');
+ foreach ($paths as $path) {
+ if (!array_key_exists($path, $fragments)) {
+ throw new ConduitException('ERR_BAD_FRAGMENT');
+ }
+ }
+
+ $results = array();
+ foreach ($fragments as $path => $fragment) {
+ $mappings = $fragment->getFragmentMappings(
+ $request->getUser(),
+ $fragment->getPath());
+
+ $file_phids = mpull(mpull($mappings, 'getLatestVersion'), 'getFilePHID');
+ $files = id(new PhabricatorFileQuery())
+ ->setViewer($request->getUser())
+ ->withPHIDs($file_phids)
+ ->execute();
+ $files = mpull($files, null, 'getPHID');
+
+ $result = array();
+ foreach ($mappings as $cpath => $child) {
+ $file_phid = $child->getLatestVersion()->getFilePHID();
+ if (!isset($files[$file_phid])) {
+ // Skip any files we don't have permission to access.
+ continue;
+ }
+
+ $file = $files[$file_phid];
+ $cpath = substr($child->getPath(), strlen($fragment->getPath()) + 1);
+ $result[] = array(
+ 'phid' => $child->getPHID(),
+ 'phid_version' => $child->getLatestVersionPHID(),
+ 'path' => $cpath,
+ 'hash' => $file->getContentHash(),
+ 'version' => $child->getLatestVersion()->getSequence(),
+ 'uri' => $file->getViewURI());
+ }
+ $results[$path] = $result;
+ }
+ return $results;
+ }
+
+}
diff --git a/src/applications/phragment/controller/PhragmentZIPController.php b/src/applications/phragment/controller/PhragmentZIPController.php
--- a/src/applications/phragment/controller/PhragmentZIPController.php
+++ b/src/applications/phragment/controller/PhragmentZIPController.php
@@ -111,29 +111,18 @@
* Returns a list of mappings like array('some/path.txt' => 'file PHID');
*/
private function getFragmentMappings(PhragmentFragment $current, $base_path) {
- $children = id(new PhragmentFragmentQuery())
- ->setViewer($this->getRequest()->getUser())
- ->needLatestVersion(true)
- ->withLeadingPath($current->getPath().'/')
- ->withDepths(array($current->getDepth() + 1))
- ->execute();
-
- if (count($children) === 0) {
- $path = substr($current->getPath(), strlen($base_path) + 1);
- if ($this->getVersion($current) === null) {
- return array();
- }
- return array($path => $this->getVersion($current)->getFilePHID());
- } else {
- $mappings = array();
- foreach ($children as $child) {
- $child_mappings = $this->getFragmentMappings($child, $base_path);
- foreach ($child_mappings as $key => $value) {
- $mappings[$key] = $value;
- }
+ $mappings = $current->getFragmentMappings(
+ $this->getRequest()->getUser(),
+ $base_path);
+
+ $result = array();
+ foreach ($mappings as $path => $fragment) {
+ $version = $this->getVersion($fragment);
+ if ($version !== null) {
+ $result[$path] = $version->getFilePHID();
}
- return $mappings;
}
+ return $result;
}
private function getVersion($fragment) {
diff --git a/src/applications/phragment/storage/PhragmentFragment.php b/src/applications/phragment/storage/PhragmentFragment.php
--- a/src/applications/phragment/storage/PhragmentFragment.php
+++ b/src/applications/phragment/storage/PhragmentFragment.php
@@ -276,6 +276,38 @@
}
+/* -( Utility ) ---------------------------------------------------------- */
+
+
+ public function getFragmentMappings(
+ PhabricatorUser $viewer,
+ $base_path) {
+
+ $children = id(new PhragmentFragmentQuery())
+ ->setViewer($viewer)
+ ->needLatestVersion(true)
+ ->withLeadingPath($this->getPath().'/')
+ ->withDepths(array($this->getDepth() + 1))
+ ->execute();
+
+ if (count($children) === 0) {
+ $path = substr($this->getPath(), strlen($base_path) + 1);
+ return array($path => $this);
+ } else {
+ $mappings = array();
+ foreach ($children as $child) {
+ $child_mappings = $child->getFragmentMappings(
+ $viewer,
+ $base_path);
+ foreach ($child_mappings as $key => $value) {
+ $mappings[$key] = $value;
+ }
+ }
+ return $mappings;
+ }
+ }
+
+
/* -( Policy Interface )--------------------------------------------------- */
File Metadata
Details
Attached
Mime Type
text/x-diff
Storage Engine
amazon-s3
Storage Format
Raw Data
Storage Handle
phabricator/hh/th/cemlzrh3zvc7oh52
Default Alt Text
D7739.id17525.diff (15 KB)
Attached To
Mode
D7739: Provide `phragment.getstate` and `phragment.getpatch` Conduit methods
Attached
Detach File
Event Timeline
Log In to Comment