Skip to content

Commit 309b238

Browse files
committed
Add commands and listeners to generate location data of files
- `ReverseGeoCoderService` download the necessary files and build the `KDTree` - `UpdateReverseGeocodingFilesCommand` command to allow to manually create the needed reverse geocoding files - `MediaLocationManager` to manager the location mappings - `MapMediaToLocationCommand` command to manually trigger location data mapping. Useful for pre-existing pictures. - `LocationManagerNodeEventListener` to react to node, user and share events. - `MapMediaToLocationJob` to reduce the load in event listeners ┌─────────────────────┐ ┌────────────►│MapMediaToLocationJob│ │ └─────────┬───────────┘ │ │ ┌────────────────────────┴───────┐ │ │LocationManagerNodeEventListener├──┐ ▼ └────────────────────────────────┘ │ ┌────────────────────┐ ┌──────────────┐ ├─►│MediaLocationManager├────►│LocationMapper│ ┌─────────────────────────┐ │ └─────────┬──────────┘ └──────────────┘ │MapMediaToLocationCommand├─────────┘ │ └─────────────────────────┘ │ ▼ ┌──────────────────────────────────┐ ┌──────────────────────┐ │UpdateReverseGeocodingFilesCommand├──►│ReverseGeoCoderService│ └──────────────────────────────────┘ └──────────────────────┘ Signed-off-by: Louis Chemineau <[email protected]>
1 parent efa2bf5 commit 309b238

19 files changed

+1154
-78
lines changed

.github/workflows/cypress.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ jobs:
2121
- name: Checkout app
2222
uses: actions/checkout@v3
2323

24+
- name: Install server dependencies
25+
run: composer install
26+
2427
- name: Read package.json node and npm engines version
2528
uses: skjnldsv/[email protected]
2629
id: versions
@@ -36,7 +39,7 @@ jobs:
3639
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
3740
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
3841

39-
- name: Install dependencies & build app
42+
- name: Install node dependencies & build app
4043
run: |
4144
npm ci
4245
TESTING=true npm run build --if-present

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>2.2.0</version>
7+
<version>2.2.1</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\UpdateReverseGeocodingFilesCommand</command>
34+
<command>OCA\Photos\Command\MapMediaToLocationCommand</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
@@ -21,5 +21,8 @@
2121
"vimeo/psalm": "^4.22",
2222
"sabre/dav": "^4.2.1",
2323
"nextcloud/ocp": "dev-master"
24+
},
25+
"require": {
26+
"hexogen/kdtree": "^0.2.5"
2427
}
2528
}

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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use OCA\Photos\Listener\TagListener;
3333
use OCA\Photos\Listener\GroupUserRemovedListener;
3434
use OCA\Photos\Listener\GroupDeletedListener;
35+
use OCA\Photos\Listener\LocationManagerNodeEventListener;
3536
use OCP\AppFramework\App;
3637
use OCP\AppFramework\Bootstrap\IBootContext;
3738
use OCP\AppFramework\Bootstrap\IBootstrap;
@@ -40,6 +41,13 @@
4041
use OCP\SystemTag\MapperEvent;
4142
use OCP\Group\Events\UserRemovedEvent;
4243
use OCP\Group\Events\GroupDeletedEvent;
44+
use OCP\Files\Cache\CacheEntryRemovedEvent;
45+
use OCP\Files\Events\Node\NodeWrittenEvent;
46+
use OCP\Share\Events\ShareCreatedEvent;
47+
use OCP\Share\Events\ShareDeletedEvent;
48+
use OCP\User\Events\UserDeletedEvent;
49+
50+
require_once __DIR__ . '/../../vendor/autoload.php';
4351

4452
class Application extends App implements IBootstrap {
4553
public const APP_ID = 'photos';
@@ -78,6 +86,13 @@ public function register(IRegistrationContext $context): void {
7886

7987
$context->registerEventListener(GroupDeletedEvent::class, GroupDeletedListener::class);
8088

89+
// Priority of -1 to be triggered after event listeners populating metadata.
90+
$context->registerEventListener(NodeWrittenEvent::class, LocationManagerNodeEventListener::class, -1);
91+
$context->registerEventListener(CacheEntryRemovedEvent::class, LocationManagerNodeEventListener::class);
92+
$context->registerEventListener(UserDeletedEvent::class, LocationManagerNodeEventListener::class);
93+
$context->registerEventListener(ShareCreatedEvent::class, LocationManagerNodeEventListener::class);
94+
$context->registerEventListener(ShareDeletedEvent::class, LocationManagerNodeEventListener::class);
95+
8196
$context->registerEventListener(SabrePluginAuthInitEvent::class, SabrePluginAuthInitListener::class);
8297

8398
$context->registerEventListener(MapperEvent::EVENT_ASSIGN, TagListener::class);
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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 OCA\Photos\Service\MediaLocationManager;
32+
use Symfony\Component\Console\Command\Command;
33+
use Symfony\Component\Console\Input\InputInterface;
34+
use Symfony\Component\Console\Input\InputOption;
35+
use Symfony\Component\Console\Output\OutputInterface;
36+
37+
class MapMediaToLocationCommand extends Command {
38+
private IRootFolder $rootFolder;
39+
private MediaLocationManager $mediaLocationManager;
40+
private IConfig $config;
41+
private IUserManager $userManager;
42+
43+
public function __construct(
44+
IRootFolder $rootFolder,
45+
MediaLocationManager $mediaLocationManager,
46+
IConfig $config,
47+
IUserManager $userManager
48+
) {
49+
parent::__construct();
50+
$this->config = $config;
51+
$this->rootFolder = $rootFolder;
52+
$this->mediaLocationManager = $mediaLocationManager;
53+
$this->userManager = $userManager;
54+
}
55+
56+
/**
57+
* Configure the command
58+
*
59+
* @return void
60+
*/
61+
protected function configure() {
62+
$this->setName('photos:map-media-to-location')
63+
->setDescription('Reverse geocode media coordinates.')
64+
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'Limit the mapping to a user.', null);
65+
}
66+
67+
/**
68+
* Execute the command
69+
*
70+
* @param InputInterface $input
71+
* @param OutputInterface $output
72+
*
73+
* @return int
74+
*/
75+
protected function execute(InputInterface $input, OutputInterface $output): int {
76+
if (!$this->config->getSystemValueBool('enable_file_metadata', true)) {
77+
throw new \Exception('File metadata is not enabled.');
78+
}
79+
80+
$userId = $input->getOption('user');
81+
if ($userId === null) {
82+
$this->scanForAllUsers();
83+
} else {
84+
$this->scanFilesForUser($userId);
85+
}
86+
87+
return 0;
88+
}
89+
90+
private function scanForAllUsers() {
91+
$users = $this->userManager->search('');
92+
93+
foreach ($users as $user) {
94+
$this->scanFilesForUser($user->getUID());
95+
}
96+
}
97+
98+
private function scanFilesForUser(string $userId) {
99+
$userFolder = $this->rootFolder->getUserFolder($userId);
100+
$this->scanFolder($userFolder);
101+
}
102+
103+
private function scanFolder(Folder $folder) {
104+
foreach ($folder->getDirectoryListing() as $node) {
105+
if ($node instanceof Folder) {
106+
$this->scanFolder($node);
107+
continue;
108+
}
109+
110+
if (!str_starts_with($node->getMimeType(), 'image')) {
111+
continue;
112+
}
113+
114+
$this->mediaLocationManager->addLocationForFileAndUser($node->getId(), $node->getOwner()->getUID());
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)