#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.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using Nuclex.Networking.Exceptions;
using Nuclex.Support;
using Nuclex.Support.Tracking;
using Nuclex.Support.Scheduling;
namespace Nuclex.Audio.Metadata.Requests {
/// Queries the CDDB server for the XMCD file of a CD
internal class CddbReadRequest :
Request, IAbortable, IProgressReporter {
/// ASCII code for the space character
private const char SP = ' ';
/// Triggered when the status of the process changes
public event EventHandler AsyncProgressChanged;
/// Initializes a new CDDB database read request
///
/// Protocol used to communication with the CDDB server
///
/// Category in which the CD's CDDB entry is stored
/// Disc id of the CD whose CDDB entry will be read
public CddbReadRequest(
CddbProtocol protocol, string category, int discId
) {
this.protocol = protocol;
this.category = category;
this.discId = discId;
}
/// 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() {
if(this.protocol != null) {
CddbProtocol theProtocol = this.protocol;
this.protocol = null;
this.exception = new AbortedException("Aborted on user request");
theProtocol.Dispose();
}
}
/// Starts the asynchronous execution of the request
public void Start() {
ThreadPool.QueueUserWorkItem(new WaitCallback(execute));
}
///
/// 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 Cddb.DatabaseEntry GatherResults() {
return this.results;
}
/// Triggers the AsyncProgressChanged event
/// New progress to report to the event subscribers
protected virtual void OnProgressAchieved(float progress) {
EventHandler copy = AsyncProgressChanged;
if(copy != null) {
copy(this, new ProgressReportEventArgs(progress));
}
}
/// Called asynchronously to execute the request
/// Not used
private void execute(object state) {
lock(this.protocol.SyncRoot) {
try {
this.protocol.SendLine(
string.Format("cddb read {0} {1:x8} ", this.category, this.discId),
5000
);
// The first reply will indicate the status of the request.
string statusLine = this.protocol.ReceiveLine(5000);
int statusCode = CddbProtocol.GetStatusCode(statusLine);
switch(statusCode) {
case 210: {
this.results = receiveDatabaseFile();
break;
}
default: {
this.exception = exceptionFromQueryStatus(
statusCode, statusLine.Substring((statusLine.Length >= 4) ? 4 : 3)
);
break;
}
}
}
catch(Exception exception) {
if(!(this.exception is AbortedException)) {
this.exception = exception;
}
}
}
OnAsyncEnded();
}
///
/// Generates an exception from the status code in the reply to a disc query
///
/// Status code provided by the server
/// Response returned by the server
///
/// The exception for the server status code or null if the status code
/// indicates success
///
private static Exception exceptionFromQueryStatus(int statusCode, string message) {
switch(statusCode) {
case 401: {
throw new Exceptions.EntryNotFoundException(message);
}
case 402: {
throw new Exceptions.ServerErrorException(message);
}
case 403: {
throw new Exceptions.DatabaseEntryCorruptException(message);
}
case 409: {
throw new Exceptions.NoHandshakeException(message);
}
default: {
return new BadResponseException(
string.Format(
"Bad response from CDDB server: invalid status code '{0}', " +
"server message is '{1}'",
statusCode, message
)
);
}
}
}
/// Retrieves the XMCD database file for the CD
/// An array of strings containing the lines of the database file
private Cddb.DatabaseEntry receiveDatabaseFile() {
XmcdDecoder decoder = new XmcdDecoder();
for(; ; ) {
string line = this.protocol.ReceiveLine(5000);
if(line == ".") {
break;
}
decoder.ProcessLine(line);
}
// All genres received, convert the list into an array that can be returned
// to the owner of the request.
return decoder.ToDatabaseEntry();
}
/// Exception that has occured during asynchronous processing
private volatile Exception exception;
/// Protocol used to communicate with the CDDB server
private CddbProtocol protocol;
/// Category in which the CD's CDDB entry is stored
private string category;
/// Disc id of the CD whose CDDB entry will be read
private int discId;
/// Results returned by the CDDB server
private Cddb.DatabaseEntry results;
}
} // namespace Nuclex.Audio.Metadata.Requests