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 +4 -4
- data/README.md +5 -2
- data/lib/imap/backup/account/connection/backup_folders.rb +13 -3
- data/lib/imap/backup/account/connection.rb +4 -0
- data/lib/imap/backup/account/folder.rb +5 -5
- data/lib/imap/backup/account.rb +13 -1
- data/lib/imap/backup/cli/helpers.rb +32 -20
- data/lib/imap/backup/cli/local.rb +84 -31
- data/lib/imap/backup/cli/remote.rb +92 -4
- data/lib/imap/backup/cli/utils.rb +11 -11
- data/lib/imap/backup/cli.rb +19 -38
- data/lib/imap/backup/client/default.rb +1 -1
- data/lib/imap/backup/downloader.rb +1 -1
- data/lib/imap/backup/mirror.rb +1 -1
- data/lib/imap/backup/setup/account/header.rb +86 -35
- data/lib/imap/backup/setup/account.rb +16 -14
- data/lib/imap/backup/version.rb +1 -1
- metadata +4 -5
- data/lib/imap/backup/cli/folders.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ccb9808337c50af0fe59f2cdc85dedcbab50b5b0980a60e2ccf10fc3734b9f37
|
4
|
+
data.tar.gz: e0b67bd5bfb040220f54c9a662c90ad545fce9c84a5fadaf6d93a485ac865ffc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
21
|
+
all_names
|
20
22
|
end
|
21
23
|
|
22
|
-
|
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
|
-
|
103
|
+
add_flags(uids, [:Deleted])
|
104
104
|
client.expunge
|
105
105
|
end
|
106
106
|
|
107
|
-
def
|
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
|
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
|
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
|
-
|
129
|
+
add_flags(existing, [:Deleted])
|
130
130
|
client.expunge
|
131
131
|
end
|
132
132
|
|
data/lib/imap/backup/account.rb
CHANGED
@@ -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 =
|
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.
|
16
|
+
def self.format_option
|
17
17
|
method_option(
|
18
|
-
"
|
19
|
-
type: :
|
20
|
-
desc: "
|
21
|
-
aliases: ["-
|
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
|
-
|
38
|
-
|
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(**
|
12
|
-
config.accounts.
|
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(**
|
27
|
+
config = load_config(**options)
|
19
28
|
connection = connection(config, email)
|
20
29
|
|
21
|
-
connection.local_folders
|
22
|
-
|
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(**
|
46
|
+
config = load_config(**options)
|
30
47
|
connection = connection(config, email)
|
31
48
|
|
32
|
-
|
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 !
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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(**
|
71
|
+
config = load_config(**options)
|
59
72
|
connection = connection(config, email)
|
60
73
|
|
61
|
-
|
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 !
|
77
|
+
raise "Folder '#{folder_name}' not found" if !serializer
|
65
78
|
|
66
79
|
uid_list = uids.split(",")
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
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
|
-
|
10
|
+
format_option
|
9
11
|
quiet_option
|
12
|
+
verbose_option
|
10
13
|
def folders(email)
|
11
|
-
|
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
|
-
|
15
|
-
|
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
|
-
|
11
|
+
config_option
|
12
12
|
quiet_option
|
13
|
+
verbose_option
|
13
14
|
def ignore_history(email)
|
14
|
-
|
15
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
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(**
|
54
|
+
config = load_config(**options)
|
55
55
|
connection = connection(config, email)
|
56
56
|
profile = thunderbird_profile(profile_name)
|
57
57
|
|
data/lib/imap/backup/cli.rb
CHANGED
@@ -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
|
58
|
-
Backup.new(
|
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
|
123
|
-
Migrate.new(source_email, destination_email, **
|
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
|
163
|
-
Mirror.new(source_email, destination_email, **
|
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
|
180
|
-
Restore.new(email,
|
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
|
193
|
-
Setup.new(
|
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
|
-
|
189
|
+
format_option
|
204
190
|
quiet_option
|
205
|
-
|
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
|
213
|
-
Stats.new(email,
|
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"
|
data/lib/imap/backup/mirror.rb
CHANGED
@@ -56,7 +56,7 @@ module Imap::Backup
|
|
56
56
|
next if !source_uid
|
57
57
|
|
58
58
|
message = serializer.get(source_uid)
|
59
|
-
folder.
|
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
|
-
|
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
|
33
|
-
account.
|
40
|
+
def modified_flag
|
41
|
+
account.modified? ? "*" : ""
|
34
42
|
end
|
35
43
|
|
36
|
-
def
|
37
|
-
|
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
|
45
|
-
|
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
|
49
|
-
|
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
|
53
|
-
|
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
|
-
"
|
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
|
-
"
|
117
|
+
["changes to unread flags will be reset during download"]
|
68
118
|
end
|
69
119
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
83
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/imap/backup/version.rb
CHANGED
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
|
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-
|
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:
|
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
|