using MinecraftClient.Protocol; using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Timers; namespace MinecraftClient.Protocol.SessionCache { /// /// Handle sessions caching and storage. /// public static class SessionCache { private const string SessionCacheFile = "SessionCache.db"; private static Dictionary sessions = new Dictionary(); private static FileSystemWatcher cachemonitor = new FileSystemWatcher(); private static Timer updatetimer = new Timer(100); private static List> pendingadds = new List>(); private static BinaryFormatter formatter = new BinaryFormatter(); /// /// Retrieve whether SessionCache contains a session for the given login. /// /// User login used with Minecraft.net /// TRUE if session is available public static bool Contains(string login) { return sessions.ContainsKey(login); } /// /// Store a session and save it to disk if required. /// /// User login used with Minecraft.net /// User session token used with Minecraft.net public static void Store(string login, SessionToken session) { if (Contains(login)) { sessions[login] = session; } else { sessions.Add(login, session); } if (Settings.SessionCaching == CacheType.Disk && updatetimer.Enabled == true) { pendingadds.Add(new KeyValuePair(login, session)); } else if (Settings.SessionCaching == CacheType.Disk) { SaveToDisk(); } } /// /// Retrieve a session token for the given login. /// /// User login used with Minecraft.net /// SessionToken for given login public static SessionToken Get(string login) { return sessions[login]; } /// /// Initialize cache monitoring to keep cache updated with external changes. /// /// TRUE if session tokens are seeded from file public static bool InitializeDiskCache() { cachemonitor.Path = AppDomain.CurrentDomain.BaseDirectory; cachemonitor.IncludeSubdirectories = false; cachemonitor.Filter = SessionCacheFile; cachemonitor.NotifyFilter = NotifyFilters.LastWrite; cachemonitor.Changed += new FileSystemEventHandler(OnChanged); cachemonitor.EnableRaisingEvents = true; updatetimer.Elapsed += HandlePending; return LoadFromDisk(); } /// /// Reloads cache on external cache file change. /// /// Sender /// Event data private static void OnChanged(object sender, FileSystemEventArgs e) { updatetimer.Stop(); updatetimer.Start(); } /// /// Called after timer elapsed. Reads disk cache and adds new/modified sessions back. /// /// Sender /// Event data private static void HandlePending(object sender, ElapsedEventArgs e) { updatetimer.Stop(); LoadFromDisk(); foreach(KeyValuePair pending in pendingadds.ToArray()) { Store(pending.Key, pending.Value); pendingadds.Remove(pending); } } /// /// Reads cache file and loads SessionTokens into SessionCache. /// /// True if data is successfully loaded private static bool LoadFromDisk() { if (Settings.DebugMessages) ConsoleIO.WriteLineFormatted("§8Updating session cache from disk"); if (File.Exists(SessionCacheFile)) { try { using (FileStream fs = new FileStream(SessionCacheFile, FileMode.Open, FileAccess.Read, FileShare.Read)) { sessions = (Dictionary)formatter.Deserialize(fs); return true; } } catch (IOException ex) { ConsoleIO.WriteLineFormatted("§8Failed to read session cache from disk: " + ex.Message); } catch (SerializationException ex2) { ConsoleIO.WriteLineFormatted("§8Got malformed data while reading session cache from disk: " + ex2.Message); } } return false; } /// /// Saves SessionToken's from SessionCache into cache file. /// private static void SaveToDisk() { if (Settings.DebugMessages) ConsoleIO.WriteLineFormatted("§8Saving session cache to disk"); bool fileexists = File.Exists(SessionCacheFile); IOException lastEx = null; int attempt = 1; while (attempt < 4) { try { using (FileStream fs = new FileStream(SessionCacheFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) { cachemonitor.EnableRaisingEvents = false; // delete existing file contents if (fileexists) { fs.SetLength(0); fs.Flush(); } formatter.Serialize(fs, sessions); cachemonitor.EnableRaisingEvents = true; } return; } catch (IOException ex) { lastEx = ex; attempt++; System.Threading.Thread.Sleep(new Random().Next(150, 350) * attempt); //CSMA/CD :) } } ConsoleIO.WriteLineFormatted("§8Failed to write session cache to disk" + (lastEx != null ? ": " + lastEx.Message : "")); } } }