TrashbinMigrator.php 4.9 KB

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