glima 0.2.1

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.
@@ -0,0 +1,26 @@
1
+ module Glima
2
+ module Command
3
+ class Push < Base
4
+
5
+ def initialize(email_file, date, thread, labels)
6
+ label_ids = labels.map(&:id) + ["INBOX", "UNREAD"]
7
+
8
+ File.open(email_file) do |source|
9
+ client.insert_user_message(
10
+ 'me',
11
+ Google::Apis::GmailV1::Message.new(label_ids: label_ids, thread_id: thread),
12
+ content_type: "message/rfc822",
13
+ internal_date_source: date,
14
+ upload_source: source) do |msg, err|
15
+ if msg
16
+ puts "pushed to: #{msg.id}"
17
+ else
18
+ STDERR.puts "Error: #{err}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ end # class Push
25
+ end # module Command
26
+ end # module Glima
@@ -0,0 +1,65 @@
1
+ module Glima
2
+ module Command
3
+ class Relabel < Base
4
+
5
+ def initialize(source_name, dest_name, dry_run)
6
+
7
+ all_labels = client.list_user_labels('me').labels.sort_by(&:name)
8
+
9
+ if /\/$/ =~ dest_name
10
+ move_to_dir = true
11
+ dest_name = dest_name.sub(/\/$/, '')
12
+ else
13
+ move_to_dir = false
14
+ end
15
+
16
+ source_labels = all_labels.find_all {|x| File.fnmatch(source_name, x.name, File::FNM_PATHNAME)}
17
+ dest_label = all_labels.find {|x| x.name == dest_name}
18
+
19
+ if source_labels.empty?
20
+ puts "Error: source #{source_name} not found"
21
+ return nil
22
+ end
23
+
24
+ if dest_label && !move_to_dir
25
+ puts "Error: dest #{dest_name} already exists"
26
+ return nil
27
+ end
28
+
29
+ if !dest_label && move_to_dir
30
+ puts "Error: dest #{dest_name} not found"
31
+ return nil
32
+ end
33
+
34
+ source_labels.each do |source_label|
35
+ dirtop = File.dirname(source_label.name)
36
+ sub_labels = all_labels.find_all {|x| File.fnmatch(source_label.name + '/*', x.name)}
37
+
38
+ ([source_label] + sub_labels).each do |label|
39
+ src = label.name
40
+ dst = dest_name + '/' + (label.name.sub(/^#{dirtop}\//, ''))
41
+
42
+ if all_labels.find {|x| x.name == dst}
43
+ puts "Error: relabel #{src} -> #{dst}: Destination already exists"
44
+ next
45
+ else
46
+ puts "relabel #{src} -> #{dst}"
47
+ end
48
+
49
+ unless dry_run
50
+ label_obj = Google::Apis::GmailV1::Label.new(id: label.id, name: dst)
51
+ client.patch_user_label('me', label.id, label_obj) do |response, err|
52
+ if response
53
+ # puts dump_label(response)
54
+ else
55
+ puts "Error: #{err}"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ end # class Relabel
64
+ end # module Command
65
+ end # module Glima
@@ -0,0 +1,32 @@
1
+ module Glima
2
+ module Command
3
+ class Scan < Base
4
+
5
+ def initialize(folder, format, search_or_range)
6
+
7
+ index = 1
8
+ client.scan_batch(folder, search_or_range) do |mail|
9
+ case format
10
+ when :mew
11
+ puts mail.format_mew(index)
12
+ when :archive
13
+ puts format_archive_friendly(mail)
14
+ else
15
+ puts mail.format_summary(index)
16
+ end
17
+ index += 1
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def format_archive_friendly(mail)
24
+ date = mail.date.strftime("%Y-%m-%d-%H%M%S")
25
+ # Replace unsafe chars for filename
26
+ subject = mail.subject.tr('!/:*?"<>|\\', '!/:*?″<>|\').gsub(/[\s ]/, '')
27
+ return "#{mail.id} #{date}-#{mail.id}-#{subject}.eml"
28
+ end
29
+
30
+ end # class Scan
31
+ end # module Command
32
+ end # module Glima
@@ -0,0 +1,22 @@
1
+ module Glima
2
+ module Command
3
+ class Trash < Base
4
+
5
+ def initialize(message_ids)
6
+
7
+ client.batch do |batch_client|
8
+ message_ids.each do |id|
9
+ batch_client.trash_user_message(id) do |res, err|
10
+ if res
11
+ puts "Trash #{id} successfully."
12
+ else
13
+ puts "Error: #{err}"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ end # class Trash
21
+ end # module Command
22
+ end # module Glima
@@ -0,0 +1,45 @@
1
+ module Glima
2
+ module Command
3
+ class Watch < Base
4
+
5
+ def initialize(queue_label = nil, mark_label = nil)
6
+
7
+ # Watch "[Gmail]/All Mail" by IMAP idle
8
+ client.watch(nil) do |ev|
9
+ next unless ev.type == :added
10
+
11
+ # Scan messages in queue_label or new message itself.
12
+ #
13
+ # If Xzip process is successful, remove queue_label
14
+ # from the source message.
15
+ #
16
+ if queue_label
17
+ target = "label:#{queue_label.name}"
18
+ target += " -label:#{mark_label.name}" if mark_label
19
+ del_labels = [queue_label]
20
+ else
21
+ target = ev.message.id
22
+ del_labels = []
23
+ end
24
+
25
+ # Also, mark_label will be added to the xzipped message.
26
+ # It is for avoidance of infinite loop.
27
+ #
28
+ if mark_label
29
+ add_labels = [mark_label]
30
+ else
31
+ add_labels = []
32
+ end
33
+
34
+ logger.info "Xzip #{target}"
35
+
36
+ Glima::Command::Xzip.new(client, logger, target,
37
+ add_dst_labels: add_labels,
38
+ del_dst_labels: del_labels,
39
+ del_src_labels: del_labels)
40
+ end
41
+ end
42
+
43
+ end # class Watch
44
+ end # module Command
45
+ end # module Glima
@@ -0,0 +1,103 @@
1
+ module Glima
2
+ module Command
3
+ class Xzip < Base
4
+ def initialize(target,
5
+ add_src_labels: [],
6
+ del_src_labels: [],
7
+ add_dst_labels: [],
8
+ del_dst_labels: [])
9
+
10
+ add_src_label_ids = add_src_labels.map(&:id)
11
+ del_src_label_ids = del_src_labels.map(&:id)
12
+ add_dst_label_ids = add_dst_labels.map(&:id)
13
+ del_dst_label_ids = del_dst_labels.map(&:id)
14
+
15
+ ids = if target =~ /^[\da-fA-F]{16}$/
16
+ [target]
17
+ else
18
+ client.find_messages(target)
19
+ end
20
+
21
+ ids.each do |message_id|
22
+ # get target mail
23
+ mail = client.get_user_smart_message(message_id) do |m, err|
24
+ if err
25
+ puts "Error: #{err}"
26
+ next
27
+ end
28
+ end
29
+
30
+ # find password candidates from nearby mails
31
+ password_candidates = []
32
+ client.nearby_mails(mail) do |nm|
33
+ logger.info "Passwordish mail: " + nm.format_summary
34
+ password_candidates += nm.find_passwordish_strings
35
+ end
36
+
37
+ # try to unlock zip attachments
38
+ unless mail.unlock_zip!(password_candidates, logger)
39
+ puts "Password unlock failed."
40
+ next
41
+ end
42
+
43
+ # push back unlocked mail to server
44
+ unless push_mail(mail, "dateHeader", add_dst_label_ids, del_dst_label_ids)
45
+ puts "Push mail failed."
46
+ next
47
+ end
48
+
49
+ # add/remove labels from the target
50
+ if add_src_label_ids.empty? && del_src_label_ids.empty?
51
+ next
52
+ end
53
+
54
+ req = {}
55
+ req[:add_label_ids] = add_src_label_ids
56
+ req[:remove_label_ids] = del_src_label_ids
57
+
58
+ req = Google::Apis::GmailV1::ModifyMessageRequest.new(req)
59
+
60
+ client.modify_message('me', message_id, req) do |res,err|
61
+ if res
62
+ puts "Update #{message_id} successfully."
63
+ else
64
+ puts "Error: #{err}"
65
+ end
66
+ end
67
+ end
68
+ end # def initialize
69
+
70
+ private
71
+
72
+ def push_mail(mail, date_source = "receivedTime", add_label_ids = [], del_label_ids = [])
73
+ label_ids = (mail.label_ids +
74
+ add_label_ids -
75
+ del_label_ids +
76
+ ["INBOX", "UNREAD"]).uniq
77
+ thid = mail.thread_id
78
+
79
+ unless date_source == "dateHeader" || date_source == "receivedTime"
80
+ raise "Unknown date type: #{date_source}"
81
+ end
82
+
83
+ mail.header["X-Glima-Processed"] = DateTime.now.rfc2822
84
+
85
+ client.insert_user_message(
86
+ 'me',
87
+ Google::Apis::GmailV1::Message.new(label_ids: label_ids, thread_id: thid),
88
+ content_type: "message/rfc822",
89
+ internal_date_source: date_source,
90
+ upload_source: StringIO.new(mail.to_s)) do |msg, err|
91
+ if msg
92
+ puts "pushed to: #{msg.id}"
93
+ return true
94
+ else
95
+ STDERR.puts "Error: #{err}"
96
+ return false
97
+ end
98
+ end
99
+ end
100
+
101
+ end # class Xzip
102
+ end # module Command
103
+ end # module Glima
@@ -0,0 +1,205 @@
1
+ require 'yaml'
2
+ require 'pp'
3
+
4
+ module Glima
5
+ class Config
6
+ # Syntax table manipulation
7
+ class Syntax
8
+ def initialize(syntax_config)
9
+ @syntax_config = syntax_config
10
+ end
11
+
12
+ def keyword_symbols
13
+ @syntax_config.keys
14
+ end
15
+
16
+ def keywords
17
+ keyword_symbols.map {|sym| sym.to_s.upcase }
18
+ end
19
+
20
+ def keyword?(word)
21
+ if word.is_a?(Symbol)
22
+ keyword_symbols.member?(word)
23
+ else
24
+ # String
25
+ keywords.member?(word)
26
+ end
27
+ end
28
+
29
+ def instance_variable_name(word)
30
+ return nil unless keyword?(word)
31
+ return '@' + as_symbol(word).to_s
32
+ end
33
+
34
+ def item_class(word)
35
+ return nil unless keyword?(word)
36
+ @syntax_config[as_symbol(word)]
37
+ end
38
+
39
+ private
40
+ def as_symbol(word)
41
+ word.to_s.downcase.sub(/^@+/, "").to_sym
42
+ end
43
+ end # class Syntax
44
+
45
+ # Parse Key-Value object in YAML
46
+ class Base
47
+ # attr_accessor :name
48
+
49
+ def self.create_from_yaml_file(yaml_file)
50
+ yaml_string = File.open(File.expand_path(yaml_file)).read
51
+ return create_from_yaml_string(yaml_string, yaml_file)
52
+ end
53
+
54
+ def self.create_from_yaml_string(yaml_string, filename = nil)
55
+ hash = YAML.load(yaml_string, filename) || {}
56
+ return new(hash)
57
+ end
58
+
59
+ def self.define_syntax(config)
60
+ @syntax = Syntax.new(config)
61
+ @syntax.keyword_symbols.each do |sym|
62
+ attr_accessor sym # XXX: attr_reader is enough?
63
+ end
64
+ end
65
+
66
+ def self.syntax
67
+ return @syntax
68
+ end
69
+
70
+ def initialize(hash = {})
71
+ @original_hash = hash
72
+ (hash || {}).each do |key, val|
73
+ raise Glima::ConfigurationError, "config syntax error (#{key})" unless syntax.keyword?(key)
74
+ var = syntax.instance_variable_name(key)
75
+ obj = create_subnode(key, val)
76
+ instance_variable_set(var, obj)
77
+ end
78
+ end
79
+
80
+ attr_reader :original_hash
81
+
82
+ def get_value(dot_separated_string = nil)
83
+ if dot_separated_string.to_s == ""
84
+ return original_hash
85
+ end
86
+
87
+ key, subkey = dot_separated_string.to_s.upcase.split(".", 2)
88
+ subnode = get_subnode(key)
89
+
90
+ if subnode.respond_to?(:get_value)
91
+ return subnode.get_value(subkey)
92
+ else
93
+ return subnode.to_s
94
+ end
95
+ end
96
+
97
+ def to_yaml
98
+ return self.to_hash.to_yaml
99
+ end
100
+
101
+ def to_hash
102
+ hash = {}
103
+ syntax.keywords.each do |key|
104
+ var = syntax.instance_variable_name(key)
105
+ obj = instance_variable_get(var)
106
+ obj = obj.respond_to?(:to_hash) ? obj.to_hash : obj.to_s
107
+ hash[key] = obj
108
+ end
109
+ return hash
110
+ end
111
+
112
+ private
113
+ def syntax
114
+ self.class.syntax
115
+ end
116
+
117
+ def get_subnode(key)
118
+ raise Glima::ConfigurationError, "Invalid key: #{key}" unless syntax.keyword?(key)
119
+ return instance_variable_get(syntax.instance_variable_name(key))
120
+ end
121
+
122
+ def create_subnode(keyword, value)
123
+ item_class = syntax.item_class(keyword)
124
+ if item_class.is_a?(Array)
125
+ return List.new(item_class.first, value)
126
+ elsif item_class == String
127
+ return value.to_s
128
+ else
129
+ return item_class.new(value)
130
+ end
131
+ end
132
+
133
+ end # class Base
134
+
135
+ # Parse Array object in YAML
136
+ class List < Base
137
+ include Enumerable
138
+
139
+ def initialize(item_class, array = [])
140
+ @original_hash = array
141
+ @configs = []
142
+ (array || []).each do |value|
143
+ item = item_class.new(value)
144
+ @configs << item
145
+ end
146
+ end
147
+
148
+ def [](key)
149
+ @configs.find {|c| c.name == key}
150
+ end
151
+
152
+ alias_method :get_subnode, :[]
153
+
154
+ def <<(conf)
155
+ @configs << conf
156
+ end
157
+
158
+ def to_hash # XXX: actually, it returns a Array
159
+ return @configs.map {|c| c.respond_to?(:to_hash) ? c.to_hash : c.to_s}
160
+ end
161
+
162
+ def each
163
+ @configs.each do |conf|
164
+ yield conf
165
+ end
166
+ end
167
+ end # List
168
+
169
+ ## concrete config classes
170
+
171
+ class General < Base
172
+ define_syntax :client_id => String,
173
+ :client_secret => String,
174
+ :token_store => String,
175
+ :context_store => String,
176
+ :zip_passwords_file => String,
177
+ :default_user => String
178
+ end # class General
179
+
180
+ # Top-Level Config
181
+ class Top < Base
182
+ define_syntax :general => General
183
+ end # class Top
184
+
185
+ def self.create_from_file(file_name)
186
+ unless File.exists?(File.expand_path(file_name))
187
+ raise Glima::ConfigurationError, "config file '#{file_name}' not found"
188
+ end
189
+ begin
190
+ return Top.create_from_yaml_file(file_name)
191
+ rescue Psych::SyntaxError => e
192
+ raise Glima::ConfigurationError, e.message
193
+ end
194
+ end
195
+
196
+ def self.create_from_string(string)
197
+ begin
198
+ return Top.create_from_yaml_string(string)
199
+ rescue Psych::SyntaxError => e
200
+ raise Glima::ConfigurationError, e.message
201
+ end
202
+ end
203
+
204
+ end # class Config
205
+ end # module Glima