-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patheditlog.py
More file actions
289 lines (223 loc) · 8.52 KB
/
editlog.py
File metadata and controls
289 lines (223 loc) · 8.52 KB
1
2
3
4
5
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
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
"""
MoinMoin - Edit log management
Copyright (c) 2000, 2001, 2002 by Jürgen Hermann <jh@web.de>
All rights reserved, see COPYING for details.
Functions to keep track of when people have changed pages, so we
can do the recent changes page and so on.
$Id$
"""
# Imports
import cgi, os, string
from MoinMoin import config, user, wikiutil
from MoinMoin.Page import Page
#############################################################################
### Basic Interface
#############################################################################
class LogBase:
""" Basic interface for log stores.
"""
def __init__(self, optstr):
self.options = optstr
def sanityCheck(self):
""" Perform a self-test, i.e. check for correct config, permissions,
etc. Return error message or `false`.
"""
return None
def addEntry(self, pagename, host, mtime, comment, action):
""" Add an entry to the editlog """
pass
#############################################################################
### Logging to text file
#############################################################################
class LogText(LogBase):
""" Storage for log entries in a plain text file.
The editlog is stored with one record per line, as tab-separated
words: pagename, host, time, hostname, userid
TODO: Check values written in are reasonable
"""
def __init__(self, optstr):
LogBase.__init__(self, optstr)
self.filename = os.path.join(config.data_dir, optstr)
def sanityCheck(self):
""" Check for editlog file access.
"""
if not os.access(self.filename, os.W_OK):
return "The edit log '%s' is not writable!" % (self.filename,)
return None
def addEntry(self, pagename, host, mtime, comment, action="SAVE"):
""" Add an entry to the editlog """
import socket
try:
hostname = socket.gethostbyaddr(host)[0]
except socket.error:
hostname = host
remap_chars = string.maketrans('\t\r\n', ' ')
comment = string.translate(comment, remap_chars)
logfile = open(self.filename, 'a+')
entry = string.join((wikiutil.quoteFilename(pagename), host, `mtime`,
hostname, user.User().id, comment, action), "\t") + "\n"
try:
# fcntl.flock(logfile.fileno(), fcntl.LOCK_EX)
logfile.seek(0, 2) # to end
logfile.write(entry)
finally:
# fcntl.flock(logfile.fileno(), fcntl.LOCK_UN)
logfile.close()
#############################################################################
### Factory
#############################################################################
def makeLogStore(option=None):
""" Creates a storage object that provides an implementation of the
storage type given in the `option` parameter; option consists
of a `schema:` part, followed by a schema-specific option string.
Currently supported schemas are: "text".
"""
if option is None: option = config.LogStore
schema, optstr = string.split(option, ':', 1)
if schema == "text":
return LogText(optstr)
return None
#############################################################################
### former code!
#############################################################################
class EditLog:
""" A read-only form of the editlog. Do NOT access the file
config.editlog_name directly, since this may well end up
in a database.
After you called next(), the following member variables are valid:
pagename, addr, ed_time, hostname, userid
"""
_NUM_FIELDS = 7
def __init__(self, **kw):
self._index = 0
self._usercache = {}
self._filename = os.path.join(config.data_dir, 'editlog')
self._lines = self._editlog_raw_lines()
if not kw.get('reverse', 0):
self._lines.reverse()
# set default member values
self._parse_log_line("")
#
# Public interface
#
def next(self):
""" Load next editlog entry, return false after last entry """
if self.peek(0):
self._index = self._index + 1
return 1
return 0
def peek(self, offset):
""" Peek `offset` entries ahead (or behind), return false after last entry """
idx = self._index + offset
if idx < 0 or len(self._lines) <= idx:
self._parse_log_line("")
return 0
self._parse_log_line(self._lines[idx])
return 1
def reset(self):
""" Reset for a new iteration """
self._index = 0
def filter(self, **kw):
""" Filter current entries, reset() does NOT clear any filter
previously set. The cursor is automatically set to the
first entry.
"""
cond = self._make_condition(kw)
rest = []
self.reset()
while self.next():
if cond(self):
rest.append(self._lines[self._index-1])
self._lines = rest
self.reset()
def find(self, **kw):
""" Find an entry, return true on success.
"""
cond = self._make_condition(kw)
for index in range(len(self._lines)):
self._parse_log_line(self._lines[index])
if cond(self):
return 1
self._parse_log_line("")
return 0
def getEditorData(self):
""" Return a string or Page object representing the user that did the edit.
"""
result = self.hostname
if self.userid:
if not self._usercache.has_key(self.userid):
self._usercache[self.userid] = user.User(self.userid)
userdata = self._usercache[self.userid]
if userdata.name:
pg = Page(userdata.name)
if pg.exists():
result = pg
else:
result = userdata.name or self.hostname
return result
def getEditor(self):
""" Return a HTML-safe string representing the user that did the edit.
"""
result = self.getEditorData()
if isinstance(result, Page):
return result.link_to()
return cgi.escape(result)
def size(self):
""" Return size in bytes.
"""
try:
return os.path.getsize(self._filename)
except os.error:
return 0
def __len__(self):
return len(self._lines)
# this would return a raw line, and we do not want that
#def __getitem__(self, key):
# return self._lines[key]
#
# Helper methods
#
def _editlog_raw_lines(self):
""" Load a list of raw editlog lines """
try:
logfile = open(self._filename, 'rt')
try:
# fcntl.flock(logfile.fileno(), fcntl.LOCK_SH)
return logfile.readlines()
finally:
# fcntl.flock(logfile.fileno(), fcntl.LOCK_UN)
logfile.close()
except IOError, er:
import errno
if er.errno == errno.ENOENT:
# just doesn't exist, return empty list
return []
else:
raise er
return []
def _parse_log_line(self, line):
""" Parse a log line to member variables:
pagename, addr, ed_time, hostname, userid
"""
fields = string.split(string.strip(line), '\t')
while len(fields) < self._NUM_FIELDS: fields.append('')
self.pagename, self.addr, self.ed_time, self.hostname, \
self.userid, self.comment, self.action = fields[:self._NUM_FIELDS]
if not self.hostname:
self.hostname = self.addr
self.pagename = wikiutil.unquoteFilename(self.pagename)
self.ed_time = float(self.ed_time or "0")
if not self.action:
self.action = 'SAVE'
def _make_condition(self, kw):
""" Create a callable that filters an entry according to values
in the dictionary "kw". The keys in that dictionary have to
be the member names of the fields ("pagename", etc.).
"""
expr = "1"
for field in ['pagename', 'addr', 'hostname', 'userid']:
if kw.has_key(field):
expr = "%s and x.%s == %s" % (expr, field, `kw[field]`)
if kw.has_key('ed_time'):
expr = "%s and int(x.ed_time) == %s" % (expr, int(kw['ed_time']))
return eval("lambda x: " + expr)