file_utilities.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "core/file_utilities.h"
  8. #include "storage/localstorage.h"
  9. #include "storage/storage_account.h"
  10. #include "base/platform/base_platform_file_utilities.h"
  11. #include "platform/platform_file_utilities.h"
  12. #include "core/application.h"
  13. #include "base/unixtime.h"
  14. #include "ui/delayed_activation.h"
  15. #include "ui/chat/attach/attach_extensions.h"
  16. #include "main/main_session.h"
  17. #include "mainwindow.h"
  18. #include <QtWidgets/QFileDialog>
  19. #include <QtCore/QCoreApplication>
  20. #include <QtCore/QStandardPaths>
  21. #include <QtGui/QDesktopServices>
  22. bool filedialogGetSaveFile(
  23. QPointer<QWidget> parent,
  24. QString &file,
  25. const QString &caption,
  26. const QString &filter,
  27. const QString &initialPath) {
  28. QStringList files;
  29. QByteArray remoteContent;
  30. Ui::PreventDelayedActivation();
  31. bool result = Platform::FileDialog::Get(
  32. parent,
  33. files,
  34. remoteContent,
  35. caption,
  36. filter,
  37. FileDialog::internal::Type::WriteFile,
  38. initialPath);
  39. file = files.isEmpty() ? QString() : files.at(0);
  40. return result;
  41. }
  42. bool filedialogGetSaveFile(
  43. QString &file,
  44. const QString &caption,
  45. const QString &filter,
  46. const QString &initialPath) {
  47. return filedialogGetSaveFile(
  48. Core::App().getFileDialogParent(),
  49. file,
  50. caption,
  51. filter,
  52. initialPath);
  53. }
  54. QString filedialogDefaultName(
  55. const QString &prefix,
  56. const QString &extension,
  57. const QString &path,
  58. bool skipExistance,
  59. TimeId fileTime) {
  60. auto directoryPath = path;
  61. if (directoryPath.isEmpty()) {
  62. if (cDialogLastPath().isEmpty()) {
  63. Platform::FileDialog::InitLastPath();
  64. }
  65. directoryPath = cDialogLastPath();
  66. }
  67. QString base;
  68. if (fileTime) {
  69. const auto date = base::unixtime::parse(fileTime);
  70. base = prefix + date.toString("_yyyy-MM-dd_HH-mm-ss");
  71. } else {
  72. struct tm tm;
  73. time_t t = time(NULL);
  74. mylocaltime(&tm, &t);
  75. const auto zero = QChar('0');
  76. base = prefix + u"_%1-%2-%3_%4-%5-%6"_q.arg(tm.tm_year + 1900).arg(tm.tm_mon + 1, 2, 10, zero).arg(tm.tm_mday, 2, 10, zero).arg(tm.tm_hour, 2, 10, zero).arg(tm.tm_min, 2, 10, zero).arg(tm.tm_sec, 2, 10, zero);
  77. }
  78. QString name;
  79. if (skipExistance) {
  80. name = base + extension;
  81. } else {
  82. QDir directory(directoryPath);
  83. const auto dir = directory.absolutePath();
  84. const auto nameBase = (dir.endsWith('/') ? dir : (dir + '/'))
  85. + base;
  86. name = nameBase + extension;
  87. for (int i = 0; QFileInfo::exists(name); ++i) {
  88. name = nameBase + u" (%1)"_q.arg(i + 2) + extension;
  89. }
  90. }
  91. return name;
  92. }
  93. QString filedialogNextFilename(
  94. const QString &name,
  95. const QString &cur,
  96. const QString &path) {
  97. QDir directory(path.isEmpty() ? cDialogLastPath() : path);
  98. int32 extIndex = name.lastIndexOf('.');
  99. QString prefix = name, extension;
  100. if (extIndex >= 0) {
  101. extension = name.mid(extIndex);
  102. prefix = name.mid(0, extIndex);
  103. }
  104. const auto dir = directory.absolutePath();
  105. const auto nameBase = (dir.endsWith('/') ? dir : (dir + '/')) + prefix;
  106. auto result = nameBase + extension;
  107. for (int i = 0; result.toLower() != cur.toLower() && QFileInfo::exists(result); ++i) {
  108. result = nameBase + u" (%1)"_q.arg(i + 2) + extension;
  109. }
  110. return result;
  111. }
  112. namespace File {
  113. void OpenUrl(const QString &url) {
  114. crl::on_main([=] {
  115. Ui::PreventDelayedActivation();
  116. Platform::File::UnsafeOpenUrl(url);
  117. });
  118. }
  119. void OpenEmailLink(const QString &email) {
  120. crl::on_main([=] {
  121. Ui::PreventDelayedActivation();
  122. Platform::File::UnsafeOpenEmailLink(email);
  123. });
  124. }
  125. void OpenWith(const QString &filepath) {
  126. InvokeQueued(QCoreApplication::instance(), [=] {
  127. if (!Platform::File::UnsafeShowOpenWithDropdown(filepath)) {
  128. Ui::PreventDelayedActivation();
  129. if (!Platform::File::UnsafeShowOpenWith(filepath)) {
  130. Platform::File::UnsafeLaunch(filepath);
  131. }
  132. }
  133. });
  134. }
  135. void Launch(const QString &filepath) {
  136. crl::on_main([=] {
  137. Ui::PreventDelayedActivation();
  138. Platform::File::UnsafeLaunch(filepath);
  139. });
  140. }
  141. void ShowInFolder(const QString &filepath) {
  142. crl::on_main([=] {
  143. Ui::PreventDelayedActivation();
  144. base::Platform::ShowInFolder(filepath);
  145. });
  146. }
  147. QString DefaultDownloadPathFolder(not_null<Main::Session*> session) {
  148. #if OS_MAC_STORE
  149. return u"Telegram Lite"_q;
  150. #else // OS_MAC_STORE
  151. return session->supportMode() ? u"Tsupport Desktop"_q : AppName.utf16();
  152. #endif // OS_MAC_STORE
  153. }
  154. QString DefaultDownloadPath(not_null<Main::Session*> session) {
  155. if (!Core::App().canReadDefaultDownloadPath()) {
  156. return session->local().tempDirectory();
  157. }
  158. return QStandardPaths::writableLocation(
  159. QStandardPaths::DownloadLocation)
  160. + '/'
  161. + DefaultDownloadPathFolder(session)
  162. + '/';
  163. }
  164. namespace internal {
  165. void UnsafeOpenUrlDefault(const QString &url) {
  166. QDesktopServices::openUrl(url);
  167. }
  168. void UnsafeOpenEmailLinkDefault(const QString &email) {
  169. auto url = QUrl(u"mailto:"_q + email);
  170. QDesktopServices::openUrl(url);
  171. }
  172. void UnsafeLaunchDefault(const QString &filepath) {
  173. QDesktopServices::openUrl(QUrl::fromLocalFile(filepath));
  174. }
  175. } // namespace internal
  176. } // namespace File
  177. namespace FileDialog {
  178. void GetOpenPath(
  179. QPointer<QWidget> parent,
  180. const QString &caption,
  181. const QString &filter,
  182. Fn<void(OpenResult &&result)> callback,
  183. Fn<void()> failed) {
  184. InvokeQueued(QCoreApplication::instance(), [=] {
  185. auto files = QStringList();
  186. auto remoteContent = QByteArray();
  187. Ui::PreventDelayedActivation();
  188. const auto success = Platform::FileDialog::Get(
  189. parent,
  190. files,
  191. remoteContent,
  192. caption,
  193. filter,
  194. FileDialog::internal::Type::ReadFile);
  195. if (success
  196. && ((!files.isEmpty() && !files[0].isEmpty())
  197. || !remoteContent.isEmpty())) {
  198. if (callback) {
  199. auto result = OpenResult();
  200. if (!files.isEmpty() && !files[0].isEmpty()) {
  201. result.paths.push_back(files[0]);
  202. }
  203. result.remoteContent = remoteContent;
  204. callback(std::move(result));
  205. }
  206. } else if (failed) {
  207. failed();
  208. }
  209. });
  210. }
  211. void GetOpenPaths(
  212. QPointer<QWidget> parent,
  213. const QString &caption,
  214. const QString &filter,
  215. Fn<void(OpenResult &&result)> callback,
  216. Fn<void()> failed) {
  217. InvokeQueued(QCoreApplication::instance(), [=] {
  218. auto files = QStringList();
  219. auto remoteContent = QByteArray();
  220. Ui::PreventDelayedActivation();
  221. const auto success = Platform::FileDialog::Get(
  222. parent,
  223. files,
  224. remoteContent,
  225. caption,
  226. filter,
  227. FileDialog::internal::Type::ReadFiles);
  228. if (success && (!files.isEmpty() || !remoteContent.isEmpty())) {
  229. if (callback) {
  230. auto result = OpenResult();
  231. result.paths = files;
  232. result.remoteContent = remoteContent;
  233. callback(std::move(result));
  234. }
  235. } else if (failed) {
  236. failed();
  237. }
  238. });
  239. }
  240. void GetWritePath(
  241. QPointer<QWidget> parent,
  242. const QString &caption,
  243. const QString &filter,
  244. const QString &initialPath,
  245. Fn<void(QString &&result)> callback,
  246. Fn<void()> failed) {
  247. InvokeQueued(QCoreApplication::instance(), [=] {
  248. auto file = QString();
  249. if (filedialogGetSaveFile(parent, file, caption, filter, initialPath)) {
  250. if (callback) {
  251. callback(std::move(file));
  252. }
  253. } else if (failed) {
  254. failed();
  255. }
  256. });
  257. }
  258. void GetFolder(
  259. QPointer<QWidget> parent,
  260. const QString &caption,
  261. const QString &initialPath,
  262. Fn<void(QString &&result)> callback,
  263. Fn<void()> failed) {
  264. InvokeQueued(QCoreApplication::instance(), [=] {
  265. auto files = QStringList();
  266. auto remoteContent = QByteArray();
  267. Ui::PreventDelayedActivation();
  268. const auto success = Platform::FileDialog::Get(
  269. parent,
  270. files,
  271. remoteContent,
  272. caption,
  273. QString(),
  274. FileDialog::internal::Type::ReadFolder,
  275. initialPath);
  276. if (success && !files.isEmpty() && !files[0].isEmpty()) {
  277. if (callback) {
  278. callback(std::move(files[0]));
  279. }
  280. } else if (failed) {
  281. failed();
  282. }
  283. });
  284. }
  285. QString AllFilesFilter() {
  286. #ifdef Q_OS_WIN
  287. return u"All files (*.*)"_q;
  288. #else // Q_OS_WIN
  289. return u"All files (*)"_q;
  290. #endif // Q_OS_WIN
  291. }
  292. QString ImagesFilter() {
  293. return u"Image files (*"_q + Ui::ImageExtensions().join(u" *"_q) + u")"_q;
  294. }
  295. QString AllOrImagesFilter() {
  296. return AllFilesFilter() + u";;"_q + ImagesFilter();
  297. }
  298. QString ImagesOrAllFilter() {
  299. return ImagesFilter() + u";;"_q + AllFilesFilter();
  300. }
  301. QString PhotoVideoFilesFilter() {
  302. return u"Image and Video Files (*.png *.jpg *.jpeg *.mp4 *.mov *.m4v);;"_q
  303. + AllFilesFilter();
  304. }
  305. const QString &Tmp() {
  306. static const auto tmp = u"tmp"_q;
  307. return tmp;
  308. }
  309. namespace internal {
  310. void InitLastPathDefault() {
  311. cSetDialogLastPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
  312. }
  313. bool GetDefault(
  314. QPointer<QWidget> parent,
  315. QStringList &files,
  316. QByteArray &remoteContent,
  317. const QString &caption,
  318. const QString &filter,
  319. FileDialog::internal::Type type,
  320. QString startFile = QString()) {
  321. if (cDialogLastPath().isEmpty()) {
  322. Platform::FileDialog::InitLastPath();
  323. }
  324. remoteContent = QByteArray();
  325. if (startFile.isEmpty() || startFile.at(0) != '/') {
  326. startFile = cDialogLastPath() + '/' + startFile;
  327. }
  328. QString file;
  329. const auto resolvedParent = (parent && parent->window()->isVisible())
  330. ? parent->window()
  331. : Core::App().getFileDialogParent();
  332. Core::App().notifyFileDialogShown(true);
  333. const auto guard = gsl::finally([] {
  334. Core::App().notifyFileDialogShown(false);
  335. });
  336. if (type == Type::ReadFiles) {
  337. files = QFileDialog::getOpenFileNames(resolvedParent, caption, startFile, filter);
  338. QString path = files.isEmpty() ? QString() : QFileInfo(files.back()).absoluteDir().absolutePath();
  339. if (!path.isEmpty() && path != cDialogLastPath()) {
  340. cSetDialogLastPath(path);
  341. Local::writeSettings();
  342. }
  343. return !files.isEmpty();
  344. } else if (type == Type::ReadFolder) {
  345. file = QFileDialog::getExistingDirectory(resolvedParent, caption, startFile);
  346. } else if (type == Type::WriteFile) {
  347. file = QFileDialog::getSaveFileName(resolvedParent, caption, startFile, filter);
  348. } else {
  349. file = QFileDialog::getOpenFileName(resolvedParent, caption, startFile, filter);
  350. }
  351. if (file.isEmpty()) {
  352. files = QStringList();
  353. return false;
  354. }
  355. if (type != Type::ReadFolder) {
  356. // Save last used directory for all queries except directory choosing.
  357. auto path = QFileInfo(file).absoluteDir().absolutePath();
  358. if (!path.isEmpty() && path != cDialogLastPath()) {
  359. cSetDialogLastPath(path);
  360. Local::writeSettings();
  361. }
  362. }
  363. files = QStringList(file);
  364. return true;
  365. }
  366. } // namespace internal
  367. } // namespace FileDialog