#!/usr/bin/python2.6 -tt
# -*- coding: utf-8 -*-

#    Copyright (C) 2010  Matthias Urlichs <matthias@urlichs.de>
#
#    This program may be distributed under the terms of the GNU GPLv3.
#
## This file is formatted with tabs.
## Do NOT introduce leading spaces.

from __future__ import division, print_function, absolute_import

from sqlmix import Db,NoData

#from sqlfuse import SqlFuse
from sqlfuse.options import options
from sqlfuse.range import Range
from sqlfuse.fs import build_path
from datetime import datetime,timedelta
from stat import S_ISBLK,S_ISCHR,S_ISDIR,S_ISFIFO,S_ISLNK,S_ISREG,S_ISSOCK
import errno
import sys
import os
from sqlfuse import DBVERSION
from math import sqrt
import posix
import time

typ = {
	"n":"new",
	"c":"write",
	"f":"finish",
	"d":"delete",
	"i":"init",
	"s":"sync",
	"t":"trim",
	}
filetyp = {
	"s":"socket",
	"f":"file",
	"d":"dir",
	"b":"block",
	"c":"char",
	"p":"fifo",
	"l":"link",
	}
def report(seq):
	for k in seq:
		print(" ".join((unicode(x) for x in k)))
	print("")

def format_time(s,ns):
	if not s:
		v = "epoch"
	else:
		v = datetime.utcfromtimestamp(s)
		if ns:
			v += timedelta(0,0,ns//1000)
			v = v.strftime("%F %T.%f")
		else:
			v = v.strftime("%F %T")
	return v

class Info(object):
	def __init__(self,db):
		def cb(n,v):
			setattr(self,n,v)
		db.DoSelect("select name,value from `info`", _callback=cb)


def node_info(db,node_id):
	yield ("node","ID",node_id)
	d = db.DoFn("select * from node where id=${id}",id=node_id, _dict=True)
	yield("cache-only", 'no' if d['autocopy'] else 'yes')
	for f in "root name files".split():
		yield (f,d[f])
	yield "secret",repr(d['secret'][0:7])+u'…'
	if d['port']:
		if d['address']:
			yield("address",d['address'])
		yield("tcpport",d['port'])
	for p,i,n in db.DoSelect("select updater.id,node.id,node.name from updater,node where updater.src=${node} and updater.dest=node.id", node=node_id, _empty=1):
		yield("updater","src",p,u"→",i,n)
	for p,i,n in db.DoSelect("select updater.id,node.id,node.name from updater,node where updater.dest=${node} and updater.src = node.id", node=node_id, _empty=1):
		yield("updater","dest",p,u"←",i,n)
	for p, in db.DoSelect("select id from todo where node=${node}", node=node_id, _empty=1):
		yield("todo",p)
	for ch,ta,tb,t in db.DoSelect("select count(*),min(id),max(id),typ from event where node=${node} group by typ", node=node_id, _empty=True):
		if ta != tb:
			yield ("change",typ[t],"first",ta)
			yield ("change",typ[t],"last",tb)
			yield ("nchanges",typ[t],ch)
		else:
			yield ("change",typ[t],"at",ta)
	try:
		ch,ta,tb = db.DoFn("select count(*),min(id),max(id) from event where node=${node}", node=node_id)
	except NoData:
		pass
	else:
		if ta != tb:
			yield ("change","*","first",ta)
			yield ("change","*","last",tb)
			yield ("nchanges","*",ch)
		else:
			yield ("change","*","at",ta)

def root_info(db,root_id):
	yield ("root","ID",root_id)
	d = db.DoFn("select * from root where id=${id}",id=root_id, _dict=True)
	for f in "name inode nfiles ndirs nblocks".split():
		yield (f,d[f])
	n, = db.DoFn("select count(*) from node where root=${root}",root=root_id)
	yield ("nnodes",n)

def fmode(mode):
	if S_ISREG(mode):
		return "file"
	if S_ISDIR(mode):
		return "dir"
	if S_ISBLK(mode):
		return "block"
	if S_ISCHR(mode):
		return "char"
	if S_ISFIFO(mode):
		return "fifo"
	if S_ISLNK(mode):
		return "link"
	if S_ISSOCK(mode):
		return "socket"
	return '?'

def inode_info(db,inum):
	yield ("inode","ID",inum)
	d = db.DoFn("select * from inode where id=${id}",id=inum, _dict=True)
	for f in "uid gid mtime atime ctime size event seq typ".split():
		v = d[f]
		if f == "typ":
			v = filetyp[v]
		elif f.endswith("time"):
			v = format_time(v,d[f+"_ns"])
		yield (f,v)
	yield ("mode","%o"%d['mode'],fmode(d['mode']))
	n, = db.DoFn("select count(*) from tree where inode=${inode}",inode=inum)
	yield ("nlinks",n)
	for k,v in db.DoSelect("select xattr_name.name, xattr.value from xattr,xattr_name where xattr.inode=${inode} and xattr.name=xattr_name.id", inode=inum, _empty=1):
		yield ("attr",k,repr(v))
	for i,c,n in db.DoSelect("select cache.id,cache.cached,node.name from cache,node where node.id=cache.node and cache.inode=${inode}", inode=inum, _empty=1):
		yield ("cache",n,str(Range(c)))
	yield("store",build_path(".",inum))
	
	try:
		def get_path(inum,prev=""):
			try:
				inum_p,name = db.DoFn("select parent,name from tree where inode=${inode}",inode=inum)
			except NoData:
				return prev,inum
			else:
				return get_path(inum_p,name+"/"+prev)


		for p,n in db.DoSelect("select parent,name from tree where inode=${inode}",inode=inum):
			path,root_inum = get_path(p,n)
			root,=db.DoFn("select name from root where inode=${root}",root=root_inum)
			yield("path",root,repr(path.encode("utf-8")))
	except NoData:
		try:
			root,=db.DoFn("select name from root where inode=${root}",root=inum)
		except NoData:
			yield("deleted",)
		else:
			yield("path",root,"/")
	for i,n in db.DoSelect("select inode,name from tree where parent=${inode}", inode=inum, _empty=1):
		yield("entry",i,repr(n.encode("utf-8")))

	for i,n in db.DoSelect("select parent,name from tree where inode=${inode}", inode=inum, _empty=1):
		yield("parent",i,repr(n.encode("utf-8")))

def event_info(db,event_id):
	yield ("event","ID",event_id)
	d = db.DoFn("select * from event where id=${id}",id=event_id, _dict=True)
	yield("typ",typ[d["typ"]])
	for f in "inode node".split():
		yield(f,d[f])
	r = d["range"]
	if r:
		r = Range(r)
		yield("range",str(r))

	try: nid, = db.DoFn("select max(id) from event where inode=${inode} and id<${id}", inode=d["inode"],id=event_id)
	except NoData: pass
	else:
		if nid: yield ("prev","inode","event",nid)

	try: nid, = db.DoFn("select min(id) from event where inode=${inode} and id>${id}", inode=d["inode"],id=event_id)
	except NoData: pass
	else:
		if nid: yield ("next","inode","event",nid)

	try: nid, = db.DoFn("select max(id) from event where node=${node} and id<${id}", node=d["node"],id=event_id)
	except NoData: pass
	else:
		if nid: yield ("prev","node","event",nid)

	try: nid, = db.DoFn("select min(id) from event where node=${node} and id>${id}", node=d["node"],id=event_id)
	except NoData: pass
	else:
		if nid: yield ("next","node","event",nid)

def updater_info(db,pnr):
	yield("updater","id",pnr)
	d = db.DoFn("select * from updater where id=${pnr}",pnr=pnr, _dict=True)
	for f in "src dest".split():
		n, = db.DoFn("select name from node where id=${node}",node=d[f])
		yield(f,n)
	yield("distance",d['distance'])
	yield("method",d['method'])
	yield("args",d['args'])
	

def progress_info(db,pnr):
	yield("progress","id",pnr)
	d = db.DoFn("select * from progress where id=${pnr}",pnr=pnr, _dict=True)
	for f in "src dest".split():
		v = d[f]
		if v is None: continue
		n, = db.DoFn("select name from node where id=${node}",node=v)
		yield(f,n)
	yield('inode',d['inode'])
	r = d["range"]
	if r is None:
		yield("range",'all')
	elif r == "":
		yield("range",'done')
	else:
		r = Range(r)
		yield("range",str(r))


def sync_cache(node):
	"""\
		For all files where the current node doesn't have a cache entry and some other node only has an
		empty cache record, add the full file locally, plus a TODO record for the other nodes.
		"""
	nodes = []
	root, = db.DoFn("select root from node where id=${node}", node=node)
	try:
		for n, in db.DoSelect("select id from node where root=${root} and id != ${node}", root=root,node=node):
			raise RuntimeError("XXX TODO")
	except NoData:
		print("Single node")
		tr = db.DoSelect("select inode from cache,inode ")
		return
	for inode, in db.DoSelect("select cache.inode from cache"):
		print(inode)

class zerosize(object):
	st_size = 0
zerosize = zerosize()

def check_node(db, node, verbose):
	"""\
		Check files in this node for consistency:

		* on-disk size at least what's in the database cache
		* cache size not larger than file size
		* if on-disk size is larger than what's in the database: truncate
		"""
	def get_path(inum,prev=""):
		try:
			inum_p,name = db.DoFn("select parent,name from tree where inode=${inode}",inode=inum)
		except NoData:
			return prev
		else:
			return get_path(inum_p,name+"/"+prev)

	node,root,files = db.DoFn("select id,root,files from node where name=${node}", node=node)
	max_inum, = db.DoFn("select max(id) from inode where typ='f' and root=${root}", root=root) or 0
	lim = 0
	step = int(sqrt(max_inum))
	todo = []
	cutoff=int(time.time())-24*60*60

	def fixsize(inum,sz):
		db.Do("update inode set size=${size} where id=${inode}", inode=inum,size=sz)

	if step<100: step=100
	while lim < max_inum:
		lastlim=lim
		if verbose:
			print(" Checking since",lim,"        ", end="\r")
			sys.stdout.flush()
		for i,sz,c, par,nam in db.DoSelect("select inode.id,inode.size,cache.cached,tree.parent,tree.name from inode left join tree on tree.inode=inode.id left join cache on cache.inode=inode.id and cache.node=${node} where inode.typ='f' and inode.root=${root} and inode.id > ${min} and inode.ctime<${tm} and inode.mtime<${tm} order by inode.id limit ${step}", root=root,min=lim,step=step,node=node,tm=cutoff, _store=True,_empty=True):
			if lim == i:
				continue
			lim = i
			if par is None:
				# deleted file
				continue

			fn = build_path(files,i)
			try:
				s = os.stat(fn)
			except EnvironmentError as e:
				if e.errno == errno.ENOENT:
					s = zerosize
				else:
					raise

			if c: c = Range(c)
			if sz < s.st_size:
				if c == Range(((0,s.st_size),)):
					print("",i,"FIXSIZE",sz,s.st_size,fn, end="\r")
					todo.append((fixsize,i,s.st_size))
				else:
					print(i,"LONG",sz,s.st_size,c,fn,get_path(par,nam))
				continue
				#if sz is too small and c is filesize: fix sz.
				#Otherwise, truncate the file.
				#fd = posix.open(fn,O_RDWR)
				#posix.ftruncate(fd,sz)
				#posix.close(fd)

			r = Range(((0,sz),))
			if c:
				lc = c.filter()
				if lc and s.st_size < lc.end:
					print(i,"SHORT",sz,s.st_size,fn,get_path(par,nam))
					continue
				r -= lc

			if r:
				# check remote data
				rc=[]

				for n,nc in db.DoSelect("select node.name,cache.cached from cache,node where node.id=cache.node and cache.inode=${inode}",node=node,inode=i, _empty=True):
					nc = Range(nc).filter()
					if c:
						rc.append(n+":"+unicode(nc))
						r -= nc
				if r:
					print(i,"MISSING",sz,r, " ".join(rc),fn,get_path(par,nam))
		db.commit()

		if lastlim==lim: break
		# this happens when the last few inodes are newly modified

	for a in todo:
		a[0](*(a[1:]))
	db.commit()


def mirror_tree(db, node,inode,dest, verbose=False,exporting=False):
	"""\
		Create a 'real' file system tree which links to the actual data.

		Ownership and permission on directories is set (but read-only).
		Files are all root-owned and not writeable.
		"""
	node,root,files = db.DoFn("select id,root,files from node where name=${node}", node=node)

	def _mirror_tree(inode,dest):
		d = db.DoFn("select * from inode where id=${id}",id=inode, _dict=True)
		if d['typ'] == 'f':
			skip = False
			if d['size'] > 0:
				try:
					c, = db.DoFn("select cached from cache where inode=${inode} and node=${node}",inode=inode,node=node)
				except NoData:
					c = "<missing>"
					cd = "<missing>"
					ci = False
				else:
					cd = Range(c)
					ci = cd.filter().equals(0,d['size'])
				if not ci:
					print("Incomplete: %s %s: %s %s, want 0-%s" % (inode,dest, c,cd,d['size']-1))
					skip = True

			fp = build_path(files,inode)
			if os.path.exists(dest):
				if not skip and not os.path.samefile(fp,dest):
					print("Warning: '%s' does not refer to '%s'" % (dest,fp))
			else:
				if skip:
					return
				if verbose:
					print("Link %s to %s"%(fp,dest))
				else:
					print(inode,fp, end="                \r")
				try:
					os.link(fp,dest)
				except EnvironmentError as e:
					if e.errno == errno.ENOENT:
						print("Warning: %s '%s' does not exist ('%s')" % (inode, fp,dest))
					else:
						raise
		elif d['typ'] == 'd':
			if not os.path.isdir(dest):
				os.mkdir(dest)
			if not exporting:
				os.chown(dest, d['uid'],d['gid'])
				os.chmod(dest, 0o555 & d['mode'])
			for i,n in db.DoSelect("select inode,name from tree where parent=${inode}",inode=inode, _empty=1,_store=1):
				_mirror_tree(i,os.path.join(dest,n))
		elif exporting:
			pass # TODO: devince nodes and symlinks
		if exporting:
			os.chown(dest, d['uid'],d['gid'])
			os.utime(dest, (d['atime'],d['mtime'])
			pass # TODO: extended attributes

	_mirror_tree(inode,dest)
	
	
	

def main():
	"""Main program."""
	
	opt=options("mode")
	if opt.profile:
		try:
			import cProfile as profile
		except Exception:
			import profile
		profile.run("_main()",opt.profile)
	else:
		_main(opt)

def _main(opt):
	db = Db(dbtye=opt.dbtype,host=opt.host,port=opt.port,database=opt.database,username=opt.username,password=opt.password)

	info=Info(db)

	if opt.mode == "info":
		node_id,root_id = db.DoFn("select id,root from node where name=${name}", name=opt.node)
		report(node_info(db,node_id))
		report(root_info(db,root_id))

		#inum, = db.DoFn("select inode from root where id=${id}", id=root_id)
		#report(inode_info(db,inum))
	elif opt.mode == "list":
		if opt.mode2 == "node":
			if opt.name: # single node
				try:
					try:
						nid = int(opt.name)
					except ValueError:
						nid,=db.DoFn("select id from node where name=${name}",name=opt.name)
					else:
						nid,=db.DoFn("select id from node where id=${id}",id=nid)
				except NoData:
					print("There is no node named '%s'"%(opt.name,), file=sys.stderr)
				else:
					report(node_info(db,nid))
			else: # list
				if opt.root:
					try:
						root, = db.DoFn("select id from root where name=${root}", root=opt.root)
					except NoData:
						print("No node named '%s' exists" % (opt.root,),file=sys.stderr)
						sys.exit(1)
					try:
						for name, in db.DoSelect("select node.name from node,root where root.name=${root} and node.root=root.id order by name",root=opt.root):
							print(name)
					except NoData:
						print("This root does not have any nodes.",file=sys.stderr)
						
				else:
					for nid,nodename,rootname in db.DoSelect("select node.id,node.name,root.name from node,root where node.root=root.id order by root.name,node.name"):
						print(nid,nodename,rootname)
		elif opt.mode2 == "progress":
			if opt.id: # single node
				try:
					id,=db.DoFn("select id from progress where id=${id}",id=opt.id)
				except NoData:
					raise # TODO reasonable error message
				else:
					report(progress_info(db,id))
			else: # list
				f=[]; g={}
				for k in "src dest inode".split():
					v = getattr(opt,k,None)
					if v:
						if k in ("src","dest"):
							v,=db.DoFn("select id from node where name=${name}", name=v)
						f.append("%s=${%s}" % (k,k))
						g[k]=v
				if f:
					f=" where "+" and ".join(f)
				else:
					f=""
				try:
					for id, in db.DoSelect("select id from progress"+f,**g):
						report(progress_info(db,id))
				except NoData:
					print("No progress nodes found.", file=sys.stderr)
		elif opt.mode2 == "updater":
			if opt.id: # single node
				try:
					id,=db.DoFn("select id from updater where id=${id}",id=opt.id)
				except NoData:
					raise # TODO reasonable error message
				else:
					report(updater_info(db,id))
			else: # list
				f=[]; g={}
				for k in "src dest method".split():
					v = getattr(opt,k,None)
					if v:
						if k in ("src","dest"):
							v,=db.DoFn("select id from node where name=${name}", name=v)
						f.append("%s=${%s}" % (k,k))
						g[k]=v
				if f:
					f=" where "+" and ".join(f)
				else:
					f=""
				try:
					for id, in db.DoSelect("select id from updater"+f,**g):
						report(updater_info(db,id))
				except NoData:
					print("No progress nodes found.", file=sys.stderr)
		elif opt.mode2 == "root":
			if opt.name:
				try:
					try:
						root_id = int(opt.name)
					except ValueError:
						root_id,=db.DoFn("select id from root where name=${name}",name=opt.name)
					else:
						root_id,=db.DoFn("select id from root where id=${id}",id=root_id)
				except NoData:
					print("There is no root named '%s'"%(opt.name,), file=sys.stderr)
					sys.exit(1)
				report(root_info(db,root_id))
			else:
				for rid,name in db.DoSelect("select id,name from root order by name"):
					print(rid,name)
		elif opt.mode2 == "inode":
			report(inode_info(db,opt.inum))
		elif opt.mode2 == "event":
			report(event_info(db,opt.event_id))
		else:
			raise NotImplementedError("I don't know how to do that.")

	elif opt.mode == "add":
		if opt.mode2 == "root":
			inode = db.Do("insert into inode (root) values(1)")
			root = db.Do("insert into root(name,inode) values(${name},${inode})", name=opt.name,inode=inode)
			db.Do("update inode set root=${root} where id=${inode}", inode=inode, root=root)
			db.commit()
		elif opt.mode2 == "node":
			try:
				r,inum = db.DoFn("select id,inode from root where name=${root}",root=opt.root)
			except NoData:
				print("No root named '%s' exists"%(opt.root,),file=sys.stderr)
				sys.exit(1)
			try:
				db.DoFn("select id from node where name=${node}", node=opt.name)
			except NoData:
				pass
			else:
				print("A node named '%s' already exists",file=sys.stderr)
				sys.exit(1)
			if opt.access == "-": opt.access = None

			e, = db.DoFn("select max(id) from event")
			# temporary ID to satisfy consistency requirements while
			# inserting (which violates the SQL standard, but then
			# MySQL is what it is)

			n = db.Do("insert into node(name,root,files,secret,`password`,event) values(${node},${root},${files},${secret},${password},${event})", node=opt.name,root=r, files=opt.files, secret=open("/dev/urandom",'r').read(int(info.secretlen)), password=opt.access, event=e)
			e = db.Do("insert into event(node,inode,typ) values(${node},${inode},'i')", inode=inum,node=n)
			db.Do("update node set event=${event} where id=${node}", event=e,node=n)
			db.Do("insert into todo(node,inode,typ) (select ${node},id,'f' from inode where root=${root} and typ = 'f')", root=r, node=n, _empty=True)
			if opt.cache is not None:
				db.Do("update node set autocopy=${copy} where id=${node}", node=n, copy= not opt.cache)

			db.commit()
		elif opt.mode2 == "updater":
			try:
				sid, = db.DoFn("select id from node where name=${name}", name=opt.src)
			except NoData:
				print("No node named '%s' exists"%(opt.src,),file=sys.stderr)
				sys.exit(1)
			try:
				did, = db.DoFn("select id from node where name=${name}", name=opt.dest)
			except NoData:
				print("No node named '%s' exists"%(opt.dest,),file=sys.stderr)
				sys.exit(1)
			args = []
			if opt.method == 'native':
				args.append(opt.address)
				if opt.tcpport is not None:
					args.append(opt.tcpport)
			uid = db.Do("insert into updater(src,dest,distance,method,args) values(${src},${dest},${distance},${method},${args})", src=sid,dest=did,distance=opt.distance,method=opt.method,args=repr(args))

		else:
			raise NotImplementedError("I don't know how to do that.")

	elif opt.mode == "del":
		if opt.mode2 == "root":
			root,inode = db.DoFn("select id,inode from root where name=${root}", root=opt.name)
			c, = db.DoFn("select count(*) from node where root=${root}", root=root)
			if c:
				print("This root still has nodes and thus cannot be deleted.", file=sys.stderr)
				sys.exit(1)
			c, = db.DoFn("select count(*) from inode where root=${root}", root=root)
			if c > 1:
				print("This root still has files and thus cannot be deleted.", file=sys.stderr)
				sys.exit(1)
			if root == 1:
				print("This is the system root and thus cannot be deleted.", file=sys.stderr)
				print("You may, however, rename it.", file=sys.stderr)
				sys.exit(1)

			# This works around a MySQL bug where consistency is checked
			# immediately, not after a COMMIT as the SQL standard says
			# … which is also the main reason we need a system root in the
			# first place.
			db.Do("update inode set root=1 where id=${inode}",inode=inode)

			db.Do("delete from root where id=${root}",root=root)
			db.Do("delete from inode where id=${inode}",inode=inode)
		elif opt.mode2 == "node":
			node,root,rname = db.DoFn("select node.id,node.root,root.name from node,root where node.name=${node} and root.id=node.root", node=opt.name)
			db.Do("delete from node where id=${node}", node=node)
			c, = db.DoFn("select count(*) from node where root=${root}", root=root)
			if not c:
				print("Warning: root %d '%s' does not have any more nodes!" % (root,rname))
		elif opt.mode2 == "updater":
			db.Do("delete from updater where id=${id}", id=opt.id)
		else:
			raise NotImplementedError("I don't know how to do that.")
	elif opt.mode == "update":
		done = 0
		if opt.mode2 == "root":
			root, = db.DoFn("select id from root where name=${root}", root=opt.name)
			if opt.newname:
				db.Do("update root set name=${name} where id=${root}", root=root, name=opt.newname)
				done += 1
		elif opt.mode2 == "node":
			node, = db.DoFn("select id from node where name=${node}", node=opt.name)
			if opt.access is not None:
				if opt.access:
					db.Do("update node set `password`=${password} where id=${node}", node=node, password=opt.access, _empty=True)
				else:
					db.Do("update node set `password`=NULL where id=${node}", node=node, _empty=True)
				done += 1
			if opt.address is not None:
				if opt.address:
					db.Do("update node set address=${address} where id=${node}", node=node, address=opt.address, _empty=True)
				else:
					db.Do("update node set address=NULL where id=${node}", node=node, _empty=True)
				done += 1
			if opt.tcpport is not None:
				if opt.tcpport:
					db.Do("update node set port=${port} where id=${node}", node=node, port=opt.tcpport, _empty=True)
				else:
					db.Do("update node set port=NULL where id=${node}", node=node, _empty=True)
				done += 1
			if opt.newname:
				db.Do("update node set name=${name} where id=${node}", node=node, name=opt.newname, _empty=True)
				done += 1
			if opt.files:
				db.Do("update node set files=${files} where id=${node}", node=node, files=opt.files, _empty=True)
				done += 1
			if opt.secret:
				# _empty is not set, for obvious reasons
				db.Do("update node set secret=${secret} where id=${node}", node=node, secret=open("/dev/urandom",'r').read(int(info.secretlen)))
				done += 1
			if opt.cache is not None:
				db.Do("update node set autocopy=${copy} where id=${node}", node=node, copy= not opt.cache, _empty=True)
				done += 1
		elif opt.mode2 == "updater":
			if opt.distance:
				db.Do("update updater set distance=${distance} where id=${id}", id=opt.id, distance=opt.distance, _empty=True)
				done += 1
		else:
			raise NotImplementedError("I don't know how to do that.")
		if not done:
			print("Nothing to do.")
	elif opt.mode == "sync":
		done = 0
		if opt.mode2 == "cache":
			node, = db.DoFn("select id from node where name=${node}", node=opt.name)
			sync_cache(node)
			done += 1
		elif opt.mode2 == "fs":
			mirror_tree(db, opt.node,opt.inode,opt.dest, opt.verbose,opt.export)
			done += 1
		elif opt.mode2 == "fsck":
			check_node(db, opt.node, opt.verbose)
			done += 1
		else:
			raise NotImplementedError("I don't know how to do that.")
		if not done:
			print("Nothing to do.")
	else:
		raise NotImplementedError("I don't yet know how to do that.")
	db.commit()

if __name__ == '__main__':
	main()
