diff --git a/linphone-desktop/assets/languages/en.ts b/linphone-desktop/assets/languages/en.ts
index b46ff46f2..cdccd93cd 100644
--- a/linphone-desktop/assets/languages/en.ts
+++ b/linphone-desktop/assets/languages/en.ts
@@ -760,13 +760,6 @@ Server url not configured.
It's necessary to restart the application. Do you want to restart now?
-
- FileMessage
-
- downloadFileTitle
- Download file
-
-
Home
@@ -1292,6 +1285,10 @@ your friend's SIP address or username.
cleanAvatarsDescription
Are you sure you want to clean all avatars?
+
+ downloadLabel
+ Download folder
+
SettingsVideo
diff --git a/linphone-desktop/assets/languages/fr.ts b/linphone-desktop/assets/languages/fr.ts
index f2818ba89..db00856e8 100644
--- a/linphone-desktop/assets/languages/fr.ts
+++ b/linphone-desktop/assets/languages/fr.ts
@@ -760,13 +760,6 @@ Url du serveur non configurée.
Voulez-vous redémarrer maintenant pour prendre en compte ces modifications ?
-
- FileMessage
-
- downloadFileTitle
- Télécharger le fichier
-
-
Home
@@ -1291,6 +1284,10 @@ un chat ou ajouter un contact.
cleanAvatarsDescription
Voulez-vous vraiment supprimer tous les avatars ?
+
+ downloadLabel
+ Dossier des téléchargements
+
SettingsVideo
diff --git a/linphone-desktop/src/app/paths/Paths.cpp b/linphone-desktop/src/app/paths/Paths.cpp
index ba8968c8e..bcfd55937 100644
--- a/linphone-desktop/src/app/paths/Paths.cpp
+++ b/linphone-desktop/src/app/paths/Paths.cpp
@@ -28,6 +28,7 @@
#include
#include "../../utils/Utils.hpp"
+
#include "config.h"
#include "Paths.hpp"
@@ -167,7 +168,7 @@ string Paths::getAvatarsDirPath () {
}
string Paths::getCallHistoryFilePath () {
- return getWritableFilePath(getAppCallHistoryFilePath());
+ return getWritableFilePath(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + PATH_AVATARS);
}
string Paths::getCapturesDirPath () {
@@ -189,6 +190,10 @@ string Paths::getFriendsListFilePath () {
return getWritableFilePath(getAppFriendsFilePath());
}
+std::string Paths::getDownloadDirPath () {
+ return getWritableDirPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
+}
+
string Paths::getLogsDirPath () {
return getWritableDirPath(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + PATH_LOGS);
}
diff --git a/linphone-desktop/src/app/paths/Paths.hpp b/linphone-desktop/src/app/paths/Paths.hpp
index e6bbda9a6..b96f30bcc 100644
--- a/linphone-desktop/src/app/paths/Paths.hpp
+++ b/linphone-desktop/src/app/paths/Paths.hpp
@@ -39,6 +39,7 @@ namespace Paths {
std::string getConfigFilePath (const QString &configPath = QString());
std::string getFactoryConfigFilePath ();
std::string getFriendsListFilePath ();
+ std::string getDownloadDirPath ();
std::string getLogsDirPath ();
std::string getMessageHistoryFilePath ();
std::string getPackageDataDirPath ();
diff --git a/linphone-desktop/src/components/chat/ChatModel.cpp b/linphone-desktop/src/components/chat/ChatModel.cpp
index ed53c13f0..30e95fce9 100644
--- a/linphone-desktop/src/components/chat/ChatModel.cpp
+++ b/linphone-desktop/src/components/chat/ChatModel.cpp
@@ -106,11 +106,9 @@ public:
private:
QList::iterator findMessageEntry (const shared_ptr &message) {
- return find_if(
- mChatModel->mEntries.begin(), mChatModel->mEntries.end(), [&message](const ChatEntryData &pair) {
+ return find_if(mChatModel->mEntries.begin(), mChatModel->mEntries.end(), [&message](const ChatEntryData &pair) {
return pair.second == message;
- }
- );
+ });
}
void signalDataChanged (const QList::iterator &it) {
@@ -390,7 +388,7 @@ void ChatModel::sendFileMessage (const QString &path) {
emit messageSent(message);
}
-void ChatModel::downloadFile (int id, const QString &downloadPath) {
+void ChatModel::downloadFile (int id) {
if (!mChatRoom)
return;
@@ -423,7 +421,20 @@ void ChatModel::downloadFile (int id, const QString &downloadPath) {
return;
}
- message->setFileTransferFilepath(::Utils::appStringToCoreString(downloadPath));
+ bool soFarSoGood;
+ const QString &safeFilePath = ::Utils::getSafeFilePath(
+ QStringLiteral("%1%2")
+ .arg(CoreManager::getInstance()->getSettingsModel()->getDownloadFolder())
+ .arg(entry.first["fileName"].toString()),
+ &soFarSoGood
+ );
+
+ if (!soFarSoGood) {
+ qWarning() << QStringLiteral("Unable to create safe file path for: %1.").arg(id);
+ return;
+ }
+
+ message->setFileTransferFilepath(::Utils::appStringToCoreString(safeFilePath));
message->setListener(mMessageHandlers);
if (message->downloadFile() < 0)
@@ -486,16 +497,14 @@ void ChatModel::removeEntry (ChatEntryData &pair) {
// We are between `beginRemoveRows` and `endRemoveRows`.
// A solution is to schedule a `removeEntry` call in the Qt main loop.
shared_ptr linphonePtr = pair.second;
- QTimer::singleShot(
- 0, this, [this, linphonePtr]() {
+ QTimer::singleShot(0, this, [this, linphonePtr]() {
auto it = find_if(mEntries.begin(), mEntries.end(), [linphonePtr](const ChatEntryData &pair) {
return pair.second == linphonePtr;
});
if (it != mEntries.end())
removeEntry(static_cast(distance(mEntries.begin(), it)));
- }
- );
+ });
}
CoreManager::getInstance()->getCore()->removeCallLog(static_pointer_cast(pair.second));
@@ -525,12 +534,9 @@ void ChatModel::insertCall (const shared_ptr &callLog) {
const ChatEntryData &pair,
const QList::iterator *start = NULL
) {
- auto it = lower_bound(
- start ? *start : mEntries.begin(), mEntries.end(), pair,
- [](const ChatEntryData &a, const ChatEntryData &b) {
+ auto it = lower_bound(start ? *start : mEntries.begin(), mEntries.end(), pair, [](const ChatEntryData &a, const ChatEntryData &b) {
return a.first["timestamp"] < b.first["timestamp"];
- }
- );
+ });
int row = static_cast(distance(mEntries.begin(), it));
diff --git a/linphone-desktop/src/components/chat/ChatModel.hpp b/linphone-desktop/src/components/chat/ChatModel.hpp
index 3a6f0f998..23d46aa10 100644
--- a/linphone-desktop/src/components/chat/ChatModel.hpp
+++ b/linphone-desktop/src/components/chat/ChatModel.hpp
@@ -97,7 +97,7 @@ public:
void sendFileMessage (const QString &path);
- void downloadFile (int id, const QString &downloadPath);
+ void downloadFile (int id);
signals:
void sipAddressChanged (const QString &sipAddress);
diff --git a/linphone-desktop/src/components/chat/ChatProxyModel.cpp b/linphone-desktop/src/components/chat/ChatProxyModel.cpp
index 7f519a8ce..30bcd32e4 100644
--- a/linphone-desktop/src/components/chat/ChatProxyModel.cpp
+++ b/linphone-desktop/src/components/chat/ChatProxyModel.cpp
@@ -69,17 +69,13 @@ ChatProxyModel::ChatProxyModel (QObject *parent) : QSortFilterProxyModel(parent)
ChatModel *chat = static_cast(mChatModelFilter->sourceModel());
- QObject::connect(
- chat, &ChatModel::messageReceived, this, [this](const shared_ptr &) {
+ QObject::connect(chat, &ChatModel::messageReceived, this, [this](const shared_ptr &) {
mMaxDisplayedEntries++;
- }
- );
+ });
- QObject::connect(
- chat, &ChatModel::messageSent, this, [this](const shared_ptr &) {
+ QObject::connect(chat, &ChatModel::messageSent, this, [this](const shared_ptr &) {
mMaxDisplayedEntries++;
- }
- );
+ });
}
void ChatProxyModel::loadMoreEntries () {
@@ -133,10 +129,10 @@ void ChatProxyModel::sendFileMessage (const QString &path) {
static_cast(mChatModelFilter->sourceModel())->sendFileMessage(path);
}
-void ChatProxyModel::downloadFile (int id, const QString &downloadPath) {
+void ChatProxyModel::downloadFile (int id) {
QModelIndex sourceIndex = mapToSource(index(id, 0));
static_cast(mChatModelFilter->sourceModel())->downloadFile(
- mChatModelFilter->mapToSource(sourceIndex).row(), downloadPath
+ mChatModelFilter->mapToSource(sourceIndex).row()
);
}
diff --git a/linphone-desktop/src/components/chat/ChatProxyModel.hpp b/linphone-desktop/src/components/chat/ChatProxyModel.hpp
index a4e0cf3be..fd58c69df 100644
--- a/linphone-desktop/src/components/chat/ChatProxyModel.hpp
+++ b/linphone-desktop/src/components/chat/ChatProxyModel.hpp
@@ -50,7 +50,7 @@ public:
Q_INVOKABLE void sendFileMessage (const QString &path);
- Q_INVOKABLE void downloadFile (int id, const QString &downloadPath);
+ Q_INVOKABLE void downloadFile (int id);
signals:
void sipAddressChanged (const QString &sipAddress);
diff --git a/linphone-desktop/src/components/settings/SettingsModel.cpp b/linphone-desktop/src/components/settings/SettingsModel.cpp
index 2e2c39615..7bd52647a 100644
--- a/linphone-desktop/src/components/settings/SettingsModel.cpp
+++ b/linphone-desktop/src/components/settings/SettingsModel.cpp
@@ -640,10 +640,27 @@ QString SettingsModel::getSavedVideosFolder () const {
}
void SettingsModel::setSavedVideosFolder (const QString &folder) {
- QString _folder = QDir::cleanPath(folder) + QDir::separator();
+ QString cleanedFolder = QDir::cleanPath(folder) + QDir::separator();
- mConfig->setString(UI_SECTION, "saved_videos_folder", ::Utils::appStringToCoreString(_folder));
- emit savedVideosFolderChanged(_folder);
+ mConfig->setString(UI_SECTION, "saved_videos_folder", ::Utils::appStringToCoreString(cleanedFolder));
+ emit savedVideosFolderChanged(cleanedFolder);
+}
+
+// -----------------------------------------------------------------------------
+
+QString SettingsModel::getDownloadFolder () const {
+ return QDir::cleanPath(
+ ::Utils::coreStringToAppString(
+ mConfig->getString(UI_SECTION, "download_folder", Paths::getDownloadDirPath())
+ )
+ ) + QDir::separator();
+}
+
+void SettingsModel::setDownloadFolder (const QString &folder) {
+ QString cleanedFolder = QDir::cleanPath(folder) + QDir::separator();
+
+ mConfig->setString(UI_SECTION, "download_folder", ::Utils::appStringToCoreString(cleanedFolder));
+ emit downloadFolderChanged(cleanedFolder);
}
// -----------------------------------------------------------------------------
diff --git a/linphone-desktop/src/components/settings/SettingsModel.hpp b/linphone-desktop/src/components/settings/SettingsModel.hpp
index 3b11d2340..fd57f6709 100644
--- a/linphone-desktop/src/components/settings/SettingsModel.hpp
+++ b/linphone-desktop/src/components/settings/SettingsModel.hpp
@@ -115,6 +115,7 @@ class SettingsModel : public QObject {
Q_PROPERTY(QString savedScreenshotsFolder READ getSavedScreenshotsFolder WRITE setSavedScreenshotsFolder NOTIFY savedScreenshotsFolderChanged);
Q_PROPERTY(QString savedVideosFolder READ getSavedVideosFolder WRITE setSavedVideosFolder NOTIFY savedVideosFolderChanged);
+ Q_PROPERTY(QString downloadFolder READ getDownloadFolder WRITE setDownloadFolder NOTIFY downloadFolderChanged);
public:
enum MediaEncryption {
@@ -264,6 +265,9 @@ public:
QString getSavedVideosFolder () const;
void setSavedVideosFolder (const QString &folder);
+ QString getDownloadFolder () const;
+ void setDownloadFolder (const QString &folder);
+
QString getRemoteProvisioning () const;
void setRemoteProvisioning (const QString &remoteProvisioning);
@@ -339,6 +343,7 @@ signals:
void savedScreenshotsFolderChanged (const QString &folder);
void savedVideosFolderChanged (const QString &folder);
+ void downloadFolderChanged (const QString &folder);
void remoteProvisioningChanged (const QString &remoteProvisioning);
void remoteProvisioningNotChanged (const QString &remoteProvisioning);
diff --git a/linphone-desktop/src/utils/Utils.cpp b/linphone-desktop/src/utils/Utils.cpp
index 24ebe2496..e13747ff8 100644
--- a/linphone-desktop/src/utils/Utils.cpp
+++ b/linphone-desktop/src/utils/Utils.cpp
@@ -20,6 +20,8 @@
* Author: Ronan Abhamon
*/
+#include
+
#include "Utils.hpp"
// =============================================================================
@@ -38,3 +40,32 @@ char *Utils::rstrstr (const char *a, const char *b) {
return nullptr;
}
+
+// -----------------------------------------------------------------------------
+
+#define SAFE_FILE_PATH_LIMIT 100
+
+QString Utils::getSafeFilePath (const QString &filePath, bool *soFarSoGood) {
+ if (soFarSoGood)
+ *soFarSoGood = true;
+
+ QFileInfo info(filePath);
+ if (!info.exists())
+ return filePath;
+
+ const QString &prefix = QStringLiteral("%1/%2").arg(info.absolutePath()).arg(info.baseName());
+ const QString &ext = info.completeSuffix();
+
+ for (int i = 1; i < SAFE_FILE_PATH_LIMIT; ++i) {
+ QString safePath = QStringLiteral("%1 (%3).%4").arg(prefix).arg(i).arg(ext);
+ if (!QFileInfo::exists(safePath))
+ return safePath;
+ }
+
+ if (soFarSoGood)
+ *soFarSoGood = false;
+
+ return QStringLiteral("");
+}
+
+#undef SAFE_FILE_PATH_LIMIT
diff --git a/linphone-desktop/src/utils/Utils.hpp b/linphone-desktop/src/utils/Utils.hpp
index 1ebcd0c71..633a486a8 100644
--- a/linphone-desktop/src/utils/Utils.hpp
+++ b/linphone-desktop/src/utils/Utils.hpp
@@ -50,6 +50,10 @@ namespace Utils {
// Reverse function of strstr.
char *rstrstr (const char *a, const char *b);
+
+ // Returns the same path given in parameter if `filePath` exists.
+ // Otherwise returns a safe path with a unique number before the extension.
+ QString getSafeFilePath (const QString &filePath, bool *soFarSoGood = nullptr);
}
#endif // UTILS_H_
diff --git a/linphone-desktop/ui/modules/Linphone/Chat/FileMessage.qml b/linphone-desktop/ui/modules/Linphone/Chat/FileMessage.qml
index bc3b56971..7eb4aecac 100644
--- a/linphone-desktop/ui/modules/Linphone/Chat/FileMessage.qml
+++ b/linphone-desktop/ui/modules/Linphone/Chat/FileMessage.qml
@@ -1,6 +1,5 @@
import QtQuick 2.7
import QtQuick.Controls 2.1
-import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.3
import Common 1.0
@@ -179,23 +178,13 @@ Row {
}
MouseArea {
- FileDialog {
- id: fileDialog
-
- folder: shortcuts.home
- selectExisting: false
- title: qsTr('downloadFileTitle')
-
- onAccepted: proxyModel.downloadFile(index, App.convertUrlToLocalPath(fileUrl))
- }
-
anchors.fill: parent
cursorShape: containsMouse
? Qt.PointingHandCursor
: Qt.ArrowCursor
hoverEnabled: true
- onClicked: fileDialog.open()
+ onClicked: proxyModel.downloadFile(index)
visible: !rectangle.isNotDelivered && !$chatEntry.isOutgoing
}
}
diff --git a/linphone-desktop/ui/views/App/Settings/SettingsUi.qml b/linphone-desktop/ui/views/App/Settings/SettingsUi.qml
index b46064464..e1352b7b2 100644
--- a/linphone-desktop/ui/views/App/Settings/SettingsUi.qml
+++ b/linphone-desktop/ui/views/App/Settings/SettingsUi.qml
@@ -96,7 +96,9 @@ TabContainer {
onAccepted: SettingsModel.savedScreenshotsFolder = selectedFile
}
}
+ }
+ FormLine {
FormGroup {
label: qsTr('savedVideosLabel')
@@ -109,6 +111,19 @@ TabContainer {
}
}
+ FormLine {
+ FormGroup {
+ label: qsTr('downloadLabel')
+
+ FileChooserButton {
+ selectedFile: SettingsModel.downloadFolder
+ selectFolder: true
+
+ onAccepted: SettingsModel.downloadFolder = selectedFile
+ }
+ }
+ }
+
FormEmptyLine {}
}