#!/usr/bin/python
import os
import re
import sys
import shutil
import logging
import getopt
import marshal
import binascii
import tempfile
from subprocess import Popen, PIPE, STDOUT
#### MODIFY THIS SECTION AS APPROPRIATE
P4CMD="/usr/local/bin/p4"
P4PORT="localhost:1666"
P4USER="p4super"
PASSFILE="superpass"
P4CLIENT="thumb_client"
CONVERTCMD="/opt/local/bin/convert"
LOGGING_LEVEL=logging.DEBUG
THUMBNAIL_GENERATOR_COUNTER="thumb-generator"
THUMBNAIL_FORMAT=".png"
THUMBNAIL_SIZE="x160"
TEMP_DIR = "/tmp"
#### END MODIFICATION SECTION
# usage
# provides usage information for the utility
def usage():
PROGRAM_USAGE = """\
Usage: ThumbnailGenerator.py [-h] [-l logfile] [-v] [-g] [-i]
-h = show help (this message)
-v = verbose (debug mode)
-f filename = specify file (depot path and version //path/to/file#nn)
-p pageNum = specify page number of file (default is 0 or first page)
-l logfile = specify logfile (default is STDOUT)
-g = generate sample client specification
-i = initialize the system, creating the logger counter and processing all changelists
"""
print(PROGRAM_USAGE)
sys.exit(0)
# generateSampleClient
# this routine generates a sample client specification. The output should be sanity
# checked, but can make creating the client spec a little easier.
def generateSampleClient():
CLIENT_PREAMBLE = """\
Client: {0}
Owner: {1}
Description:
Created by p4admin.
Root: {2}
Options: noallwrite noclobber nocompress unlocked nomodtime normdir
SubmitOptions: submitunchanged
LineEnd: local""".format(P4CLIENT, P4USER, TEMP_DIR)
CLIENT_VIEW = """\
//{0}/....ppm //{1}/{0}/....ppm
//{0}/....PPM //{1}/{0}/....PPM
//{0}/....bmp //{1}/{0}/....bmp
//{0}/....BMP //{1}/{0}/....BMP
//{0}/....jpg //{1}/{0}/....jpg
//{0}/....JPG //{1}/{0}/....JPG
//{0}/....bpm //{1}/{0}/....bpm
//{0}/....BPM //{1}/{0}/....BPM
//{0}/....gif //{1}/{0}/....gif
//{0}/....GIF //{1}/{0}/....GIF
//{0}/....pgm //{1}/{0}/....pgm
//{0}/....PGM //{1}/{0}/....PGM
//{0}/....png //{1}/{0}/....png
//{0}/....PNG //{1}/{0}/....PNG
//{0}/....xbm //{1}/{0}/....xbm
//{0}/....XBM //{1}/{0}/....XBM
//{0}/....xpm //{1}/{0}/....xpm
//{0}/....XPM //{1}/{0}/....XPM
//{0}/....tga //{1}/{0}/....tga
//{0}/....TGA //{1}/{0}/....TGA
//{0}/....psd //{1}/{0}/....psd
//{0}/....PSD //{1}/{0}/....PSD
//{0}/....PDF //{1}/{0}/....PDF
//{0}/....pdf //{1}/{0}/....pdf
//{0}/....AI //{1}/{0}/....AI
//{0}/....ai //{1}/{0}/....ai
"""
depotList = []
cmd = ["depots"]
results = p4MarshalCmd(cmd)
for r in results:
if 'type' in r and (r['type'] == "local" or r['type'] == "stream"):
depotList.append(r['name'])
print(CLIENT_PREAMBLE)
print("View:")
for d in depotList:
print(CLIENT_VIEW.format(d, P4CLIENT))
sys.exit()
# p4MarshalCmd
# executes the p4 command, results sent to a list
def p4MarshalCmd(cmd,quiet=False):
if not quiet:
logging.debug("p4 {0}".format(" ".join(cmd)))
list = []
pipe = Popen([P4CMD, "-p", P4PORT, "-u", P4USER, "-c", P4CLIENT, "-G"] + cmd, stdout=PIPE).stdout
try:
while 1:
record = marshal.load(pipe)
list.append(record)
except EOFError:
pass
pipe.close()
return list
# p4InputCmd
# executes the p4 command with input
def p4InputCmd(data,cmd,quiet=False):
if not quiet:
logging.debug("p4 {0}".format(" ".join(cmd)))
list = []
proc = Popen([P4CMD, "-p", P4PORT, "-u", P4USER, "-c", P4CLIENT, "-G"] + cmd, stdout=PIPE, stdin=PIPE, stderr=PIPE)
outPipe = proc.stdout
proc.stdin.write(data)
return proc.communicate()
# p4Cmd
# executes a p4 command, returns results
def p4Cmd(cmd,quiet=False):
if not quiet:
logging.debug("p4 {0}".format(" ".join(cmd)))
proc = Popen([P4CMD, "-p", P4PORT, "-u", P4USER, "-c", P4CLIENT] + cmd, stdout=PIPE, stderr=PIPE)
return proc.communicate()
# p4Cmd
# executes a p4 command, returns results
def convert(original,thumb,page=0):
logging.debug("convert -thumbnail {0} {1}[{2}] {3}".format(THUMBNAIL_SIZE, original, page, thumb))
proc = Popen([CONVERTCMD, "-thumbnail", THUMBNAIL_SIZE, original + "[{0}]".format(page), thumb], stdout=PIPE, stderr=PIPE)
return proc.communicate()
# containsError
# utility function to check for any error code in the results array
def containsError(results=[],logError=True):
foundError = False
for r in results:
if 'code' in r:
if r['code'] == 'error':
foundError = True
if logError:
logging.error(r['data'].strip())
elif r['code'] == 'info':
#code info output can be important in troubleshooting
logging.debug(r['data'])
return foundError
# checkLogin
# check the login ticket on the server
def checkLogin(username=""):
cmd = ["login","-s"]
result = p4MarshalCmd(cmd,quiet=True)
if containsError(result, False):
return False
else:
return True
# login
# logs the user in using the password in the password file
def login():
if PASSFILE is not None and os.path.isfile(PASSFILE):
f = open(PASSFILE)
lines = f.readlines()
f.close()
adminpass = lines[0].strip()
cmd = ["login"]
result = p4InputCmd(adminpass, cmd)
if len(result[1]) > 0:
return False
else:
return True
else:
return False
# isSubmitted
# checks to see if the changelist exists and that it is a 'submitted' state
def isSubmitted(change):
cmd = ["describe", "-s", change]
result=p4MarshalCmd(cmd)
if containsError(result, False):
return False
if result[0]['status'] == "submitted":
return True
else:
return False
# setLoggerCounter
def resetCounters():
cmd = ["counter", "logger", "0"]
p4Cmd(cmd)
cmd = ["counter", "-d", THUMBNAIL_GENERATOR_COUNTER]
p4Cmd(cmd)
# setThumbAttribute
# sets the 'thumb' attribute on the revision to the hex value specified
def setThumbAttribute(revision, hex):
cmd = ["attribute", "-e", "-f", "-n", "thumb", "-i", revision]
result = p4InputCmd(hex, cmd)
# generateThumbnail
# generates a thumbnail for the specified depot file
def generateThumbnail(depotFile, page=0):
logging.debug("generating thumbnail for " + depotFile)
file = None
rev = 0
matchObj = re.match( r'(.*)#(\d+)$', depotFile, re.M|re.I)
if matchObj:
file = matchObj.group(1)
rev = int(matchObj.group(2))
else:
file = depotFile
extension = os.path.splitext(file)[1]
p4print = tempfile.NamedTemporaryFile(dir=TEMP_DIR, prefix="p4_", suffix=extension)
p4print.close()
thumb = tempfile.NamedTemporaryFile(dir=TEMP_DIR, prefix="thumb_", suffix=THUMBNAIL_FORMAT)
thumb.close()
logging.debug("getting file from depot...")
cmd = ["print", "-o", p4print.name, depotFile]
result = p4Cmd(cmd)
logging.debug("trying to generate thumbnail...")
convert(p4print.name, thumb.name, page)
if os.path.exists(thumb.name):
file = open(thumb.name, "rb")
bytes = file.read()
file.close()
hex = binascii.hexlify(bytes)
logging.debug("storing thumbnail in metadata...")
setThumbAttribute(depotFile, hex)
os.remove(thumb.name)
os.remove(p4print.name)
# processChange
# processes the files in the specified changelist
def processChange(change):
path = "//...@{0},@{0}".format(change)
cmd = ["sync", "-p", "-n", path]
result=p4MarshalCmd(cmd)
if not containsError(result, False):
for r in result:
if r['action'] == "deleted":
continue
depotFile = "{0}#{1}".format(r['depotFile'], r['rev'])
generateThumbnail(depotFile, 0)
# initialConfiguration
# routine to initialize the system by (re)setting the logger counter, deleting
# the thumbnail generator counter, and processing all of the submitted changelists
# starting from the beginning. Needless to say, this can potentially take a long
# time to run. if you simply want to turn on the functionality, you just have
# to create the logger counter (p4 counter logger 0) and then schedule the thumbnail
# generator to run. Only new changelists will be processed, however (i.e. only new
# versions of files will have thumbnails created)
def initialConfiguration():
logging.info("performing initial configuration")
resetCounters()
cmd = ["changes"]
result = p4MarshalCmd(cmd)
if(containsError(result, False)):
errorExit("Error finding updates")
for r in reversed(result):
if r['status'] == "submitted":
change = r['change']
processChange(change)
# checkForChanges
# uses the logger command to find any changelists since the last run
def checkForChanges():
cmd=["logger", "-t", THUMBNAIL_GENERATOR_COUNTER]
result=p4MarshalCmd(cmd)
if(containsError(result, False)):
errorExit("Error finding updates")
last = 0
for r in result:
sequence = r['sequence']
change = r['attr']
if (int(change) > last) and isSubmitted(change):
logging.info("Processing changelist @{0}".format(change))
processChange(change)
last = int(change)
logging.debug("updating logger counter...")
cmd=["logger", "-c", sequence, "-t", THUMBNAIL_GENERATOR_COUNTER]
p4Cmd(cmd)
###########################################################################
##### MAIN PROGRAM STARTS HERE
#####
def main(argv=None):
verbose = False
logFile = None
depotFile = None
pageNum = 0
generateSample = False
initialize = False
try:
opts, args = getopt.getopt(argv, "hl:f:p:vgi")
for opt, arg in opts:
if opt == "-v":
verbose = True
elif opt == "-h":
usage()
elif opt == "-l":
logFile = arg
elif opt == "-f":
depotFile = arg
elif opt == "-p":
pageNum = int(arg)
elif opt == "-g":
generateSample = True
elif opt == "-i":
initialize = True
logLevel = logging.WARN
if verbose:
logLevel = logging.DEBUG
if logFile is not None:
logging.basicConfig(filename=logFile, format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p', level=logLevel)
else:
logging.basicConfig(format='[%(levelname)s] %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p', level=logLevel)
if not checkLogin():
if not login():
errorExit("Not logged in")
if generateSample:
generateSampleClient()
elif depotFile is not None:
generateThumbnail(depotFile, pageNum)
else:
if initialize:
initialConfiguration()
else:
checkForChanges()
sys.exit(0)
except getopt.GetoptError as e:
print(e)
print("ERROR: unknown argument\n")
usage()
sys.exit(2)
if __name__ == '__main__':
main(sys.argv[1:])