/// <summary> /// Synchronize new files from the vault storage provider. The /// SyncPath specifies the path to download changed files from the /// vault storage provider to. It may be populated with existing /// files, and only newer files should be transferred from the vault /// storage provider. /// /// This function is called from the transfer synchronization thread. /// </summary> /// <param name="AccountName">Supplies the account name.</param> /// <param name="SyncPath">Supplies the path to synchronize files to /// from the vault. Existing files should be datestamp compared with /// the vault before being replaced. File are downloaded from the /// vault, not uploaded, with this routine.</param> /// <param name="Context">Supplies a context handle.</param> /// <returns>TRUE on success.</returns> private static int OnSynchronizeAccountFromVault(string AccountName, string SyncPath, 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); } string OriginalAccountName = AccountName; // // 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.). // AccountName = AccountName.ToLowerInvariant(); try { bool DirCreated = false; FileStoreDirectory StoreDirectory = Container.GetDirectoryReference(AccountName); IEnumerable <string> FsFiles = null; Dictionary <string, FileStoreFile> StoreFiles = new Dictionary <string, FileStoreFile>(); // // Build an index of all files in the file store vault. // foreach (FileStoreFile StoreFile in StoreDirectory.GetFiles()) { string[] Segments = StoreFile.Uri.Segments; if (Segments.Length == 0) { continue; } string CharacterFileName = Segments[Segments.Length - 1].ToLowerInvariant(); if (!CharacterFileName.EndsWith(".bic")) { continue; } StoreFiles.Add(CharacterFileName, StoreFile); } // // Enumerate file currently in the file system directory, // transferring each corresponding file from the store // directory if the modified date of the store file is // after the modified date of the local file. // if (Directory.Exists(SyncPath)) { FsFiles = Directory.EnumerateFiles(SyncPath); foreach (string FsFileName in FsFiles) { DateTime FsLastModified = File.GetLastWriteTimeUtc(FsFileName); string CharacterFileName = Path.GetFileName(FsFileName); string Key = CharacterFileName.ToLowerInvariant(); FileStoreFile StoreFile; string TempFileName = null; if (!StoreFiles.TryGetValue(Key, out StoreFile)) { // // This file exists locally but not in the file // store vault. Keep it (any excess files may be // removed by explicit local vault cleanup). // continue; } // // Transfer the file if the file store vault has a more // recent version. // try { TempFileName = Path.GetTempFileName(); try { using (FileStream FsFile = File.Open(TempFileName, FileMode.Create)) { StoreFile.ReadIfModifiedSince(FsFile, new DateTimeOffset(FsLastModified)); } try { File.Copy(TempFileName, FsFileName, true); File.SetLastWriteTimeUtc(FsFileName, StoreFile.LastModified.Value.DateTime); } catch { // // Clean up after a failed attempt to // instantiate copy file. // ALFA.SystemInfo.SafeDeleteFile(FsFileName); throw; } if (VerboseLoggingEnabled) { Logger.Log("ServerVaultConnector.OnSynchronizeAccountFromVault: Downloaded vault file '{0}\\{1}' with modified date {2} -> {3}.", AccountName, CharacterFileName, FsLastModified, File.GetLastWriteTimeUtc(FsFileName)); } } catch (FileStoreConditionNotMetException) { // // This file was not transferred because it is // already up to date. // Logger.Log("ServerVaultConnector.OnSynchronizeAccountFromVault: Vault file '{0}\\{1}' was already up to date with modified date {2}.", AccountName, CharacterFileName, FsLastModified); } } finally { if (!String.IsNullOrEmpty(TempFileName)) { ALFA.SystemInfo.SafeDeleteFile(TempFileName); } } // // Remove this file from the list as it has already // been accounted for (it is up to date or has just // been transferred). StoreFiles.Remove(Key); } } // // Sweep any files that were still not yet processed but // existed in the file store vault. These files are // present on the canonical vault but have not yet been // populated on the local vault, so transfer them now. // foreach (var StoreFile in StoreFiles.Values) { string[] Segments = StoreFile.Uri.Segments; string CharacterFileName = Segments[Segments.Length - 1].ToLowerInvariant(); string FsFileName = SyncPath + Path.DirectorySeparatorChar + CharacterFileName; string TempFileName = null; if (!ALFA.SystemInfo.IsSafeFileName(CharacterFileName)) { throw new ApplicationException("Unsafe filename '" + CharacterFileName + "' on vault for account '" + AccountName + "'."); } if (!DirCreated) { DirCreated = Directory.Exists(SyncPath); // // Create the sync directory if it does not exist. // Attempt to preserve case from the vault store if // possible, but fall back to using the case that // the client specified at login time otherwise. // if (!DirCreated) { try { string OriginalName; StoreFile.FetchAttributes(); OriginalName = StoreFile.Metadata["OriginalFileName"]; if (OriginalName != null && OriginalName.ToLowerInvariant() == AccountName + "/" + CharacterFileName) { OriginalName = OriginalName.Split('/').FirstOrDefault(); DirectoryInfo Parent = Directory.GetParent(SyncPath); Directory.CreateDirectory(Parent.FullName + "\\" + OriginalName); if (VerboseLoggingEnabled) { Logger.Log("ServerVaultConnector.OnSynchronizeAccountFromVault: Created vault directory for account '{0}'.", OriginalName); } DirCreated = true; } } catch (Exception e) { Logger.Log("ServerVaultConnector.OnSynchronizeAccountFromVault: Exception {0} recovering canonical case for creating vault directory '{1}', using account name from client instead.", e, OriginalAccountName); } if (!DirCreated) { Directory.CreateDirectory(SyncPath); DirCreated = true; } } } try { TempFileName = Path.GetTempFileName(); using (FileStream FsFile = File.Open(TempFileName, FileMode.OpenOrCreate)) { StoreFile.Read(FsFile); } try { File.Copy(TempFileName, FsFileName); File.SetLastWriteTimeUtc(FsFileName, StoreFile.LastModified.Value.DateTime); } catch { // // Clean up after a failed attempt to // instantiate a new file. // ALFA.SystemInfo.SafeDeleteFile(FsFileName); throw; } if (VerboseLoggingEnabled) { Logger.Log("ServerVaultConnector.OnSynchronizeAccountFromVault: Downloaded new vault file '{0}\\{1}' with modified date {2}.", AccountName, CharacterFileName, File.GetLastWriteTimeUtc(FsFileName)); } } finally { ALFA.SystemInfo.SafeDeleteFile(TempFileName); } } return(1); } catch (Exception e) { Logger.Log("ServerVaultConnector.OnSynchronizeAccountFromVault('{0}', '{1}'): Exception: {2}", AccountName, SyncPath, e); throw; } } catch { return(0); } }