imap-backup 8.0.2 → 9.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/docs/delimiters-and-prefixes.md +16 -0
- data/docs/development.md +10 -0
- data/docs/restore.md +1 -3
- data/docs/setup.md +1 -1
- data/lib/imap/backup/cli/folder_enumerator.rb +96 -0
- data/lib/imap/backup/cli/migrate.rb +19 -46
- data/lib/imap/backup/cli/mirror.rb +19 -46
- data/lib/imap/backup/cli.rb +24 -1
- data/lib/imap/backup/version.rb +2 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3daac577a7328b73a4593e677826b2f168f7565487595a04b37504b863dccd46
|
4
|
+
data.tar.gz: 3290089c8c67f31d7dc7cfc53ca6c6d2dd20328727447b2164273409408d36d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df10381df343a5c4c08d8fa39310a6593b6383608926d360202feb92652bc2ad42bbeffc306789e6f4761bc06405cb2b8d51efe76e65cfc5a9507d397d36e2ae
|
7
|
+
data.tar.gz: 05a4d8b9941622fc56f68e952f71b38383a9593fe6abedb64d6c4b1a5670eee0ca2c40a27cc14a7d05d298638e3c242f056b3a8b68288a61dafae8449f51e36b
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Delimiters and Prefixes
|
2
|
+
|
3
|
+
A simple folder name is `Friends`.
|
4
|
+
|
5
|
+
Most email servers allow you to put folders inside other folders.
|
6
|
+
|
7
|
+
On most email servers, the parts of a folder's name are separated with a `/` character.
|
8
|
+
So you might have `People/Friends`.
|
9
|
+
|
10
|
+
On the other hand, some email servers use a `.`, giving `People.Friends`.
|
11
|
+
|
12
|
+
Some email servers keep most email in a parent folder, often `INBOX`, so the above folder
|
13
|
+
would be `INBOX/People/Friends`.
|
14
|
+
|
15
|
+
The `migrate` and `mirror` commands provide options to help "translate" between
|
16
|
+
the behaviour of the source and destination servers.
|
data/docs/development.md
CHANGED
@@ -4,6 +4,11 @@
|
|
4
4
|
* Restartable - calculate start point based on already downloaded messages
|
5
5
|
* Standalone - do not rely on an email client or MTA
|
6
6
|
|
7
|
+
# Development
|
8
|
+
|
9
|
+
A setup for developing under any available Ruby version is
|
10
|
+
available in the container directory.
|
11
|
+
|
7
12
|
# Testing
|
8
13
|
|
9
14
|
## Feature Specs
|
@@ -56,6 +61,11 @@ uids = imap.uid_search(["ALL"]).sort
|
|
56
61
|
fetch_data_items = imap.uid_fetch(uids, ["BODY[]"])
|
57
62
|
```
|
58
63
|
|
64
|
+
# Older Ruby Versions
|
65
|
+
|
66
|
+
Dockerfiles are available for all the supported Ruby versions,
|
67
|
+
see the `docker` directory.
|
68
|
+
|
59
69
|
# Contributing
|
60
70
|
|
61
71
|
1. Fork it
|
data/docs/restore.md
CHANGED
@@ -25,6 +25,4 @@ is already on the server is skipped.
|
|
25
25
|
|
26
26
|
## How do I restore to a different service?
|
27
27
|
|
28
|
-
|
29
|
-
2. Rename of the existing backup directory to match the new email address,
|
30
|
-
3. Run the restore.
|
28
|
+
It is best to use the `migrate` command in this case.
|
data/docs/setup.md
CHANGED
@@ -0,0 +1,96 @@
|
|
1
|
+
module Imap::Backup
|
2
|
+
class CLI::FolderEnumerator
|
3
|
+
attr_reader :destination
|
4
|
+
attr_reader :destination_delimiter
|
5
|
+
attr_reader :source
|
6
|
+
attr_reader :source_delimiter
|
7
|
+
|
8
|
+
def initialize(
|
9
|
+
destination:,
|
10
|
+
source:,
|
11
|
+
destination_delimiter: "/",
|
12
|
+
destination_prefix: "",
|
13
|
+
source_delimiter: "/",
|
14
|
+
source_prefix: ""
|
15
|
+
)
|
16
|
+
@destination = destination
|
17
|
+
@destination_delimiter = destination_delimiter
|
18
|
+
@destination_prefix = destination_prefix
|
19
|
+
@source = source
|
20
|
+
@source_delimiter = source_delimiter
|
21
|
+
@source_prefix = source_prefix
|
22
|
+
end
|
23
|
+
|
24
|
+
def each
|
25
|
+
return enum_for(:each) if !block_given?
|
26
|
+
|
27
|
+
glob = File.join(source_local_path, "**", "*.imap")
|
28
|
+
Pathname.glob(glob) do |path|
|
29
|
+
name = source_folder_name(path)
|
30
|
+
serializer = Serializer.new(source_local_path, name)
|
31
|
+
folder = destination_folder_for(name)
|
32
|
+
yield serializer, folder
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def destination_prefix_clipped
|
39
|
+
@destination_prefix_clipped ||=
|
40
|
+
if @destination_prefix.end_with?(destination_delimiter)
|
41
|
+
@destination_prefix[0..-2]
|
42
|
+
else
|
43
|
+
@destination_prefix
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def source_prefix_clipped
|
48
|
+
@source_prefix_clipped ||=
|
49
|
+
if @source_prefix.end_with?(source_delimiter)
|
50
|
+
@source_prefix[0..-2]
|
51
|
+
else
|
52
|
+
@source_prefix
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def destination_folder_for(name)
|
57
|
+
parts = name.split(source_delimiter)
|
58
|
+
no_source_prefix =
|
59
|
+
if source_prefix_clipped != "" && parts.first == source_prefix_clipped
|
60
|
+
parts[1..]
|
61
|
+
else
|
62
|
+
parts
|
63
|
+
end
|
64
|
+
|
65
|
+
with_destination_prefix =
|
66
|
+
if destination_prefix_clipped && destination_prefix_clipped != ""
|
67
|
+
no_source_prefix.unshift(destination_prefix_clipped)
|
68
|
+
else
|
69
|
+
no_source_prefix
|
70
|
+
end
|
71
|
+
|
72
|
+
destination_name = with_destination_prefix.join(destination_delimiter)
|
73
|
+
|
74
|
+
Account::Folder.new(
|
75
|
+
destination.connection,
|
76
|
+
destination_name
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
def source_local_path
|
81
|
+
source.local_path
|
82
|
+
end
|
83
|
+
|
84
|
+
def source_folder_name(imap_pathname)
|
85
|
+
base = Pathname.new(source_local_path)
|
86
|
+
imap_name = imap_pathname.relative_path_from(base).to_s
|
87
|
+
dir = File.dirname(imap_name)
|
88
|
+
stripped = File.basename(imap_name, ".imap")
|
89
|
+
if dir == "."
|
90
|
+
stripped
|
91
|
+
else
|
92
|
+
File.join(dir, stripped)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "imap/backup/cli/folder_enumerator"
|
1
2
|
require "imap/backup/migrator"
|
2
3
|
|
3
4
|
module Imap::Backup
|
@@ -5,10 +6,12 @@ module Imap::Backup
|
|
5
6
|
include Thor::Actions
|
6
7
|
include CLI::Helpers
|
7
8
|
|
9
|
+
attr_reader :destination_delimiter
|
8
10
|
attr_reader :destination_email
|
9
11
|
attr_reader :destination_prefix
|
10
12
|
attr_reader :config_path
|
11
13
|
attr_reader :reset
|
14
|
+
attr_reader :source_delimiter
|
12
15
|
attr_reader :source_email
|
13
16
|
attr_reader :source_prefix
|
14
17
|
|
@@ -16,15 +19,19 @@ module Imap::Backup
|
|
16
19
|
source_email,
|
17
20
|
destination_email,
|
18
21
|
config: nil,
|
22
|
+
destination_delimiter: "/",
|
19
23
|
destination_prefix: "",
|
20
24
|
reset: false,
|
25
|
+
source_delimiter: "/",
|
21
26
|
source_prefix: ""
|
22
27
|
)
|
23
28
|
super([])
|
29
|
+
@destination_delimiter = destination_delimiter
|
24
30
|
@destination_email = destination_email
|
25
31
|
@destination_prefix = destination_prefix
|
26
32
|
@config_path = config
|
27
33
|
@reset = reset
|
34
|
+
@source_delimiter = source_delimiter
|
28
35
|
@source_email = source_email
|
29
36
|
@source_prefix = source_prefix
|
30
37
|
end
|
@@ -51,62 +58,28 @@ module Imap::Backup
|
|
51
58
|
@config ||= load_config(config: config_path)
|
52
59
|
end
|
53
60
|
|
54
|
-
def
|
55
|
-
|
61
|
+
def enumerator_options
|
62
|
+
{
|
63
|
+
destination: destination_account,
|
64
|
+
destination_delimiter: destination_delimiter,
|
65
|
+
destination_prefix: destination_prefix,
|
66
|
+
source: source_account,
|
67
|
+
source_delimiter: source_delimiter,
|
68
|
+
source_prefix: source_prefix
|
69
|
+
}
|
56
70
|
end
|
57
71
|
|
58
72
|
def folders
|
59
|
-
|
60
|
-
|
61
|
-
glob = File.join(source_local_path, "**", "*.imap")
|
62
|
-
Pathname.glob(glob) do |path|
|
63
|
-
name = source_folder_name(path)
|
64
|
-
serializer = Serializer.new(source_local_path, name)
|
65
|
-
folder = folder_for(name)
|
66
|
-
yield serializer, folder
|
67
|
-
end
|
73
|
+
CLI::FolderEnumerator.new(**enumerator_options)
|
68
74
|
end
|
69
75
|
|
70
|
-
def
|
71
|
-
|
72
|
-
if source_prefix != "" && source_folder.start_with?(source_prefix)
|
73
|
-
source_folder.delete_prefix(source_prefix)
|
74
|
-
else
|
75
|
-
source_folder.to_s
|
76
|
-
end
|
77
|
-
|
78
|
-
with_destination_prefix =
|
79
|
-
if destination_prefix && destination_prefix != ""
|
80
|
-
destination_prefix + no_source_prefix
|
81
|
-
else
|
82
|
-
no_source_prefix
|
83
|
-
end
|
84
|
-
|
85
|
-
Account::Folder.new(
|
86
|
-
destination_account.connection,
|
87
|
-
with_destination_prefix
|
88
|
-
)
|
89
|
-
end
|
90
|
-
|
91
|
-
def source_local_path
|
92
|
-
source_account.local_path
|
76
|
+
def destination_account
|
77
|
+
config.accounts.find { |a| a.username == destination_email }
|
93
78
|
end
|
94
79
|
|
95
80
|
def source_account
|
96
81
|
config.accounts.find { |a| a.username == source_email }
|
97
82
|
end
|
98
|
-
|
99
|
-
def source_folder_name(imap_pathname)
|
100
|
-
base = Pathname.new(source_local_path)
|
101
|
-
imap_name = imap_pathname.relative_path_from(base).to_s
|
102
|
-
dir = File.dirname(imap_name)
|
103
|
-
stripped = File.basename(imap_name, ".imap")
|
104
|
-
if dir == "."
|
105
|
-
stripped
|
106
|
-
else
|
107
|
-
File.join(dir, stripped)
|
108
|
-
end
|
109
|
-
end
|
110
83
|
end
|
111
84
|
end
|
112
85
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "imap/backup/cli/folder_enumerator"
|
1
2
|
require "imap/backup/mirror"
|
2
3
|
|
3
4
|
module Imap::Backup
|
@@ -5,9 +6,11 @@ module Imap::Backup
|
|
5
6
|
include Thor::Actions
|
6
7
|
include CLI::Helpers
|
7
8
|
|
9
|
+
attr_reader :destination_delimiter
|
8
10
|
attr_reader :destination_email
|
9
11
|
attr_reader :destination_prefix
|
10
12
|
attr_reader :config_path
|
13
|
+
attr_reader :source_delimiter
|
11
14
|
attr_reader :source_email
|
12
15
|
attr_reader :source_prefix
|
13
16
|
|
@@ -15,13 +18,17 @@ module Imap::Backup
|
|
15
18
|
source_email,
|
16
19
|
destination_email,
|
17
20
|
config: nil,
|
21
|
+
destination_delimiter: "/",
|
18
22
|
destination_prefix: "",
|
23
|
+
source_delimiter: "/",
|
19
24
|
source_prefix: ""
|
20
25
|
)
|
21
26
|
super([])
|
27
|
+
@destination_delimiter = destination_delimiter
|
22
28
|
@destination_email = destination_email
|
23
29
|
@destination_prefix = destination_prefix
|
24
30
|
@config_path = config
|
31
|
+
@source_delimiter = source_delimiter
|
25
32
|
@source_email = source_email
|
26
33
|
@source_prefix = source_prefix
|
27
34
|
end
|
@@ -61,62 +68,28 @@ module Imap::Backup
|
|
61
68
|
@config = load_config(config: config_path)
|
62
69
|
end
|
63
70
|
|
64
|
-
def
|
65
|
-
|
71
|
+
def enumerator_options
|
72
|
+
{
|
73
|
+
destination: destination_account,
|
74
|
+
destination_delimiter: destination_delimiter,
|
75
|
+
destination_prefix: destination_prefix,
|
76
|
+
source: source_account,
|
77
|
+
source_delimiter: source_delimiter,
|
78
|
+
source_prefix: source_prefix
|
79
|
+
}
|
66
80
|
end
|
67
81
|
|
68
82
|
def folders
|
69
|
-
|
70
|
-
|
71
|
-
glob = File.join(source_local_path, "**", "*.imap")
|
72
|
-
Pathname.glob(glob) do |path|
|
73
|
-
name = source_folder_name(path)
|
74
|
-
serializer = Serializer.new(source_local_path, name)
|
75
|
-
folder = folder_for(name)
|
76
|
-
yield serializer, folder
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def folder_for(source_folder)
|
81
|
-
no_source_prefix =
|
82
|
-
if source_prefix != "" && source_folder.start_with?(source_prefix)
|
83
|
-
source_folder.delete_prefix(source_prefix)
|
84
|
-
else
|
85
|
-
source_folder.to_s
|
86
|
-
end
|
87
|
-
|
88
|
-
with_destination_prefix =
|
89
|
-
if destination_prefix && destination_prefix != ""
|
90
|
-
destination_prefix + no_source_prefix
|
91
|
-
else
|
92
|
-
no_source_prefix
|
93
|
-
end
|
94
|
-
|
95
|
-
Account::Folder.new(
|
96
|
-
destination_account.connection,
|
97
|
-
with_destination_prefix
|
98
|
-
)
|
83
|
+
CLI::FolderEnumerator.new(**enumerator_options)
|
99
84
|
end
|
100
85
|
|
101
|
-
def
|
102
|
-
|
86
|
+
def destination_account
|
87
|
+
config.accounts.find { |a| a.username == destination_email }
|
103
88
|
end
|
104
89
|
|
105
90
|
def source_account
|
106
91
|
config.accounts.find { |a| a.username == source_email }
|
107
92
|
end
|
108
|
-
|
109
|
-
def source_folder_name(imap_pathname)
|
110
|
-
base = Pathname.new(source_local_path)
|
111
|
-
imap_name = imap_pathname.relative_path_from(base).to_s
|
112
|
-
dir = File.dirname(imap_name)
|
113
|
-
stripped = File.basename(imap_name, ".imap")
|
114
|
-
if dir == "."
|
115
|
-
stripped
|
116
|
-
else
|
117
|
-
File.join(dir, stripped)
|
118
|
-
end
|
119
|
-
end
|
120
93
|
end
|
121
94
|
end
|
122
95
|
end
|
data/lib/imap/backup/cli.rb
CHANGED
@@ -69,9 +69,12 @@ module Imap::Backup
|
|
69
69
|
All emails which have been backed up for the "source account" (SOURCE_EMAIL) are
|
70
70
|
uploaded to the "destination account" (DESTINATION_EMAIL).
|
71
71
|
|
72
|
-
When one or other account has namespaces (i.e. prefixes like "INBOX
|
72
|
+
When one or other account has namespaces (i.e. prefixes like "INBOX"),
|
73
73
|
use the `--source-prefix=` and/or `--destination-prefix=` options.
|
74
74
|
|
75
|
+
When one or other account uses a delimiter other than `/` (i.e. `.`),
|
76
|
+
use the `--source-delimiter=` and/or `--destination-delimiter=` options.
|
77
|
+
|
75
78
|
Usually, you should migrate to an account with empty folders.
|
76
79
|
|
77
80
|
Before migrating each folder, `imap-backup` checks if the destination
|
@@ -86,6 +89,11 @@ module Imap::Backup
|
|
86
89
|
config_option
|
87
90
|
quiet_option
|
88
91
|
verbose_option
|
92
|
+
method_option(
|
93
|
+
"destination-delimiter",
|
94
|
+
type: :string,
|
95
|
+
desc: "the delimiter for destination folder names"
|
96
|
+
)
|
89
97
|
method_option(
|
90
98
|
"destination-prefix",
|
91
99
|
type: :string,
|
@@ -98,6 +106,11 @@ module Imap::Backup
|
|
98
106
|
desc: "DANGER! This option deletes all messages from destination folders before uploading",
|
99
107
|
aliases: ["-r"]
|
100
108
|
)
|
109
|
+
method_option(
|
110
|
+
"source-delimiter",
|
111
|
+
type: :string,
|
112
|
+
desc: "the delimiter for source folder names"
|
113
|
+
)
|
101
114
|
method_option(
|
102
115
|
"source-prefix",
|
103
116
|
type: :string,
|
@@ -132,12 +145,22 @@ module Imap::Backup
|
|
132
145
|
config_option
|
133
146
|
quiet_option
|
134
147
|
verbose_option
|
148
|
+
method_option(
|
149
|
+
"destination-delimiter",
|
150
|
+
type: :string,
|
151
|
+
desc: "the delimiter for destination folder names"
|
152
|
+
)
|
135
153
|
method_option(
|
136
154
|
"destination-prefix",
|
137
155
|
type: :string,
|
138
156
|
desc: "the prefix (namespace) to add to destination folder names",
|
139
157
|
aliases: ["-d"]
|
140
158
|
)
|
159
|
+
method_option(
|
160
|
+
"source-delimiter",
|
161
|
+
type: :string,
|
162
|
+
desc: "the delimiter for source folder names"
|
163
|
+
)
|
141
164
|
method_option(
|
142
165
|
"source-prefix",
|
143
166
|
type: :string,
|
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:
|
4
|
+
version: 9.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-12-
|
11
|
+
date: 2022-12-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|
@@ -218,6 +218,7 @@ files:
|
|
218
218
|
- README.md
|
219
219
|
- bin/imap-backup
|
220
220
|
- docs/configuration.md
|
221
|
+
- docs/delimiters-and-prefixes.md
|
221
222
|
- docs/development.md
|
222
223
|
- docs/restore.md
|
223
224
|
- docs/setup.md
|
@@ -240,6 +241,7 @@ files:
|
|
240
241
|
- lib/imap/backup/account/folder.rb
|
241
242
|
- lib/imap/backup/cli.rb
|
242
243
|
- lib/imap/backup/cli/backup.rb
|
244
|
+
- lib/imap/backup/cli/folder_enumerator.rb
|
243
245
|
- lib/imap/backup/cli/helpers.rb
|
244
246
|
- lib/imap/backup/cli/local.rb
|
245
247
|
- lib/imap/backup/cli/migrate.rb
|