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
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
|