Soks 0.0.7 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
data/lib/soks-servlet.rb
CHANGED
|
@@ -2,158 +2,176 @@
|
|
|
2
2
|
require 'authenticators'
|
|
3
3
|
require 'yaml'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
class ServletSettings
|
|
6
|
+
|
|
7
|
+
attr_accessor :view_controller, :wiki
|
|
8
|
+
attr_accessor :home_page
|
|
9
|
+
attr_accessor :default_view
|
|
10
|
+
attr_accessor :upload_directory
|
|
11
|
+
attr_accessor :authenticators
|
|
12
|
+
attr_accessor :static_file_directories
|
|
13
|
+
attr_accessor :force_no_cache
|
|
14
|
+
attr_accessor :content_types
|
|
15
|
+
attr_accessor :wiki_directory
|
|
16
|
+
|
|
17
|
+
def initialize( wiki, view_controller )
|
|
18
|
+
@wiki, @view_controller = wiki, view_controller
|
|
19
|
+
@home_page = 'Home Page'
|
|
20
|
+
@default_view = 'view'
|
|
21
|
+
@authenticators = []
|
|
22
|
+
@static_file_directories = {}
|
|
23
|
+
@content_types = {}
|
|
24
|
+
@force_no_cache = false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def url
|
|
28
|
+
@view_controller.root_url
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def page_name_for_url_name( url_name )
|
|
32
|
+
@view_controller.page_name_for_url_name( url_name )
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def url_name_for_page_name( page_name )
|
|
36
|
+
@view_controller.url_name_for_page_name( page_name )
|
|
37
|
+
end
|
|
38
|
+
end
|
|
6
39
|
|
|
7
40
|
class WikiServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
41
|
+
|
|
42
|
+
attr_accessor :server, :settings
|
|
43
|
+
|
|
44
|
+
def initialize( server, servlet_settings )
|
|
45
|
+
@server, @settings = server, servlet_settings
|
|
11
46
|
end
|
|
12
47
|
|
|
13
48
|
def service( request, response )
|
|
14
|
-
case request.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
when
|
|
18
|
-
verb, pagename = $1, make_pagename_valid( $2 )
|
|
19
|
-
authenticate request, response
|
|
20
|
-
make_username_valid( request )
|
|
21
|
-
if self.respond_to?( "do#{verb.capitalize}" )
|
|
22
|
-
self.send( "do#{verb.capitalize}", request, response, pagename, request.user )
|
|
23
|
-
else
|
|
24
|
-
unknownView( request, response, pagename, verb, request.user )
|
|
25
|
-
end
|
|
26
|
-
when /\/(.+)/ ; redirect( response, $1, 'view' ) # No verb given
|
|
27
|
-
when "/" ; redirect( response, $SETTINGS[:home_page], 'view' ) # No page or verb given
|
|
28
|
-
end
|
|
49
|
+
case request.path_info
|
|
50
|
+
|
|
51
|
+
# Pass some requests directly to static files
|
|
52
|
+
when '/robots.txt', '/favicon.ico'; serveStaticFile( request, response, request.path[1..-1], 'Attachment' )
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
54
|
+
# If request of the form /verb/pagename then do it
|
|
55
|
+
when /\/(\w+?)\/(.+)/; wiki_service( request, response, $1.capitalize, $2 )
|
|
56
|
+
|
|
57
|
+
# If request of the form /pagename then redirect to /view/pagename
|
|
58
|
+
when /\/(.+)/ ; redirect( response, $1, settings.default_view )
|
|
59
|
+
|
|
60
|
+
# If request of the form / then redirect to /view/home%20page
|
|
61
|
+
when "/" ; redirect( response, settings.home_page, settings.default_view )
|
|
62
|
+
|
|
33
63
|
end
|
|
34
|
-
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def wiki_service( request, response, verb, url_name )
|
|
67
|
+
authenticate request, response
|
|
68
|
+
make_username_valid( request )
|
|
69
|
+
if settings.static_file_directories.include? verb
|
|
70
|
+
serveStaticFile( request, response, url_name, verb )
|
|
71
|
+
elsif self.respond_to?( "do#{verb}" )
|
|
72
|
+
self.send( "do#{verb}", request, response,settings.page_name_for_url_name(url_name), request.user )
|
|
73
|
+
set_cache_settings(response)
|
|
74
|
+
else
|
|
75
|
+
renderView( request, response, settings.page_name_for_url_name(url_name), verb, request.user )
|
|
76
|
+
set_cache_settings(response)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
35
79
|
|
|
36
80
|
def authenticate( request, response )
|
|
37
|
-
|
|
38
|
-
if request.
|
|
81
|
+
settings.authenticators.each do |path_regex,authenticator|
|
|
82
|
+
if request.path_info.downcase =~ path_regex
|
|
39
83
|
authenticator.authenticate( request, response )
|
|
40
84
|
break
|
|
41
85
|
end
|
|
42
86
|
end
|
|
43
87
|
end
|
|
44
88
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
# These methods deal with the various views
|
|
51
|
-
|
|
52
|
-
def doAttachment( request, response, pagename, person = nil )
|
|
53
|
-
request.script_name = 'attachment'
|
|
54
|
-
request.path_info = "/#{pagename}"
|
|
55
|
-
WEBrick::HTTPServlet::FileHandler.get_instance(@server, "#{$SETTINGS[:root_directory] }/attachment",true).service(request, response)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def doFind( request, response, pagename, person )
|
|
59
|
-
response.body = @view.find( request.query['regex'].strip )
|
|
60
|
-
response['Content-Type'] ||= "text/html"
|
|
89
|
+
# A special redirect to allow WikiLink style urls
|
|
90
|
+
# Not sure if used by anyone, so may delete
|
|
91
|
+
def doWiki( request, response, pagename, person )
|
|
92
|
+
redirect( response, pagename.gsub(/([a-z])([A-Z])/,'\1 \2') )
|
|
61
93
|
end
|
|
62
94
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
95
|
+
# This passes any requests for static files onto a FileHandler
|
|
96
|
+
def serveStaticFile( request, response, url_name, view )
|
|
97
|
+
request.script_name = view
|
|
98
|
+
request.path_info = "/#{url_name}"
|
|
99
|
+
WEBrick::HTTPServlet::FileHandler.get_instance(@server, settings.static_file_directories[view], true).service(request, response)
|
|
66
100
|
end
|
|
67
101
|
|
|
68
|
-
# This
|
|
69
|
-
def
|
|
70
|
-
response.body =
|
|
71
|
-
response['Content-Type']
|
|
102
|
+
# This passes any rendering of the page onto the view class
|
|
103
|
+
def renderView( request, response, pagename, view, person )
|
|
104
|
+
response.body = view_controller.render( pagename, view, person, request.query )
|
|
105
|
+
response['Content-Type'] = settings.content_types[view] || 'text/html'
|
|
72
106
|
end
|
|
73
107
|
|
|
74
108
|
# All the following methods change the wiki, then redirect
|
|
75
|
-
|
|
76
|
-
def doWiki( request, response, pagename, person )
|
|
77
|
-
redirect( response, pagename.gsub(/([a-z])([A-Z])/,'\1 \2') )
|
|
78
|
-
end
|
|
79
109
|
|
|
80
110
|
def doSave( request, response, pagename, person )
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
redirect( response,
|
|
111
|
+
pagename = move_page_as_required( request, response, pagename, person )
|
|
112
|
+
content = request.query["content"].to_s.gsub(/\r\n/,"\n")
|
|
113
|
+
wiki.revise( pagename, content, person ) if content
|
|
114
|
+
redirect( response, pagename )
|
|
85
115
|
end
|
|
86
116
|
|
|
87
117
|
def doRollback( request, response, pagename, person )
|
|
88
|
-
|
|
89
|
-
|
|
118
|
+
if request.query['revision']
|
|
119
|
+
wiki.rollback( pagename, request.query['revision'].to_i, person )
|
|
120
|
+
end
|
|
90
121
|
redirect( response, pagename )
|
|
91
122
|
end
|
|
92
123
|
|
|
93
124
|
def doDelete( request, response, pagename, person )
|
|
94
|
-
|
|
125
|
+
wiki.delete( pagename, person )
|
|
95
126
|
redirect( response, pagename )
|
|
96
127
|
end
|
|
97
128
|
|
|
98
129
|
def doUpload( request, response, pagename, person )
|
|
99
|
-
|
|
130
|
+
pagename = move_page_as_required( request, response, pagename, person )
|
|
100
131
|
unless request.query['file'] == ""
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
redirect( response, newpagename )
|
|
132
|
+
filename = upload_file_data( request.query['file'] )
|
|
133
|
+
wiki.revise( pagename, filename, person )
|
|
134
|
+
end
|
|
135
|
+
redirect( response, pagename )
|
|
106
136
|
end
|
|
107
137
|
|
|
108
138
|
private
|
|
139
|
+
|
|
140
|
+
def redirect( response, pagename, verb = settings.default_view )
|
|
141
|
+
response.set_redirect( WEBrick::HTTPStatus::Found, "#{settings.url}/#{verb}/#{settings.url_name_for_page_name(pagename)}" )
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Moves a page if there is a newtitle in the query
|
|
145
|
+
# If the original page had 'type a title' in its tile, then it is assumed to be a template
|
|
146
|
+
# and therefore is not moved.
|
|
147
|
+
def move_page_as_required( request, response, pagename, person )
|
|
148
|
+
new_pagename = "#{request.query["titleprefix"]}#{request.query["newtitle"]}"
|
|
149
|
+
return new_pagename if pagename =~ /#{$MESSAGES[:Type_a_title_here]}/io
|
|
150
|
+
return pagename if new_pagename == pagename
|
|
151
|
+
wiki.move( pagename, new_pagename, person )
|
|
152
|
+
new_pagename
|
|
153
|
+
end
|
|
109
154
|
|
|
110
|
-
def upload_file_data( upload_data )
|
|
111
|
-
|
|
155
|
+
def upload_file_data( upload_data, destination = settings.upload_directory )
|
|
156
|
+
return "Uploads prohibited" unless destination
|
|
157
|
+
path = settings.static_file_directories[ destination ]
|
|
112
158
|
filename = File.unique_filename( path , upload_data.filename )
|
|
113
159
|
File.open( File.join( path, filename ), 'wb' ) { |file| upload_data.list.each { |data| file << data } }
|
|
114
|
-
"
|
|
160
|
+
"/#{destination}/#{filename}"
|
|
115
161
|
end
|
|
116
162
|
|
|
117
|
-
#
|
|
118
|
-
def
|
|
119
|
-
|
|
163
|
+
# Make sure the username doesn't start with Automatic
|
|
164
|
+
def make_username_valid( request )
|
|
165
|
+
request.user = "User: #{request.user}" if request.user =~ /^Automatic/i
|
|
120
166
|
end
|
|
121
167
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
request.user = "External #{request.user}" if request.user =~ /^Automatic/i
|
|
168
|
+
def set_cache_settings(response)
|
|
169
|
+
return unless @settings.force_no_cache
|
|
170
|
+
response['Cache-control'] ||= 'no-cache'
|
|
171
|
+
response['Pragma'] ||= 'no-cache'
|
|
127
172
|
end
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
$MESSAGES = YAML.load( IO.readlines("#{$SETTINGS[:root_directory]}/views/messages.yaml").join )
|
|
134
|
-
|
|
135
|
-
wiki = Wiki.new( "#{$SETTINGS[:root_directory]}/content" )
|
|
136
|
-
view = View.new( wiki, $SETTINGS[:name] )
|
|
137
|
-
|
|
138
|
-
Thread.new( automatic_agents, wiki, view ) do |block, wiki, view|
|
|
139
|
-
$LOG.info "Starting helper classes"
|
|
140
|
-
block.call( wiki, view )
|
|
141
|
-
$LOG.info "Finished starting helper classes"
|
|
142
|
-
end.priority = -2
|
|
143
|
-
|
|
144
|
-
server = WEBrick::HTTPServer.new( { :Port => $SETTINGS[:port],
|
|
145
|
-
:ServerType=> $SETTINGS[:server_type]
|
|
146
|
-
} )
|
|
147
|
-
|
|
148
|
-
server.mount("/", WikiServlet, view, $SETTINGS )
|
|
149
|
-
|
|
150
|
-
trap("INT") {
|
|
151
|
-
$LOG.warn "Trying to shutdown gracefully"
|
|
152
|
-
server.shutdown
|
|
153
|
-
wiki.shutdown
|
|
154
|
-
$LOG.info "Shutdown."
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
$LOG.warn "Starting server"
|
|
158
|
-
server.start
|
|
159
|
-
end
|
|
173
|
+
|
|
174
|
+
def wiki; @settings.wiki end
|
|
175
|
+
def view_controller; @settings.view_controller end
|
|
176
|
+
|
|
177
|
+
end
|
data/lib/soks-storage.rb
CHANGED
|
@@ -1,3 +1,48 @@
|
|
|
1
|
+
module WikiCacheStore
|
|
2
|
+
|
|
3
|
+
CACHE_EXTENSION = ".marshal"
|
|
4
|
+
|
|
5
|
+
def load_cache( cache_name )
|
|
6
|
+
return nil unless @cache_folder
|
|
7
|
+
|
|
8
|
+
cache = nil
|
|
9
|
+
|
|
10
|
+
File.open( cache_filename_for( cache_name ) ) do |f|
|
|
11
|
+
cache = Marshal.load(f)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
File.delete( cache_filename_for( cache_name ) )
|
|
15
|
+
|
|
16
|
+
$LOG.info "Loaded #{cache_name} cache"
|
|
17
|
+
|
|
18
|
+
return cache
|
|
19
|
+
|
|
20
|
+
rescue ArgumentError
|
|
21
|
+
$LOG.warn "#{cache_name} cache corrupt (bad characters in file)"
|
|
22
|
+
return nil
|
|
23
|
+
|
|
24
|
+
rescue EOFError
|
|
25
|
+
$LOG.warn "#{cache_name} cache corrupt (unexpected end of file)"
|
|
26
|
+
return nil
|
|
27
|
+
|
|
28
|
+
rescue Errno::ENOENT
|
|
29
|
+
$LOG.warn "#{cache_name} cache not found"
|
|
30
|
+
return nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def save_cache( cache_name, cache_object )
|
|
34
|
+
return nil unless @cache_folder
|
|
35
|
+
File.open( cache_filename_for( cache_name ), 'w' ) do |f|
|
|
36
|
+
f.puts Marshal.dump(cache_object)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def cache_filename_for( name )
|
|
41
|
+
File.join( @cache_folder, "#{name}#{CACHE_EXTENSION}")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
1
46
|
module WikiFlatFileStore
|
|
2
47
|
|
|
3
48
|
CONTENT_EXTENSION = '.textile'
|
|
@@ -15,8 +60,22 @@ module WikiFlatFileStore
|
|
|
15
60
|
end
|
|
16
61
|
end
|
|
17
62
|
|
|
63
|
+
def save( page )
|
|
64
|
+
save_content( page )
|
|
65
|
+
save_last_revision( page )
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def delete_files_for_page( page_name )
|
|
69
|
+
File.delete( filename_for_content( page_name ), filename_for_revisions( page_name ) )
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def move_files_for_page( old_page_name, new_page_name )
|
|
73
|
+
File.rename( filename_for_content( old_page_name ), filename_for_content( new_page_name ) )
|
|
74
|
+
File.rename( filename_for_revisions( old_page_name ), filename_for_revisions( new_page_name ) )
|
|
75
|
+
end
|
|
76
|
+
|
|
18
77
|
def check_disk_for_updated_page( pagename, force = false )
|
|
19
|
-
return unless force ||
|
|
78
|
+
return unless force || self.check_files_every # We don't care about file changes
|
|
20
79
|
filename = filename_for_content( pagename )
|
|
21
80
|
return :file_does_not_exist unless File.exists?( filename ) # File doesn't exist on disk
|
|
22
81
|
return load_page( filename ) unless page_named( pagename )# File is new on the disk, but not yet in memory
|
|
@@ -38,7 +97,7 @@ module WikiFlatFileStore
|
|
|
38
97
|
if content_newer_than_revisions?( page ) # The textile file has been modified, but the array file has not been updated to match
|
|
39
98
|
page.content = reconstruct_content_from_revisions( page.revisions )
|
|
40
99
|
page.revise( disk_content, DEFAULT_AUTHOR )
|
|
41
|
-
|
|
100
|
+
save_last_revision( page )
|
|
42
101
|
else # The textile file and the array file are in sync.
|
|
43
102
|
page.content = disk_content
|
|
44
103
|
end
|
|
@@ -84,24 +143,18 @@ module WikiFlatFileStore
|
|
|
84
143
|
def move_files_if_names_are_not_url_encoded
|
|
85
144
|
Dir[ File.join( @folder, "*#{CONTENT_EXTENSION}" ) ].each do |filename|
|
|
86
145
|
basename = File.basename( filename, '.*')
|
|
87
|
-
next if basename.url_decode.
|
|
88
|
-
new_name = File.join( File.dirname(filename), File.unique_filename( File.dirname(filename), basename.url_decode.
|
|
146
|
+
next if basename.url_decode.url_encode == basename # All ok, so no worry
|
|
147
|
+
new_name = File.join( File.dirname(filename), File.unique_filename( File.dirname(filename), basename.url_decode.url_encode + File.extname( filename) ) )
|
|
89
148
|
File.rename(filename, new_name )
|
|
90
149
|
end
|
|
91
150
|
end
|
|
92
|
-
|
|
93
|
-
def save( page )
|
|
94
|
-
add_page_to_index( page )
|
|
95
|
-
save_content( page )
|
|
96
|
-
save_revisions( page )
|
|
97
|
-
end
|
|
98
151
|
|
|
99
152
|
def save_content( page )
|
|
100
153
|
File.open(filename_for_content( page.name ), 'w' ) { |file| file.puts page.content }
|
|
101
154
|
end
|
|
102
155
|
|
|
103
156
|
# Appends the last revision onto the yaml file
|
|
104
|
-
def
|
|
157
|
+
def save_last_revision( page )
|
|
105
158
|
$LOG.info "Saving revisions for #{page.name}"
|
|
106
159
|
File.open(filename_for_revisions( page.name ), 'a' ) do |file|
|
|
107
160
|
YAML.dump( page.revisions.last.to_a, file )
|
|
@@ -109,6 +162,16 @@ module WikiFlatFileStore
|
|
|
109
162
|
end
|
|
110
163
|
end
|
|
111
164
|
|
|
165
|
+
def save_all_revisions( page )
|
|
166
|
+
$LOG.warn "Saving all revisions for #{page.name}"
|
|
167
|
+
File.open(filename_for_revisions( page.name ), 'w' ) do |file|
|
|
168
|
+
page.revisions.each do |revision|
|
|
169
|
+
YAML.dump( revision.to_a, file )
|
|
170
|
+
file.puts # Needed to ensure that documents are separated
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
112
175
|
def page_name_for( filename )
|
|
113
176
|
File.basename( filename, '.*').url_decode
|
|
114
177
|
end
|
data/lib/soks-utils.rb
CHANGED
|
@@ -30,8 +30,10 @@ class EventQueue
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def event( event, messages )
|
|
33
|
+
# $LOG.warn "#{event}, #{messages}"
|
|
33
34
|
check_thread_ok
|
|
34
35
|
@queue.enq [ event, messages ]
|
|
36
|
+
$LOG.warn "Notification queue backlog of #{@queue.size}" if @queue.size > 100
|
|
35
37
|
notify_attentive_watchers( event, *messages )
|
|
36
38
|
end
|
|
37
39
|
|
|
@@ -74,7 +76,7 @@ class EventQueue
|
|
|
74
76
|
begin
|
|
75
77
|
action_block.call(event, *messages)
|
|
76
78
|
rescue StandardError => err
|
|
77
|
-
$
|
|
79
|
+
$LOG.warn "ERROR #{err}: #{event} - #{messages.join(' ')}"
|
|
78
80
|
err.backtrace.each { |s| $stderr.puts s }
|
|
79
81
|
end
|
|
80
82
|
}
|
|
@@ -106,7 +108,31 @@ class EventQueue
|
|
|
106
108
|
def attentive_watchers
|
|
107
109
|
@attentive_watchers ||= {}
|
|
108
110
|
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
class PeriodicNotification
|
|
114
|
+
|
|
115
|
+
def initialize( *notify_about, &block)
|
|
116
|
+
@block = block
|
|
117
|
+
notify_about.each do |period|
|
|
118
|
+
start_thread( period )
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def start_thread( period )
|
|
125
|
+
Thread.new( period ) do |period|
|
|
126
|
+
while true
|
|
127
|
+
sleep seconds_to_next_period( period )
|
|
128
|
+
@block.call( period )
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
109
132
|
|
|
133
|
+
def seconds_to_next_period( period )
|
|
134
|
+
Time.now.next( period ) - Time.now
|
|
135
|
+
end
|
|
110
136
|
end
|
|
111
137
|
|
|
112
138
|
class String
|
|
@@ -145,9 +171,15 @@ class String
|
|
|
145
171
|
end
|
|
146
172
|
|
|
147
173
|
# Removes punctuation and spaces that can cause problems with page names
|
|
148
|
-
def to_valid_pagename
|
|
149
|
-
|
|
174
|
+
#def to_valid_pagename
|
|
175
|
+
# self.tr('\\\[]?{}#&^`<>/','').strip
|
|
176
|
+
#end
|
|
177
|
+
|
|
178
|
+
#Returns the changes between the lines of this string and another
|
|
179
|
+
def changes_from( other_string )
|
|
180
|
+
other_string.split("\n").diff( self.split("\n") ).map { |changeset| changeset.map { |change| change.to_a } }
|
|
150
181
|
end
|
|
182
|
+
|
|
151
183
|
end
|
|
152
184
|
|
|
153
185
|
class FiniteUniqueList
|
|
@@ -223,13 +255,55 @@ class Numeric
|
|
|
223
255
|
end
|
|
224
256
|
|
|
225
257
|
class Time
|
|
258
|
+
|
|
259
|
+
# Returns 'yesterday', 'tomorrow' for date relative to now
|
|
260
|
+
def relative_day
|
|
261
|
+
# Days difference
|
|
262
|
+
case self.days_from( Time.now )
|
|
263
|
+
when -7..-2 ; strftime('Last %A')
|
|
264
|
+
when -1 ; "Yesterday"
|
|
265
|
+
when 0 ; "Today"
|
|
266
|
+
when 1 ; "Tomorrow"
|
|
267
|
+
when 2..7 ; strftime('%A')
|
|
268
|
+
else ; strftime( (Time.now.year == self.year) ? '%d %b' :'%d %b %Y')
|
|
269
|
+
end
|
|
270
|
+
end
|
|
226
271
|
|
|
272
|
+
def days_from( other_time )
|
|
273
|
+
((Time.local(self.year, self.month, self.day)-Time.local(other_time.year, other_time.month, other_time.day))/(24*60*60)).round
|
|
274
|
+
end
|
|
275
|
+
|
|
227
276
|
# Checks whether two times are on the same day
|
|
228
277
|
def same_day?( other_time )
|
|
229
278
|
return false unless other_time.year == self.year
|
|
230
279
|
return false unless other_time.yday == self.yday
|
|
231
280
|
return true
|
|
232
281
|
end
|
|
282
|
+
|
|
283
|
+
# Returns the Time at the next occurance of the period
|
|
284
|
+
def next( period )
|
|
285
|
+
case period
|
|
286
|
+
when :sec, :second
|
|
287
|
+
next_time = self + 1
|
|
288
|
+
Time.local( next_time.year, next_time.month, next_time.day, next_time.hour, next_time.min, next_time.sec )
|
|
289
|
+
when :min, :minute
|
|
290
|
+
next_time = self + 60
|
|
291
|
+
Time.local( next_time.year, next_time.month, next_time.day, next_time.hour, next_time.min )
|
|
292
|
+
when :hour
|
|
293
|
+
next_time = self + ( 60*60 )
|
|
294
|
+
Time.local( next_time.year, next_time.month, next_time.day, next_time.hour)
|
|
295
|
+
when :day
|
|
296
|
+
next_time = self + ( 60*60*24 )
|
|
297
|
+
Time.local( next_time.year, next_time.month, next_time.day)
|
|
298
|
+
when :mon, :month
|
|
299
|
+
next_time = self + ( 60*60*24*(32-self.day) )
|
|
300
|
+
Time.local( next_time.year, next_time.month)
|
|
301
|
+
when :year
|
|
302
|
+
next_time = self + ( 60*60*24*(367-self.yday) )
|
|
303
|
+
Time.local( next_time.year )
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
233
307
|
end
|
|
234
308
|
|
|
235
309
|
class File
|