protected virtual void RemoveFromActiveList(MediaCacheRecord record) { var baseMethod = typeof(MediaCache).GetMethod("RemoveFromActiveList", BindingFlags.Instance | BindingFlags.NonPublic); if (baseMethod != null) baseMethod.Invoke(this, new object[] { record }); else Log.Error("Dianoga: Couldn't use malevolent private reflection on RemoveFromActiveList! This may mean Dianoga isn't entirely compatible with this version of Sitecore, though it should only affect a performance optimization.", this); }
// the 'active list' is an internal construct that lets Sitecore stream media to the client at the same time as it's being written to cache // unfortunately though the rest of MediaCache is virtual, these methods are inexplicably not protected virtual void AddToActiveList(MediaCacheRecord record) { var baseMethod = typeof(MediaCache).GetMethod("AddToActiveList", BindingFlags.Instance | BindingFlags.NonPublic); if (baseMethod != null) baseMethod.Invoke(this, new object[] { record }); else Log.Error("Dianoga: Couldn't use malevolent private reflection on AddToActiveList! This may mean Dianoga isn't entirely compatible with this version of Sitecore, though it should only affect a performance optimization.", this); // HEY SITECORE, CAN WE GET THESE VIRTUAL? KTHX. }
// the 'active list' is an internal construct that lets Sitecore stream media to the client at the same time as it's being written to cache // unfortunately though the rest of MediaCache is virtual, these methods are inexplicably not protected virtual void AddToActiveList(MediaCacheRecord record) { var baseMethod = typeof(MediaCache).GetMethod("AddToActiveList", BindingFlags.Instance | BindingFlags.NonPublic); if (baseMethod != null) { baseMethod.Invoke(this, new object[] { record }); } else { Log.Error("Dianoga: Couldn't use malevolent private reflection on AddToActiveList! This may mean Dianoga isn't entirely compatible with this version of Sitecore, though it should only affect a performance optimization.", this); } // HEY SITECORE, CAN WE GET THESE VIRTUAL? KTHX. }
public override bool AddStream(Media media, MediaOptions options, MediaStream stream, out MediaStream cachedStream) { /* STOCK METHOD (Decompiled) */ Assert.ArgumentNotNull(media, "media"); Assert.ArgumentNotNull(options, "options"); Assert.ArgumentNotNull(stream, "stream"); cachedStream = null; if (!CanCache(media, options)) { return(false); } MediaCacheRecord cacheRecord = CreateCacheRecord(media, options, stream); if (cacheRecord == null) { return(false); } cachedStream = cacheRecord.GetStream(); if (cachedStream == null) { return(false); } AddToActiveList(cacheRecord); /* END STOCK */ // we store the site context because on the background thread: without the Sitecore context saved (on a worker thread), that disables the media cache var currentSite = Context.Site; cacheRecord.PersistAsync((() => OnAfterPersist(cacheRecord, currentSite))); return(true); }
protected virtual void OnAfterPersist(MediaCacheRecord record, SiteContext originalSiteContext) { string mediaPath = string.Empty; try { RemoveFromActiveList(record); // Housekeeping: since we call AddStream() to insert the optimized version, we have to keep AddStream() from calling OnAfterPersist() from that call, causing an optimization loop var id = record.Media.MediaData.MediaId; if (StreamsInOptimization.Contains(id)) { lock (OptimizeLock) { if (StreamsInOptimization.Contains(id)) { StreamsInOptimization.Remove(id); return; } } } lock (OptimizeLock) { StreamsInOptimization.Add(id); } // grab the stream from cache and optimize it if (!record.HasStream) return; var stream = record.GetStream(); if (!_optimizer.CanOptimize(stream)) return; var optimizedStream = _optimizer.Process(stream, record.Options); mediaPath = stream.MediaItem.MediaPath; if (optimizedStream == null) { Log.Info("Dianoga: async optimazation result was null, not doing any optimizing for {0}".FormatWith(mediaPath), this); return; } // we have to switch the site context because we're on a background thread, and Sitecore.Context is lost (so the site is always null) // if the site is null the media cache ignores all added entries using (new SiteContextSwitcher(originalSiteContext)) { for (int i = 0; i < 5; i++) { try { // only here to satisfy out param MediaStream dgafStream; bool success = AddStream(record.Media, record.Options, optimizedStream, out dgafStream); if (dgafStream != null) dgafStream.Dispose(); if (!success) Log.Warn("Dianoga: The media cache rejected adding {0}. This is unexpected!".FormatWith(mediaPath), this); } catch (Exception ex) { Log.Error("Dianoga: Error occurred adding stream to media cache. Will retry in 10 sec.", ex, this); Thread.Sleep(10000); continue; } break; } } } catch (Exception ex) { // this runs in a background thread, and an exception here would cause IIS to terminate the app pool. Bad! So we catch/log, just in case. Log.Error("Dianoga: Exception occurred on the background thread when optimizing: {0}".FormatWith(mediaPath), ex, this); } }
private void Optimize(SiteContext currentSite, Media media, MediaOptions options) { var mediaItem = media.MediaData.MediaItem; MediaStream originalMediaStream = null; MediaStream backupMediaStream = null; MediaStream optimizedMediaStream = null; try { // switch to the right site context (see above) using (new SiteContextSwitcher(currentSite)) { //if the image is already optimized, abort task if (this.Contains(media, options)) { return; } //get stream from mediaItem to reduce memory usage using (var stream = media.GetStream(options)) { // make a copy of the stream to use var originalStream = new MemoryStream(); stream.CopyTo(originalStream); originalStream.Seek(0, SeekOrigin.Begin); originalMediaStream = new MediaStream(originalStream, media.Extension, mediaItem); // make a stream backup we can use to persist in the event of an optimization failure // (which will dispose of originalStream) var backupStream = new MemoryStream(); originalStream.CopyTo(backupStream); backupStream.Seek(0, SeekOrigin.Begin); backupMediaStream = new MediaStream(backupStream, media.Extension, mediaItem); } MediaCacheRecord cacheRecord = null; optimizedMediaStream = _optimizer.Process(originalMediaStream, options); if (optimizedMediaStream == null) { Log.Info($"Dianoga: {mediaItem.MediaPath} cannot be optimized due to media type or path exclusion", this); cacheRecord = CreateCacheRecord(media, options, backupMediaStream); } if (cacheRecord == null) { cacheRecord = CreateCacheRecord(media, options, optimizedMediaStream); } AddToActiveList(cacheRecord); cacheRecord.Persist(); RemoveFromActiveList(cacheRecord); } } catch (Exception ex) { // this runs in a background thread, and an exception here would cause IIS to terminate the app pool. Bad! So we catch/log, just in case. Log.Error($"Dianoga: Exception occurred on the background thread when optimizing: {mediaItem.MediaPath}", ex, this); } finally { // release resources used by the optimization task originalMediaStream?.Dispose(); backupMediaStream?.Dispose(); optimizedMediaStream?.Dispose(); } }
public override bool AddStream(Media media, MediaOptions options, MediaStream stream, out MediaStream cachedStream) { /* STOCK METHOD (Decompiled) */ Assert.ArgumentNotNull(media, "media"); Assert.ArgumentNotNull(options, "options"); Assert.ArgumentNotNull(stream, "stream"); cachedStream = null; if (!CanCache(media, options)) { return(false); } if (string.IsNullOrEmpty(media.MediaData.MediaId)) { return(false); } if (!stream.Stream.CanRead) { Log.Warn($"Cannot optimize {media.MediaData.MediaItem.MediaPath} because cache was passed a non readable stream.", this); return(false); } // buffer the stream if it's say a SQL stream stream.MakeStreamSeekable(); stream.Stream.Seek(0, SeekOrigin.Begin); // Sitecore will use this to stream the media while we persist cachedStream = stream; // make a copy of the stream to use temporarily while we async persist var copyStream = new MemoryStream(); stream.CopyTo(copyStream); copyStream.Seek(0, SeekOrigin.Begin); var copiedMediaStream = new MediaStream(copyStream, stream.Extension, stream.MediaItem); // we store the site context because on the background thread: without the Sitecore context saved (on a worker thread), that disables the media cache var currentSite = Context.Site; ThreadPool.QueueUserWorkItem(state => { var mediaPath = stream.MediaItem.MediaPath; try { // make a stream backup we can use to persist in the event of an optimization failure // (which will dispose of copyStream) var backupStream = new MemoryStream(); copyStream.CopyTo(backupStream); backupStream.Seek(0, SeekOrigin.Begin); var backupMediaStream = new MediaStream(backupStream, stream.Extension, stream.MediaItem); // switch to the right site context (see above) using (new SiteContextSwitcher(currentSite)) { MediaCacheRecord cacheRecord = null; var optimizedStream = _optimizer.Process(copiedMediaStream, options); if (optimizedStream == null) { Log.Info($"Dianoga: {mediaPath} cannot be optimized due to media type or path exclusion", this); cacheRecord = CreateCacheRecord(media, options, backupMediaStream); } if (cacheRecord == null) { cacheRecord = CreateCacheRecord(media, options, optimizedStream); backupMediaStream.Dispose(); } AddToActiveList(cacheRecord); cacheRecord.Persist(); RemoveFromActiveList(cacheRecord); } } catch (Exception ex) { // this runs in a background thread, and an exception here would cause IIS to terminate the app pool. Bad! So we catch/log, just in case. Log.Error($"Dianoga: Exception occurred on the background thread when optimizing: {mediaPath}", ex, this); } }); return(true); }
protected virtual void OnAfterPersist(MediaCacheRecord record, SiteContext originalSiteContext) { string mediaPath = string.Empty; try { RemoveFromActiveList(record); // Housekeeping: since we call AddStream() to insert the optimized version, we have to keep AddStream() from calling OnAfterPersist() from that call, causing an optimization loop var id = record.Media.MediaData.MediaId; if (StreamsInOptimization.Contains(id)) { lock (OptimizeLock) { if (StreamsInOptimization.Contains(id)) { StreamsInOptimization.Remove(id); return; } } } lock (OptimizeLock) { StreamsInOptimization.Add(id); } // grab the stream from cache and optimize it if (!record.HasStream) { return; } var stream = record.GetStream(); if (!_optimizer.CanOptimize(stream)) { return; } var optimizedStream = _optimizer.Process(stream, record.Options); mediaPath = stream.MediaItem.MediaPath; if (optimizedStream == null) { Log.Info("Dianoga: async optimazation result was null, not doing any optimizing for {0}".FormatWith(mediaPath), this); return; } // we have to switch the site context because we're on a background thread, and Sitecore.Context is lost (so the site is always null) // if the site is null the media cache ignores all added entries using (new SiteContextSwitcher(originalSiteContext)) { for (int i = 0; i < 5; i++) { try { // only here to satisfy out param MediaStream dgafStream; bool success = AddStream(record.Media, record.Options, optimizedStream, out dgafStream); if (dgafStream != null) { dgafStream.Dispose(); } if (!success) { Log.Warn("Dianoga: The media cache rejected adding {0}. This is unexpected!".FormatWith(mediaPath), this); } } catch (Exception ex) { Log.Error("Dianoga: Error occurred adding stream to media cache. Will retry in 10 sec.", ex, this); Thread.Sleep(10000); continue; } break; } } } catch (Exception ex) { // this runs in a background thread, and an exception here would cause IIS to terminate the app pool. Bad! So we catch/log, just in case. Log.Error("Dianoga: Exception occurred on the background thread when optimizing: {0}".FormatWith(mediaPath), ex, this); } }