context: //  **************************************************************************
  //    MtpDevice.cs
  //
  //    Copyright (C) 2006-2007 Alan McGovern
  //    Authors:
  //    Alan McGovern (alan.mcgovern@gmail.com)
  //  ***************************************************************************
  
  //  THIS FILE IS LICENSED UNDER THE MIT LICENSE AS OUTLINED IMMEDIATELY BELOW:
  //
  //  Permission is hereby granted, free of charge, to any person obtaining a
  //  copy of this software and associated documentation files (the "Software"),
  //  to deal in the Software without restriction, including without limitation
  //  the rights to use, copy, modify, merge, publish, distribute, sublicense,
  //  and/or sell copies of the Software, and to permit persons to whom the
  //  Software is furnished to do so, subject to the following conditions:
  //
  //  The above copyright notice and this permission notice shall be included in
  //  all copies or substantial portions of the Software.
  //
  //  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  //  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  //  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  //  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  //  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  //  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  //  DEALINGS IN THE SOFTWARE.
  
  
  using System;
  using System.Collections.Generic;
  using System.Runtime.InteropServices;
  
  namespace Mtp
  {
  	public delegate int ProgressFunction(ulong sent, ulong total, IntPtr data);
  
  	public class MtpDevice : IDisposable
  	{
  		internal static IntPtr Offset(IntPtr ptr, int offset)
  		{
  			if (IntPtr.Size == 8)
  			{
  				return (IntPtr)(ptr.ToInt64() + offset);
  			}
  			else
  			{
  				return (IntPtr)(ptr.ToInt32() + offset);
  			}
  		}
  
  		internal MtpDeviceHandle Handle;
  		private MtpDeviceStruct device;
  		private string name;
  		private Folder albumFolder;
  		private Folder musicFolder;
  		private Folder organizerFolder;
  		private Folder pictureFolder;
  		private Folder playlistFolder;
  		private Folder podcastFolder;
  		private Folder textFolder;
  		private Folder videoFolder;
  
  		static MtpDevice()
  		{
  			LIBMTP_Init();
  		}
  
  		public int BatteryLevel
  		{
  			get
  			{
  				ushort level, maxLevel;
  				GetBatteryLevel(Handle, out maxLevel, out level);
  				return (int)((level * 100.0) / maxLevel);
  			}
  		}
  
  		public string SerialNumber
  		{
  			get { return GetSerialnumber(Handle); }
  		}
  
  		public string Version
  		{
  			get { return GetDeviceversion(Handle); }
  		}
  
  		public string ModelName
  		{
  			get; private set;
  		}
  
  		public string Name
  		{
  			get { return name; }
  			set
  			{
  				if (SetFriendlyName(Handle, value))
  				{
  					name = value;
  				}
  			}
  		}
  
  		public Folder AlbumFolder
  		{
  			get { return albumFolder; }
  		}
  
  		public Folder MusicFolder
  		{
  			get { return musicFolder; }
  		}
  
  		public Folder OrganizerFolder
  		{
  			get { return organizerFolder; }
  		}
  
  		public Folder PictureFolder
  		{
  			get { return pictureFolder; }
  		}
  
  		public Folder PlaylistFolder
  		{
  			get { return playlistFolder; }
  		}
  
  		public Folder PodcastFolder
  		{
  			get { return podcastFolder; }
  		}
  
  		public Folder TextFolder
  		{
  			get { return textFolder; }
  		}
  
  		public Folder VideoFolder
  		{
  			get { return videoFolder; }
  		}
  
  		internal MtpDevice(MtpDeviceHandle handle, MtpDeviceStruct device)
  		{
  			this.device = device;
  			this.Handle = handle;
  			this.name = GetFriendlyName(Handle);
  			this.ModelName = GetModelName(Handle);
  			SetDefaultFolders();
  		}
  
  		internal MtpDevice(IntPtr handle, bool ownsHandle, MtpDeviceStruct device)
  			: this(new MtpDeviceHandle(handle, ownsHandle), device)
  		{
  
  		}
  
  		/// <summary>
  		/// This function scans the top level directories and stores the relevant ones so they are readily
  		/// accessible
  		/// </summary>
  		private void SetDefaultFolders()
  		{
  			List<Folder> folders = GetRootFolders();
  
  			foreach (Folder f in folders)
  			{
  				if (f.FolderId == this.device.default_album_folder)
  					albumFolder = f;
  				else if (f.FolderId == device.default_music_folder)
  				{
  					musicFolder = f;
  				}
  				else if (f.FolderId == device.default_organizer_folder)
  					organizerFolder = f;
  				else if (f.FolderId == device.default_picture_folder)
  					pictureFolder = f;
  				else if (f.FolderId == device.default_playlist_folder)
  					playlistFolder = f;
  				else if (f.FolderId == device.default_text_folder)
  					textFolder = f;
  				else if (f.FolderId == device.default_video_folder)
  					videoFolder = f;
  				else if (f.FolderId == device.default_zencast_folder)
  					podcastFolder = f;
  			}
  
  			// Fix for devices that don't have an explicit playlist folder (see BGO #590342 and #733883)
  			if (playlistFolder == null && musicFolder != null)
  			{
  				playlistFolder = musicFolder;
  			}
  		}
  
  		public void Dispose()
  		{
  			if (!Handle.IsClosed)
  				Handle.Close();
  		}
  
  		public List<Folder> GetRootFolders()
  		{
  			return Folder.GetRootFolders(this);
  		}
  
  		public List<Track> GetAllTracks()
  		{
  			return GetAllTracks(null);
  		}
  
  		public List<Track> GetAllTracks(ProgressFunction callback)
  		{
  			IntPtr ptr = Track.GetTrackListing(Handle, callback, IntPtr.Zero);
  
  			List<Track> tracks = new List<Track>();
  
  			while (ptr != IntPtr.Zero)
  			{
  				// Destroy the struct after we use it to avoid potential referencing of freed memory.
  				TrackStruct track = (TrackStruct)Marshal.PtrToStructure(ptr, typeof(TrackStruct));
  				tracks.Add(new Track(track, this));
  				Track.DestroyTrack(ptr);
  				ptr = track.next;
  			}
  
  			return tracks;
  		}
  
  		public List<Playlist> GetPlaylists()
  		{
  			return Playlist.GetPlaylists(this);
  		}
  
  		public List<Album> GetAlbums()
  		{
  			return Album.GetAlbums(this);
  		}
  
  		public List<DeviceStorage> GetStorage()
  		{
  			List<DeviceStorage> storages = new List<DeviceStorage>();
  			IntPtr ptr = device.storage;
  			while (ptr != IntPtr.Zero)
  			{
  				DeviceStorage storage = (DeviceStorage)Marshal.PtrToStructure(ptr, typeof(DeviceStorage));
  				storages.Add(storage);
  				ptr = storage.Next;
  			}
  			return storages;
  		}
  
  		public void Remove(Track track)
  		{
  			DeleteObject(Handle, track.FileId);
  		}
  
  		public void UploadTrack(string path, Track track, Folder folder)
  		{
  			UploadTrack(path, track, folder, null);
  		}
  
  		public void UploadTrack(string path, Track track, Folder folder, ProgressFunction callback)
  		{
  			if (string.IsNullOrEmpty(path))
  				throw new ArgumentNullException("path");
  			if (track == null)
  				throw new ArgumentNullException("track");
  
  			folder = folder ?? MusicFolder;
  			if (folder != null)
  			{
  				track.trackStruct.parent_id = folder.FolderId;
  			}
  
  			// We send the trackstruct by ref so that when the file_id gets filled in, our copy is updated
  			Track.SendTrack(Handle, path, ref track.trackStruct, callback, IntPtr.Zero);
  			// LibMtp.GetStorage (Handle, 0);
  		}
  
  		public FileType[] GetFileTypes()
  		{
  			Int16[] ints = GetFileTypes(Handle);
  			FileType[] file_types = new FileType[ints.Length];
  			for (int i = 0; i < ints.Length; i++)
  			{
  				file_types[i] = (FileType)ints[i];
  			}
  
  			return file_types;
  		}
  
  		public static MtpDevice Connect(RawMtpDevice rawDevice)
  		{
  			var raw = rawDevice.RawDevice;
  			IntPtr device = LIBMTP_Open_Raw_Device(raw);
  			if (device == IntPtr.Zero)
  				return null;
  			return new MtpDevice(new MtpDeviceHandle(device, true), (MtpDeviceStruct)Marshal.PtrToStructure(device, typeof(MtpDeviceStruct)));
  		}
  
  		public static List<RawMtpDevice> Detect()
  		{
  			int count = 0;
  			IntPtr ptr = IntPtr.Zero;
  			LIBMTP_Detect_Raw_Devices(ref ptr, ref count);
  
  			List<RawMtpDevice> devices = new List<RawMtpDevice>();
  			for (int i = 0; i < count; i++)
  			{
  				IntPtr offset = Offset(ptr, i * Marshal.SizeOf(typeof(RawDeviceStruct)));
  				RawDeviceStruct d = (RawDeviceStruct)Marshal.PtrToStructure(offset, typeof(RawDeviceStruct));
  				devices.Add(new RawMtpDevice(d));
  			}
  
  			return devices;
  		}
  
  		internal static void ClearErrorStack(MtpDeviceHandle handle)
  		{
  			LIBMTP_Clear_Errorstack(handle);
  		}
  
  		internal static void DeleteObject(MtpDeviceHandle handle, uint object_id)
  		{
  			if (LIBMTP_Delete_Object(handle, object_id) != 0)
  			{
  				LibMtpException.CheckErrorStack(handle);
  				throw new LibMtpException(ErrorCode.General, "Could not delete the track");
  			}
  		}
  
  		internal static void GetBatteryLevel(MtpDeviceHandle handle, out ushort maxLevel, out ushort currentLevel)
  		{
  			int result = LIBMTP_Get_Batterylevel(handle, out maxLevel, out currentLevel);
  			if (result != 0)
  			{
  				LibMtpException.CheckErrorStack(handle);
  				throw new LibMtpException(ErrorCode.General, "Could not retrieve battery stats");
  			}
  		}
  
  		internal static void GetConnectedDevices(out IntPtr list)
  		{
  			Error.CheckError(LIBMTP_Get_Connected_Devices(out list));
  		}
  
  		internal static IntPtr GetErrorStack(MtpDeviceHandle handle)
  		{
  			return LIBMTP_Get_Errorstack(handle);
  		}
  
  		internal static string GetDeviceversion(MtpDeviceHandle handle)
  		{
  			IntPtr ptr = LIBMTP_Get_Deviceversion(handle);
  			if (ptr == IntPtr.Zero)
  				return null;
  
  			return StringFromIntPtr(ptr);
  		}
  
  
  		internal static string GetFriendlyName(MtpDeviceHandle handle)
  		{
  			IntPtr ptr = LIBMTP_Get_Friendlyname(handle);
  			if (ptr == IntPtr.Zero)
  				return null;
  
  			return StringFromIntPtr(ptr);
  		}
  
  		internal static bool SetFriendlyName(MtpDeviceHandle handle, string name)
  		{
  
gt: 			bool success = LIBMTP_Set_Friendlyname(handle, name) == 0;