All pastes #1358273 Raw Edit

dropbox-cli

public python v1 · immutable
#1358273 ·published 2009-03-11 16:33 UTC
rendered paste body
#!/usr/bin/python# Dropbox CLI# Bugs? Suggestions? Please, dont hesitate to mail me.# Filip Lundborg (filip@mkeyd.net)# This does many things wrong (global status etc), but I'll let it be here anyway if someone wants# to change anything.# A more working script must be my status.py. (https://dl.getdropbox.com/u/43645/status.py)import socketimport osimport reimport sysimport timeimport urllib2# TODO: #	) Fix get_file so that it's more easy to understand. And add more security (a lot more).#	) A function in the class that reads on iface_socket#	) Add support for registering with the client# Things I've found but dont know what they do/dont have any use for yet:# # tray_action_get_menu_options (with following argument is_active\ttrue)# is_out_of_date (argument... current version?)# get_dropbox_globals (known arguments: keys active version mail icon_state)# on_x_server (arguments: display)class Dropbox:	def __init__(self):		self.connected = False	def connect(self, cmd_socket="~/.dropbox/command_socket", iface_socket="~/.dropbox/iface_socket"):		"Connects to the Dropbox command_socket, returns True if it was successfull."		self.iface_sck=socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)		self.sck=socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)		try:			self.sck.connect(os.path.expanduser(cmd_socket)) # try to connect			self.iface_sck.connect(os.path.expanduser(iface_socket))		except:			self.connected = False			return False		else: # went smooth			self.connected = True			return True		def disconnect(self):		"Tries to disconnect, returns True/False."		self.sck.close()		self.connected=False		def send_and_fetch(self, msg):		"Sends a message to the command_socket, and returns the result."		if not self.connected:			return "notok","not connected"		# TODO: check the message for strange and forbidden(?) chars		self.sck.send(msg)		res=""		while (not res.endswith("done\n")):			tmp=self.sck.recv(512)			res+=tmp			if (tmp==""):				self.connected=False				return ("notok","disconnected")		return res	def get_status(self, file):		"""Fetches the status and context options for the specified file/folder.			Returns 				("unwatched",)        -> If it's unwatched				("notok",errormsg)    -> On error				If all went as planned, and it's a file:					("ok",(status, context options))				A folder:					("okf",(status, context options, folder tag))		"""		if not self.connected:			return ("notok","not connected")		res=self.send_and_fetch("icon_overlay_file_status\npath\t%s\ndone\n" % file)				res=res.split('\n')[:-1]		if (res[0] == "ok"):			# went ok			res=res[1].split('\t')			if (res[1] == "unwatched"):				return ("unwatched",)			else:				status=res[1] # should be "up to date" or "syncing" (at least that's the two I've seen so far)			 	# now we want the context options				# and we know for fact that the file exists and are watched				res=self.send_and_fetch("icon_overlay_context_options\npaths\t%s\ndone\n" % file)				res=res.split('\n')[:-1]				if (res[0] == "ok"):					res=res[1].split('\t')[1:]					options=[]					for option in res:						tmp=option.split("~")						options.append((tmp[-1],tmp[:-1]))					if (os.path.isdir(file)):						# it's a directory						res=self.send_and_fetch("get_folder_tag\npath\t%s\ndone\n" % file)						res=res.split("\n")[:-1]						if (res[0]=="ok"):							# went ok							res=res[1].split("\t")							return ("okf",status,options,res[1])						else: # not ok? eh?							return ("notok",res[1])					else:						# it's a regular file						return ("ok",status,options)				else:					# uhm, cant see what would go wrong here. but we need to check					return ("notok",res[1])		else:			# didnt go that well			return ("notok",res[1])		def action(self, file, action):		"""Sends an action to the Dropbox-daemon.		Options I've discovered so far		On files:			revisions, copypublic (only if it's in the Public-folder)		On folders:			share, browse, copygallery (only on the Gallery folder)		All these options opens up in your default browser (chosen by gnome-open)."""		if (not self.connected):			return ("notok","not connected")		# I dont really understand the iface_socket yet, seems to output a "nop\done\n" every now		# and then when it has nothing to do?...		# we need to listen to the iface_socket to get the information from the action-command		res=""; i=500		while (len(res)!=9 and i>0): # 9 is the magic number, it's the number of the length "nop\ndone\n"			res=self.iface_sck.recv(1024) # hopefully clear the socket of all incoming 'nop's. or we will loop forever :D:D			i-=1				self.sck.send("icon_overlay_context_action\nverb\t%s\npaths\t%s\ndone\n" % (action,file))		res=""		while (not res.endswith("done\n")):			res+=self.sck.recv(512)		res=res.split("\n")[:-1]		if (res[0] == "ok"):			# we got a OK, means there should (:S I guess) be a present for us in the iface_socket			cmd=""; i=5 	# i = number of nops we should accept until we just dont care any more								# they shouldnt be that many since we connected right before the action			tmpres=""			while (cmd==""):				if (i==0):					return ("kindaok")				i-=0				tmpres+=self.iface_sck.recv(512)				tmp=tmpres.split('\n')[:-1]								while tmp!=[]:					tmpcmd=tmp[0]					if (tmpcmd=="shell_touch" or tmpcmd=="copy_to_clipboard" or tmpcmd=="launch_url"):						# it's a command. now look if there's one following item						if (len(tmp)>1):							cmd=tmpcmd							stuff=tmp[1]							break						else: break					else:						tmp=tmp[1:]			return ("ok",(cmd,stuff))		else:			return ("notok",res[1])	def get_general_status(self):		"Returns the status for the whole Dropbox-folder."		if (not self.connected):			return ("notok","not connected")		res=self.get_status(os.path.expanduser("~/Dropbox/"))		# TODO: error check here		return ("ok",res[1])# ---- DAEMON THINGYdef d_install(db, args):	if db.connected==True:		print "The daemon is already installed and running."		# TODO: print version info of the daemon (should be able to get through db)		return		available=["x86","x86_64"]	if not args[0] in available:		print "Specified architecture are not supported.\nSupported:"		for arch in available:			print "  ",arch		exit(0)	try:		url=urllib2.urlopen("http://www.getdropbox.com/download?plat=lnx."+args[0])	except:		print "Couldn't fetch the daemon from the dropbox webpage. Try again later?"		exit(0)	home=os.path.expanduser("~")	filename=url.url.split('/')[-1]	os.system("rm -r %s/.dropbox-dist/" % home)	os.system("mkdir %s/.dropbox-dist/" % home)	file_out=open("%s/.dropbox-dist/%s" % (home,filename),"wb")	if ("content-length" in url.headers.keys()):		total_bytes=int(url.headers["content-length"])		org_shows=total_bytes/10 # when we should show output		total_bytes/=1024 # in kB instead		total_bytes=str(total_bytes)+" kB"	else: total_bytes="N/A kB"		print "Starting to download %s..." % filename	read_bytes=0	res="notempty"	shows=0	while (res!=""):		res=url.read(1024)		file_out.write(res)		read_bytes+=1024		shows-=1024		if (shows<0):			shows=org_shows			print "  ... %d kB of %s" % (read_bytes/1024,total_bytes)		file_out.close()	print "Done. Unpacking..."	os.system("tar xzf %s/.dropbox-dist/%s -C %s" % (home,filename,home))	d_start()def d_start():	print "Trying to start the daemon."	file = os.path.expanduser("~/.dropbox-dist/dropboxd")	if (os.path.isfile(file)):		os.system(file)		return True	else:		print "The daemon does not exist (try %s install)" % sys.argv[0]		return False# ---- CLI# TODO: make a argument that enables launch_url to open up the page in your webbrowserdef f_action(db, args):	# first off, we need to know if the file/folder can take such an action	file=os.path.expanduser(os.path.expandvars(args[1]))	status=db.get_status(file)	if status[0].startswith("ok"):		options=dict(status[2])		if (args[0] in options or args[0] == "copygallery"):			res=db.action(file,args[0])			if res=="kindaok"	: print "done, but didnt get anything in return. greedy droboxd :("			if res[0]=="ok"	: 				print "done, got:"				print "   %s - %s" % (res[1][0],res[1][1].split('\t')[1])			else					: print "error: %s" % res[1]		else: 			print "Unable to do \"%s\" on \"%s\".\nAvailable options on this file are:" % (args[0],file)			for option in options.keys(): print "  ",option,"-",options[option][-1]	else: print "error: %s" % status[1]def f_general_status(db):	res=db.get_general_status()	if res[0]=="ok":		print res[1]	else:		print "error: "+res[1]def f_file_status(db, args):	status=db.get_status(args[0])	if (status[0].startswith("ok")):		print "%s status: %s" % ("Folder" if (status[0]=="okf") else "File", status[1])		if status[0]=="okf": print "Folder is tagged as: %s" % status[3]		print "Available options for the %s:" % ("folder" if (status[0]=="okf") else "file")		options=dict(status[2])		for option in options:			print "   %s - %s" % (option,options[option][-1])	else:		print "error: %s" % status[1]def usage(trash=""): # TODO: the trash-thingy is ugly... fix.	print "Usage: %s <command> [options] ... <command> [options]" % sys.argv[0]	print "Available commands:"	for c in func.keys():		f=func[c]		str="   %s " % c		if f[0]==0: discr=f[1] # no arguments		else: # arguments			discr=f[2]			for arg in f[1]: str+="<%s> " % arg		print str+"- " + discr# command: (number of arguments, (if numargs > 0 : [args],) explanation, (function to call,[args]))func={"copypublic"	: (1,["file"],"Copies the url to the clipboard (? I guess)",(f_action,["copypublic"])), \		"copygallery"	: (0,"Copies the gallery url to the clipboard.",(f_action,["copygallery","~/Dropbox/Photos"])), \		"revisions"		: (1,["file"],"Opens the revisions page in your browser for the specified file.",(f_action,["revisions"])), \		"share"			: (1,["file"],"Open the browser and shows share-information.",(f_action,["share"])), \		"browse"			: (1,["folder"],"Browse the specified folder in your browser.",(f_action,["browse"])), \		"status"			: (0,"Get overall status for the daemon.",(f_general_status,[])), \		"file"			: (1,["file"],"Get information about a file/folder.",(f_file_status,[])), \		"folder"			: (1,["folder"],"Get information about a file/folder.",(f_file_status,[])), \		"install"		: (1,["x86/x86_64"],"Tries to download the daemon and install it in your home directory.",(d_install,[])), \		"help"			: (0,"Gives this.",(usage,[])) \		} # TODO: add start/stop-functions for the daemon, and if possible, update (dont know how that works.)if __name__ == "__main__":	args = sys.argv[1:]	if (len(args) == 0):		usage()		exit(0)	install=False # TODO: do this "smarter"	todo=[] # list of all things we should do.	# but first we need to get them, and check so there's no invalid commands/options	while (args!=[]): # handle the commands		f=args[0].lower()		if f=="install": install=True		if (f in func):	# it's a command			tmp=func[f]			if (tmp[0]>(len(args)-1)):				print "Not enough options for \"%s\"." % f				str="Usage: %s %s " % (sys.argv[0],f)				for arg in tmp[1]:					str+="<%s> " % arg				print str				exit(0)			else:				fargs=args[1:tmp[0]+1]				args=args[tmp[0]+1:]				todo.append((tmp[-1][0],tmp[-1][1]+fargs))		else: # it's unknown			print "Unknown command \"%s\"\n" % f			usage()			exit(0)	db=Dropbox()	if (db.connect()==False):		print "error: couldn't connect to the daemond."		if (d_start()==False):			if (install==False):				exit(0)		else:			while (db.connect()==False):				print "Trying to reconnect..."				time.sleep(1)			print "It's started and we're connected."			time.sleep(5) # let it start etc	# and then we should do them all	for f,a in todo: # TODO: the functions should not need a tuple from func.		if a==[]: 	f(db)		else:			f(db,a)		db.disconnect()