LegacyVersionsBackend.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
  5. *
  6. * @author Robin Appelman <robin@icewind.nl>
  7. * @author Roeland Jago Douma <roeland@famdouma.nl>
  8. *
  9. * @license GNU AGPL version 3 or any later version
  10. *
  11. * This program is free software: you can redistribute it and/or modify
  12. * it under the terms of the GNU Affero General Public License as
  13. * published by the Free Software Foundation, either version 3 of the
  14. * License, or (at your option) any later version.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU Affero General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU Affero General Public License
  22. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  23. *
  24. */
  25. namespace OCA\Files_Versions\Versions;
  26. use OC\Files\View;
  27. use OCA\Files_Sharing\ISharedStorage;
  28. use OCA\Files_Sharing\SharedStorage;
  29. use OCA\Files_Versions\Db\VersionEntity;
  30. use OCA\Files_Versions\Db\VersionsMapper;
  31. use OCA\Files_Versions\Storage;
  32. use OCP\Files\File;
  33. use OCP\Files\FileInfo;
  34. use OCP\Files\Folder;
  35. use OCP\Files\IMimeTypeLoader;
  36. use OCP\Files\IRootFolder;
  37. use OCP\Files\NotFoundException;
  38. use OCP\Files\Storage\IStorage;
  39. use OCP\IUser;
  40. use OCP\IUserManager;
  41. class LegacyVersionsBackend implements IVersionBackend, INameableVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend {
  42. private IRootFolder $rootFolder;
  43. private IUserManager $userManager;
  44. private VersionsMapper $versionsMapper;
  45. private IMimeTypeLoader $mimeTypeLoader;
  46. public function __construct(
  47. IRootFolder $rootFolder,
  48. IUserManager $userManager,
  49. VersionsMapper $versionsMapper,
  50. IMimeTypeLoader $mimeTypeLoader
  51. ) {
  52. $this->rootFolder = $rootFolder;
  53. $this->userManager = $userManager;
  54. $this->versionsMapper = $versionsMapper;
  55. $this->mimeTypeLoader = $mimeTypeLoader;
  56. }
  57. public function useBackendForStorage(IStorage $storage): bool {
  58. return true;
  59. }
  60. public function getVersionsForFile(IUser $user, FileInfo $file): array {
  61. $storage = $file->getStorage();
  62. if ($storage->instanceOfStorage(SharedStorage::class)) {
  63. $owner = $storage->getOwner('');
  64. $user = $this->userManager->get($owner);
  65. $fileId = $file->getId();
  66. if ($fileId === null) {
  67. throw new NotFoundException("File not found ($fileId)");
  68. }
  69. if ($user === null) {
  70. throw new NotFoundException("User $owner not found for $fileId");
  71. }
  72. $userFolder = $this->rootFolder->getUserFolder($user->getUID());
  73. $nodes = $userFolder->getById($fileId);
  74. $file = array_pop($nodes);
  75. if (!$file) {
  76. throw new NotFoundException("version file not found for share owner");
  77. }
  78. } else {
  79. $userFolder = $this->rootFolder->getUserFolder($user->getUID());
  80. }
  81. $fileId = $file->getId();
  82. if ($fileId === null) {
  83. throw new NotFoundException("File not found ($fileId)");
  84. }
  85. $versions = $this->getVersionsForFileFromDB($file, $user);
  86. // Early exit if we find any version in the database.
  87. // Else we continue to populate the DB from what's on disk.
  88. if (count($versions) > 0) {
  89. return $versions;
  90. }
  91. // Insert the entry in the DB for the current version.
  92. $versionEntity = new VersionEntity();
  93. $versionEntity->setFileId($fileId);
  94. $versionEntity->setTimestamp($file->getMTime());
  95. $versionEntity->setSize($file->getSize());
  96. $versionEntity->setMimetype($this->mimeTypeLoader->getId($file->getMimetype()));
  97. $versionEntity->setMetadata([]);
  98. $this->versionsMapper->insert($versionEntity);
  99. // Insert entries in the DB for existing versions.
  100. $relativePath = $userFolder->getRelativePath($file->getPath());
  101. if ($relativePath === null) {
  102. throw new NotFoundException("Relative path not found for file $fileId (" . $file->getPath() . ')');
  103. }
  104. $versionsOnFS = Storage::getVersions($user->getUID(), $relativePath);
  105. foreach ($versionsOnFS as $version) {
  106. $versionEntity = new VersionEntity();
  107. $versionEntity->setFileId($fileId);
  108. $versionEntity->setTimestamp((int)$version['version']);
  109. $versionEntity->setSize((int)$version['size']);
  110. $versionEntity->setMimetype($this->mimeTypeLoader->getId($version['mimetype']));
  111. $versionEntity->setMetadata([]);
  112. $this->versionsMapper->insert($versionEntity);
  113. }
  114. return $this->getVersionsForFileFromDB($file, $user);
  115. }
  116. /**
  117. * @return IVersion[]
  118. */
  119. private function getVersionsForFileFromDB(FileInfo $file, IUser $user): array {
  120. $userFolder = $this->rootFolder->getUserFolder($user->getUID());
  121. return array_map(
  122. fn (VersionEntity $versionEntity) => new Version(
  123. $versionEntity->getTimestamp(),
  124. $versionEntity->getTimestamp(),
  125. $file->getName(),
  126. $versionEntity->getSize(),
  127. $this->mimeTypeLoader->getMimetypeById($versionEntity->getMimetype()),
  128. $userFolder->getRelativePath($file->getPath()),
  129. $file,
  130. $this,
  131. $user,
  132. $versionEntity->getLabel(),
  133. ),
  134. $this->versionsMapper->findAllVersionsForFileId($file->getId())
  135. );
  136. }
  137. public function createVersion(IUser $user, FileInfo $file) {
  138. $userFolder = $this->rootFolder->getUserFolder($user->getUID());
  139. $relativePath = $userFolder->getRelativePath($file->getPath());
  140. $userView = new View('/' . $user->getUID());
  141. // create all parent folders
  142. Storage::createMissingDirectories($relativePath, $userView);
  143. Storage::scheduleExpire($user->getUID(), $relativePath);
  144. // store a new version of a file
  145. $userView->copy('files/' . $relativePath, 'files_versions/' . $relativePath . '.v' . $file->getMtime());
  146. // ensure the file is scanned
  147. $userView->getFileInfo('files_versions/' . $relativePath . '.v' . $file->getMtime());
  148. }
  149. public function rollback(IVersion $version) {
  150. return Storage::rollback($version->getVersionPath(), $version->getRevisionId(), $version->getUser());
  151. }
  152. private function getVersionFolder(IUser $user): Folder {
  153. $userRoot = $this->rootFolder->getUserFolder($user->getUID())
  154. ->getParent();
  155. try {
  156. /** @var Folder $folder */
  157. $folder = $userRoot->get('files_versions');
  158. return $folder;
  159. } catch (NotFoundException $e) {
  160. return $userRoot->newFolder('files_versions');
  161. }
  162. }
  163. public function read(IVersion $version) {
  164. $versions = $this->getVersionFolder($version->getUser());
  165. /** @var File $file */
  166. $file = $versions->get($version->getVersionPath() . '.v' . $version->getRevisionId());
  167. return $file->fopen('r');
  168. }
  169. public function getVersionFile(IUser $user, FileInfo $sourceFile, $revision): File {
  170. $userFolder = $this->rootFolder->getUserFolder($user->getUID());
  171. $owner = $sourceFile->getOwner();
  172. $storage = $sourceFile->getStorage();
  173. // Shared files have their versions in the owners root folder so we need to obtain them from there
  174. if ($storage->instanceOfStorage(ISharedStorage::class) && $owner) {
  175. /** @var SharedStorage $storage */
  176. $userFolder = $this->rootFolder->getUserFolder($owner->getUID());
  177. $user = $owner;
  178. $ownerPathInStorage = $sourceFile->getInternalPath();
  179. $sourceFile = $storage->getShare()->getNode();
  180. if ($sourceFile instanceof Folder) {
  181. $sourceFile = $sourceFile->get($ownerPathInStorage);
  182. }
  183. }
  184. $versionFolder = $this->getVersionFolder($user);
  185. /** @var File $file */
  186. $file = $versionFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . '.v' . $revision);
  187. return $file;
  188. }
  189. public function setVersionLabel(IVersion $version, string $label): void {
  190. $versionEntity = $this->versionsMapper->findVersionForFileId(
  191. $version->getSourceFile()->getId(),
  192. $version->getTimestamp(),
  193. );
  194. if (trim($label) === '') {
  195. $label = null;
  196. }
  197. $versionEntity->setLabel($label ?? '');
  198. $this->versionsMapper->update($versionEntity);
  199. }
  200. public function deleteVersion(IVersion $version): void {
  201. Storage::deleteRevision($version->getVersionPath(), $version->getRevisionId());
  202. $versionEntity = $this->versionsMapper->findVersionForFileId(
  203. $version->getSourceFile()->getId(),
  204. $version->getTimestamp(),
  205. );
  206. $this->versionsMapper->delete($versionEntity);
  207. }
  208. public function createVersionEntity(File $file): void {
  209. $versionEntity = new VersionEntity();
  210. $versionEntity->setFileId($file->getId());
  211. $versionEntity->setTimestamp($file->getMTime());
  212. $versionEntity->setSize($file->getSize());
  213. $versionEntity->setMimetype($this->mimeTypeLoader->getId($file->getMimetype()));
  214. $versionEntity->setMetadata([]);
  215. $this->versionsMapper->insert($versionEntity);
  216. }
  217. public function updateVersionEntity(File $sourceFile, int $revision, array $properties): void {
  218. $versionEntity = $this->versionsMapper->findVersionForFileId($sourceFile->getId(), $revision);
  219. if (isset($properties['timestamp'])) {
  220. $versionEntity->setTimestamp($properties['timestamp']);
  221. }
  222. if (isset($properties['size'])) {
  223. $versionEntity->setSize($properties['size']);
  224. }
  225. if (isset($properties['mimetype'])) {
  226. $versionEntity->setMimetype($properties['mimetype']);
  227. }
  228. $this->versionsMapper->update($versionEntity);
  229. }
  230. public function deleteVersionsEntity(File $file): void {
  231. $this->versionsMapper->deleteAllVersionsForFileId($file->getId());
  232. }
  233. }