Browse Source

Site import from an INI file (Issue 2290)

Issue 2290 – Easier moving of site configuration between machines
https://winscp.net/tracker/2290

Source commit: 58e02521b5191ddd407922f989d351f9578cbc4d
Martin Prikryl 1 year ago
parent
commit
8b120da856

+ 33 - 0
source/core/Configuration.cpp

@@ -1846,6 +1846,39 @@ TStoredSessionList * TConfiguration::SelectOpensshSessionsForImport(
   return ImportSessionList.release();
 }
 //---------------------------------------------------------------------------
+TStoredSessionList * TConfiguration::SelectSessionsForImport(
+  TStoredSessionList * Sessions, const UnicodeString & FileName, UnicodeString & Error)
+{
+  std::unique_ptr<TStoredSessionList> ImportSessionList(CreateSessionsForImport(Sessions));
+
+  try
+  {
+    if (FileName.IsEmpty())
+    {
+      throw Exception(LoadStr(INI_SELECT));
+    }
+    else
+    {
+      std::unique_ptr<THierarchicalStorage> ImportStorage(TIniFileStorage::CreateFromPath(FileName));
+      ImportStorage->AccessMode = smRead;
+
+      if (ImportStorage->OpenSubKey(Configuration->StoredSessionsSubKey, false))
+      {
+        ImportSessionList->Load(ImportStorage.get());
+      }
+
+      UnicodeString NoSessionsError = FMTLOAD(INI_NO_SITES, (FileName));
+      SelectSessionsToImportIfAny(ImportSessionList.get(), Sessions, Error, NoSessionsError);
+    }
+  }
+  catch (Exception & E)
+  {
+    Error = E.Message;
+  }
+
+  return ImportSessionList.release();
+}
+//---------------------------------------------------------------------------
 void __fastcall TConfiguration::SetRandomSeedFile(UnicodeString value)
 {
   if (RandomSeedFile != value)

+ 1 - 0
source/core/Configuration.h

@@ -336,6 +336,7 @@ public:
   TStoredSessionList * __fastcall SelectKnownHostsSessionsForImport(
     TStrings * Lines, TStoredSessionList * Sessions, UnicodeString & Error);
   TStoredSessionList * SelectOpensshSessionsForImport(TStoredSessionList * Sessions, UnicodeString & Error);
+  TStoredSessionList * SelectSessionsForImport(TStoredSessionList * Sessions, const UnicodeString & FileName, UnicodeString & Error);
   UnicodeString GetPuttySessionsKey(const UnicodeString & RootKey);
   void RefreshPuttySshHostCAList();
 

+ 16 - 8
source/core/SessionData.cpp

@@ -5216,9 +5216,10 @@ void __fastcall TStoredSessionList::SelectAll(bool Select)
     Sessions[Index]->Selected = Select;
 }
 //---------------------------------------------------------------------
-void __fastcall TStoredSessionList::Import(TStoredSessionList * From,
+bool TStoredSessionList::Import(TStoredSessionList * From,
   bool OnlySelected, TList * Imported)
 {
+  bool Result = false;
   for (int Index = 0; Index < From->Count; Index++)
   {
     if (!OnlySelected || From->Sessions[Index]->Selected)
@@ -5228,6 +5229,7 @@ void __fastcall TStoredSessionList::Import(TStoredSessionList * From,
       Session->Modified = true;
       Session->MakeUniqueIn(this);
       Add(Session);
+      Result = true;
       if (Imported != NULL)
       {
         Imported->Add(Session);
@@ -5236,6 +5238,7 @@ void __fastcall TStoredSessionList::Import(TStoredSessionList * From,
   }
   // only modified, explicit
   Save(false, true);
+  return Result;
 }
 //---------------------------------------------------------------------
 void __fastcall TStoredSessionList::SelectSessionsToImport
@@ -5469,9 +5472,7 @@ void __fastcall TStoredSessionList::SetDefaultSettings(TSessionData * value)
 //---------------------------------------------------------------------------
 bool __fastcall TStoredSessionList::OpenHostKeysSubKey(THierarchicalStorage * Storage, bool CanCreate)
 {
-  return
-    Storage->OpenRootKey(CanCreate) &&
-    Storage->OpenSubKey(Configuration->SshHostKeysSubKey, CanCreate);
+  return Storage->OpenSubKey(Configuration->SshHostKeysSubKey, CanCreate);
 }
 //---------------------------------------------------------------------------
 THierarchicalStorage * __fastcall TStoredSessionList::CreateHostKeysStorageForWritting()
@@ -5483,7 +5484,7 @@ THierarchicalStorage * __fastcall TStoredSessionList::CreateHostKeysStorageForWr
   return Storage.release();
 }
 //---------------------------------------------------------------------------
-int __fastcall TStoredSessionList::ImportHostKeys(
+int TStoredSessionList::ImportHostKeys(
   THierarchicalStorage * SourceStorage, THierarchicalStorage * TargetStorage, TStoredSessionList * Sessions, bool OnlySelected)
 {
   int Result = 0;
@@ -5515,13 +5516,20 @@ int __fastcall TStoredSessionList::ImportHostKeys(
   return Result;
 }
 //---------------------------------------------------------------------------
-void __fastcall TStoredSessionList::ImportHostKeys(
-  const UnicodeString & SourceKey, TStoredSessionList * Sessions, bool OnlySelected)
+void TStoredSessionList::ImportHostKeys(
+  THierarchicalStorage * SourceStorage, TStoredSessionList * Sessions, bool OnlySelected)
 {
   std::unique_ptr<THierarchicalStorage> TargetStorage(CreateHostKeysStorageForWritting());
+
+  ImportHostKeys(SourceStorage, TargetStorage.get(), Sessions, OnlySelected);
+}
+//---------------------------------------------------------------------------
+void TStoredSessionList::ImportHostKeys(
+  const UnicodeString & SourceKey, TStoredSessionList * Sessions, bool OnlySelected)
+{
   std::unique_ptr<THierarchicalStorage> SourceStorage(new TRegistryStorage(SourceKey));
 
-  ImportHostKeys(SourceStorage.get(), TargetStorage.get(), Sessions, OnlySelected);
+  ImportHostKeys(SourceStorage.get(), Sessions, OnlySelected);
 }
 //---------------------------------------------------------------------------
 void __fastcall TStoredSessionList::ImportSelectedKnownHosts(TStoredSessionList * Sessions)

+ 4 - 4
source/core/SessionData.h

@@ -765,7 +765,7 @@ public:
     bool UseDefaults = false, bool PuttyImport = false);
   void __fastcall Save(THierarchicalStorage * Storage, bool All = false);
   void __fastcall SelectAll(bool Select);
-  void __fastcall Import(TStoredSessionList * From, bool OnlySelected, TList * Imported);
+  bool Import(TStoredSessionList * From, bool OnlySelected, TList * Imported);
   void __fastcall RecryptPasswords(TStrings * RecryptPasswordErrors);
   TSessionData * __fastcall AtSession(int Index)
     { return (TSessionData*)AtObject(Index); }
@@ -792,10 +792,10 @@ public:
   __property TSessionData * Sessions[int Index]  = { read=AtSession };
   __property TSessionData * DefaultSettings  = { read=FDefaultSettings, write=SetDefaultSettings };
 
-  static int __fastcall ImportHostKeys(
+  static int ImportHostKeys(
     THierarchicalStorage * SourceStorage, THierarchicalStorage * TargetStorage, TStoredSessionList * Sessions, bool OnlySelected);
-  static void __fastcall ImportHostKeys(
-    const UnicodeString & SourceKey, TStoredSessionList * Sessions, bool OnlySelected);
+  static void ImportHostKeys(THierarchicalStorage * SourceStorage, TStoredSessionList * Sessions, bool OnlySelected);
+  static void ImportHostKeys(const UnicodeString & SourceKey, TStoredSessionList * Sessions, bool OnlySelected);
   static void __fastcall ImportSelectedKnownHosts(TStoredSessionList * Sessions);
   static bool __fastcall OpenHostKeysSubKey(THierarchicalStorage * Storage, bool CanCreate);
   static void SelectKnownHostsForSelectedSessions(TStoredSessionList * KnownHosts, TStoredSessionList * Sessions);

+ 46 - 5
source/forms/ImportSessions.cpp

@@ -20,10 +20,14 @@
 #pragma resource "*.dfm"
 //---------------------------------------------------------------------
 const int OpensshIndex = 3;
-const int KnownHostsIndex = 4;
+const int KnownHostsIndex = 5;
+const int IniIndex = 4;
 //---------------------------------------------------------------------
 bool __fastcall DoImportSessionsDialog(TList * Imported)
 {
+  std::unique_ptr<TImportSessionsDialog> ImportSessionsDialog(
+    SafeFormCreate<TImportSessionsDialog>(Application));
+
   std::unique_ptr<TStrings> Errors(new TStringList());
   std::unique_ptr<TList> SessionListsList(new TList());
   UnicodeString Error;
@@ -48,6 +52,11 @@ bool __fastcall DoImportSessionsDialog(TList * Imported)
   SessionListsList->Add(OpensshImportSessionList.get());
   Errors->Add(Error);
 
+  std::unique_ptr<TStoredSessionList> IniImportSessionList(ImportSessionsDialog->SelectSessionsForImport(Error));
+  DebugAssert(IniIndex == SessionListsList->Count);
+  SessionListsList->Add(IniImportSessionList.get());
+  Errors->Add(Error);
+
   std::unique_ptr<TStoredSessionList> KnownHostsImportSessionList(
     Configuration->SelectKnownHostsSessionsForImport(StoredSessions, Error));
   DebugAssert(KnownHostsIndex == SessionListsList->Count);
@@ -56,9 +65,6 @@ bool __fastcall DoImportSessionsDialog(TList * Imported)
 
   DebugAssert(SessionListsList->Count == Errors->Count);
 
-  std::unique_ptr<TImportSessionsDialog> ImportSessionsDialog(
-    SafeFormCreate<TImportSessionsDialog>(Application));
-
   ImportSessionsDialog->Init(SessionListsList.get(), Errors.get());
 
   bool Result = ImportSessionsDialog->Execute();
@@ -69,6 +75,8 @@ bool __fastcall DoImportSessionsDialog(TList * Imported)
     TInstantOperationVisualizer Visualizer;
 
     UnicodeString PuttyHostKeysSourceKey = OriginalPuttyRegistryStorageKey;
+    TStoredSessionList * AIniImportSessionList =
+      static_cast<TStoredSessionList *>(SessionListsList->Items[IniIndex]);
     TStoredSessionList * AKnownHostsImportSessionList =
       static_cast<TStoredSessionList *>(SessionListsList->Items[KnownHostsIndex]);
 
@@ -83,9 +91,15 @@ bool __fastcall DoImportSessionsDialog(TList * Imported)
     TStoredSessionList::ImportHostKeys(PuttyHostKeysSourceKey, FilezillaImportSessionList.get(), true);
 
     StoredSessions->Import(OpensshImportSessionList.get(), true, Imported);
+
+    if (StoredSessions->Import(AIniImportSessionList, true, Imported))
+    {
+      std::unique_ptr<THierarchicalStorage> IniStorage(TIniFileStorage::CreateFromPath(ImportSessionsDialog->IniFileName));
+      TStoredSessionList::ImportHostKeys(IniStorage.get(), AIniImportSessionList, true);
+    }
+
     // The actual import will be done by ImportSelectedKnownHosts
     TStoredSessionList::SelectKnownHostsForSelectedSessions(AKnownHostsImportSessionList, OpensshImportSessionList.get());
-
     TStoredSessionList::ImportSelectedKnownHosts(AKnownHostsImportSessionList);
   }
   return Result;
@@ -132,6 +146,7 @@ TStoredSessionList * __fastcall TImportSessionsDialog::GetSessionList(int Index)
 //---------------------------------------------------------------------
 void __fastcall TImportSessionsDialog::UpdateControls()
 {
+  BrowseButton->Visible = (SourceComboBox->ItemIndex == IniIndex);
   PasteButton->Visible = (SourceComboBox->ItemIndex == KnownHostsIndex);
   EnableControl(PasteButton, IsFormatInClipboard(CF_TEXT));
   EnableControl(OKButton, ListViewAnyChecked(SessionListView2));
@@ -445,3 +460,29 @@ void __fastcall TImportSessionsDialog::PasteButtonClick(TObject * /*Sender*/)
   LoadSessions();
 }
 //---------------------------------------------------------------------------
+TStoredSessionList * TImportSessionsDialog::SelectSessionsForImport(UnicodeString & Error)
+{
+  return Configuration->SelectSessionsForImport(StoredSessions, FIniFileName, Error);
+}
+//---------------------------------------------------------------------------
+void __fastcall TImportSessionsDialog::BrowseButtonClick(TObject *)
+{
+  std::unique_ptr<TOpenDialog> OpenDialog(new TOpenDialog(Application));
+  OpenDialog->Title = LoadStr(IMPORT_INI_TITLE);
+  OpenDialog->Filter = LoadStr(EXPORT_CONF_FILTER);
+  OpenDialog->DefaultExt = L"ini";
+  OpenDialog->FileName = DefaultStr(FIniFileName, Configuration->GetDefaultIniFileExportPath());
+
+  if (OpenDialog->Execute())
+  {
+    SessionListView2->Items->Clear();
+    int Index = SourceComboBox->ItemIndex;
+    FIniFileName = OpenDialog->FileName;
+    UnicodeString Error;
+    FIniImportSessionList.reset(SelectSessionsForImport(Error));
+    FSessionListsList->Items[Index] = FIniImportSessionList.get();
+    FErrors->Strings[Index] = Error;
+    LoadSessions();
+  }
+}
+//---------------------------------------------------------------------------

+ 16 - 6
source/forms/ImportSessions.dfm

@@ -35,7 +35,7 @@ object ImportSessionsDialog: TImportSessionsDialog
     Caption = 'OK'
     Default = True
     ModalResult = 1
-    TabOrder = 4
+    TabOrder = 5
   end
   object CancelButton: TButton
     Left = 215
@@ -46,7 +46,7 @@ object ImportSessionsDialog: TImportSessionsDialog
     Cancel = True
     Caption = 'Cancel'
     ModalResult = 2
-    TabOrder = 5
+    TabOrder = 6
   end
   object SessionListView2: TListView
     Left = 8
@@ -67,7 +67,7 @@ object ImportSessionsDialog: TImportSessionsDialog
     ParentShowHint = False
     ShowColumnHeaders = False
     ShowHint = True
-    TabOrder = 2
+    TabOrder = 3
     ViewStyle = vsReport
     OnInfoTip = SessionListView2InfoTip
     OnKeyUp = SessionListView2KeyUp
@@ -80,7 +80,7 @@ object ImportSessionsDialog: TImportSessionsDialog
     Height = 25
     Anchors = [akLeft, akBottom]
     Caption = 'Un/check &all'
-    TabOrder = 3
+    TabOrder = 4
     OnClick = CheckAllButtonClick
   end
   object HelpButton: TButton
@@ -90,7 +90,7 @@ object ImportSessionsDialog: TImportSessionsDialog
     Height = 25
     Anchors = [akRight, akBottom]
     Caption = '&Help'
-    TabOrder = 6
+    TabOrder = 7
     OnClick = HelpButtonClick
   end
   object SourceComboBox: TComboBox
@@ -106,6 +106,7 @@ object ImportSessionsDialog: TImportSessionsDialog
       'KiTTY'
       'FileZilla'
       'OpenSSH'
+      'INI file'
       'known_hosts')
   end
   object ErrorPanel: TPanel
@@ -116,7 +117,7 @@ object ImportSessionsDialog: TImportSessionsDialog
     BevelOuter = bvNone
     Color = clWindow
     ParentBackground = False
-    TabOrder = 7
+    TabOrder = 8
     object ErrorLabel: TLabel
       Left = 0
       Top = 0
@@ -139,4 +140,13 @@ object ImportSessionsDialog: TImportSessionsDialog
     TabOrder = 1
     OnClick = PasteButtonClick
   end
+  object BrowseButton: TButton
+    Left = 232
+    Top = 8
+    Width = 75
+    Height = 25
+    Caption = 'B&rowse...'
+    TabOrder = 2
+    OnClick = BrowseButtonClick
+  end
 end

+ 6 - 0
source/forms/ImportSessions.h

@@ -24,6 +24,7 @@ __published:
   TPanel *ErrorPanel;
   TLabel *ErrorLabel;
   TButton *PasteButton;
+  TButton *BrowseButton;
   void __fastcall SessionListView2InfoTip(TObject *Sender,
     TListItem *Item, UnicodeString &InfoTip);
   void __fastcall SessionListView2MouseDown(TObject *Sender,
@@ -35,11 +36,14 @@ __published:
   void __fastcall HelpButtonClick(TObject *Sender);
   void __fastcall SourceComboBoxSelect(TObject *Sender);
   void __fastcall PasteButtonClick(TObject *Sender);
+  void __fastcall BrowseButtonClick(TObject *Sender);
 
 private:
   TList * FSessionListsList;
   TStrings * FErrors;
   std::unique_ptr<TStoredSessionList> FPastedKnownHosts;
+  std::unique_ptr<TStoredSessionList> FIniImportSessionList;
+  UnicodeString FIniFileName;
   void __fastcall UpdateControls();
   void __fastcall LoadSessions();
   void __fastcall ClearSelections();
@@ -56,7 +60,9 @@ private:
 public:
   virtual __fastcall TImportSessionsDialog(TComponent * AOwner);
   void __fastcall Init(TList * SessionListsList, TStrings * Errors);
+  TStoredSessionList * SelectSessionsForImport(UnicodeString & Error);
   bool __fastcall Execute();
+  __property UnicodeString IniFileName = { read = FIniFileName };
 };
 //----------------------------------------------------------------------------
 #endif

+ 2 - 0
source/resource/TextsCore.h

@@ -290,6 +290,7 @@
 #define SSH_HOST_CA_INVALID     770
 #define S3_ASSUME_ROLE_ERROR    780
 #define S3_ASSUME_ROLE_RESPONSE_ERROR 781
+#define INI_NO_SITES            782
 
 #define CORE_CONFIRMATION_STRINGS 300
 #define CONFIRM_PROLONG_TIMEOUT3 301
@@ -537,6 +538,7 @@
 #define PUBLIC_KEY_PERMISSIONS  568
 #define TIME_RELATIVE           569
 #define DAYS_SPAN               570
+#define INI_SELECT              571
 
 #define CORE_VARIABLE_STRINGS   600
 #define PUTTY_BASED_ON          601

+ 2 - 0
source/resource/TextsCore1.rc

@@ -266,6 +266,7 @@ BEGIN
   SSH_HOST_CA_INVALID, "Invalid '%s' key data."
   S3_ASSUME_ROLE_ERROR, "Error assuming role '%s'."
   S3_ASSUME_ROLE_RESPONSE_ERROR, "Unexpected response to assume role request (%s)."
+  INI_NO_SITES, "No sites found in \"%s\"."
 
   CORE_CONFIRMATION_STRINGS, "CORE_CONFIRMATION"
   CONFIRM_PROLONG_TIMEOUT3, "Host is not communicating for %d seconds.\n\nWait for another %0:d seconds?"
@@ -510,6 +511,7 @@ BEGIN
   PUBLIC_KEY_PERMISSIONS, "Though potentially wrong permissions of \"%s\" file and/or its parent folder were detected. Please check them."
   TIME_RELATIVE, "just now|today|yesterday|tomorrow|one second ago|%d seconds ago|one minute ago|%d minutes ago|one hour ago|%d hours ago|one day ago|%d days ago|one week ago|%d weeks ago|one month ago|%d months ago|one year ago|%d years ago"
   DAYS_SPAN, "%d days"
+  INI_SELECT, "Browse for INI file to import sites from."
 
   CORE_VARIABLE_STRINGS, "CORE_VARIABLE"
   PUTTY_BASED_ON, "SSH and SCP code based on PuTTY %s"

+ 1 - 0
source/resource/TextsWin.h

@@ -691,6 +691,7 @@
 #define SSH_HOST_CA_NO_HOSTS    6204
 #define SSH_HOST_CA_HOSTS_INVALID 6205
 #define LOGIN_NOT_SHOWING_AGAIN 6206
+#define IMPORT_INI_TITLE        6207
 
 // 2xxx is reserved for TextsFileZilla.h
 

+ 1 - 0
source/resource/TextsWin1.rc

@@ -696,6 +696,7 @@ BEGIN
         SSH_HOST_CA_NO_HOSTS, "No validity expression configured."
         SSH_HOST_CA_HOSTS_INVALID, "Error in validity expression."
         LOGIN_NOT_SHOWING_AGAIN, "**Stop showing Login dialog automatically?** Please confirm if you really want to WinSCP stop showing Login dialog automatically on startup and when the last session is closed.\n\nIf you change your mind later, you can revert this in Preferences on Environment > Window page.\n\nTo open the Login dialog manually, go to Tabs > New Tab [> Remote Tab] or use corresponding toolbar button."
+        IMPORT_INI_TITLE, "Select file to import sites from"
 
         WIN_VARIABLE_STRINGS, "WIN_VARIABLE"
         WINSCP_COPYRIGHT, "Copyright © 2000–2024 Martin Prikryl"