imap-backup 6.0.1 → 6.1.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: 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