From cbdaf6298af2654b6b85fcf7b561ee54935c4dcc Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 21 Aug 2020 18:37:58 +0000 Subject: [PATCH 1/3] New foreign key for the "photo" table --- database.sql | 5 +++-- static/dbstructure.config.php | 4 ++-- update.php | 7 +++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/database.sql b/database.sql index f544d319bf..3622e27930 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2020.09-dev (Red Hot Poker) --- DB_UPDATE_VERSION 1362 +-- DB_UPDATE_VERSION 1363 -- ------------------------------------------ @@ -922,7 +922,8 @@ CREATE TABLE IF NOT EXISTS `photo` ( INDEX `uid_profile` (`uid`,`profile`), INDEX `uid_album_scale_created` (`uid`,`album`(32),`scale`,`created`), INDEX `uid_album_resource-id_created` (`uid`,`album`(32),`resource-id`,`created`), - INDEX `resource-id` (`resource-id`) + INDEX `resource-id` (`resource-id`), + FOREIGN KEY (`contact-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='photo storage'; -- diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 045d0be971..e37c343f1e 100755 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -54,7 +54,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1362); + define('DB_UPDATE_VERSION', 1363); } return [ @@ -984,7 +984,7 @@ return [ "fields" => [ "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"], "uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "Owner User id"], - "contact-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["contact" => "id"], "comment" => "contact.id"], + "contact-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "foreign" => ["contact" => "id", "on delete" => "restrict"], "comment" => "contact.id"], "guid" => ["type" => "char(16)", "not null" => "1", "default" => "", "comment" => "A unique identifier for this photo"], "resource-id" => ["type" => "char(32)", "not null" => "1", "default" => "", "comment" => ""], "created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "creation date"], diff --git a/update.php b/update.php index 6152e2b1e0..a8b238b528 100644 --- a/update.php +++ b/update.php @@ -49,6 +49,7 @@ use Friendica\Database\DBStructure; use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Item; +use Friendica\Model\Photo; use Friendica\Model\User; use Friendica\Model\Storage; use Friendica\Util\DateTimeFormat; @@ -571,3 +572,9 @@ function pre_update_1358() return Update::SUCCESS; } + +function pre_update_1363() +{ + Photo::delete(["`contact-id` != ? AND NOT `contact-id` IN (SELECT `id` FROM `contact`)", 0]); + return Update::SUCCESS; +} \ No newline at end of file From 7990d08ad6399424001eebbc5704de857858c823 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 21 Aug 2020 18:39:18 +0000 Subject: [PATCH 2/3] Delete the photo entries when a user or contact is removed --- src/Worker/ExpireAndRemoveUsers.php | 3 +++ src/Worker/RemoveContact.php | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Worker/ExpireAndRemoveUsers.php b/src/Worker/ExpireAndRemoveUsers.php index 1fda31a179..c8344b6fd9 100644 --- a/src/Worker/ExpireAndRemoveUsers.php +++ b/src/Worker/ExpireAndRemoveUsers.php @@ -22,6 +22,7 @@ namespace Friendica\Worker; use Friendica\Database\DBA; +use Friendica\Model\Photo; use Friendica\Model\User; /** @@ -51,6 +52,8 @@ class ExpireAndRemoveUsers DBA::delete('contact', ['nurl' => $self['nurl'], 'self' => false]); } + Photo::delete(['uid' => $user['uid']]); + DBA::delete('user', ['uid' => $user['uid']]); } DBA::close($users); diff --git a/src/Worker/RemoveContact.php b/src/Worker/RemoveContact.php index 28a32160a0..05771e2dc5 100644 --- a/src/Worker/RemoveContact.php +++ b/src/Worker/RemoveContact.php @@ -23,8 +23,8 @@ namespace Friendica\Worker; use Friendica\Core\Logger; use Friendica\Database\DBA; -use Friendica\Core\Protocol; use Friendica\Model\Item; +use Friendica\Model\Photo; /** * Removes orphaned data from deleted contacts @@ -33,7 +33,7 @@ class RemoveContact { public static function execute($id) { // Only delete if the contact is to be deleted - $contact = DBA::selectFirst('contact', ['uid'], ['deleted' => true]); + $contact = DBA::selectFirst('contact', ['uid'], ['deleted' => true, 'id' => $id]); if (!DBA::isResult($contact)) { return; } @@ -49,6 +49,7 @@ class RemoveContact { DBA::close($items); } while (Item::exists($condition)); + Photo::delete(['uid' => $contact['uid'], 'contact-id' => $id]); DBA::delete('contact', ['id' => $id]); } } From e9d6fb9aaa264a0720c436ca36889b4b4bc140e9 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 21 Aug 2020 18:41:48 +0000 Subject: [PATCH 3/3] Store the cached avatars at the public contact --- src/Model/Contact.php | 116 ++++++++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 39 deletions(-) diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 9b0f6829d1..c5df814bf6 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -1549,18 +1549,19 @@ class Contact /** * Updates the avatar links in a contact only if needed * - * @param int $cid Contact id - * @param string $avatar Link to avatar picture - * @param bool $force force picture update + * @param int $cid Contact id + * @param string $avatar Link to avatar picture + * @param bool $force force picture update + * @param bool $create_cache Enforces the creation of cached avatar fields * * @return void * @throws HTTPException\InternalServerErrorException * @throws HTTPException\NotFoundException * @throws \ImagickException */ - public static function updateAvatar(int $cid, string $avatar, bool $force = false) + public static function updateAvatar(int $cid, string $avatar, bool $force = false, bool $create_cache = false) { - $contact = DBA::selectFirst('contact', ['uid', 'avatar', 'photo', 'thumb', 'micro', 'nurl', 'url'], ['id' => $cid, 'self' => false]); + $contact = DBA::selectFirst('contact', ['uid', 'avatar', 'photo', 'thumb', 'micro', 'nurl', 'url', 'network'], ['id' => $cid, 'self' => false]); if (!DBA::isResult($contact)) { return; } @@ -1568,7 +1569,7 @@ class Contact $uid = $contact['uid']; // Only update the cached photo links of public contacts when they already are cached - if (($uid == 0) && !$force && empty($contact['thumb']) && empty($contact['micro'])) { + if (($uid == 0) && !$force && empty($contact['thumb']) && empty($contact['micro']) && !$create_cache) { if ($contact['avatar'] != $avatar) { DBA::update('contact', ['avatar' => $avatar], ['id' => $cid]); Logger::info('Only update the avatar', ['id' => $cid, 'avatar' => $avatar, 'contact' => $contact]); @@ -1576,56 +1577,93 @@ class Contact return; } - $local_uid = User::getIdForURL($contact['url']); - if (!empty($local_uid)) { - $fields = self::selectFirst(['avatar', 'avatar-date', 'photo', 'thumb', 'micro'], ['self' => true, 'uid' => $local_uid]); + // User contacts use are updated through the public contacts + if (($uid != 0) && !in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) { + $pcid = self::getIdForURL($contact['url'], false); + if (!empty($pcid)) { + Logger::debug('Update the private contact via the public contact', ['id' => $cid, 'uid' => $uid, 'public' => $pcid]); + self::updateAvatar($pcid, $avatar, $force, true); + return; + } } - + // Replace cached avatar pictures from the default avatar with the default avatars in different sizes if (strpos($avatar, self::DEFAULT_AVATAR_PHOTO)) { $fields = ['avatar' => $avatar, 'avatar-date' => DateTimeFormat::utcNow(), 'photo' => DI::baseUrl() . self::DEFAULT_AVATAR_PHOTO, 'thumb' => DI::baseUrl() . self::DEFAULT_AVATAR_THUMB, 'micro' => DI::baseUrl() . self::DEFAULT_AVATAR_MICRO]; + Logger::debug('Use default avatar', ['id' => $cid, 'uid' => $uid]); } - if (!empty($fields)) { - if ($fields['photo'] . $fields['thumb'] . $fields['micro'] != $contact['photo'] . $contact['thumb'] . $contact['micro']) { - DBA::update('contact', $fields, ['id' => $cid]); - Photo::delete(['uid' => $uid, 'contact-id' => $cid, 'album' => Photo::CONTACT_PHOTOS]); + // Use the data from the self account + if (empty($fields)) { + $local_uid = User::getIdForURL($contact['url']); + if (!empty($local_uid)) { + $fields = self::selectFirst(['avatar', 'avatar-date', 'photo', 'thumb', 'micro'], ['self' => true, 'uid' => $local_uid]); + Logger::debug('Use owner data', ['id' => $cid, 'uid' => $uid, 'owner-uid' => $local_uid]); } + } + + if (empty($fields)) { + $update = ($contact['avatar'] != $avatar) || $force; + + if (!$update) { + $data = [ + $contact['photo'] ?? '', + $contact['thumb'] ?? '', + $contact['micro'] ?? '', + ]; + + foreach ($data as $image_uri) { + $image_rid = Photo::ridFromURI($image_uri); + if ($image_rid && !Photo::exists(['resource-id' => $image_rid, 'uid' => $uid])) { + Logger::debug('Regenerating avatar', ['contact uid' => $uid, 'cid' => $cid, 'missing photo' => $image_rid, 'avatar' => $contact['avatar']]); + $update = true; + } + } + } + + if ($update) { + $photos = Photo::importProfilePhoto($avatar, $uid, $cid, true); + if ($photos) { + $fields = ['avatar' => $avatar, 'photo' => $photos[0], 'thumb' => $photos[1], 'micro' => $photos[2], 'avatar-date' => DateTimeFormat::utcNow()]; + $update = !empty($fields); + Logger::debug('Created new cached avatars', ['id' => $cid, 'uid' => $uid, 'owner-uid' => $local_uid]); + } else { + $update = false; + } + } + } else { + $update = ($fields['photo'] . $fields['thumb'] . $fields['micro'] != $contact['photo'] . $contact['thumb'] . $contact['micro']) || $force; + } + + if (!$update) { return; } - $data = [ - $contact['photo'] ?? '', - $contact['thumb'] ?? '', - $contact['micro'] ?? '', - ]; + $cids = []; + $uids = []; + if (($uid == 0) && !in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) { + // Collect all user contacts of the given public contact + $personal_contacts = DBA::select('contact', ['id', 'uid'], + ["`nurl` = ? AND `id` != ? AND NOT `self`", $contact['nurl'], $cid]); + while ($personal_contact = DBA::fetch($personal_contacts)) { + $cids[] = $personal_contact['id']; + $uids[] = $personal_contact['uid']; + } + DBA::close($personal_contacts); - $update = ($contact['avatar'] != $avatar) || $force; - - if (!$update) { - foreach ($data as $image_uri) { - $image_rid = Photo::ridFromURI($image_uri); - if ($image_rid && !Photo::exists(['resource-id' => $image_rid, 'uid' => $uid])) { - Logger::info('Regenerating avatar', ['contact uid' => $uid, 'cid' => $cid, 'missing photo' => $image_rid, 'avatar' => $contact['avatar']]); - $update = true; - } + if (!empty($cids)) { + // Delete possibly existing cached user contact avatars + Photo::delete(['uid' => $uids, 'contact-id' => $cids, 'album' => Photo::CONTACT_PHOTOS]); } } - if ($update) { - $photos = Photo::importProfilePhoto($avatar, $uid, $cid, true); - if ($photos) { - $fields = ['avatar' => $avatar, 'photo' => $photos[0], 'thumb' => $photos[1], 'micro' => $photos[2], 'avatar-date' => DateTimeFormat::utcNow()]; - DBA::update('contact', $fields, ['id' => $cid]); - } elseif (empty($contact['avatar'])) { - // Ensure that the avatar field is set - DBA::update('contact', ['avatar' => $avatar], ['id' => $cid]); - Logger::info('Failed profile import', ['id' => $cid, 'force' => $force, 'avatar' => $avatar, 'contact' => $contact]); - } - } + $cids[] = $cid; + $uids[] = $uid; + Logger::info('Updating cached contact avatars', ['cid' => $cids, 'uid' => $uids, 'fields' => $fields]); + DBA::update('contact', $fields, ['id' => $cids]); } /**