imap-backup 5.2.0 → 6.0.1
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 +9 -2
- data/docs/development.md +10 -4
- data/imap-backup.gemspec +5 -1
- data/lib/cli_coverage.rb +11 -11
- data/lib/email/provider/base.rb +2 -0
- data/lib/email/provider/unknown.rb +2 -0
- data/lib/email/provider.rb +2 -0
- data/lib/imap/backup/account/connection/backup_folders.rb +27 -0
- data/lib/imap/backup/account/connection/client_factory.rb +54 -0
- data/lib/imap/backup/account/connection/folder_names.rb +26 -0
- data/lib/imap/backup/account/connection.rb +17 -105
- data/lib/imap/backup/account/folder.rb +9 -6
- data/lib/imap/backup/account.rb +36 -16
- data/lib/imap/backup/cli/backup.rb +1 -3
- data/lib/imap/backup/cli/folders.rb +3 -3
- data/lib/imap/backup/cli/helpers.rb +24 -22
- data/lib/imap/backup/cli/local.rb +20 -13
- data/lib/imap/backup/cli/migrate.rb +5 -11
- data/lib/imap/backup/cli/restore.rb +8 -7
- data/lib/imap/backup/cli/setup.rb +10 -8
- data/lib/imap/backup/cli/stats.rb +78 -0
- data/lib/imap/backup/cli/status.rb +2 -2
- data/lib/imap/backup/cli/utils.rb +6 -8
- data/lib/imap/backup/cli.rb +24 -3
- data/lib/imap/backup/configuration.rb +9 -21
- data/lib/imap/backup/downloader.rb +56 -34
- data/lib/imap/backup/migrator.rb +5 -5
- data/lib/imap/backup/sanitizer.rb +3 -2
- data/lib/imap/backup/serializer/appender.rb +49 -0
- data/lib/imap/backup/serializer/directory.rb +37 -0
- data/lib/imap/backup/serializer/imap.rb +144 -0
- data/lib/imap/backup/serializer/mbox.rb +33 -88
- data/lib/imap/backup/serializer/mbox_enumerator.rb +2 -0
- data/lib/imap/backup/serializer/message_enumerator.rb +29 -0
- data/lib/imap/backup/serializer/unused_name_finder.rb +25 -0
- data/lib/imap/backup/serializer.rb +160 -3
- data/lib/imap/backup/setup/account/header.rb +75 -0
- data/lib/imap/backup/setup/account.rb +41 -95
- data/lib/imap/backup/setup/asker.rb +4 -15
- data/lib/imap/backup/setup/backup_path.rb +41 -0
- data/lib/imap/backup/setup/email.rb +45 -0
- data/lib/imap/backup/setup/folder_chooser.rb +3 -3
- data/lib/imap/backup/setup/helpers.rb +2 -2
- data/lib/imap/backup/setup.rb +5 -4
- data/lib/imap/backup/thunderbird/mailbox_exporter.rb +41 -22
- data/lib/imap/backup/uploader.rb +46 -8
- data/lib/imap/backup/utils.rb +1 -1
- data/lib/imap/backup/version.rb +3 -3
- data/lib/imap/backup.rb +0 -2
- metadata +31 -105
- data/lib/imap/backup/serializer/mbox_store.rb +0 -217
- data/spec/features/backup_spec.rb +0 -108
- data/spec/features/configuration/minimal_configuration.rb +0 -15
- data/spec/features/configuration/missing_configuration.rb +0 -14
- data/spec/features/folders_spec.rb +0 -36
- data/spec/features/helper.rb +0 -2
- data/spec/features/local/list_accounts_spec.rb +0 -12
- data/spec/features/local/list_emails_spec.rb +0 -21
- data/spec/features/local/list_folders_spec.rb +0 -21
- data/spec/features/local/show_an_email_spec.rb +0 -34
- data/spec/features/migrate_spec.rb +0 -35
- data/spec/features/remote/list_account_folders_spec.rb +0 -16
- data/spec/features/restore_spec.rb +0 -162
- data/spec/features/status_spec.rb +0 -43
- data/spec/features/support/aruba.rb +0 -77
- data/spec/features/support/backup_directory.rb +0 -43
- data/spec/features/support/email_server.rb +0 -110
- data/spec/features/support/shared/connection_context.rb +0 -14
- data/spec/features/support/shared/message_fixtures.rb +0 -16
- data/spec/fixtures/connection.yml +0 -7
- data/spec/spec_helper.rb +0 -15
- data/spec/support/fixtures.rb +0 -11
- data/spec/support/higline_test_helpers.rb +0 -8
- data/spec/support/silence_logging.rb +0 -7
- data/spec/unit/email/mboxrd/message_spec.rb +0 -177
- data/spec/unit/email/provider/apple_mail_spec.rb +0 -7
- data/spec/unit/email/provider/base_spec.rb +0 -11
- data/spec/unit/email/provider/fastmail_spec.rb +0 -7
- data/spec/unit/email/provider/gmail_spec.rb +0 -7
- data/spec/unit/email/provider_spec.rb +0 -27
- data/spec/unit/imap/backup/account/connection_spec.rb +0 -405
- data/spec/unit/imap/backup/account/folder_spec.rb +0 -251
- data/spec/unit/imap/backup/cli/accounts_spec.rb +0 -47
- data/spec/unit/imap/backup/cli/helpers_spec.rb +0 -87
- data/spec/unit/imap/backup/cli/local_spec.rb +0 -81
- data/spec/unit/imap/backup/cli/utils_spec.rb +0 -62
- data/spec/unit/imap/backup/client/default_spec.rb +0 -22
- data/spec/unit/imap/backup/configuration_spec.rb +0 -238
- data/spec/unit/imap/backup/downloader_spec.rb +0 -44
- data/spec/unit/imap/backup/logger_spec.rb +0 -48
- data/spec/unit/imap/backup/migrator_spec.rb +0 -58
- data/spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb +0 -45
- data/spec/unit/imap/backup/serializer/mbox_spec.rb +0 -222
- data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +0 -329
- data/spec/unit/imap/backup/setup/account_spec.rb +0 -366
- data/spec/unit/imap/backup/setup/asker_spec.rb +0 -137
- data/spec/unit/imap/backup/setup/connection_tester_spec.rb +0 -51
- data/spec/unit/imap/backup/setup/folder_chooser_spec.rb +0 -146
- data/spec/unit/imap/backup/setup_spec.rb +0 -301
- data/spec/unit/imap/backup/uploader_spec.rb +0 -54
- data/spec/unit/imap/backup/utils_spec.rb +0 -92
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require "os"
|
|
2
|
+
|
|
3
|
+
module Imap::Backup
|
|
4
|
+
class Serializer; end
|
|
5
|
+
|
|
6
|
+
class Serializer::Directory
|
|
7
|
+
DIRECTORY_PERMISSIONS = 0o700
|
|
8
|
+
|
|
9
|
+
attr_reader :relative
|
|
10
|
+
attr_reader :path
|
|
11
|
+
|
|
12
|
+
def initialize(path, relative)
|
|
13
|
+
@path = path
|
|
14
|
+
@relative = relative
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def ensure_exists
|
|
18
|
+
if !File.directory?(full_path)
|
|
19
|
+
Utils.make_folder(
|
|
20
|
+
path, relative, DIRECTORY_PERMISSIONS
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
return if OS.windows?
|
|
25
|
+
return if Utils.mode(full_path) == DIRECTORY_PERMISSIONS
|
|
26
|
+
|
|
27
|
+
FileUtils.chmod DIRECTORY_PERMISSIONS, full_path
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def full_path
|
|
33
|
+
containing_directory = File.join(path, relative)
|
|
34
|
+
File.expand_path(containing_directory)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Imap::Backup
|
|
4
|
+
class Serializer::Imap
|
|
5
|
+
CURRENT_VERSION = 2
|
|
6
|
+
|
|
7
|
+
attr_reader :folder_path
|
|
8
|
+
attr_reader :loaded
|
|
9
|
+
|
|
10
|
+
def initialize(folder_path)
|
|
11
|
+
@folder_path = folder_path
|
|
12
|
+
@loaded = false
|
|
13
|
+
@uid_validity = nil
|
|
14
|
+
@uids = nil
|
|
15
|
+
@version = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def valid?
|
|
19
|
+
return false if !exist?
|
|
20
|
+
return false if version != CURRENT_VERSION
|
|
21
|
+
return false if !uid_validity
|
|
22
|
+
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def append(uid)
|
|
27
|
+
uids << uid
|
|
28
|
+
save
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def delete
|
|
32
|
+
return if !exist?
|
|
33
|
+
|
|
34
|
+
File.unlink(pathname)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def include?(uid)
|
|
38
|
+
uids.include?(uid)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def index(uid)
|
|
42
|
+
uids.find_index(uid)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def rename(new_path)
|
|
46
|
+
if exist?
|
|
47
|
+
old_pathname = pathname
|
|
48
|
+
@folder_path = new_path
|
|
49
|
+
File.rename(old_pathname, pathname)
|
|
50
|
+
else
|
|
51
|
+
@folder_path = new_path
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def uid_validity
|
|
56
|
+
ensure_loaded
|
|
57
|
+
@uid_validity
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def uid_validity=(value)
|
|
61
|
+
ensure_loaded
|
|
62
|
+
@uid_validity = value
|
|
63
|
+
@uids ||= []
|
|
64
|
+
save
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def uids
|
|
68
|
+
ensure_loaded
|
|
69
|
+
@uids || []
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def update_uid(old, new)
|
|
73
|
+
index = uids.find_index(old.to_i)
|
|
74
|
+
return if index.nil?
|
|
75
|
+
|
|
76
|
+
uids[index] = new.to_i
|
|
77
|
+
save
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def version
|
|
81
|
+
ensure_loaded
|
|
82
|
+
@version
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def pathname
|
|
88
|
+
"#{folder_path}.imap"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def exist?
|
|
92
|
+
File.exist?(pathname)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def ensure_loaded
|
|
96
|
+
return if loaded
|
|
97
|
+
|
|
98
|
+
data = load
|
|
99
|
+
if data
|
|
100
|
+
@uids = data[:uids].map(&:to_i)
|
|
101
|
+
@uid_validity = data[:uid_validity]
|
|
102
|
+
@version = data[:version]
|
|
103
|
+
else
|
|
104
|
+
@uids = []
|
|
105
|
+
@uid_validity = nil
|
|
106
|
+
@version = CURRENT_VERSION
|
|
107
|
+
end
|
|
108
|
+
@loaded = true
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def load
|
|
112
|
+
return nil if !exist?
|
|
113
|
+
|
|
114
|
+
data = nil
|
|
115
|
+
begin
|
|
116
|
+
content = File.read(pathname)
|
|
117
|
+
data = JSON.parse(content, symbolize_names: true)
|
|
118
|
+
rescue JSON::ParserError
|
|
119
|
+
return nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
return nil if !data.key?(:version)
|
|
123
|
+
return nil if !data.key?(:uid_validity)
|
|
124
|
+
return nil if !data.key?(:uids)
|
|
125
|
+
return nil if !data[:uids].is_a?(Array)
|
|
126
|
+
|
|
127
|
+
data
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def save
|
|
131
|
+
ensure_loaded
|
|
132
|
+
|
|
133
|
+
raise "Cannot save metadata without a uid_validity" if !uid_validity
|
|
134
|
+
|
|
135
|
+
data = {
|
|
136
|
+
version: @version,
|
|
137
|
+
uid_validity: @uid_validity,
|
|
138
|
+
uids: @uids
|
|
139
|
+
}
|
|
140
|
+
content = data.to_json
|
|
141
|
+
File.open(pathname, "w") { |f| f.write content }
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -1,116 +1,61 @@
|
|
|
1
|
-
require "forwardable"
|
|
2
|
-
|
|
3
|
-
require "imap/backup/serializer/mbox_store"
|
|
4
|
-
|
|
5
1
|
module Imap::Backup
|
|
6
2
|
class Serializer::Mbox
|
|
7
|
-
|
|
8
|
-
def_delegators :store, :mbox_pathname
|
|
9
|
-
|
|
10
|
-
attr_reader :path
|
|
11
|
-
attr_reader :folder
|
|
12
|
-
|
|
13
|
-
def initialize(path, folder)
|
|
14
|
-
@path = path
|
|
15
|
-
@folder = folder
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def apply_uid_validity(value)
|
|
19
|
-
case
|
|
20
|
-
when store.uid_validity.nil?
|
|
21
|
-
store.uid_validity = value
|
|
22
|
-
nil
|
|
23
|
-
when store.uid_validity == value
|
|
24
|
-
# NOOP
|
|
25
|
-
nil
|
|
26
|
-
else
|
|
27
|
-
apply_new_uid_validity value
|
|
28
|
-
end
|
|
29
|
-
end
|
|
3
|
+
attr_reader :folder_path
|
|
30
4
|
|
|
31
|
-
def
|
|
32
|
-
|
|
5
|
+
def initialize(folder_path)
|
|
6
|
+
@folder_path = folder_path
|
|
33
7
|
end
|
|
34
8
|
|
|
35
|
-
def
|
|
36
|
-
|
|
9
|
+
def valid?
|
|
10
|
+
exist?
|
|
37
11
|
end
|
|
38
12
|
|
|
39
|
-
def
|
|
40
|
-
|
|
13
|
+
def append(message)
|
|
14
|
+
File.open(pathname, "ab") do |file|
|
|
15
|
+
file.write message
|
|
16
|
+
end
|
|
41
17
|
end
|
|
42
18
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
end
|
|
19
|
+
def delete
|
|
20
|
+
return if !exist?
|
|
46
21
|
|
|
47
|
-
|
|
48
|
-
store.add(uid, message)
|
|
22
|
+
File.unlink(pathname)
|
|
49
23
|
end
|
|
50
24
|
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
store.rename new_name
|
|
54
|
-
end
|
|
25
|
+
def length
|
|
26
|
+
return nil if !exist?
|
|
55
27
|
|
|
56
|
-
|
|
57
|
-
store.update_uid old, new
|
|
28
|
+
File.stat(pathname).size
|
|
58
29
|
end
|
|
59
30
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def store
|
|
63
|
-
@store ||=
|
|
64
|
-
begin
|
|
65
|
-
create_containing_directory
|
|
66
|
-
Serializer::MboxStore.new(path, folder)
|
|
67
|
-
end
|
|
31
|
+
def pathname
|
|
32
|
+
"#{folder_path}.mbox"
|
|
68
33
|
end
|
|
69
34
|
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
break if !test_store.exist?
|
|
78
|
-
|
|
79
|
-
digit += 1
|
|
35
|
+
def rename(new_path)
|
|
36
|
+
if exist?
|
|
37
|
+
old_pathname = pathname
|
|
38
|
+
@folder_path = new_path
|
|
39
|
+
File.rename(old_pathname, pathname)
|
|
40
|
+
else
|
|
41
|
+
@folder_path = new_path
|
|
80
42
|
end
|
|
81
|
-
rename_store new_name, value
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def rename_store(new_name, value)
|
|
85
|
-
store.rename new_name
|
|
86
|
-
@store = nil
|
|
87
|
-
store.uid_validity = value
|
|
88
|
-
new_name
|
|
89
43
|
end
|
|
90
44
|
|
|
91
|
-
def
|
|
92
|
-
File.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def containing_directory
|
|
96
|
-
File.join(path, relative_path)
|
|
45
|
+
def rewind(length)
|
|
46
|
+
File.open(pathname, File::RDWR | File::CREAT, 0o644) do |f|
|
|
47
|
+
f.truncate(length)
|
|
48
|
+
end
|
|
97
49
|
end
|
|
98
50
|
|
|
99
|
-
def
|
|
100
|
-
File.
|
|
51
|
+
def touch
|
|
52
|
+
File.open(pathname, "a") {}
|
|
101
53
|
end
|
|
102
54
|
|
|
103
|
-
|
|
104
|
-
if !File.directory?(full_path)
|
|
105
|
-
Utils.make_folder(
|
|
106
|
-
path, relative_path, Serializer::DIRECTORY_PERMISSIONS
|
|
107
|
-
)
|
|
108
|
-
end
|
|
55
|
+
private
|
|
109
56
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
FileUtils.chmod Serializer::DIRECTORY_PERMISSIONS, full_path
|
|
113
|
-
end
|
|
57
|
+
def exist?
|
|
58
|
+
File.exist?(pathname)
|
|
114
59
|
end
|
|
115
60
|
end
|
|
116
61
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "email/mboxrd/message"
|
|
2
|
+
require "imap/backup/serializer/mbox_enumerator"
|
|
3
|
+
|
|
4
|
+
module Imap::Backup
|
|
5
|
+
class Serializer::MessageEnumerator
|
|
6
|
+
attr_reader :imap
|
|
7
|
+
attr_reader :mbox
|
|
8
|
+
|
|
9
|
+
def initialize(imap:, mbox:)
|
|
10
|
+
@imap = imap
|
|
11
|
+
@mbox = mbox
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run(uids:)
|
|
15
|
+
indexes = uids.each.with_object({}) do |uid_maybe_string, acc|
|
|
16
|
+
uid = uid_maybe_string.to_i
|
|
17
|
+
index = imap.index(uid)
|
|
18
|
+
acc[index] = uid if index
|
|
19
|
+
end
|
|
20
|
+
enumerator = Serializer::MboxEnumerator.new(mbox.pathname)
|
|
21
|
+
enumerator.each.with_index do |raw, i|
|
|
22
|
+
uid = indexes[i]
|
|
23
|
+
next if !uid
|
|
24
|
+
|
|
25
|
+
yield uid, Email::Mboxrd::Message.from_serialized(raw)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Imap::Backup
|
|
2
|
+
class Serializer::UnusedNameFinder
|
|
3
|
+
attr_reader :serializer
|
|
4
|
+
|
|
5
|
+
def initialize(serializer:)
|
|
6
|
+
@serializer = serializer
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
digit = 0
|
|
11
|
+
folder = nil
|
|
12
|
+
|
|
13
|
+
loop do
|
|
14
|
+
extra = digit.zero? ? "" : "-#{digit}"
|
|
15
|
+
folder = "#{serializer.folder}-#{serializer.uid_validity}#{extra}"
|
|
16
|
+
test = Serializer.new(serializer.path, folder)
|
|
17
|
+
break if !test.validate!
|
|
18
|
+
|
|
19
|
+
digit += 1
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
folder
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -1,6 +1,163 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
|
|
3
|
+
require "email/mboxrd/message"
|
|
4
|
+
require "imap/backup/serializer/appender"
|
|
5
|
+
require "imap/backup/serializer/imap"
|
|
6
|
+
require "imap/backup/serializer/mbox"
|
|
7
|
+
require "imap/backup/serializer/mbox_enumerator"
|
|
8
|
+
require "imap/backup/serializer/message_enumerator"
|
|
9
|
+
require "imap/backup/serializer/unused_name_finder"
|
|
10
|
+
|
|
1
11
|
module Imap::Backup
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
12
|
+
class Serializer
|
|
13
|
+
def self.folder_path_for(path:, folder:)
|
|
14
|
+
relative = File.join(path, folder)
|
|
15
|
+
File.expand_path(relative)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
extend Forwardable
|
|
19
|
+
|
|
20
|
+
def_delegator :mbox, :pathname, :mbox_pathname
|
|
21
|
+
def_delegators :imap, :uid_validity, :uids, :update_uid
|
|
22
|
+
|
|
23
|
+
attr_reader :folder
|
|
24
|
+
attr_reader :path
|
|
25
|
+
|
|
26
|
+
def initialize(path, folder)
|
|
27
|
+
@path = path
|
|
28
|
+
@folder = folder
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns true if there are existing, valid files
|
|
32
|
+
# false otherwise (in which case any existing files are deleted)
|
|
33
|
+
def validate!
|
|
34
|
+
return true if imap.valid? && mbox.valid?
|
|
35
|
+
|
|
36
|
+
imap.delete
|
|
37
|
+
@imap = nil
|
|
38
|
+
mbox.delete
|
|
39
|
+
@mbox = nil
|
|
40
|
+
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def apply_uid_validity(value)
|
|
45
|
+
validate!
|
|
46
|
+
|
|
47
|
+
case
|
|
48
|
+
when uid_validity.nil?
|
|
49
|
+
internal_force_uid_validity(value)
|
|
50
|
+
nil
|
|
51
|
+
when uid_validity == value
|
|
52
|
+
# NOOP
|
|
53
|
+
nil
|
|
54
|
+
else
|
|
55
|
+
apply_new_uid_validity value
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def force_uid_validity(value)
|
|
60
|
+
validate!
|
|
61
|
+
|
|
62
|
+
internal_force_uid_validity(value)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def append(uid, message)
|
|
66
|
+
validate!
|
|
67
|
+
|
|
68
|
+
appender = Serializer::Appender.new(folder: folder, imap: imap, mbox: mbox)
|
|
69
|
+
appender.run(uid: uid, message: message)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def load(uid_maybe_string)
|
|
73
|
+
validate!
|
|
74
|
+
|
|
75
|
+
uid = uid_maybe_string.to_i
|
|
76
|
+
message_index = imap.index(uid)
|
|
77
|
+
return nil if message_index.nil?
|
|
78
|
+
|
|
79
|
+
internal_load_nth(message_index)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def load_nth(index)
|
|
83
|
+
validate!
|
|
84
|
+
|
|
85
|
+
internal_load_nth(index)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def each_message(required_uids, &block)
|
|
89
|
+
validate!
|
|
90
|
+
|
|
91
|
+
return enum_for(:each_message, required_uids) if !block
|
|
92
|
+
|
|
93
|
+
enumerator = Serializer::MessageEnumerator.new(imap: imap, mbox: mbox)
|
|
94
|
+
enumerator.run(uids: required_uids, &block)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def rename(new_name)
|
|
100
|
+
destination = self.class.folder_path_for(path: path, folder: new_name)
|
|
101
|
+
ensure_containing_directory(new_name)
|
|
102
|
+
mbox.rename destination
|
|
103
|
+
imap.rename destination
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def internal_force_uid_validity(value)
|
|
107
|
+
imap.uid_validity = value
|
|
108
|
+
mbox.touch
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def internal_load_nth(index)
|
|
112
|
+
enumerator = Serializer::MboxEnumerator.new(mbox.pathname)
|
|
113
|
+
enumerator.each.with_index do |raw, i|
|
|
114
|
+
next if i != index
|
|
115
|
+
|
|
116
|
+
return Email::Mboxrd::Message.from_serialized(raw)
|
|
117
|
+
end
|
|
118
|
+
nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def mbox
|
|
122
|
+
@mbox ||=
|
|
123
|
+
begin
|
|
124
|
+
ensure_containing_directory(folder)
|
|
125
|
+
Serializer::Mbox.new(folder_path)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def imap
|
|
130
|
+
@imap ||=
|
|
131
|
+
begin
|
|
132
|
+
ensure_containing_directory(folder)
|
|
133
|
+
Serializer::Imap.new(folder_path)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def folder_path
|
|
138
|
+
self.class.folder_path_for(path: path, folder: folder)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def ensure_containing_directory(folder)
|
|
142
|
+
relative = File.dirname(folder)
|
|
143
|
+
directory = Serializer::Directory.new(path, relative)
|
|
144
|
+
directory.ensure_exists
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def apply_new_uid_validity(value)
|
|
148
|
+
new_name = rename_existing_folder
|
|
149
|
+
# Clear memoization so we get empty data
|
|
150
|
+
@mbox = nil
|
|
151
|
+
@imap = nil
|
|
152
|
+
internal_force_uid_validity(value)
|
|
153
|
+
|
|
154
|
+
new_name
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def rename_existing_folder
|
|
158
|
+
new_name = Serializer::UnusedNameFinder.new(serializer: self).run
|
|
159
|
+
rename new_name
|
|
160
|
+
new_name
|
|
161
|
+
end
|
|
5
162
|
end
|
|
6
163
|
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require "imap/backup/setup/helpers"
|
|
2
|
+
|
|
3
|
+
module Imap::Backup
|
|
4
|
+
class Setup; end
|
|
5
|
+
class Setup::Account; end
|
|
6
|
+
|
|
7
|
+
class Setup::Account::Header
|
|
8
|
+
attr_reader :account
|
|
9
|
+
attr_reader :menu
|
|
10
|
+
|
|
11
|
+
def initialize(menu:, account:)
|
|
12
|
+
@menu = menu
|
|
13
|
+
@account = account
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def run
|
|
17
|
+
menu.header = <<~HEADER.chomp
|
|
18
|
+
#{helpers.title_prefix} Account#{modified_flag}
|
|
19
|
+
|
|
20
|
+
email #{space}#{account.username}
|
|
21
|
+
password#{space}#{masked_password}
|
|
22
|
+
path #{space}#{local_path}
|
|
23
|
+
folders #{space}#{folders.map { |f| f[:name] }.join(', ')}#{multi_fetch_size}
|
|
24
|
+
server #{space}#{account.server}#{connection_options}
|
|
25
|
+
|
|
26
|
+
Choose an action
|
|
27
|
+
HEADER
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def folders
|
|
33
|
+
account.folders || []
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def helpers
|
|
37
|
+
Setup::Helpers.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def modified_flag
|
|
41
|
+
account.modified? ? "*" : ""
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def multi_fetch_size
|
|
45
|
+
"\nmulti-fetch #{account.multi_fetch_size}" if account.multi_fetch_size > 1
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def connection_options
|
|
49
|
+
return nil if !account.connection_options
|
|
50
|
+
|
|
51
|
+
escaped = JSON.generate(account.connection_options)
|
|
52
|
+
escaped.gsub!('"', '\"')
|
|
53
|
+
"\nconnection options '#{escaped}'"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def space
|
|
57
|
+
account.connection_options ? " " * 12 : " " * 4
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def masked_password
|
|
61
|
+
if (account.password == "") || account.password.nil?
|
|
62
|
+
"(unset)"
|
|
63
|
+
else
|
|
64
|
+
account.password.gsub(/./, "x")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def local_path
|
|
69
|
+
# In order to handle backslashes, as Highline effectively
|
|
70
|
+
# does an eval (!) on its templates, we need to doubly
|
|
71
|
+
# escape them
|
|
72
|
+
account.local_path.gsub("\\", "\\\\\\\\")
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|