From 180ddf25f41c11ce3dccab5ce706e9fb6d4dcc3c Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Tue, 30 Jan 2024 18:54:57 +0100 Subject: [PATCH 01/14] Remove describe as there's only one public method --- spec/unit/cli/restore_spec.rb | 66 +++++++++++++++++------------------ 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/spec/unit/cli/restore_spec.rb b/spec/unit/cli/restore_spec.rb index bb61f77f..84b29a19 100644 --- a/spec/unit/cli/restore_spec.rb +++ b/spec/unit/cli/restore_spec.rb @@ -22,54 +22,52 @@ module Imap::Backup action: ->(subject) { subject.run } ) - describe "#run" do - context "when an email is provided" do - it "runs restore on the account" do - subject.run + context "when an email is provided" do + it "runs restore on the account" do + subject.run - expect(account).to have_received(:restore) - end + expect(account).to have_received(:restore) end + end - context "when neither an email nor a list of account names is provided" do - let(:email) { nil } - let(:options) { {} } + context "when neither an email nor a list of account names is provided" do + let(:email) { nil } + let(:options) { {} } - before do - allow(subject).to receive(:requested_accounts) { [account] } - end + before do + allow(subject).to receive(:requested_accounts) { [account] } + end - it "runs restore on each account" do - subject.run + it "runs restore on each account" do + subject.run - expect(account).to have_received(:restore) - end + expect(account).to have_received(:restore) end + end - context "when an email and a list of account names is provided" do - let(:email) { "email" } - let(:options) { {accounts: "email2"} } + context "when an email and a list of account names is provided" do + let(:email) { "email" } + let(:options) { {accounts: "email2"} } - it "fails" do - expect do - subject.run - end.to raise_error(RuntimeError, /Missing EMAIL parameter/) - end + it "fails" do + expect do + subject.run + end.to raise_error(RuntimeError, /Missing EMAIL parameter/) end + end - context "when just a list of account names is provided" do - let(:email) { nil } - let(:options) { {accounts: "email2"} } + context "when just a list of account names is provided" do + let(:email) { nil } + let(:options) { {accounts: "email2"} } - before do - allow(subject).to receive(:requested_accounts) { [account] } - end + before do + allow(subject).to receive(:requested_accounts) { [account] } + end - it "runs restore on each account" do - subject.run + it "runs restore on each account" do + subject.run - expect(account).to have_received(:restore) - end + expect(account).to have_received(:restore) end end end From 606123a5a2f772c67b5d93a86d9c655669987f20 Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Tue, 30 Jan 2024 19:01:22 +0100 Subject: [PATCH 02/14] Have a default case, without context --- spec/unit/cli/restore_spec.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/spec/unit/cli/restore_spec.rb b/spec/unit/cli/restore_spec.rb index 84b29a19..f8a1ba3a 100644 --- a/spec/unit/cli/restore_spec.rb +++ b/spec/unit/cli/restore_spec.rb @@ -22,12 +22,10 @@ module Imap::Backup action: ->(subject) { subject.run } ) - context "when an email is provided" do - it "runs restore on the account" do - subject.run + it "runs restore on the account" do + subject.run - expect(account).to have_received(:restore) - end + expect(account).to have_received(:restore) end context "when neither an email nor a list of account names is provided" do From 6142a39511b172b02ef939858560ed24f61d8610 Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Wed, 31 Jan 2024 09:04:19 +0100 Subject: [PATCH 03/14] Typos --- lib/imap/backup/cli/transfer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/imap/backup/cli/transfer.rb b/lib/imap/backup/cli/transfer.rb index 93f24b61..d9ed8f0c 100644 --- a/lib/imap/backup/cli/transfer.rb +++ b/lib/imap/backup/cli/transfer.rb @@ -13,7 +13,7 @@ class CLI::Transfer < Thor include Thor::Actions include CLI::Helpers - # The possible vaues for the action parameter + # The possible values for the action parameter ACTIONS = %i(migrate mirror).freeze def initialize(action, source_email, destination_email, options) @@ -35,7 +35,7 @@ def initialize(action, source_email, destination_email, options) # @raise [RuntimeError] if the indicated action is unknown, # or the source and destination accounts are the same, # or either of the accounts is not configured, - # or incompatible namespace/delimter parameters have been supplied + # or incompatible namespace/delimiter parameters have been supplied # @return [void] no_commands do def run From e8aa43fc4891ba3add7717237ae1490fb2878b5d Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Wed, 31 Jan 2024 09:20:53 +0100 Subject: [PATCH 04/14] Pre-declare class --- lib/imap/backup/serializer/delayed_metadata_serializer.rb | 2 ++ lib/imap/backup/serializer/imap.rb | 2 ++ lib/imap/backup/serializer/mbox.rb | 2 ++ lib/imap/backup/serializer/message.rb | 2 ++ lib/imap/backup/serializer/message_enumerator.rb | 2 ++ lib/imap/backup/serializer/permission_checker.rb | 2 ++ lib/imap/backup/serializer/transaction.rb | 2 ++ lib/imap/backup/serializer/unused_name_finder.rb | 2 ++ lib/imap/backup/serializer/version2_migrator.rb | 2 ++ 9 files changed, 18 insertions(+) diff --git a/lib/imap/backup/serializer/delayed_metadata_serializer.rb b/lib/imap/backup/serializer/delayed_metadata_serializer.rb index 2976d2b7..c643b0a9 100644 --- a/lib/imap/backup/serializer/delayed_metadata_serializer.rb +++ b/lib/imap/backup/serializer/delayed_metadata_serializer.rb @@ -8,6 +8,8 @@ module Imap; end module Imap::Backup + class Serializer; end + # Wraps the Serializer, delaying metadata appends class Serializer::DelayedMetadataSerializer extend Forwardable diff --git a/lib/imap/backup/serializer/imap.rb b/lib/imap/backup/serializer/imap.rb index fa412c49..bd9ee742 100644 --- a/lib/imap/backup/serializer/imap.rb +++ b/lib/imap/backup/serializer/imap.rb @@ -7,6 +7,8 @@ module Imap; end module Imap::Backup + class Serializer; end + # Stores message metadata class Serializer::Imap # The version number to store in the metadata file diff --git a/lib/imap/backup/serializer/mbox.rb b/lib/imap/backup/serializer/mbox.rb index d81f5c4f..9048f7be 100644 --- a/lib/imap/backup/serializer/mbox.rb +++ b/lib/imap/backup/serializer/mbox.rb @@ -3,6 +3,8 @@ module Imap; end module Imap::Backup + class Serializer; end + # Stores messages class Serializer::Mbox # @return [String] The path of the mailbox file, without the '.mbox' extension diff --git a/lib/imap/backup/serializer/message.rb b/lib/imap/backup/serializer/message.rb index 7f656887..1dd24598 100644 --- a/lib/imap/backup/serializer/message.rb +++ b/lib/imap/backup/serializer/message.rb @@ -5,6 +5,8 @@ module Imap; end module Imap::Backup + class Serializer; end + # Represents a stored message class Serializer::Message # @return [Array[Symbol]] the message's flags diff --git a/lib/imap/backup/serializer/message_enumerator.rb b/lib/imap/backup/serializer/message_enumerator.rb index 223a7d65..b55feac6 100644 --- a/lib/imap/backup/serializer/message_enumerator.rb +++ b/lib/imap/backup/serializer/message_enumerator.rb @@ -1,6 +1,8 @@ module Imap; end module Imap::Backup + class Serializer; end + # Enumerates over a list of stores messages class Serializer::MessageEnumerator # @param imap [Serializer::Imap] the metadata serializer for the folder diff --git a/lib/imap/backup/serializer/permission_checker.rb b/lib/imap/backup/serializer/permission_checker.rb index 483ddbe1..ea658834 100644 --- a/lib/imap/backup/serializer/permission_checker.rb +++ b/lib/imap/backup/serializer/permission_checker.rb @@ -3,6 +3,8 @@ module Imap; end module Imap::Backup + class Serializer; end + # Ensures a file has the desired permissions class Serializer::PermissionChecker # @param filename [String] the file name diff --git a/lib/imap/backup/serializer/transaction.rb b/lib/imap/backup/serializer/transaction.rb index d6302c22..3ac3c0af 100644 --- a/lib/imap/backup/serializer/transaction.rb +++ b/lib/imap/backup/serializer/transaction.rb @@ -1,6 +1,8 @@ module Imap; end module Imap::Backup + class Serializer; end + # Stores data during a transaction class Serializer::Transaction # @return the transaction's stored data diff --git a/lib/imap/backup/serializer/unused_name_finder.rb b/lib/imap/backup/serializer/unused_name_finder.rb index 7c472acb..3c10c1eb 100644 --- a/lib/imap/backup/serializer/unused_name_finder.rb +++ b/lib/imap/backup/serializer/unused_name_finder.rb @@ -3,6 +3,8 @@ module Imap; end module Imap::Backup + class Serializer; end + # Finds a name that can be used to rename a serialized folder class Serializer::UnusedNameFinder # @param serializer [Serializer] a folder serializer diff --git a/lib/imap/backup/serializer/version2_migrator.rb b/lib/imap/backup/serializer/version2_migrator.rb index 6eb0f999..95adcd68 100644 --- a/lib/imap/backup/serializer/version2_migrator.rb +++ b/lib/imap/backup/serializer/version2_migrator.rb @@ -5,6 +5,8 @@ module Imap; end module Imap::Backup + class Serializer; end + # Migrates serialized folder metadata from the version 2 format to the version 3 format class Serializer::Version2Migrator # @param folder_path [String] the base pathv(without extension) of the folder backup From 32a26d66b25e6f9e90dcb42454725e00119c2ab6 Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Wed, 31 Jan 2024 09:21:40 +0100 Subject: [PATCH 05/14] Call Account::Restore directly without going through Account --- lib/imap/backup/account.rb | 9 --------- lib/imap/backup/cli/restore.rb | 12 +++++++++--- spec/unit/account_spec.rb | 14 -------------- spec/unit/cli/restore_spec.rb | 10 ++++++---- 4 files changed, 15 insertions(+), 30 deletions(-) diff --git a/lib/imap/backup/account.rb b/lib/imap/backup/account.rb index cd57170c..9bc9bfb6 100644 --- a/lib/imap/backup/account.rb +++ b/lib/imap/backup/account.rb @@ -1,7 +1,6 @@ require "json" require "imap/backup/account/client_factory" -require "imap/backup/account/restore" module Imap; end @@ -98,14 +97,6 @@ def capabilities client.capability end - # Restore the local backup to the server - # - # @return [void] - def restore - restore = Account::Restore.new(account: self) - restore.run - end - # Indicates whether the account has been configured, and is ready # to be used # diff --git a/lib/imap/backup/cli/restore.rb b/lib/imap/backup/cli/restore.rb index 64932458..fe8c27de 100644 --- a/lib/imap/backup/cli/restore.rb +++ b/lib/imap/backup/cli/restore.rb @@ -1,5 +1,6 @@ require "thor" +require "imap/backup/account/restore" require "imap/backup/cli/helpers" require "imap/backup/logger" @@ -28,10 +29,10 @@ def run case when email && !options.key?(:accounts) account = account(config, email) - account.restore + restore(account) when !email && !options.key?(:accounts) Logger.logger.info "Calling restore without an EMAIL parameter is deprecated" - config.accounts.map(&:restore) + config.accounts.each { |a| restore(a) } when email && options.key?(:accounts) raise "Missing EMAIL parameter" when !email && options.key?(:accounts) @@ -39,7 +40,7 @@ def run "Calling restore with the --account option is deprected, " \ "please pass a single EMAIL parameter" ) - requested_accounts(config).each(&:restore) + requested_accounts(config).each { |a| restore(a) } end end end @@ -48,5 +49,10 @@ def run attr_reader :email attr_reader :options + + def restore(account) + restore = Account::Restore.new(account: account) + restore.run + end end end diff --git a/spec/unit/account_spec.rb b/spec/unit/account_spec.rb index 028a2d59..c3843235 100644 --- a/spec/unit/account_spec.rb +++ b/spec/unit/account_spec.rb @@ -97,20 +97,6 @@ module Imap::Backup end end - describe "#restore" do - let(:restore) { instance_double(Account::Restore, run: nil) } - - before do - allow(Account::Restore).to receive(:new) { restore } - end - - it "runs restore" do - subject.restore - - expect(restore).to have_received(:run) - end - end - describe "#valid?" do context "with username and password" do it "is true" do diff --git a/spec/unit/cli/restore_spec.rb b/spec/unit/cli/restore_spec.rb index f8a1ba3a..43336209 100644 --- a/spec/unit/cli/restore_spec.rb +++ b/spec/unit/cli/restore_spec.rb @@ -9,12 +9,14 @@ module Imap::Backup let(:email) { "email" } let(:options) { {} } - let(:account) { instance_double(Account, username: email, restore: nil) } + let(:account) { instance_double(Account, username: email) } let(:config) { instance_double(Configuration, accounts: [account]) } + let(:restore) { instance_double(Account::Restore, run: nil) } before do allow(Configuration).to receive(:exist?) { true } allow(Configuration).to receive(:new) { config } + allow(Account::Restore).to receive(:new) { restore } end it_behaves_like( @@ -25,7 +27,7 @@ module Imap::Backup it "runs restore on the account" do subject.run - expect(account).to have_received(:restore) + expect(restore).to have_received(:run) end context "when neither an email nor a list of account names is provided" do @@ -39,7 +41,7 @@ module Imap::Backup it "runs restore on each account" do subject.run - expect(account).to have_received(:restore) + expect(restore).to have_received(:run) end end @@ -65,7 +67,7 @@ module Imap::Backup it "runs restore on each account" do subject.run - expect(account).to have_received(:restore) + expect(restore).to have_received(:run) end end end From 166b6bed1f14b9287552daa85c943f784a22807b Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Wed, 31 Jan 2024 09:54:04 +0100 Subject: [PATCH 06/14] Rename class --- .../folder_mapper.rb} | 12 ++++++------ lib/imap/backup/cli/transfer.rb | 6 +++--- .../folder_mapper_spec.rb} | 6 +++--- spec/unit/cli/transfer_spec.rb | 12 ++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) rename lib/imap/backup/{cli/folder_enumerator.rb => account/folder_mapper.rb} (95%) rename spec/unit/{cli/folder_enumerator_spec.rb => account/folder_mapper_spec.rb} (94%) diff --git a/lib/imap/backup/cli/folder_enumerator.rb b/lib/imap/backup/account/folder_mapper.rb similarity index 95% rename from lib/imap/backup/cli/folder_enumerator.rb rename to lib/imap/backup/account/folder_mapper.rb index 824d0c95..43453a5d 100644 --- a/lib/imap/backup/cli/folder_enumerator.rb +++ b/lib/imap/backup/account/folder_mapper.rb @@ -7,22 +7,22 @@ module Imap; end module Imap::Backup - class CLI < Thor; end + class Account; end # Implements a folder enumerator for backed-up accounts - class CLI::FolderEnumerator + class Account::FolderMapper def initialize( + account:, destination:, - source:, destination_delimiter: "/", destination_prefix: "", source_delimiter: "/", source_prefix: "" ) + @account = account @destination = destination @destination_delimiter = destination_delimiter @destination_prefix = destination_prefix - @source = source @source_delimiter = source_delimiter @source_prefix = source_prefix end @@ -48,7 +48,7 @@ def each attr_reader :destination attr_reader :destination_delimiter - attr_reader :source + attr_reader :account attr_reader :source_delimiter def destination_prefix_clipped @@ -94,7 +94,7 @@ def destination_folder_for_path(name) end def source_local_path - source.local_path + account.local_path end def source_folder_name(imap_pathname) diff --git a/lib/imap/backup/cli/transfer.rb b/lib/imap/backup/cli/transfer.rb index d9ed8f0c..9c1e5479 100644 --- a/lib/imap/backup/cli/transfer.rb +++ b/lib/imap/backup/cli/transfer.rb @@ -1,6 +1,6 @@ +require "imap/backup/account/folder_mapper" require "imap/backup/cli/backup" require "imap/backup/cli/helpers" -require "imap/backup/cli/folder_enumerator" require "imap/backup/logger" require "imap/backup/migrator" require "imap/backup/mirror" @@ -148,17 +148,17 @@ def config def enumerator_options { + account: source_account, destination: destination_account, destination_delimiter: destination_delimiter, destination_prefix: destination_prefix, - source: source_account, source_delimiter: source_delimiter, source_prefix: source_prefix } end def folders - CLI::FolderEnumerator.new(**enumerator_options) + Account::FolderMapper.new(**enumerator_options) end def destination_account diff --git a/spec/unit/cli/folder_enumerator_spec.rb b/spec/unit/account/folder_mapper_spec.rb similarity index 94% rename from spec/unit/cli/folder_enumerator_spec.rb rename to spec/unit/account/folder_mapper_spec.rb index 7298b491..e77e8fef 100644 --- a/spec/unit/cli/folder_enumerator_spec.rb +++ b/spec/unit/account/folder_mapper_spec.rb @@ -1,15 +1,15 @@ -require "imap/backup/cli/folder_enumerator" +require "imap/backup/account/folder_mapper" require "imap/backup/account" require "imap/backup/client/default" module Imap::Backup - RSpec.describe CLI::FolderEnumerator do + RSpec.describe Account::FolderMapper do subject { described_class.new(**options) } let(:path) { "folder_enumerator_path/foo.imap" } let(:imap_pathname) { Pathname.new(path) } - let(:options) { {source: source, destination: destination} } + let(:options) { {account: source, destination: destination} } let(:source) do instance_double(Account, username: "source", local_path: "folder_enumerator_path") end diff --git a/spec/unit/cli/transfer_spec.rb b/spec/unit/cli/transfer_spec.rb index 35e4d41b..184e9b6b 100644 --- a/spec/unit/cli/transfer_spec.rb +++ b/spec/unit/cli/transfer_spec.rb @@ -35,7 +35,7 @@ module Imap::Backup let(:migrator) { instance_double(Migrator, run: nil) } let(:mirror) { instance_double(Mirror, run: nil) } let(:backup) { instance_double(CLI::Backup, "backup_1", run: nil) } - let(:folder_enumerator) { instance_double(CLI::FolderEnumerator) } + let(:folder_mapper) { instance_double(Account::FolderMapper) } before do allow(Configuration).to receive(:exist?) { true } @@ -43,8 +43,8 @@ module Imap::Backup allow(CLI::Backup).to receive(:new) { backup } allow(Migrator).to receive(:new) { migrator } allow(Mirror).to receive(:new) { mirror } - allow(CLI::FolderEnumerator).to receive(:new) { folder_enumerator } - allow(folder_enumerator).to receive(:each).and_yield(serializer, folder) + allow(Account::FolderMapper).to receive(:new) { folder_mapper } + allow(folder_mapper).to receive(:each).and_yield(serializer, folder) end it_behaves_like( @@ -173,7 +173,7 @@ module Imap::Backup it "uses the values from the servers" do subject.run - expect(CLI::FolderEnumerator).to have_received(:new). + expect(Account::FolderMapper).to have_received(:new). with( hash_including( { @@ -211,7 +211,7 @@ module Imap::Backup it "defaults to '#{default}'" do subject.run - expect(CLI::FolderEnumerator).to have_received(:new). + expect(Account::FolderMapper).to have_received(:new). with(hash_including({parameter => default})) end end @@ -225,7 +225,7 @@ module Imap::Backup it "uses the supplied value" do subject.run - expect(CLI::FolderEnumerator).to have_received(:new). + expect(Account::FolderMapper).to have_received(:new). with(hash_including({parameter => "x"})) end end From 65592e91212fedbcbbd085e5e7549040717722a6 Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Wed, 31 Jan 2024 09:54:18 +0100 Subject: [PATCH 07/14] Remove Thor dependency --- lib/imap/backup/cli/transfer.rb | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/imap/backup/cli/transfer.rb b/lib/imap/backup/cli/transfer.rb index 9c1e5479..b3a03675 100644 --- a/lib/imap/backup/cli/transfer.rb +++ b/lib/imap/backup/cli/transfer.rb @@ -9,15 +9,13 @@ module Imap; end module Imap::Backup # Implements migration and mirroring - class CLI::Transfer < Thor - include Thor::Actions + class CLI::Transfer include CLI::Helpers # The possible values for the action parameter ACTIONS = %i(migrate mirror).freeze def initialize(action, source_email, destination_email, options) - super([]) @action = action @source_email = source_email @destination_email = destination_email @@ -37,20 +35,18 @@ def initialize(action, source_email, destination_email, options) # or either of the accounts is not configured, # or incompatible namespace/delimiter parameters have been supplied # @return [void] - no_commands do - def run - raise "Unknown action '#{action}'" if !ACTIONS.include?(action) - - process_options! - prepare_mirror if action == :mirror - - folders.each do |serializer, folder| - case action - when :migrate - Migrator.new(serializer, folder, reset: reset).run - when :mirror - Mirror.new(serializer, folder).run - end + def run + raise "Unknown action '#{action}'" if !ACTIONS.include?(action) + + process_options! + prepare_mirror if action == :mirror + + folders.each do |serializer, folder| + case action + when :migrate + Migrator.new(serializer, folder, reset: reset).run + when :mirror + Mirror.new(serializer, folder).run end end end From 91782fc99abda01de2c38bb24514908f29ca3ce1 Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Wed, 31 Jan 2024 10:03:23 +0100 Subject: [PATCH 08/14] Use the FolderMapper, so we can provide delimiters --- lib/imap/backup/account/restore.rb | 16 +++++++++++++--- spec/unit/account/restore_spec.rb | 6 +++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/imap/backup/account/restore.rb b/lib/imap/backup/account/restore.rb index 3fbe50e6..d26a5c02 100644 --- a/lib/imap/backup/account/restore.rb +++ b/lib/imap/backup/account/restore.rb @@ -1,4 +1,4 @@ -require "imap/backup/account/serialized_folders" +require "imap/backup/account/folder_mapper" require "imap/backup/uploader" module Imap; end @@ -15,8 +15,7 @@ def initialize(account:) # Runs the restore operation # @return [void] def run - serialized_folders = Account::SerializedFolders.new(account: account) - serialized_folders.each do |serializer, folder| + folders.each do |serializer, folder| Uploader.new(folder, serializer).run end end @@ -24,5 +23,16 @@ def run private attr_reader :account + + def enumerator_options + { + account: account, + destination: account + } + end + + def folders + Account::FolderMapper.new(**enumerator_options) + end end end diff --git a/spec/unit/account/restore_spec.rb b/spec/unit/account/restore_spec.rb index d34a4aed..6c57ad9c 100644 --- a/spec/unit/account/restore_spec.rb +++ b/spec/unit/account/restore_spec.rb @@ -5,12 +5,12 @@ module Imap::Backup subject { described_class.new(account: account) } let(:account) { "account" } - let(:serialized_folders) { instance_double(Account::SerializedFolders) } + let(:folder_mapper) { instance_double(Account::FolderMapper) } let(:uploader) { instance_double(Uploader, run: nil) } before do - allow(Account::SerializedFolders).to receive(:new) { serialized_folders } - allow(serialized_folders).to receive(:each).and_yield("folder", "serializer") + allow(Account::FolderMapper).to receive(:new) { folder_mapper } + allow(folder_mapper).to receive(:each).and_yield("serializer", "folder") allow(Uploader).to receive(:new) { uploader } end From 1c0d789df86115b42ec7db4e29d6035ecb604744 Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Wed, 31 Jan 2024 10:23:31 +0100 Subject: [PATCH 09/14] Implement delimiter mapping for restores --- lib/imap/backup/account/restore.rb | 7 +++++-- spec/unit/account/restore_spec.rb | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/imap/backup/account/restore.rb b/lib/imap/backup/account/restore.rb index d26a5c02..451911dc 100644 --- a/lib/imap/backup/account/restore.rb +++ b/lib/imap/backup/account/restore.rb @@ -8,8 +8,9 @@ class Account; end # Restores all backed up folders to the server class Account::Restore - def initialize(account:) + def initialize(account:, delimiter: "/") @account = account + @destination_delimiter = delimiter end # Runs the restore operation @@ -23,11 +24,13 @@ def run private attr_reader :account + attr_reader :destination_delimiter def enumerator_options { account: account, - destination: account + destination: account, + destination_delimiter: destination_delimiter } end diff --git a/spec/unit/account/restore_spec.rb b/spec/unit/account/restore_spec.rb index 6c57ad9c..f8b1dd57 100644 --- a/spec/unit/account/restore_spec.rb +++ b/spec/unit/account/restore_spec.rb @@ -2,9 +2,10 @@ module Imap::Backup RSpec.describe Account::Restore do - subject { described_class.new(account: account) } + subject { described_class.new(account: account, **options) } let(:account) { "account" } + let(:options) { {} } let(:folder_mapper) { instance_double(Account::FolderMapper) } let(:uploader) { instance_double(Uploader, run: nil) } @@ -19,5 +20,18 @@ module Imap::Backup expect(uploader).to have_received(:run) end + + context "when a delimiter is provided" do + let(:options) { {delimiter: "."} } + let(:delimited_folder) { instance_double(Account::Folder) } + let(:serializer) { instance_double(Serializer) } + + it "maps destination folders with the delimiter" do + subject.run + + expect(Account::FolderMapper).to have_received(:new). + with(hash_including(destination_delimiter: ".")) + end + end end end From eeb12c9abd6116e4bf356e411606b1c4981b5934 Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Wed, 31 Jan 2024 10:25:22 +0100 Subject: [PATCH 10/14] Implement prefix mapping for restores --- lib/imap/backup/account/restore.rb | 7 +++++-- spec/unit/account/restore_spec.rb | 13 +++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/imap/backup/account/restore.rb b/lib/imap/backup/account/restore.rb index 451911dc..ffce30b8 100644 --- a/lib/imap/backup/account/restore.rb +++ b/lib/imap/backup/account/restore.rb @@ -8,9 +8,10 @@ class Account; end # Restores all backed up folders to the server class Account::Restore - def initialize(account:, delimiter: "/") + def initialize(account:, delimiter: "/", prefix: "") @account = account @destination_delimiter = delimiter + @destination_prefix = prefix end # Runs the restore operation @@ -25,12 +26,14 @@ def run attr_reader :account attr_reader :destination_delimiter + attr_reader :destination_prefix def enumerator_options { account: account, destination: account, - destination_delimiter: destination_delimiter + destination_delimiter: destination_delimiter, + destination_prefix: destination_prefix } end diff --git a/spec/unit/account/restore_spec.rb b/spec/unit/account/restore_spec.rb index f8b1dd57..a431a537 100644 --- a/spec/unit/account/restore_spec.rb +++ b/spec/unit/account/restore_spec.rb @@ -33,5 +33,18 @@ module Imap::Backup with(hash_including(destination_delimiter: ".")) end end + + context "when a prefix is provided" do + let(:options) { {prefix: "."} } + let(:delimited_folder) { instance_double(Account::Folder) } + let(:serializer) { instance_double(Serializer) } + + it "maps destination folders with the prefix" do + subject.run + + expect(Account::FolderMapper).to have_received(:new). + with(hash_including(destination_prefix: ".")) + end + end end end From 230aca16f92fa17be6d79b1b2098f51603e0cace Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Wed, 31 Jan 2024 10:35:28 +0100 Subject: [PATCH 11/14] Accept restore options --- lib/imap/backup/cli.rb | 11 +++++++++++ lib/imap/backup/cli/restore.rb | 10 +++++++--- spec/unit/cli/restore_spec.rb | 11 +++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/imap/backup/cli.rb b/lib/imap/backup/cli.rb index 1fee3c14..078ff151 100644 --- a/lib/imap/backup/cli.rb +++ b/lib/imap/backup/cli.rb @@ -229,6 +229,17 @@ def mirror(source_email, destination_email) config_option quiet_option verbose_option + method_option( + "delimiter", + type: :string, + desc: "the delimiter for folder names" + ) + method_option( + "prefix", + type: :string, + desc: "a prefix (namespace) to add to folder names", + aliases: ["-d"] + ) # Restores backed up emails to an account # @return [void] def restore(email = nil) diff --git a/lib/imap/backup/cli/restore.rb b/lib/imap/backup/cli/restore.rb index fe8c27de..ff32f282 100644 --- a/lib/imap/backup/cli/restore.rb +++ b/lib/imap/backup/cli/restore.rb @@ -29,7 +29,7 @@ def run case when email && !options.key?(:accounts) account = account(config, email) - restore(account) + restore(account, **restore_options) when !email && !options.key?(:accounts) Logger.logger.info "Calling restore without an EMAIL parameter is deprecated" config.accounts.each { |a| restore(a) } @@ -50,9 +50,13 @@ def run attr_reader :email attr_reader :options - def restore(account) - restore = Account::Restore.new(account: account) + def restore(account, **options) + restore = Account::Restore.new(account: account, **options) restore.run end + + def restore_options + options.slice(:delimiter, :prefix) + end end end diff --git a/spec/unit/cli/restore_spec.rb b/spec/unit/cli/restore_spec.rb index 43336209..6ad664f8 100644 --- a/spec/unit/cli/restore_spec.rb +++ b/spec/unit/cli/restore_spec.rb @@ -30,6 +30,17 @@ module Imap::Backup expect(restore).to have_received(:run) end + context "when options are provided" do + let(:options) { {delimiter: "/", prefix: "CIAO"} } + + it "passes them to the restore" do + subject.run + + expect(Account::Restore).to have_received(:new). + with(hash_including(delimiter: "/", prefix: "CIAO")) + end + end + context "when neither an email nor a list of account names is provided" do let(:email) { nil } let(:options) { {} } From b4097a60525338eca68a164c3a51f613dd946769 Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Wed, 31 Jan 2024 10:37:03 +0100 Subject: [PATCH 12/14] Typo --- lib/imap/backup/cli/restore.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/imap/backup/cli/restore.rb b/lib/imap/backup/cli/restore.rb index ff32f282..88a88a17 100644 --- a/lib/imap/backup/cli/restore.rb +++ b/lib/imap/backup/cli/restore.rb @@ -37,7 +37,7 @@ def run raise "Missing EMAIL parameter" when !email && options.key?(:accounts) Logger.logger.info( - "Calling restore with the --account option is deprected, " \ + "Calling restore with the --account option is deprecated, " \ "please pass a single EMAIL parameter" ) requested_accounts(config).each { |a| restore(a) } From 52918fb976af7017e5d30bbfddd6ef618d35b28b Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Wed, 31 Jan 2024 10:39:23 +0100 Subject: [PATCH 13/14] Extract let for command --- spec/features/restore_spec.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/spec/features/restore_spec.rb b/spec/features/restore_spec.rb index cf87e7bd..6757c93f 100644 --- a/spec/features/restore_spec.rb +++ b/spec/features/restore_spec.rb @@ -24,7 +24,8 @@ email: email, folder: folder, flags: [:Draft, :$NON_SYSTEM_FLAG], **message_two ) end - let(:run_command) { run_command_and_stop("imap-backup restore #{email}") } + let(:command) { "imap-backup restore #{email}" } + let(:run_command) { run_command_and_stop(command) } let(:cleanup) do test_server.delete_folder folder test_server.disconnect @@ -215,11 +216,7 @@ **message_one ) end - let(:run_command) do - run_command_and_stop( - "imap-backup restore #{email} --config #{custom_config_path}" - ) - end + let(:command) { "imap-backup restore #{email} --config #{custom_config_path}" } it "does not raise any errors" do run_command From e4804cbbc3166e894724e8691f8fada26c1e8ec4 Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Wed, 31 Jan 2024 10:52:16 +0100 Subject: [PATCH 14/14] Add feature spec for restore delimiter and prefix parameters --- spec/features/restore_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/features/restore_spec.rb b/spec/features/restore_spec.rb index 6757c93f..35bff0c9 100644 --- a/spec/features/restore_spec.rb +++ b/spec/features/restore_spec.rb @@ -196,6 +196,21 @@ end end + context "when a prefix and delimiter are supplied" do + after do + test_server.delete_folder "CIAO.#{folder}" + test_server.delete_folder "CIAO" + end + + let(:command) { "imap-backup restore #{email} --prefix CIAO --delimiter ." } + + it "prepends the prefix to the folder name" do + run_command + + expect(test_server.folders.map(&:name)).to include("CIAO.#{folder}") + end + end + context "when a config path is supplied" do let(:custom_config_path) { File.join(File.expand_path("~/.imap-backup"), "foo.json") } let(:config_options) { super().merge(path: custom_config_path) }