From f52efae271ddb376e7223405c3a74edd7ed598b9 Mon Sep 17 00:00:00 2001 From: Bruno Wagner Goncalves Date: Fri, 1 Aug 2014 17:49:13 -0300 Subject: Changed names according to new convention on the fake service --- fake-service/Rakefile | 5 +- fake-service/config.ru | 4 +- .../features/compose_save_draft_and_send.feature | 5 +- fake-service/lib/generator.rb | 8 +- fake-service/lib/pixelated_service.rb | 27 ++ fake-service/lib/pixelated_service/all.rb | 8 + .../lib/pixelated_service/combined_observer.rb | 23 ++ fake-service/lib/pixelated_service/contact.rb | 26 ++ fake-service/lib/pixelated_service/contacts.rb | 84 ++++++ .../lib/pixelated_service/contacts_observer.rb | 54 ++++ .../lib/pixelated_service/contacts_search.rb | 71 +++++ .../lib/pixelated_service/contacts_sorter.rb | 8 + fake-service/lib/pixelated_service/fake.rb | 146 ++++++++++ fake-service/lib/pixelated_service/mail.rb | 299 +++++++++++++++++++++ .../lib/pixelated_service/mail_scope_filter.rb | 70 +++++ fake-service/lib/pixelated_service/mail_service.rb | 152 +++++++++++ fake-service/lib/pixelated_service/mailset.rb | 84 ++++++ fake-service/lib/pixelated_service/paginate.rb | 15 ++ .../lib/pixelated_service/paginated_enumerable.rb | 29 ++ fake-service/lib/pixelated_service/persona.rb | 12 + fake-service/lib/pixelated_service/search.rb | 133 +++++++++ .../lib/pixelated_service/search/and_match.rb | 25 ++ .../lib/pixelated_service/search/negate_match.rb | 22 ++ .../lib/pixelated_service/search/or_match.rb | 25 ++ .../lib/pixelated_service/search/scope_match.rb | 79 ++++++ .../lib/pixelated_service/search/string_match.rb | 37 +++ .../lib/pixelated_service/search/true_match.rb | 13 + .../lib/pixelated_service/security_casing.rb | 55 ++++ .../pixelated_service/security_casing_examples.rb | 142 ++++++++++ fake-service/lib/pixelated_service/server.rb | 82 ++++++ fake-service/lib/pixelated_service/sorted_mail.rb | 57 ++++ fake-service/lib/pixelated_service/stats.rb | 60 +++++ .../lib/pixelated_service/stats_observer.rb | 19 ++ fake-service/lib/pixelated_service/tags.rb | 150 +++++++++++ fake-service/lib/smail.rb | 27 -- fake-service/lib/smail/all.rb | 8 - fake-service/lib/smail/combined_observer.rb | 23 -- fake-service/lib/smail/contact.rb | 26 -- fake-service/lib/smail/contacts.rb | 84 ------ fake-service/lib/smail/contacts_observer.rb | 54 ---- fake-service/lib/smail/contacts_search.rb | 71 ----- fake-service/lib/smail/contacts_sorter.rb | 8 - fake-service/lib/smail/fake.rb | 146 ---------- fake-service/lib/smail/mail.rb | 299 --------------------- fake-service/lib/smail/mail_scope_filter.rb | 70 ----- fake-service/lib/smail/mail_service.rb | 152 ----------- fake-service/lib/smail/mailset.rb | 84 ------ fake-service/lib/smail/paginate.rb | 15 -- fake-service/lib/smail/paginated_enumerable.rb | 29 -- fake-service/lib/smail/persona.rb | 12 - fake-service/lib/smail/search.rb | 133 --------- fake-service/lib/smail/search/and_match.rb | 25 -- fake-service/lib/smail/search/negate_match.rb | 22 -- fake-service/lib/smail/search/or_match.rb | 25 -- fake-service/lib/smail/search/scope_match.rb | 79 ------ fake-service/lib/smail/search/string_match.rb | 37 --- fake-service/lib/smail/search/true_match.rb | 13 - fake-service/lib/smail/security_casing.rb | 55 ---- fake-service/lib/smail/security_casing_examples.rb | 142 ---------- fake-service/lib/smail/server.rb | 82 ------ fake-service/lib/smail/sorted_mail.rb | 57 ---- fake-service/lib/smail/stats.rb | 60 ----- fake-service/lib/smail/stats_observer.rb | 19 -- fake-service/lib/smail/tags.rb | 150 ----------- fake-service/spec/smail/mail_spec.rb | 10 +- fake-service/spec/spec_helper.rb | 2 +- 66 files changed, 2023 insertions(+), 2025 deletions(-) create mode 100644 fake-service/lib/pixelated_service.rb create mode 100644 fake-service/lib/pixelated_service/all.rb create mode 100644 fake-service/lib/pixelated_service/combined_observer.rb create mode 100644 fake-service/lib/pixelated_service/contact.rb create mode 100644 fake-service/lib/pixelated_service/contacts.rb create mode 100644 fake-service/lib/pixelated_service/contacts_observer.rb create mode 100644 fake-service/lib/pixelated_service/contacts_search.rb create mode 100644 fake-service/lib/pixelated_service/contacts_sorter.rb create mode 100644 fake-service/lib/pixelated_service/fake.rb create mode 100644 fake-service/lib/pixelated_service/mail.rb create mode 100644 fake-service/lib/pixelated_service/mail_scope_filter.rb create mode 100644 fake-service/lib/pixelated_service/mail_service.rb create mode 100644 fake-service/lib/pixelated_service/mailset.rb create mode 100644 fake-service/lib/pixelated_service/paginate.rb create mode 100644 fake-service/lib/pixelated_service/paginated_enumerable.rb create mode 100644 fake-service/lib/pixelated_service/persona.rb create mode 100644 fake-service/lib/pixelated_service/search.rb create mode 100644 fake-service/lib/pixelated_service/search/and_match.rb create mode 100644 fake-service/lib/pixelated_service/search/negate_match.rb create mode 100644 fake-service/lib/pixelated_service/search/or_match.rb create mode 100644 fake-service/lib/pixelated_service/search/scope_match.rb create mode 100644 fake-service/lib/pixelated_service/search/string_match.rb create mode 100644 fake-service/lib/pixelated_service/search/true_match.rb create mode 100644 fake-service/lib/pixelated_service/security_casing.rb create mode 100644 fake-service/lib/pixelated_service/security_casing_examples.rb create mode 100644 fake-service/lib/pixelated_service/server.rb create mode 100644 fake-service/lib/pixelated_service/sorted_mail.rb create mode 100644 fake-service/lib/pixelated_service/stats.rb create mode 100644 fake-service/lib/pixelated_service/stats_observer.rb create mode 100644 fake-service/lib/pixelated_service/tags.rb delete mode 100644 fake-service/lib/smail.rb delete mode 100644 fake-service/lib/smail/all.rb delete mode 100644 fake-service/lib/smail/combined_observer.rb delete mode 100644 fake-service/lib/smail/contact.rb delete mode 100644 fake-service/lib/smail/contacts.rb delete mode 100644 fake-service/lib/smail/contacts_observer.rb delete mode 100644 fake-service/lib/smail/contacts_search.rb delete mode 100644 fake-service/lib/smail/contacts_sorter.rb delete mode 100644 fake-service/lib/smail/fake.rb delete mode 100644 fake-service/lib/smail/mail.rb delete mode 100644 fake-service/lib/smail/mail_scope_filter.rb delete mode 100644 fake-service/lib/smail/mail_service.rb delete mode 100644 fake-service/lib/smail/mailset.rb delete mode 100644 fake-service/lib/smail/paginate.rb delete mode 100644 fake-service/lib/smail/paginated_enumerable.rb delete mode 100644 fake-service/lib/smail/persona.rb delete mode 100644 fake-service/lib/smail/search.rb delete mode 100644 fake-service/lib/smail/search/and_match.rb delete mode 100644 fake-service/lib/smail/search/negate_match.rb delete mode 100644 fake-service/lib/smail/search/or_match.rb delete mode 100644 fake-service/lib/smail/search/scope_match.rb delete mode 100644 fake-service/lib/smail/search/string_match.rb delete mode 100644 fake-service/lib/smail/search/true_match.rb delete mode 100644 fake-service/lib/smail/security_casing.rb delete mode 100644 fake-service/lib/smail/security_casing_examples.rb delete mode 100644 fake-service/lib/smail/server.rb delete mode 100644 fake-service/lib/smail/sorted_mail.rb delete mode 100644 fake-service/lib/smail/stats.rb delete mode 100644 fake-service/lib/smail/stats_observer.rb delete mode 100644 fake-service/lib/smail/tags.rb (limited to 'fake-service') diff --git a/fake-service/Rakefile b/fake-service/Rakefile index 3c936d1f..8b76359c 100644 --- a/fake-service/Rakefile +++ b/fake-service/Rakefile @@ -2,7 +2,7 @@ $: << "lib" require 'rubygems' require 'rakeup' -require 'smail' +require 'pixelated_service' require 'rake/packagetask' RakeUp::ServerTask.new do |t| @@ -38,7 +38,7 @@ namespace :mailset do num = (args[:num] || "10").to_i tagging = args[:with_tagging] == "true" - ms = Smail::Mailset.create name, num, tagging + ms = PixelatedService::Mailset.create name, num, tagging ms.save! end end @@ -49,4 +49,3 @@ Rake::PackageTask.new("archive", :noversion) do |p| p.package_dir = "artifacts" p.package_files.include("**/*") end - diff --git a/fake-service/config.ru b/fake-service/config.ru index cadabecc..3d9b7c4d 100644 --- a/fake-service/config.ru +++ b/fake-service/config.ru @@ -1,4 +1,4 @@ $: << "lib" -require 'smail' -run Smail::Server +require 'pixelated_service' +run PixelatedService::Server diff --git a/fake-service/features/compose_save_draft_and_send.feature b/fake-service/features/compose_save_draft_and_send.feature index d05db837..fcff4a95 100644 --- a/fake-service/features/compose_save_draft_and_send.feature +++ b/fake-service/features/compose_save_draft_and_send.feature @@ -4,12 +4,11 @@ Feature: compose mail, save draft and send mail Scenario: user composes and email, save the draft, later sends the draft and checks the sent message Given I compose a message with | subject | body | - | Smail rocks! | You should definitely use it. Cheers, User. | + | Pixelated rocks! | You should definitely use it. Cheers, User. | And for the 'To' field I type 'ab' and chose the first contact that shows And I save the draft When I open the saved draft and send it Then I see that mail under the 'sent' tag When I open that mail - Then I see that the subject reads 'Smail rocks!' + Then I see that the subject reads 'Pixelated rocks!' And I see that the body reads 'You should definitely use it. Cheers, User.' - diff --git a/fake-service/lib/generator.rb b/fake-service/lib/generator.rb index 35518844..83c1b2b2 100644 --- a/fake-service/lib/generator.rb +++ b/fake-service/lib/generator.rb @@ -59,7 +59,7 @@ module Generator def random_mail hdr = Mail.random_header bd = Mail.random_body - Smail::Mail.new( + PixelatedService::Mail.new( from: hdr[:from], to: hdr[:to], subject: hdr[:subject], @@ -71,10 +71,10 @@ module Generator hdr = Mail.random_header bd = Mail.random_body tgs = choose(ladder_distribution(1, 5)).times.map { tagset.sample }.uniq - special_tag = ([nil, nil, nil, nil, nil, nil] + Smail::Tags::SPECIAL).sample + special_tag = ([nil, nil, nil, nil, nil, nil] + PixelatedService::Tags::SPECIAL).sample status = [] status << :read if special_tag == 'sent' - mail = Smail::Mail.new( + mail = PixelatedService::Mail.new( from: hdr[:from], to: hdr[:to], subject: hdr[:subject], @@ -86,7 +86,7 @@ module Generator end def random_persona - Smail::Persona.new(Faker::Number.number(10), + PixelatedService::Persona.new(Faker::Number.number(10), Faker::Name.name, Faker::Lorem.sentence, Faker::Internet.email) diff --git a/fake-service/lib/pixelated_service.rb b/fake-service/lib/pixelated_service.rb new file mode 100644 index 00000000..e63649d9 --- /dev/null +++ b/fake-service/lib/pixelated_service.rb @@ -0,0 +1,27 @@ + +module PixelatedService +end + +require 'generator' +require 'pixelated_service/security_casing' +require 'pixelated_service/security_casing_examples' +require 'pixelated_service/stats' +require 'pixelated_service/stats_observer' +require 'pixelated_service/sorted_mail' +require 'pixelated_service/mail' +require 'pixelated_service/persona' +require 'pixelated_service/mail_service' +require 'pixelated_service/fake' +require 'pixelated_service/mailset' +require 'pixelated_service/server' +require 'pixelated_service/paginate' +require 'pixelated_service/all' +require 'pixelated_service/search' +require 'pixelated_service/tags' +require 'pixelated_service/combined_observer' +require 'pixelated_service/contacts_observer' +require 'pixelated_service/contact' +require 'pixelated_service/contacts' +require 'pixelated_service/contacts_sorter' +require 'pixelated_service/contacts_search' +require 'pixelated_service/mail_scope_filter' diff --git a/fake-service/lib/pixelated_service/all.rb b/fake-service/lib/pixelated_service/all.rb new file mode 100644 index 00000000..c2e97528 --- /dev/null +++ b/fake-service/lib/pixelated_service/all.rb @@ -0,0 +1,8 @@ + +module PixelatedService + class All + def restrict(input) + input + end + end +end diff --git a/fake-service/lib/pixelated_service/combined_observer.rb b/fake-service/lib/pixelated_service/combined_observer.rb new file mode 100644 index 00000000..a300f6e6 --- /dev/null +++ b/fake-service/lib/pixelated_service/combined_observer.rb @@ -0,0 +1,23 @@ +module PixelatedService + class CombinedObserver + def initialize(*observers) + @observers = observers + end + + def <<(observer) + @observers << observer + end + + def mail_added(mail) + @observers.each { |o| o.mail_added(mail) } + end + + def mail_removed(mail) + @observers.each { |o| o.mail_removed(mail) } + end + + def mail_updated(before, after) + @observers.each { |o| o.mail_updated(before, after) } + end + end +end diff --git a/fake-service/lib/pixelated_service/contact.rb b/fake-service/lib/pixelated_service/contact.rb new file mode 100644 index 00000000..e1c86779 --- /dev/null +++ b/fake-service/lib/pixelated_service/contact.rb @@ -0,0 +1,26 @@ + +module PixelatedService + class Contact < Struct.new(:ident, :name, :addresses, :mails_received, :mails_sent, :last_received, :last_sent, :prev, :next) + include Comparable + + def to_json(*args) + { + ident: self.ident, + name: self.name, + addresses: Array(self.addresses), + mails_received: self.mails_received || 0, + mails_sent: self.mails_sent || 0, + last_received: self.last_received, + last_sent: self.last_sent + }.to_json(*args) + end + + def comparison_value + [(self.mails_received || 0) + (self.mails_sent || 0) * 0.8, self.last_received, self.last_sent] + end + + def <=>(other) + other.comparison_value <=> self.comparison_value + end + end +end diff --git a/fake-service/lib/pixelated_service/contacts.rb b/fake-service/lib/pixelated_service/contacts.rb new file mode 100644 index 00000000..2f57387d --- /dev/null +++ b/fake-service/lib/pixelated_service/contacts.rb @@ -0,0 +1,84 @@ +require 'mail' +require 'set' + +module PixelatedService + class Contacts + include Enumerable + + def initialize(persona) + @persona = persona + @contacts = nil + @contacts_cache = {} + @contacts_lookup = {} + end + + def contact(ix) + @contacts_lookup[ix] + end + + def each + curr = @contacts + while curr + yield curr + curr = curr.next + end + end + + def normalize(addr) + addr.downcase + end + + def parse(a) + ::Mail::Address.new(a) + end + + def update c, addr + @contacts_cache[normalize(addr.address)] = c + c.name = addr.display_name if addr.display_name + (c.addresses ||= Set.new) << addr.address + end + + def create_new_contact(addr) + old_first = @contacts + c = Contact.new + c.ident = addr.hash.abs.to_s + c.next = old_first + @contacts_lookup[c.ident] = c + @contacts = c + update c, addr + c + end + + def find_or_create(addr) + parsed = parse(addr) + if cc = @contacts_cache[normalize(parsed.address)] + update cc, parsed + cc + else + create_new_contact(parsed) + end + end + + def latest(prev, n) + if prev && prev > n + prev + else + n + end + end + + def new_mail_from(a, t) + contact = find_or_create(a) + contact.last_received = latest(contact.last_received, t) + contact.mails_received ||= 0 + contact.mails_received += 1 + end + + def new_mail_to(a, t) + contact = find_or_create(a) + contact.last_sent = latest(contact.last_sent, t) + contact.mails_sent ||= 0 + contact.mails_sent += 1 + end + end +end diff --git a/fake-service/lib/pixelated_service/contacts_observer.rb b/fake-service/lib/pixelated_service/contacts_observer.rb new file mode 100644 index 00000000..8f60ac81 --- /dev/null +++ b/fake-service/lib/pixelated_service/contacts_observer.rb @@ -0,0 +1,54 @@ +module PixelatedService + class ContactsObserver + def initialize(contacts) + @contacts = contacts + end + + def extract_addresses(*addrs) + addrs.flatten.compact + end + + def all_receivers(mail, &block) + extract_addresses(mail.to, mail.cc, mail.bcc).each(&block) + end + + def all_senders(mail, &block) + extract_addresses(mail.from).each(&block) + end + + def new_receivers(before, after, &block) + (extract_addresses(after.to, after.cc, after.bcc) - extract_addresses(before.to, before.cc, before.bcc)).each(&block) + end + + def new_senders(before, after, &block) + (extract_addresses(after.from) - extract_addresses(before.from)).each(&block) + end + + def timestamp_from(mail) + mail.headers[:date] + end + + def mail_added(mail) + timestamp = timestamp_from(mail) + all_receivers(mail) do |rcv| + @contacts.new_mail_to(rcv, timestamp) + end + all_senders(mail) do |s| + @contacts.new_mail_from(s, timestamp) + end + end + + def mail_removed(mail) + end + + def mail_updated(before, after) + timestamp = timestamp_from(after) + new_receivers(before, after) do |rcv| + @contacts.new_mail_to(rcv, timestamp) + end + new_senders(before, after) do |s| + @contacts.new_mail_from(s, timestamp) + end + end + end +end diff --git a/fake-service/lib/pixelated_service/contacts_search.rb b/fake-service/lib/pixelated_service/contacts_search.rb new file mode 100644 index 00000000..f7b24ca3 --- /dev/null +++ b/fake-service/lib/pixelated_service/contacts_search.rb @@ -0,0 +1,71 @@ + +# Very simple search for contacts. The search string will be something that will be prefix matched +# using a boundary before but not after. If you put in more than one word, those two will be searched +# and ANDed together. You can use double quotes or single quotes to do the obvious thing instead +module PixelatedService + class ContactsSearch + def initialize(q) + @qtree = ContactsSearch.compile(q) + end + + def restrict(input) + input.select do |mm| + @qtree.match?(mm) + end + end + + REGEXP_DQUOTED = /"[^"]*"/ + REGEXP_SQUOTED = /'[^']*'/ + REGEXP_OTHER = /[^\s]+/ + + class AndMatch + attr_reader :data + def initialize(data = []) + @data = data + end + def <<(node) + @data << node + end + def match?(c) + self.data.all? { |mm| mm.match?(c) } + end + end + + class StringMatch + def initialize(data, quoted=false) + @data = Regexp.new(Regexp.quote(if quoted + data[1..-2] + else + data + end), Regexp::IGNORECASE) + @exact_match = /\b#{@data}/ + end + + def match_string?(str) + Array(str).any? { |ff| @exact_match.match ff } + end + + def match?(c) + match_string? ([c.name] + c.addresses.to_a).compact + end + end + + def self.compile(q) + qs = StringScanner.new(q) + qtree = AndMatch.new + until qs.eos? + res = + if qs.check(REGEXP_DQUOTED) + StringMatch.new(qs.scan(REGEXP_DQUOTED), true) + elsif qs.check(REGEXP_SQUOTED) + StringMatch.new(qs.scan(REGEXP_SQUOTED), true) + elsif qs.check(REGEXP_OTHER) + StringMatch.new(qs.scan(REGEXP_OTHER)) + end + qtree << res + qs.scan(/\s+/) + end + qtree + end + end +end diff --git a/fake-service/lib/pixelated_service/contacts_sorter.rb b/fake-service/lib/pixelated_service/contacts_sorter.rb new file mode 100644 index 00000000..e559205d --- /dev/null +++ b/fake-service/lib/pixelated_service/contacts_sorter.rb @@ -0,0 +1,8 @@ + +module PixelatedService + class ContactsSorter + def restrict(input) + input.sort + end + end +end diff --git a/fake-service/lib/pixelated_service/fake.rb b/fake-service/lib/pixelated_service/fake.rb new file mode 100644 index 00000000..48709096 --- /dev/null +++ b/fake-service/lib/pixelated_service/fake.rb @@ -0,0 +1,146 @@ +module PixelatedService + class << self + def mail_service + @mail_service ||= MailService.new + end + end + + module Fake + PERSONAS = [ + Persona.new(1, "Yago Macedo", nil, "sirineu@souza.org") + ] + + def personas + PERSONAS.map(&:ident) + end + + def persona(i) + PERSONAS.select { |x| x.ident.to_s == i}.first + end + + def mails(query, page_number, window_size) + with_timing do + stats, mails = PixelatedService.mail_service.mails(query, page_number, window_size) + { stats: stats, mails: mails.to_a } + end + end + + def contacts(query, page_number, window_size) + with_timing do + contacts = PixelatedService.mail_service.contacts(query, page_number, window_size) + { contacts: contacts.to_a } + end + end + + def contact(ix) + PixelatedService.mail_service.contact(ix) + end + + def delete_mails(query, page_number, window_size, mails_idents) + idents = mails_idents.gsub(/[\[\]]/, '').split(',').collect {|x| x.to_i} + PixelatedService.mail_service.delete_mails(query, page_number, window_size, idents) + [] + end + + def mail(i) + PixelatedService.mail_service.mail(i) + end + + def send_mail(data) + PixelatedService.mail_service.send_mail(data) + end + + def update_mail(data) + PixelatedService.mail_service.update_mail(data) + end + + def delete_mail(i) + PixelatedService.mail_service.delete_mail(i) + end + + def draft_reply_for(i) + PixelatedService.mail_service.draft_reply_for(i) + end + + def tags(i) + PixelatedService.mail_service.mail(i).tag_names + end + + def create_tag(tag_json) + PixelatedService.mail_service.create_tag tag_json + end + + def all_tags(q) + PixelatedService.mail_service.tags(q) + end + + def settags(i, body) + m = PixelatedService.mail_service.mail(i) + m.tag_names = body["newtags"] + m.tag_names + end + + def starmail(i, val) + m = PixelatedService.mail_service.mail(i) + m.starred = val if m + "" + end + + def repliedmail(i, val) + m = PixelatedService.mail_service.mail(i) + m.replied = val if m + "" + end + + def readmail(i, val) + m = PixelatedService.mail_service.mail(i) + m.read = val if m + "" + end + + def readmails(mail_idents, val) + idents = mail_idents.gsub(/[\[\]]/, '').split(',').collect {|x| x.to_i} + PixelatedService.mail_service.each { |k,v| readmail(k.ident, val) if idents.include?(k.ident) } + [] + end + + def control_create_mail + PixelatedService.mail_service.create + "" + end + + def control_delete_mails + PixelatedService.mail_service.clean + "" + end + + def control_mailset_load(name) + with_timing do + { + stats: PixelatedService.mail_service.load_mailset(name), + loaded: name + } + end + end + + def stats + PixelatedService.mail_service.stats_report + end + + def with_timing + before = Time.now + result = yield + after = Time.now + res = case result + when Hash + result.dup + when nil + {} + else + { result: result } + end + res[:timing] = { duration: after - before } + res + end + end +end diff --git a/fake-service/lib/pixelated_service/mail.rb b/fake-service/lib/pixelated_service/mail.rb new file mode 100644 index 00000000..29ae5067 --- /dev/null +++ b/fake-service/lib/pixelated_service/mail.rb @@ -0,0 +1,299 @@ +module PixelatedService + class Mail + attr_reader :to, :cc, :bcc, :from, :subject, :body, :headers, :status, :draft_reply_for + attr_accessor :ident, :security_casing + + def initialize(data = {}) + @ident = data[:ident] + @to = data[:to] + @cc = data[:cc] + @bcc = data[:bcc] + @from = data[:from] + @subject = data[:subject] + @body = data[:body] + @headers = data[:headers] || {} + @status = data[:status] || [] + @draft_reply_for = data[:draft_reply_for] || [] + self.tags = data[:tags] || Tags.new + end + + def hash + @ident.hash + end + + def eql?(object) + self == object + end + + def tags=(t) + @tags.mail = nil if @tags + @tags = t + @tags.mail = self if @tags + t + end + + def ==(object) + @ident == object.ident + end + + def tag_names + @tags.names + end + + def is_tagged?(t) + @tags.is_tagged?(t) + end + + def tag_names=(vs) + to_remove = self.tag_names - vs + to_add = vs - self.tag_names + + to_remove.each do |tn| + self.remove_tag(tn) + end + + to_add.each do |v| + self.add_tag(v) + end + end + + def add_tag(nm) + @tags.add_tag(nm) + end + + def remove_tag(nm) + @tags.remove(nm) + end + + def has_trash_tag? + tag_names.include? "trash" + end + + def starred=(v); v ? add_status(:starred) : remove_status(:starred); end + def starred?; status?(:starred); end + def read=(v); v ? add_status(:read) : remove_status(:read); end + def read?; status?(:read); end + def replied=(v); v ? add_status(:replied) : remove_status(:replied); end + def replied?; status?(:replied); end + + def add_status(n) + unless self.status?(n) + @status = @status + [n] + @tags.added_status(n) + PixelatedService.mail_service.stats_status_added(n, self) + end + end + + def remove_status(n) + if self.status?(n) + @status = @status - [n] + @tags.removed_status(n) + PixelatedService.mail_service.stats_status_removed(n, self) + end + end + + def status?(n) + @status.include?(n) + end + + def to_json(*args) + { + header: { + to: Array(@to), + from: @from, + subject: @subject, + }.merge(@headers).merge({date: @headers[:date].iso8601}), + ident: ident, + tags: @tags.names, + status: @status, + security_casing: @security_casing, + draft_reply_for: @draft_reply_for, + body: @body + }.to_json(*args) + end + + + def self.from_json(obj, new_ident = nil) + ident = obj['ident'] + draft_reply_for = obj['draft_reply_for'] + hdrs = obj['header'] + to = hdrs['to'] + cc = hdrs['cc'] + bcc = hdrs['bcc'] + from = hdrs['from'] + subject = hdrs['subject'] + new_hdrs = {} + hdrs.each do |k,v| + new_hdrs[k.to_sym] = v unless %w(from to subject).include?(k) + end + tag_names = obj['tags'] + st = obj['status'] + bd = obj['body'] + + mail = new(:subject => subject, + :from => from, + :to => Array(to), + :cc => Array(cc), + :bcc => Array(bcc), + :headers => new_hdrs, + :status => st, + :draft_reply_for => draft_reply_for, + :ident => (ident.to_s.empty? ? new_ident : ident), + :body => bd) + + tag_names.each do |tag_name| + mail.add_tag tag_name + end + + mail + + end + + def to_s + ([ + ("#{INTERNAL_TO_EXTERNAL_HEADER[:to]}: #{format_header_value_out(:to, @to)}" if @to), + ("#{INTERNAL_TO_EXTERNAL_HEADER[:from]}: #{format_header_value_out(:from, @from)}" if @from), + ("#{INTERNAL_TO_EXTERNAL_HEADER[:subject]}: #{format_header_value_out(:subject, @subject)}" if @subject), + ("#{INTERNAL_TO_EXTERNAL_HEADER[:x_tw_smail_tags]}: #{format_header_value_out(:x_tw_smail_tags, @tags.names)}" if !@tags.names.empty?), + ("#{INTERNAL_TO_EXTERNAL_HEADER[:x_tw_smail_status]}: #{format_header_value_out(:x_tw_smail_status, @status)}" if !@status.empty?), + ("#{INTERNAL_TO_EXTERNAL_HEADER[:x_tw_smail_ident]}: #{format_header_value_out(:x_tw_smail_ident, @ident)}"), + ].compact + @headers.map { |k,v| "#{INTERNAL_TO_EXTERNAL_HEADER[k]}: #{format_header_value_out(k, v)}"}).sort.join("\n") + "\n\n#{@body}" + end + + SPECIAL_HEADERS = [:subject, :from, :to, :x_tw_smail_tags, :x_tw_smail_status, :x_tw_smail_ident] + INTERNAL_TO_EXTERNAL_HEADER = { + :subject => "Subject", + :date => "Date", + :from => "From", + :to => "To", + :cc => "CC", + :bcc => "BCC", + :message_id => "Message-ID", + :mime_version => "Mime-Version", + :content_type => "Content-Type", + :content_transfer_encoding => "Content-Transfer-Encoding", + :x_tw_smail_tags => "X-TW-SMail-Tags", + :x_tw_smail_status => "X-TW-SMail-Status", + :x_tw_smail_ident => "X-TW-SMail-Ident", + } + + def format_header_value_out(k,v) + case k + when :date + v.strftime("%a, %d %b %Y %H:%M:%S %z") + when :to, :cc, :bcc + Array(v).join(", ") + when :x_tw_smail_tags, :x_tw_smail_status + v.join(", ") + else + v + end + end + + class << self + def formatted_header(k, ls) + format_header_value(k, ls[k] && ls[k][1]) + end + + def has_header(hdr_name, ls, val, otherwise) + if ls[hdr_name] + val + else + otherwise + end + end + + def time_rand from = (Time.now - 300000000), to = Time.now + Time.at(from + rand * (to.to_f - from.to_f)) + end + + # io is String or IO + def read(io, ident = nil) + io = StringIO.new(io) if io.is_a? String + headers = {} + body = "" + reading_headers = true + previous_header = nil + first = true + io.each do |ln| + if first && ln =~ /^From / + # Ignore line delimiter things + else + if reading_headers + if ln.chomp == "" + reading_headers = false + else + if previous_header && ln =~ /^\s+/ + previous_header[1] << " #{ln.strip}" + else + key, value = ln.chomp.split(/: /, 2) + previous_header = [key, value] + headers[internal_header_name(key)] = previous_header + end + end + else + body << ln + end + end + if first + first = false + end + end + + header_data = {} + headers.each do |k, (_, v)| + unless special_header?(k) + header_data[k] = format_header_value(k, v) + end + end + + unless header_data[:date] + header_data[:date] = time_rand + end + + + new(:subject => formatted_header(:subject, headers), + :from => formatted_header(:from, headers), + :to => formatted_header(:to, headers), + :headers => header_data, + :tags => formatted_header(:x_tw_smail_tags, headers), + :status => formatted_header(:x_tw_smail_status, headers), + :ident => has_header(:x_tw_smail_ident, headers, formatted_header(:x_tw_smail_ident, headers), ident), + :body => body + ) + end + + + private + def internal_header_name(k) + k.downcase.gsub(/-/, '_').to_sym + end + + def special_header?(k) + SPECIAL_HEADERS.include?(k) + end + + def format_header_value(k, v) + case k + when :date + DateTime.parse(v) + when :to, :cc, :bcc + vs = (v || "").split(/, /) + if vs.length == 1 + vs[0] + else + vs + end + when :x_tw_smail_tags + Tags.new *(v || "").split(/, /) + when :x_tw_smail_status + (v || "").split(/, /).map { |ss| ss.to_sym } + when :x_tw_smail_ident + v.to_i + else + v + end + end + end + end +end diff --git a/fake-service/lib/pixelated_service/mail_scope_filter.rb b/fake-service/lib/pixelated_service/mail_scope_filter.rb new file mode 100644 index 00000000..dae6d99e --- /dev/null +++ b/fake-service/lib/pixelated_service/mail_scope_filter.rb @@ -0,0 +1,70 @@ +module PixelatedService + module MailScopeFilter + include Enumerable + + def initialize(c) + @c = c + end + + def each + @c.each do |m| + yield m if retain?(m) + end + end + + class Default + include MailScopeFilter + + def initialize(c) + super + @tags = [Tags.get('sent'), Tags.get('trash'), Tags.get('drafts')] + end + + def retain?(m) + !(@tags.any? { |t| m.is_tagged?(t) }) + end + + class << self + def +(o) + o + end + end + end + + class All + include MailScopeFilter + + def initialize(c) + super + @t = Tags.get('trash') + end + + def retain?(m) + !m.is_tagged?(@t) + end + + class << self + def +(o) + All + end + end + end + + def self.tagged_with(n) + t = Tags.get(n) + c = Class.new + c.send :include, MailScopeFilter + c.send :define_method, :retain? do |m| + m.is_tagged?(t) + end + c.class.send :define_method, :+ do |o| + All === o ? All : self + end + c + end + + Trash = tagged_with('trash') + Sent = tagged_with('sent') + Drafts = tagged_with('drafts') + end +end diff --git a/fake-service/lib/pixelated_service/mail_service.rb b/fake-service/lib/pixelated_service/mail_service.rb new file mode 100644 index 00000000..02e36835 --- /dev/null +++ b/fake-service/lib/pixelated_service/mail_service.rb @@ -0,0 +1,152 @@ +require 'set' + +module PixelatedService + class MailService + include Enumerable + include PixelatedService::Stats + + def each + @mails.each do |mo| + yield mo + end + end + + def initialize + self.clean + end + + def contact(ix) + @contacts.contact(ix) + end + + def contacts + @contacts.to_a + end + + def clean + PixelatedService::Tags.clean + @next_ident = 0 + @reply_drafts = {} + @mails = SortedMail.new + @contacts = Contacts.new(Fake::PERSONAS[0]) + @observers = CombinedObserver.new(StatsObserver.new(self), + ContactsObserver.new(@contacts)) + end + + def create(mail=Generator.random_mail) + unless mail.ident + mail.ident = @next_ident + @next_ident += 1 + end + @mails[mail.ident] = mail + @observers.mail_added(mail) + end + + def create_tag(tag_json) + PixelatedService::Tags.create_tag tag_json['tag'] + end + + def mail(ix) + @mails[ix.to_i] + end + + def send_mail(data) + ms = Mail.from_json(data, @next_ident) + if ms.tag_names.include?("sent") + ms.remove_tag "drafts" + @reply_drafts.delete ms.draft_reply_for + elsif ms.tag_names.include?("drafts") and ms.draft_reply_for + @reply_drafts[ms.draft_reply_for] = ms.ident + end + @next_ident += 1 + @mails[ms.ident] = ms + update_status ms + @observers.mail_added(ms) + ms.ident + end + + def draft_reply_for(ident) + @mails[@reply_drafts[ident.to_i]] + end + + def update_mail(data) + mail = Mail.from_json(data) + before = @mails[mail.ident] + @mails[mail.ident] = mail + update_status mail + @observers.mail_updated(before, mail) + before.tags = nil + mail.ident + end + + def update_status(mail) + mail.read = true + mail.headers[:date] = Time.now + end + + def tags(q) + if q && !q.strip.empty? + query = /\b#{Regexp.new(Regexp.quote(q), Regexp::IGNORECASE)}/ + PixelatedService::Tags.all_tags.select do |tt| + query =~ tt.name + end + else + PixelatedService::Tags.all_tags + end + end + + def stats_report + { stats: self.stats } + end + + def delete_mail(ix) + ms = @mails[ix.to_i] + @reply_drafts.delete ms.draft_reply_for + + if ms.has_trash_tag? + m = @mails.delete ix.to_i + @observers.mail_removed(m) + m.tags = nil + else + ms.add_tag 'trash' + end + end + + def load_mailset(name) + self.clean + ms = PixelatedService::Mailset.load(name, @observers) + raise "couldn't find mailset #{name}" unless ms + @mails.add_all ms.mails + self.stats + end + + def mails(q, page, window_size) + restrictors = [All.new] + restrictors << Paginate.new(page, window_size) if window_size > 0 + restrictors << Search.new(q) + with_stats(restrictors.reverse.inject(self) do |ms, restr| + restr.restrict(ms) + end) + end + + def contacts(q, page, window_size) + restrictors = [All.new] + restrictors << ContactsSorter.new + restrictors << Paginate.new(page, window_size) if window_size > 0 + restrictors << ContactsSearch.new(q) if q + restrictors.reverse.inject(@contacts) do |c, restr| + restr.restrict(c) + end + end + + def delete_mails(q, page, window_size, idents=nil) + unless idents.nil? + @mails.each { |k,v| delete_mail(k.ident) if idents.include?(k.ident) } + else + mails(q, page, window_size).each do |m| + delete_mail m.ident + end + end + end + end +end diff --git a/fake-service/lib/pixelated_service/mailset.rb b/fake-service/lib/pixelated_service/mailset.rb new file mode 100644 index 00000000..595571e8 --- /dev/null +++ b/fake-service/lib/pixelated_service/mailset.rb @@ -0,0 +1,84 @@ +require 'set' + +module PixelatedService + class Mailset + DIR = File.expand_path File.join(File.dirname(__FILE__), "..", "..", "data", "mail-sets") + + class << self + def create(name, number, tagging) + ms = new name, number, tagging, nil + ms.generate! + ms + end + + def load(name, observers) + ms = new(name, -1, nil, observers) + if ms.load! + ms + else + nil + end + end + end + + attr_reader :mails + attr_reader :tags + + def initialize(name, number, tagging, observers) + @name, @number, @tagging, @observers = name, number, tagging, observers + end + + def generate! + @persona = Generator.random_persona + @tags = Generator.tags(Generator.ladder_distribution(4, 40)) + + @mails = {} + @tags = Set.new + (0...(@number)).each do |i| + res = if @tagging + Generator.random_tagged_mail(@tags) + else + Generator.random_mail + end + @observers.mail_added res + @tags.merge res.tags + res.ident = i + @mails[res.ident] = res + end + end + + def save! + dir = File.join(DIR, @name) + Dir.mkdir(dir) unless Dir.exists?(dir) + File.open(File.join(dir, "persona.yml"), "w") do |f| + f.write @persona.to_yaml + end + + @mails.each do |(k, m)| + nm = "mbox%08x" % m.ident + File.open(File.join(dir, nm), "w") do |f| + f.write m.to_s + end + end + end + + def load! + dir = File.join(DIR, @name) + return false unless Dir.exists?(dir) + @persona = YAML.load_file(File.join(dir, "persona.yml")) + @mails = {} + @ix = 0 + Dir["#{dir}/mbox*"].each do |f| + File.open(f) do |fio| + res = PixelatedService::Mail.read fio, @ix + res.read = true if (res.tag_names.include?('sent') || res.tag_names.include?('drafts')) + @mails[res.ident] = res + @observers.mail_added res + res.security_casing = SecurityCasingExamples::Case.case_from(res.ident.to_i) + @ix += 1 + end + end + true + end + end +end diff --git a/fake-service/lib/pixelated_service/paginate.rb b/fake-service/lib/pixelated_service/paginate.rb new file mode 100644 index 00000000..59d263d8 --- /dev/null +++ b/fake-service/lib/pixelated_service/paginate.rb @@ -0,0 +1,15 @@ + +module PixelatedService + class Paginate + def initialize(page, window_size) + @start = page * window_size + @end = (page + 1) * window_size + end + + def restrict(input) + PaginatedEnumerable.new(input, @start, @end) + end + end +end + +require 'pixelated_service/paginated_enumerable' diff --git a/fake-service/lib/pixelated_service/paginated_enumerable.rb b/fake-service/lib/pixelated_service/paginated_enumerable.rb new file mode 100644 index 00000000..b1045e2f --- /dev/null +++ b/fake-service/lib/pixelated_service/paginated_enumerable.rb @@ -0,0 +1,29 @@ + +module PixelatedService + class Paginate + class PaginatedEnumerable + include Enumerable + def initialize(input, start, e) + @input = input + @start = start + @end = e + end + + def each + @input.each_with_index do |v, ix| + if ix >= @end + return #we are done + elsif ix >= @start + yield v + end + end + end + + def each_total + @input.each do |v| + yield v + end + end + end + end +end diff --git a/fake-service/lib/pixelated_service/persona.rb b/fake-service/lib/pixelated_service/persona.rb new file mode 100644 index 00000000..c51974ed --- /dev/null +++ b/fake-service/lib/pixelated_service/persona.rb @@ -0,0 +1,12 @@ +module PixelatedService + class Persona < Struct.new :ident, :name, :signature, :address + def to_json + { + ident: self.ident, + name: self.name, + signature: self.signature, + address: self.address + }.to_json + end + end +end diff --git a/fake-service/lib/pixelated_service/search.rb b/fake-service/lib/pixelated_service/search.rb new file mode 100644 index 00000000..e8f9d9fd --- /dev/null +++ b/fake-service/lib/pixelated_service/search.rb @@ -0,0 +1,133 @@ +# Syntax notes for search: +# you can put a - in front of any search term to negate it +# you can scope a search by putting a name of a scope, a colon and then the search term WITHOUT a space. +# scoping will allow you to search for more things than otherwise available +# an unknown scope name will be assumed to be a header to search +# you can surround a search term in quotes to search for the whole thing +# multiple search terms will be ANDed together +# you can OR things by using the keyword OR/or - if you have it without parens, you will or the whole left with the whole right, until we find another or. +# if you use parenthesis, you can group together terms +# search in:_default_, in:all, in:trash, in:sent, in:drafts will only work for the WHOLE search. You can do a negation on a scoped search if it's in:trash, in:sent or in:drafts, but not for in:all + +module PixelatedService + class Search + def initialize(q) + if q + @qtree, @search_scope = Search.compile(q) + else + @qtree, @search_scope = TrueMatch.new, PixelatedService::MailScopeFilter::Default + end + end + + def restrict(input) + @search_scope.new(input).select do |mm| + @qtree.match?(mm) + end + end + + REGEXP_DQUOTED = /"[^"]*"/ + REGEXP_SQUOTED = /'[^']*'/ + REGEXP_SCOPE = /\w+:(".*?"|'.*?'|[^\s\)]+)/ + REGEXP_OTHER = /[^\s\)]+/ + + def self.scan_literal(qs) + if qs.check(REGEXP_DQUOTED) + StringMatch.new(qs.scan(REGEXP_DQUOTED), true) + elsif qs.check(REGEXP_SQUOTED) + StringMatch.new(qs.scan(REGEXP_SQUOTED), true) + elsif qs.check(REGEXP_OTHER) + StringMatch.new(qs.scan(REGEXP_OTHER)) + end + end + + def self.combine_search_scopes(l, r) + l + r + end + + def self.compile(q, qs = StringScanner.new(q)) + qtree = AndMatch.new + search_scope = PixelatedService::MailScopeFilter::Default + until qs.eos? + if qs.check(/\)/) + qs.scan(/\)/) + return optimized(qtree), search_scope + end + + negated = false + if qs.check(/-/) + negated = true + qs.scan(/-/) + end + + if qs.check(/or/i) + qs.scan(/or/i) + left = qtree + qtree = OrMatch.new(left, AndMatch.new) + else + res = + if qs.check(/\(/) + qs.scan(/\(/) + v, sc = compile(q, qs) + search_scope = search_scope + sc + v + elsif qs.check(REGEXP_DQUOTED) + StringMatch.new(qs.scan(REGEXP_DQUOTED), true) + elsif qs.check(REGEXP_SQUOTED) + StringMatch.new(qs.scan(REGEXP_SQUOTED), true) + elsif qs.check(REGEXP_SCOPE) + scope = qs.scan(/\w+/) + qs.scan(/:/) + rest_node = scan_literal(qs) + v = ScopeMatch.new(scope, rest_node) + if v.is_search_scope? && !negated + search_scope = search_scope + v.search_scope + TrueMatch.new + else + v + end + elsif qs.check(REGEXP_OTHER) + StringMatch.new(qs.scan(REGEXP_OTHER)) + end + res = NegateMatch.new(res) if negated + qtree << res + end + + qs.scan(/\s+/) + end + return optimized(qtree), search_scope + end + + def self.optimized(tree) + case tree + when AndMatch + data = tree.data.reject { |d| TrueMatch === d } + if data.length == 1 + optimized(data.first) + else + AndMatch.new(data.map { |n| optimized(n)} ) + end + when OrMatch + if tree.right.is_a?(AndMatch) && tree.right.data.empty? + optimized(tree.left) + else + OrMatch.new(optimized(tree.left), optimized(tree.right)) + end + when NegateMatch + if tree.data.is_a?(NegateMatch) + optimized(tree.data.data) + else + NegateMatch.new(optimized(tree.data)) + end + else + tree + end + end + end +end + +require 'pixelated_service/search/string_match' +require 'pixelated_service/search/scope_match' +require 'pixelated_service/search/negate_match' +require 'pixelated_service/search/and_match' +require 'pixelated_service/search/or_match' +require 'pixelated_service/search/true_match' diff --git a/fake-service/lib/pixelated_service/search/and_match.rb b/fake-service/lib/pixelated_service/search/and_match.rb new file mode 100644 index 00000000..dfd0f287 --- /dev/null +++ b/fake-service/lib/pixelated_service/search/and_match.rb @@ -0,0 +1,25 @@ +module PixelatedService + class Search + class AndMatch + attr_reader :data + def initialize(data = []) + @data = data + end + def <<(node) + @data << node + end + + def to_s + "And(#{@data.join(", ")})" + end + + def match?(mail) + self.data.all? { |mm| mm.match?(mail) } + end + + def match_string?(str) + self.data.all? { |mm| mm.match_string?(str) } + end + end + end +end diff --git a/fake-service/lib/pixelated_service/search/negate_match.rb b/fake-service/lib/pixelated_service/search/negate_match.rb new file mode 100644 index 00000000..77d880e8 --- /dev/null +++ b/fake-service/lib/pixelated_service/search/negate_match.rb @@ -0,0 +1,22 @@ +module PixelatedService + class Search + class NegateMatch + attr_reader :data + def initialize(data) + @data = data + end + + def to_s + "Negate(#@data)" + end + + def match?(mail) + !self.data.match?(mail) + end + + def match_string?(str) + !self.data.match_string?(str) + end + end + end +end diff --git a/fake-service/lib/pixelated_service/search/or_match.rb b/fake-service/lib/pixelated_service/search/or_match.rb new file mode 100644 index 00000000..2c3e50ef --- /dev/null +++ b/fake-service/lib/pixelated_service/search/or_match.rb @@ -0,0 +1,25 @@ +module PixelatedService + class Search + class OrMatch + attr_reader :left, :right + def initialize(left, right) + @left = left + @right = right + end + def <<(node) + @right << node + end + def to_s + "Or(#@left, #@right)" + end + + def match?(mail) + [@left, @right].any? { |mm| mm.match?(mail) } + end + + def match_string?(str) + [@left, @right].any? { |mm| mm.match_string?(str) } + end + end + end +end diff --git a/fake-service/lib/pixelated_service/search/scope_match.rb b/fake-service/lib/pixelated_service/search/scope_match.rb new file mode 100644 index 00000000..170c54cf --- /dev/null +++ b/fake-service/lib/pixelated_service/search/scope_match.rb @@ -0,0 +1,79 @@ + +module PixelatedService + class Search + class ScopeMatch + def initialize(scope, data) + @scope = scope.downcase.gsub(/-/, '_').to_sym + @data = data + end + + def to_s + "Scope(#@scope, #@data)" + end + + def is_search_scope? + [:in, :tag, :is].include?(@scope) && + %w(_default_ trash all sent drafts).include?(@data.match_string.downcase) + end + + def search_scope + case @data.match_string.downcase + when '_default_' + PixelatedService::MailScopeFilter::Default + when 'all' + PixelatedService::MailScopeFilter::All + when 'trash' + PixelatedService::MailScopeFilter::Trash + when 'sent' + PixelatedService::MailScopeFilter::Sent + when 'drafts' + PixelatedService::MailScopeFilter::Drafts + end + end + + def match?(mail) + strs = + case @scope + when :to + mail.to + when :from, :sender + mail.from + when :cc + mail.headers[:cc] + when :bcc + mail.headers[:bcc] + when :subject + mail.subject + when :rcpt, :rcpts, :recipient, :recipients + [mail.to, mail.headers[:cc], mail.headers[:bcc]].flatten.compact + when :body + mail.body + when :tag, :tags, :in + return @data.match_exact_string?(mail.tag_names) + # has:seal, has:imprint, has:lock + when :is + case @data.str + when "starred" + return mail.starred? + when "read" + return mail.read? + when "replied" + return mail.replied? + # sealed, imprinted, signed, locked, encrypted, + else + raise "NOT IMPLEMENTED: is:#{@data}" + end + when :before + raise "NOT IMPLEMENTED" + when :after + raise "NOT IMPLEMENTED" + when :att, :attachment + raise "NOT IMPLEMENTED" + else + mail.headers[@scope] || (return false) + end + @data.match_string? strs + end + end + end +end diff --git a/fake-service/lib/pixelated_service/search/string_match.rb b/fake-service/lib/pixelated_service/search/string_match.rb new file mode 100644 index 00000000..f9b32376 --- /dev/null +++ b/fake-service/lib/pixelated_service/search/string_match.rb @@ -0,0 +1,37 @@ +module PixelatedService + class Search + class StringMatch + attr_reader :str + def initialize(data, quoted=false) + @str = data + @quoted = quoted + @data = Regexp.new(Regexp.quote(self.match_string), Regexp::IGNORECASE) + @exact_match = /^#{@data}$/ + end + + def match_string + if @quoted + @str[1..-2] + else + @str + end + end + + def to_s + "String(#@data)" + end + + def match_string?(str) + Array(str).any? { |ff| !!(ff[@data]) } + end + + def match_exact_string?(str) + Array(str).any? { |ff| @exact_match.match ff } + end + + def match?(mail) + match_string? [mail.to, mail.from, mail.subject, mail.body] + end + end + end +end diff --git a/fake-service/lib/pixelated_service/search/true_match.rb b/fake-service/lib/pixelated_service/search/true_match.rb new file mode 100644 index 00000000..fd615f16 --- /dev/null +++ b/fake-service/lib/pixelated_service/search/true_match.rb @@ -0,0 +1,13 @@ +module PixelatedService + class Search + class TrueMatch + def match?(mail) + true + end + + def match_string?(str) + true + end + end + end +end diff --git a/fake-service/lib/pixelated_service/security_casing.rb b/fake-service/lib/pixelated_service/security_casing.rb new file mode 100644 index 00000000..6e2a77e5 --- /dev/null +++ b/fake-service/lib/pixelated_service/security_casing.rb @@ -0,0 +1,55 @@ + +module PixelatedService + class SecurityCasing < Struct.new(:imprints, :locks) + def to_json(*args) + { imprints: self.imprints, locks: self.locks }.to_json(*args) + end + + def +(other) + imprints = self.imprints + other.imprints + locks = self.locks + other.locks + SecurityCasing.new(imprints, locks) + end + + class Key < Struct.new :longid, :fingerprint, :user_ids, :connected_contacts, :state, :size, :algorithm, :trust, :validity + VALID_STATES = [:valid, :expired, :revoked] + VALID_TRUST = [:unknown, :no_trust, :marginal, :full, :ultimate] + + def to_json(*args) + { longid: self.longid, + fingerprint: self.fingerprint, + user_ids: self.user_ids, + connected_contacts: self.connected_contacts, + state: self.state, + size: self.size, + algorithm: self.algorithm, + trust: self.trust, + validity: self.validity }.to_json(*args) + end + end + + # Signature + class Imprint < Struct.new :seal, :imprint_timestamp, :algorithm, :state + VALID_STATES = [:valid, :invalid, :no_match, :from_expired, :from_revoked] + + def to_json(*args) + { seal: self.seal, + imprint_timestamp: self.imprint_timestamp, + algorithm: self.algorithm, + state: self.state }.to_json(*args) + end + end + + # Encryption + class Lock < Struct.new :key, :state, :algorithm, :key_specified_in_lock + VALID_STATES = [:valid, :failure, :no_private_key] + + def to_json(*args) + { state: self.state, + algorithm: self.algorithm, + key: self.key, + key_specified_in_lock: self.key_specified_in_lock }.to_json(*args) + end + end + end +end diff --git a/fake-service/lib/pixelated_service/security_casing_examples.rb b/fake-service/lib/pixelated_service/security_casing_examples.rb new file mode 100644 index 00000000..e194de02 --- /dev/null +++ b/fake-service/lib/pixelated_service/security_casing_examples.rb @@ -0,0 +1,142 @@ +module PixelatedService + module SecurityCasingExamples + module Key + VALID_U_U = SecurityCasing::Key.new("295C746984AF7F0C", "698E2885C1DE74E32CD503AD295C746984AF7F0C", ["Ola Bini ", + "Ola Bini "], ["1905060826932239808", "3264050876889579764"], :valid, 4096, :RSA, :ultimate, :ultimate) + + EXPIRED_UN_UN = SecurityCasing::Key.new("05A63421F637E333", "41EA1D94F26186026CD4B2B505A63421F637E333", ["Rylee Elise Fowler ", + "Rylee Fowler (gmail mail, generally unused) "], [], :expired, 2048, :RSA, :unknown, :unknown) + + REVOKED_F_UN = SecurityCasing::Key.new("11044FD19FC527CC", "E64F19EBBBE86AA97AF36FD511044FD19FC527CC", ["Michael Rogers "], [], :revoked, 2048, :RSA, :full, :unknown) + + EXPIRED_NO_TRUST = SecurityCasing::Key.new("FB3973E142A913A4", "AB465EE7022B68C42DBAD324FB3973E142A913A4", ["Michael Granger ", "Michael Granger ", "Michael Granger ", + "Michael Granger ", "Ged the Grey's Hain ", + "Michael Granger ", "Michael Granger ", + "Michael Granger ", "Michael Granger (at work) "], + [], :expired, 1024, :DSA, :no_trust, :no_trust) + + VALID_NO_TRUST = SecurityCasing::Key.new("29F16F77D77A211F", "692B652B70BC67E8EEA36E0929F16F77D77A211F", ["Christian Trabold ", + "Christian Trabold "], [], :valid, 4096, :RSA, :no_trust, :no_trust) + + EXAMPLES = [ + VALID_U_U, + SecurityCasing::Key.new("37561129CF4BE610", "D37F700C25569B6F1E1286EF37561129CF4BE610", ["Molly "], [], :valid, 4096, :RSA, :ultimate, :no_trust), + EXPIRED_UN_UN, + EXPIRED_NO_TRUST, + SecurityCasing::Key.new("E62030AB4AA41495", "BEB9D9E74B0C5167C5FC6CC8E62030AB4AA41495", ["Christopher Dell "], ["4117763089493997091"], :valid, 2048, :RSA, :unknown, :full), + SecurityCasing::Key.new("D692003DAA02C70A", "3E053E70DE40B13ADE913E7ED692003DAA02C70A", ["Tyler Hicks ", "Tyler Hicks ", "Tyler Hicks ", + "Tyler Hicks ", "Tyler Hicks ", "Tyler Hicks ", + "Tyler Hicks ", "Tyler Hicks "], + ["792826280999687388"], :valid, 4096, :RSA, :marginal, :full), + REVOKED_F_UN, + VALID_NO_TRUST, + SecurityCasing::Key.new("5DFEA1062EA46E4F", "14D07803BFF6EFA099988C4B5DFEA1062EA46E4F", ["Henri Salo ", "Henri Salo ", "Henri Salo ", + "Henri Salo "], [], :valid, 1024, :DSA, :no_trust, :ultimate), + SecurityCasing::Key.new("C44FBF8A41A80850", "EE6497E3FEC3773BAD33062DC44FBF8A41A80850", ["Seeta Gangadharan "], [], :valid, 2048, :RSA, :marginal, :full), + SecurityCasing::Key.new("7934ED27275BDB05", "9A6D46E5E7489C60A1DEFDCA7934ED27275BDB05", ["Ernesto Medina Delgado "], ["2618860400823413108"], :valid, 4096, :RSA, :full, :unknown), + SecurityCasing::Key.new("EC73D77206A7B07F", "728CFCA32AAFF261DE88971CEC73D77206A7B07F", ["Aaron Bedra "], ["1494195372012211732"], :revoked, 1024, :DSA, :ultimate, :ultimate), + SecurityCasing::Key.new("ACD5E501207FBB0E", "281C60C3D20A19C7D2608302ACD5E501207FBB0E", ["Chip Collier "], [], :valid, 2048, :RSA, :full, :ultimate), + SecurityCasing::Key.new("2ECDE8FDF22DB236", "E6D4C474A259113A02E8F2772ECDE8FDF22DB236", ["Hiroshi Nakamura (NaHi) ", "Hiroshi Nakamura (NaHi) ", + "Hiroshi Nakamura (NaHi) "], [], :revoked, 2048, :RSA, :no_trust, :marginal), + SecurityCasing::Key.new("482ECB2BDAAC67D2", "A00620D62EA9B36A3BB71BDE482ECB2BDAAC67D2", ["severino "], ["2361077248315571916"], :valid, 4096, :RSA, :full, :no_trust), + ] + + end + + module Imprint + VALID = SecurityCasing::Imprint.new(Key::VALID_U_U, Time.now - 200_123, :SHA256, :valid) + INVALID = SecurityCasing::Imprint.new(Key::VALID_U_U, Time.now - 123_345, :SHA256, :invalid) + NO_MATCH = SecurityCasing::Imprint.new(nil, Time.now - 42_134, :SHA128, :no_match) + FROM_EXPIRED = SecurityCasing::Imprint.new(Key::EXPIRED_UN_UN, Time.now - 1_002_123, :SHA128, :from_expired) + FROM_REVOKED = SecurityCasing::Imprint.new(Key::REVOKED_F_UN, Time.now - 12_123, :SHA128, :from_revoked) + VALID_FROM_NO_TRUST = SecurityCasing::Imprint.new(Key::VALID_NO_TRUST, Time.now - 10_424, :SHA512, :valid) + + EXAMPLES = [ + VALID, + INVALID, + NO_MATCH, + FROM_EXPIRED, + FROM_REVOKED, + VALID_FROM_NO_TRUST + ] + end + + module Lock + VALID_TO_SPECIFIC = SecurityCasing::Lock.new(Key::VALID_U_U, :valid, :RSA, true) + VALID_NOT_TO_SPECIFIC = SecurityCasing::Lock.new(Key::VALID_U_U, :valid, :RSA, false) + + INVALID_TO_SPECIFIC = SecurityCasing::Lock.new(Key::VALID_U_U, :failure, :RSA, true) + INVALID_NOT_TO_SPECIFIC = SecurityCasing::Lock.new(Key::VALID_U_U, :failure, :RSA, false) + + EXPIRED_TO_SPECIFIC = SecurityCasing::Lock.new(Key::EXPIRED_UN_UN, :failure, :RSA, true) + EXPIRED_NOT_TO_SPECIFIC = SecurityCasing::Lock.new(Key::EXPIRED_UN_UN, :failure, :RSA, false) + + REVOKED_TO_SPECIFIC = SecurityCasing::Lock.new(Key::REVOKED_F_UN, :failure, :RSA, true) + REVOKED_NOT_TO_SPECIFIC = SecurityCasing::Lock.new(Key::REVOKED_F_UN, :failure, :RSA, false) + + NO_KEY_TO_SPECIFIC = SecurityCasing::Lock.new(nil, :no_private_key, :RSA, true) + NO_KEY_NOT_TO_SPECIFIC = SecurityCasing::Lock.new(nil, :no_private_key, :RSA, false) + + EXAMPLES = [ + VALID_TO_SPECIFIC, + VALID_NOT_TO_SPECIFIC, + INVALID_TO_SPECIFIC, + INVALID_NOT_TO_SPECIFIC, + EXPIRED_TO_SPECIFIC, + EXPIRED_NOT_TO_SPECIFIC, + REVOKED_TO_SPECIFIC, + REVOKED_NOT_TO_SPECIFIC, + NO_KEY_TO_SPECIFIC, + NO_KEY_NOT_TO_SPECIFIC + ] + end + + module Case + NO_IMPRINTS_OR_LOCKS = SecurityCasing.new([], []) + + ONE_VALID_IMPRINT = SecurityCasing.new([Imprint::VALID], []) + THREE_VALID_IMPRINTS = SecurityCasing.new([Imprint::VALID, + Imprint::VALID, + Imprint::VALID], []) + ONE_VALID_TWO_NO_MATCH_IMPRINTS = SecurityCasing.new([Imprint::NO_MATCH, + Imprint::VALID, + Imprint::NO_MATCH], []) + ONE_INVALID_IMPRINT = SecurityCasing.new([Imprint::INVALID], []) + ONE_NO_MATCH_IMPRINT = SecurityCasing.new([Imprint::NO_MATCH], []) + FROM_EXPIRED_IMPRINT = SecurityCasing.new([Imprint::FROM_EXPIRED], []) + FROM_REVOKED_IMPRINT = SecurityCasing.new([Imprint::FROM_REVOKED], []) + FROM_VALID_WITH_NO_TRUST_IMPRINT = SecurityCasing.new([Imprint::VALID_FROM_NO_TRUST], []) + + WITH_IMPRINTS = [ONE_VALID_IMPRINT, THREE_VALID_IMPRINTS, ONE_VALID_TWO_NO_MATCH_IMPRINTS, ONE_INVALID_IMPRINT, ONE_NO_MATCH_IMPRINT, FROM_EXPIRED_IMPRINT, FROM_REVOKED_IMPRINT, FROM_VALID_WITH_NO_TRUST_IMPRINT] + + VALID_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::VALID_TO_SPECIFIC]) + VALID_NOT_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::VALID_NOT_TO_SPECIFIC]) + ONE_VALID_TWO_INVALID_LOCKS = SecurityCasing.new([], [Lock::VALID_TO_SPECIFIC, Lock::INVALID_TO_SPECIFIC, Lock::INVALID_NOT_TO_SPECIFIC]) + ONE_VALID_TWO_NO_KEY_LOCKS = SecurityCasing.new([], [Lock::NO_KEY_TO_SPECIFIC, Lock::VALID_TO_SPECIFIC, Lock::NO_KEY_NOT_TO_SPECIFIC]) + INVALID_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::INVALID_TO_SPECIFIC]) + INVALID_NOT_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::INVALID_NOT_TO_SPECIFIC]) + EXPIRED_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::EXPIRED_TO_SPECIFIC]) + EXPIRED_NOT_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::EXPIRED_NOT_TO_SPECIFIC]) + REVOKED_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::REVOKED_TO_SPECIFIC]) + REVOKED_NOT_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::REVOKED_NOT_TO_SPECIFIC]) + NO_KEY_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::NO_KEY_TO_SPECIFIC]) + NO_KEY_NOT_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::NO_KEY_NOT_TO_SPECIFIC]) + + WITH_LOCKS = [VALID_TO_SPECIFIC_LOCK, VALID_NOT_TO_SPECIFIC_LOCK, ONE_VALID_TWO_INVALID_LOCKS, ONE_VALID_TWO_NO_KEY_LOCKS, INVALID_TO_SPECIFIC_LOCK, INVALID_NOT_TO_SPECIFIC_LOCK, + EXPIRED_TO_SPECIFIC_LOCK, EXPIRED_NOT_TO_SPECIFIC_LOCK, REVOKED_TO_SPECIFIC_LOCK, REVOKED_NOT_TO_SPECIFIC_LOCK, NO_KEY_TO_SPECIFIC_LOCK, NO_KEY_NOT_TO_SPECIFIC_LOCK] + + WITH_IMPRINTS_AND_LOCKS = WITH_IMPRINTS.product(WITH_LOCKS).map { |l, r| l + r } + + EXAMPLES = [NO_IMPRINTS_OR_LOCKS] + + WITH_IMPRINTS + + WITH_LOCKS + + WITH_IMPRINTS_AND_LOCKS + + class << self + def case_from(ident) + EXAMPLES[ident % EXAMPLES.size] + end + end + end + end +end diff --git a/fake-service/lib/pixelated_service/server.rb b/fake-service/lib/pixelated_service/server.rb new file mode 100644 index 00000000..d825a5e2 --- /dev/null +++ b/fake-service/lib/pixelated_service/server.rb @@ -0,0 +1,82 @@ +require 'sinatra/base' +require 'sinatra/json' +require 'json' +require 'net/http' + +module PixelatedService + class Server < Sinatra::Base + set :root, File.join(File.dirname(__FILE__), '../../') + + def json_body; JSON.parse request.body.read.to_s; end + + if ENV['RACK_ENV'] == 'staging' + get '/' do File.read(File.join(settings.root, 'public', 'index.html')) end + end + + get '/mails' do json mails(params["q"], (params["p"] || 0).to_i, (params["w"] || -1).to_i) end + delete '/mails' do json delete_mails(params["q"], (params["p"] || 0).to_i, (params["w"] || -1).to_i, params["idents"]) end + post '/mails/read' do json readmails(params["idents"], true) end + post '/mails/unread' do json readmails(params["idents"], false) end + get '/mail/:ident' do |i| json mail(i) end + delete '/mail/:ident' do |i| json delete_mail(i) end + post '/mail/:ident/star' do |i| json starmail(i, true) end + post '/mail/:ident/unstar' do |i| json starmail(i, false) end + post '/mail/:ident/replied' do |i| json repliedmail(i, true) end + post '/mail/:ident/unreplied' do |i| json repliedmail(i, false) end + post '/mail/:ident/read' do |i| json readmail(i, true) end + post '/mail/:ident/unread' do |i| json readmail(i, false) end + get '/mail/:ident/tags' do |i| json tags(i) end + post '/mail/:ident/tags' do |i| json settags(i, json_body) end + + get '/draft_reply_for/:ident' do |i| json draft_reply_for(i) end + + get '/contacts' do json contacts(params["q"], (params["p"] || 0).to_i, (params["w"] || -1).to_i) end + get '/contact/:ident' do |i| json contact(i) end + + get '/stats' do json stats end + + get '/personas' do json personas end + get '/persona/:ident' do |i| json persona(i) end + + get '/tags' do json all_tags(params["q"]) end + + post '/mails' do + ident = send_mail json_body + json({ ident: ident }) + end + + put '/mails' do + ident = update_mail json_body + json({ ident: ident }) + end + + post '/tags' do + tag = create_tag json_body + json({ tag: tag }) + end + + post '/control/create_mail' do json control_create_mail end + post '/control/delete_mails' do json control_delete_mails end + post '/control/mailset/:name/load' do |name| json control_mailset_load(name) end + + + # pass all other requests to asset server + get '/*' do + url = "http://localhost:9000/#{params['splat'][0]}" + + resp = Net::HTTP.get_response(URI.parse(url)) + if resp.is_a?(Net::HTTPSuccess) + res = resp.body.to_s.gsub(/(href|src)=("|')\//, '\1=\2' + url + '/') + content_type resp.content_type + status resp.code + res + else + status resp.code + resp.message + end + end + + + include PixelatedService::Fake + end +end diff --git a/fake-service/lib/pixelated_service/sorted_mail.rb b/fake-service/lib/pixelated_service/sorted_mail.rb new file mode 100644 index 00000000..555d4bd4 --- /dev/null +++ b/fake-service/lib/pixelated_service/sorted_mail.rb @@ -0,0 +1,57 @@ +module PixelatedService + class SortedMail + include Enumerable + + NEWEST_FIRST = lambda do |l,r| + (r.headers[:date] || Time.now) <=> (l.headers[:date] || Time.now) + end + + def initialize(&block) + @mails = {} + @mail_order = [] + @sort_procedure = block || NEWEST_FIRST + end + + def []=(k, v) + @mails[k] = v + @mail_order << v + sort_mail_order! + v + end + + def [](k) + @mails[k] + end + + def delete(k) + v = @mails.delete(k) + @mail_order.delete(v) + v + end + + def add_all(hs) + hs.each do |h,v| + @mails[h] = v + @mail_order << v + end + sort_mail_order! + self + end + + def length + @mails.length + end + + def sort_mail_order! + @mail_order.sort!(&@sort_procedure) + @mail_order.compact! + @mail_order.uniq! + end + + def each + @mail_order.each do |m| + yield m + end + end + end +end diff --git a/fake-service/lib/pixelated_service/stats.rb b/fake-service/lib/pixelated_service/stats.rb new file mode 100644 index 00000000..3e79b226 --- /dev/null +++ b/fake-service/lib/pixelated_service/stats.rb @@ -0,0 +1,60 @@ + +module PixelatedService + module Stats + class StatsCollector + include Stats + def initialize + stats_init + end + end + + attr_reader :stats + + def stats_init + @stats = { + total: 0, + read: 0, + starred: 0, + replied: 0 + } + end + + def stats_added(m) + @stats[:total] += 1 + stats_status_added(:read, m) if m.status?(:read) + stats_status_added(:replied, m) if m.status?(:replied) + stats_status_added(:starred, m) if m.status?(:starred) + end + + def stats_removed(m) + @stats[:total] -= 1 + stats_status_removed(:read, m) if m.status?(:read) + stats_status_removed(:replied, m) if m.status?(:replied) + stats_status_removed(:starred, m) if m.status?(:starred) + end + + def stats_status_added(s, m) + @stats[s] += 1 + end + + def stats_status_removed(s, m) + @stats[s] -= 1 + end + + def each_total_helper(enum) + if enum.respond_to?(:each_total) + enum.each_total { |x| yield x } + else + enum.each { |x| yield x } + end + end + + def with_stats(enum) + sc = StatsCollector.new + each_total_helper(enum) do |e| + sc.stats_added(e) + end + [sc.stats, enum] + end + end +end diff --git a/fake-service/lib/pixelated_service/stats_observer.rb b/fake-service/lib/pixelated_service/stats_observer.rb new file mode 100644 index 00000000..f4e2831b --- /dev/null +++ b/fake-service/lib/pixelated_service/stats_observer.rb @@ -0,0 +1,19 @@ +module PixelatedService + class StatsObserver + def initialize(stats) + @stats = stats + @stats.stats_init + end + + def mail_added(mail) + @stats.stats_added mail + end + + def mail_removed(mail) + @stats.stats_removed mail + end + + def mail_updated(before, after) + end + end +end diff --git a/fake-service/lib/pixelated_service/tags.rb b/fake-service/lib/pixelated_service/tags.rb new file mode 100644 index 00000000..d2eccc51 --- /dev/null +++ b/fake-service/lib/pixelated_service/tags.rb @@ -0,0 +1,150 @@ +module PixelatedService + class Tag < Struct.new(:name, :total_count, :read, :starred, :replied, :default) + def to_json(*args) + { + name: self.name, + ident: self.name.hash.abs, + default: self.default, + counts: { + total: self.total_count, + read: self.read, + starred: self.starred, + replied: self.replied, + } + }.to_json(*args) + end + end + + class Tags + SPECIAL = %w(inbox sent drafts trash) + + def initialize(*desired_names) + @tags = desired_names.each_with_object({}) do |name, res| + res[normalized(name)] = Tags.get(name) + end + end + + def increase_all_count(t = nil) + change_all_count 1, t, :total_count + end + + def decrease_all_count(t = nil) + change_all_count -1, t, :total_count + end + + def change_all_count(v, t, s) + tags = t ? [t] : @tags.values + tags.each do |tag| + tag[s] += v + end + end + + def increase_status_count_checked(status, t = nil) + increase_status_count status, t if @mail.status?(status) + end + + def decrease_status_count_checked(status, t = nil) + decrease_status_count status, t if @mail.status?(status) + end + + def increase_status_count(status, t = nil) + change_all_count 1, t, status + end + + def decrease_status_count(status, t = nil) + change_all_count -1, t, status + end + + def decrease_all(t = nil) + decrease_all_count t + decrease_status_count_checked :read, t + decrease_status_count_checked :starred, t + decrease_status_count_checked :replied, t + end + + def increase_all(t = nil) + increase_all_count t + increase_status_count_checked :read, t + increase_status_count_checked :starred, t + increase_status_count_checked :replied, t + end + + def mail=(m) + decrease_all if @mail + @mail = m + increase_all if m + end + + def add_tag(name) + unless @tags[normalized(name)] + t = @tags[normalized(name)] = Tags.get(name) + increase_all t + end + end + + def remove(name) + if t = @tags[normalized(name)] + @tags.delete(normalized(name)) + decrease_all t + end + end + + def added_status(nm) + increase_status_count nm + end + + def removed_status(nm) + decrease_status_count nm + end + + def is_tagged?(t) + !!@tags[normalized(t.name)] + end + + def names + @tags.values.sort_by do |v| + [SPECIAL.index(normalized(v.name)) || 999, normalized(v.name)] + end.map(&:name) + end + + def normalized(n) + Tags.normalized(n) + end + + class <(other) - other.comparison_value <=> self.comparison_value - end - end -end diff --git a/fake-service/lib/smail/contacts.rb b/fake-service/lib/smail/contacts.rb deleted file mode 100644 index af523a69..00000000 --- a/fake-service/lib/smail/contacts.rb +++ /dev/null @@ -1,84 +0,0 @@ -require 'mail' -require 'set' - -module Smail - class Contacts - include Enumerable - - def initialize(persona) - @persona = persona - @contacts = nil - @contacts_cache = {} - @contacts_lookup = {} - end - - def contact(ix) - @contacts_lookup[ix] - end - - def each - curr = @contacts - while curr - yield curr - curr = curr.next - end - end - - def normalize(addr) - addr.downcase - end - - def parse(a) - ::Mail::Address.new(a) - end - - def update c, addr - @contacts_cache[normalize(addr.address)] = c - c.name = addr.display_name if addr.display_name - (c.addresses ||= Set.new) << addr.address - end - - def create_new_contact(addr) - old_first = @contacts - c = Contact.new - c.ident = addr.hash.abs.to_s - c.next = old_first - @contacts_lookup[c.ident] = c - @contacts = c - update c, addr - c - end - - def find_or_create(addr) - parsed = parse(addr) - if cc = @contacts_cache[normalize(parsed.address)] - update cc, parsed - cc - else - create_new_contact(parsed) - end - end - - def latest(prev, n) - if prev && prev > n - prev - else - n - end - end - - def new_mail_from(a, t) - contact = find_or_create(a) - contact.last_received = latest(contact.last_received, t) - contact.mails_received ||= 0 - contact.mails_received += 1 - end - - def new_mail_to(a, t) - contact = find_or_create(a) - contact.last_sent = latest(contact.last_sent, t) - contact.mails_sent ||= 0 - contact.mails_sent += 1 - end - end -end diff --git a/fake-service/lib/smail/contacts_observer.rb b/fake-service/lib/smail/contacts_observer.rb deleted file mode 100644 index 04c1670b..00000000 --- a/fake-service/lib/smail/contacts_observer.rb +++ /dev/null @@ -1,54 +0,0 @@ -module Smail - class ContactsObserver - def initialize(contacts) - @contacts = contacts - end - - def extract_addresses(*addrs) - addrs.flatten.compact - end - - def all_receivers(mail, &block) - extract_addresses(mail.to, mail.cc, mail.bcc).each(&block) - end - - def all_senders(mail, &block) - extract_addresses(mail.from).each(&block) - end - - def new_receivers(before, after, &block) - (extract_addresses(after.to, after.cc, after.bcc) - extract_addresses(before.to, before.cc, before.bcc)).each(&block) - end - - def new_senders(before, after, &block) - (extract_addresses(after.from) - extract_addresses(before.from)).each(&block) - end - - def timestamp_from(mail) - mail.headers[:date] - end - - def mail_added(mail) - timestamp = timestamp_from(mail) - all_receivers(mail) do |rcv| - @contacts.new_mail_to(rcv, timestamp) - end - all_senders(mail) do |s| - @contacts.new_mail_from(s, timestamp) - end - end - - def mail_removed(mail) - end - - def mail_updated(before, after) - timestamp = timestamp_from(after) - new_receivers(before, after) do |rcv| - @contacts.new_mail_to(rcv, timestamp) - end - new_senders(before, after) do |s| - @contacts.new_mail_from(s, timestamp) - end - end - end -end diff --git a/fake-service/lib/smail/contacts_search.rb b/fake-service/lib/smail/contacts_search.rb deleted file mode 100644 index 76ee6aa2..00000000 --- a/fake-service/lib/smail/contacts_search.rb +++ /dev/null @@ -1,71 +0,0 @@ - -# Very simple search for contacts. The search string will be something that will be prefix matched -# using a boundary before but not after. If you put in more than one word, those two will be searched -# and ANDed together. You can use double quotes or single quotes to do the obvious thing instead -module Smail - class ContactsSearch - def initialize(q) - @qtree = ContactsSearch.compile(q) - end - - def restrict(input) - input.select do |mm| - @qtree.match?(mm) - end - end - - REGEXP_DQUOTED = /"[^"]*"/ - REGEXP_SQUOTED = /'[^']*'/ - REGEXP_OTHER = /[^\s]+/ - - class AndMatch - attr_reader :data - def initialize(data = []) - @data = data - end - def <<(node) - @data << node - end - def match?(c) - self.data.all? { |mm| mm.match?(c) } - end - end - - class StringMatch - def initialize(data, quoted=false) - @data = Regexp.new(Regexp.quote(if quoted - data[1..-2] - else - data - end), Regexp::IGNORECASE) - @exact_match = /\b#{@data}/ - end - - def match_string?(str) - Array(str).any? { |ff| @exact_match.match ff } - end - - def match?(c) - match_string? ([c.name] + c.addresses.to_a).compact - end - end - - def self.compile(q) - qs = StringScanner.new(q) - qtree = AndMatch.new - until qs.eos? - res = - if qs.check(REGEXP_DQUOTED) - StringMatch.new(qs.scan(REGEXP_DQUOTED), true) - elsif qs.check(REGEXP_SQUOTED) - StringMatch.new(qs.scan(REGEXP_SQUOTED), true) - elsif qs.check(REGEXP_OTHER) - StringMatch.new(qs.scan(REGEXP_OTHER)) - end - qtree << res - qs.scan(/\s+/) - end - qtree - end - end -end diff --git a/fake-service/lib/smail/contacts_sorter.rb b/fake-service/lib/smail/contacts_sorter.rb deleted file mode 100644 index ca78177f..00000000 --- a/fake-service/lib/smail/contacts_sorter.rb +++ /dev/null @@ -1,8 +0,0 @@ - -module Smail - class ContactsSorter - def restrict(input) - input.sort - end - end -end diff --git a/fake-service/lib/smail/fake.rb b/fake-service/lib/smail/fake.rb deleted file mode 100644 index b1b468ac..00000000 --- a/fake-service/lib/smail/fake.rb +++ /dev/null @@ -1,146 +0,0 @@ -module Smail - class << self - def mail_service - @mail_service ||= MailService.new - end - end - - module Fake - PERSONAS = [ - Persona.new(1, "Yago Macedo", nil, "sirineu@souza.org") - ] - - def personas - PERSONAS.map(&:ident) - end - - def persona(i) - PERSONAS.select { |x| x.ident.to_s == i}.first - end - - def mails(query, page_number, window_size) - with_timing do - stats, mails = Smail.mail_service.mails(query, page_number, window_size) - { stats: stats, mails: mails.to_a } - end - end - - def contacts(query, page_number, window_size) - with_timing do - contacts = Smail.mail_service.contacts(query, page_number, window_size) - { contacts: contacts.to_a } - end - end - - def contact(ix) - Smail.mail_service.contact(ix) - end - - def delete_mails(query, page_number, window_size, mails_idents) - idents = mails_idents.gsub(/[\[\]]/, '').split(',').collect {|x| x.to_i} - Smail.mail_service.delete_mails(query, page_number, window_size, idents) - [] - end - - def mail(i) - Smail.mail_service.mail(i) - end - - def send_mail(data) - Smail.mail_service.send_mail(data) - end - - def update_mail(data) - Smail.mail_service.update_mail(data) - end - - def delete_mail(i) - Smail.mail_service.delete_mail(i) - end - - def draft_reply_for(i) - Smail.mail_service.draft_reply_for(i) - end - - def tags(i) - Smail.mail_service.mail(i).tag_names - end - - def create_tag(tag_json) - Smail.mail_service.create_tag tag_json - end - - def all_tags(q) - Smail.mail_service.tags(q) - end - - def settags(i, body) - m = Smail.mail_service.mail(i) - m.tag_names = body["newtags"] - m.tag_names - end - - def starmail(i, val) - m = Smail.mail_service.mail(i) - m.starred = val if m - "" - end - - def repliedmail(i, val) - m = Smail.mail_service.mail(i) - m.replied = val if m - "" - end - - def readmail(i, val) - m = Smail.mail_service.mail(i) - m.read = val if m - "" - end - - def readmails(mail_idents, val) - idents = mail_idents.gsub(/[\[\]]/, '').split(',').collect {|x| x.to_i} - Smail.mail_service.each { |k,v| readmail(k.ident, val) if idents.include?(k.ident) } - [] - end - - def control_create_mail - Smail.mail_service.create - "" - end - - def control_delete_mails - Smail.mail_service.clean - "" - end - - def control_mailset_load(name) - with_timing do - { - stats: Smail.mail_service.load_mailset(name), - loaded: name - } - end - end - - def stats - Smail.mail_service.stats_report - end - - def with_timing - before = Time.now - result = yield - after = Time.now - res = case result - when Hash - result.dup - when nil - {} - else - { result: result } - end - res[:timing] = { duration: after - before } - res - end - end -end diff --git a/fake-service/lib/smail/mail.rb b/fake-service/lib/smail/mail.rb deleted file mode 100644 index 8d1c8806..00000000 --- a/fake-service/lib/smail/mail.rb +++ /dev/null @@ -1,299 +0,0 @@ -module Smail - class Mail - attr_reader :to, :cc, :bcc, :from, :subject, :body, :headers, :status, :draft_reply_for - attr_accessor :ident, :security_casing - - def initialize(data = {}) - @ident = data[:ident] - @to = data[:to] - @cc = data[:cc] - @bcc = data[:bcc] - @from = data[:from] - @subject = data[:subject] - @body = data[:body] - @headers = data[:headers] || {} - @status = data[:status] || [] - @draft_reply_for = data[:draft_reply_for] || [] - self.tags = data[:tags] || Tags.new - end - - def hash - @ident.hash - end - - def eql?(object) - self == object - end - - def tags=(t) - @tags.mail = nil if @tags - @tags = t - @tags.mail = self if @tags - t - end - - def ==(object) - @ident == object.ident - end - - def tag_names - @tags.names - end - - def is_tagged?(t) - @tags.is_tagged?(t) - end - - def tag_names=(vs) - to_remove = self.tag_names - vs - to_add = vs - self.tag_names - - to_remove.each do |tn| - self.remove_tag(tn) - end - - to_add.each do |v| - self.add_tag(v) - end - end - - def add_tag(nm) - @tags.add_tag(nm) - end - - def remove_tag(nm) - @tags.remove(nm) - end - - def has_trash_tag? - tag_names.include? "trash" - end - - def starred=(v); v ? add_status(:starred) : remove_status(:starred); end - def starred?; status?(:starred); end - def read=(v); v ? add_status(:read) : remove_status(:read); end - def read?; status?(:read); end - def replied=(v); v ? add_status(:replied) : remove_status(:replied); end - def replied?; status?(:replied); end - - def add_status(n) - unless self.status?(n) - @status = @status + [n] - @tags.added_status(n) - Smail.mail_service.stats_status_added(n, self) - end - end - - def remove_status(n) - if self.status?(n) - @status = @status - [n] - @tags.removed_status(n) - Smail.mail_service.stats_status_removed(n, self) - end - end - - def status?(n) - @status.include?(n) - end - - def to_json(*args) - { - header: { - to: Array(@to), - from: @from, - subject: @subject, - }.merge(@headers).merge({date: @headers[:date].iso8601}), - ident: ident, - tags: @tags.names, - status: @status, - security_casing: @security_casing, - draft_reply_for: @draft_reply_for, - body: @body - }.to_json(*args) - end - - - def self.from_json(obj, new_ident = nil) - ident = obj['ident'] - draft_reply_for = obj['draft_reply_for'] - hdrs = obj['header'] - to = hdrs['to'] - cc = hdrs['cc'] - bcc = hdrs['bcc'] - from = hdrs['from'] - subject = hdrs['subject'] - new_hdrs = {} - hdrs.each do |k,v| - new_hdrs[k.to_sym] = v unless %w(from to subject).include?(k) - end - tag_names = obj['tags'] - st = obj['status'] - bd = obj['body'] - - mail = new(:subject => subject, - :from => from, - :to => Array(to), - :cc => Array(cc), - :bcc => Array(bcc), - :headers => new_hdrs, - :status => st, - :draft_reply_for => draft_reply_for, - :ident => (ident.to_s.empty? ? new_ident : ident), - :body => bd) - - tag_names.each do |tag_name| - mail.add_tag tag_name - end - - mail - - end - - def to_s - ([ - ("#{INTERNAL_TO_EXTERNAL_HEADER[:to]}: #{format_header_value_out(:to, @to)}" if @to), - ("#{INTERNAL_TO_EXTERNAL_HEADER[:from]}: #{format_header_value_out(:from, @from)}" if @from), - ("#{INTERNAL_TO_EXTERNAL_HEADER[:subject]}: #{format_header_value_out(:subject, @subject)}" if @subject), - ("#{INTERNAL_TO_EXTERNAL_HEADER[:x_tw_smail_tags]}: #{format_header_value_out(:x_tw_smail_tags, @tags.names)}" if !@tags.names.empty?), - ("#{INTERNAL_TO_EXTERNAL_HEADER[:x_tw_smail_status]}: #{format_header_value_out(:x_tw_smail_status, @status)}" if !@status.empty?), - ("#{INTERNAL_TO_EXTERNAL_HEADER[:x_tw_smail_ident]}: #{format_header_value_out(:x_tw_smail_ident, @ident)}"), - ].compact + @headers.map { |k,v| "#{INTERNAL_TO_EXTERNAL_HEADER[k]}: #{format_header_value_out(k, v)}"}).sort.join("\n") + "\n\n#{@body}" - end - - SPECIAL_HEADERS = [:subject, :from, :to, :x_tw_smail_tags, :x_tw_smail_status, :x_tw_smail_ident] - INTERNAL_TO_EXTERNAL_HEADER = { - :subject => "Subject", - :date => "Date", - :from => "From", - :to => "To", - :cc => "CC", - :bcc => "BCC", - :message_id => "Message-ID", - :mime_version => "Mime-Version", - :content_type => "Content-Type", - :content_transfer_encoding => "Content-Transfer-Encoding", - :x_tw_smail_tags => "X-TW-SMail-Tags", - :x_tw_smail_status => "X-TW-SMail-Status", - :x_tw_smail_ident => "X-TW-SMail-Ident", - } - - def format_header_value_out(k,v) - case k - when :date - v.strftime("%a, %d %b %Y %H:%M:%S %z") - when :to, :cc, :bcc - Array(v).join(", ") - when :x_tw_smail_tags, :x_tw_smail_status - v.join(", ") - else - v - end - end - - class << self - def formatted_header(k, ls) - format_header_value(k, ls[k] && ls[k][1]) - end - - def has_header(hdr_name, ls, val, otherwise) - if ls[hdr_name] - val - else - otherwise - end - end - - def time_rand from = (Time.now - 300000000), to = Time.now - Time.at(from + rand * (to.to_f - from.to_f)) - end - - # io is String or IO - def read(io, ident = nil) - io = StringIO.new(io) if io.is_a? String - headers = {} - body = "" - reading_headers = true - previous_header = nil - first = true - io.each do |ln| - if first && ln =~ /^From / - # Ignore line delimiter things - else - if reading_headers - if ln.chomp == "" - reading_headers = false - else - if previous_header && ln =~ /^\s+/ - previous_header[1] << " #{ln.strip}" - else - key, value = ln.chomp.split(/: /, 2) - previous_header = [key, value] - headers[internal_header_name(key)] = previous_header - end - end - else - body << ln - end - end - if first - first = false - end - end - - header_data = {} - headers.each do |k, (_, v)| - unless special_header?(k) - header_data[k] = format_header_value(k, v) - end - end - - unless header_data[:date] - header_data[:date] = time_rand - end - - - new(:subject => formatted_header(:subject, headers), - :from => formatted_header(:from, headers), - :to => formatted_header(:to, headers), - :headers => header_data, - :tags => formatted_header(:x_tw_smail_tags, headers), - :status => formatted_header(:x_tw_smail_status, headers), - :ident => has_header(:x_tw_smail_ident, headers, formatted_header(:x_tw_smail_ident, headers), ident), - :body => body - ) - end - - - private - def internal_header_name(k) - k.downcase.gsub(/-/, '_').to_sym - end - - def special_header?(k) - SPECIAL_HEADERS.include?(k) - end - - def format_header_value(k, v) - case k - when :date - DateTime.parse(v) - when :to, :cc, :bcc - vs = (v || "").split(/, /) - if vs.length == 1 - vs[0] - else - vs - end - when :x_tw_smail_tags - Tags.new *(v || "").split(/, /) - when :x_tw_smail_status - (v || "").split(/, /).map { |ss| ss.to_sym } - when :x_tw_smail_ident - v.to_i - else - v - end - end - end - end -end diff --git a/fake-service/lib/smail/mail_scope_filter.rb b/fake-service/lib/smail/mail_scope_filter.rb deleted file mode 100644 index c8a5e042..00000000 --- a/fake-service/lib/smail/mail_scope_filter.rb +++ /dev/null @@ -1,70 +0,0 @@ -module Smail - module MailScopeFilter - include Enumerable - - def initialize(c) - @c = c - end - - def each - @c.each do |m| - yield m if retain?(m) - end - end - - class Default - include MailScopeFilter - - def initialize(c) - super - @tags = [Tags.get('sent'), Tags.get('trash'), Tags.get('drafts')] - end - - def retain?(m) - !(@tags.any? { |t| m.is_tagged?(t) }) - end - - class << self - def +(o) - o - end - end - end - - class All - include MailScopeFilter - - def initialize(c) - super - @t = Tags.get('trash') - end - - def retain?(m) - !m.is_tagged?(@t) - end - - class << self - def +(o) - All - end - end - end - - def self.tagged_with(n) - t = Tags.get(n) - c = Class.new - c.send :include, MailScopeFilter - c.send :define_method, :retain? do |m| - m.is_tagged?(t) - end - c.class.send :define_method, :+ do |o| - All === o ? All : self - end - c - end - - Trash = tagged_with('trash') - Sent = tagged_with('sent') - Drafts = tagged_with('drafts') - end -end diff --git a/fake-service/lib/smail/mail_service.rb b/fake-service/lib/smail/mail_service.rb deleted file mode 100644 index 314feaf5..00000000 --- a/fake-service/lib/smail/mail_service.rb +++ /dev/null @@ -1,152 +0,0 @@ -require 'set' - -module Smail - class MailService - include Enumerable - include Smail::Stats - - def each - @mails.each do |mo| - yield mo - end - end - - def initialize - self.clean - end - - def contact(ix) - @contacts.contact(ix) - end - - def contacts - @contacts.to_a - end - - def clean - Smail::Tags.clean - @next_ident = 0 - @reply_drafts = {} - @mails = SortedMail.new - @contacts = Contacts.new(Fake::PERSONAS[0]) - @observers = CombinedObserver.new(StatsObserver.new(self), - ContactsObserver.new(@contacts)) - end - - def create(mail=Generator.random_mail) - unless mail.ident - mail.ident = @next_ident - @next_ident += 1 - end - @mails[mail.ident] = mail - @observers.mail_added(mail) - end - - def create_tag(tag_json) - Smail::Tags.create_tag tag_json['tag'] - end - - def mail(ix) - @mails[ix.to_i] - end - - def send_mail(data) - ms = Mail.from_json(data, @next_ident) - if ms.tag_names.include?("sent") - ms.remove_tag "drafts" - @reply_drafts.delete ms.draft_reply_for - elsif ms.tag_names.include?("drafts") and ms.draft_reply_for - @reply_drafts[ms.draft_reply_for] = ms.ident - end - @next_ident += 1 - @mails[ms.ident] = ms - update_status ms - @observers.mail_added(ms) - ms.ident - end - - def draft_reply_for(ident) - @mails[@reply_drafts[ident.to_i]] - end - - def update_mail(data) - mail = Mail.from_json(data) - before = @mails[mail.ident] - @mails[mail.ident] = mail - update_status mail - @observers.mail_updated(before, mail) - before.tags = nil - mail.ident - end - - def update_status(mail) - mail.read = true - mail.headers[:date] = Time.now - end - - def tags(q) - if q && !q.strip.empty? - query = /\b#{Regexp.new(Regexp.quote(q), Regexp::IGNORECASE)}/ - Smail::Tags.all_tags.select do |tt| - query =~ tt.name - end - else - Smail::Tags.all_tags - end - end - - def stats_report - { stats: self.stats } - end - - def delete_mail(ix) - ms = @mails[ix.to_i] - @reply_drafts.delete ms.draft_reply_for - - if ms.has_trash_tag? - m = @mails.delete ix.to_i - @observers.mail_removed(m) - m.tags = nil - else - ms.add_tag 'trash' - end - end - - def load_mailset(name) - self.clean - ms = Smail::Mailset.load(name, @observers) - raise "couldn't find mailset #{name}" unless ms - @mails.add_all ms.mails - self.stats - end - - def mails(q, page, window_size) - restrictors = [All.new] - restrictors << Paginate.new(page, window_size) if window_size > 0 - restrictors << Search.new(q) - with_stats(restrictors.reverse.inject(self) do |ms, restr| - restr.restrict(ms) - end) - end - - def contacts(q, page, window_size) - restrictors = [All.new] - restrictors << ContactsSorter.new - restrictors << Paginate.new(page, window_size) if window_size > 0 - restrictors << ContactsSearch.new(q) if q - restrictors.reverse.inject(@contacts) do |c, restr| - restr.restrict(c) - end - end - - def delete_mails(q, page, window_size, idents=nil) - unless idents.nil? - @mails.each { |k,v| delete_mail(k.ident) if idents.include?(k.ident) } - else - mails(q, page, window_size).each do |m| - delete_mail m.ident - end - end - end - end -end diff --git a/fake-service/lib/smail/mailset.rb b/fake-service/lib/smail/mailset.rb deleted file mode 100644 index 0626dad8..00000000 --- a/fake-service/lib/smail/mailset.rb +++ /dev/null @@ -1,84 +0,0 @@ -require 'set' - -module Smail - class Mailset - DIR = File.expand_path File.join(File.dirname(__FILE__), "..", "..", "data", "mail-sets") - - class << self - def create(name, number, tagging) - ms = new name, number, tagging, nil - ms.generate! - ms - end - - def load(name, observers) - ms = new(name, -1, nil, observers) - if ms.load! - ms - else - nil - end - end - end - - attr_reader :mails - attr_reader :tags - - def initialize(name, number, tagging, observers) - @name, @number, @tagging, @observers = name, number, tagging, observers - end - - def generate! - @persona = Generator.random_persona - @tags = Generator.tags(Generator.ladder_distribution(4, 40)) - - @mails = {} - @tags = Set.new - (0...(@number)).each do |i| - res = if @tagging - Generator.random_tagged_mail(@tags) - else - Generator.random_mail - end - @observers.mail_added res - @tags.merge res.tags - res.ident = i - @mails[res.ident] = res - end - end - - def save! - dir = File.join(DIR, @name) - Dir.mkdir(dir) unless Dir.exists?(dir) - File.open(File.join(dir, "persona.yml"), "w") do |f| - f.write @persona.to_yaml - end - - @mails.each do |(k, m)| - nm = "mbox%08x" % m.ident - File.open(File.join(dir, nm), "w") do |f| - f.write m.to_s - end - end - end - - def load! - dir = File.join(DIR, @name) - return false unless Dir.exists?(dir) - @persona = YAML.load_file(File.join(dir, "persona.yml")) - @mails = {} - @ix = 0 - Dir["#{dir}/mbox*"].each do |f| - File.open(f) do |fio| - res = Smail::Mail.read fio, @ix - res.read = true if (res.tag_names.include?('sent') || res.tag_names.include?('drafts')) - @mails[res.ident] = res - @observers.mail_added res - res.security_casing = SecurityCasingExamples::Case.case_from(res.ident.to_i) - @ix += 1 - end - end - true - end - end -end diff --git a/fake-service/lib/smail/paginate.rb b/fake-service/lib/smail/paginate.rb deleted file mode 100644 index 85d09196..00000000 --- a/fake-service/lib/smail/paginate.rb +++ /dev/null @@ -1,15 +0,0 @@ - -module Smail - class Paginate - def initialize(page, window_size) - @start = page * window_size - @end = (page + 1) * window_size - end - - def restrict(input) - PaginatedEnumerable.new(input, @start, @end) - end - end -end - -require 'smail/paginated_enumerable' diff --git a/fake-service/lib/smail/paginated_enumerable.rb b/fake-service/lib/smail/paginated_enumerable.rb deleted file mode 100644 index 41c7f7bd..00000000 --- a/fake-service/lib/smail/paginated_enumerable.rb +++ /dev/null @@ -1,29 +0,0 @@ - -module Smail - class Paginate - class PaginatedEnumerable - include Enumerable - def initialize(input, start, e) - @input = input - @start = start - @end = e - end - - def each - @input.each_with_index do |v, ix| - if ix >= @end - return #we are done - elsif ix >= @start - yield v - end - end - end - - def each_total - @input.each do |v| - yield v - end - end - end - end -end diff --git a/fake-service/lib/smail/persona.rb b/fake-service/lib/smail/persona.rb deleted file mode 100644 index 47fe42dd..00000000 --- a/fake-service/lib/smail/persona.rb +++ /dev/null @@ -1,12 +0,0 @@ -module Smail - class Persona < Struct.new :ident, :name, :signature, :address - def to_json - { - ident: self.ident, - name: self.name, - signature: self.signature, - address: self.address - }.to_json - end - end -end diff --git a/fake-service/lib/smail/search.rb b/fake-service/lib/smail/search.rb deleted file mode 100644 index 6c6b109e..00000000 --- a/fake-service/lib/smail/search.rb +++ /dev/null @@ -1,133 +0,0 @@ -# Syntax notes for search: -# you can put a - in front of any search term to negate it -# you can scope a search by putting a name of a scope, a colon and then the search term WITHOUT a space. -# scoping will allow you to search for more things than otherwise available -# an unknown scope name will be assumed to be a header to search -# you can surround a search term in quotes to search for the whole thing -# multiple search terms will be ANDed together -# you can OR things by using the keyword OR/or - if you have it without parens, you will or the whole left with the whole right, until we find another or. -# if you use parenthesis, you can group together terms -# search in:_default_, in:all, in:trash, in:sent, in:drafts will only work for the WHOLE search. You can do a negation on a scoped search if it's in:trash, in:sent or in:drafts, but not for in:all - -module Smail - class Search - def initialize(q) - if q - @qtree, @search_scope = Search.compile(q) - else - @qtree, @search_scope = TrueMatch.new, Smail::MailScopeFilter::Default - end - end - - def restrict(input) - @search_scope.new(input).select do |mm| - @qtree.match?(mm) - end - end - - REGEXP_DQUOTED = /"[^"]*"/ - REGEXP_SQUOTED = /'[^']*'/ - REGEXP_SCOPE = /\w+:(".*?"|'.*?'|[^\s\)]+)/ - REGEXP_OTHER = /[^\s\)]+/ - - def self.scan_literal(qs) - if qs.check(REGEXP_DQUOTED) - StringMatch.new(qs.scan(REGEXP_DQUOTED), true) - elsif qs.check(REGEXP_SQUOTED) - StringMatch.new(qs.scan(REGEXP_SQUOTED), true) - elsif qs.check(REGEXP_OTHER) - StringMatch.new(qs.scan(REGEXP_OTHER)) - end - end - - def self.combine_search_scopes(l, r) - l + r - end - - def self.compile(q, qs = StringScanner.new(q)) - qtree = AndMatch.new - search_scope = Smail::MailScopeFilter::Default - until qs.eos? - if qs.check(/\)/) - qs.scan(/\)/) - return optimized(qtree), search_scope - end - - negated = false - if qs.check(/-/) - negated = true - qs.scan(/-/) - end - - if qs.check(/or/i) - qs.scan(/or/i) - left = qtree - qtree = OrMatch.new(left, AndMatch.new) - else - res = - if qs.check(/\(/) - qs.scan(/\(/) - v, sc = compile(q, qs) - search_scope = search_scope + sc - v - elsif qs.check(REGEXP_DQUOTED) - StringMatch.new(qs.scan(REGEXP_DQUOTED), true) - elsif qs.check(REGEXP_SQUOTED) - StringMatch.new(qs.scan(REGEXP_SQUOTED), true) - elsif qs.check(REGEXP_SCOPE) - scope = qs.scan(/\w+/) - qs.scan(/:/) - rest_node = scan_literal(qs) - v = ScopeMatch.new(scope, rest_node) - if v.is_search_scope? && !negated - search_scope = search_scope + v.search_scope - TrueMatch.new - else - v - end - elsif qs.check(REGEXP_OTHER) - StringMatch.new(qs.scan(REGEXP_OTHER)) - end - res = NegateMatch.new(res) if negated - qtree << res - end - - qs.scan(/\s+/) - end - return optimized(qtree), search_scope - end - - def self.optimized(tree) - case tree - when AndMatch - data = tree.data.reject { |d| TrueMatch === d } - if data.length == 1 - optimized(data.first) - else - AndMatch.new(data.map { |n| optimized(n)} ) - end - when OrMatch - if tree.right.is_a?(AndMatch) && tree.right.data.empty? - optimized(tree.left) - else - OrMatch.new(optimized(tree.left), optimized(tree.right)) - end - when NegateMatch - if tree.data.is_a?(NegateMatch) - optimized(tree.data.data) - else - NegateMatch.new(optimized(tree.data)) - end - else - tree - end - end - end -end - -require 'smail/search/string_match' -require 'smail/search/scope_match' -require 'smail/search/negate_match' -require 'smail/search/and_match' -require 'smail/search/or_match' -require 'smail/search/true_match' diff --git a/fake-service/lib/smail/search/and_match.rb b/fake-service/lib/smail/search/and_match.rb deleted file mode 100644 index 2bc53f0d..00000000 --- a/fake-service/lib/smail/search/and_match.rb +++ /dev/null @@ -1,25 +0,0 @@ -module Smail - class Search - class AndMatch - attr_reader :data - def initialize(data = []) - @data = data - end - def <<(node) - @data << node - end - - def to_s - "And(#{@data.join(", ")})" - end - - def match?(mail) - self.data.all? { |mm| mm.match?(mail) } - end - - def match_string?(str) - self.data.all? { |mm| mm.match_string?(str) } - end - end - end -end diff --git a/fake-service/lib/smail/search/negate_match.rb b/fake-service/lib/smail/search/negate_match.rb deleted file mode 100644 index f8bb59d4..00000000 --- a/fake-service/lib/smail/search/negate_match.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Smail - class Search - class NegateMatch - attr_reader :data - def initialize(data) - @data = data - end - - def to_s - "Negate(#@data)" - end - - def match?(mail) - !self.data.match?(mail) - end - - def match_string?(str) - !self.data.match_string?(str) - end - end - end -end diff --git a/fake-service/lib/smail/search/or_match.rb b/fake-service/lib/smail/search/or_match.rb deleted file mode 100644 index 455923bf..00000000 --- a/fake-service/lib/smail/search/or_match.rb +++ /dev/null @@ -1,25 +0,0 @@ -module Smail - class Search - class OrMatch - attr_reader :left, :right - def initialize(left, right) - @left = left - @right = right - end - def <<(node) - @right << node - end - def to_s - "Or(#@left, #@right)" - end - - def match?(mail) - [@left, @right].any? { |mm| mm.match?(mail) } - end - - def match_string?(str) - [@left, @right].any? { |mm| mm.match_string?(str) } - end - end - end -end diff --git a/fake-service/lib/smail/search/scope_match.rb b/fake-service/lib/smail/search/scope_match.rb deleted file mode 100644 index 4402674d..00000000 --- a/fake-service/lib/smail/search/scope_match.rb +++ /dev/null @@ -1,79 +0,0 @@ - -module Smail - class Search - class ScopeMatch - def initialize(scope, data) - @scope = scope.downcase.gsub(/-/, '_').to_sym - @data = data - end - - def to_s - "Scope(#@scope, #@data)" - end - - def is_search_scope? - [:in, :tag, :is].include?(@scope) && - %w(_default_ trash all sent drafts).include?(@data.match_string.downcase) - end - - def search_scope - case @data.match_string.downcase - when '_default_' - Smail::MailScopeFilter::Default - when 'all' - Smail::MailScopeFilter::All - when 'trash' - Smail::MailScopeFilter::Trash - when 'sent' - Smail::MailScopeFilter::Sent - when 'drafts' - Smail::MailScopeFilter::Drafts - end - end - - def match?(mail) - strs = - case @scope - when :to - mail.to - when :from, :sender - mail.from - when :cc - mail.headers[:cc] - when :bcc - mail.headers[:bcc] - when :subject - mail.subject - when :rcpt, :rcpts, :recipient, :recipients - [mail.to, mail.headers[:cc], mail.headers[:bcc]].flatten.compact - when :body - mail.body - when :tag, :tags, :in - return @data.match_exact_string?(mail.tag_names) - # has:seal, has:imprint, has:lock - when :is - case @data.str - when "starred" - return mail.starred? - when "read" - return mail.read? - when "replied" - return mail.replied? - # sealed, imprinted, signed, locked, encrypted, - else - raise "NOT IMPLEMENTED: is:#{@data}" - end - when :before - raise "NOT IMPLEMENTED" - when :after - raise "NOT IMPLEMENTED" - when :att, :attachment - raise "NOT IMPLEMENTED" - else - mail.headers[@scope] || (return false) - end - @data.match_string? strs - end - end - end -end diff --git a/fake-service/lib/smail/search/string_match.rb b/fake-service/lib/smail/search/string_match.rb deleted file mode 100644 index fc17ab59..00000000 --- a/fake-service/lib/smail/search/string_match.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Smail - class Search - class StringMatch - attr_reader :str - def initialize(data, quoted=false) - @str = data - @quoted = quoted - @data = Regexp.new(Regexp.quote(self.match_string), Regexp::IGNORECASE) - @exact_match = /^#{@data}$/ - end - - def match_string - if @quoted - @str[1..-2] - else - @str - end - end - - def to_s - "String(#@data)" - end - - def match_string?(str) - Array(str).any? { |ff| !!(ff[@data]) } - end - - def match_exact_string?(str) - Array(str).any? { |ff| @exact_match.match ff } - end - - def match?(mail) - match_string? [mail.to, mail.from, mail.subject, mail.body] - end - end - end -end diff --git a/fake-service/lib/smail/search/true_match.rb b/fake-service/lib/smail/search/true_match.rb deleted file mode 100644 index 7ac14923..00000000 --- a/fake-service/lib/smail/search/true_match.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Smail - class Search - class TrueMatch - def match?(mail) - true - end - - def match_string?(str) - true - end - end - end -end diff --git a/fake-service/lib/smail/security_casing.rb b/fake-service/lib/smail/security_casing.rb deleted file mode 100644 index fe8ae42b..00000000 --- a/fake-service/lib/smail/security_casing.rb +++ /dev/null @@ -1,55 +0,0 @@ - -module Smail - class SecurityCasing < Struct.new(:imprints, :locks) - def to_json(*args) - { imprints: self.imprints, locks: self.locks }.to_json(*args) - end - - def +(other) - imprints = self.imprints + other.imprints - locks = self.locks + other.locks - SecurityCasing.new(imprints, locks) - end - - class Key < Struct.new :longid, :fingerprint, :user_ids, :connected_contacts, :state, :size, :algorithm, :trust, :validity - VALID_STATES = [:valid, :expired, :revoked] - VALID_TRUST = [:unknown, :no_trust, :marginal, :full, :ultimate] - - def to_json(*args) - { longid: self.longid, - fingerprint: self.fingerprint, - user_ids: self.user_ids, - connected_contacts: self.connected_contacts, - state: self.state, - size: self.size, - algorithm: self.algorithm, - trust: self.trust, - validity: self.validity }.to_json(*args) - end - end - - # Signature - class Imprint < Struct.new :seal, :imprint_timestamp, :algorithm, :state - VALID_STATES = [:valid, :invalid, :no_match, :from_expired, :from_revoked] - - def to_json(*args) - { seal: self.seal, - imprint_timestamp: self.imprint_timestamp, - algorithm: self.algorithm, - state: self.state }.to_json(*args) - end - end - - # Encryption - class Lock < Struct.new :key, :state, :algorithm, :key_specified_in_lock - VALID_STATES = [:valid, :failure, :no_private_key] - - def to_json(*args) - { state: self.state, - algorithm: self.algorithm, - key: self.key, - key_specified_in_lock: self.key_specified_in_lock }.to_json(*args) - end - end - end -end diff --git a/fake-service/lib/smail/security_casing_examples.rb b/fake-service/lib/smail/security_casing_examples.rb deleted file mode 100644 index 91bf80bb..00000000 --- a/fake-service/lib/smail/security_casing_examples.rb +++ /dev/null @@ -1,142 +0,0 @@ -module Smail - module SecurityCasingExamples - module Key - VALID_U_U = SecurityCasing::Key.new("295C746984AF7F0C", "698E2885C1DE74E32CD503AD295C746984AF7F0C", ["Ola Bini ", - "Ola Bini "], ["1905060826932239808", "3264050876889579764"], :valid, 4096, :RSA, :ultimate, :ultimate) - - EXPIRED_UN_UN = SecurityCasing::Key.new("05A63421F637E333", "41EA1D94F26186026CD4B2B505A63421F637E333", ["Rylee Elise Fowler ", - "Rylee Fowler (gmail mail, generally unused) "], [], :expired, 2048, :RSA, :unknown, :unknown) - - REVOKED_F_UN = SecurityCasing::Key.new("11044FD19FC527CC", "E64F19EBBBE86AA97AF36FD511044FD19FC527CC", ["Michael Rogers "], [], :revoked, 2048, :RSA, :full, :unknown) - - EXPIRED_NO_TRUST = SecurityCasing::Key.new("FB3973E142A913A4", "AB465EE7022B68C42DBAD324FB3973E142A913A4", ["Michael Granger ", "Michael Granger ", "Michael Granger ", - "Michael Granger ", "Ged the Grey's Hain ", - "Michael Granger ", "Michael Granger ", - "Michael Granger ", "Michael Granger (at work) "], - [], :expired, 1024, :DSA, :no_trust, :no_trust) - - VALID_NO_TRUST = SecurityCasing::Key.new("29F16F77D77A211F", "692B652B70BC67E8EEA36E0929F16F77D77A211F", ["Christian Trabold ", - "Christian Trabold "], [], :valid, 4096, :RSA, :no_trust, :no_trust) - - EXAMPLES = [ - VALID_U_U, - SecurityCasing::Key.new("37561129CF4BE610", "D37F700C25569B6F1E1286EF37561129CF4BE610", ["Molly "], [], :valid, 4096, :RSA, :ultimate, :no_trust), - EXPIRED_UN_UN, - EXPIRED_NO_TRUST, - SecurityCasing::Key.new("E62030AB4AA41495", "BEB9D9E74B0C5167C5FC6CC8E62030AB4AA41495", ["Christopher Dell "], ["4117763089493997091"], :valid, 2048, :RSA, :unknown, :full), - SecurityCasing::Key.new("D692003DAA02C70A", "3E053E70DE40B13ADE913E7ED692003DAA02C70A", ["Tyler Hicks ", "Tyler Hicks ", "Tyler Hicks ", - "Tyler Hicks ", "Tyler Hicks ", "Tyler Hicks ", - "Tyler Hicks ", "Tyler Hicks "], - ["792826280999687388"], :valid, 4096, :RSA, :marginal, :full), - REVOKED_F_UN, - VALID_NO_TRUST, - SecurityCasing::Key.new("5DFEA1062EA46E4F", "14D07803BFF6EFA099988C4B5DFEA1062EA46E4F", ["Henri Salo ", "Henri Salo ", "Henri Salo ", - "Henri Salo "], [], :valid, 1024, :DSA, :no_trust, :ultimate), - SecurityCasing::Key.new("C44FBF8A41A80850", "EE6497E3FEC3773BAD33062DC44FBF8A41A80850", ["Seeta Gangadharan "], [], :valid, 2048, :RSA, :marginal, :full), - SecurityCasing::Key.new("7934ED27275BDB05", "9A6D46E5E7489C60A1DEFDCA7934ED27275BDB05", ["Ernesto Medina Delgado "], ["2618860400823413108"], :valid, 4096, :RSA, :full, :unknown), - SecurityCasing::Key.new("EC73D77206A7B07F", "728CFCA32AAFF261DE88971CEC73D77206A7B07F", ["Aaron Bedra "], ["1494195372012211732"], :revoked, 1024, :DSA, :ultimate, :ultimate), - SecurityCasing::Key.new("ACD5E501207FBB0E", "281C60C3D20A19C7D2608302ACD5E501207FBB0E", ["Chip Collier "], [], :valid, 2048, :RSA, :full, :ultimate), - SecurityCasing::Key.new("2ECDE8FDF22DB236", "E6D4C474A259113A02E8F2772ECDE8FDF22DB236", ["Hiroshi Nakamura (NaHi) ", "Hiroshi Nakamura (NaHi) ", - "Hiroshi Nakamura (NaHi) "], [], :revoked, 2048, :RSA, :no_trust, :marginal), - SecurityCasing::Key.new("482ECB2BDAAC67D2", "A00620D62EA9B36A3BB71BDE482ECB2BDAAC67D2", ["severino "], ["2361077248315571916"], :valid, 4096, :RSA, :full, :no_trust), - ] - - end - - module Imprint - VALID = SecurityCasing::Imprint.new(Key::VALID_U_U, Time.now - 200_123, :SHA256, :valid) - INVALID = SecurityCasing::Imprint.new(Key::VALID_U_U, Time.now - 123_345, :SHA256, :invalid) - NO_MATCH = SecurityCasing::Imprint.new(nil, Time.now - 42_134, :SHA128, :no_match) - FROM_EXPIRED = SecurityCasing::Imprint.new(Key::EXPIRED_UN_UN, Time.now - 1_002_123, :SHA128, :from_expired) - FROM_REVOKED = SecurityCasing::Imprint.new(Key::REVOKED_F_UN, Time.now - 12_123, :SHA128, :from_revoked) - VALID_FROM_NO_TRUST = SecurityCasing::Imprint.new(Key::VALID_NO_TRUST, Time.now - 10_424, :SHA512, :valid) - - EXAMPLES = [ - VALID, - INVALID, - NO_MATCH, - FROM_EXPIRED, - FROM_REVOKED, - VALID_FROM_NO_TRUST - ] - end - - module Lock - VALID_TO_SPECIFIC = SecurityCasing::Lock.new(Key::VALID_U_U, :valid, :RSA, true) - VALID_NOT_TO_SPECIFIC = SecurityCasing::Lock.new(Key::VALID_U_U, :valid, :RSA, false) - - INVALID_TO_SPECIFIC = SecurityCasing::Lock.new(Key::VALID_U_U, :failure, :RSA, true) - INVALID_NOT_TO_SPECIFIC = SecurityCasing::Lock.new(Key::VALID_U_U, :failure, :RSA, false) - - EXPIRED_TO_SPECIFIC = SecurityCasing::Lock.new(Key::EXPIRED_UN_UN, :failure, :RSA, true) - EXPIRED_NOT_TO_SPECIFIC = SecurityCasing::Lock.new(Key::EXPIRED_UN_UN, :failure, :RSA, false) - - REVOKED_TO_SPECIFIC = SecurityCasing::Lock.new(Key::REVOKED_F_UN, :failure, :RSA, true) - REVOKED_NOT_TO_SPECIFIC = SecurityCasing::Lock.new(Key::REVOKED_F_UN, :failure, :RSA, false) - - NO_KEY_TO_SPECIFIC = SecurityCasing::Lock.new(nil, :no_private_key, :RSA, true) - NO_KEY_NOT_TO_SPECIFIC = SecurityCasing::Lock.new(nil, :no_private_key, :RSA, false) - - EXAMPLES = [ - VALID_TO_SPECIFIC, - VALID_NOT_TO_SPECIFIC, - INVALID_TO_SPECIFIC, - INVALID_NOT_TO_SPECIFIC, - EXPIRED_TO_SPECIFIC, - EXPIRED_NOT_TO_SPECIFIC, - REVOKED_TO_SPECIFIC, - REVOKED_NOT_TO_SPECIFIC, - NO_KEY_TO_SPECIFIC, - NO_KEY_NOT_TO_SPECIFIC - ] - end - - module Case - NO_IMPRINTS_OR_LOCKS = SecurityCasing.new([], []) - - ONE_VALID_IMPRINT = SecurityCasing.new([Imprint::VALID], []) - THREE_VALID_IMPRINTS = SecurityCasing.new([Imprint::VALID, - Imprint::VALID, - Imprint::VALID], []) - ONE_VALID_TWO_NO_MATCH_IMPRINTS = SecurityCasing.new([Imprint::NO_MATCH, - Imprint::VALID, - Imprint::NO_MATCH], []) - ONE_INVALID_IMPRINT = SecurityCasing.new([Imprint::INVALID], []) - ONE_NO_MATCH_IMPRINT = SecurityCasing.new([Imprint::NO_MATCH], []) - FROM_EXPIRED_IMPRINT = SecurityCasing.new([Imprint::FROM_EXPIRED], []) - FROM_REVOKED_IMPRINT = SecurityCasing.new([Imprint::FROM_REVOKED], []) - FROM_VALID_WITH_NO_TRUST_IMPRINT = SecurityCasing.new([Imprint::VALID_FROM_NO_TRUST], []) - - WITH_IMPRINTS = [ONE_VALID_IMPRINT, THREE_VALID_IMPRINTS, ONE_VALID_TWO_NO_MATCH_IMPRINTS, ONE_INVALID_IMPRINT, ONE_NO_MATCH_IMPRINT, FROM_EXPIRED_IMPRINT, FROM_REVOKED_IMPRINT, FROM_VALID_WITH_NO_TRUST_IMPRINT] - - VALID_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::VALID_TO_SPECIFIC]) - VALID_NOT_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::VALID_NOT_TO_SPECIFIC]) - ONE_VALID_TWO_INVALID_LOCKS = SecurityCasing.new([], [Lock::VALID_TO_SPECIFIC, Lock::INVALID_TO_SPECIFIC, Lock::INVALID_NOT_TO_SPECIFIC]) - ONE_VALID_TWO_NO_KEY_LOCKS = SecurityCasing.new([], [Lock::NO_KEY_TO_SPECIFIC, Lock::VALID_TO_SPECIFIC, Lock::NO_KEY_NOT_TO_SPECIFIC]) - INVALID_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::INVALID_TO_SPECIFIC]) - INVALID_NOT_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::INVALID_NOT_TO_SPECIFIC]) - EXPIRED_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::EXPIRED_TO_SPECIFIC]) - EXPIRED_NOT_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::EXPIRED_NOT_TO_SPECIFIC]) - REVOKED_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::REVOKED_TO_SPECIFIC]) - REVOKED_NOT_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::REVOKED_NOT_TO_SPECIFIC]) - NO_KEY_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::NO_KEY_TO_SPECIFIC]) - NO_KEY_NOT_TO_SPECIFIC_LOCK = SecurityCasing.new([], [Lock::NO_KEY_NOT_TO_SPECIFIC]) - - WITH_LOCKS = [VALID_TO_SPECIFIC_LOCK, VALID_NOT_TO_SPECIFIC_LOCK, ONE_VALID_TWO_INVALID_LOCKS, ONE_VALID_TWO_NO_KEY_LOCKS, INVALID_TO_SPECIFIC_LOCK, INVALID_NOT_TO_SPECIFIC_LOCK, - EXPIRED_TO_SPECIFIC_LOCK, EXPIRED_NOT_TO_SPECIFIC_LOCK, REVOKED_TO_SPECIFIC_LOCK, REVOKED_NOT_TO_SPECIFIC_LOCK, NO_KEY_TO_SPECIFIC_LOCK, NO_KEY_NOT_TO_SPECIFIC_LOCK] - - WITH_IMPRINTS_AND_LOCKS = WITH_IMPRINTS.product(WITH_LOCKS).map { |l, r| l + r } - - EXAMPLES = [NO_IMPRINTS_OR_LOCKS] + - WITH_IMPRINTS + - WITH_LOCKS + - WITH_IMPRINTS_AND_LOCKS - - class << self - def case_from(ident) - EXAMPLES[ident % EXAMPLES.size] - end - end - end - end -end diff --git a/fake-service/lib/smail/server.rb b/fake-service/lib/smail/server.rb deleted file mode 100644 index a5db8e03..00000000 --- a/fake-service/lib/smail/server.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'sinatra/base' -require 'sinatra/json' -require 'json' -require 'net/http' - -module Smail - class Server < Sinatra::Base - set :root, File.join(File.dirname(__FILE__), '../../') - - def json_body; JSON.parse request.body.read.to_s; end - - if ENV['RACK_ENV'] == 'staging' - get '/' do File.read(File.join(settings.root, 'public', 'index.html')) end - end - - get '/mails' do json mails(params["q"], (params["p"] || 0).to_i, (params["w"] || -1).to_i) end - delete '/mails' do json delete_mails(params["q"], (params["p"] || 0).to_i, (params["w"] || -1).to_i, params["idents"]) end - post '/mails/read' do json readmails(params["idents"], true) end - post '/mails/unread' do json readmails(params["idents"], false) end - get '/mail/:ident' do |i| json mail(i) end - delete '/mail/:ident' do |i| json delete_mail(i) end - post '/mail/:ident/star' do |i| json starmail(i, true) end - post '/mail/:ident/unstar' do |i| json starmail(i, false) end - post '/mail/:ident/replied' do |i| json repliedmail(i, true) end - post '/mail/:ident/unreplied' do |i| json repliedmail(i, false) end - post '/mail/:ident/read' do |i| json readmail(i, true) end - post '/mail/:ident/unread' do |i| json readmail(i, false) end - get '/mail/:ident/tags' do |i| json tags(i) end - post '/mail/:ident/tags' do |i| json settags(i, json_body) end - - get '/draft_reply_for/:ident' do |i| json draft_reply_for(i) end - - get '/contacts' do json contacts(params["q"], (params["p"] || 0).to_i, (params["w"] || -1).to_i) end - get '/contact/:ident' do |i| json contact(i) end - - get '/stats' do json stats end - - get '/personas' do json personas end - get '/persona/:ident' do |i| json persona(i) end - - get '/tags' do json all_tags(params["q"]) end - - post '/mails' do - ident = send_mail json_body - json({ ident: ident }) - end - - put '/mails' do - ident = update_mail json_body - json({ ident: ident }) - end - - post '/tags' do - tag = create_tag json_body - json({ tag: tag }) - end - - post '/control/create_mail' do json control_create_mail end - post '/control/delete_mails' do json control_delete_mails end - post '/control/mailset/:name/load' do |name| json control_mailset_load(name) end - - - # pass all other requests to asset server - get '/*' do - url = "http://localhost:9000/#{params['splat'][0]}" - - resp = Net::HTTP.get_response(URI.parse(url)) - if resp.is_a?(Net::HTTPSuccess) - res = resp.body.to_s.gsub(/(href|src)=("|')\//, '\1=\2' + url + '/') - content_type resp.content_type - status resp.code - res - else - status resp.code - resp.message - end - end - - - include Smail::Fake - end -end diff --git a/fake-service/lib/smail/sorted_mail.rb b/fake-service/lib/smail/sorted_mail.rb deleted file mode 100644 index 803a8348..00000000 --- a/fake-service/lib/smail/sorted_mail.rb +++ /dev/null @@ -1,57 +0,0 @@ -module Smail - class SortedMail - include Enumerable - - NEWEST_FIRST = lambda do |l,r| - (r.headers[:date] || Time.now) <=> (l.headers[:date] || Time.now) - end - - def initialize(&block) - @mails = {} - @mail_order = [] - @sort_procedure = block || NEWEST_FIRST - end - - def []=(k, v) - @mails[k] = v - @mail_order << v - sort_mail_order! - v - end - - def [](k) - @mails[k] - end - - def delete(k) - v = @mails.delete(k) - @mail_order.delete(v) - v - end - - def add_all(hs) - hs.each do |h,v| - @mails[h] = v - @mail_order << v - end - sort_mail_order! - self - end - - def length - @mails.length - end - - def sort_mail_order! - @mail_order.sort!(&@sort_procedure) - @mail_order.compact! - @mail_order.uniq! - end - - def each - @mail_order.each do |m| - yield m - end - end - end -end diff --git a/fake-service/lib/smail/stats.rb b/fake-service/lib/smail/stats.rb deleted file mode 100644 index 4e0393a4..00000000 --- a/fake-service/lib/smail/stats.rb +++ /dev/null @@ -1,60 +0,0 @@ - -module Smail - module Stats - class StatsCollector - include Stats - def initialize - stats_init - end - end - - attr_reader :stats - - def stats_init - @stats = { - total: 0, - read: 0, - starred: 0, - replied: 0 - } - end - - def stats_added(m) - @stats[:total] += 1 - stats_status_added(:read, m) if m.status?(:read) - stats_status_added(:replied, m) if m.status?(:replied) - stats_status_added(:starred, m) if m.status?(:starred) - end - - def stats_removed(m) - @stats[:total] -= 1 - stats_status_removed(:read, m) if m.status?(:read) - stats_status_removed(:replied, m) if m.status?(:replied) - stats_status_removed(:starred, m) if m.status?(:starred) - end - - def stats_status_added(s, m) - @stats[s] += 1 - end - - def stats_status_removed(s, m) - @stats[s] -= 1 - end - - def each_total_helper(enum) - if enum.respond_to?(:each_total) - enum.each_total { |x| yield x } - else - enum.each { |x| yield x } - end - end - - def with_stats(enum) - sc = StatsCollector.new - each_total_helper(enum) do |e| - sc.stats_added(e) - end - [sc.stats, enum] - end - end -end diff --git a/fake-service/lib/smail/stats_observer.rb b/fake-service/lib/smail/stats_observer.rb deleted file mode 100644 index f4f9b1cd..00000000 --- a/fake-service/lib/smail/stats_observer.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Smail - class StatsObserver - def initialize(stats) - @stats = stats - @stats.stats_init - end - - def mail_added(mail) - @stats.stats_added mail - end - - def mail_removed(mail) - @stats.stats_removed mail - end - - def mail_updated(before, after) - end - end -end diff --git a/fake-service/lib/smail/tags.rb b/fake-service/lib/smail/tags.rb deleted file mode 100644 index 0973fc3d..00000000 --- a/fake-service/lib/smail/tags.rb +++ /dev/null @@ -1,150 +0,0 @@ -module Smail - class Tag < Struct.new(:name, :total_count, :read, :starred, :replied, :default) - def to_json(*args) - { - name: self.name, - ident: self.name.hash.abs, - default: self.default, - counts: { - total: self.total_count, - read: self.read, - starred: self.starred, - replied: self.replied, - } - }.to_json(*args) - end - end - - class Tags - SPECIAL = %w(inbox sent drafts trash) - - def initialize(*desired_names) - @tags = desired_names.each_with_object({}) do |name, res| - res[normalized(name)] = Tags.get(name) - end - end - - def increase_all_count(t = nil) - change_all_count 1, t, :total_count - end - - def decrease_all_count(t = nil) - change_all_count -1, t, :total_count - end - - def change_all_count(v, t, s) - tags = t ? [t] : @tags.values - tags.each do |tag| - tag[s] += v - end - end - - def increase_status_count_checked(status, t = nil) - increase_status_count status, t if @mail.status?(status) - end - - def decrease_status_count_checked(status, t = nil) - decrease_status_count status, t if @mail.status?(status) - end - - def increase_status_count(status, t = nil) - change_all_count 1, t, status - end - - def decrease_status_count(status, t = nil) - change_all_count -1, t, status - end - - def decrease_all(t = nil) - decrease_all_count t - decrease_status_count_checked :read, t - decrease_status_count_checked :starred, t - decrease_status_count_checked :replied, t - end - - def increase_all(t = nil) - increase_all_count t - increase_status_count_checked :read, t - increase_status_count_checked :starred, t - increase_status_count_checked :replied, t - end - - def mail=(m) - decrease_all if @mail - @mail = m - increase_all if m - end - - def add_tag(name) - unless @tags[normalized(name)] - t = @tags[normalized(name)] = Tags.get(name) - increase_all t - end - end - - def remove(name) - if t = @tags[normalized(name)] - @tags.delete(normalized(name)) - decrease_all t - end - end - - def added_status(nm) - increase_status_count nm - end - - def removed_status(nm) - decrease_status_count nm - end - - def is_tagged?(t) - !!@tags[normalized(t.name)] - end - - def names - @tags.values.sort_by do |v| - [SPECIAL.index(normalized(v.name)) || 999, normalized(v.name)] - end.map(&:name) - end - - def normalized(n) - Tags.normalized(n) - end - - class <