diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -9,7 +9,7 @@ * * | name | Human readable filename. * | authorPHID | User PHID of uploader. - * | ttl | Temporary file lifetime, in seconds. + * | ttl | Temporary file expiry time, seconds after epoch (UNIX timestamp). * | viewPolicy | File visibility policy. * | isExplicitUpload | Used to show users files they explicitly uploaded. * | canCDN | Allows the file to be cached and delivered over a CDN. @@ -203,6 +203,10 @@ * To solve these problems, we use file storage as a cache and reuse the * same file again if we've previously written it. * + * When a temporary file exists, its lifetime is extended to match the passed + * ttl parameter if necessary (including making the file permanent if no ttl + * is passed) so the file isn't unexpectedly deleted by the garbage collector. + * * NOTE: This method unguards writes. * * @param string Raw file data. @@ -221,6 +225,26 @@ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $file = self::newFromFileData($data, $params); unset($unguarded); + + return $file; + } + + $desired_ttl = idx($params, 'ttl'); + $current_ttl = $file->getTtl(); + if ($current_ttl !== null) { + if ($desired_ttl === null) { + $file->setTtl(null); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $file->save(); + unset($unguarded); + } else if ($desired_ttl > $current_ttl) { + $file->setTtl($desired_ttl); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $file->save(); + unset($unguarded); + } } return $file; @@ -965,7 +989,7 @@ $builtins = mpull($builtins, null, 'getBuiltinFileKey'); $specs = array(); - foreach ($builtins as $key => $buitin) { + foreach ($builtins as $key => $builtin) { $specs[] = array( 'originalPHID' => PhabricatorPHIDConstants::PHID_VOID, 'transform' => $key, diff --git a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php --- a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php +++ b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php @@ -384,6 +384,67 @@ $this->assertEqual($ttl, $file->getTTL()); } + public function testExtendFileTtl() { + $engine = new PhabricatorTestStorageEngine(); + + $data = Filesystem::readRandomCharacters(64); + + $ttl = (time() + 60 * 60 * 24); + + $params = array( + 'name' => 'test.dat', + 'ttl' => ($ttl), + 'storageEngines' => array( + $engine, + ), + ); + + $file = PhabricatorFile::newFromFileData($data, $params); + + // Newly-generated IDs are ints, but come back from the DB as strings + $id = (string)$file->getID(); + + // Ensure the file has its life extended if necessary + + $ttl = (time() + 60 * 60 * 24 * 2); + $params['ttl'] = $ttl; + + $file = PhabricatorFile::buildFromFileDataOrHash($data, $params); + + $this->assertEqual($id, $file->getID()); + $this->assertEqual($ttl, $file->getTTL()); + + // Ensure the file's life is not shortened + + $params['ttl'] = (time() + 60 * 60 * 24); + + $file = PhabricatorFile::buildFromFileDataOrHash($data, $params); + + // Newly-set expiry times are ints, but come back from the DB as strings + $ttl = (string)$ttl; + $this->assertEqual($id, $file->getID()); + $this->assertEqual($ttl, $file->getTTL()); + + // Ensure the file is made permanent if necessary + + $ttl = null; + $params['ttl'] = $ttl; + + $file = PhabricatorFile::buildFromFileDataOrHash($data, $params); + + $this->assertEqual($id, $file->getID()); + $this->assertEqual($ttl, $file->getTTL()); + + // Ensure the file is not made temporary + + $params['ttl'] = (time() + 60 * 60 * 24); + + $file = PhabricatorFile::buildFromFileDataOrHash($data, $params); + + $this->assertEqual($id, $file->getID()); + $this->assertEqual($ttl, $file->getTTL()); + } + public function testFileTransformDelete() { // We want to test that a file deletes all its inbound transformation // records and outbound transformed derivatives when it is deleted.