imap-backup 5.0.0 → 6.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -7
- data/bin/imap-backup +4 -0
- data/docs/development.md +10 -4
- data/imap-backup.gemspec +2 -7
- data/lib/cli_coverage.rb +18 -0
- data/lib/imap/backup/account/connection.rb +7 -11
- data/lib/imap/backup/account/folder.rb +0 -16
- data/lib/imap/backup/account.rb +31 -11
- data/lib/imap/backup/cli/folders.rb +3 -3
- data/lib/imap/backup/cli/migrate.rb +3 -3
- data/lib/imap/backup/cli/restore.rb +20 -4
- data/lib/imap/backup/cli/utils.rb +2 -2
- data/lib/imap/backup/cli.rb +6 -7
- data/lib/imap/backup/configuration.rb +1 -11
- data/lib/imap/backup/downloader.rb +13 -9
- data/lib/imap/backup/serializer/directory.rb +37 -0
- data/lib/imap/backup/serializer/imap.rb +120 -0
- data/lib/imap/backup/serializer/mbox.rb +23 -94
- data/lib/imap/backup/serializer/mbox_enumerator.rb +2 -0
- data/lib/imap/backup/serializer.rb +180 -3
- data/lib/imap/backup/setup/account.rb +52 -29
- data/lib/imap/backup/setup/helpers.rb +1 -1
- data/lib/imap/backup/thunderbird/mailbox_exporter.rb +1 -1
- data/lib/imap/backup/version.rb +2 -2
- data/lib/imap/backup.rb +0 -1
- data/spec/features/backup_spec.rb +22 -29
- data/spec/features/restore_spec.rb +8 -6
- data/spec/features/support/aruba.rb +12 -3
- data/spec/features/support/backup_directory.rb +0 -4
- data/spec/features/support/email_server.rb +0 -1
- data/spec/spec_helper.rb +4 -9
- data/spec/unit/imap/backup/account/connection_spec.rb +36 -8
- data/spec/unit/imap/backup/account/folder_spec.rb +18 -16
- data/spec/unit/imap/backup/account_spec.rb +246 -0
- data/spec/unit/imap/backup/cli/accounts_spec.rb +12 -1
- data/spec/unit/imap/backup/cli/backup_spec.rb +19 -0
- data/spec/unit/imap/backup/cli/folders_spec.rb +39 -0
- data/spec/unit/imap/backup/cli/local_spec.rb +26 -7
- data/spec/unit/imap/backup/cli/migrate_spec.rb +80 -0
- data/spec/unit/imap/backup/cli/restore_spec.rb +67 -0
- data/spec/unit/imap/backup/cli/setup_spec.rb +17 -0
- data/spec/unit/imap/backup/cli/utils_spec.rb +68 -5
- data/spec/unit/imap/backup/cli_spec.rb +93 -0
- data/spec/unit/imap/backup/client/apple_mail_spec.rb +9 -0
- data/spec/unit/imap/backup/configuration_spec.rb +2 -2
- data/spec/unit/imap/backup/downloader_spec.rb +60 -8
- data/spec/unit/imap/backup/logger_spec.rb +1 -1
- data/spec/unit/imap/backup/migrator_spec.rb +1 -1
- data/spec/unit/imap/backup/sanitizer_spec.rb +42 -0
- data/spec/unit/imap/backup/serializer/directory_spec.rb +37 -0
- data/spec/unit/imap/backup/serializer/imap_spec.rb +218 -0
- data/spec/unit/imap/backup/serializer/mbox_spec.rb +62 -183
- data/spec/unit/imap/backup/serializer_spec.rb +296 -0
- data/spec/unit/imap/backup/setup/account_spec.rb +120 -25
- data/spec/unit/imap/backup/setup/helpers_spec.rb +15 -0
- data/spec/unit/imap/backup/thunderbird/mailbox_exporter_spec.rb +116 -0
- data/spec/unit/imap/backup/uploader_spec.rb +1 -1
- data/spec/unit/retry_on_error_spec.rb +34 -0
- metadata +44 -37
- data/lib/imap/backup/serializer/mbox_store.rb +0 -217
- data/lib/thunderbird/install.rb +0 -16
- data/lib/thunderbird/local_folder.rb +0 -65
- data/lib/thunderbird/profile.rb +0 -30
- data/lib/thunderbird/profiles.rb +0 -71
- data/lib/thunderbird/subdirectory.rb +0 -93
- data/lib/thunderbird/subdirectory_placeholder.rb +0 -21
- data/lib/thunderbird.rb +0 -14
- data/spec/gather_rspec_coverage.rb +0 -1
- data/spec/unit/imap/backup/serializer/mbox_store_spec.rb +0 -329
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f9cac7966fac9858fdc380d8e9638c8d479035ee4053444a104b8c3eb5a304c
|
4
|
+
data.tar.gz: 5d9785b5e34d90535c8052d216d9b3dbf46ea743672f05129604729662f42f34
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4595e4316dcdc20123a8ed2bec524d37c1de95da49281a30484349c317a996cf3c6946617a489880a0c41db41698b2ee862d9aee4f9c9b8fa34d67707ba924c
|
7
|
+
data.tar.gz: ccee268de9cb6225c72fe0cb11a2552fc793f664429d8dfa918e5b4697961c8e5e15e47ed653dac241f377385806443f17683614b8ff76075a8ff2f12e07f98c
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
|
2
|
-
[![
|
3
|
-
|
1
|
+
![Version](https://img.shields.io/gem/v/imap-backup?label=Version&logo=rubygems)
|
2
|
+
[![Build Status](https://github.com/joeyates/imap-backup/actions/workflows/main.yml/badge.svg)][CI Status]
|
3
|
+
![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/joeyates/b54fe758bfb405c04bef72dad293d707/raw/coverage.json)
|
4
|
+
![License](https://img.shields.io/github/license/joeyates/imap-backup?color=brightgreen&label=License)
|
4
5
|
|
5
6
|
# imap-backup
|
6
7
|
|
@@ -9,12 +10,12 @@
|
|
9
10
|
* [Source Code]
|
10
11
|
* [API documentation]
|
11
12
|
* [Rubygem]
|
12
|
-
* [
|
13
|
+
* [CI Status]
|
13
14
|
|
14
15
|
[Source Code]: https://github.com/joeyates/imap-backup "Source code at GitHub"
|
15
|
-
[API documentation]:
|
16
|
-
[Rubygem]:
|
17
|
-
[
|
16
|
+
[API documentation]: https://rubydoc.info/gems/imap-backup/frames "RDoc API Documentation at Rubydoc.info"
|
17
|
+
[Rubygem]: https://rubygems.org/gems/imap-backup "Ruby gem at rubygems.org"
|
18
|
+
[CI Status]: https://github.com/joeyates/imap-backup/actions/workflows/main.yml
|
18
19
|
|
19
20
|
# Installation
|
20
21
|
|
data/bin/imap-backup
CHANGED
data/docs/development.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# Testing
|
2
2
|
|
3
|
-
##
|
3
|
+
## Feature Specs
|
4
4
|
|
5
|
-
|
6
|
-
controlled by Docker Compose
|
7
|
-
|
5
|
+
Specs under `specs/features` are integration specs run against a local IMAP server
|
6
|
+
controlled by Docker Compose.
|
7
|
+
Before running the test suite, it needs to be started:
|
8
8
|
|
9
9
|
```sh
|
10
10
|
$ docker-compose up -d
|
@@ -26,6 +26,12 @@ or
|
|
26
26
|
$ rspec --tag ~docker
|
27
27
|
```
|
28
28
|
|
29
|
+
### Debugging
|
30
|
+
|
31
|
+
The feature specs are run 'out of process' via the Aruba gem.
|
32
|
+
In order to see debugging output from the process,
|
33
|
+
use `last_command_started.output`.
|
34
|
+
|
29
35
|
## Access Docker imap server
|
30
36
|
|
31
37
|
```ruby
|
data/imap-backup.gemspec
CHANGED
@@ -27,16 +27,11 @@ Gem::Specification.new do |gem|
|
|
27
27
|
gem.add_runtime_dependency "os"
|
28
28
|
gem.add_runtime_dependency "rake"
|
29
29
|
gem.add_runtime_dependency "thor", "~> 1.1"
|
30
|
+
gem.add_runtime_dependency "thunderbird", ">= 0.0.0"
|
30
31
|
|
31
32
|
gem.add_development_dependency "aruba", ">= 0.0.0"
|
32
|
-
gem.add_development_dependency "
|
33
|
-
if RUBY_ENGINE == "jruby"
|
34
|
-
gem.add_development_dependency "pry-debugger-jruby"
|
35
|
-
else
|
36
|
-
gem.add_development_dependency "pry-byebug"
|
37
|
-
end
|
33
|
+
gem.add_development_dependency "pry-byebug"
|
38
34
|
gem.add_development_dependency "rspec", ">= 3.0.0"
|
39
|
-
gem.add_development_dependency "rspec_junit_formatter"
|
40
35
|
gem.add_development_dependency "rubocop-rspec"
|
41
36
|
gem.add_development_dependency "simplecov"
|
42
37
|
end
|
data/lib/cli_coverage.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
class CliCoverage
|
2
|
+
def self.conditionally_activate
|
3
|
+
if ENV["COVERAGE"]
|
4
|
+
require "simplecov"
|
5
|
+
|
6
|
+
# Collect coverage separately
|
7
|
+
SimpleCov.command_name "#{ENV['COVERAGE']} #{ARGV.join(' ')} coverage"
|
8
|
+
|
9
|
+
# Silence output
|
10
|
+
SimpleCov.formatter = SimpleCov::Formatter::SimpleFormatter
|
11
|
+
SimpleCov.print_error_status = false
|
12
|
+
|
13
|
+
# Ensure SimpleCov doesn't filter out all out code
|
14
|
+
project_root = File.expand_path("..", __dir__)
|
15
|
+
SimpleCov.root project_root
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require "imap/backup/client/apple_mail"
|
2
2
|
require "imap/backup/client/default"
|
3
|
+
require "imap/backup/serializer/directory"
|
3
4
|
|
4
5
|
require "retry_on_error"
|
5
6
|
|
@@ -52,7 +53,7 @@ module Imap::Backup
|
|
52
53
|
def status
|
53
54
|
ensure_account_folder
|
54
55
|
backup_folders.map do |folder|
|
55
|
-
s = Serializer
|
56
|
+
s = Serializer.new(account.local_path, folder.name)
|
56
57
|
{name: folder.name, local: s.uids, remote: folder.uids}
|
57
58
|
end
|
58
59
|
end
|
@@ -69,7 +70,7 @@ module Imap::Backup
|
|
69
70
|
serializer.apply_uid_validity(folder.uid_validity)
|
70
71
|
begin
|
71
72
|
Downloader.new(
|
72
|
-
folder, serializer,
|
73
|
+
folder, serializer, multi_fetch_size: account.multi_fetch_size
|
73
74
|
).run
|
74
75
|
rescue Net::IMAP::ByeResponseError
|
75
76
|
reconnect
|
@@ -86,7 +87,7 @@ module Imap::Backup
|
|
86
87
|
base = Pathname.new(account.local_path)
|
87
88
|
Pathname.glob(glob) do |path|
|
88
89
|
name = path.relative_path_from(base).to_s[0..-6]
|
89
|
-
serializer = Serializer
|
90
|
+
serializer = Serializer.new(account.local_path, name)
|
90
91
|
folder = Account::Folder.new(self, name)
|
91
92
|
yield serializer, folder
|
92
93
|
end
|
@@ -110,7 +111,6 @@ module Imap::Backup
|
|
110
111
|
def reset
|
111
112
|
@backup_folders = nil
|
112
113
|
@client = nil
|
113
|
-
@config = nil
|
114
114
|
@folder_names = nil
|
115
115
|
@provider = nil
|
116
116
|
@server = nil
|
@@ -144,7 +144,7 @@ module Imap::Backup
|
|
144
144
|
|
145
145
|
def each_folder
|
146
146
|
backup_folders.each do |folder|
|
147
|
-
serializer = Serializer
|
147
|
+
serializer = Serializer.new(account.local_path, folder.name)
|
148
148
|
yield folder, serializer
|
149
149
|
end
|
150
150
|
end
|
@@ -161,7 +161,7 @@ module Imap::Backup
|
|
161
161
|
Imap::Backup::Logger.logger.debug(
|
162
162
|
"Backup '#{old_name}' renamed and restored to '#{new_name}'"
|
163
163
|
)
|
164
|
-
new_serializer = Serializer
|
164
|
+
new_serializer = Serializer.new(account.local_path, new_name)
|
165
165
|
new_folder = Account::Folder.new(self, new_name)
|
166
166
|
new_folder.create
|
167
167
|
new_serializer.force_uid_validity(new_folder.uid_validity)
|
@@ -180,7 +180,7 @@ module Imap::Backup
|
|
180
180
|
Utils.make_folder(
|
181
181
|
File.dirname(account.local_path),
|
182
182
|
File.basename(account.local_path),
|
183
|
-
Serializer::DIRECTORY_PERMISSIONS
|
183
|
+
Serializer::Directory::DIRECTORY_PERMISSIONS
|
184
184
|
)
|
185
185
|
end
|
186
186
|
|
@@ -195,9 +195,5 @@ module Imap::Backup
|
|
195
195
|
def provider_options
|
196
196
|
provider.options.merge(account.connection_options || {})
|
197
197
|
end
|
198
|
-
|
199
|
-
def config
|
200
|
-
@config ||= Configuration.new
|
201
|
-
end
|
202
198
|
end
|
203
199
|
end
|
@@ -68,22 +68,6 @@ module Imap::Backup
|
|
68
68
|
[]
|
69
69
|
end
|
70
70
|
|
71
|
-
def fetch(uid)
|
72
|
-
examine
|
73
|
-
fetch_data_items =
|
74
|
-
retry_on_error(errors: UID_FETCH_RETRY_CLASSES) do
|
75
|
-
client.uid_fetch([uid.to_i], [BODY_ATTRIBUTE])
|
76
|
-
end
|
77
|
-
return nil if fetch_data_items.nil?
|
78
|
-
|
79
|
-
fetch_data_item = fetch_data_items[0]
|
80
|
-
attributes = fetch_data_item.attr
|
81
|
-
|
82
|
-
attributes[BODY_ATTRIBUTE]
|
83
|
-
rescue FolderNotFound
|
84
|
-
nil
|
85
|
-
end
|
86
|
-
|
87
71
|
def fetch_multi(uids)
|
88
72
|
examine
|
89
73
|
fetch_data_items =
|
data/lib/imap/backup/account.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module Imap::Backup
|
2
2
|
class Account
|
3
|
+
DEFAULT_MULTI_FETCH_SIZE = 1
|
4
|
+
|
3
5
|
attr_reader :username
|
4
6
|
attr_reader :password
|
5
7
|
attr_reader :local_path
|
@@ -7,7 +9,6 @@ module Imap::Backup
|
|
7
9
|
attr_reader :server
|
8
10
|
attr_reader :connection_options
|
9
11
|
attr_reader :changes
|
10
|
-
attr_reader :marked_for_deletion
|
11
12
|
|
12
13
|
def initialize(options)
|
13
14
|
@username = options[:username]
|
@@ -16,6 +17,7 @@ module Imap::Backup
|
|
16
17
|
@folders = options[:folders]
|
17
18
|
@server = options[:server]
|
18
19
|
@connection_options = options[:connection_options]
|
20
|
+
@multi_fetch_size = options[:multi_fetch_size]
|
19
21
|
@changes = {}
|
20
22
|
@marked_for_deletion = false
|
21
23
|
end
|
@@ -25,18 +27,18 @@ module Imap::Backup
|
|
25
27
|
end
|
26
28
|
|
27
29
|
def valid?
|
28
|
-
username && password
|
30
|
+
username && password ? true : false
|
29
31
|
end
|
30
32
|
|
31
33
|
def modified?
|
32
34
|
changes.any?
|
33
35
|
end
|
34
36
|
|
35
|
-
def clear_changes
|
37
|
+
def clear_changes
|
36
38
|
@changes = {}
|
37
39
|
end
|
38
40
|
|
39
|
-
def mark_for_deletion
|
41
|
+
def mark_for_deletion
|
40
42
|
@marked_for_deletion = true
|
41
43
|
end
|
42
44
|
|
@@ -53,6 +55,7 @@ module Imap::Backup
|
|
53
55
|
h[:folders] = @folders if @folders
|
54
56
|
h[:server] = @server if @server
|
55
57
|
h[:connection_options] = @connection_options if @connection_options
|
58
|
+
h[:multi_fetch_size] = multi_fetch_size if @multi_fetch_size
|
56
59
|
h
|
57
60
|
end
|
58
61
|
|
@@ -82,20 +85,37 @@ module Imap::Backup
|
|
82
85
|
update(:connection_options, parsed)
|
83
86
|
end
|
84
87
|
|
88
|
+
def multi_fetch_size
|
89
|
+
int = @multi_fetch_size.to_i
|
90
|
+
if int.positive?
|
91
|
+
int
|
92
|
+
else
|
93
|
+
DEFAULT_MULTI_FETCH_SIZE
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def multi_fetch_size=(value)
|
98
|
+
parsed = value.to_i
|
99
|
+
parsed = DEFAULT_MULTI_FETCH_SIZE if !parsed.positive?
|
100
|
+
update(:multi_fetch_size, parsed)
|
101
|
+
end
|
102
|
+
|
85
103
|
private
|
86
104
|
|
87
105
|
def update(field, value)
|
106
|
+
key = :"@#{field}"
|
88
107
|
if changes[field]
|
89
108
|
change = changes[field]
|
90
|
-
|
109
|
+
if change[:from] == value
|
110
|
+
changes.delete(field)
|
111
|
+
else
|
112
|
+
change[:to] = value
|
113
|
+
end
|
114
|
+
else
|
115
|
+
current = instance_variable_get(key)
|
116
|
+
changes[field] = {from: current, to: value}
|
91
117
|
end
|
92
|
-
set_field!(field, value)
|
93
|
-
end
|
94
118
|
|
95
|
-
def set_field!(field, value)
|
96
|
-
key = :"@#{field}"
|
97
|
-
current = instance_variable_get(key)
|
98
|
-
changes[field] = {from: current, to: value}
|
99
119
|
instance_variable_set(key, value)
|
100
120
|
end
|
101
121
|
end
|
@@ -13,15 +13,15 @@ module Imap::Backup
|
|
13
13
|
no_commands do
|
14
14
|
def run
|
15
15
|
each_connection(account_names) do |connection|
|
16
|
-
puts connection.account.username
|
16
|
+
Kernel.puts connection.account.username
|
17
17
|
# TODO: Make folder_names private once this command
|
18
18
|
# has been removed.
|
19
19
|
folders = connection.folder_names
|
20
20
|
if folders.nil?
|
21
|
-
warn "Unable to list account folders"
|
21
|
+
Kernel.warn "Unable to list account folders"
|
22
22
|
return false
|
23
23
|
end
|
24
|
-
folders.each { |f| puts "\t#{f}" }
|
24
|
+
folders.each { |f| Kernel.puts "\t#{f}" }
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -39,11 +39,11 @@ module Imap::Backup
|
|
39
39
|
end
|
40
40
|
|
41
41
|
if !destination_account
|
42
|
-
raise "Account #{destination_email} does not exist"
|
42
|
+
raise "Account '#{destination_email}' does not exist"
|
43
43
|
end
|
44
44
|
|
45
45
|
if !source_account
|
46
|
-
raise "Account #{source_email} does not exist"
|
46
|
+
raise "Account '#{source_email}' does not exist"
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
@@ -61,7 +61,7 @@ module Imap::Backup
|
|
61
61
|
glob = File.join(source_local_path, "**", "*.imap")
|
62
62
|
Pathname.glob(glob) do |path|
|
63
63
|
name = source_folder_name(path)
|
64
|
-
serializer = Serializer
|
64
|
+
serializer = Serializer.new(source_local_path, name)
|
65
65
|
folder = folder_for(name)
|
66
66
|
yield serializer, folder
|
67
67
|
end
|
@@ -3,16 +3,32 @@ module Imap::Backup
|
|
3
3
|
include Thor::Actions
|
4
4
|
include CLI::Helpers
|
5
5
|
|
6
|
+
attr_reader :email
|
6
7
|
attr_reader :account_names
|
7
8
|
|
8
|
-
def initialize(options)
|
9
|
-
|
10
|
-
@account_names =
|
9
|
+
def initialize(email = nil, options)
|
10
|
+
@email = email
|
11
|
+
@account_names =
|
12
|
+
if options.key?(:accounts)
|
13
|
+
options[:accounts].split(",")
|
14
|
+
end
|
11
15
|
end
|
12
16
|
|
13
17
|
no_commands do
|
14
18
|
def run
|
15
|
-
|
19
|
+
case
|
20
|
+
when email && !account_names
|
21
|
+
connection = connection(email)
|
22
|
+
connection.restore
|
23
|
+
when !email && !account_names
|
24
|
+
Logger.logger.info "Calling restore without an EMAIL parameter is deprecated"
|
25
|
+
each_connection([]) { |connection| connection.restore }
|
26
|
+
when email && account_names.any?
|
27
|
+
raise "Pass either an email or the --accounts option, not both"
|
28
|
+
when account_names.any?
|
29
|
+
Logger.logger.info "Calling restore with the --account option is deprected, please pass a single EMAIL argument"
|
30
|
+
each_connection(account_names) { |connection| connection.restore }
|
31
|
+
end
|
16
32
|
end
|
17
33
|
end
|
18
34
|
end
|
@@ -14,7 +14,7 @@ module Imap::Backup
|
|
14
14
|
connection.backup_folders.each do |folder|
|
15
15
|
next if !folder.exist?
|
16
16
|
|
17
|
-
serializer = Serializer
|
17
|
+
serializer = Serializer.new(connection.account.local_path, folder.name)
|
18
18
|
do_ignore_folder_history(folder, serializer)
|
19
19
|
end
|
20
20
|
end
|
@@ -75,7 +75,7 @@ module Imap::Backup
|
|
75
75
|
Skipped #{uid}
|
76
76
|
MESSAGE
|
77
77
|
|
78
|
-
serializer.
|
78
|
+
serializer.append uid, message
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
data/lib/imap/backup/cli.rb
CHANGED
@@ -100,21 +100,20 @@ module Imap::Backup
|
|
100
100
|
aliases: ["-s"]
|
101
101
|
)
|
102
102
|
def migrate(source_email, destination_email)
|
103
|
-
Migrate.new(source_email, destination_email, symbolized(options)).run
|
103
|
+
Migrate.new(source_email, destination_email, **symbolized(options)).run
|
104
104
|
end
|
105
105
|
|
106
106
|
desc "remote SUBCOMMAND [OPTIONS]", "View info about online accounts"
|
107
107
|
subcommand "remote", Remote
|
108
108
|
|
109
|
-
desc "restore
|
109
|
+
desc "restore EMAIL", "Restores a single account"
|
110
110
|
long_desc <<~DESC
|
111
|
-
|
112
|
-
|
113
|
-
Instead, use `imap-backup restore ACCOUNT` to restore a single account.
|
111
|
+
Restores all backed-up emails for the supplied account to
|
112
|
+
their original server.
|
114
113
|
DESC
|
115
114
|
accounts_option
|
116
|
-
def restore
|
117
|
-
Restore.new(symbolized(options)).run
|
115
|
+
def restore(email = nil)
|
116
|
+
Restore.new(email, symbolized(options)).run
|
118
117
|
end
|
119
118
|
|
120
119
|
desc "setup", "Configure imap-backup"
|
@@ -6,7 +6,6 @@ require "imap/backup/account"
|
|
6
6
|
module Imap::Backup
|
7
7
|
class Configuration
|
8
8
|
CONFIGURATION_DIRECTORY = File.expand_path("~/.imap-backup")
|
9
|
-
DEFAULT_DOWNLOAD_BLOCK_SIZE = 1
|
10
9
|
VERSION = "2.0"
|
11
10
|
|
12
11
|
attr_reader :pathname
|
@@ -52,15 +51,6 @@ module Imap::Backup
|
|
52
51
|
end
|
53
52
|
end
|
54
53
|
|
55
|
-
def download_block_size
|
56
|
-
size = ENV["DOWNLOAD_BLOCK_SIZE"].to_i
|
57
|
-
if size > 0
|
58
|
-
size
|
59
|
-
else
|
60
|
-
DEFAULT_DOWNLOAD_BLOCK_SIZE
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
54
|
def modified?
|
65
55
|
ensure_loaded!
|
66
56
|
return true if @saved_debug != @debug
|
@@ -103,7 +93,7 @@ module Imap::Backup
|
|
103
93
|
end
|
104
94
|
|
105
95
|
def remove_modified_flags
|
106
|
-
accounts.each { |a| a.clear_changes
|
96
|
+
accounts.each { |a| a.clear_changes }
|
107
97
|
end
|
108
98
|
|
109
99
|
def remove_deleted_accounts
|
@@ -1,33 +1,34 @@
|
|
1
1
|
module Imap::Backup
|
2
|
+
class MultiFetchFailedError < StandardError; end
|
3
|
+
|
2
4
|
class Downloader
|
3
5
|
attr_reader :folder
|
4
6
|
attr_reader :serializer
|
5
|
-
attr_reader :
|
7
|
+
attr_reader :multi_fetch_size
|
6
8
|
|
7
|
-
def initialize(folder, serializer,
|
9
|
+
def initialize(folder, serializer, multi_fetch_size: 1)
|
8
10
|
@folder = folder
|
9
11
|
@serializer = serializer
|
10
|
-
@
|
12
|
+
@multi_fetch_size = multi_fetch_size
|
11
13
|
end
|
12
14
|
|
13
15
|
def run
|
14
16
|
uids = folder.uids - serializer.uids
|
15
17
|
count = uids.count
|
16
18
|
debug "#{count} new messages"
|
17
|
-
uids.each_slice(
|
19
|
+
uids.each_slice(multi_fetch_size).with_index do |block, i|
|
18
20
|
uids_and_bodies = folder.fetch_multi(block)
|
19
21
|
if uids_and_bodies.nil?
|
20
|
-
if
|
22
|
+
if multi_fetch_size > 1
|
21
23
|
debug("Multi fetch failed for UIDs #{block.join(", ")}, switching to single fetches")
|
22
|
-
|
23
|
-
redo
|
24
|
+
raise MultiFetchFailedError
|
24
25
|
else
|
25
26
|
debug("Fetch failed for UID #{block[0]} - skipping")
|
26
27
|
next
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
30
|
-
offset = i *
|
31
|
+
offset = i * multi_fetch_size + 1
|
31
32
|
uids_and_bodies.each.with_index do |uid_and_body, j|
|
32
33
|
uid = uid_and_body[:uid]
|
33
34
|
body = uid_and_body[:body]
|
@@ -38,10 +39,13 @@ module Imap::Backup
|
|
38
39
|
info("Fetch returned empty UID - skipping")
|
39
40
|
else
|
40
41
|
debug("uid: #{uid} (#{offset + j}/#{count}) - #{body.size} bytes")
|
41
|
-
serializer.
|
42
|
+
serializer.append uid, body
|
42
43
|
end
|
43
44
|
end
|
44
45
|
end
|
46
|
+
rescue MultiFetchFailedError
|
47
|
+
@multi_fetch_size = 1
|
48
|
+
retry
|
45
49
|
end
|
46
50
|
|
47
51
|
private
|
@@ -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
|