imap-backup 14.4.0 → 14.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/imap-backup +2 -2
- data/docs/api.md +20 -0
- data/docs/development.md +18 -7
- data/lib/imap/backup/account/backup.rb +6 -3
- data/lib/imap/backup/account/backup_folders.rb +6 -3
- data/lib/imap/backup/account/client_factory.rb +3 -2
- data/lib/imap/backup/account/folder.rb +10 -9
- data/lib/imap/backup/account/folder_backup.rb +5 -4
- data/lib/imap/backup/account/folder_ensurer.rb +5 -2
- data/lib/imap/backup/account/local_only_folder_deleter.rb +7 -3
- data/lib/imap/backup/account/restore.rb +5 -2
- data/lib/imap/backup/account/serialized_folders.rb +3 -2
- data/lib/imap/backup/account.rb +85 -0
- data/lib/imap/backup/cli/backup.rb +14 -12
- data/lib/imap/backup/cli/folder_enumerator.rb +5 -5
- data/lib/imap/backup/cli/local/check.rb +4 -2
- data/lib/imap/backup/cli/local.rb +50 -49
- data/lib/imap/backup/cli/remote.rb +46 -46
- data/lib/imap/backup/cli/restore.rb +5 -3
- data/lib/imap/backup/cli/setup.rb +4 -2
- data/lib/imap/backup/cli/single/backup.rb +3 -3
- data/lib/imap/backup/cli/stats.rb +54 -52
- data/lib/imap/backup/cli/transfer.rb +93 -93
- data/lib/imap/backup/cli/utils.rb +28 -28
- data/lib/imap/backup/cli.rb +12 -0
- data/lib/imap/backup/client/default.rb +5 -5
- data/lib/imap/backup/configuration.rb +5 -4
- data/lib/imap/backup/downloader.rb +6 -14
- data/lib/imap/backup/email/mboxrd/message.rb +2 -3
- data/lib/imap/backup/file_mode.rb +4 -2
- data/lib/imap/backup/flag_refresher.rb +3 -3
- data/lib/imap/backup/local_only_message_deleter.rb +5 -3
- data/lib/imap/backup/migrator.rb +6 -4
- data/lib/imap/backup/mirror/map.rb +3 -3
- data/lib/imap/backup/mirror.rb +5 -5
- data/lib/imap/backup/serializer/appender.rb +7 -4
- data/lib/imap/backup/serializer/delayed_metadata_serializer.rb +15 -2
- data/lib/imap/backup/serializer/directory.rb +12 -3
- data/lib/imap/backup/serializer/folder_maker.rb +12 -4
- data/lib/imap/backup/serializer/imap.rb +5 -1
- data/lib/imap/backup/serializer/integrity_checker.rb +10 -3
- data/lib/imap/backup/serializer/mbox.rb +2 -1
- data/lib/imap/backup/serializer/message.rb +10 -18
- data/lib/imap/backup/serializer/permission_checker.rb +5 -3
- data/lib/imap/backup/serializer/transaction.rb +4 -1
- data/lib/imap/backup/serializer/unused_name_finder.rb +4 -2
- data/lib/imap/backup/serializer/version2_migrator.rb +2 -2
- data/lib/imap/backup/serializer.rb +5 -0
- data/lib/imap/backup/setup/account/header.rb +3 -3
- data/lib/imap/backup/setup/account.rb +6 -6
- data/lib/imap/backup/setup/asker.rb +6 -4
- data/lib/imap/backup/setup/backup_path.rb +3 -4
- data/lib/imap/backup/setup/connection_tester.rb +4 -2
- data/lib/imap/backup/setup/{email.rb → email_changer.rb} +4 -4
- data/lib/imap/backup/setup/folder_chooser.rb +2 -2
- data/lib/imap/backup/setup/global_options/download_strategy_chooser.rb +2 -2
- data/lib/imap/backup/setup/global_options.rb +2 -2
- data/lib/imap/backup/setup.rb +2 -2
- data/lib/imap/backup/text/sanitizer.rb +2 -2
- data/lib/imap/backup/thunderbird/mailbox_exporter.rb +10 -8
- data/lib/imap/backup/uploader.rb +3 -3
- data/lib/imap/backup/version.rb +1 -1
- metadata +4 -4
- data/lib/imap/backup/cli_coverage.rb +0 -21
| @@ -14,8 +14,6 @@ module Imap::Backup | |
| 14 14 | 
             
                include Thor::Actions
         | 
| 15 15 | 
             
                include CLI::Helpers
         | 
| 16 16 |  | 
| 17 | 
            -
                MAX_SUBJECT = 60
         | 
| 18 | 
            -
             | 
| 19 17 | 
             
                desc "accounts", "List locally backed-up accounts"
         | 
| 20 18 | 
             
                config_option
         | 
| 21 19 | 
             
                format_option
         | 
| @@ -122,69 +120,72 @@ module Imap::Backup | |
| 122 120 | 
             
                  end
         | 
| 123 121 | 
             
                end
         | 
| 124 122 |  | 
| 125 | 
            -
                 | 
| 126 | 
            -
                  def list_emails_as_json(serializer)
         | 
| 127 | 
            -
                    emails = serializer.each_message.map do |message|
         | 
| 128 | 
            -
                      {
         | 
| 129 | 
            -
                        uid: message.uid,
         | 
| 130 | 
            -
                        date: message.date.to_s,
         | 
| 131 | 
            -
                        subject: message.subject || ""
         | 
| 132 | 
            -
                      }
         | 
| 133 | 
            -
                    end
         | 
| 134 | 
            -
                    Kernel.puts emails.to_json
         | 
| 135 | 
            -
                  end
         | 
| 136 | 
            -
             | 
| 137 | 
            -
                  def list_emails_as_text(serializer)
         | 
| 138 | 
            -
                    Kernel.puts format(
         | 
| 139 | 
            -
                      "%-10<uid>s  %-#{MAX_SUBJECT}<subject>s - %<date>s",
         | 
| 140 | 
            -
                      {uid: "UID", subject: "Subject", date: "Date"}
         | 
| 141 | 
            -
                    )
         | 
| 142 | 
            -
                    Kernel.puts "-" * (12 + MAX_SUBJECT + 28)
         | 
| 123 | 
            +
                private
         | 
| 143 124 |  | 
| 144 | 
            -
             | 
| 145 | 
            -
                      list_message_as_text message
         | 
| 146 | 
            -
                    end
         | 
| 147 | 
            -
                  end
         | 
| 125 | 
            +
                MAX_SUBJECT = 60
         | 
| 148 126 |  | 
| 149 | 
            -
             | 
| 150 | 
            -
             | 
| 127 | 
            +
                def list_emails_as_json(serializer)
         | 
| 128 | 
            +
                  emails = serializer.each_message.map do |message|
         | 
| 129 | 
            +
                    {
         | 
| 151 130 | 
             
                      uid: message.uid,
         | 
| 152 131 | 
             
                      date: message.date.to_s,
         | 
| 153 132 | 
             
                      subject: message.subject || ""
         | 
| 154 133 | 
             
                    }
         | 
| 155 | 
            -
                    if m[:subject].length > MAX_SUBJECT
         | 
| 156 | 
            -
                      Kernel.puts format("% 10<uid>u: %.#{MAX_SUBJECT - 3}<subject>s... - %<date>s", m)
         | 
| 157 | 
            -
                    else
         | 
| 158 | 
            -
                      Kernel.puts format("% 10<uid>u: %-#{MAX_SUBJECT}<subject>s - %<date>s", m)
         | 
| 159 | 
            -
                    end
         | 
| 160 134 | 
             
                  end
         | 
| 135 | 
            +
                  Kernel.puts emails.to_json
         | 
| 136 | 
            +
                end
         | 
| 161 137 |  | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
                     | 
| 166 | 
            -
             | 
| 138 | 
            +
                def list_emails_as_text(serializer)
         | 
| 139 | 
            +
                  Kernel.puts format(
         | 
| 140 | 
            +
                    "%-10<uid>s  %-#{MAX_SUBJECT}<subject>s - %<date>s",
         | 
| 141 | 
            +
                    {uid: "UID", subject: "Subject", date: "Date"}
         | 
| 142 | 
            +
                  )
         | 
| 143 | 
            +
                  Kernel.puts "-" * (12 + MAX_SUBJECT + 28)
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                  serializer.each_message.map do |message|
         | 
| 146 | 
            +
                    list_message_as_text message
         | 
| 147 | 
            +
                  end
         | 
| 148 | 
            +
                end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                def list_message_as_text(message)
         | 
| 151 | 
            +
                  m = {
         | 
| 152 | 
            +
                    uid: message.uid,
         | 
| 153 | 
            +
                    date: message.date.to_s,
         | 
| 154 | 
            +
                    subject: message.subject || ""
         | 
| 155 | 
            +
                  }
         | 
| 156 | 
            +
                  if m[:subject].length > MAX_SUBJECT
         | 
| 157 | 
            +
                    Kernel.puts format("% 10<uid>u: %.#{MAX_SUBJECT - 3}<subject>s... - %<date>s", m)
         | 
| 158 | 
            +
                  else
         | 
| 159 | 
            +
                    Kernel.puts format("% 10<uid>u: %-#{MAX_SUBJECT}<subject>s - %<date>s", m)
         | 
| 167 160 | 
             
                  end
         | 
| 161 | 
            +
                end
         | 
| 168 162 |  | 
| 169 | 
            -
             | 
| 170 | 
            -
             | 
| 171 | 
            -
             | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 175 | 
            -
             | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
             | 
| 163 | 
            +
                def show_emails_as_json(serializer, uids)
         | 
| 164 | 
            +
                  emails = serializer.each_message(uids).map do |m|
         | 
| 165 | 
            +
                    m.to_h.tap { |h| h[:body] = m.body }
         | 
| 166 | 
            +
                  end
         | 
| 167 | 
            +
                  Kernel.puts emails.to_json
         | 
| 168 | 
            +
                end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                def show_emails_as_text(serializer, uids)
         | 
| 171 | 
            +
                  serializer.each_message(uids).each do |message|
         | 
| 172 | 
            +
                    if uids.count > 1
         | 
| 173 | 
            +
                      Kernel.puts <<~HEADER
         | 
| 174 | 
            +
                        #{'-' * 80}
         | 
| 175 | 
            +
                        #{format('| UID: %-71s |', message.uid)}
         | 
| 176 | 
            +
                        #{'-' * 80}
         | 
| 177 | 
            +
                      HEADER
         | 
| 179 178 | 
             
                    end
         | 
| 179 | 
            +
                    Kernel.puts message.body
         | 
| 180 180 | 
             
                  end
         | 
| 181 | 
            +
                end
         | 
| 181 182 |  | 
| 182 | 
            -
             | 
| 183 | 
            -
             | 
| 183 | 
            +
                def config
         | 
| 184 | 
            +
                  @config ||=
         | 
| 185 | 
            +
                    begin
         | 
| 184 186 | 
             
                      non_logging_options = Logger.setup_logging(options)
         | 
| 185 187 | 
             
                      load_config(**non_logging_options)
         | 
| 186 188 | 
             
                    end
         | 
| 187 | 
            -
                  end
         | 
| 188 189 | 
             
                end
         | 
| 189 190 | 
             
              end
         | 
| 190 191 | 
             
            end
         | 
| @@ -68,63 +68,63 @@ module Imap::Backup | |
| 68 68 | 
             
                  end
         | 
| 69 69 | 
             
                end
         | 
| 70 70 |  | 
| 71 | 
            -
                 | 
| 72 | 
            -
                  def folder_names(email)
         | 
| 73 | 
            -
                    config = load_config(**options)
         | 
| 74 | 
            -
                    account = account(config, email)
         | 
| 71 | 
            +
                private
         | 
| 75 72 |  | 
| 76 | 
            -
             | 
| 77 | 
            -
                   | 
| 73 | 
            +
                def folder_names(email)
         | 
| 74 | 
            +
                  config = load_config(**options)
         | 
| 75 | 
            +
                  account = account(config, email)
         | 
| 78 76 |  | 
| 79 | 
            -
                   | 
| 80 | 
            -
             | 
| 81 | 
            -
                      {name: name}
         | 
| 82 | 
            -
                    end
         | 
| 83 | 
            -
                    Kernel.puts list.to_json
         | 
| 84 | 
            -
                  end
         | 
| 77 | 
            +
                  account.client.list
         | 
| 78 | 
            +
                end
         | 
| 85 79 |  | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
                    end
         | 
| 80 | 
            +
                def json_format_names(names)
         | 
| 81 | 
            +
                  list = names.map do |name|
         | 
| 82 | 
            +
                    {name: name}
         | 
| 90 83 | 
             
                  end
         | 
| 84 | 
            +
                  Kernel.puts list.to_json
         | 
| 85 | 
            +
                end
         | 
| 91 86 |  | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
                      other: namespace_info(namespaces.other.first),
         | 
| 96 | 
            -
                      shared: namespace_info(namespaces.shared.first)
         | 
| 97 | 
            -
                    }
         | 
| 98 | 
            -
                    Kernel.puts list.to_json
         | 
| 87 | 
            +
                def list_names(names)
         | 
| 88 | 
            +
                  names.each do |name|
         | 
| 89 | 
            +
                    Kernel.puts %("#{name}")
         | 
| 99 90 | 
             
                  end
         | 
| 91 | 
            +
                end
         | 
| 100 92 |  | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
                    )
         | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 93 | 
            +
                def json_format_namespaces(namespaces)
         | 
| 94 | 
            +
                  list = {
         | 
| 95 | 
            +
                    personal: namespace_info(namespaces.personal.first),
         | 
| 96 | 
            +
                    other: namespace_info(namespaces.other.first),
         | 
| 97 | 
            +
                    shared: namespace_info(namespaces.shared.first)
         | 
| 98 | 
            +
                  }
         | 
| 99 | 
            +
                  Kernel.puts list.to_json
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                def list_namespaces(namespaces)
         | 
| 103 | 
            +
                  Kernel.puts format(
         | 
| 104 | 
            +
                    "%-10<name>s %-10<prefix>s %<delim>s",
         | 
| 105 | 
            +
                    {name: "Name", prefix: "Prefix", delim: "Delimiter"}
         | 
| 106 | 
            +
                  )
         | 
| 107 | 
            +
                  list_namespace namespaces, :personal
         | 
| 108 | 
            +
                  list_namespace namespaces, :other
         | 
| 109 | 
            +
                  list_namespace namespaces, :shared
         | 
| 110 | 
            +
                end
         | 
| 110 111 |  | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 113 | 
            -
             | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
                    end
         | 
| 112 | 
            +
                def list_namespace(namespaces, name)
         | 
| 113 | 
            +
                  info = namespace_info(namespaces.send(name).first, quote: true)
         | 
| 114 | 
            +
                  if info
         | 
| 115 | 
            +
                    Kernel.puts format("%-10<name>s %-10<prefix>s %<delim>s", name: name, **info)
         | 
| 116 | 
            +
                  else
         | 
| 117 | 
            +
                    Kernel.puts format("%-10<name>s (Not defined)", name: name)
         | 
| 118 118 | 
             
                  end
         | 
| 119 | 
            +
                end
         | 
| 119 120 |  | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 121 | 
            +
                def namespace_info(namespace, quote: false)
         | 
| 122 | 
            +
                  return nil if !namespace
         | 
| 122 123 |  | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 125 | 
            -
             | 
| 126 | 
            -
             | 
| 127 | 
            -
                  end
         | 
| 124 | 
            +
                  {
         | 
| 125 | 
            +
                    prefix: quote ? namespace.prefix.to_json : namespace.prefix,
         | 
| 126 | 
            +
                    delim: quote ? namespace.delim.to_json : namespace.delim
         | 
| 127 | 
            +
                  }
         | 
| 128 128 | 
             
                end
         | 
| 129 129 | 
             
              end
         | 
| 130 130 | 
             
            end
         | 
| @@ -12,9 +12,6 @@ module Imap::Backup | |
| 12 12 | 
             
                include Thor::Actions
         | 
| 13 13 | 
             
                include CLI::Helpers
         | 
| 14 14 |  | 
| 15 | 
            -
                attr_reader :email
         | 
| 16 | 
            -
                attr_reader :options
         | 
| 17 | 
            -
             | 
| 18 15 | 
             
                def initialize(email = nil, options)
         | 
| 19 16 | 
             
                  super([])
         | 
| 20 17 | 
             
                  @email = email
         | 
| @@ -42,5 +39,10 @@ module Imap::Backup | |
| 42 39 | 
             
                    end
         | 
| 43 40 | 
             
                  end
         | 
| 44 41 | 
             
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                private
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                attr_reader :email
         | 
| 46 | 
            +
                attr_reader :options
         | 
| 45 47 | 
             
              end
         | 
| 46 48 | 
             
            end
         | 
| @@ -12,8 +12,6 @@ module Imap::Backup | |
| 12 12 | 
             
                include Thor::Actions
         | 
| 13 13 | 
             
                include CLI::Helpers
         | 
| 14 14 |  | 
| 15 | 
            -
                attr_reader :options
         | 
| 16 | 
            -
             | 
| 17 15 | 
             
                def initialize(options)
         | 
| 18 16 | 
             
                  super([])
         | 
| 19 17 | 
             
                  @options = options
         | 
| @@ -25,5 +23,9 @@ module Imap::Backup | |
| 25 23 | 
             
                    Setup.new(config: config).run
         | 
| 26 24 | 
             
                  end
         | 
| 27 25 | 
             
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                private
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                attr_reader :options
         | 
| 28 30 | 
             
              end
         | 
| 29 31 | 
             
            end
         | 
| @@ -11,9 +11,6 @@ module Imap::Backup | |
| 11 11 | 
             
              class CLI::Single < Thor; end
         | 
| 12 12 |  | 
| 13 13 | 
             
              class CLI::Single::Backup
         | 
| 14 | 
            -
                attr_reader :options
         | 
| 15 | 
            -
                attr_reader :password
         | 
| 16 | 
            -
             | 
| 17 14 | 
             
                def initialize(options)
         | 
| 18 15 | 
             
                  @options = options
         | 
| 19 16 | 
             
                  @password = nil
         | 
| @@ -40,6 +37,9 @@ module Imap::Backup | |
| 40 37 |  | 
| 41 38 | 
             
                private
         | 
| 42 39 |  | 
| 40 | 
            +
                attr_reader :options
         | 
| 41 | 
            +
                attr_reader :password
         | 
| 42 | 
            +
             | 
| 43 43 | 
             
                def process_options!
         | 
| 44 44 | 
             
                  if !email
         | 
| 45 45 | 
             
                    raise Thor::RequiredArgumentMissingError,
         | 
| @@ -8,17 +8,6 @@ module Imap::Backup | |
| 8 8 | 
             
                include Thor::Actions
         | 
| 9 9 | 
             
                include CLI::Helpers
         | 
| 10 10 |  | 
| 11 | 
            -
                TEXT_COLUMNS = [
         | 
| 12 | 
            -
                  {name: :folder, width: 20, alignment: :left},
         | 
| 13 | 
            -
                  {name: :remote, width: 8, alignment: :right},
         | 
| 14 | 
            -
                  {name: :both, width: 8, alignment: :right},
         | 
| 15 | 
            -
                  {name: :local, width: 8, alignment: :right}
         | 
| 16 | 
            -
                ].freeze
         | 
| 17 | 
            -
                ALIGNMENT_FORMAT_SYMBOL = {left: "-", right: " "}.freeze
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                attr_reader :email
         | 
| 20 | 
            -
                attr_reader :options
         | 
| 21 | 
            -
             | 
| 22 11 | 
             
                def initialize(email, options)
         | 
| 23 12 | 
             
                  super([])
         | 
| 24 13 | 
             
                  @email = email
         | 
| @@ -34,56 +23,69 @@ module Imap::Backup | |
| 34 23 | 
             
                      format_text stats
         | 
| 35 24 | 
             
                    end
         | 
| 36 25 | 
             
                  end
         | 
| 26 | 
            +
                end
         | 
| 37 27 |  | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
                      next if !folder.exist?
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                      serializer = Serializer.new(account.local_path, folder.name)
         | 
| 50 | 
            -
                      local_uids = serializer.uids
         | 
| 51 | 
            -
                      Logger.logger.debug("[Stats] fetching email list for '#{folder.name}'")
         | 
| 52 | 
            -
                      remote_uids = folder.uids
         | 
| 53 | 
            -
                      {
         | 
| 54 | 
            -
                        folder: folder.name,
         | 
| 55 | 
            -
                        remote: (remote_uids - local_uids).count,
         | 
| 56 | 
            -
                        both: (serializer.uids & folder.uids).count,
         | 
| 57 | 
            -
                        local: (local_uids - remote_uids).count
         | 
| 58 | 
            -
                      }
         | 
| 59 | 
            -
                    end.compact
         | 
| 60 | 
            -
                  end
         | 
| 28 | 
            +
                private
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                TEXT_COLUMNS = [
         | 
| 31 | 
            +
                  {name: :folder, width: 20, alignment: :left},
         | 
| 32 | 
            +
                  {name: :remote, width: 8, alignment: :right},
         | 
| 33 | 
            +
                  {name: :both, width: 8, alignment: :right},
         | 
| 34 | 
            +
                  {name: :local, width: 8, alignment: :right}
         | 
| 35 | 
            +
                ].freeze
         | 
| 36 | 
            +
                ALIGNMENT_FORMAT_SYMBOL = {left: "-", right: " "}.freeze
         | 
| 61 37 |  | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 38 | 
            +
                attr_reader :email
         | 
| 39 | 
            +
                attr_reader :options
         | 
| 64 40 |  | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
                        format("%#{symbol}#{column[:width]}s", count)
         | 
| 70 | 
            -
                      end.join("|")
         | 
| 41 | 
            +
                def stats
         | 
| 42 | 
            +
                  Logger.logger.debug("[Stats] loading configuration")
         | 
| 43 | 
            +
                  config = load_config(**options)
         | 
| 44 | 
            +
                  account = account(config, email)
         | 
| 71 45 |  | 
| 72 | 
            -
             | 
| 73 | 
            -
                     | 
| 74 | 
            -
                   | 
| 46 | 
            +
                  backup_folders = Account::BackupFolders.new(
         | 
| 47 | 
            +
                    client: account.client, account: account
         | 
| 48 | 
            +
                  )
         | 
| 49 | 
            +
                  backup_folders.map do |folder|
         | 
| 50 | 
            +
                    next if !folder.exist?
         | 
| 75 51 |  | 
| 76 | 
            -
             | 
| 77 | 
            -
                     | 
| 78 | 
            -
             | 
| 79 | 
            -
                     | 
| 52 | 
            +
                    serializer = Serializer.new(account.local_path, folder.name)
         | 
| 53 | 
            +
                    local_uids = serializer.uids
         | 
| 54 | 
            +
                    Logger.logger.debug("[Stats] fetching email list for '#{folder.name}'")
         | 
| 55 | 
            +
                    remote_uids = folder.uids
         | 
| 56 | 
            +
                    {
         | 
| 57 | 
            +
                      folder: folder.name,
         | 
| 58 | 
            +
                      remote: (remote_uids - local_uids).count,
         | 
| 59 | 
            +
                      both: (serializer.uids & folder.uids).count,
         | 
| 60 | 
            +
                      local: (local_uids - remote_uids).count
         | 
| 61 | 
            +
                    }
         | 
| 62 | 
            +
                  end.compact
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def format_text(stats)
         | 
| 66 | 
            +
                  Kernel.puts text_header
         | 
| 80 67 |  | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 68 | 
            +
                  stats.each do |stat|
         | 
| 69 | 
            +
                    columns = TEXT_COLUMNS.map do |column|
         | 
| 70 | 
            +
                      symbol = ALIGNMENT_FORMAT_SYMBOL[column[:alignment]]
         | 
| 71 | 
            +
                      count = stat[column[:name]]
         | 
| 72 | 
            +
                      format("%#{symbol}#{column[:width]}s", count)
         | 
| 83 73 | 
             
                    end.join("|")
         | 
| 84 74 |  | 
| 85 | 
            -
                     | 
| 75 | 
            +
                    Kernel.puts columns
         | 
| 86 76 | 
             
                  end
         | 
| 87 77 | 
             
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                def text_header
         | 
| 80 | 
            +
                  titles = TEXT_COLUMNS.map do |column|
         | 
| 81 | 
            +
                    format("%-#{column[:width]}s", column[:name])
         | 
| 82 | 
            +
                  end.join("|")
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  underline = TEXT_COLUMNS.map do |column|
         | 
| 85 | 
            +
                    "-" * column[:width]
         | 
| 86 | 
            +
                  end.join("|")
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  "#{titles}\n#{underline}"
         | 
| 89 | 
            +
                end
         | 
| 88 90 | 
             
              end
         | 
| 89 91 | 
             
            end
         | 
| @@ -14,18 +14,6 @@ module Imap::Backup | |
| 14 14 |  | 
| 15 15 | 
             
                ACTIONS = %i(migrate mirror).freeze
         | 
| 16 16 |  | 
| 17 | 
            -
                attr_reader :action
         | 
| 18 | 
            -
                attr_accessor :automatic_namespaces
         | 
| 19 | 
            -
                attr_accessor :config_path
         | 
| 20 | 
            -
                attr_accessor :destination_delimiter
         | 
| 21 | 
            -
                attr_reader :destination_email
         | 
| 22 | 
            -
                attr_accessor :destination_prefix
         | 
| 23 | 
            -
                attr_reader :options
         | 
| 24 | 
            -
                attr_accessor :reset
         | 
| 25 | 
            -
                attr_accessor :source_delimiter
         | 
| 26 | 
            -
                attr_reader :source_email
         | 
| 27 | 
            -
                attr_accessor :source_prefix
         | 
| 28 | 
            -
             | 
| 29 17 | 
             
                def initialize(action, source_email, destination_email, options)
         | 
| 30 18 | 
             
                  super([])
         | 
| 31 19 | 
             
                  @action = action
         | 
| @@ -57,108 +45,120 @@ module Imap::Backup | |
| 57 45 | 
             
                      end
         | 
| 58 46 | 
             
                    end
         | 
| 59 47 | 
             
                  end
         | 
| 48 | 
            +
                end
         | 
| 60 49 |  | 
| 61 | 
            -
             | 
| 62 | 
            -
                    self.automatic_namespaces = options[:automatic_namespaces] || false
         | 
| 63 | 
            -
                    self.config_path = options[:config]
         | 
| 64 | 
            -
                    self.destination_delimiter = options[:destination_delimiter]
         | 
| 65 | 
            -
                    self.destination_prefix = options[:destination_prefix]
         | 
| 66 | 
            -
                    self.source_delimiter = options[:source_delimiter]
         | 
| 67 | 
            -
                    self.source_prefix = options[:source_prefix]
         | 
| 68 | 
            -
                    self.reset = options[:reset] || false
         | 
| 69 | 
            -
                    check_accounts!
         | 
| 70 | 
            -
                    choose_prefixes_and_delimiters!
         | 
| 71 | 
            -
                  end
         | 
| 50 | 
            +
                private
         | 
| 72 51 |  | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 52 | 
            +
                attr_reader :action
         | 
| 53 | 
            +
                attr_accessor :automatic_namespaces
         | 
| 54 | 
            +
                attr_accessor :config_path
         | 
| 55 | 
            +
                attr_accessor :destination_delimiter
         | 
| 56 | 
            +
                attr_reader :destination_email
         | 
| 57 | 
            +
                attr_accessor :destination_prefix
         | 
| 58 | 
            +
                attr_reader :options
         | 
| 59 | 
            +
                attr_accessor :reset
         | 
| 60 | 
            +
                attr_accessor :source_delimiter
         | 
| 61 | 
            +
                attr_reader :source_email
         | 
| 62 | 
            +
                attr_accessor :source_prefix
         | 
| 77 63 |  | 
| 78 | 
            -
             | 
| 64 | 
            +
                def process_options!
         | 
| 65 | 
            +
                  self.automatic_namespaces = options[:automatic_namespaces] || false
         | 
| 66 | 
            +
                  self.config_path = options[:config]
         | 
| 67 | 
            +
                  self.destination_delimiter = options[:destination_delimiter]
         | 
| 68 | 
            +
                  self.destination_prefix = options[:destination_prefix]
         | 
| 69 | 
            +
                  self.source_delimiter = options[:source_delimiter]
         | 
| 70 | 
            +
                  self.source_prefix = options[:source_prefix]
         | 
| 71 | 
            +
                  self.reset = options[:reset] || false
         | 
| 72 | 
            +
                  check_accounts!
         | 
| 73 | 
            +
                  choose_prefixes_and_delimiters!
         | 
| 74 | 
            +
                end
         | 
| 79 75 |  | 
| 80 | 
            -
             | 
| 76 | 
            +
                def check_accounts!
         | 
| 77 | 
            +
                  if destination_email == source_email
         | 
| 78 | 
            +
                    raise "Source and destination accounts cannot be the same!"
         | 
| 81 79 | 
             
                  end
         | 
| 82 80 |  | 
| 83 | 
            -
                   | 
| 84 | 
            -
                    if automatic_namespaces
         | 
| 85 | 
            -
                      ensure_no_prefix_or_delimiter_parameters!
         | 
| 86 | 
            -
                      query_servers_for_settings
         | 
| 87 | 
            -
                    else
         | 
| 88 | 
            -
                      add_prefix_and_delimiter_defaults
         | 
| 89 | 
            -
                    end
         | 
| 90 | 
            -
                  end
         | 
| 81 | 
            +
                  raise "Account '#{destination_email}' does not exist" if !destination_account
         | 
| 91 82 |  | 
| 92 | 
            -
                   | 
| 93 | 
            -
             | 
| 94 | 
            -
                      raise "--automatic-namespaces is incompatible with --destination-delimiter"
         | 
| 95 | 
            -
                    end
         | 
| 96 | 
            -
                    if destination_prefix
         | 
| 97 | 
            -
                      raise "--automatic-namespaces is incompatible with --destination-prefix"
         | 
| 98 | 
            -
                    end
         | 
| 99 | 
            -
                    raise "--automatic-namespaces is incompatible with --source-delimiter" if source_delimiter
         | 
| 100 | 
            -
                    raise "--automatic-namespaces is incompatible with --source-prefix" if source_prefix
         | 
| 101 | 
            -
                  end
         | 
| 83 | 
            +
                  raise "Account '#{source_email}' does not exist" if !source_account
         | 
| 84 | 
            +
                end
         | 
| 102 85 |  | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
                     | 
| 86 | 
            +
                def choose_prefixes_and_delimiters!
         | 
| 87 | 
            +
                  if automatic_namespaces
         | 
| 88 | 
            +
                    ensure_no_prefix_or_delimiter_parameters!
         | 
| 89 | 
            +
                    query_servers_for_settings
         | 
| 90 | 
            +
                  else
         | 
| 91 | 
            +
                    add_prefix_and_delimiter_defaults
         | 
| 106 92 | 
             
                  end
         | 
| 93 | 
            +
                end
         | 
| 107 94 |  | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
                     | 
| 111 | 
            -
                    [personal.prefix, personal.delim]
         | 
| 95 | 
            +
                def ensure_no_prefix_or_delimiter_parameters!
         | 
| 96 | 
            +
                  if destination_delimiter
         | 
| 97 | 
            +
                    raise "--automatic-namespaces is incompatible with --destination-delimiter"
         | 
| 112 98 | 
             
                  end
         | 
| 99 | 
            +
                  raise "--automatic-namespaces is incompatible with --destination-prefix" if destination_prefix
         | 
| 100 | 
            +
                  raise "--automatic-namespaces is incompatible with --source-delimiter" if source_delimiter
         | 
| 101 | 
            +
                  raise "--automatic-namespaces is incompatible with --source-prefix" if source_prefix
         | 
| 102 | 
            +
                end
         | 
| 113 103 |  | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
                    self.source_prefix ||= ""
         | 
| 119 | 
            -
                  end
         | 
| 104 | 
            +
                def query_servers_for_settings
         | 
| 105 | 
            +
                  self.destination_prefix, self.destination_delimiter = account_settings(destination_account)
         | 
| 106 | 
            +
                  self.source_prefix, self.source_delimiter = account_settings(source_account)
         | 
| 107 | 
            +
                end
         | 
| 120 108 |  | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 109 | 
            +
                def account_settings(account)
         | 
| 110 | 
            +
                  namespaces = account.client.namespace
         | 
| 111 | 
            +
                  personal = namespaces.personal.first
         | 
| 112 | 
            +
                  [personal.prefix, personal.delim]
         | 
| 113 | 
            +
                end
         | 
| 123 114 |  | 
| 124 | 
            -
             | 
| 125 | 
            -
                   | 
| 115 | 
            +
                def add_prefix_and_delimiter_defaults
         | 
| 116 | 
            +
                  self.destination_delimiter ||= "/"
         | 
| 117 | 
            +
                  self.destination_prefix ||= ""
         | 
| 118 | 
            +
                  self.source_delimiter ||= "/"
         | 
| 119 | 
            +
                  self.source_prefix ||= ""
         | 
| 120 | 
            +
                end
         | 
| 126 121 |  | 
| 127 | 
            -
             | 
| 128 | 
            -
             | 
| 122 | 
            +
                def prepare_mirror
         | 
| 123 | 
            +
                  warn_if_source_account_is_not_in_mirror_mode
         | 
| 129 124 |  | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 132 | 
            -
                      "is not set up to make mirror backups"
         | 
| 133 | 
            -
                    Logger.logger.warn message
         | 
| 134 | 
            -
                  end
         | 
| 125 | 
            +
                  CLI::Backup.new(config: config_path, accounts: source_email).run
         | 
| 126 | 
            +
                end
         | 
| 135 127 |  | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 138 | 
            -
                  end
         | 
| 128 | 
            +
                def warn_if_source_account_is_not_in_mirror_mode
         | 
| 129 | 
            +
                  return if source_account.mirror_mode
         | 
| 139 130 |  | 
| 140 | 
            -
                   | 
| 141 | 
            -
                    {
         | 
| 142 | 
            -
             | 
| 143 | 
            -
             | 
| 144 | 
            -
             | 
| 145 | 
            -
                      source: source_account,
         | 
| 146 | 
            -
                      source_delimiter: source_delimiter,
         | 
| 147 | 
            -
                      source_prefix: source_prefix
         | 
| 148 | 
            -
                    }
         | 
| 149 | 
            -
                  end
         | 
| 131 | 
            +
                  message =
         | 
| 132 | 
            +
                    "The account '#{source_account.username}' " \
         | 
| 133 | 
            +
                    "is not set up to make mirror backups"
         | 
| 134 | 
            +
                  Logger.logger.warn message
         | 
| 135 | 
            +
                end
         | 
| 150 136 |  | 
| 151 | 
            -
             | 
| 152 | 
            -
             | 
| 153 | 
            -
             | 
| 137 | 
            +
                def config
         | 
| 138 | 
            +
                  @config ||= load_config(config: config_path)
         | 
| 139 | 
            +
                end
         | 
| 154 140 |  | 
| 155 | 
            -
             | 
| 156 | 
            -
             | 
| 157 | 
            -
             | 
| 141 | 
            +
                def enumerator_options
         | 
| 142 | 
            +
                  {
         | 
| 143 | 
            +
                    destination: destination_account,
         | 
| 144 | 
            +
                    destination_delimiter: destination_delimiter,
         | 
| 145 | 
            +
                    destination_prefix: destination_prefix,
         | 
| 146 | 
            +
                    source: source_account,
         | 
| 147 | 
            +
                    source_delimiter: source_delimiter,
         | 
| 148 | 
            +
                    source_prefix: source_prefix
         | 
| 149 | 
            +
                  }
         | 
| 150 | 
            +
                end
         | 
| 158 151 |  | 
| 159 | 
            -
             | 
| 160 | 
            -
             | 
| 161 | 
            -
             | 
| 152 | 
            +
                def folders
         | 
| 153 | 
            +
                  CLI::FolderEnumerator.new(**enumerator_options)
         | 
| 154 | 
            +
                end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                def destination_account
         | 
| 157 | 
            +
                  config.accounts.find { |a| a.username == destination_email }
         | 
| 158 | 
            +
                end
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                def source_account
         | 
| 161 | 
            +
                  config.accounts.find { |a| a.username == source_email }
         | 
| 162 162 | 
             
                end
         | 
| 163 163 | 
             
              end
         | 
| 164 164 | 
             
            end
         |