root/HTTP/WikiControlService.lua

Revision 1423 (checked in by rsz, 5 months ago)

cleanup

Line 
1 --------------------------------------------------------------------------------
2 -- Title:               WikiControlService.lua
3 -- Description:         Like a square peg in a round hole
4 -- Author:              Raphaël Szwarc http://alt.textdrive.com/lua/
5 -- Creation Date:       January 30, 2007
6 -- Legal:               Copyright (C) 2007 Raphaël Szwarc
7 --                      Under the terms of the MIT License
8 --                      http://www.opensource.org/licenses/mit-license.html
9 --------------------------------------------------------------------------------
10
11 -- import dependencies
12 local HTTP = require( 'HTTP' )
13 local HTTPExtra = require( 'HTTPExtra' )
14 local HTTPService = require( 'HTTPService' )
15 local Template = require( 'Template' )
16 local Wiki = require( 'Wiki' )
17 local WikiContent = require( 'WikiContent' )
18 local WikiContentService = require( 'WikiContentService' )
19 local WikiService = require( 'WikiService' )
20
21 local BaseLink = WikiService.BaseLink
22 local DateLink = WikiService.DateLink
23 local FeedLink = WikiService.FeedLink
24 local IndexLink = WikiService.IndexLink
25
26 local Encode = WikiService.Encode
27 local Path = WikiService.Path
28
29 local os = require( 'os' )
30
31 local error = error
32 local getmetatable = getmetatable
33 local ipairs = ipairs
34 local pairs = pairs
35 local pcall = pcall
36 local require = require
37 local setmetatable = setmetatable
38 local tonumber = tonumber
39 local tostring = tostring
40 local type = type
41
42 --------------------------------------------------------------------------------
43 -- WikiControlService
44 --------------------------------------------------------------------------------
45
46 module( 'WikiControlService' )
47 _VERSION = '1.0'
48
49 local self = setmetatable( _M, {} )
50 local meta = getmetatable( self )
51
52 --------------------------------------------------------------------------------
53 -- Utilities
54 --------------------------------------------------------------------------------
55
56 local function Message( self )
57     local aMessage = self.message
58    
59     if aMessage then
60         local someTypes = { authorization = 'warning', check = 'error', count = 'warning', delete = 'error', rename = 'error', time = 'warning', token = 'error', value = 'warning' }
61         local aType = someTypes[ self.type ] or 'warning'
62         local WikiMessage = require( 'WikiMessage' )
63         local aMessage = WikiMessage( aMessage, aType )
64        
65         return aMessage
66     end
67 end
68
69 local function Token()
70     local Token = require( 'Token' )
71     local aToken = Token( HTTP.request.url )
72     local aList = { 'check', 'count', 'delete', 'rename', 'time', 'token', 'value' }
73     local aMap = {}
74    
75     for _, aValue in ipairs( aList ) do
76         aMap[ aValue ] = aToken[ aValue ]
77     end
78    
79     return aMap
80 end
81
82 local function Context()
83     local someTypes = { check = nil, count = 'number', delete = 'string', rename = 'string', time = 'number', token = 'string', value = 'string' }
84     local someFunctions = { check = nil, count = tonumber, delete = tostring, rename = tostring, time = tonumber, token = tostring, value = tostring }
85     local aToken = Token()
86     local aParameter = HTTP.request.parameter
87     local aContext = {}
88    
89     for aKey, aValue in pairs( aToken ) do
90         local aType = someTypes[ aKey ]
91         local aFunction = someFunctions[ aKey ]
92         local aValue = aParameter[ aValue ]
93
94         if aValue and aFunction then
95             aValue = aFunction( aValue )
96         end
97
98         if aValue and tostring( aValue ):len() == 0 then
99             aValue = nil
100         end
101        
102         if not aType or type( aValue ) == aType then
103             aContext[ aKey ] = aValue
104         end
105     end
106    
107     return aContext
108 end
109
110 local function Validate( aKey, aValue, aName )
111     local aContext = Context()
112     local aToken = Token()
113     local anAuthorization = HTTP.request.authorization
114    
115     if aContext.check then
116         return false, 'check', 'Invalid data. Try again.'
117     end
118    
119     if not aContext.time or ( os.time() - aContext.time ) < 3 then
120         return false, 'time', 'Too fast. Try again, but slowly.'
121     end
122    
123     if not aContext.token or aContext.token ~= aToken.token then
124         return false, 'token', 'Invalid data. Try again.'
125     end
126    
127     if aKey == 'rename'
128     and ( not aContext.value
129     or not WikiContent[ aContext.value ]
130     or WikiContent[ aContext.value ] == aName ) then
131         return false, 'name', 'Invalid name. Try to a different one.'
132     end
133
134     if aValue ~= Token()[ aKey ] then
135         return false, aKey, 'Invalid data. Try again.'
136     end
137
138     if aKey == 'delete'
139     and ( not aContext.count or aContext.count < 2 ) then
140         local aMessage = ( 'Are you sure you want to %s “%s”? If yes, press “%s” again.' ):format( aKey, aName, aKey )
141    
142         return false, 'count', aMessage
143     end
144
145     if aKey == 'rename'
146     and ( not aContext.count or aContext.count < 2 ) then
147         local aValue = WikiContent[ aContext.value ]
148         local aMessage = ( 'Are you sure you want to %s “%s” to “%s”? If yes, press “%s” again.' ):format( aKey, aName, aValue, aKey )
149    
150         return false, 'count', aMessage
151     end
152
153     if not anAuthorization.user
154     or not anAuthorization.password
155     or anAuthorization.password ~= aName then
156         local aMessage = ( 'Invalid authorization. Please identify yourself, using “%s” as the password.' ):format( aName )
157        
158         HTTP.response.authorization.realm = 'Nanoki'
159    
160         return false, 'authorization', aMessage
161     end
162    
163     if aName == 'log' then
164         return false, aKey, 'Sorry. Cannot do that.'
165     end
166            
167     return true
168 end
169
170 --------------------------------------------------------------------------------
171 -- Service methods
172 --------------------------------------------------------------------------------
173
174 function self:get()
175     local aLayoutTemplate = Template[ 'WikiLayout.txt' ]
176     local aTemplate = Template[ 'WikiControlService.txt' ]
177     local aContent = self.content
178     local aDate = os.date( '!*t', aContent.creation )
179     local aDateLink = DateLink( aDate.year, aDate.month, aDate.day )
180     local aContext = Context()
181     local aToken = Token()
182    
183     aTemplate[ 'actionPath' ] = Encode( HTTP.request.url.path )
184     aTemplate[ 'title' ] = Encode( aContent.data.title .. ' — Control' )
185     aTemplate[ 'message' ] = Message( self )
186     aTemplate[ 'name' ] = Encode( aContent.name )
187
188     aTemplate[ 'checkToken' ] = Encode( aToken.check )
189     aTemplate[ 'check' ] = Encode( aContext.check )
190     aTemplate[ 'countToken' ] = Encode( aToken.count )
191     aTemplate[ 'count' ] = Encode( ( aContext.count or 0 ) + 1 )
192     aTemplate[ 'deleteToken' ] = Encode( aToken.delete )
193     aTemplate[ 'renameToken' ] = Encode( aToken.rename )
194     aTemplate[ 'timeToken' ] = Encode( aToken.time )
195     aTemplate[ 'time' ] = Encode( os.time() )
196     aTemplate[ 'token' ] = Encode( aToken.token )
197     aTemplate[ 'valueToken' ] = Encode( aToken.value )
198     aTemplate[ 'value' ] = Encode( aContext.value or aContent.name )
199
200     aLayoutTemplate[ 'baseLink' ] = Encode( BaseLink() )
201     aLayoutTemplate[ 'indexLink' ] = Encode( IndexLink( aContent.prefix ) )
202     aLayoutTemplate[ 'dateLink' ] = Encode( aDateLink )
203     aLayoutTemplate[ 'feedLink' ] = FeedLink()
204     aLayoutTemplate[ 'path' ] = Path( self )
205     aLayoutTemplate[ 'query' ] = nil
206     aLayoutTemplate[ 'robot' ] = 'noindex,nofollow'
207     aLayoutTemplate[ 'title' ] = Encode( aContent.data.title .. ' — Control' )   
208     aLayoutTemplate[ 'content' ] = aTemplate
209
210     return tostring( aLayoutTemplate )
211 end
212
213 function self:postDelete( aToken )
214     local aContent = self.content
215     local isValid, aType, aMessage = Validate( 'delete', aToken, aContent.name )
216    
217     if isValid then
218         local aName = aContent.name
219         local aCall = function()
220             local lfs = require( 'lfs' )
221             local aLogContent = WikiContent( 'log' )
222             local aLock = aLogContent.lock
223            
224             if aLock and lfs.lock( aLock, 'w' ) then
225                 local WikiEditorService = require( 'WikiEditorService' )
226                 local aMessage = ( 'Deleted “%s”.' ):format( aName )
227                 local aText = ( '%s    \n_%s_    ' ):format( aLogContent.text, aMessage )
228                 local aContext = { title = aLogContent.data.title, text = aText, version = aLogContent.version }
229                 local aService = WikiEditorService( aLogContent )
230
231                 Wiki[ aName ] = false
232    
233                 aService( aLogContent.name, aContext )
234                
235                 aLock:close()
236                
237                 return nil, HTTPService[ WikiContentService( aLogContent ) ]
238             end
239            
240             if aLock then
241                 aLock:close()
242             end
243            
244             error( ( 'Failed to lock %q' ):format( aLogContent.name ) )
245         end
246         local aStatus, aResult, aLocation = pcall( aCall )
247
248         if aStatus then
249             return aResult, aLocation
250         end
251
252         self.message = aResult
253         self.type = 'delete'
254     end
255
256     self.message = aMessage
257     self.type = aType
258    
259     return self:get()
260 end
261
262 function self:postRename( aToken )
263     local aContent = self.content
264     local isValid, aType, aMessage = Validate( 'rename', aToken, aContent.name )
265    
266     if isValid then
267         local aName = aContent.name
268         local aNewName = Context()[ 'value' ]
269         local aCall = function()
270             Wiki[ aName ] = aNewName
271         end
272         local aStatus, aResult = pcall( aCall )
273        
274         if aStatus then
275             local WikiEditorService = require( 'WikiEditorService' )
276             local aNewContent = WikiContent( aNewName )
277             local aNewName = aNewContent.name
278             local aMessage = ( 'Renamed from “%s” to “%s”.' ):format( aName, aNewName )
279             local aPattern = ( '%s/file/' ):format( aName ):gsub( '(%W)', '%%%1' )
280             local aNewPattern = ( '%s/file/' ):format( aNewName ):gsub( '%%', '%%%%' )
281             local aText = aNewContent.text:gsub( aPattern, aNewPattern )
282             local aText = ( '%s    \n_%s_    ' ):format( aText, aMessage )
283             local aContext = { title = aNewContent.data.title, text = aText, version = aNewContent.version }
284             local aService = WikiEditorService( aNewContent )
285
286             aService( aNewContent.name, aContext )
287            
288             return nil, HTTPService[ WikiContentService( aNewContent ) ]
289         end
290        
291         self.message = aResult
292         self.type = 'rename'
293     end
294
295     self.message = aMessage
296     self.type = aType
297    
298     return self:get()
299 end
300
301 --------------------------------------------------------------------------------
302 -- Metamethods
303 --------------------------------------------------------------------------------
304
305 function meta:__call( aContent, aParent )
306     local aControl = { content = aContent, parent = aParent }
307    
308     setmetatable( aControl, self )
309     self.__index = self   
310    
311     return aControl
312 end
313
314 function meta:__concat( aValue )
315     return tostring( self ) .. tostring( aValue )
316 end
317
318 function meta:__tostring()
319     return ( '%s/%s' ):format( self._NAME, self._VERSION )
320 end
321
322 function self:__concat( aValue )
323     return tostring( self ) .. tostring( aValue )
324 end
325
326 function self:__tostring()
327     return 'Control'
328 end
Note: See TracBrowser for help on using the browser.