public static MovieSignature parseMediaMatch(MovieMatch movieMatch)
        {
            lock (loadingLock) {
              if (signatureBuilders == null) {
                  signatureBuilders = new List<ISignatureBuilder>();
                  signatureBuilders.Add(new HashBuilder());
                  signatureBuilders.Add(new LocalBuilder());
                  signatureBuilders.Add(new BlurayMetaBuilder());
                  signatureBuilders.Add(new MetaServicesBuilder());
                  signatureBuilders.Add(new NfoBuilder());
                  signatureBuilders.Add(new ImdbBuilder());
              }
              }

              MovieSignature movieSignature = new MovieSignature(movieMatch.LocalMedia);
              foreach (ISignatureBuilder builder in signatureBuilders) {
              SignatureBuilderResult result = builder.UpdateSignature(movieSignature);
              // if a builder returns CONCLUSIVE it has updated the signature with
              // what is believed to be accurate data and we can exit the loop
              // Currently only the Hash and Imdb builder can return this status
              if (result == SignatureBuilderResult.CONCLUSIVE)
                  break;
              }

              return movieSignature;
        }
 public ManualAssignPopup(MovieMatch match)
 {
     InitializeComponent();
     foreach (DBLocalMedia currFile in match.LocalMedia) {
         fileListBox.Items.Add(currFile.File);
     }
     uxTitle.Text = match.Signature.Title;
     uxYear.Text = match.Signature.Year.ToString();
 }
 public SearchStringPopup(MovieMatch match)
 {
     InitializeComponent();
     foreach (DBLocalMedia currFile in match.LocalMedia) {
         fileListBox.Items.Add(currFile.File);
     }
     movieMatch = match;
     uxTitle.Text = movieMatch.Signature.Title;
     uxYear.Text = movieMatch.Signature.Year.ToString();
     uxImdbId.Text = movieMatch.Signature.ImdbId;
 }
        private void movieStatusChangedListener(MovieMatch obj, MovieImporterAction action)
        {
            // we dont care about any messages except those about media matches
            if (obj == null)
                return;

            // This ensures we are thread safe. Makes sure this method is run by
            // the thread that created this panel.
            if (InvokeRequired) {
                Delegate method = new MovieImporter.MovieStatusChangedHandler(movieStatusChangedListener);
                object[] parameters = new object[] { obj, action };
                this.Invoke(method, parameters);
                return;
            }

            // if this is not a message about our files ignore it
            if (!obj.LocalMedia.Contains(localMedia[0]))
                return;

            // store the current match
            mediaMatch = obj;

            switch (action) {
                case MovieImporterAction.ADDED:
                case MovieImporterAction.PENDING:
                    statusLabel.Text = "Possible match retrieval pending...";
                    break;
                case MovieImporterAction.GETTING_MATCHES:
                    statusLabel.Text = "Retrieving possible matches...";
                    break;
                case MovieImporterAction.NEED_INPUT:
                    updateCombo();

                    // auto choose the default (mainly to pull in metadata. we can
                    // easily undo if the user picks another movie.
                    mediaMatch.Selected = (PossibleMatch)possibleMatchesCombo.SelectedItem;
                    mediaMatch.HighPriority = true;
                    MovingPicturesCore.Importer.Approve(mediaMatch);

                    break;
                case MovieImporterAction.APPROVED:
                    statusLabel.Text = "Close match found...";
                    updateCombo();
                    break;
                case MovieImporterAction.GETTING_DETAILS:
                    statusLabel.Text = "Retrieving movie details...";
                    break;
                case MovieImporterAction.COMMITED:
                    statusLabel.Text = "Match commited...";
                    movieDetails.DatabaseObject = mediaMatch.Selected.Movie;
                    break;
            }
        }
        // Adds the files to the importer for processing. If a file has recently been commited
        // and it's readded, it will be reprocessed.
        private void ScanFiles(List<DBLocalMedia> importFileList, bool highPriority)
        {
            if (importFileList == null)
                return;

            List<DBLocalMedia> currFileSet = new List<DBLocalMedia>();
            bool alwaysGroup = MovingPicturesCore.Settings.AlwaysGroupByFolder;

            // sort the paths in alphabetical order
            importFileList.Sort(new DBLocalMediaPathComparer());

            foreach (DBLocalMedia currFile in importFileList) {

                string fileName = currFile.File.Name;

                // if we have already loaded this file, move to the next
                if (currFile.ID != null) {
                    logger.Debug("SKIPPED: File='{0}', Reason='Already in the system'.", fileName);
                    continue;
                }

                // File is already in the matching system
                if (matchLookup.ContainsKey(currFile)) {
                    logger.Debug("SKIPPED: File='{0}', Reason='Already being matched'", fileName);
                    continue;
                }

                // Files on logical volumes should have a serial number.
                // Blocking these files from the import process prevents unnecessary duplications
                if (!currFile.ImportPath.IsUnc && currFile.VolumeSerial == string.Empty) {
                    logger.Debug("SKIPPED: File='{0}', Reason='Missing volume serial'", fileName);
                    continue;
                }

                // exclude samplefiles and ignore them
                if (VideoUtility.isSampleFile(currFile.File)) {
                    logger.Info("SKIPPED: File='{0}', Bytes={1}, Reason='Sample detected'", fileName, currFile.File.Length);
                    continue;
                }

                // Locked files are put in the files queue (to be processed later)
                if (MovingPicturesCore.Settings.DelayLockedFiles && currFile.File.IsLocked()) {
                    filesQueue.Add(currFile);
                    logger.Info("DELAYED: File='{0}', Reason='File is locked'", fileName);
                    continue;
                }

                // Check file with existing localmedia (only for writable media)
                if (!currFile.ImportPath.IsOpticalDrive) {

                    #region Moved/Renamed Files

                    // catch files that were moved/renamed while the plugin was not running
                    List<DBLocalMedia> existingMedia = null;
                    if (!currFile.IsImageFile && (currFile.IsDVD || currFile.IsBluray) && currFile.DiscId != null)
                        existingMedia = DBLocalMedia.GetEntriesByDiscId(currFile.DiscId);
                    else if (currFile.FileHash != null)
                        existingMedia = DBLocalMedia.GetEntriesByHash(currFile.FileHash);

                    // process existing media if applicable
                    if (existingMedia != null && existingMedia.Count > 0) {
                        bool moved = false;
                        lock (filesDeleted)
                        {
                            foreach (DBLocalMedia oldMedia in existingMedia)
                            {
                                bool queued = filesDeleted.Contains(oldMedia);
                                if (queued || oldMedia.ImportPath.Replaced || oldMedia.IsRemoved)
                                {
                                    // remove the old media object from the removal queue if it was present.
                                    if (queued) {
                                        filesDeleted.Remove(oldMedia);
                                    }

                                    logger.Info("File '{0}' was moved/renamed to '{1}'. Updating existing entry.", oldMedia.FullPath, currFile.FullPath);
                                    // update our old media object with the new information
                                    oldMedia.ImportPath = currFile.ImportPath;
                                    oldMedia.File = currFile.File;
                                    oldMedia.UpdateVolumeInformation();
                                    oldMedia.Commit();
                                    moved = true;
                                    break;
                                }
                            }
                        }
                        // if we updated a moved/renamed file we can discard it from the list
                        if (moved) continue;
                    }

                    #endregion

                    #region Additional Multipart Files

                    DirectoryInfo currDir = currFile.File.Directory;
                    DBLocalMedia partnerMedia = null;

                    // check for folder multipart
                    if (Utility.isFolderMultipart(currDir.Name)) {
                        List<DBLocalMedia>  possiblePartners = DBLocalMedia.GetAll(currDir.Parent.FullName + "%");
                        foreach (DBLocalMedia partner in possiblePartners) {
                            if (!partner.Ignored && Utility.isFolderMultipart(partner.File.Directory.Name) && currDir.FullName != partner.File.Directory.FullName)
                            {
                                partnerMedia = partner;
                                break;
                            }
                        }
                    }

                    // check for file multipart
                    if (partnerMedia == null && (alwaysGroup || Utility.isFileMultiPart(currFile.File))) {
                        List<DBLocalMedia> possiblePartners = DBLocalMedia.GetAll(currFile.File.DirectoryName + "%");
                        foreach (DBLocalMedia partner in possiblePartners) {
                            if (!partner.Ignored) {

                                if (alwaysGroup) {
                                    partnerMedia = partner;
                                    break;
                                }
                                if (AdvancedStringComparer.Levenshtein(currFile.File.Name, partner.File.Name) < 3) {
                                    partnerMedia = partner;
                                    break;
                                }
                            }
                        }
                    }

                    // associate this file with an existing partner
                    if (partnerMedia != null && partnerMedia.AttachedMovies.Count == 1) {
                        DBMovieInfo movie = partnerMedia.AttachedMovies[0];
                        movie.LocalMedia.Add(currFile);

                        // Sort on path and recommit part numbers for all related media
                        movie.LocalMedia.Sort(new DBLocalMediaPathComparer());
                        for (int i = 0; i < movie.LocalMedia.Count; i++) {
                            DBLocalMedia media = movie.LocalMedia[i];
                            media.Part = i + 1;
                            media.Commit();
                        }

                        // Commit movie, log and move to next file
                        movie.Commit();
                        logger.Info("File '{0}' was associated with existing movie '{1}' as an additional multi-part media (part {2}).", currFile.FullPath, movie.Title, currFile.Part);
                        continue;
                    }

                    #endregion

                }

                // if we have no previous files, move on so we can check if the next file
                // is a pair to this one.
                if (currFileSet.Count == 0) {
                    currFileSet.Add(currFile);
                    continue;
                }

                // check if the currFile is a part of the same movie as the previous
                // file(s)
                bool isAdditionalMatch = true;
                bool isMultiPartFolderMatch = false;

                foreach (DBLocalMedia otherFile in currFileSet) {

                    DirectoryInfo currentDir = currFile.File.Directory;
                    DirectoryInfo otherDir = otherFile.File.Directory;

                    // if both files are located in folders marked as multi-part folders
                    if (Utility.isFolderMultipart(currentDir.Name) && Utility.isFolderMultipart(otherDir.Name)) {
                        // check if they share the same parent folder, if not then they are not a pair
                        if (!currentDir.Parent.FullName.Equals(otherDir.Parent.FullName)) {
                            isAdditionalMatch = false;
                            break;
                        }
                        else {
                            isMultiPartFolderMatch = true;
                        }
                    }
                    else {
                        // if files are not in the same folder we assume they are not a pair
                        if (!currFile.File.DirectoryName.Equals(otherFile.File.DirectoryName)) {
                            isAdditionalMatch = false;
                            break;
                        }
                    }

                    // if the setting always group files in the same folder is used just group them
                    // without checking differences at all.
                    // @todo: maybe place this below the character differences count?
                    if (alwaysGroup)
                        break;

                    // if the filename differ by more than two characters
                    // assume they are not a pair
                    if (AdvancedStringComparer.Levenshtein(currFile.File.Name, otherFile.File.Name) > 2) {
                        isAdditionalMatch = false;
                        break;
                    }

                    // if the multi-part naming convention doesn't match up
                    // and we didnt match based on multipart folder names assume they are not a pair
                    if (!isMultiPartFolderMatch && !Utility.isFileMultiPart(currFile.File)) {
                        isAdditionalMatch = false;
                        break;
                    }

                }

                // if it's a match store it and move onto the next file to see if
                // it is part of the set too
                if (isAdditionalMatch) {
                    currFileSet.Add(currFile);
                    continue;
                }

                // if it's not a match, add the previous file set and then start a new one
                // with the current file.
                if (!isAdditionalMatch) {
                    MovieMatch newMatch = new MovieMatch();
                    newMatch.LocalMedia = currFileSet;

                    lock (pendingMatches.SyncRoot) {
                        pendingMatches.Add(newMatch);
                    }

                    allMatches.Add(newMatch);
                    foreach (DBLocalMedia subFile in currFileSet)
                        matchLookup.Add(subFile, newMatch);

                    if (MovieStatusChanged != null)
                        MovieStatusChanged(newMatch, MovieImporterAction.ADDED);

                    if (highPriority)
                        Reprocess(newMatch);

                    currFileSet = new List<DBLocalMedia>();
                    currFileSet.Add(currFile);
                }

            }
            // queue up the last set of files
            if (currFileSet.Count > 0) {
                MovieMatch newMatch = new MovieMatch();
                newMatch.LocalMedia = currFileSet;
                lock (pendingMatches.SyncRoot) {
                    pendingMatches.Add(newMatch);
                }

                allMatches.Add(newMatch);
                foreach (DBLocalMedia subFile in currFileSet)
                    matchLookup.Add(subFile, newMatch);

                if (MovieStatusChanged != null)
                    MovieStatusChanged(newMatch, MovieImporterAction.ADDED);

                if (highPriority)
                    Reprocess(newMatch);
            }
        }
        private GUIListItem GetListItem(MovieMatch match)
        {
            // create or grab our list item
            GUIListItem listItem = null;
            if (listItemLookup.ContainsKey(match)) {
                listItem = listItemLookup[match];
            }
            else {
                listItem = new GUIListItem();
                listItem.OnItemSelected += new GUIListItem.ItemSelectedHandler(ListItemSelected);
                listItemLookup[match] = listItem;
            }

            // populate it with most recent info
            listItem.Label = match.LocalMediaString;
            listItem.Label2 = (match.Selected != null ? match.Selected.DisplayMember : "");
            listItem.Label3 = match.LongLocalMediaString;
            listItem.IsPlayed = match.LocalMedia.Count == 0 ? false : match.LocalMedia[0].Ignored;
            listItem.AlbumInfoTag = match;

            return listItem;
        }
        /// <summary>
        /// Receives updates about specific items in the importer and updates the UI accordingly.
        /// </summary>
        private void MovieStatusChangedListener(MovieMatch match, MovieImporterAction action)
        {
            lock (statusChangedSyncToken) {
                // if the importer fired up or shut down clear out our display
                if (action == MovieImporterAction.STARTED || action == MovieImporterAction.STOPPED) {
                    allItems.Clear();
                    pendingItems.Clear();
                    completedItems.Clear();

                    listItemLookup.Clear();
                    allFilesListControl.ListItems.Clear();
                    pendingFilesListControl.ListItems.Clear();
                    completedFileListControl.ListItems.Clear();
                    return;
                }

                // our message is about a specific match
                GUIListItem listItem = GetListItem(match);
                AddToList(listItem, FilterMode.ALL);

                switch (action) {
                    // file is queued but not yet processed have no icon
                    case MovieImporterAction.ADDED:
                    case MovieImporterAction.ADDED_FROM_SPLIT:
                    case MovieImporterAction.ADDED_FROM_JOIN:
                        listItem.PinImage = IdleIcon;
                        break;

                    // files that are currently being scanned are blue
                    case MovieImporterAction.PENDING:
                    case MovieImporterAction.GETTING_MATCHES:
                    case MovieImporterAction.APPROVED:
                    case MovieImporterAction.GETTING_DETAILS:
                        listItem.PinImage = ProcessingIcon;
                        break;

                    // files that need help from the user are yellow
                    case MovieImporterAction.NEED_INPUT:
                        listItem.PinImage = NeedInputIcon;
                        AddToList(listItem, FilterMode.PENDING);
                        break;

                    // files that have successfully imported are green
                    case MovieImporterAction.COMMITED:
                        listItem.PinImage = DoneIcon;
                        AddToList(listItem, FilterMode.COMPLETED);
                        RemoveFromList(listItem, FilterMode.PENDING);
                        break;

                    case MovieImporterAction.IGNORED:
                        listItem.PinImage = IgnoredIcon;
                        AddToList(listItem, FilterMode.PENDING);
                        RemoveFromList(listItem, FilterMode.COMPLETED);
                        break;
                }

                UpdateArtwork();
            }
        }
        // Returns a possible match set for the given media file(s)
        // using the given custom search string
        private void GetMatches(MovieMatch mediaMatch)
        {
            List<DBMovieInfo> movieList;
            List<PossibleMatch> rankedMovieList = new List<PossibleMatch>();

            // notify any listeners we are checking for matches
            if (MovieStatusChanged != null)
                MovieStatusChanged(mediaMatch, MovieImporterAction.GETTING_MATCHES);

            // Get the MovieSignature
            MovieSignature signature = mediaMatch.Signature;

            // grab a list of movies from our dataProvider and rank each returned movie on
            // how close a match it is

            if (mediaMatch.PreferedDataSource != null)
                movieList = mediaMatch.PreferedDataSource.Provider.Get(signature);
            else
                movieList = MovingPicturesCore.DataProviderManager.Get(signature);

            DBSourceInfo lastSource = null;
            bool multipleSources = false;
            foreach (DBMovieInfo currMovie in movieList) {
                if (lastSource == null)
                    lastSource = currMovie.PrimarySource;

                // check if our list of possible matches is from multiple sources
                if (lastSource != currMovie.PrimarySource)
                    multipleSources = true;

                // Create a Possible Match object
                PossibleMatch currMatch = new PossibleMatch();

                // Add the movie
                currMatch.Movie = currMovie;

                // Get the matching score for this movie
                currMatch.Result = signature.GetMatchResult(currMovie);

                // Add the match to the ranked movie list
                rankedMovieList.Add(currMatch);
            }

            // if we have multiple sources, make sure we display info about where each possible
            // match is coming from
            if (multipleSources)
                foreach (PossibleMatch currMatch in rankedMovieList)
                    currMatch.DisplaySourceInfo = true;

            mediaMatch.PossibleMatches = rankedMovieList;
        }
        private bool DisplaySearchDialog(MovieMatch selectedFile)
        {
            GUIDialogMenu dialog = (GUIDialogMenu)GUIWindowManager.GetWindow((int)GUIWindow.Window.WINDOW_DIALOG_MENU);
            if (dialog == null) {
                logger.Error("Could not create search dialog.");
                return false;
            }

            int maxid = 0;
            dialog.Reset();
            dialog.SetHeading(Translation.Search);

            int titleId = ++maxid;
            dialog.Add(string.Format("{0}: {1}", Translation.Title, selectedFile.Signature.Title));

            int yearId = ++maxid;
            dialog.Add(string.Format("{0}: {1}", Translation.Year, selectedFile.Signature.Year));

            int imdbId = ++maxid;
            dialog.Add(string.Format("{0}: {1}", Translation.ImdbId, selectedFile.Signature.ImdbId));

            dialog.DoModal(GUIWindowManager.ActiveWindow);

            // user picked nothing, go back to previous dialog
            if (dialog.SelectedId == -1) {
                return false;
            }

            // build and display our virtual keyboard
            VirtualKeyboard keyboard = (VirtualKeyboard)GUIWindowManager.GetWindow((int)GUIWindow.Window.WINDOW_VIRTUAL_KEYBOARD);
            keyboard.Reset();
            keyboard.IsSearchKeyboard = true;

            if (dialog.SelectedId == titleId) keyboard.Text = selectedFile.Signature.Title;
            if (dialog.SelectedId == yearId) keyboard.Text = (selectedFile.Signature.Year == null) ? "" : selectedFile.Signature.Year.ToString();
            if (dialog.SelectedId == imdbId) keyboard.Text = (selectedFile.Signature.ImdbId == null) ? "" : selectedFile.Signature.ImdbId;
            keyboard.DoModal(GUIWindowManager.ActiveWindow);

            // if the user escaped out redisplay the searchdialog
            if (!keyboard.IsConfirmed) return DisplaySearchDialog(selectedFile);

            // user entered something so update the movie signature
            if (dialog.SelectedId == titleId) selectedFile.Signature.Title = keyboard.Text;
            if (dialog.SelectedId == yearId) selectedFile.Signature.Year = Convert.ToInt32(keyboard.Text);
            if (dialog.SelectedId == imdbId) selectedFile.Signature.ImdbId = keyboard.Text;
            MovingPicturesCore.Importer.Reprocess(selectedFile);

            return true;
        }
        // removes the given match from all pending process lists
        private void RemoveFromMatchLists(MovieMatch match)
        {
            lock (pendingMatches.SyncRoot) {
                if (pendingMatches.Contains(match))
                    pendingMatches.Remove(match);
            }

            lock (priorityPendingMatches.SyncRoot) {
                if (priorityPendingMatches.Contains(match)) {
                    priorityPendingMatches.Remove(match);
                }
            }

            lock (matchesNeedingInput.SyncRoot) {
                if (matchesNeedingInput.Contains(match))
                    matchesNeedingInput.Remove(match);
            }

            lock (approvedMatches.SyncRoot) {
                if (approvedMatches.Contains(match))
                    approvedMatches.Remove(match);
            }

            lock (priorityApprovedMatches.SyncRoot) {
                if (priorityApprovedMatches.Contains(match))
                    priorityApprovedMatches.Remove(match);
            }

            lock (commitedMatches.SyncRoot) {
                if (commitedMatches.Contains(match)) {
                    commitedMatches.Remove(match);
                }
            }

            lock (retrievingDetailsMatches.SyncRoot) {
                if (retrievingDetailsMatches.Contains(match)) {
                    retrievingDetailsMatches.Remove(match);
                }
            }

            foreach (DBLocalMedia currFile in match.LocalMedia) {
                if (matchLookup.ContainsKey(currFile)) {
                    matchLookup.Remove(currFile);
                    allMatches.Remove(match);
                }
            }
        }
        // Approves the MovieMatch for detail processing and commit. THis shold be
        // used in conjunction with the MatchListChanged event when a NEED_INPUT action
        // is received.
        public void Approve(MovieMatch match)
        {
            if (match.Selected == null)
                return;

            RemoveFromMatchLists(match);

            // clear the ignored flag in case these files were previously on the disable list
            foreach (DBLocalMedia currFile in match.LocalMedia) {
                currFile.Ignored = false;
            }

            // select the list to add this match to based on priority
            ArrayList approveList;
            if (match.HighPriority) approveList = priorityApprovedMatches;
            else approveList = approvedMatches;

            lock (approveList.SyncRoot) {
                approveList.Insert(0, match);
                allMatches.Add(match);
                foreach (DBLocalMedia currFile in match.LocalMedia)
                    matchLookup[currFile] = match;
            }

            // notify any listeners of the status change
            logger.Info("User approved " + match.LocalMediaString + " as " + match.Selected.Movie.Title);
            if (MovieStatusChanged != null)
                MovieStatusChanged(match, MovieImporterAction.APPROVED);
        }
        // rescans for possible movie matches using the specified search string
        public void Reprocess(MovieMatch match)
        {
            RemoveFromMatchLists(match);

            if (match.ExistingMovieInfo == null)
                RemoveCommitedRelations(match.LocalMedia);

            // clear the ignored flag in case these files were previously on the disable list
            foreach (DBLocalMedia currFile in match.LocalMedia) {
                currFile.Ignored = false;
            }

            match.PossibleMatches.Clear();

            lock (priorityPendingMatches) {
                match.HighPriority = true;
                priorityPendingMatches.Add(match);
                allMatches.Add(match);
                foreach (DBLocalMedia currFile in match.LocalMedia)
                    matchLookup[currFile] = match;
            }

            // notify any listeners of the status change
            logger.Info("User reprocessing " + match.LocalMediaString);
            if (MovieStatusChanged != null)
                MovieStatusChanged(match, MovieImporterAction.PENDING);
        }
        // takes the given match containing multiple files and splits it up into
        // individual matches for each file
        public void Split(MovieMatch match)
        {
            if (match == null || match.LocalMedia.Count < 2)
                return;

            RemoveFromMatchLists(match);
            RemoveCommitedRelations(match.LocalMedia);
            match.Deleted = true;

            // notify any listeners of the status change
            logger.Info("User split pair " + match.LocalMediaString);
            if (MovieStatusChanged != null)
                MovieStatusChanged(match, MovieImporterAction.REMOVED_FROM_SPLIT);

            foreach (DBLocalMedia currFile in match.LocalMedia) {
                // clear the ignored flag in case these files were previously on the disable list
                currFile.Ignored = false;

                MovieMatch newMatch = new MovieMatch();
                newMatch.LocalMedia.Add(currFile);
                lock (priorityPendingMatches.SyncRoot) {
                    newMatch.HighPriority = true;
                    priorityPendingMatches.Insert(0, newMatch);
                }

                if (MovieStatusChanged != null)
                    MovieStatusChanged(newMatch, MovieImporterAction.ADDED_FROM_SPLIT);
            }
        }
        public void ManualAssign(MovieMatch match)
        {
            if (match.Selected == null)
                return;

            // remove match from all lists
            RemoveFromMatchLists(match);

            // clear the ignored flag in case these files were previously on the disable list
            foreach (DBLocalMedia currFile in match.LocalMedia) {
                currFile.Ignored = false;
            }

            // assign files to movie
            AssignAndCommit(match, false);

            // add match to the committed list
            commitedMatches.Add(match);

            // grab mediainfo
            UpdateMediaInfo(match);

            // notify any listeners of the status change
            logger.Info("User manually assigned " + match.LocalMediaString + "as " + match.Selected.Movie.Title);
            if (MovieStatusChanged != null)
                MovieStatusChanged(match, MovieImporterAction.MANUAL);
        }
        // given multiple matches, a new match is created that is the sum of the previous
        // parts. In practice this means that two parts of the same movie were joined together
        // to be treated as one.
        public void Join(List<MovieMatch> matchList)
        {
            if (matchList == null || matchList.Count < 2)
                return;

            List<DBLocalMedia> fileList = new List<DBLocalMedia>();

            // build the file list and clear out old matches
            foreach (MovieMatch currMatch in matchList) {
                RemoveFromMatchLists(currMatch);
                RemoveCommitedRelations(currMatch.LocalMedia);
                currMatch.Deleted = true;
                fileList.AddRange(currMatch.LocalMedia);

                // notify any listeners of the status change
                if (MovieStatusChanged != null)
                    MovieStatusChanged(currMatch, MovieImporterAction.REMOVED_FROM_JOIN);
            }

            // build the new match and add it for processing
            MovieMatch newMatch = new MovieMatch();
            newMatch.LocalMedia = fileList;
            newMatch.LocalMedia.Sort(new DBLocalMediaComparer());
            lock (priorityPendingMatches.SyncRoot) {
                newMatch.HighPriority = true;
                priorityPendingMatches.Insert(0, newMatch);
            }

            // notify any listeners of the status change
            logger.Info("User joined " + newMatch.LocalMediaString);
            if (MovieStatusChanged != null)
                MovieStatusChanged(newMatch, MovieImporterAction.ADDED_FROM_JOIN);
        }
        // removes any association with the file(s) in the MovieMatch and flags the files
        // to be ignored in the future.
        public void Ignore(MovieMatch match)
        {
            RemoveFromMatchLists(match);
            RemoveCommitedRelations(match.LocalMedia);

            foreach (DBLocalMedia currFile in match.LocalMedia) {
                currFile.Ignored = true;
                currFile.Commit();
            }

            // add match to the committed list
            commitedMatches.Add(match);

            // notify any listeners of the status change
            logger.Info("User ignored " + match.LocalMediaString);
            if (MovieStatusChanged != null)
                MovieStatusChanged(match, MovieImporterAction.IGNORED);
        }
        private void movieStatusChangedListener(MovieMatch obj, MovieImporterAction action)
        {
            // This ensures we are thread safe. Makes sure this method is run by
            // the thread that created this panel.
            if (InvokeRequired) {
                Delegate method = new MovieImporter.MovieStatusChangedHandler(movieStatusChangedListener);
                object[] parameters = new object[] { obj, action };
                this.Invoke(method, parameters);
                return;
            }

            if (action == MovieImporterAction.STARTED)
                return;

            if (action == MovieImporterAction.STOPPED) {
                unapprovedMatchesBindingSource.Clear();
                return;
            }

            if (action == MovieImporterAction.REMOVED_FROM_SPLIT ||
                action == MovieImporterAction.REMOVED_FROM_JOIN) {

                lastSplitJoinLocation = unapprovedMatchesBindingSource.IndexOf(obj);
                unapprovedMatchesBindingSource.Remove(obj);
                clearSelection = true;
                return;
            }

            // add the match if necessary and grab the row number
            int rowNum;
            if (action == MovieImporterAction.ADDED)
                rowNum = unapprovedMatchesBindingSource.Add(obj);
            else if (action == MovieImporterAction.ADDED_FROM_SPLIT ||
                     action == MovieImporterAction.ADDED_FROM_JOIN) {

                unapprovedMatchesBindingSource.Insert(lastSplitJoinLocation, obj);
                if (clearSelection) {
                    unapprovedGrid.ClearSelection();
                    clearSelection = false;
                }
                unapprovedGrid.Rows[lastSplitJoinLocation].Selected = true;

                rowNum = lastSplitJoinLocation;
                lastSplitJoinLocation++;
            } else
                rowNum = unapprovedMatchesBindingSource.IndexOf(obj);

            // setup tooltip for filename
            DataGridViewTextBoxCell filenameCell = (DataGridViewTextBoxCell)unapprovedGrid.Rows[rowNum].Cells["unapprovedLocalMediaColumn"];
            filenameCell.ToolTipText = obj.LongLocalMediaString;

            // setup the combo box of possible matches
            DataGridViewComboBoxCell movieListCombo = (DataGridViewComboBoxCell)unapprovedGrid.Rows[rowNum].Cells["unapprovedPossibleMatchesColumn"];
            movieListCombo.Items.Clear();
            foreach (PossibleMatch currMatch in obj.PossibleMatches)
                movieListCombo.Items.Add(currMatch);

            // set the status icon
            DataGridViewImageCell imageCell = (DataGridViewImageCell)unapprovedGrid.Rows[rowNum].Cells["statusColumn"];
            switch (action) {
                case MovieImporterAction.ADDED:
                case MovieImporterAction.ADDED_FROM_SPLIT:
                case MovieImporterAction.ADDED_FROM_JOIN:
                    imageCell.Value = blank;
                    break;
                case MovieImporterAction.PENDING:
                    imageCell.Value = Resources.arrow_rotate_clockwise;
                    break;
                case MovieImporterAction.GETTING_MATCHES:
                    imageCell.Value = Resources.arrow_down1;
                    break;
                case MovieImporterAction.NEED_INPUT:
                    imageCell.Value = Resources.information;
                    break;
                case MovieImporterAction.APPROVED:
                    imageCell.Value = Resources.approved;
                    break;
                case MovieImporterAction.GETTING_DETAILS:
                    imageCell.Value = Resources.approved;
                    break;
                case MovieImporterAction.COMMITED:
                    imageCell.Value = Resources.accept;
                    break;
                case MovieImporterAction.IGNORED:
                    imageCell.Value = Resources.ignored;
                    break;
                case MovieImporterAction.MANUAL:
                    imageCell.Value = Resources.accept; // @TODO change icon
                    break;
            }

            updateButtons();
        }
        private void AssignAndCommit(MovieMatch match, bool update)
        {
            lock (match) {
                // if we already have a movie object with assigned files, just update
                if (match.ExistingMovieInfo != null && update) {
                    DBMovieInfo movie = match.ExistingMovieInfo;

                    // Using IMovieProvider
                    string siteID = match.Selected.Movie.GetSourceMovieInfo(match.PreferedDataSource).Identifier;
                    movie.GetSourceMovieInfo(match.PreferedDataSource).Identifier = siteID;

                    // and update from that
                    match.PreferedDataSource.Provider.Update(movie);
                    movie.Commit();
                }

                // no movie object exists so go ahead and assign our retrieved details.
                else {
                    AssignFileToMovie(match.LocalMedia, match.Selected.Movie, update);
                }
            }
        }
        // This will add the specified movie to the importer for reprocessing, using the
        // specified data source.
        public void Update(DBMovieInfo movie, DBSourceInfo source)
        {
            MovieMatch newMatch = new MovieMatch();
            newMatch.ExistingMovieInfo = movie;
            newMatch.PreferedDataSource = source;
            newMatch.LocalMedia = movie.LocalMedia;

            if (matchLookup.ContainsKey(movie.LocalMedia[0]))
                RemoveFromMatchLists(matchLookup[movie.LocalMedia[0]]);

            lock (priorityPendingMatches.SyncRoot) {
                priorityPendingMatches.Add(newMatch);
            }

            allMatches.Add(newMatch);
            foreach (DBLocalMedia subFile in movie.LocalMedia)
                matchLookup.Add(subFile, newMatch);

            if (MovieStatusChanged != null)
                MovieStatusChanged(newMatch, MovieImporterAction.ADDED);
        }
 private void UpdateMediaInfo(MovieMatch match)
 {
     foreach (DBLocalMedia currFile in match.LocalMedia) {
         currFile.UpdateMediaInfo();
         currFile.Commit();
     }
 }