Skip to content

Commit

Permalink
Merge branch 'jb46560-cleanup-avatars' into 'master'
Browse files Browse the repository at this point in the history
Jb46560 cleanup avatars

See merge request mer-core/nemo-qml-plugin-contacts!55
  • Loading branch information
blam committed Feb 22, 2021
2 parents f708778 + 07cc9ca commit a051d1b
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 1 deletion.
28 changes: 28 additions & 0 deletions lib/seasidecache.cpp
Expand Up @@ -1475,6 +1475,29 @@ QUrl SeasideCache::filteredAvatarUrl(const QContact &contact, const QStringList
return QUrl();
}

bool SeasideCache::removeLocalAvatarFile(const QContact &contact, const QContactAvatar &avatar)
{
if (avatar.isEmpty() || contact.collectionId() != localCollectionId()) {
return false;
}

const QString avatarPath = avatar.imageUrl().isLocalFile()
? avatar.imageUrl().toLocalFile()
: avatar.imageUrl().toString();

// Check that the avatar is a system-generated file before deleting it, to avoid deleting
// user-created files.
static const QString dataPath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).value(0);
static const QString avatarCachePath = QString("%1/data/avatars").arg(dataPath);
static const QString avatarSystemPath = QString("%1/system").arg(dataPath);

if (avatarPath.startsWith(avatarCachePath) || avatarPath.startsWith(avatarSystemPath)) {
return QFile::remove(avatarPath);
}

return false;
}

QString SeasideCache::normalizePhoneNumber(const QString &input, bool validate)
{
QtContactsSqliteExtensions::NormalizePhoneNumberFlags normalizeFlags(QtContactsSqliteExtensions::KeepPhoneNumberDialString);
Expand Down Expand Up @@ -2144,6 +2167,11 @@ void SeasideCache::contactsRemoved(const QList<QContactId> &ids)
// Remove the links to addressible details
updateContactIndexing(item->contact, QContact(), item->iid, QSet<QContactDetail::DetailType>(), item);

// Delete the avatar file assets of removed local contacts.
foreach (const QContactAvatar &avatar, item->contact.details<QContactAvatar>()) {
removeLocalAvatarFile(item->contact, avatar);
}

if (!m_keepPopulated) {
presentIds.append(id);
}
Expand Down
2 changes: 2 additions & 0 deletions lib/seasidecache.h
Expand Up @@ -53,6 +53,7 @@
#include <QContactIdFetchRequest>
#include <QContactName>
#include <QContactCollectionId>
#include <QContactAvatar>

#include <QTranslator>
#include <QBasicTimer>
Expand Down Expand Up @@ -363,6 +364,7 @@ class CONTACTCACHE_EXPORT SeasideCache : public QObject
static QString generateDisplayLabel(const QContact &contact, DisplayLabelOrder order = FirstNameFirst, bool fallbackToNonNameDetails = true);
static QString generateDisplayLabelFromNonNameDetails(const QContact &contact);
static QUrl filteredAvatarUrl(const QContact &contact, const QStringList &metadataFragments = QStringList());
static bool removeLocalAvatarFile(const QContact &contact, const QContactAvatar &avatar);

static QString normalizePhoneNumber(const QString &input, bool validate = false);
static QString minimizePhoneNumber(const QString &input, bool validate = false);
Expand Down
4 changes: 4 additions & 0 deletions src/seasideperson.cpp
Expand Up @@ -55,6 +55,7 @@
#include <QVersitWriter>
#include <QVersitContactExporter>

#include <QFile>
#include <QDebug>

QTVERSIT_USE_NAMESPACE
Expand Down Expand Up @@ -464,11 +465,14 @@ void SeasidePerson::setAvatarUrl(QUrl avatarUrl)
} else {
// We can only have one local avatar
QContactAvatar obsoleteAvatar(avatar);
SeasideCache::removeLocalAvatarFile(*mContact, obsoleteAvatar);
mContact->removeDetail(&obsoleteAvatar);
}
}
}

SeasideCache::removeLocalAvatarFile(*mContact, localAvatar);

localAvatar.setImageUrl(avatarUrl);
localAvatar.setValue(QContactAvatar::FieldMetaData, localMetadata);
mContact->saveDetail(&localAvatar);
Expand Down
5 changes: 5 additions & 0 deletions tests/tst_seasidefilteredmodel/seasidecache.cpp
Expand Up @@ -460,6 +460,11 @@ QUrl SeasideCache::filteredAvatarUrl(const QContact &contact, const QStringList
return QUrl();
}

bool SeasideCache::removeLocalAvatarFile(const QContact &, const QContactAvatar &)
{
return false;
}

QString SeasideCache::normalizePhoneNumber(const QString &input, bool)
{
return input;
Expand Down
2 changes: 2 additions & 0 deletions tests/tst_seasidefilteredmodel/seasidecache.h
Expand Up @@ -8,6 +8,7 @@
#include <QContactId>
#include <QContactManager>
#include <QContactCollection>
#include <QContactAvatar>

#include <QAbstractListModel>

Expand Down Expand Up @@ -218,6 +219,7 @@ class SeasideCache : public QObject
static QString generateDisplayLabel(const QContact &contact, DisplayLabelOrder order = FirstNameFirst);
static QString generateDisplayLabelFromNonNameDetails(const QContact &contact);
static QUrl filteredAvatarUrl(const QContact &contact, const QStringList &metadataFragments = QStringList());
static bool removeLocalAvatarFile(const QContact &, const QContactAvatar &);

static QString normalizePhoneNumber(const QString &input, bool validate = false);
static QString minimizePhoneNumber(const QString &input, bool validate = false);
Expand Down
81 changes: 80 additions & 1 deletion tools/contacts-tool/main.cpp
Expand Up @@ -35,6 +35,11 @@
// Qt
#include <QCoreApplication>
#include <QTextStream>
#include <QFile>
#include <QDir>
#include <QStandardPaths>
#include <QUrl>
#include <QFileInfo>
#include <QDebug>

// Contacts
Expand Down Expand Up @@ -92,7 +97,8 @@ void invalidUsage(const QString &app)
" links <ID...> - lists links for contacts matching the supplied ID list\n"
" delete <ID...> - removes contacts matching the supplied ID list\n"
" dump [ID...] - displays contact details in debug format\n"
" collections - lists all contact collections"
" remove-stale-files - deletes contact file assets that are no longer used by any contacts\n"
" collections - lists all contact collections\n"
" help - show this command summary\n"
" version - show the application version\n"
"\n"
Expand Down Expand Up @@ -812,6 +818,76 @@ int dumpCollections()
return 0;
}

QString localFilePathWithoutScheme(const QString &filePath)
{
QString path;
QUrl url(filePath);
if (url.isLocalFile()) {
path = url.isLocalFile();
} else {
path = filePath;
}
return path.startsWith('/')
? path.replace(QLatin1String("//"), QLatin1String("/")) // remove redundant chars
: QString();
}

void removeStaleAvatars(QContactManager *manager, const QString &avatarDirPath)
{
QContactFetchHint fetchHint;
fetchHint.setOptimizationHints(QContactFetchHint::NoRelationships);
fetchHint.setDetailTypesHint(QList<QContactDetail::DetailType>()
<< QContactDetail::TypeAvatar);
QContactDetailFilter hasAvatarFilter;
hasAvatarFilter.setDetailType(QContactDetail::TypeAvatar);

QSet<QString> avatarImageUrls;
const QList<QContact> contacts = manager->contacts(hasAvatarFilter, QList<QContactSortOrder>(), fetchHint);
for (const QContact &contact : contacts) {
const QList<QContactAvatar> avatars = contact.details<QContactAvatar>();
for (const QContactAvatar &avatar : avatars) {
const QString localFilePath = localFilePathWithoutScheme(avatar.imageUrl().toString());
if (!localFilePath.isEmpty()) {
avatarImageUrls.insert(localFilePath);
}
}
}

QDir avatarDir(avatarDirPath);
QStringList removedFiles;
const QStringList avatarFileNames = avatarDir.entryList(QDir::Files);
for (const QString &avatarFileName : avatarFileNames) {
const QString avatarFilePath = localFilePathWithoutScheme(avatarDir.absoluteFilePath(avatarFileName));
if (!avatarImageUrls.remove(avatarFilePath)) {
removedFiles.append(avatarFilePath);
QFile::remove(avatarFilePath);
}
}

QTextStream output(stdout);
if (removedFiles.count()) {
output << "Removed unused avatars:\n " << removedFiles.join("\n ") << "\n";
} else {
output << "Nothing to remove, no unused avatars found in " << avatarDirPath << "\n";
}
}

int removeStaleFiles()
{
const QString dataPath = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).value(0);

// For non-privileged contact manager
QMap<QString, QString> parameters;
parameters.insert(QString::fromLatin1("nonprivileged"), QString::fromLatin1("true"));
QContactManager mgr(QString::fromLatin1("org.nemomobile.contacts.sqlite"), parameters);
removeStaleAvatars(&mgr, QString("%1/system/Contacts/avatars").arg(dataPath));

// For privileged contact manager
removeStaleAvatars(&manager(), QString("%1/data/avatars").arg(dataPath));

return 0;
}

}

int main(int argc, char **argv)
Expand Down Expand Up @@ -861,6 +937,9 @@ int main(int argc, char **argv)
if (command == QString::fromLatin1("dump")) {
return dumpContacts(remaining, end);
}
if (command == QString::fromLatin1("remove-stale-files")) {
return removeStaleFiles();
}
if (command == QString::fromLatin1("collections")) {
return dumpCollections();
}
Expand Down

0 comments on commit a051d1b

Please sign in to comment.