< Summary

Information
Class: OffRouteMap.FileCacheProvider
Assembly: OffRouteMap
File(s): D:\a\OffRouteMap\OffRouteMap\FileCacheProvider.cs
Tag: 5_18712131505
Line coverage
8%
Covered lines: 8
Uncovered lines: 85
Coverable lines: 93
Total lines: 223
Line coverage: 8.6%
Branch coverage
6%
Covered branches: 2
Total branches: 30
Branch coverage: 6.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%4.13480%
PutImageToCache(...)0%620%
GetImageFromCache(...)0%2040%
DeleteOlderThan(...)0%2040%
BackgroundCleanup()0%110100%
GetCacheSizeBytes()0%620%
Dispose()0%2040%

File(s)

D:\a\OffRouteMap\OffRouteMap\FileCacheProvider.cs

#LineLine coverage
 1using GMap.NET;
 2using GMap.NET.WindowsPresentation;
 3using System.Collections.Concurrent;
 4using System.IO;
 5
 6// thanks to GPT5-mini for greate basic help here!
 7
 8namespace OffRouteMap
 9{
 10
 11    public class FileCacheProvider : PureImageCache, IDisposable
 12    {
 13        readonly string root;
 14        readonly long maxCacheBytes;
 415        readonly ReaderWriterLockSlim rw = new ReaderWriterLockSlim();
 416        readonly ConcurrentQueue<string> cleanupQueue = new ConcurrentQueue<string>();
 17        readonly Thread cleanupThread;
 18        bool disposed;
 19
 420        public FileCacheProvider (string rootPath, long maxCacheBytes = 0)
 21        {
 422            root = rootPath ?? throw new ArgumentNullException(nameof(rootPath));
 423            this.maxCacheBytes = maxCacheBytes;
 424            Directory.CreateDirectory(root);
 25
 426            if (maxCacheBytes > 0)
 27            {
 028                cleanupThread = new Thread(BackgroundCleanup) { IsBackground = true };
 029                cleanupThread.Start();
 30            }
 431        }
 32
 33        /// <summary>
 34        /// Store map tile as file.
 35        /// </summary>
 36        /// <param name="tile">raw image bytes (png/jpg)</param>
 37        /// <param name="type">provider-specific type (can be ignored or used to subfolder)</param>
 38        /// <param name="pos"></param>
 39        /// <param name="zoom"></param>
 40        /// <returns>false on error</returns>
 41        public bool PutImageToCache (byte[] tile, int type, GPoint pos, int zoom)
 42        {
 43            try
 44            {
 045                long x = pos.X;
 046                long y = pos.Y;
 47
 048                string baseDir = root;
 49                //string folder = (type == 0) ? "" : GMapProviders.TryGetProvider(type).Name;
 50                //var baseDir = string.IsNullOrEmpty(folder) ? root : Path.Combine(root, folder);
 051                Directory.CreateDirectory(baseDir);
 52
 53                // @todo .png always?!
 054                var path = Path.Combine(baseDir, zoom.ToString(), x.ToString(), y + ".png");
 055                Directory.CreateDirectory(Path.GetDirectoryName(path));
 56
 057                rw.EnterWriteLock();
 58                try
 59                {
 060                    File.WriteAllBytes(path, tile);
 061                    File.SetLastWriteTimeUtc(path, DateTime.UtcNow);
 062                }
 063                finally { rw.ExitWriteLock(); }
 64
 065                if (maxCacheBytes > 0) cleanupQueue.Enqueue(path);
 66
 067                return true;
 68            }
 069            catch
 70            {
 071                return false;
 72            }
 073        }
 74
 75        /// <summary>
 76        /// Load map tile from file cache.
 77        /// </summary>
 78        /// <param name="type"></param>
 79        /// <param name="pos"></param>
 80        /// <param name="zoom"></param>
 81        /// <returns>tile as pureImage or null on fail</returns>
 82        public PureImage GetImageFromCache (int type, GPoint pos, int zoom)
 83        {
 84            try
 85            {
 086                long x = pos.X;
 087                long y = pos.Y;
 88
 089                string baseDir = root;
 90
 91                //string folder = (type == 0) ? "" : GMapProviders.TryGetProvider(type).Name;
 92                //var baseDir = string.IsNullOrEmpty(folder) ? root : Path.Combine(root, folder);
 93
 94                // @todo .png always?!
 095                var path = Path.Combine(baseDir, zoom.ToString(), x.ToString(), y + ".png");
 96
 097                if (!File.Exists(path)) return null;
 98
 099                rw.EnterReadLock();
 100                byte[] bytes;
 101                try
 102                {
 0103                    bytes = File.ReadAllBytes(path);
 0104                    File.SetLastWriteTimeUtc(path, DateTime.UtcNow);
 0105                }
 0106                finally { rw.ExitReadLock(); }
 107
 0108                MemoryStream stm = new MemoryStream(bytes, 0, bytes.Length, false, true);
 109
 0110                var img = GMapImageProxy.Instance.FromStream(stm);
 0111                if (img != null)
 112                {
 0113                    img.Data = stm;
 114                }
 115
 0116                return img;
 117            }
 0118            catch
 119            {
 0120                return null;
 121            }
 0122        }
 123
 124        /// <summary>
 125        /// delete files older than date; if type != null, limit to that subfolder
 126        /// returns number of deleted tiles
 127        /// </summary>
 128        public int DeleteOlderThan (DateTime date, int? type)
 129        {
 0130            int deleted = 0;
 131            try
 132            {
 0133                string baseDir = root;
 134                //if (type.HasValue && type.Value != 0)
 135                //    baseDir = Path.Combine(root, GMapProviders.TryGetProvider((int)type).Name);
 136
 0137                if (!Directory.Exists(baseDir)) return 0;
 138
 0139                rw.EnterWriteLock();
 140                try
 141                {
 0142                    var files = Directory.EnumerateFiles(baseDir, "*.png", SearchOption.AllDirectories)
 0143                        .Where(f => File.GetLastWriteTimeUtc(f) < date)
 0144                        .ToList();
 145
 0146                    foreach (var f in files)
 147                    {
 148                        try
 149                        {
 0150                            File.Delete(f);
 0151                            deleted++;
 0152                        }
 0153                        catch { }
 154                    }
 0155                }
 0156                finally { rw.ExitWriteLock(); }
 0157            }
 0158            catch { }
 0159            return deleted;
 0160        }
 161
 162        /// <summary>
 163        /// Background cleanup: enforce maxCacheBytes using LRU
 164        /// </summary>
 165        void BackgroundCleanup ()
 166        {
 0167            while (!disposed)
 168            {
 169                try
 170                {
 0171                    Thread.Sleep(TimeSpan.FromSeconds(10));
 0172                    if (cleanupQueue.IsEmpty && GetCacheSizeBytes() <= maxCacheBytes) continue;
 173
 0174                    rw.EnterWriteLock();
 175                    try
 176                    {
 0177                        var files = Directory.EnumerateFiles(root, "*.png", SearchOption.AllDirectories)
 0178                            .Select(p => new FileInfo(p))
 0179                            .OrderBy(fi => fi.LastWriteTimeUtc)
 0180                            .ToList();
 181
 0182                        long total = files.Sum(f => f.Length);
 0183                        foreach (var fi in files)
 184                        {
 0185                            if (total <= maxCacheBytes) break;
 186                            try
 187                            {
 0188                                total -= fi.Length;
 0189                                fi.Delete();
 0190                            }
 0191                            catch { }
 192                        }
 0193                    }
 0194                    finally { rw.ExitWriteLock(); }
 0195                }
 0196                catch { Thread.Sleep(1000); }
 197            }
 0198        }
 199
 200        long GetCacheSizeBytes ()
 201        {
 202            try
 203            {
 0204                rw.EnterReadLock();
 205                try
 206                {
 0207                    if (!Directory.Exists(root)) return 0;
 0208                    return Directory.EnumerateFiles(root, "*.png", SearchOption.AllDirectories)
 0209                        .Sum(f => new FileInfo(f).Length);
 210                }
 0211                finally { rw.ExitReadLock(); }
 212            }
 0213            catch { return 0; }
 0214        }
 215
 216        public void Dispose ()
 217        {
 0218            disposed = true;
 0219            try { cleanupThread?.Join(500); } catch { }
 0220            rw?.Dispose();
 0221        }
 222    }
 223}