Soks 0.0.7 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +2 -0
- data/README.txt +3 -2
- data/TODO.txt +31 -0
- data/bin/soks-create-wiki.rb +0 -1
- data/lib/authenticators.rb +30 -4
- data/lib/helpers/counter-helpers.rb +132 -0
- data/lib/helpers/default-helpers.rb +170 -169
- data/lib/helpers/mail2wiki-helper.rb +18 -22
- data/lib/helpers/maintenance-helpers.rb +149 -0
- data/lib/helpers/rss2wiki-helper.rb +7 -8
- data/lib/soks-model.rb +82 -54
- data/lib/soks-servlet.rb +126 -108
- data/lib/soks-storage.rb +74 -11
- data/lib/soks-utils.rb +77 -3
- data/lib/soks-view.rb +169 -103
- data/lib/soks.rb +5 -23
- data/templates/default/attachment/newpage.js +4 -13
- data/templates/default/attachment/print_stylesheet.css +2 -7
- data/templates/default/caches/readme.txt +1 -0
- data/templates/default/content/Api%20for%20classes%20to%20modify%20the%20wiki.textile +2 -0
- data/templates/default/content/Author.textile +4 -1
- data/templates/default/content/Automatic%20Summaries.textile +16 -53
- data/templates/default/content/Automatic%20linking%20between%20pages.textile +3 -3
- data/templates/default/content/{bug%3A%20competing%20edits.textile → Bug%3A%20Competing%20edits.textile} +9 -0
- data/templates/default/content/Bug%3A%20Does%20not%20make%20use%20of%20if%2Dmodified%2Dsince%20r.textile +2 -0
- data/templates/default/content/Bug%3A%20E%2Dmail%20addresses%20with%20hyphens%20not%20recognised.textile +17 -0
- data/templates/default/content/Bug%3A%20Email%20adresses%20in%20page%20titles%20cause%20incorrec.textile +3 -0
- data/templates/default/content/Bug%3A%20GEM%20limits%20title%20lengths.textile +3 -1
- data/templates/default/content/Bug%3A%20Memory%20leak.textile +13 -0
- data/templates/default/content/Bug%3A%20Pages%20that%20link%20here%20may%20not%20appear%20on%20r.textile +13 -0
- data/templates/default/content/Bug%3A%20Textile%20mishandles%20paragraphs.textile +4 -0
- data/templates/default/content/Bug%3A%20Unanticipated%20Rollbacks.textile +2 -0
- data/templates/default/content/Bug%3A%20notextile%20does%20not%20prevent%20page%20inserts.textile +2 -0
- data/templates/default/content/Home%20Page.textile +3 -1
- data/templates/default/content/How%20to%20administrate%20this%20wiki.textile +23 -13
- data/templates/default/content/How%20to%20change%20the%20way%20this%20wiki%20looks.textile +3 -1
- data/templates/default/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +22 -0
- data/templates/default/content/How%20to%20get%20the%20latest%20Soks%20from%20cvs.textile +2 -0
- data/templates/default/content/How%20to%20hack%20soks.textile +2 -0
- data/templates/default/content/How%20to%20import%20a%20site%20from%20instiki.textile +2 -0
- data/templates/default/content/{How%20to%20import%20data%20to%20this%20wiki.textile → How%20to%20import%20data.textile} +3 -7
- data/templates/default/content/How%20to%20install%20Soks.textile +2 -0
- data/templates/default/content/How%20to%20password%20protect%20your%20wiki.textile +21 -11
- data/templates/default/content/How%20to%20report%20a%20bug.textile +2 -1
- data/templates/default/content/How%20to%20upgrade%20soks.textile +22 -0
- data/templates/default/content/How%20to%20use%20the%20keyboard%20shortcuts.textile +2 -2
- data/templates/default/content/How%20to%20use%20this%20wiki.textile +3 -1
- data/templates/default/content/List%20of%20changes.textile +84 -118
- data/templates/default/content/News%3A%20Version%201%2D0%2D0%20released.textile +19 -0
- data/templates/default/content/Pages%20to%20include%20in%20the%20distribution.textile +51 -0
- data/templates/default/content/Per%20Wiki%20Templates.textile +2 -0
- data/templates/default/content/Planned%20Features.textile +30 -9
- data/templates/default/content/README.textile +3 -2
- data/templates/default/content/RSS%20feed.textile +1 -1
- data/templates/default/content/Recent%20changes%20to%20this%20site.textile +283 -0
- data/templates/default/content/SOKS%20features.textile +3 -0
- data/templates/default/content/Site%20Index.textile +202 -0
- data/templates/default/content/Soks%20Licence.textile +2 -0
- data/templates/default/content/Tag%3A%20Include%20this%20page%20in%20the%20distribution.textile +6 -0
- data/templates/default/start.rb +67 -123
- data/templates/default/version.txt +1 -1
- data/templates/default/views/Page_edit.rhtml +7 -7
- data/templates/default/views/{Page_search_results.rhtml → Page_find.rhtml} +9 -3
- data/templates/default/views/Page_linksfromrss.rhtml +24 -0
- data/templates/default/views/Page_listrss.rhtml +46 -0
- data/templates/default/views/Page_meta.rhtml +1 -1
- data/templates/default/views/Page_revision.rhtml +39 -0
- data/templates/default/views/Page_revisions.rhtml +13 -5
- data/templates/default/views/Page_rss.rhtml +8 -8
- data/templates/default/views/Page_view.rhtml +3 -3
- data/templates/default/views/UploadPage_edit.rhtml +8 -8
- data/templates/default/views/frame.rhtml +8 -8
- data/templates/default/views/messages.yaml +1 -0
- data/test/html/2006Mar.html +66 -0
- data/test/html/poignant.html +36 -0
- data/test/html/poignant.textile +36 -0
- data/test/mock-objects.rb +69 -0
- data/test/stress_url_calls.rb +33 -0
- data/test/stress_urls.txt +68 -0
- data/test/test_counter-helper.rb +158 -0
- data/test/test_soks-helper-maintenance.rb +106 -0
- data/test/test_soks-helpers.rb +104 -0
- data/test/test_soks-model.rb +144 -0
- data/test/test_soks-servlet.rb +231 -0
- data/test/test_soks-storage.rb +70 -31
- data/test/test_soks-utils.rb +112 -13
- data/test/test_soks-view.rb +141 -3
- metadata +38 -27
- data/templates/default/content/A%20page%20with%20an%20umlaut%20%F6%20in%20its%20title.textile +0 -1
- data/templates/default/content/All%20News.textile +0 -26
- data/templates/default/content/Bil%20Kleb.textile +0 -1
- data/templates/default/content/Bil.textile +0 -1
- data/templates/default/content/Bill%20Wood.textile +0 -3
- data/templates/default/content/Bug%3A%20RSS%20feed%20does%20not%20validate.textile +0 -10
- data/templates/default/content/Bug%3A%20Type%20a%20title%20here.textile +0 -31
- data/templates/default/content/Instructions%20and%20Howtos.textile +0 -21
- data/templates/default/content/Latest%20News.textile +0 -26
- data/templates/default/content/New%20Recent%20Changes%20class.textile +0 -68
- data/templates/default/content/New%20page%20templates%20or%20categories%20code.textile +0 -68
- data/templates/default/content/News%3A%20Version%200%2E0%2E6%20Released.textile +0 -13
- data/templates/default/content/Recent%20Blog%20Entries.textile +0 -5
- data/templates/default/content/Recent%20Changes%20to%20This%20Site.textile +0 -286
- data/templates/default/content/Ruby.textile +0 -9
- data/templates/default/content/Skorgu.textile +0 -3
- data/templates/default/content/ctrl%2Dn.textile +0 -1
- data/templates/default/content/let%20me%20know.textile +0 -1
- data/templates/default/content/sandbox.textile +0 -20
- data/templates/default/content/tamc.textile +0 -1
- data/templates/default/content/tamc2.textile +0 -1
@@ -3,7 +3,7 @@ require 'net/imap'
|
|
3
3
|
# From Dave Burt on comp.lang.ruby
|
4
4
|
class String
|
5
5
|
def from_quoted_printable
|
6
|
-
self.gsub(/\r\n/, "\n").unpack("M").first
|
6
|
+
self.gsub(/\r\n/, "\n").gsub(/=(?![\dA-F]{2})/,'=3D').unpack("M").first
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
@@ -14,20 +14,16 @@ class Message
|
|
14
14
|
def initialize( imap, message_id )
|
15
15
|
@imap, @message_id = imap, message_id
|
16
16
|
envelope = @imap.fetch( @message_id, 'ENVELOPE' ).first.attr['ENVELOPE']
|
17
|
-
@subject = envelope['subject']
|
18
|
-
@sender_name = envelope['from'].first['name']
|
17
|
+
@subject = envelope['subject'].gsub(/^(Fw|Re):?/i,'').strip
|
18
|
+
@sender_name = envelope['from'].first['name'].gsub(/@/,' at ')
|
19
19
|
@date = envelope['date']
|
20
|
-
@sender_email = envelope['from'].first['mailbox'] + '
|
20
|
+
@sender_email = envelope['from'].first['mailbox'] + ' at ' + envelope['from'].first['host']
|
21
|
+
@sender_name = @sender_email unless @sender_name && @sender_name.size > 1
|
21
22
|
@text = plain_text_content_from_message( message_id )
|
22
23
|
end
|
23
24
|
|
24
25
|
def plain_text_content_from_message( id )
|
25
|
-
|
26
|
-
text
|
27
|
-
end
|
28
|
-
|
29
|
-
def textile
|
30
|
-
text.gsub(/[\*-]{2,}/,'').gsub(/([^\n])\n([^\n])/,'\1\2').gsub(/^[ \t]+/,'')
|
26
|
+
@imap.fetch( id, 'BODY[1]' ).first.attr['BODY[1]'].from_quoted_printable
|
31
27
|
end
|
32
28
|
|
33
29
|
end
|
@@ -38,31 +34,29 @@ class Mail2WikiHelper
|
|
38
34
|
:server => 'imap.hermes.cam.ac.uk',
|
39
35
|
:username => 'tamc2',
|
40
36
|
:password => 'missing_a_password',
|
41
|
-
:mailbox => '
|
42
|
-
:
|
37
|
+
:mailbox => 'test',
|
38
|
+
:check_event => :hour,
|
43
39
|
:subject_regexp => /.*/,
|
40
|
+
:keyword => 'PutInWiki'
|
44
41
|
}
|
45
42
|
|
46
43
|
def initialize( wiki, settings = {} )
|
47
44
|
@settings = DEFAULT_SETTINGS.merge( settings )
|
48
45
|
@wiki = wiki
|
49
46
|
check_mailbox
|
50
|
-
|
51
|
-
loop do
|
52
|
-
sleep @settings[:check_period ]
|
53
|
-
check_mailbox
|
54
|
-
end
|
55
|
-
end
|
47
|
+
@wiki.watch_for(@settings[:check_event]) { check_mailbox }
|
56
48
|
end
|
57
49
|
|
58
50
|
private
|
59
51
|
|
60
52
|
def check_mailbox
|
53
|
+
$LOG.info "Checking #{@settings[:mailbox]} on #{@settings[:server]}"
|
61
54
|
login
|
62
55
|
select_mailbox
|
63
56
|
new_messages_for_wiki do |message_id|
|
64
57
|
this_message = Message.new( @imap, message_id )
|
65
58
|
if this_message.subject =~ @settings[:subject_regexp]
|
59
|
+
$LOG.info "Adding '#{this_message.subject}' to wiki"
|
66
60
|
add_message_to_wiki( this_message )
|
67
61
|
mark_as_added( message_id )
|
68
62
|
end
|
@@ -78,8 +72,10 @@ class Mail2WikiHelper
|
|
78
72
|
text = current_page.textile
|
79
73
|
end
|
80
74
|
text << "\n\n"
|
81
|
-
text << "#{message.date} from #{message.sender_name} #{message.sender_email}\n"
|
82
|
-
text <<
|
75
|
+
text << "*Copied from Email on #{message.date} from #{message.sender_name} (#{message.sender_email})*\n\n"
|
76
|
+
text << "<pre>\n"
|
77
|
+
text << message.text
|
78
|
+
text << "\n</pre>\n"
|
83
79
|
@wiki.revise(message.subject, text, message.sender_name )
|
84
80
|
end
|
85
81
|
|
@@ -98,11 +94,11 @@ class Mail2WikiHelper
|
|
98
94
|
end
|
99
95
|
|
100
96
|
def new_messages_for_wiki
|
101
|
-
@imap.search(
|
97
|
+
@imap.search("UNKEYWORD #{@settings[:keyword]}").each { |id| yield id }
|
102
98
|
end
|
103
99
|
|
104
100
|
def mark_as_added( id )
|
105
|
-
|
101
|
+
@imap.store( id, '+FLAGS', [@settings[:keyword]] )
|
106
102
|
end
|
107
103
|
|
108
104
|
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
class DeleteOldPagesHelper
|
2
|
+
|
3
|
+
# Default wakes up each day at midnight and wipes all deleted pages more than 100 days old
|
4
|
+
def initialize( wiki, event_to_check_on = :day, age_to_wipe_at = 60*60*24*100 )
|
5
|
+
@wiki = wiki
|
6
|
+
@age_to_wipe_at = age_to_wipe_at
|
7
|
+
@wiki.watch_for(event_to_check_on) { check_and_delete_pages }
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def check_and_delete_pages
|
13
|
+
@wiki.each(false) do |name, page|
|
14
|
+
next unless page.deleted?
|
15
|
+
next unless old_enough_to_wipe?( page )
|
16
|
+
$LOG.warn "Permanently wiping #{name} from wiki AND from disk"
|
17
|
+
@wiki.wipe_from_disk( name )
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def old_enough_to_wipe?( page )
|
22
|
+
(Time.now - page.revised_on) > @age_to_wipe_at
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class DeleteOldRevisionsHelper
|
28
|
+
|
29
|
+
AUTHOR = 'Automatic Revision Remover'
|
30
|
+
|
31
|
+
# Default wakes up each day at midnight and wipes all revisions more than 100 days old if there are more than 20 revisions in the page
|
32
|
+
def initialize( wiki, event_to_check_on = :day, age_to_wipe_at = 60*60*24*365, maximum_revisions = 20 )
|
33
|
+
@wiki = wiki
|
34
|
+
@age_to_wipe_at = age_to_wipe_at
|
35
|
+
@maximum_revisions = maximum_revisions
|
36
|
+
@wiki.watch_for(event_to_check_on) { check_and_delete_revisions }
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def check_and_delete_revisions
|
42
|
+
@wiki.each do |name, page|
|
43
|
+
next unless page.revisions.size > @maximum_revisions
|
44
|
+
next unless old_enough_to_delete?( page.revisions.first )
|
45
|
+
delete_old_revisions_from( page )
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete_old_revisions_from( page )
|
50
|
+
page.content_lock.synchronize do
|
51
|
+
delete_revisions_at_or_below = page.revisions.size - @maximum_revisions - 1
|
52
|
+
delete_revisions_at_or_below -= 1 while not old_enough_to_delete? page.revisions[delete_revisions_at_or_below]
|
53
|
+
|
54
|
+
new_revisions = []
|
55
|
+
new_revisions << Revision.new( page,
|
56
|
+
new_revisions.length,
|
57
|
+
page.revisions[delete_revisions_at_or_below].content.changes_from(""),
|
58
|
+
AUTHOR,
|
59
|
+
page.revisions[delete_revisions_at_or_below].created_on )
|
60
|
+
|
61
|
+
page.revisions[ (delete_revisions_at_or_below+1)..page.revisions.size].each do |revision|
|
62
|
+
new_revisions << Revision.new( page,
|
63
|
+
new_revisions.length,
|
64
|
+
revision.changes,
|
65
|
+
revision.author,
|
66
|
+
revision.created_on )
|
67
|
+
end
|
68
|
+
page.revisions = new_revisions
|
69
|
+
@wiki.save_all_revisions( page )
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def old_enough_to_delete?( revision )
|
74
|
+
(Time.now - revision.created_on) > @age_to_wipe_at
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class MergeOldRevisionsHelper
|
79
|
+
|
80
|
+
# Default wakes up each hour and merges all revisions more than 24 hours old, by the same author, and that are created within an hour of each other
|
81
|
+
def initialize( wiki, event_to_check_on = :day, minimum_age_to_merge = 60*60*24*365, maximum_time_between_revisions_for_merge = 60*60 )
|
82
|
+
@wiki = wiki
|
83
|
+
@minimum_age_to_merge = minimum_age_to_merge
|
84
|
+
@maximum_time_between_revisions_for_merge = maximum_time_between_revisions_for_merge
|
85
|
+
@wiki.watch_for(event_to_check_on) { check_for_pages_to_merge }
|
86
|
+
end
|
87
|
+
|
88
|
+
def check_for_pages_to_merge
|
89
|
+
@wiki.each do |name, page|
|
90
|
+
page.content_lock.synchronize do
|
91
|
+
check_revisions_to_merge_on page
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def check_revisions_to_merge_on( page )
|
97
|
+
return if page.empty?
|
98
|
+
change_made = false
|
99
|
+
new_revisions = []
|
100
|
+
next_revision = page.revisions.first
|
101
|
+
while next_revision
|
102
|
+
ending_revision = next_revision
|
103
|
+
|
104
|
+
while can_merge?( next_revision, ending_revision.following_revision )
|
105
|
+
ending_revision = ending_revision.following_revision
|
106
|
+
end
|
107
|
+
|
108
|
+
changes = if ending_revision == next_revision
|
109
|
+
next_revision.changes
|
110
|
+
else
|
111
|
+
change_made = true
|
112
|
+
ending_revision.content.changes_from( next_revision.previous_content )
|
113
|
+
end
|
114
|
+
|
115
|
+
new_revisions << Revision.new( page,
|
116
|
+
new_revisions.length,
|
117
|
+
changes,
|
118
|
+
ending_revision.author,
|
119
|
+
ending_revision.created_on )
|
120
|
+
|
121
|
+
next_revision = ending_revision.following_revision
|
122
|
+
end
|
123
|
+
if change_made
|
124
|
+
page.revisions = new_revisions
|
125
|
+
@wiki.save_all_revisions( page )
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def can_merge?( revision_a, revision_b )
|
130
|
+
return false unless revision_a && revision_b
|
131
|
+
return false unless same_author?( revision_a, revision_b )
|
132
|
+
return false unless revised_at_a_similar_time?( revision_a, revision_b )
|
133
|
+
return false unless not_to_recent?( revision_a )
|
134
|
+
return false unless not_to_recent?( revision_b )
|
135
|
+
true
|
136
|
+
end
|
137
|
+
|
138
|
+
def same_author?( a, b )
|
139
|
+
a.author == b.author
|
140
|
+
end
|
141
|
+
|
142
|
+
def revised_at_a_similar_time?( a, b )
|
143
|
+
(a.revised_on - b.revised_on).abs < @maximum_time_between_revisions_for_merge
|
144
|
+
end
|
145
|
+
|
146
|
+
def not_to_recent?( a_revision )
|
147
|
+
(Time.now - a_revision.revised_on) > @minimum_age_to_merge
|
148
|
+
end
|
149
|
+
end
|
@@ -7,19 +7,18 @@ class RSS2WikiHelper
|
|
7
7
|
DEFAULT_SETTINGS = {
|
8
8
|
:url => 'http://localhost:8000/rss/recent%20changes%20to%20this%20site',
|
9
9
|
:pagename => nil, # If nil, uses channel title,
|
10
|
-
:
|
10
|
+
:update_on_event => :hour,
|
11
11
|
:author => 'AutomaticRSS2Wiki',
|
12
12
|
}
|
13
13
|
|
14
14
|
def initialize( wiki, settings = {} )
|
15
15
|
@settings = DEFAULT_SETTINGS.merge( settings )
|
16
16
|
@wiki = wiki
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
17
|
+
update_rss
|
18
|
+
update_wiki
|
19
|
+
@wiki.watch_for(@settings[:update_on_event]) do
|
20
|
+
update_rss
|
21
|
+
update_wiki
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
@@ -36,7 +35,7 @@ class RSS2WikiHelper
|
|
36
35
|
end
|
37
36
|
|
38
37
|
def update_rss
|
39
|
-
$
|
38
|
+
$LOG.info "Updating feed"
|
40
39
|
open(@settings[:url]) do |http|
|
41
40
|
@rss = RSS::Parser.parse( http.read , false)
|
42
41
|
end
|
data/lib/soks-model.rb
CHANGED
@@ -2,8 +2,8 @@ require 'thread'
|
|
2
2
|
|
3
3
|
# Revision stores changes as a diff against the more recent content version
|
4
4
|
class Revision
|
5
|
-
attr_reader :number,
|
6
|
-
attr_reader
|
5
|
+
attr_reader :number, :author, :page
|
6
|
+
attr_reader :changes, :created_on
|
7
7
|
alias :revised_on :created_on
|
8
8
|
|
9
9
|
def initialize( page, number, changes, author, created_on = Time.now )
|
@@ -13,7 +13,7 @@ class Revision
|
|
13
13
|
# Recreates the content of the page AFTER this revision had been made.
|
14
14
|
# Done by recursively applying diffs to more recent versions.
|
15
15
|
def content
|
16
|
-
|
16
|
+
following_revision ? following_revision.previous_content : page.content
|
17
17
|
end
|
18
18
|
|
19
19
|
# Recreateds the content of the page BEFORE this revision had been made
|
@@ -21,8 +21,14 @@ class Revision
|
|
21
21
|
content.split("\n").unpatch!(@changes).join("\n")
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
24
|
+
def previous_revision
|
25
|
+
return nil if number == 0
|
26
|
+
page.revision( number - 1 )
|
27
|
+
end
|
28
|
+
|
29
|
+
def following_revision
|
30
|
+
page.revision( number + 1 )
|
31
|
+
end
|
26
32
|
|
27
33
|
def method_missing( symbol, *args )
|
28
34
|
raise(ArgumentError, "Revision does not respond to #{symbol}", caller) unless @page && @page.respond_to?( symbol )
|
@@ -41,10 +47,9 @@ class Page
|
|
41
47
|
|
42
48
|
# Returns an empty version of itself.
|
43
49
|
def self.empty( name )
|
44
|
-
|
45
|
-
|
50
|
+
empty = self.new( name )
|
51
|
+
empty.revise( $MESSAGES[:Type_what_you_want_here_and_click_save], "NoOne" )
|
46
52
|
class << empty
|
47
|
-
def textile; "[[#{$MESSAGES[:Create]} #{name} => /edit/#{name} ]]" end
|
48
53
|
def empty?; true; end
|
49
54
|
end
|
50
55
|
empty
|
@@ -60,15 +65,9 @@ class Page
|
|
60
65
|
# Revises the content of this page, creating a new revision class that stores the changes
|
61
66
|
def revise( new_content, author )
|
62
67
|
return nil if new_content == @content
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
# @revisions[-1] = Revision.new( self, @revisions.length - 1, changes , author )
|
67
|
-
#else
|
68
|
-
changes = changes_between( @content, new_content )
|
69
|
-
return nil if changes.empty?
|
70
|
-
@revisions << Revision.new( self, @revisions.length, changes , author )
|
71
|
-
#end
|
68
|
+
changes = new_content.changes_from @content
|
69
|
+
return nil if changes.empty?
|
70
|
+
@revisions << Revision.new( self, @revisions.length, changes , author )
|
72
71
|
@content = new_content
|
73
72
|
@revisions.last
|
74
73
|
end
|
@@ -76,13 +75,13 @@ class Page
|
|
76
75
|
# Returns the content of this page to that of a previous version
|
77
76
|
def rollback( number, author )
|
78
77
|
revise( ( number < 0 ) ? $MESSAGES[:page_deleted] : @revisions[ number ].content, author )
|
79
|
-
end
|
78
|
+
end
|
80
79
|
|
81
80
|
def revision( number ) @revisions[ number ] end
|
82
81
|
|
83
82
|
def deleted?
|
84
83
|
( content =~ /^#{$MESSAGES[:page_deleted]}/i ) ||
|
85
|
-
( content =~ /^#{$MESSAGES[:content_moved_to]} /i )
|
84
|
+
( content =~ /^#{$MESSAGES[:content_moved_to]} /i ) ? true : false
|
86
85
|
end
|
87
86
|
|
88
87
|
def empty?; @revisions.empty? end
|
@@ -96,10 +95,10 @@ class Page
|
|
96
95
|
def is_inserted_into( page )
|
97
96
|
@links_lock.synchronize { @inserted_into << page unless @inserted_into.include? page }
|
98
97
|
end
|
99
|
-
|
100
|
-
def textile( content = @content ) content end
|
101
98
|
|
102
99
|
def name_for_index; name.downcase end
|
100
|
+
|
101
|
+
# Refactored changes_between into the String class in soks-utils
|
103
102
|
|
104
103
|
# Any unhandled calls are passed onto the latest revision (e.g. author, creation time etc)
|
105
104
|
def method_missing( symbol, *args )
|
@@ -109,20 +108,10 @@ class Page
|
|
109
108
|
raise ArgumentError,"Page does not respond to #{symbol}", caller
|
110
109
|
end
|
111
110
|
end
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
def
|
116
|
-
old_content.split("\n").diff( new_content.split("\n") ).map { |changeset| changeset.map { |change| change.to_a } }
|
117
|
-
end
|
118
|
-
|
119
|
-
# This is no longer used, because I've found it confuses people.
|
120
|
-
def the_same_author_recently_revised_this_page( author )
|
121
|
-
return false if empty?
|
122
|
-
return false unless author == self.author
|
123
|
-
((Time.now - self.revised_on) < (60*5) ) # 5 Minutes
|
124
|
-
end
|
125
|
-
|
111
|
+
end
|
112
|
+
|
113
|
+
class EmptyPage < Page
|
114
|
+
def empty?; true; end
|
126
115
|
end
|
127
116
|
|
128
117
|
#Serves as a marker, so ImagePage and AttachmentPage can re-use the same view templates
|
@@ -130,43 +119,50 @@ class UploadPage < Page
|
|
130
119
|
end
|
131
120
|
|
132
121
|
class ImagePage < UploadPage
|
133
|
-
def textile( content = @content )
|
134
|
-
deleted? ? content : "!#{$SETTINGS[:url]}#{content.strip}!:#{$SETTINGS[:url]}/view/#{@name.url_encode}"
|
135
|
-
end
|
136
|
-
|
137
122
|
def name_for_index; @name[10..-1].strip.downcase end
|
138
123
|
end
|
139
124
|
|
140
125
|
class AttachmentPage < UploadPage
|
141
|
-
def textile( content = @content )
|
142
|
-
deleted? ? content : %Q{[[ #{name} => #{$SETTINGS[:url]}#{content} ]]\n}
|
143
|
-
end
|
144
126
|
def name_for_index; @name[ 9..-1].strip.downcase end
|
145
127
|
end
|
146
128
|
|
147
129
|
# This class has turned into a behmoth, need to refactor.
|
148
130
|
class Wiki
|
149
131
|
include WikiFlatFileStore
|
132
|
+
include WikiCacheStore
|
150
133
|
include Enumerable
|
151
134
|
include Notify # Will notify any watchers if underlying files change
|
135
|
+
|
136
|
+
attr_accessor :check_files_every
|
152
137
|
|
153
138
|
PAGE_CLASSES = [
|
154
139
|
[ /^picture of/i, ImagePage ],
|
155
140
|
[ /^attached/i, AttachmentPage ],
|
156
141
|
[ /.*/, Page ]
|
157
142
|
]
|
143
|
+
|
144
|
+
CACHE_NAME = 'pages'
|
158
145
|
|
159
|
-
def initialize(
|
160
|
-
@
|
161
|
-
@
|
146
|
+
def initialize( content_folder, cache_folder = nil )
|
147
|
+
@cache_folder = cache_folder
|
148
|
+
@folder = content_folder
|
149
|
+
@pages = load_cache(CACHE_NAME) || {}
|
162
150
|
@shutting_down = false
|
151
|
+
@check_files_every = nil
|
152
|
+
watch_for(:start) { start }
|
153
|
+
end
|
154
|
+
|
155
|
+
def start
|
163
156
|
load_all_pages
|
164
157
|
start_watching_files
|
158
|
+
setup_periodic_notifications
|
165
159
|
end
|
166
160
|
|
167
161
|
def shutdown
|
162
|
+
notify :shutdown
|
168
163
|
sleep(1) until event_queue.empty?
|
169
164
|
@shutting_down = true # Stop further modifications
|
165
|
+
save_cache(CACHE_NAME, @pages)
|
170
166
|
end
|
171
167
|
|
172
168
|
def page( name )
|
@@ -178,7 +174,7 @@ class Wiki
|
|
178
174
|
end
|
179
175
|
|
180
176
|
def exists?( name )
|
181
|
-
@pages.include?( name.downcase ) && !
|
177
|
+
@pages.include?( name.downcase ) && !page_named( name ).deleted?
|
182
178
|
end
|
183
179
|
|
184
180
|
def revise( pagename, content, author )
|
@@ -187,22 +183,43 @@ class Wiki
|
|
187
183
|
mutate( pagename ) { |page| page.revise( content, author ) }
|
188
184
|
end
|
189
185
|
|
186
|
+
def move( old_pagename, new_pagename, author )
|
187
|
+
old_content = page(old_pagename).content
|
188
|
+
revise( old_pagename, "#{$MESSAGES[:content_moved_to]} [[#{new_pagename}]]", author )
|
189
|
+
revise( new_pagename, "#{$MESSAGES[:content_moved_from]} [[#{old_pagename}]]", author )
|
190
|
+
revise( new_pagename, old_content, author )
|
191
|
+
end
|
192
|
+
|
190
193
|
def rollback( pagename, number, author )
|
191
194
|
raise "Sorry! Shutting down..." if @shutting_down
|
192
195
|
check_disk_for_updated_page pagename
|
193
196
|
mutate( pagename ) { |page| page.rollback( number, author ) }
|
194
197
|
end
|
195
198
|
|
199
|
+
def delete( pagename, author )
|
200
|
+
revise( pagename, $MESSAGES[:page_deleted], author )
|
201
|
+
end
|
202
|
+
|
203
|
+
def wipe_from_disk( pagename )
|
204
|
+
page = page_named( pagename )
|
205
|
+
raise "Page not deleted!" unless page.deleted?
|
206
|
+
page.content_lock.synchronize do
|
207
|
+
delete_files_for_page( pagename.downcase )
|
208
|
+
@pages.delete( pagename.downcase )
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
196
212
|
private
|
197
213
|
|
214
|
+
def setup_periodic_notifications
|
215
|
+
PeriodicNotification.new( :year, :month, :day, :hour, :min ) do |period|
|
216
|
+
notify period unless @shutting_down
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
198
220
|
def start_watching_files
|
199
|
-
return unless
|
200
|
-
|
201
|
-
loop do
|
202
|
-
sleep $SETTINGS[:check_files_every]
|
203
|
-
load_all_pages
|
204
|
-
end
|
205
|
-
end.priority = -5
|
221
|
+
return unless check_files_every
|
222
|
+
watch_for(check_files_every) { load_all_pages }
|
206
223
|
end
|
207
224
|
|
208
225
|
def new_page( name, initializer = :new )
|
@@ -216,8 +233,19 @@ class Wiki
|
|
216
233
|
page = page_named( pagename ) || new_page( pagename )
|
217
234
|
revision = nil
|
218
235
|
page.content_lock.synchronize do
|
236
|
+
# Check if the capitalisation of the page has changed
|
237
|
+
unless page.name == pagename
|
238
|
+
move_files_for_page( page.name, pagename )
|
239
|
+
page.name = pagename
|
240
|
+
notify :page_title_recapitalized, page
|
241
|
+
end
|
242
|
+
# Yield to the mutator block
|
219
243
|
revision, dont_save = yield page
|
220
|
-
|
244
|
+
# Save page if required
|
245
|
+
if revision && dont_save != :dont_save
|
246
|
+
save page
|
247
|
+
add_page_to_index( page )
|
248
|
+
end
|
221
249
|
end
|
222
250
|
if revision
|
223
251
|
notify :page_revised, page, revision
|