imap-backup 4.0.1 → 4.0.2

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: 97ae9089aa19dee6fcf7a1c211091671a6befe882d5b664654a25d6d10e81704
4
- data.tar.gz: 2677746ed662c457a738033224da11a40a76367e4031e7636b33391d51ee4659
3
+ metadata.gz: 14c0bd789a2532461bbfbe92dd3bb2466228fe0d14b676c308f766a5c62941cf
4
+ data.tar.gz: 06203ea6c126b17b26f784f2f91f3dd72d1bfd431fe100428931d624de85c53e
5
5
  SHA512:
6
- metadata.gz: 5e97ba8ab64e27924166609c1cb80e52dbfa8136b4248324a7589e143d64ebddbb415ad6c4c7a0e472732db55c717d14e08be47c7640efcb36029c80b10938ef
7
- data.tar.gz: 2fd74f2b86946b79d07a1354a55b00444022838fcd4968a6a2f0dc15494f4c2d80d0ea06b1e0290b921e7f2242566a56fb8e9bd6bf03b5a88595871c91d08604
6
+ metadata.gz: a4de838badaaf992ae6ff46d590bb782810f4dae83aeeb9bd1495525bddfc396341374e18c51bec2e71a1cf66135335f2e21bdd6632ced9f64bc81f93f4eafe1
7
+ data.tar.gz: 20cc1c12289c753a3814205fd7cf125a2b234b567301a37f88da65b7f86b26a66274209fc8e2218975877f6568df3d991f1db03f76fe4262c8415adc3a59d6e6
data/docs/development.md CHANGED
@@ -2,18 +2,12 @@
2
2
 
3
3
  ## Integration Tests
4
4
 
5
- Integration tests (feature specs) are run against a Docker image
6
- (antespi/docker-imap-devel:latest).
7
-
8
- In one shell, run the Docker image:
5
+ Integration tests (feature specs) are run against a local IMAP server
6
+ controlled by Docker Compose, which needs to be started
7
+ before running the test suite.
9
8
 
10
9
  ```sh
11
- $ docker run \
12
- --env MAIL_ADDRESS=address@example.org \
13
- --env MAIL_PASS=pass \
14
- --env MAILNAME=example.org \
15
- --publish 8993:993 \
16
- antespi/docker-imap-devel:latest
10
+ $ docker-compose up -d
17
11
  ```
18
12
 
19
13
  ```sh
data/imap-backup.gemspec CHANGED
@@ -17,10 +17,6 @@ Gem::Specification.new do |gem|
17
17
  gem.files += %w[imap-backup.gemspec]
18
18
  gem.files += %w[LICENSE README.md]
19
19
 
20
- gem.extra_rdoc_files += Dir.glob("README.md")
21
- gem.extra_rdoc_files += Dir.glob("docs/*.md")
22
- gem.rdoc_options = ["--main", "README.md"]
23
-
24
20
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
25
21
  gem.test_files = Dir.glob("spec/**/*{.rb,.yml}")
26
22
  gem.require_paths = ["lib"]
@@ -1,9 +1,13 @@
1
+ require "forwardable"
1
2
  require "mail"
2
3
 
3
4
  module Email; end
4
5
 
5
6
  module Email::Mboxrd
6
7
  class Message
8
+ extend Forwardable
9
+ def_delegators :@parsed, :subject
10
+
7
11
  attr_reader :supplied_body
8
12
 
9
13
  def self.from_serialized(serialized)
@@ -36,12 +40,12 @@ module Email::Mboxrd
36
40
  supplied_body.gsub(/(?<!\r)\n/, "\r\n")
37
41
  end
38
42
 
43
+ private
44
+
39
45
  def parsed
40
46
  @parsed ||= Mail.new(supplied_body)
41
47
  end
42
48
 
43
- private
44
-
45
49
  def from
46
50
  @from ||=
47
51
  begin
@@ -5,6 +5,19 @@ module Imap::Backup::CLI::Helpers
5
5
  options.each.with_object({}) { |(k, v), acc| acc[k.intern] = v }
6
6
  end
7
7
 
8
+ def account(email)
9
+ connections = Imap::Backup::Configuration::List.new
10
+ account = connections.accounts.find { |a| a[:username] == email }
11
+ raise "#{email} is not a configured account" if !account
12
+ account
13
+ end
14
+
15
+ def connection(email)
16
+ account = account(email)
17
+
18
+ Imap::Backup::Account::Connection.new(account)
19
+ end
20
+
8
21
  def each_connection(names)
9
22
  begin
10
23
  connections = Imap::Backup::Configuration::List.new(names)
@@ -6,49 +6,43 @@ module Imap::Backup
6
6
  desc "accounts", "List locally backed-up accounts"
7
7
  def accounts
8
8
  connections = Imap::Backup::Configuration::List.new
9
- connections.accounts.each { |a| puts a[:username] }
9
+ connections.accounts.each { |a| Kernel.puts a[:username] }
10
10
  end
11
11
 
12
12
  desc "folders EMAIL", "List account folders"
13
13
  def folders(email)
14
- connections = Imap::Backup::Configuration::List.new
15
- account = connections.accounts.find { |a| a[:username] == email }
16
- raise "#{email} is not a configured account" if !account
14
+ connection = connection(email)
17
15
 
18
- account_connection = Imap::Backup::Account::Connection.new(account)
19
- account_connection.local_folders.each do |_s, f|
20
- puts %("#{f.name}")
16
+ connection.local_folders.each do |_s, f|
17
+ Kernel.puts %("#{f.name}")
21
18
  end
22
19
  end
23
20
 
24
21
  desc "list EMAIL FOLDER", "List emails in a folder"
25
22
  def list(email, folder_name)
26
- connections = Imap::Backup::Configuration::List.new
27
- account = connections.accounts.find { |a| a[:username] == email }
28
- raise "#{email} is not a configured account" if !account
23
+ connection = connection(email)
29
24
 
30
- account_connection = Imap::Backup::Account::Connection.new(account)
31
- folder_serializer, _folder = account_connection.local_folders.find do |(_s, f)|
25
+ folder_serializer, _folder = connection.local_folders.find do |(_s, f)|
32
26
  f.name == folder_name
33
27
  end
34
28
  raise "Folder '#{folder_name}' not found" if !folder_serializer
35
29
 
36
30
  max_subject = 60
37
- puts format("%-10<uid>s %-#{max_subject}<subject>s - %<date>s", {uid: "UID", subject: "Subject", date: "Date"})
38
- puts "-" * (12 + max_subject + 28)
31
+ Kernel.puts format("%-10<uid>s %-#{max_subject}<subject>s - %<date>s", {uid: "UID", subject: "Subject", date: "Date"})
32
+ Kernel.puts "-" * (12 + max_subject + 28)
39
33
 
40
34
  uids = folder_serializer.uids
41
35
 
42
36
  folder_serializer.each_message(uids).map do |uid, message|
43
37
  m = {
44
38
  uid: uid,
45
- date: message.parsed.date.to_s,
46
- subject: message.parsed.subject || ""
39
+ date: message.date.to_s,
40
+ subject: message.subject || ""
47
41
  }
48
42
  if m[:subject].length > max_subject
49
- puts format("% 10<uid>u: %.#{max_subject - 3}<subject>s... - %<date>s", m)
43
+ Kernel.puts format("% 10<uid>u: %.#{max_subject - 3}<subject>s... - %<date>s", m)
50
44
  else
51
- puts format("% 10<uid>u: %-#{max_subject}<subject>s - %<date>s", m)
45
+ Kernel.puts format("% 10<uid>u: %-#{max_subject}<subject>s - %<date>s", m)
52
46
  end
53
47
  end
54
48
  end
@@ -60,12 +54,9 @@ module Imap::Backup
60
54
  the UID.
61
55
  DESC
62
56
  def show(email, folder_name, uids)
63
- connections = Imap::Backup::Configuration::List.new
64
- account = connections.accounts.find { |a| a[:username] == email }
65
- raise "#{email} is not a configured account" if !account
57
+ connection = connection(email)
66
58
 
67
- account_connection = Imap::Backup::Account::Connection.new(account)
68
- folder_serializer, _folder = account_connection.local_folders.find do |(_s, f)|
59
+ folder_serializer, _folder = connection.local_folders.find do |(_s, f)|
69
60
  f.name == folder_name
70
61
  end
71
62
  raise "Folder '#{folder_name}' not found" if !folder_serializer
@@ -73,13 +64,13 @@ module Imap::Backup
73
64
  uid_list = uids.split(",")
74
65
  folder_serializer.each_message(uid_list).each do |uid, message|
75
66
  if uid_list.count > 1
76
- puts <<~HEADER
67
+ Kernel.puts <<~HEADER
77
68
  #{'-' * 80}
78
69
  #{format('| UID: %-71s |', uid)}
79
70
  #{'-' * 80}
80
71
  HEADER
81
72
  end
82
- puts message.supplied_body
73
+ Kernel.puts message.supplied_body
83
74
  end
84
75
  end
85
76
  end
@@ -1,26 +1,64 @@
1
+ require "imap/backup/thunderbird/mailbox_exporter"
2
+
1
3
  module Imap::Backup
2
4
  class CLI::Utils < Thor
3
5
  include Thor::Actions
6
+ include CLI::Helpers
4
7
 
5
8
  FAKE_EMAIL = "fake@email.com"
6
9
 
7
10
  desc "ignore-history EMAIL", "Skip downloading emails up to today for all configured folders"
8
11
  def ignore_history(email)
9
- connections = Imap::Backup::Configuration::List.new
10
- account = connections.accounts.find { |a| a[:username] == email }
11
- raise "#{email} is not a configured account" if !account
12
-
13
- connection = Imap::Backup::Account::Connection.new(account)
12
+ connection = connection(email)
14
13
 
15
- connection.local_folders.each do |_s, f|
14
+ connection.local_folders.each do |serializer, folder|
16
15
  next if !folder.exist?
17
- do_ignore_folder_history(connection, folder)
16
+ do_ignore_folder_history(folder, serializer)
17
+ end
18
+ end
19
+
20
+ desc "export-to-thunderbird EMAIL [OPTIONS]",
21
+ <<~DOC
22
+ [Experimental] Copy backed up emails to Thunderbird.
23
+ A folder called 'imap-backup/EMAIL' is created under 'Local Folders'.
24
+ DOC
25
+ method_option(
26
+ "force",
27
+ type: :boolean,
28
+ banner: "overwrite existing mailboxes",
29
+ aliases: ["-f"]
30
+ )
31
+ method_option(
32
+ "profile",
33
+ type: :string,
34
+ banner: "the name of the Thunderbird profile to copy emails to",
35
+ aliases: ["-p"]
36
+ )
37
+ def export_to_thunderbird(email)
38
+ opts = symbolized(options)
39
+ force = opts.key?(:force) ? opts[:force] : false
40
+ profile_name = opts[:profile]
41
+
42
+ connection = connection(email)
43
+ profile = thunderbird_profile(profile_name)
44
+
45
+ if !profile
46
+ if profile_name
47
+ raise "Thunderbird profile '#{profile_name}' not found"
48
+ else
49
+ raise "Default Thunderbird profile not found"
50
+ end
51
+ end
52
+
53
+ connection.local_folders.each do |serializer, folder|
54
+ Thunderbird::MailboxExporter.new(
55
+ email, serializer, profile, force: force
56
+ ).run
18
57
  end
19
58
  end
20
59
 
21
60
  no_commands do
22
- def do_ignore_folder_history(connection, folder)
23
- serializer = Imap::Backup::Serializer::Mbox.new(connection.local_path, folder.name)
61
+ def do_ignore_folder_history(folder, serializer)
24
62
  uids = folder.uids - serializer.uids
25
63
  Imap::Backup.logger.info "Folder '#{folder.name}' - #{uids.length} messages"
26
64
 
@@ -36,6 +74,14 @@ module Imap::Backup
36
74
  serializer.save(uid, message)
37
75
  end
38
76
  end
77
+
78
+ def thunderbird_profile(name = nil)
79
+ if name
80
+ Thunderbird::Profiles.new.profile(name)
81
+ else
82
+ Thunderbird::Profiles.new.default
83
+ end
84
+ end
39
85
  end
40
86
  end
41
87
  end
@@ -1,7 +1,12 @@
1
+ require "forwardable"
2
+
1
3
  require "imap/backup/serializer/mbox_store"
2
4
 
3
5
  module Imap::Backup
4
6
  class Serializer::Mbox
7
+ extend Forwardable
8
+ def_delegators :store, :mbox_pathname
9
+
5
10
  attr_reader :path
6
11
  attr_reader :folder
7
12
 
@@ -121,6 +121,14 @@ module Imap::Backup
121
121
  @folder = new_name
122
122
  end
123
123
 
124
+ def mbox_pathname
125
+ absolute_path("#{folder}.mbox")
126
+ end
127
+
128
+ def imap_pathname
129
+ absolute_path("#{folder}.imap")
130
+ end
131
+
124
132
  private
125
133
 
126
134
  def do_load
@@ -205,13 +213,5 @@ module Imap::Backup
205
213
  def absolute_path(relative_path)
206
214
  File.join(path, relative_path)
207
215
  end
208
-
209
- def mbox_pathname
210
- absolute_path("#{folder}.mbox")
211
- end
212
-
213
- def imap_pathname
214
- absolute_path("#{folder}.imap")
215
- end
216
216
  end
217
217
  end
@@ -0,0 +1,71 @@
1
+ require "thunderbird/local_folder"
2
+ require "thunderbird/mailbox"
3
+ require "thunderbird/profiles"
4
+
5
+ module Imap::Backup
6
+ class Thunderbird::MailboxExporter
7
+ EXPORT_PREFIX = "imap-backup"
8
+
9
+ attr_reader :email
10
+ attr_reader :serializer
11
+ attr_reader :profile
12
+ attr_reader :force
13
+
14
+ def initialize(email, serializer, profile, force: false)
15
+ @email = email
16
+ @serializer = serializer
17
+ @profile = profile
18
+ @force = force
19
+ end
20
+
21
+ def run
22
+ local_folder.set_up
23
+
24
+ if mailbox.msf_exists?
25
+ if force
26
+ Kernel.puts "Deleting '#{mailbox.msf_path}' as --force option was supplied"
27
+ File.unlink mailbox.msf_path
28
+ else
29
+ Kernel.puts "Skipping export of '#{folder.name}' as '#{mailbox.msf_path}' exists"
30
+ return false
31
+ end
32
+ end
33
+
34
+ if mailbox.exists?
35
+ if force
36
+ Kernel.puts "Overwriting '#{mailbox.path}' as --force option was supplied"
37
+ else
38
+ Kernel.puts "Skipping export of '#{folder.name}' as '#{mailbox.path}' exists"
39
+ return false
40
+ end
41
+ end
42
+
43
+ FileUtils.cp serializer.mbox_pathname, mailbox.path
44
+
45
+ true
46
+ end
47
+
48
+ private
49
+
50
+ def local_folder
51
+ @local_folder ||= begin
52
+ folder_path = File.dirname(serializer.folder)
53
+ top_level_folders = [EXPORT_PREFIX, email]
54
+ prefixed_folder_path =
55
+ if folder_path == "."
56
+ File.join(top_level_folders)
57
+ else
58
+ File.join(top_level_folders, folder_path)
59
+ end
60
+ Thunderbird::LocalFolder.new(profile, prefixed_folder_path)
61
+ end
62
+ end
63
+
64
+ def mailbox
65
+ @mailbox ||= begin
66
+ mailbox_name = File.basename(serializer.folder)
67
+ Thunderbird::Mailbox.new(local_folder, mailbox_name)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -3,7 +3,7 @@ module Imap; end
3
3
  module Imap::Backup
4
4
  MAJOR = 4
5
5
  MINOR = 0
6
- REVISION = 1
6
+ REVISION = 2
7
7
  PRE = nil
8
8
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
9
9
  end
@@ -0,0 +1,97 @@
1
+ require "thunderbird/profile"
2
+ require "thunderbird/local_folder_placeholder"
3
+
4
+ class Thunderbird::LocalFolder
5
+ attr_reader :folder_path
6
+ attr_reader :profile
7
+
8
+ def initialize(profile, folder_path)
9
+ @profile = profile
10
+ @folder_path = folder_path
11
+ end
12
+
13
+ def local_folder_placeholder
14
+ if parent
15
+ path = File.join(parent.full_path, folder_path_elements[-1])
16
+ Thunderbird::LocalFolderPlaceholder.new(path)
17
+ end
18
+ end
19
+
20
+ def directory_is_directory?
21
+ File.directory?(full_path)
22
+ end
23
+
24
+ def full_path
25
+ File.join(profile.local_folders_path, relative_path)
26
+ end
27
+
28
+ def parent
29
+ if folder_path_elements.count > 0
30
+ self.class.new(profile, File.join(folder_path_elements[0..-2]))
31
+ end
32
+ end
33
+
34
+ def folder_path_elements
35
+ folder_path.split(File::SEPARATOR)
36
+ end
37
+
38
+ def directory_exists?
39
+ File.exists?(full_path)
40
+ end
41
+
42
+ def is_directory?
43
+ File.directory?(full_path)
44
+ end
45
+
46
+ def subdirectories
47
+ folder_path_elements.map { |p| "#{p}.sbd" }
48
+ end
49
+
50
+ def relative_path
51
+ File.join(subdirectories)
52
+ end
53
+
54
+ def set_up
55
+ ok = check
56
+ return if !ok
57
+
58
+ ensure_initialized
59
+ end
60
+
61
+ private
62
+
63
+ def ensure_initialized
64
+ return true if !parent
65
+
66
+ parent.ensure_initialized
67
+
68
+ local_folder_placeholder.ensure_initialized
69
+
70
+ FileUtils.mkdir_p full_path
71
+ end
72
+
73
+ def check
74
+ return true if !parent
75
+
76
+ parent_ok = parent.check
77
+
78
+ return if !parent_ok
79
+
80
+ case
81
+ when local_folder_placeholder.exists? && !directory_exists?
82
+ Kernel.puts "Can't set up folder '#{folder_path}': '#{local_folder_placeholder.path}' exists, but '#{full_path}' is missing"
83
+ false
84
+ when directory_exists? && !local_folder_placeholder.exists?
85
+ Kernel.puts "Can't set up folder '#{folder_path}': '#{full_path}' exists, but '#{local_folder_placeholder.path}' is missing"
86
+ false
87
+ when local_folder_placeholder.exists? && !local_folder_placeholder.is_regular?
88
+ Kernel.puts "Can't set up folder '#{folder_path}': '#{local_folder_placeholder.path}' exists, but it is not a regular file"
89
+ false
90
+ when directory_exists? && !is_directory?
91
+ Kernel.puts "Can't set up folder '#{folder_path}': '#{full_path}' exists, but it is not a directory"
92
+ false
93
+ else
94
+ true
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,21 @@
1
+ # Each subdirectory is "accompanied" by a blank
2
+ # file of the same name (without the '.sbd' extension)
3
+ class Thunderbird::LocalFolderPlaceholder
4
+ attr_reader :path
5
+
6
+ def initialize(path)
7
+ @path = path
8
+ end
9
+
10
+ def exists?
11
+ File.exists?(path)
12
+ end
13
+
14
+ def is_regular?
15
+ File.file?(path)
16
+ end
17
+
18
+ def ensure_initialized
19
+ FileUtils.touch path
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ class Thunderbird::Mailbox
2
+ attr_reader :local_folder
3
+ attr_reader :name
4
+
5
+ def initialize(local_folder, name)
6
+ @local_folder = local_folder
7
+ @name = name
8
+ end
9
+
10
+ def path
11
+ File.join(local_folder.full_path, name)
12
+ end
13
+
14
+ def exists?
15
+ File.exists?(path)
16
+ end
17
+
18
+ def msf_path
19
+ path + ".msf"
20
+ end
21
+
22
+ def msf_exists?
23
+ File.exists?(msf_path)
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ require "thunderbird"
2
+
3
+ class Thunderbird::Profile
4
+ attr_reader :title
5
+ attr_reader :entries
6
+
7
+ # entries are lines from profile.ini
8
+ def initialize(title, entries)
9
+ @title = title
10
+ @entries = entries
11
+ end
12
+
13
+ def root
14
+ if relative?
15
+ File.join(Thunderbird.new.data_path, entries[:Path])
16
+ else
17
+ entries[:Path]
18
+ end
19
+ end
20
+
21
+ def local_folders_path
22
+ File.join(root, "Mail", "Local Folders")
23
+ end
24
+
25
+ private
26
+
27
+ def relative?
28
+ entries[:IsRelative] == "1"
29
+ end
30
+ end
@@ -0,0 +1,63 @@
1
+ require "thunderbird"
2
+ require "thunderbird/profile"
3
+
4
+ # http://kb.mozillazine.org/Profiles.ini_file
5
+ class Thunderbird::Profiles
6
+ def default
7
+ title, entries = blocks.find { |_name, entries| entries[:Default] == "1" }
8
+
9
+ Thunderbird::Profile.new(title, entries) if title
10
+ end
11
+
12
+ def profile(name)
13
+ title, entries = blocks.find { |_name, entries| entries[:Name] == name }
14
+
15
+ return nil if !title
16
+
17
+ Thunderbird::Profile.new(title, entries) if title
18
+ end
19
+
20
+ private
21
+
22
+ # Parse profiles.ini.
23
+ # Blocks start with a title, e.g. '[Abc]'
24
+ # and are followed by a number of lines
25
+ def blocks
26
+ @blocks ||= begin
27
+ blocks = {}
28
+ File.open(profiles_ini_path, "rb") do |f|
29
+ title = nil
30
+ entries = nil
31
+
32
+ loop do
33
+ line = f.gets
34
+ break if !line
35
+ line.chomp!
36
+
37
+ # Is this line the start of a new block
38
+ match = line.match(/\A\[([A-Za-z0-9]+)\]\z/)
39
+ if match
40
+ # Store what we got before this title as a new block
41
+ blocks[title] = entries if title
42
+
43
+ # Start a new block
44
+ title = match[1]
45
+ entries = {}
46
+ else
47
+ # Collect entries until we get to the next title
48
+ if line != ""
49
+ key, value = line.split("=")
50
+ entries[key.to_sym] = value
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ blocks
57
+ end
58
+ end
59
+
60
+ def profiles_ini_path
61
+ File.join(Thunderbird.new.data_path, "profiles.ini")
62
+ end
63
+ end
@@ -0,0 +1,6 @@
1
+ class Thunderbird
2
+ def data_path
3
+ # TODO: Handle other OSes
4
+ File.join(Dir.home, ".thunderbird")
5
+ end
6
+ end
@@ -0,0 +1,70 @@
1
+ describe Imap::Backup::CLI::Local do
2
+ let(:list) do
3
+ instance_double(Imap::Backup::Configuration::List, accounts: accounts)
4
+ end
5
+ let(:accounts) { [{username: email}] }
6
+ let(:connection) do
7
+ instance_double(
8
+ Imap::Backup::Account::Connection,
9
+ local_folders: local_folders
10
+ )
11
+ end
12
+ let(:local_folders) { [[serializer, folder]] }
13
+ let(:folder) { instance_double(Imap::Backup::Account::Folder, name: "bar") }
14
+ let(:serializer) do
15
+ instance_double(
16
+ Imap::Backup::Serializer::Mbox,
17
+ uids: uids,
18
+ each_message: [[123, message]]
19
+ )
20
+ end
21
+ let(:uids) { ["123"] }
22
+ let(:message) do
23
+ instance_double(
24
+ Email::Mboxrd::Message,
25
+ date: Date.today,
26
+ subject: "Ciao",
27
+ supplied_body: "Supplied"
28
+ )
29
+ end
30
+ let(:email) { "foo@example.com" }
31
+
32
+ before do
33
+ allow(Kernel).to receive(:puts)
34
+ allow(Imap::Backup::Configuration::List).to receive(:new) { list }
35
+ allow(Imap::Backup::Account::Connection).to receive(:new) { connection }
36
+ allow(Mail).to receive(:new) { mail }
37
+ end
38
+
39
+ describe "accounts" do
40
+ it "lists configured emails" do
41
+ subject.accounts
42
+
43
+ expect(Kernel).to have_received(:puts).with(email)
44
+ end
45
+ end
46
+
47
+ describe "folders" do
48
+ it "lists downloaded folders in quotes" do
49
+ subject.folders(email)
50
+
51
+ expect(Kernel).to have_received(:puts).with(%("bar"))
52
+ end
53
+ end
54
+
55
+ describe "list" do
56
+ it "lists downloaded emails" do
57
+ subject.list(email, "bar")
58
+
59
+ expect(Kernel).to have_received(:puts).with(/Ciao/)
60
+ end
61
+ end
62
+
63
+ describe "show" do
64
+ it "prints a downloaded email" do
65
+ subject.show(email, "bar", "123")
66
+
67
+ expect(Kernel).to have_received(:puts).with("Supplied")
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,50 @@
1
+ describe Imap::Backup::CLI::Utils do
2
+ let(:list) do
3
+ instance_double(Imap::Backup::Configuration::List, accounts: accounts)
4
+ end
5
+ let(:accounts) { [{username: email}] }
6
+ let(:connection) do
7
+ instance_double(
8
+ Imap::Backup::Account::Connection,
9
+ local_folders: local_folders
10
+ )
11
+ end
12
+ let(:local_folders) { [[serializer, folder]] }
13
+ let(:folder) do
14
+ instance_double(
15
+ Imap::Backup::Account::Folder,
16
+ exist?: true,
17
+ name: "name",
18
+ uid_validity: "uid_validity",
19
+ uids: ["123", "456"]
20
+ )
21
+ end
22
+ let(:serializer) do
23
+ instance_double(
24
+ Imap::Backup::Serializer::Mbox,
25
+ uids: ["123", "789"],
26
+ apply_uid_validity: nil,
27
+ save: nil
28
+ )
29
+ end
30
+ let(:email) { "foo@example.com" }
31
+
32
+ before do
33
+ allow(Imap::Backup::Configuration::List).to receive(:new) { list }
34
+ allow(Imap::Backup::Account::Connection).to receive(:new) { connection }
35
+ end
36
+
37
+ describe "ignore_history" do
38
+ it "ensures the local UID validity matches the server" do
39
+ subject.ignore_history(email)
40
+
41
+ expect(serializer).to have_received(:apply_uid_validity).with("uid_validity")
42
+ end
43
+
44
+ it "fills the local folder with fake emails" do
45
+ subject.ignore_history(email)
46
+
47
+ expect(serializer).to have_received(:save).with("456", /From: fake@email.com/)
48
+ end
49
+ end
50
+ 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: 4.0.1
4
+ version: 4.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Yates
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-06 00:00:00.000000000 Z
11
+ date: 2021-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -156,11 +156,7 @@ email:
156
156
  executables:
157
157
  - imap-backup
158
158
  extensions: []
159
- extra_rdoc_files:
160
- - README.md
161
- - docs/setup.md
162
- - docs/restore.md
163
- - docs/development.md
159
+ extra_rdoc_files: []
164
160
  files:
165
161
  - LICENSE
166
162
  - README.md
@@ -195,10 +191,17 @@ files:
195
191
  - lib/imap/backup/serializer/mbox.rb
196
192
  - lib/imap/backup/serializer/mbox_enumerator.rb
197
193
  - lib/imap/backup/serializer/mbox_store.rb
194
+ - lib/imap/backup/thunderbird/mailbox_exporter.rb
198
195
  - lib/imap/backup/uploader.rb
199
196
  - lib/imap/backup/utils.rb
200
197
  - lib/imap/backup/version.rb
201
198
  - lib/retry_on_error.rb
199
+ - lib/thunderbird.rb
200
+ - lib/thunderbird/local_folder.rb
201
+ - lib/thunderbird/local_folder_placeholder.rb
202
+ - lib/thunderbird/mailbox.rb
203
+ - lib/thunderbird/profile.rb
204
+ - lib/thunderbird/profiles.rb
202
205
  - spec/features/backup_spec.rb
203
206
  - spec/features/helper.rb
204
207
  - spec/features/restore_spec.rb
@@ -217,6 +220,8 @@ files:
217
220
  - spec/unit/email/provider_spec.rb
218
221
  - spec/unit/imap/backup/account/connection_spec.rb
219
222
  - spec/unit/imap/backup/account/folder_spec.rb
223
+ - spec/unit/imap/backup/cli/local_spec.rb
224
+ - spec/unit/imap/backup/cli/utils_spec.rb
220
225
  - spec/unit/imap/backup/configuration/account_spec.rb
221
226
  - spec/unit/imap/backup/configuration/asker_spec.rb
222
227
  - spec/unit/imap/backup/configuration/connection_tester_spec.rb
@@ -236,9 +241,7 @@ licenses:
236
241
  - MIT
237
242
  metadata: {}
238
243
  post_install_message:
239
- rdoc_options:
240
- - "--main"
241
- - README.md
244
+ rdoc_options: []
242
245
  require_paths:
243
246
  - lib
244
247
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -252,40 +255,42 @@ required_rubygems_version: !ruby/object:Gem::Requirement
252
255
  - !ruby/object:Gem::Version
253
256
  version: '0'
254
257
  requirements: []
255
- rubygems_version: 3.1.4
258
+ rubygems_version: 3.1.2
256
259
  signing_key:
257
260
  specification_version: 4
258
261
  summary: Backup GMail (or other IMAP) accounts to disk
259
262
  test_files:
260
263
  - spec/spec_helper.rb
264
+ - spec/fixtures/connection.yml
265
+ - spec/unit/email/provider_spec.rb
266
+ - spec/unit/email/mboxrd/message_spec.rb
261
267
  - spec/unit/imap/backup_spec.rb
262
- - spec/unit/imap/backup/configuration/account_spec.rb
263
- - spec/unit/imap/backup/configuration/list_spec.rb
268
+ - spec/unit/imap/backup/cli/utils_spec.rb
269
+ - spec/unit/imap/backup/cli/local_spec.rb
270
+ - spec/unit/imap/backup/utils_spec.rb
271
+ - spec/unit/imap/backup/downloader_spec.rb
272
+ - spec/unit/imap/backup/configuration/store_spec.rb
264
273
  - spec/unit/imap/backup/configuration/connection_tester_spec.rb
265
- - spec/unit/imap/backup/configuration/setup_spec.rb
266
274
  - spec/unit/imap/backup/configuration/asker_spec.rb
275
+ - spec/unit/imap/backup/configuration/setup_spec.rb
267
276
  - spec/unit/imap/backup/configuration/folder_chooser_spec.rb
268
- - spec/unit/imap/backup/configuration/store_spec.rb
277
+ - spec/unit/imap/backup/configuration/account_spec.rb
278
+ - spec/unit/imap/backup/configuration/list_spec.rb
279
+ - spec/unit/imap/backup/account/folder_spec.rb
280
+ - spec/unit/imap/backup/account/connection_spec.rb
269
281
  - spec/unit/imap/backup/serializer/mbox_spec.rb
270
282
  - spec/unit/imap/backup/serializer/mbox_store_spec.rb
271
283
  - spec/unit/imap/backup/serializer/mbox_enumerator_spec.rb
272
- - spec/unit/imap/backup/downloader_spec.rb
273
284
  - spec/unit/imap/backup/uploader_spec.rb
274
- - spec/unit/imap/backup/account/folder_spec.rb
275
- - spec/unit/imap/backup/account/connection_spec.rb
276
- - spec/unit/imap/backup/utils_spec.rb
277
- - spec/unit/email/mboxrd/message_spec.rb
278
- - spec/unit/email/provider_spec.rb
285
+ - spec/support/fixtures.rb
286
+ - spec/support/shared_examples/account_flagging.rb
287
+ - spec/support/higline_test_helpers.rb
288
+ - spec/support/silence_logging.rb
289
+ - spec/gather_rspec_coverage.rb
279
290
  - spec/features/backup_spec.rb
280
291
  - spec/features/helper.rb
281
- - spec/features/restore_spec.rb
282
- - spec/features/support/email_server.rb
283
292
  - spec/features/support/backup_directory.rb
284
- - spec/features/support/shared/connection_context.rb
285
293
  - spec/features/support/shared/message_fixtures.rb
286
- - spec/support/fixtures.rb
287
- - spec/support/silence_logging.rb
288
- - spec/support/higline_test_helpers.rb
289
- - spec/support/shared_examples/account_flagging.rb
290
- - spec/fixtures/connection.yml
291
- - spec/gather_rspec_coverage.rb
294
+ - spec/features/support/shared/connection_context.rb
295
+ - spec/features/support/email_server.rb
296
+ - spec/features/restore_spec.rb