diff -Naur instiki-0.9.1/app/controllers/wiki.rb instiki-0.9.1_notify-m1/app/controllers/wiki.rb --- instiki-0.9.1/app/controllers/wiki.rb Mon Jun 7 15:19:22 2004 +++ instiki-0.9.1_notify-m1/app/controllers/wiki.rb Thu Sep 23 17:36:34 2004 @@ -1,5 +1,7 @@ require "cgi" require "redcloth_for_tex" +require "net/smtp" +require "digest/md5" class WikiController < ActionControllerServlet EXPORT_DIRECTORY = File.dirname(__FILE__) + "/../../storage/" unless const_defined?("EXPORT_DIRECTORY") @@ -148,7 +150,11 @@ @params["password"].empty? ? nil : @params["password"], @params["published"] ? true : false, @params["brackets_only"] ? true : false, - @params["count_pages"] ? true : false + @params["count_pages"] ? true : false, + @params["smtp_server"], + @params["notify_from"], + @params["secret"], + @params["external_url"] ) redirect_show("HomePage", @params["address"]) @@ -165,7 +171,8 @@ # Within a single page -------------------------------------------------------- - def show + def show + @subscriber = read_cookie("subscriber") || "" if @page = wiki.read_page(web_address, page_name) begin render_action "page" @@ -237,8 +244,8 @@ web_address, page_name, @params["content"], Time.now, Author.new(@params["author"], remote_ip) ) - end - + end + write_cookie("author", @params["author"], true) redirect_show(page_name) end @@ -254,7 +261,41 @@ redirect_show end - + def subscribe + @page = wiki.read_page(web_address, page_name) + @subscriber = read_cookie("subscriber") || "" + render + end + + def verify + #EKBXXX Sensible default for external URL + digest = Digest::MD5.hexdigest(web.secret + @params["subscriber"]) + msg = [ "Subject: Confirm #{(@params["action"] == "unsubscribe") ? "unsubscribe from" : "subscribe to"} #{page_name} in #{web.name}\n", + "\n", + "Click this URL to confirm your subscription update:\n", + "\n", + "#{web.external_url}/confirm/#{page_name}?digest=#{CGI.escape(digest)}&subscriber=#{CGI.escape(@params["subscriber"])}&action=#{CGI.escape(@params["action"])}\n" ] + #EKBXXX need to generalize sending mail so we don't repeat this code for notification msgs. + #EKBXXX Make sure headers are set appropriately + Net::SMTP.start(web.smtp_server) do |smtp| + smtp.sendmail(msg, web.notify_from, @params["subscriber"] ) + end + write_cookie("subscriber", @params["subscriber"], true) + redirect_show(page_name) + end + + def confirm + @page = wiki.read_page(web_address, page_name) + @confirmed = Digest::MD5.hexdigest(web.secret + @params["subscriber"]) == @params["digest"] + if @confirmed + if @params["action"] == "subscribe" + wiki.subscribe_page(web_address, page_name, @params["subscriber"]) + else + wiki.unsubscribe_page(web_address, page_name, @params["subscriber"]) + end + end + render + end protected def before_action diff -Naur instiki-0.9.1/app/models/page.rb instiki-0.9.1_notify-m1/app/models/page.rb --- instiki-0.9.1/app/models/page.rb Mon Jun 7 15:19:22 2004 +++ instiki-0.9.1_notify-m1/app/models/page.rb Thu Sep 23 18:04:52 2004 @@ -9,23 +9,26 @@ CONTINOUS_REVISION_PERIOD = 30 * 60 # 30 minutes - attr_reader :name, :revisions, :web + attr_reader :name, :revisions, :web + attr_reader :subscribers def initialize(web, name, content, created_at, author) @web, @name, @revisions = web, name, [] - revise(content, created_at, author) + revise(content, created_at, author) + @subscribers = {} end def revise(content, created_at, author) if !@revisions.empty? && continous_revision?(created_at, author) - @revisions.last.created_at = Time.now + @revisions.last.created_at = created_at @revisions.last.content = content @revisions.last.clear_display_cache else @revisions << Revision.new(self, @revisions.length, content, created_at, author) end - web.refresh_pages_with_references(name) if @revisions.length == 1 + web.refresh_pages_with_references(name) if @revisions.length == 1 + end def rollback(revision_number, created_at, author_ip = nil) @@ -72,6 +75,38 @@ def author_link(options = {}) web.make_link(author, nil, options) + end + + def subscribe(subscriber) + @subscribers[subscriber] = true + end + + def unsubscribe(subscriber) + @subscribers.delete subscriber + end + + def notify_revision_number() + now = Time.now + puts "XXX notify_revision now=#{now} empty=#{@revisions.empty?}" + return nil if @revisions.empty? + puts "XXX last.notified=#{@revisions.last.notified}" + return nil if @revisions.last.notified + puts "XXX last.created_at=#{@revisions.last.created_at}" + if @revisions.last.created_at + CONTINOUS_REVISION_PERIOD < now + puts "XXX got it" + return @revisions.length - 1 + end + puts "XXX not the last revision, trying the next to last count=#{@revisions.length}" + return nil if @revisions.length < 2 + puts "XXX -2.notified=#{@revisions[-2].notified}" + return nil if @revisions[-2].notified + puts "XXX got the second last" + return @revisions.length - 2 + end + + def revision_notified(revision_number) + puts "XXX set notified for revision #{revision_number}" + @revisions[revision_number].notified = true; end private diff -Naur instiki-0.9.1/app/models/revision.rb instiki-0.9.1_notify-m1/app/models/revision.rb --- instiki-0.9.1/app/models/revision.rb Mon Jun 7 15:19:22 2004 +++ instiki-0.9.1_notify-m1/app/models/revision.rb Thu Sep 23 04:47:16 2004 @@ -10,10 +10,12 @@ require "page" class Revision - attr_accessor :page, :number, :content, :created_at, :author + attr_accessor :page, :number, :content, :created_at, :author + attr_accessor :notified def initialize(page, number, content, created_at, author) - @page, @number, @created_at, @author = page, number, created_at, author + @page, @number, @created_at, @author = page, number, created_at, author + @notified = false; self.content = content end diff -Naur instiki-0.9.1/app/models/web.rb instiki-0.9.1_notify-m1/app/models/web.rb --- instiki-0.9.1/app/models/web.rb Mon Jun 7 15:19:22 2004 +++ instiki-0.9.1_notify-m1/app/models/web.rb Thu Sep 23 03:26:40 2004 @@ -6,7 +6,8 @@ class Web attr_accessor :pages, :name, :address, :password - attr_accessor :markup, :color, :safe_mode, :additional_style, :published, :brackets_only, :count_pages + attr_accessor :markup, :color, :safe_mode, :additional_style, :published, :brackets_only, :count_pages + attr_accessor :smtp_server, :notify_from, :secret, :external_url def initialize(name, address, password = nil) @name, @address, @password, @safe_mode = name, address, password, false @@ -74,7 +75,11 @@ def markup() @markup || :textile end def color() @color || "008B26" end def brackets_only() @brackets_only || false end - def count_pages() @count_pages || false end + def count_pages() @count_pages || false end + def smtp_server() @smtp_server || "" end + def notify_from() @notify_from || "" end + def secret() @secret || "CHANGE ME" end + def external_url() @external_url || "" end private # Returns an array of all the wiki words in any current revision diff -Naur instiki-0.9.1/app/models/wiki_service.rb instiki-0.9.1_notify-m1/app/models/wiki_service.rb --- instiki-0.9.1/app/models/wiki_service.rb Mon Jun 7 15:19:22 2004 +++ instiki-0.9.1_notify-m1/app/models/wiki_service.rb Thu Sep 23 17:06:20 2004 @@ -6,10 +6,12 @@ require "author" class WikiService < MadeleineService - attr_reader :webs, :system + attr_reader :webs, :system + attr_reader :pending_notify def initialize - @webs, @system = {}, {} + @webs, @system = {}, {} + @pending_notify = {} end def setup? @@ -30,7 +32,7 @@ end def update_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false, - password = nil, published = false, brackets_only = false, count_pages = false) + password = nil, published = false, brackets_only = false, count_pages = false, smtp_server="", notify_from="", secret="CHANGE ME", external_url="") if old_address != new_address @webs[new_address] = @webs[old_address] @webs.delete(old_address) @@ -44,7 +46,12 @@ name, markup, color, additional_style, safe_mode web.password, web.published, web.brackets_only, web.count_pages = - password, published, brackets_only, count_pages + password, published, brackets_only, count_pages + + # EKBXXX smtp_server and secret should definitely be system-wide. notify_from and external_url should also be, but should be over-rideable by the web + # EKBXXX also need smtp port, username, password, etc. + web.smtp_server, web.notify_from, web.secret, web.external_url = smtp_server, notify_from, secret, external_url + end def read_page(web_address, page_name) @@ -53,24 +60,46 @@ def write_page(web_address, page_name, content, written_on, author) page = Page.new(@webs[web_address], page_name, content, written_on, author) - @webs[web_address].add_page(page) + @webs[web_address].add_page(page) + pending_notify[[web_address, page_name]] = true page end def revise_page(web_address, page_name, content, revised_on, author) page = read_page(web_address, page_name) - page.revise(content, revised_on, author) + page.revise(content, revised_on, author) + pending_notify[[web_address, page_name]] = true page end def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil) page = read_page(web_address, page_name) - page.rollback(revision_number, created_at, author_id) + page.rollback(revision_number, created_at, author_id) + pending_notify[[web_address, page_name]] = true page end def remove_orphaned_pages(web_address) @webs[web_address].remove_pages(@webs[web_address].select.orphaned_pages) + end + + def subscribe_page(web_address, page_name, subscriber) + page = read_page(web_address, page_name) + page.subscribe(subscriber) + end + + def unsubscribe_page(web_address, page_name, subscriber) + page = read_page(web_address, page_name) + page.unsubscribe(subscriber) + end + + def revision_notified(web_address, page_name, revision_number) + page = read_page(web_address, page_name) + page.revision_notified(revision_number) + if page.revisions.length - 1 == revision_number + puts "XXX Removing #{web_address}/#{page_name} from notify pending list" + @pending_notify.delete [web_address, page_name] + end end private diff -Naur instiki-0.9.1/app/others/notify.rb instiki-0.9.1_notify-m1/app/others/notify.rb --- instiki-0.9.1/app/others/notify.rb Thu Jan 1 00:00:00 1970 +++ instiki-0.9.1_notify-m1/app/others/notify.rb Thu Sep 23 18:16:18 2004 @@ -0,0 +1,45 @@ +class Notifier + + def Notifier.start + Thread.new do + wiki = WikiService.instance + while true + puts "XXX Checking for notifications" + begin + wiki.pending_notify.each do | web_page, val | + web_name, page_name = *web_page + puts "XXX Checking #{web_name}/#{page_name}" + web = wiki.webs[web_name] + page = wiki.read_page(web_name, page_name) + rev = page.notify_revision_number + puts "XXX revision = #{rev}" + if rev + unless web.smtp_server.empty? || page.subscribers.empty? + #EKBXXX sensible default external URL + puts "XXX sending msg to #{page.subscribers.keys}" + msg = [ "Subject: #{page.name} in #{web.name} has been #{rev == 0 ? "created" : "revised"}\n", + "\n", + "Click this URL to view the page:\n", + "\n", + "#{web.external_url}/show/#{CGI.escape(page.name)}\n", + "\n", + "To stop recieving these notifications, click the\n", + "Unsubscribe link at the bottom of the above page.\n"] + #EKBXXX need to generalize sending mail so we don't repeat this code for e-mail verification msgs. + #EKBXXX Make sure headers are set appropriately for bulk mail + Net::SMTP.start(web.smtp_server) do |smtp| + smtp.sendmail(msg, web.notify_from, page.subscribers.keys) + end + end + wiki.revision_notified(web_name, page_name, rev) + end + end + rescue + puts "Exception in notification polling thread: #{$!} #{$@.first}" + end + puts "XXX waiting 15 mins" + sleep(60 * 15) # 15 minutes between checks for notifications (EKBXXX make constant) + end + end + end +end diff -Naur instiki-0.9.1/app/views/wiki/confirm.rhtml instiki-0.9.1_notify-m1/app/views/wiki/confirm.rhtml --- instiki-0.9.1/app/views/wiki/confirm.rhtml Thu Jan 1 00:00:00 1970 +++ instiki-0.9.1_notify-m1/app/views/wiki/confirm.rhtml Thu Sep 23 03:37:48 2004 @@ -0,0 +1,16 @@ +<% + @title = "Confirm Update of Subscription to #{@page.name}" + @content_width = 720 + @hide_navigation = true +%><%= sub_template "top" %> + +
> + Your <%= (@params["action"] == "unsubscribe") ? "unsubscription from" : "subscription to" %> <%= @page.name %> has + <% if @confirmed %> + been confirmed. + <% else %> + NOT been confirmed.
Check that the URL is identical to that sent in the e-mail. + <% end %> +
+ +<%= sub_template "bottom" %> \ No newline at end of file diff -Naur instiki-0.9.1/app/views/wiki/edit_web.rhtml instiki-0.9.1_notify-m1/app/views/wiki/edit_web.rhtml --- instiki-0.9.1/app/views/wiki/edit_web.rhtml Mon Jun 7 15:19:22 2004 +++ instiki-0.9.1_notify-m1/app/views/wiki/edit_web.rhtml Thu Sep 23 17:35:56 2004 @@ -69,6 +69,41 @@diff -Naur instiki-0.9.1/app/views/wiki/page.rhtml instiki-0.9.1_notify-m1/app/views/wiki/page.rhtml --- instiki-0.9.1/app/views/wiki/page.rhtml Mon Jun 7 15:19:23 2004 +++ instiki-0.9.1_notify-m1/app/views/wiki/page.rhtml Thu Sep 23 01:20:28 2004 @@ -59,7 +59,16 @@ | Linked from: <%= @page.references.collect { |ref| ref.link }.join(", ") %> - <% end %> + <% end %> + + <% unless web.smtp_server.empty? %> + <% if @page.subscribers.include?(@subscriber) %> + | Unsubscribe + <% else %> + | Subscribe + <% end %> + <% end %> +