@@ -26,177 +26,177 @@ |
||
| 26 | 26 | #[\PHPUnit\Framework\Attributes\Group('DB')] |
| 27 | 27 | class ExpireSharesJobTest extends \Test\TestCase { |
| 28 | 28 | |
| 29 | - /** @var ExpireSharesJob */ |
|
| 30 | - private $job; |
|
| 31 | - |
|
| 32 | - /** @var IDBConnection */ |
|
| 33 | - private $connection; |
|
| 34 | - |
|
| 35 | - /** @var string */ |
|
| 36 | - private $user1; |
|
| 37 | - |
|
| 38 | - /** @var string */ |
|
| 39 | - private $user2; |
|
| 40 | - |
|
| 41 | - protected function setUp(): void { |
|
| 42 | - parent::setUp(); |
|
| 43 | - |
|
| 44 | - $this->connection = Server::get(IDBConnection::class); |
|
| 45 | - // clear occasional leftover shares from other tests |
|
| 46 | - $qb = $this->connection->getQueryBuilder(); |
|
| 47 | - $qb->delete('share')->executeStatement(); |
|
| 48 | - |
|
| 49 | - $this->user1 = $this->getUniqueID('user1_'); |
|
| 50 | - $this->user2 = $this->getUniqueID('user2_'); |
|
| 51 | - |
|
| 52 | - $userManager = Server::get(IUserManager::class); |
|
| 53 | - $userManager->createUser($this->user1, 'longrandompassword'); |
|
| 54 | - $userManager->createUser($this->user2, 'longrandompassword'); |
|
| 55 | - |
|
| 56 | - \OC::registerShareHooks(Server::get(SystemConfig::class)); |
|
| 57 | - |
|
| 58 | - $this->job = new ExpireSharesJob(Server::get(ITimeFactory::class), Server::get(IManager::class), $this->connection); |
|
| 59 | - } |
|
| 60 | - |
|
| 61 | - protected function tearDown(): void { |
|
| 62 | - $qb = $this->connection->getQueryBuilder(); |
|
| 63 | - $qb->delete('share')->executeStatement(); |
|
| 64 | - |
|
| 65 | - $userManager = Server::get(IUserManager::class); |
|
| 66 | - $user1 = $userManager->get($this->user1); |
|
| 67 | - if ($user1) { |
|
| 68 | - $user1->delete(); |
|
| 69 | - } |
|
| 70 | - $user2 = $userManager->get($this->user2); |
|
| 71 | - if ($user2) { |
|
| 72 | - $user2->delete(); |
|
| 73 | - } |
|
| 74 | - |
|
| 75 | - $this->logout(); |
|
| 76 | - |
|
| 77 | - parent::tearDown(); |
|
| 78 | - } |
|
| 79 | - |
|
| 80 | - private function getShares() { |
|
| 81 | - $shares = []; |
|
| 82 | - $qb = $this->connection->getQueryBuilder(); |
|
| 83 | - |
|
| 84 | - $result = $qb->select('*') |
|
| 85 | - ->from('share') |
|
| 86 | - ->executeQuery(); |
|
| 87 | - |
|
| 88 | - while ($row = $result->fetchAssociative()) { |
|
| 89 | - $shares[] = $row; |
|
| 90 | - } |
|
| 91 | - $result->closeCursor(); |
|
| 92 | - return $shares; |
|
| 93 | - } |
|
| 94 | - |
|
| 95 | - public static function dataExpireLinkShare() { |
|
| 96 | - return [ |
|
| 97 | - [false, '', false, false], |
|
| 98 | - [false, '', true, false], |
|
| 99 | - [true, 'P1D', false, true], |
|
| 100 | - [true, 'P1D', true, false], |
|
| 101 | - [true, 'P1W', false, true], |
|
| 102 | - [true, 'P1W', true, false], |
|
| 103 | - [true, 'P1M', false, true], |
|
| 104 | - [true, 'P1M', true, false], |
|
| 105 | - [true, 'P1Y', false, true], |
|
| 106 | - [true, 'P1Y', true, false], |
|
| 107 | - ]; |
|
| 108 | - } |
|
| 109 | - |
|
| 110 | - /** |
|
| 111 | - * |
|
| 112 | - * @param bool addExpiration Should we add an expire date |
|
| 113 | - * @param string $interval The dateInterval |
|
| 114 | - * @param bool $addInterval If true add to the current time if false subtract |
|
| 115 | - * @param bool $shouldExpire Should this share be expired |
|
| 116 | - */ |
|
| 117 | - #[\PHPUnit\Framework\Attributes\DataProvider('dataExpireLinkShare')] |
|
| 118 | - public function testExpireLinkShare($addExpiration, $interval, $addInterval, $shouldExpire): void { |
|
| 119 | - $this->loginAsUser($this->user1); |
|
| 120 | - |
|
| 121 | - $user1Folder = \OC::$server->getUserFolder($this->user1); |
|
| 122 | - $testFolder = $user1Folder->newFolder('test'); |
|
| 123 | - |
|
| 124 | - $shareManager = Server::get(\OCP\Share\IManager::class); |
|
| 125 | - $share = $shareManager->newShare(); |
|
| 126 | - |
|
| 127 | - $share->setNode($testFolder) |
|
| 128 | - ->setShareType(IShare::TYPE_LINK) |
|
| 129 | - ->setPermissions(Constants::PERMISSION_READ) |
|
| 130 | - ->setSharedBy($this->user1); |
|
| 131 | - |
|
| 132 | - $shareManager->createShare($share); |
|
| 133 | - |
|
| 134 | - $shares = $this->getShares(); |
|
| 135 | - $this->assertCount(1, $shares); |
|
| 136 | - reset($shares); |
|
| 137 | - $share = current($shares); |
|
| 138 | - |
|
| 139 | - if ($addExpiration) { |
|
| 140 | - $expire = new \DateTime(); |
|
| 141 | - $expire->setTime(0, 0, 0); |
|
| 142 | - if ($addInterval) { |
|
| 143 | - $expire->add(new \DateInterval($interval)); |
|
| 144 | - } else { |
|
| 145 | - $expire->sub(new \DateInterval($interval)); |
|
| 146 | - } |
|
| 147 | - $expire = $expire->format('Y-m-d 00:00:00'); |
|
| 148 | - |
|
| 149 | - // Set expiration date to yesterday |
|
| 150 | - $qb = $this->connection->getQueryBuilder(); |
|
| 151 | - $qb->update('share') |
|
| 152 | - ->set('expiration', $qb->createParameter('expiration')) |
|
| 153 | - ->where($qb->expr()->eq('id', $qb->createParameter('id'))) |
|
| 154 | - ->setParameter('id', $share['id']) |
|
| 155 | - ->setParameter('expiration', $expire) |
|
| 156 | - ->executeStatement(); |
|
| 157 | - |
|
| 158 | - $shares = $this->getShares(); |
|
| 159 | - $this->assertCount(1, $shares); |
|
| 160 | - } |
|
| 161 | - |
|
| 162 | - $this->logout(); |
|
| 163 | - |
|
| 164 | - $this->job->run([]); |
|
| 165 | - |
|
| 166 | - $shares = $this->getShares(); |
|
| 167 | - |
|
| 168 | - if ($shouldExpire) { |
|
| 169 | - $this->assertCount(0, $shares); |
|
| 170 | - } else { |
|
| 171 | - $this->assertCount(1, $shares); |
|
| 172 | - } |
|
| 173 | - } |
|
| 174 | - |
|
| 175 | - public function testDoNotExpireOtherShares(): void { |
|
| 176 | - $this->loginAsUser($this->user1); |
|
| 177 | - |
|
| 178 | - $user1Folder = \OC::$server->getUserFolder($this->user1); |
|
| 179 | - $testFolder = $user1Folder->newFolder('test'); |
|
| 180 | - |
|
| 181 | - $shareManager = Server::get(\OCP\Share\IManager::class); |
|
| 182 | - $share = $shareManager->newShare(); |
|
| 183 | - |
|
| 184 | - $share->setNode($testFolder) |
|
| 185 | - ->setShareType(IShare::TYPE_USER) |
|
| 186 | - ->setPermissions(Constants::PERMISSION_READ) |
|
| 187 | - ->setSharedBy($this->user1) |
|
| 188 | - ->setSharedWith($this->user2); |
|
| 189 | - |
|
| 190 | - $shareManager->createShare($share); |
|
| 191 | - |
|
| 192 | - $shares = $this->getShares(); |
|
| 193 | - $this->assertCount(1, $shares); |
|
| 194 | - |
|
| 195 | - $this->logout(); |
|
| 196 | - |
|
| 197 | - $this->job->run([]); |
|
| 198 | - |
|
| 199 | - $shares = $this->getShares(); |
|
| 200 | - $this->assertCount(1, $shares); |
|
| 201 | - } |
|
| 29 | + /** @var ExpireSharesJob */ |
|
| 30 | + private $job; |
|
| 31 | + |
|
| 32 | + /** @var IDBConnection */ |
|
| 33 | + private $connection; |
|
| 34 | + |
|
| 35 | + /** @var string */ |
|
| 36 | + private $user1; |
|
| 37 | + |
|
| 38 | + /** @var string */ |
|
| 39 | + private $user2; |
|
| 40 | + |
|
| 41 | + protected function setUp(): void { |
|
| 42 | + parent::setUp(); |
|
| 43 | + |
|
| 44 | + $this->connection = Server::get(IDBConnection::class); |
|
| 45 | + // clear occasional leftover shares from other tests |
|
| 46 | + $qb = $this->connection->getQueryBuilder(); |
|
| 47 | + $qb->delete('share')->executeStatement(); |
|
| 48 | + |
|
| 49 | + $this->user1 = $this->getUniqueID('user1_'); |
|
| 50 | + $this->user2 = $this->getUniqueID('user2_'); |
|
| 51 | + |
|
| 52 | + $userManager = Server::get(IUserManager::class); |
|
| 53 | + $userManager->createUser($this->user1, 'longrandompassword'); |
|
| 54 | + $userManager->createUser($this->user2, 'longrandompassword'); |
|
| 55 | + |
|
| 56 | + \OC::registerShareHooks(Server::get(SystemConfig::class)); |
|
| 57 | + |
|
| 58 | + $this->job = new ExpireSharesJob(Server::get(ITimeFactory::class), Server::get(IManager::class), $this->connection); |
|
| 59 | + } |
|
| 60 | + |
|
| 61 | + protected function tearDown(): void { |
|
| 62 | + $qb = $this->connection->getQueryBuilder(); |
|
| 63 | + $qb->delete('share')->executeStatement(); |
|
| 64 | + |
|
| 65 | + $userManager = Server::get(IUserManager::class); |
|
| 66 | + $user1 = $userManager->get($this->user1); |
|
| 67 | + if ($user1) { |
|
| 68 | + $user1->delete(); |
|
| 69 | + } |
|
| 70 | + $user2 = $userManager->get($this->user2); |
|
| 71 | + if ($user2) { |
|
| 72 | + $user2->delete(); |
|
| 73 | + } |
|
| 74 | + |
|
| 75 | + $this->logout(); |
|
| 76 | + |
|
| 77 | + parent::tearDown(); |
|
| 78 | + } |
|
| 79 | + |
|
| 80 | + private function getShares() { |
|
| 81 | + $shares = []; |
|
| 82 | + $qb = $this->connection->getQueryBuilder(); |
|
| 83 | + |
|
| 84 | + $result = $qb->select('*') |
|
| 85 | + ->from('share') |
|
| 86 | + ->executeQuery(); |
|
| 87 | + |
|
| 88 | + while ($row = $result->fetchAssociative()) { |
|
| 89 | + $shares[] = $row; |
|
| 90 | + } |
|
| 91 | + $result->closeCursor(); |
|
| 92 | + return $shares; |
|
| 93 | + } |
|
| 94 | + |
|
| 95 | + public static function dataExpireLinkShare() { |
|
| 96 | + return [ |
|
| 97 | + [false, '', false, false], |
|
| 98 | + [false, '', true, false], |
|
| 99 | + [true, 'P1D', false, true], |
|
| 100 | + [true, 'P1D', true, false], |
|
| 101 | + [true, 'P1W', false, true], |
|
| 102 | + [true, 'P1W', true, false], |
|
| 103 | + [true, 'P1M', false, true], |
|
| 104 | + [true, 'P1M', true, false], |
|
| 105 | + [true, 'P1Y', false, true], |
|
| 106 | + [true, 'P1Y', true, false], |
|
| 107 | + ]; |
|
| 108 | + } |
|
| 109 | + |
|
| 110 | + /** |
|
| 111 | + * |
|
| 112 | + * @param bool addExpiration Should we add an expire date |
|
| 113 | + * @param string $interval The dateInterval |
|
| 114 | + * @param bool $addInterval If true add to the current time if false subtract |
|
| 115 | + * @param bool $shouldExpire Should this share be expired |
|
| 116 | + */ |
|
| 117 | + #[\PHPUnit\Framework\Attributes\DataProvider('dataExpireLinkShare')] |
|
| 118 | + public function testExpireLinkShare($addExpiration, $interval, $addInterval, $shouldExpire): void { |
|
| 119 | + $this->loginAsUser($this->user1); |
|
| 120 | + |
|
| 121 | + $user1Folder = \OC::$server->getUserFolder($this->user1); |
|
| 122 | + $testFolder = $user1Folder->newFolder('test'); |
|
| 123 | + |
|
| 124 | + $shareManager = Server::get(\OCP\Share\IManager::class); |
|
| 125 | + $share = $shareManager->newShare(); |
|
| 126 | + |
|
| 127 | + $share->setNode($testFolder) |
|
| 128 | + ->setShareType(IShare::TYPE_LINK) |
|
| 129 | + ->setPermissions(Constants::PERMISSION_READ) |
|
| 130 | + ->setSharedBy($this->user1); |
|
| 131 | + |
|
| 132 | + $shareManager->createShare($share); |
|
| 133 | + |
|
| 134 | + $shares = $this->getShares(); |
|
| 135 | + $this->assertCount(1, $shares); |
|
| 136 | + reset($shares); |
|
| 137 | + $share = current($shares); |
|
| 138 | + |
|
| 139 | + if ($addExpiration) { |
|
| 140 | + $expire = new \DateTime(); |
|
| 141 | + $expire->setTime(0, 0, 0); |
|
| 142 | + if ($addInterval) { |
|
| 143 | + $expire->add(new \DateInterval($interval)); |
|
| 144 | + } else { |
|
| 145 | + $expire->sub(new \DateInterval($interval)); |
|
| 146 | + } |
|
| 147 | + $expire = $expire->format('Y-m-d 00:00:00'); |
|
| 148 | + |
|
| 149 | + // Set expiration date to yesterday |
|
| 150 | + $qb = $this->connection->getQueryBuilder(); |
|
| 151 | + $qb->update('share') |
|
| 152 | + ->set('expiration', $qb->createParameter('expiration')) |
|
| 153 | + ->where($qb->expr()->eq('id', $qb->createParameter('id'))) |
|
| 154 | + ->setParameter('id', $share['id']) |
|
| 155 | + ->setParameter('expiration', $expire) |
|
| 156 | + ->executeStatement(); |
|
| 157 | + |
|
| 158 | + $shares = $this->getShares(); |
|
| 159 | + $this->assertCount(1, $shares); |
|
| 160 | + } |
|
| 161 | + |
|
| 162 | + $this->logout(); |
|
| 163 | + |
|
| 164 | + $this->job->run([]); |
|
| 165 | + |
|
| 166 | + $shares = $this->getShares(); |
|
| 167 | + |
|
| 168 | + if ($shouldExpire) { |
|
| 169 | + $this->assertCount(0, $shares); |
|
| 170 | + } else { |
|
| 171 | + $this->assertCount(1, $shares); |
|
| 172 | + } |
|
| 173 | + } |
|
| 174 | + |
|
| 175 | + public function testDoNotExpireOtherShares(): void { |
|
| 176 | + $this->loginAsUser($this->user1); |
|
| 177 | + |
|
| 178 | + $user1Folder = \OC::$server->getUserFolder($this->user1); |
|
| 179 | + $testFolder = $user1Folder->newFolder('test'); |
|
| 180 | + |
|
| 181 | + $shareManager = Server::get(\OCP\Share\IManager::class); |
|
| 182 | + $share = $shareManager->newShare(); |
|
| 183 | + |
|
| 184 | + $share->setNode($testFolder) |
|
| 185 | + ->setShareType(IShare::TYPE_USER) |
|
| 186 | + ->setPermissions(Constants::PERMISSION_READ) |
|
| 187 | + ->setSharedBy($this->user1) |
|
| 188 | + ->setSharedWith($this->user2); |
|
| 189 | + |
|
| 190 | + $shareManager->createShare($share); |
|
| 191 | + |
|
| 192 | + $shares = $this->getShares(); |
|
| 193 | + $this->assertCount(1, $shares); |
|
| 194 | + |
|
| 195 | + $this->logout(); |
|
| 196 | + |
|
| 197 | + $this->job->run([]); |
|
| 198 | + |
|
| 199 | + $shares = $this->getShares(); |
|
| 200 | + $this->assertCount(1, $shares); |
|
| 201 | + } |
|
| 202 | 202 | } |
@@ -21,87 +21,87 @@ |
||
| 21 | 21 | #[\PHPUnit\Framework\Attributes\Group('DB')] |
| 22 | 22 | class SetPasswordColumnTest extends TestCase { |
| 23 | 23 | |
| 24 | - /** @var IDBConnection */ |
|
| 25 | - private $connection; |
|
| 26 | - |
|
| 27 | - /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ |
|
| 28 | - private $config; |
|
| 29 | - |
|
| 30 | - /** @var SetPasswordColumn */ |
|
| 31 | - private $migration; |
|
| 32 | - |
|
| 33 | - private $table = 'share'; |
|
| 34 | - |
|
| 35 | - protected function setUp(): void { |
|
| 36 | - parent::setUp(); |
|
| 37 | - |
|
| 38 | - $this->connection = Server::get(IDBConnection::class); |
|
| 39 | - $this->config = $this->createMock(IConfig::class); |
|
| 40 | - $this->migration = new SetPasswordColumn($this->connection, $this->config); |
|
| 41 | - |
|
| 42 | - $this->cleanDB(); |
|
| 43 | - } |
|
| 44 | - |
|
| 45 | - protected function tearDown(): void { |
|
| 46 | - parent::tearDown(); |
|
| 47 | - $this->cleanDB(); |
|
| 48 | - } |
|
| 49 | - |
|
| 50 | - private function cleanDB() { |
|
| 51 | - $query = $this->connection->getQueryBuilder(); |
|
| 52 | - $query->delete($this->table)->executeStatement(); |
|
| 53 | - } |
|
| 54 | - |
|
| 55 | - public function testAddPasswordColumn(): void { |
|
| 56 | - $this->config->expects($this->once()) |
|
| 57 | - ->method('getAppValue') |
|
| 58 | - ->with('files_sharing', 'installed_version', '0.0.0') |
|
| 59 | - ->willReturn('1.3.0'); |
|
| 60 | - |
|
| 61 | - $shareTypes = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL, IShare::TYPE_LINK]; |
|
| 62 | - |
|
| 63 | - foreach ($shareTypes as $shareType) { |
|
| 64 | - for ($i = 0; $i < 5; $i++) { |
|
| 65 | - $query = $this->connection->getQueryBuilder(); |
|
| 66 | - $query->insert($this->table) |
|
| 67 | - ->values([ |
|
| 68 | - 'share_type' => $query->createNamedParameter($shareType), |
|
| 69 | - 'share_with' => $query->createNamedParameter('shareWith'), |
|
| 70 | - 'uid_owner' => $query->createNamedParameter('user' . $i), |
|
| 71 | - 'uid_initiator' => $query->createNamedParameter(null), |
|
| 72 | - 'parent' => $query->createNamedParameter(0), |
|
| 73 | - 'item_type' => $query->createNamedParameter('file'), |
|
| 74 | - 'item_source' => $query->createNamedParameter('2'), |
|
| 75 | - 'item_target' => $query->createNamedParameter('/2'), |
|
| 76 | - 'file_source' => $query->createNamedParameter(2), |
|
| 77 | - 'file_target' => $query->createNamedParameter('/foobar'), |
|
| 78 | - 'permissions' => $query->createNamedParameter(31), |
|
| 79 | - 'stime' => $query->createNamedParameter(time()), |
|
| 80 | - ]); |
|
| 81 | - |
|
| 82 | - $this->assertSame(1, $query->executeStatement()); |
|
| 83 | - } |
|
| 84 | - } |
|
| 85 | - |
|
| 86 | - /** @var IOutput $output */ |
|
| 87 | - $output = $this->createMock(IOutput::class); |
|
| 88 | - $this->migration->run($output); |
|
| 89 | - |
|
| 90 | - $query = $this->connection->getQueryBuilder(); |
|
| 91 | - $query->select('*') |
|
| 92 | - ->from('share'); |
|
| 93 | - $result = $query->executeQuery(); |
|
| 94 | - $allShares = $result->fetchAllAssociative(); |
|
| 95 | - $result->closeCursor(); |
|
| 96 | - |
|
| 97 | - foreach ($allShares as $share) { |
|
| 98 | - if ((int)$share['share_type'] === IShare::TYPE_LINK) { |
|
| 99 | - $this->assertNull($share['share_with']); |
|
| 100 | - $this->assertSame('shareWith', $share['password']); |
|
| 101 | - } else { |
|
| 102 | - $this->assertSame('shareWith', $share['share_with']); |
|
| 103 | - $this->assertNull($share['password']); |
|
| 104 | - } |
|
| 105 | - } |
|
| 106 | - } |
|
| 24 | + /** @var IDBConnection */ |
|
| 25 | + private $connection; |
|
| 26 | + |
|
| 27 | + /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ |
|
| 28 | + private $config; |
|
| 29 | + |
|
| 30 | + /** @var SetPasswordColumn */ |
|
| 31 | + private $migration; |
|
| 32 | + |
|
| 33 | + private $table = 'share'; |
|
| 34 | + |
|
| 35 | + protected function setUp(): void { |
|
| 36 | + parent::setUp(); |
|
| 37 | + |
|
| 38 | + $this->connection = Server::get(IDBConnection::class); |
|
| 39 | + $this->config = $this->createMock(IConfig::class); |
|
| 40 | + $this->migration = new SetPasswordColumn($this->connection, $this->config); |
|
| 41 | + |
|
| 42 | + $this->cleanDB(); |
|
| 43 | + } |
|
| 44 | + |
|
| 45 | + protected function tearDown(): void { |
|
| 46 | + parent::tearDown(); |
|
| 47 | + $this->cleanDB(); |
|
| 48 | + } |
|
| 49 | + |
|
| 50 | + private function cleanDB() { |
|
| 51 | + $query = $this->connection->getQueryBuilder(); |
|
| 52 | + $query->delete($this->table)->executeStatement(); |
|
| 53 | + } |
|
| 54 | + |
|
| 55 | + public function testAddPasswordColumn(): void { |
|
| 56 | + $this->config->expects($this->once()) |
|
| 57 | + ->method('getAppValue') |
|
| 58 | + ->with('files_sharing', 'installed_version', '0.0.0') |
|
| 59 | + ->willReturn('1.3.0'); |
|
| 60 | + |
|
| 61 | + $shareTypes = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL, IShare::TYPE_LINK]; |
|
| 62 | + |
|
| 63 | + foreach ($shareTypes as $shareType) { |
|
| 64 | + for ($i = 0; $i < 5; $i++) { |
|
| 65 | + $query = $this->connection->getQueryBuilder(); |
|
| 66 | + $query->insert($this->table) |
|
| 67 | + ->values([ |
|
| 68 | + 'share_type' => $query->createNamedParameter($shareType), |
|
| 69 | + 'share_with' => $query->createNamedParameter('shareWith'), |
|
| 70 | + 'uid_owner' => $query->createNamedParameter('user' . $i), |
|
| 71 | + 'uid_initiator' => $query->createNamedParameter(null), |
|
| 72 | + 'parent' => $query->createNamedParameter(0), |
|
| 73 | + 'item_type' => $query->createNamedParameter('file'), |
|
| 74 | + 'item_source' => $query->createNamedParameter('2'), |
|
| 75 | + 'item_target' => $query->createNamedParameter('/2'), |
|
| 76 | + 'file_source' => $query->createNamedParameter(2), |
|
| 77 | + 'file_target' => $query->createNamedParameter('/foobar'), |
|
| 78 | + 'permissions' => $query->createNamedParameter(31), |
|
| 79 | + 'stime' => $query->createNamedParameter(time()), |
|
| 80 | + ]); |
|
| 81 | + |
|
| 82 | + $this->assertSame(1, $query->executeStatement()); |
|
| 83 | + } |
|
| 84 | + } |
|
| 85 | + |
|
| 86 | + /** @var IOutput $output */ |
|
| 87 | + $output = $this->createMock(IOutput::class); |
|
| 88 | + $this->migration->run($output); |
|
| 89 | + |
|
| 90 | + $query = $this->connection->getQueryBuilder(); |
|
| 91 | + $query->select('*') |
|
| 92 | + ->from('share'); |
|
| 93 | + $result = $query->executeQuery(); |
|
| 94 | + $allShares = $result->fetchAllAssociative(); |
|
| 95 | + $result->closeCursor(); |
|
| 96 | + |
|
| 97 | + foreach ($allShares as $share) { |
|
| 98 | + if ((int)$share['share_type'] === IShare::TYPE_LINK) { |
|
| 99 | + $this->assertNull($share['share_with']); |
|
| 100 | + $this->assertSame('shareWith', $share['password']); |
|
| 101 | + } else { |
|
| 102 | + $this->assertSame('shareWith', $share['share_with']); |
|
| 103 | + $this->assertNull($share['password']); |
|
| 104 | + } |
|
| 105 | + } |
|
| 106 | + } |
|
| 107 | 107 | } |
@@ -33,275 +33,275 @@ |
||
| 33 | 33 | * Send a reminder via email to the sharee(s) if the folder is still empty a predefined time before the expiration date |
| 34 | 34 | */ |
| 35 | 35 | class SharesReminderJob extends TimedJob { |
| 36 | - private const SECONDS_BEFORE_REMINDER = 24 * 60 * 60; |
|
| 37 | - private const CHUNK_SIZE = 1000; |
|
| 38 | - private int $folderMimeTypeId; |
|
| 36 | + private const SECONDS_BEFORE_REMINDER = 24 * 60 * 60; |
|
| 37 | + private const CHUNK_SIZE = 1000; |
|
| 38 | + private int $folderMimeTypeId; |
|
| 39 | 39 | |
| 40 | - public function __construct( |
|
| 41 | - ITimeFactory $time, |
|
| 42 | - private readonly IDBConnection $db, |
|
| 43 | - private readonly IManager $shareManager, |
|
| 44 | - private readonly IUserManager $userManager, |
|
| 45 | - private readonly LoggerInterface $logger, |
|
| 46 | - private readonly IURLGenerator $urlGenerator, |
|
| 47 | - private readonly IFactory $l10nFactory, |
|
| 48 | - private readonly IMailer $mailer, |
|
| 49 | - private readonly Defaults $defaults, |
|
| 50 | - IMimeTypeLoader $mimeTypeLoader, |
|
| 51 | - ) { |
|
| 52 | - parent::__construct($time); |
|
| 53 | - $this->setInterval(60 * 60); |
|
| 54 | - $this->folderMimeTypeId = $mimeTypeLoader->getId(ICacheEntry::DIRECTORY_MIMETYPE); |
|
| 55 | - } |
|
| 40 | + public function __construct( |
|
| 41 | + ITimeFactory $time, |
|
| 42 | + private readonly IDBConnection $db, |
|
| 43 | + private readonly IManager $shareManager, |
|
| 44 | + private readonly IUserManager $userManager, |
|
| 45 | + private readonly LoggerInterface $logger, |
|
| 46 | + private readonly IURLGenerator $urlGenerator, |
|
| 47 | + private readonly IFactory $l10nFactory, |
|
| 48 | + private readonly IMailer $mailer, |
|
| 49 | + private readonly Defaults $defaults, |
|
| 50 | + IMimeTypeLoader $mimeTypeLoader, |
|
| 51 | + ) { |
|
| 52 | + parent::__construct($time); |
|
| 53 | + $this->setInterval(60 * 60); |
|
| 54 | + $this->folderMimeTypeId = $mimeTypeLoader->getId(ICacheEntry::DIRECTORY_MIMETYPE); |
|
| 55 | + } |
|
| 56 | 56 | |
| 57 | 57 | |
| 58 | - /** |
|
| 59 | - * Makes the background job do its work |
|
| 60 | - * |
|
| 61 | - * @param array $argument unused argument |
|
| 62 | - * @throws Exception if a database error occurs |
|
| 63 | - */ |
|
| 64 | - public function run(mixed $argument): void { |
|
| 65 | - foreach ($this->getShares() as $share) { |
|
| 66 | - $reminderInfo = $this->prepareReminder($share); |
|
| 67 | - if ($reminderInfo !== null) { |
|
| 68 | - $this->sendReminder($reminderInfo); |
|
| 69 | - } |
|
| 70 | - } |
|
| 71 | - } |
|
| 58 | + /** |
|
| 59 | + * Makes the background job do its work |
|
| 60 | + * |
|
| 61 | + * @param array $argument unused argument |
|
| 62 | + * @throws Exception if a database error occurs |
|
| 63 | + */ |
|
| 64 | + public function run(mixed $argument): void { |
|
| 65 | + foreach ($this->getShares() as $share) { |
|
| 66 | + $reminderInfo = $this->prepareReminder($share); |
|
| 67 | + if ($reminderInfo !== null) { |
|
| 68 | + $this->sendReminder($reminderInfo); |
|
| 69 | + } |
|
| 70 | + } |
|
| 71 | + } |
|
| 72 | 72 | |
| 73 | - /** |
|
| 74 | - * Finds all shares of empty folders, for which the user has write permissions. |
|
| 75 | - * The returned shares are of type user or email only, have expiration dates within the specified time frame |
|
| 76 | - * and have not yet received a reminder. |
|
| 77 | - * |
|
| 78 | - * @return array<IShare>|\Iterator |
|
| 79 | - * @throws Exception if a database error occurs |
|
| 80 | - */ |
|
| 81 | - private function getShares(): array|\Iterator { |
|
| 82 | - if ($this->db->getShardDefinition('filecache')) { |
|
| 83 | - $sharesResult = $this->getSharesDataSharded(); |
|
| 84 | - } else { |
|
| 85 | - $sharesResult = $this->getSharesData(); |
|
| 86 | - } |
|
| 87 | - foreach ($sharesResult as $share) { |
|
| 88 | - if ($share['share_type'] === IShare::TYPE_EMAIL) { |
|
| 89 | - $id = "ocMailShare:$share[id]"; |
|
| 90 | - } else { |
|
| 91 | - $id = "ocinternal:$share[id]"; |
|
| 92 | - } |
|
| 73 | + /** |
|
| 74 | + * Finds all shares of empty folders, for which the user has write permissions. |
|
| 75 | + * The returned shares are of type user or email only, have expiration dates within the specified time frame |
|
| 76 | + * and have not yet received a reminder. |
|
| 77 | + * |
|
| 78 | + * @return array<IShare>|\Iterator |
|
| 79 | + * @throws Exception if a database error occurs |
|
| 80 | + */ |
|
| 81 | + private function getShares(): array|\Iterator { |
|
| 82 | + if ($this->db->getShardDefinition('filecache')) { |
|
| 83 | + $sharesResult = $this->getSharesDataSharded(); |
|
| 84 | + } else { |
|
| 85 | + $sharesResult = $this->getSharesData(); |
|
| 86 | + } |
|
| 87 | + foreach ($sharesResult as $share) { |
|
| 88 | + if ($share['share_type'] === IShare::TYPE_EMAIL) { |
|
| 89 | + $id = "ocMailShare:$share[id]"; |
|
| 90 | + } else { |
|
| 91 | + $id = "ocinternal:$share[id]"; |
|
| 92 | + } |
|
| 93 | 93 | |
| 94 | - try { |
|
| 95 | - yield $this->shareManager->getShareById($id); |
|
| 96 | - } catch (ShareNotFound) { |
|
| 97 | - $this->logger->error("Share with ID $id not found."); |
|
| 98 | - } |
|
| 99 | - } |
|
| 100 | - } |
|
| 94 | + try { |
|
| 95 | + yield $this->shareManager->getShareById($id); |
|
| 96 | + } catch (ShareNotFound) { |
|
| 97 | + $this->logger->error("Share with ID $id not found."); |
|
| 98 | + } |
|
| 99 | + } |
|
| 100 | + } |
|
| 101 | 101 | |
| 102 | - /** |
|
| 103 | - * @return list<array{id: int, share_type: int}> |
|
| 104 | - */ |
|
| 105 | - private function getSharesData(): array { |
|
| 106 | - $minDate = new \DateTime(); |
|
| 107 | - $maxDate = new \DateTime(); |
|
| 108 | - $maxDate->setTimestamp($maxDate->getTimestamp() + self::SECONDS_BEFORE_REMINDER); |
|
| 102 | + /** |
|
| 103 | + * @return list<array{id: int, share_type: int}> |
|
| 104 | + */ |
|
| 105 | + private function getSharesData(): array { |
|
| 106 | + $minDate = new \DateTime(); |
|
| 107 | + $maxDate = new \DateTime(); |
|
| 108 | + $maxDate->setTimestamp($maxDate->getTimestamp() + self::SECONDS_BEFORE_REMINDER); |
|
| 109 | 109 | |
| 110 | - $qb = $this->db->getQueryBuilder(); |
|
| 111 | - $qb->select('s.id', 's.share_type') |
|
| 112 | - ->from('share', 's') |
|
| 113 | - ->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('f.parent', 's.file_source')) |
|
| 114 | - ->where( |
|
| 115 | - $qb->expr()->andX( |
|
| 116 | - $qb->expr()->orX( |
|
| 117 | - $qb->expr()->eq('s.share_type', $qb->expr()->literal(IShare::TYPE_USER)), |
|
| 118 | - $qb->expr()->eq('s.share_type', $qb->expr()->literal(IShare::TYPE_EMAIL)) |
|
| 119 | - ), |
|
| 120 | - $qb->expr()->eq('s.item_type', $qb->expr()->literal('folder')), |
|
| 121 | - $qb->expr()->gte('s.expiration', $qb->createNamedParameter($minDate, IQueryBuilder::PARAM_DATE)), |
|
| 122 | - $qb->expr()->lte('s.expiration', $qb->createNamedParameter($maxDate, IQueryBuilder::PARAM_DATE)), |
|
| 123 | - $qb->expr()->eq('s.reminder_sent', $qb->createNamedParameter( |
|
| 124 | - false, IQueryBuilder::PARAM_BOOL |
|
| 125 | - )), |
|
| 126 | - $qb->expr()->eq( |
|
| 127 | - $qb->expr()->bitwiseAnd('s.permissions', Constants::PERMISSION_CREATE), |
|
| 128 | - $qb->createNamedParameter(Constants::PERMISSION_CREATE, IQueryBuilder::PARAM_INT) |
|
| 129 | - ), |
|
| 130 | - $qb->expr()->isNull('f.fileid') |
|
| 131 | - ) |
|
| 132 | - ) |
|
| 133 | - ->setMaxResults(SharesReminderJob::CHUNK_SIZE); |
|
| 110 | + $qb = $this->db->getQueryBuilder(); |
|
| 111 | + $qb->select('s.id', 's.share_type') |
|
| 112 | + ->from('share', 's') |
|
| 113 | + ->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('f.parent', 's.file_source')) |
|
| 114 | + ->where( |
|
| 115 | + $qb->expr()->andX( |
|
| 116 | + $qb->expr()->orX( |
|
| 117 | + $qb->expr()->eq('s.share_type', $qb->expr()->literal(IShare::TYPE_USER)), |
|
| 118 | + $qb->expr()->eq('s.share_type', $qb->expr()->literal(IShare::TYPE_EMAIL)) |
|
| 119 | + ), |
|
| 120 | + $qb->expr()->eq('s.item_type', $qb->expr()->literal('folder')), |
|
| 121 | + $qb->expr()->gte('s.expiration', $qb->createNamedParameter($minDate, IQueryBuilder::PARAM_DATE)), |
|
| 122 | + $qb->expr()->lte('s.expiration', $qb->createNamedParameter($maxDate, IQueryBuilder::PARAM_DATE)), |
|
| 123 | + $qb->expr()->eq('s.reminder_sent', $qb->createNamedParameter( |
|
| 124 | + false, IQueryBuilder::PARAM_BOOL |
|
| 125 | + )), |
|
| 126 | + $qb->expr()->eq( |
|
| 127 | + $qb->expr()->bitwiseAnd('s.permissions', Constants::PERMISSION_CREATE), |
|
| 128 | + $qb->createNamedParameter(Constants::PERMISSION_CREATE, IQueryBuilder::PARAM_INT) |
|
| 129 | + ), |
|
| 130 | + $qb->expr()->isNull('f.fileid') |
|
| 131 | + ) |
|
| 132 | + ) |
|
| 133 | + ->setMaxResults(SharesReminderJob::CHUNK_SIZE); |
|
| 134 | 134 | |
| 135 | - $shares = $qb->executeQuery()->fetchAllAssociative(); |
|
| 136 | - return array_map(fn ($share): array => [ |
|
| 137 | - 'id' => (int)$share['id'], |
|
| 138 | - 'share_type' => (int)$share['share_type'], |
|
| 139 | - ], $shares); |
|
| 140 | - } |
|
| 135 | + $shares = $qb->executeQuery()->fetchAllAssociative(); |
|
| 136 | + return array_map(fn ($share): array => [ |
|
| 137 | + 'id' => (int)$share['id'], |
|
| 138 | + 'share_type' => (int)$share['share_type'], |
|
| 139 | + ], $shares); |
|
| 140 | + } |
|
| 141 | 141 | |
| 142 | - /** |
|
| 143 | - * Sharding compatible version of getSharesData |
|
| 144 | - * |
|
| 145 | - * @return list<array{id: int, share_type: int, file_source: int}> |
|
| 146 | - */ |
|
| 147 | - private function getSharesDataSharded(): array|\Iterator { |
|
| 148 | - $minDate = new \DateTime(); |
|
| 149 | - $maxDate = new \DateTime(); |
|
| 150 | - $maxDate->setTimestamp($maxDate->getTimestamp() + self::SECONDS_BEFORE_REMINDER); |
|
| 142 | + /** |
|
| 143 | + * Sharding compatible version of getSharesData |
|
| 144 | + * |
|
| 145 | + * @return list<array{id: int, share_type: int, file_source: int}> |
|
| 146 | + */ |
|
| 147 | + private function getSharesDataSharded(): array|\Iterator { |
|
| 148 | + $minDate = new \DateTime(); |
|
| 149 | + $maxDate = new \DateTime(); |
|
| 150 | + $maxDate->setTimestamp($maxDate->getTimestamp() + self::SECONDS_BEFORE_REMINDER); |
|
| 151 | 151 | |
| 152 | - $qb = $this->db->getQueryBuilder(); |
|
| 153 | - $qb->select('s.id', 's.share_type', 's.file_source') |
|
| 154 | - ->from('share', 's') |
|
| 155 | - ->where( |
|
| 156 | - $qb->expr()->andX( |
|
| 157 | - $qb->expr()->orX( |
|
| 158 | - $qb->expr()->eq('s.share_type', $qb->expr()->literal(IShare::TYPE_USER)), |
|
| 159 | - $qb->expr()->eq('s.share_type', $qb->expr()->literal(IShare::TYPE_EMAIL)) |
|
| 160 | - ), |
|
| 161 | - $qb->expr()->eq('s.item_type', $qb->expr()->literal('folder')), |
|
| 162 | - $qb->expr()->gte('s.expiration', $qb->createNamedParameter($minDate, IQueryBuilder::PARAM_DATE)), |
|
| 163 | - $qb->expr()->lte('s.expiration', $qb->createNamedParameter($maxDate, IQueryBuilder::PARAM_DATE)), |
|
| 164 | - $qb->expr()->eq('s.reminder_sent', $qb->createNamedParameter( |
|
| 165 | - false, IQueryBuilder::PARAM_BOOL |
|
| 166 | - )), |
|
| 167 | - $qb->expr()->eq( |
|
| 168 | - $qb->expr()->bitwiseAnd('s.permissions', Constants::PERMISSION_CREATE), |
|
| 169 | - $qb->createNamedParameter(Constants::PERMISSION_CREATE, IQueryBuilder::PARAM_INT) |
|
| 170 | - ), |
|
| 171 | - ) |
|
| 172 | - ); |
|
| 152 | + $qb = $this->db->getQueryBuilder(); |
|
| 153 | + $qb->select('s.id', 's.share_type', 's.file_source') |
|
| 154 | + ->from('share', 's') |
|
| 155 | + ->where( |
|
| 156 | + $qb->expr()->andX( |
|
| 157 | + $qb->expr()->orX( |
|
| 158 | + $qb->expr()->eq('s.share_type', $qb->expr()->literal(IShare::TYPE_USER)), |
|
| 159 | + $qb->expr()->eq('s.share_type', $qb->expr()->literal(IShare::TYPE_EMAIL)) |
|
| 160 | + ), |
|
| 161 | + $qb->expr()->eq('s.item_type', $qb->expr()->literal('folder')), |
|
| 162 | + $qb->expr()->gte('s.expiration', $qb->createNamedParameter($minDate, IQueryBuilder::PARAM_DATE)), |
|
| 163 | + $qb->expr()->lte('s.expiration', $qb->createNamedParameter($maxDate, IQueryBuilder::PARAM_DATE)), |
|
| 164 | + $qb->expr()->eq('s.reminder_sent', $qb->createNamedParameter( |
|
| 165 | + false, IQueryBuilder::PARAM_BOOL |
|
| 166 | + )), |
|
| 167 | + $qb->expr()->eq( |
|
| 168 | + $qb->expr()->bitwiseAnd('s.permissions', Constants::PERMISSION_CREATE), |
|
| 169 | + $qb->createNamedParameter(Constants::PERMISSION_CREATE, IQueryBuilder::PARAM_INT) |
|
| 170 | + ), |
|
| 171 | + ) |
|
| 172 | + ); |
|
| 173 | 173 | |
| 174 | - $shares = $qb->executeQuery()->fetchAllAssociative(); |
|
| 175 | - $shares = array_map(fn ($share): array => [ |
|
| 176 | - 'id' => (int)$share['id'], |
|
| 177 | - 'share_type' => (int)$share['share_type'], |
|
| 178 | - 'file_source' => (int)$share['file_source'], |
|
| 179 | - ], $shares); |
|
| 180 | - return $this->filterSharesWithEmptyFolders($shares, self::CHUNK_SIZE); |
|
| 181 | - } |
|
| 174 | + $shares = $qb->executeQuery()->fetchAllAssociative(); |
|
| 175 | + $shares = array_map(fn ($share): array => [ |
|
| 176 | + 'id' => (int)$share['id'], |
|
| 177 | + 'share_type' => (int)$share['share_type'], |
|
| 178 | + 'file_source' => (int)$share['file_source'], |
|
| 179 | + ], $shares); |
|
| 180 | + return $this->filterSharesWithEmptyFolders($shares, self::CHUNK_SIZE); |
|
| 181 | + } |
|
| 182 | 182 | |
| 183 | - /** |
|
| 184 | - * Check which of the supplied file ids is an empty folder until there are `$maxResults` folders |
|
| 185 | - * @param list<array{id: int, share_type: int, file_source: int}> $shares |
|
| 186 | - * @return list<array{id: int, share_type: int, file_source: int}> |
|
| 187 | - */ |
|
| 188 | - private function filterSharesWithEmptyFolders(array $shares, int $maxResults): array { |
|
| 189 | - $query = $this->db->getQueryBuilder(); |
|
| 190 | - $query->select('fileid') |
|
| 191 | - ->from('filecache') |
|
| 192 | - ->where($query->expr()->eq('size', $query->createNamedParameter(0), IQueryBuilder::PARAM_INT_ARRAY)) |
|
| 193 | - ->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($this->folderMimeTypeId, IQueryBuilder::PARAM_INT))) |
|
| 194 | - ->andWhere($query->expr()->in('fileid', $query->createParameter('fileids'))); |
|
| 195 | - $chunks = array_chunk($shares, SharesReminderJob::CHUNK_SIZE); |
|
| 196 | - $results = []; |
|
| 197 | - foreach ($chunks as $chunk) { |
|
| 198 | - $chunkFileIds = array_map(fn ($share): int => $share['file_source'], $chunk); |
|
| 199 | - $chunkByFileId = array_combine($chunkFileIds, $chunk); |
|
| 200 | - $query->setParameter('fileids', $chunkFileIds, IQueryBuilder::PARAM_INT_ARRAY); |
|
| 201 | - $chunkResults = $query->executeQuery()->fetchFirstColumn(); |
|
| 202 | - foreach ($chunkResults as $folderId) { |
|
| 203 | - $results[] = $chunkByFileId[$folderId]; |
|
| 204 | - } |
|
| 205 | - if (count($results) >= $maxResults) { |
|
| 206 | - break; |
|
| 207 | - } |
|
| 208 | - } |
|
| 209 | - return $results; |
|
| 210 | - } |
|
| 183 | + /** |
|
| 184 | + * Check which of the supplied file ids is an empty folder until there are `$maxResults` folders |
|
| 185 | + * @param list<array{id: int, share_type: int, file_source: int}> $shares |
|
| 186 | + * @return list<array{id: int, share_type: int, file_source: int}> |
|
| 187 | + */ |
|
| 188 | + private function filterSharesWithEmptyFolders(array $shares, int $maxResults): array { |
|
| 189 | + $query = $this->db->getQueryBuilder(); |
|
| 190 | + $query->select('fileid') |
|
| 191 | + ->from('filecache') |
|
| 192 | + ->where($query->expr()->eq('size', $query->createNamedParameter(0), IQueryBuilder::PARAM_INT_ARRAY)) |
|
| 193 | + ->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($this->folderMimeTypeId, IQueryBuilder::PARAM_INT))) |
|
| 194 | + ->andWhere($query->expr()->in('fileid', $query->createParameter('fileids'))); |
|
| 195 | + $chunks = array_chunk($shares, SharesReminderJob::CHUNK_SIZE); |
|
| 196 | + $results = []; |
|
| 197 | + foreach ($chunks as $chunk) { |
|
| 198 | + $chunkFileIds = array_map(fn ($share): int => $share['file_source'], $chunk); |
|
| 199 | + $chunkByFileId = array_combine($chunkFileIds, $chunk); |
|
| 200 | + $query->setParameter('fileids', $chunkFileIds, IQueryBuilder::PARAM_INT_ARRAY); |
|
| 201 | + $chunkResults = $query->executeQuery()->fetchFirstColumn(); |
|
| 202 | + foreach ($chunkResults as $folderId) { |
|
| 203 | + $results[] = $chunkByFileId[$folderId]; |
|
| 204 | + } |
|
| 205 | + if (count($results) >= $maxResults) { |
|
| 206 | + break; |
|
| 207 | + } |
|
| 208 | + } |
|
| 209 | + return $results; |
|
| 210 | + } |
|
| 211 | 211 | |
| 212 | - /** |
|
| 213 | - * Retrieves and returns all the necessary data before sending a reminder. |
|
| 214 | - * It also updates the reminder sent flag for the affected shares (to avoid multiple reminders). |
|
| 215 | - * |
|
| 216 | - * @param IShare $share Share that was obtained with {@link getShares} |
|
| 217 | - * @return array|null Info needed to send a reminder |
|
| 218 | - */ |
|
| 219 | - private function prepareReminder(IShare $share): ?array { |
|
| 220 | - $sharedWith = $share->getSharedWith(); |
|
| 221 | - $reminderInfo = []; |
|
| 222 | - if ($share->getShareType() == IShare::TYPE_USER) { |
|
| 223 | - $user = $this->userManager->get($sharedWith); |
|
| 224 | - if ($user === null) { |
|
| 225 | - return null; |
|
| 226 | - } |
|
| 227 | - $reminderInfo['email'] = $user->getEMailAddress(); |
|
| 228 | - $reminderInfo['userLang'] = $this->l10nFactory->getUserLanguage($user); |
|
| 229 | - $reminderInfo['folderLink'] = $this->urlGenerator->linkToRouteAbsolute('files.view.index', [ |
|
| 230 | - 'dir' => $share->getTarget() |
|
| 231 | - ]); |
|
| 232 | - } else { |
|
| 233 | - $reminderInfo['email'] = $sharedWith; |
|
| 234 | - $reminderInfo['folderLink'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', [ |
|
| 235 | - 'token' => $share->getToken() |
|
| 236 | - ]); |
|
| 237 | - } |
|
| 238 | - if (empty($reminderInfo['email'])) { |
|
| 239 | - return null; |
|
| 240 | - } |
|
| 212 | + /** |
|
| 213 | + * Retrieves and returns all the necessary data before sending a reminder. |
|
| 214 | + * It also updates the reminder sent flag for the affected shares (to avoid multiple reminders). |
|
| 215 | + * |
|
| 216 | + * @param IShare $share Share that was obtained with {@link getShares} |
|
| 217 | + * @return array|null Info needed to send a reminder |
|
| 218 | + */ |
|
| 219 | + private function prepareReminder(IShare $share): ?array { |
|
| 220 | + $sharedWith = $share->getSharedWith(); |
|
| 221 | + $reminderInfo = []; |
|
| 222 | + if ($share->getShareType() == IShare::TYPE_USER) { |
|
| 223 | + $user = $this->userManager->get($sharedWith); |
|
| 224 | + if ($user === null) { |
|
| 225 | + return null; |
|
| 226 | + } |
|
| 227 | + $reminderInfo['email'] = $user->getEMailAddress(); |
|
| 228 | + $reminderInfo['userLang'] = $this->l10nFactory->getUserLanguage($user); |
|
| 229 | + $reminderInfo['folderLink'] = $this->urlGenerator->linkToRouteAbsolute('files.view.index', [ |
|
| 230 | + 'dir' => $share->getTarget() |
|
| 231 | + ]); |
|
| 232 | + } else { |
|
| 233 | + $reminderInfo['email'] = $sharedWith; |
|
| 234 | + $reminderInfo['folderLink'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', [ |
|
| 235 | + 'token' => $share->getToken() |
|
| 236 | + ]); |
|
| 237 | + } |
|
| 238 | + if (empty($reminderInfo['email'])) { |
|
| 239 | + return null; |
|
| 240 | + } |
|
| 241 | 241 | |
| 242 | - try { |
|
| 243 | - $reminderInfo['folderName'] = $share->getNode()->getName(); |
|
| 244 | - } catch (NotFoundException) { |
|
| 245 | - $id = $share->getFullId(); |
|
| 246 | - $this->logger->error("File by share ID $id not found."); |
|
| 247 | - } |
|
| 248 | - $share->setReminderSent(true); |
|
| 249 | - $this->shareManager->updateShare($share); |
|
| 250 | - return $reminderInfo; |
|
| 251 | - } |
|
| 242 | + try { |
|
| 243 | + $reminderInfo['folderName'] = $share->getNode()->getName(); |
|
| 244 | + } catch (NotFoundException) { |
|
| 245 | + $id = $share->getFullId(); |
|
| 246 | + $this->logger->error("File by share ID $id not found."); |
|
| 247 | + } |
|
| 248 | + $share->setReminderSent(true); |
|
| 249 | + $this->shareManager->updateShare($share); |
|
| 250 | + return $reminderInfo; |
|
| 251 | + } |
|
| 252 | 252 | |
| 253 | - /** |
|
| 254 | - * This method accepts data obtained by {@link prepareReminder} and sends reminder email. |
|
| 255 | - * |
|
| 256 | - * @param array $reminderInfo |
|
| 257 | - * @return void |
|
| 258 | - */ |
|
| 259 | - private function sendReminder(array $reminderInfo): void { |
|
| 260 | - $instanceName = $this->defaults->getName(); |
|
| 261 | - $from = [Util::getDefaultEmailAddress($instanceName) => $instanceName]; |
|
| 262 | - $l = $this->l10nFactory->get('files_sharing', $reminderInfo['userLang'] ?? null); |
|
| 263 | - $emailTemplate = $this->generateEMailTemplate($l, [ |
|
| 264 | - 'link' => $reminderInfo['folderLink'], 'name' => $reminderInfo['folderName'] |
|
| 265 | - ]); |
|
| 253 | + /** |
|
| 254 | + * This method accepts data obtained by {@link prepareReminder} and sends reminder email. |
|
| 255 | + * |
|
| 256 | + * @param array $reminderInfo |
|
| 257 | + * @return void |
|
| 258 | + */ |
|
| 259 | + private function sendReminder(array $reminderInfo): void { |
|
| 260 | + $instanceName = $this->defaults->getName(); |
|
| 261 | + $from = [Util::getDefaultEmailAddress($instanceName) => $instanceName]; |
|
| 262 | + $l = $this->l10nFactory->get('files_sharing', $reminderInfo['userLang'] ?? null); |
|
| 263 | + $emailTemplate = $this->generateEMailTemplate($l, [ |
|
| 264 | + 'link' => $reminderInfo['folderLink'], 'name' => $reminderInfo['folderName'] |
|
| 265 | + ]); |
|
| 266 | 266 | |
| 267 | - $message = $this->mailer->createMessage(); |
|
| 268 | - $message->setFrom($from); |
|
| 269 | - $message->setTo([$reminderInfo['email']]); |
|
| 270 | - $message->useTemplate($emailTemplate); |
|
| 271 | - $errorText = "Sending email with share reminder to $reminderInfo[email] failed."; |
|
| 272 | - try { |
|
| 273 | - $failedRecipients = $this->mailer->send($message); |
|
| 274 | - if (count($failedRecipients) > 0) { |
|
| 275 | - $this->logger->error($errorText); |
|
| 276 | - } |
|
| 277 | - } catch (\Exception) { |
|
| 278 | - $this->logger->error($errorText); |
|
| 279 | - } |
|
| 280 | - } |
|
| 267 | + $message = $this->mailer->createMessage(); |
|
| 268 | + $message->setFrom($from); |
|
| 269 | + $message->setTo([$reminderInfo['email']]); |
|
| 270 | + $message->useTemplate($emailTemplate); |
|
| 271 | + $errorText = "Sending email with share reminder to $reminderInfo[email] failed."; |
|
| 272 | + try { |
|
| 273 | + $failedRecipients = $this->mailer->send($message); |
|
| 274 | + if (count($failedRecipients) > 0) { |
|
| 275 | + $this->logger->error($errorText); |
|
| 276 | + } |
|
| 277 | + } catch (\Exception) { |
|
| 278 | + $this->logger->error($errorText); |
|
| 279 | + } |
|
| 280 | + } |
|
| 281 | 281 | |
| 282 | - /** |
|
| 283 | - * Returns the reminder email template |
|
| 284 | - * |
|
| 285 | - * @param IL10N $l |
|
| 286 | - * @param array $folder Folder the user should be reminded of |
|
| 287 | - * @return IEMailTemplate |
|
| 288 | - */ |
|
| 289 | - private function generateEMailTemplate(IL10N $l, array $folder): IEMailTemplate { |
|
| 290 | - $emailTemplate = $this->mailer->createEMailTemplate('files_sharing.SharesReminder', [ |
|
| 291 | - 'folder' => $folder, |
|
| 292 | - ]); |
|
| 293 | - $emailTemplate->addHeader(); |
|
| 294 | - $emailTemplate->setSubject( |
|
| 295 | - $l->t('Remember to upload the files to %s', [$folder['name']]) |
|
| 296 | - ); |
|
| 297 | - $emailTemplate->addBodyText($l->t( |
|
| 298 | - 'We would like to kindly remind you that you have not yet uploaded any files to the shared folder.' |
|
| 299 | - )); |
|
| 300 | - $emailTemplate->addBodyButton( |
|
| 301 | - $l->t('Open "%s"', [$folder['name']]), |
|
| 302 | - $folder['link'] |
|
| 303 | - ); |
|
| 304 | - $emailTemplate->addFooter(); |
|
| 305 | - return $emailTemplate; |
|
| 306 | - } |
|
| 282 | + /** |
|
| 283 | + * Returns the reminder email template |
|
| 284 | + * |
|
| 285 | + * @param IL10N $l |
|
| 286 | + * @param array $folder Folder the user should be reminded of |
|
| 287 | + * @return IEMailTemplate |
|
| 288 | + */ |
|
| 289 | + private function generateEMailTemplate(IL10N $l, array $folder): IEMailTemplate { |
|
| 290 | + $emailTemplate = $this->mailer->createEMailTemplate('files_sharing.SharesReminder', [ |
|
| 291 | + 'folder' => $folder, |
|
| 292 | + ]); |
|
| 293 | + $emailTemplate->addHeader(); |
|
| 294 | + $emailTemplate->setSubject( |
|
| 295 | + $l->t('Remember to upload the files to %s', [$folder['name']]) |
|
| 296 | + ); |
|
| 297 | + $emailTemplate->addBodyText($l->t( |
|
| 298 | + 'We would like to kindly remind you that you have not yet uploaded any files to the shared folder.' |
|
| 299 | + )); |
|
| 300 | + $emailTemplate->addBodyButton( |
|
| 301 | + $l->t('Open "%s"', [$folder['name']]), |
|
| 302 | + $folder['link'] |
|
| 303 | + ); |
|
| 304 | + $emailTemplate->addFooter(); |
|
| 305 | + return $emailTemplate; |
|
| 306 | + } |
|
| 307 | 307 | } |
@@ -78,7 +78,7 @@ discard block |
||
| 78 | 78 | * @return array<IShare>|\Iterator |
| 79 | 79 | * @throws Exception if a database error occurs |
| 80 | 80 | */ |
| 81 | - private function getShares(): array|\Iterator { |
|
| 81 | + private function getShares(): array | \Iterator { |
|
| 82 | 82 | if ($this->db->getShardDefinition('filecache')) { |
| 83 | 83 | $sharesResult = $this->getSharesDataSharded(); |
| 84 | 84 | } else { |
@@ -134,8 +134,8 @@ discard block |
||
| 134 | 134 | |
| 135 | 135 | $shares = $qb->executeQuery()->fetchAllAssociative(); |
| 136 | 136 | return array_map(fn ($share): array => [ |
| 137 | - 'id' => (int)$share['id'], |
|
| 138 | - 'share_type' => (int)$share['share_type'], |
|
| 137 | + 'id' => (int) $share['id'], |
|
| 138 | + 'share_type' => (int) $share['share_type'], |
|
| 139 | 139 | ], $shares); |
| 140 | 140 | } |
| 141 | 141 | |
@@ -144,7 +144,7 @@ discard block |
||
| 144 | 144 | * |
| 145 | 145 | * @return list<array{id: int, share_type: int, file_source: int}> |
| 146 | 146 | */ |
| 147 | - private function getSharesDataSharded(): array|\Iterator { |
|
| 147 | + private function getSharesDataSharded(): array | \Iterator { |
|
| 148 | 148 | $minDate = new \DateTime(); |
| 149 | 149 | $maxDate = new \DateTime(); |
| 150 | 150 | $maxDate->setTimestamp($maxDate->getTimestamp() + self::SECONDS_BEFORE_REMINDER); |
@@ -173,9 +173,9 @@ discard block |
||
| 173 | 173 | |
| 174 | 174 | $shares = $qb->executeQuery()->fetchAllAssociative(); |
| 175 | 175 | $shares = array_map(fn ($share): array => [ |
| 176 | - 'id' => (int)$share['id'], |
|
| 177 | - 'share_type' => (int)$share['share_type'], |
|
| 178 | - 'file_source' => (int)$share['file_source'], |
|
| 176 | + 'id' => (int) $share['id'], |
|
| 177 | + 'share_type' => (int) $share['share_type'], |
|
| 178 | + 'file_source' => (int) $share['file_source'], |
|
| 179 | 179 | ], $shares); |
| 180 | 180 | return $this->filterSharesWithEmptyFolders($shares, self::CHUNK_SIZE); |
| 181 | 181 | } |
@@ -15,88 +15,88 @@ |
||
| 15 | 15 | use OCP\IDBConnection; |
| 16 | 16 | |
| 17 | 17 | class OrphanHelper { |
| 18 | - public function __construct( |
|
| 19 | - private IDBConnection $connection, |
|
| 20 | - private IRootFolder $rootFolder, |
|
| 21 | - private IUserMountCache $userMountCache, |
|
| 22 | - ) { |
|
| 23 | - } |
|
| 18 | + public function __construct( |
|
| 19 | + private IDBConnection $connection, |
|
| 20 | + private IRootFolder $rootFolder, |
|
| 21 | + private IUserMountCache $userMountCache, |
|
| 22 | + ) { |
|
| 23 | + } |
|
| 24 | 24 | |
| 25 | - public function isShareValid(string $owner, int $fileId): bool { |
|
| 26 | - try { |
|
| 27 | - $userFolder = $this->rootFolder->getUserFolder($owner); |
|
| 28 | - } catch (NoUserException $e) { |
|
| 29 | - return false; |
|
| 30 | - } |
|
| 31 | - $node = $userFolder->getFirstNodeById($fileId); |
|
| 32 | - return $node !== null; |
|
| 33 | - } |
|
| 25 | + public function isShareValid(string $owner, int $fileId): bool { |
|
| 26 | + try { |
|
| 27 | + $userFolder = $this->rootFolder->getUserFolder($owner); |
|
| 28 | + } catch (NoUserException $e) { |
|
| 29 | + return false; |
|
| 30 | + } |
|
| 31 | + $node = $userFolder->getFirstNodeById($fileId); |
|
| 32 | + return $node !== null; |
|
| 33 | + } |
|
| 34 | 34 | |
| 35 | - /** |
|
| 36 | - * @param int[] $ids |
|
| 37 | - * @return void |
|
| 38 | - */ |
|
| 39 | - public function deleteShares(array $ids): void { |
|
| 40 | - $query = $this->connection->getQueryBuilder(); |
|
| 41 | - $query->delete('share') |
|
| 42 | - ->where($query->expr()->in('id', $query->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))); |
|
| 43 | - $query->executeStatement(); |
|
| 44 | - } |
|
| 35 | + /** |
|
| 36 | + * @param int[] $ids |
|
| 37 | + * @return void |
|
| 38 | + */ |
|
| 39 | + public function deleteShares(array $ids): void { |
|
| 40 | + $query = $this->connection->getQueryBuilder(); |
|
| 41 | + $query->delete('share') |
|
| 42 | + ->where($query->expr()->in('id', $query->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))); |
|
| 43 | + $query->executeStatement(); |
|
| 44 | + } |
|
| 45 | 45 | |
| 46 | - public function fileExists(int $fileId): bool { |
|
| 47 | - $query = $this->connection->getQueryBuilder(); |
|
| 48 | - $query->select('fileid') |
|
| 49 | - ->from('filecache') |
|
| 50 | - ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); |
|
| 51 | - return $query->executeQuery()->fetchOne() !== false; |
|
| 52 | - } |
|
| 46 | + public function fileExists(int $fileId): bool { |
|
| 47 | + $query = $this->connection->getQueryBuilder(); |
|
| 48 | + $query->select('fileid') |
|
| 49 | + ->from('filecache') |
|
| 50 | + ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); |
|
| 51 | + return $query->executeQuery()->fetchOne() !== false; |
|
| 52 | + } |
|
| 53 | 53 | |
| 54 | - /** |
|
| 55 | - * @return \Traversable<int, array{id: int, owner: string, fileid: int, target: string}> |
|
| 56 | - */ |
|
| 57 | - public function getAllShares(?string $owner = null, ?string $with = null) { |
|
| 58 | - $query = $this->connection->getQueryBuilder(); |
|
| 59 | - $query->select('id', 'file_source', 'uid_owner', 'file_target') |
|
| 60 | - ->from('share') |
|
| 61 | - ->where($query->expr()->in('item_type', $query->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY))); |
|
| 54 | + /** |
|
| 55 | + * @return \Traversable<int, array{id: int, owner: string, fileid: int, target: string}> |
|
| 56 | + */ |
|
| 57 | + public function getAllShares(?string $owner = null, ?string $with = null) { |
|
| 58 | + $query = $this->connection->getQueryBuilder(); |
|
| 59 | + $query->select('id', 'file_source', 'uid_owner', 'file_target') |
|
| 60 | + ->from('share') |
|
| 61 | + ->where($query->expr()->in('item_type', $query->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY))); |
|
| 62 | 62 | |
| 63 | - if ($owner !== null) { |
|
| 64 | - $query->andWhere($query->expr()->eq('uid_owner', $query->createNamedParameter($owner))); |
|
| 65 | - } |
|
| 66 | - if ($with !== null) { |
|
| 67 | - $query->andWhere($query->expr()->eq('share_with', $query->createNamedParameter($with))); |
|
| 68 | - } |
|
| 63 | + if ($owner !== null) { |
|
| 64 | + $query->andWhere($query->expr()->eq('uid_owner', $query->createNamedParameter($owner))); |
|
| 65 | + } |
|
| 66 | + if ($with !== null) { |
|
| 67 | + $query->andWhere($query->expr()->eq('share_with', $query->createNamedParameter($with))); |
|
| 68 | + } |
|
| 69 | 69 | |
| 70 | - $result = $query->executeQuery(); |
|
| 71 | - while ($row = $result->fetchAssociative()) { |
|
| 72 | - yield [ |
|
| 73 | - 'id' => (int)$row['id'], |
|
| 74 | - 'owner' => (string)$row['uid_owner'], |
|
| 75 | - 'fileid' => (int)$row['file_source'], |
|
| 76 | - 'target' => (string)$row['file_target'], |
|
| 77 | - ]; |
|
| 78 | - } |
|
| 79 | - } |
|
| 70 | + $result = $query->executeQuery(); |
|
| 71 | + while ($row = $result->fetchAssociative()) { |
|
| 72 | + yield [ |
|
| 73 | + 'id' => (int)$row['id'], |
|
| 74 | + 'owner' => (string)$row['uid_owner'], |
|
| 75 | + 'fileid' => (int)$row['file_source'], |
|
| 76 | + 'target' => (string)$row['file_target'], |
|
| 77 | + ]; |
|
| 78 | + } |
|
| 79 | + } |
|
| 80 | 80 | |
| 81 | - public function findOwner(int $fileId): ?string { |
|
| 82 | - $mounts = $this->userMountCache->getMountsForFileId($fileId); |
|
| 83 | - if (!$mounts) { |
|
| 84 | - return null; |
|
| 85 | - } |
|
| 86 | - foreach ($mounts as $mount) { |
|
| 87 | - $userHomeMountPoint = '/' . $mount->getUser()->getUID() . '/'; |
|
| 88 | - if ($mount->getMountPoint() === $userHomeMountPoint) { |
|
| 89 | - return $mount->getUser()->getUID(); |
|
| 90 | - } |
|
| 91 | - } |
|
| 92 | - return null; |
|
| 93 | - } |
|
| 81 | + public function findOwner(int $fileId): ?string { |
|
| 82 | + $mounts = $this->userMountCache->getMountsForFileId($fileId); |
|
| 83 | + if (!$mounts) { |
|
| 84 | + return null; |
|
| 85 | + } |
|
| 86 | + foreach ($mounts as $mount) { |
|
| 87 | + $userHomeMountPoint = '/' . $mount->getUser()->getUID() . '/'; |
|
| 88 | + if ($mount->getMountPoint() === $userHomeMountPoint) { |
|
| 89 | + return $mount->getUser()->getUID(); |
|
| 90 | + } |
|
| 91 | + } |
|
| 92 | + return null; |
|
| 93 | + } |
|
| 94 | 94 | |
| 95 | - public function updateShareOwner(int $shareId, string $owner): void { |
|
| 96 | - $query = $this->connection->getQueryBuilder(); |
|
| 97 | - $query->update('share') |
|
| 98 | - ->set('uid_owner', $query->createNamedParameter($owner)) |
|
| 99 | - ->where($query->expr()->eq('id', $query->createNamedParameter($shareId, IQueryBuilder::PARAM_INT))); |
|
| 100 | - $query->executeStatement(); |
|
| 101 | - } |
|
| 95 | + public function updateShareOwner(int $shareId, string $owner): void { |
|
| 96 | + $query = $this->connection->getQueryBuilder(); |
|
| 97 | + $query->update('share') |
|
| 98 | + ->set('uid_owner', $query->createNamedParameter($owner)) |
|
| 99 | + ->where($query->expr()->eq('id', $query->createNamedParameter($shareId, IQueryBuilder::PARAM_INT))); |
|
| 100 | + $query->executeStatement(); |
|
| 101 | + } |
|
| 102 | 102 | } |
@@ -70,10 +70,10 @@ discard block |
||
| 70 | 70 | $result = $query->executeQuery(); |
| 71 | 71 | while ($row = $result->fetchAssociative()) { |
| 72 | 72 | yield [ |
| 73 | - 'id' => (int)$row['id'], |
|
| 74 | - 'owner' => (string)$row['uid_owner'], |
|
| 75 | - 'fileid' => (int)$row['file_source'], |
|
| 76 | - 'target' => (string)$row['file_target'], |
|
| 73 | + 'id' => (int) $row['id'], |
|
| 74 | + 'owner' => (string) $row['uid_owner'], |
|
| 75 | + 'fileid' => (int) $row['file_source'], |
|
| 76 | + 'target' => (string) $row['file_target'], |
|
| 77 | 77 | ]; |
| 78 | 78 | } |
| 79 | 79 | } |
@@ -84,7 +84,7 @@ discard block |
||
| 84 | 84 | return null; |
| 85 | 85 | } |
| 86 | 86 | foreach ($mounts as $mount) { |
| 87 | - $userHomeMountPoint = '/' . $mount->getUser()->getUID() . '/'; |
|
| 87 | + $userHomeMountPoint = '/'.$mount->getUser()->getUID().'/'; |
|
| 88 | 88 | if ($mount->getMountPoint() === $userHomeMountPoint) { |
| 89 | 89 | return $mount->getUser()->getUID(); |
| 90 | 90 | } |
@@ -18,33 +18,33 @@ |
||
| 18 | 18 | |
| 19 | 19 | class FederatedSharesDiscoverJob extends TimedJob { |
| 20 | 20 | |
| 21 | - public function __construct( |
|
| 22 | - ITimeFactory $time, |
|
| 23 | - private IDBConnection $connection, |
|
| 24 | - private IDiscoveryService $discoveryService, |
|
| 25 | - private IOCMDiscoveryService $ocmDiscoveryService, |
|
| 26 | - private LoggerInterface $logger, |
|
| 27 | - ) { |
|
| 28 | - parent::__construct($time); |
|
| 29 | - $this->setInterval(24 * 60 * 60); |
|
| 30 | - $this->setTimeSensitivity(self::TIME_INSENSITIVE); |
|
| 31 | - } |
|
| 32 | - |
|
| 33 | - public function run($argument) { |
|
| 34 | - $qb = $this->connection->getQueryBuilder(); |
|
| 35 | - |
|
| 36 | - $qb->selectDistinct('remote') |
|
| 37 | - ->from('share_external'); |
|
| 38 | - |
|
| 39 | - $result = $qb->executeQuery(); |
|
| 40 | - while ($row = $result->fetchAssociative()) { |
|
| 41 | - $this->discoveryService->discover($row['remote'], 'FEDERATED_SHARING', true); |
|
| 42 | - try { |
|
| 43 | - $this->ocmDiscoveryService->discover($row['remote'], true); |
|
| 44 | - } catch (OCMProviderException $e) { |
|
| 45 | - $this->logger->info('exception while running files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob', ['exception' => $e]); |
|
| 46 | - } |
|
| 47 | - } |
|
| 48 | - $result->closeCursor(); |
|
| 49 | - } |
|
| 21 | + public function __construct( |
|
| 22 | + ITimeFactory $time, |
|
| 23 | + private IDBConnection $connection, |
|
| 24 | + private IDiscoveryService $discoveryService, |
|
| 25 | + private IOCMDiscoveryService $ocmDiscoveryService, |
|
| 26 | + private LoggerInterface $logger, |
|
| 27 | + ) { |
|
| 28 | + parent::__construct($time); |
|
| 29 | + $this->setInterval(24 * 60 * 60); |
|
| 30 | + $this->setTimeSensitivity(self::TIME_INSENSITIVE); |
|
| 31 | + } |
|
| 32 | + |
|
| 33 | + public function run($argument) { |
|
| 34 | + $qb = $this->connection->getQueryBuilder(); |
|
| 35 | + |
|
| 36 | + $qb->selectDistinct('remote') |
|
| 37 | + ->from('share_external'); |
|
| 38 | + |
|
| 39 | + $result = $qb->executeQuery(); |
|
| 40 | + while ($row = $result->fetchAssociative()) { |
|
| 41 | + $this->discoveryService->discover($row['remote'], 'FEDERATED_SHARING', true); |
|
| 42 | + try { |
|
| 43 | + $this->ocmDiscoveryService->discover($row['remote'], true); |
|
| 44 | + } catch (OCMProviderException $e) { |
|
| 45 | + $this->logger->info('exception while running files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob', ['exception' => $e]); |
|
| 46 | + } |
|
| 47 | + } |
|
| 48 | + $result->closeCursor(); |
|
| 49 | + } |
|
| 50 | 50 | } |
@@ -21,149 +21,149 @@ |
||
| 21 | 21 | */ |
| 22 | 22 | class CleanupRemoteStorages extends Command { |
| 23 | 23 | |
| 24 | - public function __construct( |
|
| 25 | - protected IDBConnection $connection, |
|
| 26 | - private ICloudIdManager $cloudIdManager, |
|
| 27 | - ) { |
|
| 28 | - parent::__construct(); |
|
| 29 | - } |
|
| 30 | - |
|
| 31 | - protected function configure() { |
|
| 32 | - $this |
|
| 33 | - ->setName('sharing:cleanup-remote-storages') |
|
| 34 | - ->setDescription('Cleanup shared storage entries that have no matching entry in the shares_external table') |
|
| 35 | - ->addOption( |
|
| 36 | - 'dry-run', |
|
| 37 | - null, |
|
| 38 | - InputOption::VALUE_NONE, |
|
| 39 | - 'only show which storages would be deleted' |
|
| 40 | - ); |
|
| 41 | - } |
|
| 42 | - |
|
| 43 | - public function execute(InputInterface $input, OutputInterface $output): int { |
|
| 44 | - $remoteStorages = $this->getRemoteStorages(); |
|
| 45 | - |
|
| 46 | - $output->writeln(count($remoteStorages) . ' remote storage(s) need(s) to be checked'); |
|
| 47 | - |
|
| 48 | - $remoteShareIds = $this->getRemoteShareIds(); |
|
| 49 | - |
|
| 50 | - $output->writeln(count($remoteShareIds) . ' remote share(s) exist'); |
|
| 51 | - |
|
| 52 | - foreach ($remoteShareIds as $id => $remoteShareId) { |
|
| 53 | - if (isset($remoteStorages[$remoteShareId])) { |
|
| 54 | - if ($input->getOption('dry-run') || $output->isVerbose()) { |
|
| 55 | - $output->writeln("<info>$remoteShareId belongs to remote share $id</info>"); |
|
| 56 | - } |
|
| 57 | - |
|
| 58 | - unset($remoteStorages[$remoteShareId]); |
|
| 59 | - } else { |
|
| 60 | - $output->writeln("<comment>$remoteShareId for share $id has no matching storage, yet</comment>"); |
|
| 61 | - } |
|
| 62 | - } |
|
| 63 | - |
|
| 64 | - if (empty($remoteStorages)) { |
|
| 65 | - $output->writeln('<info>no storages deleted</info>'); |
|
| 66 | - } else { |
|
| 67 | - $dryRun = $input->getOption('dry-run'); |
|
| 68 | - foreach ($remoteStorages as $id => $numericId) { |
|
| 69 | - if ($dryRun) { |
|
| 70 | - $output->writeln("<error>$id [$numericId] can be deleted</error>"); |
|
| 71 | - $this->countFiles($numericId, $output); |
|
| 72 | - } else { |
|
| 73 | - $this->deleteStorage($id, $numericId, $output); |
|
| 74 | - } |
|
| 75 | - } |
|
| 76 | - } |
|
| 77 | - return 0; |
|
| 78 | - } |
|
| 79 | - |
|
| 80 | - public function countFiles($numericId, OutputInterface $output) { |
|
| 81 | - $queryBuilder = $this->connection->getQueryBuilder(); |
|
| 82 | - $queryBuilder->select($queryBuilder->func()->count('fileid')) |
|
| 83 | - ->from('filecache') |
|
| 84 | - ->where($queryBuilder->expr()->eq( |
|
| 85 | - 'storage', |
|
| 86 | - $queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR), |
|
| 87 | - IQueryBuilder::PARAM_STR) |
|
| 88 | - ); |
|
| 89 | - $result = $queryBuilder->executeQuery(); |
|
| 90 | - $count = $result->fetchOne(); |
|
| 91 | - $result->closeCursor(); |
|
| 92 | - $output->writeln("$count files can be deleted for storage $numericId"); |
|
| 93 | - } |
|
| 94 | - |
|
| 95 | - public function deleteStorage($id, $numericId, OutputInterface $output) { |
|
| 96 | - $queryBuilder = $this->connection->getQueryBuilder(); |
|
| 97 | - $queryBuilder->delete('storages') |
|
| 98 | - ->where($queryBuilder->expr()->eq( |
|
| 99 | - 'id', |
|
| 100 | - $queryBuilder->createNamedParameter($id, IQueryBuilder::PARAM_STR), |
|
| 101 | - IQueryBuilder::PARAM_STR) |
|
| 102 | - ); |
|
| 103 | - $output->write("deleting $id [$numericId] ... "); |
|
| 104 | - $count = $queryBuilder->executeStatement(); |
|
| 105 | - $output->writeln("deleted $count storage"); |
|
| 106 | - $this->deleteFiles($numericId, $output); |
|
| 107 | - } |
|
| 108 | - |
|
| 109 | - public function deleteFiles($numericId, OutputInterface $output) { |
|
| 110 | - $queryBuilder = $this->connection->getQueryBuilder(); |
|
| 111 | - $queryBuilder->delete('filecache') |
|
| 112 | - ->where($queryBuilder->expr()->eq( |
|
| 113 | - 'storage', |
|
| 114 | - $queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR), |
|
| 115 | - IQueryBuilder::PARAM_STR) |
|
| 116 | - ); |
|
| 117 | - $output->write("deleting files for storage $numericId ... "); |
|
| 118 | - $count = $queryBuilder->executeStatement(); |
|
| 119 | - $output->writeln("deleted $count files"); |
|
| 120 | - } |
|
| 121 | - |
|
| 122 | - public function getRemoteStorages() { |
|
| 123 | - $queryBuilder = $this->connection->getQueryBuilder(); |
|
| 124 | - $queryBuilder->select(['id', 'numeric_id']) |
|
| 125 | - ->from('storages') |
|
| 126 | - ->where($queryBuilder->expr()->like( |
|
| 127 | - 'id', |
|
| 128 | - // match all 'shared::' + 32 characters storages |
|
| 129 | - $queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::') . str_repeat('_', 32)), |
|
| 130 | - IQueryBuilder::PARAM_STR) |
|
| 131 | - ) |
|
| 132 | - ->andWhere($queryBuilder->expr()->notLike( |
|
| 133 | - 'id', |
|
| 134 | - // but not the ones starting with a '/', they are for normal shares |
|
| 135 | - $queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::/') . '%'), |
|
| 136 | - IQueryBuilder::PARAM_STR) |
|
| 137 | - ) |
|
| 138 | - ->orderBy('numeric_id'); |
|
| 139 | - $result = $queryBuilder->executeQuery(); |
|
| 140 | - |
|
| 141 | - $remoteStorages = []; |
|
| 142 | - |
|
| 143 | - while ($row = $result->fetchAssociative()) { |
|
| 144 | - $remoteStorages[$row['id']] = $row['numeric_id']; |
|
| 145 | - } |
|
| 146 | - $result->closeCursor(); |
|
| 147 | - |
|
| 148 | - return $remoteStorages; |
|
| 149 | - } |
|
| 150 | - |
|
| 151 | - public function getRemoteShareIds() { |
|
| 152 | - $queryBuilder = $this->connection->getQueryBuilder(); |
|
| 153 | - $queryBuilder->select(['id', 'share_token', 'owner', 'remote']) |
|
| 154 | - ->from('share_external'); |
|
| 155 | - $result = $queryBuilder->executeQuery(); |
|
| 156 | - |
|
| 157 | - $remoteShareIds = []; |
|
| 158 | - |
|
| 159 | - while ($row = $result->fetchAssociative()) { |
|
| 160 | - $cloudId = $this->cloudIdManager->getCloudId($row['owner'], $row['remote']); |
|
| 161 | - $remote = $cloudId->getRemote(); |
|
| 162 | - |
|
| 163 | - $remoteShareIds[$row['id']] = 'shared::' . md5($row['share_token'] . '@' . $remote); |
|
| 164 | - } |
|
| 165 | - $result->closeCursor(); |
|
| 166 | - |
|
| 167 | - return $remoteShareIds; |
|
| 168 | - } |
|
| 24 | + public function __construct( |
|
| 25 | + protected IDBConnection $connection, |
|
| 26 | + private ICloudIdManager $cloudIdManager, |
|
| 27 | + ) { |
|
| 28 | + parent::__construct(); |
|
| 29 | + } |
|
| 30 | + |
|
| 31 | + protected function configure() { |
|
| 32 | + $this |
|
| 33 | + ->setName('sharing:cleanup-remote-storages') |
|
| 34 | + ->setDescription('Cleanup shared storage entries that have no matching entry in the shares_external table') |
|
| 35 | + ->addOption( |
|
| 36 | + 'dry-run', |
|
| 37 | + null, |
|
| 38 | + InputOption::VALUE_NONE, |
|
| 39 | + 'only show which storages would be deleted' |
|
| 40 | + ); |
|
| 41 | + } |
|
| 42 | + |
|
| 43 | + public function execute(InputInterface $input, OutputInterface $output): int { |
|
| 44 | + $remoteStorages = $this->getRemoteStorages(); |
|
| 45 | + |
|
| 46 | + $output->writeln(count($remoteStorages) . ' remote storage(s) need(s) to be checked'); |
|
| 47 | + |
|
| 48 | + $remoteShareIds = $this->getRemoteShareIds(); |
|
| 49 | + |
|
| 50 | + $output->writeln(count($remoteShareIds) . ' remote share(s) exist'); |
|
| 51 | + |
|
| 52 | + foreach ($remoteShareIds as $id => $remoteShareId) { |
|
| 53 | + if (isset($remoteStorages[$remoteShareId])) { |
|
| 54 | + if ($input->getOption('dry-run') || $output->isVerbose()) { |
|
| 55 | + $output->writeln("<info>$remoteShareId belongs to remote share $id</info>"); |
|
| 56 | + } |
|
| 57 | + |
|
| 58 | + unset($remoteStorages[$remoteShareId]); |
|
| 59 | + } else { |
|
| 60 | + $output->writeln("<comment>$remoteShareId for share $id has no matching storage, yet</comment>"); |
|
| 61 | + } |
|
| 62 | + } |
|
| 63 | + |
|
| 64 | + if (empty($remoteStorages)) { |
|
| 65 | + $output->writeln('<info>no storages deleted</info>'); |
|
| 66 | + } else { |
|
| 67 | + $dryRun = $input->getOption('dry-run'); |
|
| 68 | + foreach ($remoteStorages as $id => $numericId) { |
|
| 69 | + if ($dryRun) { |
|
| 70 | + $output->writeln("<error>$id [$numericId] can be deleted</error>"); |
|
| 71 | + $this->countFiles($numericId, $output); |
|
| 72 | + } else { |
|
| 73 | + $this->deleteStorage($id, $numericId, $output); |
|
| 74 | + } |
|
| 75 | + } |
|
| 76 | + } |
|
| 77 | + return 0; |
|
| 78 | + } |
|
| 79 | + |
|
| 80 | + public function countFiles($numericId, OutputInterface $output) { |
|
| 81 | + $queryBuilder = $this->connection->getQueryBuilder(); |
|
| 82 | + $queryBuilder->select($queryBuilder->func()->count('fileid')) |
|
| 83 | + ->from('filecache') |
|
| 84 | + ->where($queryBuilder->expr()->eq( |
|
| 85 | + 'storage', |
|
| 86 | + $queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR), |
|
| 87 | + IQueryBuilder::PARAM_STR) |
|
| 88 | + ); |
|
| 89 | + $result = $queryBuilder->executeQuery(); |
|
| 90 | + $count = $result->fetchOne(); |
|
| 91 | + $result->closeCursor(); |
|
| 92 | + $output->writeln("$count files can be deleted for storage $numericId"); |
|
| 93 | + } |
|
| 94 | + |
|
| 95 | + public function deleteStorage($id, $numericId, OutputInterface $output) { |
|
| 96 | + $queryBuilder = $this->connection->getQueryBuilder(); |
|
| 97 | + $queryBuilder->delete('storages') |
|
| 98 | + ->where($queryBuilder->expr()->eq( |
|
| 99 | + 'id', |
|
| 100 | + $queryBuilder->createNamedParameter($id, IQueryBuilder::PARAM_STR), |
|
| 101 | + IQueryBuilder::PARAM_STR) |
|
| 102 | + ); |
|
| 103 | + $output->write("deleting $id [$numericId] ... "); |
|
| 104 | + $count = $queryBuilder->executeStatement(); |
|
| 105 | + $output->writeln("deleted $count storage"); |
|
| 106 | + $this->deleteFiles($numericId, $output); |
|
| 107 | + } |
|
| 108 | + |
|
| 109 | + public function deleteFiles($numericId, OutputInterface $output) { |
|
| 110 | + $queryBuilder = $this->connection->getQueryBuilder(); |
|
| 111 | + $queryBuilder->delete('filecache') |
|
| 112 | + ->where($queryBuilder->expr()->eq( |
|
| 113 | + 'storage', |
|
| 114 | + $queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR), |
|
| 115 | + IQueryBuilder::PARAM_STR) |
|
| 116 | + ); |
|
| 117 | + $output->write("deleting files for storage $numericId ... "); |
|
| 118 | + $count = $queryBuilder->executeStatement(); |
|
| 119 | + $output->writeln("deleted $count files"); |
|
| 120 | + } |
|
| 121 | + |
|
| 122 | + public function getRemoteStorages() { |
|
| 123 | + $queryBuilder = $this->connection->getQueryBuilder(); |
|
| 124 | + $queryBuilder->select(['id', 'numeric_id']) |
|
| 125 | + ->from('storages') |
|
| 126 | + ->where($queryBuilder->expr()->like( |
|
| 127 | + 'id', |
|
| 128 | + // match all 'shared::' + 32 characters storages |
|
| 129 | + $queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::') . str_repeat('_', 32)), |
|
| 130 | + IQueryBuilder::PARAM_STR) |
|
| 131 | + ) |
|
| 132 | + ->andWhere($queryBuilder->expr()->notLike( |
|
| 133 | + 'id', |
|
| 134 | + // but not the ones starting with a '/', they are for normal shares |
|
| 135 | + $queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::/') . '%'), |
|
| 136 | + IQueryBuilder::PARAM_STR) |
|
| 137 | + ) |
|
| 138 | + ->orderBy('numeric_id'); |
|
| 139 | + $result = $queryBuilder->executeQuery(); |
|
| 140 | + |
|
| 141 | + $remoteStorages = []; |
|
| 142 | + |
|
| 143 | + while ($row = $result->fetchAssociative()) { |
|
| 144 | + $remoteStorages[$row['id']] = $row['numeric_id']; |
|
| 145 | + } |
|
| 146 | + $result->closeCursor(); |
|
| 147 | + |
|
| 148 | + return $remoteStorages; |
|
| 149 | + } |
|
| 150 | + |
|
| 151 | + public function getRemoteShareIds() { |
|
| 152 | + $queryBuilder = $this->connection->getQueryBuilder(); |
|
| 153 | + $queryBuilder->select(['id', 'share_token', 'owner', 'remote']) |
|
| 154 | + ->from('share_external'); |
|
| 155 | + $result = $queryBuilder->executeQuery(); |
|
| 156 | + |
|
| 157 | + $remoteShareIds = []; |
|
| 158 | + |
|
| 159 | + while ($row = $result->fetchAssociative()) { |
|
| 160 | + $cloudId = $this->cloudIdManager->getCloudId($row['owner'], $row['remote']); |
|
| 161 | + $remote = $cloudId->getRemote(); |
|
| 162 | + |
|
| 163 | + $remoteShareIds[$row['id']] = 'shared::' . md5($row['share_token'] . '@' . $remote); |
|
| 164 | + } |
|
| 165 | + $result->closeCursor(); |
|
| 166 | + |
|
| 167 | + return $remoteShareIds; |
|
| 168 | + } |
|
| 169 | 169 | } |
@@ -28,243 +28,243 @@ |
||
| 28 | 28 | * Shared mount points can be moved by the user |
| 29 | 29 | */ |
| 30 | 30 | class SharedMount extends MountPoint implements MoveableMount, ISharedMountPoint { |
| 31 | - /** |
|
| 32 | - * @var SharedStorage $storage |
|
| 33 | - */ |
|
| 34 | - protected $storage = null; |
|
| 35 | - |
|
| 36 | - /** @var IShare */ |
|
| 37 | - private $superShare; |
|
| 38 | - |
|
| 39 | - /** @var IShare[] */ |
|
| 40 | - private $groupedShares; |
|
| 41 | - |
|
| 42 | - public function __construct( |
|
| 43 | - $storage, |
|
| 44 | - array $mountpoints, |
|
| 45 | - $arguments, |
|
| 46 | - IStorageFactory $loader, |
|
| 47 | - private View $recipientView, |
|
| 48 | - CappedMemoryCache $folderExistCache, |
|
| 49 | - private IEventDispatcher $eventDispatcher, |
|
| 50 | - private IUser $user, |
|
| 51 | - bool $alreadyVerified, |
|
| 52 | - ) { |
|
| 53 | - $this->superShare = $arguments['superShare']; |
|
| 54 | - $this->groupedShares = $arguments['groupedShares']; |
|
| 55 | - |
|
| 56 | - $absMountPoint = '/' . $user->getUID() . '/files/' . trim($this->superShare->getTarget(), '/') . '/'; |
|
| 57 | - |
|
| 58 | - // after the mountpoint is verified for the first time, only new mountpoints (e.g. groupfolders can overwrite the target) |
|
| 59 | - if (!$alreadyVerified || isset($mountpoints[$absMountPoint])) { |
|
| 60 | - $newMountPoint = $this->verifyMountPoint($this->superShare, $mountpoints, $folderExistCache); |
|
| 61 | - $absMountPoint = '/' . $user->getUID() . '/files/' . trim($newMountPoint, '/') . '/'; |
|
| 62 | - } |
|
| 63 | - |
|
| 64 | - parent::__construct($storage, $absMountPoint, $arguments, $loader, null, null, MountProvider::class); |
|
| 65 | - } |
|
| 66 | - |
|
| 67 | - /** |
|
| 68 | - * check if the parent folder exists otherwise move the mount point up |
|
| 69 | - * |
|
| 70 | - * @param IShare $share |
|
| 71 | - * @param SharedMount[] $mountpoints |
|
| 72 | - * @param CappedMemoryCache<bool> $folderExistCache |
|
| 73 | - * @return string |
|
| 74 | - */ |
|
| 75 | - private function verifyMountPoint( |
|
| 76 | - IShare $share, |
|
| 77 | - array $mountpoints, |
|
| 78 | - CappedMemoryCache $folderExistCache, |
|
| 79 | - ) { |
|
| 80 | - $mountPoint = basename($share->getTarget()); |
|
| 81 | - $parent = dirname($share->getTarget()); |
|
| 82 | - |
|
| 83 | - $event = new VerifyMountPointEvent($share, $this->recipientView, $parent); |
|
| 84 | - $this->eventDispatcher->dispatchTyped($event); |
|
| 85 | - $parent = $event->getParent(); |
|
| 86 | - |
|
| 87 | - $cached = $folderExistCache->get($parent); |
|
| 88 | - if ($cached) { |
|
| 89 | - $parentExists = $cached; |
|
| 90 | - } else { |
|
| 91 | - $parentExists = $this->recipientView->is_dir($parent); |
|
| 92 | - $folderExistCache->set($parent, $parentExists); |
|
| 93 | - } |
|
| 94 | - if (!$parentExists) { |
|
| 95 | - $parent = Helper::getShareFolder($this->recipientView, $this->user->getUID()); |
|
| 96 | - } |
|
| 97 | - |
|
| 98 | - $newMountPoint = $this->generateUniqueTarget( |
|
| 99 | - Filesystem::normalizePath($parent . '/' . $mountPoint), |
|
| 100 | - $this->recipientView, |
|
| 101 | - $mountpoints |
|
| 102 | - ); |
|
| 103 | - |
|
| 104 | - if ($newMountPoint !== $share->getTarget()) { |
|
| 105 | - $this->updateFileTarget($newMountPoint, $share); |
|
| 106 | - } |
|
| 107 | - |
|
| 108 | - return $newMountPoint; |
|
| 109 | - } |
|
| 110 | - |
|
| 111 | - /** |
|
| 112 | - * update fileTarget in the database if the mount point changed |
|
| 113 | - * |
|
| 114 | - * @param string $newPath |
|
| 115 | - * @param IShare $share |
|
| 116 | - * @return bool |
|
| 117 | - */ |
|
| 118 | - private function updateFileTarget($newPath, &$share) { |
|
| 119 | - $share->setTarget($newPath); |
|
| 120 | - |
|
| 121 | - foreach ($this->groupedShares as $tmpShare) { |
|
| 122 | - $tmpShare->setTarget($newPath); |
|
| 123 | - Server::get(\OCP\Share\IManager::class)->moveShare($tmpShare, $this->user->getUID()); |
|
| 124 | - } |
|
| 125 | - |
|
| 126 | - $this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->user)); |
|
| 127 | - } |
|
| 128 | - |
|
| 129 | - |
|
| 130 | - /** |
|
| 131 | - * @param string $path |
|
| 132 | - * @param View $view |
|
| 133 | - * @param SharedMount[] $mountpoints |
|
| 134 | - * @return mixed |
|
| 135 | - */ |
|
| 136 | - private function generateUniqueTarget($path, $view, array $mountpoints) { |
|
| 137 | - $pathinfo = pathinfo($path); |
|
| 138 | - $ext = isset($pathinfo['extension']) ? '.' . $pathinfo['extension'] : ''; |
|
| 139 | - $name = $pathinfo['filename']; |
|
| 140 | - $dir = $pathinfo['dirname']; |
|
| 141 | - |
|
| 142 | - $i = 2; |
|
| 143 | - $absolutePath = $this->recipientView->getAbsolutePath($path) . '/'; |
|
| 144 | - while ($view->file_exists($path) || isset($mountpoints[$absolutePath])) { |
|
| 145 | - $path = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext); |
|
| 146 | - $absolutePath = $this->recipientView->getAbsolutePath($path) . '/'; |
|
| 147 | - $i++; |
|
| 148 | - } |
|
| 149 | - |
|
| 150 | - return $path; |
|
| 151 | - } |
|
| 152 | - |
|
| 153 | - /** |
|
| 154 | - * Format a path to be relative to the /user/files/ directory |
|
| 155 | - * |
|
| 156 | - * @param string $path the absolute path |
|
| 157 | - * @return string e.g. turns '/admin/files/test.txt' into '/test.txt' |
|
| 158 | - * @throws BrokenPath |
|
| 159 | - */ |
|
| 160 | - protected function stripUserFilesPath($path) { |
|
| 161 | - $trimmed = ltrim($path, '/'); |
|
| 162 | - $split = explode('/', $trimmed); |
|
| 163 | - |
|
| 164 | - // it is not a file relative to data/user/files |
|
| 165 | - if (count($split) < 3 || $split[1] !== 'files') { |
|
| 166 | - Server::get(LoggerInterface::class)->error('Can not strip userid and "files/" from path: ' . $path, ['app' => 'files_sharing']); |
|
| 167 | - throw new BrokenPath('Path does not start with /user/files', 10); |
|
| 168 | - } |
|
| 169 | - |
|
| 170 | - // skip 'user' and 'files' |
|
| 171 | - $sliced = array_slice($split, 2); |
|
| 172 | - $relPath = implode('/', $sliced); |
|
| 173 | - |
|
| 174 | - return '/' . $relPath; |
|
| 175 | - } |
|
| 176 | - |
|
| 177 | - /** |
|
| 178 | - * Move the mount point to $target |
|
| 179 | - * |
|
| 180 | - * @param string $target the target mount point |
|
| 181 | - * @return bool |
|
| 182 | - */ |
|
| 183 | - public function moveMount($target) { |
|
| 184 | - $relTargetPath = $this->stripUserFilesPath($target); |
|
| 185 | - $share = $this->storage->getShare(); |
|
| 186 | - |
|
| 187 | - $result = true; |
|
| 188 | - |
|
| 189 | - try { |
|
| 190 | - $this->updateFileTarget($relTargetPath, $share); |
|
| 191 | - $this->setMountPoint($target); |
|
| 192 | - $this->storage->setMountPoint($relTargetPath); |
|
| 193 | - } catch (\Exception $e) { |
|
| 194 | - Server::get(LoggerInterface::class)->error( |
|
| 195 | - 'Could not rename mount point for shared folder "' . $this->getMountPoint() . '" to "' . $target . '"', |
|
| 196 | - [ |
|
| 197 | - 'app' => 'files_sharing', |
|
| 198 | - 'exception' => $e, |
|
| 199 | - ] |
|
| 200 | - ); |
|
| 201 | - } |
|
| 202 | - |
|
| 203 | - return $result; |
|
| 204 | - } |
|
| 205 | - |
|
| 206 | - /** |
|
| 207 | - * Remove the mount points |
|
| 208 | - * |
|
| 209 | - * @return bool |
|
| 210 | - */ |
|
| 211 | - public function removeMount() { |
|
| 212 | - $mountManager = Filesystem::getMountManager(); |
|
| 213 | - /** @var SharedStorage $storage */ |
|
| 214 | - $storage = $this->getStorage(); |
|
| 215 | - $result = $storage->unshareStorage(); |
|
| 216 | - $mountManager->removeMount($this->mountPoint); |
|
| 217 | - |
|
| 218 | - return $result; |
|
| 219 | - } |
|
| 220 | - |
|
| 221 | - /** |
|
| 222 | - * @return IShare |
|
| 223 | - */ |
|
| 224 | - public function getShare() { |
|
| 225 | - return $this->superShare; |
|
| 226 | - } |
|
| 227 | - |
|
| 228 | - /** |
|
| 229 | - * @return IShare[] |
|
| 230 | - */ |
|
| 231 | - public function getGroupedShares(): array { |
|
| 232 | - return $this->groupedShares; |
|
| 233 | - } |
|
| 234 | - |
|
| 235 | - /** |
|
| 236 | - * Get the file id of the root of the storage |
|
| 237 | - * |
|
| 238 | - * @return int |
|
| 239 | - */ |
|
| 240 | - public function getStorageRootId() { |
|
| 241 | - return $this->getShare()->getNodeId(); |
|
| 242 | - } |
|
| 243 | - |
|
| 244 | - /** |
|
| 245 | - * @return int |
|
| 246 | - */ |
|
| 247 | - public function getNumericStorageId() { |
|
| 248 | - if (!is_null($this->getShare()->getNodeCacheEntry())) { |
|
| 249 | - return $this->getShare()->getNodeCacheEntry()->getStorageId(); |
|
| 250 | - } else { |
|
| 251 | - $builder = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 252 | - |
|
| 253 | - $query = $builder->select('storage') |
|
| 254 | - ->from('filecache') |
|
| 255 | - ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($this->getStorageRootId()))); |
|
| 256 | - |
|
| 257 | - $result = $query->executeQuery(); |
|
| 258 | - $row = $result->fetchAssociative(); |
|
| 259 | - $result->closeCursor(); |
|
| 260 | - if ($row) { |
|
| 261 | - return (int)$row['storage']; |
|
| 262 | - } |
|
| 263 | - return -1; |
|
| 264 | - } |
|
| 265 | - } |
|
| 266 | - |
|
| 267 | - public function getMountType() { |
|
| 268 | - return 'shared'; |
|
| 269 | - } |
|
| 31 | + /** |
|
| 32 | + * @var SharedStorage $storage |
|
| 33 | + */ |
|
| 34 | + protected $storage = null; |
|
| 35 | + |
|
| 36 | + /** @var IShare */ |
|
| 37 | + private $superShare; |
|
| 38 | + |
|
| 39 | + /** @var IShare[] */ |
|
| 40 | + private $groupedShares; |
|
| 41 | + |
|
| 42 | + public function __construct( |
|
| 43 | + $storage, |
|
| 44 | + array $mountpoints, |
|
| 45 | + $arguments, |
|
| 46 | + IStorageFactory $loader, |
|
| 47 | + private View $recipientView, |
|
| 48 | + CappedMemoryCache $folderExistCache, |
|
| 49 | + private IEventDispatcher $eventDispatcher, |
|
| 50 | + private IUser $user, |
|
| 51 | + bool $alreadyVerified, |
|
| 52 | + ) { |
|
| 53 | + $this->superShare = $arguments['superShare']; |
|
| 54 | + $this->groupedShares = $arguments['groupedShares']; |
|
| 55 | + |
|
| 56 | + $absMountPoint = '/' . $user->getUID() . '/files/' . trim($this->superShare->getTarget(), '/') . '/'; |
|
| 57 | + |
|
| 58 | + // after the mountpoint is verified for the first time, only new mountpoints (e.g. groupfolders can overwrite the target) |
|
| 59 | + if (!$alreadyVerified || isset($mountpoints[$absMountPoint])) { |
|
| 60 | + $newMountPoint = $this->verifyMountPoint($this->superShare, $mountpoints, $folderExistCache); |
|
| 61 | + $absMountPoint = '/' . $user->getUID() . '/files/' . trim($newMountPoint, '/') . '/'; |
|
| 62 | + } |
|
| 63 | + |
|
| 64 | + parent::__construct($storage, $absMountPoint, $arguments, $loader, null, null, MountProvider::class); |
|
| 65 | + } |
|
| 66 | + |
|
| 67 | + /** |
|
| 68 | + * check if the parent folder exists otherwise move the mount point up |
|
| 69 | + * |
|
| 70 | + * @param IShare $share |
|
| 71 | + * @param SharedMount[] $mountpoints |
|
| 72 | + * @param CappedMemoryCache<bool> $folderExistCache |
|
| 73 | + * @return string |
|
| 74 | + */ |
|
| 75 | + private function verifyMountPoint( |
|
| 76 | + IShare $share, |
|
| 77 | + array $mountpoints, |
|
| 78 | + CappedMemoryCache $folderExistCache, |
|
| 79 | + ) { |
|
| 80 | + $mountPoint = basename($share->getTarget()); |
|
| 81 | + $parent = dirname($share->getTarget()); |
|
| 82 | + |
|
| 83 | + $event = new VerifyMountPointEvent($share, $this->recipientView, $parent); |
|
| 84 | + $this->eventDispatcher->dispatchTyped($event); |
|
| 85 | + $parent = $event->getParent(); |
|
| 86 | + |
|
| 87 | + $cached = $folderExistCache->get($parent); |
|
| 88 | + if ($cached) { |
|
| 89 | + $parentExists = $cached; |
|
| 90 | + } else { |
|
| 91 | + $parentExists = $this->recipientView->is_dir($parent); |
|
| 92 | + $folderExistCache->set($parent, $parentExists); |
|
| 93 | + } |
|
| 94 | + if (!$parentExists) { |
|
| 95 | + $parent = Helper::getShareFolder($this->recipientView, $this->user->getUID()); |
|
| 96 | + } |
|
| 97 | + |
|
| 98 | + $newMountPoint = $this->generateUniqueTarget( |
|
| 99 | + Filesystem::normalizePath($parent . '/' . $mountPoint), |
|
| 100 | + $this->recipientView, |
|
| 101 | + $mountpoints |
|
| 102 | + ); |
|
| 103 | + |
|
| 104 | + if ($newMountPoint !== $share->getTarget()) { |
|
| 105 | + $this->updateFileTarget($newMountPoint, $share); |
|
| 106 | + } |
|
| 107 | + |
|
| 108 | + return $newMountPoint; |
|
| 109 | + } |
|
| 110 | + |
|
| 111 | + /** |
|
| 112 | + * update fileTarget in the database if the mount point changed |
|
| 113 | + * |
|
| 114 | + * @param string $newPath |
|
| 115 | + * @param IShare $share |
|
| 116 | + * @return bool |
|
| 117 | + */ |
|
| 118 | + private function updateFileTarget($newPath, &$share) { |
|
| 119 | + $share->setTarget($newPath); |
|
| 120 | + |
|
| 121 | + foreach ($this->groupedShares as $tmpShare) { |
|
| 122 | + $tmpShare->setTarget($newPath); |
|
| 123 | + Server::get(\OCP\Share\IManager::class)->moveShare($tmpShare, $this->user->getUID()); |
|
| 124 | + } |
|
| 125 | + |
|
| 126 | + $this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->user)); |
|
| 127 | + } |
|
| 128 | + |
|
| 129 | + |
|
| 130 | + /** |
|
| 131 | + * @param string $path |
|
| 132 | + * @param View $view |
|
| 133 | + * @param SharedMount[] $mountpoints |
|
| 134 | + * @return mixed |
|
| 135 | + */ |
|
| 136 | + private function generateUniqueTarget($path, $view, array $mountpoints) { |
|
| 137 | + $pathinfo = pathinfo($path); |
|
| 138 | + $ext = isset($pathinfo['extension']) ? '.' . $pathinfo['extension'] : ''; |
|
| 139 | + $name = $pathinfo['filename']; |
|
| 140 | + $dir = $pathinfo['dirname']; |
|
| 141 | + |
|
| 142 | + $i = 2; |
|
| 143 | + $absolutePath = $this->recipientView->getAbsolutePath($path) . '/'; |
|
| 144 | + while ($view->file_exists($path) || isset($mountpoints[$absolutePath])) { |
|
| 145 | + $path = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext); |
|
| 146 | + $absolutePath = $this->recipientView->getAbsolutePath($path) . '/'; |
|
| 147 | + $i++; |
|
| 148 | + } |
|
| 149 | + |
|
| 150 | + return $path; |
|
| 151 | + } |
|
| 152 | + |
|
| 153 | + /** |
|
| 154 | + * Format a path to be relative to the /user/files/ directory |
|
| 155 | + * |
|
| 156 | + * @param string $path the absolute path |
|
| 157 | + * @return string e.g. turns '/admin/files/test.txt' into '/test.txt' |
|
| 158 | + * @throws BrokenPath |
|
| 159 | + */ |
|
| 160 | + protected function stripUserFilesPath($path) { |
|
| 161 | + $trimmed = ltrim($path, '/'); |
|
| 162 | + $split = explode('/', $trimmed); |
|
| 163 | + |
|
| 164 | + // it is not a file relative to data/user/files |
|
| 165 | + if (count($split) < 3 || $split[1] !== 'files') { |
|
| 166 | + Server::get(LoggerInterface::class)->error('Can not strip userid and "files/" from path: ' . $path, ['app' => 'files_sharing']); |
|
| 167 | + throw new BrokenPath('Path does not start with /user/files', 10); |
|
| 168 | + } |
|
| 169 | + |
|
| 170 | + // skip 'user' and 'files' |
|
| 171 | + $sliced = array_slice($split, 2); |
|
| 172 | + $relPath = implode('/', $sliced); |
|
| 173 | + |
|
| 174 | + return '/' . $relPath; |
|
| 175 | + } |
|
| 176 | + |
|
| 177 | + /** |
|
| 178 | + * Move the mount point to $target |
|
| 179 | + * |
|
| 180 | + * @param string $target the target mount point |
|
| 181 | + * @return bool |
|
| 182 | + */ |
|
| 183 | + public function moveMount($target) { |
|
| 184 | + $relTargetPath = $this->stripUserFilesPath($target); |
|
| 185 | + $share = $this->storage->getShare(); |
|
| 186 | + |
|
| 187 | + $result = true; |
|
| 188 | + |
|
| 189 | + try { |
|
| 190 | + $this->updateFileTarget($relTargetPath, $share); |
|
| 191 | + $this->setMountPoint($target); |
|
| 192 | + $this->storage->setMountPoint($relTargetPath); |
|
| 193 | + } catch (\Exception $e) { |
|
| 194 | + Server::get(LoggerInterface::class)->error( |
|
| 195 | + 'Could not rename mount point for shared folder "' . $this->getMountPoint() . '" to "' . $target . '"', |
|
| 196 | + [ |
|
| 197 | + 'app' => 'files_sharing', |
|
| 198 | + 'exception' => $e, |
|
| 199 | + ] |
|
| 200 | + ); |
|
| 201 | + } |
|
| 202 | + |
|
| 203 | + return $result; |
|
| 204 | + } |
|
| 205 | + |
|
| 206 | + /** |
|
| 207 | + * Remove the mount points |
|
| 208 | + * |
|
| 209 | + * @return bool |
|
| 210 | + */ |
|
| 211 | + public function removeMount() { |
|
| 212 | + $mountManager = Filesystem::getMountManager(); |
|
| 213 | + /** @var SharedStorage $storage */ |
|
| 214 | + $storage = $this->getStorage(); |
|
| 215 | + $result = $storage->unshareStorage(); |
|
| 216 | + $mountManager->removeMount($this->mountPoint); |
|
| 217 | + |
|
| 218 | + return $result; |
|
| 219 | + } |
|
| 220 | + |
|
| 221 | + /** |
|
| 222 | + * @return IShare |
|
| 223 | + */ |
|
| 224 | + public function getShare() { |
|
| 225 | + return $this->superShare; |
|
| 226 | + } |
|
| 227 | + |
|
| 228 | + /** |
|
| 229 | + * @return IShare[] |
|
| 230 | + */ |
|
| 231 | + public function getGroupedShares(): array { |
|
| 232 | + return $this->groupedShares; |
|
| 233 | + } |
|
| 234 | + |
|
| 235 | + /** |
|
| 236 | + * Get the file id of the root of the storage |
|
| 237 | + * |
|
| 238 | + * @return int |
|
| 239 | + */ |
|
| 240 | + public function getStorageRootId() { |
|
| 241 | + return $this->getShare()->getNodeId(); |
|
| 242 | + } |
|
| 243 | + |
|
| 244 | + /** |
|
| 245 | + * @return int |
|
| 246 | + */ |
|
| 247 | + public function getNumericStorageId() { |
|
| 248 | + if (!is_null($this->getShare()->getNodeCacheEntry())) { |
|
| 249 | + return $this->getShare()->getNodeCacheEntry()->getStorageId(); |
|
| 250 | + } else { |
|
| 251 | + $builder = Server::get(IDBConnection::class)->getQueryBuilder(); |
|
| 252 | + |
|
| 253 | + $query = $builder->select('storage') |
|
| 254 | + ->from('filecache') |
|
| 255 | + ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($this->getStorageRootId()))); |
|
| 256 | + |
|
| 257 | + $result = $query->executeQuery(); |
|
| 258 | + $row = $result->fetchAssociative(); |
|
| 259 | + $result->closeCursor(); |
|
| 260 | + if ($row) { |
|
| 261 | + return (int)$row['storage']; |
|
| 262 | + } |
|
| 263 | + return -1; |
|
| 264 | + } |
|
| 265 | + } |
|
| 266 | + |
|
| 267 | + public function getMountType() { |
|
| 268 | + return 'shared'; |
|
| 269 | + } |
|
| 270 | 270 | } |
@@ -53,12 +53,12 @@ discard block |
||
| 53 | 53 | $this->superShare = $arguments['superShare']; |
| 54 | 54 | $this->groupedShares = $arguments['groupedShares']; |
| 55 | 55 | |
| 56 | - $absMountPoint = '/' . $user->getUID() . '/files/' . trim($this->superShare->getTarget(), '/') . '/'; |
|
| 56 | + $absMountPoint = '/'.$user->getUID().'/files/'.trim($this->superShare->getTarget(), '/').'/'; |
|
| 57 | 57 | |
| 58 | 58 | // after the mountpoint is verified for the first time, only new mountpoints (e.g. groupfolders can overwrite the target) |
| 59 | 59 | if (!$alreadyVerified || isset($mountpoints[$absMountPoint])) { |
| 60 | 60 | $newMountPoint = $this->verifyMountPoint($this->superShare, $mountpoints, $folderExistCache); |
| 61 | - $absMountPoint = '/' . $user->getUID() . '/files/' . trim($newMountPoint, '/') . '/'; |
|
| 61 | + $absMountPoint = '/'.$user->getUID().'/files/'.trim($newMountPoint, '/').'/'; |
|
| 62 | 62 | } |
| 63 | 63 | |
| 64 | 64 | parent::__construct($storage, $absMountPoint, $arguments, $loader, null, null, MountProvider::class); |
@@ -96,7 +96,7 @@ discard block |
||
| 96 | 96 | } |
| 97 | 97 | |
| 98 | 98 | $newMountPoint = $this->generateUniqueTarget( |
| 99 | - Filesystem::normalizePath($parent . '/' . $mountPoint), |
|
| 99 | + Filesystem::normalizePath($parent.'/'.$mountPoint), |
|
| 100 | 100 | $this->recipientView, |
| 101 | 101 | $mountpoints |
| 102 | 102 | ); |
@@ -135,15 +135,15 @@ discard block |
||
| 135 | 135 | */ |
| 136 | 136 | private function generateUniqueTarget($path, $view, array $mountpoints) { |
| 137 | 137 | $pathinfo = pathinfo($path); |
| 138 | - $ext = isset($pathinfo['extension']) ? '.' . $pathinfo['extension'] : ''; |
|
| 138 | + $ext = isset($pathinfo['extension']) ? '.'.$pathinfo['extension'] : ''; |
|
| 139 | 139 | $name = $pathinfo['filename']; |
| 140 | 140 | $dir = $pathinfo['dirname']; |
| 141 | 141 | |
| 142 | 142 | $i = 2; |
| 143 | - $absolutePath = $this->recipientView->getAbsolutePath($path) . '/'; |
|
| 143 | + $absolutePath = $this->recipientView->getAbsolutePath($path).'/'; |
|
| 144 | 144 | while ($view->file_exists($path) || isset($mountpoints[$absolutePath])) { |
| 145 | - $path = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext); |
|
| 146 | - $absolutePath = $this->recipientView->getAbsolutePath($path) . '/'; |
|
| 145 | + $path = Filesystem::normalizePath($dir.'/'.$name.' ('.$i.')'.$ext); |
|
| 146 | + $absolutePath = $this->recipientView->getAbsolutePath($path).'/'; |
|
| 147 | 147 | $i++; |
| 148 | 148 | } |
| 149 | 149 | |
@@ -163,7 +163,7 @@ discard block |
||
| 163 | 163 | |
| 164 | 164 | // it is not a file relative to data/user/files |
| 165 | 165 | if (count($split) < 3 || $split[1] !== 'files') { |
| 166 | - Server::get(LoggerInterface::class)->error('Can not strip userid and "files/" from path: ' . $path, ['app' => 'files_sharing']); |
|
| 166 | + Server::get(LoggerInterface::class)->error('Can not strip userid and "files/" from path: '.$path, ['app' => 'files_sharing']); |
|
| 167 | 167 | throw new BrokenPath('Path does not start with /user/files', 10); |
| 168 | 168 | } |
| 169 | 169 | |
@@ -171,7 +171,7 @@ discard block |
||
| 171 | 171 | $sliced = array_slice($split, 2); |
| 172 | 172 | $relPath = implode('/', $sliced); |
| 173 | 173 | |
| 174 | - return '/' . $relPath; |
|
| 174 | + return '/'.$relPath; |
|
| 175 | 175 | } |
| 176 | 176 | |
| 177 | 177 | /** |
@@ -192,7 +192,7 @@ discard block |
||
| 192 | 192 | $this->storage->setMountPoint($relTargetPath); |
| 193 | 193 | } catch (\Exception $e) { |
| 194 | 194 | Server::get(LoggerInterface::class)->error( |
| 195 | - 'Could not rename mount point for shared folder "' . $this->getMountPoint() . '" to "' . $target . '"', |
|
| 195 | + 'Could not rename mount point for shared folder "'.$this->getMountPoint().'" to "'.$target.'"', |
|
| 196 | 196 | [ |
| 197 | 197 | 'app' => 'files_sharing', |
| 198 | 198 | 'exception' => $e, |
@@ -258,7 +258,7 @@ discard block |
||
| 258 | 258 | $row = $result->fetchAssociative(); |
| 259 | 259 | $result->closeCursor(); |
| 260 | 260 | if ($row) { |
| 261 | - return (int)$row['storage']; |
|
| 261 | + return (int) $row['storage']; |
|
| 262 | 262 | } |
| 263 | 263 | return -1; |
| 264 | 264 | } |
@@ -21,112 +21,112 @@ |
||
| 21 | 21 | */ |
| 22 | 22 | class DeleteOrphanedSharesJob extends TimedJob { |
| 23 | 23 | |
| 24 | - use TTransactional; |
|
| 24 | + use TTransactional; |
|
| 25 | 25 | |
| 26 | - private const CHUNK_SIZE = 1000; |
|
| 26 | + private const CHUNK_SIZE = 1000; |
|
| 27 | 27 | |
| 28 | - private const INTERVAL = 24 * 60 * 60; |
|
| 28 | + private const INTERVAL = 24 * 60 * 60; |
|
| 29 | 29 | |
| 30 | - /** |
|
| 31 | - * sets the correct interval for this timed job |
|
| 32 | - */ |
|
| 33 | - public function __construct( |
|
| 34 | - ITimeFactory $time, |
|
| 35 | - private IDBConnection $db, |
|
| 36 | - private LoggerInterface $logger, |
|
| 37 | - ) { |
|
| 38 | - parent::__construct($time); |
|
| 30 | + /** |
|
| 31 | + * sets the correct interval for this timed job |
|
| 32 | + */ |
|
| 33 | + public function __construct( |
|
| 34 | + ITimeFactory $time, |
|
| 35 | + private IDBConnection $db, |
|
| 36 | + private LoggerInterface $logger, |
|
| 37 | + ) { |
|
| 38 | + parent::__construct($time); |
|
| 39 | 39 | |
| 40 | - $this->setInterval(self::INTERVAL); // 1 day |
|
| 41 | - $this->setTimeSensitivity(self::TIME_INSENSITIVE); |
|
| 42 | - } |
|
| 40 | + $this->setInterval(self::INTERVAL); // 1 day |
|
| 41 | + $this->setTimeSensitivity(self::TIME_INSENSITIVE); |
|
| 42 | + } |
|
| 43 | 43 | |
| 44 | - /** |
|
| 45 | - * Makes the background job do its work |
|
| 46 | - * |
|
| 47 | - * @param array $argument unused argument |
|
| 48 | - */ |
|
| 49 | - public function run($argument) { |
|
| 50 | - if ($this->db->getShardDefinition('filecache')) { |
|
| 51 | - $this->shardingCleanup(); |
|
| 52 | - return; |
|
| 53 | - } |
|
| 44 | + /** |
|
| 45 | + * Makes the background job do its work |
|
| 46 | + * |
|
| 47 | + * @param array $argument unused argument |
|
| 48 | + */ |
|
| 49 | + public function run($argument) { |
|
| 50 | + if ($this->db->getShardDefinition('filecache')) { |
|
| 51 | + $this->shardingCleanup(); |
|
| 52 | + return; |
|
| 53 | + } |
|
| 54 | 54 | |
| 55 | - $qbSelect = $this->db->getQueryBuilder(); |
|
| 56 | - $qbSelect->select('id') |
|
| 57 | - ->from('share', 's') |
|
| 58 | - ->leftJoin('s', 'filecache', 'fc', $qbSelect->expr()->eq('s.file_source', 'fc.fileid')) |
|
| 59 | - ->where($qbSelect->expr()->isNull('fc.fileid')) |
|
| 60 | - ->setMaxResults(self::CHUNK_SIZE); |
|
| 61 | - $deleteQb = $this->db->getQueryBuilder(); |
|
| 62 | - $deleteQb->delete('share') |
|
| 63 | - ->where( |
|
| 64 | - $deleteQb->expr()->in('id', $deleteQb->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY) |
|
| 65 | - ); |
|
| 55 | + $qbSelect = $this->db->getQueryBuilder(); |
|
| 56 | + $qbSelect->select('id') |
|
| 57 | + ->from('share', 's') |
|
| 58 | + ->leftJoin('s', 'filecache', 'fc', $qbSelect->expr()->eq('s.file_source', 'fc.fileid')) |
|
| 59 | + ->where($qbSelect->expr()->isNull('fc.fileid')) |
|
| 60 | + ->setMaxResults(self::CHUNK_SIZE); |
|
| 61 | + $deleteQb = $this->db->getQueryBuilder(); |
|
| 62 | + $deleteQb->delete('share') |
|
| 63 | + ->where( |
|
| 64 | + $deleteQb->expr()->in('id', $deleteQb->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY) |
|
| 65 | + ); |
|
| 66 | 66 | |
| 67 | - /** |
|
| 68 | - * Read a chunk of orphan rows and delete them. Continue as long as the |
|
| 69 | - * chunk is filled and time before the next cron run does not run out. |
|
| 70 | - * |
|
| 71 | - * Note: With isolation level READ COMMITTED, the database will allow |
|
| 72 | - * other transactions to delete rows between our SELECT and DELETE. In |
|
| 73 | - * that (unlikely) case, our DELETE will have fewer affected rows than |
|
| 74 | - * IDs passed for the WHERE IN. If this happens while processing a full |
|
| 75 | - * chunk, the logic below will stop prematurely. |
|
| 76 | - * Note: The queries below are optimized for low database locking. They |
|
| 77 | - * could be combined into one single DELETE with join or sub query, but |
|
| 78 | - * that has shown to (dead)lock often. |
|
| 79 | - */ |
|
| 80 | - $cutOff = $this->time->getTime() + self::INTERVAL; |
|
| 81 | - do { |
|
| 82 | - $deleted = $this->atomic(function () use ($qbSelect, $deleteQb) { |
|
| 83 | - $result = $qbSelect->executeQuery(); |
|
| 84 | - $ids = array_map('intval', $result->fetchFirstColumn()); |
|
| 85 | - $result->closeCursor(); |
|
| 86 | - $deleteQb->setParameter('ids', $ids, IQueryBuilder::PARAM_INT_ARRAY); |
|
| 87 | - $deleted = $deleteQb->executeStatement(); |
|
| 88 | - $this->logger->debug('{deleted} orphaned share(s) deleted', [ |
|
| 89 | - 'app' => 'DeleteOrphanedSharesJob', |
|
| 90 | - 'deleted' => $deleted, |
|
| 91 | - ]); |
|
| 92 | - return $deleted; |
|
| 93 | - }, $this->db); |
|
| 94 | - } while ($deleted >= self::CHUNK_SIZE && $this->time->getTime() <= $cutOff); |
|
| 95 | - } |
|
| 67 | + /** |
|
| 68 | + * Read a chunk of orphan rows and delete them. Continue as long as the |
|
| 69 | + * chunk is filled and time before the next cron run does not run out. |
|
| 70 | + * |
|
| 71 | + * Note: With isolation level READ COMMITTED, the database will allow |
|
| 72 | + * other transactions to delete rows between our SELECT and DELETE. In |
|
| 73 | + * that (unlikely) case, our DELETE will have fewer affected rows than |
|
| 74 | + * IDs passed for the WHERE IN. If this happens while processing a full |
|
| 75 | + * chunk, the logic below will stop prematurely. |
|
| 76 | + * Note: The queries below are optimized for low database locking. They |
|
| 77 | + * could be combined into one single DELETE with join or sub query, but |
|
| 78 | + * that has shown to (dead)lock often. |
|
| 79 | + */ |
|
| 80 | + $cutOff = $this->time->getTime() + self::INTERVAL; |
|
| 81 | + do { |
|
| 82 | + $deleted = $this->atomic(function () use ($qbSelect, $deleteQb) { |
|
| 83 | + $result = $qbSelect->executeQuery(); |
|
| 84 | + $ids = array_map('intval', $result->fetchFirstColumn()); |
|
| 85 | + $result->closeCursor(); |
|
| 86 | + $deleteQb->setParameter('ids', $ids, IQueryBuilder::PARAM_INT_ARRAY); |
|
| 87 | + $deleted = $deleteQb->executeStatement(); |
|
| 88 | + $this->logger->debug('{deleted} orphaned share(s) deleted', [ |
|
| 89 | + 'app' => 'DeleteOrphanedSharesJob', |
|
| 90 | + 'deleted' => $deleted, |
|
| 91 | + ]); |
|
| 92 | + return $deleted; |
|
| 93 | + }, $this->db); |
|
| 94 | + } while ($deleted >= self::CHUNK_SIZE && $this->time->getTime() <= $cutOff); |
|
| 95 | + } |
|
| 96 | 96 | |
| 97 | - private function shardingCleanup(): void { |
|
| 98 | - $qb = $this->db->getQueryBuilder(); |
|
| 99 | - $qb->selectDistinct('file_source') |
|
| 100 | - ->from('share', 's'); |
|
| 101 | - $sourceFiles = $qb->executeQuery()->fetchFirstColumn(); |
|
| 97 | + private function shardingCleanup(): void { |
|
| 98 | + $qb = $this->db->getQueryBuilder(); |
|
| 99 | + $qb->selectDistinct('file_source') |
|
| 100 | + ->from('share', 's'); |
|
| 101 | + $sourceFiles = $qb->executeQuery()->fetchFirstColumn(); |
|
| 102 | 102 | |
| 103 | - $deleteQb = $this->db->getQueryBuilder(); |
|
| 104 | - $deleteQb->delete('share') |
|
| 105 | - ->where( |
|
| 106 | - $deleteQb->expr()->in('file_source', $deleteQb->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY) |
|
| 107 | - ); |
|
| 103 | + $deleteQb = $this->db->getQueryBuilder(); |
|
| 104 | + $deleteQb->delete('share') |
|
| 105 | + ->where( |
|
| 106 | + $deleteQb->expr()->in('file_source', $deleteQb->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY) |
|
| 107 | + ); |
|
| 108 | 108 | |
| 109 | - $chunks = array_chunk($sourceFiles, self::CHUNK_SIZE); |
|
| 110 | - foreach ($chunks as $chunk) { |
|
| 111 | - $deletedFiles = $this->findMissingSources($chunk); |
|
| 112 | - $this->atomic(function () use ($deletedFiles, $deleteQb) { |
|
| 113 | - $deleteQb->setParameter('ids', $deletedFiles, IQueryBuilder::PARAM_INT_ARRAY); |
|
| 114 | - $deleted = $deleteQb->executeStatement(); |
|
| 115 | - $this->logger->debug('{deleted} orphaned share(s) deleted', [ |
|
| 116 | - 'app' => 'DeleteOrphanedSharesJob', |
|
| 117 | - 'deleted' => $deleted, |
|
| 118 | - ]); |
|
| 119 | - return $deleted; |
|
| 120 | - }, $this->db); |
|
| 121 | - } |
|
| 122 | - } |
|
| 109 | + $chunks = array_chunk($sourceFiles, self::CHUNK_SIZE); |
|
| 110 | + foreach ($chunks as $chunk) { |
|
| 111 | + $deletedFiles = $this->findMissingSources($chunk); |
|
| 112 | + $this->atomic(function () use ($deletedFiles, $deleteQb) { |
|
| 113 | + $deleteQb->setParameter('ids', $deletedFiles, IQueryBuilder::PARAM_INT_ARRAY); |
|
| 114 | + $deleted = $deleteQb->executeStatement(); |
|
| 115 | + $this->logger->debug('{deleted} orphaned share(s) deleted', [ |
|
| 116 | + 'app' => 'DeleteOrphanedSharesJob', |
|
| 117 | + 'deleted' => $deleted, |
|
| 118 | + ]); |
|
| 119 | + return $deleted; |
|
| 120 | + }, $this->db); |
|
| 121 | + } |
|
| 122 | + } |
|
| 123 | 123 | |
| 124 | - private function findMissingSources(array $ids): array { |
|
| 125 | - $qb = $this->db->getQueryBuilder(); |
|
| 126 | - $qb->select('fileid') |
|
| 127 | - ->from('filecache') |
|
| 128 | - ->where($qb->expr()->in('fileid', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))); |
|
| 129 | - $found = $qb->executeQuery()->fetchFirstColumn(); |
|
| 130 | - return array_diff($ids, $found); |
|
| 131 | - } |
|
| 124 | + private function findMissingSources(array $ids): array { |
|
| 125 | + $qb = $this->db->getQueryBuilder(); |
|
| 126 | + $qb->select('fileid') |
|
| 127 | + ->from('filecache') |
|
| 128 | + ->where($qb->expr()->in('fileid', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))); |
|
| 129 | + $found = $qb->executeQuery()->fetchFirstColumn(); |
|
| 130 | + return array_diff($ids, $found); |
|
| 131 | + } |
|
| 132 | 132 | } |
@@ -79,7 +79,7 @@ discard block |
||
| 79 | 79 | */ |
| 80 | 80 | $cutOff = $this->time->getTime() + self::INTERVAL; |
| 81 | 81 | do { |
| 82 | - $deleted = $this->atomic(function () use ($qbSelect, $deleteQb) { |
|
| 82 | + $deleted = $this->atomic(function() use ($qbSelect, $deleteQb) { |
|
| 83 | 83 | $result = $qbSelect->executeQuery(); |
| 84 | 84 | $ids = array_map('intval', $result->fetchFirstColumn()); |
| 85 | 85 | $result->closeCursor(); |
@@ -109,7 +109,7 @@ discard block |
||
| 109 | 109 | $chunks = array_chunk($sourceFiles, self::CHUNK_SIZE); |
| 110 | 110 | foreach ($chunks as $chunk) { |
| 111 | 111 | $deletedFiles = $this->findMissingSources($chunk); |
| 112 | - $this->atomic(function () use ($deletedFiles, $deleteQb) { |
|
| 112 | + $this->atomic(function() use ($deletedFiles, $deleteQb) { |
|
| 113 | 113 | $deleteQb->setParameter('ids', $deletedFiles, IQueryBuilder::PARAM_INT_ARRAY); |
| 114 | 114 | $deleted = $deleteQb->executeStatement(); |
| 115 | 115 | $this->logger->debug('{deleted} orphaned share(s) deleted', [ |
@@ -17,52 +17,52 @@ |
||
| 17 | 17 | use OCP\Server; |
| 18 | 18 | |
| 19 | 19 | class MountProvider implements IMountProvider { |
| 20 | - public const STORAGE = '\OCA\Files_Sharing\External\Storage'; |
|
| 20 | + public const STORAGE = '\OCA\Files_Sharing\External\Storage'; |
|
| 21 | 21 | |
| 22 | - /** |
|
| 23 | - * @var callable |
|
| 24 | - */ |
|
| 25 | - private $managerProvider; |
|
| 22 | + /** |
|
| 23 | + * @var callable |
|
| 24 | + */ |
|
| 25 | + private $managerProvider; |
|
| 26 | 26 | |
| 27 | - /** |
|
| 28 | - * @param IDBConnection $connection |
|
| 29 | - * @param callable $managerProvider due to setup order we need a callable that return the manager instead of the manager itself |
|
| 30 | - * @param ICloudIdManager $cloudIdManager |
|
| 31 | - */ |
|
| 32 | - public function __construct( |
|
| 33 | - private IDBConnection $connection, |
|
| 34 | - callable $managerProvider, |
|
| 35 | - private ICloudIdManager $cloudIdManager, |
|
| 36 | - ) { |
|
| 37 | - $this->managerProvider = $managerProvider; |
|
| 38 | - } |
|
| 27 | + /** |
|
| 28 | + * @param IDBConnection $connection |
|
| 29 | + * @param callable $managerProvider due to setup order we need a callable that return the manager instead of the manager itself |
|
| 30 | + * @param ICloudIdManager $cloudIdManager |
|
| 31 | + */ |
|
| 32 | + public function __construct( |
|
| 33 | + private IDBConnection $connection, |
|
| 34 | + callable $managerProvider, |
|
| 35 | + private ICloudIdManager $cloudIdManager, |
|
| 36 | + ) { |
|
| 37 | + $this->managerProvider = $managerProvider; |
|
| 38 | + } |
|
| 39 | 39 | |
| 40 | - public function getMount(IUser $user, $data, IStorageFactory $storageFactory) { |
|
| 41 | - $managerProvider = $this->managerProvider; |
|
| 42 | - $manager = $managerProvider(); |
|
| 43 | - $data['manager'] = $manager; |
|
| 44 | - $mountPoint = '/' . $user->getUID() . '/files/' . ltrim($data['mountpoint'], '/'); |
|
| 45 | - $data['mountpoint'] = $mountPoint; |
|
| 46 | - $data['cloudId'] = $this->cloudIdManager->getCloudId($data['owner'], $data['remote']); |
|
| 47 | - $data['certificateManager'] = \OC::$server->getCertificateManager(); |
|
| 48 | - $data['HttpClientService'] = Server::get(IClientService::class); |
|
| 49 | - return new Mount(self::STORAGE, $mountPoint, $data, $manager, $storageFactory); |
|
| 50 | - } |
|
| 40 | + public function getMount(IUser $user, $data, IStorageFactory $storageFactory) { |
|
| 41 | + $managerProvider = $this->managerProvider; |
|
| 42 | + $manager = $managerProvider(); |
|
| 43 | + $data['manager'] = $manager; |
|
| 44 | + $mountPoint = '/' . $user->getUID() . '/files/' . ltrim($data['mountpoint'], '/'); |
|
| 45 | + $data['mountpoint'] = $mountPoint; |
|
| 46 | + $data['cloudId'] = $this->cloudIdManager->getCloudId($data['owner'], $data['remote']); |
|
| 47 | + $data['certificateManager'] = \OC::$server->getCertificateManager(); |
|
| 48 | + $data['HttpClientService'] = Server::get(IClientService::class); |
|
| 49 | + return new Mount(self::STORAGE, $mountPoint, $data, $manager, $storageFactory); |
|
| 50 | + } |
|
| 51 | 51 | |
| 52 | - public function getMountsForUser(IUser $user, IStorageFactory $loader) { |
|
| 53 | - $qb = $this->connection->getQueryBuilder(); |
|
| 54 | - $qb->select('remote', 'share_token', 'password', 'mountpoint', 'owner') |
|
| 55 | - ->from('share_external') |
|
| 56 | - ->where($qb->expr()->eq('user', $qb->createNamedParameter($user->getUID()))) |
|
| 57 | - ->andWhere($qb->expr()->eq('accepted', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT))); |
|
| 58 | - $result = $qb->executeQuery(); |
|
| 59 | - $mounts = []; |
|
| 60 | - while ($row = $result->fetchAssociative()) { |
|
| 61 | - $row['manager'] = $this; |
|
| 62 | - $row['token'] = $row['share_token']; |
|
| 63 | - $mounts[] = $this->getMount($user, $row, $loader); |
|
| 64 | - } |
|
| 65 | - $result->closeCursor(); |
|
| 66 | - return $mounts; |
|
| 67 | - } |
|
| 52 | + public function getMountsForUser(IUser $user, IStorageFactory $loader) { |
|
| 53 | + $qb = $this->connection->getQueryBuilder(); |
|
| 54 | + $qb->select('remote', 'share_token', 'password', 'mountpoint', 'owner') |
|
| 55 | + ->from('share_external') |
|
| 56 | + ->where($qb->expr()->eq('user', $qb->createNamedParameter($user->getUID()))) |
|
| 57 | + ->andWhere($qb->expr()->eq('accepted', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT))); |
|
| 58 | + $result = $qb->executeQuery(); |
|
| 59 | + $mounts = []; |
|
| 60 | + while ($row = $result->fetchAssociative()) { |
|
| 61 | + $row['manager'] = $this; |
|
| 62 | + $row['token'] = $row['share_token']; |
|
| 63 | + $mounts[] = $this->getMount($user, $row, $loader); |
|
| 64 | + } |
|
| 65 | + $result->closeCursor(); |
|
| 66 | + return $mounts; |
|
| 67 | + } |
|
| 68 | 68 | } |