imap-backup 8.0.0.rc1 → 8.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f852c0552212ba77c4e4dda834b8727860a788ea73ecf395255c248b9f728e4
4
- data.tar.gz: 1a33b9e2efd04d4e9dcd4e48b401d7001330de8950477947aa4547d15c9554e8
3
+ metadata.gz: ccb9808337c50af0fe59f2cdc85dedcbab50b5b0980a60e2ccf10fc3734b9f37
4
+ data.tar.gz: e0b67bd5bfb040220f54c9a662c90ad545fce9c84a5fadaf6d93a485ac865ffc
5
5
  SHA512:
6
- metadata.gz: 10ec5bf1f6426652d8b1875e1dbc4150ac7191aea988fbe2235d9966cb3394df1a6353d541f1de0c65a13c15a3b01d3502d4c36634427831244f2996a80d5bf6
7
- data.tar.gz: eba30293362b4bc0c79e3a389cf56d3d218addec339fbc802aa245df8ef8f6f0f7b0588f6ee7ec1b5def04dbdddff39bea91370718d0dab0026cf2189474e82d
6
+ metadata.gz: 4070e85e86a627a0791924cba209d044dd71fd54a7d0d79a7c34b9aade4d4827ae020ed3a78d5e4169d0d8aa188ce34741f5a6a44d4f0ae1b035e6bbead30df8
7
+ data.tar.gz: b20ac83f5bffea68ab4dd3ae12fcef6df78cb20aab136864ba97e23ddaa414fe344e122d430bf1de242e393e50ccef916ee4e1fba77353836aee9e148c5a3da8
data/README.md CHANGED
@@ -70,7 +70,6 @@ Alternatively, add it to your crontab.
70
70
  # Commands
71
71
 
72
72
  * [backup](docs/commands/backup.md)
73
- * [folders](docs/commands/folders.md)
74
73
  * [local accounts](docs/commands/local-accounts.md)
75
74
  * [local folders](docs/commands/local-folders.md)
76
75
  * [local list](docs/commands/local-list.md)
@@ -104,4 +103,8 @@ If you have problems:
104
103
 
105
104
  # Development
106
105
 
107
- See the [Development documentation](./docs/development.md)
106
+ See the [Development documentation](./docs/development.md) for notes
107
+ on development and testing.
108
+
109
+ See [the CHANGELOG](./CHANGELOG.md) to a list of changes that have been
110
+ made in each release.
@@ -12,16 +12,26 @@ module Imap::Backup
12
12
  end
13
13
 
14
14
  def run
15
+ all_names = Account::Connection::FolderNames.new(client: client, account: account).run
16
+
15
17
  names =
16
18
  if account.folders&.any?
17
19
  account.folders.map { |af| af[:name] }
18
20
  else
19
- Account::Connection::FolderNames.new(client: client, account: account).run
21
+ all_names
20
22
  end
21
23
 
22
- names.map do |name|
24
+ all_names.map do |name|
25
+ backup =
26
+ if account.folder_blacklist
27
+ !names.include?(name)
28
+ else
29
+ names.include?(name)
30
+ end
31
+ next if !backup
32
+
23
33
  Account::Folder.new(account.connection, name)
24
- end
34
+ end.compact
25
35
  end
26
36
  end
27
37
  end
@@ -27,6 +27,10 @@ module Imap::Backup
27
27
  Account::Connection::BackupFolders.new(client: client, account: account).run
28
28
  end
29
29
 
30
+ def namespaces
31
+ client.namespace
32
+ end
33
+
30
34
  def run_backup(refresh: false)
31
35
  Logger.logger.info "Running backup of account: #{account.username}"
32
36
  # start the connection so we get logging messages in the right order
@@ -100,24 +100,24 @@ module Imap::Backup
100
100
  end
101
101
 
102
102
  def delete_multi(uids)
103
- set_flags(uids, [:Deleted])
103
+ add_flags(uids, [:Deleted])
104
104
  client.expunge
105
105
  end
106
106
 
107
- def apply_flags(uids, flags)
107
+ def set_flags(uids, flags)
108
108
  client.select(utf7_encoded_name)
109
109
  flags.reject! { |f| f == :Recent }
110
110
  client.uid_store(uids, "FLAGS", flags)
111
111
  end
112
112
 
113
- def set_flags(uids, flags)
113
+ def add_flags(uids, flags)
114
114
  # Use read-write access, via `select`
115
115
  client.select(utf7_encoded_name)
116
116
  flags.reject! { |f| f == :Recent }
117
117
  client.uid_store(uids, "+FLAGS", flags)
118
118
  end
119
119
 
120
- def unset_flags(uids, flags)
120
+ def remove_flags(uids, flags)
121
121
  client.select(utf7_encoded_name)
122
122
  client.uid_store(uids, "-FLAGS", flags)
123
123
  end
@@ -126,7 +126,7 @@ module Imap::Backup
126
126
  existing = uids
127
127
  return if existing.empty?
128
128
 
129
- set_flags(existing, [:Deleted])
129
+ add_flags(existing, [:Deleted])
130
130
  client.expunge
131
131
  end
132
132
 
@@ -8,6 +8,7 @@ module Imap::Backup
8
8
  attr_reader :password
9
9
  attr_reader :local_path
10
10
  attr_reader :folders
11
+ attr_reader :folder_blacklist
11
12
  attr_reader :mirror_mode
12
13
  attr_reader :server
13
14
  attr_reader :connection_options
@@ -19,6 +20,7 @@ module Imap::Backup
19
20
  @password = options[:password]
20
21
  @local_path = options[:local_path]
21
22
  @folders = options[:folders]
23
+ @folder_blacklist = options[:folder_blacklist]
22
24
  @mirror_mode = options[:mirror_mode]
23
25
  @server = options[:server]
24
26
  @connection_options = options[:connection_options]
@@ -57,6 +59,7 @@ module Imap::Backup
57
59
  h = {username: @username, password: @password}
58
60
  h[:local_path] = @local_path if @local_path
59
61
  h[:folders] = @folders if @folders
62
+ h[:folder_blacklist] = true if @folder_blacklist
60
63
  h[:mirror_mode] = true if @mirror_mode
61
64
  h[:server] = @server if @server
62
65
  h[:connection_options] = @connection_options if @connection_options
@@ -85,6 +88,10 @@ module Imap::Backup
85
88
  update(:folders, value)
86
89
  end
87
90
 
91
+ def folder_blacklist=(value)
92
+ update(:folder_blacklist, value)
93
+ end
94
+
88
95
  def mirror_mode=(value)
89
96
  update(:mirror_mode, value)
90
97
  end
@@ -94,7 +101,12 @@ module Imap::Backup
94
101
  end
95
102
 
96
103
  def connection_options=(value)
97
- parsed = JSON.parse(value, symbolize_names: true)
104
+ parsed =
105
+ if value == ""
106
+ nil
107
+ else
108
+ JSON.parse(value, symbolize_names: true)
109
+ end
98
110
  update(:connection_options, parsed)
99
111
  end
100
112
 
@@ -13,12 +13,12 @@ module Imap::Backup
13
13
  )
14
14
  end
15
15
 
16
- def self.verbose_option
16
+ def self.format_option
17
17
  method_option(
18
- "verbose",
19
- type: :boolean,
20
- desc: "increase the amount of logging",
21
- aliases: ["-v"]
18
+ "format",
19
+ type: :string,
20
+ desc: "the output type, 'text' for plain text or 'json'",
21
+ aliases: ["-f"]
22
22
  )
23
23
  end
24
24
 
@@ -30,13 +30,37 @@ module Imap::Backup
30
30
  aliases: ["-q"]
31
31
  )
32
32
  end
33
+
34
+ def self.verbose_option
35
+ method_option(
36
+ "verbose",
37
+ type: :boolean,
38
+ desc: "increase the amount of logging",
39
+ aliases: ["-v"]
40
+ )
41
+ end
33
42
  end
34
43
  end
35
44
 
45
+ def options
46
+ @symbolized_options ||= # rubocop:disable Naming/MemoizedInstanceVariableName
47
+ begin
48
+ options = super()
49
+ options.each.with_object({}) do |(k, v), acc|
50
+ key =
51
+ if k.is_a?(String)
52
+ k.gsub("-", "_").intern
53
+ else
54
+ k
55
+ end
56
+ acc[key] = v
57
+ end
58
+ end
59
+ end
60
+
36
61
  def load_config(**options)
37
- opts = symbolized(options)
38
- path = opts[:config]
39
- require_exists = opts.key?(:require_exists) ? opts[:require_exists] : true
62
+ path = options[:config]
63
+ require_exists = options.key?(:require_exists) ? options[:require_exists] : true
40
64
  if require_exists
41
65
  exists = Configuration.exist?(path: path)
42
66
  if !exists
@@ -47,18 +71,6 @@ module Imap::Backup
47
71
  Configuration.new(path: path)
48
72
  end
49
73
 
50
- def symbolized(options)
51
- options.each.with_object({}) do |(k, v), acc|
52
- key =
53
- if k.is_a?(String)
54
- k.gsub("-", "_").intern
55
- else
56
- k
57
- end
58
- acc[key] = v
59
- end
60
- end
61
-
62
74
  def account(config, email)
63
75
  account = config.accounts.find { |a| a.username == email }
64
76
  raise "#{email} is not a configured account" if !account
@@ -7,43 +7,55 @@ module Imap::Backup
7
7
 
8
8
  desc "accounts", "List locally backed-up accounts"
9
9
  config_option
10
+ format_option
10
11
  def accounts
11
- config = load_config(**symbolized(options))
12
- config.accounts.each { |a| Kernel.puts a.username }
12
+ config = load_config(**options)
13
+ names = config.accounts.map(&:username)
14
+ case options[:format]
15
+ when "json"
16
+ list = names.map { |n| {username: n} }
17
+ Kernel.puts list.to_json
18
+ else
19
+ names.each { |n| Kernel.puts n }
20
+ end
13
21
  end
14
22
 
15
23
  desc "folders EMAIL", "List backed up folders"
16
24
  config_option
25
+ format_option
17
26
  def folders(email)
18
- config = load_config(**symbolized(options))
27
+ config = load_config(**options)
19
28
  connection = connection(config, email)
20
29
 
21
- connection.local_folders.each do |_s, f|
22
- Kernel.puts %("#{f.name}")
30
+ folders = connection.local_folders
31
+ case options[:format]
32
+ when "json"
33
+ list = folders.map { |_s, f| {name: f.name} }
34
+ Kernel.puts list.to_json
35
+ else
36
+ folders.each do |_s, f|
37
+ Kernel.puts %("#{f.name}")
38
+ end
23
39
  end
24
40
  end
25
41
 
26
42
  desc "list EMAIL FOLDER", "List emails in a folder"
27
43
  config_option
44
+ format_option
28
45
  def list(email, folder_name)
29
- config = load_config(**symbolized(options))
46
+ config = load_config(**options)
30
47
  connection = connection(config, email)
31
48
 
32
- folder_serializer, _folder = connection.local_folders.find do |(_s, f)|
49
+ serializer, _folder = connection.local_folders.find do |(_s, f)|
33
50
  f.name == folder_name
34
51
  end
35
- raise "Folder '#{folder_name}' not found" if !folder_serializer
36
-
37
- Kernel.puts format(
38
- "%-10<uid>s %-#{MAX_SUBJECT}<subject>s - %<date>s",
39
- {uid: "UID", subject: "Subject", date: "Date"}
40
- )
41
- Kernel.puts "-" * (12 + MAX_SUBJECT + 28)
52
+ raise "Folder '#{folder_name}' not found" if !serializer
42
53
 
43
- uids = folder_serializer.uids
44
-
45
- folder_serializer.each_message(uids).map do |message|
46
- list_message message
54
+ case options[:format]
55
+ when "json"
56
+ list_emails_as_json serializer
57
+ else
58
+ list_emails_as_text serializer
47
59
  end
48
60
  end
49
61
 
@@ -54,30 +66,51 @@ module Imap::Backup
54
66
  the UID.
55
67
  DESC
56
68
  config_option
69
+ format_option
57
70
  def show(email, folder_name, uids)
58
- config = load_config(**symbolized(options))
71
+ config = load_config(**options)
59
72
  connection = connection(config, email)
60
73
 
61
- folder_serializer, _folder = connection.local_folders.find do |(_s, f)|
74
+ serializer, _folder = connection.local_folders.find do |(_s, f)|
62
75
  f.name == folder_name
63
76
  end
64
- raise "Folder '#{folder_name}' not found" if !folder_serializer
77
+ raise "Folder '#{folder_name}' not found" if !serializer
65
78
 
66
79
  uid_list = uids.split(",")
67
- folder_serializer.each_message(uid_list).each do |message|
68
- if uid_list.count > 1
69
- Kernel.puts <<~HEADER
70
- #{'-' * 80}
71
- #{format('| UID: %-71s |', message.uid)}
72
- #{'-' * 80}
73
- HEADER
74
- end
75
- Kernel.puts message.body
80
+
81
+ case options[:format]
82
+ when "json"
83
+ show_emails_as_json serializer, uid_list
84
+ else
85
+ show_emails_as_text serializer, uid_list
76
86
  end
77
87
  end
78
88
 
79
89
  no_commands do
80
- def list_message(message)
90
+ def list_emails_as_json(serializer)
91
+ emails = serializer.each_message.map do |message|
92
+ {
93
+ uid: message.uid,
94
+ date: message.date.to_s,
95
+ subject: message.subject || ""
96
+ }
97
+ end
98
+ Kernel.puts emails.to_json
99
+ end
100
+
101
+ def list_emails_as_text(serializer)
102
+ Kernel.puts format(
103
+ "%-10<uid>s %-#{MAX_SUBJECT}<subject>s - %<date>s",
104
+ {uid: "UID", subject: "Subject", date: "Date"}
105
+ )
106
+ Kernel.puts "-" * (12 + MAX_SUBJECT + 28)
107
+
108
+ serializer.each_message.map do |message|
109
+ list_message_as_text message
110
+ end
111
+ end
112
+
113
+ def list_message_as_text(message)
81
114
  m = {
82
115
  uid: message.uid,
83
116
  date: message.date.to_s,
@@ -89,6 +122,26 @@ module Imap::Backup
89
122
  Kernel.puts format("% 10<uid>u: %-#{MAX_SUBJECT}<subject>s - %<date>s", m)
90
123
  end
91
124
  end
125
+
126
+ def show_emails_as_json(serializer, uids)
127
+ emails = serializer.each_message(uids).map do |m|
128
+ m.to_h.tap { |h| h[:body] = m.body }
129
+ end
130
+ Kernel.puts emails.to_json
131
+ end
132
+
133
+ def show_emails_as_text(serializer, uids)
134
+ serializer.each_message(uids).each do |message|
135
+ if uids.count > 1
136
+ Kernel.puts <<~HEADER
137
+ #{'-' * 80}
138
+ #{format('| UID: %-71s |', message.uid)}
139
+ #{'-' * 80}
140
+ HEADER
141
+ end
142
+ Kernel.puts message.body
143
+ end
144
+ end
92
145
  end
93
146
  end
94
147
  end
@@ -1,3 +1,5 @@
1
+ require "imap/backup/logger"
2
+
1
3
  module Imap::Backup
2
4
  class CLI::Remote < Thor
3
5
  include Thor::Actions
@@ -5,14 +7,100 @@ module Imap::Backup
5
7
 
6
8
  desc "folders EMAIL", "List account folders"
7
9
  config_option
8
- verbose_option
10
+ format_option
9
11
  quiet_option
12
+ verbose_option
10
13
  def folders(email)
11
- config = load_config(**symbolized(options))
14
+ Imap::Backup::Logger.setup_logging options
15
+ names = names(email)
16
+ case options[:format]
17
+ when "json"
18
+ json_format_names names
19
+ else
20
+ list_names names
21
+ end
22
+ end
23
+
24
+ desc "namespaces EMAIL", "List account namespaces"
25
+ long_desc <<~DESC
26
+ Lists namespaces defined for an email account.
27
+
28
+ This command is useful in deciding the parameters for
29
+ the `imap-backup migrate` and `imap-backup mirror` commands.
30
+ DESC
31
+ config_option
32
+ format_option
33
+ quiet_option
34
+ verbose_option
35
+ def namespaces(email)
36
+ Imap::Backup::Logger.setup_logging options
37
+ config = load_config(**options)
12
38
  connection = connection(config, email)
39
+ namespaces = connection.namespaces
40
+ case options[:format]
41
+ when "json"
42
+ json_format_namespaces namespaces
43
+ else
44
+ list_namespaces namespaces
45
+ end
46
+ end
47
+
48
+ no_commands do
49
+ def names(email)
50
+ config = load_config(**options)
51
+ connection = connection(config, email)
52
+
53
+ connection.folder_names
54
+ end
55
+
56
+ def json_format_names(names)
57
+ list = names.map do |name|
58
+ {name: name}
59
+ end
60
+ Kernel.puts list.to_json
61
+ end
62
+
63
+ def list_names(names)
64
+ names.each do |name|
65
+ Kernel.puts %("#{name}")
66
+ end
67
+ end
68
+
69
+ def json_format_namespaces(namespaces)
70
+ list = {
71
+ personal: namespace_info(namespaces.personal.first),
72
+ other: namespace_info(namespaces.other.first),
73
+ shared: namespace_info(namespaces.shared.first)
74
+ }
75
+ Kernel.puts list.to_json
76
+ end
77
+
78
+ def list_namespaces(namespaces)
79
+ Kernel.puts format(
80
+ "%-10<name>s %-10<prefix>s %<delim>s",
81
+ {name: "Name", prefix: "Prefix", delim: "Delimiter"}
82
+ )
83
+ list_namespace namespaces, :personal
84
+ list_namespace namespaces, :other
85
+ list_namespace namespaces, :shared
86
+ end
87
+
88
+ def list_namespace(namespaces, name)
89
+ info = namespace_info(namespaces.send(name).first, quote: true)
90
+ if info
91
+ Kernel.puts format("%-10<name>s %-10<prefix>s %<delim>s", name: name, **info)
92
+ else
93
+ Kernel.puts format("%-10<name>s (Not defined)", name: name)
94
+ end
95
+ end
96
+
97
+ def namespace_info(namespace, quote: false)
98
+ return nil if !namespace
13
99
 
14
- connection.folder_names.each do |name|
15
- Kernel.puts %("#{name}")
100
+ {
101
+ prefix: quote ? namespace.prefix.to_json : namespace.prefix,
102
+ delim: quote ? namespace.delim.to_json : namespace.delim
103
+ }
16
104
  end
17
105
  end
18
106
  end
@@ -8,12 +8,12 @@ module Imap::Backup
8
8
  FAKE_EMAIL = "fake@email.com".freeze
9
9
 
10
10
  desc "ignore-history EMAIL", "Skip downloading emails up to today for all configured folders"
11
- verbose_option
11
+ config_option
12
12
  quiet_option
13
+ verbose_option
13
14
  def ignore_history(email)
14
- opts = symbolized(options)
15
- Imap::Backup::Logger.setup_logging opts
16
- config = load_config(**opts)
15
+ Imap::Backup::Logger.setup_logging options
16
+ config = load_config(**options)
17
17
  connection = connection(config, email)
18
18
 
19
19
  connection.backup_folders.each do |folder|
@@ -27,12 +27,13 @@ module Imap::Backup
27
27
  desc(
28
28
  "export-to-thunderbird EMAIL [OPTIONS]",
29
29
  <<~DOC
30
- [Experimental] Copy backed up emails to Thunderbird.
30
+ Copy backed up emails to Thunderbird.
31
31
  A folder called 'imap-backup/EMAIL' is created under 'Local Folders'.
32
32
  DOC
33
33
  )
34
- verbose_option
34
+ config_option
35
35
  quiet_option
36
+ verbose_option
36
37
  method_option(
37
38
  "force",
38
39
  type: :boolean,
@@ -46,12 +47,11 @@ module Imap::Backup
46
47
  aliases: ["-p"]
47
48
  )
48
49
  def export_to_thunderbird(email)
49
- opts = symbolized(options)
50
- Imap::Backup::Logger.setup_logging opts
51
- force = opts.key?(:force) ? opts[:force] : false
52
- profile_name = opts[:profile]
50
+ Imap::Backup::Logger.setup_logging options
51
+ force = options.key?(:force) ? options[:force] : false
52
+ profile_name = options[:profile]
53
53
 
54
- config = load_config(**opts)
54
+ config = load_config(**options)
55
55
  connection = connection(config, email)
56
56
  profile = thunderbird_profile(profile_name)
57
57
 
@@ -45,8 +45,8 @@ module Imap::Backup
45
45
  DESC
46
46
  accounts_option
47
47
  config_option
48
- verbose_option
49
48
  quiet_option
49
+ verbose_option
50
50
  method_option(
51
51
  "refresh",
52
52
  type: :boolean,
@@ -54,21 +54,8 @@ module Imap::Backup
54
54
  aliases: ["-r"]
55
55
  )
56
56
  def backup
57
- Imap::Backup::Logger.setup_logging symbolized(options)
58
- Backup.new(symbolized(options)).run
59
- end
60
-
61
- desc "folders [OPTIONS]", "This command is deprecated, use `imap-backup remote folders ACCOUNT`"
62
- long_desc <<~DESC
63
- Lists all folders of all configured accounts.
64
- This command is deprecated.
65
- Instead, use a combination of `imap-backup local accounts` to get the list of accounts,
66
- and `imap-backup remote folders ACCOUNT` to get the folder list.
67
- DESC
68
- accounts_option
69
- config_option
70
- def folders
71
- Folders.new(symbolized(options)).run
57
+ Imap::Backup::Logger.setup_logging options
58
+ Backup.new(options).run
72
59
  end
73
60
 
74
61
  desc "local SUBCOMMAND [OPTIONS]", "View local info"
@@ -76,7 +63,6 @@ module Imap::Backup
76
63
 
77
64
  desc(
78
65
  "migrate SOURCE_EMAIL DESTINATION_EMAIL [OPTIONS]",
79
- "[Experimental] " \
80
66
  "Uploads backed-up emails from account SOURCE_EMAIL to account DESTINATION_EMAIL"
81
67
  )
82
68
  long_desc <<~DESC
@@ -98,8 +84,8 @@ module Imap::Backup
98
84
  deleted before uploading the migrated emails.
99
85
  DESC
100
86
  config_option
101
- verbose_option
102
87
  quiet_option
88
+ verbose_option
103
89
  method_option(
104
90
  "destination-prefix",
105
91
  type: :string,
@@ -119,8 +105,8 @@ module Imap::Backup
119
105
  aliases: ["-s"]
120
106
  )
121
107
  def migrate(source_email, destination_email)
122
- Imap::Backup::Logger.setup_logging symbolized(options)
123
- Migrate.new(source_email, destination_email, **symbolized(options)).run
108
+ Imap::Backup::Logger.setup_logging options
109
+ Migrate.new(source_email, destination_email, **options).run
124
110
  end
125
111
 
126
112
  desc(
@@ -144,8 +130,8 @@ module Imap::Backup
144
130
  the known UIDs on the source account to those on the destination account.
145
131
  DESC
146
132
  config_option
147
- verbose_option
148
133
  quiet_option
134
+ verbose_option
149
135
  method_option(
150
136
  "destination-prefix",
151
137
  type: :string,
@@ -159,8 +145,8 @@ module Imap::Backup
159
145
  aliases: ["-s"]
160
146
  )
161
147
  def mirror(source_email, destination_email)
162
- Imap::Backup::Logger.setup_logging symbolized(options)
163
- Mirror.new(source_email, destination_email, **symbolized(options)).run
148
+ Imap::Backup::Logger.setup_logging options
149
+ Mirror.new(source_email, destination_email, **options).run
164
150
  end
165
151
 
166
152
  desc "remote SUBCOMMAND [OPTIONS]", "View info about online accounts"
@@ -173,11 +159,11 @@ module Imap::Backup
173
159
  DESC
174
160
  accounts_option
175
161
  config_option
176
- verbose_option
177
162
  quiet_option
163
+ verbose_option
178
164
  def restore(email = nil)
179
- Imap::Backup::Logger.setup_logging symbolized(options)
180
- Restore.new(email, symbolized(options)).run
165
+ Imap::Backup::Logger.setup_logging options
166
+ Restore.new(email, options).run
181
167
  end
182
168
 
183
169
  desc "setup", "Configure imap-backup"
@@ -186,11 +172,11 @@ module Imap::Backup
186
172
  Configure email accounts to back up.
187
173
  DESC
188
174
  config_option
189
- verbose_option
190
175
  quiet_option
176
+ verbose_option
191
177
  def setup
192
- Imap::Backup::Logger.setup_logging symbolized(options)
193
- Setup.new(symbolized(options)).run
178
+ Imap::Backup::Logger.setup_logging options
179
+ Setup.new(options).run
194
180
  end
195
181
 
196
182
  desc "stats EMAIL [OPTIONS]", "Print stats for each account folder"
@@ -200,17 +186,12 @@ module Imap::Backup
200
186
  are only present in the backup (as they have been deleted on the server) "local".
201
187
  DESC
202
188
  config_option
203
- verbose_option
189
+ format_option
204
190
  quiet_option
205
- method_option(
206
- "format",
207
- type: :string,
208
- desc: "the output type, text (plain text) or json",
209
- aliases: ["-f"]
210
- )
191
+ verbose_option
211
192
  def stats(email)
212
- Imap::Backup::Logger.setup_logging symbolized(options)
213
- Stats.new(email, symbolized(options)).run
193
+ Imap::Backup::Logger.setup_logging options
194
+ Stats.new(email, options).run
214
195
  end
215
196
 
216
197
  desc "utils SUBCOMMAND [OPTIONS]", "Various utilities"
@@ -7,7 +7,7 @@ module Imap::Backup
7
7
  class Client::Default
8
8
  extend Forwardable
9
9
  def_delegators :imap, *%i(
10
- append authenticate create expunge login
10
+ append authenticate create expunge login namespace
11
11
  responses uid_fetch uid_search uid_store
12
12
  )
13
13
 
@@ -51,7 +51,7 @@ module Imap::Backup
51
51
  if changed.any?
52
52
  ids = changed.join(", ")
53
53
  debug "Removing '\Seen' flag for the following messages: #{ids}"
54
- folder.unset_flags(changed, [:Seen])
54
+ folder.remove_flags(changed, [:Seen])
55
55
  end
56
56
  uids_and_bodies
57
57
  else
@@ -56,7 +56,7 @@ module Imap::Backup
56
56
  next if !source_uid
57
57
 
58
58
  message = serializer.get(source_uid)
59
- folder.apply_flags([destination_uid], message.flags) if flags.sort != message.flags.sort
59
+ folder.set_flags([destination_uid], message.flags) if flags.sort != message.flags.sort
60
60
  end
61
61
  end
62
62
 
@@ -14,14 +14,22 @@ module Imap::Backup
14
14
  end
15
15
 
16
16
  def run
17
+ rows = [
18
+ email,
19
+ password,
20
+ server,
21
+ connection_options,
22
+ mode,
23
+ path,
24
+ folders,
25
+ multi_fetch,
26
+ reset_seen_flags_after_fetch
27
+ ].compact
28
+
17
29
  menu.header = <<~HEADER.chomp
18
30
  #{helpers.title_prefix} Account#{modified_flag}
19
31
 
20
- email #{space}#{account.username}
21
- password#{space}#{masked_password}
22
- path #{space}#{local_path}
23
- folders #{space}#{folders.map { |f| f[:name] }.join(', ')}#{mirror_mode}#{multi_fetch_size}
24
- server #{space}#{account.server}#{connection_options}#{reset_seen_flags_after_fetch}
32
+ #{format_rows(rows)}
25
33
 
26
34
  Choose an action
27
35
  HEADER
@@ -29,28 +37,70 @@ module Imap::Backup
29
37
 
30
38
  private
31
39
 
32
- def folders
33
- account.folders || []
40
+ def modified_flag
41
+ account.modified? ? "*" : ""
34
42
  end
35
43
 
36
- def mirror_mode
37
- if account.mirror_mode
38
- "\nmode #{space}mirror emails"
39
- else
40
- "\nmode #{space}keep all emails"
41
- end
44
+ def email
45
+ ["email", account.username]
42
46
  end
43
47
 
44
- def helpers
45
- Setup::Helpers.new
48
+ def password
49
+ masked_password =
50
+ if (account.password == "") || account.password.nil?
51
+ "(unset)"
52
+ else
53
+ account.password.gsub(/./, "x")
54
+ end
55
+ ["password", masked_password]
46
56
  end
47
57
 
48
- def modified_flag
49
- account.modified? ? "*" : ""
58
+ def path
59
+ # In order to handle backslashes, as Highline effectively
60
+ # does an eval (!) on its templates, we need to doubly
61
+ # escape them
62
+ local_path = account.local_path.gsub("\\", "\\\\\\\\")
63
+ ["path", local_path]
50
64
  end
51
65
 
52
- def multi_fetch_size
53
- "\nmulti-fetch #{account.multi_fetch_size}" if account.multi_fetch_size > 1
66
+ def folders
67
+ label =
68
+ if account.folder_blacklist
69
+ "exclude"
70
+ else
71
+ "include"
72
+ end
73
+ items = account.folders || []
74
+ list =
75
+ case
76
+ when items.any?
77
+ items.map { |f| f[:name] }.join(", ")
78
+ when !account.folder_blacklist
79
+ "(all folders)"
80
+ else
81
+ "(all folders) <- you have opted to not backup any folders!"
82
+ end
83
+ [label, list]
84
+ end
85
+
86
+ def mode
87
+ value =
88
+ if account.mirror_mode
89
+ "mirror emails"
90
+ else
91
+ "keep all emails"
92
+ end
93
+ ["mode", value]
94
+ end
95
+
96
+ def multi_fetch
97
+ return nil if account.multi_fetch_size == 1
98
+
99
+ ["multi-fetch", account.multi_fetch_size]
100
+ end
101
+
102
+ def server
103
+ ["server", account.server]
54
104
  end
55
105
 
56
106
  def connection_options
@@ -58,32 +108,33 @@ module Imap::Backup
58
108
 
59
109
  escaped = JSON.generate(account.connection_options)
60
110
  escaped.gsub!('"', '\"')
61
- "\nconnection options '#{escaped}'"
111
+ ["connection options", "'#{escaped}'"]
62
112
  end
63
113
 
64
114
  def reset_seen_flags_after_fetch
65
115
  return nil if !account.reset_seen_flags_after_fetch
66
116
 
67
- "\nchanges to unread flags will be reset during download"
117
+ ["changes to unread flags will be reset during download"]
68
118
  end
69
119
 
70
- def space
71
- account.connection_options ? " " * 12 : " " * 4
72
- end
73
-
74
- def masked_password
75
- if (account.password == "") || account.password.nil?
76
- "(unset)"
77
- else
78
- account.password.gsub(/./, "x")
120
+ def format_rows(rows)
121
+ largest_label, _value = rows.max_by do |(label, value)|
122
+ if value
123
+ label.length
124
+ else
125
+ 0
126
+ end
79
127
  end
128
+ rows.map do |(label, value)|
129
+ format(
130
+ "%-#{largest_label.length}<label>s %<value>s",
131
+ {label: label, value: value}
132
+ )
133
+ end.join("\n")
80
134
  end
81
135
 
82
- def local_path
83
- # In order to handle backslashes, as Highline effectively
84
- # does an eval (!) on its templates, we need to doubly
85
- # escape them
86
- account.local_path.gsub("\\", "\\\\\\\\")
136
+ def helpers
137
+ Setup::Helpers.new
87
138
  end
88
139
  end
89
140
  end
@@ -36,14 +36,15 @@ module Imap::Backup
36
36
  header menu
37
37
  modify_email menu
38
38
  modify_password menu
39
+ modify_server menu
40
+ modify_connection_options menu
41
+ test_connection menu
42
+ toggle_mirror_mode menu
39
43
  modify_backup_path menu
44
+ toggle_folder_blacklist menu
40
45
  choose_folders menu
41
- toggle_mirror_mode menu
42
46
  modify_multi_fetch_size menu
43
- modify_server menu
44
- modify_connection_options menu
45
47
  toggle_reset_seen_flags_after_fetch menu
46
- test_connection menu
47
48
  delete_account menu
48
49
  menu.choice("(q) return to main menu") { throw :done }
49
50
  menu.hidden("quit") { throw :done }
@@ -74,8 +75,17 @@ module Imap::Backup
74
75
  end
75
76
  end
76
77
 
78
+ def toggle_folder_blacklist(menu)
79
+ menu_item = "toggle folder inclusion mode (whitelist/blacklist)"
80
+ new_value = account.folder_blacklist ? nil : true
81
+ menu.choice(menu_item) do
82
+ account.folder_blacklist = new_value
83
+ end
84
+ end
85
+
77
86
  def choose_folders(menu)
78
- menu.choice("choose backup folders") do
87
+ action = account.folder_blacklist ? "exclude from backups" : "include in backups"
88
+ menu.choice("choose folders to #{action}") do
79
89
  Setup::FolderChooser.new(account).run
80
90
  end
81
91
  end
@@ -105,15 +115,7 @@ module Imap::Backup
105
115
 
106
116
  def modify_connection_options(menu)
107
117
  menu.choice("modify connection options") do
108
- default =
109
- if account.connection_options
110
- account.connection_options.to_json
111
- else
112
- ""
113
- end
114
- connection_options = highline.ask("connections options (as JSON): ") do |q|
115
- q.default = default
116
- end
118
+ connection_options = highline.ask("connections options (as JSON): ")
117
119
  if !connection_options.nil?
118
120
  begin
119
121
  account.connection_options = connection_options
@@ -4,6 +4,6 @@ module Imap::Backup
4
4
  MAJOR = 8
5
5
  MINOR = 0
6
6
  REVISION = 0
7
- PRE = "rc1".freeze
7
+ PRE = nil
8
8
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imap-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.0.rc1
4
+ version: 8.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Yates
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-19 00:00:00.000000000 Z
11
+ date: 2022-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -240,7 +240,6 @@ files:
240
240
  - lib/imap/backup/account/folder.rb
241
241
  - lib/imap/backup/cli.rb
242
242
  - lib/imap/backup/cli/backup.rb
243
- - lib/imap/backup/cli/folders.rb
244
243
  - lib/imap/backup/cli/helpers.rb
245
244
  - lib/imap/backup/cli/local.rb
246
245
  - lib/imap/backup/cli/migrate.rb
@@ -300,9 +299,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
300
299
  version: '2.6'
301
300
  required_rubygems_version: !ruby/object:Gem::Requirement
302
301
  requirements:
303
- - - ">"
302
+ - - ">="
304
303
  - !ruby/object:Gem::Version
305
- version: 1.3.1
304
+ version: '0'
306
305
  requirements: []
307
306
  rubygems_version: 3.3.7
308
307
  signing_key:
@@ -1,35 +0,0 @@
1
- module Imap::Backup
2
- class CLI::Folders < Thor
3
- include Thor::Actions
4
- include CLI::Helpers
5
-
6
- attr_reader :emails
7
- attr_reader :options
8
-
9
- def initialize(options)
10
- super([])
11
- @options = options
12
- end
13
-
14
- no_commands do
15
- def run
16
- config = load_config(**options)
17
- each_connection(config, emails) do |connection|
18
- Kernel.puts connection.account.username
19
- # TODO: Make folder_names private once this command
20
- # has been removed.
21
- folders = connection.folder_names
22
- if folders.nil?
23
- Kernel.warn "Unable to list account folders"
24
- return false
25
- end
26
- folders.each { |f| Kernel.puts "\t#{f}" }
27
- end
28
- end
29
-
30
- def emails
31
- (options[:accounts] || "").split(",")
32
- end
33
- end
34
- end
35
- end