diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index f7bbdd812..2dc85d25b 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
-require_once './vendor/autoload.php';
+require_once __DIR__ . '/vendor/autoload.php';
use Nextcloud\CodingStandard\Config;
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 0f6774bdb..20185c650 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -43,4 +43,8 @@
OCA\Photos\Sabre\Album\PropFindPlugin
+
+
+ OCA\Photos\Jobs\AutomaticLocationMapperJob
+
\ No newline at end of file
diff --git a/lib/Command/MapMediaToLocationCommand.php b/lib/Command/MapMediaToLocationCommand.php
index 900840b82..5895b2919 100644
--- a/lib/Command/MapMediaToLocationCommand.php
+++ b/lib/Command/MapMediaToLocationCommand.php
@@ -74,6 +74,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
private function scanForAllUsers(OutputInterface $output): void {
$users = $this->userManager->search('');
+ $output->writeln("Scanning all users:");
foreach ($users as $user) {
$this->scanFilesForUser($user->getUID(), $output);
}
@@ -81,9 +82,11 @@ private function scanForAllUsers(OutputInterface $output): void {
private function scanFilesForUser(string $userId, OutputInterface $output): void {
$userFolder = $this->rootFolder->getUserFolder($userId);
- $output->write("- Scanning files for $userId");
+ $output->write(" - Scanning files for $userId");
+ $startTime = time();
$count = $this->scanFolder($userFolder);
- $output->writeln("\r- Scanned $count files for $userId");
+ $timeElapse = time() - $startTime;
+ $output->writeln(" - $count files, $timeElapse sec");
}
private function scanFolder(Folder $folder): int {
@@ -104,7 +107,7 @@ private function scanFolder(Folder $folder): int {
continue;
}
- $this->mediaLocationManager->addLocationForFileAndUser($node->getId());
+ $this->mediaLocationManager->setLocationForFile($node->getId());
$count++;
}
diff --git a/lib/DB/Location/LocationMapper.php b/lib/DB/Location/LocationMapper.php
index d3bbbfc8d..e3b9cac39 100644
--- a/lib/DB/Location/LocationMapper.php
+++ b/lib/DB/Location/LocationMapper.php
@@ -99,7 +99,7 @@ public function findFilesForUserAndLocation(string $userId, string $location) {
);
}
- public function addLocationForFileAndUser(string $location, int $fileId): void {
+ public function setLocationForFile(string $location, int $fileId): void {
try {
$query = $this->connection->getQueryBuilder();
$query->insert('file_metadata')
diff --git a/lib/Jobs/AutomaticLocationMapperJob.php b/lib/Jobs/AutomaticLocationMapperJob.php
new file mode 100644
index 000000000..121cd7c6f
--- /dev/null
+++ b/lib/Jobs/AutomaticLocationMapperJob.php
@@ -0,0 +1,110 @@
+
+ *
+ * @author Louis Chemineau
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Photos\Jobs;
+
+use OCA\Photos\AppInfo\Application;
+use OCA\Photos\Service\MediaLocationManager;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\TimedJob;
+use OCP\Files\Folder;
+use OCP\Files\IRootFolder;
+use OCP\IConfig;
+use OCP\IUserManager;
+
+class AutomaticLocationMapperJob extends TimedJob {
+ public function __construct(
+ ITimeFactory $time,
+ private IConfig $config,
+ private IRootFolder $rootFolder,
+ private IUserManager $userManager,
+ private MediaLocationManager $mediaLocationManager,
+ ) {
+ parent::__construct($time);
+ $this->mediaLocationManager = $mediaLocationManager;
+
+ $this->setTimeSensitivity(\OCP\BackgroundJob\IJob::TIME_INSENSITIVE);
+ $this->setInterval(24 * 3600);
+ }
+
+ protected function run($argument) {
+ $nextUser = $this->config->getAppValue(Application::APP_ID, 'nextUserToScan', '');
+ $startTime = null;
+ $users = $this->userManager->search('');
+
+ if ($nextUser === '') {
+ $nextUser = $users[array_key_first($users)]->getUID();
+ }
+
+ foreach ($users as $user) {
+ if ($startTime === null) {
+ // Skip all user before nextUser.
+ if ($nextUser === $user->getUID()) {
+ $startTime = time();
+ } else {
+ continue;
+ }
+ }
+
+ // Stop if execution time is more than one hour.
+ // And register another job for later.
+ if (time() - $startTime > 60 * 60) {
+ $this->config->setAppValue(Application::APP_ID, 'nextUserToScan', $user->getUID());
+ return;
+ }
+
+ $this->scanFilesForUser($user->getUID());
+ }
+
+ // TODO: kill this background job
+ $this->config->setAppValue(Application::APP_ID, 'nextUserToScan', '');
+ }
+
+ private function scanFilesForUser(string $userId): void {
+ $userFolder = $this->rootFolder->getUserFolder($userId);
+ $this->scanFolder($userFolder);
+ }
+
+
+ private function scanFolder(Folder $folder): void {
+ // Do not scan share and other moveable mounts.
+ if ($folder->getMountPoint() instanceof \OC\Files\Mount\MoveableMount) {
+ return;
+ }
+
+ foreach ($folder->getDirectoryListing() as $node) {
+ if ($node instanceof Folder) {
+ $this->scanFolder($node);
+ continue;
+ }
+
+ if (!str_starts_with($node->getMimeType(), 'image')) {
+ continue;
+ }
+
+ $this->mediaLocationManager->setLocationForFile($node->getId());
+ }
+ }
+}
diff --git a/lib/Jobs/MapMediaToLocationJob.php b/lib/Jobs/MapMediaToLocationJob.php
index 59c525129..1f3a9f4f0 100644
--- a/lib/Jobs/MapMediaToLocationJob.php
+++ b/lib/Jobs/MapMediaToLocationJob.php
@@ -43,6 +43,6 @@ public function __construct(
protected function run($argument) {
[$fileId] = $argument;
- $this->mediaLocationManager->addLocationForFileAndUser($fileId);
+ $this->mediaLocationManager->setLocationForFile($fileId);
}
}
diff --git a/lib/Listener/LocationManagerEventListener.php b/lib/Listener/LocationManagerEventListener.php
index e737bad61..9a78243e8 100644
--- a/lib/Listener/LocationManagerEventListener.php
+++ b/lib/Listener/LocationManagerEventListener.php
@@ -31,9 +31,7 @@
use OCP\IConfig;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
-use OCP\Files\Events\Node\NodeDeletedEvent;
use OCP\Files\Events\Node\NodeWrittenEvent;
-use OCP\User\Events\UserDeletedEvent;
/**
* Listener to create, update or remove location info from the database.
diff --git a/lib/Service/MediaLocationManager.php b/lib/Service/MediaLocationManager.php
index ef5a6c7a6..ddaa24d34 100644
--- a/lib/Service/MediaLocationManager.php
+++ b/lib/Service/MediaLocationManager.php
@@ -36,14 +36,14 @@ public function __construct(
) {
}
- public function addLocationForFileAndUser(int $fileId): void {
+ public function setLocationForFile(int $fileId): void {
$location = $this->getLocationForFile($fileId);
if ($location === null) {
return;
}
- $this->locationMapper->addLocationForFileAndUser($location, $fileId);
+ $this->locationMapper->setLocationForFile($location, $fileId);
}
public function updateLocationForFile(int $fileId): void {
diff --git a/lib/Service/ReverseGeoCoderService.php b/lib/Service/ReverseGeoCoderService.php
index 5b57b1362..94685b02f 100644
--- a/lib/Service/ReverseGeoCoderService.php
+++ b/lib/Service/ReverseGeoCoderService.php
@@ -55,15 +55,7 @@ public function __construct(
}
public function getLocationForCoordinates(float $latitude, float $longitude): string {
- if ($this->fsSearcher === null) {
- $this->buildKDTree();
- $kdTreeFileContent = $this->geoNameFolder->getFile("cities1000.bin")->getContent();
- $kdTreeTmpFileName = tempnam(sys_get_temp_dir(), "nextcloud_photos_");
- file_put_contents($kdTreeTmpFileName, $kdTreeFileContent);
- $fsTree = new FSKDTree($kdTreeTmpFileName, new ItemFactory());
- $this->fsSearcher = new NearestSearch($fsTree);
- }
-
+ $this->loadKdTree();
$result = $this->fsSearcher->search(new Point([$latitude, $longitude]), 1);
return $this->getLocationNameForLocationId($result[0]->getId());
}
@@ -160,4 +152,17 @@ public function buildKDTree($force = false): void {
$kdTreeString = file_get_contents($kdTreeTmpFileName);
$this->geoNameFolder->newFile('cities1000.bin', $kdTreeString);
}
+
+ private function loadKdTree(): void {
+ if ($this->fsSearcher !== null) {
+ return;
+ }
+
+ $this->buildKDTree();
+ $kdTreeFileContent = $this->geoNameFolder->getFile("cities1000.bin")->getContent();
+ $kdTreeTmpFileName = tempnam(sys_get_temp_dir(), "nextcloud_photos_");
+ file_put_contents($kdTreeTmpFileName, $kdTreeFileContent);
+ $fsTree = new FSKDTree($kdTreeTmpFileName, new ItemFactory());
+ $this->fsSearcher = new NearestSearch($fsTree);
+ }
}
diff --git a/tests/stub.phpstub b/tests/stub.phpstub
index b0232e08d..1cf150a3b 100644
--- a/tests/stub.phpstub
+++ b/tests/stub.phpstub
@@ -118,6 +118,7 @@ namespace Symfony\Component\Console\Question {
namespace Symfony\Component\Console\Output {
class OutputInterface {
public const VERBOSITY_VERBOSE = 1;
+ public function write($messages, $newline = false, $options = 0);
public function writeln(string $text, int $flat = 0) {}
}
}
@@ -686,3 +687,25 @@ namespace OCA\DAV\Upload {
namespace Doctrine\DBAL\Exception {
class UniqueConstraintViolationException extends \Exception {}
}
+
+namespace OC\Files\Mount;
+
+/**
+ * Defines the mount point to be (re)moved by the user
+ */
+interface MoveableMount {
+ /**
+ * Move the mount point to $target
+ *
+ * @param string $target the target mount point
+ * @return bool
+ */
+ public function moveMount($target);
+
+ /**
+ * Remove the mount points
+ *
+ * @return bool
+ */
+ public function removeMount();
+}