| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- /******************************************************************************/
- #include "stdafx.h"
- /******************************************************************************/
- UpdaterClass Updater;
- /******************************************************************************/
- UpdateWindowClass UpdateWindow;
- /******************************************************************************/
- bool UpdateDo()
- {
- Physics.del(); // del physics and unload DLL's
- bool inside=StartsPath(App.exe(), Updater.path);
- Str old=App.exe(); // remember old app name
- #if WINDOWS
- // move exe to temporary file so 'FMoveDir' will succeed for the exe as well
- if(inside)
- {
- Str rel=SkipStartPath(App.exe(), Updater.path);
- if(FExistSystem(Updater.update_path+rel)) // if there exists a replacement for that file
- {
- 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;}
- App.deleteSelfAtExit(); // delete the "Esenthel Old" file
- }
- }
- #endif
- // remove those that are not wanted (do this first, before replacing files)
- FREPA(Updater.local_remove) // go from start to recycle folders first
- {
- Str full=Updater.path+Updater.local_files[Updater.local_remove[i]].full_name;
- #if WINDOWS
- if(EqualPath(full, App.exe())){App.deleteSelfAtExit(); continue;} // on Windows we can't recycle self
- #endif
- FRecycle(full);
- }
- // replace all files
- FMoveDir(Updater.update_path, Updater.path);
- if(inside && FExistSystem(old))RunAtExit=old;
- if(InstallerMode)
- {
- Str ext;
- #if WINDOWS
- ext=".exe";
- #elif MAC
- ext=".app";
- #elif LINUX
- ext="";
- #else
- #error unknown platform
- #endif
- CreateShortcut(Updater.path+"Esenthel"+ext, SystemPath(SP_DESKTOP ).tailSlash(true)+"Esenthel");
- CreateShortcut(Updater.path+"Esenthel"+ext, SystemPath(SP_MENU_PROG).tailSlash(true)+"Esenthel");
- }
- // finish
- if(RunAtExit.is())
- {
- // if we run app on exit, then no messages need to be displayed
- }else // we won't run any app
- {
- Explore(Updater.path); // open folder so user can see what's happened there
- WindowHide(); // hide window because 'WindowMsgBox' causes a pause
- /*if(inside)*/WindowMsgBox("Success", InstallerMode ? "Esenthel has installed successfully." : "Esenthel has updated successfully."); // show confirmation using OS msg box
- //else Gui.msgBox("Success", "Esenthel has updated properly.");
- }
- /*if(inside)*/return false; // close the application
- //else StateInstall.set(StateFadeTime); // if we're not inside then it means we're an installer
- }
- /******************************************************************************/
- State StateUpdate(UpdateUpdate, DrawUpdate, InitUpdate, ShutUpdate);
- Str UpdateMessage;
- /******************************************************************************/
- bool InitUpdate()
- {
- WindowSetWorking();
- SetKbExclusive();
- Proj.close();
- // auto close all processes in path folder except this one - App.exe
- Memc<uint> proc; ProcList(proc);
- REPA(proc)if(proc[i]!=App.processID())
- {
- Str proc_name=ProcName(proc[i]);
- if(StartsPath(proc_name, Updater.path))ProcClose(proc_name);
- }
- UpdateMessage.clear();
- return true;
- }
- void ShutUpdate()
- {
-
- }
- bool UpdateUpdate()
- {
- if(Kb.bp(KB_ESC) || Kb.bp(KB_NAV_BACK))
- {
- if(!InstallerMode)StateProjectList.set(StateFadeTime);
- }
- Time.wait(1000/30);
- Gui.update();
- Server.update(null, true);
- if(Ms.bp(3))WindowToggle();
- UpdateMessage.clear();
- Memc<uint> proc; ProcList(proc);
- REPA(proc)if(proc[i]!=App.processID())
- {
- Str proc_name=ProcName(proc[i]);
- if(StartsPath(proc_name, Updater.path))
- {
- if(!UpdateMessage.is())UpdateMessage+="Waiting for applications to exit:";
- UpdateMessage.line()+=proc_name;
- }
- }
- if(!UpdateMessage.is()) // if there are no active apps then perform update
- return UpdateDo();
- return true;
- }
- void DrawUpdate()
- {
- D.clear(BackgroundColor());
- Gui.draw();
- D.text(Rect(-D.w(), -D.h(), D.w(), D.h()), UpdateMessage);
- }
- /******************************************************************************/
- State StateInstall(UpdateInstall, DrawInstall, InitInstall, ShutInstall);
- WindowIO InstallIO;
- bool InstallerMode=false;
- /******************************************************************************/
- Str InstallPath()
- {
- Str path=InstallIO.path(); path.tailSlash(true)+=InstallIO.subPath();
- if(InstallIO.textline().is())
- {
- if(FullPath(InstallIO.textline()))path=InstallIO.textline();else path.tailSlash(true)+=InstallIO.textline();
- path=Replace(NormalizePath(path), '/', '\\');
- }
- if(path.tailSlash(false).is())
- {
- Str end=GetBase(path); if(!Contains(end, "Esenthel"))path.tailSlash(true)+="Esenthel";
- }
- return path;
- }
- void SelectInstall(C Str &path, ptr)
- {
- if(!InstallPath().is())InstallIO.activate();else Updater.create(); // reactivate if user selected empty/invalid path
- }
- void ResizeInstall()
- {
- flt w=Min(0.6f, D.w());
- InstallIO.rect(Rect(-w, -D.h(), w, D.h()-0.17f));
- }
- /******************************************************************************/
- bool InitInstall()
- {
- SetKbExclusive();
- if(!InstallIO.is())
- {
- InstallIO.create(S, S, S, SelectInstall, SelectInstall).modeDirSelect(); FlagDisable(InstallIO.flag, WIN_MOVABLE|WIN_RESIZABLE);
- InstallIO.button[2].func(MiscRegion::Quit, Misc).hide();
- InstallIO.cancel .func(MiscRegion::Quit, Misc);
- InstallIO.activate();
- ResizeInstall();
- }
- Gui+=UpdateProgress.create(Rect_C(0, -0.05f, 1, 0.045f));
- Gui+=InstallIO;
- return true;
- }
- void ShutInstall()
- {
- UpdateProgress.del();
- Gui-=InstallIO;
- }
- bool UpdateInstall()
- {
- if(!App.active())Time.wait(1000/30);else if(!D.sync())Time.wait(1000/100);
- Gui.update();
- if(!InstallIO.visible() && !Updater.updating())
- {
- if(Updater.ready) // success
- {
- StateUpdate.set(StateFadeTime);
- }else // failed
- {
- InstallIO.activate();
- WindowSetError(Updater.progress());
- }
- }
- if(!InstallIO.visible())WindowSetProgress(Updater.progress());
- UpdateProgress.set(Updater.progress());
- if(Ms.bp(3))WindowToggle();
- return true;
- }
- void DrawInstall()
- {
- D.clear(BackgroundColor());
- TextStyleParams ts;
- ts.size=0.055f; D.text(ts, 0, D.h()-0.05f, InstallIO.visible() ? "Please select path for Esenthel installation" : "Installing to");
- ts.size=0.045f; D.text(ts, 0, D.h()-0.11f, S+"\""+InstallPath()+'"');
- Gui.draw();
- }
- /******************************************************************************/
- /******************************************************************************/
- cchar8 *UpdaterClass::TutorialsProjID="541oob6a_h_5el6-6!zan_50";
- const int UpdaterClass::MaxDownloadAttempts=3;
- /******************************************************************************/
- bool UpdaterClass::CreateFailedDownload(int &failed, C Str &file, ptr user) {failed=0; return true;}
- FILE_LIST_MODE UpdaterClass::Filter(C FileFind &ff, UpdaterClass &updater)
- {
- if(updater.thread.wantStop())return FILE_LIST_BREAK;
- if(ff.name==".DS_Store")return FILE_LIST_SKIP;
- Str rel=SkipStartPath(ff.pathName(), updater.path);
- if( rel=="Settings.txt"
- || rel=="Server Settings"
- || rel=="Projects (Server)"
- || rel=="Esenthel Old.exe"
- || rel=="Esenthel Old.exe.bat" // used by 'App.deleteSelfAtExit'
- || rel=="Esenthel Old.app"
- || rel=="Esenthel Old"
- || rel=="Bin/Store.dat"
- || rel=="Bin/Code Editor.font"
- || EqualPath(rel, "Bin/Update"))return FILE_LIST_SKIP; // skip these elements
- if(StartsPath(rel, "Projects"))
- {
- Str project=SkipStartPath(rel, "Projects");
- if( project.is())
- {
- if(!StartsPath(project, TutorialsProjID))return FILE_LIST_SKIP; // list only default "Tutorials" project
- }
- }
- updater.local_files.New().set(rel, ff);
- return FILE_LIST_CONTINUE;
- }
- FILE_LIST_MODE UpdaterClass::FilterUpdate(C FileFind &ff, UpdaterClass &updater)
- {
- if(updater.thread.wantStop())return FILE_LIST_BREAK;
- if(ff.name==".DS_Store")return FILE_LIST_SKIP;
- updater.local_files.New().set(SkipStartPath(ff.pathName(), updater.update_path), ff);
- return FILE_LIST_CONTINUE;
- }
- FILE_LIST_MODE UpdaterClass::HasUpdate(C FileFind &ff, UpdaterClass &updater)
- {
- if(updater.thread.wantStop())return FILE_LIST_BREAK;
- if(ff.name==".DS_Store")return FILE_LIST_SKIP;
- Str rel=SkipStartPath(ff.pathName(), updater.update_path);
- if(StartsPath(rel, "Projects"))
- {
- Str project=SkipStartPath(rel, "Projects");
- if(!project.is() )return FILE_LIST_CONTINUE; // keep checking inside but don't report "Projects" folder as an update
- if( project==TutorialsProjID)return FILE_LIST_SKIP ; // don't check inside and don't report "Tutorials" project as an update
- }
- 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
- return FILE_LIST_BREAK;
- }
- bool UpdaterClass::hasUpdate()
- {
- if(0) // don't notify about updates if they're just about file removal
- if(C Pak *pak=patcher.index())
- REPA(local_remove) // iterate all files for removal
- {
- C Str &name=local_files[local_remove[i]].full_name;
- if(!pak->find(name))if(FExistSystem(path+name))return true; // if a file is not present on the server but exists locally
- }
- has_update=false; FList(update_path, HasUpdate, T); // check if there are any files to update (except "Tutorials" project)
- return has_update;
- }
- bool UpdaterClass::updating() {return thread.active();}
- flt UpdaterClass::progress() {if(long total=patcher.filesSize())return dbl(patcher.progress())/total; return 0;}
- bool UpdaterClass::Update(Thread &thread)
- {
- if(InstallerMode)App.stayAwake(AWAKE_SYSTEM);
- bool ok=((UpdaterClass*)thread.user)->update();
- if(InstallerMode)App.stayAwake(AWAKE_OFF);
- return ok;
- }
- bool UpdaterClass::update() // !! this is called on secondary thread !!
- {
- // download latest ver
- Str upload_name;
- #if WINDOWS
- upload_name="Esenthel";
- #elif MAC
- upload_name="Esenthel Mac";
- #elif LINUX
- upload_name="Esenthel Linux";
- #else
- #error unknown platform
- #endif
- failed_download.clear();
- patcher.create("http://www.esenthel.com/download/Patcher", upload_name);
- patcher.downloadIndex();
- // check local ver (do this before update ver)
- path=(InstallerMode ? InstallPath() : GetPath(App.exe())).tailSlash(true);
- FList(path, Filter, T); if(thread.wantStop())return false;
- // check local update ver (do this after original ver so these files will overwrite previous info)
- update_path=path+"Bin\\Update\\";
- FList(update_path, FilterUpdate, T); if(thread.wantStop())return false;
- // wait for patcher index
- for(; ; Time.wait(1))
- {
- if(thread.wantStop())return false;
- if(patcher.indexState()==DWNL_ERROR){if(InstallerMode)Gui.msgBox(S, "Can't access Esenthel Server"); return false;}
- if(patcher.indexState()==DWNL_DONE )break;
- }
- // process what to remove/download
- Memc<int> server_download;
- if(!patcher.compare(local_files, local_remove, server_download))return false;
- 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')
- if( deleted) // if deleted any file then list local files again, in case now we have them up to date
- {
- local_files.clear();
- FList( path, Filter , T); if(thread.wantStop())return false;
- FList(update_path, FilterUpdate, T); if(thread.wantStop())return false;
- if(!patcher.compare(local_files, local_remove, server_download))return false;
- }
- FREPA(server_download) patcher.downloadFile(server_download[i]); // from root to download folders first
- 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')
- // download files
- bool ok=true;
- for(; ; )
- {
- if(thread.wantStop()){patcher.del(); return false;}
- Patcher::Downloaded download;
- if(patcher.getNextDownload(download))
- {
- if(download.success)
- {
- Str full=update_path+download.full_name;
- switch(download.type)
- {
- case FSTD_DIR: FCreateDirs(full); break;
- case FSTD_LINK:
- {
- FCreateDirs(GetPath(full));
- FDelFile(full);
- 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;}
- }break;
- case FSTD_FILE:
- {
- FCreateDirs(GetPath(full));
- 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;}
- }break;
- }
- }else
- {
- int &failed =*failed_download(download.full_name);
- if(++failed>=MaxDownloadAttempts)
- {
- ok=false;
- 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;}
- }else // try again
- {
- patcher.downloadFile(download.index);
- }
- }
- }else
- if(!patcher.filesLeft())break;else Time.wait(1);
- }
- // update
- if(ok) // if all succeeded
- if(InstallerMode || hasUpdate()) // there is an actual update
- {
- show=ready=true;
- }
- return false;
- }
- void UpdaterClass::create()
- {
- del();
- #if DESKTOP && !STEAM
- #if DEBUG
- if(ForceInstaller>=-1)
- #endif
- thread.create(Update, this);
- #endif
- }
- void UpdaterClass::del()
- {
- thread.del(); // delete the thread first
- ready=show=false;
- patcher .del();
- local_remove.del();
- local_files .del();
- }
- UpdaterClass::~UpdaterClass() {del();}
- void UpdateWindowClass::ApplyDo(bool all_saved, ptr) {if(all_saved){UpdateWindow.hide(); if(Updater.ready)StateUpdate.set(StateFadeTime);}}
- void UpdateWindowClass::ShowChanges(UpdateWindowClass &uw) {Explore("http://www.esenthel.com/forum/forumdisplay.php?fid=8");}
- void UpdateWindowClass::Apply(UpdateWindowClass &uw) {if(StateActive==&StateProject)SaveChanges(ApplyDo);else ApplyDo();}
- void UpdateWindowClass::create()
- {
- Gui+=::EE::Window::create(Rect_C(0, 0, 1, 0.48f)).barVisible(false).hide(); button[2].func(HideEditAct, SCAST(GuiObj, T)).show();
- 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.");
- T +=apply .create(Rect_D(clientWidth()*1/6, -clientHeight()+0.04f, 0.26f, 0.06f), "Apply" ).focusable(false).func(Apply, T);
- T +=show_changes.create(Rect_D(clientWidth()/2 , -clientHeight()+0.04f, 0.32f, 0.06f), "Show Changes").focusable(false).func(ShowChanges, T);
- 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));
- }
- GuiObj* UpdateWindowClass::test(C GuiPC &gpc, C Vec2 &pos, GuiObj* &mouse_wheel)
- {
- return (alpha()>=1) ? ::EE::Window::test(gpc, pos, mouse_wheel) : null; // check for alpha to avoid accidental clicks when window suddenly appears
- }
- void UpdateWindowClass::update(C GuiPC &gpc)
- {
- ::EE::ClosableWindow::update(gpc);
- if(Updater.show)
- {
- Updater.show=false;
- fadeIn();
- }
- }
- UpdaterClass::UpdaterClass() : ready(false), show(false), has_update(false), failed_download(ComparePathCI, CreateFailedDownload) {}
- /******************************************************************************/
|