Skip to content

Commit

Permalink
Add background job to map users' picture
Browse files Browse the repository at this point in the history
Signed-off-by: Louis Chemineau <louis@chmn.me>
  • Loading branch information
artonge committed Feb 20, 2023
1 parent 58e7f53 commit 4d4f6fd
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

require_once './vendor/autoload.php';
require_once __DIR__ . '/vendor/autoload.php';

use Nextcloud\CodingStandard\Config;

Expand Down
4 changes: 4 additions & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,8 @@
<plugin>OCA\Photos\Sabre\Album\PropFindPlugin</plugin>
</plugins>
</sabre>

<background-jobs>
<job>OCA\Photos\Jobs\AutomaticLocationMapperJob</job>
</background-jobs>
</info>
9 changes: 6 additions & 3 deletions lib/Command/MapMediaToLocationCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,19 @@ 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);
}
}

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 {
Expand All @@ -104,7 +107,7 @@ private function scanFolder(Folder $folder): int {
continue;
}

$this->mediaLocationManager->addLocationForFileAndUser($node->getId());
$this->mediaLocationManager->setLocationForFile($node->getId());
$count++;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/DB/Location/LocationMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
110 changes: 110 additions & 0 deletions lib/Jobs/AutomaticLocationMapperJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Louis Chemineau <louis@chmn.me>
*
* @author Louis Chemineau <louis@chmn.me>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/

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());
}
}
}
2 changes: 1 addition & 1 deletion lib/Jobs/MapMediaToLocationJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ public function __construct(
protected function run($argument) {
[$fileId] = $argument;

$this->mediaLocationManager->addLocationForFileAndUser($fileId);
$this->mediaLocationManager->setLocationForFile($fileId);
}
}
2 changes: 0 additions & 2 deletions lib/Listener/LocationManagerEventListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions lib/Service/MediaLocationManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
23 changes: 14 additions & 9 deletions lib/Service/ReverseGeoCoderService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down Expand Up @@ -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);
}
}
23 changes: 23 additions & 0 deletions tests/stub.phpstub
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
}
}
Expand Down Expand Up @@ -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();
}

0 comments on commit 4d4f6fd

Please sign in to comment.