PageRenderTime 71ms CodeModel.GetById 12ms app.highlight 52ms RepoModel.GetById 0ms app.codeStats 0ms

/shabti/templates/pyblosxom/data/blog/plugins/xmlrpc_metaweblog.py

https://bitbucket.org/gawel/shabti
Python | 537 lines | 532 code | 0 blank | 5 comment | 0 complexity | dbdf9e468a9aae2e1e6c795e9c81c3a8 MD5 | raw file
  1"""
  2Copyright (c) 2003-2005 Ted Leung
  3
  4Permission is hereby granted, free of charge, to any person obtaining
  5a copy of this software and associated documentation files (the
  6"Software"), to deal in the Software without restriction, including
  7without limitation the rights to use, copy, modify, merge, publish,
  8distribute, sublicense, and/or sell copies of the Software, and to
  9permit persons to whom the Software is furnished to do so, subject to
 10the following conditions:
 11
 12The above copyright notice and this permission notice shall be
 13included in all copies or substantial portions of the Software.
 14
 15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 16EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 17MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 18NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 19LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 20OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 21WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 22
 23options for config.py:
 24
 25if py['xmlrpc_metaweblog_ping'] == 'True' then autoping will be invoked to
 26generate trackbacks and pingbacks
 27
 28Implements the metaWeblog XML-RPC interface.
 29See spec here: http://www.xmlrpc.com/metaWeblogApi
 30
 31Implemented methods:
 32  - metaWeblog.newPost
 33  - metaWeblog.editPost
 34  - metaWeblog.getPost
 35  - metaWeblog.getCategories
 36  - metaWeblog.getRecentPosts
 37
 38"""
 39import os, xmlrpclib, re, time, os.path
 40from Pyblosxom import tools, plugin_utils
 41from Pyblosxom.entries.fileentry import FileEntry
 42
 43def cb_start(args):
 44    request = args["request"]
 45    config = request.getConfiguration()
 46
 47
 48#
 49# Pyblosxom callback API functions
 50#
 51    
 52def verify_installation(request):
 53    config = request.getConfiguration()
 54    retval = 1
 55
 56    # all config properties are optional
 57    if not config.has_key('xmlrpc_metaweblog_ping'):
 58        print("missing optional property: 'xmlrpc_metaweblog_ping'")
 59
 60    return retval
 61
 62def cb_xmlrpc_register(args):
 63    """
 64    Binds the methods that we handle with the function instances.
 65    Part of the pyblosxom callback api
 66
 67    @param args: the callback arguments
 68    @type args: dict
 69    """
 70    args["methods"].update( 
 71            {'metaWeblog.newPost':metaWeblog_newPost,
 72             'metaWeblog.editPost':metaWeblog_editPost,
 73             'metaWeblog.getPost':metaWeblog_getPost,
 74             'metaWeblog.newMediaObject':metaWeblog_newMediaObject,
 75             'metaWeblog.getCategories':metaWeblog_getCategories,
 76             'metaWeblog.getRecentPosts':metaWeblog_getRecentPosts})
 77    
 78    return args
 79
 80def authenticate(request, username, password):
 81    """
 82    Handles authentication.  This works by getting the xmlrpc dispatcher
 83    plugin Python module and then calling authenticate on that.
 84    
 85    @param request: the pyblosxom Request instance
 86    @type  request: Request
 87    
 88    @param username: the username
 89    @type  username: string
 90    
 91    @param password: the password
 92    @type  password: string
 93    """
 94    xmlrpc_plugin = plugin_utils.get_plugin_by_name("xmlrpc")
 95    xmlrpc_plugin.authenticate(request, username, password)
 96
 97#
 98# metaWeblog API functions
 99#
100
101def metaWeblog_editPost(request, postid, username, password, struct, publish):
102    """
103    Edit an existing post
104    Part of the metaWeblog API
105
106    @param request: the pyblosxom Request instance
107    @type  request: Request
108
109    @param username: the username
110    @type  username: string
111
112    @param password: the password
113    @type  password: string
114
115    @param struct: the metaWeblog api struct
116    @type  struct: dict
117
118    @param publish: to publish (true) or not
119    @type  publish: boolean
120
121    @returns an xmlrpclib boolean -- true if the edit was successful, false otherwise
122    """
123    logger = tools.getLogger()
124    logger.debug("editPost %s %s %s" % (postid, struct, publish))
125
126    authenticate(request, username, password)
127    config = request.getConfiguration()
128    ping = config.get('xmlrpc_metaweblog_ping',0)
129
130    return xmlrpclib.Boolean(_writePost(config, username, postid, struct, publish, ping))
131
132def metaWeblog_newPost(request, blogid, username, password, struct, publish):
133    """
134    Create a new entry on the server
135    Part of the metaWeblog API
136
137    if py['xmlrpc_metaweblog_ping'] == 'True' then autoping will be invoked to
138    generate trackbacks and pingbacks
139
140    @param request: the pyblosxom Request instance
141    @type  request: Request
142
143    @param username: the username
144    @type  username: string
145
146    @param password: the password
147    @type  password: string
148
149    @param struct: the metaWeblog API struct
150    @type  struct: dict
151
152    @param publish: to publish (true) or not
153    @type  publish: boolean
154    """
155    logger = tools.getLogger()
156    logger.debug("newPost %s %s %s" % (blogid, struct, publish))
157    authenticate(request, username, password)
158    config = request.getConfiguration()
159    ping = config.get('xmlrpc_metaweblog_ping',0)
160
161    postId = _buildPostId(request, blogid, struct)
162    result = _writePost(config, username, postId, struct, publish, ping)
163    
164    if result:
165        return postId
166    else:
167        return xmlrpclib.Boolean(False)
168
169    
170def metaWeblog_getPost(request, postid, username, password):
171    """
172    Get a single post from the server
173    Part of the metaWeblog API
174
175    @param request: the pyblosxom Request instance
176    @type  request: Request
177
178    @param postid: the id of the post
179    @type postid: string
180
181    @param username: the username
182    @type  username: string
183
184    @param password: the password
185    @type  password: string
186
187    @returns the post whose id is postid
188    @rtype dict
189    """
190    logger = tools.getLogger()
191    logger.debug("getPost: postid: %s" % (postid,))
192    authenticate(request, username, password)
193    config = request.getConfiguration()
194
195    logger.debug("datadir = %s, file = %s.txt" % (config['datadir'], postid))
196    entry = FileEntry(request, os.path.join(config['datadir'],"%s.txt" % postid), config['datadir'])
197    post = { 'permaLink': "%s/%s/%s/%s#%s" % (config['base_url'], entry['yr'],entry['mo_num'],entry['da'],entry['fn']),
198             'title':entry['title'],
199             'description':entry['body'], 
200             'postid':re.sub(r'^/', '', "%s/%s"% (entry['absolute_path'], entry['fn'])),
201             'categories':[entry['absolute_path']],
202             'dateCreated':xmlrpclib.DateTime(entry['w3cdate']) }
203    return post
204
205def metaWeblog_getCategories(request, blogid, username, password):
206    """
207    Get the available categories
208    Part of the metaWeblog API
209
210    @param request: the pyblosxom Request instance
211    @type  request: Request
212
213    @param blogid: the id of the blog
214    @type blogid: string
215
216    @param username: the username
217    @type  username: string
218
219    @param password: the password
220    @type  password: string
221
222    @returns list of categories (each category is a string)
223    @rtype list
224    """
225    logger = tools.getLogger()
226    logger.debug("getCategories blogid: %s" % blogid)
227    authenticate(request, username, password)
228    config = request.getConfiguration()
229
230    clist = _getCategories(request)
231
232    return clist
233
234def metaWeblog_getRecentPosts(request, blogid, username, password, numberOfPosts):
235    """
236    Get the most recent posts
237    Part of the metaWeblog API
238
239    @param request: the pyblosxom Request instance
240    @type  request: Request
241
242    @param blogid: the id of the blog
243    @type blogid: string
244
245    @param username: the username
246    @type  username: string
247
248    @param password: the password
249    @type  password: string
250
251    @param numberOfPosts: the number of posts to retreive
252    @type  numberOfPosts: int
253
254    @returns list of dicts, one per post
255    @rtype list
256    """
257    logger = tools.getLogger()
258    logger.debug("getRecentPosts blogid:%s count:%s" % (blogid, numberOfPosts))
259    authenticate(request, username, password)
260    config = request.getConfiguration()
261
262    filelist = tools.Walk(request, config['datadir'], int(config['depth']), pattern=_allEntriesPattern(request))
263
264    entryList = []
265    for f in filelist:
266        entry = FileEntry(request, f, config['datadir'])
267        entryList.append((entry._mtime, entry))
268    entryList.sort()
269    entryList.reverse()
270    try:
271        numberOfPosts = int(numberOfPosts)
272    except:
273        logger.error("Couldn't convert numberOfPosts")
274        numberOfPosts = 5
275    entryList = [ x[1] for x in entryList ][: numberOfPosts]
276
277    def fix_path(path):
278        if path == "":
279            return '/'
280        else:
281            return path
282
283    posts = [ { 'permaLink': "%s/%s/%s/%s#%s" % (config['base_url'], x['yr'],x['mo_num'],x['da'],x['fn']),
284                'title':x['title'],
285                'description':x['body'], 
286                'postid':re.sub(r'^/', '', "%s/%s"% (x['absolute_path'], x['fn'])),
287                'categories':[ fix_path(x['absolute_path'])],
288                'dateCreated':xmlrpclib.DateTime(x['w3cdate']) }  for x in entryList ]
289
290    return posts
291
292def metaWeblog_newMediaObject(request, blogid, username, password, struct):
293    """
294    Create a new media object
295    Part of the metaWeblog API
296
297    @param request: the pyblosxom Request instance
298    @type  request: Request
299
300    @param blogid: the id of the blog
301    @type blogid: string
302
303    @param username: the username
304    @type  username: string
305
306    @param password: the password
307    @type  password: string
308
309    @param struct: the metaWeblog API struct
310    @type  struct: dict
311    """
312    logger = tools.getLogger()
313    logger.debug("newMediaObject")
314    authenticate(request, username, password)
315    config = request.getConfiguration()
316
317    name = struct['name']
318    mimeType = struct['type']
319    bits = struct['bits']
320    
321    root = config['xmlrpc_metaweblog_image_dir']
322
323    path = os.path.join("%s/%s" % (root, name))
324    logger.debug("newMediaObject: %s,%s, %s, %s " % (name, path, mimeType, bits))
325    f = None
326    try:
327        f = open(path, 'wb')
328        f.write(bits.data)
329        f.close()
330    except:
331        if f is not None:
332            f.close()
333        return 0
334#    return { 'url': "%s/%s%s" % (config['base_url'],config['xmlrpc_metaweblog_image_prefix'],name) }
335    return { 'url': "%s/%s" % (config['base_url'],name) }
336
337#
338# helper functions
339#
340def _allEntriesPattern(request):
341    """
342    Return a regular expression pattern that includes all valid entry
343    extensions and their unpublished (suffexed with '-') versions.  This
344    allows these entries to be displayed in getRecentPosts, and to be counted
345    in _getEntryCount()
346
347    @param request: the HTTP Request
348    @type request: Request
349
350    @returns a regular expression pattern
351    @rtype string
352    """
353    # make sure to count file with extensions ending in -
354    ext = request.getData()['extensions']
355    pats = list(ext.keys()) # copy it
356    for p in ext.keys():
357        pats.append(p+"-")
358    entryPattern = re.compile(r'.*\.(' + '|'.join(pats) + r')$')
359
360    return entryPattern
361
362def _buildPostId(request, blogid, struct):
363    """
364    Construct the id for the post
365
366    The algorithm used for constructing the post id is to concatenate the
367    pyblosxom category (directory path, with the datadir prefix removed) with
368    the count of entries.  This means that postids are increasing integers.
369
370    @param request: the HTTP Request
371    @type request: Request
372
373    @param blogid: the id of the blog
374    @type blogid: string
375
376    @param struct: the metaWeblog API struct
377    @type struct: dict
378
379    @return the post id
380    @rtype string
381    """
382    config = request.getConfiguration()
383
384    category = ''
385    try:
386        category = struct['categories'][0]
387    except:
388        pass
389
390    count = _getEntryCount(request)
391
392    if not category == '':
393        postId = os.path.join(category, "%d" % count)
394    else:
395        postId = os.path.join("%d" % count)
396
397    logger = tools.getLogger()
398    logger.debug(postId)
399    return postId
400
401def _getEntryCount(request):
402    """
403    Return a count of the number of published and unpublished
404    (suffixed with '-') entries
405
406    @param request: the HTTP Request
407    @type request: Request
408
409    @return the number of published an unpublished entries
410    @rtype int
411    """
412    config = request.getConfiguration()
413    root = config['datadir']
414
415    elist = tools.Walk(request, root, pattern=_allEntriesPattern(request))
416    elist = [mem[len(root)+1:] for mem in elist]
417    
418    elistmap = {}
419    for mem in elist:
420        mem = os.path.dirname(mem)
421        elistmap[mem] = 1 + elistmap.get(mem, 0)
422        mem = mem.split(os.sep)
423    return len(elist)
424
425def _getCategories(request):
426    """
427    Return a list of all the categories in this pyblosxom blog,
428    excluding CVS and comments directories, but including category directories
429    which have no entries (newly created categories).
430
431    Each item in the list contains the following items:
432      - categoryId
433      - description
434      - categoryName
435      - htmlUrl
436      - rssUrl
437
438    @param request: the HTTP Request
439    @type request: Request
440
441    @return a list of dicts
442    @rtype list of dicts
443    """
444    # now make a list of all the categories
445    # bypass tools.walk in order to pickup empty categories
446    config = request.getConfiguration()
447    root = config['datadir']
448
449    def walk_filter(arg, dirname, files):
450        if dirname==root:
451            return
452        if not dirname.endswith('CVS') and not dirname.startswith(root+'/comments'):
453            cleanDirName = dirname.replace(root + '/', '')
454            dirDescriptor = {}
455            dirDescriptor["categoryId"] = cleanDirName
456            dirDescriptor["description"] = cleanDirName
457            dirDescriptor["categoryName"] = cleanDirName
458            dirDescriptor["htmlUrl"] = config["base_url"] + "/" + cleanDirName
459            dirDescriptor["rssURL"] = config["base_url"] + "/" + cleanDirName +"/?flavor=rss"
460            arg.append(dirDescriptor)
461 
462    clist = []
463    os.path.walk(root, walk_filter, clist)
464    clist.append("/")
465    clist.sort()                   
466
467    return clist
468
469def _writePost(config, username, postid, struct, publish=True, ping=False):    
470    """
471    Write a post file into pyblosxom
472
473    @param config: the pyblosxom configuration
474    @type config: config
475
476    @param username: the username of the poster
477    @type username: string
478
479    @param postid: the id of the post to be written
480    @type postid: string
481
482    @param struct: the metaWeblog API struct
483    @type struct: dict
484
485    @param publish: to publish (true) or not
486    @type publish: boolean
487
488    @param ping: whether or not to invoke autoping (true) or not
489    @type ping: boolean
490    """
491    root = config['datadir']
492    path = os.path.join(root,"%s.txt" % postid)
493
494    logger = tools.getLogger()
495    logger.debug("path = "+path)
496    if not publish:
497        path += '-'
498
499    content = "%s\n#author %s\n%s" % (struct['title'], username, struct['description'])
500
501    try:
502        atime, mtime = (0, 0)
503        if os.path.isfile(path):
504            atime, mtime = os.stat(path)[7:9]
505        if struct.has_key('dateCreated'):
506            import types
507            dc = struct['dateCreated']
508            if type(dc) == types.StringType:
509                mtime = time.mktime(time.strptime(dc, '%Y%m%dT%H:%M:%S'))
510            elif type(dc) == types.InstanceType:
511                mtime = time.mktime(time.strptime(str(dc), '%Y%m%dT%H:%M:%SZ'))
512                
513        f = open(path,'w')
514        f.write(content)
515        f.close()
516
517        if atime != 0 and mtime != 0:
518            try:
519                os.utime(path, (atime, mtime))
520            except:
521                pass
522    except: 
523        if f is not None:
524            f.close()
525        return 0
526    if ping:
527        try:
528            import autoping
529            os.chdir(root)
530            autoping.autoping("%s.txt" % postid)
531        except OSError:
532            logger.error("autoping failed for %s with OSError %" % postid)
533            pass
534        except:
535            logger.error("autoping failed for %s" % path)
536            pass
537    return 1