← Back to team overview

kicad-developers team mailing list archive

Re: [PATCH] lib-cache-rescue upgrade

 

No - I attached another in response to JP's recent mail about Coverity.
Here it is again:
Chris,

Is this patch good to go?  I'm having trouble keeping up with the patch
volume so I want to make sure before I commit it.

Thanks,

Wayne

On 6/18/2015 3:07 PM, Chris Pavlina wrote:
> Hi all,
>
> It's come to my attention that we have another old project compatibility
> issue:
>
>
https://forum.kicad.info/t/exisiting-schematic-component-not-being-found-in-library-error/976
>
> The behavior when loading a very old project with case-insensitive part
> names isn't nice at all, there's no easy way to change them over. It
> gives suggestions for new part names, but you still have to change them
> one by one.
>
> I adapted lib-cache-rescue into a more generic project-rescue, which can
> handle multiple types of rescue cases, and added one for missing symbols
> with differently cased versions available.
>
> It looks roughly like this now (though I've changed the descriptive text
> at the top since taking that screenshot):
>
> http://misc.c4757p.com/newrescue.png
>
> Please, as many people as possible, try this out.
>
> --
> Chris
>
>
>
> _______________________________________________
> Mailing list: https://launchpad.net/~kicad-developers
> Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
> Unsubscribe : https://launchpad.net/~kicad-developers
> More help   : https://help.launchpad.net/ListHelp
>

_______________________________________________
Mailing list: https://launchpad.net/~kicad-developers
Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
Unsubscribe : https://launchpad.net/~kicad-developers
More help   : https://help.launchpad.net/ListHelp
diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt
index 0982735..d6cc1cd 100644
--- a/eeschema/CMakeLists.txt
+++ b/eeschema/CMakeLists.txt
@@ -114,7 +114,6 @@ set( EESCHEMA_SRCS
     libedit_undo_redo.cpp
     lib_arc.cpp
     lib_bezier.cpp
-    lib_cache_rescue.cpp
     lib_circle.cpp
     lib_collectors.cpp
     lib_draw_item.cpp
@@ -139,6 +138,7 @@ set( EESCHEMA_SRCS
     plot_schematic_PS.cpp
     plot_schematic_PDF.cpp
     plot_schematic_SVG.cpp
+    project_rescue.cpp
     sch_base_frame.cpp
     sch_bitmap.cpp
     sch_bus_entry.cpp
diff --git a/eeschema/class_library.cpp b/eeschema/class_library.cpp
index 4fac061..bbda657 100644
--- a/eeschema/class_library.cpp
+++ b/eeschema/class_library.cpp
@@ -38,7 +38,7 @@
 #include <richio.h>
 #include <config_params.h>
 #include <wildcards_and_files_ext.h>
-#include <lib_cache_rescue.h>
+#include <project_rescue.h>
 //#include <richio.h>
 
 #include <general.h>
diff --git a/eeschema/dialogs/dialog_rescue_each.cpp b/eeschema/dialogs/dialog_rescue_each.cpp
index 8b896f4..31c424a 100644
--- a/eeschema/dialogs/dialog_rescue_each.cpp
+++ b/eeschema/dialogs/dialog_rescue_each.cpp
@@ -32,7 +32,7 @@
 #include <set>
 #include <vector>
 #include <boost/foreach.hpp>
-#include <lib_cache_rescue.h>
+#include <project_rescue.h>
 #include <eeschema_config.h>
 
 class DIALOG_RESCUE_EACH: public DIALOG_RESCUE_EACH_BASE
@@ -43,24 +43,19 @@ public:
      * This dialog asks the user which rescuable, cached parts he wants to rescue.
      * Any rejects will be pruned from aCandidates.
      * @param aCaller - the SCH_EDIT_FRAME calling this
-     * @param aCandidates - the list of RESCUE_CANDIDATES
-     * @param aComponents - a vector of all the components in the schematic
+     * @param aRescuer - the active RESCUER instance
      * @param aAskShowAgain - if true, a "Never Show Again" button will be included
      */
-    DIALOG_RESCUE_EACH( SCH_EDIT_FRAME* aParent, std::vector<RESCUE_CANDIDATE>& aCandidates,
-            std::vector<SCH_COMPONENT*>& aComponents, bool aAskShowAgain );
+    DIALOG_RESCUE_EACH( SCH_EDIT_FRAME* aParent, RESCUER& aRescuer, bool aAskShowAgain );
 
     ~DIALOG_RESCUE_EACH();
 
 private:
     SCH_EDIT_FRAME* m_Parent;
     wxConfigBase*   m_Config;
-    std::vector<RESCUE_CANDIDATE>* m_Candidates;
-    std::vector<SCH_COMPONENT*>* m_Components;
+    RESCUER*        m_Rescuer;
     bool            m_AskShowAgain;
 
-    bool m_insideUpdateEvent;
-
     bool TransferDataToWindow();
     bool TransferDataFromWindow();
     void PopulateConflictList();
@@ -75,31 +70,22 @@ private:
 };
 
 
-DIALOG_RESCUE_EACH::DIALOG_RESCUE_EACH( SCH_EDIT_FRAME* aParent, std::vector<RESCUE_CANDIDATE>& aCandidates,
-        std::vector<SCH_COMPONENT*>& aComponents, bool aAskShowAgain )
-
+DIALOG_RESCUE_EACH::DIALOG_RESCUE_EACH( SCH_EDIT_FRAME* aParent, RESCUER& aRescuer,
+            bool aAskShowAgain )
     : DIALOG_RESCUE_EACH_BASE( aParent ),
       m_Parent( aParent ),
-      m_Candidates( &aCandidates ),
-      m_Components( &aComponents ),
-      m_AskShowAgain( aAskShowAgain ),
-      m_insideUpdateEvent( false )
+      m_Rescuer( &aRescuer ),
+      m_AskShowAgain( aAskShowAgain )
 {
     m_Config = Kiface().KifaceSettings();
     m_stdButtonsOK->SetDefault();
 
     // Set the info message, customized to include the proper suffix.
-    wxString info_message;
-    info_message.Printf(
-        _( "This project uses symbols that no longer match the ones in the system libraries.\n"
-           "Using this tool, you can rescue these cached symbols into a new library.\n"
-           "\n"
-           "Choose \"Rescue\" for any parts you would like to save from this project's cache,\n"
-           "or press \"Cancel\" to allow the symbols to be updated to the new versions.\n"
-           "\n"
-           "All rescued components will be renamed with a new suffix of \"-RESCUE-%s\"\n"
-           "to avoid naming conflicts." ),
-        Prj().GetProjectName() );
+    wxString info_message =
+        _( "It looks like this project was made using older schematic component libraries.\n"
+           "Some parts may need to be relinked to a different symbol name, and some symbols\n"
+           "may need to be \"rescued\" into a new library.\n"
+           "The following changes are recommended to update the project.\n" );
     m_lblInfo->SetLabel( info_message );
 }
 
@@ -114,8 +100,9 @@ bool DIALOG_RESCUE_EACH::TransferDataToWindow()
     if( !wxDialog::TransferDataToWindow() )
         return false;
 
-    m_ListOfConflicts->AppendToggleColumn( _( "Rescue symbol" ) );
-    m_ListOfConflicts->AppendTextColumn( _( "Symbol name" ) );
+    m_ListOfConflicts->AppendToggleColumn( _( "Accept" ) );
+    m_ListOfConflicts->AppendTextColumn( _( "Symbol" ) );
+    m_ListOfConflicts->AppendTextColumn( _( "Action" ) );
     m_ListOfInstances->AppendTextColumn( _( "Reference" ) );
     m_ListOfInstances->AppendTextColumn( _( "Value" ) );
     PopulateConflictList();
@@ -136,11 +123,13 @@ bool DIALOG_RESCUE_EACH::TransferDataToWindow()
 void DIALOG_RESCUE_EACH::PopulateConflictList()
 {
     wxVector<wxVariant> data;
-    BOOST_FOREACH( RESCUE_CANDIDATE& each_candidate, *m_Candidates )
+    BOOST_FOREACH( RESCUE_CANDIDATE& each_candidate, m_Rescuer->m_all_candidates )
     {
         data.clear();
         data.push_back( wxVariant( true ) );
-        data.push_back( each_candidate.requested_name );
+        data.push_back( each_candidate.GetRequestedName() );
+        data.push_back( each_candidate.GetActionDescription() );
+
         m_ListOfConflicts->AppendItem( data );
     }
 }
@@ -155,12 +144,12 @@ void DIALOG_RESCUE_EACH::PopulateInstanceList()
     if( row == wxNOT_FOUND )
         row = 0;
 
-    RESCUE_CANDIDATE& selected_part = (*m_Candidates)[row];
+    RESCUE_CANDIDATE& selected_part = m_Rescuer->m_all_candidates[row];
 
     wxVector<wxVariant> data;
-    BOOST_FOREACH( SCH_COMPONENT* each_component, *m_Components )
+    BOOST_FOREACH( SCH_COMPONENT* each_component, *m_Rescuer->GetComponents() )
     {
-        if( each_component->GetPartName() != selected_part.requested_name )
+        if( each_component->GetPartName() != selected_part.GetRequestedName() )
             continue;
 
         SCH_FIELD* valueField = each_component->GetField( 1 );
@@ -181,9 +170,10 @@ void DIALOG_RESCUE_EACH::OnHandleCachePreviewRepaint( wxPaintEvent& aRepaintEven
     if( row == wxNOT_FOUND )
         row = 0;
 
-    RESCUE_CANDIDATE& selected_part = (*m_Candidates)[row];
+    RESCUE_CANDIDATE& selected_part = m_Rescuer->m_all_candidates[row];
 
-    renderPreview( selected_part.cache_candidate, 0, m_componentViewOld );
+    if( selected_part.GetCacheCandidate() )
+        renderPreview( selected_part.GetCacheCandidate(), 0, m_componentViewOld );
 }
 
 
@@ -194,9 +184,10 @@ void DIALOG_RESCUE_EACH::OnHandleLibraryPreviewRepaint( wxPaintEvent& aRepaintEv
     if( row == wxNOT_FOUND )
         row = 0;
 
-    RESCUE_CANDIDATE& selected_part = (*m_Candidates)[row];
+    RESCUE_CANDIDATE& selected_part = m_Rescuer->m_all_candidates[row];
 
-    renderPreview( selected_part.lib_candidate, 0, m_componentViewNew );
+    if( selected_part.GetLibCandidate() )
+        renderPreview( selected_part.GetLibCandidate(), 0, m_componentViewNew );
 }
 
 
@@ -269,19 +260,15 @@ bool DIALOG_RESCUE_EACH::TransferDataFromWindow()
     if( !wxDialog::TransferDataFromWindow() )
         return false;
 
-    std::vector<RESCUE_CANDIDATE>::iterator it = m_Candidates->begin();
-    for( size_t index = 0; it != m_Candidates->end(); ++index )
+    for( size_t index = 0; index < m_Rescuer->GetCandidateCount(); ++index )
     {
         wxVariant val;
         m_ListOfConflicts->GetValue( val, index, 0 );
         bool rescue_part = val.GetBool();
 
-        if( !rescue_part )
-            m_Candidates->erase( it );
-        else
-            ++it;
+        if( rescue_part )
+            m_Rescuer->m_chosen_candidates.push_back( &m_Rescuer->m_all_candidates[index] );
     }
-
     return true;
 }
 
@@ -299,7 +286,7 @@ void DIALOG_RESCUE_EACH::OnNeverShowClick( wxCommandEvent& aEvent )
     if( resp == wxID_YES )
     {
         m_Config->Write( RESCUE_NEVER_SHOW_KEY, true );
-        m_Candidates->clear();
+        m_Rescuer->m_chosen_candidates.clear();
         Close();
     }
 }
@@ -307,14 +294,13 @@ void DIALOG_RESCUE_EACH::OnNeverShowClick( wxCommandEvent& aEvent )
 
 void DIALOG_RESCUE_EACH::OnCancelClick( wxCommandEvent& aEvent )
 {
-    m_Candidates->clear();
+    m_Rescuer->m_chosen_candidates.clear();
     DIALOG_RESCUE_EACH_BASE::OnCancelClick( aEvent );
 }
 
 
-int InvokeDialogRescueEach( SCH_EDIT_FRAME* aCaller, std::vector<RESCUE_CANDIDATE>& aCandidates,
-        std::vector<SCH_COMPONENT*>& aComponents, bool aAskShowAgain )
+int InvokeDialogRescueEach( SCH_EDIT_FRAME* aCaller, RESCUER& aRescuer, bool aAskShowAgain )
 {
-    DIALOG_RESCUE_EACH dlg( aCaller, aCandidates, aComponents, aAskShowAgain );
+    DIALOG_RESCUE_EACH dlg( aCaller, aRescuer, aAskShowAgain );
     return dlg.ShowModal();
 }
diff --git a/eeschema/dialogs/dialog_rescue_each_base.cpp b/eeschema/dialogs/dialog_rescue_each_base.cpp
index 2e7549d..99cdbcc 100644
--- a/eeschema/dialogs/dialog_rescue_each_base.cpp
+++ b/eeschema/dialogs/dialog_rescue_each_base.cpp
@@ -20,7 +20,7 @@ DIALOG_RESCUE_EACH_BASE::DIALOG_RESCUE_EACH_BASE( wxWindow* parent, wxWindowID i
 	m_lblInfo->Wrap( 500 );
 	bSizerMain->Add( m_lblInfo, 0, wxALL|wxEXPAND, 5 );
 	
-	m_staticText5 = new wxStaticText( this, wxID_ANY, _("Symbols with cache/library conflicts:"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText5 = new wxStaticText( this, wxID_ANY, _("Symbols to update:"), wxDefaultPosition, wxDefaultSize, 0 );
 	m_staticText5->Wrap( -1 );
 	m_staticText5->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) );
 	
diff --git a/eeschema/dialogs/dialog_rescue_each_base.fbp b/eeschema/dialogs/dialog_rescue_each_base.fbp
index dee774b..fcc7882 100644
--- a/eeschema/dialogs/dialog_rescue_each_base.fbp
+++ b/eeschema/dialogs/dialog_rescue_each_base.fbp
@@ -208,7 +208,7 @@
                         <property name="gripper">0</property>
                         <property name="hidden">0</property>
                         <property name="id">wxID_ANY</property>
-                        <property name="label">Symbols with cache/library conflicts:</property>
+                        <property name="label">Symbols to update:</property>
                         <property name="max_size"></property>
                         <property name="maximize_button">0</property>
                         <property name="maximum_size"></property>
diff --git a/eeschema/files-io.cpp b/eeschema/files-io.cpp
index 39922f5..3bf97b9 100644
--- a/eeschema/files-io.cpp
+++ b/eeschema/files-io.cpp
@@ -43,7 +43,7 @@
 #include <sch_sheet_path.h>
 #include <sch_component.h>
 #include <wildcards_and_files_ext.h>
-#include <lib_cache_rescue.h>
+#include <project_rescue.h>
 #include <eeschema_config.h>
 
 
@@ -307,7 +307,7 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
 
         UpdateFileHistory( fullFileName );
 
-        // Check to see whether some old, cached library parts need to be rescued
+        // Check to see whether some old library parts need to be rescued
         // Only do this if RescueNeverShow was not set.
         wxConfigBase *config = Kiface().KifaceSettings();
         bool rescueNeverShow = false;
@@ -315,7 +315,7 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
 
         if( !rescueNeverShow )
         {
-            if( RescueCacheConflicts( false ) )
+            if( RescueProject( false ) )
             {
                 GetScreen()->CheckComponentsToPartsLinks();
                 GetScreen()->TestDanglingEnds();
diff --git a/eeschema/invoke_sch_dialog.h b/eeschema/invoke_sch_dialog.h
index 0a9cbe4..e4395ee 100644
--- a/eeschema/invoke_sch_dialog.h
+++ b/eeschema/invoke_sch_dialog.h
@@ -48,8 +48,7 @@ class wxDialog;
 class LIB_PART;
 class PART_LIBS;
 class SCH_COMPONENT;
-class RESCUE_CANDIDATE;
-class RESCUE_LOG;
+class RESCUER;
 
 // Often this is not used in the prototypes, since wxFrame is good enough and would
 // represent maximum information hiding.
@@ -60,12 +59,10 @@ class SCH_EDIT_FRAME;
  * This dialog asks the user which rescuable, cached parts he wants to rescue.
  * Any rejects will be pruned from aCandidates.
  * @param aCaller - the SCH_EDIT_FRAME calling this
- * @param aCandidates - the list of RESCUE_CANDIDATES
- * @param aComponents - a vector of all the components in the schematic
+ * @param aRescuer - the active RESCUER instance
  * @param aAskShowAgain - if true, a "Never Show Again" button will be included
  */
-int InvokeDialogRescueEach( SCH_EDIT_FRAME* aCaller, std::vector<RESCUE_CANDIDATE>& aCandidates,
-        std::vector<SCH_COMPONENT*>& aComponents, bool aAskShowAgain );
+int InvokeDialogRescueEach( SCH_EDIT_FRAME* aCaller, RESCUER& aRescuer, bool aAskShowAgain );
 
 /// Create and show DIALOG_ANNOTATE and return whatever
 /// DIALOG_ANNOTATE::ShowModal() returns.
diff --git a/eeschema/lib_cache_rescue.cpp b/eeschema/lib_cache_rescue.cpp
deleted file mode 100644
index da74997..0000000
--- a/eeschema/lib_cache_rescue.cpp
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * This program source code file is part of KiCad, a free EDA CAD application.
- *
- * Copyright (C) 2015 Chris Pavlina <pavlina.chris@xxxxxxxxx>
- * Copyright (C) 2015 KiCad Developers, see change_log.txt for contributors.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, you may find one here:
- * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
- * or you may search the http://www.gnu.org website for the version 2 license,
- * or you may write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
- */
-
-#include <class_drawpanel.h>
-#include <class_library.h>
-#include <confirm.h>
-#include <invoke_sch_dialog.h>
-#include <kicad_device_context.h>
-#include <lib_cache_rescue.h>
-#include <sch_component.h>
-#include <sch_sheet.h>
-#include <schframe.h>
-#include <wildcards_and_files_ext.h>
-
-#include <boost/foreach.hpp>
-#include <map>
-
-
-typedef std::pair<SCH_COMPONENT*, wxString> COMPONENT_NAME_PAIR;
-
-
-/**
- * Function save_library
- * writes the library out to disk. Returns true on success.
- *
- * @param aFileName - Filename to receive the library
- * @param aLibrary - Library to write
- * @param aEditFrame - the calling SCH_EDIT_FRAME
- */
-static bool save_library( const wxString& aFileName, PART_LIB* aLibrary, SCH_EDIT_FRAME* aEditFrame )
-{
-    try
-    {
-        FILE_OUTPUTFORMATTER formatter( aFileName );
-
-        if( !aLibrary->Save( formatter ) )
-        {
-            wxString msg = wxString::Format( _(
-                "An error occurred attempting to save component library '%s'." ),
-                GetChars( aFileName )
-                );
-            DisplayError( aEditFrame, msg );
-            return false;
-        }
-    }
-    catch( ... /* IO_ERROR ioe */ )
-    {
-        wxString msg = wxString::Format( _(
-            "Failed to create component library file '%s'" ),
-            GetChars( aFileName )
-            );
-        DisplayError( aEditFrame, msg );
-        return false;
-    }
-
-    return true;
-}
-
-
-/**
- * Function insert_library
- * inserts a library into the project and refreshes libraries.
- *
- * @param aProject - project that will be modified
- * @param aLibrary - PART_LIB to add
- * @param aIndex - index in the list at which the library is to be inserted
- *
- * @return true on success, false on failure
- */
-static bool insert_library( PROJECT *aProject, PART_LIB *aLibrary, size_t aIndex ) throw( boost::bad_pointer )
-{
-    wxArrayString libNames;
-    wxString libPaths;
-
-    wxString libName = aLibrary->GetName();
-    PART_LIBS *libs = dynamic_cast<PART_LIBS*>( aProject->GetElem( PROJECT::ELEM_SCH_PART_LIBS ) );
-    if( !libs )
-    {
-        libs = new PART_LIBS();
-        aProject->SetElem( PROJECT::ELEM_SCH_PART_LIBS, libs );
-    }
-
-    PART_LIBS::LibNamesAndPaths( aProject, false, &libPaths, &libNames );
-
-    // Make sure the library is not already in the list
-    while( libNames.Index( libName ) != wxNOT_FOUND )
-        libNames.Remove( libName );
-
-    // Add the library to the list and save
-    libNames.Insert( libName, aIndex );
-    PART_LIBS::LibNamesAndPaths( aProject, true, &libPaths, &libNames );
-
-    // Save the old libraries in case there is a problem after clear(). We'll
-    // put them back in.
-    boost::ptr_vector<PART_LIB> libsSave;
-    libsSave.transfer( libsSave.end(), libs->begin(), libs->end(), *libs );
-
-    aProject->SetElem( PROJECT::ELEM_SCH_PART_LIBS, NULL );
-
-    libs = new PART_LIBS();
-    try
-    {
-        libs->LoadAllLibraries( aProject );
-    }
-    catch( const PARSE_ERROR& e )
-    {
-        // Some libraries were not found. There's no point in showing the error,
-        // because it was already shown. Just don't do anything.
-    }
-    catch( const IO_ERROR& e )
-    {
-        // Restore the old list
-        libs->clear();
-        libs->transfer( libs->end(), libsSave.begin(), libsSave.end(), libsSave );
-        return false;
-    }
-    aProject->SetElem( PROJECT::ELEM_SCH_PART_LIBS, libs );
-
-    return true;
-}
-
-
-/**
- * Function get_components
- * Fills a vector with all of the project's components, to ease iterating over them.
- *
- * @param aComponents - a vector that will take the components
- */
-static void get_components( std::vector<SCH_COMPONENT*>& aComponents )
-{
-    SCH_SCREENS screens;
-    for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
-    {
-        for( SCH_ITEM* item = screen->GetDrawItems(); item; item = item->Next() )
-        {
-            if( item->Type() != SCH_COMPONENT_T ) continue;
-            SCH_COMPONENT* component = dynamic_cast<SCH_COMPONENT*>( item );
-            aComponents.push_back( component );
-        }
-    }
-}
-
-
-/**
- * Function find_component
- * Search the libraries for the first component with a given name.
- *
- * @param aName - name to search for
- * @param aLibs - the loaded PART_LIBS
- * @param aCached - whether we are looking for the cached part
- */
-static LIB_PART* find_component( wxString aName, PART_LIBS* aLibs, bool aCached )
-{
-    LIB_PART *part = NULL;
-
-    BOOST_FOREACH( PART_LIB& each_lib, *aLibs )
-    {
-        if( aCached && !each_lib.IsCache() )
-            continue;
-
-        if( !aCached && each_lib.IsCache() )
-            continue;
-
-        part = each_lib.FindPart( aName );
-        if( part )
-            break;
-    }
-
-    return part;
-}
-
-
-/**
- * Function find_rescues
- * Search components for any that request a part that conflicts with the
- * library parts.
- *
- * This is done from the component side to track requested aliases.
- *
- * @param aComponents - a vector of components to scan
- * @param aLibs - the loaded PART_LIBS
- * @param aCandidates - a vector to hold rescue candidates
- */
-static void find_rescues( std::vector<SCH_COMPONENT*>& aComponents, PART_LIBS* aLibs,
-        std::vector<RESCUE_CANDIDATE>& aCandidates )
-{
-    // We need to narrow down the list and avoid having multiple copies of the
-    // same name. Therefore, we'll assemble in a map first, before pushing to
-    // the vector.
-    typedef std::map<wxString, RESCUE_CANDIDATE> candidate_map_t;
-    candidate_map_t candidate_map;
-    BOOST_FOREACH( SCH_COMPONENT* each_component, aComponents )
-    {
-        wxString part_name( each_component->GetPartName() );
-        LIB_PART* cache_match = find_component( part_name, aLibs, /* aCached */ true );
-        LIB_PART* lib_match = find_component( part_name, aLibs, /* aCached */ false );
-
-        // Test whether there is a conflict
-        if( !cache_match || !lib_match )
-            continue;
-        if( !cache_match->PinsConflictWith( *lib_match, /* aTestNums */ true, /* aTestNames */ true,
-                /* aTestType */ true, /* aTestOrientation */ true, /* aTestLength */ false ))
-            continue;
-
-        RESCUE_CANDIDATE candidate;
-        candidate.requested_name = part_name;
-        candidate.cache_candidate = cache_match;
-        candidate.lib_candidate = lib_match;
-
-        candidate_map[part_name] = candidate;
-    }
-
-    // Now, dump the map into aCandidates
-    BOOST_FOREACH( const candidate_map_t::value_type& each_pair, candidate_map )
-    {
-        aCandidates.push_back( each_pair.second );
-    }
-}
-
-
-/**
- * Function create_rescue_library
- * Creates and returns a PART_LIB object for storing rescued components.
- * @param aFileName - wxFileName to receive the library's file name
- */
-static PART_LIB* create_rescue_library( wxFileName& aFileName )
-{
-    wxFileName fn( g_RootSheet->GetScreen()->GetFileName() );
-    fn.SetName( fn.GetName() + wxT( "-rescue" ) );
-    fn.SetExt( SchematicLibraryFileExtension );
-    aFileName.SetPath( fn.GetPath() );
-    aFileName.SetName( fn.GetName() );
-    aFileName.SetExt( wxT( "lib" ) );
-    return new PART_LIB( LIBRARY_TYPE_EESCHEMA, fn.GetFullPath() );
-}
-
-
-/**
- * Function rescue_components
- * Rescues components from aCandidates into aLibrary
- * @param aCandidates - list of final rescue candidates to be rescued
- * @param aLibrary - library for them to be rescued into
- * @param aSuffix - part name suffix to apply to them
- */
-static void rescue_components( std::vector<RESCUE_CANDIDATE>& aCandidates, PART_LIB* aLibrary, const wxString &aSuffix )
-{
-    BOOST_FOREACH( RESCUE_CANDIDATE& each_candidate, aCandidates )
-    {
-        LIB_PART new_part( *each_candidate.cache_candidate, aLibrary );
-        new_part.SetName( each_candidate.requested_name + aSuffix );
-        new_part.RemoveAllAliases();
-        aLibrary->AddPart( &new_part );
-    }
-}
-
-
-/**
- * Function update_components
- * Update components to reflect changed names of rescued parts.
- * Saves components with the original names to aRescueLog to allow recovering from errors and
- * displaying summary.
- *
- * @param aComponents - a populated list of all components
- * @param aCandidates - list of rescue candidates
- * @param aSuffix - part name suffix
- * @param aRescueLog - rescue log
- */
-static void update_components( std::vector<SCH_COMPONENT*>& aComponents, std::vector<RESCUE_CANDIDATE>& aCandidates,
-        const wxString& aSuffix, std::vector<RESCUE_LOG>& aRescueLog )
-{
-    BOOST_FOREACH( RESCUE_CANDIDATE& each_candidate, aCandidates )
-    {
-        BOOST_FOREACH( SCH_COMPONENT* each_component, aComponents )
-        {
-            if( each_component->GetPartName() != each_candidate.requested_name ) continue;
-
-            wxString new_name = each_candidate.requested_name + aSuffix;
-            each_component->SetPartName( new_name );
-
-            RESCUE_LOG log_item;
-            log_item.component = each_component;
-            log_item.old_name = each_candidate.requested_name;
-            log_item.new_name = new_name;
-            aRescueLog.push_back( log_item );
-        }
-    }
-}
-
-
-bool SCH_EDIT_FRAME::RescueCacheConflicts( bool aRunningOnDemand )
-{
-    // Data that will be used throughout the operation
-    std::vector<RESCUE_CANDIDATE>   candidates;
-    std::vector<SCH_COMPONENT*>     components;
-    PART_LIBS*              libs;
-    wxString                part_name_suffix;
-    PROJECT*                prj;
-
-    // Prepare data
-    get_components( components );
-    prj = &Prj();
-    libs = prj->SchLibs();
-    part_name_suffix =  wxT( "-RESCUE-" ) + prj->GetProjectName();
-
-    // Start!
-    find_rescues( components, libs, candidates );
-    if( candidates.empty() )
-    {
-        if( aRunningOnDemand )
-        {
-            wxMessageDialog dlg( this, _( "There are no conflicting symbols to rescue from the cache." ) );
-            dlg.ShowModal();
-        }
-        return true;
-    }
-    InvokeDialogRescueEach( this, candidates, components, /* aAskShowAgain */ !aRunningOnDemand );
-    wxFileName library_fn;
-    std::auto_ptr<PART_LIB> rescue_lib( create_rescue_library( library_fn ) );
-    rescue_components( candidates, rescue_lib.get(), part_name_suffix );
-
-    // If no components were rescued, let the user know what's going on. He might
-    // have clicked cancel by mistake, and should have some indication of that.
-    if( candidates.empty() )
-    {
-        wxMessageDialog dlg( this, _( "No cached symbols were rescued." ) );
-        dlg.ShowModal();
-        return true;
-    }
-
-    if( !save_library( library_fn.GetFullPath(), rescue_lib.get(), this ) )
-    {
-        // Save failed. Do not update the components.
-        return false;
-    }
-
-    // Update components to reflect changed names
-    std::vector<RESCUE_LOG> rescue_log;
-    update_components( components, candidates, part_name_suffix, rescue_log );
-
-    wxASSERT( !rescue_log.empty() );
-
-    // Try inserting the library into the project
-    if( insert_library( prj, rescue_lib.get(), 0 ) )
-    {
-        // Clean up wire ends
-        INSTALL_UNBUFFERED_DC( dc, m_canvas );
-        GetScreen()->SchematicCleanUp( NULL, &dc );
-        m_canvas->Refresh( true );
-        OnModify();
-
-        return true;
-    }
-    else
-    {
-        // Unsuccessful! Restore all the components
-        BOOST_FOREACH( RESCUE_LOG& rescue_log_item, rescue_log )
-        {
-            rescue_log_item.component->SetPartName( rescue_log_item.old_name );
-        }
-        wxMessageDialog dlg( this, _( "An error occurred while attempting to rescue components. No changes have been made." ) );
-        dlg.ShowModal();
-        return false;
-    }
-}
diff --git a/eeschema/lib_cache_rescue.h b/eeschema/lib_cache_rescue.h
deleted file mode 100644
index b84138f..0000000
--- a/eeschema/lib_cache_rescue.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * This program source code file is part of KiCad, a free EDA CAD application.
- *
- * Copyright (C) 2015 Chris Pavlina <pavlina.chris@xxxxxxxxx>
- * Copyright (C) 2015 KiCad Developers, see change_log.txt for contributors.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, you may find one here:
- * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
- * or you may search the http://www.gnu.org website for the version 2 license,
- * or you may write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
- */
-
-#ifndef _LIB_CACHE_RESCUE_H_
-#define _LIB_CACHE_RESCUE_H_
-
-/* This code handles the case where an old schematic has parts that have
- * changed in the system libraries, such that their pins no longer line up.
- * The function of note is a member of SCH_EDIT_FRAME, defined thus:
- *
- * bool SCH_EDIT_FRAME::RescueCacheConflicts( bool aSilentIfNone );
- *
- * When this is called, a list of component names referring to conflicting
- * symbols is compiled. If this list is empty, then the function displays
- * a notification and returns (if aSilentIfNone is true, the notification is
- * silenced).
- *
- * The user is then prompted to select which parts he would like to rescue.
- * Any remaining after he's through are rescued: they are renamed to avoid
- * further conflicts, and then they are copied into a new library. The
- * schematic components are updated to link to these new names, the library
- * is saved, and the library is added to the project at the top of the
- * search path.
- */
-
-#include <vector>
-#include <wx/string.h>
-
-class LIB_PART;
-class SCH_COMPONENT;
-
-class RESCUE_CANDIDATE
-{
-public:
-    wxString   requested_name;
-    LIB_PART*  cache_candidate;
-    LIB_PART*  lib_candidate;
-};
-
-class RESCUE_LOG
-{
-public:
-    SCH_COMPONENT*  component;
-    wxString        old_name;
-    wxString        new_name;
-};
-
-#endif // _LIB_CACHE_RESCUE_H_
diff --git a/eeschema/menubar.cpp b/eeschema/menubar.cpp
index d3f1205..c959c25 100644
--- a/eeschema/menubar.cpp
+++ b/eeschema/menubar.cpp
@@ -433,8 +433,8 @@ void SCH_EDIT_FRAME::ReCreateMenuBar()
 
     AddMenuItem( toolsMenu,
                  ID_RESCUE_CACHED,
-                 _( "&Rescue Cached Components" ),
-                 _( "Find old components in the project cache and rescue them to a new library" ),
+                 _( "&Rescue Old Components" ),
+                 _( "Find old components in the project and rename/rescue them" ),
                  KiBitmap( copycomponent_xpm ) );
 
     toolsMenu->AppendSeparator();
diff --git a/eeschema/project_rescue.cpp b/eeschema/project_rescue.cpp
new file mode 100644
index 0000000..7150928
--- /dev/null
+++ b/eeschema/project_rescue.cpp
@@ -0,0 +1,540 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2015 Chris Pavlina <pavlina.chris@xxxxxxxxx>
+ * Copyright (C) 2015 KiCad Developers, see change_log.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include <class_drawpanel.h>
+#include <class_library.h>
+#include <confirm.h>
+#include <invoke_sch_dialog.h>
+#include <kicad_device_context.h>
+#include <project_rescue.h>
+#include <sch_component.h>
+#include <sch_sheet.h>
+#include <schframe.h>
+#include <wildcards_and_files_ext.h>
+
+#include <boost/foreach.hpp>
+#include <map>
+
+
+typedef std::pair<SCH_COMPONENT*, wxString> COMPONENT_NAME_PAIR;
+
+
+/**
+ * Function save_library
+ * writes the library out to disk. Returns true on success.
+ *
+ * @param aFileName - Filename to receive the library
+ * @param aLibrary - Library to write
+ * @param aEditFrame - the calling SCH_EDIT_FRAME
+ */
+static bool save_library( const wxString& aFileName, PART_LIB* aLibrary, SCH_EDIT_FRAME* aEditFrame )
+{
+    try
+    {
+        FILE_OUTPUTFORMATTER formatter( aFileName );
+
+        if( !aLibrary->Save( formatter ) )
+        {
+            wxString msg = wxString::Format( _(
+                "An error occurred attempting to save component library '%s'." ),
+                GetChars( aFileName )
+                );
+            DisplayError( aEditFrame, msg );
+            return false;
+        }
+    }
+    catch( ... /* IO_ERROR ioe */ )
+    {
+        wxString msg = wxString::Format( _(
+            "Failed to create component library file '%s'" ),
+            GetChars( aFileName )
+            );
+        DisplayError( aEditFrame, msg );
+        return false;
+    }
+
+    return true;
+}
+
+
+/**
+ * Function insert_library
+ * inserts a library into the project and refreshes libraries.
+ *
+ * @param aProject - project that will be modified
+ * @param aLibrary - PART_LIB to add
+ * @param aIndex - index in the list at which the library is to be inserted
+ *
+ * @return true on success, false on failure
+ */
+static bool insert_library( PROJECT *aProject, PART_LIB *aLibrary, size_t aIndex ) throw( boost::bad_pointer )
+{
+    wxArrayString libNames;
+    wxString libPaths;
+
+    wxString libName = aLibrary->GetName();
+    PART_LIBS *libs = dynamic_cast<PART_LIBS*>( aProject->GetElem( PROJECT::ELEM_SCH_PART_LIBS ) );
+    if( !libs )
+    {
+        libs = new PART_LIBS();
+        aProject->SetElem( PROJECT::ELEM_SCH_PART_LIBS, libs );
+    }
+
+    PART_LIBS::LibNamesAndPaths( aProject, false, &libPaths, &libNames );
+
+    // Make sure the library is not already in the list
+    while( libNames.Index( libName ) != wxNOT_FOUND )
+        libNames.Remove( libName );
+
+    // Add the library to the list and save
+    libNames.Insert( libName, aIndex );
+    PART_LIBS::LibNamesAndPaths( aProject, true, &libPaths, &libNames );
+
+    // Save the old libraries in case there is a problem after clear(). We'll
+    // put them back in.
+    boost::ptr_vector<PART_LIB> libsSave;
+    libsSave.transfer( libsSave.end(), libs->begin(), libs->end(), *libs );
+
+    aProject->SetElem( PROJECT::ELEM_SCH_PART_LIBS, NULL );
+
+    libs = new PART_LIBS();
+    try
+    {
+        libs->LoadAllLibraries( aProject );
+    }
+    catch( const PARSE_ERROR& e )
+    {
+        // Some libraries were not found. There's no point in showing the error,
+        // because it was already shown. Just don't do anything.
+    }
+    catch( const IO_ERROR& e )
+    {
+        // Restore the old list
+        libs->clear();
+        libs->transfer( libs->end(), libsSave.begin(), libsSave.end(), libsSave );
+        return false;
+    }
+    aProject->SetElem( PROJECT::ELEM_SCH_PART_LIBS, libs );
+
+    return true;
+}
+
+
+/**
+ * Function get_components
+ * Fills a vector with all of the project's components, to ease iterating over them.
+ *
+ * @param aComponents - a vector that will take the components
+ */
+static void get_components( std::vector<SCH_COMPONENT*>& aComponents )
+{
+    SCH_SCREENS screens;
+    for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
+    {
+        for( SCH_ITEM* item = screen->GetDrawItems(); item; item = item->Next() )
+        {
+            if( item->Type() != SCH_COMPONENT_T ) continue;
+            SCH_COMPONENT* component = dynamic_cast<SCH_COMPONENT*>( item );
+            aComponents.push_back( component );
+        }
+    }
+}
+
+
+/**
+ * Function find_component
+ * Search the libraries for the first component with a given name.
+ *
+ * @param aName - name to search for
+ * @param aLibs - the loaded PART_LIBS
+ * @param aCached - whether we are looking for the cached part
+ */
+static LIB_PART* find_component( wxString aName, PART_LIBS* aLibs, bool aCached )
+{
+    LIB_PART *part = NULL;
+
+    BOOST_FOREACH( PART_LIB& each_lib, *aLibs )
+    {
+        if( aCached && !each_lib.IsCache() )
+            continue;
+
+        if( !aCached && each_lib.IsCache() )
+            continue;
+
+        part = each_lib.FindPart( aName );
+        if( part )
+            break;
+    }
+
+    return part;
+}
+
+
+void RESCUER::RemoveDuplicates()
+{
+    std::vector<wxString> names_seen;
+
+    for( boost::ptr_vector<RESCUE_CANDIDATE>::iterator it = m_all_candidates.begin();
+            it != m_all_candidates.end(); )
+    {
+        bool seen_already = false;
+        BOOST_FOREACH( wxString& name_seen, names_seen )
+        {
+            if( name_seen == it->GetRequestedName() )
+            {
+                seen_already = true;
+                break;
+            }
+        }
+
+        if( seen_already )
+        {
+            it = m_all_candidates.erase( it );
+        }
+        else
+        {
+            names_seen.push_back( it->GetRequestedName() );
+            ++it;
+        }
+    }
+}
+
+
+class RESCUE_CASE_CANDIDATE: public RESCUE_CANDIDATE
+{
+    wxString m_requested_name;
+    wxString m_new_name;
+    LIB_PART* m_lib_candidate;
+
+public:
+    /**
+     * Function FindRescues
+     * Grab all possible RESCUE_CASE_CANDIDATES into a vector.
+     * @param aRescuer - the working RESCUER instance.
+     * @param aCandidates - the vector the will hold the candidates.
+     */
+    static void FindRescues( RESCUER& aRescuer, boost::ptr_vector<RESCUE_CANDIDATE>& aCandidates )
+    {
+        typedef std::map<wxString, RESCUE_CASE_CANDIDATE> candidate_map_t;
+        candidate_map_t candidate_map;
+
+        BOOST_FOREACH( SCH_COMPONENT* each_component, *( aRescuer.GetComponents() ) )
+        {
+            wxString part_name( each_component->GetPartName() );
+
+            LIB_PART* case_sensitive_match = find_component( part_name, aRescuer.GetLibs(), /* aCached */ true );
+            std::vector<LIB_ALIAS*> case_insensitive_matches;
+            aRescuer.GetLibs()->FindLibraryNearEntries( case_insensitive_matches, part_name );
+
+            if( case_sensitive_match || !( case_insensitive_matches.size() ) )
+                continue;
+
+            RESCUE_CASE_CANDIDATE candidate(
+                part_name, case_insensitive_matches[0]->GetName(),
+                case_insensitive_matches[0]->GetPart() );
+            candidate_map[part_name] = candidate;
+        }
+
+        // Now, dump the map into aCandidates
+        BOOST_FOREACH( const candidate_map_t::value_type& each_pair, candidate_map )
+        {
+            aCandidates.push_back( new RESCUE_CASE_CANDIDATE( each_pair.second ) );
+        }
+    }
+
+    /**
+     * Constructor RESCUE_CANDIDATE
+     * @param aRequestedName - the name the schematic asks for
+     * @param aNewName - the name we want to change it to
+     * @param aLibCandidate - the part that will give us
+     */
+    RESCUE_CASE_CANDIDATE( const wxString& aRequestedName, const wxString& aNewName,
+            LIB_PART* aLibCandidate )
+        : m_requested_name( aRequestedName ),
+          m_new_name( aNewName ),
+          m_lib_candidate( aLibCandidate ) { }
+
+    RESCUE_CASE_CANDIDATE() {}
+
+    virtual wxString GetRequestedName() const { return m_requested_name; }
+    virtual wxString GetNewName() const { return m_new_name; }
+    virtual LIB_PART* GetLibCandidate() const { return m_lib_candidate; }
+    virtual wxString GetActionDescription() const
+    {
+        wxString action;
+        action.Printf( _( "Rename to %s" ), m_new_name );
+        return action;
+    }
+
+    virtual bool PerformAction( RESCUER* aRescuer )
+    {
+        BOOST_FOREACH( SCH_COMPONENT* each_component, *aRescuer->GetComponents() )
+        {
+            if( each_component->GetPartName() != m_requested_name ) continue;
+            each_component->SetPartName( m_new_name );
+            aRescuer->LogRescue( each_component, m_requested_name, m_new_name );
+        }
+        return true;
+    }
+};
+
+
+class RESCUE_CACHE_CANDIDATE: public RESCUE_CANDIDATE
+{
+    wxString m_requested_name;
+    wxString m_new_name;
+    LIB_PART* m_cache_candidate;
+    LIB_PART* m_lib_candidate;
+
+    static std::auto_ptr<PART_LIB> m_rescue_lib;
+    static wxFileName m_library_fn;
+
+public:
+    /**
+     * Function FindRescues
+     * Grab all possible RESCUE_CACHE_CANDIDATEs into a vector.
+     * @param aRescuer - the working RESCUER instance.
+     * @param aCandidates - the vector the will hold the candidates.
+     */
+    static void FindRescues( RESCUER& aRescuer, boost::ptr_vector<RESCUE_CANDIDATE>& aCandidates )
+    {
+        typedef std::map<wxString, RESCUE_CACHE_CANDIDATE> candidate_map_t;
+        candidate_map_t candidate_map;
+
+        wxString part_name_suffix = wxT( "-RESCUE-" ) + aRescuer.GetPrj()->GetProjectName();
+
+        BOOST_FOREACH( SCH_COMPONENT* each_component, *( aRescuer.GetComponents() ) )
+        {
+            wxString part_name( each_component->GetPartName() );
+
+            LIB_PART* cache_match = find_component( part_name, aRescuer.GetLibs(), /* aCached */ true );
+            LIB_PART* lib_match = find_component( part_name, aRescuer.GetLibs(), /* aCached */ false );
+
+            // Test whether there is a conflict
+            if( !cache_match || !lib_match )
+                continue;
+            if( !cache_match->PinsConflictWith( *lib_match, /* aTestNums */ true,
+                    /* aTestNames */ true, /* aTestType */ true, /* aTestOrientation */ true,
+                    /* aTestLength */ false ))
+                continue;
+
+            RESCUE_CACHE_CANDIDATE candidate(
+                part_name, part_name + part_name_suffix,
+                cache_match, lib_match );
+            candidate_map[part_name] = candidate;
+        }
+
+        // Now, dump the map into aCandidates
+        BOOST_FOREACH( const candidate_map_t::value_type& each_pair, candidate_map )
+        {
+            aCandidates.push_back( new RESCUE_CACHE_CANDIDATE( each_pair.second ) );
+        }
+    }
+
+    /**
+     * Constructor RESCUE_CACHE_CANDIDATE
+     * @param aRequestedName - the name the schematic asks for
+     * @param aNewName - the name we want to change it to
+     * @param aCacheCandidate - the part from the cache
+     * @param aLibCandidate - the part that would be loaded from the library
+     */
+    RESCUE_CACHE_CANDIDATE( const wxString& aRequestedName, const wxString& aNewName,
+            LIB_PART* aCacheCandidate, LIB_PART* aLibCandidate )
+        : m_requested_name( aRequestedName ),
+          m_new_name( aNewName ),
+          m_cache_candidate( aCacheCandidate ),
+          m_lib_candidate( aLibCandidate ) { }
+
+    RESCUE_CACHE_CANDIDATE() {}
+
+    virtual wxString GetRequestedName() const { return m_requested_name; }
+    virtual wxString GetNewName() const { return m_new_name; }
+    virtual LIB_PART* GetCacheCandidate() const { return m_cache_candidate; }
+    virtual LIB_PART* GetLibCandidate() const { return m_lib_candidate; }
+    virtual wxString GetActionDescription() const
+    {
+        wxString action;
+        action.Printf( _( "Rescue %s as %s" ), m_requested_name, m_new_name );
+        return action;
+    }
+
+    /**
+     * Function OpenRescueLibrary
+     * Creates the new rescue library. Must be called before calling any PerformAction()s.
+     */
+    static void OpenRescueLibrary()
+    {
+        wxFileName fn( g_RootSheet->GetScreen()->GetFileName() );
+        fn.SetName( fn.GetName() + wxT( "-rescue" ) );
+        fn.SetExt( SchematicLibraryFileExtension );
+        m_library_fn.SetPath( fn.GetPath() );
+        m_library_fn.SetName( fn.GetName() );
+        m_library_fn.SetExt( wxT( "lib" ) );
+
+        std::auto_ptr<PART_LIB> rescue_lib( new PART_LIB( LIBRARY_TYPE_EESCHEMA,
+                        fn.GetFullPath() ) );
+
+        m_rescue_lib = rescue_lib;
+    }
+
+    virtual bool PerformAction( RESCUER* aRescuer )
+    {
+        LIB_PART new_part( *m_cache_candidate, m_rescue_lib.get() );
+        new_part.SetName( m_new_name );
+        new_part.RemoveAllAliases();
+        RESCUE_CACHE_CANDIDATE::m_rescue_lib.get()->AddPart( &new_part );
+
+        BOOST_FOREACH( SCH_COMPONENT* each_component, *aRescuer->GetComponents() )
+        {
+            if( each_component->GetPartName() != m_requested_name ) continue;
+            each_component->SetPartName( m_new_name );
+            aRescuer->LogRescue( each_component, m_requested_name, m_new_name );
+        }
+        return true;
+    }
+
+    /**
+     * Function WriteRescueLibrary
+     * Writes out the rescue library. Called after successful PerformAction()s. If this fails,
+     * undo the actions.
+     * @return True on success.
+     */
+    static bool WriteRescueLibrary( SCH_EDIT_FRAME *aEditFrame, PROJECT* aProject )
+    {
+
+        if( !save_library( m_library_fn.GetFullPath(), m_rescue_lib.get(), aEditFrame ) )
+            return false;
+        return insert_library( aProject, m_rescue_lib.get(), 0 );
+    }
+};
+
+std::auto_ptr<PART_LIB> RESCUE_CACHE_CANDIDATE::m_rescue_lib;
+wxFileName RESCUE_CACHE_CANDIDATE::m_library_fn;
+
+RESCUER::RESCUER( SCH_EDIT_FRAME& aEditFrame, PROJECT& aProject )
+{
+    get_components( m_components );
+    m_prj = &aProject;
+    m_libs = m_prj->SchLibs();
+    m_edit_frame = &aEditFrame;
+}
+
+
+void RESCUER::FindCandidates()
+{
+    RESCUE_CASE_CANDIDATE::FindRescues( *this, m_all_candidates );
+    RESCUE_CACHE_CANDIDATE::FindRescues( *this, m_all_candidates );
+}
+
+
+void RESCUER::InvokeDialog( bool aAskShowAgain )
+{
+    InvokeDialogRescueEach( m_edit_frame, *this, aAskShowAgain );
+}
+
+
+void RESCUER::LogRescue( SCH_COMPONENT *aComponent, const wxString &aOldName,
+        const wxString &aNewName )
+{
+    RESCUE_LOG logitem;
+    logitem.component = aComponent;
+    logitem.old_name = aOldName;
+    logitem.new_name = aNewName;
+    m_rescue_log.push_back( logitem );
+}
+
+
+bool RESCUER::DoRescues()
+{
+    BOOST_FOREACH( RESCUE_CANDIDATE* each_candidate, m_chosen_candidates )
+    {
+        if( ! each_candidate->PerformAction( this ) )
+            return false;
+    }
+    return true;
+}
+
+
+void RESCUER::UndoRescues()
+{
+    BOOST_FOREACH( RESCUE_LOG& each_logitem, m_rescue_log )
+    {
+        each_logitem.component->SetPartName( each_logitem.old_name );
+    }
+}
+
+
+bool SCH_EDIT_FRAME::RescueProject( bool aRunningOnDemand )
+{
+    // Data that will be used throughout the operation
+    std::vector<RESCUE_CANDIDATE>   candidates;
+    wxString                part_name_suffix;
+
+    RESCUER rescuer( *this, Prj() );
+
+    rescuer.FindCandidates();
+
+    if( ! rescuer.GetCandidateCount() )
+    {
+        if( aRunningOnDemand )
+        {
+            wxMessageDialog dlg( this, _( "This project has nothing to rescue." ) );
+            dlg.ShowModal();
+        }
+        return true;
+    }
+
+    rescuer.RemoveDuplicates();
+
+    rescuer.InvokeDialog( !aRunningOnDemand );
+
+    // If no components were rescued, let the user know what's going on. He might
+    // have clicked cancel by mistake, and should have some indication of that.
+    if( !rescuer.GetChosenCandidateCount() )
+    {
+        wxMessageDialog dlg( this, _( "No symbols were rescued." ) );
+        dlg.ShowModal();
+        return true;
+    }
+
+    RESCUE_CACHE_CANDIDATE::OpenRescueLibrary();
+
+    if( !rescuer.DoRescues() )
+    {
+        rescuer.UndoRescues();
+        return false;
+    }
+
+    RESCUE_CACHE_CANDIDATE::WriteRescueLibrary( this, &Prj() );
+
+    Prj().SetElem( PROJECT::ELEM_SCH_PART_LIBS, NULL );
+
+    // Clean up wire ends
+    INSTALL_UNBUFFERED_DC( dc, m_canvas );
+    GetScreen()->SchematicCleanUp( NULL, &dc );
+    m_canvas->Refresh( true );
+    OnModify();
+
+    return true;
+}
diff --git a/eeschema/project_rescue.h b/eeschema/project_rescue.h
new file mode 100644
index 0000000..bc961da
--- /dev/null
+++ b/eeschema/project_rescue.h
@@ -0,0 +1,190 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2015 Chris Pavlina <pavlina.chris@xxxxxxxxx>
+ * Copyright (C) 2015 KiCad Developers, see change_log.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef _LIB_CACHE_RESCUE_H_
+#define _LIB_CACHE_RESCUE_H_
+
+/* This code handles the case where an old schematic was made before
+ * various changes were made, either to KiCad or to the libraries, and
+ * the project needs to be recovered. The function of note is a member
+ * of SCH_EDIT_FRAME, defined thus:
+ *
+ * bool SCH_EDIT_FRAME::RescueProject( bool aSilentIfNone );
+ *
+ * When this is called, a list of problematic components is compiled. If
+ * this list is empty, then the function displays a notification and returns
+ * (if aSilentIfNone is true, the notification is silenced).
+ */
+
+#include <schframe.h>
+
+#include <vector>
+#include <wx/string.h>
+#include <boost/ptr_container/ptr_vector.hpp>
+
+class LIB_PART;
+class SCH_COMPONENT;
+class RESCUER;
+
+enum RESCUE_TYPE
+{
+    RESCUE_CONFLICT,
+    RESCUE_CASE,
+};
+
+class RESCUE_CANDIDATE
+{
+public:
+    /**
+     * Function GetRequestedName
+     * Get the name that was originally requested in the schematic
+     */
+    virtual wxString GetRequestedName() const = 0;
+
+    /**
+     * Function GetNewName
+     * Get the name we're proposing changing it to
+     */
+    virtual wxString GetNewName() const = 0;
+
+    /**
+     * Function GetCacheCandidate
+     * Get the part that can be loaded from the project cache, if possible, or
+     * else NULL.
+     */
+    virtual LIB_PART* GetCacheCandidate() const { return NULL; }
+
+    /**
+     * Function GetLibCandidate
+     * Get the part the would be loaded from the libraries, if possible, or else
+     * NULL.
+     */
+    virtual LIB_PART* GetLibCandidate() const { return NULL; }
+
+    /**
+     * Function GetActionDescription
+     * Get a description of the action proposed, for displaying in the UI.
+     */
+    virtual wxString GetActionDescription() const = 0;
+
+    /**
+     * Function PerformAction
+     * Perform the actual rescue action. If successful, this must log the rescue using
+     * RESCUER::LogRescue to allow it to be reversed.
+     * @return True on success.
+     */
+    virtual bool PerformAction( RESCUER* aRescuer ) = 0;
+};
+
+class RESCUE_LOG
+{
+public:
+    SCH_COMPONENT*  component;
+    wxString        old_name;
+    wxString        new_name;
+};
+
+class RESCUER
+{
+    friend class DIALOG_RESCUE_EACH;
+
+    std::vector<SCH_COMPONENT*> m_components;
+    PART_LIBS* m_libs;
+    PROJECT* m_prj;
+    SCH_EDIT_FRAME* m_edit_frame;
+
+    boost::ptr_vector<RESCUE_CANDIDATE> m_all_candidates;
+    std::vector<RESCUE_CANDIDATE*> m_chosen_candidates;
+
+    std::vector<RESCUE_LOG> m_rescue_log;
+
+public:
+    RESCUER( SCH_EDIT_FRAME& aEditFrame, PROJECT& aProject );
+
+    /**
+     * Function FindCandidates
+     * Populate the RESCUER with all possible candidates.
+     */
+    void FindCandidates();
+
+    /**
+     * Function RemoveDuplicates
+     * Filter out duplicately named rescue candidates.
+     */
+    void RemoveDuplicates();
+
+    /**
+     * Function GetCandidateCount
+     */
+    size_t GetCandidateCount() { return m_all_candidates.size(); }
+
+    /**
+     * Function GetChosenCandidateCount
+     */
+    size_t GetChosenCandidateCount() { return m_chosen_candidates.size(); }
+
+    /**
+     * Function GetComponents
+     */
+    std::vector<SCH_COMPONENT*>* GetComponents() { return &m_components; }
+
+    /**
+     * Function GetLibs
+     */
+    PART_LIBS* GetLibs() { return m_libs; }
+
+    /**
+     * Function GetPrj
+     */
+    PROJECT* GetPrj() { return m_prj; }
+
+    /**
+     * Function InvokeDialog
+     * Display a dialog to allow the user to select rescues.
+     * @param aAskShowAgain - whether the "Never Show Again" button should be visible
+     */
+    void InvokeDialog( bool aAskShowAgain );
+
+    /**
+     * Function LogRescue
+     * Used by individual RESCUE_CANDIDATEs to log a rescue for undoing.
+     */
+    void LogRescue( SCH_COMPONENT *aComponent, const wxString& aOldName,
+            const wxString& aNewName );
+
+    /**
+     * Function DoRescues
+     * Perform all chosen rescue actions, logging them to be undone if necessary.
+     * @return True on success
+     */
+    bool DoRescues();
+
+    /**
+     * Function UndoRescues
+     * Reverse the effects of all rescues on the project.
+     */
+    void UndoRescues();
+};
+
+#endif // _LIB_CACHE_RESCUE_H_
diff --git a/eeschema/schframe.cpp b/eeschema/schframe.cpp
index 769d6cd..9053a56 100644
--- a/eeschema/schframe.cpp
+++ b/eeschema/schframe.cpp
@@ -243,7 +243,7 @@ BEGIN_EVENT_TABLE( SCH_EDIT_FRAME, EDA_DRAW_FRAME )
     EVT_TOOL( ID_RUN_LIBRARY, SCH_EDIT_FRAME::OnOpenLibraryEditor )
     EVT_TOOL( ID_POPUP_SCH_CALL_LIBEDIT_AND_LOAD_CMP, SCH_EDIT_FRAME::OnOpenLibraryEditor )
     EVT_TOOL( ID_TO_LIBVIEW, SCH_EDIT_FRAME::OnOpenLibraryViewer )
-    EVT_TOOL( ID_RESCUE_CACHED, SCH_EDIT_FRAME::OnRescueCached )
+    EVT_TOOL( ID_RESCUE_CACHED, SCH_EDIT_FRAME::OnRescueProject )
 
     EVT_TOOL( ID_RUN_PCB, SCH_EDIT_FRAME::OnOpenPcbnew )
     EVT_TOOL( ID_RUN_PCB_MODULE_EDITOR, SCH_EDIT_FRAME::OnOpenPcbModuleEditor )
@@ -1120,9 +1120,9 @@ void SCH_EDIT_FRAME::OnOpenLibraryEditor( wxCommandEvent& event )
     }
 }
 
-void SCH_EDIT_FRAME::OnRescueCached( wxCommandEvent& event )
+void SCH_EDIT_FRAME::OnRescueProject( wxCommandEvent& event )
 {
-    RescueCacheConflicts( true );
+    RescueProject( true );
 }
 
 void SCH_EDIT_FRAME::OnExit( wxCommandEvent& event )
diff --git a/eeschema/schframe.h b/eeschema/schframe.h
index 9f55bb1..38fb7e5 100644
--- a/eeschema/schframe.h
+++ b/eeschema/schframe.h
@@ -832,7 +832,7 @@ private:
     void OnOpenPcbModuleEditor( wxCommandEvent& event );
     void OnOpenCvpcb( wxCommandEvent& event );
     void OnOpenLibraryEditor( wxCommandEvent& event );
-    void OnRescueCached( wxCommandEvent& event );
+    void OnRescueProject( wxCommandEvent& event );
     void OnPreferencesOptions( wxCommandEvent& event );
     void OnCancelCurrentCommand( wxCommandEvent& aEvent );
 
@@ -1307,10 +1307,13 @@ public:
     bool CreateArchiveLibrary( const wxString& aFileName );
 
     /**
-     * Function RescueCacheConflicts
-     * exports components from the '-cache' library that conflict with parts
-     * in the project libraries to a new library, renaming them to add a suffix
-     * of the root document's name to avoid conflicts.
+     * Function RescueProject
+     * performs rescue operations to recover old projects from before certain
+     * changes were made.
+     *
+     * - Exports cached symbols that conflict with new symbols to a separate
+     *   library
+     * - Renames symbols named before libraries were case sensitive
      *
      * @param aRunningOnDemand - indicates whether the tool has been called up by the user
      *      (as opposed to being run automatically). If true, an information dialog is
@@ -1318,7 +1321,7 @@ public:
      *      if there are no components to rescue, and a "Never Show Again" button is
      *      displayed.
      */
-    bool RescueCacheConflicts( bool aRunningOnDemand );
+    bool RescueProject( bool aRunningOnDemand );
 
     /**
      * Function PrintPage

References