2
// UPnPContainerSource.cs
5
// Tobias 'topfs2' Arrskog <tobias.arrskog@gmail.com>
7
// Copyright (C) 2011 Tobias 'topfs2' Arrskog
9
// Permission is hereby granted, free of charge, to any person obtaining
10
// a copy of this software and associated documentation files (the
11
// "Software"), to deal in the Software without restriction, including
12
// without limitation the rights to use, copy, modify, merge, publish,
13
// distribute, sublicense, and/or sell copies of the Software, and to
14
// permit persons to whom the Software is furnished to do so, subject to
15
// the following conditions:
17
// The above copyright notice and this permission notice shall be
18
// included in all copies or substantial portions of the Software.
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
using System.Collections.Generic;
35
using Mono.Upnp.Dcp.MediaServer1.ContentDirectory1;
36
using Mono.Upnp.Dcp.MediaServer1.ContentDirectory1.AV;
39
using Banshee.Collection;
40
using Banshee.Configuration;
41
using Banshee.Sources;
42
using Banshee.ServiceStack;
46
namespace Banshee.UPnPClient
48
public class UPnPServerSource : Source
51
private UPnPMusicSource music_source;
52
private UPnPVideoSource video_source;
53
private SchemaEntry<bool> expanded_schema;
55
public UPnPServerSource (Device device) : base (Catalog.GetString ("UPnP Share"), device.FriendlyName, 300)
57
Properties.SetStringList ("Icon.Name", "computer", "network-server");
58
TypeUniqueId = "upnp-container";
59
expanded_schema = new SchemaEntry<bool> ("plugins.upnp." + device.Udn, "expanded", true,
60
"UPnP Share expanded", "UPnP Share expanded" );
63
ContentDirectoryController content_directory = null;
65
foreach (Service service in device.Services) {
66
Log.Debug ("UPnPService \"" + device.FriendlyName + "\" Implements " + service.Type);
67
if (service.Type.Type == "ContentDirectory") {
68
content_directory = new ContentDirectoryController (service.GetController());
72
if (content_directory == null) {
73
throw new ArgumentNullException("content_directory");
76
ThreadAssist.Spawn (delegate {
77
Parse (device, content_directory);
84
if (music_source != null) {
85
RemoveChildSource (music_source);
89
if (video_source != null) {
90
RemoveChildSource (video_source);
95
delegate void ChunkHandler<T> (Results<T> results);
97
void HandleResults<T> (Results<T> results, RemoteContentDirectory remote_dir, ChunkHandler<T> chunkHandler)
99
bool has_results = results.Count > 0;
101
while (has_results) {
102
chunkHandler (results);
104
has_results = results.HasMoreResults;
106
results = results.GetMoreResults (remote_dir);
111
List<string[]> FindBrowseQuirks (Device device)
113
List<string[]> core = new List<string[]>();
114
if (device.ModelName == "MediaTomb" && device.ModelNumber == "0.12.1") {
115
core.Add (new string[2] { "Audio", "Albums" });
116
core.Add (new string[2] { "Video", "All Video" });
117
} else if (device.ModelName == "Coherence UPnP A/V MediaServer" && device.ModelNumber == "0.6.6.2") {
118
core.Add (new string[1] { "Albums" });
120
core.Add (new string[0]);
126
void Parse (Device device, ContentDirectoryController content_directory)
128
RemoteContentDirectory remote_dir = new RemoteContentDirectory (content_directory);
129
DateTime begin = DateTime.Now;
130
Container root = remote_dir.GetRootObject ();
131
bool recursive_browse = !content_directory.CanSearch;
134
if (!recursive_browse) {
136
Log.Debug ("Content directory is searchable, let's search");
138
HandleResults<MusicTrack> (remote_dir.Search<MusicTrack>(root, visitor => visitor.VisitDerivedFrom("upnp:class", "object.item.audioItem.musicTrack"), new ResultsSettings()),
141
List<MusicTrack> music_tracks = new List<MusicTrack>();
142
foreach (var item in chunk) {
143
music_tracks.Add (item as MusicTrack);
146
AddMusic (music_tracks);
149
HandleResults<VideoItem> (remote_dir.Search<VideoItem>(root, visitor => visitor.VisitDerivedFrom("upnp:class", "object.item.videoItem"), new ResultsSettings()),
152
List<VideoItem> video_tracks = new List<VideoItem>();
153
foreach (var item in chunk) {
154
video_tracks.Add (item as VideoItem);
157
AddVideo (video_tracks);
159
} catch (System.InvalidCastException exception) {
160
Log.Exception (exception);
161
recursive_browse = true;
165
if (recursive_browse) {
166
Log.Debug ("Content directory is not searchable, let's browse recursively");
167
List<MusicTrack> music_tracks = new List<MusicTrack> ();
168
List<VideoItem> video_tracks = new List<VideoItem> ();
170
foreach (var hierarchy in FindBrowseQuirks (device)) {
171
TraverseContainer (remote_dir, root, hierarchy, 0, music_tracks, video_tracks);
174
if (music_tracks.Count > 0) {
175
AddMusic (music_tracks);
177
if (video_tracks.Count > 0) {
178
AddVideo (video_tracks);
181
} catch (Exception exception) {
182
Log.Exception (exception);
185
Log.Debug ("Found all items on the service, took " + (DateTime.Now - begin).ToString());
188
void TraverseContainer (RemoteContentDirectory remote_dir, Container container, string[] hierarchy, int position, List<MusicTrack> music_tracks, List<VideoItem> video_tracks)
190
if (hierarchy != null && hierarchy.Length > position) {
191
HandleResults<Mono.Upnp.Dcp.MediaServer1.ContentDirectory1.Object> (
192
remote_dir.GetChildren<Mono.Upnp.Dcp.MediaServer1.ContentDirectory1.Object> (container),
195
foreach (var upnp_object in chunk) {
196
if (upnp_object is Container && upnp_object.Title == hierarchy[position]) {
197
TraverseContainer (remote_dir, upnp_object as Container, hierarchy, position + 1, music_tracks, video_tracks);
202
ParseContainer (remote_dir, container, 0, music_tracks, video_tracks);
206
void ParseContainer (RemoteContentDirectory remote_dir, Container container, int depth, List<MusicTrack> music_tracks, List<VideoItem> video_tracks)
208
if (depth > 10 || (container.ChildCount != null && container.ChildCount == 0)) {
212
HandleResults<Mono.Upnp.Dcp.MediaServer1.ContentDirectory1.Object> (
213
remote_dir.GetChildren<Mono.Upnp.Dcp.MediaServer1.ContentDirectory1.Object> (container),
216
foreach (var upnp_object in chunk) {
217
if (upnp_object is Item) {
218
Item item = upnp_object as Item;
220
if (item.IsReference || item.Resources.Count == 0) {
224
if (item is MusicTrack) {
225
music_tracks.Add (item as MusicTrack);
226
} else if (item is VideoItem) {
227
video_tracks.Add (item as VideoItem);
229
} else if (upnp_object is Container) {
230
ParseContainer (remote_dir, upnp_object as Container, depth + 1, music_tracks, video_tracks);
233
if (music_tracks.Count > 500) {
234
AddMusic (music_tracks);
235
music_tracks.Clear ();
237
if (video_tracks.Count > 100) {
238
AddVideo (video_tracks);
239
video_tracks.Clear ();
245
public void Disconnect ()
247
if (music_source != null) {
248
music_source.Disconnect ();
251
if (video_source != null) {
252
video_source.Disconnect ();
256
private void AddMusic (List<MusicTrack> tracks)
258
if (music_source == null) {
259
music_source = new UPnPMusicSource (udn);
260
AddChildSource (music_source);
263
music_source.AddTracks (tracks);
266
private void AddVideo (List<VideoItem> tracks)
268
if (video_source == null) {
269
video_source = new UPnPVideoSource (udn);
270
AddChildSource (video_source);
273
video_source.AddTracks (tracks);
276
public override bool? AutoExpand {
277
get { return expanded_schema.Get (); }
280
public override bool Expanded {
281
get { return expanded_schema.Get (); }
282
set { expanded_schema.Set (value); }
285
public override bool CanActivate {
286
get { return false; }
289
public override bool CanRename {
290
get { return false; }