Editor Update.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. /******************************************************************************/
  4. UpdaterClass Updater;
  5. /******************************************************************************/
  6. UpdateWindowClass UpdateWindow;
  7. /******************************************************************************/
  8. bool UpdateDo()
  9. {
  10. Physics.del(); // del physics and unload DLL's
  11. bool inside=StartsPath(App.exe(), Updater.path);
  12. Str old=App.exe(); // remember old app name
  13. #if WINDOWS
  14. // move exe to temporary file so 'FMoveDir' will succeed for the exe as well
  15. if(inside)
  16. {
  17. Str rel=SkipStartPath(App.exe(), Updater.path);
  18. if(FExistSystem(Updater.update_path+rel)) // if there exists a replacement for that file
  19. {
  20. if(!App.renameSelf(Updater.path+"Esenthel Old."+GetExt(old))){Gui.msgBox(S, "Can't update self"); if(InstallerMode)StateInstall.set(StateFadeTime);else StateProjectList.set(StateFadeTime); return true;}
  21. App.deleteSelfAtExit(); // delete the "Esenthel Old" file
  22. }
  23. }
  24. #endif
  25. // remove those that are not wanted (do this first, before replacing files)
  26. FREPA(Updater.local_remove) // go from start to recycle folders first
  27. {
  28. Str full=Updater.path+Updater.local_files[Updater.local_remove[i]].full_name;
  29. #if WINDOWS
  30. if(EqualPath(full, App.exe())){App.deleteSelfAtExit(); continue;} // on Windows we can't recycle self
  31. #endif
  32. FRecycle(full);
  33. }
  34. // replace all files
  35. FMoveDir(Updater.update_path, Updater.path);
  36. if(inside && FExistSystem(old))RunAtExit=old;
  37. if(InstallerMode)
  38. {
  39. Str ext;
  40. #if WINDOWS
  41. ext=".exe";
  42. #elif MAC
  43. ext=".app";
  44. #elif LINUX
  45. ext="";
  46. #else
  47. #error unknown platform
  48. #endif
  49. CreateShortcut(Updater.path+"Esenthel"+ext, SystemPath(SP_DESKTOP ).tailSlash(true)+"Esenthel");
  50. CreateShortcut(Updater.path+"Esenthel"+ext, SystemPath(SP_MENU_PROG).tailSlash(true)+"Esenthel");
  51. }
  52. // finish
  53. if(RunAtExit.is())
  54. {
  55. // if we run app on exit, then no messages need to be displayed
  56. }else // we won't run any app
  57. {
  58. Explore(Updater.path); // open folder so user can see what's happened there
  59. WindowHide(); // hide window because 'WindowMsgBox' causes a pause
  60. /*if(inside)*/WindowMsgBox("Success", InstallerMode ? "Esenthel has installed successfully." : "Esenthel has updated successfully."); // show confirmation using OS msg box
  61. //else Gui.msgBox("Success", "Esenthel has updated properly.");
  62. }
  63. /*if(inside)*/return false; // close the application
  64. //else StateInstall.set(StateFadeTime); // if we're not inside then it means we're an installer
  65. }
  66. /******************************************************************************/
  67. State StateUpdate(UpdateUpdate, DrawUpdate, InitUpdate, ShutUpdate);
  68. Str UpdateMessage;
  69. /******************************************************************************/
  70. bool InitUpdate()
  71. {
  72. WindowSetWorking();
  73. SetKbExclusive();
  74. Proj.close();
  75. // auto close all processes in path folder except this one - App.exe
  76. Memc<uint> proc; ProcList(proc);
  77. REPA(proc)if(proc[i]!=App.processID())
  78. {
  79. Str proc_name=ProcName(proc[i]);
  80. if(StartsPath(proc_name, Updater.path))ProcClose(proc_name);
  81. }
  82. UpdateMessage.clear();
  83. return true;
  84. }
  85. void ShutUpdate()
  86. {
  87. }
  88. bool UpdateUpdate()
  89. {
  90. if(Kb.bp(KB_ESC) || Kb.bp(KB_NAV_BACK))
  91. {
  92. if(!InstallerMode)StateProjectList.set(StateFadeTime);
  93. }
  94. Time.wait(1000/30);
  95. Gui.update();
  96. Server.update(null, true);
  97. if(Ms.bp(3))WindowToggle();
  98. UpdateMessage.clear();
  99. Memc<uint> proc; ProcList(proc);
  100. REPA(proc)if(proc[i]!=App.processID())
  101. {
  102. Str proc_name=ProcName(proc[i]);
  103. if(StartsPath(proc_name, Updater.path))
  104. {
  105. if(!UpdateMessage.is())UpdateMessage+="Waiting for applications to exit:";
  106. UpdateMessage.line()+=proc_name;
  107. }
  108. }
  109. if(!UpdateMessage.is()) // if there are no active apps then perform update
  110. return UpdateDo();
  111. return true;
  112. }
  113. void DrawUpdate()
  114. {
  115. D.clear(BackgroundColor());
  116. Gui.draw();
  117. D.text(Rect(-D.w(), -D.h(), D.w(), D.h()), UpdateMessage);
  118. }
  119. /******************************************************************************/
  120. State StateInstall(UpdateInstall, DrawInstall, InitInstall, ShutInstall);
  121. WindowIO InstallIO;
  122. bool InstallerMode=false;
  123. /******************************************************************************/
  124. Str InstallPath()
  125. {
  126. Str path=InstallIO.path(); path.tailSlash(true)+=InstallIO.subPath();
  127. if(InstallIO.textline().is())
  128. {
  129. if(FullPath(InstallIO.textline()))path=InstallIO.textline();else path.tailSlash(true)+=InstallIO.textline();
  130. path=Replace(NormalizePath(path), '/', '\\');
  131. }
  132. if(path.tailSlash(false).is())
  133. {
  134. Str end=GetBase(path); if(!Contains(end, "Esenthel"))path.tailSlash(true)+="Esenthel";
  135. }
  136. return path;
  137. }
  138. void SelectInstall(C Str &path, ptr)
  139. {
  140. if(!InstallPath().is())InstallIO.activate();else Updater.create(); // reactivate if user selected empty/invalid path
  141. }
  142. void ResizeInstall()
  143. {
  144. flt w=Min(0.6f, D.w());
  145. InstallIO.rect(Rect(-w, -D.h(), w, D.h()-0.17f));
  146. }
  147. /******************************************************************************/
  148. bool InitInstall()
  149. {
  150. SetKbExclusive();
  151. if(!InstallIO.is())
  152. {
  153. InstallIO.create(S, S, S, SelectInstall, SelectInstall).modeDirSelect(); FlagDisable(InstallIO.flag, WIN_MOVABLE|WIN_RESIZABLE);
  154. InstallIO.button[2].func(MiscRegion::Quit, Misc).hide();
  155. InstallIO.cancel .func(MiscRegion::Quit, Misc);
  156. InstallIO.activate();
  157. ResizeInstall();
  158. }
  159. Gui+=UpdateProgress.create(Rect_C(0, -0.05f, 1, 0.045f));
  160. Gui+=InstallIO;
  161. return true;
  162. }
  163. void ShutInstall()
  164. {
  165. UpdateProgress.del();
  166. Gui-=InstallIO;
  167. }
  168. bool UpdateInstall()
  169. {
  170. if(!App.active())Time.wait(1000/30);else if(!D.sync())Time.wait(1000/100);
  171. Gui.update();
  172. if(!InstallIO.visible() && !Updater.updating())
  173. {
  174. if(Updater.ready) // success
  175. {
  176. StateUpdate.set(StateFadeTime);
  177. }else // failed
  178. {
  179. InstallIO.activate();
  180. WindowSetError(Updater.progress());
  181. }
  182. }
  183. if(!InstallIO.visible())WindowSetProgress(Updater.progress());
  184. UpdateProgress.set(Updater.progress());
  185. if(Ms.bp(3))WindowToggle();
  186. return true;
  187. }
  188. void DrawInstall()
  189. {
  190. D.clear(BackgroundColor());
  191. TextStyleParams ts;
  192. ts.size=0.055f; D.text(ts, 0, D.h()-0.05f, InstallIO.visible() ? "Please select path for Esenthel installation" : "Installing to");
  193. ts.size=0.045f; D.text(ts, 0, D.h()-0.11f, S+"\""+InstallPath()+'"');
  194. Gui.draw();
  195. }
  196. /******************************************************************************/
  197. /******************************************************************************/
  198. cchar8 *UpdaterClass::TutorialsProjID="541oob6a_h_5el6-6!zan_50";
  199. const int UpdaterClass::MaxDownloadAttempts=3;
  200. /******************************************************************************/
  201. bool UpdaterClass::CreateFailedDownload(int &failed, C Str &file, ptr user) {failed=0; return true;}
  202. FILE_LIST_MODE UpdaterClass::Filter(C FileFind &ff, UpdaterClass &updater)
  203. {
  204. if(updater.thread.wantStop())return FILE_LIST_BREAK;
  205. if(ff.name==".DS_Store")return FILE_LIST_SKIP;
  206. Str rel=SkipStartPath(ff.pathName(), updater.path);
  207. if( rel=="Settings.txt"
  208. || rel=="Server Settings"
  209. || rel=="Projects (Server)"
  210. || rel=="Esenthel Old.exe"
  211. || rel=="Esenthel Old.exe.bat" // used by 'App.deleteSelfAtExit'
  212. || rel=="Esenthel Old.app"
  213. || rel=="Esenthel Old"
  214. || rel=="Bin/Store.dat"
  215. || rel=="Bin/Code Editor.font"
  216. || EqualPath(rel, "Bin/Update"))return FILE_LIST_SKIP; // skip these elements
  217. if(StartsPath(rel, "Projects"))
  218. {
  219. Str project=SkipStartPath(rel, "Projects");
  220. if( project.is())
  221. {
  222. if(!StartsPath(project, TutorialsProjID))return FILE_LIST_SKIP; // list only default "Tutorials" project
  223. }
  224. }
  225. updater.local_files.New().set(rel, ff);
  226. return FILE_LIST_CONTINUE;
  227. }
  228. FILE_LIST_MODE UpdaterClass::FilterUpdate(C FileFind &ff, UpdaterClass &updater)
  229. {
  230. if(updater.thread.wantStop())return FILE_LIST_BREAK;
  231. if(ff.name==".DS_Store")return FILE_LIST_SKIP;
  232. updater.local_files.New().set(SkipStartPath(ff.pathName(), updater.update_path), ff);
  233. return FILE_LIST_CONTINUE;
  234. }
  235. FILE_LIST_MODE UpdaterClass::HasUpdate(C FileFind &ff, UpdaterClass &updater)
  236. {
  237. if(updater.thread.wantStop())return FILE_LIST_BREAK;
  238. if(ff.name==".DS_Store")return FILE_LIST_SKIP;
  239. Str rel=SkipStartPath(ff.pathName(), updater.update_path);
  240. if(StartsPath(rel, "Projects"))
  241. {
  242. Str project=SkipStartPath(rel, "Projects");
  243. if(!project.is() )return FILE_LIST_CONTINUE; // keep checking inside but don't report "Projects" folder as an update
  244. if( project==TutorialsProjID)return FILE_LIST_SKIP ; // don't check inside and don't report "Tutorials" project as an update
  245. }
  246. updater.has_update=true; // this is a file other than "Projects" folder and "Tutorials" project, which means that it's a new update, report that we have an update and stop processing
  247. return FILE_LIST_BREAK;
  248. }
  249. bool UpdaterClass::hasUpdate()
  250. {
  251. if(0) // don't notify about updates if they're just about file removal
  252. if(C Pak *pak=patcher.index())
  253. REPA(local_remove) // iterate all files for removal
  254. {
  255. C Str &name=local_files[local_remove[i]].full_name;
  256. if(!pak->find(name))if(FExistSystem(path+name))return true; // if a file is not present on the server but exists locally
  257. }
  258. has_update=false; FList(update_path, HasUpdate, T); // check if there are any files to update (except "Tutorials" project)
  259. return has_update;
  260. }
  261. bool UpdaterClass::updating() {return thread.active();}
  262. flt UpdaterClass::progress() {if(long total=patcher.filesSize())return dbl(patcher.progress())/total; return 0;}
  263. bool UpdaterClass::Update(Thread &thread)
  264. {
  265. if(InstallerMode)App.stayAwake(AWAKE_SYSTEM);
  266. bool ok=((UpdaterClass*)thread.user)->update();
  267. if(InstallerMode)App.stayAwake(AWAKE_OFF);
  268. return ok;
  269. }
  270. bool UpdaterClass::update() // !! this is called on secondary thread !!
  271. {
  272. // download latest ver
  273. Str upload_name;
  274. #if WINDOWS
  275. upload_name="Esenthel";
  276. #elif MAC
  277. upload_name="Esenthel Mac";
  278. #elif LINUX
  279. upload_name="Esenthel Linux";
  280. #else
  281. #error unknown platform
  282. #endif
  283. failed_download.clear();
  284. patcher.create("http://www.esenthel.com/download/Patcher", upload_name);
  285. patcher.downloadIndex();
  286. // check local ver (do this before update ver)
  287. path=(InstallerMode ? InstallPath() : GetPath(App.exe())).tailSlash(true);
  288. FList(path, Filter, T); if(thread.wantStop())return false;
  289. // check local update ver (do this after original ver so these files will overwrite previous info)
  290. update_path=path+"Bin\\Update\\";
  291. FList(update_path, FilterUpdate, T); if(thread.wantStop())return false;
  292. // wait for patcher index
  293. for(; ; Time.wait(1))
  294. {
  295. if(thread.wantStop())return false;
  296. if(patcher.indexState()==DWNL_ERROR){if(InstallerMode)Gui.msgBox(S, "Can't access Esenthel Server"); return false;}
  297. if(patcher.indexState()==DWNL_DONE )break;
  298. }
  299. // process what to remove/download
  300. Memc<int> server_download;
  301. if(!patcher.compare(local_files, local_remove, server_download))return false;
  302. bool deleted=false; REPA(local_remove)deleted|=FDel(update_path+local_files[local_remove[i]].full_name); // from leafs to root to delete files first (we're operating on temporary files here so there's no need for 'FRecycle')
  303. if( deleted) // if deleted any file then list local files again, in case now we have them up to date
  304. {
  305. local_files.clear();
  306. FList( path, Filter , T); if(thread.wantStop())return false;
  307. FList(update_path, FilterUpdate, T); if(thread.wantStop())return false;
  308. if(!patcher.compare(local_files, local_remove, server_download))return false;
  309. }
  310. FREPA(server_download) patcher.downloadFile(server_download[i]); // from root to download folders first
  311. REPA( local_remove )FDel(update_path+local_files[local_remove [i]].full_name); // from leafs to root to delete files first (we're operating on temporary files here so there's no need for 'FRecycle')
  312. // download files
  313. bool ok=true;
  314. for(; ; )
  315. {
  316. if(thread.wantStop()){patcher.del(); return false;}
  317. Patcher::Downloaded download;
  318. if(patcher.getNextDownload(download))
  319. {
  320. if(download.success)
  321. {
  322. Str full=update_path+download.full_name;
  323. switch(download.type)
  324. {
  325. case FSTD_DIR: FCreateDirs(full); break;
  326. case FSTD_LINK:
  327. {
  328. FCreateDirs(GetPath(full));
  329. FDelFile(full);
  330. if(CreateSymLink(full, DecodeSymLink(download.data)))FTimeUTC(full, download.modify_time_utc);else{Gui.msgBox("Error", S+"Can't write to:\n\""+full+'"'); patcher.del(); return false;}
  331. }break;
  332. case FSTD_FILE:
  333. {
  334. FCreateDirs(GetPath(full));
  335. if(!SafeOverwrite(download.data, full, &download.modify_time_utc)){Gui.msgBox("Error", S+"Can't write to:\n\""+full+"\"\nTry running as administrator."); patcher.del(); return false;}
  336. }break;
  337. }
  338. }else
  339. {
  340. int &failed =*failed_download(download.full_name);
  341. if(++failed>=MaxDownloadAttempts)
  342. {
  343. ok=false;
  344. if(InstallerMode){Gui.msgBox(S, S+"Error downloading file:\n\""+download.full_name+"\"\nPlease try again.\nUsing the same path will resume the download."); patcher.del(); return false;}
  345. }else // try again
  346. {
  347. patcher.downloadFile(download.index);
  348. }
  349. }
  350. }else
  351. if(!patcher.filesLeft())break;else Time.wait(1);
  352. }
  353. // update
  354. if(ok) // if all succeeded
  355. if(InstallerMode || hasUpdate()) // there is an actual update
  356. {
  357. show=ready=true;
  358. }
  359. return false;
  360. }
  361. void UpdaterClass::create()
  362. {
  363. del();
  364. #if DESKTOP && !STEAM
  365. #if DEBUG
  366. if(ForceInstaller>=-1)
  367. #endif
  368. thread.create(Update, this);
  369. #endif
  370. }
  371. void UpdaterClass::del()
  372. {
  373. thread.del(); // delete the thread first
  374. ready=show=false;
  375. patcher .del();
  376. local_remove.del();
  377. local_files .del();
  378. }
  379. UpdaterClass::~UpdaterClass() {del();}
  380. void UpdateWindowClass::ApplyDo(bool all_saved, ptr) {if(all_saved){UpdateWindow.hide(); if(Updater.ready)StateUpdate.set(StateFadeTime);}}
  381. void UpdateWindowClass::ShowChanges(UpdateWindowClass &uw) {Explore("http://www.esenthel.com/forum/forumdisplay.php?fid=8");}
  382. void UpdateWindowClass::Apply(UpdateWindowClass &uw) {if(StateActive==&StateProject)SaveChanges(ApplyDo);else ApplyDo();}
  383. void UpdateWindowClass::create()
  384. {
  385. Gui+=::EE::Window::create(Rect_C(0, 0, 1, 0.48f)).barVisible(false).hide(); button[2].func(HideEditAct, SCAST(GuiObj, T)).show();
  386. T +=text .create(Vec2 (clientWidth()/2, -0.19f), "An update to Esenthel is available.\nWould you like to apply it now?\n\nWarning: Applying update will restore the default\n\"Tutorials\" project to its original state.\nAny changes you've made to it will be lost.");
  387. T +=apply .create(Rect_D(clientWidth()*1/6, -clientHeight()+0.04f, 0.26f, 0.06f), "Apply" ).focusable(false).func(Apply, T);
  388. T +=show_changes.create(Rect_D(clientWidth()/2 , -clientHeight()+0.04f, 0.32f, 0.06f), "Show Changes").focusable(false).func(ShowChanges, T);
  389. T +=not_now .create(Rect_D(clientWidth()*5/6, -clientHeight()+0.04f, 0.26f, 0.06f), "Not Now" ).focusable(false).func(HideEditAct, SCAST(GuiObj, T));
  390. }
  391. GuiObj* UpdateWindowClass::test(C GuiPC &gpc, C Vec2 &pos, GuiObj* &mouse_wheel)
  392. {
  393. return (alpha()>=1) ? ::EE::Window::test(gpc, pos, mouse_wheel) : null; // check for alpha to avoid accidental clicks when window suddenly appears
  394. }
  395. void UpdateWindowClass::update(C GuiPC &gpc)
  396. {
  397. ::EE::ClosableWindow::update(gpc);
  398. if(Updater.show)
  399. {
  400. Updater.show=false;
  401. fadeIn();
  402. }
  403. }
  404. UpdaterClass::UpdaterClass() : ready(false), show(false), has_update(false), failed_download(ComparePathCI, CreateFailedDownload) {}
  405. /******************************************************************************/