imap-backup 6.0.1 → 6.1.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: 6d4b9386097e77af34706992fb42b6ff9edd3a62a3cfb98e0b3363d864ee75dc
4
- data.tar.gz: d72ec8493ea2436ccb033f2957d44236b71d3c607e25b15b0b755e93f011da10
3
+ metadata.gz: 2a05fedc57275cadd2a1bd22efbe88ef5ec6ee72843400499cac8b8df8e80c35
4
+ data.tar.gz: f0e8e859b5fb1bb8480730b3ea62abc7470f5d0ddb469113bbdac0e4f2a88d1a
5
5
  SHA512:
6
- metadata.gz: ab38ba548fa009c6f718f8da9b9fbb971751297eac03d2839e14f247c06d6858a155350291619090ef8165ffeb865674a19ef51c13dbf1220928106abfc79fa5
7
- data.tar.gz: ad4e17a2b6b0b5a9ee1cc4737c324981069525a129018278514e51e8664008c71fc610539df58a2da590084bb7e3c46cd635fe1409ba0b53ad56bd241edfb9c8
6
+ metadata.gz: bfb4de1a3048a9f29ee30fb56c7493524a7d688a6eab2ec3307005ba3c8d341a99c961acd1d8f588384dc91571b4528482931e52724df42dff19a26553568fb0
7
+ data.tar.gz: a411ad26b4ce3eb631c97d94c9242c1eb48fd57d23cffcec84f5c829d9354b506115b6c8278ee38430e27f0d6ca1400d55f74adce5eb99f94db2963f235c6d7a
@@ -4,4 +4,8 @@ class Email::Provider::AppleMail < Email::Provider::Base
4
4
  def host
5
5
  "imap.mail.me.com"
6
6
  end
7
+
8
+ def sets_seen_flags_on_fetch?
9
+ true
10
+ end
7
11
  end
@@ -7,4 +7,8 @@ class Email::Provider::Base
7
7
  {port: 993, ssl: {ssl_version: :TLSv1_2}}
8
8
  # rubocop:enable Naming/VariableNumber
9
9
  end
10
+
11
+ def sets_seen_flags_on_fetch?
12
+ false
13
+ end
10
14
  end
@@ -0,0 +1,11 @@
1
+ require "email/provider/base"
2
+
3
+ class Email::Provider::Purelymail < Email::Provider::Base
4
+ def host
5
+ "mailserver.purelymail.com"
6
+ end
7
+
8
+ def sets_seen_flags_on_fetch?
9
+ true
10
+ end
11
+ end
@@ -1,6 +1,7 @@
1
1
  require "email/provider/apple_mail"
2
2
  require "email/provider/fastmail"
3
3
  require "email/provider/gmail"
4
+ require "email/provider/purelymail"
4
5
  require "email/provider/unknown"
5
6
 
6
7
  module Email; end
@@ -21,6 +22,8 @@ class Email::Provider
21
22
  Email::Provider::AppleMail.new
22
23
  when address.end_with?("@me.com")
23
24
  Email::Provider::AppleMail.new
25
+ when address.end_with?("@purelymail.com")
26
+ Email::Provider::Purelymail.new
24
27
  else
25
28
  Email::Provider::Unknown.new
26
29
  end
@@ -1,3 +1,4 @@
1
+ require "email/provider"
1
2
  require "retry_on_error"
2
3
 
3
4
  module Imap::Backup
@@ -1,4 +1,3 @@
1
- require "email/provider"
2
1
  require "imap/backup/client/apple_mail"
3
2
  require "imap/backup/client/default"
4
3
  require "imap/backup/account/connection/backup_folders"
@@ -46,7 +45,10 @@ module Imap::Backup
46
45
  serializer.apply_uid_validity(folder.uid_validity)
47
46
  begin
48
47
  Downloader.new(
49
- folder, serializer, multi_fetch_size: account.multi_fetch_size
48
+ folder,
49
+ serializer,
50
+ multi_fetch_size: account.multi_fetch_size,
51
+ reset_seen_flags_after_fetch: account.reset_seen_flags_after_fetch
50
52
  ).run
51
53
  rescue Net::IMAP::ByeResponseError
52
54
  reconnect
@@ -105,6 +107,8 @@ module Imap::Backup
105
107
  end
106
108
 
107
109
  def ensure_account_folder
110
+ raise "The backup path for #{account.username} is not set" if !account.local_path
111
+
108
112
  Utils.make_folder(
109
113
  File.dirname(account.local_path),
110
114
  File.basename(account.local_path),
@@ -98,14 +98,33 @@ module Imap::Backup
98
98
  end
99
99
  end
100
100
 
101
- def clear
102
- existing = uids
101
+ def set_flags(uids, flags)
103
102
  # Use read-write access, via `select`
104
103
  client.select(utf7_encoded_name)
105
- client.uid_store(existing, "+FLAGS", [:Deleted])
104
+ client.uid_store(uids, "+FLAGS", flags)
105
+ end
106
+
107
+ def unset_flags(uids, flags)
108
+ client.select(utf7_encoded_name)
109
+ client.uid_store(uids, "-FLAGS", flags)
110
+ end
111
+
112
+ def clear
113
+ set_flags(uids, [:Deleted])
106
114
  client.expunge
107
115
  end
108
116
 
117
+ def unseen(uids)
118
+ messages = uids.map(&:to_s).join(",")
119
+ examine
120
+ client.uid_search([messages, "UNSEEN"])
121
+ rescue NoMethodError
122
+ # Apple Mail returns an empty response when searches have no results
123
+ []
124
+ rescue FolderNotFound
125
+ nil
126
+ end
127
+
109
128
  private
110
129
 
111
130
  def examine
@@ -8,6 +8,7 @@ module Imap::Backup
8
8
  attr_reader :folders
9
9
  attr_reader :server
10
10
  attr_reader :connection_options
11
+ attr_reader :reset_seen_flags_after_fetch
11
12
  attr_reader :changes
12
13
 
13
14
  def initialize(options)
@@ -18,6 +19,7 @@ module Imap::Backup
18
19
  @server = options[:server]
19
20
  @connection_options = options[:connection_options]
20
21
  @multi_fetch_size = options[:multi_fetch_size]
22
+ @reset_seen_flags_after_fetch = options[:reset_seen_flags_after_fetch]
21
23
  @connection = nil
22
24
  @changes = {}
23
25
  @marked_for_deletion = false
@@ -54,6 +56,9 @@ module Imap::Backup
54
56
  h[:server] = @server if @server
55
57
  h[:connection_options] = @connection_options if @connection_options
56
58
  h[:multi_fetch_size] = multi_fetch_size if @multi_fetch_size
59
+ if @reset_seen_flags_after_fetch
60
+ h[:reset_seen_flags_after_fetch] = @reset_seen_flags_after_fetch
61
+ end
57
62
  h
58
63
  end
59
64
 
@@ -99,6 +104,10 @@ module Imap::Backup
99
104
  update(:multi_fetch_size, parsed)
100
105
  end
101
106
 
107
+ def reset_seen_flags_after_fetch=(value)
108
+ update(:reset_seen_flags_after_fetch, value)
109
+ end
110
+
102
111
  private
103
112
 
104
113
  def update(field, value)
@@ -5,11 +5,21 @@ module Imap::Backup
5
5
  attr_reader :folder
6
6
  attr_reader :serializer
7
7
  attr_reader :multi_fetch_size
8
+ # Some IMAP providers, notably Apple Mail, set the '\Seen' flag
9
+ # on emails when they are fetched. By setting `:reset_seen_flags_after_fetch`,
10
+ # a workaround is activated which checks which emails are 'unseen' before
11
+ # and after the fetch, and removes the '\Seen' flag from those which have changed.
12
+ # As this check is susceptible to 'race conditions', i.e. when a different
13
+ # client sets the '\Seen' flag while imap-backup is fetching, it is best
14
+ # to only use it when required (i.e. for IMAP providers which always
15
+ # mark messages as '\Seen' when accessed).
16
+ attr_reader :reset_seen_flags_after_fetch
8
17
 
9
- def initialize(folder, serializer, multi_fetch_size: 1)
18
+ def initialize(folder, serializer, multi_fetch_size: 1, reset_seen_flags_after_fetch: false)
10
19
  @folder = folder
11
20
  @serializer = serializer
12
21
  @multi_fetch_size = multi_fetch_size
22
+ @reset_seen_flags_after_fetch = reset_seen_flags_after_fetch
13
23
  @uids = nil
14
24
  end
15
25
 
@@ -30,7 +40,23 @@ module Imap::Backup
30
40
  private
31
41
 
32
42
  def download_block(block, index)
33
- uids_and_bodies = folder.fetch_multi(block)
43
+ uids_and_bodies =
44
+ if reset_seen_flags_after_fetch
45
+ before_unseen = folder.unseen(block)
46
+ debug "Pre-fetch unseen messages: #{before_unseen.join(', ')}"
47
+ uids_and_bodies = folder.fetch_multi(block)
48
+ after_unseen = folder.unseen(block)
49
+ debug "Post-fetch unseen messages: #{after_unseen.join(', ')}"
50
+ changed = before_unseen - after_unseen
51
+ if changed.any?
52
+ ids = changed.join(", ")
53
+ debug "Removing '\Seen' flag for the following messages: #{ids}"
54
+ folder.unset_flags(changed, [:Seen])
55
+ end
56
+ uids_and_bodies
57
+ else
58
+ folder.fetch_multi(block)
59
+ end
34
60
  if uids_and_bodies.nil?
35
61
  if multi_fetch_size > 1
36
62
  uids = block.join(", ")
@@ -21,7 +21,7 @@ module Imap::Backup
21
21
  password#{space}#{masked_password}
22
22
  path #{space}#{local_path}
23
23
  folders #{space}#{folders.map { |f| f[:name] }.join(', ')}#{multi_fetch_size}
24
- server #{space}#{account.server}#{connection_options}
24
+ server #{space}#{account.server}#{connection_options}#{reset_seen_flags_after_fetch}
25
25
 
26
26
  Choose an action
27
27
  HEADER
@@ -53,6 +53,12 @@ module Imap::Backup
53
53
  "\nconnection options '#{escaped}'"
54
54
  end
55
55
 
56
+ def reset_seen_flags_after_fetch
57
+ return nil if !account.reset_seen_flags_after_fetch
58
+
59
+ "\nchanges to unread flags will be reset during download"
60
+ end
61
+
56
62
  def space
57
63
  account.connection_options ? " " * 12 : " " * 4
58
64
  end
@@ -37,6 +37,7 @@ module Imap::Backup
37
37
  modify_multi_fetch_size menu
38
38
  modify_server menu
39
39
  modify_connection_options menu
40
+ toggle_reset_seen_flags_after_fetch menu
40
41
  test_connection menu
41
42
  delete_account menu
42
43
  menu.choice("(q) return to main menu") { throw :done }
@@ -103,6 +104,19 @@ module Imap::Backup
103
104
  end
104
105
  end
105
106
 
107
+ def toggle_reset_seen_flags_after_fetch(menu)
108
+ menu_item =
109
+ if account.reset_seen_flags_after_fetch
110
+ "don't fix changes to unread flags during download"
111
+ else
112
+ "fix changes to unread flags during download"
113
+ end
114
+ new_value = account.reset_seen_flags_after_fetch ? nil : true
115
+ menu.choice(menu_item) do
116
+ account.reset_seen_flags_after_fetch = new_value
117
+ end
118
+ end
119
+
106
120
  def test_connection(menu)
107
121
  menu.choice("test connection") do
108
122
  result = Setup::ConnectionTester.new(account).test
@@ -12,13 +12,17 @@ module Imap::Backup
12
12
 
13
13
  def run
14
14
  account.local_path = highline.ask("backup directory: ") do |q|
15
- q.default = account.local_path
15
+ q.default = account.local_path || default
16
16
  q.readline = true
17
17
  q.validate = ->(path) { path_modification_validator(path) }
18
18
  q.responses[:not_valid] = "Choose a different directory "
19
19
  end
20
20
  end
21
21
 
22
+ def default
23
+ File.join(config.path, account.username.tr("@", "_"))
24
+ end
25
+
22
26
  private
23
27
 
24
28
  def highline
@@ -80,11 +80,11 @@ module Imap::Backup
80
80
  Imap::Backup::Account.new(
81
81
  username: username,
82
82
  password: "",
83
- local_path: File.join(config.path, username.tr("@", "_")),
84
83
  folders: []
85
84
  ).tap do |a|
86
- server = ::Email::Provider.for_address(username)
87
- a.server = server.host if server.host
85
+ provider = ::Email::Provider.for_address(username)
86
+ a.server = provider.host if provider.host
87
+ a.reset_seen_flags_after_fetch = true if provider.sets_seen_flags_on_fetch?
88
88
  end
89
89
  end
90
90
 
@@ -2,8 +2,8 @@ module Imap; end
2
2
 
3
3
  module Imap::Backup
4
4
  MAJOR = 6
5
- MINOR = 0
6
- REVISION = 1
5
+ MINOR = 1
6
+ REVISION = 0
7
7
  PRE = nil
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: 6.0.1
4
+ version: 6.1.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-07-09 00:00:00.000000000 Z
11
+ date: 2022-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -200,6 +200,7 @@ files:
200
200
  - lib/email/provider/base.rb
201
201
  - lib/email/provider/fastmail.rb
202
202
  - lib/email/provider/gmail.rb
203
+ - lib/email/provider/purelymail.rb
203
204
  - lib/email/provider/unknown.rb
204
205
  - lib/imap/backup.rb
205
206
  - lib/imap/backup/account.rb