imap-backup 8.0.1 → 9.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e9bb0714badd71868da39868ccf19337cab655b34a0b75e19fa4022efdba7e1
4
- data.tar.gz: 077b1accfaa164424e49ed98913124a04c0d5014eb52b2b79676ec41d038c942
3
+ metadata.gz: 3a95742a7ae3b07a12f7cb95d50bcea360530ee351edbca8d9b93e5c4579090a
4
+ data.tar.gz: 9d7d43ba0f44f13a19272214dfe8ca265c72f7d1173ccf67c4d4a873de007a81
5
5
  SHA512:
6
- metadata.gz: b9475fd7fb40295af794ad94572b8df8733a26a75c2045a0dd26feec655391796206c5d6f777b2255e3ddd5f5658c1c59186c8b63026650e1312eebf90e4c2ec
7
- data.tar.gz: 53ae3936d98b3b88b1127a632d5933feaf42429b2917efd8442c3963631ef12ebb5498546b25b014eedcbe7e255cb68ae40520d1ce0f7f67604e8158d2530862
6
+ metadata.gz: 155eeb475d56b03bc8012fc8b9b2882fbd8bcaad4366ee2119952f710de79d94a6709c81185ccc49b4f20f6d333523272de4cfa3742c1ff05f797830d1cb916e
7
+ data.tar.gz: dc6c0ce95c22c81512eb71791f6a75ecc8fe35f011e48fd0d68266771fac96f234664f22316fd1e98fbfc93397713f60375555442577dc0977eedcabfc2cf1ea
data/README.md CHANGED
@@ -7,18 +7,20 @@
7
7
 
8
8
  Backup, restore and migrate email accounts.
9
9
 
10
- The backups can then be restored, used to migrate to another service,
11
- inspected or exported.
10
+ # Modes
12
11
 
13
- * [Source Code]
14
- * [Code Documentation]
15
- * [Rubygem]
16
- * [CI Status]
12
+ There are two types of backups:
17
13
 
18
- [Source Code]: https://github.com/joeyates/imap-backup "Source code at GitHub"
19
- [Code Documentation]: https://rubydoc.info/gems/imap-backup/frames "Code Documentation at Rubydoc.info"
20
- [Rubygem]: https://rubygems.org/gems/imap-backup "Ruby gem at rubygems.org"
21
- [CI Status]: https://github.com/joeyates/imap-backup/actions/workflows/main.yml
14
+ * Keep all (the default) - progressively saves a local copy of all emails,
15
+ * Mirror - adds and deletes emails from the local copy to keep it up to date with the account.
16
+
17
+ # What You Can Do with a Backup
18
+
19
+ * Migrate - use the local copy to populate emails on another account. This is a once-only action that deletes any existing emails on the destination account.
20
+ * Mirror - make a destination account match the local copy. This action can be repeated.
21
+ * Restore - push the local copy back to the original account.
22
+
23
+ See below for a [full list of commands](#Commands).
22
24
 
23
25
  # Installation
24
26
 
@@ -67,6 +69,9 @@ imap-backup
67
69
 
68
70
  Alternatively, add it to your crontab.
69
71
 
72
+ Backups can also be inspected, for example via [local show](docs/commands/local-show.md)
73
+ and exported via [utils export-to-thunderbird](docs/commands/utils-export-to-thunderbird.md).
74
+
70
75
  # Commands
71
76
 
72
77
  * [backup](docs/commands/backup.md)
@@ -99,7 +104,7 @@ imap-backup help COMMAND
99
104
  If you have problems:
100
105
 
101
106
  1. ensure that you have the latest release,
102
- 2. turn on debugging output via the `imap-backup setup` main menu.
107
+ 2. run `imap-backup` with the `-v` or `--verbose` parameter.
103
108
 
104
109
  # Development
105
110
 
@@ -108,3 +113,13 @@ on development and testing.
108
113
 
109
114
  See [the CHANGELOG](./CHANGELOG.md) to a list of changes that have been
110
115
  made in each release.
116
+
117
+ * [Source Code]
118
+ * [Code Documentation]
119
+ * [Rubygem]
120
+ * [CI Status]
121
+
122
+ [Source Code]: https://github.com/joeyates/imap-backup "Source code at GitHub"
123
+ [Code Documentation]: https://rubydoc.info/gems/imap-backup/frames "Code Documentation at Rubydoc.info"
124
+ [Rubygem]: https://rubygems.org/gems/imap-backup "Ruby gem at rubygems.org"
125
+ [CI Status]: https://github.com/joeyates/imap-backup/actions/workflows/main.yml
@@ -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/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
- 1. Run setup and add the new server, (but don't run any backups),
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
@@ -2,4 +2,4 @@
2
2
 
3
3
  ## Set the server for a Google Apps account
4
4
 
5
- Use imap.gmail.com
5
+ Use `imap.gmail.com` as the 'server' setting.
data/imap-backup.gemspec CHANGED
@@ -22,8 +22,8 @@ Gem::Specification.new do |gem|
22
22
  gem.required_ruby_version = ">= 2.6"
23
23
 
24
24
  gem.add_runtime_dependency "highline"
25
- gem.add_runtime_dependency "mail"
26
- gem.add_runtime_dependency "net-imap"
25
+ gem.add_runtime_dependency "mail", "2.7.1"
26
+ gem.add_runtime_dependency "net-imap", ">= 0.3.2"
27
27
  gem.add_runtime_dependency "net-smtp"
28
28
  gem.add_runtime_dependency "os"
29
29
  gem.add_runtime_dependency "rake"
@@ -154,8 +154,9 @@ module Imap::Backup
154
154
  end
155
155
 
156
156
  def extract_uid(response)
157
- @uid_validity, uid = response.data.code.data.split.map(&:to_i)
158
- uid
157
+ uid_data = response.data.code.data
158
+ @uid_validity = uid_data.uidvalidity
159
+ uid_data.assigned_uids.first
159
160
  end
160
161
 
161
162
  def utf7_encoded_name
@@ -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 destination_account
55
- config.accounts.find { |a| a.username == destination_email }
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
- return enum_for(:folders) if !block_given?
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 folder_for(source_folder)
71
- no_source_prefix =
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 destination_account
65
- config.accounts.find { |a| a.username == destination_email }
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
- return enum_for(:folders) if !block_given?
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 source_local_path
102
- source_account.local_path
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
@@ -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,
@@ -1,9 +1,9 @@
1
1
  module Imap; end
2
2
 
3
3
  module Imap::Backup
4
- MAJOR = 8
4
+ MAJOR = 9
5
5
  MINOR = 0
6
- REVISION = 1
7
- PRE = nil
6
+ REVISION = 0
7
+ PRE = "rc1".freeze
8
8
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
9
9
  end
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.1
4
+ version: 9.0.0.rc1
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-24 00:00:00.000000000 Z
11
+ date: 2022-12-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -28,30 +28,30 @@ dependencies:
28
28
  name: mail
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 2.7.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 2.7.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: net-imap
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 0.3.2
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: 0.3.2
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: net-smtp
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -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
@@ -299,9 +301,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
299
301
  version: '2.6'
300
302
  required_rubygems_version: !ruby/object:Gem::Requirement
301
303
  requirements:
302
- - - ">="
304
+ - - ">"
303
305
  - !ruby/object:Gem::Version
304
- version: '0'
306
+ version: 1.3.1
305
307
  requirements: []
306
308
  rubygems_version: 3.3.7
307
309
  signing_key: