/* * updater.cpp by Matthias Braun * * Copyright (C) 2002 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation (version 2 of the License) * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include #include #include #include #include #include #include #include #include #include "util/sleep.h" #include "util/strutil.h" #include "pawsupdaterwindow.h" #include "paws/pawsmanager.h" #include "paws/pawswidget.h" #include "psupdaterengine.h" #include "updater.h" #include "updaterconfig.h" #include "registry.h" #include "fileutil.h" #include "download.h" #include "registrycreator.h" #include "change.h" #include "changelist.h" #include "registryfile.h" #include "registry.h" #include "updaterglobals.h" #ifdef CS_PLATFORM_WIN32 #include #endif using updater::RegistryFile; using updater::FileList; using updater::Module; namespace updater { Updater::Updater () : registrycache(NULL), newregistry(NULL) { running = false; cleanupNeeded = true; } Updater::~Updater () { if (thread.IsValid()) thread->Stop(); // Save our config if (vfs) { configdoc->Write (vfs, configfilename); } delete downloader; delete[] configfilename; vfs = NULL; registrycache = NULL; newregistry = NULL; } bool Updater::Initialize(iObjectRegistry* object_reg ) { //Load stuff csString configfile("/this/updaterconfig.xml"); objreg = object_reg;; configfilename = NULL; // Load our config vfs = psupdaterengine->GetVFS(); if (!vfs) psupdaterengine->CriticalError("Couldn't find vfs plugin!"); csRef xml = csPtr (new csTinyDocumentSystem); if (!xml) psupdaterengine->CriticalError("Couldn't create xml document system!"); //Try to read files csRef buf = vfs->ReadFile (configfile.GetData()); if (!buf || !buf->GetSize()) psupdaterengine->CriticalError("Couldn't open config file '%s'!", configfile.GetData()); //Try to parse file configdoc = xml->CreateDocument(); const char* error = configdoc->Parse (buf); if (error) psupdaterengine->CriticalError("XML Parsing error in file '%s': %s.", configfile.GetData(), error); //Try to get root csRef root = configdoc->GetRoot (); if (!root) psupdaterengine->CriticalError("Couldn't get config file rootnode!"); //Try to get node config csRef confignode = root->GetNode ("config"); if (!confignode) psupdaterengine->CriticalError("Couldn't find config node in configfile!"); //Parse the XML stuff in Config config = new Config(confignode); // Load updater config if (!config->Initialize()) return false; configfilename = csStrNew (configfile); downloader = new Downloader; //Set proxy downloader->SetProxy( config->GetProxy()->active, config->GetProxy()->host.GetData(), config->GetProxy()->port ); RefreshModules(); return true; } void Updater::RefreshModules() { config->ModuleSetup (modulechoice); } bool Updater::LoadRegistryCache (const char* filename) { registrycache = csPtr (new Registry(objreg)); registrycache->Load(filename); return true; } void Updater::SaveRegistryCache (const char* filename) { if (!newregistry) psupdaterengine->CriticalError("newregistry==NULL!!!"); newregistry->Save (filename); } bool Updater::DownloadRegistry () { psupdaterengine->OutToScreen("Downloading registry..."); // Download server registry csRef thispath = psupdaterengine->GetVFS()->GetRealMountPaths("/this/"); //As long as we have at least one real directory mounted to /this/ we're happy. if (thispath->Length() == 0) { psupdaterengine->OutToScreen("Error..VFS directory /this/ not mounted."); return false; } MakeDirectory("updatertemp"); if ( !downloader->DownloadFile ("updatertemp/repos.zip", config->GetCurrentRepositoryURL(), true) ) return false; newregistry = LoadRepositoryZip("updatertemp/repos.zip"); if (!newregistry.IsValid()) { psupdaterengine->OutToScreen("Couldn't load downloaded registry!"); return false; } psupdaterengine->OutToScreen("Loaded registry"); return true; } csPtr Updater::LoadRepositoryZip(const char* zip) { //zip is a relative real path, so append /this/ if (!psupdaterengine->GetVFS()->Mount("/repos",csString("$^")+zip)) { psupdaterengine->CriticalError("Couldn't mount path %s!\n",zip); return NULL; } // Load the repository Registry* registry = new Registry(objreg); if(!registry->Load ("/repos/repository.xml")) { printf("Registry load failed!!"); return false; } // Unmount and delete zip since we don't need it anymore now if (!psupdaterengine->GetVFS()->Unmount("/repos",csString("$^")+zip)) { /* printf("Couldn't unmount VFS path!\n"); delete registry; return NULL; */ } return registry; } bool Updater::CheckUpdates(ChangeList& changelist) { //printf("Checking for updates\n"); // Part0: Short Paths, if both registries are the same don't do any // updating or expensive md5 calculation if (newregistry && registrycache && newregistry == registrycache) { printf ( "No changes. Skipping MD5 check.\n"); printf ( "Remove updatercurrent.xml if you want to force a MD5 test.\n"); return true; } // Part1: Deleted Files // If we have a registry cache loaded compare the 2 registries to see if // some files have been deleted if (newregistry && registrycache) { for (size_t i=0;iGetModule (module); // If we didn't use this module the last update, don't use it now if(mod1 && !mod1->GetUsed()) continue; Module* mod2 = newregistry->GetModule (module); if (!mod1 || !mod2) continue; // the filelists are in order. unsigned int p1=0, p2=0; while (p1< mod1->GetRegistryLength() && p2< mod2->GetRegistryLength()) { RegistryFile* file1 = mod1->GetRegistryFile(p1); RegistryFile* file2 = mod2->GetRegistryFile(p2); if (file2->inZip && file2->removeFromZip) { p1++; if (!vfs->Mount("/zip",csString("$^")+file2->parentZip)) { printf("Couldn't mount VFS path!\n"); continue; } csString path; path = "/zip/"; path += file2->file; if (vfs->Exists(path.GetData())) { // If we should remove without whole zips, remove. Otherwise trigger download of new zip if(!GetConfig()->DownloadWholeZips()) changelist.Push (Change (Change::REMOVEFILE, module, file2)); else changelist.Push (Change (Change::UPDATEZIP, module, file2)); } if (!vfs->Unmount("/zip",csString("$^")+file2->parentZip)) { printf("Couldn't unmount VFS path!\n"); continue; } continue; } if (*file1 == *file2) { p1++; p2++; continue; } if (*file1 < *file2) { CheckUpdatesFileRemoved (changelist, mod1,mod2, file1); p1++; } if (*file2 < *file1) { // ignore added files here p2++; } p1++; p2++; } while (p1GetRegistryLength()) { RegistryFile* file1 = mod1->GetRegistryFile(p1); CheckUpdatesFileRemoved (changelist, mod1,mod2, file1); p1++; } } } // Part2: Check for updated files csArray skipped; for (size_t m=0;mGetModule (module); if (!mod) { printf ( "Warning: Couldn't find module '%s' in registry.\n", module); continue; } for (unsigned int f=0;fGetRegistryLength();f++) { RegistryFile* file = mod->GetRegistryFile(f); if (file->removeFromZip) continue; csString path; if (file->parentZip != "" && file->inZip) { // See if the zip is read only FileStat* stat = StatFile(file->parentZip); if(stat && stat->readonly) { // Don't report it twice or more bool found = false; for(size_t i = 0;i < skipped.Length();i++) { if(skipped[i] == file->parentZip) { found = true; break; } } if(!found) // Found, delete duplicate { printf("Skipping file zip %s, because it's readonly\n", file->parentZip.GetData()); skipped.Push(file->parentZip); continue; } else continue; } if (!vfs->Mount("/zip",csString("$^")+file->parentZip)) { printf("Couldn't mount temporary VFS path (%s)!\n",file->parentZip.GetData()); continue; } path = "/zip/"; path.Append(file->zipFile); } else { // See if the file is read only FileStat* stat = StatFile(file->file); if(stat && stat->readonly) { printf("Skipping file %s, because it's readonly\n",file->file.GetData()); continue; } path = "/this/" + file->file; } // Check our md5 sum MD5Sum md5; bool md5status = md5.ReadFile (path, (file->flags & RegistryFile::FLAG_TEXTFILE) ? true : false); //Unmount temp VFS if (file->parentZip != "" && file->inZip) { if (!vfs->Unmount("/zip",csString("$^")+file->parentZip.GetData())) { printf("Couldn't unmount temporary VFS path (%s)!\n",file->parentZip.GetData()); continue; } } if (!md5status) //assume default error (File not found) { // If the file didn't exist create a new one if(GetConfig()->DownloadWholeZips() && file->parentZip != "" && file->inZip) changelist.Push (Change (Change::UPDATEZIP, module, file)); else changelist.Push (Change (Change::CREATEFILE, module, file)); continue; } // See if the file itself is update since the last time, else we // will delete the users mods if (file->flags & RegistryFile::FLAG_CONFIGFILE && registrycache) { Module* oldModule = registrycache->GetModule(module); if (oldModule && f < oldModule->GetRegistryLength()) { if (oldModule->GetRegistryFile(f)->md5 == file->md5) { continue; } } } if (md5 == file->md5) { } else { if(GetConfig()->DownloadWholeZips() && file->parentZip != "" && file->inZip) changelist.Push (Change (Change::UPDATEZIP, module, file)); else changelist.Push (Change (Change::UPDATEFILE, module, file)); } } } RemoveDuplicatedZips(&changelist); DoZipPercentCheck(&changelist); return true; } bool Updater::CheckUpdatesFileRemoved (ChangeList& changelist, Module* module, Module* old_module, RegistryFile* file) { // Loop through the whole module to be sure size_t p=0; while (p< old_module->GetRegistryLength()) { RegistryFile* file1 = old_module->GetRegistryFile(p); if(file1->file == file->file) { printf("No action (Moved)\n"); return false; } p++; } // Check if this file is a system file for(size_t i = 0;i < GetConfig()->systemFiles.Length();i++) { if(GetConfig()->systemFiles.Get(i) == file->file) { printf("No action (System)\n"); return false; } } // test if our md5 still is the same as the old md5 if (!psupdaterengine->GetVFS()->Exists("/this/" + file->file)) { printf("No action (Not found)\n"); // return when no file was found return true; } if (config->MakeBackup()) { printf("Move\n"); changelist.Push (Change (Change::MOVEFILEAWAY, module->GetName(), file)); } else { printf("Delete\n"); changelist.Push (Change (Change::REMOVEFILE, module->GetName(), file)); } return true; } void Updater::CopyZipDir( csString dir, csString directory ) { //printf("Looking in: /download/%s\n", dir.GetData() ); csRef buff = psupdaterengine->GetVFS()->FindFiles("/downzip/" + dir); for (size_t i = 0;i < buff->Length();i++) { csString file = buff->Get(i); bool direct = false; if (file.GetAt(file.Length()-1) == '/') direct = true; file = file.Slice(9,file.Length()); if (direct) { csString subpath = buff->Get(i); CopyZipDir(subpath.Slice(9,subpath.Length()), directory); } else { if (file) { size_t first = file.FindFirst('/'); csString myFile = file.Slice( first+1, file.Length() ); csString from; from.Format("/downzip/%s", file.GetData() ); csString to; to.Format("/special/%s/%s", directory.GetData(), file.GetData() ); psupdaterengine->GetUpdater()->CopyFile( from, to, true ); } } } } bool Updater::PerformUpdates (ChangeList& changelist) { pawsUpdaterWindow* updWnd = NULL; if (!psupdaterengine->IsSilent()) { float totalSize=0; for (size_t i=0; iparentSize; else totalSize += changelist[i].file->size; } psupdaterengine->LockStatusMutex(); updWnd = (pawsUpdaterWindow*)PawsManager::GetSingleton().FindWidget("updater"); updWnd->SetTotalSize(totalSize); psupdaterengine->UnlockStatusMutex(); } psupdaterengine->OutToScreen("==== Update started ===="); for (size_t i=0;iGetCurrentFilesDirectory(); csString serverpath; switch (changelist[i].type) { case Change::MOVEFILEAWAY: { csString path("moved/"); path.Append(changelist[i].file->file); CopyFile(changelist[i].file->file,path); //remove the file down there } case Change::REMOVEFILE: { UpdateRemoveFile( changelist[i] ); break; } case Change::UPDATEZIP: { UpdateZip( changelist[i] ); break; } case Change::UPDATEFILE: case Change::CREATEFILE: { UpdateCreateFile( changelist[i] ); break; } default: fprintf (stderr, "Unknown Change Mode ?!?\n"); break; } } // Unmount zip if(prevZip.GetData() != NULL) { psupdaterengine->OutToScreen("Syncing zip.."); if (!vfs->Sync()) { printf("Couldn't sync VFS!\n"); } printf("Unmounting ZIP file %s..\n",prevZip.GetData()); if (!vfs->Unmount( "/zip",prevZip.GetData() )) { printf("Couldn't unmount VFS path!\n"); } } return true; } bool Updater::CopyFile(csString name1, csString name2, bool vfsPath, bool silent) { csString n1; csString n2; csString file = name2; if(vfsPath) { csRef buff = vfs->GetRealPath(name2); if(!buff) { printf("Couldn't get the real filename for %s!\n",name2.GetData()); return false; } file = buff->GetData(); } FileStat* stat = StatFile(file); if(stat && stat->readonly) { printf("Won't write to %s, because it's readonly\n",file.GetData()); return true; // Return true to bypass DLL checks and stuff } if (!vfsPath) { n1 = "/this/" + name1; n2 = "/this/" + name2; } else { n1= name1; n2= name2; } csRef buffer = vfs->ReadFile(n1.GetData(),true); if (!buffer) { if(!silent) printf("Couldn't read file %s!\n",n1.GetData()); return false; } if (!vfs->WriteFile(n2.GetData(), buffer->GetData(), buffer->GetSize() ) ) { if(!silent) printf("Couldn't write to %s!\n", n2.GetData()); return false; } else { printf("Written %s!\n", n2.GetData()); } return true; } bool Updater::DoUpdate (iObjectRegistry* objreg) { pawsUpdaterWindow* updWnd = NULL; if(!psupdaterengine->IsSilent()) updWnd = (pawsUpdaterWindow*)PawsManager::GetSingleton().FindWidget("updater"); // Check the server's version file, and download the registry if needed bool newRegistry = CheckRegistryVersion(); if (newRegistry) { if (!DownloadRegistry()) { printf("Couldn't download updater registry from server!\n"); return false; } psupdaterengine->OutToScreen("Checking for updates... This may take a while."); } else { psupdaterengine->OutToScreen("There are no new updates available."); psupdaterengine->OutToScreen("If you want a full update, delete the file version.dat and restart the updater."); //psupdaterengine->OutToScreen("Checking for missing/damaged files... This may take a while."); //TOFIX: it doesn't download the registry from server, so this will NOT work! //It should ask to the user if he wants to check for damaged files. return false; } // Load cache LoadRegistryCache ("/this/updatercurrent.xml"); // Check if we have a new version of the updater to download if (!CheckNewUpdater()) return false; ChangeList list; if ( CheckUpdates(list) ) { if (list.Length()) { psupdaterengine->OutToScreen("I'll perform the following updates:"); csString output = list.CreateList(true); // Write log csRef file = vfs->Open("/this/updater.log",VFS_FILE_WRITE); file->Write(output.GetData(),(size_t)output.Length()); file->Flush(); } else { psupdaterengine->OutToScreen("All files are up-to-date!"); } } else return false; printf("Updates: %d\n", list.Length() ); // Let the user see the Total files etc if (list.Length()) psSleep(3000); if (list.Length() > 0) { // Apply changes if (!PerformUpdates (list) ) return false; if (failed.Length() > 0) { csString output; output = "Following files failed:"; psupdaterengine->OutToScreen(output.GetData()); for (size_t i = 0; i < failed.Length(); i++) { output = failed.Get(i); psupdaterengine->OutToScreen(output.GetData()); } failed.DeleteAll(); } } psupdaterengine->OutToScreen("Please wait while saving files.."); // Disable the exit button to make our point if (updWnd) updWnd->SetExit(false); // Save the registry and version info if (newRegistry) SaveRegistryCache("/this/updatercurrent.xml"); MoveFile("/this/updatertemp/version.dat","/this/version.dat"); psupdaterengine->OutToScreen("==== Update complete ===="); if (updWnd) updWnd->SetExit(true); if (failed.Length() > 0) return false; return true; } int Updater::CreateRegistry (iObjectRegistry* objreg, const char* path) { printf("Now Creating the Repository in %s\n", path); RegistryCreator* creator = new RegistryCreator(objreg); csString strPath = path; if (strPath.GetAt(strPath.Length()-1) != CS_PATH_SEPARATOR) { strPath += CS_PATH_SEPARATOR; } csRef fileBuff = psupdaterengine->GetVFS()->GetRealPath(strPath); csString vfsPath; if (!fileBuff) vfsPath = strPath; else vfsPath = fileBuff->GetData(); printf("Mounting %s as the VFS path /storage\n", vfsPath.GetData() ); // Mount copy-to path if (!psupdaterengine->GetVFS()->Mount("/storage",vfsPath)) { psupdaterengine->CriticalError("Couldn't mount path %s!\n",path); return 0; } printf ("Scaninning directories..\n"); Registry* registry = creator->CreateRegistry();; // Mount repository zip if (!psupdaterengine->GetVFS()->Mount("/repos",vfsPath+"repository.zip")) { csString path = vfsPath+"repository.zip"; psupdaterengine->CriticalError("Couldn't mount path %s!\n",path.GetData()); return 0; } printf("Writing repository.xml...\n"); registry->Save ("/repos/repository.xml"); if (!vfs->Sync()) { printf("Couldn't Sync VFS path!\n"); } if (!psupdaterengine->GetVFS()->Unmount("/repos",vfsPath+"repository.zip")) { printf("Couldn't unmount VFS path!\n"); return 0; } printf("Writing version.dat...\n"); csString data = CreateVersionInfo(registry); psupdaterengine->GetVFS()->WriteFile("/storage/version.dat", data.GetData(), data.Length() ); registrycache = registry; return 0; if (!psupdaterengine->GetVFS()->Unmount("/storage",vfsPath)) { printf("Couldn't unmount VFS path!\n"); return 0; } } void Updater::Run() { //Do the stuff DoUpdate(objreg); //Cleanup and stop EndUpdate(); //Quit the updater if we are in silent mode //(It will stop the thread in the destructor anyway) if (psupdaterengine->IsSilent()) psupdaterengine->QuitClient(); } void Updater::EndUpdate() { //Clean out temp dir if (cleanupNeeded) CleanUp(); //Reset file name and progress if (!psupdaterengine->IsSilent()) { psupdaterengine->LockStatusMutex(); pawsUpdaterWindow* updWnd = (pawsUpdaterWindow*)PawsManager::GetSingleton().FindWidget("updater"); updWnd->UpdateFileSize("",0); updWnd->SetUpdateButtonText("Update"); // Change button from "Cancel" back to "Update" psupdaterengine->UnlockStatusMutex(); } running = false; thread->Stop(); thread = NULL; } void Updater::StartUpdate() { if (!running) { if (!thread.IsValid()) thread = csThread::Create (this); if (thread.IsValid()) { thread->Start(); running = true; } } } void Updater::Die() { if(thread.IsValid()) { thread->Stop(); running = false; } } bool Updater::CheckNewUpdater() { if (GetConfig()->GetMirror()->NoVersion() || psupdaterengine->GetUpdater()->GetConfig()->GetSkipVerCheck() ) { psupdaterengine->OutToScreen("Skipping version check, suit yourself"); return true; } if (newregistry) printf("Using newregistry\n"); else printf("Using registrycache\n"); Registry* registry = (newregistry) ? newregistry : registrycache ; if (registry->GetUpdaterVersion() < UPDATER_VERSION) { // If we are in active mode, print to console too psupdaterengine->OutToScreen("Your updater version %d is newer than the server's %d... Server is out of date; stopping.",UPDATER_VERSION,registry->GetUpdaterVersion()); if (!psupdaterengine->IsSilent()) printf("Your updater version %d is newer than the server's %d... Server is out of date; stopping.",UPDATER_VERSION,registry->GetUpdaterVersion()); return false; } else if (registry->GetUpdaterVersion() > UPDATER_VERSION) { // If we are in active mode, print to console too psupdaterengine->OutToScreen("Your updater version %d is older than the server's %d..",UPDATER_VERSION,registry->GetUpdaterVersion()); psupdaterengine->OutToScreen("Updating halted"); if (!psupdaterengine->IsSilent()) printf("Your updater version %d is older than the server's %d..\n",UPDATER_VERSION,registry->GetUpdaterVersion()); } else { if (GetConfig()->UpdateUpdaterByMD5()) { MD5Sum updater; updater.ReadFile(csString("/this/") + registry->GetUpdaterPath(),false); csString updaterMD5 = registry->GetUpdaterMD5(); if( updaterMD5.GetData() == NULL || updaterMD5 == updater.Get() ) return true; else { psupdaterengine->OutToScreen("Server has an updater with same version, but different checksum, going to fetch it..."); printf("Server has an updater with same version, but different checksum, going to fetch it.\n"); printf("%s - %s",updaterMD5.GetData(),updater.Get()); } } else return true; } // Prepare to enter selfupdating mode #ifdef CS_PLATFORM_WIN32 UpdateUpdater(); #else // ToDo: Add *NIX here csString mesg; mesg = "WARNING: New updater can't be fetched since your OS isn't yet supported for automatic updating. "; mesg += "You should be able to fetch and install it manually either from the mainsite or from the forums"; PawsManager::GetSingleton().CreateWarningBox(mesg); psupdaterengine->OutToScreen("Sleeping for 10 seconds for you to read the message..."); csSleep(10000); psupdaterengine->OutToScreen("Going to continue with the update, unexpected things may occurr"); return true; // ToDo: Add *NIX and some MacOS stuff here #endif //Quit this updater psupdaterengine->QuitClient(); // avoid the clean up or the self-update will not work cleanupNeeded = false; return false; } void Updater::CopyDirectory(const char* vfsPath,const char* to) { csRef files = vfs->FindFiles(vfsPath); csString root = vfsPath; // Copy all files for (size_t i = 0;i < files->Length();i++) { csString fileName = files->Get(i); fileName = fileName.Slice(root.Length(),fileName.Length()-root.Length()); if(fileName.Slice(fileName.Length()-1,1) == "/") { CopyDirectory(root + fileName,to + fileName); continue; } if(!CopyFile(root + fileName,to + fileName,true)) printf("ERROR! Couldn't copy selfupdating file (%s)!\n",fileName.GetData()); } } bool Updater::UpdateUpdater() { #ifdef CS_PLATFORM_WIN32 int version = newregistry->GetUpdaterVersion(); csString fileName = "updater"; fileName += version; fileName +=".zip"; csString ver; ver = version; for(size_t i = 1;i < ver.Length();i++) { ver.Insert(i,'.'); i++; // Jump twice } if(version != UPDATER_VERSION) psupdaterengine->OutToScreen( "Updating updater to version " + ver ); else psupdaterengine->OutToScreen( "Updating updater to another build"); bool error = false; MakeDirectory("updatertemp"); if (downloader->DownloadFile("updatertemp/updater.zip",config->GetCurrentFilesDirectory() + csString("/") + fileName,false)) { // Copy all files in the zip if (!vfs->Mount("/zip","updatertemp/updater.zip" )) { printf("Couldn't mount VFS path!\n"); error=true; } if(!error) { // Copy all files CopyDirectory("/zip/","/this/updatertemp/"); Sleep(5000); if (!vfs->Unmount( "/zip","updatertemp/updater.zip" )) { printf("Couldn't unmount VFS path!\n"); error = true; } } } else { error = true; } if(error) { MessageBox(0,"Updating failed!\nPlease try again later","Updater - Selfupdating",MB_ICONWARNING); psupdaterengine->QuitClient(); return false; } // Delete the temp file psupdaterengine->GetVFS()->DeleteFile("/this/updatertemp/updater.zip"); // Delete unwanted files psupdaterengine->GetVFS()->DeleteFile("/this/updatertemp/version.dat"); // Launch the new updater csString p; p = "\"updatertemp\\updater.exe\" -install=.."; printf("Launching updatertemp\\updater.exe -install=.."); psupdaterengine->LaunchProgram(p,"updatertemp"); psupdaterengine->QuitClient(); #else printf("Can't selfupdate on other systems than Windows, sorry"); #endif return true; } void Updater::CleanUp() { if (vfs->Exists("/this/updatertemp")) { #ifdef CS_PLATFORM_WIN32 // 9x doesn't have rd /S csRef files = vfs->FindFiles("/this/updatertemp/"); // remove all files for (size_t i = 0;i < files->Length();i++) { csString fileName = files->Get(i); fileName = fileName.Slice(6,fileName.Length()-6); fileName = ConvertToSystemPath(fileName); DeleteFileA(fileName.GetData()); // Win32 api to remove zip files, VFS can't do that } RemoveDirectory("updatertemp"); #else system("rm -fr updatertemp"); #endif } } void Updater::Compare() { if (!DownloadRegistry ()) { printf("Couldn't download updateregistry from server!\n"); return; } ChangeList list; if (CheckUpdates (list) ) { if (list.Length()) { for (size_t i=0; ifile; if (change.file->inZip) { entry += " (ZIP)"; } psupdaterengine->OutToScreen(entry); } } else { psupdaterengine->OutToScreen("No changed files found"); } } } bool Updater::IsRunning() { return running; } void Updater::CreateToDoList(ChangeList* changelist,int mirror) { // Print all address to the files to download Mirror* mir = GetConfig()->GetMirror(mirror); if(!mir) { psupdaterengine->CriticalError("Couldn't find mirror %d!",mirror); return; } csString todo; for(size_t i = 0;i < changelist->Length();i++) { Change change = changelist->Get(i); if(change.type == Change::UPDATEFILE ||change.type == Change::CREATEFILE ||change.type == Change::UPDATEZIP ) { todo += mir->GetBaseURL(); todo += mir->GetFilesDirectory(); todo.Append ("/"); todo.Append (change.module); todo.Append ("/"); if(change.type == Change::UPDATEZIP) { csString zip = change.file->parentZip; zip = zip.Slice(0,zip.FindLast('.')) + "_all.zip"; todo.Append (zip); } else todo.Append (change.file->file); todo.Append ("\n"); } } printf("ToDo:\n%s",todo.GetData()); psupdaterengine->QuitClient(); } void Updater::RemoveDuplicatedZips(ChangeList* changelist) { // Remove duplicated ZIPs if we are downloading whole zips if(GetConfig()->DownloadWholeZips()) { csArray zips; for(size_t z = 0;z < changelist->Length();z++) { Change chg = changelist->Get(z); if(chg.type == Change::UPDATEZIP) { // Don't add it twice or more bool found = false; for(size_t i = 0;i < zips.Length();i++) { if(zips[i] == chg.file->parentZip) { found = true; break; } } if(found) // Found, delete duplicate { //printf("Got duplicate of zip %s (%d) removing..\n",zips[i].GetData(),z); changelist->DeleteIndex(z); z--; } else // Not found, add zip to list { //printf("Zip to be updated: %s (%d)\n",chg.file->parentZip.GetData(),z); zips.Push(chg.file->parentZip); } continue; } } } } size_t Updater::GetBytesInZip(const char* zip) { if(!zip) return 0; // Mount ZIP to check if (!vfs->Mount("/zip",csString("$^")+zip)) { printf("Couldn't mount temporary VFS path (%s)!\n",zip); return 0; } // Get em size_t files = GetBytesInZipDir(""); if (!vfs->Unmount("/zip",csString("$^")+zip)) printf("Couldn't unmount temporary VFS path (%s)!\n",zip); return files; } size_t Updater::GetBytesInZipDir(const char* dir) { if(!dir) return 0; csString zipDir = dir; size_t files = 0; // Get files csRef filesArr = psupdaterengine->GetVFS()->FindFiles("/zip/" + zipDir); // Count em baby! for(size_t i = 0; i < filesArr->Length(); i++) { bool dir = false; csString fileName = filesArr->Get(i); if (fileName.GetAt(fileName.Length()-1) == '/') dir = true; if(dir) files += GetBytesInZipDir(fileName.Slice(5,fileName.Length())); else { size_t size = 0; psupdaterengine->GetVFS()->GetFileSize(fileName,size); files += size; } } return files; } void Updater::DoZipPercentCheck(ChangeList* changelist) { // This is a quite time consuming function, but the user has got time csPDelArray zips; for(size_t i = 0; i < changelist->Length();i++) { size_t z; if(changelist->Get(i).file->parentZip == "") continue; if(changelist->Get(i).type != Change::UPDATEFILE && changelist->Get(i).type != Change::CREATEFILE) continue; bool found = false; for(z = 0; z < zips.Length();z++) { if(zips[z]->zip == changelist->Get(i).file->parentZip) { found = true; break; } } if(!found) { Updater::ZipPercent* zipPer = new Updater::ZipPercent; zipPer->zip = changelist->Get(i).file->parentZip; zipPer->update = changelist->Get(i).file->size; zipPer->bytes = GetBytesInZip(zipPer->zip); zipPer->oneFile = changelist->Get(i).file; zipPer->module = changelist->Get(i).module; zips.Push(zipPer); } else { Updater::ZipPercent* zipPer = zips[z]; zipPer->update += changelist->Get(i).file->size; } } // Check our %s for(size_t z = 0; z < zips.Length();z++) { Updater::ZipPercent* zipPer = zips[z]; double result = zipPer->update; if(zipPer->bytes != 0) { result /= zipPer->bytes; result *= 100; } else result = 100; if(result > GetConfig()->GetWholeZipPrecent()) { for(size_t i = 0; i < changelist->Length();i++) { if(changelist->Get(i).file->parentZip != zipPer->zip) continue; changelist->DeleteIndex(i); i--; } changelist->Push(Change (Change::UPDATEZIP, zipPer->module, zipPer->oneFile)); } } } void Updater::Size( size_t size, float& fsize, csString& unit ) { //Make it not so many numbers.. if (size > 1024 && size < (1024 * 1024)) { fsize = size / 1024; unit = "KB"; } else if ( size > (1024*1024) ) { fsize = float(size / 1024) / 1024; unit = "MB"; } else { fsize = size; unit = "B"; } } bool Updater::IsSpecialZip( csString& zip ) { size_t findSubDir = zip.FindFirst('/'); return (findSubDir != (size_t)-1); } bool Updater::HandleSpecialZipUpdate( Change& change ) { RegistryFile* file = change.file; printf("Registery File: %s\n", file->file.GetData() ); // Figures out the name of the zip file we need. For example something inside the // maps cache dir this generates the name cache.zip csString specialZipDownload = file->file.Slice(file->parentZip.Length()+1); size_t endPos = specialZipDownload.FindFirst('/'); specialZipDownload = specialZipDownload.Slice(0,endPos); specialZipDownload.Append(".zip"); printf("Downloading Zip: %s\n", specialZipDownload.GetData() ); // Figure out where in the repository this file actually is. csString serverpath = config->GetCurrentFilesDirectory(); serverpath.Append("/"); serverpath.Append(change.module); serverpath.Append("/"); serverpath.Append(file->parentZip); serverpath.Append("/"); serverpath.Append(specialZipDownload); printf("Download: %s URL: %s\n", config->GetTempFile().GetData(), serverpath.GetData() ); if ( specialsDownloaded.Contains(serverpath) != csArrayItemNotFound ) { return true; } if (!downloader->DownloadFile (config->GetTempFile().GetData(), serverpath, true)) { printf("Failed to download!\n"); failed.Push(file->file); return false; } specialsDownloaded.Push( serverpath ); // Mount the zip file that this special zip is for. Mount it as /special/ printf("Mounting ZIP file %s..\n",file->parentZip.GetData()); if (!vfs->Mount("/special",csString("$^")+file->parentZip.GetData() )) { printf("Couldn't mount VFS path!\n"); return false; } // Take the name and remove the .zip part from it to get the sub directory name. csString directory = specialZipDownload.Slice(0, specialZipDownload.Length() - 4 ); // Mount the file that was just downloaded as /downzip/ printf("Mounting /downzip /this/%s\n", config->GetTempFile().GetData() ); if (!vfs->Mount("/downzip", config->GetTempFile().GetData())) { printf("Couldn't mount downloaded file\n"); return false; } // Copy all the files from one mount point to the other in the correct location. csRef buff = vfs->FindFiles("/downzip/"); for ( size_t z = 0; z < buff->Length(); z++ ) { bool isDirectory = false; csString fileName = buff->Get(z); if ( fileName.GetAt(fileName.Length()-1) == '/' ) { isDirectory = true; } fileName = fileName.Slice(9, fileName.Length()); if ( isDirectory ) { csString subPath = buff->Get(z); CopyZipDir(subPath.Slice(9, subPath.Length()), directory); } else { //printf("Copy /downzip/%s to /special/%s/%s \n", fileName.GetData(), directory.GetData(), fileName.GetData() ); csString from; from.Format("/downzip/%s", fileName.GetData() ); csString to; to.Format("/special/%s/%s", directory.GetData(), fileName.GetData() ); psupdaterengine->GetUpdater()->CopyFile( from, to, true ); } } vfs->Sync(); vfs->Unmount( "/downzip/", config->GetTempFile() ); vfs->Unmount( "/special", csString("$^")+file->parentZip.GetData() ); return true; } void Updater::UpdateCreateFile( Change& change ) { csString serverpath = config->GetCurrentFilesDirectory(); serverpath.Append ("/"); serverpath.Append (change.module); serverpath.Append ("/"); serverpath.Append (change.file->file); size_t size = change.file->size; float fsize; csString unit; Size( size, fsize, unit ); csString output; output.Format("%s (%0.2f %s)",(const char*) change.file->file, fsize,unit.GetData()); // Notify the user if (!psupdaterengine->IsSilent()) { pawsUpdaterWindow* updWnd = updWnd = (pawsUpdaterWindow*)PawsManager::GetSingleton().FindWidget("updater"); psupdaterengine->LockStatusMutex(); updWnd->UpdateFileSize(output.GetData(),change.file->size); psupdaterengine->UnlockStatusMutex(); } else printf("%s ",output.GetData()); bool isSpecial = IsSpecialZip( change.file->zipFile ); if ( isSpecial ) { HandleSpecialZipUpdate( change ); return; } // Now do the download if (!downloader->DownloadFile (config->GetTempFile().GetData(), serverpath, true)) { failed.Push(change.file->file); return; } // Check MD5 MD5Sum md5; md5.ReadFile ("/this/" + config->GetTempFile(), false); if (! (md5 == change.file->md5)) { printf("MD5 sum doesn't match after downloading %s. Skipping\n", (const char*) change.file->file); failed.Push(change.file->file); return; } //Merge file with ZIP if (change.file->parentZip != "") { csString path = change.file->parentZip; FileStat* stat = StatFile(path); if(stat && stat->readonly) { printf("Won't write to %s, because it's readonly\n",path.GetData()); return; } // Keeper of the sync number static int syncNumber = 0; // Was this the prev zip? if(prevZip != change.file->parentZip) { // Sync if(prevZip != "") { psupdaterengine->OutToScreen("Syncing zip.."); if (!vfs->Sync()) { printf("Couldn't sync VFS!\n"); return; } syncNumber = 0; } // Unmount prev zip if(prevZip.GetData() != NULL) { printf("Unmounting ZIP file %s..\n",prevZip.GetData()); if (!vfs->Unmount( "/zip",prevZip.GetData() )) { printf("Couldn't unmount VFS path!\n"); return; } } // Mount new zip printf("Mounting ZIP file %s..\n",change.file->parentZip.GetData()); if (!vfs->Mount("/zip",csString("$^")+change.file->parentZip.GetData() )) { printf("Couldn't mount VFS path!\n"); return; } // Update prev zip prevZip = csString("$^")+change.file->parentZip; } if(!CopyFile( "/this/" + config->GetTempFile() ,"/zip/" + change.file->zipFile, true)) { failed.Push(change.file->file); return; } // Check the sync number if it's time to sync the zip now if(syncNumber == GetConfig()->ZipSyncNumber()) { psupdaterengine->OutToScreen("Syncing zip.."); if (!vfs->Sync()) { printf("Couldn't sync VFS!\n"); return; } syncNumber = 0; } syncNumber++; csString doneStr("Merged "); doneStr += change.file->file; psupdaterengine->OutToScreen(doneStr.GetData()); return; } //Copy temp file to real location if(!CopyFile(config->GetTempFile(),change.file->file,false,true)) { #ifdef CS_PLATFORM_WIN32 printf("Assuming file in use: %s... ",change.file->file.GetData()); // If we failed to copy file, it might be in use if(!psupdaterengine->GetVFS()->Exists("/this/updatertemp")) MakeDirectory("updatertemp"); if(!CopyFile(config->GetTempFile(),change.file->file)) { printf("Failed\n"); system("move updatertemp\\" + change.file->file); } else { printf("Success\n"); return; } #endif printf("Failed to update %s!\n",change.file->file.GetData()); failed.Push(change.file->file); return; } csString doneStr("Updated "); doneStr += change.file->file; psupdaterengine->OutToScreen(doneStr.GetData()); //downloadedbytes += changelist[i].file->size; } void Updater::UpdateZip( Change& change ) { // We got orders to download a whole zip file, let's do it RegistryFile* downFile = change.file; // Create the URL csString serverpath = config->GetCurrentFilesDirectory(); serverpath.Append ("/"); serverpath.Append (change.module); serverpath.Append ("/"); size_t size = change.file->parentSize; float fsize; csString unit; Size( size, fsize, unit ); csString output; output.Format("%s (%0.2f %s)",(const char*) downFile->parentZip, fsize,unit.GetData()); // Notify the user if (!psupdaterengine->IsSilent()) { pawsUpdaterWindow* updWnd = updWnd = (pawsUpdaterWindow*)PawsManager::GetSingleton().FindWidget("updater"); psupdaterengine->LockStatusMutex(); updWnd->UpdateFileSize(output,change.file->parentSize); psupdaterengine->UnlockStatusMutex(); } else printf("%s ",downFile->parentZip.GetData()); csString zip = downFile->parentZip; zip = zip.Slice(0,zip.FindLast('.')) + "_all.zip"; // Add the final path to the serverpath serverpath.Append (zip); // Now do the download if (!downloader->DownloadFile (config->GetTempFile().GetData(), serverpath, true)) { failed.Push(zip); return; } // Check MD5 MD5Sum md5; md5.ReadFile ("/this/" + config->GetTempFile(), false); if (! (md5 == change.file->parentMD5)) { printf("MD5 sum doesn't match after downloading %s. Skipping\n", (const char*) change.file->parentZip); failed.Push(change.file->parentZip); return; } //Copy temp file to real location if(!CopyFile(config->GetTempFile(),downFile->parentZip)) { failed.Push(zip); return; } csString doneStr("Got Zip "); doneStr += downFile->parentZip; psupdaterengine->OutToScreen(doneStr.GetData()); } void Updater::UpdateRemoveFile( Change& change ) { csString path; // Figure out the path of the file. Either inside a zip or on /this/ if ( change.file->inZip ) { path = "/zip/" + change.file->file; if ( !vfs->Mount("/zip", csString("$^")+change.file->parentZip.GetData()) ) { printf("Could not mount VFS path\n"); return; } } else { path = "/this/" + change.file->file; } RemoveFile( path ); if ( change.file->inZip ) { if ( !vfs->Unmount("/zip", csString("$^")+change.file->parentZip.GetData()) ) { printf("Could not unmount VFS path\n"); return; } } // Report what was done. csString output; if ( change.type == Change::MOVEFILEAWAY ) output = "Moved "; else output = "Removed "; output.AppendFmt( "%s", change.file->file.GetData() ); psupdaterengine->OutToScreen( output ); if (!psupdaterengine->IsSilent()) { pawsUpdaterWindow* updWnd = updWnd = (pawsUpdaterWindow*)PawsManager::GetSingleton().FindWidget("updater"); psupdaterengine->LockStatusMutex(); updWnd->UpdateFileSize(output.GetData(), change.file->size); psupdaterengine->UnlockStatusMutex(); } } bool Updater::CheckRegistryVersion() { psupdaterengine->OutToScreen("Checking registry version..."); // Get last version data downloaded unsigned int oldVersion = GetVersionFromFile("/this/version.dat"); MakeDirectory("updatertemp"); if ( !downloader->DownloadFile("/updatertemp/version.dat", config->GetCurrentVersionURL(), false, true) ) return false; // Get version data just downloaded unsigned int newVersion = GetVersionFromFile("/this/updatertemp/version.dat"); //psupdaterengine->OutToScreen("%u -> %u",oldVersion,newVersion); if (oldVersion > newVersion) psupdaterengine->OutToScreen("Your version seems newer than server, if you want a clean update, delete version.dat and restart updater."); return oldVersion < newVersion; } unsigned int Updater::GetVersionFromFile(const char* file) { csRef data = psupdaterengine->GetVFS()->ReadFile(file); if (!data) return 0; if ( !ValidateVersionInfo(data->GetData()) ) { printf("Bad version info in file: %s\n", file); psupdaterengine->GetVFS()->DeleteFile(file); return 0; } csString tmp(data->GetData()); tmp.DeleteAt(0,33); return atoi( tmp.GetDataSafe() ); } bool Updater::ValidateVersionInfo(const char* info) { WordArray data(info); return data.GetCount() == 2 && // Exactly 2 segments data[0].Length() == 32 && // First is the 32 character version stamp data.GetInt(1) > 0; // Second is the version number } csString Updater::CreateVersionInfo(Registry* reg) { csString versionStamp( reg->GetReposVersion() ); csString timeStamp; timeStamp.Format("%u", (unsigned int)time(0) ); return versionStamp + " " + timeStamp; } void Updater::MoveFile(const char* from, const char* to) { csRef data = psupdaterengine->GetVFS()->ReadFile(from); if ( data && psupdaterengine->GetVFS()->WriteFile(to, data->GetData(), data->GetSize()) ) { psupdaterengine->GetVFS()->DeleteFile(from); } } } // end of namespace updater