imap-backup 4.0.1 → 4.0.2

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