imap-backup 4.0.1 → 4.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -1
- data/docs/development.md +5 -12
- data/imap-backup.gemspec +1 -4
- data/lib/email/mboxrd/message.rb +6 -2
- data/lib/email/provider/apple_mail.rb +7 -0
- data/lib/email/provider/base.rb +8 -0
- data/lib/email/provider/fastmail.rb +7 -0
- data/lib/email/provider/gmail.rb +7 -0
- data/lib/email/provider/unknown.rb +11 -0
- data/lib/email/provider.rb +16 -26
- data/lib/imap/backup/account/connection.rb +19 -29
- data/lib/imap/backup/account/folder.rb +9 -10
- data/lib/imap/backup/account.rb +103 -0
- data/lib/imap/backup/cli/helpers.rb +14 -0
- data/lib/imap/backup/cli/local.rb +19 -25
- data/lib/imap/backup/cli/utils.rb +67 -10
- data/lib/imap/backup/client/apple_mail.rb +11 -0
- data/lib/imap/backup/client/default.rb +51 -0
- data/lib/imap/backup/configuration/account.rb +3 -1
- data/lib/imap/backup/configuration/connection_tester.rb +1 -1
- data/lib/imap/backup/configuration/store.rb +10 -5
- data/lib/imap/backup/downloader.rb +3 -4
- data/lib/imap/backup/serializer/mbox.rb +5 -0
- data/lib/imap/backup/serializer/mbox_store.rb +8 -8
- data/lib/imap/backup/thunderbird/mailbox_exporter.rb +58 -0
- data/lib/imap/backup/version.rb +1 -1
- data/lib/imap/backup.rb +1 -0
- data/lib/thunderbird/install.rb +16 -0
- data/lib/thunderbird/local_folder.rb +65 -0
- data/lib/thunderbird/profile.rb +30 -0
- data/lib/thunderbird/profiles.rb +71 -0
- data/lib/thunderbird/subdirectory.rb +96 -0
- data/lib/thunderbird/subdirectory_placeholder.rb +21 -0
- data/lib/thunderbird.rb +14 -0
- data/spec/features/restore_spec.rb +1 -1
- data/spec/features/support/email_server.rb +2 -2
- data/spec/unit/email/provider/apple_mail_spec.rb +7 -0
- data/spec/unit/email/provider/base_spec.rb +11 -0
- data/spec/unit/email/provider/fastmail_spec.rb +7 -0
- data/spec/unit/email/provider/gmail_spec.rb +7 -0
- data/spec/unit/email/provider_spec.rb +12 -25
- data/spec/unit/imap/backup/account/connection_spec.rb +26 -51
- data/spec/unit/imap/backup/account/folder_spec.rb +22 -22
- data/spec/unit/imap/backup/cli/local_spec.rb +70 -0
- data/spec/unit/imap/backup/cli/utils_spec.rb +50 -0
- data/spec/unit/imap/backup/client/default_spec.rb +22 -0
- data/spec/unit/imap/backup/configuration/connection_tester_spec.rb +3 -3
- data/spec/unit/imap/backup/configuration/store_spec.rb +25 -12
- data/spec/unit/imap/backup/downloader_spec.rb +1 -2
- metadata +68 -30
@@ -1,26 +1,67 @@
|
|
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
|
-
FAKE_EMAIL = "fake@email.com"
|
8
|
+
FAKE_EMAIL = "fake@email.com".freeze
|
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
|
-
|
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 |
|
14
|
+
connection.local_folders.each do |serializer, folder|
|
16
15
|
next if !folder.exist?
|
17
|
-
|
16
|
+
|
17
|
+
do_ignore_folder_history(folder, serializer)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc(
|
22
|
+
"export-to-thunderbird EMAIL [OPTIONS]",
|
23
|
+
<<~DOC
|
24
|
+
[Experimental] Copy backed up emails to Thunderbird.
|
25
|
+
A folder called 'imap-backup/EMAIL' is created under 'Local Folders'.
|
26
|
+
DOC
|
27
|
+
)
|
28
|
+
method_option(
|
29
|
+
"force",
|
30
|
+
type: :boolean,
|
31
|
+
banner: "overwrite existing mailboxes",
|
32
|
+
aliases: ["-f"]
|
33
|
+
)
|
34
|
+
method_option(
|
35
|
+
"profile",
|
36
|
+
type: :string,
|
37
|
+
banner: "the name of the Thunderbird profile to copy emails to",
|
38
|
+
aliases: ["-p"]
|
39
|
+
)
|
40
|
+
def export_to_thunderbird(email)
|
41
|
+
opts = symbolized(options)
|
42
|
+
force = opts.key?(:force) ? opts[:force] : false
|
43
|
+
profile_name = opts[:profile]
|
44
|
+
|
45
|
+
connection = connection(email)
|
46
|
+
profile = thunderbird_profile(profile_name)
|
47
|
+
|
48
|
+
if !profile
|
49
|
+
if profile_name
|
50
|
+
raise "Thunderbird profile '#{profile_name}' not found"
|
51
|
+
else
|
52
|
+
raise "Default Thunderbird profile not found"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
connection.local_folders.each do |serializer, _folder|
|
57
|
+
Thunderbird::MailboxExporter.new(
|
58
|
+
email, serializer, profile, force: force
|
59
|
+
).run
|
18
60
|
end
|
19
61
|
end
|
20
62
|
|
21
63
|
no_commands do
|
22
|
-
def do_ignore_folder_history(
|
23
|
-
serializer = Imap::Backup::Serializer::Mbox.new(connection.local_path, folder.name)
|
64
|
+
def do_ignore_folder_history(folder, serializer)
|
24
65
|
uids = folder.uids - serializer.uids
|
25
66
|
Imap::Backup.logger.info "Folder '#{folder.name}' - #{uids.length} messages"
|
26
67
|
|
@@ -36,6 +77,22 @@ module Imap::Backup
|
|
36
77
|
serializer.save(uid, message)
|
37
78
|
end
|
38
79
|
end
|
80
|
+
|
81
|
+
def thunderbird_profile(name = nil)
|
82
|
+
profiles = Thunderbird::Profiles.new
|
83
|
+
if name
|
84
|
+
profiles.profile(name)
|
85
|
+
else
|
86
|
+
if profiles.installs.count > 1
|
87
|
+
raise <<~MESSAGE
|
88
|
+
Thunderbird has multiple installs, so no default profile exists.
|
89
|
+
Please supply a profile name
|
90
|
+
MESSAGE
|
91
|
+
end
|
92
|
+
|
93
|
+
profiles.installs[0].default
|
94
|
+
end
|
95
|
+
end
|
39
96
|
end
|
40
97
|
end
|
41
98
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "net/imap"
|
3
|
+
|
4
|
+
module Imap::Backup
|
5
|
+
module Client; end
|
6
|
+
|
7
|
+
class Client::Default
|
8
|
+
extend Forwardable
|
9
|
+
def_delegators :imap, *%i(
|
10
|
+
append authenticate create disconnect examine
|
11
|
+
login responses uid_fetch uid_search
|
12
|
+
)
|
13
|
+
|
14
|
+
attr_reader :args
|
15
|
+
|
16
|
+
def initialize(*args)
|
17
|
+
@args = args
|
18
|
+
end
|
19
|
+
|
20
|
+
def list
|
21
|
+
root = provider_root
|
22
|
+
mailbox_lists = imap.list(root, "*")
|
23
|
+
|
24
|
+
return [] if mailbox_lists.nil?
|
25
|
+
|
26
|
+
mailbox_lists.map { |ml| extract_name(ml) }
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def imap
|
32
|
+
@imap ||= Net::IMAP.new(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_name(mailbox_list)
|
36
|
+
utf7_encoded = mailbox_list.name
|
37
|
+
Net::IMAP.decode_utf7(utf7_encoded)
|
38
|
+
end
|
39
|
+
|
40
|
+
# 6.3.8. LIST Command
|
41
|
+
# An empty ("" string) mailbox name argument is a special request to
|
42
|
+
# return the hierarchy delimiter and the root name of the name given
|
43
|
+
# in the reference.
|
44
|
+
def provider_root
|
45
|
+
@provider_root ||= begin
|
46
|
+
root_info = imap.list("", "")[0]
|
47
|
+
root_info.name
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -147,10 +147,12 @@ module Imap::Backup
|
|
147
147
|
|
148
148
|
def default_server(username)
|
149
149
|
provider = Email::Provider.for_address(username)
|
150
|
-
|
150
|
+
|
151
|
+
if provider.is_a?(Email::Provider::Unknown)
|
151
152
|
Kernel.puts "Can't decide provider for email address '#{username}'"
|
152
153
|
return nil
|
153
154
|
end
|
155
|
+
|
154
156
|
provider.host
|
155
157
|
end
|
156
158
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "json"
|
2
|
+
require "os"
|
2
3
|
|
3
4
|
module Imap::Backup
|
4
5
|
module Configuration; end
|
@@ -25,11 +26,12 @@ module Imap::Backup
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def save
|
28
|
-
|
29
|
+
FileUtils.mkdir(path) if !File.directory?(path)
|
30
|
+
make_private(path) if !windows?
|
29
31
|
remove_modified_flags
|
30
32
|
remove_deleted_accounts
|
31
33
|
File.open(pathname, "w") { |f| f.write(JSON.pretty_generate(data)) }
|
32
|
-
FileUtils.chmod
|
34
|
+
FileUtils.chmod(0o600, pathname) if !windows?
|
33
35
|
end
|
34
36
|
|
35
37
|
def accounts
|
@@ -54,7 +56,7 @@ module Imap::Backup
|
|
54
56
|
@data ||=
|
55
57
|
begin
|
56
58
|
if File.exist?(pathname)
|
57
|
-
Utils.check_permissions
|
59
|
+
Utils.check_permissions(pathname, 0o600) if !windows?
|
58
60
|
contents = File.read(pathname)
|
59
61
|
data = JSON.parse(contents, symbolize_names: true)
|
60
62
|
else
|
@@ -73,9 +75,12 @@ module Imap::Backup
|
|
73
75
|
accounts.reject! { |a| a[:delete] }
|
74
76
|
end
|
75
77
|
|
76
|
-
def
|
77
|
-
FileUtils.mkdir(path) if !File.directory?(path)
|
78
|
+
def make_private(path)
|
78
79
|
FileUtils.chmod(0o700, path) if Utils.mode(path) != 0o700
|
79
80
|
end
|
81
|
+
|
82
|
+
def windows?
|
83
|
+
OS.windows?
|
84
|
+
end
|
80
85
|
end
|
81
86
|
end
|
@@ -13,16 +13,15 @@ module Imap::Backup
|
|
13
13
|
count = uids.count
|
14
14
|
Imap::Backup.logger.debug "[#{folder.name}] #{count} new messages"
|
15
15
|
uids.each.with_index do |uid, i|
|
16
|
-
|
16
|
+
body = folder.fetch(uid)
|
17
17
|
log_prefix = "[#{folder.name}] uid: #{uid} (#{i + 1}/#{count}) -"
|
18
|
-
if
|
18
|
+
if body.nil?
|
19
19
|
Imap::Backup.logger.debug("#{log_prefix} not available - skipped")
|
20
20
|
next
|
21
21
|
end
|
22
22
|
Imap::Backup.logger.debug(
|
23
|
-
"#{log_prefix} #{
|
23
|
+
"#{log_prefix} #{body.size} bytes"
|
24
24
|
)
|
25
|
-
body = message["RFC822"]
|
26
25
|
serializer.save(uid, body)
|
27
26
|
end
|
28
27
|
end
|
@@ -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,58 @@
|
|
1
|
+
require "thunderbird/local_folder"
|
2
|
+
require "thunderbird/profiles"
|
3
|
+
|
4
|
+
module Imap::Backup
|
5
|
+
class Thunderbird::MailboxExporter
|
6
|
+
EXPORT_PREFIX = "imap-backup".freeze
|
7
|
+
|
8
|
+
attr_reader :email
|
9
|
+
attr_reader :serializer
|
10
|
+
attr_reader :profile
|
11
|
+
attr_reader :force
|
12
|
+
|
13
|
+
def initialize(email, serializer, profile, force: false)
|
14
|
+
@email = email
|
15
|
+
@serializer = serializer
|
16
|
+
@profile = profile
|
17
|
+
@force = force
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
local_folder_ok = local_folder.set_up
|
22
|
+
return if !local_folder_ok
|
23
|
+
|
24
|
+
if local_folder.msf_exists?
|
25
|
+
if force
|
26
|
+
Kernel.puts "Deleting '#{local_folder.msf_path}' as --force option was supplied"
|
27
|
+
File.unlink local_folder.msf_path
|
28
|
+
else
|
29
|
+
Kernel.puts "Skipping export of '#{serializer.folder}' as '#{local_folder.msf_path}' exists"
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
if local_folder.exists?
|
35
|
+
if force
|
36
|
+
Kernel.puts "Overwriting '#{local_folder.path}' as --force option was supplied"
|
37
|
+
else
|
38
|
+
Kernel.puts "Skipping export of '#{serializer.folder}' as '#{local_folder.path}' exists"
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
FileUtils.cp serializer.mbox_pathname, local_folder.full_path
|
44
|
+
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def local_folder
|
51
|
+
@local_folder ||= begin
|
52
|
+
top_level_folders = [EXPORT_PREFIX, email]
|
53
|
+
prefixed_folder_path = File.join(top_level_folders, serializer.folder)
|
54
|
+
Thunderbird::LocalFolder.new(profile, prefixed_folder_path)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/imap/backup/version.rb
CHANGED
data/lib/imap/backup.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "thunderbird/profiles"
|
2
|
+
|
3
|
+
class Thunderbird::Install
|
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 default
|
14
|
+
Thunderbird::Profiles.new.profile_for_path(entries[:Default])
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "thunderbird/profile"
|
2
|
+
require "thunderbird/subdirectory"
|
3
|
+
|
4
|
+
# A local folder is a file containing emails
|
5
|
+
class Thunderbird::LocalFolder
|
6
|
+
attr_reader :path
|
7
|
+
attr_reader :profile
|
8
|
+
|
9
|
+
def initialize(profile, path)
|
10
|
+
@profile = profile
|
11
|
+
@path = path
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_up
|
15
|
+
return if path_elements.empty?
|
16
|
+
|
17
|
+
return true if !in_subdirectory?
|
18
|
+
|
19
|
+
subdirectory.set_up
|
20
|
+
end
|
21
|
+
|
22
|
+
def full_path
|
23
|
+
if in_subdirectory?
|
24
|
+
File.join(subdirectory.full_path, folder_name)
|
25
|
+
else
|
26
|
+
folder_name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def exists?
|
31
|
+
File.exist?(full_path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def msf_path
|
35
|
+
"#{path}.msf"
|
36
|
+
end
|
37
|
+
|
38
|
+
def msf_exists?
|
39
|
+
File.exist?(msf_path)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def in_subdirectory?
|
45
|
+
path_elements.count > 1
|
46
|
+
end
|
47
|
+
|
48
|
+
def subdirectory
|
49
|
+
return nil if !in_subdirectory?
|
50
|
+
|
51
|
+
Thunderbird::Subdirectory.new(profile, subdirectory_path)
|
52
|
+
end
|
53
|
+
|
54
|
+
def path_elements
|
55
|
+
path.split(File::SEPARATOR)
|
56
|
+
end
|
57
|
+
|
58
|
+
def subdirectory_path
|
59
|
+
File.join(path_elements[0..-2])
|
60
|
+
end
|
61
|
+
|
62
|
+
def folder_name
|
63
|
+
path_elements[-1]
|
64
|
+
end
|
65
|
+
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,71 @@
|
|
1
|
+
require "thunderbird"
|
2
|
+
require "thunderbird/install"
|
3
|
+
require "thunderbird/profile"
|
4
|
+
|
5
|
+
# http://kb.mozillazine.org/Profiles.ini_file
|
6
|
+
class Thunderbird::Profiles
|
7
|
+
def profile_for_path(path)
|
8
|
+
title, entries = blocks.find { |_name, entries| entries[:Path] == path }
|
9
|
+
|
10
|
+
Thunderbird::Profile.new(title, entries) if title
|
11
|
+
end
|
12
|
+
|
13
|
+
def profile(name)
|
14
|
+
title, entries = blocks.find { |_name, entries| entries[:Name] == name }
|
15
|
+
|
16
|
+
Thunderbird::Profile.new(title, entries) if title
|
17
|
+
end
|
18
|
+
|
19
|
+
def installs
|
20
|
+
@installs ||= begin
|
21
|
+
pairs = blocks.filter { |name, _entries| name.start_with?("Install") }
|
22
|
+
pairs.map { |title, entries| Thunderbird::Install.new(title, entries) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Parse profiles.ini.
|
29
|
+
# Blocks start with a title, e.g. '[Abc]'
|
30
|
+
# and are followed by a number of lines
|
31
|
+
def blocks
|
32
|
+
@blocks ||= begin
|
33
|
+
blocks = {}
|
34
|
+
File.open(profiles_ini_path, "rb") do |f|
|
35
|
+
title = nil
|
36
|
+
entries = nil
|
37
|
+
|
38
|
+
loop do
|
39
|
+
line = f.gets
|
40
|
+
if !line
|
41
|
+
blocks[title] = entries if title
|
42
|
+
break
|
43
|
+
end
|
44
|
+
|
45
|
+
line.chomp!
|
46
|
+
|
47
|
+
# Is this line the start of a new block
|
48
|
+
match = line.match(/\A\[([A-Za-z0-9]+)\]\z/)
|
49
|
+
if match
|
50
|
+
# Store what we got before this title as a new block
|
51
|
+
blocks[title] = entries if title
|
52
|
+
|
53
|
+
# Start a new block
|
54
|
+
title = match[1]
|
55
|
+
entries = {}
|
56
|
+
elsif line != ""
|
57
|
+
# Collect entries until we get to the next title
|
58
|
+
key, value = line.split("=")
|
59
|
+
entries[key.to_sym] = value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
blocks
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def profiles_ini_path
|
69
|
+
File.join(Thunderbird.new.data_path, "profiles.ini")
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "thunderbird/subdirectory_placeholder"
|
2
|
+
|
3
|
+
class Thunderbird::Subdirectory
|
4
|
+
# `path` is the UI path, it doesn't have the '.sbd' extensions
|
5
|
+
# that are present in the real, file system path
|
6
|
+
attr_reader :path
|
7
|
+
attr_reader :profile
|
8
|
+
|
9
|
+
def initialize(profile, path)
|
10
|
+
@profile = profile
|
11
|
+
@path = path
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_up
|
15
|
+
raise "Cannot create a subdirectory without a path" if !sub_directory?
|
16
|
+
|
17
|
+
if sub_sub_directory?
|
18
|
+
parent_ok = parent.set_up
|
19
|
+
return false if !parent_ok
|
20
|
+
end
|
21
|
+
|
22
|
+
ok = check
|
23
|
+
return false if !ok
|
24
|
+
|
25
|
+
FileUtils.mkdir_p full_path
|
26
|
+
placeholder.touch
|
27
|
+
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
# subdirectory relative path is 'Foo.sbd/Bar.sbd/Baz.sbd'
|
32
|
+
def full_path
|
33
|
+
relative_path = File.join(subdirectories)
|
34
|
+
File.join(profile.local_folders_path, relative_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def sub_directory?
|
40
|
+
path_elements.any?
|
41
|
+
end
|
42
|
+
|
43
|
+
def sub_sub_directory?
|
44
|
+
path_elements.count > 1
|
45
|
+
end
|
46
|
+
|
47
|
+
def parent
|
48
|
+
return nil if !sub_sub_directory?
|
49
|
+
|
50
|
+
self.class.new(profile, File.join(path_elements[0..-2]))
|
51
|
+
end
|
52
|
+
|
53
|
+
# placeholder relative path is 'Foo.sbd/Bar.sbd/Baz'
|
54
|
+
def placeholder
|
55
|
+
@placeholder = begin
|
56
|
+
relative_path = File.join(subdirectories[0..-2], path_elements[-1])
|
57
|
+
path = File.join(profile.local_folders_path, relative_path)
|
58
|
+
Thunderbird::SubdirectoryPlaceholder.new(path)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def path_elements
|
63
|
+
path.split(File::SEPARATOR)
|
64
|
+
end
|
65
|
+
|
66
|
+
def exists?
|
67
|
+
File.exist?(full_path)
|
68
|
+
end
|
69
|
+
|
70
|
+
def directory?
|
71
|
+
File.directory?(full_path)
|
72
|
+
end
|
73
|
+
|
74
|
+
def subdirectories
|
75
|
+
path_elements.map { |p| "#{p}.sbd" }
|
76
|
+
end
|
77
|
+
|
78
|
+
def check
|
79
|
+
case
|
80
|
+
when placeholder.exists? && !exists?
|
81
|
+
Kernel.puts "Can't set up folder '#{folder_path}': '#{placeholder.path}' exists, but '#{full_path}' is missing"
|
82
|
+
false
|
83
|
+
when exists? && !placeholder.exists?
|
84
|
+
Kernel.puts "Can't set up folder '#{folder_path}': '#{full_path}' exists, but '#{placeholder.path}' is missing"
|
85
|
+
false
|
86
|
+
when placeholder.exists? && !placeholder.regular?
|
87
|
+
Kernel.puts "Can't set up folder '#{folder_path}': '#{placeholder.path}' exists, but it is not a regular file"
|
88
|
+
false
|
89
|
+
when exists? && !directory?
|
90
|
+
Kernel.puts "Can't set up folder '#{folder_path}': '#{full_path}' exists, but it is not a directory"
|
91
|
+
false
|
92
|
+
else
|
93
|
+
true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
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::SubdirectoryPlaceholder
|
4
|
+
attr_reader :path
|
5
|
+
|
6
|
+
def initialize(path)
|
7
|
+
@path = path
|
8
|
+
end
|
9
|
+
|
10
|
+
def exists?
|
11
|
+
File.exist?(path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def regular?
|
15
|
+
File.file?(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def touch
|
19
|
+
FileUtils.touch path
|
20
|
+
end
|
21
|
+
end
|
data/lib/thunderbird.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "os"
|
2
|
+
|
3
|
+
class Thunderbird
|
4
|
+
def data_path
|
5
|
+
case
|
6
|
+
when OS.linux?
|
7
|
+
File.join(Dir.home, ".thunderbird")
|
8
|
+
when OS.mac?
|
9
|
+
File.join(Dir.home, "Library", "Thunderbird")
|
10
|
+
when OS.windows?
|
11
|
+
File.join(ENV["APPDATA"].gsub("\\", "/"), "Thunderbird")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|