imap-backup 13.3.0 → 14.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52be0d33c65e865c04931e81821a563e8203a001a951b8ec6624f0fded2180aa
4
- data.tar.gz: 2fece56b157329aa6e8cd0bc73d66d5ec3c3923f126822977910dc904d955a1a
3
+ metadata.gz: c98349e8780fc3170e7ca735e6469fb38c9eeb3b7f023eec8d5f7d1e80e7258b
4
+ data.tar.gz: 3ebb575e74b72ac190f52b192cf8a5af54302799f4feec7d707cb438b4e5f542
5
5
  SHA512:
6
- metadata.gz: 26c0c0434bf7743a3ce9b8b6c0f24b05b14682f4e52fcacf4813cec7a913df720f6413a066b25db810e04d4e18411cfbd79119430f37785186b3013757a2e76d
7
- data.tar.gz: 3f250c3272221488bb8b9c08b1f68198a1a6b3f45bd75c31ccc6b2284c0e2130d65bcd91783c6c85b65b4e1bbdbe09378a1ff15e595f8ab3ae016d2faeb53aee
6
+ metadata.gz: 11a1f328334d32eaf54ec2b013bf444f9434704c9e218bf51e34798a675b33be1569a0293ddacd521d923528e8dbb534c836877e302af11ad5fd8be0ceb764a3
7
+ data.tar.gz: 8f40f2ed79c32716b366a678da2c065b1df83a206901411bb3667ee31183779e668bd2619dd252cbaee46683a65ab9c5c3374afa30fa73cc0dc5b03aa83efa43
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
  [![Build Status](https://github.com/joeyates/imap-backup/actions/workflows/main.yml/badge.svg)][CI Status]
3
3
  ![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/joeyates/b54fe758bfb405c04bef72dad293d707/raw/coverage.json)
4
4
  ![License](https://img.shields.io/github/license/joeyates/imap-backup?color=brightgreen&label=License)
5
+ [![Stars](https://img.shields.io/github/stars/joeyates/imap-backup?style=social)][GitHub Stars]
5
6
 
6
7
  # imap-backup
7
8
 
@@ -11,7 +12,7 @@ Backup, restore and migrate email accounts.
11
12
 
12
13
  ```sh
13
14
  brew install imap-backup # for macOS
14
- gem install imap-backup # for Linux
15
+ gem install imap-backup --no-document # for Linux
15
16
  imap-backup setup
16
17
  imap-backup
17
18
  ```
@@ -35,6 +36,8 @@ See below for a [full list of commands](#Commands).
35
36
 
36
37
  ## Homebrew (macOS)
37
38
 
39
+ ![Homebrew installs](https://img.shields.io/homebrew/installs/dm/imap-backup?label=Homebrew%20installs)
40
+
38
41
  If you have [Homebrew](https://brew.sh/), do this:
39
42
 
40
43
  ```sh
@@ -44,7 +47,7 @@ brew install imap-backup
44
47
  ## As a Ruby Gem
45
48
 
46
49
  ```sh
47
- gem install imap-backup
50
+ gem install imap-backup --no-document
48
51
  ```
49
52
 
50
53
  If that doesn't work, see the [detailed installation instructions](docs/installation/rubygem.md).
@@ -55,8 +58,10 @@ If you want to use imap-backup directly from the source code, see [here](docs/in
55
58
 
56
59
  # Setup
57
60
 
58
- As a first step, you need to add accounts via a menu-driven command
59
- line program:
61
+ Normally you will want to backup a number of email accounts.
62
+ Doing so requires the creation of a config file.
63
+
64
+ You do this via a menu-driven command line program:
60
65
 
61
66
  Run:
62
67
 
@@ -64,6 +69,10 @@ Run:
64
69
  imap-backup setup
65
70
  ```
66
71
 
72
+ As an alternative, if you only want to backup a single account,
73
+ you can pass all the necessary parameters directly to the `single backup` command
74
+ (see the [`single backup`](docs/commands/single-backup.md) docs).
75
+
67
76
  ## GMail
68
77
 
69
78
  To use imap-backup with GMail, Office 365 and other services that require
@@ -80,24 +89,25 @@ imap-backup
80
89
 
81
90
  Alternatively, add it to your crontab.
82
91
 
83
- Backups can also be inspected, for example via [local show](docs/commands/local-show.md)
84
- and exported via [utils export-to-thunderbird](docs/commands/utils-export-to-thunderbird.md).
92
+ Backups can also be inspected, for example via [`local show`](docs/commands/local-show.md)
93
+ and exported via [`utils export-to-thunderbird`](docs/commands/utils-export-to-thunderbird.md).
85
94
 
86
95
  # Commands
87
96
 
88
- * [backup](docs/commands/backup.md)
89
- * [local accounts](docs/commands/local-accounts.md)
90
- * [local check](docs/commands/local-check.md)
91
- * [local folders](docs/commands/local-folders.md)
92
- * [local list](docs/commands/local-list.md)
93
- * [local show](docs/commands/local-show.md)
94
- * [migrate](docs/commands/migrate.md)
95
- * [mirror](docs/commands/mirror.md)
96
- * [remote folders](docs/commands/remote-folders.md)
97
- * [restore](docs/commands/restore.md)
98
- * [setup](docs/commands/setup.md)
99
- * [utils export-to-thunderbird](docs/commands/utils-export-to-thunderbird.md)
100
- * [utils ignore-history](docs/commands/utils-ignore-history.md)
97
+ * [`backup`](docs/commands/backup.md)
98
+ * [`local accounts`](docs/commands/local-accounts.md)
99
+ * [`local check`](docs/commands/local-check.md)
100
+ * [`local folders`](docs/commands/local-folders.md)
101
+ * [`local list`](docs/commands/local-list.md)
102
+ * [`local show`](docs/commands/local-show.md)
103
+ * [`migrate`](docs/commands/migrate.md)
104
+ * [`mirror`](docs/commands/mirror.md)
105
+ * [`remote folders`](docs/commands/remote-folders.md)
106
+ * [`restore`](docs/commands/restore.md)
107
+ * [`setup`](docs/commands/setup.md)
108
+ * [`single backup`](docs/commands/single-backup.md)
109
+ * [`utils export-to-thunderbird`](docs/commands/utils-export-to-thunderbird.md)
110
+ * [`utils ignore-history`](docs/commands/utils-ignore-history.md)
101
111
 
102
112
  For a full list of available commands, run
103
113
 
@@ -118,46 +128,10 @@ to improve backup speed.
118
128
 
119
129
  These are activated via two settings:
120
130
 
121
- * Global setting "Delay download writes"
122
- * Account setting "Multi-fetch size"
123
-
124
- As with all performance tweaks, there are trade-offs.
125
- If you are using a small virtual server or Raspberry Pi
126
- to run your backups, you will probably want to leave
127
- the default settings.
128
- If, on the other hand, you are using a computer with a
129
- fair bit of RAM, and you are dealing with a *lot* of email,
130
- then changing these settings may be worthwhile.
131
-
132
- ## Delay download writes
133
-
134
- This setting affects all account backups.
135
-
136
- By default, `imap-backup` uses the "delay metadata" strategy.
137
- As messages are being backed-up, the message *text*
138
- is written to disk, while the related metadata is stored in memory.
131
+ * Global setting "Delay download writes",
132
+ * Account setting "Multi-fetch size".
139
133
 
140
- While this uses a little more memory, it avoids rewiting a growing JSON
141
- file for every message, speeding things up and reducing disk wear.
142
-
143
- The alternative strategy, called "direct", writes everything to disk
144
- as it is received. This method is slower, but has the advantage
145
- of using slightly less memory, which may be important on very
146
- resource-limited systems, like Raspberry Pis.
147
-
148
- ## Multi-fetch Size
149
-
150
- By default, during backup, each message is downloaded one-by-one.
151
-
152
- Using this setting, you can download chunks of emails at a time,
153
- potentially speeding up the process.
154
-
155
- Using multi-fetch *will* mean that the backup process will use
156
- more memory - equivalent to the size of the groups of messages
157
- that are downloaded.
158
-
159
- This behaviour may also exceed the rate limits on your email provider,
160
- so it's best to check before cranking it up!
134
+ See [the performance document](docs/performance.md) for more information.
161
135
 
162
136
  # Troubleshooting
163
137
 
@@ -168,10 +142,12 @@ If you have problems:
168
142
 
169
143
  # Development
170
144
 
145
+ ![Activity](https://img.shields.io/github/last-commit/joeyates/imap-backup/main)
146
+
171
147
  See the [Development documentation](./docs/development.md) for notes
172
148
  on development and testing.
173
149
 
174
- See [the CHANGELOG](./CHANGELOG.md) to a list of changes that have been
150
+ See [the CHANGELOG](./CHANGELOG.md) for a list of changes that have been
175
151
  made in each release.
176
152
 
177
153
  * [Source Code]
@@ -180,6 +156,7 @@ made in each release.
180
156
  * [CI Status]
181
157
 
182
158
  [Source Code]: https://github.com/joeyates/imap-backup "Source code at GitHub"
159
+ [GitHub Stars]: https://github.com/joeyates/imap-backup/stargazers "GitHub Stars"
183
160
  [Code Documentation]: https://rubydoc.info/gems/imap-backup/frames "Code Documentation at Rubydoc.info"
184
161
  [Rubygem]: https://rubygems.org/gems/imap-backup "Ruby gem at rubygems.org"
185
162
  [CI Status]: https://github.com/joeyates/imap-backup/actions/workflows/main.yml
data/docs/development.md CHANGED
@@ -1,6 +1,15 @@
1
+ # Repository
2
+
3
+ After cloning the repo, run the following command to get
4
+ better `git blame` output:
5
+
6
+ ```sh
7
+ git config --local blame.ignoreRevsFile .git-blame-ignore-revs
8
+ ```
9
+
1
10
  # Design Goals
2
11
 
3
- * Secure - use a local file protected by permissions
12
+ * Secure - use a local configuration file protected by permissions
4
13
  * Restartable - calculate start point based on already downloaded messages
5
14
  * Standalone - do not rely on an email client or MTA
6
15
 
@@ -0,0 +1,53 @@
1
+ # Performace
2
+
3
+ The two performance-related settings are "Download strategy",
4
+ which is a global setting,
5
+ and "Multi-fetch size", which is an Account-level setting.
6
+
7
+ As with all performance tweaks, there are trade-offs.
8
+
9
+ # Overview
10
+
11
+ The defaults, which suit most machines and plays nice with servers is:
12
+
13
+ * Download strategy: "delay writing metadata",
14
+ * Multi-fetch size: 1.
15
+
16
+ If you are using a resource-limited machine like
17
+ a small virtual server or Raspberry Pi
18
+ to run your backups, you can change "Download strategy".
19
+
20
+ If your email provider supports it,
21
+ and you don't have tight memeory limits,
22
+ increase "Multi-fetch size" for faster backups.
23
+
24
+ # Delay download writes
25
+
26
+ This is a global setting, affecting all account backups.
27
+
28
+ By default, `imap-backup` uses the "delay writing metadata" strategy.
29
+ As messages are being backed-up, the message *text*
30
+ is written to disk, while the related metadata is stored in memory.
31
+
32
+ While this uses a *little* more memory, it avoids rewiting a growing JSON
33
+ file for every message, speeding things up and reducing disk wear.
34
+
35
+ The alternative strategy, called "write straight to disk",
36
+ writes everything to disk as it is received.
37
+ This method is slower, but has the advantage
38
+ of using slightly less memory, which may be important on very
39
+ resource-limited systems, like Raspberry Pis.
40
+
41
+ # Multi-fetch Size
42
+
43
+ By default, during backup, each message is downloaded one-by-one.
44
+
45
+ Using this setting, you can download chunks of emails at a time,
46
+ potentially speeding up the process.
47
+
48
+ Using multi-fetch *will* mean that the backup process will use
49
+ more memory - equivalent to the size of the groups of messages
50
+ that are downloaded.
51
+
52
+ This behaviour may also exceed the rate limits on your email provider,
53
+ so it's best to check before cranking it up!
@@ -24,7 +24,7 @@ module Imap::Backup
24
24
  configured =
25
25
  case
26
26
  when account.folders&.any?
27
- account.folders.map { |af| af[:name] }
27
+ account.folders
28
28
  when account.folder_blacklist
29
29
  []
30
30
  else
@@ -1,6 +1,8 @@
1
1
  require "forwardable"
2
+ require "logger"
2
3
  require "net/imap"
3
4
 
5
+ require "imap/backup/logger"
4
6
  require "retry_on_error"
5
7
 
6
8
  module Imap; end
@@ -31,12 +33,19 @@ module Imap::Backup
31
33
  end
32
34
 
33
35
  def exist?
36
+ previous_level = Imap::Backup::Logger.logger.level
37
+ previous_debug = Net::IMAP.debug
38
+ Imap::Backup::Logger.logger.level = ::Logger::Severity::UNKNOWN
39
+ Net::IMAP.debug = false
34
40
  retry_on_error(errors: EXAMINE_RETRY_CLASSES) do
35
41
  examine
36
42
  end
37
43
  true
38
44
  rescue FolderNotFound
39
45
  false
46
+ ensure
47
+ Imap::Backup::Logger.logger.level = previous_level
48
+ Net::IMAP.debug = previous_debug
40
49
  end
41
50
 
42
51
  def create
@@ -68,7 +77,7 @@ module Imap::Backup
68
77
  'This is caused by `@responses["SEARCH"] being unset/undefined. ' \
69
78
  "Among others, Apple Mail servers send empty responses when " \
70
79
  "folders are empty, causing this error."
71
- Logger.logger.warn message
80
+ Imap::Backup::Logger.logger.warn message
72
81
  []
73
82
  end
74
83
 
@@ -150,7 +159,8 @@ module Imap::Backup
150
159
  def examine
151
160
  client.examine(utf7_encoded_name)
152
161
  rescue Net::IMAP::NoResponseError
153
- Logger.logger.warn "Folder '#{name}' does not exist on server"
162
+ Imap::Backup::Logger.logger.warn "Folder '#{name}' does not exist on server"
163
+ Imap::Backup::Logger.logger.warn caller.join("\n")
154
164
  raise FolderNotFound, "Folder '#{name}' does not exist on server"
155
165
  end
156
166
 
@@ -1,6 +1,7 @@
1
1
  require "thor"
2
2
 
3
3
  require "imap/backup/configuration"
4
+ require "imap/backup/configuration_not_found"
4
5
 
5
6
  module Imap; end
6
7
 
@@ -46,12 +47,27 @@ module Imap::Backup
46
47
  )
47
48
  end
48
49
 
50
+ def self.refresh_option
51
+ method_option(
52
+ "refresh",
53
+ type: :boolean,
54
+ desc: "in the default 'keep all emails' mode, " \
55
+ "updates flags for messages that are already downloaded",
56
+ aliases: ["-r"]
57
+ )
58
+ end
59
+
49
60
  def self.verbose_option
50
61
  method_option(
51
62
  "verbose",
52
63
  type: :boolean,
53
- desc: "increase the amount of logging",
54
- aliases: ["-v"]
64
+ desc:
65
+ "increase the amount of logging. " \
66
+ "Without this option, the program gives minimal output. " \
67
+ "Using this option once gives more detailed output. " \
68
+ "Whereas, using this option twice also shows all IMAP network calls",
69
+ aliases: ["-v"],
70
+ repeatable: true
55
71
  )
56
72
  end
57
73
  end
@@ -3,6 +3,7 @@ require "thor"
3
3
  require "imap/backup/account/serialized_folders"
4
4
  require "imap/backup/cli/helpers"
5
5
  require "imap/backup/cli/local/check"
6
+ require "imap/backup/logger"
6
7
 
7
8
  module Imap; end
8
9
 
@@ -0,0 +1,131 @@
1
+ require "thor"
2
+
3
+ require "imap/backup/account"
4
+ require "imap/backup/account/backup"
5
+ require "imap/backup/configuration"
6
+
7
+ module Imap; end
8
+
9
+ module Imap::Backup
10
+ class CLI < Thor; end
11
+ class CLI::Single < Thor; end
12
+
13
+ class CLI::Single::Backup
14
+ attr_reader :options
15
+ attr_reader :password
16
+
17
+ def initialize(options)
18
+ @options = options
19
+ @password = nil
20
+ end
21
+
22
+ def run
23
+ process_options!
24
+ account = Account.new(
25
+ username: email,
26
+ password: password,
27
+ server: server,
28
+ download_strategy: download_strategy,
29
+ folder_blacklist: folder_blacklist,
30
+ local_path: local_path,
31
+ mirror: mirror,
32
+ reset_seen_flags_after_fetch: reset_seen_flags_after_fetch
33
+ )
34
+ account.connection_options = connection_options if connection_options
35
+ account.folders = folders if folders.any?
36
+ account.multi_fetch_size = multi_fetch_size if multi_fetch_size
37
+ backup = Account::Backup.new(account: account, refresh: refresh)
38
+ backup.run
39
+ end
40
+
41
+ private
42
+
43
+ def process_options!
44
+ if !email
45
+ raise Thor::RequiredArgumentMissingError,
46
+ "No value provided for required options '--email'"
47
+ end
48
+ if !server
49
+ raise Thor::RequiredArgumentMissingError,
50
+ "No value provided for required options '--server'"
51
+ end
52
+ handle_password_options!
53
+ end
54
+
55
+ def handle_password_options!
56
+ plain = options[:password]
57
+ env = options[:password_environment_variable]
58
+ file = options[:password_file]
59
+ case [plain, env, file]
60
+ when [nil, nil, nil]
61
+ raise Thor::RequiredArgumentMissingError,
62
+ "Supply one of the --password... parameters"
63
+ when [plain, nil, nil]
64
+ @password = plain
65
+ when [nil, env, nil]
66
+ @password = ENV.fetch(env)
67
+ when [nil, nil, file]
68
+ @password = File.read(file).gsub(/\n$/, "")
69
+ else
70
+ raise ArgumentError, "Supply only one of the --password... parameters"
71
+ end
72
+ end
73
+
74
+ def connection_options
75
+ options[:connection_options]
76
+ end
77
+
78
+ def download_strategy
79
+ @download_strategy =
80
+ case options[:download_strategy]
81
+ when nil
82
+ Configuration::DEFAULT_STRATEGY
83
+ when "delay"
84
+ "delay_metadata"
85
+ when "direct"
86
+ "direct"
87
+ else
88
+ raise ArgumentError, "Unknown download_strategy: '#{options[:download_strategy]}'"
89
+ end
90
+ end
91
+
92
+ def email
93
+ options[:email]
94
+ end
95
+
96
+ def folder_blacklist
97
+ options[:folder_blacklist] ? true : false
98
+ end
99
+
100
+ def folders
101
+ @folders ||= options[:folder] || []
102
+ end
103
+
104
+ def local_path
105
+ return options[:path] if options.key?(:path)
106
+
107
+ for_account = email.tr("@", "_")
108
+ File.join(Dir.pwd, for_account)
109
+ end
110
+
111
+ def mirror
112
+ options[:mirror] ? true : false
113
+ end
114
+
115
+ def multi_fetch_size
116
+ options[:multi_fetch_size]
117
+ end
118
+
119
+ def refresh
120
+ options[:refresh] ? true : false
121
+ end
122
+
123
+ def reset_seen_flags_after_fetch
124
+ options[:reset_seen_flags_after_fetch] ? true : false
125
+ end
126
+
127
+ def server
128
+ options[:server]
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,179 @@
1
+ require "thor"
2
+
3
+ require "imap/backup/logger"
4
+ require "imap/backup/cli/helpers"
5
+ require "imap/backup/cli/single/backup"
6
+
7
+ module Imap; end
8
+
9
+ module Imap::Backup
10
+ class CLI < Thor; end
11
+
12
+ class CLI::Single < Thor
13
+ include CLI::Helpers
14
+
15
+ desc "backup", "Backup a single email account based on command-line parameters"
16
+ long_desc <<~DESC
17
+ This is a "stand-alone" backup command that doesn't require
18
+ a configuration file.
19
+
20
+ At a minimum, you need to supply the email, the server and the
21
+ password. (There are three ways of specifying the password)
22
+
23
+ $ imap-backup single backup
24
+ --email me@example.com
25
+ --password MyS3kr1t
26
+ --server imap.example.com
27
+
28
+ Instead of supplying the password directly on the command line,
29
+ there are two alternatives.
30
+ You can set an environment variable (with any name) to your
31
+ password, then pass the name of the environment variable.
32
+
33
+ For example, if MY_IMAP_PASSWORD is set to your password,
34
+
35
+ $ imap-backup single backup
36
+ --email me@example.com
37
+ --password-environment-variable MY_IMAP_PASSWORD
38
+ --server imap.example.com
39
+
40
+ Alternatively, you can supply the name of a file that contains
41
+ the password.
42
+
43
+ For example, in `~/imap-password`:
44
+
45
+ `MyS3kr1t`
46
+
47
+ $ imap-backup single backup
48
+ --email me@example.com
49
+ --password-file ~/imap-password
50
+ --server imap.example.com
51
+
52
+ If you need to use an insecure connection (this normally happens
53
+ when running an OAuth2 proxy), you can specify server connection options
54
+ in JSON:
55
+
56
+ $ imap-backup single backup
57
+ --email me@example.com
58
+ --password MyS3kr1t
59
+ --server imap.example.com
60
+ --connection-options '{"ssl":{"verify_mode":0}}'
61
+ DESC
62
+ method_option(
63
+ "email",
64
+ type: :string,
65
+ desc: "the email address",
66
+ aliases: ["-e"]
67
+ )
68
+ method_option(
69
+ "server",
70
+ type: :string,
71
+ desc: "the address of the IMAP server",
72
+ aliases: ["-s"]
73
+ )
74
+ method_option(
75
+ "password",
76
+ type: :string,
77
+ desc: "your password. " \
78
+ "As an alternative, use the --password-environment-variable " \
79
+ "or --password-file parameter. " \
80
+ "You need to pass exactly one of these parameters. " \
81
+ "If you pass more than one of the --password... parameters together " \
82
+ "you will get an error.",
83
+ aliases: ["-p"]
84
+ )
85
+ method_option(
86
+ "password-environment-variable",
87
+ type: :string,
88
+ desc: "an environment variable that is set to your password",
89
+ aliases: ["-e"]
90
+ )
91
+ method_option(
92
+ "password-file",
93
+ type: :string,
94
+ desc: "a file containing your password. " \
95
+ "Note that to make it easier to create such files, " \
96
+ "trailing newlines will be removed. " \
97
+ "If you happen to have a password that ends in a newline (!), " \
98
+ "you can't use this parameter.",
99
+ aliases: ["-W"]
100
+ )
101
+ method_option(
102
+ "path",
103
+ type: "string",
104
+ desc: "the path of the directory where backups are to be saved. " \
105
+ "If the directory does not exists, it will be created. " \
106
+ "If not set, this is set to a diretory under the current path " \
107
+ "which is derived from the username, by replacing '@' with '_'.",
108
+ aliases: ["-P"]
109
+ )
110
+ method_option(
111
+ "folder",
112
+ type: :string,
113
+ desc: "a folder (this option can be given any number of times). " \
114
+ "By default, all of an account's folders are backed up. " \
115
+ "If you supply any --folder parameters, " \
116
+ "only **those** folders are backed up. " \
117
+ "See also --folder-blacklist.",
118
+ repeatable: true,
119
+ aliases: ["-F"]
120
+ )
121
+ method_option(
122
+ "folder-blacklist",
123
+ type: :boolean,
124
+ desc: "if this option is given, the list of --folders specified " \
125
+ "will treated as a blacklist - " \
126
+ "those folders will be skipped and " \
127
+ "all others will be backed up.",
128
+ default: false,
129
+ aliases: ["-b"]
130
+ )
131
+ method_option(
132
+ "mirror",
133
+ type: :boolean,
134
+ desc: "if this option is given, " \
135
+ "emails that are removed from the server " \
136
+ "will be removed from the local backup.",
137
+ aliases: ["-m"]
138
+ )
139
+ method_option(
140
+ "multi-fetch-size",
141
+ type: :numeric,
142
+ desc: "the number of emails to download at a time",
143
+ default: 1,
144
+ aliases: ["-n"]
145
+ )
146
+ method_option(
147
+ "connection-options",
148
+ type: :string,
149
+ desc: "an optional JSON string with options for the IMAP connection",
150
+ aliases: ["-o"]
151
+ )
152
+ method_option(
153
+ "download-strategy",
154
+ type: :string,
155
+ desc: "the download strategy to adopt. " \
156
+ "For details, see the documentation for this setting " \
157
+ "in the setup program.",
158
+ enum: %w(delay direct),
159
+ default: "delay",
160
+ aliases: ["-S"]
161
+ )
162
+ refresh_option
163
+ method_option(
164
+ "reset-seen-flags-after-fetch",
165
+ type: :boolean,
166
+ desc: "reset 'Seen' flags after backup. " \
167
+ "For details, see the documentation for this setting " \
168
+ "in the setup program.",
169
+ aliases: ["-R"]
170
+ )
171
+ quiet_option
172
+ verbose_option
173
+ def backup
174
+ non_logging_options = Imap::Backup::Logger.setup_logging(options)
175
+ direct = Backup.new(non_logging_options)
176
+ direct.run
177
+ end
178
+ end
179
+ end
@@ -1,6 +1,7 @@
1
1
  require "thor"
2
2
 
3
3
  require "imap/backup/logger"
4
+ require "imap/backup/version"
4
5
 
5
6
  module Imap; end
6
7
 
@@ -13,14 +14,13 @@ module Imap::Backup
13
14
  autoload :Remote, "imap/backup/cli/remote"
14
15
  autoload :Restore, "imap/backup/cli/restore"
15
16
  autoload :Setup, "imap/backup/cli/setup"
17
+ autoload :Single, "imap/backup/cli/single"
16
18
  autoload :Stats, "imap/backup/cli/stats"
17
19
  autoload :Transfer, "imap/backup/cli/transfer"
18
20
  autoload :Utils, "imap/backup/cli/utils"
19
21
 
20
22
  include Helpers
21
23
 
22
- VERSION_ARGUMENTS = %w(-v --version).freeze
23
-
24
24
  NAMESPACE_CONFIGURATION_DESCRIPTION = <<~DESC.freeze
25
25
  Some IMAP servers use namespaces (i.e. prefixes like "INBOX"),
26
26
  while others, while others concatenate the names of subfolders
@@ -44,9 +44,8 @@ module Imap::Backup
44
44
 
45
45
  default_task :backup
46
46
 
47
- def self.start(*args)
48
- version_argument = ARGV & VERSION_ARGUMENTS
49
- if version_argument.any?
47
+ def self.start(args)
48
+ if args.include?("--version")
50
49
  new.version
51
50
  exit 0
52
51
  end
@@ -80,13 +79,8 @@ module Imap::Backup
80
79
  accounts_option
81
80
  config_option
82
81
  quiet_option
82
+ refresh_option
83
83
  verbose_option
84
- method_option(
85
- "refresh",
86
- type: :boolean,
87
- desc: "in 'keep all emails' mode, update flags for messages that are already downloaded",
88
- aliases: ["-r"]
89
- )
90
84
  def backup
91
85
  non_logging_options = Imap::Backup::Logger.setup_logging(options)
92
86
  Backup.new(non_logging_options).run
@@ -171,6 +165,10 @@ module Imap::Backup
171
165
  a new file is created alongside the normal backup files (.imap and .mbox)
172
166
  This file has a '.mirror' extension. This file contains a mapping of
173
167
  the known UIDs on the source account to those on the destination account.
168
+
169
+ Some configuration may be necessary, as follows:
170
+
171
+ #{NAMESPACE_CONFIGURATION_DESCRIPTION}
174
172
  DESC
175
173
  config_option
176
174
  quiet_option
@@ -237,6 +235,9 @@ module Imap::Backup
237
235
  CLI::Setup.new(non_logging_options).run
238
236
  end
239
237
 
238
+ desc "single SUBCOMMAND [OPTIONS]", "Run actions on a single account"
239
+ subcommand "single", Single
240
+
240
241
  desc "stats EMAIL [OPTIONS]", "Print stats for each account folder"
241
242
  long_desc <<~DESC
242
243
  For each account folder, lists three counts of emails:
@@ -11,7 +11,8 @@ module Imap; end
11
11
  module Imap::Backup
12
12
  class Configuration
13
13
  CONFIGURATION_DIRECTORY = File.expand_path("~/.imap-backup")
14
- VERSION = "2.1".freeze
14
+ VERSION_2_1 = "2.1".freeze
15
+ VERSION = "2.2".freeze
15
16
  DEFAULT_STRATEGY = "delay_metadata".freeze
16
17
  DOWNLOAD_STRATEGIES = [
17
18
  {key: "direct", description: "write straight to disk"},
@@ -81,7 +82,7 @@ module Imap::Backup
81
82
  inject_global_attributes(accounts)
82
83
  end
83
84
 
84
- def download_strategy_modified
85
+ def download_strategy_modified?
85
86
  ensure_loaded!
86
87
 
87
88
  @download_strategy_modified
@@ -90,7 +91,7 @@ module Imap::Backup
90
91
  def modified?
91
92
  ensure_loaded!
92
93
 
93
- return true if download_strategy_modified
94
+ return true if download_strategy_modified?
94
95
 
95
96
  accounts.any? { |a| a.modified? || a.marked_for_deletion? }
96
97
  end
@@ -121,12 +122,30 @@ module Imap::Backup
121
122
  else
122
123
  DEFAULT_STRATEGY
123
124
  end
124
- data
125
+ if data[:version] == VERSION_2_1
126
+ dehashify_folders(data)
127
+ else
128
+ data
129
+ end
125
130
  else
126
131
  {accounts: [], download_strategy: DEFAULT_STRATEGY}
127
132
  end
128
133
  end
129
134
 
135
+ def dehashify_folders(data)
136
+ data[:version] = VERSION
137
+
138
+ data[:accounts].each do |account|
139
+ next if !account.key?(:folders)
140
+
141
+ folders = account[:folders]
142
+ names = folders.map { |f| f[:name] }
143
+ account[:folders] = names
144
+ end
145
+
146
+ data
147
+ end
148
+
130
149
  def remove_modified_flags
131
150
  @download_strategy_modified = false
132
151
  accounts.each(&:clear_changes)
@@ -1,3 +1,5 @@
1
+ require "net/imap"
2
+
1
3
  module Imap; end
2
4
 
3
5
  module Imap::Backup
@@ -16,19 +16,20 @@ module Imap::Backup
16
16
  def self.setup_logging(options = {})
17
17
  copy = options.clone
18
18
  quiet = copy.delete(:quiet)
19
- verbose = copy.delete(:verbose)
19
+ verbose = copy.delete(:verbose) || []
20
+ verbose_count = count(verbose)
20
21
  level =
21
22
  case
22
23
  when quiet
23
24
  ::Logger::Severity::UNKNOWN
24
- when verbose
25
+ when verbose_count >= 2
25
26
  ::Logger::Severity::DEBUG
26
27
  else
27
28
  ::Logger::Severity::INFO
28
29
  end
29
30
  logger.level = level
30
- debug = level == ::Logger::Severity::DEBUG
31
- Net::IMAP.debug = debug
31
+
32
+ Net::IMAP.debug = (verbose_count >= 3)
32
33
 
33
34
  copy
34
35
  end
@@ -43,6 +44,10 @@ module Imap::Backup
43
44
  $stderr = previous_stderr
44
45
  end
45
46
 
47
+ def self.count(verbose)
48
+ verbose.reduce(1) { |acc, v| acc + (v ? 1 : -1) }
49
+ end
50
+
46
51
  attr_reader :logger
47
52
 
48
53
  def initialize
@@ -78,7 +78,7 @@ module Imap::Backup
78
78
  list =
79
79
  case
80
80
  when items.any?
81
- items.map { |f| f[:name] }.join(", ")
81
+ items.join(", ")
82
82
  when !account.folder_blacklist
83
83
  "(all folders)"
84
84
  else
@@ -1,3 +1,5 @@
1
+ require "net/imap"
2
+
1
3
  module Imap; end
2
4
 
3
5
  module Imap::Backup
@@ -63,18 +63,18 @@ module Imap::Backup
63
63
  config_folders = account.folders
64
64
  return false if config_folders.nil?
65
65
 
66
- config_folders.find { |f| f[:name] == folder_name }
66
+ config_folders.find { |f| f == folder_name }
67
67
  end
68
68
 
69
69
  def remove_missing
70
70
  removed = []
71
71
  config_folders = []
72
72
  account.folders.each do |f|
73
- found = folder_names.find { |folder| folder == f[:name] }
73
+ found = folder_names.find { |folder| folder == f }
74
74
  if found
75
75
  config_folders << f
76
76
  else
77
- removed << f[:name]
77
+ removed << f
78
78
  end
79
79
  end
80
80
 
@@ -91,11 +91,11 @@ module Imap::Backup
91
91
 
92
92
  def toggle_selection(folder_name)
93
93
  if selected?(folder_name)
94
- new_list = account.folders.reject { |f| f[:name] == folder_name }
94
+ new_list = account.folders.reject { |f| f == folder_name }
95
95
  account.folders = new_list
96
96
  else
97
97
  existing = account.folders || []
98
- account.folders = existing + [{name: folder_name}]
98
+ account.folders = existing << folder_name
99
99
  end
100
100
  end
101
101
 
@@ -42,7 +42,7 @@ module Imap::Backup
42
42
  def change_download_strategy(menu)
43
43
  strategies = Imap::Backup::Configuration::DOWNLOAD_STRATEGIES
44
44
  current = strategies.find { |s| s[:key] == config.download_strategy }
45
- changed = config.download_strategy_modified ? " *" : ""
45
+ changed = config.download_strategy_modified? ? " *" : ""
46
46
  menu.choice("change download strategy (currently: '#{current[:description]}')#{changed}") do
47
47
  DownloadStrategyChooser.new(config: config).run
48
48
  end
@@ -76,7 +76,7 @@ module Imap::Backup
76
76
  end
77
77
 
78
78
  def modify_global_options(menu)
79
- changed = config.modified? ? " *" : ""
79
+ changed = config.download_strategy_modified? ? " *" : ""
80
80
  menu.choice("modify global options#{changed}") do
81
81
  GlobalOptions.new(config: config).run
82
82
  end
@@ -1,8 +1,8 @@
1
1
  module Imap; end
2
2
 
3
3
  module Imap::Backup
4
- MAJOR = 13
5
- MINOR = 3
4
+ MAJOR = 14
5
+ MINOR = 0
6
6
  REVISION = 0
7
7
  PRE = nil
8
8
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
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: 13.3.0
4
+ version: 14.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: 2023-10-06 00:00:00.000000000 Z
11
+ date: 2023-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -136,6 +136,7 @@ files:
136
136
  - docs/delimiters-and-prefixes.md
137
137
  - docs/development.md
138
138
  - docs/migrate-server-keep-address.md
139
+ - docs/performance.md
139
140
  - imap-backup.gemspec
140
141
  - lib/cli_coverage.rb
141
142
  - lib/email/mboxrd/message.rb
@@ -165,6 +166,8 @@ files:
165
166
  - lib/imap/backup/cli/remote.rb
166
167
  - lib/imap/backup/cli/restore.rb
167
168
  - lib/imap/backup/cli/setup.rb
169
+ - lib/imap/backup/cli/single.rb
170
+ - lib/imap/backup/cli/single/backup.rb
168
171
  - lib/imap/backup/cli/stats.rb
169
172
  - lib/imap/backup/cli/transfer.rb
170
173
  - lib/imap/backup/cli/utils.rb