diff --git a/Caching/FileDb/FileDb.dll b/Caching/FileDb/FileDb.dll index 73f4061b..794da39c 100644 Binary files a/Caching/FileDb/FileDb.dll and b/Caching/FileDb/FileDb.dll differ diff --git a/Caching/FileDb/FileDb.xml b/Caching/FileDb/FileDb.xml index 4f7062c5..c1440eb3 100644 --- a/Caching/FileDb/FileDb.xml +++ b/Caching/FileDb/FileDb.xml @@ -6,8 +6,10 @@ - Represents an open FileDb database file. None of the FileDb classes are re-entrant - - access to the class objects must be syncronised by the calling application. + Represents an open FileDb database file. All of the FileDb classes/methods are re-entrant - + there is no need to syncronise access to the class objects by the calling application. + However you should use the try-finally pattern when you open a FileDb to ensure + prompt closing in the finally code block. @@ -503,34 +505,39 @@ The name of the Field to rename - + + Used to set your own encryptor. Use this for cross-platform encryption scenarios, where you + have control over the encryption method being used. See the Windows Sample apps for an example. + + + + + + + Convienience method to encrypt a string value. You must first call SetEncryptor with an Encryptor. + + The string to encrypt + The encrypted value + + + + + Decrypt a string value. You must first call SetEncryptor with an Encryptor. + + The string to decrypt + The decrypted value + + + + + *** Only works on Windows platform. Use SetEncryptor to provide your own encryptor for cross platform databases *** + *** Do not use this method anymore *** Allows you to set an encryption key after the database has been opened. You must set the encryption key before reading or writing to the database. Encryption is "all or nothing", meaning all records are either encrypted or not. A string value to use as the encryption key - - - - - Encrypt a string value. - Not syncronized. - - The key to use for encryption - The value to encrypt - The encrypted value as a string - - - - - Decrypt a string value. - Not syncronized. - - The key to use for decryption - The value to decrypt - The decrypted value as a string - @@ -892,6 +899,39 @@ The record index + + + + Class uses the .NET AesManaged class for data encryption, but ONLY on the Windows platform + build...the PCL build just returns the same data without doing anything. This is because + encryption namespace isn't available for PCLs. In this case, create your own Encryptor + class using the IEncryptor interface and set it into the FileDb object via SetEncryptor. + + + + + + Constructor taking a key (password) and salt as a string + + + + + + + + Encrypt the passed byte array + + The data to encrypt + The encrypted data + + + + + Decrypt the passed byte array + + The data to decrypt + The decrypted data + diff --git a/Caching/FileDb/FileDbPcl.dll b/Caching/FileDb/FileDbPcl.dll index 1789b1ce..4a129a34 100644 Binary files a/Caching/FileDb/FileDbPcl.dll and b/Caching/FileDb/FileDbPcl.dll differ diff --git a/Caching/FileDb/FileDbPcl.xml b/Caching/FileDb/FileDbPcl.xml index 3c65bc3d..87939810 100644 --- a/Caching/FileDb/FileDbPcl.xml +++ b/Caching/FileDb/FileDbPcl.xml @@ -6,8 +6,10 @@ - Represents an open FileDb database file. None of the FileDb classes are re-entrant - - access to the class objects must be syncronised by the calling application. + Represents an open FileDb database file. All of the FileDb classes/methods are re-entrant - + there is no need to syncronise access to the class objects by the calling application. + However you should use the try-finally pattern when you open a FileDb to ensure + prompt closing in the finally code block. @@ -471,34 +473,39 @@ The name of the Field to rename - + + Used to set your own encryptor. Use this for cross-platform encryption scenarios, where you + have control over the encryption method being used. See the Windows Sample apps for an example. + + + + + + + Convienience method to encrypt a string value. You must first call SetEncryptor with an Encryptor. + + The string to encrypt + The encrypted value + + + + + Decrypt a string value. You must first call SetEncryptor with an Encryptor. + + The string to decrypt + The decrypted value + + + + + *** Only works on Windows platform. Use SetEncryptor to provide your own encryptor for cross platform databases *** + *** Do not use this method anymore *** Allows you to set an encryption key after the database has been opened. You must set the encryption key before reading or writing to the database. Encryption is "all or nothing", meaning all records are either encrypted or not. A string value to use as the encryption key - - - - - Encrypt a string value. - Not syncronized. - - The key to use for encryption - The value to encrypt - The encrypted value as a string - - - - - Decrypt a string value. - Not syncronized. - - The key to use for decryption - The value to decrypt - The decrypted value as a string - @@ -860,6 +867,15 @@ The record index + + + + Class uses the .NET AesManaged class for data encryption, but ONLY on the Windows platform + build...the PCL build just returns the same data without doing anything. This is because + encryption namespace isn't available for PCLs. In this case, create your own Encryptor + class using the IEncryptor interface and set it into the FileDb object via SetEncryptor. + + diff --git a/Caching/FileDbCache.WPF/FileDbCache.WPF.csproj b/Caching/FileDbCache.WPF/FileDbCache.WPF.csproj index ab6c3a81..6dd330cb 100644 --- a/Caching/FileDbCache.WPF/FileDbCache.WPF.csproj +++ b/Caching/FileDbCache.WPF/FileDbCache.WPF.csproj @@ -44,12 +44,9 @@ False ..\FileDb\FileDb.dll - - - diff --git a/Caching/FileDbCache.WPF/FileDbCache.cs b/Caching/FileDbCache.WPF/FileDbCache.cs index b7942d65..03c8c761 100644 --- a/Caching/FileDbCache.WPF/FileDbCache.cs +++ b/Caching/FileDbCache.WPF/FileDbCache.cs @@ -9,7 +9,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.Caching; -using System.Windows.Media.Imaging; using FileDbNs; namespace MapControl.Caching @@ -17,7 +16,6 @@ namespace MapControl.Caching /// /// ObjectCache implementation based on FileDb, a free and simple No-SQL database by EzTools Software. /// See http://www.eztools-software.com/tools/filedb/. - /// The only valid data type for cached values is System.Windows.Media.Imaging.BitmapFrame. /// public class FileDbCache : ObjectCache, IDisposable { @@ -196,40 +194,19 @@ namespace MapControl.Caching if (fileDb.IsOpen) { - Record record = null; - try { - record = fileDb.GetRecordByKey(key, new string[] { valueField }, false); + var record = fileDb.GetRecordByKey(key, new string[] { valueField }, false); + + if (record != null) + { + return record[0]; + } } catch (Exception ex) { Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message); } - - if (record != null) - { - try - { - using (var memoryStream = new MemoryStream((byte[])record[0])) - { - return BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); - } - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: Decoding \"{0}\" failed: {1}", key, ex.Message); - } - - try - { - fileDb.DeleteRecordByKey(key); - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.DeleteRecordByKey(\"{0}\") failed: {1}", key, ex.Message); - } - } } return null; @@ -254,6 +231,11 @@ namespace MapControl.Caching throw new ArgumentNullException("The parameter key must not be null."); } + if (value == null) + { + throw new ArgumentNullException("The parameter value must not be null."); + } + if (policy == null) { throw new ArgumentNullException("The parameter policy must not be null."); @@ -264,50 +246,22 @@ namespace MapControl.Caching throw new NotSupportedException("The parameter regionName must be null."); } - var bitmap = value as BitmapFrame; - - if (bitmap == null) - { - throw new ArgumentException("The parameter value must contain a System.Windows.Media.Imaging.BitmapFrame."); - } - if (fileDb.IsOpen) { - byte[] buffer = null; + var expires = DateTime.MaxValue; - try + if (policy.AbsoluteExpiration != InfiniteAbsoluteExpiration) { - var encoder = new PngBitmapEncoder(); - encoder.Frames.Add(bitmap); - - using (var memoryStream = new MemoryStream()) - { - encoder.Save(memoryStream); - buffer = memoryStream.ToArray(); - } + expires = policy.AbsoluteExpiration.DateTime; } - catch (Exception ex) + else if (policy.SlidingExpiration != NoSlidingExpiration) { - Debug.WriteLine("FileDbCache: Encoding \"{0}\" failed: {1}", key, ex.Message); + expires = DateTime.UtcNow + policy.SlidingExpiration; } - if (buffer != null) + if (!AddOrUpdateRecord(key, value, expires) && RepairDatabase()) { - var expires = DateTime.MaxValue; - - if (policy.AbsoluteExpiration != InfiniteAbsoluteExpiration) - { - expires = policy.AbsoluteExpiration.DateTime; - } - else if (policy.SlidingExpiration != NoSlidingExpiration) - { - expires = DateTime.UtcNow + policy.SlidingExpiration; - } - - if (!AddOrUpdateRecord(key, buffer, expires) && RepairDatabase()) - { - AddOrUpdateRecord(key, buffer, expires); - } + AddOrUpdateRecord(key, value, expires); } } } @@ -447,7 +401,7 @@ namespace MapControl.Caching return false; } - private bool AddOrUpdateRecord(string key, byte[] value, DateTime expires) + private bool AddOrUpdateRecord(string key, object value, DateTime expires) { var fieldValues = new FieldValues(3); fieldValues.Add(valueField, value); diff --git a/Caching/FileDbCache.WPF/Properties/AssemblyInfo.cs b/Caching/FileDbCache.WPF/Properties/AssemblyInfo.cs index df431a18..a61ba671 100644 --- a/Caching/FileDbCache.WPF/Properties/AssemblyInfo.cs +++ b/Caching/FileDbCache.WPF/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.6.0")] -[assembly: AssemblyFileVersion("2.6.0")] +[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyFileVersion("2.7.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs b/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs index aa679200..103245ae 100644 --- a/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs +++ b/Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.6.0")] -[assembly: AssemblyFileVersion("2.6.0")] +[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyFileVersion("2.7.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/Caching/ImageFileCache.WPF/ImageFileCache.WPF.csproj b/Caching/ImageFileCache.WPF/ImageFileCache.WPF.csproj index b3514996..cd2a9813 100644 --- a/Caching/ImageFileCache.WPF/ImageFileCache.WPF.csproj +++ b/Caching/ImageFileCache.WPF/ImageFileCache.WPF.csproj @@ -40,12 +40,9 @@ ..\..\MapControl.snk - - - diff --git a/Caching/ImageFileCache.WPF/ImageFileCache.cs b/Caching/ImageFileCache.WPF/ImageFileCache.cs index d9031e94..627a5cb3 100644 --- a/Caching/ImageFileCache.WPF/ImageFileCache.cs +++ b/Caching/ImageFileCache.WPF/ImageFileCache.cs @@ -6,22 +6,32 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Linq; using System.Runtime.Caching; using System.Security.AccessControl; using System.Security.Principal; -using System.Windows.Media.Imaging; namespace MapControl.Caching { /// /// ObjectCache implementation based on local image files. - /// The only valid data type for cached values is System.Windows.Media.Imaging.BitmapFrame. + /// The only valid data type for cached values is byte[]. /// public class ImageFileCache : ObjectCache { + private static readonly Tuple[] imageFileTypes = new Tuple[] + { + new Tuple(".png", new byte[] { 0x89, 0x50, 0x4E, 0x47, 0xD, 0xA, 0x1A, 0xA }), + new Tuple(".jpg", new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0, 0x10, 0x4A, 0x46, 0x49, 0x46, 0 }), + new Tuple(".bmp", new byte[] { 0x42, 0x4D }), + new Tuple(".gif", new byte[] { 0x47, 0x49, 0x46 }), + new Tuple(".tif", new byte[] { 0x49, 0x49, 42, 0 }), + new Tuple(".tif", new byte[] { 0x4D, 0x4D, 0, 42 }), + new Tuple(".wdp", new byte[] { 0x49, 0x49, 0xBC }), + new Tuple(".bin", new byte[] { }), + }; + private static readonly FileSystemAccessRule fullControlRule = new FileSystemAccessRule( new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null), FileSystemRights.FullControl, AccessControlType.Allow); @@ -87,44 +97,52 @@ namespace MapControl.Caching public override bool Contains(string key, string regionName = null) { - return memoryCache.Contains(key, regionName) || FindFile(GetPath(key)) != null; + if (key == null) + { + throw new ArgumentNullException("The parameter key must not be null."); + } + + if (regionName != null) + { + throw new NotSupportedException("The parameter regionName must be null."); + } + + return memoryCache.Contains(key) || FindFile(key) != null; } public override object Get(string key, string regionName = null) { - var bitmap = memoryCache.Get(key, regionName) as BitmapFrame; - - if (bitmap == null) + if (key == null) { - try - { - var path = FindFile(GetPath(key)); + throw new ArgumentNullException("The parameter key must not be null."); + } - if (path != null) + if (regionName != null) + { + throw new NotSupportedException("The parameter regionName must be null."); + } + + var buffer = memoryCache.Get(key) as byte[]; + + if (buffer == null) + { + var path = FindFile(key); + + if (path != null) + { + try { - using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - bitmap = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); - - var metadata = (BitmapMetadata)bitmap.Metadata; - DateTime expiration; - - // metadata.DateTaken must be parsed in CurrentCulture - if (metadata != null && - metadata.DateTaken != null && - DateTime.TryParse(metadata.DateTaken, CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expiration)) - { - memoryCache.Set(key, bitmap, expiration, regionName); - } - } + buffer = File.ReadAllBytes(path); + memoryCache.Set(key, buffer, new CacheItemPolicy()); + } + catch (Exception ex) + { + Debug.WriteLine("ImageFileCache: Writing file {0} failed: {1}", path, ex.Message); } - } - catch - { } } - return bitmap; + return buffer; } public override CacheItem GetCacheItem(string key, string regionName = null) @@ -141,59 +159,32 @@ namespace MapControl.Caching public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null) { - var bitmap = value as BitmapFrame; - - if (bitmap == null) + if (key == null) { - throw new ArgumentException("The parameter value must contain a System.Windows.Media.Imaging.BitmapFrame."); + throw new ArgumentNullException("The parameter key must not be null."); } - var metadata = (BitmapMetadata)bitmap.Metadata; - var format = metadata != null ? metadata.Format : "bmp"; - BitmapEncoder encoder = null; - - switch (format) + if (regionName != null) { - case "bmp": - encoder = new BmpBitmapEncoder(); - break; - case "gif": - encoder = new GifBitmapEncoder(); - break; - case "jpg": - encoder = new JpegBitmapEncoder(); - break; - case "png": - encoder = new PngBitmapEncoder(); - break; - case "tiff": - encoder = new TiffBitmapEncoder(); - break; - case "wmphoto": - encoder = new WmpBitmapEncoder(); - break; - default: - break; + throw new NotSupportedException("The parameter regionName must be null."); } - if (encoder == null) + var buffer = value as byte[]; + + if (buffer == null || buffer.Length == 0) { - throw new NotSupportedException(string.Format("The bitmap format {0} is not supported.", format)); + throw new NotSupportedException("The parameter value must be a non-empty byte array."); } - memoryCache.Set(key, bitmap, policy, regionName); + memoryCache.Set(key, buffer, policy); - var path = string.Format("{0}.{1}", GetPath(key), format); + var path = Path.Combine(rootFolder, key) + + imageFileTypes.First(t => t.Item2.SequenceEqual(buffer.Take(t.Item2.Length))).Item1; try { Directory.CreateDirectory(Path.GetDirectoryName(path)); - - using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write)) - { - encoder.Frames.Add(bitmap); - encoder.Save(fileStream); - } + File.WriteAllBytes(path, buffer); var fileSecurity = File.GetAccessControl(path); fileSecurity.AddAccessRule(fullControlRule); @@ -240,43 +231,56 @@ namespace MapControl.Caching public override object Remove(string key, string regionName = null) { - var oldValue = Get(key, regionName); - - memoryCache.Remove(key, regionName); - - try + if (key == null) { - var path = FindFile(GetPath(key)); + throw new ArgumentNullException("The parameter key must not be null."); + } - if (path != null) + if (regionName != null) + { + throw new NotSupportedException("The parameter regionName must be null."); + } + + memoryCache.Remove(key); + + var path = FindFile(key); + + if (path != null) + { + try { File.Delete(path); } - } - catch - { + catch (Exception ex) + { + Debug.WriteLine("ImageFileCache: Removing file {0} failed: {1}", path, ex.Message); + } } - return oldValue; + return null; } - private string GetPath(string key) + private string FindFile(string key) { - return Path.Combine(rootFolder, key); - } + var path = Path.Combine(rootFolder, key); - private static string FindFile(string path) - { - if (!string.IsNullOrEmpty(Path.GetExtension(path))) + try { - return path; + if (!string.IsNullOrEmpty(Path.GetExtension(path))) + { + return path; + } + + string folderName = Path.GetDirectoryName(path); + + if (Directory.Exists(folderName)) + { + return Directory.EnumerateFiles(folderName, Path.GetFileName(path) + ".*").FirstOrDefault(); + } } - - string folderName = Path.GetDirectoryName(path); - - if (Directory.Exists(folderName)) + catch (Exception ex) { - return Directory.EnumerateFiles(folderName, Path.GetFileName(path) + ".*").FirstOrDefault(); + Debug.WriteLine("ImageFileCache: Finding file {0} failed: {1}", path, ex.Message); } return null; diff --git a/Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs b/Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs index c585e4bb..943d8964 100644 --- a/Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs +++ b/Caching/ImageFileCache.WPF/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.6.0")] -[assembly: AssemblyFileVersion("2.6.0")] +[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyFileVersion("2.7.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/Caching/ImageFileCache.WinRT/ImageFileCache.cs b/Caching/ImageFileCache.WinRT/ImageFileCache.cs index e22af7ec..dc2d6b8e 100644 --- a/Caching/ImageFileCache.WinRT/ImageFileCache.cs +++ b/Caching/ImageFileCache.WinRT/ImageFileCache.cs @@ -40,30 +40,26 @@ namespace MapControl.Caching { var item = await rootFolder.TryGetItemAsync(key); - if (item == null || !item.IsOfType(StorageItemTypes.File)) + if (item != null && item.IsOfType(StorageItemTypes.File)) { - return null; + var file = (StorageFile)item; + //Debug.WriteLine("ImageFileCache: Reading file {0}", file.Path); + + try + { + return new ImageCacheItem + { + Buffer = await FileIO.ReadBufferAsync(file), + Expires = (await file.Properties.GetImagePropertiesAsync()).DateTaken.UtcDateTime + }; + } + catch (Exception ex) + { + Debug.WriteLine("ImageFileCache: Reading file {0} failed: {1}", file.Path, ex.Message); + } } - var file = (StorageFile)item; - - var cacheItem = new ImageCacheItem - { - Buffer = await FileIO.ReadBufferAsync(file) - }; - - try - { - // Use ImageProperties.DateTaken to get expiration date - var imageProperties = await file.Properties.GetImagePropertiesAsync(); - cacheItem.Expires = imageProperties.DateTaken.UtcDateTime; - } - catch - { - } - - //Debug.WriteLine("Loaded cached image {0}", file.Path); - return cacheItem; + return null; } public virtual async Task SetAsync(string key, IBuffer buffer, DateTime expires) @@ -79,16 +75,18 @@ namespace MapControl.Caching } var file = await folder.CreateFileAsync(names[names.Length - 1], CreationCollisionOption.ReplaceExisting); + //Debug.WriteLine("ImageFileCache: Writing file {0}", file.Path); + await FileIO.WriteBufferAsync(file, buffer); - // Use ImageProperties.DateTaken to store expiration date - var imageProperties = await file.Properties.GetImagePropertiesAsync(); - imageProperties.DateTaken = expires; - await imageProperties.SavePropertiesAsync(); + // Store expiration date in ImageProperties.DateTaken + var properties = await file.Properties.GetImagePropertiesAsync(); + properties.DateTaken = expires; + await properties.SavePropertiesAsync(); } catch (Exception ex) { - Debug.WriteLine(ex.Message); + Debug.WriteLine("ImageFileCache: Writing file {0}\\{1} failed: {2}", rootFolder.Path, key, ex.Message); } } } diff --git a/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs b/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs index ab237da0..0f9bdc17 100644 --- a/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs +++ b/Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.6.0")] -[assembly: AssemblyFileVersion("2.6.0")] +[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyFileVersion("2.7.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/BingMapsTileLayer.cs b/MapControl/BingMapsTileLayer.cs index 34bf9fd4..ba7a45b9 100644 --- a/MapControl/BingMapsTileLayer.cs +++ b/MapControl/BingMapsTileLayer.cs @@ -18,6 +18,9 @@ using System.Windows.Media.Imaging; namespace MapControl { + /// + /// Displays Bing Maps tiles. The static ApiKey property must be set to a Bing Maps API Key. + /// public class BingMapsTileLayer : TileLayer { public enum MapMode @@ -26,6 +29,12 @@ namespace MapControl } public BingMapsTileLayer() + : this(new TileImageLoader()) + { + } + + public BingMapsTileLayer(ITileImageLoader tileImageLoader) + : base(tileImageLoader) { MinZoomLevel = 1; MaxZoomLevel = 21; @@ -43,7 +52,7 @@ namespace MapControl if (string.IsNullOrEmpty(ApiKey)) { - throw new InvalidOperationException("A Bing Maps API Key must be assigned to the ApiKey property."); + throw new InvalidOperationException("BingMapsTileLayer requires a Bing Maps API Key."); } var uri = string.Format("http://dev.virtualearth.net/REST/V1/Imagery/Metadata/{0}?output=xml&key={1}", Mode, ApiKey); diff --git a/MapControl/BingMapsTileSource.cs b/MapControl/BingMapsTileSource.cs index 656816d7..6cd8044a 100644 --- a/MapControl/BingMapsTileSource.cs +++ b/MapControl/BingMapsTileSource.cs @@ -8,7 +8,7 @@ namespace MapControl { internal class BingMapsTileSource : TileSource { - private string[] subdomains; + private readonly string[] subdomains; public BingMapsTileSource(string uriFormat, string[] subdomains) : base(uriFormat) diff --git a/MapControl/Map.Silverlight.cs b/MapControl/Map.Silverlight.cs index 1a6b4852..a260c941 100644 --- a/MapControl/Map.Silverlight.cs +++ b/MapControl/Map.Silverlight.cs @@ -8,7 +8,7 @@ using System.Windows.Input; namespace MapControl { /// - /// Default input event handling. + /// MapBase with default input event handling. /// public class Map : MapBase { diff --git a/MapControl/Map.WPF.cs b/MapControl/Map.WPF.cs index 73b5f509..57c92d46 100644 --- a/MapControl/Map.WPF.cs +++ b/MapControl/Map.WPF.cs @@ -8,7 +8,7 @@ using System.Windows.Input; namespace MapControl { /// - /// Default input event handling. + /// MapBase with default input event handling. /// public class Map : MapBase { diff --git a/MapControl/Map.WinRT.cs b/MapControl/Map.WinRT.cs index 8674ac7b..d3f32043 100644 --- a/MapControl/Map.WinRT.cs +++ b/MapControl/Map.WinRT.cs @@ -10,7 +10,7 @@ using Windows.UI.Xaml.Input; namespace MapControl { /// - /// Default input event handling. + /// MapBase with default input event handling. /// public class Map : MapBase { diff --git a/MapControl/MapBase.cs b/MapControl/MapBase.cs index aae93df7..818c6ecd 100644 --- a/MapControl/MapBase.cs +++ b/MapControl/MapBase.cs @@ -21,7 +21,7 @@ using System.Windows.Media.Animation; namespace MapControl { /// - /// The map control. Renders map content provided by the TileLayer or TileLayers property. + /// The map control. Displays map content provided by the TileLayer or TileLayers property. /// The visible map area is defined by the Center and ZoomLevel properties. /// The map can be rotated by an angle that is given by the Heading property. /// MapBase can contain map overlay child elements like other MapPanels or MapItemsControls. diff --git a/MapControl/Properties/AssemblyInfo.cs b/MapControl/Properties/AssemblyInfo.cs index 97ed6cc2..2ff000c3 100644 --- a/MapControl/Properties/AssemblyInfo.cs +++ b/MapControl/Properties/AssemblyInfo.cs @@ -14,8 +14,8 @@ using System.Windows; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.6.0")] -[assembly: AssemblyFileVersion("2.6.0")] +[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyFileVersion("2.7.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/MapControl/TileImageLoader.WPF.cs b/MapControl/TileImageLoader.WPF.cs index e5898770..4bbfc635 100644 --- a/MapControl/TileImageLoader.WPF.cs +++ b/MapControl/TileImageLoader.WPF.cs @@ -11,6 +11,7 @@ using System.IO; using System.Linq; using System.Net; using System.Runtime.Caching; +using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Media; @@ -197,7 +198,7 @@ namespace MapControl } catch (Exception ex) { - Debug.WriteLine("Loading tile image failed: {0}", (object)ex.Message); + Debug.WriteLine("ImageTileSource.LoadImage: " + ex.Message); } return image; @@ -218,7 +219,7 @@ namespace MapControl } catch (Exception ex) { - Debug.WriteLine("Creating tile image failed: {0}", (object)ex.Message); + Debug.WriteLine("{0}: {1}", path, ex.Message); } } @@ -232,7 +233,11 @@ namespace MapControl try { var request = HttpWebRequest.CreateHttp(uri); - request.UserAgent = HttpUserAgent; + + if (HttpUserAgent != null) + { + request.UserAgent = HttpUserAgent; + } using (var response = (HttpWebResponse)request.GetResponse()) { @@ -240,23 +245,24 @@ namespace MapControl using (var memoryStream = new MemoryStream()) { responseStream.CopyTo(memoryStream); - memoryStream.Position = 0; - image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); - } + memoryStream.Seek(0, SeekOrigin.Begin); - if (cacheKey != null) - { - SetCachedImage(cacheKey, image, GetExpiration(response.Headers)); + image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + + if (cacheKey != null) + { + SetCachedImage(cacheKey, memoryStream, GetExpiration(response.Headers)); + } } } } catch (WebException ex) { - Debug.WriteLine("Downloading {0} failed: {1}: {2}", uri, ex.Status, ex.Message); + Debug.WriteLine("{0}: {1}: {2}", uri, ex.Status, ex.Message); } catch (Exception ex) { - Debug.WriteLine("Downloading {0} failed: {1}", uri, ex.Message); + Debug.WriteLine("{0}: {1}", uri, ex.Message); } return image; @@ -274,34 +280,44 @@ namespace MapControl private static bool GetCachedImage(string cacheKey, out BitmapSource image) { - image = Cache.Get(cacheKey) as BitmapSource; + image = null; - if (image == null) + var buffer = Cache.Get(cacheKey) as byte[]; + + if (buffer != null) { - return false; + try + { + using (var memoryStream = new MemoryStream(buffer)) + { + image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + } + + DateTime expiration = DateTime.MinValue; + + if (buffer.Length >= 16 && Encoding.ASCII.GetString(buffer, buffer.Length - 16, 8) == "EXPIRES:") + { + expiration = new DateTime(BitConverter.ToInt64(buffer, buffer.Length - 8), DateTimeKind.Utc); + } + + return expiration > DateTime.UtcNow; + } + catch (Exception ex) + { + Debug.WriteLine("{0}: {1}", cacheKey, ex.Message); + } } - var metadata = (BitmapMetadata)image.Metadata; - DateTime expiration; - - // get cache expiration date from BitmapMetadata.DateTaken, must be parsed with CurrentCulture - return metadata == null - || metadata.DateTaken == null - || !DateTime.TryParse(metadata.DateTaken, CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expiration) - || expiration > DateTime.UtcNow; + return false; } - private static void SetCachedImage(string cacheKey, BitmapSource image, DateTime expiration) + private static void SetCachedImage(string cacheKey, MemoryStream memoryStream, DateTime expiration) { - var bitmap = BitmapFrame.Create(image); - var metadata = (BitmapMetadata)bitmap.Metadata; + memoryStream.Seek(0, SeekOrigin.End); + memoryStream.Write(Encoding.ASCII.GetBytes("EXPIRES:"), 0, 8); + memoryStream.Write(BitConverter.GetBytes(expiration.Ticks), 0, 8); - // store cache expiration date in BitmapMetadata.DateTaken - metadata.DateTaken = expiration.ToString(CultureInfo.InvariantCulture); - metadata.Freeze(); - bitmap.Freeze(); - - Cache.Set(cacheKey, bitmap, new CacheItemPolicy { AbsoluteExpiration = expiration }); + Cache.Set(cacheKey, memoryStream.ToArray(), new CacheItemPolicy { AbsoluteExpiration = expiration }); //Debug.WriteLine("Cached {0}, Expires {1}", cacheKey, expiration); } diff --git a/MapControl/TileLayer.cs b/MapControl/TileLayer.cs index dc684381..0a41ef55 100644 --- a/MapControl/TileLayer.cs +++ b/MapControl/TileLayer.cs @@ -20,7 +20,7 @@ using System.Windows.Threading; namespace MapControl { /// - /// Fills a rectangular area with map tiles from a TileSource. + /// Fills the map viewport with map tiles from a TileSource. /// #if NETFX_CORE [ContentProperty(Name = "TileSource")] diff --git a/MapControl/TileSource.cs b/MapControl/TileSource.cs index 53ecb058..1d1ac2fe 100644 --- a/MapControl/TileSource.cs +++ b/MapControl/TileSource.cs @@ -153,7 +153,7 @@ namespace MapControl } return new Uri(uriFormat - .Replace("{i}", new string(quadkey[zoomLevel - 1], 1)) + .Replace("{i}", new string(quadkey, zoomLevel - 1, 1)) .Replace("{q}", new string(quadkey)), UriKind.RelativeOrAbsolute); } diff --git a/MapControl/WinRT/Properties/AssemblyInfo.cs b/MapControl/WinRT/Properties/AssemblyInfo.cs index 154be8d8..f7a0b7bc 100644 --- a/MapControl/WinRT/Properties/AssemblyInfo.cs +++ b/MapControl/WinRT/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.6.0")] -[assembly: AssemblyFileVersion("2.6.0")] +[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyFileVersion("2.7.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/PhoneApplication/Properties/AssemblyInfo.cs b/SampleApps/PhoneApplication/Properties/AssemblyInfo.cs index 3283e985..0e9afe16 100644 --- a/SampleApps/PhoneApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/PhoneApplication/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.6.0")] -[assembly: AssemblyFileVersion("2.6.0")] +[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyFileVersion("2.7.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs b/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs index 6dfb8ef5..d17eed8a 100644 --- a/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs +++ b/SampleApps/SilverlightApplication.Web/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.6.0")] -[assembly: AssemblyFileVersion("2.6.0")] +[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyFileVersion("2.7.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs b/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs index b4743952..41917307 100644 --- a/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/SilverlightApplication/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.6.0")] -[assembly: AssemblyFileVersion("2.6.0")] +[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyFileVersion("2.7.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] diff --git a/SampleApps/StoreApplication/Properties/AssemblyInfo.cs b/SampleApps/StoreApplication/Properties/AssemblyInfo.cs index 3c55f8b6..6d2d925a 100644 --- a/SampleApps/StoreApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/StoreApplication/Properties/AssemblyInfo.cs @@ -8,7 +8,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("2.6.0")] -[assembly: AssemblyFileVersion("2.6.0")] +[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyFileVersion("2.7.0")] [assembly: AssemblyConfiguration("")] [assembly: ComVisible(false)] diff --git a/SampleApps/UniversalApp/MainPage.xaml.cs b/SampleApps/UniversalApp/MainPage.xaml.cs index 195842fa..68929a4d 100644 --- a/SampleApps/UniversalApp/MainPage.xaml.cs +++ b/SampleApps/UniversalApp/MainPage.xaml.cs @@ -10,6 +10,7 @@ namespace UniversalApp public MainPage() { //TileImageLoader.Cache = new MapControl.Caching.ImageFileCache(); + //TileImageLoader.Cache = new MapControl.Caching.FileDbCache(); this.InitializeComponent(); } diff --git a/SampleApps/UniversalApp/Properties/AssemblyInfo.cs b/SampleApps/UniversalApp/Properties/AssemblyInfo.cs index 596f4506..9880050f 100644 --- a/SampleApps/UniversalApp/Properties/AssemblyInfo.cs +++ b/SampleApps/UniversalApp/Properties/AssemblyInfo.cs @@ -8,7 +8,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("2.6.0")] -[assembly: AssemblyFileVersion("2.6.0")] +[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyFileVersion("2.7.0")] [assembly: AssemblyConfiguration("")] [assembly: ComVisible(false)] diff --git a/SampleApps/UniversalApp/UniversalApp.csproj b/SampleApps/UniversalApp/UniversalApp.csproj index 9be56786..54d3daf8 100644 --- a/SampleApps/UniversalApp/UniversalApp.csproj +++ b/SampleApps/UniversalApp/UniversalApp.csproj @@ -144,6 +144,10 @@ + + {c7bf2b18-cc74-430b-bcb2-600304efa3d8} + FileDbCache.WinRT + {f789647e-96f7-43e3-a895-fa3fe8d01260} ImageFileCache.WinRT diff --git a/SampleApps/WpfApplication/Properties/AssemblyInfo.cs b/SampleApps/WpfApplication/Properties/AssemblyInfo.cs index a8ade36e..c7f56aa3 100644 --- a/SampleApps/WpfApplication/Properties/AssemblyInfo.cs +++ b/SampleApps/WpfApplication/Properties/AssemblyInfo.cs @@ -7,8 +7,8 @@ using System.Runtime.InteropServices; [assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.6.0")] -[assembly: AssemblyFileVersion("2.6.0")] +[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyFileVersion("2.7.0")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCulture("")] [assembly: ComVisible(false)]