feat(utility): migrate fav links.

Add helper function to get path to links folder.

Signed-off-by: Camila Ayres <hello@camilasan.com>
This commit is contained in:
Camila Ayres 2025-07-09 13:57:30 +02:00
parent 4b7ef6eab7
commit 1cc9f689c4
No known key found for this signature in database
GPG Key ID: 7A4A6121E88E2AD4
5 changed files with 168 additions and 35 deletions

View File

@ -54,11 +54,25 @@ namespace Utility {
OCSYNC_EXPORT void usleep(int usec);
OCSYNC_EXPORT QString formatFingerprint(const QByteArray &, bool colonSeparated = true);
/**
* @brief Creates the Desktop.ini file which contains the folder IconResource shown as a favorite link
* @brief Create favorite link for sync folder with application name and icon
*
* @param folder absolute file path to folder
*/
OCSYNC_EXPORT void setupFavLink(const QString &folder);
/**
* @brief Migrate favorite link for sync folder with new application name and icon
*
* @param folder absolute file path to folder
* @param lnkName the new name of the lnk file
*/
OCSYNC_EXPORT void migrateFavLink(const QString &folder, const QString &lnkName = QString());
/**
* @brief Creates or overwrite the Desktop.ini file to use new folder IconResource shown as a favorite link
*
* @param folder absolute file path to folder
* @param overwrite boolean to create or overwrite ini file
*/
OCSYNC_EXPORT void setupDesktopIni(const QString &folder, bool overwrite = false);
/**
* @brief Removes the Desktop.ini file which contains the folder IconResource shown as a favorite link
*
@ -66,6 +80,9 @@ namespace Utility {
*/
OCSYNC_EXPORT void removeFavLink(const QString &folder);
// convenience system path to links folder
OCSYNC_EXPORT QString systemPathToLinks();
OCSYNC_EXPORT bool writeRandomFile(const QString &fname, int size = -1);
OCSYNC_EXPORT QString octetsToString(const qint64 octets);
OCSYNC_EXPORT QByteArray userAgentString();
@ -350,6 +367,5 @@ inline constexpr bool Utility::isBSD()
return false;
#endif
}
}
#endif // UTILITY_H

View File

@ -56,6 +56,18 @@ void Utility::removeFavLink(const QString &folder)
Q_UNUSED(folder)
}
void Utility::migrateFavLink(const QString &folder)
{
Q_UNUSED(folder)
}
QString Utility::syncFolderDisplayName(const QString &folder, const QString &displayName)
{
Q_UNUSED(folder)
Q_UNUSED(displayName)
return {};
}
static Result<void, QString> writePlistToFile(NSString *plistFile, NSDictionary *plist)
{
NSError *error = nil;

View File

@ -65,6 +65,18 @@ bool Utility::hasLaunchOnStartup(const QString &appName)
return QFile::exists(desktopFileLocation);
}
void Utility::migrateFavLink(const QString &folder)
{
Q_UNUSED(folder)
}
QString Utility::syncFolderDisplayName(const QString &folder, const QString &displayName)
{
Q_UNUSED(folder)
Q_UNUSED(displayName)
return {};
}
void Utility::setLaunchOnStartup(const QString &appName, const QString &guiName, bool enable)
{
const auto userAutoStartPath = getUserAutostartDir();

View File

@ -7,6 +7,7 @@
#include "asserts.h"
#include "utility.h"
#include "gui/configgui.h"
#include "config.h"
#include <comdef.h>
#include <Lmcons.h>
@ -99,55 +100,128 @@ QVector<Utility::ProcessInfosForOpenFile> Utility::queryProcessInfosKeepingFileO
return results;
}
void Utility::setupFavLink(const QString &folder)
QString Utility::systemPathToLinks()
{
static const QString pathToLinks = [] {
PWSTR path = nullptr;
if (SHGetKnownFolderPath(FOLDERID_Links, 0, nullptr, &path) != S_OK) {
qCWarning(lcUtility) << "SHGetKnownFolderPath failed.";
CoTaskMemFree(path);
return QString();
}
const auto links = QDir::fromNativeSeparators(QString::fromWCharArray(path));
CoTaskMemFree(path);
return links;
}();
return pathToLinks;
}
void Utility::setupDesktopIni(const QString &folder, bool overwrite)
{
// First create a Desktop.ini so that the folder and favorite link show our application's icon.
QFile desktopIni(folder + QLatin1String("/Desktop.ini"));
if (desktopIni.exists()) {
if (!overwrite && desktopIni.exists()) {
qCWarning(lcUtility) << desktopIni.fileName() << "already exists, not overwriting it to set the folder icon.";
} else {
qCInfo(lcUtility) << "Creating" << desktopIni.fileName() << "to set a folder icon in Explorer.";
desktopIni.open(QFile::WriteOnly);
desktopIni.write("[.ShellClassInfo]\r\nIconResource=");
desktopIni.write(QDir::toNativeSeparators(qApp->applicationFilePath()).toUtf8());
#ifdef APPLICATION_FOLDER_ICON_INDEX
const auto iconIndex = APPLICATION_FOLDER_ICON_INDEX;
#else
const auto iconIndex = "0";
#endif
desktopIni.write(",");
desktopIni.write(iconIndex);
desktopIni.write("\r\n");
desktopIni.close();
// Set the folder as system and Desktop.ini as hidden+system for explorer to pick it.
// https://msdn.microsoft.com/en-us/library/windows/desktop/cc144102
DWORD folderAttrs = GetFileAttributesW((wchar_t *)folder.utf16());
SetFileAttributesW((wchar_t *)folder.utf16(), folderAttrs | FILE_ATTRIBUTE_SYSTEM);
SetFileAttributesW((wchar_t *)desktopIni.fileName().utf16(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
return;
}
/* Use new WINAPI functions */
PWSTR path;
if (!SHGetKnownFolderPath(FOLDERID_Links, 0, nullptr, &path) == S_OK) {
qCDebug(lcUtility) << "Creating" << desktopIni.fileName() << "to set a folder icon in Explorer.";
desktopIni.open(QFile::WriteOnly);
desktopIni.write("[.ShellClassInfo]\r\nIconResource=");
desktopIni.write(QDir::toNativeSeparators(qApp->applicationFilePath()).toUtf8());
#ifdef APPLICATION_FOLDER_ICON_INDEX
const auto iconIndex = APPLICATION_FOLDER_ICON_INDEX;
#else
const auto iconIndex = "0";
#endif
desktopIni.write(",");
desktopIni.write(iconIndex);
desktopIni.write("\r\n");
desktopIni.close();
// Set the folder as system and Desktop.ini as hidden+system for explorer to pick it.
// https://msdn.microsoft.com/en-us/library/windows/desktop/cc144102
DWORD folderAttrs = GetFileAttributesW((wchar_t *)folder.utf16());
SetFileAttributesW((wchar_t *)folder.utf16(), folderAttrs | FILE_ATTRIBUTE_SYSTEM);
SetFileAttributesW((wchar_t *)desktopIni.fileName().utf16(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
}
void Utility::setupFavLink(const QString &folder)
{
// create Desktop.ini
setupDesktopIni(folder);
const auto pathToLinks = systemPathToLinks();
if (pathToLinks.isEmpty()) {
qCWarning(lcUtility) << "SHGetKnownFolderPath for " << folder << "has failed.";
return;
}
// Windows Explorer: Place under "Favorites" (Links)
const auto links = QDir::fromNativeSeparators(QString::fromWCharArray(path));
CoTaskMemFree(path);
const QDir folderDir(QDir::fromNativeSeparators(folder));
const QString filePath = folderDir.dirName() + QLatin1String(".lnk");
const auto linkName = QDir(links).filePath(filePath);
const auto linkName = QDir().filePath(filePath);
qCInfo(lcUtility) << "Creating favorite link from" << folder << "to" << linkName;
qCDebug(lcUtility) << "Creating favorite link from" << folder << "to" << linkName;
if (!QFile::link(folder, linkName)) {
qCWarning(lcUtility) << "linking" << folder << "to" << linkName << "failed!";
}
}
QString Utility::syncFolderDisplayName(const QString &currentDisplayName, const QString &newName)
{
const auto nextcloud = QStringLiteral("Nextcloud");
if (!currentDisplayName.startsWith(nextcloud)) {
qCWarning(lcUtility) << "Nothings needs to be rename for" << currentDisplayName;
return currentDisplayName;
}
QString digits;
for (auto letter = std::crbegin(currentDisplayName); letter != std::crend(currentDisplayName); ++letter) {
if (!letter->isDigit()) {
break;
}
digits.prepend(*letter);
}
return newName + digits;
}
void Utility::migrateFavLink(const QString &folder)
{
//overwrite Desktop.ini, update icon
setupDesktopIni(folder, true);
const QDir folderDir(QDir::fromNativeSeparators(folder));
const auto oldDirName = folderDir.dirName();
const auto nextcloud = QStringLiteral("Nextcloud");
if (!oldDirName.startsWith(nextcloud)) {
qCWarning(lcUtility) << "Link name does not need to change for" << folder;
return;
}
const auto pathToLinks = systemPathToLinks();
if (pathToLinks.isEmpty()) {
qCWarning(lcUtility) << "SHGetKnownFolderPath for links has failed.";
return;
}
const QDir dirPathToLinks(pathToLinks);
const auto oldLnkFilename = dirPathToLinks.filePath(oldDirName + QLatin1String(".lnk"));
const auto newLnkFilename = dirPathToLinks.filePath(syncFolderDisplayName(oldDirName, QStringLiteral(APPLICATION_NAME)) + QLatin1String(".lnk"));
if (QFile::exists(newLnkFilename)) {
qCWarning(lcUtility) << "New lnk file already exists" << newLnkFilename;
return;
}
qCDebug(lcUtility) << "Renaming favorite link from" << oldLnkFilename << "to" << newLnkFilename;
if (QFile::rename(oldLnkFilename, newLnkFilename) && !QFile::rename(oldLnkFilename, newLnkFilename)) {
qCWarning(lcUtility) << "renaming" << oldLnkFilename << "to" << newLnkFilename << "failed!";
}
}
void Utility::removeFavLink(const QString &folder)
{
const QDir folderDir(folder);
@ -176,7 +250,7 @@ void Utility::removeFavLink(const QString &folder)
const auto linkName = QDir(links).absoluteFilePath(folderDir.dirName() + QLatin1String(".lnk"));
qCInfo(lcUtility) << "Removing favorite link from" << folder << "to" << linkName;
qCDebug(lcUtility) << "Removing favorite link from" << folder << "to" << linkName;
if (!QFile::remove(linkName)) {
qCWarning(lcUtility) << "Removing a favorite link from" << folder << "to" << linkName << "failed.";
}

View File

@ -536,6 +536,7 @@ void FolderMan::setupLegacyFolder(const QString &fileNamePath, AccountState *acc
const auto journalPath = settings.value(QLatin1String("journalPath")).toString();
const auto paused = settings.value(QLatin1String("paused"), false).toBool();
const auto ignoreHiddenFiles = settings.value(QLatin1String("ignoreHiddenFiles"), false).toBool();
const auto virtualFilesMode = settings.value(QLatin1String("virtualFilesMode"), false).toString();
if (path.isEmpty()) {
qCDebug(lcFolderMan) << "localPath is empty";
@ -565,7 +566,21 @@ void FolderMan::setupLegacyFolder(const QString &fileNamePath, AccountState *acc
folderDefinition.paused = paused;
folderDefinition.ignoreHiddenFiles = ignoreHiddenFiles;
if (const auto folder = addFolderInternal(folderDefinition, accountState, std::make_unique<VfsOff>())) {
if (const auto vfsMode = Vfs::modeFromString(virtualFilesMode)) {
folderDefinition.virtualFilesMode = *vfsMode;
} else {
qCWarning(lcFolderMan) << "Unknown virtualFilesMode:" << virtualFilesMode << "assuming 'off'";
}
qCDebug(lcFolderMan) << "folderDefinition.alias" << folderDefinition.alias;
qCDebug(lcFolderMan) << "folderDefinition.virtualFilesMode" << folderDefinition.virtualFilesMode;
auto vfs = createVfsFromPlugin(folderDefinition.virtualFilesMode);
if (!vfs && folderDefinition.virtualFilesMode != Vfs::Off) {
qCWarning(lcFolderMan) << "Could not load plugin for mode" << folderDefinition.virtualFilesMode;
}
if (const auto folder = addFolderInternal(folderDefinition, accountState, std::move(vfs))) {
auto ok = true;
auto legacyBlacklist = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList,
&ok);
@ -593,6 +608,10 @@ void FolderMan::setupLegacyFolder(const QString &fileNamePath, AccountState *acc
scheduleFolder(folder);
emit folderSyncStateChange(folder);
#ifdef Q_OS_WIN
Utility::migrateFavLink(folder->cleanPath());
#endif
}
settings.endGroup(); // folder alias
}