package de.dtele.control {
import de.dtele.control.events.*;
import de.dtele.data.*;
import de.dtele.messages.Message;
import de.dtele.messages.MessageManager;
import de.dtele.messages.MessageResponse;
import de.dtele.messages.events.MessageResponseEvent;
import de.dtele.net.Connection;
import de.dtele.net.MediaRequest;
import de.dtele.net.events.ConnectionErrorEvent;
import de.dtele.net.events.ConnectionEvent;
import de.dtele.net.protocol.HTTPProtocolHandler;
import de.dtele.net.protocol.UnknownProtocolHandler;
import de.dtele.net.protocol.WebSocketProtocolHandler;
import de.dtele.providers.ProviderManager;
import de.dtele.settings.SettingsManager;
import flash.errors.IllegalOperationError;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.ProgressEvent;
import flash.net.FileReference;
import flash.utils.Dictionary;
import flash.utils.getQualifiedClassName;
import mx.collections.ArrayCollection;
import mx.collections.ListCollectionView;
/**
* Dispatched when the <code>selectedSource</code> property changes
*
* @eventType de.dtele.control.events.SourceEvent.SELECTED
*/
[Event("sourceSelected", type="de.dtele.control.events.SourceEvent")]
/**
* Dispatched when a source has been added
*
* @eventType de.dtele.control.events.SourceEvent.ADDED
*/
[Event("sourceAdded", type="de.dtele.control.events.SourceEvent")]
/**
* Dispatched when a source has been removed
*
* @eventType de.dtele.control.events.SourceEvent.REMOVED
*/
[Event("sourceRemoved", type="de.dtele.control.events.SourceEvent")]
/**
* Dispatched when the <code>selectedResource</code> property changes
*
* @eventType de.dtele.control.events.ResourceEvent.SELECTED
*/
[Event("resourceSelected", type="de.dtele.control.events.ResourceEvent")]
/**
* Dispatched when a resource has been added
*
* @eventType de.dtele.control.events.ResourceEvent.ADDED
*/
[Event("resourceAdded", type="de.dtele.control.events.ResourceEvent")]
/**
* Dispatched when a resource has been removed
*
* @eventType de.dtele.control.events.ResourceEvent.REMOVED
*/
[Event("resourceRemoved", type="de.dtele.control.events.ResourceEvent")]
/**
* Dispatched when a credentials for a request to a source are required
*
* @eventType de.dtele.control.events.CredentialsEvent.REQUEST
*/
[Event("credentialsRequest", type="de.dtele.control.events.CredentialsEvent")]
/**
* The central instance responsible
* for handling all interaction
*
* @author Mathias Brodala
*/
public final class MediaManager extends EventDispatcher {
private static var _instance:MediaManager;
/**
* The singleton instance of the media manager
*/
public static function get instance():MediaManager {
if (_instance == null) {
_instance = new MediaManager();
}
return _instance;
}
private var _sources:ListCollectionView = new ArrayCollection();
/**
* The list of managed sources
*/
[Bindable]public function get sources():ListCollectionView { return this._sources; };
protected function set sources(sources:ListCollectionView):void { this._sources = sources; }
private var _selectedSource:ISource;
/**
* The currently selected source
*/
[Bindable]public function get selectedSource():ISource { return this._selectedSource; }
public function set selectedSource(source:ISource):void {
if (source != this._selectedSource) {
this._selectedSource = source;
this.dispatchEvent(new SourceEvent(SourceEvent.SELECTED, source));
SettingsManager.instance.set("selectedSourceURL", source.url);
}
}
private var _selectedResource:IResource;
/**
* The currently selected resource
*/
[Bindable]public function get selectedResource():IResource { return this._selectedResource; }
public function set selectedResource(resource:IResource):void {
if (resource != this._selectedResource) {
this._selectedResource = resource;
this.dispatchEvent(new ResourceEvent(ResourceEvent.SELECTED, resource));
}
}
/**
* URLs of sources currently being added
*/
private var pending:Array = [];
/**
* Sets up the media manager
*/
public function MediaManager() {
if (_instance != null) {
throw new IllegalOperationError("Cannot instantiate " + getQualifiedClassName(this) + ". " +
"Use the singleton instance instead.");
}
ProviderManager.instance.addProvider("protocolHandlers", "__unknown__", UnknownProtocolHandler);
ProviderManager.instance.addProvider("protocolHandlers", "http", HTTPProtocolHandler);
ProviderManager.instance.addProvider("protocolHandlers", "ws", WebSocketProtocolHandler);
this.addEventListener(SourceEvent.ADDED, this.onSourceAdded);
this.addEventListener(SourceEvent.REMOVED, this.onSourceRemoved);
}
/**
* Restores the state from stored settings
*/
public function restoreState():void {
var selectedSourceURL:String = SettingsManager.instance.get("selectedSourceURL") as String;
function restoreSelectedSource(e:SourceEvent):void {
if (e.source.url == selectedSourceURL) {
MediaManager.instance.selectedSource = e.source;
MediaManager.instance.removeEventListener(SourceEvent.ADDED, restoreSelectedSource);
}
}
this.addEventListener(SourceEvent.ADDED, restoreSelectedSource);
SettingsManager.instance.setDefault("sourceURLs", this.sources);
for each (var url:String in SettingsManager.instance.get("sourceURLs")) {
this.addSource(url);
}
}
/**
* Collects the URLs of all currently available sources
*
* @return An array of URL strings
*/
private function getSourceURLs():Array {
return this.sources.toArray().map(function(source:ISource, ...a):String {
return source.url;
});
}
/**
* Performs a MediaRequest to a source,
* asking for credentials if required
*
* @param request The MediaRequest
* @param source The source to send the request to
*/
private function sendAuthenticated(request:MediaRequest, source:ISource):void {
if (source.requiresCredentials(request.type)) {
this.dispatchEvent(new CredentialsEvent(
CredentialsEvent.REQUEST,
source,
request,
function(credentials:Credentials):void {
if (credentials) {
request.credentials = credentials;
}
source.connection.send(request);
}
));
} else {
if (request.type in source.credentials) {
request.credentials = source.credentials[request.type];
}
source.connection.send(request);
}
}
/**
* Adds a source from an URL
*
* @param url The URL to add a source from
*/
public function addSource(url:String):void {
if (!url) {
return;
}
if (this.pending.indexOf(url) > -1) {
MessageManager.instance.addWarning(
"Quelle wird hinzugefügt",
"Die Quelle " + url + " wird gerade hinzugefügt."
);
return;
}
this.pending.push(url);
function sameURL(source:Source, ...a):Boolean {
return url == source.url;
}
trace("[MediaManager] Adding source from " + url);
if (this.sources.toArray().some(sameURL)) {
MessageManager.instance.addWarning(
"Quelle nicht hinzugefügt",
"Die Quelle " + url + " wurde bereits hinzugefügt."
);
return;
}
var connection:Connection = new Connection(url);
connection.addEventListener(ConnectionEvent.OPENED, this.onConnectionOpen);
connection.addEventListener(ConnectionEvent.CLOSED, this.onConnectionClose);
connection.addEventListener(ConnectionEvent.RESPONSE, this.onConnectionResponse);
connection.addEventListener(ConnectionErrorEvent.ERROR, this.onConnectionError);
connection.open();
}
/**
* Removes a source
*
* @param source The source to remove
*/
public function removeSource(source:ISource):void {
trace("[MediaManager] Removing source " + source);
source.connection.close();
}
/**
* Adds a resource to a source
*
* @param resource The resource to add
* @param source The source to add the resource to
*/
public function addResource(resource:Object, source:ISource):void {
this.addResources([resource], source);
}
/**
* Adds resources to a source
*
* @param resources The resources to add
* @param source The source to add the resources to
*/
public function addResources(resources:Array, source:ISource):void {
if (!(MediaRequest.ADD in source.allowed)) {
MessageManager.instance.addError(
"Hinzufügen fehlgeschlagen",
"Das Hinzufügen von Ressourcen zu " + source + " ist nicht möglich."
);
return;
}
var request:MediaRequest = new MediaRequest(MediaRequest.ADD);
resources.forEach(function(resource:Object, ...a):void {
if (resource is FileReference) {
request.files.push(resource as FileReference);
} else if (resource is IResource) {
request.resources[resource.url] = resource.properties;
}
});
var progressMessage:Message = new Message(
"Ressourcen hinzufügen",
"Die Resourcen werden zu " + source + " hinzugefügt",
Message.PROGRESS
);
progressMessage.addEventListener(MessageResponseEvent.RESPONSE,
function(e:MessageResponseEvent):void {
if (e.response.type == MessageResponse.CANCEL) {
request.cancel();
}
}
);
MessageManager.instance.addMessage(progressMessage);
request.addEventListener(ProgressEvent.PROGRESS, function(e:ProgressEvent):void {
progressMessage.dispatchEvent(e);
});
request.addEventListener(Event.COMPLETE, function(e:Event):void {
progressMessage.dispatchEvent(e);
});
request.addEventListener(Event.CANCEL, function(e:Event):void {
progressMessage.dispatchEvent(e);
});
this.sendAuthenticated(request, source);
}
/**
* Removes a resource from its source
*
* @param resource The resource to remove
*/
public function removeResource(resource:IResource):void {
this.removeResources(Vector.<IResource>([resource]));
}
/**
* Removes resources from their sources
*
* @param resources The resources to remove
*/
public function removeResources(resources:Vector.<IResource>):void {
var removals:Dictionary = new Dictionary();
for each (var resource:IResource in resources) {
if (!(MediaRequest.REMOVE in resource.source)) {
MessageManager.instance.addError(
"Entfernen fehlgeschlagen",
"Das Entfernen von Ressourcen von einer der Quellen ist nicht möglich."
);
return;
}
if (!(resource.source in removals)) {
removals[resource.source] = [];
}
removals[resource.source].push(resource);
};
for (var source:Object in removals) {
var request:MediaRequest = new MediaRequest(MediaRequest.REMOVE);
request.resources = removals[source];
this.sendAuthenticated(request, source as ISource);
}
}
/**
* Moves a resource from its source to another source
*
* @param resource The resource to move
* @param targetSource The source to move the resource to
*/
public function moveResource(resource:IResource, targetSource:ISource):void {}
/**
* Moves resources from their sources to another source
*
* @param resources The resources to move
* @param targetSource The source to move the resources to
*/
public function moveResources(resources:Vector.<IResource>, targetSource:ISource):void {}
/**
* Tries to find a source based on a given URL
*
* @param url The URL to find a source for
* @return A source or null
*/
public function findSource(url:String):ISource {
for each (var source:ISource in this.sources) {
if (source.url == url) {
return source;
}
}
return null;
}
/**
* Tries to find a resource based on a given URL
*
* @param url The URL to find a resource for
* @return A resource or null
*/
public function findResource(url:String):IResource {
var resources:Vector.<IResource> = this.findResources(Vector.<String>([url]));
return (resources.length == 1 ? resources[0] : null);
}
/**
* Tries to find a resource based on given URLs
*
* @param urls The URLs to find resources for
* @return A list of resources, empty if none where found
*/
public function findResources(urls:Vector.<String>):Vector.<IResource> {
var resources:Vector.<IResource> = new Vector.<IResource>();
var resourceURLs:String = "\0" + urls.join("\0");
for each (var source:ISource in this.sources) {
for each (var resource:IResource in source.resources) {
if (resourceURLs.indexOf("\0" + resource.url) > -1) {
resources.push(resource);
}
}
};
return resources;
}
/**
* Requests the list of resources of a source,
* selects the newly added source if possible
* and persists the sources
*/
private function onSourceAdded(e:SourceEvent):void {
this.pending.splice(this.pending.indexOf(e.source.url), 1);
if (!this.selectedSource) {
this.selectedSource = e.source;
}
if (!(MediaRequest.LIST in e.source.allowed)) {
MessageManager.instance.addError(
"Ressourcen-Auflistung nicht möglich",
"Die Ressourcen von " +
e.source.properties.title +
"können nicht aufgelistet werden."
);
return;
}
var request:MediaRequest = new MediaRequest(MediaRequest.LIST);
this.sendAuthenticated(request, e.source);
SettingsManager.instance.set("sourceURLs", this.getSourceURLs());
}
/**
* Persists the source URLs
*/
private function onSourceRemoved(e:SourceEvent):void {
SettingsManager.instance.set("sourceURLs", this.getSourceURLs());
}
/**
* Requests the info data of a newly connected source
*/
private function onConnectionOpen(e:ConnectionEvent):void {
var connection:Connection = e.target as Connection;
var source:String = this.findSource(connection.url) as String || connection.url;
trace("[MediaManager] Connection to " + source + " opened");
connection.send(new MediaRequest(MediaRequest.INFO));
}
/**
* Removes a disconnected source and selects the
* last added source if possible
*/
private function onConnectionClose(e:ConnectionEvent):void {
var connection:Connection = e.target as Connection;
var source:ISource = this.findSource(connection.url);
trace("[MediaManager] Connection to " + source + " closed");
MessageManager.instance.addWarning(
"Verbindung geschlossen",
"Die Verbindung zu " + (source as String || connection.url) + " wurde geschlossen."
);
if (source) {
this.sources.removeItemAt(this.sources.getItemIndex(source));
this.dispatchEvent(new SourceEvent(SourceEvent.REMOVED, source));
if ((source == this.selectedSource) && this.sources.length > 0) {
this.selectedSource = this.sources.getItemAt(this.sources.length - 1) as ISource;
} else {
this.selectedSource = null;
}
}
}
private function onConnectionResponse(e:ConnectionEvent):void {
trace("[MediaManager] Got response with result " + e.response.result);
var connection:Connection = e.target as Connection;
if (e.response.type == MediaRequest.INFO) {
var newSource:ISource = new Source(connection.url, e.response.info);
newSource.connection = connection;
this.sources.addItem(newSource);
this.dispatchEvent(new SourceEvent(SourceEvent.ADDED, newSource));
MessageManager.instance.addInfo(
"Quelle hinzugefügt",
"Die Quelle " + newSource.properties.title + " wurde hinzugefügt."
);
} else {
var source:ISource = this.findSource(connection.url);
switch (e.response.type) {
case MediaRequest.LIST:
source.resources.removeAll();
case MediaRequest.LIST:
case MediaRequest.ADD:
for (var addedURL:String in e.response.resources) {
var resource:IResource = new Resource(source, addedURL, e.response.resources[addedURL]);
source.resources.addItem(resource);
this.dispatchEvent(new ResourceEvent(ResourceEvent.ADDED, resource));
}
break;
case MediaRequest.REMOVE:
var urls:Vector.<String> = Vector.<String>(e.response.resources);
var resources:Vector.<IResource> = this.findResources(urls);
resources.forEach(function(resource:IResource):void {
source.resources.removeItemAt(source.resources.getItemIndex(resource));
this.dispatchEvent(new ResourceEvent(ResourceEvent.REMOVED, resource));
MessageManager.instance.addInfo(
"Entfernen erfolgreich",
"Das Entfernen von " + resource +
" von " +source.properties.title +
" war erfolgreich."
);
});
break;
}
if (e.request && e.request.credentials && !(e.request.type in source.credentials)) {
source.credentials[e.request.type] = e.request.credentials;
}
}
}
private function onConnectionError(e:ConnectionErrorEvent):void {
var connection:Connection = e.target as Connection;
var source:String = this.findSource(connection.url) as String || connection.url;
try { this.pending.splice(this.pending.indexOf(connection.url), 1); } catch (e:Error) {}
trace("[MediaManager] Got connection error: " + e.error.code);
if (e.request) {
var request:MediaRequest = e.request;
var message:String = (e.error.message ? ": " + e.error.message : "");
switch (e.request.type) {
case MediaRequest.INFO:
MessageManager.instance.addError(
"Informationsabfrage fehlgeschlagen",
"Fehler beim Abfragen der Informationen über " +
source + message
);
break;
case MediaRequest.LIST:
MessageManager.instance.addError(
"Ressourcen-Auflistung fehlgeschlagen",
"Fehler beim Auflisten der Ressourcen von " +
source + message
);
break;
case MediaRequest.ADD:
MessageManager.instance.addError(
"Hinzufügen fehlgeschlagen",
"Fehler beim Hinzufügen von Ressourcen zu " +
source + message
);
break;
case MediaRequest.REMOVE:
MessageManager.instance.addError(
"Entfernen fehlgeschlagen",
"Fehler beim Entfernen von Ressourcen von " +
source + message
);
break;
}
} else {
MessageManager.instance.addError(
"Verbindung fehlgeschlagen",
"Die Verbindung zu " + source + " ist fehlgeschlagen: " +
e.error.message
);
}
}
/**
* Default handler for credentials request, indented to signify failure
*
* @param e The credentials event
*/
private function onCredentialsRequest(e:CredentialsEvent):void {
if (e.isDefaultPrevented()) {
trace("[MediaManager] prevented");
return;
}
e.callback(null);
}
}
}