/** * Gets all versions of a note * * @NoAdminRequired * @NoCSRFRequired * @CORS * * @return array */ public function getAllVersions() { $source = $this->request->getParam("file_name", ""); list($uid, $filename) = Storage::getUidAndFilename($source); $versions = Storage::getVersions($uid, $filename, $source); $versionsResults = array(); if (is_array($versions) && count($versions) > 0) { require_once __DIR__ . '/../3rdparty/finediff/finediff.php'; $users_view = new View('/' . $uid); $currentData = $users_view->file_get_contents('files/' . $filename); // $previousData = $currentData; // $versions = array_reverse( $versions, true ); foreach ($versions as $versionData) { // get timestamp of version $mtime = (int) $versionData["version"]; // get filename of note version $versionFileName = 'files_versions/' . $filename . '.v' . $mtime; // load the data from the file $data = $users_view->file_get_contents($versionFileName); // calculate diff between versions $opcodes = \FineDiff::getDiffOpcodes($currentData, $data); $html = \FineDiff::renderDiffToHTMLFromOpcodes($currentData, $opcodes); $versionsResults[] = array("timestamp" => $mtime, "humanReadableTimestamp" => $versionData["humanReadableTimestamp"], "diffHtml" => $html, "data" => $data); // $previousData = $data; } // $versionsResults = array_reverse( $versionsResults ); } return array("file_name" => $source, "versions" => $versionsResults); }
public function handle() { $userManager = \OC::$server->getUserManager(); if (!$userManager->userExists($this->user)) { // User has been deleted already return; } \OC_Util::setupFS($this->user); Storage::expire($this->fileName, $this->versionsSize, $this->neededSpace); \OC_Util::tearDownFS(); }
protected function run($argument) { $maxAge = $this->expiration->getMaxAgeAsTimestamp(); if (!$maxAge) { return; } $this->userManager->callForAllUsers(function (IUser $user) { $uid = $user->getUID(); if (!$this->setupFS($uid)) { return; } Storage::expireOlderThanMaxForUser($uid); }); }
protected function run($argument) { $maxAge = $this->expiration->getMaxAgeAsTimestamp(); if (!$maxAge) { return; } $users = $this->userManager->search(''); $isFSready = false; foreach ($users as $user) { $uid = $user->getUID(); if (!$isFSready) { if (!$this->setupFS($uid)) { continue; } $isFSready = true; } Storage::expireOlderThanMaxForUser($uid); } \OC_Util::tearDownFS(); }
/** * decrypt versions from given file * @param string $filelist list of decrypted files, relative to data/user/files * @return boolean */ private function decryptVersions($filelist) { $successful = true; if (\OCP\App::isEnabled('files_versions')) { foreach ($filelist as $filename) { $versions = \OCA\Files_Versions\Storage::getVersions($this->userId, $filename); foreach ($versions as $version) { $path = '/' . $this->userId . '/files_versions/' . $version['path'] . '.v' . $version['version']; $encHandle = fopen('crypt://' . $path, 'rb'); if ($encHandle === false) { \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '", decryption failed!', \OCP\Util::FATAL); $successful = false; continue; } $plainHandle = $this->view->fopen($path . '.part', 'wb'); if ($plainHandle === false) { \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '.part", decryption failed!', \OCP\Util::FATAL); $successful = false; continue; } stream_copy_to_stream($encHandle, $plainHandle); fclose($encHandle); fclose($plainHandle); $this->view->rename($path . '.part', $path); } } } return $successful; }
/** * @param \OC\Files\View $view * @param string $path */ private function createAndCheckVersions(\OC\Files\View $view, $path) { $view->file_put_contents($path, 'test file'); $view->file_put_contents($path, 'version 1'); $view->file_put_contents($path, 'version 2'); $this->loginAsUser(self::TEST_VERSIONS_USER); // need to scan for the versions list($rootStorage, ) = $this->rootView->resolvePath(self::TEST_VERSIONS_USER . '/files_versions'); $rootStorage->getScanner()->scan('files_versions'); $versions = \OCA\Files_Versions\Storage::getVersions(self::TEST_VERSIONS_USER, '/' . $path); // note: we cannot predict how many versions are created due to // test run timing $this->assertGreaterThan(0, count($versions)); }
/** * Move file versions to trash so that they can be restored later * * @param string $file_path path to original file * @param string $filename of deleted file * @param integer $timestamp when the file was deleted * * @return int size of stored versions */ private static function retainVersions($file_path, $filename, $timestamp) { $size = 0; if (\OCP\App::isEnabled('files_versions')) { // disable proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; $user = \OCP\User::getUser(); $rootView = new \OC\Files\View('/'); list($owner, $ownerPath) = self::getUidAndFilename($file_path); if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) { $size += self::calculateSize(new \OC\Files\View('/' . $owner . '/files_versions/' . $ownerPath)); if ($owner !== $user) { self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView); } $rootView->rename($owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp); } else { if ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) { foreach ($versions as $v) { $size += $rootView->filesize($owner . '/files_versions' . $v['path'] . '.v' . $v['version']); if ($owner !== $user) { $rootView->copy($owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp); } $rootView->rename($owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp); } } } // enable proxy \OC_FileProxy::$enabled = $proxyStatus; } return $size; }
/** * Move file versions to trash so that they can be restored later * * @param string $file_path path to original file * @param string $filename of deleted file * @param string $owner owner user id * @param string $ownerPath path relative to the owner's home storage * @param integer $timestamp when the file was deleted * * @return int size of stored versions */ private static function retainVersions($file_path, $filename, $owner, $ownerPath, $timestamp) { $size = 0; if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) { $user = \OCP\User::getUser(); $rootView = new \OC\Files\View('/'); if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) { $size += self::calculateSize(new \OC\Files\View('/' . $owner . '/files_versions/' . $ownerPath)); if ($owner !== $user) { self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView); } self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp); } else { if ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) { foreach ($versions as $v) { $size += $rootView->filesize($owner . '/files_versions/' . $v['path'] . '.v' . $v['version']); if ($owner !== $user) { self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp); } self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp); } } } } return $size; }
public function handle() { \OC_Util::setupFS($this->user); Storage::expire($this->fileName, $this->versionsSize, $this->neededSpace); \OC_Util::tearDownFS(); }
/** * Expire versions which exceed the quota * * @param string $filename * @return bool|int|null */ public static function expire($filename) { $config = \OC::$server->getConfig(); $expiration = self::getExpiration(); if ($config->getSystemValue('files_versions', Storage::DEFAULTENABLED) == 'true' && $expiration->isEnabled()) { if (!Filesystem::file_exists($filename)) { return false; } list($uid, $filename) = self::getUidAndFilename($filename); if (empty($filename)) { // file maybe renamed or deleted return false; } $versionsFileview = new View('/' . $uid . '/files_versions'); // get available disk space for user $user = \OC::$server->getUserManager()->get($uid); $softQuota = true; $quota = $user->getQuota(); if ($quota === null || $quota === 'none') { $quota = Filesystem::free_space('/'); $softQuota = false; } else { $quota = \OCP\Util::computerFileSize($quota); } // make sure that we have the current size of the version history $versionsSize = self::getVersionsSize($uid); // calculate available space for version history // subtract size of files and current versions size from quota if ($quota >= 0) { if ($softQuota) { $files_view = new View('/' . $uid . '/files'); $rootInfo = $files_view->getFileInfo('/', false); $free = $quota - $rootInfo['size']; // remaining free space for user if ($free > 0) { $availableSpace = $free * self::DEFAULTMAXSIZE / 100 - $versionsSize; // how much space can be used for versions } else { $availableSpace = $free - $versionsSize; } } else { $availableSpace = $quota; } } else { $availableSpace = PHP_INT_MAX; } $allVersions = Storage::getVersions($uid, $filename); $time = time(); list($toDelete, $sizeOfDeletedVersions) = self::getExpireList($time, $allVersions, $availableSpace <= 0); $availableSpace = $availableSpace + $sizeOfDeletedVersions; $versionsSize = $versionsSize - $sizeOfDeletedVersions; // if still not enough free space we rearrange the versions from all files if ($availableSpace <= 0) { $result = Storage::getAllVersions($uid); $allVersions = $result['all']; foreach ($result['by_file'] as $versions) { list($toDeleteNew, $size) = self::getExpireList($time, $versions, $availableSpace <= 0); $toDelete = array_merge($toDelete, $toDeleteNew); $sizeOfDeletedVersions += $size; } $availableSpace = $availableSpace + $sizeOfDeletedVersions; $versionsSize = $versionsSize - $sizeOfDeletedVersions; } foreach ($toDelete as $key => $path) { \OC_Hook::emit('\\OCP\\Versions', 'preDelete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED)); self::deleteVersion($versionsFileview, $path); \OC_Hook::emit('\\OCP\\Versions', 'delete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED)); unset($allVersions[$key]); // update array with the versions we keep \OCP\Util::writeLog('files_versions', "Expire: " . $path, \OCP\Util::DEBUG); } // Check if enough space is available after versions are rearranged. // If not we delete the oldest versions until we meet the size limit for versions, // but always keep the two latest versions $numOfVersions = count($allVersions) - 2; $i = 0; // sort oldest first and make sure that we start at the first element ksort($allVersions); reset($allVersions); while ($availableSpace < 0 && $i < $numOfVersions) { $version = current($allVersions); \OC_Hook::emit('\\OCP\\Versions', 'preDelete', array('path' => $version['path'] . '.v' . $version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED)); self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']); \OC_Hook::emit('\\OCP\\Versions', 'delete', array('path' => $version['path'] . '.v' . $version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED)); \OCP\Util::writeLog('files_versions', 'running out of space! Delete oldest version: ' . $version['path'] . '.v' . $version['version'], \OCP\Util::DEBUG); $versionsSize -= $version['size']; $availableSpace += $version['size']; next($allVersions); $i++; } return $versionsSize; // finally return the new size of the version history } return false; }
/** * test if we find all versions and if the versions array contain * the correct 'path' and 'name' */ public function testGetVersions() { $t1 = time(); // second version is two weeks older, this way we make sure that no // version will be expired $t2 = $t1 - 60 * 60 * 24 * 14; // create some versions $v1 = self::USERS_VERSIONS_ROOT . '/subfolder/test.txt.v' . $t1; $v2 = self::USERS_VERSIONS_ROOT . '/subfolder/test.txt.v' . $t2; $this->rootView->mkdir(self::USERS_VERSIONS_ROOT . '/subfolder/'); $this->rootView->file_put_contents($v1, 'version1'); $this->rootView->file_put_contents($v2, 'version2'); // execute copy hook of versions app $versions = \OCA\Files_Versions\Storage::getVersions(self::TEST_VERSIONS_USER, '/subfolder/test.txt'); $this->assertSame(2, count($versions)); foreach ($versions as $version) { $this->assertSame('/subfolder/test.txt', $version['path']); $this->assertSame('test.txt', $version['name']); } //cleanup $this->rootView->deleteAll(self::USERS_VERSIONS_ROOT . '/subfolder'); }
function testDownloadVersions() { // login as admin self::loginHelper(self::TEST_ENCRYPTION_SHARE_USER1); $rootView = new \OC\Files\View(); // save file twice to create a new version \OC\Files\Filesystem::file_put_contents($this->filename, "revision1"); \OCA\Files_Versions\Storage::store($this->filename); \OC\Files\Filesystem::file_put_contents($this->filename, "revision2"); // check if the owner can retrieve the correct version $versions = \OCA\Files_Versions\Storage::getVersions(self::TEST_ENCRYPTION_SHARE_USER1, $this->filename); $this->assertSame(1, count($versions)); $version = reset($versions); $versionUser1 = $rootView->file_get_contents('/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_versions/' . $this->filename . '.v' . $version['version']); $this->assertSame('revision1', $versionUser1); // share the file $fileInfo = \OC\Files\Filesystem::getFileInfo($this->filename); $this->assertInstanceOf('\\OC\\Files\\FileInfo', $fileInfo); $this->assertTrue(\OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, self::TEST_ENCRYPTION_SHARE_USER2, \OCP\Constants::PERMISSION_ALL)); // try to download the version as user2 self::loginHelper(self::TEST_ENCRYPTION_SHARE_USER2); $versionUser2 = $rootView->file_get_contents('/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_versions/' . $this->filename . '.v' . $version['version']); $this->assertSame('revision1', $versionUser2); //cleanup self::loginHelper(self::TEST_ENCRYPTION_SHARE_USER1); \OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, self::TEST_ENCRYPTION_SHARE_USER2); \OC\Files\Filesystem::unlink($this->filename); }
} $file = array_key_exists('file', $_GET) ? (string) urldecode($_GET['file']) : ''; $maxX = array_key_exists('x', $_GET) ? (int) $_GET['x'] : 44; $maxY = array_key_exists('y', $_GET) ? (int) $_GET['y'] : 44; $version = array_key_exists('version', $_GET) ? $_GET['version'] : ''; $scalingUp = array_key_exists('scalingup', $_GET) ? (bool) $_GET['scalingup'] : true; if ($file === '' && $version === '') { \OC_Response::setStatus(400); //400 Bad Request \OC_Log::write('versions-preview', 'No file parameter was passed', \OC_Log::DEBUG); exit; } if ($maxX === 0 || $maxY === 0) { \OC_Response::setStatus(400); //400 Bad Request \OC_Log::write('versions-preview', 'x and/or y set to 0', \OC_Log::DEBUG); exit; } try { list($user, $file) = \OCA\Files_Versions\Storage::getUidAndFilename($file); $preview = new \OC\Preview($user, 'files_versions', $file . '.v' . $version); $mimetype = \OC_Helper::getFileNameMimeType($file); $preview->setMimetype($mimetype); $preview->setMaxX($maxX); $preview->setMaxY($maxY); $preview->setScalingUp($scalingUp); $preview->showPreview(); } catch (\Exception $e) { \OC_Response::setStatus(500); \OC_Log::write('core', $e->getmessage(), \OC_Log::DEBUG); }
/** * @NoAdminRequired * @NoCSRFRequired * @PublicPage * Given an access token and a fileId, returns the contents of the file. * Expects a valid token in access_token parameter. */ public function wopiGetFile($fileId) { $token = $this->request->getParam('access_token'); $arr = explode('_', $fileId, 2); $version = '0'; if (count($arr) == 2) { $fileId = $arr[0]; $version = $arr[1]; } \OC::$server->getLogger()->debug('Getting contents of file {fileId}, version {version} by token {token}.', ['app' => $this->appName, 'fileId' => $fileId, 'version' => $version, 'token' => $token]); $row = new Db\Wopi(); $row->loadBy('token', $token); //TODO: Support X-WOPIMaxExpectedSize header. $res = $row->getPathForToken($fileId, $version, $token); $ownerid = $res['owner']; // Login the user to see his mount locations $this->loginUser($ownerid); $filename = ''; // If some previous version is requested, fetch it from Files_Version app if ($version !== '0') { \OCP\JSON::checkAppEnabled('files_versions'); // Setup the FS \OC_Util::tearDownFS(); \OC_Util::setupFS($ownerid, '/' . $ownerid . '/files'); list($ownerid, $filename) = \OCA\Files_Versions\Storage::getUidAndFilename($res['path']); $filename = '/files_versions/' . $filename . '.v' . $version; \OC_Util::tearDownFS(); } else { $filename = '/files' . $res['path']; } // Close the session created for user login \OC::$server->getSession()->close(); return new DownloadResponse($this->request, $ownerid, $filename); }