Index: ConfigurationForm.cs =================================================================== --- ConfigurationForm.cs (Revision 163) +++ ConfigurationForm.cs (Arbeitskopie) @@ -171,6 +171,8 @@ textDeviceName.Text = device.Name; textMediaRoot.Text = device.MediaRoot; textRecognizePattern.Text = device.RecognizePattern; + textExportPlaylist.Text = device.ExportPlaylist.File; + textExportPlaylist.Tag = (long)device.ExportPlaylist.Type; foreach (SyncPattern pattern in deviceConfiguration.SyncPatterns) { @@ -224,6 +226,7 @@ textMediaRoot.Enabled = true; textRecognizePattern.Enabled = true; comboAssociatePlaylist.Enabled = true; + textExportPlaylist.Enabled = true; if (!forNewDevice) buttonDelete.Enabled = true; @@ -231,6 +234,8 @@ buttonBrowseMediaRoot.Enabled = true; buttonCreateUniqueFile.Enabled = true; comboAssociatePlaylist.Enabled = true; + buttonBrowseExportPlaylist.Enabled = true; + buttonClearExportPlaylist.Enabled = true; } @@ -244,12 +249,15 @@ textMediaRoot.Enabled = false; textRecognizePattern.Enabled = false; comboAssociatePlaylist.Enabled = false; + textExportPlaylist.Enabled = false; buttonBrowseMediaRoot.Enabled = false; buttonCreateUniqueFile.Enabled = false; buttonDelete.Enabled = false; buttonSave.Enabled = false; comboAssociatePlaylist.Enabled = false; + buttonBrowseExportPlaylist.Enabled = false; + buttonClearExportPlaylist.Enabled = false; } private void comboSyncPatterns_SelectedIndexChanged(object sender, EventArgs e) @@ -294,6 +302,8 @@ textRecognizePattern.Text = ""; comboSyncPatterns.SelectedIndex = 0; comboAssociatePlaylist.SelectedIndex = 0; + textExportPlaylist.Text = ""; + textExportPlaylist.Tag = ""; } /// @@ -342,7 +352,13 @@ string mediaroot = textMediaRoot.Text; string recognizePattern = textRecognizePattern.Text; string associatedPlaylist = (string)comboAssociatePlaylist.SelectedItem; - + string exportplaylist = textExportPlaylist.Text; + long exportplaylisttype = 0; + if (textExportPlaylist.Tag.ToString().Length > 0) + { + exportplaylisttype = (long)textExportPlaylist.Tag; + } + if (deviceName.Length == 0) { MessageBox.Show(this, "Please enter a name for the device.", "Missing information", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); @@ -355,17 +371,31 @@ return; } + /*if (mediaroot.Length == 0) + { + MessageBox.Show(this, "Please enter a media folder for the device.", "Missing information", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + return; + }*/ + if (recognizePattern.Length == 0) { MessageBox.Show(this, "Please enter a recognize pattern for the device.", "Missing information", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; } + /*if (exportplaylist.Length == 0) + { + MessageBox.Show(this, "Please enter a export playlist for the device.", "Missing information", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + return; + }*/ + Device newDevice = new Device(); newDevice.Name = deviceName; newDevice.MediaRoot = mediaroot; newDevice.RecognizePattern = recognizePattern; newDevice.Playlist = (associatedPlaylist == "Use device name..." ? "" : associatedPlaylist); + newDevice.ExportPlaylist.File = exportplaylist; + newDevice.ExportPlaylist.Type = (ExportPlaylist.PlaylistTypes)exportplaylisttype; foreach (SyncPattern sp in deviceConfiguration.SyncPatterns) { if (sp.Name != syncPattern) @@ -402,6 +432,7 @@ device.RecognizePattern = newDevice.RecognizePattern; device.SyncPattern = newDevice.SyncPattern; device.Playlist = newDevice.Playlist; + device.ExportPlaylist = newDevice.ExportPlaylist; break; } @@ -604,6 +635,55 @@ configurationChanged = true; buttonOK.Enabled = true; } + private void buttonBrowseExportPlaylist_Click(object sender, EventArgs e) + { + //FolderBrowserDialog dlg = new FolderBrowserDialog(); + SaveFileDialog dlg = new SaveFileDialog(); + dlg.AddExtension = true; + dlg.Filter = "Basic M3U (*.m3u)|*.m3u|Extended M3U (*.m3u)|*.m3u|Windows Media (*.wpl)|*.wpl|Zune Playlist (*.zpl)|*.zpl"; + dlg.FilterIndex = 0; + //dlg.DefaultExt = "m3u"; + dlg.FileName = Path.GetFileName(textExportPlaylist.Text); + //dlg.InitialDirectory = Path.GetDirectoryName(textExportPlaylist.Text); + + if (dlg.ShowDialog() == DialogResult.Cancel) + return; + + //This check is to make sure that the application do not throw an exception if a path + //of length less than 3 is selected. Normally this do not occur, but there has been + //reportet incidents where unsupported, special devices have given an empty path in return... + //See bug report 1443246. + // https://sourceforge.net/tracker/index.php?func=detail&aid=1443246&group_id=149133&atid=773786 + if (dlg.FileName.Length >= 3) + { + textExportPlaylist.Text = dlg.FileName.Substring(3, dlg.FileName.Length - 3); + + switch (dlg.FilterIndex) + { + case 1: + textExportPlaylist.Tag = (long)ExportPlaylist.PlaylistTypes.M3U; + break; + case 2: + textExportPlaylist.Tag = (long)ExportPlaylist.PlaylistTypes.EXT; + break; + case 3: + textExportPlaylist.Tag = (long)ExportPlaylist.PlaylistTypes.WPL; + break; + case 4: + textExportPlaylist.Tag = (long)ExportPlaylist.PlaylistTypes.ZPL; + break; + default: + textExportPlaylist.Tag = (long)ExportPlaylist.PlaylistTypes.None; + break; + } + } + } + + private void buttonClearExportPlaylist_Click(object sender, EventArgs e) + { + textExportPlaylist.Text = ""; + textExportPlaylist.Tag = ""; + } } } \ No newline at end of file Index: ConfigurationForm.Designer.cs =================================================================== --- ConfigurationForm.Designer.cs (Revision 163) +++ ConfigurationForm.Designer.cs (Arbeitskopie) @@ -40,10 +40,14 @@ this.buttonBrowseMediaRoot = new System.Windows.Forms.Button(); this.comboSyncPatterns = new System.Windows.Forms.ComboBox(); this.buttonCreateUniqueFile = new System.Windows.Forms.Button(); + this.buttonBrowseExportPlaylist = new System.Windows.Forms.Button(); + this.buttonClearExportPlaylist = new System.Windows.Forms.Button(); this.textRecognizePattern = new System.Windows.Forms.TextBox(); this.textMediaRoot = new System.Windows.Forms.TextBox(); this.groupBox2 = new System.Windows.Forms.GroupBox(); this.groupDeviceInformation = new System.Windows.Forms.GroupBox(); + this.textExportPlaylist = new System.Windows.Forms.TextBox(); + this.label6 = new System.Windows.Forms.Label(); this.comboAssociatePlaylist = new System.Windows.Forms.ComboBox(); this.label5 = new System.Windows.Forms.Label(); this.buttonDelete = new System.Windows.Forms.Button(); @@ -71,9 +75,9 @@ this.groupBox1.Controls.Add(this.checkAutocloseSyncWindow); this.groupBox1.Controls.Add(this.checkUseListFolder); this.groupBox1.Controls.Add(this.checkNotifications); - this.groupBox1.Location = new System.Drawing.Point(2, 2); + this.groupBox1.Location = new System.Drawing.Point(2, 1); this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(512, 96); + this.groupBox1.Size = new System.Drawing.Size(512, 97); this.groupBox1.TabIndex = 0; this.groupBox1.TabStop = false; this.groupBox1.Text = "Agent"; @@ -179,6 +183,30 @@ this.buttonCreateUniqueFile.UseVisualStyleBackColor = true; this.buttonCreateUniqueFile.Click += new System.EventHandler(this.buttonCreateUniqueFile_Click); // + // buttonBrowseExportPlaylist + // + this.buttonBrowseExportPlaylist.Enabled = false; + this.buttonBrowseExportPlaylist.Location = new System.Drawing.Point(358, 140); + this.buttonBrowseExportPlaylist.Name = "buttonBrowseExportPlaylist"; + this.buttonBrowseExportPlaylist.Size = new System.Drawing.Size(65, 23); + this.buttonBrowseExportPlaylist.TabIndex = 17; + this.buttonBrowseExportPlaylist.Text = "Choose"; + this.toolTip.SetToolTip(this.buttonBrowseExportPlaylist, "Browse for the folder if the device is already connected."); + this.buttonBrowseExportPlaylist.UseVisualStyleBackColor = true; + this.buttonBrowseExportPlaylist.Click += new System.EventHandler(this.buttonBrowseExportPlaylist_Click); + // + // buttonClearExportPlaylist + // + this.buttonClearExportPlaylist.Enabled = false; + this.buttonClearExportPlaylist.Location = new System.Drawing.Point(428, 140); + this.buttonClearExportPlaylist.Name = "buttonClearExportPlaylist"; + this.buttonClearExportPlaylist.Size = new System.Drawing.Size(65, 23); + this.buttonClearExportPlaylist.TabIndex = 18; + this.buttonClearExportPlaylist.Text = "Clear"; + this.toolTip.SetToolTip(this.buttonClearExportPlaylist, "Browse for the folder if the device is already connected."); + this.buttonClearExportPlaylist.UseVisualStyleBackColor = true; + this.buttonClearExportPlaylist.Click += new System.EventHandler(this.buttonClearExportPlaylist_Click); + // // textRecognizePattern // this.textRecognizePattern.Enabled = false; @@ -209,13 +237,17 @@ this.groupBox2.Controls.Add(this.listDevices); this.groupBox2.Location = new System.Drawing.Point(2, 104); this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(512, 357); + this.groupBox2.Size = new System.Drawing.Size(512, 387); this.groupBox2.TabIndex = 1; this.groupBox2.TabStop = false; this.groupBox2.Text = "Devices"; // // groupDeviceInformation // + this.groupDeviceInformation.Controls.Add(this.buttonClearExportPlaylist); + this.groupDeviceInformation.Controls.Add(this.buttonBrowseExportPlaylist); + this.groupDeviceInformation.Controls.Add(this.textExportPlaylist); + this.groupDeviceInformation.Controls.Add(this.label6); this.groupDeviceInformation.Controls.Add(this.buttonCreateUniqueFile); this.groupDeviceInformation.Controls.Add(this.comboAssociatePlaylist); this.groupDeviceInformation.Controls.Add(this.label5); @@ -233,11 +265,28 @@ this.groupDeviceInformation.Controls.Add(this.label1); this.groupDeviceInformation.Location = new System.Drawing.Point(6, 182); this.groupDeviceInformation.Name = "groupDeviceInformation"; - this.groupDeviceInformation.Size = new System.Drawing.Size(500, 167); + this.groupDeviceInformation.Size = new System.Drawing.Size(500, 198); this.groupDeviceInformation.TabIndex = 1; this.groupDeviceInformation.TabStop = false; this.groupDeviceInformation.Text = "Device information"; // + // textExportPlaylist + // + this.textExportPlaylist.Enabled = false; + this.textExportPlaylist.Location = new System.Drawing.Point(140, 142); + this.textExportPlaylist.Name = "textExportPlaylist"; + this.textExportPlaylist.Size = new System.Drawing.Size(212, 20); + this.textExportPlaylist.TabIndex = 16; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(6, 146); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(42, 13); + this.label6.TabIndex = 15; + this.label6.Text = "Playlist:"; + // // comboAssociatePlaylist // this.comboAssociatePlaylist.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; @@ -262,7 +311,7 @@ // buttonDelete // this.buttonDelete.Enabled = false; - this.buttonDelete.Location = new System.Drawing.Point(290, 138); + this.buttonDelete.Location = new System.Drawing.Point(290, 168); this.buttonDelete.Name = "buttonDelete"; this.buttonDelete.Size = new System.Drawing.Size(84, 23); this.buttonDelete.TabIndex = 11; @@ -273,7 +322,7 @@ // buttonSave // this.buttonSave.Enabled = false; - this.buttonSave.Location = new System.Drawing.Point(215, 138); + this.buttonSave.Location = new System.Drawing.Point(215, 168); this.buttonSave.Name = "buttonSave"; this.buttonSave.Size = new System.Drawing.Size(75, 23); this.buttonSave.TabIndex = 10; @@ -283,7 +332,7 @@ // // buttonNew // - this.buttonNew.Location = new System.Drawing.Point(140, 138); + this.buttonNew.Location = new System.Drawing.Point(140, 168); this.buttonNew.Name = "buttonNew"; this.buttonNew.Size = new System.Drawing.Size(75, 23); this.buttonNew.TabIndex = 9; @@ -360,7 +409,7 @@ // buttonCancel // this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.buttonCancel.Location = new System.Drawing.Point(439, 465); + this.buttonCancel.Location = new System.Drawing.Point(439, 497); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(75, 23); this.buttonCancel.TabIndex = 2; @@ -369,7 +418,7 @@ // buttonOK // this.buttonOK.Enabled = false; - this.buttonOK.Location = new System.Drawing.Point(2, 465); + this.buttonOK.Location = new System.Drawing.Point(2, 497); this.buttonOK.Name = "buttonOK"; this.buttonOK.Size = new System.Drawing.Size(75, 23); this.buttonOK.TabIndex = 3; @@ -381,7 +430,7 @@ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.buttonCancel; - this.ClientSize = new System.Drawing.Size(518, 492); + this.ClientSize = new System.Drawing.Size(518, 525); this.Controls.Add(this.buttonOK); this.Controls.Add(this.buttonCancel); this.Controls.Add(this.groupBox2); @@ -435,5 +484,10 @@ private System.Windows.Forms.Button buttonCreateUniqueFile; private System.Windows.Forms.CheckBox checkWarnOnSystemDrives; private System.Windows.Forms.CheckBox checkConfirmMusicLocation; + + private System.Windows.Forms.Button buttonBrowseExportPlaylist; + private System.Windows.Forms.TextBox textExportPlaylist; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Button buttonClearExportPlaylist; } } \ No newline at end of file Index: ConfigurationForm.resx =================================================================== --- ConfigurationForm.resx (Revision 163) +++ ConfigurationForm.resx (Arbeitskopie) @@ -120,18 +120,12 @@ 17, 17 - - 17, 17 - 107, 17 ONLY playlists with a name matching the device name will be sorted into this folder. Dynamic playlists with a different name, or special playlists such as 'Party shuffle' will not be put in the 'My Devices' folder. - - 107, 17 - Index: ConnectedDevicesManagerImpl.cs =================================================================== --- ConnectedDevicesManagerImpl.cs (Revision 163) +++ ConnectedDevicesManagerImpl.cs (Arbeitskopie) @@ -4,6 +4,7 @@ using System.Text; using System.IO; using Jaranweb.iTunesAgent.Configuration12; +using System.Windows.Forms; namespace Jaranweb.iTunesAgent { @@ -59,7 +60,7 @@ IEnumerator keys = connectedDevices.Keys.GetEnumerator(); while (keys.MoveNext()) { - string driveLetter = (string)keys.Current; + string driveLetter = ((string)keys.Current).Substring(0, 3); bool found = false; Device device = (Device)connectedDevices[keys.Current]; @@ -67,11 +68,14 @@ //Loop through all drive info objects to look for the current drive. foreach (DriveInfo di in drives) { - Device recognized = RecognizeDevice(di); - if (di.Name == driveLetter && (recognized != null && recognized.Name == device.Name)) - { - found = true; - break; + //MessageBox.Show(di.RootDirectory + " - " + RecognizeDeviceCount(di), "", MessageBoxButtons.OK); + + for (int i = 0; i < RecognizeDeviceCount(di); i++) { + Device recognized = RecognizeDevice(di, i); + if (di.Name == driveLetter && (recognized != null && recognized.Name == device.Name)) { + found = true; + //break; + } } } @@ -95,16 +99,17 @@ //the defined patterns for devices that iTunes Agent recognizes. foreach (DriveInfo di in drives) { - Device recognized = RecognizeDevice(di); - if (recognized == null) - continue; + for (int i = 0; i < RecognizeDeviceCount(di); i++) { + Device recognized = RecognizeDevice(di, i); + if (recognized == null) + continue; - if (connectedDevices.ContainsKey(di.Name)) - continue; + if (connectedDevices.ContainsKey(di.Name + "_" + i)) + continue; - connectedDevices.Add(di.Name, recognized); - OnDeviceConnected(di, recognized); - + connectedDevices.Add(di.Name + "_" + i, recognized); + OnDeviceConnected(di, recognized); + } } } @@ -128,16 +133,64 @@ return null; } + private int RecognizeDeviceCount(DriveInfo drive) { + int i = 0; + + foreach (Device d in deviceConfig.Devices) { + if (Directory.Exists(drive.Name + d.RecognizePattern) || + File.Exists(drive.Name + d.RecognizePattern) || + drive.VolumeLabel == d.RecognizePattern) { + + i++; + } + } + + return i; + } + + private Device RecognizeDevice(DriveInfo drive, int index) { + int i = 0; + + foreach (Device d in deviceConfig.Devices) { + if (Directory.Exists(drive.Name + d.RecognizePattern) || + File.Exists(drive.Name + d.RecognizePattern) || + drive.VolumeLabel == d.RecognizePattern) { + + if (i == index) + return d; + + i++; + } + } + + return null; + } + /// /// Remove a device from the list of disconnected devices. /// /// The name of the drive where the device was connected. private void RemoveDevice(string drive) { - connectedDevices.Remove(drive); + IEnumerator keys = connectedDevices.Keys.GetEnumerator(); + while (keys.MoveNext()) + { + if (keys.Current.ToString().StartsWith(drive)) { + connectedDevices.Remove(keys.Current); + keys = connectedDevices.Keys.GetEnumerator(); + } + } } /// + /// Remove a device from the list of disconnected devices. + /// + /// The name of the drive where the device was connected. + private void RemoveDevice(string drive, int index) { + connectedDevices.Remove(drive + "_" + index); + } + + /// /// Method for sending out device disconnected events. /// /// The name of the drive where the device was located. Index: device-config.xml =================================================================== --- device-config.xml (Revision 163) +++ device-config.xml (Arbeitskopie) @@ -22,6 +22,13 @@ This pattern organizes all tracks in the root media folder of the device. All tracks from all artists are in one folder and names like this: Artist - Name. + + playlist-order + Playlist-Order Folder + %TRACKIDXSPACE%%ARTIST% - %NAME% + This pattern organizes all tracks in the root media folder of the device. All tracks from all artists are in one folder and names like this: Index Artist - Name. + + Index: Device.cs =================================================================== --- Device.cs (Revision 163) +++ Device.cs (Arbeitskopie) @@ -6,6 +6,34 @@ namespace Jaranweb.iTunesAgent.Configuration12 { + public class ExportPlaylist + { + + public enum PlaylistTypes { None = 0, M3U, EXT, WPL, ZPL }; + + private string file; + + private PlaylistTypes type; + + /// + /// Accessor for File. + /// + public string File + { + get { return file; } + set { file = value; } + } + + /// + /// Accessor for Type. + /// + public PlaylistTypes Type + { + get { return type; } + set { type = value; } + } + } + public class Device { private string name; @@ -18,6 +46,8 @@ private string playlist; + private ExportPlaylist exportPlaylist = new ExportPlaylist(); + private ArrayList initialTracks = new ArrayList(); /// @@ -66,6 +96,15 @@ } /// + /// Accessor for Export. + /// + public ExportPlaylist ExportPlaylist + { + get { return exportPlaylist; } + set { exportPlaylist = value; } + } + + /// /// Accessor for intialTracks. /// [XmlIgnore] Index: Docs/device-config.xml =================================================================== --- Docs/device-config.xml (Revision 163) +++ Docs/device-config.xml (Arbeitskopie) @@ -22,6 +22,13 @@ This pattern organizes all tracks in the root media folder of the device. All tracks from all artists are in one folder and names like this: Artist - Name. + + playlist-order + Playlist-Order Folder + %TRACKIDXSPACE%%ARTIST% - %NAME% + This pattern organizes all tracks in the root media folder of the device. All tracks from all artists are in one folder and names like this: Index Artist - Name. + + Index: IPlaylistWriter.cs =================================================================== --- IPlaylistWriter.cs (Revision 0) +++ IPlaylistWriter.cs (Revision 0) @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +using iTunesLib; + +namespace Jaranweb.iTunesAgent.PlaylistWriter +{ + /// + /// Provides common functionality to playlist writer implementations. + /// + abstract class IPlaylistWriter + { + protected string fileName; + protected string playlist; + + public IPlaylistWriter(string fileName, string playlist) + { + // Cleans up playlists with * characters in name. + this.fileName = fileName.Replace('*', '_'); + this.playlist = playlist; + } + + /// + /// Writes the passed track to the current playlist file. + /// + /// The display name details for the track. + /// Typed reference for the track, providing additional data. + public abstract void WriteTrack( string trackName, IITTrack track ); + + /// + /// Closes the file handle for the playlist being written. + /// + public abstract void Close(); + + /// + /// Deletes the current playlist file from disk. + /// + public void Delete() + { + new FileInfo(fileName).Delete(); + } + } +} Index: iTunes Agent.csproj =================================================================== --- iTunes Agent.csproj (Revision 163) +++ iTunes Agent.csproj (Arbeitskopie) @@ -150,9 +150,12 @@ + + + Form @@ -210,6 +213,8 @@ + + @@ -257,6 +262,7 @@ - $(ProjectDir)Versioner.exe "$(ProjectDir)Properties\AssemblyInfo.cs" + + \ No newline at end of file Index: M3UExtPlaylistWriter.cs =================================================================== --- M3UExtPlaylistWriter.cs (Revision 0) +++ M3UExtPlaylistWriter.cs (Revision 0) @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using iTunesLib; + +namespace Jaranweb.iTunesAgent.PlaylistWriter +{ + /// + /// Creates an M3U playlist in the Extended format. This format is described at + /// these URLs: + /// http://hanna.pyxidis.org/tech/m3u.html + /// http://en.wikipedia.org/wiki/M3U + /// http://www.assistanttools.com/articles/m3u_playlist_format.shtml + /// + class M3UExtPlaylistWriter : IPlaylistWriter + { + private StreamWriter _writer; + private string _thePlaylist; + + private const string FILE_HEADER = "#EXTM3U"; + private const string LINE_FORMAT_STRING = "{0}{1}, {2} - {3}"; + private const string LINE_PREFIX = "#EXTINF:"; + + public M3UExtPlaylistWriter(string fileName, string playlist) : base(fileName, playlist) + { + _thePlaylist = playlist; + _writer = new StreamWriter(fileName, false, System.Text.Encoding.GetEncoding("utf-8")); + + /// File format requires a single line header + _writer.WriteLine(FILE_HEADER); + } + + public override void WriteTrack(string trackName, IITTrack track) + { + /// Each track has two lines in the playlist file. The first contains + /// display info (seconds, name), plus the path/filename. + _writer.WriteLine(string.Format(LINE_FORMAT_STRING, LINE_PREFIX, track.Time.ToString(), track.Artist, track.Name)); + _writer.WriteLine(trackName); + } + + public override void Close() + { + _writer.Close(); + _writer.Dispose(); + } + } +} Index: M3UPlaylistWriter.cs =================================================================== --- M3UPlaylistWriter.cs (Revision 0) +++ M3UPlaylistWriter.cs (Revision 0) @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using iTunesLib; + +namespace Jaranweb.iTunesAgent.PlaylistWriter +{ + /// + /// Generates basic M3U playlist files. + /// + class M3UPlaylistWriter : IPlaylistWriter + { + private StreamWriter writer; + + public M3UPlaylistWriter(String fileName, string playlist ) : base(fileName, playlist) + { + writer = new StreamWriter(fileName, false, System.Text.Encoding.GetEncoding("utf-8")); + } + + public override void WriteTrack(string trackName, IITTrack track) + { + writer.WriteLine( trackName ); + } + + public override void Close() + { + writer.Close(); + } + } +} Index: MainForm.cs =================================================================== --- MainForm.cs (Revision 163) +++ MainForm.cs (Arbeitskopie) @@ -482,7 +482,7 @@ { return; } - + // Create synchronizer and form. synchronizer = new StandardSynchronizer(); syncForm = new StandardSynchronizerForm(); @@ -538,15 +538,13 @@ public void PerformSynchronize() { - - Hashtable deviceinfo = connectedDevices.GetConnectedDevicesWithDrives(); IEnumerator keys = deviceinfo.Keys.GetEnumerator(); - try - { + + try { while (keys.MoveNext()) { - string drive = (string)keys.Current; + string drive = ((string)keys.Current).Substring(0, 3); Device device = (Device)deviceinfo[keys.Current]; if (configuration.ConfirmMusicLocation && !GetMusicLocationConfirmation(drive + device.MediaRoot)) @@ -572,7 +570,6 @@ synchronizer.SynchronizeComplete += new SynchronizeCompleteEventHandler(OnSynchronizeComplete); synchronizer.SynchronizeCancelled += new SynchronizeCancelledEventHandler(OnSynchronizeCancelled); synchronizer.SynchronizeDevice((IITUserPlaylist)playlist, drive, device); - } } catch (Exception ex) @@ -594,8 +591,8 @@ private void OnSynchronizeComplete(object sender) { - if (syncForm != null && configuration.CloseSyncWindowOnSuccess) - syncForm.CloseSafe(); + //if (syncForm != null && configuration.CloseSyncWindowOnSuccess) + // syncForm.CloseSafe(); if (configuration.ShowNotificationPopups) itaTray.ShowBalloonTip(5, "Synchronize complete", "Your device was successfully synchronized with iTunes.", ToolTipIcon.Info); Index: Resources/device-config.xml =================================================================== --- Resources/device-config.xml (Revision 163) +++ Resources/device-config.xml (Arbeitskopie) @@ -22,6 +22,13 @@ This pattern organizes all tracks in the root media folder of the device. All tracks from all artists are in one folder and names like this: Artist - Name. + + playlist-order + Playlist-Order Folder + %TRACKIDXSPACE%%ARTIST% - %NAME% + This pattern organizes all tracks in the root media folder of the device. All tracks from all artists are in one folder and names like this: Index Artist - Name. + + Index: StandardSynchronizer.cs =================================================================== --- StandardSynchronizer.cs (Revision 163) +++ StandardSynchronizer.cs (Arbeitskopie) @@ -9,6 +9,7 @@ using System.Runtime.InteropServices; using System.Diagnostics; using Jaranweb.iTunesAgent.Configuration12; +using Jaranweb.iTunesAgent.PlaylistWriter; using System.Security.AccessControl; using log4net; @@ -131,10 +132,44 @@ string deviceMediaRoot = drive + (device.MediaRoot.Length > 0 ? device.MediaRoot + "\\" : ""); + string playlistFile = null; + if(device.ExportPlaylist.File.Length > 0) + playlistFile = drive + device.ExportPlaylist.File; + + IPlaylistWriter playlistWriter = null; + + if (File.Exists(playlistFile)) + { + File.Delete(playlistFile); + } + try { - foreach (IITTrack track in playlist.Tracks) + // Create a new instance of one of the IPlaylistWriter implementations to + // handle the file's contents. + switch (device.ExportPlaylist.Type) { + case ExportPlaylist.PlaylistTypes.M3U: + playlistWriter = new M3UPlaylistWriter(playlistFile, playlist.Name); + break; + case ExportPlaylist.PlaylistTypes.EXT: + playlistWriter = new M3UExtPlaylistWriter(playlistFile, playlist.Name); + break; + case ExportPlaylist.PlaylistTypes.WPL: + playlistWriter = new WPLPlaylistWriter(playlistFile, playlist.Name); + break; + case ExportPlaylist.PlaylistTypes.ZPL: + playlistWriter = new ZPLPlaylistWriter(playlistFile, playlist.Name); + break; + default: + break; + } + + //foreach (IITTrack track in playlist.Tracks) + for (int i = 1; i <= playlist.Tracks.Count ; i++) + { + IITTrack track = playlist.Tracks.get_ItemByPlayOrder(i); + if (syncForm.GetOperationCancelled()) { syncForm.SetCurrentStatus("Synchronization cancelled. 0 tracks added, 0 tracks removed."); @@ -156,7 +191,7 @@ try { - pathOnDevice = SyncPatternTranslator.Translate(devicePattern, (IITFileOrCDTrack)addTrack); + pathOnDevice = SyncPatternTranslator.Translate(devicePattern, (IITFileOrCDTrack)addTrack, i); } catch (Exception ex) { @@ -299,7 +334,7 @@ syncForm.SetMaxProgressValue(syncList.Count); syncForm.SetProgressValue(0); - //Check for new track in the playlist which should be copied to the device + // Check for new track in the playlist which should be copied to the device // NEW foreach: traverse synchronization list instead of playlist // Thanks to Robert Grabowski. foreach (string filePath in syncList.Keys) @@ -324,8 +359,25 @@ string trackPath = filePath.Substring(deviceMediaRoot.Length); // hack: cut out media root + string trackRelativePath = null; + if (playlistFile != null) + EvaluateRelativePath(Path.GetDirectoryName(playlistFile), filePath); + + //syncForm.AddLogText(trackPath + " | " + trackRelativePath, Color.Purple); + + /* + * if (trackRelativePath.StartsWith(".\\")) + * trackRelativePath = trackRelativePath.Substring(2); + */ + + if (File.Exists(filePath)) + { + if (playlistWriter != null) + playlistWriter.WriteTrack(trackRelativePath, track); + continue; + } try { @@ -335,8 +387,10 @@ File.Copy(((IITFileOrCDTrack)track).Location, filePath, true); File.SetAttributes(filePath, FileAttributes.Normal); + + if (playlistWriter != null) + playlistWriter.WriteTrack(trackRelativePath, track); - syncForm.AddLogText(filePath + " copied successfully.", Color.Green); } @@ -354,9 +408,7 @@ tracksAdded++; } - } - catch (MissingTrackException ex) - { + } catch (MissingTrackException ex) { syncForm.SetCurrentStatus(""); String message = "You have a missing file in your library. Please remove the track '" + ex.Track.Artist + " - " + ex.Track.Name + "' and try again. I am sorry for the inconvenience."; syncForm.AddLogText(message, Color.Red); @@ -367,9 +419,7 @@ l.Error(message, ex); return; - } - catch (Exception ex) - { + } catch (Exception ex) { string message = "An error occured while copying new tracks: " + ex.Message; syncForm.SetCurrentStatus(""); syncForm.AddLogText(message, @@ -383,6 +433,12 @@ return; } + if (playlistWriter != null) + { + playlistWriter.Close(); + playlistWriter = null; + } + syncForm.SetCurrentStatus("Synchronization completed. " + tracksAdded + " track(s) added, " + tracksRemoved + " track(s) removed."); syncForm.AddLogText("Completed. " + tracksAdded + " track(s) copied to your device.", Color.Green); @@ -393,6 +449,58 @@ /// + /// Resolve the relative path of a file to another file + /// + /// The path of the file, which is absolute. + /// The path of the file, which should be made relative. + private static string EvaluateRelativePath(string mainDirPath, string absoluteFilePath) + { + string[] firstPathParts = mainDirPath.Trim(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar); + + string[] secondPathParts = absoluteFilePath.Trim(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar); + + int sameCounter = 0; + + for (int i = 0; i < Math.Min(firstPathParts.Length, secondPathParts.Length); i++) + { + if (!firstPathParts[i].ToLower().Equals(secondPathParts[i].ToLower())) + { + break; + } + sameCounter++; + } + + if (sameCounter == 0) + { + return absoluteFilePath; + } + + string newPath = String.Empty; + + for (int i = sameCounter; i < firstPathParts.Length; i++) + { + if (i > sameCounter) + { + newPath += Path.DirectorySeparatorChar; + } + newPath += ".."; + } + + if (newPath.Length == 0) + { + newPath = "."; + } + + for (int i = sameCounter; i < secondPathParts.Length; i++) + { + newPath += Path.DirectorySeparatorChar; + newPath += secondPathParts[i]; + } + + return newPath; + } + + /// /// Check if the necessary folders for the given track exists. If not, create them. /// /// The path of the track, relative to the device media root. Index: SyncPatternTranslator.cs =================================================================== --- SyncPatternTranslator.cs (Revision 163) +++ SyncPatternTranslator.cs (Arbeitskopie) @@ -25,7 +25,7 @@ /// SyncPattern to translate. /// iTunes track containing track information. /// A string representation of pattern and track. - public static string Translate(SyncPattern pattern, IITFileOrCDTrack track) + public static string Translate(SyncPattern pattern, IITFileOrCDTrack track, long lTrackIndex) { try { @@ -48,13 +48,11 @@ { //%TRACKNUMSPACE% patternstring = patternstring.Replace("%TRACKNUMSPACE%", - (track.TrackNumber.ToString().Length == 1 ? - "0" + track.TrackNumber.ToString() : track.TrackNumber.ToString()) + " "); + track.TrackNumber.ToString("00") + " "); //%TRACKNUM% patternstring = patternstring.Replace("%TRACKNUM%", - (track.TrackNumber.ToString().Length == 1 ? - "0" + track.TrackNumber.ToString() : track.TrackNumber.ToString())); + track.TrackNumber.ToString("00")); } else //If there are no track number set for the track { @@ -64,6 +62,24 @@ patternstring = patternstring.Replace("%TRACKNUM%", ""); } + if (lTrackIndex != 0) + { + //%TRACKIDXSPACE% + patternstring = patternstring.Replace("%TRACKIDXSPACE%", + lTrackIndex.ToString("000") + " "); + + //%TRACKIDX% + patternstring = patternstring.Replace("%TRACKIDX%", + lTrackIndex.ToString("000")); + } + else //If there is no track index set for the track + { + //%TRACKIDXSPACE% + patternstring = patternstring.Replace("%TRACKIDXSPACE%", ""); + //%TRACKIDX% + patternstring = patternstring.Replace("%TRACKIDX%", ""); + } + if (track.Location == null) { l.Debug("track.Location is null!"); Index: SyncPatternTranslatorTest.cs =================================================================== --- SyncPatternTranslatorTest.cs (Revision 163) +++ SyncPatternTranslatorTest.cs (Arbeitskopie) @@ -26,7 +26,7 @@ track.Location = "C:\\Music\\Coldplay\\X&Y\\Fix You.mp3"; Assert.AreEqual("Coldplay\\X&Y\\Fix You.mp3", - SyncPatternTranslator.Translate(pattern, track)); + SyncPatternTranslator.Translate(pattern, track, 0)); } } Index: WPLPlaylistWriter.cs =================================================================== --- WPLPlaylistWriter.cs (Revision 0) +++ WPLPlaylistWriter.cs (Revision 0) @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Xml; + +using iTunesLib; + +namespace Jaranweb.iTunesAgent.PlaylistWriter +{ + /// + /// Generates playlist files in the WPL playlist format used by Windows Media Player. + /// + class WPLPlaylistWriter : IPlaylistWriter + { + private XmlWriter xmlWriter; + + public WPLPlaylistWriter(string fileName, string playlist) : base(fileName, playlist) + { + xmlWriter = new XmlTextWriter(fileName, System.Text.Encoding.GetEncoding("utf-8")); + + xmlWriter.WriteRaw("\n"); + xmlWriter.WriteStartElement("smil"); + xmlWriter.WriteStartElement("head"); + xmlWriter.WriteElementString("author", ""); + xmlWriter.WriteElementString("title", playlist); + xmlWriter.WriteEndElement(); + xmlWriter.WriteStartElement("body"); + xmlWriter.WriteStartElement("seq"); + } + + public override void WriteTrack(string trackName, IITTrack track) + { + xmlWriter.WriteStartElement("media"); + xmlWriter.WriteAttributeString("src", trackName); + xmlWriter.WriteEndElement(); + } + + public override void Close() + { + xmlWriter.WriteEndElement(); + xmlWriter.WriteEndElement(); + xmlWriter.WriteEndElement(); + xmlWriter.Close(); + } + } +} Index: ZPLPlaylistWriter.cs =================================================================== --- ZPLPlaylistWriter.cs (Revision 0) +++ ZPLPlaylistWriter.cs (Revision 0) @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Xml; + +using iTunesLib; + +namespace Jaranweb.iTunesAgent.PlaylistWriter +{ + /// + /// Generates playlist files in the ZPL playlist format used by Microsoft Zune Media Player. + /// + class ZPLPlaylistWriter : IPlaylistWriter + { + private XmlWriter xmlWriter; + + public ZPLPlaylistWriter(string fileName, string playlist) : base(fileName, playlist) + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + settings.OmitXmlDeclaration = true; + settings.Encoding = System.Text.Encoding.GetEncoding("utf-8"); + + xmlWriter = XmlWriter.Create(fileName, settings); + + xmlWriter.WriteRaw("\n"); + xmlWriter.WriteStartElement("smil"); + xmlWriter.WriteStartElement("head"); + + xmlWriter.WriteStartElement("meta"); + xmlWriter.WriteAttributeString("name", "Generator"); + xmlWriter.WriteAttributeString("content", "Zune -- 1.3.5728.0"); + xmlWriter.WriteEndElement(); + + xmlWriter.WriteElementString("author", ""); + xmlWriter.WriteElementString("title", playlist); + xmlWriter.WriteEndElement(); + xmlWriter.WriteStartElement("body"); + xmlWriter.WriteStartElement("seq"); + } + + public override void WriteTrack(string trackName, IITTrack track) + { + xmlWriter.WriteStartElement("media"); + xmlWriter.WriteAttributeString("src", trackName); + xmlWriter.WriteEndElement(); + } + + public override void Close() + { + xmlWriter.WriteEndElement(); + xmlWriter.WriteEndElement(); + xmlWriter.WriteEndElement(); + xmlWriter.Close(); + } + } +}