Commit 1fdd2fc0 authored by Gabriel Burt's avatar Gabriel Burt Committed by Gabriel Burt

Create a HyenaSqliteCommand to reuse for adding tracks to playlists -

2008-09-05  Gabriel Burt  <gabriel.burt@gmail.com>

	* src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod/IpodSource.cs: Create a
	HyenaSqliteCommand to reuse for adding tracks to playlists - should be
	faster.

	* src/Dap/Banshee.Dap.Mtp/Banshee.Dap.Mtp/MtpSource.cs: Load and save
	playlists on MTP devices.

	* src/Dap/Banshee.Dap.Mtp/Banshee.Dap.Mtp/MtpTrackInfo.cs: Save the
	Mtp.Track's ID in the ExternalID column, cleanup cruft.

	* src/Dap/Banshee.Dap/Banshee.Dap/DapLibrarySync.cs: Don't add empty
	playlists to devices.

	* src/Core/Banshee.Services/Banshee.Sources/SourceManager.cs: Fix
	gui-thread issue.

	* src/Libraries/Mtp/Mtp/Folder.cs: Override ToString for debugging.

	* src/Libraries/Mtp/Mtp/Album.cs: Factor a lot of code that's useful for
	the Playlist class into AbstractTrackList, subclass from it.

	* src/Libraries/Mtp/Makefile.am:
	* src/Libraries/Mtp/Mtp/AbstractTrackList.cs: Factored out code from Album

	* src/Libraries/Mtp/Mtp/MtpDevice.cs: Add GetPlaylists () method.

	* src/Libraries/Mtp/Mtp/Playlist.cs: Subclass from AbstractTrackList,
	implement required methods, add to build.


svn path=/trunk/banshee/; revision=4474
parent df63a046
2008-09-05 Gabriel Burt <gabriel.burt@gmail.com>
* src/Dap/Banshee.Dap.Ipod/Banshee.Dap.Ipod/IpodSource.cs: Create a
HyenaSqliteCommand to reuse for adding tracks to playlists - should be
faster.
* src/Dap/Banshee.Dap.Mtp/Banshee.Dap.Mtp/MtpSource.cs: Load and save
playlists on MTP devices.
* src/Dap/Banshee.Dap.Mtp/Banshee.Dap.Mtp/MtpTrackInfo.cs: Save the
Mtp.Track's ID in the ExternalID column, cleanup cruft.
* src/Dap/Banshee.Dap/Banshee.Dap/DapLibrarySync.cs: Don't add empty
playlists to devices.
* src/Core/Banshee.Services/Banshee.Sources/SourceManager.cs: Fix
gui-thread issue.
* src/Libraries/Mtp/Mtp/Folder.cs: Override ToString for debugging.
* src/Libraries/Mtp/Mtp/Album.cs: Factor a lot of code that's useful for
the Playlist class into AbstractTrackList, subclass from it.
* src/Libraries/Mtp/Makefile.am:
* src/Libraries/Mtp/Mtp/AbstractTrackList.cs: Factored out code from Album
* src/Libraries/Mtp/Mtp/MtpDevice.cs: Add GetPlaylists () method.
* src/Libraries/Mtp/Mtp/Playlist.cs: Subclass from AbstractTrackList,
implement required methods, add to build.
2008-09-05 Gabriel Burt <gabriel.burt@gmail.com>
* src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Data/PodcastTrackListModel.cs:
......@@ -185,7 +185,6 @@ namespace Banshee.Sources
public void RemoveSource (Source source, bool recursivelyDispose)
{
Banshee.Base.ThreadAssist.AssertInMainThread ();
if(source == null || !ContainsSource (source)) {
return;
}
......@@ -211,16 +210,18 @@ namespace Banshee.Sources
}
}
if(source == active_source) {
SetActiveSource(default_source);
}
SourceEventHandler handler = SourceRemoved;
if(handler != null) {
SourceEventArgs args = new SourceEventArgs();
args.Source = source;
handler(args);
}
Banshee.Base.ThreadAssist.ProxyToMain (delegate {
if(source == active_source) {
SetActiveSource(default_source);
}
SourceEventHandler handler = SourceRemoved;
if(handler != null) {
SourceEventArgs args = new SourceEventArgs();
args.Source = source;
handler(args);
}
});
}
public void RemoveSource(Type type)
......
......@@ -211,8 +211,9 @@ namespace Banshee.Dap.Ipod
}
}
string insert_sql = @"INSERT INTO CorePlaylistEntries (PlaylistID, TrackID)
SELECT ?, TrackID FROM CoreTracks WHERE PrimarySourceID = ? AND ExternalID = ?";
Hyena.Data.Sqlite.HyenaSqliteCommand insert_cmd = new Hyena.Data.Sqlite.HyenaSqliteCommand (
@"INSERT INTO CorePlaylistEntries (PlaylistID, TrackID)
SELECT ?, TrackID FROM CoreTracks WHERE PrimarySourceID = ? AND ExternalID = ?");
foreach (IPod.Playlist playlist in ipod_device.TrackDatabase.Playlists) {
if (playlist.IsOnTheGo) { // || playlist.IsPodcast) {
Console.WriteLine ("have playlist {0} with {1} items but ignoring b/c otg or podcast", playlist.Name, playlist.Tracks.Count);
......@@ -223,7 +224,7 @@ namespace Banshee.Dap.Ipod
// We use the IPod.Track.Id here b/c we just shoved it into ExternalID above when we loaded
// the tracks, however when we sync, the Track.Id values may/will change.
foreach (IPod.Track track in playlist.Tracks) {
ServiceManager.DbConnection.Execute (insert_sql, pl_src.DbId, this.DbId, track.Id);
ServiceManager.DbConnection.Execute (insert_cmd, pl_src.DbId, this.DbId, track.Id);
}
pl_src.UpdateCounts ();
AddChildSource (pl_src);
......
......@@ -34,12 +34,14 @@ using Mono.Unix;
using Hyena;
using Hyena.Collections;
using Mtp;
using MTP = Mtp;
using Banshee.Base;
using Banshee.Dap;
using Banshee.ServiceStack;
using Banshee.Library;
using Banshee.Sources;
using Banshee.Playlist;
using Banshee.Configuration;
using Banshee.Collection;
using Banshee.Collection.Database;
......@@ -172,6 +174,21 @@ namespace Banshee.Dap.Mtp
track_map[track.TrackId] = mtp_track;
}
}
Hyena.Data.Sqlite.HyenaSqliteCommand insert_cmd = new Hyena.Data.Sqlite.HyenaSqliteCommand (
@"INSERT INTO CorePlaylistEntries (PlaylistID, TrackID)
SELECT ?, TrackID FROM CoreTracks WHERE PrimarySourceID = ? AND ExternalID = ?");
foreach (MTP.Playlist playlist in mtp_device.GetPlaylists ()) {
PlaylistSource pl_src = new PlaylistSource (playlist.Name, this.DbId);
pl_src.Save ();
// TODO a transaction would make sense here (when the threading issue is fixed)
foreach (int id in playlist.TrackIds) {
ServiceManager.DbConnection.Execute (insert_cmd, pl_src.DbId, this.DbId, id);
}
pl_src.UpdateCounts ();
AddChildSource (pl_src);
}
} catch (Exception e) {
Log.Exception (e);
}
......@@ -195,6 +212,33 @@ namespace Banshee.Dap.Mtp
}
}
public override void SyncPlaylists ()
{
lock (mtp_device) {
List<MTP.Playlist> device_playlists = new List<MTP.Playlist> (mtp_device.GetPlaylists ());
foreach (MTP.Playlist playlist in device_playlists) {
playlist.Remove ();
}
device_playlists.Clear ();
// Add playlists from Banshee to the device
foreach (Source child in Children) {
PlaylistSource from = child as PlaylistSource;
if (from != null && from.Count > 0) {
MTP.Playlist playlist = new MTP.Playlist (mtp_device, from.Name);
foreach (int track_id in ServiceManager.DbConnection.QueryEnumerable<int> (String.Format (
"SELECT CoreTracks.ExternalID FROM CoreTracks{0} WHERE {1}",
from.DatabaseTrackModel.JoinFragment, from.DatabaseTrackModel.Condition)))
{
playlist.AddTrack (track_id);
}
playlist.Save ();
}
}
}
}
public override bool CanRename {
get { return !(IsAdding || IsDeleting); }
}
......@@ -229,25 +273,25 @@ namespace Banshee.Dap.Mtp
public override long BytesUsed {
get {
long count = 0;
long count = 0;
lock (mtp_device) {
foreach (DeviceStorage s in mtp_device.GetStorage ()) {
count += (long) s.MaxCapacity - (long) s.FreeSpaceInBytes;
}
}
return count;
return count;
}
}
public override long BytesCapacity {
get {
long count = 0;
long count = 0;
lock (mtp_device) {
foreach (DeviceStorage s in mtp_device.GetStorage ()) {
count += (long) s.MaxCapacity;
}
}
return count;
return count;
}
}
......@@ -262,7 +306,6 @@ namespace Banshee.Dap.Mtp
Track mtp_track = TrackInfoToMtpTrack (track, fromUri);
bool video = (track.MediaAttributes & TrackMediaAttributes.VideoStream) != 0;
Console.WriteLine ("Sending file {0}, is video? {1}", fromUri.LocalPath, video);
lock (mtp_device) {
mtp_device.UploadTrack (fromUri.LocalPath, mtp_track, GetFolderForTrack (track), OnUploadProgress);
}
......@@ -302,12 +345,13 @@ namespace Banshee.Dap.Mtp
private Folder GetFolderForTrack (TrackInfo track)
{
if (track.HasAttribute (TrackMediaAttributes.Podcast))
if (track.HasAttribute (TrackMediaAttributes.Podcast)) {
return mtp_device.PodcastFolder;
else if (track.HasAttribute (TrackMediaAttributes.VideoStream))
} else if (track.HasAttribute (TrackMediaAttributes.VideoStream)) {
return mtp_device.VideoFolder;
else
} else {
return mtp_device.MusicFolder;
}
}
private int OnUploadProgress (ulong sent, ulong total, IntPtr data)
......@@ -330,7 +374,7 @@ namespace Banshee.Dap.Mtp
if (album_cache.ContainsKey (key)) {
Album album = album_cache[key];
album.RemoveTrack (mtp_track);
if (album.TrackCount == 0) {
if (album.Count == 0) {
album.Remove ();
album_cache.Remove (key);
}
......@@ -356,7 +400,6 @@ namespace Banshee.Dap.Mtp
}
ServiceManager.SourceManager.RemoveSource (this);
mtp_device = null;
mtp_source = null;
}
......
......@@ -53,18 +53,20 @@ namespace Banshee.Dap.Mtp
}
public MtpTrackInfo (MtpDevice device, Track file) : base()
{
{
this.file = file;
AlbumTitle = file.Album;
ExternalId = file.FileId;
AlbumTitle = file.Album;
ArtistName = file.Artist;
Duration = TimeSpan.FromMilliseconds (file.Duration);
Genre = file.Genre;
PlayCount = file.UseCount < 0 ? 0 : (int) file.UseCount;
rating = file.Rating < 0 ? 0 : (file.Rating / 20);
Rating = file.Rating < 0 ? 0 : (file.Rating / 20);
TrackTitle = file.Title;
TrackNumber = file.TrackNumber < 0 ? 0 : (int)file.TrackNumber;
Year = file.Year;
BitRate = (int)file.Bitrate;
FileSize = (long)file.FileSize;
MediaAttributes = TrackMediaAttributes.AudioStream;
......@@ -73,67 +75,35 @@ namespace Banshee.Dap.Mtp
SetAttributeIf (file.InFolder (device.MusicFolder), TrackMediaAttributes.Music);
SetAttributeIf (file.InFolder (device.VideoFolder), TrackMediaAttributes.VideoStream);
}
// TODO set VideoStream for video podcast episodes
/*Profile profile = ServiceManager.Get<MediaProfileManager> ().GetProfileForExtension (System.IO.Path.GetExtension (file.FileName));
if (profile != null) {
profile.
}*/
// This can be implemented if there's enough people requesting it
CanPlay = false;
CanSaveToDatabase = true;
//NeedSync = false;
// TODO detect if this is a video file and set the MediaAttributes appropriately?
/*Profile profile = ServiceManager.Get<MediaProfileManager> ().GetProfileForExtension (System.IO.Path.GetExtension (file.FileName));
if (profile != null) {
profile.
}*/
// Set a URI even though it's not actually accessible through normal API's.
Uri = new SafeUri (GetPathFromMtpTrack (file));
// Set a URI even though it's not actually accessible through normal API's.
Uri = new SafeUri (GetPathFromMtpTrack (file));
}
internal static void ToMtpTrack (TrackInfo track, Track f)
{
f.Album = track.AlbumTitle;
f.Artist = track.ArtistName;
f.Duration = (uint)track.Duration.TotalMilliseconds;
f.Genre = track.Genre;
f.Rating = (ushort)(track.Rating * 20);
f.Title = track.TrackTitle;
f.TrackNumber = (ushort)track.TrackNumber;
f.UseCount = (uint)track.PlayCount;
f.Album = track.AlbumTitle;
f.Artist = track.ArtistName;
f.Duration = (uint)track.Duration.TotalMilliseconds;
f.Genre = track.Genre;
f.Rating = (ushort)(track.Rating * 20);
f.Title = track.TrackTitle;
f.TrackNumber = (ushort)track.TrackNumber;
f.UseCount = (uint)track.PlayCount;
f.Year = track.Year;
}
/*public override bool Equals (object o)
{
MtpDapTrackInfo dapInfo = o as MtpDapTrackInfo;
return dapInfo == null ? false : Equals(dapInfo);
}
// FIXME: Is this enough? Does it matter if i just match metadata?
public bool Equals(MtpDapTrackInfo info)
{
return this.file.Equals(info.file);
return info == null ? false
: this.album == info.album
&& this.artist == info.artist
&& this.title == info.title
&& this.track_number == info.track_number;
//f.Bitrate = (uint)track.BitRate;
f.FileSize = (ulong)track.FileSize;
}
public override int GetHashCode ()
{
int result = 0;
result ^= (int)track_number;
if(album != null) result ^= album.GetHashCode();
if(artist != null) result ^= artist.GetHashCode();
if(title != null) result ^= title.GetHashCode();
return result;
}*/
/*protected override void WriteUpdate ()
{
OnChanged();
}*/
}
}
......@@ -211,6 +211,9 @@ namespace Banshee.Dap
// as normal playlists
IList<AbstractPlaylistSource> playlists = GetSyncPlaylists ();
foreach (AbstractPlaylistSource from in playlists) {
if (from.Count == 0) {
continue;
}
PlaylistSource to = new PlaylistSource (from.Name, sync.Dap.DbId);
to.Save ();
......@@ -222,7 +225,7 @@ namespace Banshee.Dap
from.DatabaseTrackModel.JoinFragment, from.DatabaseTrackModel.Condition),
to.DbId, sync.Dap.DbId
);
to.DatabaseTrackModel.UpdateUnfilteredAggregates ();
to.UpdateCounts ();
sync.Dap.AddChildSource (to);
}
}
......
......@@ -3,6 +3,7 @@ TARGET = library
LINK = $(REF_MTP)
SOURCES = \
Mtp/AbstractTrackList.cs \
Mtp/Album.cs \
Mtp/Error.cs \
Mtp/ErrorCode.cs \
......@@ -10,6 +11,7 @@ SOURCES = \
Mtp/FileType.cs \
Mtp/Folder.cs \
Mtp/MtpDevice.cs \
Mtp/Playlist.cs \
Mtp/Track.cs
if ENABLE_MTP
......
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Mtp
{
public abstract class AbstractTrackList
{
private bool saved;
private List<int> track_ids;
private MtpDevice device;
public abstract uint Count { get; protected set; }
public abstract string Name { get; set; }
protected abstract IntPtr TracksPtr { get; set; }
protected abstract int Create ();
protected abstract int Update ();
public bool Saved { get { return saved; } }
protected MtpDevice Device { get { return device; } }
public IList<int> TrackIds {
get { return track_ids; }
}
public AbstractTrackList (MtpDevice device, string name)
{
this.device = device;
track_ids = new List<int> ();
}
internal AbstractTrackList (MtpDevice device, IntPtr tracks, uint count)
{
this.device = device;
this.saved = true;
if (tracks != IntPtr.Zero) {
int [] vals = new int [count];
Marshal.Copy ((IntPtr)tracks, (int[])vals, 0, (int)count);
track_ids = new List<int> (vals);
} else {
track_ids = new List<int> ();
}
}
public void AddTrack (Track track)
{
AddTrack ((int)track.FileId);
}
public void AddTrack (int track_id)
{
track_ids.Add (track_id);
Count++;
}
public void RemoveTrack (Track track)
{
RemoveTrack ((int)track.FileId);
}
public void RemoveTrack (int track_id)
{
track_ids.Remove (track_id);
Count--;
}
public void ClearTracks ()
{
track_ids.Clear ();
Count = 0;
}
public virtual void Save ()
{
Count = (uint) track_ids.Count;
Console.WriteLine ("saving {0} {1} with {2} tracks", this.GetType(), this.Name, this.Count);
if (TracksPtr != IntPtr.Zero) {
Marshal.FreeHGlobal (TracksPtr);
TracksPtr = IntPtr.Zero;
}
if (Count == 0) {
TracksPtr = IntPtr.Zero;
} else {
TracksPtr = Marshal.AllocHGlobal (Marshal.SizeOf (typeof (int)) * (int)Count);
Marshal.Copy (track_ids.ToArray (), 0, TracksPtr, (int)Count);
}
if (saved) {
saved = Update () == 0;
} else {
saved = Create () == 0;
}
if (TracksPtr != IntPtr.Zero) {
Marshal.FreeHGlobal (TracksPtr);
TracksPtr = IntPtr.Zero;
}
}
}
}
......@@ -33,26 +33,15 @@ using System.Runtime.InteropServices;
namespace Mtp
{
public class Album
public class Album : AbstractTrackList
{
private AlbumStruct album;
private MtpDevice device;
private bool saved;
private List<int> track_ids;
public uint AlbumId {
get { return saved ? album.album_id : 0; }
get { return Saved ? album.album_id : 0; }
}
public List<int> TrackIds {
get { return track_ids; }
}
public bool Saved {
get { return saved; }
}
public string Name {
public override string Name {
get { return album.name; }
set {
album.name = value;
......@@ -73,119 +62,79 @@ namespace Mtp
}
}
public uint TrackCount {
public override uint Count {
get { return album.no_tracks; }
set {
protected set {
album.no_tracks = value;
}
}
public Album (MtpDevice device, string name, string artist, string genre)
protected override IntPtr TracksPtr {
get { return album.tracks; }
set { album.tracks = value; }
}
public Album (MtpDevice device, string name, string artist, string genre) : base (device, name)
{
this.device = device;
this.album = new AlbumStruct ();
this.album.tracks = IntPtr.Zero;
TracksPtr = IntPtr.Zero;
Name = name;
Artist = artist;
Genre = genre;
TrackCount = 0;
track_ids = new List<int> ();
Count = 0;
}
internal Album (MtpDevice device, AlbumStruct album)
internal Album (MtpDevice device, AlbumStruct album) : base (device, album.tracks, album.no_tracks)
{
this.device = device;
this.album = album;
this.saved = true;
if (album.tracks != IntPtr.Zero) {
int [] vals = new int [TrackCount];
Marshal.Copy ((IntPtr)album.tracks, (int[])vals, 0, (int)TrackCount);
track_ids = new List<int> (vals);
} else {
track_ids = new List<int> ();
}
}
public void Save ()
public override void Save ()
{
Save (null, 0, 0);
}
public void Save (byte [] cover_art, uint width, uint height)
{
TrackCount = (uint) track_ids.Count;
if (album.tracks != IntPtr.Zero) {
Marshal.FreeHGlobal (album.tracks);
album.tracks = IntPtr.Zero;
}
if (TrackCount == 0) {
album.tracks = IntPtr.Zero;
} else {
album.tracks = Marshal.AllocHGlobal (Marshal.SizeOf (typeof (int)) * (int)TrackCount);
Marshal.Copy (track_ids.ToArray (), 0, album.tracks, (int)TrackCount);
}
if (saved) {
saved = LIBMTP_Update_Album (device.Handle, ref album) == 0;
} else {
saved = LIBMTP_Create_New_Album (device.Handle, ref album, 0) == 0;
base.Save ();
if (Saved) {
if (cover_art == null) {
return;
}
FileSampleData cover = new FileSampleData ();
cover.data = Marshal.AllocHGlobal (Marshal.SizeOf (typeof (byte)) * cover_art.Length);
Marshal.Copy (cover_art, 0, cover.data, cover_art.Length);
cover.size = (ulong)cover_art.Length;
cover.width = width;
cover.height = height;
cover.filetype = FileType.JPEG;
if (FileSample.LIBMTP_Send_Representative_Sample (Device.Handle, AlbumId, ref cover) != 0) {
//Console.WriteLine ("failed to send representative sample file");
}
Marshal.FreeHGlobal (cover.data);
}
if (album.tracks != IntPtr.Zero) {
Marshal.FreeHGlobal (album.tracks);
album.tracks = IntPtr.Zero;
}
if (!saved)
return;
if (cover_art == null) {
return;
}
FileSampleData cover = new FileSampleData ();
cover.data = Marshal.AllocHGlobal (Marshal.SizeOf (typeof (byte)) * cover_art.Length);
Marshal.Copy (cover_art, 0, cover.data, cover_art.Length);
cover.size = (ulong)cover_art.Length;
cover.width = width;
cover.height = height;
cover.filetype = FileType.JPEG;
if (FileSample.LIBMTP_Send_Representative_Sample (device.Handle, AlbumId, ref cover) != 0) {
//Console.WriteLine ("failed to send representative sample file");
}
Marshal.FreeHGlobal (cover.data);
}
public void AddTrack (Track track)
protected override int Create ()
{
track_ids.Add ((int)track.FileId);
TrackCount++;
return LIBMTP_Create_New_Album (Device.Handle, ref album, 0);
}
public void RemoveTrack (Track track)
protected override int Update ()
{
track_ids.Remove ((int)track.FileId);
TrackCount--;
return LIBMTP_Update_Album (Device.Handle, ref album);
}
public void ClearTracks ()
{
track_ids.Clear ();
TrackCount = 0;
}
public void Remove ()
{
MtpDevice.LIBMTP_Delete_Object(device.Handle, AlbumId);
MtpDevice.LIBMTP_Delete_Object(Device.Handle, AlbumId);
}
public override string ToString ()
{
return String.Format ("Album < Id: {4}, '{0}' by '{1}', genre '{2}', tracks {3} >", Name, Artist, Genre, TrackCount, AlbumId);
return String.Format ("Album < Id: {4}, '{0}' by '{1}', genre '{2}', tracks {3} >", Name, Artist, Genre, Count, AlbumId);
}
public static Album GetById (MtpDevice device, uint id)
......
......@@ -112,6 +112,11 @@ namespace Mtp
{
MtpDevice.DeleteObject(device.Handle, FolderId);
}
public override string ToString ()
{
return String.Format ("{0} (id {1}, parent id {2})", Name, FolderId, ParentId);
}
internal static List<Folder> GetRootFolders (MtpDevice device)
{
......
......@@ -175,26 +175,38 @@ namespace Mtp
List<Track> tracks = new List<Track>();
while (ptr != IntPtr.Zero)
{
while (ptr != IntPtr.Zero) {
TrackStruct track = (TrackStruct)Marshal.PtrToStructure(ptr, typeof(TrackStruct));
Track.DestroyTrack (ptr);
tracks.Add(new Track(track, this));
tracks.Add (new Track (track, this));
ptr = track.next;
}
return tracks;
}
public List<Playlist> GetPlaylists ()