diff options
author | Vitaly Takmazov | 2013-03-29 15:05:32 +0400 |
---|---|---|
committer | Vitaly Takmazov | 2013-03-29 15:05:32 +0400 |
commit | 4c92def17fef70c35e578ffb2c5e6acde3b9247d (patch) | |
tree | d1529dc0a60eca2032d55b8a7c68b5687f40ed83 /Juick | |
parent | 9f19cd09bfad13715bb4eda46e7782f56674e26c (diff) |
revert PersistentImgeCache: too slow :)
Diffstat (limited to 'Juick')
-rw-r--r-- | Juick/Controls/MessageList.xaml | 3 | ||||
-rw-r--r-- | Juick/Converters/UriToImageSourceConverter.cs | 31 | ||||
-rw-r--r-- | Juick/Juick.csproj | 5 | ||||
-rw-r--r-- | Juick/Storage/ImageCache.cs | 160 | ||||
-rw-r--r-- | Juick/Storage/PersistentImageCache.cs | 880 | ||||
-rw-r--r-- | Juick/Storage/SystemImageCache.cs | 50 | ||||
-rw-r--r-- | Juick/Threading/OneShotDispatcherTimer.cs | 160 | ||||
-rw-r--r-- | Juick/ViewModels/PostItem.cs | 14 | ||||
-rw-r--r-- | Juick/ViewModels/ViewModelBase.cs | 2 |
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; |