private void MenuItemFindSynergyFiles_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                //just supporting first attached device for now
                NwdPortableDeviceCollection col =
                    new NwdPortableDeviceCollection();
                col.Refresh();

                if (col.Count() > 0)
                {
                    NwdPortableDevice device = col.First();

                    if (device != null)
                    {
                        if (unprocessedSynergyFiles == null ||
                            unprocessedSynergyFiles.Count < 1)
                        {
                            IEnumerable<NwdUriProcessEntry> synergyFiles =
                                GetDeviceSynergyFiles(device);

                            unprocessedSynergyFiles =
                                new Stack<NwdUriProcessEntry>(synergyFiles);

                            Display.Grid(synergyFiles.Count() +
                                " synergy files found", unprocessedSynergyFiles);
                        }
                        else
                        {
                            string msg = unprocessedSynergyFiles.Count +
                                " unprocessed synergy file entries found";

                            Display.Message(msg);
                        }

                        if (unprocessedSynergyFiles != null)
                        {
                            List<NwdUriProcessEntry> processed =
                                new List<NwdUriProcessEntry>();

                            bool again = true;
                            string msg = "Enter processing segment size";

                            while (again && unprocessedSynergyFiles.Count() > 0)
                            {
                                int repetitionSegmentSize =
                                    NineWorldsDeep.UI.Prompt.ForInteger(msg);

                                IEnumerable<NwdUriProcessEntry> currentlyProcessing =
                                    unprocessedSynergyFiles.Pop(repetitionSegmentSize);

                                int copiedFilesCount = 0;
                                Stopwatch watch = Stopwatch.StartNew();

                                foreach (NwdUriProcessEntry pe in currentlyProcessing)
                                {
                                    if (pe.DeviceObject is NwdPortableDeviceFile)
                                    {
                                        NwdPortableDeviceFile pdf =
                                            (NwdPortableDeviceFile)pe.DeviceObject;

                                        string localFolderPath =
                                            Configuration.MtpSynergySyncPath;

                                        device.DownloadFile(pdf, localFolderPath);

                                        copiedFilesCount++;
                                    }

                                    pe.Processed = true;
                                    processed.Add(pe);
                                }

                                watch.Stop();

                                string displayMsg = processed.Count +
                                    " processed / " +
                                    unprocessedSynergyFiles.Count +
                                    " unprocessed";

                                Display.Grid(displayMsg,
                                             processed,
                                             unprocessedSynergyFiles);

                                again =
                                NineWorldsDeep.UI.Prompt.Confirm("Processing time: "
                                + watch.Elapsed.ToString() +
                                " to process " + copiedFilesCount +
                                " entries. Process more entries?");
                            }

                        }

                    }
                }
                else
                {
                    Display.Message("no devices found");
                }

            }
            catch (Exception ex)
            {
                Display.Exception(ex);
            }
        }
        private void MenuItemFindToBeRemovedPlayList_Click(object sender, RoutedEventArgs e)
        {
            //just supporting first attached device for now, can cycle through multiples later
            NwdPortableDeviceCollection col = new NwdPortableDeviceCollection();

            try
            {
                col.Refresh();
            }
            catch (Exception ex)
            {
                Display.Exception(ex);
            }

            if (col.Count() > 0)
            {
                NwdPortableDevice device = col.First();

                if (device != null)
                {
                    //if(unprocessedDeletions == null ||
                    //    unprocessedDeletions.Count < 1)
                    if (unprocessedDeletions.IsEmptyOrNull())
                    {
                        toBeRemoved = GetFirstToBeRemovedPlaylist();

                        if (toBeRemoved == null)
                        {
                            MessageBox.Show("Playlist(s) 'to be removed[*]' not found");
                        }
                        else
                        {
                            MessageBox.Show("found [" + toBeRemoved.Name + "]");

                            //copy file to local file system
                            string localDestinationFolder = Configuration.PlaylistsFolder;
                            device.DownloadFile(toBeRemoved, localDestinationFolder);

                            string localFilePath =
                                System.IO.Path.Combine(localDestinationFolder, toBeRemoved.Name);

                            MessageBox.Show("copied file to [" + localFilePath + "]");

                            List<string> entries = File.ReadAllLines(localFilePath).ToList();

                            MessageBox.Show("found " + entries.Count + " entries");

                            List<NwdUri> lst = new List<NwdUri>();
                            List<string> invalidPaths = new List<string>();

                            foreach (string entry in entries)
                            {
                                NwdUri newUri = Configuration.NwdPathToNwdUri(entry);
                                if (newUri != null)
                                {
                                    lst.Add(newUri);
                                }
                                else
                                {
                                    invalidPaths.Add(entry);
                                }
                            }

                            var pathList = (from entry in invalidPaths
                                            select new
                                            {
                                                Path = entry
                                            }).ToList();

                            if (pathList.Count > 0)
                            {
                                Display.Grid(invalidPaths.Count + " invalid paths skipped", lst, pathList);
                            }
                            else
                            {
                                Display.Grid("0 invalid paths skipped", lst);
                            }

                            var processEntries = (from nwdUri in lst
                                                  select new NwdUriProcessEntry(nwdUri)).ToList();

                            unprocessedDeletions =
                                new Stack<NwdUriProcessEntry>(processEntries);
                        }

                    }
                    else
                    {
                        MessageBox.Show(unprocessedDeletions.Count
                            + " unprocessed entries found for ["
                            + toBeRemoved.Name + "]");
                    }

                    if (unprocessedDeletions != null)
                    {

                        List<NwdUriProcessEntry> processed =
                        new List<NwdUriProcessEntry>();

                        bool again = true;
                        string msg = "Enter processing segment size (keep in mind that " +
                            "caching works better the higher this number is, but the " +
                            "higher the number, the longer each iteration will obviously " +
                            "take, find a balance that works for your device)";

                        while (again && unprocessedDeletions.Count() > 0)
                        {
                            int repetitionSegmentSize =
                            NineWorldsDeep.UI.Prompt.ForInteger(msg);

                            IEnumerable<NwdUriProcessEntry> currentlyProcessing =
                                unprocessedDeletions.Pop(repetitionSegmentSize);

                            Stopwatch watch = Stopwatch.StartNew();
                            var res = FindByUriCached(currentlyProcessing.ToNwdUris());
                            watch.Stop(); //just profiling find by uri

                            foreach (NwdUriProcessEntry pe in currentlyProcessing)
                            {
                                if (res[pe.URI].Count > 0)
                                {
                                    pe.DeviceObject = res[pe.URI].First();
                                }

                                pe.Processed = true;
                                processed.Add(pe);
                            }

                            again =
                                NineWorldsDeep.UI.Prompt.Confirm("Processing took "
                                + watch.Elapsed.ToString() +
                                " milliseconds to complete. Process more entries?");
                        }

                        Display.Grid(processed.Count() + " processed / "
                            + unprocessedDeletions.Count() + " unprocessed", processed);

                        if (NineWorldsDeep.UI.Prompt.Confirm("Delete all processed and found files?"))
                        {
                            int deletionCount = 0;

                            foreach (NwdUriProcessEntry pe in processed)
                            {
                                if (pe.FoundOnDevice)
                                {
                                    NwdPortableDeviceObject pdo = pe.DeviceObject;

                                    if (pdo is NwdPortableDeviceFile)
                                    {
                                        try
                                        {
                                            NwdPortableDeviceFile pdf =
                                                (NwdPortableDeviceFile)pdo;

                                            device.DeleteFile(pdf);
                                            deletionCount++;
                                            MessageBox.Show(deletionCount + " files deleted");
                                        }
                                        catch(Exception ex)
                                        {
                                            Display.Exception(ex);
                                        }
                                    }
                                }
                            }

                            if (unprocessedDeletions.Count < 1 && toBeRemoved != null)
                            {
                                string confirmMsg =
                                    "No more unprocessed entries for [" +
                                    toBeRemoved.Name + "] delete file?";

                                if (NineWorldsDeep.UI.Prompt.Confirm(confirmMsg))
                                {
                                    device.DeleteFile(toBeRemoved);

                                    MessageBox.Show("[" + toBeRemoved.Name + "] deleted.");

                                    unprocessedDeletions = null;
                                    toBeRemoved = null;
                                }
                            }
                        }

                    }
                }
            }
            else
            {
                MessageBox.Show("no devices found.");
            }
        }
        private void MenuItemEnumerateContent_Click(object sender, RoutedEventArgs e)
        {
            //TODO: LICENSE NOTES
            //enumerating content: https://cgeers.wordpress.com/2011/06/05/wpd-enumerating-content/
            DisplayTextViewList();

            string msg = "This currently works, but takes FOREVER, so I " +
                "am working on borrowing some logic from jMTP to make " +
                "it quicker like my Java experiments. For now, please " +
                "only press OK if you are prepared to wait a really " +
                "long time (for testing) or you don't mind killing the " +
                "program manually...";

            if (MessageBox.Show(msg,
                                "Are you sure?",
                                MessageBoxButton.OKCancel,
                                MessageBoxImage.Exclamation) == MessageBoxResult.OK)
            {
                var col = new NwdPortableDeviceCollection();

                col.Refresh();

                string output = "";
                bool first = true;

                foreach (var device in col)
                {
                    if (first)
                    {
                        first = false;
                    }
                    else
                    {
                        output += Environment.NewLine;
                    }

                    device.Connect();
                    output += "Friendly Name: " + device.FriendlyName;
                    output += " Model: " + device.Model;
                    output += " Device Type: " + device.DeviceType;

                    var folder = device.GetContents();
                    foreach (var item in folder.Files)
                    {
                        output += Environment.NewLine;
                        output += DisplayObject(item);
                    }

                    device.Disconnect();
                }

                if (first)
                {
                    output = "No devices found.";
                }

                txtBox.Text = output;
            }
            else
            {
                txtBox.Text = "Operation aborted.";
            }
        }
        private void MenuItemEnumerateDevices_Click(object sender, RoutedEventArgs e)
        {
            //TODO: LICENSE NOTES
            //enumerating devices: https://cgeers.wordpress.com/2011/05/22/enumerating-windows-portable-devices/
            DisplayTextViewList();

            var col = new NwdPortableDeviceCollection();

            col.Refresh();

            string output = "";
            bool first = true;

            foreach (var device in col)
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    output += Environment.NewLine;
                }

                device.Connect();
                output += "Friendly Name: " + device.FriendlyName;
                output += " Model: " + device.Model;
                output += " Device Type: " + device.DeviceType;

                device.Disconnect();
            }

            if (first)
            {
                output = "No devices found.";
            }

            txtBox.Text = output;
        }
        private MultiMap<string, NwdPortableDeviceObject> FindByUriCached(string uri)
        {
            MultiMap<string, NwdPortableDeviceObject> masterMap =
                new MultiMap<string, NwdPortableDeviceObject>();

            NwdUri nwdUri = new NwdUri(uri);

            var col = new NwdPortableDeviceCollection();

            try
            {
                col.Refresh();
            }
            catch (Exception ex)
            {
                Display.Exception(ex);
            }

            foreach (var device in col)
            {
                if (device.RootFolder != null)
                {
                    //device level
                    foreach (NwdPortableDeviceObject pdo
                        in device.RootFolder.Files)
                    {
                        //start a new multimap for each device/storage location
                        MultiMap<string, NwdPortableDeviceObject> found =
                            new MultiMap<string, NwdPortableDeviceObject>();

                        //need to reset Uri for each device/storage location
                        nwdUri.ResetStack();

                        //internal vs external storage level
                        if (pdo is NwdPortableDeviceFolder)
                        {
                            NwdPortableDeviceFolder folder =
                                (NwdPortableDeviceFolder)pdo;

                            Retrieve(device, folder, ref nwdUri, ref found);
                        }

                        //add found into master map
                        masterMap.AddAll(found);
                    }
                }
            }

            return masterMap;
        }
        private List<NwdPortableDeviceObject> FindByUri(string uri)
        {
            List<NwdPortableDeviceObject> found =
                new List<NwdPortableDeviceObject>();

            NwdUri nwdUri = new NwdUri(uri);

            var col = new NwdPortableDeviceCollection();

            try
            {
                col.Refresh();
            }
            catch (Exception ex)
            {
                Display.Exception(ex);
            }

            foreach (var device in col)
            {
                if (device.RootFolder != null)
                {
                    //device level
                    foreach (NwdPortableDeviceObject pdo
                        in device.RootFolder.Files)
                    {
                        //need to reset Uri for each device/storage location
                        nwdUri.ResetStack();

                        //internal vs external storage level
                        if (pdo is NwdPortableDeviceFolder)
                        {
                            NwdPortableDeviceFolder folder =
                                (NwdPortableDeviceFolder)pdo;

                            Retrieve(device, folder, ref nwdUri, ref found);
                        }
                    }
                }
            }

            return found;
        }
        private void MenuItemTransferringContentToDevice_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("This one isn't even active, as my device uses " +
                "alphanumeric object ids with no human readable hierarchy. " +
                "It makes copying this tutorial verbatim non-functional, " +
                "but I have adapted the tutorial into a 'transfer file into " +
                "this folder' context menu option. The code in this example " +
                "won't run as I've disabled it, but its in the source for " +
                "reference sake.");

            bool disabled = true;

            //disabled
            if (!disabled)
            {
                var devices = new NwdPortableDeviceCollection();
                devices.Refresh();
                var kindle = devices.First();
                kindle.Connect();

                kindle.TransferContentToDevice(
                    @"d:\temp\Kindle_Users_Guide.azw",
                    @"g:\documents");

                kindle.Disconnect();
            }
        }
        private void MenuItemGetDevices_Click(object sender, RoutedEventArgs e)
        {
            DisplayParentChild();

            lvParent.Items.Clear();
            lvChild.Items.Clear();

            var col = new NwdPortableDeviceCollection();

            try
            {
                col.Refresh();
            }
            catch (Exception ex)
            {
                Display.Exception(ex);
            }

            foreach (var device in col)
            {
                lvParent.Items.Add(device);
            }
        }
        private void MenuItemFindTopLevelFolder_Click(object sender, RoutedEventArgs e)
        {
            string uri = NineWorldsDeep.UI.Prompt.ForNwdUri();

            Parser.Parser p = new Parser.Parser();

            string nodeName = p.GetKeyNode(0, uri);

            MultiMap<string, NwdPortableDeviceObject> uriNodeToPdos =
                new MultiMap<string, NwdPortableDeviceObject>();

            var col = new NwdPortableDeviceCollection();

            try
            {
                col.Refresh();
            }
            catch (Exception ex)
            {
                Display.Exception(ex);
            }

            foreach (var device in col)
            {
                if (device.RootFolder != null)
                {
                    //device level
                    foreach (NwdPortableDeviceObject pdo
                        in device.RootFolder.Files)
                    {
                        //internal vs external storage level
                        if (pdo is NwdPortableDeviceFolder)
                        {
                            NwdPortableDeviceFolder folder =
                                (NwdPortableDeviceFolder)pdo;

                            folder.Refresh(device);

                            if (folder.Files != null)
                            {
                                foreach (NwdPortableDeviceObject pdoTopLevelFolderOrFile in folder.Files)
                                {
                                    //top level folder level

                                    if (pdoTopLevelFolderOrFile.Name.Equals(nodeName))
                                    {
                                        uriNodeToPdos.Add(nodeName, pdoTopLevelFolderOrFile);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            MessageBox.Show("found " + uriNodeToPdos[nodeName].Count() + " matching nodes");
        }