imap-backup 5.2.0 → 6.0.0.rc2
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/lib/cli_coverage.rb +1 -1
- data/lib/imap/backup/account/connection.rb +7 -11
- 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/utils.rb +2 -2
- 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 +3 -3
- data/lib/imap/backup.rb +0 -1
- data/spec/features/backup_spec.rb +8 -16
- data/spec/features/support/aruba.rb +4 -3
- data/spec/unit/imap/backup/account/connection_spec.rb +36 -8
- data/spec/unit/imap/backup/account/folder_spec.rb +10 -0
- 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 +59 -7
- 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 +36 -7
- data/lib/imap/backup/serializer/mbox_store.rb +0 -217
- 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,3 +1,8 @@
|
|
1
|
+

|
2
|
+
[][CI Status]
|
3
|
+

|
4
|
+

|
5
|
+
|
1
6
|
# imap-backup
|
2
7
|
|
3
8
|
*Backup GMail (or other IMAP) accounts to disk*
|
@@ -5,10 +10,12 @@
|
|
5
10
|
* [Source Code]
|
6
11
|
* [API documentation]
|
7
12
|
* [Rubygem]
|
13
|
+
* [CI Status]
|
8
14
|
|
9
15
|
[Source Code]: https://github.com/joeyates/imap-backup "Source code at GitHub"
|
10
|
-
[API documentation]:
|
11
|
-
[Rubygem]:
|
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
|
12
19
|
|
13
20
|
# Installation
|
14
21
|
|
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/lib/cli_coverage.rb
CHANGED
@@ -4,7 +4,7 @@ class CliCoverage
|
|
4
4
|
require "simplecov"
|
5
5
|
|
6
6
|
# Collect coverage separately
|
7
|
-
SimpleCov.command_name "#{ENV['COVERAGE']} coverage"
|
7
|
+
SimpleCov.command_name "#{ENV['COVERAGE']} #{ARGV.join(' ')} coverage"
|
8
8
|
|
9
9
|
# Silence output
|
10
10
|
SimpleCov.formatter = SimpleCov::Formatter::SimpleFormatter
|
@@ -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
|
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
|
@@ -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
|
|
@@ -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
|
@@ -0,0 +1,120 @@
|
|
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
|
+
end
|
16
|
+
|
17
|
+
def append(uid)
|
18
|
+
uids << uid
|
19
|
+
save
|
20
|
+
end
|
21
|
+
|
22
|
+
def exist?
|
23
|
+
File.exist?(pathname)
|
24
|
+
end
|
25
|
+
|
26
|
+
def include?(uid)
|
27
|
+
uids.include?(uid)
|
28
|
+
end
|
29
|
+
|
30
|
+
def index(uid)
|
31
|
+
uids.find_index(uid)
|
32
|
+
end
|
33
|
+
|
34
|
+
def rename(new_path)
|
35
|
+
if exist?
|
36
|
+
old_pathname = pathname
|
37
|
+
@folder_path = new_path
|
38
|
+
File.rename(old_pathname, pathname)
|
39
|
+
else
|
40
|
+
@folder_path = new_path
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def uid_validity
|
45
|
+
ensure_loaded
|
46
|
+
@uid_validity
|
47
|
+
end
|
48
|
+
|
49
|
+
def uid_validity=(value)
|
50
|
+
ensure_loaded
|
51
|
+
@uid_validity = value
|
52
|
+
@uids ||= []
|
53
|
+
save
|
54
|
+
end
|
55
|
+
|
56
|
+
def uids
|
57
|
+
ensure_loaded
|
58
|
+
@uids || []
|
59
|
+
end
|
60
|
+
|
61
|
+
def update_uid(old, new)
|
62
|
+
index = uids.find_index(old.to_i)
|
63
|
+
return if index.nil?
|
64
|
+
|
65
|
+
uids[index] = new.to_i
|
66
|
+
save
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def pathname
|
72
|
+
"#{folder_path}.imap"
|
73
|
+
end
|
74
|
+
|
75
|
+
def ensure_loaded
|
76
|
+
return if loaded
|
77
|
+
|
78
|
+
data = load
|
79
|
+
if data
|
80
|
+
@uids = data[:uids].map(&:to_i)
|
81
|
+
@uid_validity = data[:uid_validity]
|
82
|
+
else
|
83
|
+
@uids = []
|
84
|
+
@uid_validity = nil
|
85
|
+
end
|
86
|
+
@loaded = true
|
87
|
+
end
|
88
|
+
|
89
|
+
def load
|
90
|
+
return nil if !exist?
|
91
|
+
|
92
|
+
data = nil
|
93
|
+
begin
|
94
|
+
content = File.read(pathname)
|
95
|
+
data = JSON.parse(content, symbolize_names: true)
|
96
|
+
rescue JSON::ParserError
|
97
|
+
return nil
|
98
|
+
end
|
99
|
+
|
100
|
+
return nil if !data.key?(:uids)
|
101
|
+
return nil if !data[:uids].is_a?(Array)
|
102
|
+
|
103
|
+
data
|
104
|
+
end
|
105
|
+
|
106
|
+
def save
|
107
|
+
ensure_loaded
|
108
|
+
|
109
|
+
raise "Cannot save metadata without a uid_validity" if !uid_validity
|
110
|
+
|
111
|
+
data = {
|
112
|
+
version: CURRENT_VERSION,
|
113
|
+
uid_validity: @uid_validity,
|
114
|
+
uids: @uids
|
115
|
+
}
|
116
|
+
content = data.to_json
|
117
|
+
File.open(pathname, "w") { |f| f.write content }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|