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

https://bitbucket.org/gawel/shabti · Python · 537 lines · 435 code · 20 blank · 82 comment · 4 complexity · dbdf9e468a9aae2e1e6c795e9c81c3a8 MD5 · raw file

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