summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Juick/Controls/MessageList.xaml3
-rw-r--r--Juick/Converters/UriToImageSourceConverter.cs31
-rw-r--r--Juick/Juick.csproj5
-rw-r--r--Juick/Storage/ImageCache.cs160
-rw-r--r--Juick/Storage/PersistentImageCache.cs880
-rw-r--r--Juick/Storage/SystemImageCache.cs50
-rw-r--r--Juick/Threading/OneShotDispatcherTimer.cs160
-rw-r--r--Juick/ViewModels/PostItem.cs14
-rw-r--r--Juick/ViewModels/ViewModelBase.cs2
9 files changed, 8 insertions, 1297 deletions
diff --git a/Juick/Controls/MessageList.xaml b/Juick/Controls/MessageList.xaml
index b53b7b8..7a5882f 100644
--- a/Juick/Controls/MessageList.xaml
+++ b/Juick/Controls/MessageList.xaml
@@ -13,7 +13,6 @@
d:DesignHeight="480" d:DesignWidth="480">
<UserControl.Resources>
<converters:MidToUriConverter x:Key="uriConverter" />
- <converters:UriToImageSourceConverter x:Key="imgCacheConverter" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
@@ -38,7 +37,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
- <Image Source="{Binding AvatarUri, Converter={StaticResource imgCacheConverter}}" Grid.Row="0" Grid.Column="0" Margin="3" />
+ <Image bindings:LowProfileImageLoader.UriSource="{Binding AvatarUri}" Grid.Row="0" Grid.Column="0" Margin="3" />
<TextBlock Text="{Binding Username}" Grid.Row="0" Grid.Column="1"
Margin="5,0,5,5" VerticalAlignment="Top"
HorizontalAlignment="Left"
diff --git a/Juick/Converters/UriToImageSourceConverter.cs b/Juick/Converters/UriToImageSourceConverter.cs
deleted file mode 100644
index 4c26254..0000000
--- a/Juick/Converters/UriToImageSourceConverter.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System;
-using System.Net;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Documents;
-using System.Windows.Ink;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Animation;
-using System.Windows.Shapes;
-using System.Windows.Data;
-using System.Globalization;
-using Kawagoe.Storage;
-
-namespace Juick.Converters
-{
- public class UriToImageSourceConverter : IValueConverter
- {
- static ImageCache _cache = new PersistentImageCache("avatarsCache");
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- {
- Uri avatarUri = (Uri)value;
- return _cache.Get(avatarUri);
- }
-
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- {
- return null;
- }
- }
-}
diff --git a/Juick/Juick.csproj b/Juick/Juick.csproj
index 3b467f4..8e47876 100644
--- a/Juick/Juick.csproj
+++ b/Juick/Juick.csproj
@@ -88,7 +88,6 @@
<DependentUpon>MessageList.xaml</DependentUpon>
</Compile>
<Compile Include="Converters\MidToUriConverter.cs" />
- <Compile Include="Converters\UriToImageSourceConverter.cs" />
<Compile Include="LoginView.xaml.cs">
<DependentUpon>LoginView.xaml</DependentUpon>
</Compile>
@@ -99,10 +98,6 @@
<DependentUpon>NewPostView.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="Storage\ImageCache.cs" />
- <Compile Include="Storage\PersistentImageCache.cs" />
- <Compile Include="Storage\SystemImageCache.cs" />
- <Compile Include="Threading\OneShotDispatcherTimer.cs" />
<Compile Include="ThreadView.xaml.cs">
<DependentUpon>ThreadView.xaml</DependentUpon>
</Compile>
diff --git a/Juick/Storage/ImageCache.cs b/Juick/Storage/ImageCache.cs
deleted file mode 100644
index e45dc62..0000000
--- a/Juick/Storage/ImageCache.cs
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2010 Andreas Saudemont (andreas.saudemont@gmail.com)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-using System;
-using System.Windows;
-using System.Windows.Media;
-
-namespace Kawagoe.Storage
-{
- /// <summary>
- /// Defines the base clase for image cache implementations.
- /// </summary>
- public abstract class ImageCache
- {
- /// <summary>
- /// The name of the default image cache.
- /// </summary>
- public const string DefaultImageCacheName = "default";
-
- private static ImageCache _defaultImageCache = null;
- private static object _defaultImageCacheLock = new object();
-
- /// <summary>
- /// The default image cache.
- /// If not set explicitely, a <see cref="PersistentImageCache"/> instance is used by default.
- /// </summary>
- public static ImageCache Default
- {
- get
- {
- if (!Deployment.Current.Dispatcher.CheckAccess())
- {
- throw new UnauthorizedAccessException("invalid cross-thread access");
- }
- lock (_defaultImageCacheLock)
- {
- if (_defaultImageCache == null)
- {
- _defaultImageCache = new PersistentImageCache(DefaultImageCacheName);
- }
- return _defaultImageCache;
- }
- }
- set
- {
- if (!Deployment.Current.Dispatcher.CheckAccess())
- {
- throw new UnauthorizedAccessException("invalid cross-thread access");
- }
- lock (_defaultImageCacheLock)
- {
- _defaultImageCache = value;
- }
- }
- }
-
- /// <summary>
- /// Initializes a new <see cref="ImageCache"/> instance.
- /// </summary>
- protected ImageCache(string name)
- {
- if (string.IsNullOrEmpty(name))
- {
- throw new ArgumentException();
- }
- Name = name;
- }
-
- /// <summary>
- /// The name of the image cache.
- /// </summary>
- protected string Name
- {
- get;
- private set;
- }
-
- /// <summary>
- /// Retrieves the source for the image with the specified URI from the cache, downloading it
- /// if needed.
- /// </summary>
- /// <param name="imageUri">The URI of the image. Must be an absolute URI.</param>
- /// <returns>An ImageSource object, or <c>null</c> if <paramref name="imageUri"/> is <c>null</c> or not an absolute URI.</returns>
- /// <exception cref="UnauthorizedAccessException">The method is not called in the UI thread.</exception>
- public ImageSource Get(Uri imageUri)
- {
- if (!Deployment.Current.Dispatcher.CheckAccess())
- {
- throw new UnauthorizedAccessException("invalid cross-thread access");
- }
- if (imageUri == null || !imageUri.IsAbsoluteUri)
- {
- return null;
- }
- return GetInternal(imageUri);
- }
-
- /// <summary>
- /// Retrieves the source for the image with the specified URI from the cache, downloading it
- /// if needed.
- /// </summary>
- /// <param name="imageUriString">The URI of the image. Must be an absolute URI.</param>
- /// <returns>An ImageSource object, or <c>null</c> if <paramref name="imageUriString"/> is <c>null</c>,
- /// the empty string, or not an absolute URI.</returns>
- /// <exception cref="UnauthorizedAccessException">The method is not called in the UI thread.</exception>
- public ImageSource Get(string imageUriString)
- {
- if (!Deployment.Current.Dispatcher.CheckAccess())
- {
- throw new UnauthorizedAccessException("invalid cross-thread access");
- }
- if (string.IsNullOrEmpty(imageUriString))
- {
- return null;
- }
- Uri imageUri;
- try
- {
- imageUri = new Uri(imageUriString, UriKind.Absolute);
- }
- catch (Exception)
- {
- return null;
- }
- return Get(imageUri);
- }
-
- /// <summary>
- /// The actual implementation of <see cref="ImageCache.Get"/>.
- /// </summary>
- protected abstract ImageSource GetInternal(Uri imageUri);
-
- /// <summary>
- /// Deletes all the images from the cache.
- /// This method can block the current thread for a long time; it is advised to call it from
- /// a background thread.
- /// </summary>
- public abstract void Clear();
-
- /// <summary>
- /// Overrides object.ToString().
- /// </summary>
- /// <returns></returns>
- public override string ToString()
- {
- return string.Format("ImageCache:{0}", Name);
- }
- }
-}
diff --git a/Juick/Storage/PersistentImageCache.cs b/Juick/Storage/PersistentImageCache.cs
deleted file mode 100644
index 1472148..0000000
--- a/Juick/Storage/PersistentImageCache.cs
+++ /dev/null
@@ -1,880 +0,0 @@
-// Copyright 2010 Andreas Saudemont (andreas.saudemont@gmail.com)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.IO.IsolatedStorage;
-using System.Net;
-using System.Security.Cryptography;
-using System.Text;
-using System.Threading;
-using System.Windows;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using Kawagoe.Threading;
-
-namespace Kawagoe.Storage
-{
- /// <summary>
- /// Implements an <see cref="ImageCache"/> on top of the isolated storage.
- /// </summary>
- public class PersistentImageCache : ImageCache
- {
- /// <summary>
- /// The default value of <see cref="PersistentImageCache.ExpirationDelay"/>.
- /// </summary>
- public static readonly TimeSpan DefaultExpirationDelay = TimeSpan.FromDays(1);
-
- /// <summary>
- /// The default value of <see cref="PersistentImageCache.MemoryCacheCapacity"/>.
- /// </summary>
- public const int DefaultMemoryCacheCapacity = 100;
-
- private const string ImageDataExtension = "data";
- private const string ImageTimestampExtension = "tstamp";
-
- private TimeSpan _expirationDelay = DefaultExpirationDelay;
-
- /// <summary>
- /// Initializes a new <see cref="PersistentImageCache"/> instance with the specified name.
- /// </summary>
- public PersistentImageCache(string name)
- : base(name)
- {
- }
-
- /// <summary>
- /// The delay after which an image, once downloaded, is consider expired and is deleted
- /// from the cache.
- /// </summary>
- public TimeSpan ExpirationDelay
- {
- get
- {
- return _expirationDelay;
- }
- set
- {
- if (value.TotalMinutes < 1)
- {
- throw new ArgumentOutOfRangeException();
- }
- _expirationDelay = value;
- RequestCachePruning();
- }
- }
-
- /// <summary>
- /// Implements <see cref="ImageCache.GetInternal"/>.
- /// </summary>
- protected override ImageSource GetInternal(Uri imageUri)
- {
- BitmapImage imageSource = new BitmapImage();
-
- string imageKey = GetImageKey(imageUri);
- Stream imageDataStream = LoadImageFromMemoryCache(imageKey);
- if (imageDataStream != null)
- {
- imageSource.SetSource(imageDataStream);
- return imageSource;
- }
-
- WeakReference imageSourceRef = new WeakReference(imageSource);
- ThreadPool.QueueUserWorkItem((state) =>
- {
- LoadImageSource(imageUri, imageSourceRef);
- });
- return imageSource;
- }
-
- private void LoadImageSource(Uri imageUri, WeakReference imageSourceRef)
- {
- BitmapImage imageSource = imageSourceRef.Target as BitmapImage;
- if (imageSource == null)
- {
- return;
- }
-
- string imageKey = GetImageKey(imageUri);
- Stream imageDataStream = LoadImageFromMemoryCache(imageKey);
- if (imageDataStream == null)
- {
- imageDataStream = ReadImageDataFromCache(imageKey);
- }
- if (imageDataStream != null)
- {
- Deployment.Current.Dispatcher.BeginInvoke(() =>
- {
- imageSource.SetSource(imageDataStream);
- });
- }
- else
- {
- RequestImageDownload(imageUri, imageSourceRef);
- }
- }
-
- /// <summary>
- /// Implements <see cref="ImageCache.Clear"/>.
- /// </summary>
- public override void Clear()
- {
- lock (_storeLock)
- lock (_memoryCacheLock)
- {
- ClearMemoryCache();
- DeleteAllImagesFromStore();
- }
- }
-
- #region Image Downloads
-
- private readonly Dictionary<Uri, ImageRequest> _pendingRequests = new Dictionary<Uri, ImageRequest>();
-
- private void RequestImageDownload(Uri imageUri, WeakReference imageSourceRef)
- {
- if (imageUri == null || imageSourceRef == null || imageSourceRef.Target == null)
- {
- return;
- }
-
- lock (_pendingRequests)
- {
- PrunePendingRequests();
-
- if (_pendingRequests.ContainsKey(imageUri))
- {
- ImageRequest request = _pendingRequests[imageUri];
- lock (request)
- {
- _pendingRequests[imageUri].SourceRefs.Add(imageSourceRef);
- }
- }
- else
- {
- ImageRequest request = new ImageRequest(imageUri);
- request.Completed += OnImageRequestCompleted;
- request.SourceRefs.Add(imageSourceRef);
- _pendingRequests[imageUri] = request;
- try
- {
- request.Start();
- }
- catch (Exception)
- {
- _pendingRequests.Remove(imageUri);
- }
- }
- }
- }
-
- private void OnImageRequestCompleted(object sender, EventArgs e)
- {
- ImageRequest request = sender as ImageRequest;
- if (request == null)
- {
- return;
- }
-
- lock (_pendingRequests)
- {
- PrunePendingRequests();
-
- if (!_pendingRequests.ContainsKey(request.ImageUri))
- {
- return;
- }
- _pendingRequests.Remove(request.ImageUri);
-
- if (request.ImageData == null || request.ImageData.Length == 0)
- {
- return;
- }
-
- string imageKey = GetImageKey(request.ImageUri);
- WriteImageToCache(imageKey, request.ImageData);
- WriteImageToMemoryCache(imageKey, request.ImageData);
-
- foreach (WeakReference sourceRef in request.SourceRefs)
- {
- BitmapSource imageSource = sourceRef.Target as BitmapSource;
- if (imageSource != null)
- {
- Stream imageDataStream = new MemoryStream(request.ImageData);
- Deployment.Current.Dispatcher.BeginInvoke(() =>
- {
- imageSource.SetSource(imageDataStream);
- });
- }
- }
- }
- }
-
- private void PrunePendingRequests()
- {
- lock (_pendingRequests)
- {
- List<Uri> obsoleteUris = null;
-
- foreach (Uri imageUri in _pendingRequests.Keys)
- {
- ImageRequest request = _pendingRequests[imageUri];
- bool hasSources = false;
- foreach (WeakReference sourceRef in request.SourceRefs)
- {
- if (sourceRef.Target != null)
- {
- hasSources = true;
- break;
- }
- }
- if (!hasSources)
- {
- if (obsoleteUris == null)
- {
- obsoleteUris = new List<Uri>();
- }
- obsoleteUris.Add(imageUri);
- }
- }
-
- if (obsoleteUris != null)
- {
- foreach (Uri obsoleteUri in obsoleteUris)
- {
- ImageRequest request = _pendingRequests[obsoleteUri];
- _pendingRequests.Remove(obsoleteUri);
- request.Cancel();
- }
- }
- }
- }
-
- private class ImageRequest
- {
- private bool _started = false;
- private HttpWebRequest _webRequest = null;
- private Stream _responseInputStream = null;
- private byte[] _responseBuffer = new byte[4096];
- private MemoryStream _responseDataStream = new MemoryStream();
-
- public ImageRequest(Uri imageUri)
- {
- ImageUri = imageUri;
- ImageData = null;
- SourceRefs = new List<WeakReference>();
- }
-
- public Uri ImageUri
- {
- get;
- private set;
- }
-
- public byte[] ImageData
- {
- get;
- private set;
- }
-
- public IList<WeakReference> SourceRefs
- {
- get;
- private set;
- }
-
- public void Start()
- {
- lock (this)
- {
- if (_started)
- {
- return;
- }
- _started = true;
-
- _webRequest = (HttpWebRequest)HttpWebRequest.Create(ImageUri);
- _webRequest.BeginGetResponse(OnGotResponse, null);
- }
- }
-
- public void Cancel()
- {
- lock (this)
- {
- if (!_started)
- {
- return;
- }
- HttpWebRequest webRequest = _webRequest;
- ReleaseResources();
- if (webRequest != null)
- {
- try
- {
- webRequest.Abort();
- }
- catch (Exception) { }
- }
- }
- }
-
- public event EventHandler Completed;
-
- private void OnGotResponse(IAsyncResult asyncResult)
- {
- lock (this)
- {
- if (_webRequest == null)
- {
- return;
- }
- try
- {
- HttpWebResponse webResponse = (HttpWebResponse)_webRequest.EndGetResponse(asyncResult);
- _responseInputStream = webResponse.GetResponseStream();
- _responseInputStream.BeginRead(_responseBuffer, 0, _responseBuffer.Length, OnReadResponseCompleted, null);
- }
- catch (Exception)
- {
- NotifyCompletion();
- }
- }
- }
-
- private void OnReadResponseCompleted(IAsyncResult asyncResult)
- {
- lock (this)
- {
- if (_responseInputStream == null)
- {
- return;
- }
- try
- {
- int readCount = _responseInputStream.EndRead(asyncResult);
- if (readCount > 0)
- {
- _responseDataStream.Write(_responseBuffer, 0, readCount);
- _responseInputStream.BeginRead(_responseBuffer, 0, _responseBuffer.Length, OnReadResponseCompleted, null);
- }
- else
- {
- if (_responseDataStream.Length > 0)
- {
- ImageData = _responseDataStream.ToArray();
- }
- NotifyCompletion();
- }
- }
- catch (Exception)
- {
- NotifyCompletion();
- }
- }
- }
-
- private void NotifyCompletion()
- {
- lock (this)
- {
- ReleaseResources();
-
- ThreadPool.QueueUserWorkItem((state) =>
- {
- if (Completed == null)
- {
- return;
- }
- try
- {
- Completed(this, EventArgs.Empty);
- }
- catch (Exception) { }
- });
- }
- }
-
- private void ReleaseResources()
- {
- lock (this)
- {
- _responseBuffer = null;
- _responseDataStream = null;
- if (_responseInputStream != null)
- {
- try { _responseInputStream.Dispose(); }
- catch (Exception) { }
- _responseInputStream = null;
- }
- _webRequest = null;
- }
- }
- }
-
- #endregion
-
- #region Store Access
-
- private readonly object _storeLock = new object();
- private IsolatedStorageFile _store = null;
- private readonly SHA1 _hasher = new SHA1Managed();
-
- /// <summary>
- /// The name of directory in isolated storage that contains the files of this image cache.
- /// </summary>
- private string StoreDirectoryName
- {
- get
- {
- return "ImageCache_" + Name;
- }
- }
-
- /// <summary>
- /// The isolated storage file used by the cache.
- /// </summary>
- private IsolatedStorageFile Store
- {
- get
- {
- lock (_storeLock)
- {
- if (_store == null)
- {
- _store = IsolatedStorageFile.GetUserStoreForApplication();
- if (!_store.DirectoryExists(StoreDirectoryName))
- {
- _store.CreateDirectory(StoreDirectoryName);
- }
- }
- return _store;
- }
- }
- }
-
- private string GetImageKey(Uri imageUri)
- {
- byte[] imageUriBytes = Encoding.UTF8.GetBytes(imageUri.ToString());
- byte[] hash;
- lock (_hasher)
- {
- hash = _hasher.ComputeHash(imageUriBytes);
- }
- return BitConverter.ToString(hash).Replace("-", "");
- }
-
- private string GetImageFilePath(string imageKey)
- {
- return Path.Combine(StoreDirectoryName, imageKey) + "." + ImageDataExtension;
- }
-
- private string GetTimestampFilePath(string imageKey)
- {
- return Path.Combine(StoreDirectoryName, imageKey) + "." + ImageTimestampExtension;
- }
-
- private Stream ReadImageDataFromCache(string imageKey)
- {
- RequestCachePruning();
-
- MemoryStream dataStream = null;
- try
- {
- string imageFilePath = GetImageFilePath(imageKey);
- lock (_storeLock)
- {
- if (!Store.FileExists(imageFilePath))
- {
- return null;
- }
- if (GetImageTimestamp(imageKey).Add(ExpirationDelay) < DateTime.UtcNow)
- {
- DeleteImageFromCache(imageKey);
- return null;
- }
- using (IsolatedStorageFileStream fileStream = Store.OpenFile(imageFilePath, FileMode.Open, FileAccess.Read))
- {
- if (fileStream.Length > int.MaxValue)
- {
- return null;
- }
- dataStream = new MemoryStream((int)fileStream.Length);
- byte[] buffer = new byte[4096];
- while (dataStream.Length < fileStream.Length)
- {
- int readCount = fileStream.Read(buffer, 0, Math.Min(buffer.Length, (int)(fileStream.Length - dataStream.Length)));
- if (readCount <= 0)
- {
- throw new NotSupportedException();
- }
- dataStream.Write(buffer, 0, readCount);
- }
- }
- WriteImageToMemoryCache(imageKey, dataStream.ToArray());
- return dataStream;
- }
- }
- catch (Exception)
- {
- if (dataStream != null)
- {
- try { dataStream.Dispose(); }
- catch (Exception) { }
- }
- }
- return null;
- }
-
- private void WriteImageToCache(string imageKey, byte[] imageData)
- {
- RequestCachePruning();
-
- string imageFilePath = GetImageFilePath(imageKey);
- try
- {
- lock (_storeLock)
- {
- IsolatedStorageFileStream fileStream;
- if (Store.FileExists(imageFilePath))
- {
- fileStream = Store.OpenFile(imageFilePath, FileMode.Create, FileAccess.Write);
- }
- else
- {
- fileStream = Store.OpenFile(imageFilePath, FileMode.CreateNew, FileAccess.Write);
- }
- using (fileStream)
- {
- fileStream.Seek(0, SeekOrigin.Begin);
- while (fileStream.Position < imageData.Length)
- {
- fileStream.Write(imageData, (int)fileStream.Position, (int)(imageData.Length - fileStream.Position));
- }
- }
- SetImageTimestamp(imageKey, DateTime.UtcNow);
- }
- }
- catch (Exception)
- {
- try
- {
- Store.DeleteFile(imageFilePath);
- }
- catch (Exception) { }
- }
- }
-
- private void PrunePersistentCache()
- {
- try
- {
- lock (_storeLock)
- {
- string searchPattern = Path.Combine(StoreDirectoryName, string.Format("*.{0}", ImageDataExtension));
- string[] fileNames = Store.GetFileNames(searchPattern);
- foreach (string fileName in fileNames)
- {
- if (!fileName.EndsWith("." + ImageDataExtension))
- {
- continue;
- }
- string imageKey = fileName.Remove(Math.Max(fileName.Length - ImageDataExtension.Length - 1, 0));
- if (GetImageTimestamp(imageKey).Add(ExpirationDelay) < DateTime.UtcNow)
- {
- DeleteImageFromCache(imageKey);
- }
- }
- }
- }
- catch (Exception) { }
- }
-
- private void DeleteImageFromCache(string imageKey)
- {
- string imageFilePath = GetImageFilePath(imageKey);
- string timestampFilePath = GetTimestampFilePath(imageKey);
- lock (_storeLock)
- {
- try
- {
- if (Store.FileExists(imageFilePath))
- {
- Store.DeleteFile(imageFilePath);
- }
- }
- catch (Exception) { }
- try
- {
- if (Store.FileExists(timestampFilePath))
- {
- Store.DeleteFile(timestampFilePath);
- }
- }
- catch (Exception) { }
- }
- }
-
- private void DeleteAllImagesFromStore()
- {
- lock (_storeLock)
- {
- string searchPattern = Path.Combine(StoreDirectoryName, "*.*");
- try
- {
- string[] fileNames = Store.GetFileNames(searchPattern);
- foreach (string fileName in fileNames)
- {
- string filePath = Path.Combine(StoreDirectoryName, fileName);
- try
- {
- Store.DeleteFile(filePath);
- }
- catch (Exception) { }
- }
- }
- catch (Exception) { }
- }
- }
-
- private DateTime GetImageTimestamp(string imageKey)
- {
- string timestampFilePath = GetTimestampFilePath(imageKey);
- try
- {
- lock (_storeLock)
- {
- if (!Store.FileExists(timestampFilePath))
- {
- return DateTime.MinValue;
- }
- using (IsolatedStorageFileStream fileStream = Store.OpenFile(timestampFilePath, FileMode.Open, FileAccess.Read))
- using (StreamReader fileStreamReader = new StreamReader(fileStream, Encoding.UTF8))
- {
- string timestampString = fileStreamReader.ReadToEnd();
- return DateTime.Parse(timestampString).ToUniversalTime();
- }
- }
- }
- catch (Exception)
- {
- return DateTime.MinValue;
- }
- }
-
- private void SetImageTimestamp(string imageKey, DateTime timestamp)
- {
- string timestampFilePath = GetTimestampFilePath(imageKey);
- try
- {
- lock (_storeLock)
- {
- IsolatedStorageFileStream fileStream;
- if (Store.FileExists(timestampFilePath))
- {
- fileStream = Store.OpenFile(timestampFilePath, FileMode.Create, FileAccess.Write);
- }
- else
- {
- fileStream = Store.OpenFile(timestampFilePath, FileMode.CreateNew, FileAccess.Write);
- }
- using (fileStream)
- using (StreamWriter fileStreamWriter = new StreamWriter(fileStream, Encoding.UTF8))
- {
- fileStreamWriter.Write(timestamp.ToUniversalTime().ToString("u"));
- }
- }
- }
- catch (Exception) { }
- }
-
- #endregion
-
- #region Cache Pruning
-
- private static readonly TimeSpan CachePruningInterval = TimeSpan.FromMinutes(1);
- private static readonly TimeSpan CachePruningTimerDuration = TimeSpan.FromSeconds(5);
-
- private DateTime _cachePruningTimestamp = DateTime.MinValue;
- private OneShotDispatcherTimer _cachePruningTimer = null;
-
- private void RequestCachePruning()
- {
- lock (this)
- {
- if (_cachePruningTimer != null || _cachePruningTimestamp.Add(CachePruningInterval) >= DateTime.UtcNow)
- {
- return;
- }
- Deployment.Current.Dispatcher.BeginInvoke(() =>
- {
- if (_cachePruningTimer != null)
- {
- return;
- }
- _cachePruningTimer = OneShotDispatcherTimer.CreateAndStart(CachePruningTimerDuration, OnCachePruningTimerFired);
- });
- }
- }
-
- private void OnCachePruningTimerFired(object sender, EventArgs e)
- {
- if (sender != _cachePruningTimer)
- {
- return;
- }
- _cachePruningTimer = null;
- _cachePruningTimestamp = DateTime.UtcNow;
- ThreadPool.QueueUserWorkItem((state) => { PruneCache(); });
- }
-
- private void PruneCache()
- {
- PrunePersistentCache();
- PruneMemoryCache();
- }
-
- #endregion
-
- #region Memory Cache
-
- private readonly object _memoryCacheLock = new object();
- private int _memoryCacheCapacity = DefaultMemoryCacheCapacity;
- private Dictionary<string, LinkedListNode<byte[]>> _memoryCacheNodes = new Dictionary<string, LinkedListNode<byte[]>>(DefaultMemoryCacheCapacity);
- private LinkedList<byte[]> _memoryCacheList = new LinkedList<byte[]>();
-
- /// <summary>
- /// The capacity of the in-memory cache.
- /// If set to zero, the in-memory cache is disabled.
- /// </summary>
- public int MemoryCacheCapacity
- {
- get
- {
- return _memoryCacheCapacity;
- }
- set
- {
- if (value < 0)
- {
- throw new ArgumentOutOfRangeException();
- }
- lock (_memoryCacheLock)
- {
- _memoryCacheCapacity = value;
- PruneMemoryCache();
- }
- }
- }
-
- private Stream LoadImageFromMemoryCache(string imageKey)
- {
- lock (_memoryCacheLock)
- {
- if (_memoryCacheCapacity == 0)
- {
- return null;
- }
- if (!_memoryCacheNodes.ContainsKey(imageKey))
- {
- return null;
- }
- LinkedListNode<byte[]> node = _memoryCacheNodes[imageKey];
- if (node.List == _memoryCacheList)
- {
- _memoryCacheList.Remove(node);
- }
- _memoryCacheList.AddLast(node.Value);
- PruneMemoryCache();
- return new MemoryStream(node.Value);
- }
- }
-
- private void WriteImageToMemoryCache(string imageKey, byte[] imageData)
- {
- if (string.IsNullOrEmpty(imageKey) || imageData == null || imageData.Length == 0)
- {
- return;
- }
- lock (_memoryCacheLock)
- {
- if (_memoryCacheCapacity == 0)
- {
- return;
- }
- if (_memoryCacheNodes.ContainsKey(imageKey))
- {
- _memoryCacheList.Remove(_memoryCacheNodes[imageKey]);
- }
- LinkedListNode<byte[]> newNode = _memoryCacheList.AddLast(imageData);
- PruneMemoryCache();
- _memoryCacheNodes[imageKey] = newNode;
- }
- }
-
- private void PruneMemoryCache()
- {
- lock (_memoryCacheLock)
- {
- if (_memoryCacheCapacity == 0)
- {
- ClearMemoryCache();
- return;
- }
- while (_memoryCacheList.Count > _memoryCacheCapacity)
- {
- DeleteFirstMemoryCacheNode();
- }
- }
- }
-
- private void DeleteFirstMemoryCacheNode()
- {
- lock (_memoryCacheLock)
- {
- LinkedListNode<byte[]> node = _memoryCacheList.First;
- if (node == null)
- {
- return;
- }
- _memoryCacheList.Remove(node);
- foreach (string imageKey in _memoryCacheNodes.Keys)
- {
- if (_memoryCacheNodes[imageKey] == node)
- {
- _memoryCacheNodes.Remove(imageKey);
- break;
- }
- }
- }
- }
-
- private void ClearMemoryCache()
- {
- lock (_memoryCacheLock)
- {
- _memoryCacheNodes.Clear();
- _memoryCacheList.Clear();
- }
- }
-
- #endregion
-
- public override string ToString()
- {
- return string.Format("PersistentImageCache({0})", Name);
- }
- }
-}
diff --git a/Juick/Storage/SystemImageCache.cs b/Juick/Storage/SystemImageCache.cs
deleted file mode 100644
index 0f62f6b..0000000
--- a/Juick/Storage/SystemImageCache.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2010 Andreas Saudemont (andreas.saudemont@gmail.com)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-using System;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-
-namespace Kawagoe.Storage
-{
- /// <summary>
- /// Implements an <see cref="ImageCache"/> using the cache mechanism provided by the system.
- /// </summary>
- public class SystemImageCache : ImageCache
- {
- /// <summary>
- /// Initializes a new <see cref="SystemImageCache"/> instance with the specified name.
- /// </summary>
- public SystemImageCache(string name)
- : base(name)
- {
- }
-
- /// <summary>
- /// Implements <see cref="ImageCache.GetInternal"/>.
- /// </summary>
- protected override ImageSource GetInternal(Uri imageUri)
- {
- return new BitmapImage(imageUri);
- }
-
- /// <summary>
- /// Implements <see cref="ImageCache.Clear"/>.
- /// </summary>
- public override void Clear()
- {
- // do nothing
- }
- }
-}
diff --git a/Juick/Threading/OneShotDispatcherTimer.cs b/Juick/Threading/OneShotDispatcherTimer.cs
deleted file mode 100644
index 4b2854d..0000000
--- a/Juick/Threading/OneShotDispatcherTimer.cs
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2010 Andreas Saudemont (andreas.saudemont@gmail.com)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-using System;
-using System.Windows.Threading;
-
-namespace Kawagoe.Threading
-{
- /// <summary>
- /// Provides a one-shot timer integrated to the Dispatcher queue.
- /// </summary>
- public class OneShotDispatcherTimer
- {
- /// <summary>
- /// Creates a new <see cref="OneShotDispatcherTimer"/> and starts it.
- /// </summary>
- /// <param name="duration">The duration of the timer.</param>
- /// <param name="callback">The delegate that will be called when the timer fires.</param>
- /// <returns>The newly created timer.</returns>
- public static OneShotDispatcherTimer CreateAndStart(TimeSpan duration, EventHandler callback)
- {
- OneShotDispatcherTimer timer = new OneShotDispatcherTimer();
- timer.Duration = duration;
- timer.Fired += callback;
- timer.Start();
- return timer;
- }
-
- private TimeSpan _duration = TimeSpan.Zero;
- private DispatcherTimer _timer = null;
-
- /// <summary>
- /// Initializes a new <see cref="OneShotDispatcherTimer"/> instance.
- /// </summary>
- public OneShotDispatcherTimer()
- {
- }
-
- /// <summary>
- /// The duration of the timer. The default is 00:00:00.
- /// </summary>
- /// <remarks>
- /// Setting the value of this property takes effect the next time the timer is started.
- /// </remarks>
- /// <exception cref="ArgumentOutOfRangeException">The specified value when setting this property represents
- /// a negative time internal.</exception>
- public TimeSpan Duration
- {
- get
- {
- return _duration;
- }
- set
- {
- if (value.TotalMilliseconds < 0)
- {
- throw new ArgumentOutOfRangeException();
- }
- _duration = value;
- }
- }
-
- /// <summary>
- /// Indicates whether the timer is currently started.
- /// </summary>
- public bool IsStarted
- {
- get
- {
- return (_timer != null);
- }
- }
-
- /// <summary>
- /// Occurs when the one-shot timer fires.
- /// </summary>
- public event EventHandler Fired;
-
- /// <summary>
- /// Raises the <see cref="Fired"/> event.
- /// </summary>
- private void RaiseFired()
- {
- if (Fired != null)
- {
- try
- {
- Fired(this, EventArgs.Empty);
- }
- catch (Exception) { }
- }
- }
-
- /// <summary>
- /// Starts the timer.
- /// This method has no effect if the timer is already started.
- /// </summary>
- /// <remarks>
- /// The same <see cref="OneShotDispatcherTimer"/> instance can be started and stopped multiple times.
- /// </remarks>
- public void Start()
- {
- if (_timer != null)
- {
- return;
- }
-
- _timer = new DispatcherTimer();
- _timer.Interval = _duration;
- _timer.Tick += OnTimerTick;
- _timer.Start();
- }
-
- /// <summary>
- /// Stops the timer.
- /// This method has no effect if the timer is not started.
- /// </summary>
- /// <remarks>
- /// The <see cref="Fired"/> event is guaranteed not to be raised once this method has been invoked
- /// and until the timer is started again.
- /// </remarks>
- public void Stop()
- {
- if (_timer == null)
- {
- return;
- }
- try
- {
- _timer.Stop();
- }
- catch (Exception) { }
- _timer = null;
- }
-
- /// <summary>
- /// Listens to Tick events on the underlying timer.
- /// </summary>
- private void OnTimerTick(object sender, EventArgs e)
- {
- if (sender != _timer)
- {
- return;
- }
- Stop();
- RaiseFired();
- }
- }
-}
diff --git a/Juick/ViewModels/PostItem.cs b/Juick/ViewModels/PostItem.cs
index e88f6f1..2072cf5 100644
--- a/Juick/ViewModels/PostItem.cs
+++ b/Juick/ViewModels/PostItem.cs
@@ -31,14 +31,14 @@ namespace Juick.ViewModels
public int MID {get;set;}
public int RID {get;set;}
+
+ public string Username {get;set;}
+
+ public Uri AvatarUri {get;set;}
+
+ public Uri Attachment {get;set;}
- public string Username { get; set; }
-
- public Uri AvatarUri { get; set; }
-
- public Uri Attachment { get; set; }
-
- public string Status { get; set; }
+ public string Status {get;set;}
public string MessageText { get; set; }
diff --git a/Juick/ViewModels/ViewModelBase.cs b/Juick/ViewModels/ViewModelBase.cs
index 27fdc42..2427a80 100644
--- a/Juick/ViewModels/ViewModelBase.cs
+++ b/Juick/ViewModels/ViewModelBase.cs
@@ -9,13 +9,11 @@ using System.Windows.Media.Imaging;
using Juick.Classes;
using JuickApi;
using RestSharp;
-using Kawagoe.Storage;
namespace Juick.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
- ImageCache _cache = new PersistentImageCache("avatars");
static readonly string IsDataLoadingPropertyName = ExpressionHelper.GetPropertyName<ViewModelBase>(x => x.IsDataLoading);
bool isDataLoading;