instiki 0.9.2 → 0.10.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/CHANGELOG +165 -0
- data/README +68 -172
- data/app/controllers/admin_controller.rb +94 -0
- data/app/controllers/application.rb +131 -0
- data/app/controllers/file_controller.rb +129 -0
- data/app/controllers/wiki_controller.rb +354 -0
- data/{libraries/view_helper.rb → app/helpers/application_helper.rb} +68 -33
- data/app/models/author.rb +3 -3
- data/app/models/chunks/category.rb +33 -31
- data/app/models/chunks/chunk.rb +86 -20
- data/app/models/chunks/engines.rb +54 -38
- data/app/models/chunks/include.rb +41 -29
- data/app/models/chunks/literal.rb +31 -19
- data/app/models/chunks/nowiki.rb +28 -31
- data/app/models/chunks/test.rb +18 -18
- data/app/models/chunks/uri.rb +182 -97
- data/app/models/chunks/wiki.rb +141 -82
- data/app/models/file_yard.rb +58 -0
- data/app/models/page.rb +112 -86
- data/app/models/page_lock.rb +22 -23
- data/app/models/page_set.rb +89 -64
- data/app/models/revision.rb +123 -90
- data/app/models/web.rb +176 -89
- data/app/models/wiki_content.rb +207 -105
- data/app/models/wiki_service.rb +233 -83
- data/app/models/wiki_words.rb +23 -25
- data/app/views/{wiki/new_system.rhtml → admin/create_system.rhtml} +83 -78
- data/app/views/{wiki/new_web.rhtml → admin/create_web.rhtml} +69 -64
- data/app/views/admin/edit_web.rhtml +136 -0
- data/app/views/file/file.rhtml +19 -0
- data/app/views/file/import.rhtml +23 -0
- data/app/views/layouts/default.rhtml +85 -0
- data/app/views/markdown_help.rhtml +12 -16
- data/app/views/mixed_help.rhtml +7 -0
- data/app/views/navigation.rhtml +30 -19
- data/app/views/rdoc_help.rhtml +12 -16
- data/app/views/textile_help.rhtml +24 -28
- data/app/views/wiki/authors.rhtml +11 -13
- data/app/views/wiki/edit.rhtml +39 -31
- data/app/views/wiki/export.rhtml +12 -14
- data/app/views/wiki/feeds.rhtml +14 -10
- data/app/views/wiki/list.rhtml +64 -57
- data/app/views/wiki/locked.rhtml +23 -14
- data/app/views/wiki/login.rhtml +14 -11
- data/app/views/wiki/new.rhtml +31 -27
- data/app/views/wiki/page.rhtml +115 -81
- data/app/views/wiki/print.rhtml +14 -16
- data/app/views/wiki/published.rhtml +9 -10
- data/app/views/wiki/recently_revised.rhtml +27 -30
- data/app/views/wiki/revision.rhtml +103 -81
- data/app/views/wiki/rollback.rhtml +14 -9
- data/app/views/wiki/rss_feed.rhtml +22 -22
- data/app/views/wiki/search.rhtml +38 -15
- data/app/views/wiki/tex.rhtml +22 -22
- data/app/views/wiki/tex_web.rhtml +34 -34
- data/app/views/wiki/web_list.rhtml +18 -13
- data/app/views/wiki_words_help.rhtml +9 -8
- data/config/environment.rb +82 -0
- data/config/environments/development.rb +5 -0
- data/config/environments/production.rb +4 -0
- data/config/environments/test.rb +17 -0
- data/config/routes.rb +18 -0
- data/instiki +6 -67
- data/instiki.rb +3 -0
- data/lib/active_record_stub.rb +31 -0
- data/{libraries/diff → lib}/diff.rb +444 -475
- data/lib/instiki_errors.rb +15 -0
- data/{libraries → lib}/rdocsupport.rb +151 -155
- data/lib/redcloth_for_tex.rb +736 -0
- data/natives/osx/desktop_launcher/AppDelegate.h +18 -0
- data/natives/osx/desktop_launcher/AppDelegate.mm +109 -0
- data/natives/osx/desktop_launcher/Credits.html +16 -0
- data/natives/osx/desktop_launcher/English.lproj/InfoPlist.strings +0 -0
- data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib +13 -0
- data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib +24 -0
- data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/objects.nib +0 -0
- data/natives/osx/desktop_launcher/Info.plist +13 -0
- data/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj +592 -0
- data/natives/osx/desktop_launcher/Instiki_Prefix.pch +7 -0
- data/natives/osx/desktop_launcher/MakeDMG.sh +9 -0
- data/natives/osx/desktop_launcher/main.mm +14 -0
- data/natives/osx/desktop_launcher/version.plist +16 -0
- data/public/404.html +6 -0
- data/public/500.html +6 -0
- data/public/dispatch.rb +10 -0
- data/public/favicon.ico +0 -0
- data/public/javascripts/edit_web.js +52 -0
- data/public/javascripts/prototype.js +336 -0
- data/{app/views/static_style_sheet.rhtml → public/stylesheets/instiki.css} +221 -198
- data/script/breakpointer +4 -0
- data/script/server +93 -0
- metadata +59 -32
- data/app/controllers/wiki.rb +0 -389
- data/app/models/chunks/match.rb +0 -19
- data/app/views/bottom.rhtml +0 -4
- data/app/views/top.rhtml +0 -49
- data/app/views/wiki/edit_web.rhtml +0 -138
- data/libraries/action_controller_servlet.rb +0 -177
- data/libraries/erb.rb +0 -490
- data/libraries/madeleine_service.rb +0 -68
- data/libraries/redcloth_for_tex.rb +0 -869
- data/libraries/web_controller_server.rb +0 -81
data/app/models/wiki_content.rb
CHANGED
@@ -1,105 +1,207 @@
|
|
1
|
-
require 'cgi'
|
2
|
-
require 'chunks/engines'
|
3
|
-
require 'chunks/category'
|
4
|
-
require 'chunks/include'
|
5
|
-
require 'chunks/wiki'
|
6
|
-
require 'chunks/literal'
|
7
|
-
require 'chunks/uri'
|
8
|
-
require 'chunks/nowiki'
|
9
|
-
|
10
|
-
# Wiki content is just a string that can process itself with a chain of
|
11
|
-
# actions. The actions can modify wiki content so that certain parts of
|
12
|
-
# it are protected from being rendered by later actions.
|
13
|
-
#
|
14
|
-
# When wiki content is rendered, it can be interrogated to find out
|
15
|
-
# which chunks were rendered. This means things like categories, wiki
|
16
|
-
# links, can be determined.
|
17
|
-
#
|
18
|
-
# Exactly how wiki content is rendered is determined by a number of
|
19
|
-
# settings that are optionally passed in to a constructor. The current
|
20
|
-
# options are:
|
21
|
-
# * :engine
|
22
|
-
# => The structural markup engine to use (Textile, Markdown, RDoc)
|
23
|
-
# * :engine_opts
|
24
|
-
# => A list of options to pass to the markup engines (safe modes, etc)
|
25
|
-
# * :pre_engine_actions
|
26
|
-
# => A list of render actions or chunks to be processed before the
|
27
|
-
# markup engine is applied. By default this is:
|
28
|
-
# Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word
|
29
|
-
# * :post_engine_actions
|
30
|
-
# => A list of render actions or chunks to apply after the markup
|
31
|
-
# engine. By default these are:
|
32
|
-
# Literal::Pre, Literal::Tags
|
33
|
-
# * :mode
|
34
|
-
# => How should the content be rendered? For normal display (
|
35
|
-
# publishing (:publish) or export (:export)?
|
36
|
-
#
|
37
|
-
# AUTHOR: Mark Reid <mark @ threewordslong . com>
|
38
|
-
# CREATED: 15th May 2004
|
39
|
-
# UPDATED: 22nd May 2004
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
@
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
def
|
78
|
-
|
79
|
-
end
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
end
|
1
|
+
require 'cgi'
|
2
|
+
require 'chunks/engines'
|
3
|
+
require 'chunks/category'
|
4
|
+
require 'chunks/include'
|
5
|
+
require 'chunks/wiki'
|
6
|
+
require 'chunks/literal'
|
7
|
+
require 'chunks/uri'
|
8
|
+
require 'chunks/nowiki'
|
9
|
+
|
10
|
+
# Wiki content is just a string that can process itself with a chain of
|
11
|
+
# actions. The actions can modify wiki content so that certain parts of
|
12
|
+
# it are protected from being rendered by later actions.
|
13
|
+
#
|
14
|
+
# When wiki content is rendered, it can be interrogated to find out
|
15
|
+
# which chunks were rendered. This means things like categories, wiki
|
16
|
+
# links, can be determined.
|
17
|
+
#
|
18
|
+
# Exactly how wiki content is rendered is determined by a number of
|
19
|
+
# settings that are optionally passed in to a constructor. The current
|
20
|
+
# options are:
|
21
|
+
# * :engine
|
22
|
+
# => The structural markup engine to use (Textile, Markdown, RDoc)
|
23
|
+
# * :engine_opts
|
24
|
+
# => A list of options to pass to the markup engines (safe modes, etc)
|
25
|
+
# * :pre_engine_actions
|
26
|
+
# => A list of render actions or chunks to be processed before the
|
27
|
+
# markup engine is applied. By default this is:
|
28
|
+
# Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word
|
29
|
+
# * :post_engine_actions
|
30
|
+
# => A list of render actions or chunks to apply after the markup
|
31
|
+
# engine. By default these are:
|
32
|
+
# Literal::Pre, Literal::Tags
|
33
|
+
# * :mode
|
34
|
+
# => How should the content be rendered? For normal display (show),
|
35
|
+
# publishing (:publish) or export (:export)?
|
36
|
+
#
|
37
|
+
# AUTHOR: Mark Reid <mark @ threewordslong . com>
|
38
|
+
# CREATED: 15th May 2004
|
39
|
+
# UPDATED: 22nd May 2004
|
40
|
+
|
41
|
+
module ChunkManager
|
42
|
+
attr_reader :chunks_by_type, :chunks_by_id, :chunks, :chunk_id
|
43
|
+
|
44
|
+
ACTIVE_CHUNKS = [ NoWiki, Category, WikiChunk::Link, URIChunk, LocalURIChunk,
|
45
|
+
WikiChunk::Word ]
|
46
|
+
|
47
|
+
HIDE_CHUNKS = [ Literal::Pre, Literal::Tags ]
|
48
|
+
|
49
|
+
MASK_RE = {
|
50
|
+
ACTIVE_CHUNKS => Chunk::Abstract.mask_re(ACTIVE_CHUNKS),
|
51
|
+
HIDE_CHUNKS => Chunk::Abstract.mask_re(HIDE_CHUNKS)
|
52
|
+
}
|
53
|
+
|
54
|
+
def init_chunk_manager
|
55
|
+
@chunks_by_type = Hash.new
|
56
|
+
Chunk::Abstract::derivatives.each{|chunk_type|
|
57
|
+
@chunks_by_type[chunk_type] = Array.new
|
58
|
+
}
|
59
|
+
@chunks_by_id = Hash.new
|
60
|
+
@chunks = []
|
61
|
+
@chunk_id = 0
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_chunk(c)
|
65
|
+
@chunks_by_type[c.class] << c
|
66
|
+
@chunks_by_id[c.id] = c
|
67
|
+
@chunks << c
|
68
|
+
@chunk_id += 1
|
69
|
+
end
|
70
|
+
|
71
|
+
def delete_chunk(c)
|
72
|
+
@chunks_by_type[c.class].delete(c)
|
73
|
+
@chunks_by_id.delete(c.id)
|
74
|
+
@chunks.delete(c)
|
75
|
+
end
|
76
|
+
|
77
|
+
def merge_chunks(other)
|
78
|
+
other.chunks.each{|c| add_chunk(c)}
|
79
|
+
end
|
80
|
+
|
81
|
+
def scan_chunkid(text)
|
82
|
+
text.scan(MASK_RE[ACTIVE_CHUNKS]){|a| yield a[0] }
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_chunks(chunk_type)
|
86
|
+
@chunks.select { |chunk| chunk.kind_of?(chunk_type) and chunk.rendered? }
|
87
|
+
end
|
88
|
+
|
89
|
+
# for testing and WikiContentStub; we need a page_id even if we have no page
|
90
|
+
def page_id
|
91
|
+
0
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
# A simplified version of WikiContent. Useful to avoid recursion problems in
|
97
|
+
# WikiContent.new
|
98
|
+
class WikiContentStub < String
|
99
|
+
attr_reader :options
|
100
|
+
include ChunkManager
|
101
|
+
def initialize(content, options)
|
102
|
+
super(content)
|
103
|
+
@options = options
|
104
|
+
init_chunk_manager
|
105
|
+
end
|
106
|
+
|
107
|
+
# Detects the mask strings contained in the text of chunks of type chunk_types
|
108
|
+
# and yields the corresponding chunk ids
|
109
|
+
# example: content = "chunk123categorychunk <pre>chunk456categorychunk</pre>"
|
110
|
+
# inside_chunks(Literal::Pre) ==> yield 456
|
111
|
+
def inside_chunks(chunk_types)
|
112
|
+
chunk_types.each{|chunk_type| chunk_type.apply_to(self) }
|
113
|
+
|
114
|
+
chunk_types.each{|chunk_type| @chunks_by_type[chunk_type].each{|hide_chunk|
|
115
|
+
scan_chunkid(hide_chunk.text){|id| yield id }
|
116
|
+
}
|
117
|
+
}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class WikiContent < String
|
122
|
+
|
123
|
+
include ChunkManager
|
124
|
+
|
125
|
+
DEFAULT_OPTS = {
|
126
|
+
:active_chunks => ACTIVE_CHUNKS,
|
127
|
+
:engine => Engines::Textile,
|
128
|
+
:engine_opts => [],
|
129
|
+
:mode => :show
|
130
|
+
}.freeze
|
131
|
+
|
132
|
+
attr_reader :web, :options, :revision, :not_rendered, :pre_rendered
|
133
|
+
|
134
|
+
# Create a new wiki content string from the given one.
|
135
|
+
# The options are explained at the top of this file.
|
136
|
+
def initialize(revision, options = {})
|
137
|
+
@revision = revision
|
138
|
+
@web = @revision.page.web
|
139
|
+
|
140
|
+
@options = DEFAULT_OPTS.dup.merge(options)
|
141
|
+
@options[:engine] = Engines::MAP[@web.markup]
|
142
|
+
@options[:engine_opts] = [:filter_html, :filter_styles] if @web.safe_mode
|
143
|
+
@options[:active_chunks] = (ACTIVE_CHUNKS - [WikiChunk::Word] ) if @web.brackets_only
|
144
|
+
|
145
|
+
super(@revision.content)
|
146
|
+
init_chunk_manager
|
147
|
+
build_chunks
|
148
|
+
@not_rendered = String.new(self)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Call @web.page_link using current options.
|
152
|
+
def page_link(name, text, link_type)
|
153
|
+
@options[:link_type] = (link_type || :show)
|
154
|
+
@web.make_link(name, text, @options)
|
155
|
+
end
|
156
|
+
|
157
|
+
def build_chunks
|
158
|
+
# create and mask Includes and "active_chunks" chunks
|
159
|
+
Include.apply_to(self)
|
160
|
+
@options[:active_chunks].each{|chunk_type| chunk_type.apply_to(self)}
|
161
|
+
|
162
|
+
# Handle hiding contexts like "pre" and "code" etc..
|
163
|
+
# The markup (textile, rdoc etc) can produce such contexts with its own syntax.
|
164
|
+
# To reveal them, we work on a copy of the content.
|
165
|
+
# The copy is rendered and used to detect the chunks that are inside protecting context
|
166
|
+
# These chunks are reverted on the original content string.
|
167
|
+
|
168
|
+
copy = WikiContentStub.new(self, @options)
|
169
|
+
@options[:engine].apply_to(copy)
|
170
|
+
|
171
|
+
copy.inside_chunks(HIDE_CHUNKS) do |id|
|
172
|
+
@chunks_by_id[id].revert
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def pre_render!
|
177
|
+
unless @pre_rendered
|
178
|
+
@chunks_by_type[Include].each{|chunk| chunk.unmask }
|
179
|
+
@pre_rendered = String.new(self)
|
180
|
+
end
|
181
|
+
@pre_rendered
|
182
|
+
end
|
183
|
+
|
184
|
+
def render!
|
185
|
+
pre_render!
|
186
|
+
@options[:engine].apply_to(self)
|
187
|
+
# unmask in one go. $~[1] is the chunk id
|
188
|
+
gsub!(MASK_RE[ACTIVE_CHUNKS]){
|
189
|
+
if chunk = @chunks_by_id[$~[1]]
|
190
|
+
chunk.unmask_text
|
191
|
+
# if we match a chunkmask that existed in the original content string
|
192
|
+
# just keep it as it is
|
193
|
+
else
|
194
|
+
$~[0]
|
195
|
+
end}
|
196
|
+
self
|
197
|
+
end
|
198
|
+
|
199
|
+
def page_name
|
200
|
+
@revision.page.name
|
201
|
+
end
|
202
|
+
|
203
|
+
def page_id
|
204
|
+
@revision.page.id
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
data/app/models/wiki_service.rb
CHANGED
@@ -1,83 +1,233 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
web
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
1
|
+
require 'open-uri'
|
2
|
+
require 'yaml'
|
3
|
+
require 'madeleine'
|
4
|
+
require 'madeleine/automatic'
|
5
|
+
require 'madeleine/zmarshal'
|
6
|
+
|
7
|
+
require 'web'
|
8
|
+
require 'page'
|
9
|
+
require 'author'
|
10
|
+
require 'file_yard'
|
11
|
+
require 'instiki_errors'
|
12
|
+
|
13
|
+
module AbstractWikiService
|
14
|
+
|
15
|
+
attr_reader :webs, :system
|
16
|
+
|
17
|
+
def authenticate(password)
|
18
|
+
# system['password'] variant is for compatibility with storages from older versions
|
19
|
+
password == (@system[:password] || @system['password'] || 'instiki')
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_web(name, address, password = nil)
|
23
|
+
@webs[address] = Web.new(self, name, address, password) unless @webs[address]
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete_web(address)
|
27
|
+
@webs[address] = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def file_yard(web)
|
31
|
+
raise "Web #{@web.name} does not belong to this wiki service" unless @webs.values.include?(web)
|
32
|
+
# TODO cache FileYards
|
33
|
+
FileYard.new("#{self.storage_path}/#{web.address}", web.max_upload_size)
|
34
|
+
end
|
35
|
+
|
36
|
+
def init_wiki_service
|
37
|
+
@webs = {}
|
38
|
+
@system = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def read_page(web_address, page_name)
|
42
|
+
ApplicationController.logger.debug "Reading page '#{page_name}' from web '#{web_address}'"
|
43
|
+
web = @webs[web_address]
|
44
|
+
if web.nil?
|
45
|
+
ApplicationController.logger.debug "Web '#{web_address}' not found"
|
46
|
+
return nil
|
47
|
+
else
|
48
|
+
page = web.pages[page_name]
|
49
|
+
ApplicationController.logger.debug "Page '#{page_name}' #{page.nil? ? 'not' : ''} found"
|
50
|
+
return page
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def remove_orphaned_pages(web_address)
|
55
|
+
@webs[web_address].remove_pages(@webs[web_address].select.orphaned_pages)
|
56
|
+
end
|
57
|
+
|
58
|
+
def revise_page(web_address, page_name, content, revised_on, author)
|
59
|
+
page = read_page(web_address, page_name)
|
60
|
+
page.revise(content, revised_on, author)
|
61
|
+
page
|
62
|
+
end
|
63
|
+
|
64
|
+
def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil)
|
65
|
+
page = read_page(web_address, page_name)
|
66
|
+
page.rollback(revision_number, created_at, author_id)
|
67
|
+
page
|
68
|
+
end
|
69
|
+
|
70
|
+
def setup(password, web_name, web_address)
|
71
|
+
@system[:password] = password
|
72
|
+
create_web(web_name, web_address)
|
73
|
+
end
|
74
|
+
|
75
|
+
def setup?
|
76
|
+
not (@webs.empty?)
|
77
|
+
end
|
78
|
+
|
79
|
+
def edit_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false,
|
80
|
+
password = nil, published = false, brackets_only = false, count_pages = false,
|
81
|
+
allow_uploads = true, max_upload_size = nil)
|
82
|
+
|
83
|
+
if not @webs.key? old_address
|
84
|
+
raise Instiki::ValidationError.new("Web with address '#{old_address}' does not exist")
|
85
|
+
end
|
86
|
+
|
87
|
+
if old_address != new_address
|
88
|
+
if @webs.key? new_address
|
89
|
+
raise Instiki::ValidationError.new("There is already a web with address '#{new_address}'")
|
90
|
+
end
|
91
|
+
@webs[new_address] = @webs[old_address]
|
92
|
+
@webs.delete(old_address)
|
93
|
+
@webs[new_address].address = new_address
|
94
|
+
end
|
95
|
+
|
96
|
+
web = @webs[new_address]
|
97
|
+
web.refresh_revisions if settings_changed?(web, markup, safe_mode, brackets_only)
|
98
|
+
|
99
|
+
web.name, web.markup, web.color, web.additional_style, web.safe_mode =
|
100
|
+
name, markup, color, additional_style, safe_mode
|
101
|
+
|
102
|
+
web.password, web.published, web.brackets_only, web.count_pages =
|
103
|
+
password, published, brackets_only, count_pages, allow_uploads
|
104
|
+
web.allow_uploads, web.max_upload_size = allow_uploads, max_upload_size.to_i
|
105
|
+
end
|
106
|
+
|
107
|
+
def write_page(web_address, page_name, content, written_on, author)
|
108
|
+
page = Page.new(@webs[web_address], page_name, content, written_on, author)
|
109
|
+
@webs[web_address].add_page(page)
|
110
|
+
page
|
111
|
+
end
|
112
|
+
|
113
|
+
def storage_path
|
114
|
+
self.class.storage_path
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
def settings_changed?(web, markup, safe_mode, brackets_only)
|
119
|
+
web.markup != markup ||
|
120
|
+
web.safe_mode != safe_mode ||
|
121
|
+
web.brackets_only != brackets_only
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class WikiService
|
126
|
+
|
127
|
+
include AbstractWikiService
|
128
|
+
include Madeleine::Automatic::Interceptor
|
129
|
+
|
130
|
+
# These methods do not change the state of persistent objects, and
|
131
|
+
# should not be ogged by Madeleine
|
132
|
+
automatic_read_only :authenticate, :read_page, :setup?, :webs, :storage_path, :file_yard
|
133
|
+
|
134
|
+
@@storage_path = './storage/'
|
135
|
+
|
136
|
+
class << self
|
137
|
+
|
138
|
+
def storage_path=(storage_path)
|
139
|
+
@@storage_path = storage_path
|
140
|
+
end
|
141
|
+
|
142
|
+
def storage_path
|
143
|
+
@@storage_path
|
144
|
+
end
|
145
|
+
|
146
|
+
def clean_storage
|
147
|
+
MadeleineServer.clean_storage(self)
|
148
|
+
end
|
149
|
+
|
150
|
+
def instance
|
151
|
+
@madeleine ||= MadeleineServer.new(self)
|
152
|
+
@system = @madeleine.system
|
153
|
+
return @system
|
154
|
+
end
|
155
|
+
|
156
|
+
def snapshot
|
157
|
+
@madeleine.snapshot
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
def initialize
|
163
|
+
init_wiki_service
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
class MadeleineServer
|
169
|
+
|
170
|
+
attr_reader :storage_path
|
171
|
+
|
172
|
+
# Clears all the command_log and snapshot files located in the storage directory, so the
|
173
|
+
# database is essentially dropped and recreated as blank
|
174
|
+
def self.clean_storage(service)
|
175
|
+
begin
|
176
|
+
Dir.foreach(service.storage_path) do |file|
|
177
|
+
if file =~ /(command_log|snapshot)$/
|
178
|
+
File.delete(File.join(service.storage_path, file))
|
179
|
+
end
|
180
|
+
end
|
181
|
+
rescue
|
182
|
+
Dir.mkdir(service.storage_path)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def initialize(service)
|
187
|
+
@storage_path = service.storage_path
|
188
|
+
@server = Madeleine::Automatic::AutomaticSnapshotMadeleine.new(service.storage_path,
|
189
|
+
Madeleine::ZMarshal.new) {
|
190
|
+
service.new
|
191
|
+
}
|
192
|
+
start_snapshot_thread
|
193
|
+
end
|
194
|
+
|
195
|
+
def command_log_present?
|
196
|
+
not Dir[storage_path + '/*.command_log'].empty?
|
197
|
+
end
|
198
|
+
|
199
|
+
def snapshot
|
200
|
+
@server.take_snapshot
|
201
|
+
end
|
202
|
+
|
203
|
+
def start_snapshot_thread
|
204
|
+
Thread.new(@server) {
|
205
|
+
hours_since_last_snapshot = 0
|
206
|
+
while true
|
207
|
+
begin
|
208
|
+
hours_since_last_snapshot += 1
|
209
|
+
# Take a snapshot if there is a command log, or 24 hours
|
210
|
+
# have passed since the last snapshot
|
211
|
+
if command_log_present? or hours_since_last_snapshot >= 24
|
212
|
+
ActionController::Base.logger.info "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}] " +
|
213
|
+
'Taking a Madeleine snapshot'
|
214
|
+
snapshot
|
215
|
+
hours_since_last_snapshot = 0
|
216
|
+
end
|
217
|
+
sleep(1.hour)
|
218
|
+
rescue => e
|
219
|
+
ActionController::Base.logger.error(e)
|
220
|
+
# wait for a minute (not to spoof the log with the same error)
|
221
|
+
# and go back into the loop, to keep trying
|
222
|
+
sleep(1.minute)
|
223
|
+
ActionController::Base.logger.info("Retrying to save a snapshot")
|
224
|
+
end
|
225
|
+
end
|
226
|
+
}
|
227
|
+
end
|
228
|
+
|
229
|
+
def system
|
230
|
+
@server.system
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|