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.Cache { /// /// Handle sessions caching and storage. /// public static class SessionCache { const string filename = "cache.bin"; 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.CacheType == CacheType.DISK && updatetimer.Enabled == true) { pendingadds.Add(new KeyValuePair(login, session)); }else if (Settings.CacheType == 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 = filename; 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) { 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 (File.Exists(filename)) { try { using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { sessions = (Dictionary)formatter.Deserialize(fs); return true; } } catch (IOException ex) { Console.WriteLine("Error reading cached sessions from disk: " + ex.Message); } catch (SerializationException) { Console.WriteLine("Malformed sessions from cache file "); } } return false; } /// /// Saves SessionToken's from SessionCache into cache file. /// private static void SaveToDisk() { bool fileexists = File.Exists(filename); using (FileStream fs = new FileStream(filename, 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; } } } }