/// <summary> /// Download a content patch file from a file store. Currently, Azure /// file stores are assumed. Both compressed and uncompressed versions /// of the file are tried in respective order. /// </summary> /// <param name="FileStorePath">Supplies the remote file name that /// designates the file to download.</param> /// <param name="LocalFileName">Supplies the local file name to /// download to.</param> /// <param name="ConnectionString">Supplies the file store connection /// string.</param> /// <param name="Script">Supplies the script object.</param> private static void DownloadContentPatchFromFileStore(string FileStorePath, string LocalFileName, string ConnectionString, ACR_ServerCommunicator Script) { if (String.IsNullOrEmpty(ConnectionString)) { throw new NotSupportedException(); } // // Initialize the file store provider. // FileStore UpdaterStore = FileStoreProvider.CreateAzureFileStore(ConnectionString); FileStoreContainer UpdaterContainer = UpdaterStore.GetContainerReference(FileStoreNamespace.ACRUpdater); FileStoreFile UpdaterFile = UpdaterContainer.GetFileReference(FileStorePath + ".gzip"); // // First attempt to retrieve a gzip compressed version of the file // to patch. If that fails then fall back to a plaintext version. // try { using (MemoryStream MemStream = new MemoryStream()) { UpdaterFile.Read(MemStream); MemStream.Position = 0; using (FileStream OutStream = File.Create(LocalFileName)) { using (GZipStream CompressedStream = new GZipStream(MemStream, CompressionMode.Decompress)) { CompressedStream.CopyTo(OutStream); } } } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format("ModuleContentPatcher.DownloadContentPatchFromFileStore: Couldn't retrieve compressed file {0} from Azure, trying uncompressed file, due to exception: {1}", FileStorePath, e)); UpdaterFile = UpdaterContainer.GetFileReference(FileStorePath); using (FileStream OutStream = File.Create(LocalFileName)) { UpdaterFile.Read(OutStream); } } }
/// <summary> /// Check if a file store file exists. /// </summary> /// <param name="FileStorePath">Supplies the remote file name that /// designates the file to check for existance.</param> /// <param name="ConnectionString">Supplies the file store connection /// string.</param> /// <returns>True if the file store file exists.</returns> private static bool ContentPatchFileStoreFileExists(string FileStorePath, string ConnectionString) { if (String.IsNullOrEmpty(ConnectionString)) { return(false); } // // Initialize the file store provider. // FileStore UpdaterStore = FileStoreProvider.CreateAzureFileStore(ConnectionString); FileStoreContainer UpdaterContainer = UpdaterStore.GetContainerReference(FileStoreNamespace.ACRUpdater); FileStoreFile UpdaterFile = UpdaterContainer.GetFileReference(FileStorePath + ".gzip"); try { if (UpdaterFile.Exists()) { return(true); } } catch { } UpdaterFile = UpdaterContainer.GetFileReference(FileStorePath); try { if (UpdaterFile.Exists()) { return(true); } } catch { } return(false); }
/// <summary> /// Synchronize a single character file from the spool directory to the /// remote storage provider. The contents of the file to transfer are /// already in memory. /// /// This function is called from the transfer synchronization thread. /// </summary> /// <param name="AccountName">Supplies the account name.</param> /// <param name="CharacterFileName">Supplies the character file name, /// without path (e.g. character.bic).</param> /// <param name="Data">Supplies a pointer to the BIC memory /// image.</param> /// <param name="Length">Supplies the length, in bytes, of the BIC to /// transfer.</param> /// <param name="Context">Supplies a context handle.</param> /// <returns>TRUE on success.</returns> private static int OnSynchronizeAccountFileToVault(string AccountName, string CharacterFile, IntPtr Data, IntPtr Length, IntPtr Context) { try { // // Pass through to the default implementation if the connection // string is not defined in the database. // if (String.IsNullOrEmpty(StoreConnectionString)) { return(1); } try { if (Length == IntPtr.Zero) { return(0); } // // Canonicalize names to lowercase as the file store may be // case sensitive and maintaining a mapping table in the // database is problematic since the first save for a new // account may be observed before the players record for // that player is created (and a player could log in to two // servers simultaneously and create orphaned records that // way, or similarly during a database outage, etc.). // // The original filename is stored as metadata to keep local // filesystems case preserving on servers. // string OriginalFileName = AccountName + "/" + CharacterFile; AccountName = AccountName.ToLowerInvariant(); CharacterFile = CharacterFile.ToLowerInvariant(); // // Get the file store file for the character file and // replace it with the new character file. // FileStoreDirectory StoreDirectory = Container.GetDirectoryReference(AccountName); FileStoreFile StoreFile = StoreDirectory.GetFileReference(CharacterFile); byte[] CharacterData = new byte[(int)Length]; Marshal.Copy(Data, CharacterData, 0, CharacterData.Length); StoreFile.Metadata["OriginalFileName"] = OriginalFileName; StoreFile.Write(new MemoryStream(CharacterData)); if (VerboseLoggingEnabled) { Logger.Log("ServerVaultConnector.OnSynchronizeAccountFileToVault: Uploaded vault file '{0}\\{1}'.", AccountName, CharacterFile); } return(1); } catch (Exception e) { Logger.Log("ServerVaultConnector.OnSynchronizeAccountFileToVault('{0}', '{1}'): Exception: {2}", AccountName, CharacterFile, e); throw; } } catch { return(0); } }
static void Main(string[] args) { string ConnectionString = null; string DownloadTo = null; string UploadFrom = null; string DeleteFileName = null; bool IncludeAllFiles = false; Console.CancelKeyPress += Console_CancelKeyPress; Console.WriteLine("ALFA Azure Vault Management Tool v{0}", Assembly.GetExecutingAssembly().GetName().Version); Console.WriteLine(); for (int i = 0; i < args.Length; i += 1) { if (i + 1 < args.Length && args[i].ToLowerInvariant() == "-connectionstring") { ConnectionString = args[i + 1]; i += 1; continue; } else if (i + 1 < args.Length && args[i].ToLowerInvariant() == "-download") { DownloadTo = args[i + 1]; i += 1; continue; } else if (i + 1 < args.Length && args[i].ToLowerInvariant() == "-upload") { UploadFrom = args[i + 1]; i += 1; continue; } else if (i + 1 < args.Length && args[i].ToLowerInvariant() == "-delete") { DeleteFileName = args[i + 1]; i += 1; continue; } else if (args[i].ToLowerInvariant() == "-all") { IncludeAllFiles = true; continue; } Console.WriteLine("Unrecognized argument: {0}", args[i]); PrintUsage(); return; } if ((String.IsNullOrEmpty(ConnectionString)) || ((String.IsNullOrEmpty(DownloadTo) ? 0 : 1) + (String.IsNullOrEmpty(UploadFrom) ? 0 : 1) + (String.IsNullOrEmpty(DeleteFileName) ? 0 : 1) != 1)) { PrintUsage(); return; } try { FileStore Store = FileStoreProvider.CreateAzureFileStore(ConnectionString); FileStoreContainer Container = Store.GetContainerReference(FileStoreNamespace.ServerVault); if (!String.IsNullOrEmpty(DownloadTo)) { Console.WriteLine("Downloading from vault to {0} in (CTRL+C in 5 seconds to cancel)...", DownloadTo); Thread.Sleep(5000); foreach (FileStoreDirectory Directory in Container.GetDirectories()) { foreach (FileStoreFile File in Directory.GetFiles()) { string RemoteName; if (AbortProgram) { Console.WriteLine("Cancelled."); return; } // // Attempt to create the local file using the // original filename case. // File.FetchAttributes(); if (File.Metadata.TryGetValue("OriginalFileName", out RemoteName)) { if (RemoteName.ToLowerInvariant() != File.Name) { Console.WriteLine("* File {0} OriginalFileName {1} is malformed, ignoring.", File.Name, RemoteName); RemoteName = File.Name; } } else { RemoteName = File.Name; } string RemoteDirName = Path.GetDirectoryName(RemoteName); string RemoteFileName = Path.GetFileName(RemoteName); if (!IncludeAllFiles && !RemoteFileName.ToLowerInvariant().EndsWith(".bic")) { Console.WriteLine("* File {0} is not a character file, skipping...", RemoteName); continue; } if (!ALFA.SystemInfo.IsSafeFileName(RemoteFileName) || RemoteDirName.IndexOfAny(Path.GetInvalidPathChars()) != -1) { Console.WriteLine("* File {0} has an unsafe filename, skipping...", RemoteName); continue; } string FileName = String.Format("{0}{1}{2}", DownloadTo, Path.DirectorySeparatorChar, RemoteDirName); FileStream FsFile = null; DateTimeOffset?Offset = null; if (!System.IO.Directory.Exists(FileName)) { System.IO.Directory.CreateDirectory(FileName); } FileName = String.Format("{0}{1}{2}", FileName, Path.DirectorySeparatorChar, RemoteFileName); // // Download the file if it didn't exist locally, or // if the local file was older than the vault based // file. // try { try { FsFile = System.IO.File.Open(FileName, FileMode.Open, FileAccess.ReadWrite); Offset = System.IO.File.GetLastWriteTimeUtc(FileName); } catch (DirectoryNotFoundException) { } catch (FileNotFoundException) { } if (FsFile == null) { FsFile = System.IO.File.Open(FileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); Offset = null; } if (Offset != null) { Console.Write("Downloading file {0}...", FileName); try { try { File.ReadIfModifiedSince(FsFile, Offset.Value); } catch { FsFile.Dispose(); System.IO.File.SetLastWriteTimeUtc(FileName, Offset.Value.UtcDateTime); throw; } FsFile.Dispose(); System.IO.File.SetLastWriteTimeUtc(FileName, File.LastModified.Value.UtcDateTime); Console.WriteLine(" done."); } catch (FileStoreConditionNotMetException) { Console.WriteLine(" already up to date."); } } else { Console.Write("Downloading file {0}...", FileName); try { File.Read(FsFile); } catch { FsFile.Dispose(); System.IO.File.Delete(FileName); } FsFile.Dispose(); System.IO.File.SetLastWriteTimeUtc(FileName, File.LastModified.Value.UtcDateTime); Console.WriteLine("done."); } } finally { FsFile.Dispose(); } } } } else if (!String.IsNullOrEmpty(UploadFrom)) { Console.WriteLine("Uploading from {0} to vault (CTRL+C in 5 seconds to cancel)...", UploadFrom); Thread.Sleep(5000); Container.CreateIfNotExists(); // // Canonicalize the upload from base path so that relative // filenames can be ascertained for naming files that are // transferred to the vault. // UploadFrom = Path.GetFullPath(UploadFrom); if (UploadFrom.EndsWith(Path.DirectorySeparatorChar.ToString())) { UploadFrom = UploadFrom.Substring(0, UploadFrom.Length - 1); } foreach (string FsFileName in Directory.EnumerateFiles(UploadFrom, "*.bic", SearchOption.AllDirectories)) { string RemoteName; if (AbortProgram) { Console.WriteLine("Cancelled."); return; } if (!IncludeAllFiles && !FsFileName.ToLowerInvariant().EndsWith(".bic")) { Console.WriteLine("* File {0} is not a character file, skipping...", FsFileName); continue; } // // Upload files that did not exist or which were newer // on the local side. // RemoteName = FsFileName.Substring(UploadFrom.Length + 1); DateTimeOffset FsLastModified = System.IO.File.GetLastWriteTimeUtc(FsFileName); using (FileStream FsFile = System.IO.File.OpenRead(FsFileName)) { Console.Write("Uploading file {0}...", RemoteName); try { FileStoreFile RemoteFile = Container.GetFileReference(RemoteName.Replace('\\', '/').ToLowerInvariant()); try { RemoteFile.FetchAttributes(); if (RemoteFile.LastModified >= FsLastModified) { Console.WriteLine(" already up to date."); continue; } } catch { } RemoteFile.Metadata["OriginalFileName"] = RemoteName.Replace('\\', '/'); RemoteFile.WriteIfNotModifiedSince(FsFile, FsLastModified); Console.WriteLine(" done."); } catch (FileStoreConditionNotMetException) { Console.WriteLine(" already up to date."); } } } } else if (!String.IsNullOrEmpty(DeleteFileName)) { Console.Write("Deleting file {0} from vault (CTRL+C in 5 seconds to cancel)...", DeleteFileName); Thread.Sleep(5000); if (AbortProgram) { Console.WriteLine("Cancelled."); return; } // // Delete vault file if it existed. // FileStoreFile RemoteFile = Container.GetFileReference(DeleteFileName.Replace('\\', '/').ToLowerInvariant()); if (!RemoteFile.Exists()) { Console.Write(" file not found on the vault."); } else { RemoteFile.Delete(); Console.Write(" done"); } } } catch (Exception e) { Console.WriteLine("Exception: {0}", e); return; } }
/// <summary> /// This thread function measures vault latency every /// VAULT_MEASUREMENT_INTERVAL milliseconds. /// /// Note that, because this function does not execute from a script /// context, it cannot call script functions. Instead, a companion /// DelayCommand continuation on the main server thread will check the /// current latency value and save it as appropriate. /// </summary> private static void VaultPingThreadRoutine() { string VaultPingFile = String.Format( "{0}Server{1}.txt", SystemInfo.GetCentralVaultPath(), LocalServerId); for (; ;) { // // Open the ping file on the vault. If we fail to open it then // something has gone wrong with the vault connection. // try { string ConnectionString = FileStoreProvider.DefaultVaultConnectionString; uint Tick = (uint)Environment.TickCount; using (StreamWriter PingFile = File.CreateText(VaultPingFile)) { PingFile.WriteLine( "Server {0} is up at {1}.", LocalServerId, DateTime.UtcNow); } if (!String.IsNullOrEmpty(ConnectionString)) { FileStore Store = FileStoreProvider.CreateAzureFileStore(ConnectionString); FileStoreContainer StoreContainer = Store.GetContainerReference(FileStoreNamespace.ServerVault); FileStoreFile StoreFile = StoreContainer.GetFileReference(String.Format("Server{0}.txt", LocalServerId)); using (MemoryStream MemStream = new MemoryStream()) { StoreFile.Write(MemStream); } } Tick = (uint)Environment.TickCount - Tick; // // Report the response time. // lock (CurrentLatencyLock) { CurrentVaultLatency = (int)Tick; } } catch (Exception e) { Logger.Log("LatencyMonitor.VaultPingThreadRoutine: Exception pinging server vault: {0}", e); // // Report a response time of -1 to indicate that no // measurement could be taken. // lock (CurrentLatencyLock) { CurrentVaultLatency = (int)-1; } } Thread.Sleep(VAULT_MEASUREMENT_INTERVAL); } }