Back when I moved lemondor from an old G4 sitting in my closet to the server generously provided by Andreas Fuchs, which for all I know is located in someone's closet in Austria, I had some database transfer troubles. One bit of trouble that wasn't fixed until tonight was that the first hundred or so lemonodor posts had their formatting mangled, due to somehow having their “convert linebreaks” option set. I was too lazy to search through every post to find the ones with the incorrect setting and fix it using a not-very-speedy web interface that only allowed me to edit one post at a time.
Tonight I finally got fed up and wrote some code that uses Movable Type's XML-RPC support to automate the repair.
First thing, I needed to be able to do XML-RPC from lisp. Thank you, S-XML-RPC.
(require :s-xml-rpc) (use-package :s-xml-rpc)
Then I wrote a function to fetch posts by ID.
(defparameter *username* "") (defparameter *password* "") (defun get-post (id) (xml-rpc-call (encode-xml-rpc-call "metaWeblog.getPost" id *username* *password*) :host "lemonodor.com" :url "http://lemonodor.com/mt/mt-xmlrpc.cgi"))
Posts are represented by s-xml-rpc-structs containing association lists of properties.
Fixing a post is a matter of checking its mt_convert_breaks property and setting it to false if it's true. I return nil from fix-post if the post is OK, otherwise it returns the new post structure.
(defparameter *convert-key* :|mt_convert_breaks|) (defun fix-post (post) (if (equal (get-xml-rpc-struct-member post *convert-key*) "1") (set-convert-breaks post NIL) nil))
Changing the value of a property is done by creating a new alist containing the new, desired value.
(defun set-convert-breaks (post breaks-p) (let ((new-alist (mapcar #'(lambda (pair) (if (eq (car pair) *convert-key*) (cons (car pair) (if breaks-p "1" "0")) pair))) (xml-rpc-struct-alist post))) (s-xml-rpc::make-xml-rpc-struct :alist new-alist)))
Once we've switched the flag, we need to save the post back to the weblog. I pass NIL for the “publish” argument, which tells MT not to rebuild all the static pages that contain the post (rebuilding the whole site once I've finished is much more efficient).
(defun save-post (post) (let ((id (get-xml-rpc-struct-member post :|postid|))) (assert (and (stringp id) (> (length id) 0))) (xml-rpc-call (encode-xml-rpc-call "metaWeblog.editPost" id *username* *password* post NIL) :host "lemonodor.com" :url "http://lemonodor.com/mt/mt-xmlrpc.cgi")))
Now it's time to loop over all the posts and fix them. I know that the highest post ID in my database is about 950-something, so I'll just loop over the first 1000 and stop when we get an exception or I see that we're past the block of badly formatted posts.
(defun fix-all-posts () (dotimes (i 1000) (let ((id (1+ i))) ;; post IDs are 1-based. (format T "~&~5S" id) (let ((post (get-post id))) (format T " ~40S" (get-xml-rpc-struct-member post :|title|)) (let ((new-post (fix-post post))) (when new-post (format T " ...fixing") (save-post new-post)))))))
And now it's time to run the code.
CL-USER> (fix-all-posts) 1 "3D Development" ...fixing 2 "The Tendency of Unresolved Bugs" ...fixing 3 "Inside Orbitz" 4 "On Hacker" 5 "Alex Moffat and Ehud Lamm" ...fixing ;; Etc.
And thus ends my dabblings in the MetaWeblog API from Lisp. It seems to have worked, but let me know if you see anything that looks like it might be wrong.
Posted by jjwiseman at October 14, 2004 03:22 AMWhy the mapcar in set-convert-breaks? Could you use assoc and copy-alist?
Like this?
(defun set-convert-breaks (post breaks-p)
(let ((new-alist (copy-alist (xml-rpc-struct-alist post))))
(setf (cdr (assoc new-alist *convert-key*))
(if breaks-p "1" "0"))
(s-xml-rpc::make-xml-rpc-struct :alist new-alist)))
Oh, I have a bunch of excuses. I could say it was three in the morning when I wrote the code. I could say that you should have seen it before I realized how ugly it was, and cleaned it up specifically in the hope that I wouldn't be embarrassed by commenters. But really, I just didn't think of it.
Posted by: John on October 14, 2004 10:50 AM