1 /* 2 * This file is part of the "GKMap". 3 * GKMap project borrowed from GMap.NET (by radioman). 4 * 5 * Copyright (C) 2009-2018 by radioman (email@radioman.lt). 6 * This program is licensed under the FLAT EARTH License. 7 */ 8 9 using System; 10 using System.Collections.Generic; 11 using System.Diagnostics; 12 using System.IO; 13 using System.Net; 14 using System.Net.NetworkInformation; 15 using System.Security.Cryptography; 16 using System.Text; 17 using System.Threading; 18 using GKMap.CacheProviders; 19 using GKMap.MapProviders; 20 21 namespace GKMap 22 { 23 public enum CacheType 24 { 25 GeocoderCache, 26 PlacemarkCache, 27 UrlCache, 28 } 29 30 /// <summary> 31 /// maps manager 32 /// </summary> 33 public class GMaps : Singleton<GMaps> 34 { 35 private volatile bool fAbortCacheLoop; 36 private Thread fCacheThread; 37 private bool fCacheExists = true; 38 private volatile bool fCacheOnIdleRead = true; 39 private string fImageCacheLocation; 40 private int fReadingCache; 41 private readonly Queue<CacheQueueItem> fTileCacheQueue = new Queue<CacheQueueItem>(); 42 43 internal readonly AutoResetEvent WaitForCache = new AutoResetEvent(false); 44 internal volatile bool NoMapInstances = false; 45 46 private static bool fCacheDelay; 47 private static string fCacheLocation; 48 49 50 /// <summary> 51 /// primary cache provider, by default: ultra fast SQLite 52 /// </summary> 53 public IPureImageCache ImageCache { get; private set; } 54 55 public string ImageCacheLocation 56 { 57 get { 58 return fImageCacheLocation; 59 } 60 set { 61 fImageCacheLocation = value; 62 63 var cache = ImageCache as SQLitePureImageCache; 64 if (cache != null) { 65 cache.CacheLocation = value; 66 } 67 68 fCacheDelay = true; 69 } 70 } 71 72 public static string CacheLocation 73 { 74 get { 75 if (string.IsNullOrEmpty(fCacheLocation)) { 76 ResetCacheLocation(); 77 } 78 79 return fCacheLocation; 80 } 81 set { 82 if (string.IsNullOrEmpty(value)) { // setting to null resets to default 83 ResetCacheLocation(); 84 } else { 85 fCacheLocation = value; 86 } 87 88 if (fCacheDelay) { 89 Instance.ImageCacheLocation = fCacheLocation; 90 } 91 } 92 } 93 94 /// <summary> 95 /// MemoryCache provider 96 /// </summary> 97 public readonly MemoryCache MemoryCache = new MemoryCache(); 98 99 /// <summary> 100 /// internal proxy for image management 101 /// </summary> 102 internal static PureImageProxy TileImageProxy; 103 104 GMaps()105 public GMaps() 106 { 107 if (Instance != null) { 108 throw (new Exception("You have tried to create a new singleton class where you should have instanced it. Replace your \"new class()\" with \"class.Instance\"")); 109 } 110 111 ServicePointManager.DefaultConnectionLimit = 5; 112 InitCache(); 113 } 114 InitCache()115 private void InitCache() 116 { 117 ImageCache = new SQLitePureImageCache(); 118 119 string newCache = CacheLocation; 120 string oldCache = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + Path.DirectorySeparatorChar + "GKMap" + Path.DirectorySeparatorChar; 121 122 // move database to non-roaming user directory 123 if (Directory.Exists(oldCache)) { 124 try { 125 if (Directory.Exists(newCache)) { 126 Directory.Delete(oldCache, true); 127 } else { 128 Directory.Move(oldCache, newCache); 129 } 130 ImageCacheLocation = newCache; 131 } catch (Exception ex) { 132 ImageCacheLocation = oldCache; 133 Trace.WriteLine("SQLitePureImageCache, moving data: " + ex); 134 } 135 } else { 136 ImageCacheLocation = newCache; 137 } 138 } 139 140 private static readonly SHA1CryptoServiceProvider HashProvider = new SHA1CryptoServiceProvider(); 141 ConvertToHash(ref string s)142 private static void ConvertToHash(ref string s) 143 { 144 s = BitConverter.ToString(HashProvider.ComputeHash(Encoding.Unicode.GetBytes(s))); 145 } 146 SaveContent(string url, CacheType type, string content)147 public void SaveContent(string url, CacheType type, string content) 148 { 149 if (!fCacheExists) 150 return; 151 152 try { 153 ConvertToHash(ref url); 154 155 string dir = Path.Combine(fImageCacheLocation, type.ToString()) + Path.DirectorySeparatorChar; 156 157 // recreate dir 158 if (!Directory.Exists(dir)) { 159 Directory.CreateDirectory(dir); 160 } 161 162 string file = dir + url + ".txt"; 163 164 using (StreamWriter writer = new StreamWriter(file, false, Encoding.UTF8)) { 165 writer.Write(content); 166 } 167 } catch (Exception ex) { 168 Debug.WriteLine("SaveContent: " + ex); 169 } 170 } 171 GetContent(string url, CacheType type, TimeSpan stayInCache)172 public string GetContent(string url, CacheType type, TimeSpan stayInCache) 173 { 174 if (!fCacheExists) 175 return string.Empty; 176 177 string ret = null; 178 179 try { 180 ConvertToHash(ref url); 181 182 string dir = Path.Combine(fImageCacheLocation, type.ToString()) + Path.DirectorySeparatorChar; 183 string file = dir + url + ".txt"; 184 185 if (File.Exists(file)) { 186 var writeTime = File.GetLastWriteTime(file); 187 if (DateTime.Now - writeTime < stayInCache) { 188 using (StreamReader r = new StreamReader(file, Encoding.UTF8)) { 189 ret = r.ReadToEnd(); 190 } 191 } else { 192 File.Delete(file); 193 } 194 } 195 } catch (Exception ex) { 196 ret = null; 197 Debug.WriteLine("GetContent: " + ex); 198 } 199 200 return ret; 201 } 202 GetContent(string url, CacheType type)203 public string GetContent(string url, CacheType type) 204 { 205 return GetContent(url, type, TimeSpan.FromDays(88)); 206 } 207 ResetCacheLocation()208 private static void ResetCacheLocation() 209 { 210 string appDataLocation = Stuff.GetApplicationDataFolderPath(); 211 212 if (string.IsNullOrEmpty(appDataLocation)) { 213 Instance.fCacheExists = false; 214 } else { 215 CacheLocation = appDataLocation; 216 } 217 } 218 219 /// <summary> 220 /// </summary> Initialize(PureImageProxy imageProxy)221 public static void Initialize(PureImageProxy imageProxy) 222 { 223 TileImageProxy = imageProxy; 224 225 // triggers dynamic SQLite loading, call this before you use SQLite for other reasons than caching maps 226 SQLitePureImageCache.Ping(); 227 } 228 229 /// <summary> 230 /// enqueue tile to cache 231 /// </summary> 232 /// <param name="task"></param> EnqueueCacheTask(CacheQueueItem task)233 private void EnqueueCacheTask(CacheQueueItem task) 234 { 235 lock (fTileCacheQueue) { 236 if (!fTileCacheQueue.Contains(task)) { 237 Debug.WriteLine("EnqueueCacheTask: " + task); 238 239 fTileCacheQueue.Enqueue(task); 240 241 if (fCacheThread != null && fCacheThread.IsAlive) { 242 WaitForCache.Set(); 243 } else if (fCacheThread == null || fCacheThread.ThreadState == System.Threading.ThreadState.Stopped || fCacheThread.ThreadState == System.Threading.ThreadState.Unstarted) { 244 fCacheThread = null; 245 fCacheThread = new Thread(CacheThreadLoop); 246 fCacheThread.Name = "CacheEngine"; 247 fCacheThread.IsBackground = false; 248 fCacheThread.Priority = ThreadPriority.Lowest; 249 250 fAbortCacheLoop = false; 251 fCacheThread.Start(); 252 } 253 } 254 } 255 } 256 257 /// <summary> 258 /// immediately stops background tile caching, call it if you want fast exit the process 259 /// </summary> CancelTileCaching()260 public void CancelTileCaching() 261 { 262 Debug.WriteLine("CancelTileCaching..."); 263 264 fAbortCacheLoop = true; 265 lock (fTileCacheQueue) { 266 fTileCacheQueue.Clear(); 267 WaitForCache.Set(); 268 } 269 } 270 271 /// <summary> 272 /// live for cache 273 /// </summary> CacheThreadLoop()274 private void CacheThreadLoop() 275 { 276 Debug.WriteLine("CacheEngine: start"); 277 278 bool startEvent = false; 279 280 while (!fAbortCacheLoop) { 281 try { 282 CacheQueueItem? task = null; 283 284 int left; 285 lock (fTileCacheQueue) { 286 left = fTileCacheQueue.Count; 287 if (left > 0) { 288 task = fTileCacheQueue.Dequeue(); 289 } 290 } 291 292 if (task.HasValue) { 293 if (startEvent) { 294 startEvent = false; 295 } 296 297 // check if stream wasn't disposed somehow 298 var taskVal = task.Value; 299 if (taskVal.Img != null) { 300 Debug.WriteLine("CacheEngine[" + left + "]: storing tile " + taskVal + ", " + taskVal.Img.Length / 1024 + "kB..."); 301 302 if (ImageCache != null) { 303 if (fCacheOnIdleRead) { 304 while (Interlocked.Decrement(ref fReadingCache) > 0) { 305 Thread.Sleep(1000); 306 } 307 } 308 ImageCache.PutImageToCache(taskVal.Img, taskVal.Tile.Type, taskVal.Tile.Pos, taskVal.Tile.Zoom); 309 } 310 311 taskVal.Clear(); 312 313 // boost cache engine 314 Thread.Sleep(333); 315 } else { 316 Debug.WriteLine("CacheEngineLoop: skip, tile disposed to early -> " + taskVal); 317 } 318 } else { 319 if (!startEvent) { 320 startEvent = true; 321 } 322 323 if (fAbortCacheLoop || NoMapInstances || !WaitForCache.WaitOne(33333, false)) { 324 break; 325 } 326 } 327 } catch (AbandonedMutexException) { 328 break; 329 } catch (Exception ex) { 330 Debug.WriteLine("CacheEngineLoop: " + ex); 331 } 332 } 333 Debug.WriteLine("CacheEngine: stop"); 334 } 335 336 /// <summary> 337 /// gets image from tile server 338 /// </summary> 339 /// <param name="provider"></param> 340 /// <param name="pos"></param> 341 /// <param name="zoom"></param> 342 /// <returns></returns> GetImageFrom(GMapProvider provider, GPoint pos, int zoom, out Exception result)343 public PureImage GetImageFrom(GMapProvider provider, GPoint pos, int zoom, out Exception result) 344 { 345 PureImage ret = null; 346 result = null; 347 348 try { 349 var rawTile = new RawTile(provider.DbId, pos, zoom); 350 351 // let't check memory first 352 var m = MemoryCache.GetTileFromMemoryCache(rawTile); 353 if (m != null && TileImageProxy != null) { 354 ret = TileImageProxy.FromArray(m); 355 } 356 357 if (ret == null) { 358 if (ImageCache != null) { 359 // hold writer for 5s 360 if (fCacheOnIdleRead) { 361 Interlocked.Exchange(ref fReadingCache, 5); 362 } 363 364 ret = ImageCache.GetImageFromCache(provider.DbId, pos, zoom); 365 if (ret != null) { 366 MemoryCache.AddTileToMemoryCache(rawTile, ret.Data.GetBuffer()); 367 return ret; 368 } 369 } 370 371 ret = provider.GetTileImage(pos, zoom); 372 // Enqueue Cache 373 if (ret != null) { 374 MemoryCache.AddTileToMemoryCache(rawTile, ret.Data.GetBuffer()); 375 376 EnqueueCacheTask(new CacheQueueItem(rawTile, ret.Data.GetBuffer())); 377 } 378 } 379 } catch (Exception ex) { 380 result = ex; 381 ret = null; 382 Debug.WriteLine("GetImageFrom: " + ex); 383 } 384 385 return ret; 386 } 387 PingNetwork(string hostNameOrAddress)388 public static bool PingNetwork(string hostNameOrAddress) 389 { 390 bool pingStatus; 391 392 using (Ping p = new Ping()) { 393 byte[] buffer = Encoding.ASCII.GetBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); 394 int timeout = 4444; // 4s 395 396 try { 397 PingReply reply = p.Send(hostNameOrAddress, timeout, buffer); 398 pingStatus = (reply != null && reply.Status == IPStatus.Success); 399 } catch (Exception) { 400 pingStatus = false; 401 } 402 } 403 404 return pingStatus; 405 } 406 } 407 } 408