From 83aaf4091dc6f1e851d094df9425a78c8a1b0d3f Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 28 Aug 2016 13:45:25 +1000 Subject: [PATCH] Should be using the 'lastmodified' provided for our own 'if modified since' header, fixes texture packs from some web servers always being redownloaded. (Thanks CookieNetwork) --- ClassicalSharp/Game/Game.Properties.cs | 1 + ClassicalSharp/Network/INetworkProcessor.cs | 19 ++-- .../Network/Utils/AsyncDownloader.cs | 43 ++++--- ClassicalSharp/TexturePack/TextureCache.cs | 105 +++++++++++------- 4 files changed, 100 insertions(+), 68 deletions(-) diff --git a/ClassicalSharp/Game/Game.Properties.cs b/ClassicalSharp/Game/Game.Properties.cs index 0a4871596..b587b8419 100644 --- a/ClassicalSharp/Game/Game.Properties.cs +++ b/ClassicalSharp/Game/Game.Properties.cs @@ -192,6 +192,7 @@ namespace ClassicalSharp { internal EntryList AcceptedUrls = new EntryList( "acceptedurls.txt" ); internal EntryList DeniedUrls = new EntryList( "deniedurls.txt" ); internal EntryList ETags = new EntryList( "etags.txt" ); + internal EntryList LastModified = new EntryList( "lastmodified.txt" ); /// Calculates the amount that the hotbar widget should be scaled by when rendered. diff --git a/ClassicalSharp/Network/INetworkProcessor.cs b/ClassicalSharp/Network/INetworkProcessor.cs index b20ffb545..55cfa6642 100644 --- a/ClassicalSharp/Network/INetworkProcessor.cs +++ b/ClassicalSharp/Network/INetworkProcessor.cs @@ -98,8 +98,8 @@ namespace ClassicalSharp { void DownloadTexturePack( string url ) { if( game.DeniedUrls.HasEntry( url ) ) return; - DateTime lastModified = TextureCache.GetLastModifiedFromCache( url ); - string etag = TextureCache.GetETagFromCache( url, game.ETags ); + DateTime lastModified = TextureCache.GetLastModified( url, game.LastModified ); + string etag = TextureCache.GetETag( url, game.ETags ); if( url.Contains( ".zip" ) ) game.AsyncDownloader.DownloadData( url, true, "texturePack", @@ -134,10 +134,12 @@ namespace ClassicalSharp { game.Drawer2D.ConvertTo32Bpp( ref bmp ); } if( !game.ChangeTerrainAtlas( bmp ) ) { bmp.Dispose(); return; } - TextureCache.AddToCache( item.Url, bmp ); - TextureCache.AddETagToCache( item.Url, item.ETag, game.ETags ); + + TextureCache.Add( item.Url, bmp ); + TextureCache.AddETag( item.Url, item.ETag, game.ETags ); + TextureCache.AdddLastModified( item.Url, item.LastModified, game.LastModified ); } else { - Bitmap bmp = TextureCache.GetBitmapFromCache( item.Url ); + Bitmap bmp = TextureCache.GetBitmap( item.Url ); if( bmp == null ) { // e.g. 404 errors ExtractDefault(); } else if( item.Url != game.World.TextureUrl ) { @@ -154,10 +156,11 @@ namespace ClassicalSharp { TexturePackExtractor extractor = new TexturePackExtractor(); extractor.Extract( (byte[])item.Data, game ); - TextureCache.AddToCache( item.Url, (byte[])item.Data ); - TextureCache.AddETagToCache( item.Url, item.ETag, game.ETags ); + TextureCache.Add( item.Url, (byte[])item.Data ); + TextureCache.AddETag( item.Url, item.ETag, game.ETags ); + TextureCache.AdddLastModified( item.Url, item.LastModified, game.LastModified ); } else { - byte[] data = TextureCache.GetDataFromCache( item.Url ); + byte[] data = TextureCache.GetData( item.Url ); if( data == null ) { // e.g. 404 errors ExtractDefault(); } else if( item.Url != game.World.TextureUrl ) { diff --git a/ClassicalSharp/Network/Utils/AsyncDownloader.cs b/ClassicalSharp/Network/Utils/AsyncDownloader.cs index 001321ad7..72072e163 100644 --- a/ClassicalSharp/Network/Utils/AsyncDownloader.cs +++ b/ClassicalSharp/Network/Utils/AsyncDownloader.cs @@ -27,9 +27,9 @@ namespace ClassicalSharp.Network { public int CurrentItemProgress = -3; public AsyncDownloader() { } - public AsyncDownloader( string skinServer ) { Init( skinServer ); } + public AsyncDownloader( string skinServer ) { Init( skinServer ); } public void Init( Game game ) { Init( game.skinServer ); } - + void Init( string skinServer ) { this.skinServer = skinServer; WebRequest.DefaultWebProxy = null; @@ -40,7 +40,7 @@ namespace ClassicalSharp.Network { worker.Start(); } - public void Ready( Game game ) { } + public void Ready( Game game ) { } public void Reset( Game game ) { lock( requestLocker ) requests.Clear(); @@ -56,49 +56,49 @@ namespace ClassicalSharp.Network { string strippedSkinName = Utils.StripColours( skinName ); string url = Utils.IsUrlPrefix( skinName, 0 ) ? skinName : skinServer + strippedSkinName + ".png"; - AddRequest( url, true, identifier, RequestType.Bitmap, + AddRequest( url, true, identifier, RequestType.Bitmap, DateTime.MinValue , null); } /// Asynchronously downloads a bitmap image from the specified url. public void DownloadImage( string url, bool priority, string identifier ) { - AddRequest( url, priority, identifier, RequestType.Bitmap, + AddRequest( url, priority, identifier, RequestType.Bitmap, DateTime.MinValue, null ); } /// Asynchronously downloads a string from the specified url. public void DownloadPage( string url, bool priority, string identifier ) { - AddRequest( url, priority, identifier, RequestType.String, + AddRequest( url, priority, identifier, RequestType.String, DateTime.MinValue, null ); } /// Asynchronously downloads a byte array. public void DownloadData( string url, bool priority, string identifier ) { - AddRequest( url, priority, identifier, RequestType.ByteArray, + AddRequest( url, priority, identifier, RequestType.ByteArray, DateTime.MinValue, null ); } /// Asynchronously downloads a bitmap image. - public void DownloadImage( string url, bool priority, string identifier, + public void DownloadImage( string url, bool priority, string identifier, DateTime lastModified, string etag ) { - AddRequest( url, priority, identifier, RequestType.Bitmap, + AddRequest( url, priority, identifier, RequestType.Bitmap, lastModified, etag ); } /// Asynchronously downloads a byte array. - public void DownloadData( string url, bool priority, string identifier, + public void DownloadData( string url, bool priority, string identifier, DateTime lastModified, string etag ) { - AddRequest( url, priority, identifier, RequestType.ByteArray, + AddRequest( url, priority, identifier, RequestType.ByteArray, lastModified, etag ); } /// Asynchronously retrieves the content length of the body response. public void RetrieveContentLength( string url, bool priority, string identifier ) { - AddRequest( url, priority, identifier, RequestType.ContentLength, + AddRequest( url, priority, identifier, RequestType.ContentLength, DateTime.MinValue, null ); } - void AddRequest( string url, bool priority, string identifier, + void AddRequest( string url, bool priority, string identifier, RequestType type, DateTime lastModified, string etag ) { lock( requestLocker ) { Request request = new Request( url, identifier, type, lastModified, etag ); @@ -197,11 +197,14 @@ namespace ClassicalSharp.Network { object value = null; HttpStatusCode status = HttpStatusCode.OK; string etag = null; + DateTime lastModified = DateTime.MinValue; try { HttpWebRequest req = MakeRequest( request ); using( HttpWebResponse response = (HttpWebResponse)req.GetResponse() ) { etag = response.Headers[HttpResponseHeader.ETag]; + if( response.Headers[HttpResponseHeader.LastModified] != null ) + lastModified = response.LastModified; value = DownloadContent( request, response ); } } catch( Exception ex ) { @@ -220,7 +223,8 @@ namespace ClassicalSharp.Network { lock( downloadedLocker ) { DownloadedItem oldItem; - DownloadedItem newItem = new DownloadedItem( value, request.TimeAdded, url, status, etag ); + DownloadedItem newItem = new DownloadedItem( value, request.TimeAdded, url, + status, etag, lastModified ); if( downloaded.TryGetValue( request.Identifier, out oldItem ) ) { if( oldItem.TimeAdded > newItem.TimeAdded ) { @@ -325,7 +329,7 @@ namespace ClassicalSharp.Network { /// ETag of the item most recently cached. (if any) public string ETag; - public Request( string url, string identifier, RequestType type, + public Request( string url, string identifier, RequestType type, DateTime lastModified, string etag ) { Url = url; Identifier = identifier; @@ -357,14 +361,19 @@ namespace ClassicalSharp.Network { /// Unique identifier assigned by the server to this item. public string ETag; - public DownloadedItem( object data, DateTime timeAdded, - string url, HttpStatusCode code, string etag ) { + /// Time the server indicates this item was last modified. + public DateTime LastModified; + + public DownloadedItem( object data, DateTime timeAdded, + string url, HttpStatusCode code, + string etag, DateTime lastModified ) { Data = data; TimeAdded = timeAdded; TimeDownloaded = DateTime.UtcNow; Url = url; ResponseCode = code; ETag = etag; + LastModified = lastModified; } } } \ No newline at end of file diff --git a/ClassicalSharp/TexturePack/TextureCache.cs b/ClassicalSharp/TexturePack/TextureCache.cs index 3a0dc6b02..21d2a26fe 100644 --- a/ClassicalSharp/TexturePack/TextureCache.cs +++ b/ClassicalSharp/TexturePack/TextureCache.cs @@ -13,54 +13,76 @@ namespace ClassicalSharp.TexturePack { /// Caches terrain atlases and texture packs to avoid making redundant downloads. public static class TextureCache { + /// Gets whether the given url has data associated with it in the cache. + public static bool HasUrl( string url ) { + return File.Exists( MakePath( url ) ); + } + /// Gets the bitmap associated with the url from the cache, returning null if the bitmap /// for the url was not found in the cache or the bitmap in the cache was corrupted. - public static Bitmap GetBitmapFromCache( string url ) { + public static Bitmap GetBitmap( string url ) { string path = MakePath( url ); if( !File.Exists( path ) ) return null; try { return new Bitmap( path ); } catch( ArgumentException ex ) { - ErrorHandler.LogError( "Cache.GetBitmapFromCache", ex ); + ErrorHandler.LogError( "Cache.GetBitmap", ex ); return null; } catch( IOException ex ) { - ErrorHandler.LogError( "Cache.GetBitmapFromCache", ex ); + ErrorHandler.LogError( "Cache.GetBitmap", ex ); return null; } } - /// Gets the data associated with the url from the cache, returning null if the + /// Gets the data associated with the url from the cache, returning null if the /// data for the url was not found in the cache. - public static byte[] GetDataFromCache( string url ) { + public static byte[] GetData( string url ) { string path = MakePath( url ); if( !File.Exists( path ) ) return null; try { return File.ReadAllBytes( path ); } catch( IOException ex ) { - ErrorHandler.LogError( "Cache.GetDataFromCache", ex ); + ErrorHandler.LogError( "Cache.GetData", ex ); return null; } } - /// Gets the time the data associated with the url from the cache was last modified, + /// Gets the time the data associated with the url from the cache was last modified, /// returning DateTime.MinValue if data for the url was not found in the cache. - public static DateTime GetLastModifiedFromCache( string url ) { - string path = MakePath( url ); - if( !File.Exists( path ) ) - return DateTime.MinValue; + public static DateTime GetLastModified( string url, EntryList tags ) { + string entry = GetFromTags( url, tags ); + long ticks = 0; + if( entry != null && long.TryParse( entry, out ticks ) ) + return new DateTime( ticks, DateTimeKind.Utc ); + string path = MakePath( url ); + if( !File.Exists( path ) ) return DateTime.MinValue; return File.GetLastWriteTimeUtc( path ); } - - /// Gets whether the given url has a bitmap associated with it in the cache. - public static bool IsInCache( string url ) { - return File.Exists( MakePath( url ) ); + + public static string GetETag( string url, EntryList tags ) { + return GetFromTags( url, tags ); } + static string GetFromTags( string url, EntryList tags ) { + string crc32 = CRC32( url ); + + for( int i = 0; i < tags.Entries.Count; i++ ) { + string entry = tags.Entries[i]; + if( !entry.StartsWith( crc32 ) ) continue; + + int sepIndex = entry.IndexOf( ' ' ); + if( sepIndex == -1 ) continue; + return entry.Substring( sepIndex + 1 ); + } + return null; + } + + /// Adds the url and the bitmap associated with it to the cache. - public static void AddToCache( string url, Bitmap bmp ) { + public static void Add( string url, Bitmap bmp ) { string path = MakePath( url ); try { string basePath = PathIO.Combine( Program.AppDirectory, Folder ); @@ -75,7 +97,7 @@ namespace ClassicalSharp.TexturePack { } /// Adds the url and the data associated with it to the cache. - public static void AddToCache( string url, byte[] data ) { + public static void Add( string url, byte[] data ) { string path = MakePath( url ); try { string basePath = PathIO.Combine( Program.AppDirectory, Folder ); @@ -87,52 +109,49 @@ namespace ClassicalSharp.TexturePack { ErrorHandler.LogError( "Cache.AddToCache", ex ); } } - - public static string GetETagFromCache( string url, EntryList tags ) { - byte[] utf8 = Encoding.UTF8.GetBytes( url ); - string crc32 = CRC32( utf8 ).ToString(); - - for( int i = 0; i < tags.Entries.Count; i++ ) { - string entry = tags.Entries[i]; - if( !entry.StartsWith( crc32 ) ) continue; - - int sepIndex = entry.IndexOf( ' ' ); - if( sepIndex == -1 ) continue; - return entry.Substring( sepIndex + 1 ); - } - return null; + + public static void AddETag( string url, string etag, EntryList tags ) { + if( etag == null ) return; + AddToTags( url, etag, tags ); } - public static void AddETagToCache( string url, string etag, EntryList tags ) { - if( etag == null ) return; - byte[] utf8 = Encoding.UTF8.GetBytes( url ); - string crc32 = CRC32( utf8 ).ToString(); - + public static void AdddLastModified( string url, DateTime lastModified, EntryList tags ) { + if( lastModified == DateTime.MinValue ) return; + string data = lastModified.ToUniversalTime().Ticks.ToString(); + AddToTags( url, data, tags ); + } + + static void AddToTags( string url, string data, EntryList tags ) { + string crc32 = CRC32( url ); for( int i = 0; i < tags.Entries.Count; i++ ) { if( !tags.Entries[i].StartsWith( crc32 ) ) continue; - tags.Entries[i] = crc32 + " " + etag; + tags.Entries[i] = crc32 + " " + data; tags.Save(); return; } - tags.AddEntry( crc32 + " " + etag ); + tags.AddEntry( crc32 + " " + data ); } + const string Folder = "texturecache"; static string MakePath( string url ) { - byte[] utf8 = Encoding.UTF8.GetBytes( url ); - uint crc32 = CRC32( utf8 ); + string crc32 = CRC32( url ); string basePath = PathIO.Combine( Program.AppDirectory, Folder ); - return PathIO.Combine( basePath, crc32.ToString() ); + return PathIO.Combine( basePath, crc32 ); } - static uint CRC32( byte[] data ) { + static string CRC32( string url ) { + byte[] data = Encoding.UTF8.GetBytes( url ); uint crc = 0xffffffffU; + for( int i = 0; i < data.Length; i++ ) { crc ^= data[i]; for( int j = 0; j < 8; j++ ) crc = (crc >> 1) ^ (crc & 1) * 0xEDB88320; } - return crc ^ 0xffffffffU; + + uint result = crc ^ 0xffffffffU; + return result.ToString(); } } }