Version 2.7.0.

- WPF TileImageLoader reverted to caching byte arrays instead of BitmapFrames.
- Uses FileDb version 6.1.
This commit is contained in:
ClemensF 2015-11-28 21:09:25 +01:00
parent bc30e1d9ca
commit 5adcd6568e
32 changed files with 336 additions and 300 deletions

Binary file not shown.

View file

@ -6,8 +6,10 @@
<members> <members>
<member name="T:FileDbNs.FileDb"> <member name="T:FileDbNs.FileDb">
<summary> <summary>
Represents an open FileDb database file. None of the FileDb classes are re-entrant - Represents an open FileDb database file. All of the FileDb classes/methods are re-entrant -
access to the class objects must be syncronised by the calling application. 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.
</summary> </summary>
</member> </member>
@ -503,34 +505,39 @@
<param name="fieldName">The name of the Field to rename</param> <param name="fieldName">The name of the Field to rename</param>
</member> </member>
<member name="M:FileDbNs.FileDb.SetEncryptionKey(System.String)"> <member name="M:FileDbNs.FileDb.SetEncryptor(FileDbNs.IEncryptor)">
<summary> <summary>
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.
</summary>
<param name="encryptor"></param>
</member>
<member name="M:FileDbNs.FileDb.EncryptString(System.String)">
<summary>
Convienience method to encrypt a string value. You must first call SetEncryptor with an Encryptor.
</summary>
<param name="value">The string to encrypt</param>
<returns>The encrypted value</returns>
</member>
<member name="M:FileDbNs.FileDb.DecryptString(System.String)">
<summary>
Decrypt a string value. You must first call SetEncryptor with an Encryptor.
</summary>
<param name="value">The string to decrypt</param>
<returns>The decrypted value</returns>
</member>
<member name="M:FileDbNs.FileDb.SetEncryptionKey(System.String,System.String)">
<summary>
*** 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 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", the encryption key before reading or writing to the database. Encryption is "all or nothing",
meaning all records are either encrypted or not. meaning all records are either encrypted or not.
</summary> </summary>
<param name="encryptionKey">A string value to use as the encryption key</param> <param name="encryptionKey">A string value to use as the encryption key</param>
</member>
<member name="M:FileDbNs.FileDb.EncryptString(System.String,System.String)">
<summary>
Encrypt a string value.
Not syncronized.
</summary>
<param name="encryptKey">The key to use for encryption</param>
<param name="value">The value to encrypt</param>
<returns>The encrypted value as a string</returns>
</member>
<member name="M:FileDbNs.FileDb.DecryptString(System.String,System.String)">
<summary>
Decrypt a string value.
Not syncronized.
</summary>
<param name="encryptKey">The key to use for decryption</param>
<param name="value">The value to decrypt</param>
<returns>The decrypted value as a string</returns>
</member> </member>
<member name="M:FileDbNs.FileDb.SelectRecords``1(FileDbNs.FilterExpression)"> <member name="M:FileDbNs.FileDb.SelectRecords``1(FileDbNs.FilterExpression)">
<summary> <summary>
@ -892,6 +899,39 @@
</summary> </summary>
<param name="index">The record index</param> <param name="index">The record index</param>
</member>
<member name="T:FileDbNs.Encryptor">
<summary>
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.
</summary>
</member>
<member name="M:FileDbNs.Encryptor.#ctor(System.String,System.String)">
<summary>
Constructor taking a key (password) and salt as a string
</summary>
<param name="encryptionKey"></param>
<param name="salt"></param>
</member>
<member name="M:FileDbNs.Encryptor.Encrypt(System.Byte[])">
<summary>
Encrypt the passed byte array
</summary>
<param name="dataToEncrypt">The data to encrypt</param>
<returns>The encrypted data</returns>
</member>
<member name="M:FileDbNs.Encryptor.Decrypt(System.Byte[])">
<summary>
Decrypt the passed byte array
</summary>
<param name="encryptedData">The data to decrypt</param>
<returns>The decrypted data</returns>
</member> </member>
<member name="T:FileDbNs.DataTypeEnum_old"> <member name="T:FileDbNs.DataTypeEnum_old">
<summary> <summary>

Binary file not shown.

View file

@ -6,8 +6,10 @@
<members> <members>
<member name="T:FileDbNs.FileDb"> <member name="T:FileDbNs.FileDb">
<summary> <summary>
Represents an open FileDb database file. None of the FileDb classes are re-entrant - Represents an open FileDb database file. All of the FileDb classes/methods are re-entrant -
access to the class objects must be syncronised by the calling application. 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.
</summary> </summary>
</member> </member>
@ -471,34 +473,39 @@
<param name="fieldName">The name of the Field to rename</param> <param name="fieldName">The name of the Field to rename</param>
</member> </member>
<member name="M:FileDbNs.FileDb.SetEncryptionKey(System.String)"> <member name="M:FileDbNs.FileDb.SetEncryptor(FileDbNs.IEncryptor)">
<summary> <summary>
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.
</summary>
<param name="encryptor"></param>
</member>
<member name="M:FileDbNs.FileDb.EncryptString(System.String)">
<summary>
Convienience method to encrypt a string value. You must first call SetEncryptor with an Encryptor.
</summary>
<param name="value">The string to encrypt</param>
<returns>The encrypted value</returns>
</member>
<member name="M:FileDbNs.FileDb.DecryptString(System.String)">
<summary>
Decrypt a string value. You must first call SetEncryptor with an Encryptor.
</summary>
<param name="value">The string to decrypt</param>
<returns>The decrypted value</returns>
</member>
<member name="M:FileDbNs.FileDb.SetEncryptionKey(System.String,System.String)">
<summary>
*** 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 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", the encryption key before reading or writing to the database. Encryption is "all or nothing",
meaning all records are either encrypted or not. meaning all records are either encrypted or not.
</summary> </summary>
<param name="encryptionKey">A string value to use as the encryption key</param> <param name="encryptionKey">A string value to use as the encryption key</param>
</member>
<member name="M:FileDbNs.FileDb.EncryptString(System.String,System.String)">
<summary>
Encrypt a string value.
Not syncronized.
</summary>
<param name="encryptKey">The key to use for encryption</param>
<param name="value">The value to encrypt</param>
<returns>The encrypted value as a string</returns>
</member>
<member name="M:FileDbNs.FileDb.DecryptString(System.String,System.String)">
<summary>
Decrypt a string value.
Not syncronized.
</summary>
<param name="encryptKey">The key to use for decryption</param>
<param name="value">The value to decrypt</param>
<returns>The decrypted value as a string</returns>
</member> </member>
<member name="M:FileDbNs.FileDb.SelectRecords``1(FileDbNs.FilterExpression)"> <member name="M:FileDbNs.FileDb.SelectRecords``1(FileDbNs.FilterExpression)">
<summary> <summary>
@ -860,6 +867,15 @@
</summary> </summary>
<param name="index">The record index</param> <param name="index">The record index</param>
</member>
<member name="T:FileDbNs.Encryptor">
<summary>
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.
</summary>
</member> </member>
<member name="T:FileDbNs.DataTypeEnum_old"> <member name="T:FileDbNs.DataTypeEnum_old">
<summary> <summary>

View file

@ -44,12 +44,9 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\FileDb\FileDb.dll</HintPath> <HintPath>..\FileDb\FileDb.dll</HintPath>
</Reference> </Reference>
<Reference Include="PresentationCore" />
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Runtime.Caching" /> <Reference Include="System.Runtime.Caching" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="FileDbCache.cs" /> <Compile Include="FileDbCache.cs" />

View file

@ -9,7 +9,6 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.Caching; using System.Runtime.Caching;
using System.Windows.Media.Imaging;
using FileDbNs; using FileDbNs;
namespace MapControl.Caching namespace MapControl.Caching
@ -17,7 +16,6 @@ namespace MapControl.Caching
/// <summary> /// <summary>
/// ObjectCache implementation based on FileDb, a free and simple No-SQL database by EzTools Software. /// ObjectCache implementation based on FileDb, a free and simple No-SQL database by EzTools Software.
/// See http://www.eztools-software.com/tools/filedb/. /// See http://www.eztools-software.com/tools/filedb/.
/// The only valid data type for cached values is System.Windows.Media.Imaging.BitmapFrame.
/// </summary> /// </summary>
public class FileDbCache : ObjectCache, IDisposable public class FileDbCache : ObjectCache, IDisposable
{ {
@ -196,40 +194,19 @@ namespace MapControl.Caching
if (fileDb.IsOpen) if (fileDb.IsOpen)
{ {
Record record = null;
try 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) catch (Exception ex)
{ {
Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message); 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; return null;
@ -254,6 +231,11 @@ namespace MapControl.Caching
throw new ArgumentNullException("The parameter key must not be null."); 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) if (policy == null)
{ {
throw new ArgumentNullException("The parameter policy must not be 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."); 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) if (fileDb.IsOpen)
{ {
byte[] buffer = null; var expires = DateTime.MaxValue;
try if (policy.AbsoluteExpiration != InfiniteAbsoluteExpiration)
{ {
var encoder = new PngBitmapEncoder(); expires = policy.AbsoluteExpiration.DateTime;
encoder.Frames.Add(bitmap);
using (var memoryStream = new MemoryStream())
{
encoder.Save(memoryStream);
buffer = memoryStream.ToArray();
}
} }
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; AddOrUpdateRecord(key, value, expires);
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);
}
} }
} }
} }
@ -447,7 +401,7 @@ namespace MapControl.Caching
return false; 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); var fieldValues = new FieldValues(3);
fieldValues.Add(valueField, value); fieldValues.Add(valueField, value);

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.6.0")] [assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.6.0")] [assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.6.0")] [assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.6.0")] [assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -40,12 +40,9 @@
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="PresentationCore" />
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Runtime.Caching" /> <Reference Include="System.Runtime.Caching" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ImageFileCache.cs" /> <Compile Include="ImageFileCache.cs" />

View file

@ -6,22 +6,32 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.Caching; using System.Runtime.Caching;
using System.Security.AccessControl; using System.Security.AccessControl;
using System.Security.Principal; using System.Security.Principal;
using System.Windows.Media.Imaging;
namespace MapControl.Caching namespace MapControl.Caching
{ {
/// <summary> /// <summary>
/// ObjectCache implementation based on local image files. /// 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[].
/// </summary> /// </summary>
public class ImageFileCache : ObjectCache public class ImageFileCache : ObjectCache
{ {
private static readonly Tuple<string, byte[]>[] imageFileTypes = new Tuple<string, byte[]>[]
{
new Tuple<string, byte[]>(".png", new byte[] { 0x89, 0x50, 0x4E, 0x47, 0xD, 0xA, 0x1A, 0xA }),
new Tuple<string, byte[]>(".jpg", new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0, 0x10, 0x4A, 0x46, 0x49, 0x46, 0 }),
new Tuple<string, byte[]>(".bmp", new byte[] { 0x42, 0x4D }),
new Tuple<string, byte[]>(".gif", new byte[] { 0x47, 0x49, 0x46 }),
new Tuple<string, byte[]>(".tif", new byte[] { 0x49, 0x49, 42, 0 }),
new Tuple<string, byte[]>(".tif", new byte[] { 0x4D, 0x4D, 0, 42 }),
new Tuple<string, byte[]>(".wdp", new byte[] { 0x49, 0x49, 0xBC }),
new Tuple<string, byte[]>(".bin", new byte[] { }),
};
private static readonly FileSystemAccessRule fullControlRule = new FileSystemAccessRule( private static readonly FileSystemAccessRule fullControlRule = new FileSystemAccessRule(
new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null), new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null),
FileSystemRights.FullControl, AccessControlType.Allow); FileSystemRights.FullControl, AccessControlType.Allow);
@ -87,44 +97,52 @@ namespace MapControl.Caching
public override bool Contains(string key, string regionName = null) 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) public override object Get(string key, string regionName = null)
{ {
var bitmap = memoryCache.Get(key, regionName) as BitmapFrame; if (key == null)
if (bitmap == null)
{ {
try throw new ArgumentNullException("The parameter key must not be null.");
{ }
var path = FindFile(GetPath(key));
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)) buffer = File.ReadAllBytes(path);
{ memoryCache.Set(key, buffer, new CacheItemPolicy());
bitmap = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); }
catch (Exception ex)
var metadata = (BitmapMetadata)bitmap.Metadata; {
DateTime expiration; Debug.WriteLine("ImageFileCache: Writing file {0} failed: {1}", path, ex.Message);
// 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);
}
}
} }
}
catch
{
} }
} }
return bitmap; return buffer;
} }
public override CacheItem GetCacheItem(string key, string regionName = null) 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) public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
{ {
var bitmap = value as BitmapFrame; if (key == null)
if (bitmap == 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; if (regionName != null)
var format = metadata != null ? metadata.Format : "bmp";
BitmapEncoder encoder = null;
switch (format)
{ {
case "bmp": throw new NotSupportedException("The parameter regionName must be null.");
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;
} }
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 try
{ {
Directory.CreateDirectory(Path.GetDirectoryName(path)); Directory.CreateDirectory(Path.GetDirectoryName(path));
File.WriteAllBytes(path, buffer);
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write))
{
encoder.Frames.Add(bitmap);
encoder.Save(fileStream);
}
var fileSecurity = File.GetAccessControl(path); var fileSecurity = File.GetAccessControl(path);
fileSecurity.AddAccessRule(fullControlRule); fileSecurity.AddAccessRule(fullControlRule);
@ -240,43 +231,56 @@ namespace MapControl.Caching
public override object Remove(string key, string regionName = null) public override object Remove(string key, string regionName = null)
{ {
var oldValue = Get(key, regionName); if (key == null)
memoryCache.Remove(key, regionName);
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.");
}
memoryCache.Remove(key);
var path = FindFile(key);
if (path != null)
{
try
{ {
File.Delete(path); File.Delete(path);
} }
} catch (Exception ex)
catch {
{ 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) try
{
if (!string.IsNullOrEmpty(Path.GetExtension(path)))
{ {
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();
}
} }
catch (Exception ex)
string folderName = Path.GetDirectoryName(path);
if (Directory.Exists(folderName))
{ {
return Directory.EnumerateFiles(folderName, Path.GetFileName(path) + ".*").FirstOrDefault(); Debug.WriteLine("ImageFileCache: Finding file {0} failed: {1}", path, ex.Message);
} }
return null; return null;

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.6.0")] [assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.6.0")] [assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -40,30 +40,26 @@ namespace MapControl.Caching
{ {
var item = await rootFolder.TryGetItemAsync(key); 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; return null;
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;
} }
public virtual async Task SetAsync(string key, IBuffer buffer, DateTime expires) 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); var file = await folder.CreateFileAsync(names[names.Length - 1], CreationCollisionOption.ReplaceExisting);
//Debug.WriteLine("ImageFileCache: Writing file {0}", file.Path);
await FileIO.WriteBufferAsync(file, buffer); await FileIO.WriteBufferAsync(file, buffer);
// Use ImageProperties.DateTaken to store expiration date // Store expiration date in ImageProperties.DateTaken
var imageProperties = await file.Properties.GetImagePropertiesAsync(); var properties = await file.Properties.GetImagePropertiesAsync();
imageProperties.DateTaken = expires; properties.DateTaken = expires;
await imageProperties.SavePropertiesAsync(); await properties.SavePropertiesAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.WriteLine(ex.Message); Debug.WriteLine("ImageFileCache: Writing file {0}\\{1} failed: {2}", rootFolder.Path, key, ex.Message);
} }
} }
} }

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.6.0")] [assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.6.0")] [assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -18,6 +18,9 @@ using System.Windows.Media.Imaging;
namespace MapControl namespace MapControl
{ {
/// <summary>
/// Displays Bing Maps tiles. The static ApiKey property must be set to a Bing Maps API Key.
/// </summary>
public class BingMapsTileLayer : TileLayer public class BingMapsTileLayer : TileLayer
{ {
public enum MapMode public enum MapMode
@ -26,6 +29,12 @@ namespace MapControl
} }
public BingMapsTileLayer() public BingMapsTileLayer()
: this(new TileImageLoader())
{
}
public BingMapsTileLayer(ITileImageLoader tileImageLoader)
: base(tileImageLoader)
{ {
MinZoomLevel = 1; MinZoomLevel = 1;
MaxZoomLevel = 21; MaxZoomLevel = 21;
@ -43,7 +52,7 @@ namespace MapControl
if (string.IsNullOrEmpty(ApiKey)) 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); var uri = string.Format("http://dev.virtualearth.net/REST/V1/Imagery/Metadata/{0}?output=xml&key={1}", Mode, ApiKey);

View file

@ -8,7 +8,7 @@ namespace MapControl
{ {
internal class BingMapsTileSource : TileSource internal class BingMapsTileSource : TileSource
{ {
private string[] subdomains; private readonly string[] subdomains;
public BingMapsTileSource(string uriFormat, string[] subdomains) public BingMapsTileSource(string uriFormat, string[] subdomains)
: base(uriFormat) : base(uriFormat)

View file

@ -8,7 +8,7 @@ using System.Windows.Input;
namespace MapControl namespace MapControl
{ {
/// <summary> /// <summary>
/// Default input event handling. /// MapBase with default input event handling.
/// </summary> /// </summary>
public class Map : MapBase public class Map : MapBase
{ {

View file

@ -8,7 +8,7 @@ using System.Windows.Input;
namespace MapControl namespace MapControl
{ {
/// <summary> /// <summary>
/// Default input event handling. /// MapBase with default input event handling.
/// </summary> /// </summary>
public class Map : MapBase public class Map : MapBase
{ {

View file

@ -10,7 +10,7 @@ using Windows.UI.Xaml.Input;
namespace MapControl namespace MapControl
{ {
/// <summary> /// <summary>
/// Default input event handling. /// MapBase with default input event handling.
/// </summary> /// </summary>
public class Map : MapBase public class Map : MapBase
{ {

View file

@ -21,7 +21,7 @@ using System.Windows.Media.Animation;
namespace MapControl namespace MapControl
{ {
/// <summary> /// <summary>
/// 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 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. /// 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. /// MapBase can contain map overlay child elements like other MapPanels or MapItemsControls.

View file

@ -14,8 +14,8 @@ using System.Windows;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.6.0")] [assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.6.0")] [assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -11,6 +11,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Runtime.Caching; using System.Runtime.Caching;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Media; using System.Windows.Media;
@ -197,7 +198,7 @@ namespace MapControl
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.WriteLine("Loading tile image failed: {0}", (object)ex.Message); Debug.WriteLine("ImageTileSource.LoadImage: " + ex.Message);
} }
return image; return image;
@ -218,7 +219,7 @@ namespace MapControl
} }
catch (Exception ex) 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 try
{ {
var request = HttpWebRequest.CreateHttp(uri); var request = HttpWebRequest.CreateHttp(uri);
request.UserAgent = HttpUserAgent;
if (HttpUserAgent != null)
{
request.UserAgent = HttpUserAgent;
}
using (var response = (HttpWebResponse)request.GetResponse()) using (var response = (HttpWebResponse)request.GetResponse())
{ {
@ -240,23 +245,24 @@ namespace MapControl
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
responseStream.CopyTo(memoryStream); responseStream.CopyTo(memoryStream);
memoryStream.Position = 0; memoryStream.Seek(0, SeekOrigin.Begin);
image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
if (cacheKey != null) image = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
{
SetCachedImage(cacheKey, image, GetExpiration(response.Headers)); if (cacheKey != null)
{
SetCachedImage(cacheKey, memoryStream, GetExpiration(response.Headers));
}
} }
} }
} }
catch (WebException ex) 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) catch (Exception ex)
{ {
Debug.WriteLine("Downloading {0} failed: {1}", uri, ex.Message); Debug.WriteLine("{0}: {1}", uri, ex.Message);
} }
return image; return image;
@ -274,34 +280,44 @@ namespace MapControl
private static bool GetCachedImage(string cacheKey, out BitmapSource image) 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; return false;
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;
} }
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); memoryStream.Seek(0, SeekOrigin.End);
var metadata = (BitmapMetadata)bitmap.Metadata; memoryStream.Write(Encoding.ASCII.GetBytes("EXPIRES:"), 0, 8);
memoryStream.Write(BitConverter.GetBytes(expiration.Ticks), 0, 8);
// store cache expiration date in BitmapMetadata.DateTaken Cache.Set(cacheKey, memoryStream.ToArray(), new CacheItemPolicy { AbsoluteExpiration = expiration });
metadata.DateTaken = expiration.ToString(CultureInfo.InvariantCulture);
metadata.Freeze();
bitmap.Freeze();
Cache.Set(cacheKey, bitmap, new CacheItemPolicy { AbsoluteExpiration = expiration });
//Debug.WriteLine("Cached {0}, Expires {1}", cacheKey, expiration); //Debug.WriteLine("Cached {0}, Expires {1}", cacheKey, expiration);
} }

View file

@ -20,7 +20,7 @@ using System.Windows.Threading;
namespace MapControl namespace MapControl
{ {
/// <summary> /// <summary>
/// Fills a rectangular area with map tiles from a TileSource. /// Fills the map viewport with map tiles from a TileSource.
/// </summary> /// </summary>
#if NETFX_CORE #if NETFX_CORE
[ContentProperty(Name = "TileSource")] [ContentProperty(Name = "TileSource")]

View file

@ -153,7 +153,7 @@ namespace MapControl
} }
return new Uri(uriFormat 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)), .Replace("{q}", new string(quadkey)),
UriKind.RelativeOrAbsolute); UriKind.RelativeOrAbsolute);
} }

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.6.0")] [assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.6.0")] [assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.6.0")] [assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.6.0")] [assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.6.0")] [assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.6.0")] [assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.6.0")] [assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.6.0")] [assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("2.6.0")] [assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.6.0")] [assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -10,6 +10,7 @@ namespace UniversalApp
public MainPage() public MainPage()
{ {
//TileImageLoader.Cache = new MapControl.Caching.ImageFileCache(); //TileImageLoader.Cache = new MapControl.Caching.ImageFileCache();
//TileImageLoader.Cache = new MapControl.Caching.FileDbCache();
this.InitializeComponent(); this.InitializeComponent();
} }

View file

@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("2.6.0")] [assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.6.0")] [assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]

View file

@ -144,6 +144,10 @@
</Page> </Page>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Caching\FileDbCache.WinRT\FileDbCache.WinRT.csproj">
<Project>{c7bf2b18-cc74-430b-bcb2-600304efa3d8}</Project>
<Name>FileDbCache.WinRT</Name>
</ProjectReference>
<ProjectReference Include="..\..\Caching\ImageFileCache.WinRT\ImageFileCache.WinRT.csproj"> <ProjectReference Include="..\..\Caching\ImageFileCache.WinRT\ImageFileCache.WinRT.csproj">
<Project>{f789647e-96f7-43e3-a895-fa3fe8d01260}</Project> <Project>{f789647e-96f7-43e3-a895-fa3fe8d01260}</Project>
<Name>ImageFileCache.WinRT</Name> <Name>ImageFileCache.WinRT</Name>

View file

@ -7,8 +7,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCompany("Clemens Fischer")] [assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("© 2015 Clemens Fischer")] [assembly: AssemblyCopyright("© 2015 Clemens Fischer")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("2.6.0")] [assembly: AssemblyVersion("2.7.0")]
[assembly: AssemblyFileVersion("2.6.0")] [assembly: AssemblyFileVersion("2.7.0")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]