imap-backup 8.0.0.rc1 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
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