P4Sync.py #14

  • //
  • guest/
  • sven_erik_knop/
  • P4Pythonlib/
  • scripts/
  • P4Sync.py
  • View
  • Commits
  • Open Download .zip Download (7 KB)
#!/usr/bin/env python3
#
# Copyright (C) 2012 Sven Erik Knop, Perforce Software Ltd
#
# Idea:
# Sync files from the default location (or, with -p, -u, -c, -H, -P options)
# Show progress using the Tkinter.tkk ProgressBar, P4.OutputHandler and P4.Progress
#

from __future__ import print_function

from argparse import ArgumentParser
import P4

try:
	import Tkinter as tk
	import ttk
	import Queue as queue
except ImportError:
	import tkinter as tk
	from tkinter import ttk
	import queue

import threading

class P4Command:
	def __init__(self):
		self.parser = ArgumentParser(
			description=self.description(),
			epilog="Copyright (C) 2012 Sven Erik Knop, Perforce Software Ltd"
		)
		self.parser.add_argument('-p', '--port', 		help="P4PORT")
		self.parser.add_argument('-c', '--client',		help="P4CLIENT")
		self.parser.add_argument('-u', '--user',		help="P4USER")
		self.parser.add_argument('-H', '--host',		help="P4HOST")
		self.parser.add_argument('-P', '--password',	help="P4PASSWD")
		self.parser.add_argument('-d', '--directory',	help="CWD")

		self.addArguments()

		self.myOptions = self.parser.parse_args()
		self.p4 = P4.P4()
		
		if self.myOptions.port:
			self.p4.port = self.myOptions.port
		if self.myOptions.client:
			self.p4.client = self.myOptions.client
		if self.myOptions.user:
			self.p4.user = self.myOptions.user
		if self.myOptions.host:
			self.p4.host = self.myOptions.host
		if self.myOptions.password:
			self.p4.password = self.myOptions.password
		if self.myOptions.directory:
			self.p4.cwd = self.myOptions.directory

	def description(self):
		return "P4Command"
	
	def addArguments(self):
		pass

class GuiPart(tk.Tk):
	def __init__(self, p4sync, queue):
		tk.Tk.__init__(self)
		
		self.columnconfigure(0, weight=1)
		self.rowconfigure(0, weight=1)
		
		self.title("P4 Sync")
		mainframe = ttk.Frame(self, padding="3 3 12 12 ")
		mainframe.grid(column=0, row=0, sticky="NWSE")
		mainframe.columnconfigure(0, weight=1) # resize progress bar horizontally
		mainframe.rowconfigure(0, weight=0)    # don't move the progress bar up or down
		mainframe.columnconfigure(1, weight=0) # and leave the label and button where they are 
		mainframe.rowconfigure(1, weight=1)    # resize the output listbox
		self.queue = queue
		self.p4sync = p4sync
		
		self.progress = ttk.Progressbar(mainframe, orient="horizontal", length=300, mode="determinate")
		self.progress.grid(column=0, row=0, sticky="EW")
		
		self.progress["value"] = 0
		self.progress["maximum"] = 100

		self.percent = tk.StringVar()
		self.percent.set("0.0 %")
		ttk.Label(mainframe, textvariable=self.percent).grid(column=1, row=0)
		
		frame = ttk.Frame(mainframe)
		frame.grid(column=0, row=1,sticky="NSEW")

		frame.columnconfigure(0, weight=1)
		frame.rowconfigure(0, weight=1)
		
		self.yscrollbar= ttk.Scrollbar(frame, orient=tk.VERTICAL)
		self.yscrollbar.grid(column=1, row=0, sticky="NS")
		
		self.xscrollbar = ttk.Scrollbar(frame, orient=tk.HORIZONTAL)
		self.xscrollbar.grid(column=0, row=1, sticky="EW")
		
		self.listbox = tk.Listbox(frame, height=10,width=40, 
								yscrollcommand=self.yscrollbar.set,
								xscrollcommand=self.xscrollbar.set)
		self.listbox.grid(column=0, row=0, sticky="NSEW" )
		
		self.yscrollbar["command"] = self.listbox.yview
		self.xscrollbar["command"] = self.listbox.xview
		

		self.button = ttk.Button(mainframe, text="Sync", command=p4sync.start)
		self.button.grid(column=1,row=1,sticky=tk.S)
		
		for child in mainframe.winfo_children(): child.grid_configure(padx=5, pady=5)

	def periodicCall(self):
		self.processIncoming()
			
		self.after(100, self.periodicCall)
	
	class StartRefresh:
		def __init__(self):
			pass
		def apply(self, gui):
			gui.button.configure(text="Cancel")
			gui.button.configure(command=gui.p4sync.cancel)
	
	class FinishedRefresh:
		def __init__(self):
			pass
		def apply(self, gui):
			gui.button.configure(text="Quit")
			gui.button.configure(command=gui.p4sync.quit)
	
	class DescriptionRefresh:
		def __init__(self, description):
			self.description = description
		def apply(self, gui):
			gui.title(self.description)
			gui.progress["value"] = 0
	
	class UpdateRefresh:
		def __init__(self, value):
			self.value = value
		def apply(self, gui):
			gui.progress["value"] = self.value
			gui.percent.set("%.1f %%" % self.value)
	
	class DepotFileRefresh:
		def __init__(self, filename):
			self.filename = filename
		def apply(self, gui):
			gui.listbox.insert(tk.END, self.filename + "\n")
			gui.listbox.yview(tk.END)

	
	def processIncoming(self):
		"""Read from queue, process results"""
		
		while self.queue.qsize():
			try:
				refresh = self.queue.get(0)
				refresh.apply(self)
			except queue.Empty:
				pass


class P4Sync(P4Command):
	def __init__(self):
		P4Command.__init__(self)
		
		self.queue = queue.Queue()
		self.gui = GuiPart(self, self.queue)
		
	def addArguments(self):
		self.parser.add_argument('-n', '--preview', dest='syncOptions', action='append_const', const='-n', help="Preview only, no syncing")
		self.parser.add_argument('--force', dest='syncOptions', action='append_const', const='-f', help="Force the update regardless of previous syncs")
		self.parser.add_argument('--print', dest='syncOptions', action='append_const', const='-p', help="Update local workspace without updating the database")
		self.parser.add_argument('--keep', dest='syncOptions', action='append_const', const='-k',  help="Update database without updating the local workspace")

		self.parser.add_argument('filepaths', type=str, nargs='*', help="[file[revRange] ...]")

	def description(self):
		return "P4Sync"
	
	class Callback(P4.Progress, P4.OutputHandler):
		def __init__(self, queue):
			self.queue = queue
			self.totalFileCount = 0
			self.totalFileSize = 0		
			self.response=P4.OutputHandler.HANDLED
		
		def outputStat(self, stat):
			if 'totalFileCount' in stat:
				self.totalFileCount = int(stat['totalFileCount'])
			if 'totalFileSize' in stat:
				self.totalFileSize = int(stat['totalFileSize'])
			if 'depotFile' in stat:
				refresh = GuiPart.DepotFileRefresh(stat['depotFile'] + "#" + stat['rev'])
				self.queue.put(refresh)
			return self.response
		
		def outputMessage(self, message):
			print("Error: ", message)
			return P4.OutputHandler.CANCEL
		
		def init(self, type):
			self.type = type
	
		def setDescription(self, description, unit):
			refresh = GuiPart.DescriptionRefresh(description)
			
			self.queue.put(refresh)
			
			# print("TotalFileCount: ", self.totalFileCount)
			# print("TotalFileSize: ", self.totalFileSize)
			
			
		def setTotal(self, total):
			pass
			# print("Total: %s" % total)
	
		def update(self, position):
			self.position = position
			
			# print("Position: %s" % position)

			refresh = GuiPart.UpdateRefresh(100 * int(position) / self.totalFileCount)
			
			self.queue.put( refresh )
	
		def done(self, fail):
			self.fail = fail
			# print("Done")

	def start(self):
		self.thread = threading.Thread(target=self.runInThread)
		self.thread.start()
		
		self.queue.put(GuiPart.StartRefresh())
		
		self.gui.periodicCall()
	
	def cancel(self):
		self.callback.response=P4.OutputHandler.CANCEL
	
	
	def quit(self):
		import sys
		sys.exit(0)
	
	def runInThread(self):
		with self.p4.connect():
			args = ['-q']
			if self.myOptions.syncOptions: # is None if no options specified
				args += self.myOptions.syncOptions
			args += self.myOptions.filepaths

			self.callback = P4Sync.Callback(self.queue)			
			result = self.p4.run_sync(args, progress=self.callback, handler=self.callback, exception_level=0)
		self.queue.put(GuiPart.FinishedRefresh())
	
if __name__ == '__main__':
	p4sync = P4Sync()
	p4sync.gui.mainloop()

	
# Change User Description Committed
#14 8207 Sven Erik Knop Refactoring, GuiPart is now the only user of Tk, P4Sync is completely independent of the GUI.

This means I can now easily plug in other graphics frameworks or do something else with P4Sync. It also means I can hopefully reuse the GuiPart for P4Submit.
#13 8206 Sven Erik Knop Don't need finish, just putting the event in the queue is enough.
#12 8199 Sven Erik Knop Ensure P4Sync.py actually works with Python 3.
Tested with Python 3.3

Oh, and changed the type to xtext. Thanks Tom.
#11 8198 Sven Erik Knop Cleanup of comments and removed extra superclasses not needed anymore.
#10 8196 Sven Erik Knop ...
and removed superfluous print ...
#9 8195 Sven Erik Knop Added the revision number to the output for completeness.
#8 8194 Sven Erik Knop Now resizing works as well.
I declare this version 1.0 :-)
#7 8193 Sven Erik Knop Works well enough, the yscrollbar does its job.
Stopped worrying about the xscrollbar for now.

Cancel and Quit work.
#6 8192 Sven Erik Knop Works halfway, but the scrollbar is not pretty.
#5 8191 Sven Erik Knop Now with output of files in a listbox.
#4 8190 Sven Erik Knop Replaced crude refresh with Visitor pattern for easier extension.
#3 8189 Sven Erik Knop Now with a percentage string that tells me how far in the sync I have got.
Laid out in a simple 2x2 grid.
#2 8186 Sven Erik Knop First working version of P4Sync

Requires Python2.7, P4Python 2012.2+ and Tkinter (+Tkk) to work.
Syntax (from --help):

usage: P4Sync.py [-h] [-p PORT] [-c CLIENT] [-u USER] [-H HOST] [-P PASSWORD]
                 [-d DIRECTORY] [-n] [--force] [--print] [--keep]
                 [filepaths [filepaths ...]]

P4Sync

positional arguments:
  filepaths             [file[revRange] ...]

optional arguments:
  -h, --help            show this help message and exit
  -p PORT, --port PORT  P4PORT
  -c CLIENT, --client CLIENT
                        P4CLIENT
  -u USER, --user USER  P4USER
  -H HOST, --host HOST  P4HOST
  -P PASSWORD, --password PASSWORD
                        P4PASSWD
  -d DIRECTORY, --directory DIRECTORY
                        CWD
  -n, --preview         Preview only, no syncing
  --force               Force the update regardless of previous syncs
  --print               Update local workspace without updating the database
  --keep                Update database without updating the local workspace

Copyright (C) 2012 Sven Erik Knop, Perforce Software Ltd

Point it to a Perforce Server with the parameters or start in an environment that
has P4CONFIG or P4PORT/P4USER/P4CLIENT set, provide a file path if you do not
want to sync everything in that workspace. Wait for the GUI to start up and
press "Sync".
There is debug output on the console for now.
#1 8185 Sven Erik Knop None working example for a P4Sync progress bar