public static bool ProcessContentPatches(string ContentPatchPath, ALFA.Database Database, ACR_ServerCommunicator Script, string ConnectionString)
{
bool ContentChanged = false;
string Version = Database.ACR_GetHAKVersion();
List<ContentPatchFile> PatchFiles = new List<ContentPatchFile>();
ContentPatchPaths LocalPaths = new ContentPatchPaths()
{
HakPath = ALFA.SystemInfo.GetHakDirectory(),
OverridePath = ALFA.SystemInfo.GetOverrideDirectory() + "ACR_ContentPatches"
};
string RemoteContentPatchPath = String.Format("{0}{1}\\{2}", ALFA.SystemInfo.GetCentralVaultPath(), ContentPatchPath, Version);
string FileStoreContentPatchPath = String.Format("{0}/{1}", ContentPatchPath, Version).Replace('\\', '/');
bool RecompileModule = false;
bool SentNotification = false;
Database.ACR_SQLQuery(String.Format(
"SELECT `FileName`, `Location`, `Checksum`, `RecompileModule` FROM `content_patch_files` WHERE `HakVersion` = '{0}'",
Database.ACR_SQLEncodeSpecialChars(Version)));
while (Database.ACR_SQLFetch())
{
ContentPatchFile PatchFile = new ContentPatchFile();
PatchFile.FileName = Database.ACR_SQLGetData(0);
PatchFile.Location = Database.ACR_SQLGetData(1);
PatchFile.Checksum = Database.ACR_SQLGetData(2);
PatchFile.RecompileModule = ALFA.Database.ACR_ConvertDatabaseStringToBoolean(Database.ACR_SQLGetData(3));
if (PatchFile.Location != "override" &&
PatchFile.Location != "hak")
{
continue;
}
if (!ALFA.SystemInfo.IsSafeFileName(PatchFile.FileName))
continue;
PatchFiles.Add(PatchFile);
}
if (!Directory.Exists(LocalPaths.OverridePath))
Directory.CreateDirectory(LocalPaths.OverridePath);
//
// Remove entries in the ACR patch override directory that are not
// listed in the patch table. These may be patches for a previous
// version, and are not applicable.
//
foreach (string DirFile in Directory.GetFiles(LocalPaths.OverridePath))
{
ContentPatchFile FoundPatch = (from PF in PatchFiles
where (PF.FileName.Equals(Path.GetFileName(DirFile), StringComparison.InvariantCultureIgnoreCase) && PF.Location == "override")
select PF).FirstOrDefault();
if (FoundPatch == null)
{
Database.ACR_IncrementStatistic("CONTENT_PATCH_REMOVE_FILE");
Script.WriteTimestampedLogEntry(String.Format(
"ModuleContentPatcher.ProcessContentPatches: Removing extraneous file {0} from {1}", Path.GetFileName(DirFile), LocalPaths.OverridePath));
File.Delete(DirFile);
ContentChanged = true;
}
}
//
// Verify that the MD5 checksum of each of the content files in the
// ACR patch override directory matches the database's expected
// checksum. If not (or if the file in question didn't exist),
// then copy the new version over from the central vault.
//
using (MD5CryptoServiceProvider MD5Csp = new MD5CryptoServiceProvider())
{
foreach (ContentPatchFile PatchFile in PatchFiles)
{
bool Matched = false;
bool Rename = false;
FileUpdateMethod UpdateMethod;
string LocalPath = PatchFile.GetLocalPath(LocalPaths);
string RemotePath = String.Format("{0}\\{1}", RemoteContentPatchPath, PatchFile.FileName);
string FileStorePath = String.Format("{0}/{1}", FileStoreContentPatchPath, PatchFile.FileName).Replace('\\', '/');
string LocalHashString = "<no such file>";
string TransferTempFilePath = LocalPath + ".patchxfer";
if (File.Exists(LocalPath))
{
LocalHashString = GetFileChecksum(LocalPath, MD5Csp);
Matched = (LocalHashString.ToString() == PatchFile.Checksum.ToLower());
}
if (Matched)
{
Script.WriteTimestampedLogEntry(String.Format(
"ModuleContentPatcher.ProcessContentPatches: Content patch file {0} is up to date (checksum {1}).",
PatchFile.FileName,
LocalHashString));
}
else
{
if (File.Exists(TransferTempFilePath))
{
try
{
File.Delete(TransferTempFilePath);
}
catch (Exception e)
{
Script.WriteTimestampedLogEntry(String.Format(
"ModuleContentPatcher.ProcessContentPatches: Warning: Exception {0} removing transfer temp file {1} for transfer file {2}.",
e,
TransferTempFilePath,
PatchFile.FileName));
}
}
Script.WriteTimestampedLogEntry(String.Format(
"ModuleContentPatcher.ProcessContentPatches: Content patch file {0} needs to be updated (local checksum {1}, remote checksum {2}). Copying file...",
PatchFile.FileName,
LocalHashString,
PatchFile.Checksum));
if (PatchFile.RecompileModule)
{
Script.WriteTimestampedLogEntry(String.Format(
"ModuleContentPatcher.ProcessContentPatches: Content patch file {0} requires a module recompile, flagging module for recompilation.",
PatchFile.FileName));
RecompileModule = true;
}
if (!SentNotification)
{
Script.SendInfrastructureIrcMessage(String.Format(
"Server '{0}' is applying a content patch, and will restart shortly.",
Script.GetName(Script.GetModule())));
SentNotification = true;
}
Script.SendInfrastructureDiagnosticIrcMessage(String.Format(
"Server '{0}' is updating content file '{1}'.",
Script.GetName(Script.GetModule()),
PatchFile.FileName));
//
// The file needs to be updated. Copy it over and
// re-validate the checksum. If the checksum did not
// match, log an error and delete the file.
//
try
{
try
{
//
// Try first to download via the file store
// provider. If that fails (e.g. the file
// store is not supported), then fall back to
// the traditional remote vault share transfer
// mechanism.
//
try
{
DownloadContentPatchFromFileStore(FileStorePath, TransferTempFilePath, ConnectionString, Script);
}
catch (Exception e)
{
Script.WriteTimestampedLogEntry(String.Format("ModuleContentPatcher.ProcessContentPatches: Couldn't retrieve uncompressed file {0} from Azure, falling back to file share, due to exception: {1}", FileStorePath, e));
File.Copy(RemotePath, TransferTempFilePath, true);
}
}
catch
{
throw;
}
UpdateMethod = PatchFile.UpdateMethod;
//
// If we are patching a hak, rename it away so that
// the file can be written to.
//
switch (UpdateMethod)
{
case FileUpdateMethod.FileUpdateMethodRename:
if (File.Exists(LocalPath))
{
string OldFileName = LocalPath + ".old";
if (File.Exists(OldFileName))
File.Delete(OldFileName);
File.Move(LocalPath, OldFileName);
Rename = true;
}
break;
case FileUpdateMethod.FileUpdateMethodDirectReplace:
break;
}
Database.ACR_IncrementStatistic("CONTENT_PATCH_" + PatchFile.Location.ToUpper());
try
{
if (Rename)
File.Move(TransferTempFilePath, LocalPath);
else
File.Copy(TransferTempFilePath, LocalPath, true);
}
catch
{
if (UpdateMethod == FileUpdateMethod.FileUpdateMethodRename)
{
string OldFileName = LocalPath + ".old";
try
{
if (File.Exists(LocalPath))
File.Delete(LocalPath);
}
catch
{
}
File.Move(OldFileName, LocalPath);
}
else
{
File.Delete(LocalPath);
}
throw;
}
if (GetFileChecksum(LocalPath, MD5Csp) != PatchFile.Checksum.ToLower())
{
Script.WriteTimestampedLogEntry(String.Format(
"ModuleContentPatcher.ProcessContentPatches: Content patch file {0} was copied, but checksum did not match {1}! This may indicate a configuration error in the database, or file corruption in transit.",
PatchFile.FileName,
PatchFile.Checksum));
Script.SendInfrastructureDiagnosticIrcMessage(String.Format(
"Server '{0}' had checksum mismatch for content patch file {1} after hotfix file deployment, rolling back file.",
Script.GetName(Script.GetModule()),
PatchFile.FileName));
if (UpdateMethod == FileUpdateMethod.FileUpdateMethodRename)
{
string OldFileName = LocalPath + ".old";
try
{
if (File.Exists(LocalPath))
File.Delete(LocalPath);
}
catch
{
}
File.Move(OldFileName, LocalPath);
}
else
{
File.Delete(LocalPath);
}
Database.ACR_IncrementStatistic("CONTENT_PATCH_INCORRECT_CHECKSUM");
}
else
{
ContentChanged = true;
Database.ACR_IncrementStatistic("CONTENT_PATCH_UPDATED_FILE");
Script.WriteTimestampedLogEntry(String.Format(
"ModuleContentPatcher.ProcessContentPatches: Successfully updated content patch file {0}.",
PatchFile.FileName));
}
}
catch (Exception e)
{
Script.WriteTimestampedLogEntry(String.Format(
"ModuleContentPatcher.ProcessContentPatches: Exception {0} updating content patch file {1}.",
e,
PatchFile.FileName));
Script.SendInfrastructureDiagnosticIrcMessage(String.Format(
"Server '{0}' encountered exception deploying content patch file {1}, rolling back file.",
Script.GetName(Script.GetModule()),
PatchFile.FileName));
}
}
if (File.Exists(TransferTempFilePath))
{
try
{
File.Delete(TransferTempFilePath);
}
catch (Exception e)
{
Script.WriteTimestampedLogEntry(String.Format(
"ModuleContentPatcher.ProcessContentPatches: Warning: Exception {0} removing transfer temp file {1} for transfer file {2} after patching completed.",
e,
TransferTempFilePath,
PatchFile.FileName));
}
}
}
}
//
// Update autodownloader configuration, as necessary.
//
try
{
if (ProcessModuleDownloaderResourcesUpdates(Database, Script))
{
Script.WriteTimestampedLogEntry("ModuleContentPatcher.ProcessContentPatches: Autodownloader configuration updated.");
ContentChanged = true;
}
}
catch (Exception e)
{
Script.WriteTimestampedLogEntry(String.Format(
"ModuleContentPatcher.ProcessContentPatches: Warning: Exception {0} updating autodownloader configuration.",
e));
Script.SendInfrastructureDiagnosticIrcMessage(String.Format(
"Server '{0}' encountered exception updating autodownloader configuration.",
Script.GetName(Script.GetModule())));
}
if (ContentChanged)
{
if (RecompileModule)
{
Script.WriteTimestampedLogEntry("ModuleContentPatcher.ProcessContentPatches: A module recompile is required; recompiling module...");
CompileModuleScripts(Script, Database);
}
Database.ACR_IncrementStatistic("CONTENT_PATCH_REBOOT");
Script.SendInfrastructureDiagnosticIrcMessage(String.Format(
"Server '{0}' restarting after content patch deployment (old HAK ACR version {1} build date {2}, old module ACR version {3}).",
Script.GetName(Script.GetModule()),
Database.ACR_GetHAKVersion(),
Database.ACR_GetHAKBuildDate(),
Database.ACR_GetVersion()));
}
return ContentChanged;
}