LegacyVersionsBackend.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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\SharedStorage;
  28. use OCA\Files_Versions\Db\VersionEntity;
  29. use OCA\Files_Versions\Db\VersionsMapper;
  30. use OCA\Files_Versions\Storage;
  31. use OCP\Files\File;
  32. use OCP\Files\FileInfo;
  33. use OCP\Files\Folder;
  34. use OCP\Files\IMimeTypeLoader;
  35. use OCP\Files\IRootFolder;
  36. use OCP\Files\Node;
  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. $versionFolder = $this->getVersionFolder($user);
  172. /** @var File $file */
  173. $file = $versionFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . '.v' . $revision);
  174. return $file;
  175. }
  176. public function setVersionLabel(IVersion $version, string $label): void {
  177. $versionEntity = $this->versionsMapper->findVersionForFileId(
  178. $version->getSourceFile()->getId(),
  179. $version->getTimestamp(),
  180. );
  181. if (trim($label) === '') {
  182. $label = null;
  183. }
  184. $versionEntity->setLabel($label ?? '');
  185. $this->versionsMapper->update($versionEntity);
  186. }
  187. public function deleteVersion(IVersion $version): void {
  188. Storage::deleteRevision($version->getVersionPath(), $version->getRevisionId());
  189. $versionEntity = $this->versionsMapper->findVersionForFileId(
  190. $version->getSourceFile()->getId(),
  191. $version->getTimestamp(),
  192. );
  193. $this->versionsMapper->delete($versionEntity);
  194. }
  195. public function createVersionEntity(File $file): void {
  196. $versionEntity = new VersionEntity();
  197. $versionEntity->setFileId($file->getId());
  198. $versionEntity->setTimestamp($file->getMTime());
  199. $versionEntity->setSize($file->getSize());
  200. $versionEntity->setMimetype($this->mimeTypeLoader->getId($file->getMimetype()));
  201. $versionEntity->setMetadata([]);
  202. $this->versionsMapper->insert($versionEntity);
  203. }
  204. public function updateVersionEntity(File $sourceFile, int $revision, array $properties): void {
  205. $versionEntity = $this->versionsMapper->findVersionForFileId($sourceFile->getId(), $revision);
  206. if (isset($properties['timestamp'])) {
  207. $versionEntity->setTimestamp($properties['timestamp']);
  208. }
  209. if (isset($properties['size'])) {
  210. $versionEntity->setSize($properties['size']);
  211. }
  212. if (isset($properties['mimetype'])) {
  213. $versionEntity->setMimetype($properties['mimetype']);
  214. }
  215. $this->versionsMapper->update($versionEntity);
  216. }
  217. public function deleteVersionsEntity(File $file): void {
  218. $this->versionsMapper->deleteAllVersionsForFileId($file->getId());
  219. }
  220. }