TrashbinMigrator.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OCA\Files_Trashbin\UserMigration;
  8. use OCA\Files_Trashbin\AppInfo\Application;
  9. use OCP\Files\Folder;
  10. use OCP\Files\IRootFolder;
  11. use OCP\Files\NotFoundException;
  12. use OCP\IDBConnection;
  13. use OCP\IL10N;
  14. use OCP\IUser;
  15. use OCP\UserMigration\IExportDestination;
  16. use OCP\UserMigration\IImportSource;
  17. use OCP\UserMigration\IMigrator;
  18. use OCP\UserMigration\ISizeEstimationMigrator;
  19. use OCP\UserMigration\TMigratorBasicVersionHandling;
  20. use OCP\UserMigration\UserMigrationException;
  21. use Symfony\Component\Console\Output\OutputInterface;
  22. class TrashbinMigrator implements IMigrator, ISizeEstimationMigrator {
  23. use TMigratorBasicVersionHandling;
  24. protected const PATH_FILES_FOLDER = Application::APP_ID.'/files';
  25. protected const PATH_LOCATIONS_FILE = Application::APP_ID.'/locations.json';
  26. protected IRootFolder $root;
  27. protected IDBConnection $dbc;
  28. protected IL10N $l10n;
  29. public function __construct(
  30. IRootFolder $rootFolder,
  31. IDBConnection $dbc,
  32. IL10N $l10n
  33. ) {
  34. $this->root = $rootFolder;
  35. $this->dbc = $dbc;
  36. $this->l10n = $l10n;
  37. }
  38. /**
  39. * {@inheritDoc}
  40. */
  41. public function getEstimatedExportSize(IUser $user): int|float {
  42. $uid = $user->getUID();
  43. try {
  44. $trashbinFolder = $this->root->get('/'.$uid.'/files_trashbin');
  45. if (!$trashbinFolder instanceof Folder) {
  46. return 0;
  47. }
  48. return ceil($trashbinFolder->getSize() / 1024);
  49. } catch (\Throwable $e) {
  50. return 0;
  51. }
  52. }
  53. /**
  54. * {@inheritDoc}
  55. */
  56. public function export(IUser $user, IExportDestination $exportDestination, OutputInterface $output): void {
  57. $output->writeln('Exporting trashbin into ' . Application::APP_ID . '…');
  58. $uid = $user->getUID();
  59. try {
  60. $trashbinFolder = $this->root->get('/'.$uid.'/files_trashbin');
  61. if (!$trashbinFolder instanceof Folder) {
  62. throw new UserMigrationException('/'.$uid.'/files_trashbin is not a folder');
  63. }
  64. $output->writeln("Exporting trashbin files…");
  65. $exportDestination->copyFolder($trashbinFolder, static::PATH_FILES_FOLDER);
  66. $originalLocations = [];
  67. // TODO Export all extra data and bump migrator to v2
  68. foreach (\OCA\Files_Trashbin\Trashbin::getExtraData($uid) as $filename => $extraData) {
  69. $locationData = [];
  70. foreach ($extraData as $timestamp => ['location' => $location]) {
  71. $locationData[$timestamp] = $location;
  72. }
  73. $originalLocations[$filename] = $locationData;
  74. }
  75. $exportDestination->addFileContents(static::PATH_LOCATIONS_FILE, json_encode($originalLocations));
  76. } catch (NotFoundException $e) {
  77. $output->writeln("No trashbin to export…");
  78. } catch (\Throwable $e) {
  79. throw new UserMigrationException("Could not export trashbin: ".$e->getMessage(), 0, $e);
  80. }
  81. }
  82. /**
  83. * {@inheritDoc}
  84. */
  85. public function import(IUser $user, IImportSource $importSource, OutputInterface $output): void {
  86. if ($importSource->getMigratorVersion($this->getId()) === null) {
  87. $output->writeln('No version for ' . static::class . ', skipping import…');
  88. return;
  89. }
  90. $output->writeln('Importing trashbin from ' . Application::APP_ID . '…');
  91. $uid = $user->getUID();
  92. if ($importSource->pathExists(static::PATH_FILES_FOLDER)) {
  93. try {
  94. $trashbinFolder = $this->root->get('/'.$uid.'/files_trashbin');
  95. if (!$trashbinFolder instanceof Folder) {
  96. throw new UserMigrationException('Could not import trashbin, /'.$uid.'/files_trashbin is not a folder');
  97. }
  98. } catch (NotFoundException $e) {
  99. $trashbinFolder = $this->root->newFolder('/'.$uid.'/files_trashbin');
  100. }
  101. $output->writeln("Importing trashbin files…");
  102. try {
  103. $importSource->copyToFolder($trashbinFolder, static::PATH_FILES_FOLDER);
  104. } catch (\Throwable $e) {
  105. throw new UserMigrationException("Could not import trashbin.", 0, $e);
  106. }
  107. $locations = json_decode($importSource->getFileContents(static::PATH_LOCATIONS_FILE), true, 512, JSON_THROW_ON_ERROR);
  108. $qb = $this->dbc->getQueryBuilder();
  109. $qb->insert('files_trash')
  110. ->values([
  111. 'id' => $qb->createParameter('id'),
  112. 'timestamp' => $qb->createParameter('timestamp'),
  113. 'location' => $qb->createParameter('location'),
  114. 'user' => $qb->createNamedParameter($uid),
  115. ]);
  116. foreach ($locations as $id => $fileLocations) {
  117. foreach ($fileLocations as $timestamp => $location) {
  118. $qb
  119. ->setParameter('id', $id)
  120. ->setParameter('timestamp', $timestamp)
  121. ->setParameter('location', $location)
  122. ;
  123. $qb->executeStatement();
  124. }
  125. }
  126. } else {
  127. $output->writeln("No trashbin to import…");
  128. }
  129. }
  130. /**
  131. * {@inheritDoc}
  132. */
  133. public function getId(): string {
  134. return 'trashbin';
  135. }
  136. /**
  137. * {@inheritDoc}
  138. */
  139. public function getDisplayName(): string {
  140. return $this->l10n->t('Deleted files');
  141. }
  142. /**
  143. * {@inheritDoc}
  144. */
  145. public function getDescription(): string {
  146. return $this->l10n->t('Deleted files and folders in the trash bin (may expire during export if you are low on storage space)');
  147. }
  148. }