You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
isso/wynaut/imprt.py

185 lines
5.7 KiB

# -*- encoding: utf-8 -*-
from __future__ import division
import sys
import os
import time
import tempfile
import textwrap
from time import mktime, strptime
from xml.etree import ElementTree
from collections import defaultdict
try:
from urlparse import urlparse
except ImportError:
from urllib.parse import urlparse
try:
input = raw_input
except NameError:
pass
from werkzeug.utils import cached_property
from isso.db import SQLite3
from isso.core import Config
from wynaut import get_parser
class Import(object):
def __init__(self):
self.last = 0
try:
self.cols = int(os.popen('stty size', 'r').read().split()[1])
except IndexError:
self.cols = 25
def progress(self, current, max, msg):
if time.time() - self.last < 0.1:
return
sys.stdout.write("\r{0}".format(" "*self.cols))
sys.stdout.write("\r[{0:.3}%] {1:.{2}}".format(
current/max*100, msg.strip(), self.cols - 9))
sys.stdout.flush()
self.last = time.time()
def done(self, msg):
sys.stdout.write("\r{0}".format(" "*self.cols))
sys.stdout.write("\r[100%] {0}\n".format(msg.strip()))
sys.stdout.flush()
class Disqus(Import):
ns = '{http://disqus.com}'
internals = '{http://disqus.com/disqus-internals}'
def __init__(self, xmlfile):
super(Disqus, self).__init__()
self.tree = ElementTree.parse(xmlfile)
self._threads = set([])
self._posts = set([])
@cached_property
def threads(self):
return [thr for thr in self.tree.findall("{0}thread".format(Disqus.ns))
if thr.find("{0}id".format(Disqus.ns)).text is not None]
@cached_property
def posts(self):
return self.tree.findall("{0}post".format(Disqus.ns))
def migrate(self, db):
# map thread id to list of posts
rv = defaultdict(list)
for post in self.posts:
item = {
'dsq:id': post.attrib.get(Disqus.internals + 'id'),
'text': post.find('%smessage' % Disqus.ns).text,
'author': post.find('{0}author/{0}name'.format(Disqus.ns)).text,
'email': post.find('{0}author/{0}email'.format(Disqus.ns)).text,
'created': mktime(strptime(
post.find('%screatedAt' % Disqus.ns).text, '%Y-%m-%dT%H:%M:%SZ')),
'remote_addr': '127.0.0.0',
'mode': 1 if post.find("%sisDeleted" % Disqus.ns).text == "false" else 4
}
if post.find(Disqus.ns + 'parent') is not None:
item['dsq:parent'] = post.find(Disqus.ns + 'parent').attrib.get(Disqus.internals + 'id')
rv[post.find('%sthread' % Disqus.ns).attrib.get(Disqus.internals + 'id')].append(item)
for i, thread in enumerate(self.threads):
self.progress(i, len(self.threads), thread.find('{0}id'.format(Disqus.ns)).text)
# skip (possibly?) duplicate, but empty thread elements
if thread.find('%sid' % Disqus.ns).text is None:
continue
id = thread.attrib.get(Disqus.internals + 'id')
if id in rv:
self._threads.add(id)
self._insert(db, thread, rv[id])
# in case a comment has been deleted (and no further childs)
db.comments._remove_stale()
self.done("{0} threads, {1} comments".format(len(self._threads), len(self._posts)))
orphans = set(map(lambda e: e.attrib.get(Disqus.internals + "id"), self.posts)) - self._posts
if orphans:
print("Found %i orphans:" % len(orphans))
for post in self.posts:
if post.attrib.get(Disqus.internals + "id") not in orphans:
continue
print(" * %s by %s <%s>" % (post.attrib.get(Disqus.internals + "id"),
post.find("{0}author/{0}name".format(Disqus.ns)).text,
post.find("{0}author/{0}email".format(Disqus.ns)).text))
print(textwrap.fill(post.find("%smessage" % Disqus.ns).text,
initial_indent=" ", subsequent_indent=" "))
def _insert(self, db, thread, posts):
path = urlparse(thread.find('%slink' % Disqus.ns).text).path
remap = dict()
if path not in db.threads:
db.threads.new(path, thread.find('%stitle' % Disqus.ns).text.strip())
for item in sorted(posts, key=lambda k: k['created']):
dsq_id = item.pop('dsq:id')
item['parent'] = remap.get(item.pop('dsq:parent', None))
rv = db.comments.add(path, item)
remap[dsq_id] = rv["id"]
self._posts.update(set(remap.keys()))
def main():
parser = get_parser("import Disqus XML export")
parser.add_argument("dump", metavar="FILE")
parser.add_argument("-y", "--yes", dest="yes", action="store_true",
help="always confirm actions")
parser.add_argument("-n", "--dry-run", dest="dryrun", action="store_true",
help="perform a trial run with no changes made")
parser.add_argument("-f", "--from", dest="type", choices=["disqus", "csv"])
args = parser.parse_args()
conf = Config.load(args.conf)
xxx = tempfile.NamedTemporaryFile()
dbpath = conf.get("general", "dbpath") if not args.dryrun else xxx.name
if args.type == "disqus":
importer = Disqus(args.dump)
elif args.type == "csv":
pass
db = SQLite3(dbpath, conf)
if db.execute("SELECT * FROM comments").fetchone():
if not args.yes and input("Isso DB is not empty! Continue? [y/N]: ") not in ("y", "Y"):
raise SystemExit("Abort.")
importer.migrate(db)