public function handleRequest(AphrontRequest $request)
 {
     $viewer = $request->getViewer();
     $aid = $request->getURIData('aid');
     $bid = $request->getURIData('bid');
     // If "aid" is "x", then it means the user wants to generate
     // a patch of an empty file to the version specified by "bid".
     $ids = array($aid, $bid);
     if ($aid === 'x') {
         $ids = array($bid);
     }
     $versions = id(new PhragmentFragmentVersionQuery())->setViewer($viewer)->withIDs($ids)->execute();
     $version_a = null;
     if ($aid !== 'x') {
         $version_a = idx($versions, $aid, null);
         if ($version_a === null) {
             return new Aphront404Response();
         }
     }
     $version_b = idx($versions, $bid, null);
     if ($version_b === null) {
         return new Aphront404Response();
     }
     $file_phids = array();
     if ($version_a !== null) {
         $file_phids[] = $version_a->getFilePHID();
     }
     $file_phids[] = $version_b->getFilePHID();
     $files = id(new PhabricatorFileQuery())->setViewer($viewer)->withPHIDs($file_phids)->execute();
     $files = mpull($files, null, 'getPHID');
     $file_a = null;
     if ($version_a != null) {
         $file_a = idx($files, $version_a->getFilePHID(), null);
     }
     $file_b = idx($files, $version_b->getFilePHID(), null);
     $patch = PhragmentPatchUtil::calculatePatch($file_a, $file_b);
     if ($patch === null) {
         // There are no differences between the two files, so we output
         // an empty patch.
         $patch = '';
     }
     $a_sequence = 'x';
     if ($version_a !== null) {
         $a_sequence = $version_a->getSequence();
     }
     $name = $version_b->getFragment()->getName() . '.' . $a_sequence . '.' . $version_b->getSequence() . '.patch';
     $return = $version_b->getURI();
     if ($request->getExists('return')) {
         $return = $request->getStr('return');
     }
     $result = PhabricatorFile::buildFromFileDataOrHash($patch, array('name' => $name, 'mime-type' => 'text/plain', 'ttl' => time() + 60 * 60 * 24));
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     $result->attachToObject($version_b->getFragmentPHID());
     unset($unguarded);
     return id(new AphrontRedirectResponse())->setURI($result->getDownloadURI($return));
 }
 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, 'fileOld' => null, 'fileNew' => $files[$file_phid], 'hashOld' => $hash_caller, 'hashNew' => $hash_current, 'patchURI' => 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, 'fileOld' => null, 'fileNew' => null, 'hashOld' => $hash_caller, 'hashNew' => PhragmentPatchUtil::EMPTY_HASH, 'patchURI' => 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, 'fileOld' => null, 'fileNew' => $files[$file_phid], 'hashOld' => PhragmentPatchUtil::EMPTY_HASH, 'hashNew' => $hash_current, 'patchURI' => 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['hashOld'] !== PhragmentPatchUtil::EMPTY_HASH) {
             $hashes[] = $patch['hashOld'];
         }
     }
     $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['hashOld'] !== PhragmentPatchUtil::EMPTY_HASH) {
             if (array_key_exists($patch['hashOld'], $old_files)) {
                 $patches[$key]['fileOld'] = $old_files[$patch['hashOld']];
             } else {
                 // We either can't see or can't read the old file.
                 $patches[$key]['hashOld'] = PhragmentPatchUtil::EMPTY_HASH;
                 $patches[$key]['fileOld'] = 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]['fileOld'], $patches[$key]['fileNew']);
         unset($patches[$key]['fileOld']);
         unset($patches[$key]['fileNew']);
         $file = PhabricatorFile::buildFromFileDataOrHash($data, array('name' => 'patch.dmp', 'ttl' => time() + 60 * 60 * 24));
         $patches[$key]['patchURI'] = $file->getDownloadURI();
     }
     return $patches;
 }