Skip to content

Commit 2d310e1

Browse files
committed
Add CLI and hooks to reverse geocode pictures' coordinates
Signed-off-by: Louis Chemineau <[email protected]>
1 parent abc4f5f commit 2d310e1

16 files changed

Lines changed: 1051 additions & 63 deletions

appinfo/info.xml

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
<?xml version="1.0"?>
2-
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
3-
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
2+
<info xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
43
<id>photos</id>
54
<name>Photos</name>
65
<summary>Your memories under your control</summary>
76
<description>Your memories under your control</description>
8-
<version>1.8.0</version>
7+
<version>1.9.0</version>
98
<licence>agpl</licence>
10-
<author mail="[email protected]">John Molakvoæ</author>
9+
<author mail="[email protected]">John Molakvoæ</author>
1110
<namespace>Photos</namespace>
1211
<category>multimedia</category>
1312
<types>
1413
<dav />
1514
<authentication />
1615
</types>
1716

18-
<website>https://github.com/nextcloud/photos</website>
19-
<bugs>https://github.com/nextcloud/photos/issues</bugs>
17+
<website>https://github.com/nextcloud/photos</website>
18+
<bugs>https://github.com/nextcloud/photos/issues</bugs>
2019
<repository>https://github.com/nextcloud/photos.git</repository>
2120
<default_enable />
2221
<dependencies>
@@ -30,6 +29,11 @@
3029
</navigation>
3130
</navigations>
3231

32+
<commands>
33+
<command>OCA\Photos\Command\UpdateReverseGeocodingFiles</command>
34+
<command>OCA\Photos\Command\MapMediaToLocation</command>
35+
</commands>
36+
3337
<sabre>
3438
<collections>
3539
<collection>OCA\Photos\Sabre\RootCollection</collection>
@@ -39,4 +43,4 @@
3943
<plugin>OCA\Photos\Sabre\Album\PropFindPlugin</plugin>
4044
</plugins>
4145
</sabre>
42-
</info>
46+
</info>

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,8 @@
2020
"vimeo/psalm": "^4.22",
2121
"sabre/dav": "^4.2.1",
2222
"nextcloud/ocp": "dev-master"
23+
},
24+
"require": {
25+
"hexogen/kdtree": "^0.2.0"
2326
}
2427
}

composer.lock

Lines changed: 64 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/Album/AlbumFile.php

Lines changed: 11 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,11 @@
2323

2424
namespace OCA\Photos\Album;
2525

26-
use OC\Metadata\FileMetadata;
26+
use OCA\Photos\DB\PhotosFile;
2727

28-
class AlbumFile {
29-
private int $fileId;
30-
private string $name;
31-
private string $mimeType;
32-
private int $size;
33-
private int $mtime;
34-
private string $etag;
28+
class AlbumFile extends PhotosFile{
3529
private int $added;
3630
private string $owner;
37-
/** @var array<string, FileMetadata> */
38-
private array $metaData = [];
3931

4032
public function __construct(
4133
int $fileId,
@@ -47,52 +39,19 @@ public function __construct(
4739
int $added,
4840
string $owner
4941
) {
50-
$this->fileId = $fileId;
51-
$this->name = $name;
52-
$this->mimeType = $mimeType;
53-
$this->size = $size;
54-
$this->mtime = $mtime;
55-
$this->etag = $etag;
42+
parent::__construct(
43+
$fileId,
44+
$name,
45+
$mimeType,
46+
$size,
47+
$mtime,
48+
$etag
49+
);
50+
5651
$this->added = $added;
5752
$this->owner = $owner;
5853
}
5954

60-
public function getFileId(): int {
61-
return $this->fileId;
62-
}
63-
64-
public function getName(): string {
65-
return $this->name;
66-
}
67-
68-
public function getMimeType(): string {
69-
return $this->mimeType;
70-
}
71-
72-
public function getSize(): int {
73-
return $this->size;
74-
}
75-
76-
public function getMTime(): int {
77-
return $this->mtime;
78-
}
79-
80-
public function getEtag(): string {
81-
return $this->etag;
82-
}
83-
84-
public function setMetadata(string $key, FileMetadata $value): void {
85-
$this->metaData[$key] = $value;
86-
}
87-
88-
public function hasMetadata(string $key): bool {
89-
return isset($this->metaData[$key]);
90-
}
91-
92-
public function getMetadata(string $key): FileMetadata {
93-
return $this->metaData[$key];
94-
}
95-
9655
public function getAdded(): int {
9756
return $this->added;
9857
}

lib/AppInfo/Application.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,17 @@
2929
use OCA\Photos\Listener\SabrePluginAuthInitListener;
3030
use OCA\DAV\Connector\Sabre\Principal;
3131
use OCA\Photos\Listener\CacheEntryRemovedListener;
32+
use OCA\Photos\Listener\LocationManagerNodeEventListener;
3233
use OCP\AppFramework\App;
3334
use OCP\AppFramework\Bootstrap\IBootContext;
3435
use OCP\AppFramework\Bootstrap\IBootstrap;
3536
use OCP\AppFramework\Bootstrap\IRegistrationContext;
37+
use OCP\Files\Cache\CacheEntryInsertedEvent;
3638
use OCP\Files\Cache\CacheEntryRemovedEvent;
39+
use OCP\Files\Cache\CacheEntryUpdatedEvent;
40+
use OCP\Share\Events\ShareCreatedEvent;
41+
use OCP\Share\Events\ShareDeletedEvent;
42+
use OCP\User\Events\UserDeletedEvent;
3743

3844
class Application extends App implements IBootstrap {
3945
public const APP_ID = 'photos';
@@ -66,7 +72,15 @@ public function register(IRegistrationContext $context): void {
6672
/** Register $principalBackend for the DAV collection */
6773
$context->registerServiceAlias('principalBackend', Principal::class);
6874
$context->registerEventListener(CacheEntryRemovedEvent::class, CacheEntryRemovedListener::class);
75+
$context->registerEventListener(CacheEntryRemovedEvent::class, LocationManagerNodeEventListener::class);
76+
// Priority of -1 to be triggered after event listeners populating metadata.
77+
$context->registerEventListener(CacheEntryInsertedEvent::class, LocationManagerNodeEventListener::class, -1);
78+
$context->registerEventListener(CacheEntryUpdatedEvent::class, LocationManagerNodeEventListener::class, -1);
79+
$context->registerEventListener(UserDeletedEvent::class, LocationManagerNodeEventListener::class);
80+
$context->registerEventListener(ShareCreatedEvent::class, LocationManagerNodeEventListener::class);
81+
$context->registerEventListener(ShareDeletedEvent::class, LocationManagerNodeEventListener::class);
6982
$context->registerEventListener(SabrePluginAuthInitEvent::class, SabrePluginAuthInitListener::class);
83+
require_once __DIR__ . '/../../vendor/autoload.php';
7084
}
7185

7286
public function boot(IBootContext $context): void {

lib/Command/MapMediaToLocation.php

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* @copyright Copyright (c) 2022 Louis Chemineau <[email protected]>
6+
*
7+
* @author Louis Chemineau <[email protected]>
8+
*
9+
* @license AGPL-3.0-or-later
10+
*
11+
* This program is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Affero General Public License as
13+
* published by the Free Software Foundation, either version 3 of the
14+
* License, or (at your option) any later version.
15+
*
16+
* This program is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
* GNU Affero General Public License for more details.
20+
*
21+
* You should have received a copy of the GNU Affero General Public License
22+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
23+
*
24+
*/
25+
namespace OCA\Photos\Command;
26+
27+
use OCP\IConfig;
28+
use OCP\IUserManager;
29+
use OCP\Files\IRootFolder;
30+
use OCP\Files\Folder;
31+
use OCP\BackgroundJob\IJobList;
32+
use OCA\Photos\Service\MediaLocationManager;
33+
use Symfony\Component\Console\Command\Command;
34+
use Symfony\Component\Console\Input\InputInterface;
35+
use Symfony\Component\Console\Input\InputOption;
36+
use Symfony\Component\Console\Output\OutputInterface;
37+
38+
class MapMediaToLocation extends Command {
39+
private IRootFolder $rootFolder;
40+
private MediaLocationManager $mediaLocationManager;
41+
private IConfig $config;
42+
private IUserManager $userManager;
43+
44+
public function __construct(
45+
IJobList $jobList,
46+
IRootFolder $rootFolder,
47+
MediaLocationManager $mediaLocationManager,
48+
IConfig $config,
49+
IUserManager $userManager
50+
) {
51+
parent::__construct();
52+
$this->config = $config;
53+
$this->jobList = $jobList;
54+
$this->rootFolder = $rootFolder;
55+
$this->mediaLocationManager = $mediaLocationManager;
56+
$this->userManager = $userManager;
57+
}
58+
59+
/**
60+
* Configure the command
61+
*
62+
* @return void
63+
*/
64+
protected function configure() {
65+
$this->setName('photos:map-media-to-location')
66+
->setDescription('Reverse geocode media coordinates.')
67+
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'Limit the mapping to a user.', null);
68+
}
69+
70+
/**
71+
* Execute the command
72+
*
73+
* @param InputInterface $input
74+
* @param OutputInterface $output
75+
*
76+
* @return int
77+
*/
78+
protected function execute(InputInterface $input, OutputInterface $output): int {
79+
if (!$this->config->getSystemValueBool('enable_file_metadata', true)) {
80+
throw new \Exception('File metadata is not enabled.');
81+
}
82+
83+
$userId = $input->getOption('user');
84+
if ($userId === null) {
85+
$this->scanForAllUsers();
86+
} else {
87+
$this->scanFilesForUser($userId);
88+
}
89+
90+
return 0;
91+
}
92+
93+
private function scanForAllUsers() {
94+
$users = $this->userManager->search('');
95+
96+
foreach ($users as $user) {
97+
$this->scanFilesForUser($user->getUID());
98+
}
99+
}
100+
101+
private function scanFilesForUser(string $userId) {
102+
$userFolder = $this->rootFolder->getUserFolder($userId);
103+
$this->scanFolder($userFolder);
104+
}
105+
106+
private function scanFolder(Folder $folder) {
107+
foreach ($folder->getDirectoryListing() as $node) {
108+
if ($node instanceof Folder) {
109+
$this->scanFolder($node);
110+
continue;
111+
}
112+
113+
if (!str_starts_with($node->getMimeType(), 'image')) {
114+
continue;
115+
}
116+
117+
$this->mediaLocationManager->addLocationForFileAndUser($node->getId(), $node->getOwner()->getUID());
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)