Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add username label on shared photos #1266

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ function w($base, $param)
['name' => 'Map#clusters', 'url' => '/api/map/clusters', 'verb' => 'GET'],
['name' => 'Map#init', 'url' => '/api/map/init', 'verb' => 'GET'],

['name' => 'Uid#name', 'url' => '/api/uid/name/{id}', 'verb' => 'GET'],

['name' => 'Archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],

['name' => 'Image#preview', 'url' => '/api/image/preview/{id}', 'verb' => 'GET'],
Expand Down
55 changes: 55 additions & 0 deletions lib/Controller/UidController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2022 Varun Patil <[email protected]>
* @author Varun Patil <[email protected]>
* @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\Memories\Controller;

use OCA\Memories\Util;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;

class UidController extends GenericApiController
{
/**
* @NoAdminRequired
*
* @NoCSRFRequired
*
* @PublicPage
*
* Get display name for a Nextcloud user id
*
* @param string fileid
*/
public function name(
string $uid,
): Http\Response {
return Util::guardEx(static function () use ($uid) {
$userManager = \OC::$server->get(\OCP\IUserManager::class);
$user = $userManager->get($uid);

return new JSONResponse([
'user_display' => $user ? $user->getDisplayName() : null,
], Http::STATUS_OK);
});
}
}
1 change: 1 addition & 0 deletions lib/Db/TimelineQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class TimelineQuery
'f.etag', 'f.name AS basename',
'f.size', 'm.epoch', // auid
'mimetypes.mimetype',
'm.uid',
];

protected ?TimelineRoot $_root = null; // cache
Expand Down
5 changes: 5 additions & 0 deletions lib/Db/TimelineQueryDays.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use OCA\Memories\ClustersBackend;
use OCA\Memories\Exif;
use OCA\Memories\Settings\SystemConfig;
use OCA\Memories\Util;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;

Expand Down Expand Up @@ -315,6 +316,10 @@ private function postProcessDayPhoto(array &$row, bool $monthView = false): void
unset($row['liveid']);
}

if ($row['uid'] === Util::getUID()) {
unset($row['uid']);
}

// Favorite field, may not be present
if ($row['categoryid'] ?? null) {
$row['isfavorite'] = 1;
Expand Down
3 changes: 2 additions & 1 deletion lib/Db/TimelineQuerySingleItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function getSingleItem(int $fileId): ?array
public function getInfoById(int $id, bool $basic): array
{
$qb = $this->connection->getQueryBuilder();
$qb->select('fileid', 'dayid', 'datetaken', 'w', 'h')
$qb->select('fileid', 'dayid', 'datetaken', 'w', 'h', 'uid')
->from('memories')
->where($qb->expr()->eq('fileid', $qb->createNamedParameter($id, \PDO::PARAM_INT)))
;
Expand All @@ -63,6 +63,7 @@ public function getInfoById(int $id, bool $basic): array
'w' => (int) $row['w'],
'h' => (int) $row['h'],
'datetaken' => Util::sqlUtcToTimestamp($row['datetaken']),
'uid' => $row['uid'],
];

// Return if only basic info is needed
Expand Down
2 changes: 2 additions & 0 deletions lib/Db/TimelineWrite.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public function processFile(
// Get parameters
$mtime = $file->getMtime();
$fileId = $file->getId();
$uid = $file->getOwner()?->getUID();
$isvideo = Index::isVideo($file);

// Get previous row
Expand Down Expand Up @@ -172,6 +173,7 @@ public function processFile(
'orphan' => $query->createNamedParameter(false, IQueryBuilder::PARAM_BOOL),
'buid' => $query->createNamedParameter($buid, IQueryBuilder::PARAM_STR),
'parent' => $query->createNamedParameter($file->getParent()->getId(), IQueryBuilder::PARAM_INT),
'uid' => $query->createNamedParameter($uid, IQueryBuilder::PARAM_STR),
];

// There is no easy way to UPSERT in standard SQL
Expand Down
120 changes: 120 additions & 0 deletions lib/Migration/Version800001Date20241026171056.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Varun Patil <[email protected]>
* @author Varun Patil <[email protected]>
* @license GNU AGPL version 3 or any later version
*
* 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\Memories\Migration;

use OCP\DB\ISchemaWrapper;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

/**
* FIXME Auto-generated migration step: Please modify to your needs!
*/
class Version800001Date20241026171056 extends SimpleMigrationStep
{
public function __construct(private IDBConnection $dbc) {}

/**
* @param \Closure(): ISchemaWrapper $schemaClosure
*/
public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void {}

/**
* @param \Closure(): ISchemaWrapper $schemaClosure
*/
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options): ?ISchemaWrapper
{
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if (!$schema->hasTable('memories')) {
throw new \Exception('Memories table does not exist');
}

$table = $schema->getTable('memories');

if (!$table->hasColumn('uid')) {
$table->addColumn('uid', 'string', [
'notnull' => false,
'length' => 64,
]);
}

return $schema;
}

/**
* @param \Closure(): ISchemaWrapper $schemaClosure
*/
public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options): void
{
// create database triggers; this will never throw
\OCA\Memories\Db\AddMissingIndices::createFilecacheTriggers($output);

// migrate uid values from filecache
try {
$output->info('Migrating values for uid from filecache');

$platform = $this->dbc->getDatabasePlatform();

// copy existing parent values from filecache
if (preg_match('/mysql|mariadb/i', $platform::class)) {
$this->dbc->executeQuery(
'UPDATE *PREFIX*memories AS m
JOIN *PREFIX*filecache AS f ON f.fileid = m.fileid
JOIN *PREFIX*storages AS s ON f.storage = s.numeric_id
SET m.uid = SUBSTRING_INDEX(s.id, \'::\', -1)
WHERE s.id LIKE \'home::%\'
',
);
} elseif (preg_match('/postgres/i', $platform::class)) {
$this->dbc->executeQuery(
'UPDATE *PREFIX*memories AS m
SET uid = split_part(s.id, \'::\', 2)
FROM *PREFIX*filecache AS f
JOIN *PREFIX*storages AS s ON f.storage = s.numeric_id
WHERE f.fileid = m.fileid
AND s.id LIKE \'home::%\'
',
);
} elseif (preg_match('/sqlite/i', $platform::class)) {
$this->dbc->executeQuery(
'UPDATE memories AS m
SET uid = SUBSTR(s.id, INSTR(s.id, \'::\') + 2)
FROM filecache AS f
JOIN storages AS s ON f.storage = s.numeric_id
WHERE f.fileid = m.fileid
AND s.id LIKE \'home::%\'
',
);
} else {
throw new \Exception('Unsupported '.$platform::class);
}

$output->info('Values for uid migrated successfully');
} catch (\Exception $e) {
$output->warning('Failed to copy uid values from fileid: '.$e->getMessage());
$output->warning('Please run occ memories:index -f');
}
}
}
24 changes: 23 additions & 1 deletion src/components/Metadata.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@
<AlbumsList class="albums" :albums="albums" />
</div>

<div v-if="uid">
<div class="section-title">{{ t('memories', 'Shared By') }}</div>
<div class="top-field">
<div class="icon">
<NcAvatar key="uid" :user="uid" :showUserStatus="false" :size="24" />
</div>

<div class="text">
<span class="title">{{ userDisplay }}</span>
</div>
</div>
</div>

<div class="section-title">{{ t('memories', 'Metadata') }}</div>
<div v-for="field of topFields" :key="field.title" :class="`top-field top-field--${field.id}`">
<div class="icon">
Expand Down Expand Up @@ -90,9 +103,10 @@ import TagIcon from 'vue-material-design-icons/Tag.vue';
import * as utils from '@services/utils';
import * as dav from '@services/dav';
import { API } from '@services/API';

import type { IAlbum, IFace, IImageInfo, IPhoto, IExif } from '@typings';

const NcAvatar = () => import('@nextcloud/vue/dist/Components/NcAvatar.js');

interface TopField {
id?: string;
title: string;
Expand All @@ -110,6 +124,7 @@ export default defineComponent({
AlbumsList,
Cluster,
EditIcon,
NcAvatar,
},

mixins: [UserConfig],
Expand All @@ -120,6 +135,8 @@ export default defineComponent({
exif: {} as IExif,
baseInfo: {} as IImageInfo,
error: false,
uid: null as string | null,
userDisplay: '',

loading: 0,
state: 0,
Expand Down Expand Up @@ -419,6 +436,11 @@ export default defineComponent({
this.filename = this.baseInfo.basename;
this.exif = this.baseInfo.exif ?? {};

// set user info
this.uid = this.baseInfo.uid ?? null;
if (this.uid == utils.uid) this.uid = null;
this.userDisplay = await utils.getUserDisplayName(this.uid);

return this.baseInfo;
},

Expand Down
33 changes: 32 additions & 1 deletion src/components/frame/Photo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,16 @@
</div>
</div>

<div class="flag bottom-left">
<div class="flag bottom-right">
<StarIcon :size="22" v-if="data.flag & c.FLAG_IS_FAVORITE" />
<LocalIcon :size="22" v-if="data.flag & c.FLAG_IS_LOCAL" />
</div>

<div class="flag bottom-left">
<AccountMultipleIcon :size="22" v-if="data.uid" />
<span class="username" v-if="data.uid">{{ owner }}</span>
</div>

<div
class="img-outer fill-block"
:class="{ 'memories-livephoto': data.liveid }"
Expand Down Expand Up @@ -81,6 +86,7 @@ import StarIcon from 'vue-material-design-icons/Star.vue';
import VideoIcon from 'vue-material-design-icons/PlayCircleOutline.vue';
import LocalIcon from 'vue-material-design-icons/CloudOff.vue';
import RawIcon from 'vue-material-design-icons/Raw.vue';
import AccountMultipleIcon from 'vue-material-design-icons/AccountMultiple.vue';

import type { IDay, IPhoto } from '@typings';
import type XImg from '@components/XImg.vue';
Expand All @@ -96,6 +102,7 @@ export default defineComponent({
StarIcon,
LocalIcon,
RawIcon,
AccountMultipleIcon,
},

props: {
Expand Down Expand Up @@ -126,6 +133,7 @@ export default defineComponent({
requested: false,
},
faceSrc: null as string | null,
userDisplay: null as string | null,
}),

watch: {
Expand Down Expand Up @@ -173,6 +181,10 @@ export default defineComponent({
return null;
},

owner(): string {
return this.userDisplay ?? this.data.uid ?? '';
},

videoUrl(): string | null {
if (this.data.liveid) {
return utils.getLivePhotoVideoUrl(this.data, true);
Expand Down Expand Up @@ -260,9 +272,14 @@ export default defineComponent({
);
},

async addUserDisplay() {
this.userDisplay = await utils.getUserDisplayName(this.data.uid ?? null);
},

/** Post load tasks */
load() {
this.addFaceRect();
this.addUserDisplay();
},

/** Error in loading image */
Expand Down Expand Up @@ -417,6 +434,20 @@ $icon-size: $icon-half-size * 2;
}
}

&.bottom-right {
bottom: var(--icon-dist);
right: var(--icon-dist);
.p-outer.selected > & {
transform: translate($icon-size, -$icon-size);
}
}

> .username {
font-size: 0.75em;
font-weight: bold;
margin-left: 3px;
}

> .video {
display: flex;
line-height: 22px; // force text height to match
Expand Down
Loading