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.
Files changed (102) hide show
  1. data/CHANGELOG +165 -0
  2. data/README +68 -172
  3. data/app/controllers/admin_controller.rb +94 -0
  4. data/app/controllers/application.rb +131 -0
  5. data/app/controllers/file_controller.rb +129 -0
  6. data/app/controllers/wiki_controller.rb +354 -0
  7. data/{libraries/view_helper.rb → app/helpers/application_helper.rb} +68 -33
  8. data/app/models/author.rb +3 -3
  9. data/app/models/chunks/category.rb +33 -31
  10. data/app/models/chunks/chunk.rb +86 -20
  11. data/app/models/chunks/engines.rb +54 -38
  12. data/app/models/chunks/include.rb +41 -29
  13. data/app/models/chunks/literal.rb +31 -19
  14. data/app/models/chunks/nowiki.rb +28 -31
  15. data/app/models/chunks/test.rb +18 -18
  16. data/app/models/chunks/uri.rb +182 -97
  17. data/app/models/chunks/wiki.rb +141 -82
  18. data/app/models/file_yard.rb +58 -0
  19. data/app/models/page.rb +112 -86
  20. data/app/models/page_lock.rb +22 -23
  21. data/app/models/page_set.rb +89 -64
  22. data/app/models/revision.rb +123 -90
  23. data/app/models/web.rb +176 -89
  24. data/app/models/wiki_content.rb +207 -105
  25. data/app/models/wiki_service.rb +233 -83
  26. data/app/models/wiki_words.rb +23 -25
  27. data/app/views/{wiki/new_system.rhtml → admin/create_system.rhtml} +83 -78
  28. data/app/views/{wiki/new_web.rhtml → admin/create_web.rhtml} +69 -64
  29. data/app/views/admin/edit_web.rhtml +136 -0
  30. data/app/views/file/file.rhtml +19 -0
  31. data/app/views/file/import.rhtml +23 -0
  32. data/app/views/layouts/default.rhtml +85 -0
  33. data/app/views/markdown_help.rhtml +12 -16
  34. data/app/views/mixed_help.rhtml +7 -0
  35. data/app/views/navigation.rhtml +30 -19
  36. data/app/views/rdoc_help.rhtml +12 -16
  37. data/app/views/textile_help.rhtml +24 -28
  38. data/app/views/wiki/authors.rhtml +11 -13
  39. data/app/views/wiki/edit.rhtml +39 -31
  40. data/app/views/wiki/export.rhtml +12 -14
  41. data/app/views/wiki/feeds.rhtml +14 -10
  42. data/app/views/wiki/list.rhtml +64 -57
  43. data/app/views/wiki/locked.rhtml +23 -14
  44. data/app/views/wiki/login.rhtml +14 -11
  45. data/app/views/wiki/new.rhtml +31 -27
  46. data/app/views/wiki/page.rhtml +115 -81
  47. data/app/views/wiki/print.rhtml +14 -16
  48. data/app/views/wiki/published.rhtml +9 -10
  49. data/app/views/wiki/recently_revised.rhtml +27 -30
  50. data/app/views/wiki/revision.rhtml +103 -81
  51. data/app/views/wiki/rollback.rhtml +14 -9
  52. data/app/views/wiki/rss_feed.rhtml +22 -22
  53. data/app/views/wiki/search.rhtml +38 -15
  54. data/app/views/wiki/tex.rhtml +22 -22
  55. data/app/views/wiki/tex_web.rhtml +34 -34
  56. data/app/views/wiki/web_list.rhtml +18 -13
  57. data/app/views/wiki_words_help.rhtml +9 -8
  58. data/config/environment.rb +82 -0
  59. data/config/environments/development.rb +5 -0
  60. data/config/environments/production.rb +4 -0
  61. data/config/environments/test.rb +17 -0
  62. data/config/routes.rb +18 -0
  63. data/instiki +6 -67
  64. data/instiki.rb +3 -0
  65. data/lib/active_record_stub.rb +31 -0
  66. data/{libraries/diff → lib}/diff.rb +444 -475
  67. data/lib/instiki_errors.rb +15 -0
  68. data/{libraries → lib}/rdocsupport.rb +151 -155
  69. data/lib/redcloth_for_tex.rb +736 -0
  70. data/natives/osx/desktop_launcher/AppDelegate.h +18 -0
  71. data/natives/osx/desktop_launcher/AppDelegate.mm +109 -0
  72. data/natives/osx/desktop_launcher/Credits.html +16 -0
  73. data/natives/osx/desktop_launcher/English.lproj/InfoPlist.strings +0 -0
  74. data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib +13 -0
  75. data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib +24 -0
  76. data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/objects.nib +0 -0
  77. data/natives/osx/desktop_launcher/Info.plist +13 -0
  78. data/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj +592 -0
  79. data/natives/osx/desktop_launcher/Instiki_Prefix.pch +7 -0
  80. data/natives/osx/desktop_launcher/MakeDMG.sh +9 -0
  81. data/natives/osx/desktop_launcher/main.mm +14 -0
  82. data/natives/osx/desktop_launcher/version.plist +16 -0
  83. data/public/404.html +6 -0
  84. data/public/500.html +6 -0
  85. data/public/dispatch.rb +10 -0
  86. data/public/favicon.ico +0 -0
  87. data/public/javascripts/edit_web.js +52 -0
  88. data/public/javascripts/prototype.js +336 -0
  89. data/{app/views/static_style_sheet.rhtml → public/stylesheets/instiki.css} +221 -198
  90. data/script/breakpointer +4 -0
  91. data/script/server +93 -0
  92. metadata +59 -32
  93. data/app/controllers/wiki.rb +0 -389
  94. data/app/models/chunks/match.rb +0 -19
  95. data/app/views/bottom.rhtml +0 -4
  96. data/app/views/top.rhtml +0 -49
  97. data/app/views/wiki/edit_web.rhtml +0 -138
  98. data/libraries/action_controller_servlet.rb +0 -177
  99. data/libraries/erb.rb +0 -490
  100. data/libraries/madeleine_service.rb +0 -68
  101. data/libraries/redcloth_for_tex.rb +0 -869
  102. data/libraries/web_controller_server.rb +0 -81
@@ -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 (: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
- class WikiContent < String
41
-
42
- PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word ]
43
- POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ]
44
- DEFAULT_OPTS = {
45
- :pre_engine_actions => PRE_ENGINE_ACTIONS,
46
- :post_engine_actions => POST_ENGINE_ACTIONS,
47
- :engine => Engines::Textile,
48
- :engine_opts => [],
49
- :mode => [:display]
50
- }
51
-
52
- attr_reader :web, :options, :rendered
53
-
54
- # Create a new wiki content string from the given one.
55
- # The options are explained at the top of this file.
56
- def initialize(revision, options = {})
57
- @revision = revision
58
- @web = @revision.page.web
59
-
60
- # Deep copy of DEFAULT_OPTS to ensure that changes to PRE/POST_ENGINE_ACTIONS stay local
61
- @options = Marshal.load(Marshal.dump(DEFAULT_OPTS)).update(options)
62
- @options[:engine] = Engines::MAP[@web.markup] || Engines::Textile
63
- @options[:engine_opts] = (@web.safe_mode ? [:filter_html, :filter_styles] : [])
64
-
65
- @options[:pre_engine_actions].delete(WikiChunk::Word) if @web.brackets_only
66
-
67
- super(@revision.content)
68
-
69
- begin
70
- render!(@options[:pre_engine_actions] + [@options[:engine]] + @options[:post_engine_actions])
71
- rescue => e
72
- @rendered = e.message
73
- end
74
- end
75
-
76
- # Call @web.page_link using current options.
77
- def page_link(name, text)
78
- @web.make_link(name, text, @options)
79
- end
80
-
81
- # Find all the chunks of the given types
82
- def find_chunks(chunk_type)
83
- rendered.select { |chunk| chunk.kind_of?(chunk_type) }
84
- end
85
-
86
- # Render this content using the specified actions.
87
- def render!(chunk_types)
88
- @chunks = []
89
- chunk_types.each { |chunk_type| self.apply_type!(chunk_type) }
90
-
91
- @rendered = @chunks.map { |chunk| chunk.unmask(self) }.compact
92
- (@chunks - @rendered).each { |chunk| chunk.revert(self) }
93
- end
94
-
95
- # Find all the chunks of the given type in this content
96
- # Each time the type's pattern is matched, create a new
97
- # chunk for it, and replace the occurance of the chunk
98
- # in this content with its mask.
99
- def apply_type!(chunk_type)
100
- self.gsub!( chunk_type.pattern ) do |match|
101
- @chunks << chunk_type.new($~)
102
- @chunks.last.mask(self)
103
- end
104
- end
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
@@ -1,83 +1,233 @@
1
- $:.unshift(File.dirname(__FILE__) + "/../../libraries/")
2
-
3
- require "madeleine_service"
4
- require "web"
5
- require "page"
6
- require "author"
7
-
8
- class WikiService < MadeleineService
9
- attr_reader :webs, :system
10
-
11
- def initialize
12
- @webs, @system = {}, {}
13
- end
14
-
15
- def setup?
16
- !@system.empty?
17
- end
18
-
19
- def authenticate(password)
20
- password == (@system["password"] || "instiki")
21
- end
22
-
23
- def setup(password, web_name, web_address)
24
- @system["password"] = password
25
- create_web(web_name, web_address)
26
- end
27
-
28
- def create_web(name, address, password = nil)
29
- @webs[address] = Web.new(name, address, password) unless @webs[address]
30
- end
31
-
32
- def update_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false,
33
- password = nil, published = false, brackets_only = false, count_pages = false)
34
- if old_address != new_address
35
- @webs[new_address] = @webs[old_address]
36
- @webs.delete(old_address)
37
- @webs[new_address].address = new_address
38
- end
39
-
40
- web = @webs[new_address]
41
- web.refresh_revisions if settings_changed?(web, markup, safe_mode, brackets_only)
42
-
43
- web.name, web.markup, web.color, web.additional_style, web.safe_mode =
44
- name, markup, color, additional_style, safe_mode
45
-
46
- web.password, web.published, web.brackets_only, web.count_pages =
47
- password, published, brackets_only, count_pages
48
- end
49
-
50
- def read_page(web_address, page_name)
51
- web = @webs[web_address]
52
- web ? web.pages[page_name] : nil
53
- end
54
-
55
- def write_page(web_address, page_name, content, written_on, author)
56
- page = Page.new(@webs[web_address], page_name, content, written_on, author)
57
- @webs[web_address].add_page(page)
58
- page
59
- end
60
-
61
- def revise_page(web_address, page_name, content, revised_on, author)
62
- page = read_page(web_address, page_name)
63
- page.revise(content, revised_on, author)
64
- page
65
- end
66
-
67
- def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil)
68
- page = read_page(web_address, page_name)
69
- page.rollback(revision_number, created_at, author_id)
70
- page
71
- end
72
-
73
- def remove_orphaned_pages(web_address)
74
- @webs[web_address].remove_pages(@webs[web_address].select.orphaned_pages)
75
- end
76
-
77
- private
78
- def settings_changed?(web, markup, safe_mode, brackets_only)
79
- web.markup != markup ||
80
- web.safe_mode != safe_mode ||
81
- web.brackets_only != brackets_only
82
- end
83
- end
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