PageRenderTime 79ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/libspotify-sharp/src/PlaylistContainer.cs

https://github.com/Cyphor/libspotify-sharp
C# | 425 lines | 301 code | 97 blank | 27 comment | 34 complexity | ad06f56bce54dc53e248c2cc74338aa4 MD5 | raw file
  1. /*
  2. Copyright (c) 2009 Jonas Larsson, jonas@hallerud.se
  3. Permission is hereby granted, free of charge, to any person
  4. obtaining a copy of this software and associated documentation
  5. files (the "Software"), to deal in the Software without
  6. restriction, including without limitation the rights to use,
  7. copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the
  9. Software is furnished to do so, subject to the following
  10. conditions:
  11. The above copyright notice and this permission notice shall be
  12. included in all copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  14. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  15. OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  16. NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  17. HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  18. WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  20. OTHER DEALINGS IN THE SOFTWARE.
  21. */
  22. using System;
  23. using System.Runtime.InteropServices;
  24. using System.Collections.Generic;
  25. using System.Threading;
  26. namespace Spotify
  27. {
  28. public delegate void PlaylistContainerEventHandler(PlaylistContainer sender, PlaylistContainerEventArgs e);
  29. public class PlaylistContainer : IDisposable
  30. {
  31. #region Callbacks & static stuff
  32. private delegate void playlist_added_delegate(IntPtr containerPtr, IntPtr playlistPtr, int position, IntPtr userDataPtr);
  33. private delegate void playlist_removed_delegate(IntPtr containerPtr, IntPtr playlistPtr, int position, IntPtr userDataPtr);
  34. private delegate void playlist_moved_delegate(IntPtr containerPtr, IntPtr playlistPtr, int position, int new_position, IntPtr userDataPtr);
  35. private delegate void container_loaded_delegate(IntPtr containerPtr, IntPtr userDataPtr);
  36. private static playlist_added_delegate playlist_added = new playlist_added_delegate(PlaylistAddedCallback);
  37. private static playlist_removed_delegate playlist_removed = new playlist_removed_delegate(PlaylistRemovedCallback);
  38. private static playlist_moved_delegate playlist_moved = new playlist_moved_delegate(PlaylistMovedCallback);
  39. private static container_loaded_delegate container_loaded = new container_loaded_delegate(ContainerLoadedCallback);
  40. private static Dictionary<IntPtr, PlaylistContainer> containers = new Dictionary<IntPtr, PlaylistContainer>();
  41. private static libspotify.sp_playlistcontainer_callbacks callbacks;
  42. private static IntPtr callbacksPtr = IntPtr.Zero;
  43. #endregion
  44. #region Events
  45. public event PlaylistContainerEventHandler OnPlaylistAdded;
  46. public event PlaylistContainerEventHandler OnPlaylistRemoved;
  47. public event PlaylistContainerEventHandler OnPlaylistMoved;
  48. public event PlaylistContainerEventHandler OnContainerLoaded;
  49. #endregion
  50. #region Private declarations
  51. private IntPtr containerPtr = IntPtr.Zero;
  52. private List<Playlist> playlists = new List<Playlist>();
  53. private Dictionary<IntPtr, int> playlistIndexByPtr = new Dictionary<IntPtr, int>();
  54. private Session owningSession = null;
  55. #endregion
  56. #region Ctor
  57. internal PlaylistContainer(IntPtr containerPtr, Session owningSession)
  58. {
  59. if(containerPtr == IntPtr.Zero)
  60. throw new ArgumentException("containerPtr can not be zero");
  61. lock(libspotify.Mutex)
  62. {
  63. if(containers.ContainsKey(containerPtr))
  64. throw new Exception("libspotify-sharp internal error, creating playlist container for the second time");
  65. libspotify.sp_playlistcontainer_add_callbacks(containerPtr, callbacksPtr, IntPtr.Zero);
  66. this.owningSession = owningSession;
  67. this.containerPtr = containerPtr;
  68. containers.Add(containerPtr, this);
  69. for(int i = 0; i < PlaylistCount; i++)
  70. {
  71. IntPtr playlistPtr = IntPtr.Zero;
  72. playlistPtr = libspotify.sp_playlistcontainer_playlist(containerPtr, i);
  73. Playlist p = new Playlist(playlistPtr, owningSession);
  74. playlists.Add(p);
  75. playlistIndexByPtr.Add(playlistPtr, i);
  76. }
  77. }
  78. }
  79. #endregion
  80. #region Properties
  81. public int PlaylistCount
  82. {
  83. get
  84. {
  85. CheckDisposed(true);
  86. lock(libspotify.Mutex)
  87. return libspotify.sp_playlistcontainer_num_playlists(containerPtr);
  88. }
  89. }
  90. public Playlist[] CurrentLists
  91. {
  92. get
  93. {
  94. lock(libspotify.Mutex)
  95. {
  96. CheckDisposed(true);
  97. return playlists.ToArray();
  98. }
  99. }
  100. }
  101. #endregion
  102. #region Callback & static methods
  103. static PlaylistContainer()
  104. {
  105. lock(libspotify.Mutex)
  106. {
  107. callbacks = new libspotify.sp_playlistcontainer_callbacks();
  108. callbacks.playlist_added = Marshal.GetFunctionPointerForDelegate(playlist_added);
  109. callbacks.playlist_moved = Marshal.GetFunctionPointerForDelegate(playlist_moved);
  110. callbacks.playlist_removed = Marshal.GetFunctionPointerForDelegate(playlist_removed);
  111. callbacks.container_loaded = Marshal.GetFunctionPointerForDelegate(container_loaded);
  112. int size = Marshal.SizeOf(callbacks);
  113. callbacksPtr = Marshal.AllocHGlobal(size);
  114. Marshal.StructureToPtr(callbacks, callbacksPtr, true);
  115. }
  116. }
  117. private static PlaylistContainer GetContainer(IntPtr containerPtr)
  118. {
  119. PlaylistContainer pc = null;
  120. if(containers.TryGetValue(containerPtr, out pc))
  121. return pc;
  122. else
  123. return null;
  124. }
  125. private static void PlaylistAddedCallback(IntPtr containerPtr, IntPtr playlistPtr, int position, IntPtr userDataPtr)
  126. {
  127. PlaylistContainer pc = GetContainer(containerPtr);
  128. if(pc != null)
  129. {
  130. Playlist pl;
  131. Playlist[] currentLists;
  132. lock(libspotify.Mutex)
  133. {
  134. pl = new Playlist(playlistPtr, pc.owningSession);
  135. pc.playlists.Insert(position, pl);
  136. pc.playlistIndexByPtr[playlistPtr] = position;
  137. currentLists = pc.CurrentLists;
  138. }
  139. pc.owningSession.EnqueueEventWorkItem(new EventWorkItem(pc.OnPlaylistAdded,
  140. new object[] {pc, new PlaylistContainerEventArgs(pl, position, -1, currentLists) }));
  141. }
  142. }
  143. private static void PlaylistRemovedCallback(IntPtr containerPtr, IntPtr playlistPtr, int position, IntPtr userDataPtr)
  144. {
  145. PlaylistContainer pc = GetContainer(containerPtr);
  146. if(pc != null)
  147. {
  148. Playlist pl;
  149. Playlist[] currentLists;
  150. lock(libspotify.Mutex)
  151. {
  152. if(!pc.playlists[position].playlistPtr.Equals(playlistPtr))
  153. throw new Exception("libspotify-sharp internal error, playlist position and pointer is inconsistent on remove");
  154. pl = pc.playlists[position];
  155. pc.playlists.RemoveAt(position);
  156. pc.playlistIndexByPtr.Remove(playlistPtr);
  157. pl.Dispose();
  158. currentLists = pc.CurrentLists;
  159. }
  160. pc.owningSession.EnqueueEventWorkItem(new EventWorkItem(pc.OnPlaylistRemoved,
  161. new object[] {pc, new PlaylistContainerEventArgs(pl, position, -1, currentLists) }));
  162. }
  163. }
  164. private static void PlaylistMovedCallback(IntPtr containerPtr, IntPtr playlistPtr, int position, int new_position, IntPtr userDataPtr)
  165. {
  166. PlaylistContainer pc = GetContainer(containerPtr);
  167. if(pc != null)
  168. {
  169. Playlist pl;
  170. Playlist[] currentLists;
  171. lock(libspotify.Mutex)
  172. {
  173. // when moving a playlist "up", the indices are re-arranged so need to adjust the new_position
  174. // when moving a playlist "down" all is good...
  175. if(position < new_position){
  176. new_position--;
  177. }
  178. pl = pc.playlists[position];
  179. pc.playlists.RemoveAt(position);
  180. pc.playlists.Insert(new_position, pl);
  181. currentLists = pc.CurrentLists;
  182. }
  183. pc.owningSession.EnqueueEventWorkItem(new EventWorkItem(pc.OnPlaylistMoved,
  184. new object[] {pc, new PlaylistContainerEventArgs(pl, position, new_position, currentLists) }));
  185. }
  186. }
  187. private static void ContainerLoadedCallback(IntPtr containerPtr, IntPtr userDataPtr)
  188. {
  189. PlaylistContainer pc = GetContainer(containerPtr);
  190. if(pc != null)
  191. {
  192. Playlist[] currentLists;
  193. lock(libspotify.Mutex)
  194. {
  195. currentLists = pc.CurrentLists;
  196. }
  197. // It's more practical to have this event on Session IMHO. Let's have both.
  198. pc.owningSession.EnqueueEventWorkItem(new EventWorkItem(pc.OnContainerLoaded,
  199. new object[] {pc, new PlaylistContainerEventArgs(null, -1, -1, currentLists) }));
  200. pc.owningSession.PlaylistContainerLoaded();
  201. }
  202. }
  203. #endregion
  204. #region Private methods
  205. #endregion
  206. #region Public methods
  207. public Playlist AddNewPlaylist(string name)
  208. {
  209. CheckDisposed(true);
  210. Playlist result = null;
  211. if(string.IsNullOrEmpty(name) || string.IsNullOrEmpty(name.Trim()) || name.Length >= 256)
  212. throw new ArgumentException("invalid playlist name");
  213. lock(libspotify.Mutex)
  214. {
  215. IntPtr playlistPtr = libspotify.sp_playlistcontainer_add_new_playlist(containerPtr, name);
  216. int index;
  217. if(playlistIndexByPtr.TryGetValue(playlistPtr, out index))
  218. result = playlists[index];
  219. else
  220. result = null;
  221. }
  222. return result;
  223. }
  224. public Playlist AddPlaylist(Link link)
  225. {
  226. CheckDisposed(true);
  227. Playlist result = null;
  228. lock(libspotify.Mutex)
  229. {
  230. IntPtr playlistPtr = libspotify.sp_playlistcontainer_add_playlist(containerPtr, link.linkPtr);
  231. int index;
  232. if(playlistIndexByPtr.TryGetValue(playlistPtr, out index))
  233. result = playlists[index];
  234. else
  235. result = null;
  236. }
  237. return result;
  238. }
  239. public sp_error RemovePlaylist(Playlist playlist)
  240. {
  241. CheckDisposed(true);
  242. sp_error result = sp_error.INVALID_INDATA;
  243. int index = -1;
  244. lock(libspotify.Mutex)
  245. {
  246. if(playlistIndexByPtr.TryGetValue(playlist.playlistPtr, out index))
  247. {
  248. if(!playlist.playlistPtr.Equals(playlists[index].playlistPtr))
  249. throw new Exception("libspotify-sharp internal error, playlist position and pointer is inconsistent on remove");
  250. libspotify.sp_playlistcontainer_remove_playlist(containerPtr, index);
  251. }
  252. }
  253. return result;
  254. }
  255. public sp_error MovePlaylist(Playlist playlist, int newPosition)
  256. {
  257. CheckDisposed(true);
  258. sp_error result = sp_error.INVALID_INDATA;
  259. int index = -1;
  260. lock(libspotify.Mutex)
  261. {
  262. if(playlistIndexByPtr.TryGetValue(playlist.playlistPtr, out index))
  263. {
  264. if(!playlist.playlistPtr.Equals(playlists[index].playlistPtr))
  265. throw new Exception("libspotify-sharp internal error, playlist position and pointer is inconsistent on move");
  266. result = libspotify.sp_playlistcontainer_move_playlist(containerPtr, index, newPosition);
  267. }
  268. }
  269. return result;
  270. }
  271. #endregion
  272. #region Clean up
  273. ~PlaylistContainer()
  274. {
  275. Dispose(false);
  276. }
  277. private void Dispose(bool disposing)
  278. {
  279. try
  280. {
  281. if (disposing)
  282. {
  283. OnPlaylistAdded = null;
  284. OnPlaylistMoved = null;
  285. OnPlaylistRemoved = null;
  286. }
  287. if(containerPtr == IntPtr.Zero)
  288. return;
  289. containers.Remove(containerPtr);
  290. if(containerPtr != IntPtr.Zero)
  291. {
  292. libspotify.sp_playlistcontainer_remove_callbacks(containerPtr, callbacksPtr, IntPtr.Zero);
  293. containerPtr = IntPtr.Zero;
  294. foreach(Playlist p in playlists.ToArray())
  295. {
  296. p.Dispose();
  297. }
  298. playlists.Clear();
  299. playlistIndexByPtr.Clear();
  300. }
  301. }
  302. catch
  303. {
  304. }
  305. }
  306. public void Dispose()
  307. {
  308. if(containerPtr == IntPtr.Zero)
  309. return;
  310. Dispose(true);
  311. GC.SuppressFinalize(this);
  312. }
  313. private bool CheckDisposed(bool throwOnDisposed)
  314. {
  315. lock(libspotify.Mutex)
  316. {
  317. bool result = containerPtr == IntPtr.Zero;
  318. if(result && throwOnDisposed)
  319. throw new ObjectDisposedException("PlaylistContainer");
  320. return result;
  321. }
  322. }
  323. #endregion
  324. }
  325. }