#region CPL License /* Nuclex Framework Copyright (C) 2002-2009 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Threading; using Nuclex.Support.Tracking; using Nuclex.Support.Scheduling; namespace Nuclex.Audio.Verification.Requests { /// /// Provides audio file verfication services using the AccurateRip database /// internal partial class AccurateRipRetrievalRequest : Request, IAbortable { /// Initializes a new request for handling an AccurateRip query /// /// Disc id of the CD calculated using AccurateRip's primary algorithm /// /// /// Disc id of the CD calculated using AccurateRip's secondary algorithm /// /// /// Disc id of the CD calculated using the CDDB algorithm /// /// Number of tracks on the CD public AccurateRipRetrievalRequest( int discId1, int discId2, int cddbDiscId, int trackCount ) { // Remember the IDs of the CD we asked for so we notice when data from // the wrong CD would be returned (not that this should happen, but for reasons // of robustness, we check for it) this.requestedDiscId1 = discId1; this.requestedDiscId2 = discId2; this.requestedCddbDiscId = cddbDiscId; this.requestedTrackCount = trackCount; } /// Starts the request /// /// The request will run asynchronously. If an error occurs during preparation /// of the request, the AsyncEnded event will be triggered in the calling thread. /// public void Start() { try { // Build a query string that can be sent to the AccurateRip server in order // to obtain the CRCs of the individual tracks // // Example queryString (found on the internet, no idea what CD it even is) // c/5/8/dBAR-010-0012085c-009054b5-6a0b3d0a.bin // string queryString = string.Format( "{0:x1}/{1:x1}/{2:x1}/dBAR-{3:d3}-{4:x8}-{5:x8}-{6:x8}.bin", this.requestedDiscId1 & 0xF, (this.requestedDiscId1 >> 4) & 0xF, (this.requestedDiscId1 >> 8) & 0xF, this.requestedTrackCount, this.requestedDiscId1, this.requestedDiscId2, this.requestedCddbDiscId ); // Build a request to the AccurateRip server using the prepared query URL this.request = WebRequest.Create( "http://www.accuraterip.com/accuraterip/" + queryString ); request.Method = "GET"; request.Timeout = 15000; // 15 seconds is plenty! // We run the request asynchronously so the user can do something else while // the request ist executing. request.BeginGetResponse( new AsyncCallback(accurateRipResponseReceived), null ); } catch(Exception exception) { this.exception = exception; OnAsyncEnded(); } } /// Aborts the running process. Can be called from any thread. /// /// The receiver should honor the abort request and stop whatever it is /// doing as soon as possible. The method does not impose any requirement /// on the timeliness of the reaction of the running process, but implementers /// are advised to not ignore the abort request and urged to try and design /// their code in such a way that it can be stopped in a reasonable time /// (eg. within 1 second of the abort request). /// public void AsyncAbort() { // Special internal knowledge: We don't have to worry about the Start() method // still running because the AccurateRip class that's using this request will not // hand out the request to the user before Start() has completed. Thus we can // assume that the request and asyncResult fields are either valid or that the // request has already ended, in which case it is too late to abort. // Perform this inside of a lock in case the call arrives after lock(this) { if(this.request != null) { this.aborting = true; this.request.Abort(); // Will invoke the Request callback! } } // lock(this) } /// /// Allows the specific request implementation to re-throw an exception if /// the background process finished unsuccessfully /// protected override void ReraiseExceptions() { if(this.exception != null) { throw this.exception; } } /// /// Allows the specific request implementation to re-throw an exception if /// the background process finished unsuccessfully /// protected override AccurateRip.CdInfo[] GatherResults() { return this.results; } /// /// Callback invoked when a response from the AccurateRip server has been received or /// when the web request has been aborted /// /// /// Asynchronous result handle of the completed or aborted web request /// private void accurateRipResponseReceived(IAsyncResult asyncResult) { try { lock(this) { // If the request was aborted, avoid calling the EndGetResponse() method since // it will only trigger an exception. There have been some discussions about // whether .NET requires you to call EndGetResponse() even then, but the idea // is that WebRequest.Abort() replaces EndGetResponse() which I believe is the // design goal as this behavior is the same across many classes in the .NET // framework (compare implementation of Socket, SerialPort, etc.) if(this.aborting) { // Set the exception (instead of throwing it). This is slightly more efficient // and, most importantly, doesn't cause the debugger to break right here if the // user has configured it to stop on throw. this.exception = new AbortedException("Request has been aborted"); } else { // Request was not aborted // At this point, We can now be sure that we've not been aborted yet and that // any abort calls happening in right now will have to wait before the lock and // thus, only get to continue when the nulled our 'request' field. WebResponse response = request.EndGetResponse(asyncResult); // Begin decoding the response data. The request might still be receiving data // from the server asynchronously but the header has been decoded at this point // and the BinaryReader will block until the required data has been received // when accessing the response stream. Blocking is not an issue since we can be // sure that we're running in a ThreadPool thread right now. Stream responseDataStream = response.GetResponseStream(); this.results = AccurateRipParser.DecodeQueryResponse( new BinaryReader(responseDataStream) ); } this.request = null; } // lock(this) } catch(Exception exception) { this.exception = exception; } OnAsyncEnded(); } /// Exception that occured during the processing of the request private volatile Exception exception; /// Set to true to indicate that the request is being aborted private bool aborting; /// Results returned by the query to the AccurateRip database private AccurateRip.CdInfo[] results; /// /// Disc id of the requested CD calculated using AccurateRip's primary algorithm /// private int requestedDiscId1; /// /// Disc id of the requested CD calculated using AccurateRip's secondary algorithm /// private int requestedDiscId2; /// Disc id of the requested CD calculated using the CDDB algorithm private int requestedCddbDiscId; /// Number of tracks on the requested CD private int requestedTrackCount; /// Asynchronously running web request to the AccurateRip server private volatile WebRequest request; } } // namespace Nuclex.Audio.Verification.Requests