imap-backup 4.0.1 → 4.0.5
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 +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
|