Page MenuHomePhabricator

D7739.id17525.diff

D7739.id17525.diff

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

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)

Event Timeline