mirror of
https://github.com/ClemensFischer/XAML-Map-Control.git
synced 2026-04-05 06:26:41 +00:00
Version 2.4.0.
- Added ImageFileCache and FileDbCache for WinRT - Improved TileImageLoader - Removed TileContainer, TileLayer can be added as MapBase child - Removed attached property MapPanel.ViewportPosition
This commit is contained in:
parent
f04025067c
commit
3b6545e738
84 changed files with 5504 additions and 1940 deletions
BIN
Caching/FileDb/FileDb.dll
Normal file
BIN
Caching/FileDb/FileDb.dll
Normal file
Binary file not shown.
1870
Caching/FileDb/FileDb.xml
Normal file
1870
Caching/FileDb/FileDb.xml
Normal file
File diff suppressed because it is too large
Load diff
BIN
Caching/FileDb/FileDbPcl.dll
Normal file
BIN
Caching/FileDb/FileDbPcl.dll
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load diff
983
Caching/FileDb/Help.html
Normal file
983
Caching/FileDb/Help.html
Normal file
|
|
@ -0,0 +1,983 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Language" content="en-au">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<title>FileDb Overview</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<b>
|
||||
<p><font face="Arial" size="4" color="#0066CC">Overview</font></p>
|
||||
</b>
|
||||
<p><font face="Arial">FileDb is a simple database designed as a simple
|
||||
local database solution for Xamarin Cross-Platform phone (Android, IOS, WinPhone)
|
||||
or any .NET application. <u>FileDb is a No-SQL database</u>
|
||||
meant for use as a local data store for applications.
|
||||
Here are some important points about FileDb:</font></p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<font face="Arial">Stores one table per file, including its index</font></li>
|
||||
<li>
|
||||
<font face="Arial">Extremely Lightweight - less than 50K</font></li>
|
||||
<li>
|
||||
<font face="Arial">Supports field types Int, UInt, Bool, String, Byte,
|
||||
Float, Double and DateTime and also arrays of the same
|
||||
types</font></li>
|
||||
<li>
|
||||
<font face="Arial">Index supports a single Primary Key field (optional)</font></li>
|
||||
<li>
|
||||
<font face="Arial">Compiled versions for Windows Phone
|
||||
RT/PCL,
|
||||
.NET</font></li>
|
||||
<li>
|
||||
<font face="Arial">FileDb is VERY FAST</font></li>
|
||||
<li>
|
||||
<font face="Arial">FileDb is FREE to use in your applications</font></li>
|
||||
<li>
|
||||
<font face="Arial">Use with LINQ to Objects to achieve full relational
|
||||
capability</font></li>
|
||||
</ul>
|
||||
|
||||
<p><font face="Arial">FileDb was specifically designed to use only native
|
||||
.NET data types so there would no need to translate
|
||||
between database storage and the CLR data types.
|
||||
So you can just as easily read/write a String[] field as
|
||||
you would an Int field. Another feature is that a
|
||||
database file created on any .NET platform will work on
|
||||
any other. So you can create a database file on
|
||||
your Windows machine and it can be used in a Silverlight
|
||||
or Windows Phone app.</font></p>
|
||||
|
||||
<p><font color="#0066CC" face="Arial"><b>LINQ + FileDb gives you full
|
||||
relational database capability</b></font></p>
|
||||
<p><font face="Arial">Even though FileDb is a "flat-file" database, using
|
||||
LINQ it becomes fully relational! LINQ
|
||||
to Objects allows you to join Tables together just as
|
||||
you would do in SQL. All of the power of LINQ is
|
||||
available to you: Joins, Grouping, Sum - the lot.
|
||||
(See
|
||||
the examples below.)</font></p>
|
||||
<p><font face="Arial">FileDb also has a built-in
|
||||
query filter parser so you can write SQL-like
|
||||
filter expressions to make filtering data easy, like
|
||||
this:</font></p>
|
||||
<blockquote>
|
||||
<p><b>
|
||||
<font color="#663300" face="Courier New" size="2">
|
||||
string filter = "FirstName IN ('Cindy', 'John') AND Age > 32"</font></b></p>
|
||||
</blockquote>
|
||||
<p><font face="Arial">Use FileDb in your .NET and mobile
|
||||
applications where you need a searchable, updatable
|
||||
local database.</font><p>
|
||||
<b><font face="Arial" color="#0066CC">Use FileDb with Xamarin cross-platform app
|
||||
development</font></b><p><font face="Arial">Most phone apps only require a
|
||||
simple database. By purchasing a source code license you can compile
|
||||
FileDb into your IOS, Android and Windows Phone projects and use the same exact
|
||||
data layer code for them all. This is much easier than using Sqlite
|
||||
wrappers. With FileDb's built in encryption you can have everything you
|
||||
need to have a secure data layer.</font></p>
|
||||
<p>
|
||||
<font color="#0066CC" face="Arial"><b>FileDb Database Overview</b></font></p>
|
||||
|
||||
<p><font face="Arial">FileDb is a simple database designed for use on any .NET platform such as Windows
|
||||
Phone and Silverlight, but its also great for any .NET app where simple local
|
||||
database storage is needed. For example, instead of using XML config files you
|
||||
could use a FileDb database to store and retrieve application data much more
|
||||
efficiently. FileDb allows only a single table per database file, so when we
|
||||
talk about a FileDb database we really mean a single table with an index. The
|
||||
index is stored in the file with the data, and allows an optional Primary Key.</font></p>
|
||||
<p><font face="Arial">FileDb is NOT a relational database - it is NO-SLQ,
|
||||
meaning you can't directly issue SQL commands for querying, adding or updating. However,
|
||||
you CAN use LINQ with
|
||||
FileDb to get full relational query capabilities. And FileDb does include an Expression Parser which parses SQL-like filter
|
||||
expressions, which makes searching, updating and deleting very easy - see below for an example.</font></p>
|
||||
<p><font face="Arial"><u>And FileDb supports using powerful Regular Expressions for
|
||||
filtering.</u></font></p>
|
||||
<p><font face="Arial"><u>FileDb supports AES encryption</u> at the record level. This
|
||||
means the database schema is not encrypted (field names, etc.), but each record
|
||||
is entirely encrypted. Encryption is "all or nothing", meaning it expects that
|
||||
either all records are encrypted or all records are not encrypted. You turn
|
||||
encryption on by passing an encryption key when opening the database.</font></p>
|
||||
<u>
|
||||
<p dir="ltr"><font face="Arial">FileDb is thread-safe for multithreading environments, so it can be accessed from
|
||||
multiple threads at the same time without worrying about database corruption.</font></u></p>
|
||||
<p><font face="Arial">FileDb databases can only be opened by a single
|
||||
application. Any attempt to open the file when already open will fail.
|
||||
This makes sense since its meant for use by a single application at a time
|
||||
(FileDb is not meant as a multi-user database, such as SQL Server Express).<br>
|
||||
</font></p>
|
||||
<p><b><font color="#0066CC" face="Arial">FileDb Classes</font></b></p>
|
||||
<p><font face="Arial">The main FileDb classes are: <b>FileDb</b>, <b>Table</b>,
|
||||
<b>Field</b> and <b>Record</b>. </font> </p>
|
||||
<dir>
|
||||
<b>
|
||||
<li><font face="Arial">FileDb</font></b><font face="Arial">: Represents a database file. All database operations are
|
||||
initiated through this class.<br>
|
||||
</li>
|
||||
</font>
|
||||
<b>
|
||||
<li><font face="Arial">Table</font></b><font face="Arial">: Represents a two
|
||||
dimensional dataset returned from a query. A Table consists of <b>Fields </b>
|
||||
and <b>Records</b>.<br>
|
||||
</li>
|
||||
</font>
|
||||
<b>
|
||||
<li><font face="Arial">Field</font></b><font face="Arial">: Defines the
|
||||
properties of the table column, such as Name and DataType.<br>
|
||||
</li>
|
||||
</font>
|
||||
<b>
|
||||
<li><font face="Arial">Fields</font></b><font face="Arial">: A List of Field
|
||||
objects.<br>
|
||||
</font></li>
|
||||
<li>
|
||||
<font face="Arial">
|
||||
<b>
|
||||
Record</b>: A list of data objects represents a single row in a Table.
|
||||
Implements IEnumerable and the Data property which is used for DataBinding.<br>
|
||||
</li>
|
||||
</font>
|
||||
<b>
|
||||
<li><font face="Arial">Records</font></b><font face="Arial">: A List of
|
||||
Record objects.<br>
|
||||
</li>
|
||||
</font>
|
||||
<b>
|
||||
<li><font face="Arial">FieldValues</font></b><font face="Arial">: A simple
|
||||
Name/Value pair Dictionary. Use this class when adding and updating records.<br>
|
||||
</li>
|
||||
</font>
|
||||
<b>
|
||||
<li><font face="Arial">FilterExpression</font></b><font face="Arial">: Used
|
||||
to filter records for query, update and delete.<br>
|
||||
</li>
|
||||
</font>
|
||||
<b>
|
||||
<li><font face="Arial">FilterExpressionGroup</font></b><font face="Arial">:
|
||||
Used to create compound expressions by grouping FilterExpressions and
|
||||
FilterExpressionGroups.<br>
|
||||
</font></li>
|
||||
</dir>
|
||||
<p><b><font color="#0066CC" face="Arial">Database Fields</font></b></p>
|
||||
<p><font face="Arial">Fields (or Columns) can be of several common types:
|
||||
String, Int, UInt, Bool, Byte, Float, Double and DateTime, or can also be an array of any of
|
||||
these types.</font></p>
|
||||
<p><font face="Arial">Int Fields can be AutoIncrementing, and you can optionally
|
||||
specify one field to be Primary Key (it must be of type Int or String).</font></p>
|
||||
<p><font face="Arial">FileDb doesn't support the notion of NULL fields for the
|
||||
non-array type. Only array type fields can have NULL values. The non-array field
|
||||
values will always have a value, either zero or empty.</font></p>
|
||||
<p><font color="#0066CC" face="Arial"><b>FileDb Records</b></font></p>
|
||||
<p><font face="Arial">FileDb supports two methods of data retrieval. You can say the
|
||||
"default" way is with the built-in Record and Records classes. Think of
|
||||
Record as the .NET DataRow class, and think of Table as a DataTable. Table
|
||||
is a list of Records, and a Record holds the actual values. You access Field
|
||||
values using indexing just as you would a DataRow, like this:</font></p>
|
||||
<blockquote>
|
||||
<font FACE="Courier New">
|
||||
<table border="0" width="100%" id="table25" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td>
|
||||
<font face="Courier New" size="2">FileDb employeesDb = new FileDb();<br>
|
||||
employeesDb.Open( Employees.fdb" );<br>
|
||||
<br>
|
||||
Table employees = employeesDb.SelectAllRecords();<br>
|
||||
Record record =
|
||||
employees[0];<br>
|
||||
int id = (int) record["EmployeeId"];<br>
|
||||
// or<br>
|
||||
id
|
||||
= (int) record[0];</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<p><font face="Arial">To use a Table with LINQ, you do this:</font></p>
|
||||
<blockquote>
|
||||
<font FACE="Courier New">
|
||||
<table border="0" width="100%" id="table26" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td>
|
||||
<font FACE="Courier New" size="2">var recs = from e in
|
||||
employees<br>
|
||||
where (string)
|
||||
e["FirstName"] == "John"<br>
|
||||
select e;</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<p><font face="Arial">Notice we have to cast the record value to a string.
|
||||
This is because, just like with the DataRow, Record
|
||||
values are all type object.</font></p>
|
||||
<p><font color="#0066CC" face="Arial"><b>Records and Custom Objects</b></font></p>
|
||||
<p><font face="Arial">Records are great because they require no additional
|
||||
programming and they work with LINQ, albeit with some
|
||||
casting. But you can use your own custom classes
|
||||
if you want because FileDb has template (generic)
|
||||
overloads for each of the SelectRecords methods.
|
||||
You only need to create a class with public properties
|
||||
which match the names of the fields you want to use.
|
||||
Here's an example using the Employees table.</font></p>
|
||||
<blockquote>
|
||||
<font FACE="Courier New">
|
||||
<table border="0" width="100%" id="table27" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td>
|
||||
<font FACE="Courier New" size="2">public class Employee<br>
|
||||
{<br>
|
||||
public int EmployeeID { get; set; }<br>
|
||||
public string LastName { get; set; }<br>
|
||||
|
||||
public string
|
||||
FirstName { get; set; }<br>
|
||||
|
||||
public string Title {
|
||||
get; set; }<br>
|
||||
|
||||
public string
|
||||
TitleOfCourtesy { get; set; }<br>
|
||||
|
||||
public DateTime
|
||||
BirthDate { get; set; }<br>
|
||||
|
||||
public DateTime
|
||||
HireDate { get; set; }<br>
|
||||
|
||||
public string Address
|
||||
{ get; set; }<br>
|
||||
|
||||
public string City {
|
||||
get; set; }<br>
|
||||
|
||||
public string Region {
|
||||
get; set; }<br>
|
||||
|
||||
public string
|
||||
PostalCode { get; set; }<br>
|
||||
|
||||
public string Country
|
||||
{ get; set; }<br>
|
||||
|
||||
public string
|
||||
HomePhone { get; set; }<br>
|
||||
|
||||
public string
|
||||
Extension { get; set; }<br>
|
||||
|
||||
public Byte[] Photo {
|
||||
get; set; }<br>
|
||||
|
||||
public string Notes {
|
||||
get; set; }<br>
|
||||
|
||||
public int ReportsTo {
|
||||
get; set; }<br>
|
||||
}</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<p><font face="Arial">The templated SelectRecords versions return a IList<T>
|
||||
where T is your custom type.</font></p>
|
||||
<blockquote>
|
||||
<font FACE="Courier New">
|
||||
<table border="0" width="100%" id="table28" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td>
|
||||
<font FACE="Courier New" size="2">
|
||||
IList<Employee>
|
||||
employees = employeesDb.SelectAllRecords<Employee>();<br>
|
||||
Employee employee
|
||||
= employees[0];<br>
|
||||
int id = Employee.EmployeeId;<br>
|
||||
<br>
|
||||
var emps = from e in
|
||||
employees<br>
|
||||
where e.FirstName
|
||||
== "John"<br>
|
||||
select e;</td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<p><font face="Arial">As you can see, this is much cleaner code. And
|
||||
its actually more efficient since the Record class has
|
||||
more overhead because its not as simple.</font></p>
|
||||
<p><b><font color="#0066CC" face="Arial">Searching and Filtering</font></b></p>
|
||||
<p><font face="Arial">FileDb uses FilterExpressions and
|
||||
FilterExpressionGroups to filter records in queries and
|
||||
updates. We use FilterExpressions for simple queries
|
||||
which consist of a single field comparison (field =
|
||||
'value') and we use FilterExpressionGroups for compound
|
||||
expressions, where multiple expressions and grouping are
|
||||
required. You can add either FilterExpressions or
|
||||
FilterExpressionGroups to a FilterExpressionGroup, thus
|
||||
creating complex expresssions (FileDb processes
|
||||
FilterExpressionGroups recursively).</font></p>
|
||||
<p><font face="Arial">You can either create your own manually in code or
|
||||
use the built-in Expression Parser to create them for
|
||||
you. The Expression Parser recognizes standard SQL
|
||||
comparison operators. You can see it used in the
|
||||
examples below. It also recognizes REGEX, which
|
||||
uses Regular Expressions, and CONTAINS which uses <b>
|
||||
String.Contains</b>. See the section on
|
||||
Regular Expressions below for more info. Field names
|
||||
prefixed with ~ specifies no-case comparison (for
|
||||
strings only).</font></p>
|
||||
<p><font face="Arial">Each time you use () around an expression, a new
|
||||
FilterExpressionGroup will be created. The inner-most expressions are evaluated
|
||||
first, just as in SQL.</font></p>
|
||||
<b>
|
||||
<p><font color="#333333" face="Arial">Example 1: Create a FilterExpression</font></p>
|
||||
</b>
|
||||
<blockquote>
|
||||
<font FACE="Courier New">
|
||||
<table border="0" width="100%" id="table29" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td>
|
||||
<font size="2" FACE="Courier New">// build an expression manually<br>
|
||||
FilterExpression searchExp = new FilterExpression( "LastName", "Peacock",
|
||||
Equality.Equal );<br>
|
||||
<br>
|
||||
// build the same expression using the
|
||||
parser<br>
|
||||
searchExp = FilterExpression.Parse( "LastName = 'Peacock'" ); <br>
|
||||
Table table = employeesDb.SelectRecords(
|
||||
searchExp,
|
||||
new string[] { "ID", "LastName" } );<br>
|
||||
<br>
|
||||
// Or you can simply pass the string filter
|
||||
directly - a FilterExpression will be
|
||||
created in the same way as above<br>
|
||||
<br>
|
||||
table = employeesDb.SelectRecords( "LastName
|
||||
= 'Peacock'", new string[] { "ID", "LastName"
|
||||
} );<br>
|
||||
<br>
|
||||
foreach( Record record in table )<br>
|
||||
{<br>
|
||||
foreach( object value in record )<br>
|
||||
{<br>
|
||||
|
||||
Debug.WriteLine( value );<br>
|
||||
|
||||
}<br>
|
||||
}</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<b>
|
||||
<p><font color="#333333" face="Arial"><br>
|
||||
Example 2: Create a FilterExpressionGroup</font></p>
|
||||
</b>
|
||||
<p><font face="Arial">This example creates two identical FilterExpressionGroups,
|
||||
one using the Expression Parser and the other with code.</font></p>
|
||||
<blockquote>
|
||||
<font FACE="Courier New">
|
||||
<table border="0" width="100%" id="table30" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td><font size="2" FACE="Courier New">// For string fields there are
|
||||
2 ways to specify </font><font size="2" FACE="Courier New">no-case
|
||||
comparisons: you can prefix fieldnames with ~ or you can use ~= as
|
||||
demonstrated below<br>
|
||||
// The first form is needed when using the IN operator, eg.<br>
|
||||
FilterExpressionGroup filterExpGrp =
|
||||
FilterExpressionGroup.Parse( "(FirstName ~= 'andrew' OR ~FirstName = 'nancy')
|
||||
AND LastName = 'Fuller'" );<br>
|
||||
Table table = employeesDb.SelectRecords( filterExpGrp );<br>
|
||||
<br>
|
||||
// equivalent building it manually<br>
|
||||
var fname1Exp = new FilterExpression( "FirstName", "andrew", Equality.Equal,
|
||||
MatchType.IgnoreCase );<br>
|
||||
var fname2Exp = new FilterExpression( "FirstName", "nancy", Equality.Equal,
|
||||
MatchType.IgnoreCase );<br>
|
||||
var lnameExp = new FilterExpression( "LastName", "Fuller", Equality.Equal );
|
||||
// this constructor defaults to MatchType.UseCase<br>
|
||||
var fnamesGrp = new FilterExpressionGroup();<br>
|
||||
fnamesGrp.Add( BoolOp.Or, fname1Exp );<br>
|
||||
fnamesGrp.Add( BoolOp.Or, fname2Exp );<br>
|
||||
var allNamesGrp = new FilterExpressionGroup();<br>
|
||||
allNamesGrp.Add( BoolOp.And, lnameExp );<br>
|
||||
allNamesGrp.Add( BoolOp.And, fnamesGrp );<br>
|
||||
<br>
|
||||
table = employeesDb.SelectRecords( allNamesGrp );<br>
|
||||
<br>
|
||||
// or just pass the filter string directly<br>
|
||||
<br>
|
||||
table = employeesDb.SelectRecords( "(FirstName ~= 'andrew' OR ~FirstName = 'nancy')
|
||||
AND LastName = 'Fuller'" );<br>
|
||||
</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<p><font face="Arial"><br>
|
||||
FileDb supports these comparison operators:</font></p>
|
||||
<font face="Arial" size="2">
|
||||
<font face="Courier New">
|
||||
<blockquote>
|
||||
<table border="0" id="table31" style="border-collapse: collapse" bgcolor="#FBEDBB" cellpadding="7">
|
||||
<tr>
|
||||
<font face="Courier New" size="2">
|
||||
<td><b>=</b></td>
|
||||
</font><font face="Arial" size="2">
|
||||
<td width="18"> </td>
|
||||
<font face="Courier New">
|
||||
<td><font face="Arial">Equality</font></td>
|
||||
</font></font>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>~<b>=</b></td>
|
||||
</font><font face="Arial">
|
||||
<td width="18"> </td>
|
||||
<font face="Courier New">
|
||||
<td>No-case equality - strings only</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font face="Courier New"><b><></b></font></td>
|
||||
</font><font face="Arial">
|
||||
<td width="18"> </td>
|
||||
<font face="Courier New">
|
||||
<td><font face="Arial">Not Equal</font></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font face="Courier New"><b>!=</b></font></td>
|
||||
</font><font face="Arial">
|
||||
<td width="18"> </td>
|
||||
<font face="Courier New">
|
||||
<td><font face="Arial">Not Equal (same as <>)</font></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font face="Courier New"><b>>=</b></font></td>
|
||||
</font><font face="Arial">
|
||||
<td width="18"> </td>
|
||||
<font face="Courier New">
|
||||
<td><font face="Arial">Greater than or Equal</font></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><font face="Courier New"><b><=</b></font></td>
|
||||
</font><font face="Arial">
|
||||
<td width="18"> </td>
|
||||
<font face="Courier New">
|
||||
<td><font face="Arial">Less than or Equal</font></td>
|
||||
</tr>
|
||||
</font>
|
||||
<font face="Courier New" size="2">
|
||||
<tr>
|
||||
<td><b><font face="Courier New">REGEX</font></b></td>
|
||||
</font>
|
||||
</font>
|
||||
<font face="Arial" size="2">
|
||||
<font face="Arial">
|
||||
<td width="18"> </td>
|
||||
<td><font face="Arial">Use Regular Expression</font></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<font face="Arial" size="2">
|
||||
<td><b><font face="Courier New">CONTAINS</font></b></td>
|
||||
<font face="Arial">
|
||||
<td width="18"> </td>
|
||||
<td><font face="Arial">Use the .NET String's Contains method</font></td>
|
||||
</font>
|
||||
</font>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b><font face="Courier New">IN</font></b></td>
|
||||
<td width="18"> </td>
|
||||
<td><font face="Arial">Creates a HashSet of values to use like SQL
|
||||
IN operator</font></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<font face="Arial" size="2">
|
||||
<td><b><font face="Courier New">NOT</font></b></td>
|
||||
<font face="Arial">
|
||||
<td width="18"> </td>
|
||||
<td><font face="Arial">Logical Negation, e.g NOT CONTAINS</font></td>
|
||||
</font>
|
||||
</font>
|
||||
</tr>
|
||||
</table>
|
||||
</blockquote>
|
||||
</font>
|
||||
</font>
|
||||
</font></font></font></font></font>
|
||||
<font face="Arial" size="2">
|
||||
<p> </p>
|
||||
</font>
|
||||
<p><b><font face="Arial" color="#0066CC">REGEX - </font>
|
||||
<font color="#0066CC" face="Arial" size="3">Regular Expressions in searches and filtering</font></b></p>
|
||||
<p><font face="Arial" size="3">FileDb supports using Regular Expressions. You can use any RegEx supported by
|
||||
.NET. The Expression Parser supports MatchType.RegEx using the REGEX operator.
|
||||
Internally, FileDb uses FilterExpressions to evaluate fields. You don't
|
||||
need to use them because you can pass in filter strings and they'll be parsed
|
||||
into FilterExpressions/FilterExpressionGroups for you. This is just to
|
||||
show you how can create them manually if you want to. In
|
||||
the example below, both FilterExpressionGroups are identical.</font></p>
|
||||
<font FACE="Courier New" size="2">
|
||||
<blockquote>
|
||||
<font FACE="Courier New">
|
||||
<table border="0" id="table32" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td><font size="2" FACE="Courier New">// Using the Expression Parser<br>
|
||||
<br>
|
||||
// You can use brackets around fieldnames if there are spaces in the
|
||||
name<br>
|
||||
FilterExpressionGroup filterExpGrp = FilterExpressionGroup.Parse( "(~FirstName = 'steven' OR [FirstName]
|
||||
REGEX 'NANCY') AND LastName = 'Fuller'" );<br>
|
||||
Table table = employeesDb.SelectRecords( filterExpGrp );<br>
|
||||
<br>
|
||||
// we can manually build the same FilterExpressionGroup<br>
|
||||
var fname1Exp = FilterExpression.Parse( "~FirstName = steven" );<br>
|
||||
var fname2Exp = new FilterExpression( "FirstName", "NANCY", Equality.Regex );<br>
|
||||
var lnameExp = new FilterExpression( "LastName", "Fuller", Equality.Equal );<br>
|
||||
var fnamesGrp = new FilterExpressionGroup();<br>
|
||||
fnamesGrp.Add( BoolOp.Or, fname1Exp );<br>
|
||||
fnamesGrp.Add( BoolOp.Or, fname2Exp );<br>
|
||||
var allNamesGrp = new FilterExpressionGroup();<br>
|
||||
allNamesGrp.Add( BoolOp.And, lnameExp );<br>
|
||||
allNamesGrp.Add( BoolOp.And, fnamesGrp );<br>
|
||||
<br>
|
||||
table = employeesDb.SelectRecords( allNamesGrp );</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
</font>
|
||||
<font face="Arial" size="2">
|
||||
<p> </p>
|
||||
</font>
|
||||
<p><b><font color="#0066CC" face="Arial" size="3">Using CONTAINS in searches and filtering</font></b></p>
|
||||
<p><font face="Arial" size="3">FileDb supports using Regular Expressions. You can use any RegEx supported by
|
||||
.NET. The Expression Parser supports MatchType.RegEx using the REGEX operator.
|
||||
Internally, FileDb uses FilterExpressions to evaluate fields. You don't
|
||||
need to use them because you can pass in filter strings and they'll be parsed
|
||||
into FilterExpressions/FilterExpressionGroups for you. This is just to
|
||||
show you how can create them manually if you want to. In
|
||||
the example below, both FilterExpressionGroups are identical.</font></p>
|
||||
<font FACE="Courier New" size="2">
|
||||
<blockquote>
|
||||
<font FACE="Courier New">
|
||||
<table border="0" id="table42" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td><font size="2" FACE="Courier New">// You can use brackets around fieldnames if there are spaces in the
|
||||
name<br>
|
||||
FilterExpressionGroup filterExpGrp = FilterExpressionGroup.Parse( "(~FirstName = 'steven' OR [FirstName]
|
||||
CONTAINS 'NANC') AND LastName = 'Fuller'" );<br>
|
||||
Table table = employeesDb.SelectRecords( filterExpGrp );<br>
|
||||
<br>
|
||||
// we can manually build the same FilterExpressionGroup<br>
|
||||
var fname1Exp = FilterExpression.Parse( "~FirstName = steven" );<br>
|
||||
var fname2Exp = new FilterExpression( "FirstName", "NANCY", Equality.Contains );<br>
|
||||
var lnameExp = new FilterExpression( "LastName", "Fuller", Equality.Equal );<br>
|
||||
var fnamesGrp = new FilterExpressionGroup();<br>
|
||||
fnamesGrp.Add( BoolOp.Or, fname1Exp );<br>
|
||||
fnamesGrp.Add( BoolOp.Or, fname2Exp );<br>
|
||||
var allNamesGrp = new FilterExpressionGroup();<br>
|
||||
allNamesGrp.Add( BoolOp.And, lnameExp );<br>
|
||||
allNamesGrp.Add( BoolOp.And, fnamesGrp );<br>
|
||||
<br>
|
||||
table = employeesDb.SelectRecords( allNamesGrp );</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
</font>
|
||||
<p> </p>
|
||||
<p><b><font color="#0066CC" face="Arial" size="3">Sort Ordering</font></b></p>
|
||||
<p><font face="Arial" size="3">Query methods allow for sorting the results by fields. To
|
||||
get a reverse sort, prefix the sort field list with !. To get a no-case sort,
|
||||
prefix with ~. To get both reverse and no-case sort, use both ! and ~.</font></p>
|
||||
<p><font face="Arial" size="3">Example:</font></p>
|
||||
<blockquote>
|
||||
<font FACE="Courier New" size="2">
|
||||
<table border="0" id="table33" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td>
|
||||
<font FACE="Courier New" size="2">
|
||||
Table table = employeesDb.SelectAllRecords( new string[] { "ID", "Firstname", "LastName",
|
||||
"Age" }, false, new string[] { "~LastName", "~FirstName", "!Age" } );</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<p><font color="#0066CC" face="Arial" size="3"><b>Selecting a Table from a Table</b></font></p>
|
||||
<p><font face="Arial" size="3">Another very powerful feature of FileDb is the ability to select a Table from
|
||||
another Table. This would allow you to be able to select data from a Table
|
||||
after the database file has been closed, for example.</font></p>
|
||||
<p><font face="Arial" size="3">Example:</font></p>
|
||||
<blockquote>
|
||||
<font FACE="Courier New" size="2">
|
||||
<table border="0" id="table34" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td>
|
||||
<font FACE="Courier New" size="2">
|
||||
customersDb.Open( path + "Customers.fdb" );<br>
|
||||
<br>
|
||||
// select all fields and records from the database table<br>
|
||||
Table customers = customersDb.SelectAllRecords();<br>
|
||||
<br>
|
||||
Table subCusts = customers.SelectRecords( "CustomerID <> 'ALFKI'",<br>
|
||||
new string[] { "CustomerID", "CompanyName", "City" }, new string[] { "~City", "~CompanyName"
|
||||
} );</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<p><b><font color="#0066CC" face="Arial" size="3">Encryption</font></b></p>
|
||||
<p><font face="Arial" size="3">Using encryption with FileDb is simple. You only need to
|
||||
specify a string key when you open the database. After that everything is
|
||||
automatic. The only caveat is you must set a key before you add any records.
|
||||
Once a single record has been added without a key set you cannot later add
|
||||
records with a key. Its all or nothing. Likewise, you cannot add records with
|
||||
encryption and later add records without. You must set the encryption key
|
||||
each time you add any records.</font></p>
|
||||
<p><b><font color="#0066CC" face="Arial" size="3">Persisting Tables</font></b></p>
|
||||
<p><font face="Arial" size="3">You can easily save a Table as a new database using
|
||||
Table.SaveToDb. This method creates a new database file using the Fields
|
||||
in the Table then populates it using the Records in the Table. For
|
||||
example, you can select subsets of your data, save it as a new database and send
|
||||
it over the Internet.</font></p>
|
||||
<blockquote>
|
||||
<font FACE="Courier New">
|
||||
<table border="0" id="table35" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td><font FACE="Courier New" size="2">Table table = employeesDb.SelectAllRecords( new string[] { "ID", "Firstname", "LastName"
|
||||
} );<br>
|
||||
table.SaveToDb( "Names.fdb" );</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<p><font face="Arial" size="3">You can also save a Table to a database from the
|
||||
FileDb Explorer. Just right-click on the Grid to show
|
||||
the context menu and select the "Create database from
|
||||
Table..." menu item.<br>
|
||||
</font></p>
|
||||
<p><font color="#0066CC" face="Arial" size="3"><b>Using LINQ to Objects with FileDb</b></font></p>
|
||||
<p><font face="Arial" size="3">Microsoft has done an amazing job with LINQ.
|
||||
They have invested a huge amount of time, effort and $
|
||||
in this technology which allows you to query just about
|
||||
any kind of data in a SQL-like way. We use LINQ
|
||||
with FileDb to join Tables as we would using SQL.
|
||||
The difference is that instead of doing it all in a
|
||||
single step with SQL, we must do it in two steps.
|
||||
First we select the data Tables from the database files
|
||||
then we use LINQ to join them together.</font></p>
|
||||
<p><font face="Arial" size="3">LINQ to Objects produces a list of anonymous types as
|
||||
its result set. This is good because we get
|
||||
strongly typed data objects which we can easily use in WPF/Silverlight apps.</font></p>
|
||||
<p><font face="Arial" size="3">Here is an example of doing a simple select using
|
||||
LINQ:</font></p>
|
||||
<blockquote>
|
||||
<font FACE="Courier New">
|
||||
<table border="0" id="table36" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td><font face="Courier New" size="2">//
|
||||
Using the IN operator. Notice the ~
|
||||
prefix on the LastName field. This is how
|
||||
<br>
|
||||
// you can specify case insensitive searches<br>
|
||||
<br>
|
||||
Table
|
||||
employees = employeesDb.SelectRecords( "~LastName
|
||||
IN ('Fuller', 'Peacock')" );<br>
|
||||
<br>
|
||||
var query =
|
||||
from record in employees<br>
|
||||
select new<br>
|
||||
{<br>
|
||||
ID = record["EmployeeId"],<br>
|
||||
Name = record["FirstName"] + " " + record["LastName"],<br>
|
||||
Title = record["Title"]<br>
|
||||
};<br>
|
||||
<br>
|
||||
foreach( var rec in query )<br>
|
||||
{<br>
|
||||
Debug.WriteLine( rec.ToString() );<br>
|
||||
}</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
<p><font face="Arial" size="3">The only thing LINQ did for us in this example was
|
||||
gave us a typed list of anonymous objects. Here's
|
||||
the same thing but with custom objects:</font></p>
|
||||
<font FACE="Courier New">
|
||||
<table border="0" id="table37" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td><font size="2">IList<Employee></font><font face="Courier New" size="2"> employees
|
||||
= employeesDb.SelectRecords<Employee>(
|
||||
"~LastName
|
||||
IN ('Fuller', 'Peacock')" );<br>
|
||||
<br>
|
||||
var query =<br>
|
||||
from e in employees<br>
|
||||
select e;<br>
|
||||
<br>
|
||||
foreach( var emp in query )<br>
|
||||
{<br>
|
||||
Debug.WriteLine( emp.ToString() );<br>
|
||||
}</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
</blockquote>
|
||||
<p dir="ltr"><font face="Arial" size="3">Now lets tap into LINQ's real power to join tables together
|
||||
like a SQL inner join. Notice in the following example we use the </font>
|
||||
<font face="Courier New" size="2">FilterExpression.CreateInExpressionFromTable</font><font face="Arial"><font size="3">
|
||||
method. We do this to get only the records we are going to need with LINQ.
|
||||
So using FileDb with LINQ is a two step process. You first select the
|
||||
records you will need then use them in the LINQ query. If your database
|
||||
files are large, you can filter the records like this. Otherwise you can
|
||||
just select all records.</font></p>
|
||||
<blockquote>
|
||||
<font FACE="Courier New">
|
||||
<table border="0" id="table38" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td><font face="Courier New" size="2">FileDb customersDb = new FileDb(),<br>
|
||||
ordersDb = new FileDb(),<br>
|
||||
orderDetailsDb = new FileDb(),<br>
|
||||
productsDb = new FileDb();<br>
|
||||
<br>
|
||||
customersDb.Open( "Customers.fdb" );<br>
|
||||
ordersDb.Open( "Orders.fdb" );<br>
|
||||
orderDetailsDb.Open( "OrderDetails.fdb" );<br>
|
||||
productsDb.Open( "Products.fdb" );<br>
|
||||
<br>
|
||||
// get our target Customer records<br>
|
||||
// Note that we should select only fields we
|
||||
need from each table, but to keep the code<br>
|
||||
// simple for this example we just pass in null for the field
|
||||
list<br>
|
||||
<br>
|
||||
FilterExpression filterExp =
|
||||
FilterExpression.Parse( "CustomerID IN( 'ALFKI',
|
||||
'BONAP' )" );<br>
|
||||
FileDbNs.Table customers =
|
||||
customersDb.SelectRecords( filterExp );<br>
|
||||
<br>
|
||||
// now get only Order records for the target
|
||||
Customer records<br>
|
||||
// CreateInExpressionFromTable will create
|
||||
an IN FilterExpression, which uses a HashSet
|
||||
<br>
|
||||
// for high efficiency when filtering
|
||||
records<br>
|
||||
<br>
|
||||
filterExp =
|
||||
FilterExpression.CreateInExpressionFromTable(
|
||||
"CustomerID", customers, "CustomerID" );<br>
|
||||
FileDbNs.Table orders =
|
||||
ordersDb.SelectRecords( filterExp );<br>
|
||||
<br>
|
||||
// now get only OrderDetails records for the
|
||||
target Order records<br>
|
||||
<br>
|
||||
filterExp =
|
||||
FilterExpression.CreateInExpressionFromTable(
|
||||
"OrderID", orders, "OrderID" );<br>
|
||||
FileDbNs.Table orderDetails =
|
||||
orderDetailsDb.SelectRecords( filterExp );<br>
|
||||
<br>
|
||||
// now get only Product records for the
|
||||
target OrderDetails records<br>
|
||||
<br>
|
||||
filterExp =
|
||||
FilterExpression.CreateInExpressionFromTable(
|
||||
"ProductID", orderDetails, "ProductID" );<br>
|
||||
FileDbNs.Table products =
|
||||
productsDb.SelectRecords( filterExp );<br>
|
||||
<br>
|
||||
// now we're ready to do the join<br>
|
||||
<br>
|
||||
var query =<br>
|
||||
from custRec in customers<br>
|
||||
join orderRec in orders on custRec["CustomerID"]
|
||||
equals orderRec["CustomerID"]<br>
|
||||
join orderDetailRec in orderDetails on
|
||||
orderRec["OrderID"] equals
|
||||
orderDetailRec["OrderID"]<br>
|
||||
join productRec in products on
|
||||
orderDetailRec["ProductID"] equals
|
||||
productRec["ProductID"]<br>
|
||||
select new<br>
|
||||
{<br>
|
||||
ID = custRec["CustomerID"],<br>
|
||||
CompanyName = custRec["CompanyName"],<br>
|
||||
OrderID = orderRec["OrderID"],<br>
|
||||
OrderDate = orderRec["OrderDate"],<br>
|
||||
ProductName = productRec["ProductName"],<br>
|
||||
UnitPrice = orderDetailRec["UnitPrice"],<br>
|
||||
Quantity = orderDetailRec["Quantity"]<br>
|
||||
};<br>
|
||||
<br>
|
||||
foreach( var rec in query )<br>
|
||||
{<br>
|
||||
Debug.WriteLine( rec.ToString() );<br>
|
||||
}</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</font>
|
||||
<p><font face="Arial" size="3">Here's the same thing again using custom objects:</font></p>
|
||||
<font FACE="Courier New">
|
||||
<table border="0" id="table39" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td><font face="Courier New" size="2">// get our target Customer records<br>
|
||||
<br>
|
||||
FilterExpression filterExp =
|
||||
FilterExpression.Parse( "CustomerID IN( 'ALFKI',
|
||||
'BONAP' )" );<br>
|
||||
IList<Customer> customers =
|
||||
customersDb.SelectRecords<Customer>( filterExp );<br>
|
||||
<br>
|
||||
filterExp =
|
||||
FilterExpression.CreateInExpressionFromTable<Customer>(
|
||||
"CustomerID", customers, "CustomerID" );<br>
|
||||
IList<Order> orders =
|
||||
ordersDb.SelectRecords<Order>( filterExp );<br>
|
||||
<br>
|
||||
// now get only OrderDetails records for the
|
||||
target Order records<br>
|
||||
<br>
|
||||
filterExp =
|
||||
FilterExpression.CreateInExpressionFromTable<Order>(
|
||||
"OrderID", orders, "OrderID" );<br>
|
||||
IList<OrderDetail> orderDetails =
|
||||
orderDetailsDb.SelectRecords<OrderDetail>( filterExp );<br>
|
||||
<br>
|
||||
// now get only Product records for the
|
||||
target OrderDetails records<br>
|
||||
<br>
|
||||
filterExp =
|
||||
FilterExpression.CreateInExpressionFromTable<OrderDetail>(
|
||||
"ProductID", orderDetails, "ProductID" );<br>
|
||||
IList<Product> products =
|
||||
productsDb.SelectRecords<Product>(( filterExp );<br>
|
||||
<br>
|
||||
// now we're ready to do the join<br>
|
||||
<br>
|
||||
var query =<br>
|
||||
from custRec in customers<br>
|
||||
join orderRec in orders on custRec.CustomerID
|
||||
equals orderRec.CustomerID<br>
|
||||
join orderDetailRec in orderDetails on
|
||||
orderRec.OrderID equals orderDetailRec.OrderID<br>
|
||||
join productRec in products on
|
||||
orderDetailRec.ProductID equals productRec.ProductID<br>
|
||||
select new<br>
|
||||
{<br>
|
||||
ID = custRec.CustomerID,<br>
|
||||
CompanyName = custRec.CompanyName,<br>
|
||||
OrderID = orderRec.OrderID,<br>
|
||||
OrderDate = orderRec.OrderDate,<br>
|
||||
ProductName = productRec.ProductName,<br>
|
||||
UnitPrice = orderDetailRec.UnitPrice,<br>
|
||||
Quantity = orderDetailRec.Quantity<br>
|
||||
};<br>
|
||||
<br>
|
||||
foreach( var rec in query )<br>
|
||||
{<br>
|
||||
Debug.WriteLine( rec.ToString() );<br>
|
||||
}</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</blockquote>
|
||||
<p> </p>
|
||||
</font>
|
||||
<p><b>
|
||||
<font color="#0066CC" face="Arial" size="3">Creating a Database</font></p>
|
||||
</b>
|
||||
<p><font face="Arial" size="3">You create your database programmatically by defining
|
||||
Fields and adding them to an array then calling FileDb.Create, similar to below.
|
||||
Notice we set the ID field to be AutoIncrementing and PrimaryKey. This code
|
||||
creates a database with every type of field.</font></p>
|
||||
<font FACE="Courier New">
|
||||
<blockquote>
|
||||
<table border="0" width="100%" id="table40" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td><font size="2" FACE="Courier New">Field field;<br>
|
||||
var fieldLst = new List<Field>( 20 );<br>
|
||||
field = new Field( "ID", DataType.Int );<br>
|
||||
field.AutoIncStart = 0;<br>
|
||||
field.IsPrimaryKey = true;<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "FirstName", DataType.String );<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "LastName", DataType.String );<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "BirthDate", DataType.DateTime );<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "IsCitizen", DataType.Bool );<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "DoubleField", DataType.Double );<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "ByteField", DataType.Byte );<br>
|
||||
fields.Add( field );<br>
|
||||
<br>
|
||||
// array types<br>
|
||||
field = new Field( "StringArrayField", DataType.String );<br>
|
||||
field.IsArray = true;<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "ByteArrayField", DataType.Byte );<br>
|
||||
field.IsArray = true;<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "IntArrayField", DataType.Int );<br>
|
||||
field.IsArray = true;<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "DoubleArrayField", DataType.Double );<br>
|
||||
field.IsArray = true;<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "DateTimeArrayField", DataType.DateTime );<br>
|
||||
field.IsArray = true;<br>
|
||||
fields.Add( field );<br>
|
||||
field = new Field( "BoolArray", DataType.Bool );<br>
|
||||
field.IsArray = true;<br>
|
||||
fields.Add( field );</font><font size="2"><br>
|
||||
<br>
|
||||
var myDb = new FileDb();</font><font size="2" FACE="Courier New"><br>
|
||||
myDb.Create( "MyDatabase.fdb", fieldLst.ToArray() );</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</blockquote>
|
||||
</font>
|
||||
<p><b><font color="#0066CC" face="Arial" size="3"><br>
|
||||
Adding Records</font></b></p>
|
||||
<p><font face="Arial" size="3">You add records to a database by creating a FieldValues
|
||||
object and adding field values. You do not need to represent every field of the
|
||||
database. Fields that are missing will be initialized to the default value (zero
|
||||
for numeric types, DateTime.MinValue,
|
||||
empty for String and NULL for array types).</font></p>
|
||||
<font FACE="Courier New">
|
||||
<blockquote>
|
||||
<table border="0" width="100%" id="table41" bgcolor="#FBEDBB" cellpadding="10">
|
||||
<tr>
|
||||
<td>
|
||||
<font size="2" FACE="Courier New">var record = new FieldValues();<br>
|
||||
record.Add( "FirstName", "Nancy" );<br>
|
||||
record.Add( "LastName", "Davolio" );<br>
|
||||
record.Add( "BirthDate", new DateTime( 1968, 12, 8 ) );<br>
|
||||
record.Add( "IsCitizen", true );<br>
|
||||
record.Add( "Double", 1.23 );<br>
|
||||
record.Add( "Byte", 1 );<br>
|
||||
record.Add( "StringArray", new string[] { "s1", "s2", "s3" } );<br>
|
||||
record.Add( "ByteArray", new Byte[] { 1, 2, 3, 4 } );<br>
|
||||
record.Add( "IntArray", new int[] { 100, 200, 300, 400 } );<br>
|
||||
record.Add( "DoubleArray", new double[] { 1.2, 2.4, 3.6, 4.8 } );<br>
|
||||
record.Add( "DateTimeArray", new DateTime[] { DateTime.Now, DateTime.Now,
|
||||
DateTime.Now, DateTime.Now } );<br>
|
||||
record.Add( "BoolArray", new bool[] { true, false, true, false } );<br>
|
||||
<br>
|
||||
myDb.AddRecord( record );</font></td>
|
||||
</tr>
|
||||
</table>
|
||||
</blockquote>
|
||||
<p> </p>
|
||||
</font>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -8,8 +8,8 @@
|
|||
<ProjectGuid>{EF44F661-B98A-4676-927F-85D138F82300}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Caching</RootNamespace>
|
||||
<AssemblyName>FileDbCache</AssemblyName>
|
||||
<RootNamespace>MapControl.Caching</RootNamespace>
|
||||
<AssemblyName>FileDbCache.WPF</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
|
|
@ -40,27 +40,27 @@
|
|||
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FileDb, Version=3.10.0.0, Culture=neutral, PublicKeyToken=68cc942b9efb3282, processorArchitecture=MSIL">
|
||||
<Reference Include="FileDb, Version=5.0.1.0, Culture=neutral, PublicKeyToken=ba3f58a0e60cd01d, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>FileDb\FileDb.dll</HintPath>
|
||||
<HintPath>..\FileDb\FileDb.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Runtime.Caching" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="FileDbCache.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="FileDb\FileDb.dll" />
|
||||
<Content Include="FileDb\FileDb.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\MapControl.snk">
|
||||
<Link>MapControl.snk</Link>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
|
@ -7,28 +7,30 @@ using System.Collections.Generic;
|
|||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Caching;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Windows.Media.Imaging;
|
||||
using FileDbNs;
|
||||
|
||||
namespace Caching
|
||||
namespace MapControl.Caching
|
||||
{
|
||||
/// <summary>
|
||||
/// ObjectCache implementation based on EzTools FileDb - http://www.eztools-software.com/tools/filedb/.
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class FileDbCache : ObjectCache
|
||||
public class FileDbCache : ObjectCache, IDisposable
|
||||
{
|
||||
private const string keyField = "Key";
|
||||
private const string valueField = "Value";
|
||||
private const string expiresField = "Expires";
|
||||
|
||||
private readonly BinaryFormatter formatter = new BinaryFormatter();
|
||||
private readonly FileDb fileDb = new FileDb { AutoFlush = false, AutoCleanThreshold = -1 };
|
||||
private readonly FileDb fileDb = new FileDb { AutoFlush = true, AutoCleanThreshold = -1 };
|
||||
private readonly string name;
|
||||
private readonly string path;
|
||||
|
||||
public FileDbCache(string name, NameValueCollection config)
|
||||
: this(name, config["directory"])
|
||||
: this(name, config["folder"])
|
||||
{
|
||||
var autoFlush = config["autoFlush"];
|
||||
var autoCleanThreshold = config["autoCleanThreshold"];
|
||||
|
|
@ -58,20 +60,20 @@ namespace Caching
|
|||
}
|
||||
}
|
||||
|
||||
public FileDbCache(string name, string directory)
|
||||
public FileDbCache(string name, string folder)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException("The parameter name must not be null or empty or only white-space.");
|
||||
throw new ArgumentException("The parameter name must not be null or empty or consist only of white-space characters.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(directory))
|
||||
if (string.IsNullOrWhiteSpace(folder))
|
||||
{
|
||||
throw new ArgumentException("The parameter directory must not be null or empty or only white-space.");
|
||||
throw new ArgumentException("The parameter folder must not be null or empty or consist only of white-space characters.");
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
path = Path.Combine(directory, name.Trim());
|
||||
path = Path.Combine(folder, name);
|
||||
|
||||
if (string.IsNullOrEmpty(Path.GetExtension(path)))
|
||||
{
|
||||
|
|
@ -81,25 +83,16 @@ namespace Caching
|
|||
try
|
||||
{
|
||||
fileDb.Open(path, false);
|
||||
Trace.TraceInformation("FileDbCache: Opened database with {0} cached items in {1}", fileDb.NumRecords, path);
|
||||
Debug.WriteLine("FileDbCache: Opened database with {0} cached items in {1}.", fileDb.NumRecords, path);
|
||||
|
||||
fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, EqualityEnum.LessThan));
|
||||
|
||||
if (fileDb.NumDeleted > 0)
|
||||
{
|
||||
Trace.TraceInformation("FileDbCache: Deleted {0} expired items", fileDb.NumDeleted);
|
||||
fileDb.Clean();
|
||||
}
|
||||
Clean();
|
||||
}
|
||||
catch
|
||||
{
|
||||
CreateDatabase();
|
||||
}
|
||||
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
|
||||
}
|
||||
AppDomain.CurrentDomain.ProcessExit += (s, e) => Close();
|
||||
}
|
||||
|
||||
public bool AutoFlush
|
||||
|
|
@ -147,50 +140,19 @@ namespace Caching
|
|||
throw new NotSupportedException("The parameter regionName must be null.");
|
||||
}
|
||||
|
||||
long count = 0;
|
||||
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
try
|
||||
{
|
||||
count = fileDb.NumRecords;
|
||||
return fileDb.NumRecords;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: FileDb.NumRecords failed: {0}", ex.Message);
|
||||
|
||||
if (RepairDatabase())
|
||||
{
|
||||
count = fileDb.NumRecords;
|
||||
}
|
||||
Debug.WriteLine("FileDbCache: FileDb.NumRecords failed: {0}", (object)ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private Record GetRecord(string key)
|
||||
{
|
||||
Record record = null;
|
||||
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
try
|
||||
{
|
||||
record = fileDb.GetRecordByKey(key, new string[] { valueField }, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
|
||||
|
||||
if (RepairDatabase())
|
||||
{
|
||||
record = fileDb.GetRecordByKey(key, new string[] { valueField }, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return record;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override bool Contains(string key, string regionName = null)
|
||||
|
|
@ -205,7 +167,19 @@ namespace Caching
|
|||
throw new NotSupportedException("The parameter regionName must be null.");
|
||||
}
|
||||
|
||||
return GetRecord(key) != null;
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
try
|
||||
{
|
||||
return fileDb.GetRecordByKey(key, new string[0], false) != null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override object Get(string key, string regionName = null)
|
||||
|
|
@ -220,57 +194,57 @@ namespace Caching
|
|||
throw new NotSupportedException("The parameter regionName must be null.");
|
||||
}
|
||||
|
||||
object value = null;
|
||||
var record = GetRecord(key);
|
||||
|
||||
if (record != null)
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
Record record = null;
|
||||
|
||||
try
|
||||
{
|
||||
using (var stream = new MemoryStream((byte[])record[0]))
|
||||
{
|
||||
value = formatter.Deserialize(stream);
|
||||
}
|
||||
record = fileDb.GetRecordByKey(key, new string[] { valueField }, false);
|
||||
}
|
||||
catch (Exception ex1)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: Deserializing item \"{0}\" failed: {1}", key, ex1.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 ex2)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: FileDb.DeleteRecordByKey(\"{0}\") failed: {1}", key, ex2.Message);
|
||||
Debug.WriteLine("FileDbCache: FileDb.DeleteRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
return null;
|
||||
}
|
||||
|
||||
public override CacheItem GetCacheItem(string key, string regionName = null)
|
||||
{
|
||||
var value = Get(key, regionName);
|
||||
|
||||
return value != null ? new CacheItem(key, value) : null;
|
||||
}
|
||||
|
||||
public override IDictionary<string, object> GetValues(IEnumerable<string> keys, string regionName = null)
|
||||
{
|
||||
if (regionName != null)
|
||||
{
|
||||
throw new NotSupportedException("The parameter regionName must be null.");
|
||||
}
|
||||
|
||||
var values = new Dictionary<string, object>();
|
||||
|
||||
foreach (string key in keys)
|
||||
{
|
||||
values[key] = Get(key);
|
||||
}
|
||||
|
||||
return values;
|
||||
return keys.ToDictionary(key => key, key => Get(key, regionName));
|
||||
}
|
||||
|
||||
public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
|
||||
|
|
@ -280,9 +254,9 @@ namespace Caching
|
|||
throw new ArgumentNullException("The parameter key must not be null.");
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
if (policy == null)
|
||||
{
|
||||
throw new ArgumentNullException("The parameter value must not be null.");
|
||||
throw new ArgumentNullException("The parameter policy must not be null.");
|
||||
}
|
||||
|
||||
if (regionName != null)
|
||||
|
|
@ -290,24 +264,34 @@ namespace 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[] valueBuffer = null;
|
||||
byte[] buffer = null;
|
||||
|
||||
try
|
||||
{
|
||||
using (var stream = new MemoryStream())
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(bitmap);
|
||||
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
formatter.Serialize(stream, value);
|
||||
valueBuffer = stream.ToArray();
|
||||
encoder.Save(memoryStream);
|
||||
buffer = memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: Serializing item \"{0}\" failed: {1}", key, ex.Message);
|
||||
Debug.WriteLine("FileDbCache: Encoding \"{0}\" failed: {1}", key, ex.Message);
|
||||
}
|
||||
|
||||
if (valueBuffer != null)
|
||||
if (buffer != null)
|
||||
{
|
||||
var expires = DateTime.MaxValue;
|
||||
|
||||
|
|
@ -320,18 +304,9 @@ namespace Caching
|
|||
expires = DateTime.UtcNow + policy.SlidingExpiration;
|
||||
}
|
||||
|
||||
try
|
||||
if (!AddOrUpdateRecord(key, buffer, expires) && RepairDatabase())
|
||||
{
|
||||
AddOrUpdateRecord(key, valueBuffer, expires);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: FileDb.UpdateRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
|
||||
|
||||
if (RepairDatabase())
|
||||
{
|
||||
AddOrUpdateRecord(key, valueBuffer, expires);
|
||||
}
|
||||
AddOrUpdateRecord(key, buffer, expires);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -350,14 +325,18 @@ namespace Caching
|
|||
public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null)
|
||||
{
|
||||
var oldValue = Get(key, regionName);
|
||||
|
||||
Set(key, value, policy);
|
||||
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
public override CacheItem AddOrGetExisting(CacheItem item, CacheItemPolicy policy)
|
||||
{
|
||||
var oldItem = GetCacheItem(item.Key, item.RegionName);
|
||||
|
||||
Set(item, policy);
|
||||
|
||||
return oldItem;
|
||||
}
|
||||
|
||||
|
|
@ -378,63 +357,51 @@ namespace Caching
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: FileDb.DeleteRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
|
||||
Debug.WriteLine("FileDbCache: FileDb.DeleteRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
try
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
fileDb.Flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: FileDb.Flush() failed: {0}", ex.Message);
|
||||
fileDb.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
try
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
if (fileDb.IsOpen)
|
||||
fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, ComparisonOperatorEnum.LessThan));
|
||||
|
||||
if (fileDb.NumDeleted > 0)
|
||||
{
|
||||
Debug.WriteLine("FileDbCache: Deleted {0} expired items.", fileDb.NumDeleted);
|
||||
fileDb.Clean();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: FileDb.Clean() failed: {0}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
private void Close()
|
||||
{
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
fileDb.Close();
|
||||
AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnProcessExit(object sender, EventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void CreateDatabase()
|
||||
{
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
fileDb.Close();
|
||||
}
|
||||
Close();
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
|
|
@ -445,15 +412,14 @@ namespace Caching
|
|||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
}
|
||||
|
||||
fileDb.Create(path,
|
||||
new Field[]
|
||||
fileDb.Create(path, new Field[]
|
||||
{
|
||||
new Field(keyField, DataTypeEnum.String) { IsPrimaryKey = true },
|
||||
new Field(valueField, DataTypeEnum.Byte) { IsArray = true },
|
||||
new Field(expiresField, DataTypeEnum.DateTime)
|
||||
});
|
||||
|
||||
Trace.TraceInformation("FileDbCache: Created database {0}", path);
|
||||
Debug.WriteLine("FileDbCache: Created database {0}.", (object)path);
|
||||
}
|
||||
|
||||
private bool RepairDatabase()
|
||||
|
|
@ -461,40 +427,71 @@ namespace Caching
|
|||
try
|
||||
{
|
||||
fileDb.Reindex();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex1)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: FileDb.Reindex() failed: {0}", ex1.Message);
|
||||
Debug.WriteLine("FileDbCache: FileDb.Reindex() failed: {0}", (object)ex.Message);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CreateDatabase();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("FileDbCache: Creating database {0} failed: {1}", path, ex.Message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool AddOrUpdateRecord(string key, byte[] value, DateTime expires)
|
||||
{
|
||||
var fieldValues = new FieldValues(3);
|
||||
fieldValues.Add(valueField, value);
|
||||
fieldValues.Add(expiresField, expires);
|
||||
|
||||
bool recordExists;
|
||||
|
||||
try
|
||||
{
|
||||
recordExists = fileDb.GetRecordByKey(key, new string[0], false) != null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recordExists)
|
||||
{
|
||||
try
|
||||
{
|
||||
CreateDatabase();
|
||||
fileDb.UpdateRecordByKey(key, fieldValues);
|
||||
}
|
||||
catch (Exception ex2)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("FileDbCache: Creating database {0} failed: {1}", path, ex2.Message);
|
||||
Debug.WriteLine("FileDbCache: FileDb.UpdateRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
fieldValues.Add(keyField, key);
|
||||
fileDb.AddRecord(fieldValues);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("FileDbCache: FileDb.AddRecord(\"{0}\") failed: {1}", key, ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AddOrUpdateRecord(string key, object value, DateTime expires)
|
||||
{
|
||||
var fieldValues = new FieldValues(3); // capacity
|
||||
fieldValues.Add(valueField, value);
|
||||
fieldValues.Add(expiresField, expires);
|
||||
|
||||
if (fileDb.GetRecordByKey(key, new string[0], false) == null)
|
||||
{
|
||||
fieldValues.Add(keyField, key);
|
||||
fileDb.AddRecord(fieldValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
fileDb.UpdateRecordByKey(key, fieldValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("FileDbCache")]
|
||||
[assembly: AssemblyTitle("XAML Map Control FileDbCache (WPF)")]
|
||||
[assembly: AssemblyDescription("ObjectCache implementation based on EzTools FileDb")]
|
||||
[assembly: AssemblyProduct("XAML Map Control")]
|
||||
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyVersion("2.3.1")]
|
||||
[assembly: AssemblyFileVersion("2.3.1")]
|
||||
[assembly: AssemblyVersion("2.4.0")]
|
||||
[assembly: AssemblyFileVersion("2.4.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
74
Caching/FileDbCache.WinRT/FileDbCache.WinRT.csproj
Normal file
74
Caching/FileDbCache.WinRT/FileDbCache.WinRT.csproj
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{C7BF2B18-CC74-430B-BCB2-600304EFA3D8}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>MapControl.Caching</RootNamespace>
|
||||
<AssemblyName>FileDbCache.WinRT</AssemblyName>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<TargetFrameworkProfile>Profile32</TargetFrameworkProfile>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<TargetPlatform Include="WindowsPhoneApp, Version=8.1" />
|
||||
<TargetPlatform Include="Windows, Version=8.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="FileDbCache.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\MapControl.snk">
|
||||
<Link>MapControl.snk</Link>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\MapControl\WinRT\MapControl.WinRT.csproj">
|
||||
<Project>{63cefdf7-5170-43b6-86f8-5c4a383a1615}</Project>
|
||||
<Name>MapControl.WinRT</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FileDbPcl">
|
||||
<HintPath>..\FileDb\FileDbPcl.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
278
Caching/FileDbCache.WinRT/FileDbCache.cs
Normal file
278
Caching/FileDbCache.WinRT/FileDbCache.cs
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
|
||||
// Copyright © 2014 Clemens Fischer
|
||||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI.Xaml;
|
||||
using FileDbNs;
|
||||
|
||||
namespace MapControl.Caching
|
||||
{
|
||||
/// <summary>
|
||||
/// IImageCache implementation based on FileDb, a free and simple No-SQL database by EzTools Software.
|
||||
/// See http://www.eztools-software.com/tools/filedb/.
|
||||
/// </summary>
|
||||
public class FileDbCache : IImageCache, IDisposable
|
||||
{
|
||||
private const string keyField = "Key";
|
||||
private const string valueField = "Value";
|
||||
private const string expiresField = "Expires";
|
||||
|
||||
private readonly FileDb fileDb = new FileDb { AutoFlush = true, AutoCleanThreshold = -1 };
|
||||
private readonly StorageFolder folder;
|
||||
private readonly string name;
|
||||
|
||||
public FileDbCache(string name = null, StorageFolder folder = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
name = TileImageLoader.DefaultCacheName;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Path.GetExtension(name)))
|
||||
{
|
||||
name += ".fdb";
|
||||
}
|
||||
|
||||
if (folder == null)
|
||||
{
|
||||
folder = TileImageLoader.DefaultCacheFolder;
|
||||
}
|
||||
|
||||
this.folder = folder;
|
||||
this.name = name;
|
||||
|
||||
Application.Current.Resuming += async (s, e) => await Open();
|
||||
Application.Current.Suspending += (s, e) => Close();
|
||||
|
||||
var task = Open();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, ComparisonOperatorEnum.LessThan));
|
||||
|
||||
if (fileDb.NumDeleted > 0)
|
||||
{
|
||||
Debug.WriteLine("FileDbCache: Deleted {0} expired items.", fileDb.NumDeleted);
|
||||
fileDb.Clean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ImageCacheItem> GetAsync(string key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException("The parameter key must not be null.");
|
||||
}
|
||||
|
||||
if (!fileDb.IsOpen)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await Task.Run(() => Get(key));
|
||||
}
|
||||
|
||||
public async Task SetAsync(string key, IBuffer buffer, DateTime expires)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException("The parameter key must not be null.");
|
||||
}
|
||||
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException("The parameter buffer must not be null.");
|
||||
}
|
||||
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
var bytes = buffer.ToArray();
|
||||
var ok = await Task.Run(() => AddOrUpdateRecord(key, bytes, expires));
|
||||
|
||||
if (!ok && (await RepairDatabase()))
|
||||
{
|
||||
await Task.Run(() => AddOrUpdateRecord(key, bytes, expires));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Open()
|
||||
{
|
||||
if (!fileDb.IsOpen)
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = await folder.GetFileAsync(name);
|
||||
var stream = await file.OpenAsync(FileAccessMode.ReadWrite);
|
||||
|
||||
fileDb.Open(stream.AsStream());
|
||||
Debug.WriteLine("FileDbCache: Opened database with {0} cached items in {1}.", fileDb.NumRecords, file.Path);
|
||||
|
||||
Clean();
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
await CreateDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
private void Close()
|
||||
{
|
||||
if (fileDb.IsOpen)
|
||||
{
|
||||
fileDb.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateDatabase()
|
||||
{
|
||||
Close();
|
||||
|
||||
var file = await folder.CreateFileAsync(name, CreationCollisionOption.ReplaceExisting);
|
||||
var stream = await file.OpenAsync(FileAccessMode.ReadWrite);
|
||||
|
||||
fileDb.Create(stream.AsStream(), new Field[]
|
||||
{
|
||||
new Field(keyField, DataTypeEnum.String) { IsPrimaryKey = true },
|
||||
new Field(valueField, DataTypeEnum.Byte) { IsArray = true },
|
||||
new Field(expiresField, DataTypeEnum.DateTime)
|
||||
});
|
||||
|
||||
Debug.WriteLine("FileDbCache: Created database {0}.", file.Path);
|
||||
}
|
||||
|
||||
private async Task<bool> RepairDatabase()
|
||||
{
|
||||
try
|
||||
{
|
||||
fileDb.Reindex();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("FileDbCache: FileDb.Reindex() failed: {0}", ex.Message);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await CreateDatabase();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("FileDbCache: Creating database {0} failed: {1}", Path.Combine(folder.Path, name), ex.Message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private ImageCacheItem Get(string key)
|
||||
{
|
||||
var fields = new string[] { valueField, expiresField };
|
||||
Record record = null;
|
||||
|
||||
try
|
||||
{
|
||||
record = fileDb.GetRecordByKey(key, fields, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
|
||||
}
|
||||
|
||||
if (record != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new ImageCacheItem
|
||||
{
|
||||
Buffer = ((byte[])record[0]).AsBuffer(),
|
||||
Expires = (DateTime)record[1]
|
||||
};
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
private bool AddOrUpdateRecord(string key, byte[] value, DateTime expires)
|
||||
{
|
||||
var fieldValues = new FieldValues(3);
|
||||
fieldValues.Add(valueField, value);
|
||||
fieldValues.Add(expiresField, expires);
|
||||
|
||||
bool recordExists;
|
||||
|
||||
try
|
||||
{
|
||||
recordExists = fileDb.GetRecordByKey(key, new string[0], false) != null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recordExists)
|
||||
{
|
||||
try
|
||||
{
|
||||
fileDb.UpdateRecordByKey(key, fieldValues);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("FileDbCache: FileDb.UpdateRecordByKey(\"{0}\") failed: {1}", key, ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
fieldValues.Add(keyField, key);
|
||||
fileDb.AddRecord(fieldValues);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine("FileDbCache: FileDb.AddRecord(\"{0}\") failed: {1}", key, ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Debug.WriteLine("Cached item {0}, expires {1}", key, expires);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs
Normal file
14
Caching/FileDbCache.WinRT/Properties/AssemblyInfo.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("XAML Map Control FileDbCache (WinRT)")]
|
||||
[assembly: AssemblyDescription("IImageCache implementation based on EzTools FileDb")]
|
||||
[assembly: AssemblyProduct("XAML Map Control")]
|
||||
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyVersion("2.4.0")]
|
||||
[assembly: AssemblyFileVersion("2.4.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
Binary file not shown.
|
|
@ -1,5 +0,0 @@
|
|||
FileDbCache uses FileDb by Brett Goodman (aka EzTools), a simple No-SQL database for .NET.
|
||||
FileDb is Open Source on Google Code Hosting at http://code.google.com/p/filedb-database/
|
||||
and licensed under the Apache License 2.0, http://www.apache.org/licenses/LICENSE-2.0.
|
||||
See the product homepage at http://www.eztools-software.com/tools/filedb/.
|
||||
Download FileDb from http://www.eztools-software.com/downloads/filedb.exe.
|
||||
|
|
@ -8,8 +8,8 @@
|
|||
<ProjectGuid>{86470440-FEE2-4120-AF5A-3762FB9C536F}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Caching</RootNamespace>
|
||||
<AssemblyName>ImageFileCache</AssemblyName>
|
||||
<RootNamespace>MapControl.Caching</RootNamespace>
|
||||
<AssemblyName>ImageFileCache.WPF</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
|
|
@ -40,9 +40,12 @@
|
|||
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Runtime.Caching" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ImageFileCache.cs" />
|
||||
|
|
@ -6,61 +6,52 @@ 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 Caching
|
||||
namespace MapControl.Caching
|
||||
{
|
||||
/// <summary>
|
||||
/// ObjectCache implementation based on local image files.
|
||||
/// The only valid data type for cached values is a byte array containing an
|
||||
/// 8-byte timestamp followed by a PNG, JPEG, BMP, GIF, TIFF or WMP image buffer.
|
||||
/// The only valid data type for cached values is System.Windows.Media.Imaging.BitmapFrame.
|
||||
/// </summary>
|
||||
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 }),
|
||||
};
|
||||
|
||||
private static readonly FileSystemAccessRule fullControlRule = new FileSystemAccessRule(
|
||||
new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null),
|
||||
FileSystemRights.FullControl, AccessControlType.Allow);
|
||||
|
||||
private readonly MemoryCache memoryCache = MemoryCache.Default;
|
||||
private readonly string name;
|
||||
private readonly string directory;
|
||||
private readonly string rootFolder;
|
||||
|
||||
public ImageFileCache(string name, NameValueCollection config)
|
||||
: this(name, config["directory"])
|
||||
: this(name, config["folder"])
|
||||
{
|
||||
}
|
||||
|
||||
public ImageFileCache(string name, string directory)
|
||||
public ImageFileCache(string name, string folder)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException("The parameter name must not be null or empty or only white-space.");
|
||||
throw new ArgumentException("The parameter name must not be null or empty or consist only of white-space characters.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(directory))
|
||||
if (string.IsNullOrWhiteSpace(folder))
|
||||
{
|
||||
throw new ArgumentException("The parameter directory must not be null or empty or only white-space.");
|
||||
throw new ArgumentException("The parameter folder must not be null or empty or consist only of white-space characters.");
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
this.directory = Path.Combine(directory, name.Trim());
|
||||
Directory.CreateDirectory(this.directory);
|
||||
rootFolder = Path.Combine(folder, name);
|
||||
Directory.CreateDirectory(rootFolder);
|
||||
|
||||
Trace.TraceInformation("Created ImageFileCache in {0}", this.directory);
|
||||
Debug.WriteLine("Created ImageFileCache in {0}.", (object)rootFolder);
|
||||
}
|
||||
|
||||
public override string Name
|
||||
|
|
@ -81,46 +72,29 @@ namespace Caching
|
|||
|
||||
protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
||||
{
|
||||
throw new NotSupportedException("LocalFileCache does not support the ability to enumerate items.");
|
||||
throw new NotSupportedException("ImageFileCache does not support the ability to enumerate items.");
|
||||
}
|
||||
|
||||
public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable<string> keys, string regionName = null)
|
||||
{
|
||||
throw new NotSupportedException("LocalFileCache does not support the ability to create change monitors.");
|
||||
throw new NotSupportedException("ImageFileCache does not support the ability to create change monitors.");
|
||||
}
|
||||
|
||||
public override long GetCount(string regionName = null)
|
||||
{
|
||||
throw new NotSupportedException("LocalFileCache does not support the ability to count items.");
|
||||
throw new NotSupportedException("ImageFileCache does not support the ability to count items.");
|
||||
}
|
||||
|
||||
public override bool Contains(string key, string regionName = null)
|
||||
{
|
||||
if (regionName != null)
|
||||
{
|
||||
throw new NotSupportedException("The parameter regionName must be null.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return MemoryCache.Default.Contains(key) || FindFile(GetPath(key)) != null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return memoryCache.Contains(key, regionName) || FindFile(GetPath(key)) != null;
|
||||
}
|
||||
|
||||
public override object Get(string key, string regionName = null)
|
||||
{
|
||||
if (regionName != null)
|
||||
{
|
||||
throw new NotSupportedException("The parameter regionName must be null.");
|
||||
}
|
||||
var bitmap = memoryCache.Get(key, regionName) as BitmapFrame;
|
||||
|
||||
var value = MemoryCache.Default.Get(key);
|
||||
|
||||
if (value == null)
|
||||
if (bitmap == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -128,20 +102,20 @@ namespace Caching
|
|||
|
||||
if (path != null)
|
||||
{
|
||||
var expirationTime = File.GetLastAccessTimeUtc(path);
|
||||
var creationTime = File.GetLastWriteTimeUtc(path);
|
||||
|
||||
if (expirationTime < creationTime)
|
||||
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
expirationTime = creationTime;
|
||||
}
|
||||
bitmap = BitmapFrame.Create(fileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
||||
|
||||
using (var fileStream = new FileStream(path, FileMode.Open))
|
||||
using (var memoryStream = new MemoryStream((int)(fileStream.Length + 8)))
|
||||
{
|
||||
memoryStream.Write(BitConverter.GetBytes(expirationTime.ToBinary()), 0, 8);
|
||||
fileStream.CopyTo(memoryStream);
|
||||
value = memoryStream.GetBuffer();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -150,7 +124,7 @@ namespace Caching
|
|||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public override CacheItem GetCacheItem(string key, string regionName = null)
|
||||
|
|
@ -162,58 +136,72 @@ namespace Caching
|
|||
|
||||
public override IDictionary<string, object> GetValues(IEnumerable<string> keys, string regionName = null)
|
||||
{
|
||||
if (regionName != null)
|
||||
{
|
||||
throw new NotSupportedException("The parameter regionName must be null.");
|
||||
}
|
||||
|
||||
var values = new Dictionary<string, object>();
|
||||
|
||||
foreach (string key in keys)
|
||||
{
|
||||
values[key] = Get(key);
|
||||
}
|
||||
|
||||
return values;
|
||||
return keys.ToDictionary(key => key, key => Get(key, regionName));
|
||||
}
|
||||
|
||||
public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
|
||||
{
|
||||
if (regionName != null)
|
||||
var bitmap = value as BitmapFrame;
|
||||
|
||||
if (bitmap == null)
|
||||
{
|
||||
throw new NotSupportedException("The parameter regionName must be null.");
|
||||
throw new ArgumentException("The parameter value must contain a System.Windows.Media.Imaging.BitmapFrame.");
|
||||
}
|
||||
|
||||
var buffer = value as byte[];
|
||||
var metadata = (BitmapMetadata)bitmap.Metadata;
|
||||
var format = metadata != null ? metadata.Format : "bmp";
|
||||
BitmapEncoder encoder = null;
|
||||
|
||||
if (buffer == null || buffer.Length <= 8)
|
||||
switch (format)
|
||||
{
|
||||
throw new NotSupportedException("The parameter value must be a byte[] containing at least 9 bytes.");
|
||||
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;
|
||||
}
|
||||
|
||||
MemoryCache.Default.Set(key, buffer, policy);
|
||||
if (encoder == null)
|
||||
{
|
||||
throw new NotSupportedException(string.Format("The bitmap format {0} is not supported.", format));
|
||||
}
|
||||
|
||||
var path = GetPath(key) + GetFileExtension(buffer);
|
||||
memoryCache.Set(key, bitmap, policy, regionName);
|
||||
|
||||
var path = string.Format("{0}.{1}", GetPath(key), format);
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
using (var fileStream = new FileStream(path, FileMode.Create))
|
||||
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
fileStream.Write(buffer, 8, buffer.Length - 8);
|
||||
encoder.Frames.Add(bitmap);
|
||||
encoder.Save(fileStream);
|
||||
}
|
||||
|
||||
var expirationTime = DateTime.FromBinary(BitConverter.ToInt64(buffer, 0));
|
||||
File.SetLastAccessTimeUtc(path, expirationTime);
|
||||
|
||||
var fileSecurity = File.GetAccessControl(path);
|
||||
fileSecurity.AddAccessRule(fullControlRule);
|
||||
File.SetAccessControl(path, fileSecurity);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("ImageFileCache: Writing file {0} failed: {1}", path, ex.Message);
|
||||
Debug.WriteLine("ImageFileCache: Writing file {0} failed: {1}", path, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +241,8 @@ namespace Caching
|
|||
public override object Remove(string key, string regionName = null)
|
||||
{
|
||||
var oldValue = Get(key, regionName);
|
||||
MemoryCache.Default.Remove(key);
|
||||
|
||||
memoryCache.Remove(key, regionName);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -273,7 +262,7 @@ namespace Caching
|
|||
|
||||
private string GetPath(string key)
|
||||
{
|
||||
return Path.Combine(directory, key);
|
||||
return Path.Combine(rootFolder, key);
|
||||
}
|
||||
|
||||
private static string FindFile(string path)
|
||||
|
|
@ -283,34 +272,14 @@ namespace Caching
|
|||
return path;
|
||||
}
|
||||
|
||||
string directoryName = Path.GetDirectoryName(path);
|
||||
string folderName = Path.GetDirectoryName(path);
|
||||
|
||||
if (Directory.Exists(directoryName))
|
||||
if (Directory.Exists(folderName))
|
||||
{
|
||||
return Directory.EnumerateFiles(directoryName, Path.GetFileName(path) + ".*").FirstOrDefault();
|
||||
return Directory.EnumerateFiles(folderName, Path.GetFileName(path) + ".*").FirstOrDefault();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetFileExtension(byte[] buffer)
|
||||
{
|
||||
var fileType = imageFileTypes.FirstOrDefault(t =>
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (t.Item2.Length <= buffer.Length - 8)
|
||||
{
|
||||
while (i < t.Item2.Length && t.Item2[i] == buffer[i + 8])
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return i == t.Item2.Length;
|
||||
});
|
||||
|
||||
return fileType != null ? fileType.Item1 : ".bin";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("ImageFileCache")]
|
||||
[assembly: AssemblyTitle("XAML Map Control ImageFileCache (WPF)")]
|
||||
[assembly: AssemblyDescription("ObjectCache implementation based on local image files")]
|
||||
[assembly: AssemblyProduct("XAML Map Control")]
|
||||
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyVersion("2.3.1")]
|
||||
[assembly: AssemblyFileVersion("2.3.1")]
|
||||
[assembly: AssemblyVersion("2.4.0")]
|
||||
[assembly: AssemblyFileVersion("2.4.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
66
Caching/ImageFileCache.WinRT/ImageFileCache.WinRT.csproj
Normal file
66
Caching/ImageFileCache.WinRT/ImageFileCache.WinRT.csproj
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{F789647E-96F7-43E3-A895-FA3FE8D01260}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>MapControl.Caching</RootNamespace>
|
||||
<AssemblyName>ImageFileCache.WinRT</AssemblyName>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{BC8A1FFA-BEE3-4634-8014-F334798102B3};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<TargetPlatformIdentifier>Windows</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion>8.1</TargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_APP</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_APP</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- A reference to the entire .NET Framework is automatically included -->
|
||||
<None Include="..\..\MapControl.snk">
|
||||
<Link>MapControl.snk</Link>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ImageFileCache.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\MapControl\WinRT\MapControl.WinRT.csproj">
|
||||
<Project>{63cefdf7-5170-43b6-86f8-5c4a383a1615}</Project>
|
||||
<Name>MapControl.WinRT</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
94
Caching/ImageFileCache.WinRT/ImageFileCache.cs
Normal file
94
Caching/ImageFileCache.WinRT/ImageFileCache.cs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
|
||||
// Copyright © 2014 Clemens Fischer
|
||||
// Licensed under the Microsoft Public License (Ms-PL)
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace MapControl.Caching
|
||||
{
|
||||
public class ImageFileCache : IImageCache
|
||||
{
|
||||
private readonly string name;
|
||||
private StorageFolder rootFolder;
|
||||
|
||||
public ImageFileCache(string name = null, StorageFolder folder = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
name = TileImageLoader.DefaultCacheName;
|
||||
}
|
||||
|
||||
if (folder == null)
|
||||
{
|
||||
folder = TileImageLoader.DefaultCacheFolder;
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
|
||||
folder.CreateFolderAsync(name, CreationCollisionOption.OpenIfExists).Completed = (o, s) =>
|
||||
{
|
||||
rootFolder = o.GetResults();
|
||||
Debug.WriteLine("Created ImageFileCache in {0}.", rootFolder.Path);
|
||||
};
|
||||
}
|
||||
|
||||
public virtual async Task<ImageCacheItem> GetAsync(string key)
|
||||
{
|
||||
var item = await rootFolder.TryGetItemAsync(key);
|
||||
|
||||
if (item == null || !item.IsOfType(StorageItemTypes.File))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
}
|
||||
|
||||
return cacheItem;
|
||||
}
|
||||
|
||||
public virtual async Task SetAsync(string key, IBuffer buffer, DateTime expires)
|
||||
{
|
||||
try
|
||||
{
|
||||
var names = key.Split('\\');
|
||||
var folder = rootFolder;
|
||||
|
||||
for (int i = 0; i < names.Length - 1; i++)
|
||||
{
|
||||
folder = await folder.CreateFolderAsync(names[i], CreationCollisionOption.OpenIfExists);
|
||||
}
|
||||
|
||||
var file = await folder.CreateFileAsync(names[names.Length - 1], CreationCollisionOption.ReplaceExisting);
|
||||
await FileIO.WriteBufferAsync(file, buffer);
|
||||
|
||||
// Use ImageProperties.DateTaken to store expiration date
|
||||
var imageProperties = await file.Properties.GetImagePropertiesAsync();
|
||||
imageProperties.DateTaken = expires;
|
||||
await imageProperties.SavePropertiesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs
Normal file
14
Caching/ImageFileCache.WinRT/Properties/AssemblyInfo.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("XAML Map Control ImageFileCache (WinRT)")]
|
||||
[assembly: AssemblyDescription("IImageCache implementation based on local image files")]
|
||||
[assembly: AssemblyProduct("XAML Map Control")]
|
||||
[assembly: AssemblyCompany("Clemens Fischer")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014 Clemens Fischer")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyVersion("2.4.0")]
|
||||
[assembly: AssemblyFileVersion("2.4.0")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
Loading…
Add table
Add a link
Reference in a new issue