FileUtil.cpp 76 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "EditorDefs.h"
  9. #include "FileUtil.h"
  10. #include "FileUtil_Common.h"
  11. // Qt
  12. #include <QMenu>
  13. #include <QMessageBox>
  14. #include <QApplication>
  15. #include <QClipboard>
  16. #include <QDesktopServices>
  17. #include <QEvent>
  18. #include <QProcess>
  19. #include <QPushButton>
  20. #include <QThread>
  21. // AzCore
  22. #include <AzCore/Component/TickBus.h>
  23. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  24. #include <AzCore/Utils/Utils.h>
  25. // AzFramework
  26. // AzQtComponents
  27. #include <AzQtComponents/Utilities/DesktopUtilities.h>
  28. // AzToolsFramework
  29. #include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
  30. #include <AzToolsFramework/UI/UICore/ProgressShield.hxx>
  31. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  32. #include <AzToolsFramework/Thumbnails/SourceControlThumbnailBus.h>
  33. // Editor
  34. #include "QtUtilWin.h"
  35. #include "Settings.h"
  36. #include "MainWindow.h"
  37. #include "CheckOutDialog.h"
  38. #include "Dialogs/Generic/UserOptions.h"
  39. #include "UsedResources.h"
  40. #include "StringHelpers.h"
  41. #include "AutoDirectoryRestoreFileDialog.h"
  42. #include "EditorPreferencesDialog.h"
  43. namespace Platform
  44. {
  45. // Forward declare platform specific functions
  46. bool RunCommandWithArguments(const QString& command, const QStringList& argsList);
  47. bool RunEditorWithArg(const QString& editor, const QString& arg);
  48. bool OpenUri(const QUrl& uri);
  49. QString GetDefaultEditor(const Common::EditFileType fileType);
  50. QString MakePlatformFileEditString(QString pathToEdit, int lineToEdit);
  51. bool CreatePath(const QString& strPath);
  52. const char* GetLuaCompilerName();
  53. } // namespace Platform
  54. bool CFileUtil::s_singleFileDlgPref[IFileUtil::EFILE_TYPE_LAST] = { true, true, true, true };
  55. bool CFileUtil::s_multiFileDlgPref[IFileUtil::EFILE_TYPE_LAST] = { true, true, true, true };
  56. CAutoRestorePrimaryCDRoot::~CAutoRestorePrimaryCDRoot()
  57. {
  58. QDir::setCurrent(GetIEditor()->GetPrimaryCDFolder());
  59. }
  60. bool CFileUtil::ExtractFile(QString& file, bool bMsgBoxAskForExtraction, const char* pDestinationFilename)
  61. {
  62. CCryFile cryfile;
  63. if (cryfile.Open(file.toUtf8().data(), "rb"))
  64. {
  65. // Check if in pack.
  66. if (cryfile.IsInPak())
  67. {
  68. if (bMsgBoxAskForExtraction)
  69. {
  70. AZ::IO::FixedMaxPath sPakName{ cryfile.GetPakPath() };
  71. // Cannot edit file in pack, suggest to extract it for editing.
  72. if (QMessageBox::critical(QApplication::activeWindow(), QString(),
  73. QObject::tr("File %1 is inside a PAK file %2\r\nDo you want it to be extracted for editing ?").arg(file, sPakName.c_str()),
  74. QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
  75. {
  76. return false;
  77. }
  78. }
  79. if (pDestinationFilename)
  80. {
  81. file = pDestinationFilename;
  82. }
  83. CFileUtil::CreatePath(Path::GetPath(file));
  84. // Extract it from Pak file.
  85. QFile diskFile(file);
  86. if (diskFile.open(QFile::WriteOnly))
  87. {
  88. // Copy data from packed file to disk file.
  89. auto data = AZStd::make_unique<char[]>(cryfile.GetLength());
  90. cryfile.ReadRaw(data.get(), cryfile.GetLength());
  91. diskFile.write(data.get(), cryfile.GetLength());
  92. }
  93. else
  94. {
  95. Warning("Failed to create file %s on disk", file.toUtf8().data());
  96. }
  97. }
  98. else
  99. {
  100. if (auto fileIoBase = AZ::IO::FileIOBase::GetInstance(); fileIoBase != nullptr)
  101. {
  102. if (AZ::IO::FixedMaxPath resolvedFilePath; fileIoBase->ResolvePath(resolvedFilePath, cryfile.GetFilename()))
  103. {
  104. file = QString::fromUtf8(resolvedFilePath.c_str(), static_cast<int>(resolvedFilePath.Native().size()));
  105. }
  106. }
  107. }
  108. return true;
  109. }
  110. return false;
  111. }
  112. //////////////////////////////////////////////////////////////////////////
  113. QString CFileUtil::GetEditorForFileTypeFromPreferences(const Common::EditFileType fileType)
  114. {
  115. QString textEditor;
  116. switch (fileType)
  117. {
  118. case Common::EditFileType::FILE_TYPE_SHADER:
  119. return gSettings.textEditorForShaders;
  120. case Common::EditFileType::FILE_TYPE_BSPACE:
  121. return gSettings.textEditorForBspaces;
  122. case Common::EditFileType::FILE_TYPE_SCRIPT:
  123. return gSettings.textEditorForScript;
  124. case Common::EditFileType::FILE_TYPE_TEXTURE:
  125. return gSettings.textureEditor;
  126. case Common::EditFileType::FILE_TYPE_ANIMATION:
  127. return gSettings.animEditor;
  128. default:
  129. AZ_Assert(false, "Unknown file type.");
  130. return "";
  131. }
  132. }
  133. void CFileUtil::HandlePrefsDialogForFileType(const Common::EditFileType fileType)
  134. {
  135. // Open the preferences dialog.
  136. EditorPreferencesDialog dlg(MainWindow::instance());
  137. dlg.open();
  138. // Assign a filter string so that only the appropriate option shows up.
  139. switch (fileType)
  140. {
  141. case Common::EditFileType::FILE_TYPE_SHADER:
  142. dlg.SetFilterText("Shaders Editor");
  143. break;
  144. case Common::EditFileType::FILE_TYPE_BSPACE:
  145. dlg.SetFilterText("BSpace Editor");
  146. break;
  147. case Common::EditFileType::FILE_TYPE_SCRIPT:
  148. dlg.SetFilterText("Scripts Editor");
  149. break;
  150. case Common::EditFileType::FILE_TYPE_TEXTURE:
  151. dlg.SetFilterText("Texture Editor");
  152. break;
  153. case Common::EditFileType::FILE_TYPE_ANIMATION:
  154. dlg.SetFilterText("Animation Editor");
  155. break;
  156. default:
  157. AZ_Assert(false, "Unknown file type.");
  158. break;
  159. }
  160. // Wait for the dialog to complete.
  161. dlg.exec();
  162. }
  163. AZStd::string CFileUtil::GetSettingsKeyForFileType(const Common::EditFileType fileType)
  164. {
  165. switch (fileType)
  166. {
  167. case Common::EditFileType::FILE_TYPE_BSPACE:
  168. return "Settings|TextEditorBSpaces";
  169. case Common::EditFileType::FILE_TYPE_SHADER:
  170. return "Settings|TextEditorShaders";
  171. case Common::EditFileType::FILE_TYPE_SCRIPT:
  172. return "Settings|TextEditorScript";
  173. case Common::EditFileType::FILE_TYPE_TEXTURE:
  174. return "Settings|TextureEditor";
  175. case Common::EditFileType::FILE_TYPE_ANIMATION:
  176. return "Settings|AnimationEditor";
  177. default:
  178. AZ_Assert(false, "Unknown file type.");
  179. }
  180. return "";
  181. }
  182. QString CFileUtil::HandleNoEditorAssigned(const Common::EditFileType fileType)
  183. {
  184. QMessageBox dialog(AzToolsFramework::GetActiveWindow());
  185. dialog.setWindowTitle(QString());
  186. QAbstractButton* defaultButton = nullptr;
  187. QAbstractButton* assignButton = nullptr;
  188. QString defaultEditor = Platform::GetDefaultEditor(fileType);
  189. if (defaultEditor.isEmpty())
  190. {
  191. dialog.setText(QObject::tr("No editor is set for opening this file type. Would you like to go to update the default program?"));
  192. assignButton = dialog.addButton(QObject::tr("Settings"), QMessageBox::YesRole);
  193. dialog.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
  194. }
  195. else
  196. {
  197. QString editorCapitalized = defaultEditor;
  198. editorCapitalized[0] = editorCapitalized[0].toUpper();
  199. dialog.setText(
  200. QObject::tr(
  201. "No editor is set for opening this file type. Would you like to open the file using %1 or update the default program?")
  202. .arg(editorCapitalized));
  203. defaultButton = dialog.addButton(editorCapitalized, QMessageBox::YesRole);
  204. assignButton = dialog.addButton(QObject::tr("Settings"), QMessageBox::YesRole);
  205. dialog.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
  206. }
  207. dialog.exec();
  208. if (dialog.clickedButton() == defaultButton)
  209. {
  210. // Save the new default editor to settings.
  211. AZStd::string editorName = defaultEditor.toUtf8().data();
  212. AZStd::any editorAny = AZStd::make_any<AZStd::string>(defaultEditor.toUtf8().data());
  213. gSettings.SetValue(GetSettingsKeyForFileType(fileType), editorAny);
  214. return defaultEditor;
  215. }
  216. else if (dialog.clickedButton() == assignButton)
  217. {
  218. HandlePrefsDialogForFileType(fileType);
  219. return GetEditorForFileTypeFromPreferences(fileType);
  220. }
  221. return "";
  222. }
  223. QString CFileUtil::HandleEditorOpenFailure(const Common::EditFileType fileType, const QString& currentEditor)
  224. {
  225. QMessageBox dialog(AzToolsFramework::GetActiveWindow());
  226. dialog.setWindowTitle(QString());
  227. QAbstractButton* defaultButton = nullptr;
  228. QAbstractButton* assignButton = nullptr;
  229. QString defaultEditor = Platform::GetDefaultEditor(fileType);
  230. if (defaultEditor == currentEditor)
  231. {
  232. dialog.setText(
  233. QObject::tr("Failed to run %1. Would you like to go to the settings and update the default program?").arg(currentEditor));
  234. assignButton = dialog.addButton(QObject::tr("Settings"), QMessageBox::YesRole);
  235. dialog.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
  236. }
  237. else
  238. {
  239. QString editorCapitalized = defaultEditor;
  240. editorCapitalized[0] = editorCapitalized[0].toUpper();
  241. dialog.setText(QObject::tr("Failed to run %1. Would you like to use %2, or go to the settings and update the default program?")
  242. .arg(currentEditor)
  243. .arg(editorCapitalized));
  244. defaultButton = dialog.addButton(editorCapitalized, QMessageBox::YesRole);
  245. assignButton = dialog.addButton(QObject::tr("Settings"), QMessageBox::YesRole);
  246. dialog.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
  247. }
  248. dialog.exec();
  249. if (dialog.clickedButton() == defaultButton)
  250. {
  251. return defaultEditor;
  252. }
  253. else if (dialog.clickedButton() == assignButton)
  254. {
  255. HandlePrefsDialogForFileType(fileType);
  256. return GetEditorForFileTypeFromPreferences(fileType);
  257. }
  258. return "";
  259. }
  260. //////////////////////////////////////////////////////////////////////////
  261. void CFileUtil::EditTextFile(const char* txtFile, int line, IFileUtil::ETextFileType fileType)
  262. {
  263. QString file = txtFile;
  264. QString fullPathName = Path::GamePathToFullPath(file);
  265. ExtractFile(fullPathName);
  266. QString cmd = Platform::MakePlatformFileEditString(fullPathName, line);
  267. Common::EditFileType editFileType = Common::EditFileType::FILE_TYPE_SCRIPT;
  268. switch (fileType)
  269. {
  270. case IFileUtil::ETextFileType::FILE_TYPE_BSPACE:
  271. editFileType = Common::EditFileType::FILE_TYPE_BSPACE;
  272. break;
  273. case IFileUtil::ETextFileType::FILE_TYPE_SCRIPT:
  274. editFileType = Common::EditFileType::FILE_TYPE_SCRIPT;
  275. break;
  276. case IFileUtil::ETextFileType::FILE_TYPE_SHADER:
  277. editFileType = Common::EditFileType::FILE_TYPE_SHADER;
  278. break;
  279. default:
  280. // Ensure nothing's been added to the ETextFileType enum we don't know about.
  281. AZ_Assert(false, "Unknown IFileUtil::ETextFileType value.");
  282. break;
  283. }
  284. EditFile(cmd, editFileType);
  285. }
  286. //////////////////////////////////////////////////////////////////////////
  287. void CFileUtil::EditTextureFile(const char* textureFile, [[maybe_unused]] bool bUseGameFolder)
  288. {
  289. using namespace AzToolsFramework;
  290. using namespace AzToolsFramework::AssetSystem;
  291. AZStd::string fullTexturePath;
  292. bool fullTexturePathFound = false;
  293. AZStd::string relativePath = textureFile;
  294. // First check if we have been given an empty path
  295. QString warningTitle = QObject::tr("Cannot open file!");
  296. if (relativePath.empty())
  297. {
  298. QString messageString = QObject::tr("Texture path is empty. You need to assign a texture first.");
  299. QMessageBox::warning(AzToolsFramework::GetActiveWindow(), warningTitle, messageString);
  300. return;
  301. }
  302. AssetSystemRequestBus::BroadcastResult(
  303. fullTexturePathFound, &AssetSystemRequest::GetFullSourcePathFromRelativeProductPath, relativePath, fullTexturePath);
  304. if (!fullTexturePathFound)
  305. {
  306. QString messageString = QObject::tr("Failed to find absolute path to %1 - could not open texture editor.").arg(textureFile);
  307. QMessageBox::warning(AzToolsFramework::GetActiveWindow(), warningTitle, messageString);
  308. return;
  309. }
  310. EditFile(fullTexturePath.c_str(), Common::EditFileType::FILE_TYPE_TEXTURE);
  311. }
  312. //////////////////////////////////////////////////////////////////////////
  313. void CFileUtil::EditFile(const QString& filename, const Common::EditFileType fileType)
  314. {
  315. QString editor = GetEditorForFileTypeFromPreferences(fileType);
  316. if (editor.isEmpty())
  317. {
  318. editor = HandleNoEditorAssigned(fileType);
  319. }
  320. // If editor is still not set, just drop out.
  321. if (editor.isEmpty())
  322. {
  323. return;
  324. }
  325. // Keep trying to open the file if the user changes the editor. If not, just drop out.
  326. while (!Platform::RunEditorWithArg(editor, filename))
  327. {
  328. editor = HandleEditorOpenFailure(fileType, editor);
  329. if (editor.isEmpty())
  330. {
  331. return;
  332. }
  333. }
  334. }
  335. //////////////////////////////////////////////////////////////////////////
  336. void CFileUtil::FormatFilterString(QString& filter)
  337. {
  338. const int numPipeChars = static_cast<int>(std::count(filter.begin(), filter.end(), '|'));
  339. if (numPipeChars == 1)
  340. {
  341. filter = QStringLiteral("%1||").arg(filter);
  342. }
  343. else if (numPipeChars > 1)
  344. {
  345. assert(numPipeChars % 2 != 0);
  346. if (!filter.contains("||"))
  347. {
  348. filter = QStringLiteral("%1||").arg(filter);
  349. }
  350. }
  351. else if (!filter.isEmpty())
  352. {
  353. filter = QStringLiteral("%1|%2||").arg(filter, filter);
  354. }
  355. }
  356. //////////////////////////////////////////////////////////////////////////
  357. bool CFileUtil::SelectFile(const QString& fileSpec, const QString& searchFolder, QString& fullFileName)
  358. {
  359. QtUtil::QtMFCScopedHWNDCapture cap;
  360. CAutoDirectoryRestoreFileDialog dlg(QFileDialog::AcceptOpen, QFileDialog::ExistingFile, {}, searchFolder, fileSpec, {}, {}, cap);
  361. if (dlg.exec())
  362. {
  363. fullFileName = dlg.selectedFiles().first();
  364. return true;
  365. /*
  366. if (!fileName.IsEmpty())
  367. {
  368. relativeFileName = Path::GetRelativePath( fileName );
  369. if (!relativeFileName.IsEmpty())
  370. {
  371. return true;
  372. }
  373. else
  374. {
  375. Warning( "You must select files from %s folder",(const char*)GetIEditor()->GetPrimaryCDFolder(); );
  376. }
  377. }
  378. */
  379. }
  380. // CSelectFileDlg cDialog;
  381. // bool res = cDialog.SelectFileName( &fileName,&relativeFileName,fileSpec,searchFolder );
  382. return false;
  383. }
  384. bool CFileUtil::SelectFiles(const QString& fileSpec, const QString& searchFolder, QStringList& files)
  385. {
  386. QtUtil::QtMFCScopedHWNDCapture cap;
  387. CAutoDirectoryRestoreFileDialog dlg(QFileDialog::AcceptOpen, QFileDialog::ExistingFiles, {}, searchFolder, fileSpec, {}, {}, cap);
  388. if (dlg.exec())
  389. {
  390. const QStringList selected = dlg.selectedFiles();
  391. foreach(const QString&file, selected)
  392. {
  393. files.push_back(file);
  394. }
  395. }
  396. if (!files.empty())
  397. {
  398. return true;
  399. }
  400. return false;
  401. }
  402. bool CFileUtil::SelectSaveFile(const QString& fileFilter, const QString& defaulExtension, const QString& startFolder, QString& fileName)
  403. {
  404. QtUtil::QtMFCScopedHWNDCapture cap;
  405. CAutoDirectoryRestoreFileDialog dlg(QFileDialog::AcceptSave, {}, defaulExtension, startFolder, fileFilter, {}, {}, cap);
  406. if (dlg.exec())
  407. {
  408. fileName = dlg.selectedFiles().first();
  409. return true;
  410. }
  411. return false;
  412. }
  413. //////////////////////////////////////////////////////////////////////////
  414. // Get directory contents.
  415. //////////////////////////////////////////////////////////////////////////
  416. inline bool ScanDirectoryFiles(const QString& root, const QString& path, const QString& fileSpec, IFileUtil::FileArray& files, bool bSkipPaks)
  417. {
  418. bool anyFound = false;
  419. QString dir = Path::AddPathSlash(root + path);
  420. QString findFilter = Path::Make(dir, fileSpec);
  421. auto pIPak = GetIEditor()->GetSystem()->GetIPak();
  422. // Add all directories.
  423. AZ::IO::ArchiveFileIterator fhandle = pIPak->FindFirst(findFilter.toUtf8().data());
  424. if (fhandle)
  425. {
  426. do
  427. {
  428. // Skip back folders.
  429. if (fhandle.m_filename.front() == '.')
  430. {
  431. continue;
  432. }
  433. if ((fhandle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory) // skip sub directories.
  434. {
  435. continue;
  436. }
  437. if (bSkipPaks && (fhandle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Archive) == AZ::IO::FileDesc::Attribute::Archive)
  438. {
  439. continue;
  440. }
  441. anyFound = true;
  442. IFileUtil::FileDesc file;
  443. file.filename = path + fhandle.m_filename.data();
  444. file.attrib = static_cast<unsigned int>(fhandle.m_fileDesc.nAttrib);
  445. file.size = fhandle.m_fileDesc.nSize;
  446. file.time_access = fhandle.m_fileDesc.tAccess;
  447. file.time_create = fhandle.m_fileDesc.tCreate;
  448. file.time_write = fhandle.m_fileDesc.tWrite;
  449. files.push_back(file);
  450. } while (fhandle = pIPak->FindNext(fhandle));
  451. pIPak->FindClose(fhandle);
  452. }
  453. /*
  454. CFileFind finder;
  455. bool bWorking = finder.FindFile( Path::Make(dir,fileSpec) );
  456. while (bWorking)
  457. {
  458. bWorking = finder.FindNextFile();
  459. if (finder.IsDots())
  460. continue;
  461. if (!finder.IsDirectory())
  462. {
  463. anyFound = true;
  464. IFileUtil::FileDesc fd;
  465. fd.filename = dir + finder.GetFileName();
  466. fd.nFileSize = finder.GetLength();
  467. finder.GetCreationTime( &fd.ftCreationTime );
  468. finder.GetLastAccessTime( &fd.ftLastAccessTime );
  469. finder.GetLastWriteTime( &fd.ftLastWriteTime );
  470. fd.dwFileAttributes = 0;
  471. if (finder.IsArchived())
  472. fd.dwFileAttributes |= FILE_ATTRIBUTE_ARCHIVE;
  473. if (finder.IsCompressed())
  474. fd.dwFileAttributes |= FILE_ATTRIBUTE_COMPRESSED;
  475. if (finder.IsNormal())
  476. fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
  477. if (finder.IsHidden())
  478. fd.dwFileAttributes = FILE_ATTRIBUTE_HIDDEN;
  479. if (finder.IsReadOnly())
  480. fd.dwFileAttributes = FILE_ATTRIBUTE_READONLY;
  481. if (finder.IsSystem())
  482. fd.dwFileAttributes = FILE_ATTRIBUTE_SYSTEM;
  483. if (finder.IsTemporary())
  484. fd.dwFileAttributes = FILE_ATTRIBUTE_TEMPORARY;
  485. files.push_back(fd);
  486. }
  487. }
  488. */
  489. return anyFound;
  490. }
  491. //////////////////////////////////////////////////////////////////////////
  492. // Get directory contents.
  493. //////////////////////////////////////////////////////////////////////////
  494. inline int ScanDirectoryRecursive(const QString& root, const QString& path, const QString& fileSpec, IFileUtil::FileArray& files, bool recursive, bool addDirAlso,
  495. IFileUtil::ScanDirectoryUpdateCallBack updateCB, bool bSkipPaks)
  496. {
  497. bool anyFound = false;
  498. QString dir = Path::AddPathSlash(root + path);
  499. if (updateCB)
  500. {
  501. QString msg = QObject::tr("Scanning %1...").arg(dir);
  502. if (updateCB(msg) == false)
  503. {
  504. return -1;
  505. }
  506. }
  507. if (ScanDirectoryFiles(root, Path::AddPathSlash(path), fileSpec, files, bSkipPaks))
  508. {
  509. anyFound = true;
  510. }
  511. if (recursive)
  512. {
  513. /*
  514. CFileFind finder;
  515. bool bWorking = finder.FindFile( Path::Make(dir,"*.*") );
  516. while (bWorking)
  517. {
  518. bWorking = finder.FindNextFile();
  519. if (finder.IsDots())
  520. continue;
  521. if (finder.IsDirectory())
  522. {
  523. // Scan directory.
  524. if (ScanDirectoryRecursive( root,Path::AddBackslash(path+finder.GetFileName()),fileSpec,files,recursive ))
  525. anyFound = true;
  526. }
  527. }
  528. */
  529. auto pIPak = GetIEditor()->GetSystem()->GetIPak();
  530. // Add all directories.
  531. AZ::IO::ArchiveFileIterator fhandle = pIPak->FindFirst(Path::Make(dir, "*").toUtf8().data());
  532. if (fhandle)
  533. {
  534. do
  535. {
  536. // Skip back folders.
  537. if (fhandle.m_filename.front() == '.')
  538. {
  539. continue;
  540. }
  541. if ((fhandle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) != AZ::IO::FileDesc::Attribute::Subdirectory) // skip not directories.
  542. {
  543. continue;
  544. }
  545. if (bSkipPaks && (fhandle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Archive) == AZ::IO::FileDesc::Attribute::Archive)
  546. {
  547. continue;
  548. }
  549. if (addDirAlso)
  550. {
  551. IFileUtil::FileDesc Dir;
  552. Dir.filename = path + QString::fromUtf8(fhandle.m_filename.data(), aznumeric_cast<int>(fhandle.m_filename.size()));
  553. Dir.attrib = static_cast<unsigned int>(fhandle.m_fileDesc.nAttrib);
  554. Dir.size = fhandle.m_fileDesc.nSize;
  555. Dir.time_access = fhandle.m_fileDesc.tAccess;
  556. Dir.time_create = fhandle.m_fileDesc.tCreate;
  557. Dir.time_write = fhandle.m_fileDesc.tWrite;
  558. files.push_back(Dir);
  559. }
  560. // Scan directory.
  561. int result = ScanDirectoryRecursive(root, Path::AddPathSlash(path + fhandle.m_filename.data()), fileSpec, files, recursive, addDirAlso, updateCB, bSkipPaks);
  562. if (result == -1)
  563. // Cancel the scan immediately.
  564. {
  565. pIPak->FindClose(fhandle);
  566. return -1;
  567. }
  568. else if (result == 1)
  569. {
  570. anyFound = true;
  571. }
  572. } while (fhandle = pIPak->FindNext(fhandle));
  573. pIPak->FindClose(fhandle);
  574. }
  575. }
  576. return anyFound ? 1 : 0;
  577. }
  578. //////////////////////////////////////////////////////////////////////////
  579. bool CFileUtil::ScanDirectory(const QString& path, const QString& file, IFileUtil::FileArray& files, bool recursive,
  580. bool addDirAlso, IFileUtil::ScanDirectoryUpdateCallBack updateCB, bool bSkipPaks)
  581. {
  582. QString fileSpec = Path::GetFile(file);
  583. QString localPath = Path::GetPath(file);
  584. return ScanDirectoryRecursive(Path::AddPathSlash(path), localPath, fileSpec, files, recursive, addDirAlso, updateCB, bSkipPaks) > 0;
  585. }
  586. void CFileUtil::ShowInExplorer([[maybe_unused]] const QString& path)
  587. {
  588. AZStd::string assetRoot;
  589. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  590. {
  591. settingsRegistry->Get(assetRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
  592. }
  593. auto fullpath = QString::fromUtf8(assetRoot.c_str(), aznumeric_cast<int>(assetRoot.size()));
  594. AzQtComponents::ShowFileOnDesktop(fullpath);
  595. }
  596. /*
  597. bool CFileUtil::ScanDirectory( const QString &startDirectory,const QString &searchPath,const QString &fileSpec,FileArray &files, bool recursive=true )
  598. {
  599. return ScanDirectoryRecursive(startDirectory,SearchPath,file,files,recursive );
  600. }
  601. */
  602. //////////////////////////////////////////////////////////////////////////
  603. bool CFileUtil::OverwriteFile(const QString& filename)
  604. {
  605. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  606. AZ_Assert(fileIO, "FileIO is not initialized.");
  607. QString adjFileName = Path::GamePathToFullPath(filename);
  608. AZStd::string filePath = adjFileName.toUtf8().data();
  609. if (!fileIO->IsReadOnly(filePath.c_str()))
  610. {
  611. // if its already writable, we can just RequestEdit async and return immediately
  612. // RequestEdit will mark it for "add" if it needs to be added.
  613. using namespace AzToolsFramework;
  614. SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestEdit, filePath.c_str(), true, [](bool, const SourceControlFileInfo&) {});
  615. return true;
  616. }
  617. // Otherwise, show the checkout dialog
  618. if (!CCheckOutDialog::IsForAll())
  619. {
  620. QtUtil::QtMFCScopedHWNDCapture cap;
  621. CCheckOutDialog dlg(adjFileName, cap);
  622. dlg.exec();
  623. }
  624. bool opSuccess = false;
  625. switch (CCheckOutDialog::LastResult())
  626. {
  627. case CCheckOutDialog::CANCEL:
  628. break;
  629. case CCheckOutDialog::CHECKOUT:
  630. opSuccess = CheckoutFile(filePath.c_str());
  631. break;
  632. case CCheckOutDialog::OVERWRITE:
  633. opSuccess = AZ::IO::SystemFile::SetWritable(filePath.c_str(), true);
  634. break;
  635. default:
  636. AZ_Assert(false, "Unsupported result returned from CCheckoutDialog");
  637. }
  638. return opSuccess;
  639. }
  640. //////////////////////////////////////////////////////////////////////////
  641. void BlockAndWait(const bool& opComplete, QWidget* parent, const char* message)
  642. {
  643. bool useProgressShield = false;
  644. bool isGUIThread = false;
  645. if (QApplication::instance()->thread() == QThread::currentThread())
  646. {
  647. isGUIThread = true;
  648. if (!parent)
  649. {
  650. parent = QApplication::activeWindow() ? QApplication::activeWindow() : MainWindow::instance();
  651. }
  652. useProgressShield = parent ? true : false;
  653. }
  654. if (useProgressShield)
  655. {
  656. // ProgressShield will internally pump the Qt Event Pump and the AZ::TickBus.
  657. AzToolsFramework::ProgressShield::LegacyShowAndWait(parent, parent->tr(message),
  658. [&opComplete](int& current, int& max)
  659. {
  660. current = 0;
  661. max = 0;
  662. return opComplete;
  663. },
  664. 500);
  665. }
  666. else
  667. {
  668. // either we are not on the main thread or we are not using the progress shield.
  669. while (!opComplete)
  670. {
  671. // we can ONLY interact with the application event loop or the AZ::TickBus from the GUI thread.
  672. if (isGUIThread)
  673. {
  674. // note that 16ms below is not the amount of time to wait, its the maximum time that
  675. // processEvents is allowed to keep processing them if they just keep being emitted.
  676. // adding a maximum time here means that we get an opportunity to pump the TickBus
  677. // periodically even during a flood of events.
  678. QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 16);
  679. AZ::TickBus::ExecuteQueuedEvents();
  680. }
  681. // if we are not the main thread then the above will be done by the main thread, and we can just wait for it to happen.
  682. // its fairly important we don't sleep for really long because this legacy code is often invoked in a blocking loop
  683. // for many items, and in the worst case, any time we spend sleeping here will be added to each item.
  684. AZStd::this_thread::yield();
  685. }
  686. }
  687. }
  688. //////////////////////////////////////////////////////////////////////////
  689. bool CFileUtil::CheckoutFile(const char* filename, QWidget* parentWindow)
  690. {
  691. using namespace AzToolsFramework;
  692. bool scOpSuccess = false;
  693. bool scOpComplete = false;
  694. SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestEdit, filename, true,
  695. [&scOpSuccess, &scOpComplete, filename](bool success, const SourceControlFileInfo& /*info*/)
  696. {
  697. scOpSuccess = success;
  698. scOpComplete = true;
  699. Thumbnailer::SourceControlThumbnailRequestBus::Broadcast(&Thumbnailer::SourceControlThumbnailRequests::FileStatusChanged, filename);
  700. }
  701. );
  702. BlockAndWait(scOpComplete, parentWindow, "Checking out for edit...");
  703. return scOpSuccess;
  704. }
  705. //////////////////////////////////////////////////////////////////////////
  706. bool CFileUtil::RevertFile(const char* filename, QWidget* parentWindow)
  707. {
  708. using namespace AzToolsFramework;
  709. bool scOpSuccess = false;
  710. bool scOpComplete = false;
  711. SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestRevert, filename,
  712. [&scOpSuccess, &scOpComplete, filename](bool success, const SourceControlFileInfo& /*info*/)
  713. {
  714. scOpSuccess = success;
  715. scOpComplete = true;
  716. Thumbnailer::SourceControlThumbnailRequestBus::Broadcast(&Thumbnailer::SourceControlThumbnailRequests::FileStatusChanged, filename);
  717. }
  718. );
  719. BlockAndWait(scOpComplete, parentWindow, "Discarding Changes...");
  720. return scOpSuccess;
  721. }
  722. //////////////////////////////////////////////////////////////////////////
  723. bool CFileUtil::RenameFile(const char* sourceFile, const char* targetFile, QWidget* parentWindow)
  724. {
  725. using namespace AzToolsFramework;
  726. bool scOpSuccess = false;
  727. bool scOpComplete = false;
  728. SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestRename, sourceFile, targetFile,
  729. [&scOpSuccess, &scOpComplete](bool success, const SourceControlFileInfo& /*info*/)
  730. {
  731. scOpSuccess = success;
  732. scOpComplete = true;
  733. }
  734. );
  735. BlockAndWait(scOpComplete, parentWindow, "Renaming file...");
  736. return scOpSuccess;
  737. }
  738. bool override;
  739. //////////////////////////////////////////////////////////////////////////
  740. bool CFileUtil::DeleteFromSourceControl(const char* filename, QWidget* parentWindow)
  741. {
  742. using namespace AzToolsFramework;
  743. bool scOpSuccess = false;
  744. bool scOpComplete = false;
  745. SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestDelete, filename,
  746. [&scOpSuccess, &scOpComplete, filename](bool success, const SourceControlFileInfo& /*info*/)
  747. {
  748. scOpSuccess = success;
  749. scOpComplete = true;
  750. Thumbnailer::SourceControlThumbnailRequestBus::Broadcast(&Thumbnailer::SourceControlThumbnailRequests::FileStatusChanged, filename);
  751. }
  752. );
  753. BlockAndWait(scOpComplete, parentWindow, "Marking for deletion...");
  754. return scOpSuccess;
  755. }
  756. //////////////////////////////////////////////////////////////////////////
  757. bool CFileUtil::GetLatestFromSourceControl(const char* filename, QWidget* parentWindow)
  758. {
  759. using namespace AzToolsFramework;
  760. bool scOpSuccess = false;
  761. bool scOpComplete = false;
  762. SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestLatest, filename,
  763. [&scOpSuccess, &scOpComplete, filename](bool success, const SourceControlFileInfo& /*info*/)
  764. {
  765. scOpSuccess = success;
  766. scOpComplete = true;
  767. Thumbnailer::SourceControlThumbnailRequestBus::Broadcast(&Thumbnailer::SourceControlThumbnailRequests::FileStatusChanged, filename);
  768. }
  769. );
  770. BlockAndWait(scOpComplete, parentWindow, "Requesting latest version of file...");
  771. return scOpSuccess;
  772. }
  773. //////////////////////////////////////////////////////////////////////////
  774. bool CFileUtil::GetFileInfoFromSourceControl(const char* filename, AzToolsFramework::SourceControlFileInfo& fileInfo, QWidget* parentWindow)
  775. {
  776. using namespace AzToolsFramework;
  777. bool scOpSuccess = false;
  778. bool scOpComplete = false;
  779. SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::GetFileInfo, filename,
  780. [&fileInfo, &scOpSuccess, &scOpComplete](bool success, const SourceControlFileInfo& info)
  781. {
  782. fileInfo = info;
  783. scOpSuccess = success;
  784. scOpComplete = true;
  785. }
  786. );
  787. BlockAndWait(scOpComplete, parentWindow, "Getting file status...");
  788. return scOpSuccess;
  789. }
  790. // Create new directory, check if directory already exist.
  791. static bool CheckAndCreateDirectory(const QString& path)
  792. {
  793. // QFileInfo does not handle mixed separators (/ and \) gracefully, so cleaning up path
  794. const QString cleanPath = QDir::cleanPath(path).replace('\\', '/');
  795. QFileInfo fileInfo(cleanPath);
  796. if (fileInfo.isDir())
  797. {
  798. return true;
  799. }
  800. else if (!fileInfo.exists())
  801. {
  802. return QDir().mkpath(cleanPath);
  803. }
  804. return false;
  805. }
  806. static bool MoveFileReplaceExisting(const QString& existingFileName, const QString& newFileName)
  807. {
  808. bool moveSuccessful = false;
  809. // Delete the new file if it already exists
  810. QFile newFile(newFileName);
  811. if (newFile.exists())
  812. {
  813. newFile.setPermissions(newFile.permissions() | QFile::ReadOther | QFile::WriteOther);
  814. newFile.remove();
  815. }
  816. // Rename the existing file if it exists
  817. QFile existingFile(existingFileName);
  818. if (existingFile.exists())
  819. {
  820. existingFile.setPermissions(existingFile.permissions() | QFile::ReadOther | QFile::WriteOther);
  821. moveSuccessful = existingFile.rename(newFileName);
  822. }
  823. return moveSuccessful;
  824. }
  825. //////////////////////////////////////////////////////////////////////////
  826. bool CFileUtil::CreateDirectory(const char* directory)
  827. {
  828. QString path = directory;
  829. if (GetIEditor()->GetConsoleVar("ed_lowercasepaths"))
  830. {
  831. path = path.toLower();
  832. }
  833. return CheckAndCreateDirectory(path);
  834. }
  835. //////////////////////////////////////////////////////////////////////////
  836. void CFileUtil::BackupFile(const char* filename)
  837. {
  838. // Make a backup of previous file.
  839. bool makeBackup = true;
  840. QString bakFilename = Path::ReplaceExtension(filename, "bak");
  841. // Check if backup needed.
  842. QFile bak(filename);
  843. if (bak.open(QFile::ReadOnly))
  844. {
  845. if (bak.size() <= 0)
  846. {
  847. makeBackup = false;
  848. }
  849. }
  850. else
  851. {
  852. makeBackup = false;
  853. }
  854. bak.close();
  855. if (makeBackup)
  856. {
  857. QString bakFilename2 = Path::ReplaceExtension(bakFilename, "bak2");
  858. MoveFileReplaceExisting(bakFilename, bakFilename2);
  859. MoveFileReplaceExisting(filename, bakFilename);
  860. }
  861. }
  862. //////////////////////////////////////////////////////////////////////////
  863. void CFileUtil::BackupFileDated(const char* filename, bool bUseBackupSubDirectory /*=false*/)
  864. {
  865. bool makeBackup = true;
  866. {
  867. // Check if backup needed.
  868. QFile bak(filename);
  869. if (bak.open(QFile::ReadOnly))
  870. {
  871. if (bak.size() <= 0)
  872. {
  873. makeBackup = false;
  874. }
  875. }
  876. else
  877. {
  878. makeBackup = false;
  879. }
  880. }
  881. if (makeBackup)
  882. {
  883. // Generate new filename
  884. time_t ltime;
  885. time(&ltime);
  886. tm today;
  887. #if AZ_TRAIT_USE_SECURE_CRT_FUNCTIONS
  888. localtime_s(&today, &ltime);
  889. #else
  890. today = *localtime(&ltime);
  891. #endif
  892. char sTemp[128];
  893. strftime(sTemp, sizeof(sTemp), ".%Y%m%d.%H%M%S.", &today);
  894. QString bakFilename = Path::RemoveExtension(filename) + sTemp + Path::GetExt(filename);
  895. if (bUseBackupSubDirectory)
  896. {
  897. QString sBackupPath = Path::ToUnixPath(Path::GetPath(filename)) + QString("/backups");
  898. CFileUtil::CreateDirectory(sBackupPath.toUtf8().data());
  899. bakFilename = sBackupPath + QString("/") + Path::GetFile(bakFilename);
  900. }
  901. // Do the backup
  902. MoveFileReplaceExisting(filename, bakFilename);
  903. }
  904. }
  905. //////////////////////////////////////////////////////////////////////////
  906. bool CFileUtil::Deltree(const char* szFolder, [[maybe_unused]] bool bRecurse)
  907. {
  908. return QDir(szFolder).removeRecursively();
  909. }
  910. //////////////////////////////////////////////////////////////////////////
  911. bool CFileUtil::Exists(const QString& strPath, bool boDirectory, IFileUtil::FileDesc* pDesc)
  912. {
  913. auto pIPak = GetIEditor()->GetSystem()->GetIPak();
  914. bool boIsDirectory(false);
  915. AZ::IO::ArchiveFileIterator nFindHandle = pIPak->FindFirst(strPath.toUtf8().data());
  916. // If it found nothing, no matter if it is a file or directory, it was not found.
  917. if (!nFindHandle)
  918. {
  919. return false;
  920. }
  921. pIPak->FindClose(nFindHandle);
  922. if ((nFindHandle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory)
  923. {
  924. boIsDirectory = true;
  925. }
  926. else if (pDesc)
  927. {
  928. pDesc->filename = strPath;
  929. pDesc->attrib = static_cast<unsigned int>(nFindHandle.m_fileDesc.nAttrib);
  930. pDesc->size = nFindHandle.m_fileDesc.nSize;
  931. pDesc->time_access = nFindHandle.m_fileDesc.tAccess;
  932. pDesc->time_create = nFindHandle.m_fileDesc.tCreate;
  933. pDesc->time_write = nFindHandle.m_fileDesc.tWrite;
  934. }
  935. // If we are seeking directories...
  936. if (boDirectory)
  937. {
  938. // The return value will tell us if the found element is a directory.
  939. return boIsDirectory;
  940. }
  941. else
  942. {
  943. // If we are not seeking directories...
  944. // We return true if the found element is not a directory.
  945. return !boIsDirectory;
  946. }
  947. }
  948. //////////////////////////////////////////////////////////////////////////
  949. bool CFileUtil::FileExists(const QString& strFilePath, IFileUtil::FileDesc* pDesc)
  950. {
  951. return Exists(strFilePath, false, pDesc);
  952. }
  953. //////////////////////////////////////////////////////////////////////////
  954. bool CFileUtil::PathExists(const QString& strPath)
  955. {
  956. return Exists(strPath, true);
  957. }
  958. bool CFileUtil::GetDiskFileSize(const char* pFilePath, uint64& rOutSize)
  959. {
  960. const QFileInfo fi(pFilePath);
  961. rOutSize = fi.size();
  962. return fi.exists();
  963. }
  964. //////////////////////////////////////////////////////////////////////////
  965. bool CFileUtil::IsFileExclusivelyAccessable(const QString& strFilePath)
  966. {
  967. // this was simply trying to open the file before, so keep it like that
  968. QFile f(strFilePath);
  969. return f.open(QFile::ReadOnly);
  970. }
  971. //////////////////////////////////////////////////////////////////////////
  972. bool CFileUtil::CreatePath(const QString& strPath)
  973. {
  974. #if !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
  975. bool pathCreated = true;
  976. QString cleanPath = QDir::cleanPath(strPath);
  977. QDir path(cleanPath);
  978. if (!path.exists())
  979. {
  980. pathCreated = path.mkpath(cleanPath);
  981. }
  982. return pathCreated;
  983. #else
  984. QString strDriveLetter;
  985. QString strDirectory;
  986. QString strFilename;
  987. QString strExtension;
  988. QString strCurrentDirectoryPath;
  989. QStringList cstrDirectoryQueue;
  990. size_t nCurrentPathQueue(0);
  991. size_t nTotalPathQueueElements(0);
  992. bool bnLastDirectoryWasCreated(false);
  993. if (PathExists(strPath))
  994. {
  995. return true;
  996. }
  997. Path::SplitPath(strPath, strDriveLetter, strDirectory, strFilename, strExtension);
  998. Path::GetDirectoryQueue(strDirectory, cstrDirectoryQueue);
  999. if (!strDriveLetter.isEmpty())
  1000. {
  1001. strCurrentDirectoryPath = strDriveLetter;
  1002. strCurrentDirectoryPath += AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING;
  1003. }
  1004. nTotalPathQueueElements = cstrDirectoryQueue.size();
  1005. for (nCurrentPathQueue = 0; nCurrentPathQueue < nTotalPathQueueElements; ++nCurrentPathQueue)
  1006. {
  1007. strCurrentDirectoryPath += cstrDirectoryQueue[static_cast<int>(nCurrentPathQueue)];
  1008. strCurrentDirectoryPath += AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING;
  1009. // The value which will go out of this loop is the result of the attempt to create the
  1010. // last directory, only.
  1011. strCurrentDirectoryPath = Path::CaselessPaths(strCurrentDirectoryPath);
  1012. bnLastDirectoryWasCreated = QDir().mkpath(strCurrentDirectoryPath);
  1013. }
  1014. if (!bnLastDirectoryWasCreated)
  1015. {
  1016. if (!QDir(strCurrentDirectoryPath).exists())
  1017. {
  1018. return false;
  1019. }
  1020. }
  1021. return true;
  1022. #endif // !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
  1023. }
  1024. //////////////////////////////////////////////////////////////////////////
  1025. bool CFileUtil::DeleteFile(const QString& strPath)
  1026. {
  1027. QFile(strPath).setPermissions(QFile::ReadOther | QFile::WriteOther);
  1028. return QFile::remove(strPath);
  1029. }
  1030. //////////////////////////////////////////////////////////////////////////
  1031. bool CFileUtil::RemoveDirectory(const QString& strPath)
  1032. {
  1033. return Deltree(strPath.toUtf8().data(), true);
  1034. }
  1035. void CFileUtil::ForEach(const QString& path, std::function<void(const QString&)> predicate, bool recurse)
  1036. {
  1037. bool trailingSlash = path.endsWith('/') || path.endsWith('\\');
  1038. const QString dirName = trailingSlash ? path.left(path.length() - 1) : path;
  1039. QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags;
  1040. if (recurse)
  1041. {
  1042. flags = QDirIterator::Subdirectories;
  1043. }
  1044. QDirIterator dirIterator(path, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, flags);
  1045. while (dirIterator.hasNext())
  1046. {
  1047. dirIterator.next();
  1048. QString filePath = Path::ToUnixPath(dirIterator.filePath());
  1049. predicate(filePath);
  1050. }
  1051. }
  1052. //////////////////////////////////////////////////////////////////////////
  1053. IFileUtil::ECopyTreeResult CFileUtil::CopyTree(const QString& strSourceDirectory, const QString& strTargetDirectory, bool boRecurse, bool boConfirmOverwrite, const char* const ignoreFilesAndFolders)
  1054. {
  1055. static CUserOptions oFileOptions;
  1056. static CUserOptions oDirectoryOptions;
  1057. CUserOptions::CUserOptionsReferenceCountHelper oFileOptionsHelper(oFileOptions);
  1058. CUserOptions::CUserOptionsReferenceCountHelper oDirectoryOptionsHelper(oDirectoryOptions);
  1059. IFileUtil::ECopyTreeResult eCopyResult(IFileUtil::ETREECOPYOK);
  1060. QStringList cFiles;
  1061. QStringList cDirectories;
  1062. size_t nCurrent(0);
  1063. size_t nTotal(0);
  1064. // For this function to work properly, it has to first process all files in the directory AND JUST AFTER IT
  1065. // work on the sub-folders...this is NOT OBVIOUS, but imagine the case where you have a hierarchy of folders,
  1066. // all with the same names and all with the same files names inside. If you would make a depth-first search
  1067. // you could end up with the files from the deepest folder in ALL your folders.
  1068. std::vector<AZStd::string> ignoredPatterns;
  1069. StringHelpers::Split(ignoreFilesAndFolders, "|", false, ignoredPatterns);
  1070. QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags;
  1071. if (boRecurse)
  1072. {
  1073. flags = QDirIterator::Subdirectories;
  1074. }
  1075. QDir sourceDir(strSourceDirectory);
  1076. QDir targetDir(strTargetDirectory);
  1077. QDirIterator dirIterator(strSourceDirectory, {"*.*"}, QDir::Files, flags);
  1078. if (!dirIterator.hasNext())
  1079. {
  1080. return IFileUtil::ETREECOPYOK;
  1081. }
  1082. while (dirIterator.hasNext())
  1083. {
  1084. const QString filePath = dirIterator.next();
  1085. const QString fileName = QFileInfo(filePath).fileName();
  1086. bool ignored = false;
  1087. for (const AZStd::string& ignoredFile : ignoredPatterns)
  1088. {
  1089. if (StringHelpers::CompareIgnoreCase(fileName.toStdString().c_str(), ignoredFile.c_str()) == 0)
  1090. {
  1091. ignored = true;
  1092. break;
  1093. }
  1094. }
  1095. if (ignored)
  1096. {
  1097. continue;
  1098. }
  1099. QFileInfo fileInfo(filePath);
  1100. if (fileInfo.isDir())
  1101. {
  1102. if (boRecurse)
  1103. {
  1104. cDirectories.push_back(fileName);
  1105. }
  1106. }
  1107. else
  1108. {
  1109. cFiles.push_back(fileName);
  1110. }
  1111. }
  1112. // First we copy all files (maybe not all, depending on the user options...)
  1113. nTotal = cFiles.size();
  1114. for (nCurrent = 0; nCurrent < nTotal; ++nCurrent)
  1115. {
  1116. bool bnLastFileWasCopied(false);
  1117. if (eCopyResult == IFileUtil::ETREECOPYUSERCANCELED)
  1118. {
  1119. return eCopyResult;
  1120. }
  1121. QString sourceName = sourceDir.absoluteFilePath(cFiles[static_cast<int>(nCurrent)]);
  1122. QString targetName = targetDir.absoluteFilePath(cFiles[static_cast<int>(nCurrent)]);
  1123. if (boConfirmOverwrite)
  1124. {
  1125. if (QFileInfo::exists(targetName))
  1126. {
  1127. // If the directory already exists...
  1128. // we must warn our user about the possible actions.
  1129. int nUserOption(0);
  1130. if (boConfirmOverwrite)
  1131. {
  1132. // If the option is not valid to all folder, we must ask anyway again the user option.
  1133. if (!oFileOptions.IsOptionToAll())
  1134. {
  1135. const int ret = QMessageBox::question(AzToolsFramework::GetActiveWindow(),
  1136. QObject::tr("Confirm file overwrite?"),
  1137. QObject::tr("There is already a file named \"%1\" in the target folder. Do you want to move this file anyway replacing the old one?")
  1138. .arg(cFiles[static_cast<int>(nCurrent)]),
  1139. QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
  1140. switch (ret) {
  1141. case QMessageBox::YesToAll: /* fall-through */
  1142. case QMessageBox::Yes: nUserOption = IDYES; break;
  1143. case QMessageBox::No: nUserOption = IDNO; break;
  1144. case QMessageBox::Cancel: nUserOption = IDCANCEL; break;
  1145. }
  1146. oFileOptions.SetOption(nUserOption, ret == QMessageBox::YesToAll);
  1147. }
  1148. else
  1149. {
  1150. nUserOption = oFileOptions.GetOption();
  1151. }
  1152. }
  1153. switch (nUserOption)
  1154. {
  1155. case IDYES:
  1156. {
  1157. // Actually, we need to do nothing in this case.
  1158. }
  1159. break;
  1160. case IDNO:
  1161. {
  1162. eCopyResult = IFileUtil::ETREECOPYUSERDIDNTCOPYSOMEITEMS;
  1163. continue;
  1164. }
  1165. break;
  1166. // This IS ALWAYS for all... so it's easy to deal with.
  1167. case IDCANCEL:
  1168. {
  1169. return IFileUtil::ETREECOPYUSERCANCELED;
  1170. }
  1171. break;
  1172. }
  1173. }
  1174. }
  1175. bnLastFileWasCopied = QFile::copy(sourceName, targetName);
  1176. if (!bnLastFileWasCopied)
  1177. {
  1178. eCopyResult = IFileUtil::ETREECOPYFAIL;
  1179. }
  1180. }
  1181. // Now we can recurse into the directories, if needed.
  1182. nTotal = cDirectories.size();
  1183. for (nCurrent = 0; nCurrent < nTotal; ++nCurrent)
  1184. {
  1185. if (eCopyResult == IFileUtil::ETREECOPYUSERCANCELED)
  1186. {
  1187. return eCopyResult;
  1188. }
  1189. bool bnLastDirectoryWasCreated(false);
  1190. QString sourceName = sourceDir.absoluteFilePath(cDirectories[static_cast<int>(nCurrent)]);
  1191. QString targetName = targetDir.absoluteFilePath(cDirectories[static_cast<int>(nCurrent)]);
  1192. bnLastDirectoryWasCreated = QDir().mkpath(targetName);
  1193. if (!bnLastDirectoryWasCreated)
  1194. {
  1195. if (!QDir(targetName).exists())
  1196. {
  1197. return IFileUtil::ETREECOPYFAIL;
  1198. }
  1199. else
  1200. {
  1201. // If the directory already exists...
  1202. // we must warn our user about the possible actions.
  1203. int nUserOption(0);
  1204. if (boConfirmOverwrite)
  1205. {
  1206. // If the option is not valid to all folder, we must ask anyway again the user option.
  1207. if (!oDirectoryOptions.IsOptionToAll())
  1208. {
  1209. const int ret = QMessageBox::question(AzToolsFramework::GetActiveWindow(),
  1210. QObject::tr("Confirm directory overwrite?"),
  1211. QObject::tr("There is already a folder named \"%1\" in the target folder. Do you want to move this folder anyway?")
  1212. .arg(cDirectories[static_cast<int>(nCurrent)]),
  1213. QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
  1214. switch (ret) {
  1215. case QMessageBox::YesToAll: /* fall-through */
  1216. case QMessageBox::Yes: nUserOption = IDYES; break;
  1217. case QMessageBox::No: nUserOption = IDNO; break;
  1218. case QMessageBox::Cancel: nUserOption = IDCANCEL; break;
  1219. }
  1220. oDirectoryOptions.SetOption(nUserOption, ret == QMessageBox::YesToAll);
  1221. }
  1222. else
  1223. {
  1224. nUserOption = oDirectoryOptions.GetOption();
  1225. }
  1226. }
  1227. switch (nUserOption)
  1228. {
  1229. case IDYES:
  1230. {
  1231. // Actually, we need to do nothing in this case.
  1232. }
  1233. break;
  1234. case IDNO:
  1235. {
  1236. // If no, we just need to go to the next item.
  1237. eCopyResult = IFileUtil::ETREECOPYUSERDIDNTCOPYSOMEITEMS;
  1238. continue;
  1239. }
  1240. break;
  1241. // This IS ALWAYS for all... so it's easy to deal with.
  1242. case IDCANCEL:
  1243. {
  1244. return IFileUtil::ETREECOPYUSERCANCELED;
  1245. }
  1246. break;
  1247. }
  1248. }
  1249. }
  1250. eCopyResult = CopyTree(sourceName, targetName, boRecurse, boConfirmOverwrite, ignoreFilesAndFolders);
  1251. }
  1252. return eCopyResult;
  1253. }
  1254. //////////////////////////////////////////////////////////////////////////
  1255. IFileUtil::ECopyTreeResult CFileUtil::CopyFile(const QString& strSourceFile, const QString& strTargetFile, bool boConfirmOverwrite, ProgressRoutine pfnProgress, bool* pbCancel)
  1256. {
  1257. CUserOptions oFileOptions;
  1258. IFileUtil::ECopyTreeResult eCopyResult(IFileUtil::ETREECOPYOK);
  1259. bool bnLastFileWasCopied(false);
  1260. QString name(strSourceFile);
  1261. QString strQueryFilename;
  1262. QString strFullStargetName;
  1263. QString strTargetName(strTargetFile);
  1264. if (GetIEditor()->GetConsoleVar("ed_lowercasepaths"))
  1265. {
  1266. strTargetName = strTargetName.toLower();
  1267. }
  1268. QString strDriveLetter, strDirectory, strFilename, strExtension;
  1269. Path::SplitPath(strTargetName, strDriveLetter, strDirectory, strFilename, strExtension);
  1270. strFullStargetName = strDriveLetter;
  1271. strFullStargetName += strDirectory;
  1272. if (strFilename.isEmpty())
  1273. {
  1274. strFullStargetName += Path::GetFileName(strSourceFile);
  1275. strFullStargetName += ".";
  1276. strFullStargetName += Path::GetExt(strSourceFile);
  1277. }
  1278. else
  1279. {
  1280. strFullStargetName += strFilename;
  1281. strFullStargetName += strExtension;
  1282. }
  1283. if (boConfirmOverwrite)
  1284. {
  1285. if (QFileInfo::exists(strFullStargetName))
  1286. {
  1287. strQueryFilename = strFilename;
  1288. if (strFilename.isEmpty())
  1289. {
  1290. strQueryFilename = Path::GetFileName(strSourceFile);
  1291. strQueryFilename += ".";
  1292. strQueryFilename += Path::GetExt(strSourceFile);
  1293. }
  1294. else
  1295. {
  1296. strQueryFilename += strExtension;
  1297. }
  1298. // If the directory already exists...
  1299. // we must warn our user about the possible actions.
  1300. int nUserOption(0);
  1301. if (boConfirmOverwrite)
  1302. {
  1303. // If the option is not valid to all folder, we must ask anyway again the user option.
  1304. if (!oFileOptions.IsOptionToAll())
  1305. {
  1306. const int ret = QMessageBox::question(AzToolsFramework::GetActiveWindow(),
  1307. QObject::tr("Confirm file overwrite?"),
  1308. QObject::tr("There is already a file named \"%1\" in the target folder. Do you want to move this file anyway replacing the old one?")
  1309. .arg(strQueryFilename),
  1310. QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
  1311. switch (ret) {
  1312. case QMessageBox::Yes: nUserOption = IDYES; break;
  1313. case QMessageBox::No: nUserOption = IDNO; break;
  1314. case QMessageBox::Cancel: nUserOption = IDCANCEL; break;
  1315. }
  1316. oFileOptions.SetOption(nUserOption, false);
  1317. }
  1318. else
  1319. {
  1320. nUserOption = oFileOptions.GetOption();
  1321. }
  1322. }
  1323. switch (nUserOption)
  1324. {
  1325. case IDYES:
  1326. {
  1327. // Actually, we need to do nothing in this case.
  1328. }
  1329. break;
  1330. case IDNO:
  1331. {
  1332. return eCopyResult = IFileUtil::ETREECOPYUSERCANCELED;
  1333. }
  1334. break;
  1335. // This IS ALWAYS for all... so it's easy to deal with.
  1336. case IDCANCEL:
  1337. {
  1338. return IFileUtil::ETREECOPYUSERCANCELED;
  1339. }
  1340. break;
  1341. }
  1342. }
  1343. }
  1344. bnLastFileWasCopied = false;
  1345. QFile source(name);
  1346. if (source.open(QFile::ReadOnly))
  1347. {
  1348. QFile out(strFullStargetName);
  1349. if (out.open(QFile::ReadWrite))
  1350. {
  1351. bnLastFileWasCopied = true;
  1352. char block[4096];
  1353. qint64 totalRead = 0;
  1354. while (!source.atEnd())
  1355. {
  1356. qint64 in = source.read(block, sizeof(block));
  1357. if (in <= 0)
  1358. {
  1359. break;
  1360. }
  1361. totalRead += in;
  1362. if (in != out.write(block, in))
  1363. {
  1364. bnLastFileWasCopied = false;
  1365. break;
  1366. }
  1367. if (pbCancel && *pbCancel == true)
  1368. {
  1369. bnLastFileWasCopied = false;
  1370. break;
  1371. }
  1372. if (pfnProgress)
  1373. {
  1374. pfnProgress(source.size(), totalRead, 0, 0, 0, 0, nullptr, nullptr, nullptr);
  1375. }
  1376. }
  1377. if (totalRead != source.size())
  1378. {
  1379. bnLastFileWasCopied = false;
  1380. }
  1381. }
  1382. }
  1383. if (!bnLastFileWasCopied)
  1384. {
  1385. eCopyResult = IFileUtil::ETREECOPYFAIL;
  1386. }
  1387. return eCopyResult;
  1388. }
  1389. //////////////////////////////////////////////////////////////////////////
  1390. IFileUtil::ECopyTreeResult CFileUtil::MoveTree(const QString& strSourceDirectory, const QString& strTargetDirectory, bool boRecurse, bool boConfirmOverwrite)
  1391. {
  1392. static CUserOptions oFileOptions;
  1393. static CUserOptions oDirectoryOptions;
  1394. CUserOptions::CUserOptionsReferenceCountHelper oFileOptionsHelper(oFileOptions);
  1395. CUserOptions::CUserOptionsReferenceCountHelper oDirectoryOptionsHelper(oDirectoryOptions);
  1396. IFileUtil::ECopyTreeResult eCopyResult(IFileUtil::ETREECOPYOK);
  1397. QStringList cFiles;
  1398. QStringList cDirectories;
  1399. size_t nCurrent(0);
  1400. size_t nTotal(0);
  1401. // For this function to work properly, it has to first process all files in the directory AND JUST AFTER IT
  1402. // work on the sub-folders...this is NOT OBVIOUS, but imagine the case where you have a hierarchy of folders,
  1403. // all with the same names and all with the same files names inside. If you would make a depth-first search
  1404. // you could end up with the files from the deepest folder in ALL your folders.
  1405. QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags;
  1406. if (boRecurse)
  1407. {
  1408. flags = QDirIterator::Subdirectories;
  1409. }
  1410. QDirIterator dirIterator(strSourceDirectory, {"*.*"}, QDir::Files, flags);
  1411. if (!dirIterator.hasNext())
  1412. {
  1413. return IFileUtil::ETREECOPYOK;
  1414. }
  1415. QDir sourceDir(strSourceDirectory);
  1416. QDir targetDir(strTargetDirectory);
  1417. while (dirIterator.hasNext())
  1418. {
  1419. const QString filePath = dirIterator.next();
  1420. const QString fileName = QFileInfo(filePath).fileName();
  1421. QFileInfo fileInfo(filePath);
  1422. if (fileInfo.isDir())
  1423. {
  1424. if (boRecurse)
  1425. {
  1426. cDirectories.push_back(fileName);
  1427. }
  1428. }
  1429. else
  1430. {
  1431. cFiles.push_back(fileName);
  1432. }
  1433. }
  1434. // First we copy all files (maybe not all, depending on the user options...)
  1435. nTotal = cFiles.size();
  1436. for (nCurrent = 0; nCurrent < nTotal; ++nCurrent)
  1437. {
  1438. if (eCopyResult == IFileUtil::ETREECOPYUSERCANCELED)
  1439. {
  1440. return eCopyResult;
  1441. }
  1442. bool bnLastFileWasCopied(false);
  1443. QString sourceName(sourceDir.absoluteFilePath(cFiles[static_cast<int>(nCurrent)]));
  1444. QString targetName(targetDir.absoluteFilePath(cFiles[static_cast<int>(nCurrent)]));
  1445. if (boConfirmOverwrite)
  1446. {
  1447. if (QFileInfo::exists(targetName))
  1448. {
  1449. // If the directory already exists...
  1450. // we must warn our user about the possible actions.
  1451. int nUserOption(0);
  1452. if (boConfirmOverwrite)
  1453. {
  1454. // If the option is not valid to all folder, we must ask anyway again the user option.
  1455. if (!oFileOptions.IsOptionToAll())
  1456. {
  1457. const int ret = QMessageBox::question(AzToolsFramework::GetActiveWindow(),
  1458. QObject::tr("Confirm file overwrite?"),
  1459. QObject::tr("There is already a file named \"%1\" in the target folder. Do you want to move this file anyway replacing the old one?")
  1460. .arg(cFiles[static_cast<int>(nCurrent)]),
  1461. QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
  1462. switch (ret) {
  1463. case QMessageBox::YesToAll: /* fall-through */
  1464. case QMessageBox::Yes: nUserOption = IDYES; break;
  1465. case QMessageBox::No: nUserOption = IDNO; break;
  1466. case QMessageBox::Cancel: nUserOption = IDCANCEL; break;
  1467. }
  1468. oFileOptions.SetOption(nUserOption, ret == QMessageBox::YesToAll);
  1469. }
  1470. else
  1471. {
  1472. nUserOption = oFileOptions.GetOption();
  1473. }
  1474. }
  1475. switch (nUserOption)
  1476. {
  1477. case IDYES:
  1478. {
  1479. // Actually, we need to do nothing in this case.
  1480. }
  1481. break;
  1482. case IDNO:
  1483. {
  1484. eCopyResult = IFileUtil::ETREECOPYUSERDIDNTCOPYSOMEITEMS;
  1485. continue;
  1486. }
  1487. break;
  1488. // This IS ALWAYS for all... so it's easy to deal with.
  1489. case IDCANCEL:
  1490. {
  1491. return IFileUtil::ETREECOPYUSERCANCELED;
  1492. }
  1493. break;
  1494. }
  1495. }
  1496. }
  1497. bnLastFileWasCopied = MoveFileReplaceExisting(sourceName, targetName);
  1498. if (!bnLastFileWasCopied)
  1499. {
  1500. eCopyResult = IFileUtil::ETREECOPYFAIL;
  1501. }
  1502. }
  1503. // Now we can recurse into the directories, if needed.
  1504. nTotal = cDirectories.size();
  1505. for (nCurrent = 0; nCurrent < nTotal; ++nCurrent)
  1506. {
  1507. bool bnLastDirectoryWasCreated(false);
  1508. if (eCopyResult == IFileUtil::ETREECOPYUSERCANCELED)
  1509. {
  1510. return eCopyResult;
  1511. }
  1512. QString sourceName(sourceDir.absoluteFilePath(cDirectories[static_cast<int>(nCurrent)]));
  1513. QString targetName(targetDir.absoluteFilePath(cDirectories[static_cast<int>(nCurrent)]));
  1514. bnLastDirectoryWasCreated = QDir().mkdir(targetName);
  1515. if (!bnLastDirectoryWasCreated)
  1516. {
  1517. if (!QDir(targetName).exists())
  1518. {
  1519. return IFileUtil::ETREECOPYFAIL;
  1520. }
  1521. else
  1522. {
  1523. // If the directory already exists...
  1524. // we must warn our user about the possible actions.
  1525. int nUserOption(0);
  1526. if (boConfirmOverwrite)
  1527. {
  1528. // If the option is not valid to all folder, we must ask anyway again the user option.
  1529. if (!oDirectoryOptions.IsOptionToAll())
  1530. {
  1531. const int ret = QMessageBox::question(AzToolsFramework::GetActiveWindow(),
  1532. QObject::tr("Confirm directory overwrite?"),
  1533. QObject::tr("There is already a folder named \"%1\" in the target folder. Do you want to move this folder anyway?")
  1534. .arg(cDirectories[static_cast<int>(nCurrent)]),
  1535. QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
  1536. switch (ret) {
  1537. case QMessageBox::YesToAll: /* fall-through */
  1538. case QMessageBox::Yes: nUserOption = IDYES; break;
  1539. case QMessageBox::No: nUserOption = IDNO; break;
  1540. case QMessageBox::Cancel: nUserOption = IDCANCEL; break;
  1541. }
  1542. oDirectoryOptions.SetOption(nUserOption, ret == QMessageBox::YesToAll);
  1543. }
  1544. else
  1545. {
  1546. nUserOption = oDirectoryOptions.GetOption();
  1547. }
  1548. }
  1549. switch (nUserOption)
  1550. {
  1551. case IDYES:
  1552. {
  1553. // Actually, we need to do nothing in this case.
  1554. }
  1555. break;
  1556. case IDNO:
  1557. {
  1558. // If no, we just need to go to the next item.
  1559. eCopyResult = IFileUtil::ETREECOPYUSERDIDNTCOPYSOMEITEMS;
  1560. continue;
  1561. }
  1562. break;
  1563. // This IS ALWAYS for all... so it's easy to deal with.
  1564. case IDCANCEL:
  1565. {
  1566. return IFileUtil::ETREECOPYUSERCANCELED;
  1567. }
  1568. break;
  1569. }
  1570. }
  1571. }
  1572. eCopyResult = MoveTree(sourceName, targetName, boRecurse, boConfirmOverwrite);
  1573. }
  1574. CFileUtil::RemoveDirectory(strSourceDirectory);
  1575. return eCopyResult;
  1576. }
  1577. void CFileUtil::PopulateQMenu(QWidget* caller, QMenu* menu, AZStd::string_view fullGamePath)
  1578. {
  1579. PopulateQMenu(caller, menu, fullGamePath, nullptr);
  1580. }
  1581. void CFileUtil::PopulateQMenu(QWidget* caller, QMenu* menu, AZStd::string_view fullGamePath, bool* isSelected)
  1582. {
  1583. using namespace AzToolsFramework;
  1584. // Normalize the full path so we get consistent separators
  1585. AZStd::string fullFilePath(fullGamePath);
  1586. AzFramework::StringFunc::Path::Normalize(fullFilePath);
  1587. QString fullPath(fullFilePath.c_str());
  1588. QFileInfo fileInfo(fullPath);
  1589. if (isSelected)
  1590. {
  1591. *isSelected = false;
  1592. }
  1593. uint32 nFileAttr = CFileUtil::GetAttributes(fullPath.toUtf8().data());
  1594. QAction* action;
  1595. // NOTE: isSelected being passed in implies that the menu filled from this call must have exec() called on it, and not show.
  1596. if (isSelected)
  1597. {
  1598. action = new QAction(QObject::tr("Select"), nullptr);
  1599. QObject::connect(action, &QAction::triggered, action, [isSelected]() { *isSelected = true; });
  1600. if (menu->isEmpty())
  1601. {
  1602. menu->addAction(action);
  1603. }
  1604. else
  1605. {
  1606. menu->insertAction(menu->actions()[0], action);
  1607. }
  1608. }
  1609. action = menu->addAction(AzQtComponents::fileBrowserActionName(), [=]()
  1610. {
  1611. if (nFileAttr & SCC_FILE_ATTRIBUTE_INPAK)
  1612. {
  1613. QString path = QDir::toNativeSeparators(Path::GetPath(fullPath));
  1614. QDesktopServices::openUrl(QUrl::fromLocalFile(path));
  1615. }
  1616. else
  1617. {
  1618. AzQtComponents::ShowFileOnDesktop(fullPath);
  1619. }
  1620. });
  1621. action->setDisabled(nFileAttr & SCC_FILE_ATTRIBUTE_INPAK);
  1622. action = menu->addAction(QObject::tr("Copy Name To Clipboard"), [=]()
  1623. {
  1624. QString fileName = fileInfo.completeBaseName();
  1625. QApplication::clipboard()->setText(fileName);
  1626. });
  1627. action = menu->addAction(QObject::tr("Copy Path To Clipboard"), [fullPath]() { QApplication::clipboard()->setText(fullPath); });
  1628. AzToolsFramework::SourceControlState sourceControlState = AzToolsFramework::SourceControlState::Disabled;
  1629. AzToolsFramework::SourceControlConnectionRequestBus::BroadcastResult(sourceControlState, &AzToolsFramework::SourceControlConnectionRequests::GetSourceControlState);
  1630. if (fileInfo.isFile() && sourceControlState == AzToolsFramework::SourceControlState::Active && nFileAttr != SCC_FILE_ATTRIBUTE_INVALID)
  1631. {
  1632. bool isEnableSC = nFileAttr & SCC_FILE_ATTRIBUTE_MANAGED;
  1633. bool isInPak = nFileAttr & SCC_FILE_ATTRIBUTE_INPAK;
  1634. menu->addSeparator();
  1635. if (isInPak && !isEnableSC)
  1636. {
  1637. menu->addAction(QObject::tr("File In Pak (Read Only)"));
  1638. menu->setDisabled(true);
  1639. }
  1640. else
  1641. {
  1642. action = menu->addAction(QObject::tr("Check Out"), [fullPath, caller]()
  1643. {
  1644. if (!CheckoutFile(fullPath.toUtf8().data(), caller))
  1645. {
  1646. QMessageBox::warning(caller, QObject::tr("Error"),
  1647. QObject::tr("Source Control Check Out Failed.\r\nCheck if Source Control Provider is correctly setup and working directory is correct."));
  1648. }
  1649. });
  1650. action->setEnabled(isEnableSC && !isInPak && (nFileAttr & SCC_FILE_ATTRIBUTE_READONLY));
  1651. action = menu->addAction(QObject::tr("Undo Check Out"), [fullPath, caller]()
  1652. {
  1653. if (!RevertFile(fullPath.toUtf8().data(), caller))
  1654. {
  1655. QMessageBox::warning(caller, QObject::tr("Error"),
  1656. QObject::tr("Source Control Undo Check Out Failed.\r\nCheck if Source Control Provider is correctly setup and working directory is correct."));
  1657. }
  1658. });
  1659. action->setEnabled(isEnableSC && !isInPak && (nFileAttr & SCC_FILE_ATTRIBUTE_CHECKEDOUT));
  1660. action = menu->addAction(QObject::tr("Get Latest Version"), [fullPath, caller]()
  1661. {
  1662. AzToolsFramework::SourceControlState sourceControlState = AzToolsFramework::SourceControlState::Disabled;
  1663. AzToolsFramework::SourceControlConnectionRequestBus::BroadcastResult(sourceControlState, &AzToolsFramework::SourceControlConnectionRequests::GetSourceControlState);
  1664. if (sourceControlState == AzToolsFramework::SourceControlState::Active)
  1665. {
  1666. if (!CFileUtil::GetLatestFromSourceControl(fullPath.toUtf8().data(), caller))
  1667. {
  1668. QMessageBox::warning(caller, QObject::tr("Error"),
  1669. QObject::tr("Source Control failed to get latest version of file.\r\nCheck if Source Control Provider is setup correctly."
  1670. "\r\n\r\nAdditionally, this operation will fail on files that have local changes\r\nthat are not currently checked out, in order to prevent data loss."
  1671. "\r\nIn this case, please reconcile offline work directly from Source Control Provider."));
  1672. }
  1673. }
  1674. });
  1675. action->setEnabled(isEnableSC);
  1676. action = menu->addAction(QObject::tr("Add To Source Control"), [fullPath, caller]()
  1677. {
  1678. if (!CheckoutFile(fullPath.toUtf8().data(), caller))
  1679. {
  1680. QMessageBox::warning(caller, QObject::tr("Error"),
  1681. QObject::tr("Source Control Add Failed.\r\nCheck if Source Control Provider is correctly setup and working directory is correct."));
  1682. }
  1683. });
  1684. action->setDisabled(isEnableSC);
  1685. }
  1686. }
  1687. }
  1688. uint32 CFileUtil::GetAttributes(const char* filename, bool bUseSourceControl /*= true*/)
  1689. {
  1690. using namespace AzToolsFramework;
  1691. bool scOpSuccess = false;
  1692. SourceControlFileInfo fileInfo;
  1693. if (bUseSourceControl)
  1694. {
  1695. using SCRequest = SourceControlConnectionRequestBus;
  1696. SourceControlState state = SourceControlState::Disabled;
  1697. SCRequest::BroadcastResult(state, &SCRequest::Events::GetSourceControlState);
  1698. if (state == SourceControlState::ConfigurationInvalid)
  1699. {
  1700. return SCC_FILE_ATTRIBUTE_INVALID;
  1701. }
  1702. if (state == SourceControlState::Active)
  1703. {
  1704. using SCCommand = SourceControlCommandBus;
  1705. bool scOpComplete = false;
  1706. SCCommand::Broadcast(&SCCommand::Events::GetFileInfo, filename,
  1707. [&fileInfo, &scOpSuccess, &scOpComplete](bool success, const SourceControlFileInfo& info)
  1708. {
  1709. fileInfo = info;
  1710. scOpSuccess = success;
  1711. scOpComplete = true;
  1712. }
  1713. );
  1714. BlockAndWait(scOpComplete, nullptr, "Getting file status...");
  1715. // we intended to use source control, but the operation failed.
  1716. // do not fall through to checking as if bUseSourceControl was false
  1717. if (!scOpSuccess)
  1718. {
  1719. return SCC_FILE_ATTRIBUTE_INVALID;
  1720. }
  1721. }
  1722. }
  1723. CCryFile file;
  1724. bool isCryFile = file.Open(filename, "rb");
  1725. // Using source control and our fstat succeeded.
  1726. // Translate SourceControlStatus to (legacy) ESccFileAttributes
  1727. if (scOpSuccess)
  1728. {
  1729. uint32 sccFileAttr = AZ::IO::SystemFile::Exists(filename) ? SCC_FILE_ATTRIBUTE_NORMAL : SCC_FILE_ATTRIBUTE_INVALID;
  1730. if (fileInfo.HasFlag(SourceControlFlags::SCF_Tracked))
  1731. {
  1732. sccFileAttr |= SCC_FILE_ATTRIBUTE_MANAGED;
  1733. }
  1734. if (fileInfo.HasFlag(SourceControlFlags::SCF_OpenByUser))
  1735. {
  1736. sccFileAttr |= SCC_FILE_ATTRIBUTE_MANAGED | SCC_FILE_ATTRIBUTE_CHECKEDOUT;
  1737. }
  1738. if ((sccFileAttr & SCC_FILE_ATTRIBUTE_MANAGED) == SCC_FILE_ATTRIBUTE_MANAGED)
  1739. {
  1740. if (fileInfo.HasFlag(SourceControlFlags::SCF_OutOfDate))
  1741. {
  1742. sccFileAttr |= SCC_FILE_ATTRIBUTE_NOTATHEAD;
  1743. }
  1744. if (fileInfo.HasFlag(SourceControlFlags::SCF_OtherOpen))
  1745. {
  1746. sccFileAttr |= SCC_FILE_ATTRIBUTE_CHECKEDOUT | SCC_FILE_ATTRIBUTE_BYANOTHER;
  1747. }
  1748. if (fileInfo.HasFlag(SourceControlFlags::SCF_PendingAdd))
  1749. {
  1750. sccFileAttr |= SCC_FILE_ATTRIBUTE_ADD;
  1751. }
  1752. }
  1753. if (fileInfo.IsReadOnly())
  1754. {
  1755. sccFileAttr |= SCC_FILE_ATTRIBUTE_READONLY;
  1756. }
  1757. if (file.IsInPak())
  1758. {
  1759. sccFileAttr |= SCC_FILE_ATTRIBUTE_READONLY | SCC_FILE_ATTRIBUTE_INPAK;
  1760. }
  1761. return sccFileAttr;
  1762. }
  1763. // We've asked not to use source control OR we disabled source control
  1764. if (!isCryFile)
  1765. {
  1766. return SCC_FILE_ATTRIBUTE_INVALID;
  1767. }
  1768. if (file.IsInPak())
  1769. {
  1770. return SCC_FILE_ATTRIBUTE_READONLY | SCC_FILE_ATTRIBUTE_INPAK;
  1771. }
  1772. auto fileIoBase = AZ::IO::FileIOBase::GetInstance();
  1773. if (!fileIoBase->Exists(file.GetFilename()))
  1774. {
  1775. return SCC_FILE_ATTRIBUTE_INVALID;
  1776. }
  1777. if (fileIoBase->IsReadOnly(file.GetFilename()))
  1778. {
  1779. return SCC_FILE_ATTRIBUTE_NORMAL | SCC_FILE_ATTRIBUTE_READONLY;
  1780. }
  1781. return SCC_FILE_ATTRIBUTE_NORMAL;
  1782. }
  1783. bool CFileUtil::CompareFiles(const QString& strFilePath1, const QString& strFilePath2)
  1784. {
  1785. // Get the size of both files. If either fails we say they are different (most likely one doesn't exist)
  1786. uint64 size1 = 0;
  1787. uint64 size2 = 0;
  1788. if (!GetDiskFileSize(strFilePath1.toUtf8().data(), size1) || !GetDiskFileSize(strFilePath2.toUtf8().data(), size2))
  1789. {
  1790. return false;
  1791. }
  1792. // If the files are different sizes return false
  1793. if (size1 != size2)
  1794. {
  1795. return false;
  1796. }
  1797. // Sizes are the same, we need to compare the bytes. Try to open both files for read.
  1798. CCryFile file1, file2;
  1799. if (!file1.Open(strFilePath1.toUtf8().data(), "rb") || !file2.Open(strFilePath2.toUtf8().data(), "rb"))
  1800. {
  1801. return false;
  1802. }
  1803. const uint64 bufSize = 4096;
  1804. char buf1[bufSize], buf2[bufSize];
  1805. for (uint64 i = 0; i < size1; i += bufSize)
  1806. {
  1807. size_t amtRead1 = file1.ReadRaw(buf1, bufSize);
  1808. size_t amtRead2 = file2.ReadRaw(buf2, bufSize);
  1809. // Not a match if we didn't read the same amount from each file
  1810. if (amtRead1 != amtRead2)
  1811. {
  1812. return false;
  1813. }
  1814. // Not a match if we didn't read the amount of data we expected
  1815. if (amtRead1 != bufSize && i + amtRead1 != size1)
  1816. {
  1817. return false;
  1818. }
  1819. // Not a match if the buffers aren't the same
  1820. if (memcmp(buf1, buf2, amtRead1) != 0)
  1821. {
  1822. return false;
  1823. }
  1824. }
  1825. return true;
  1826. }
  1827. bool CFileUtil::SortAscendingFileNames(const IFileUtil::FileDesc& desc1, const IFileUtil::FileDesc& desc2)
  1828. {
  1829. return desc1.filename.compare(desc2.filename) == -1 ? true : false;
  1830. }
  1831. bool CFileUtil::SortDescendingFileNames(const IFileUtil::FileDesc& desc1, const IFileUtil::FileDesc& desc2)
  1832. {
  1833. return desc1.filename.compare(desc2.filename) == 1 ? true : false;
  1834. }
  1835. bool CFileUtil::SortAscendingDates(const IFileUtil::FileDesc& desc1, const IFileUtil::FileDesc& desc2)
  1836. {
  1837. return desc1.time_write < desc2.time_write;
  1838. }
  1839. bool CFileUtil::SortDescendingDates(const IFileUtil::FileDesc& desc1, const IFileUtil::FileDesc& desc2)
  1840. {
  1841. return desc1.time_write > desc2.time_write;
  1842. }
  1843. bool CFileUtil::SortAscendingSizes(const IFileUtil::FileDesc& desc1, const IFileUtil::FileDesc& desc2)
  1844. {
  1845. return desc1.size > desc2.size;
  1846. }
  1847. bool CFileUtil::SortDescendingSizes(const IFileUtil::FileDesc& desc1, const IFileUtil::FileDesc& desc2)
  1848. {
  1849. return desc1.size < desc2.size;
  1850. }
  1851. bool CFileUtil::IsAbsPath(const QString& filepath)
  1852. {
  1853. return (!filepath.isEmpty() && ((filepath[1] == ':' && (filepath[2] == '\\' || filepath[2] == '/')
  1854. || (filepath[0] == '\\' || filepath[0] == '/'))));
  1855. }
  1856. CTempFileHelper::CTempFileHelper(const char* pFileName)
  1857. {
  1858. char resolvedPath[AZ_MAX_PATH_LEN] = { 0 };
  1859. AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(pFileName, resolvedPath, AZ_MAX_PATH_LEN);
  1860. m_fileName = QString::fromUtf8(resolvedPath);
  1861. // the official pattern for temp files in the editor is /$tmp[0-9]*_"
  1862. // so we'll follow this pattern to make sure its ignored by asset processor.
  1863. // the _h_ is added to be unique (helper) in case someone else is also making temp files.
  1864. Path::ReplaceFilename(m_fileName, "$tmp_h_" + Path::GetFileName(QString::fromUtf8(pFileName)), m_tempFileName);
  1865. CFileUtil::DeleteFile(m_tempFileName);
  1866. }
  1867. CTempFileHelper::~CTempFileHelper()
  1868. {
  1869. CFileUtil::DeleteFile(m_tempFileName);
  1870. }
  1871. bool CTempFileHelper::UpdateFile(bool bBackup)
  1872. {
  1873. // First, check if the files are actually different
  1874. if (!CFileUtil::CompareFiles(m_tempFileName, m_fileName))
  1875. {
  1876. // If the file changed, make sure the destination file is writable
  1877. if (!CFileUtil::OverwriteFile(m_fileName))
  1878. {
  1879. CFileUtil::DeleteFile(m_tempFileName);
  1880. return false;
  1881. }
  1882. // Back up the current file if requested
  1883. if (bBackup)
  1884. {
  1885. CFileUtil::BackupFile(m_fileName.toUtf8().data());
  1886. }
  1887. // Move the temp file over the top of the destination file
  1888. return MoveFileReplaceExisting(m_tempFileName, m_fileName);
  1889. }
  1890. // If the files are the same, just delete the temp file and return.
  1891. else
  1892. {
  1893. CFileUtil::DeleteFile(m_tempFileName);
  1894. return true;
  1895. }
  1896. }