imap-backup 13.3.0 → 14.0.0

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: 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