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 +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
|