# Perforce Defect Tracking Integration Project
# <http://www.ravenbrook.com/project/p4dti/>
#
# TRACKER.PY -- PYTHON INTERFACE TO TRACKER
#
# Robert Cowham, Vaccaperna Systems Limited, 2003-11-20
#
#
# 1. INTRODUCTION
#
# This document implements a Python interface to Tracker
# It requires the Python interface package ctypes (0.62 or later) which is available from:
# http://starship.python.net/crew/theller/ctypes/
from ctypes import *
import catalog
from config_loader import config
import os
import re
import sys
import string
import time
import types
import dt_tracker
import configure_tracker
import p4dti_exceptions
import traceback
import win32api
error = "Tracker interface error"
TRK_VERSION_ID = 500001
TRK_MAX_STRING = 255
TRK_NO_KEEP_ALIVE = 4
TRK_E_DATA_TRUNCATED = 6
TRK_E_NO_MORE_DATA = 7
TRK_E_NOT_LOGGED_IN = 10
TRK_E_UNABLE_TO_CONNECT = 13
TRK_E_ITEM_NOT_FOUND = 21
TRK_E_INVALID_FIELD = 28
TRK_E_RECORD_LOCKED = 50
TRK_ERROR_LIST = [
'TRK_SUCCESS ',
'TRK_E_VERSION_MISMATCH ',
'TRK_E_OUT_OF_MEMORY ',
'TRK_E_BAD_HANDLE ',
'TRK_E_BAD_INPUT_POINTER ',
'TRK_E_BAD_INPUT_VALUE ',
'TRK_E_DATA_TRUNCATED ',
'TRK_E_NO_MORE_DATA ',
'TRK_E_LIST_NOT_INITIALIZED ',
'TRK_E_END_OF_LIST ',
'TRK_E_NOT_LOGGED_IN ',
'TRK_E_SERVER_NOT_PREPARED ',
'TRK_E_BAD_DATABASE_VERSION ',
'TRK_E_UNABLE_TO_CONNECT ',
'TRK_E_UNABLE_TO_DISCONNECT ',
'TRK_E_UNABLE_TO_START_TIMER ',
'TRK_E_NO_DATA_SOURCES ',
'TRK_E_NO_PROJECTS ',
'TRK_E_WRITE_FAILED ',
'TRK_E_PERMISSION_DENIED ',
'TRK_E_SET_FIELD_DENIED ',
'TRK_E_ITEM_NOT_FOUND ',
'TRK_E_CANNOT_ACCESS_DATABASE ',
'TRK_E_CANNOT_ACCESS_QUERY ',
'TRK_E_CANNOT_ACCESS_INTRAY ',
'TRK_E_CANNOT_OPEN_FILE ',
'TRK_E_INVALID_DBMS_TYPE ',
'TRK_E_INVALID_RECORD_TYPE ',
'TRK_E_INVALID_FIELD ',
'TRK_E_INVALID_CHOICE ',
'TRK_E_INVALID_USER ',
'TRK_E_INVALID_SUBMITTER ',
'TRK_E_INVALID_OWNER ',
'TRK_E_INVALID_DATE ',
'TRK_E_INVALID_STORED_QUERY ',
'TRK_E_INVALID_MODE ',
'TRK_E_INVALID_MESSAGE ',
'TRK_E_VALUE_OUT_OF_RANGE ',
'TRK_E_WRONG_FIELD_TYPE ',
'TRK_E_NO_CURRENT_RECORD ',
'TRK_E_NO_CURRENT_NOTE ',
'TRK_E_NO_CURRENT_ATTACHED_FILE ',
'TRK_E_NO_CURRENT_ASSOCIATION ',
'TRK_E_NO_RECORD_BEGIN ',
'TRK_E_NO_MODULE ',
'TRK_E_USER_CANCELLED ',
'TRK_E_SEMAPHORE_TIMEOUT ',
'TRK_E_SEMAPHORE_ERROR ',
'TRK_E_INVALID_SERVER_NAME ',
'TRK_E_NOT_LICENSED ',
'TRK_E_RECORD_LOCKED ',
'TRK_E_RECORD_NOT_LOCKED ',
'TRK_E_UNMATCHED_PARENS ',
'TRK_E_NO_CURRENT_TRANSITION ',
'TRK_E_NO_CURRENT_RULE ',
'TRK_E_UNKNOWN_RULE ',
'TRK_E_RULE_ASSERTION_FAILED ',
'TRK_E_ITEM_UNCHANGED ',
'TRK_E_TRANSITION_NOT_ALLOWED ',
'TRK_E_NO_CURRENT_STYLESHEET ',
'TRK_E_NO_CURRENT_FORM ',
'TRK_E_NO_CURRENT_VALUE ',
'TRK_E_FORM_FIELD_ACCESS ',
'TRK_E_INVALID_QBID_STRING ',
'TRK_E_FORM_INVALID_FIELD ',
'TRK_E_PARTIAL_SUCCESS ',
]
def trk_error(err):
if err >= 0 and err < len(TRK_ERROR_LIST):
return TRK_ERROR_LIST[err]
return "Unknown TRK error " + str(err)
class init_access:
need_release = False
def __init__(self, trk):
if not trk.initialised:
self.need_release = True
self.trk = trk
self.trk._init_access()
def release(self):
if self.need_release:
self.trk._release_access()
self.need_release = False
class tracker:
initialised = False
db = None
cursor = None
rid = None
sid = None
replication = None
logger = None
user = None
password = None
project = None
server = None
curr_tran_id = 0
update_count = 0
# 2. TRACKER INTERFACE
def __init__(self, config, user, password, project, server):
self.config = config
# Make sure we can find dll to load
if self.config.tracker_path <> None:
if string.find(os.environ['PATH'], self.config.tracker_path) < 0:
os.putenv("PATH", os.environ['PATH'] + ";" + self.config.tracker_path)
self.trk = windll.trktooln
self.project = project
self.server = server
self.logger = config.logger
self.rid = config.rid
self.sid = config.sid
def _init(self):
# Initialise handles
if self.initialised:
return
self.handle = c_int()
result = self.trk.TrkHandleAlloc(TRK_VERSION_ID, byref(self.handle))
if result:
raise error, catalog.msg(1400, ("TrkHandleAlloc", trk_error(result)))
result = self.trk.TrkSetNumericAttribute(self.handle, TRK_NO_KEEP_ALIVE, 1);
if result:
raise error, catalog.msg(1400, ("TrkSetNumericAttribute", trk_error(result)))
self.rechandle = c_int()
result = self.trk.TrkRecordHandleAlloc(self.handle, byref(self.rechandle))
if result:
raise error, catalog.msg(1400, ("TrkRecordHandleAlloc", trk_error(result)))
self.assoc_handle = c_int()
result = self.trk.TrkAssociationHandleAlloc(self.rechandle, byref(self.assoc_handle))
if result:
raise error, catalog.msg(1400, ("TrkAssociationHandleAlloc", trk_error(result)))
self.note_handle = c_int()
result = self.trk.TrkNoteHandleAlloc(self.rechandle, byref(self.note_handle))
if result:
raise error, catalog.msg(1400, ("TrkNotesHandleAlloc", trk_error(result)))
self.initialised = True
def _release(self):
# Free handles
if not self.initialised:
return
result = self.trk.TrkAssociationHandleFree(byref(self.assoc_handle))
if result:
raise error, catalog.msg(1400, ("TrkAssociationHandleFree", trk_error(result)))
result = self.trk.TrkNoteHandleFree(byref(self.note_handle))
if result:
raise error, catalog.msg(1400, ("TrkNotesHandleFree", trk_error(result)))
result = self.trk.TrkRecordHandleFree(byref(self.rechandle))
if result:
raise error, catalog.msg(1400, ("TrkRecordHandleFree", trk_error(result)))
result = self.trk.TrkHandleFree(byref(self.handle))
if result:
raise error, catalog.msg(1400, ("TrkHandleFree", trk_error(result)))
self.initialised = False
def _init_access(self):
self.login(self.config.tracker_user, self.config.tracker_password)
def _release_access(self):
self.logout()
def _reset_note_handle(self):
result = self.trk.TrkNoteHandleFree(byref(self.note_handle))
if result:
raise error, catalog.msg(1400, ("TrkNotesHandleFree", trk_error(result)))
result = self.trk.TrkNoteHandleAlloc(self.rechandle, byref(self.note_handle))
if result:
raise error, catalog.msg(1400, ("TrkNotesHandleAlloc", trk_error(result)))
def reset_assoc_handle(self):
result = self.trk.TrkAssociationHandleAlloc(self.rechandle, byref(self.assoc_handle))
if result:
raise error, catalog.msg(1400, ("TrkAssociationHandleAlloc", trk_error(result)))
def login(self, user, password):
"""Login with different user if not already logged in as that user.
Note this implementation assumes all users have the same password!"""
self.log(1403, (user, self.user))
if self.user == user:
return
if self.user <> None:
self.logout()
self._init()
self.user = user
self.password = password
self.log(1404, (self.user, self.password, self.project, self.server))
result = self.trk.TrkProjectLoginEx(self.handle, self.user, self.password, self.project, self.server)
if result == TRK_E_UNABLE_TO_CONNECT:
# Log message for administrator and try loggin in as Replicator
result = self.trk.TrkProjectLoginEx(self.handle, self.config.tracker_user, self.password, self.project, self.server)
if result == TRK_E_UNABLE_TO_CONNECT:
raise error, catalog.msg(1402, (self.server, self.project, self.user, self.config.tracker_user))
elif result:
raise error, catalog.msg(1400, ("TrkProjectLoginEx", trk_error(result)))
else:
# success - so just warn administrator
self.log(1401, (self.server, self.project, self.user, self.config.tracker_user))
self.config.mail_report(catalog.msg(1401, (self.server, self.project, self.user, self.config.tracker_user)), [])
self.user = self.config.tracker_user
elif result:
raise error, catalog.msg(1400, ("TrkProjectLoginEx", trk_error(result)))
def logout(self):
if self.user <> None:
self.log(1405, (self.user))
result = self.trk.TrkProjectLogout(self.handle)
self.user = None
if result and result <> TRK_E_NOT_LOGGED_IN:
self._release()
raise error, catalog.msg(1400, ("TrkProjectLogout", trk_error(result)))
self._release()
def first_replication(self, start_date):
self.replication = start_date
def log(self, id, args = ()):
msg = catalog.msg(id, args)
self.logger.log(msg)
def ini_file(self):
pathname = os.path.join(os.getcwd(), 'tracker.ini')
return pathname
def load_marker(self):
"""Marker of where we last processed to - Tracker transaction id."""
marker = int(win32api.GetProfileVal('Tracker', 'Marker', '0', self.ini_file()))
marker_date = win32api.GetProfileVal('Tracker', 'Marker_date', '', self.ini_file())
if marker_date == '':
marker_date = '2000-01-01 00:00:00'
self.marker_date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
return (marker, marker_date)
def save_marker(self):
"""Save where we have got to - Tracker transaction id."""
if self.curr_tran_id > self.last_tran_id:
self.last_tran_id = self.curr_tran_id
win32api.WriteProfileVal('Tracker', 'Marker', str(self.last_tran_id), self.ini_file())
win32api.WriteProfileVal('Tracker', 'Marker_date', self.marker_date, self.ini_file())
def get_choices(self, field_name):
access = init_access(self)
result = self.trk.TrkInitChoiceList(self.handle, field_name, self.config.RECORD_TYPE_SCR)
if result:
access.release()
raise error, catalog.msg(1408, ("TrkInitChoiceList", field_name, trk_error(result)))
buf = create_string_buffer(TRK_MAX_STRING)
choices = []
while (0 == self.trk.TrkGetNextChoice(self.handle, TRK_MAX_STRING, buf)):
choices.append(buf.value)
access.release()
return choices
def user_id_and_email_list(self):
access = init_access(self)
result = self.trk.TrkInitUserList(self.handle)
if result:
access.release()
raise error, catalog.msg(1400, ("TrkInitUserList", trk_error(result)))
buf = create_string_buffer(TRK_MAX_STRING)
users = []
while (0 == self.trk.TrkGetNextUser(self.handle, TRK_MAX_STRING, buf)):
user_name = buf.value
result = self.trk.TrkGetUserEmail(self.handle, buf, TRK_MAX_STRING, buf)
if result:
access.release()
raise error, catalog.msg(1408, ("TrkGetUserEmail", user_name, trk_error(result)))
email = buf.value
email = re.sub(' ', '_', email)
result = self.trk.TrkGetUserFullName(self.handle, user_name, TRK_MAX_STRING, buf)
if result:
access.release()
raise error, catalog.msg(1408, ("TrkGetUserFullName", user_name, trk_error(result)))
fullname = buf.value
fullname = re.sub(' ', '_', fullname)
users.append((user_name, email, fullname))
access.release()
return users
def field_exists(self, field_name):
"""Ensure field exists!."""
assert self.initialised
i = c_int(0)
result = self.trk.TrkGetFieldType(self.handle, field_name, self.config.RECORD_TYPE_SCR, byref(i))
if result == TRK_E_INVALID_FIELD:
return False
elif result:
raise error, catalog.msg(1408, ("TrkGetFieldType", field_name, trk_error(result)))
return False
else:
return True
def _get_string_value(self, field_name):
buf = create_string_buffer(TRK_MAX_STRING)
result = self.trk.TrkGetStringFieldValue(self.rechandle, field_name, TRK_MAX_STRING, buf)
if result:
raise error, catalog.msg(1408, ("TrkGetStringFieldValue", field_name, trk_error(result)))
return buf.value
def _get_description_value(self):
buf_len = c_int(0)
result = self.trk.TrkGetDescriptionDataLength(self.rechandle, byref(buf_len))
if result:
raise error, catalog.msg(1400, ("TrkGetDescriptionDataLength", trk_error(result)))
buf_len.value += 1
buf = c_char_p('\000' * buf_len.value)
data_left = c_int(0)
result = self.trk.TrkGetDescriptionData(self.rechandle, buf_len, buf, byref(data_left))
if result and result <> TRK_E_NO_MORE_DATA:
raise error, catalog.msg(1400, ("TrkGetDescriptionData", trk_error(result)))
# Trim all trailing newlines as they cause problems and aren't preserved in p4
desc = buf.value
while len(desc) > 1 and desc[-2] == '\r' and desc[-1] == '\n':
if desc[-4] == '\r' and desc[-3] == '\n':
desc = desc[:-2]
else:
break
if len(desc) > 1 and desc[-2] <> '\r' and desc[-1] <> '\n':
desc = desc + '\r\n'
# remove blanks from the end of lines
desc = re.sub('[ \t]+\r\n', '\r\n', desc)
return desc
def _note_find_note(self, field):
# Find appropriate notes record
result = self.trk.TrkInitNoteList(self.note_handle)
if result:
raise error, catalog.msg(1400, ("TrkInitNoteList", trk_error(result)))
buf = create_string_buffer(TRK_MAX_STRING + 1)
while (0 == self.trk.TrkGetNextNote(self.note_handle)):
result = self.trk.TrkGetNoteTitle(self.note_handle, TRK_MAX_STRING, buf)
if result:
raise error, catalog.msg(1400, ("TrkGetNoteTitle", trk_error(result)))
# print "Note title '%s'" % buf.value
if buf.value == field:
return True
return False
def _get_note_value(self, field):
if self._note_find_note(field):
buf_len = c_int(0)
result = self.trk.TrkGetNoteDataLength(self.note_handle, byref(buf_len))
if result:
raise error, catalog.msg(1400, ("TrkGetNoteDataLength", trk_error(result)))
buf_len.value += 1
buf = c_char_p('\000' * buf_len.value)
data_left = c_int(0)
result = self.trk.TrkGetNoteData(self.note_handle, buf_len, buf, byref(data_left))
if result and result <> TRK_E_NO_MORE_DATA:
raise error, catalog.msg(1400, ("TrkGetNoteData", trk_error(result)))
# Trim all trailing newlines as they cause problems and aren't preserved in p4
desc = buf.value
while len(desc) > 1 and desc[-2] == '\r' and desc[-1] == '\n':
if desc[-4] == '\r' and desc[-3] == '\n':
desc = desc[:-2]
else:
break
if len(desc) > 1 and desc[-2] <> '\r' and desc[-1] <> '\n':
desc = desc + '\r\n'
# remove blanks from the end of lines
desc = re.sub('[ \t]+\r\n', '\r\n', desc)
return desc
return ""
def _set_string_value(self, field_name, value):
result = self.trk.TrkSetStringFieldValue(self.rechandle, field_name, value)
if result:
raise error, catalog.msg(1400, ("TrkSetStringFieldValue", trk_error(result)))
def _set_note_value(self, field, desc):
if not self._note_find_note(field):
if desc == '':
return
else:
self.curr_tran_id += 1
result = self.trk.TrkAddNewNote(self.note_handle)
if result:
raise error, catalog.msg(1400, ("TrkAddNewNote", trk_error(result)))
if desc == '':
self.curr_tran_id += 1
result = self.trk.TrkDeleteNote(self.note_handle)
if result:
raise error, catalog.msg(1400, ("TrkDeleteNote", trk_error(result)))
else:
buf_len = c_int(len(desc))
buf = c_char_p(desc)
self.curr_tran_id += 1
result = self.trk.TrkSetNoteData(self.note_handle, buf_len, buf, 0)
if result:
raise error, catalog.msg(1400, ("TrkSetNoteData", trk_error(result)))
title_buf = c_char_p(field)
result = self.trk.TrkSetNoteTitle(self.note_handle, title_buf)
if result:
raise error, catalog.msg(1400, ("TrkSetNoteTitle", trk_error(result)))
def _set_description_value(self, desc):
buf_len = c_int(len(desc))
buf = c_char_p(desc)
result = self.trk.TrkSetDescriptionData(self.rechandle, buf_len, buf, 0)
if result:
raise error, catalog.msg(1400, ("TrkSetDescriptionData", trk_error(result)))
def _get_int_value(self, field_name):
int = c_int()
result = self.trk.TrkGetNumericFieldValue(self.rechandle, field_name, byref(int))
if result:
raise error, catalog.msg(1400, ("TrkGetNumericFieldValue", trk_error(result)))
return int.value
def _get_value(self, field):
tr_fields = self.config.tr_fields
if tr_fields[field] == "int":
return str(self._get_int_value(field))
elif tr_fields[field] == "desc":
return str(self._get_description_value())
elif tr_fields[field] == "note":
return str(self._get_note_value(field))
else:
return str(self._get_string_value(field))
def _get_all_values(self):
tr_fields = self.config.tr_fields
vals = {}
for f in tr_fields.keys():
vals[f] = self._get_value(f)
self.log(1406, vals)
return vals
def _set_value(self, field, value):
tr_fields = self.config.tr_fields
if tr_fields[field] == "int":
self.set_int_value(field, value)
elif tr_fields[field] == "desc":
self._set_description_value(value)
elif tr_fields[field] == "note":
self._set_note_value(field, value)
else:
self._set_string_value(field, value)
def _localtime(self):
t = time.time()
t -= time.timezone
if time.daylight <> 0:
t -= time.altzone
return t
def _time_after(self, secs, period):
cmp_time = self._localtime()
cmp_time -= period
cmp_time += 5 * 60 # fugde factor of a minute
return secs > cmp_time
def _get_query(self, marker_date):
# Decide on appropriate query depending on how long since we last checked
marker_date_secs = configure_tracker.convert_isodate_to_secs(marker_date)
if self._time_after(marker_date_secs, (60 * 60 * 24)):
return self.config.query_all_scrs_changed_in_last_day
elif self._time_after(marker_date_secs, (60 * 60 * 24 * 7)):
return self.config.query_all_scrs_changed_in_last_week
else:
return self.config.query_all_scrs
def changed_bugs_since(self, marker):
# Find bugs new, touched, or changed (by someone other than
# this replicator) since the given transaction_id, which are not
# being replicated by any other replicator.
# print "In changed_bugs_since"
restart = 500
tran_id = c_int(marker[0])
marker_date = marker[1]
last_tran_id = c_int(0)
self.curr_tran_id = tran_id.value
query = self._get_query(marker_date)
result = self.trk.TrkQueryInitRecordList(self.rechandle, query, tran_id, byref(last_tran_id))
if result:
raise error, catalog.msg(1408, ("TrkQueryInitRecordList", query, trk_error(result)))
self.last_tran_id = last_tran_id.value # Save for later user
# print "Initialised query %s" % query
issues = []
tr_fields = self.config.tr_fields.copy()
# ignore_fields = ['Perforce Note'] # Causes memory leak
ignore_fields = []
count = 0
while (0 == self.trk.TrkGetNextRecord(self.rechandle)):
count += 1
fields = {}
for f in self.config.initial_fields:
fields[f] = self._get_value(f)
if self.config.replicate_p(fields):
for f in tr_fields.keys():
if not f in self.config.initial_fields and not f in ignore_fields:
fields[f] = self._get_value(f)
for f in ignore_fields:
fields[f] = ''
issues.append(fields)
if len(issues) % 10 == 0:
print "Read %d issues" % len(issues)
if count % restart == 0:
self._read_reset(count)
self.curr_tran_id = last_tran_id.value
# print "Found %d issues" % len(issues)
return issues
def specific_bugs(self, bug_list):
# Find all specified bugs (from list of ids).
restart = 250
issues = []
tr_fields = self.config.tr_fields.copy()
ignore_fields = []
count = 0
for bug_id in bug_list:
rec_id = c_int(bug_id)
rec_type = c_int(self.config.RECORD_TYPE_SCR)
result = self.trk.TrkGetSingleRecord(self.rechandle, rec_id, rec_type)
if result:
raise error, catalog.msg(1408, ("TrkGetSingleRecord", str(bug_id), trk_error(result)))
count += 1
fields = {}
for f in self.config.initial_fields:
fields[f] = self._get_value(f)
if self.config.replicate_p(fields):
for f in tr_fields.keys():
if not f in self.config.initial_fields and not f in ignore_fields:
fields[f] = self._get_value(f)
for f in ignore_fields:
fields[f] = ''
issues.append(fields)
if count % restart == 0:
print "%s Read %d records" % (time.strftime("%H:%M:%S", time.localtime()), count)
self._reset_login()
print "Found %d issues" % len(issues)
return issues
def _reset_login(self):
# Logout and back in resetting memory to avoid leaks
user = self.user
self.logout()
self.login(user, self.password)
def _read_reset(self, count):
# This used to reset login and start over already read items.
# Problem now fixed by setting TRK_NO_KEEP_ALIVE
print "%s Read %d records" % (time.strftime("%H:%M:%S", time.localtime()), count)
def debug_all_bugs_since(self, start_date_secs):
restart = 500
access = init_access(self)
# Find either open bugs or bugs submitted since specified date
date_trans = dt_tracker.date_translator()
tran_id = c_int(0)
last_tran_id = c_int(0)
result = self.trk.TrkQueryInitRecordList(self.rechandle, self.config.query_all_scrs, tran_id, byref(last_tran_id))
if result:
access.release()
raise error, catalog.msg(1408, ("TrkQueryInitRecordList", self.config.query_all_scrs, trk_error(result)))
self.last_tran_id = last_tran_id.value # Save for later user
statuses = {}
issues = []
ids = []
last_id = 0
count = 0
tr_fields = self.config.tr_fields.copy()
ignore_fields = ['Perforce Note'] # Causes memory leak
# ignore_fields = []
while (0 == self.trk.TrkGetNextRecord(self.rechandle)):
try:
count += 1
fields = {}
for f in self.config.initial_fields:
fields[f] = self._get_value(f)
if self.config.replicate_p(fields):
for f in tr_fields.keys():
if f not in self.config.initial_fields and not f in ignore_fields:
fields[f] = self._get_value(f)
for f in ignore_fields:
fields[f] = ''
issues.append(fields)
ids.append(fields['Id'])
# Check for valid Status field
if not statuses.has_key(fields["Status"]):
statuses[fields["Status"]] = 1
last_id = fields["Id"]
if count % restart == 0:
self._read_reset(count)
except:
print "Last record read %d '%s'" % (count, last_id)
print "Problem reading record '%s'" % fields
traceback.print_exc(None, sys.stdout)
access.release()
print "Last record read %d, '%s'" % (count, last_id)
print "Statuses encountered '%s'" % (", ".join(statuses.keys()))
print "Found %d issues" % len(issues)
print "IDs of valid issues:"
for i in range(len(ids)):
print ids[i], ", ",
if i > 0 and i % 10 == 0:
print ""
print ""
def all_bugs_since(self, start_date_secs):
restart = 500
# Find either open bugs or bugs submitted since specified date
date_trans = dt_tracker.date_translator()
access = init_access(self)
tran_id = c_int(0)
last_tran_id = c_int(0)
result = self.trk.TrkQueryInitRecordList(self.rechandle, self.config.query_all_scrs, tran_id, byref(last_tran_id))
if result:
raise error, catalog.msg(1408, ("TrkQueryInitRecordList", self.config.query_all_scrs, trk_error(result)))
self.last_tran_id = last_tran_id.value # Save for later user
issues = []
tr_fields = self.config.tr_fields.copy()
# del tr_fields['Perforce Note'] # Causes memory leak
count = 0
while (0 == self.trk.TrkGetNextRecord(self.rechandle)):
count += 1
fields = {}
for f in self.config.initial_fields:
fields[f] = self._get_value(f)
if self.config.replicate_p(fields):
for f in tr_fields.keys():
if f not in self.config.initial_fields:
fields[f] = self._get_value(f)
issues.append(fields)
if count % restart == 0:
self._read_reset(count)
return issues
def delete_issues_before(self, end_issue_id):
# Delete issues before this - RATHER DANGEROUS - provided for testing only!!!!
access = init_access(self)
tran_id = c_int(0)
last_tran_id = c_int(0)
result = self.trk.TrkQueryInitRecordList(self.rechandle, self.config.query_all_scrs, tran_id, byref(last_tran_id))
if result:
access.release()
raise error, catalog.msg(1408, ("TrkQueryInitRecordList", self.config.query_all_scrs, trk_error(result)))
while (0 == self.trk.TrkGetNextRecord(self.rechandle)):
id = int(self._get_value('Id'))
if id < end_issue_id:
print "Deleting issue %d" % (id)
result = self.trk.TrkDeleteRecord(self.rechandle)
if result:
access.release()
raise error, catalog.msg(1408, ("TrkDeleteRecord", str(id), trk_error(result)))
access.release()
def _begin_transaction(self):
result = self.trk.TrkUpdateRecordBegin(self.rechandle)
if result == TRK_E_RECORD_LOCKED:
raise p4dti_exceptions.RecordLockedError(catalog.msg(1403))
elif result:
raise error, catalog.msg(1400, ("TrkUpdateRecordBegin", trk_error(result)))
def _commit_transaction(self):
tran_id = c_int()
result = self.trk.TrkUpdateRecordCommit(self.rechandle, byref(tran_id))
if result:
raise error, catalog.msg(1400, ("TrkUpdateRecordCommit", trk_error(result)))
if self.curr_tran_id + 1 == tran_id.value:
self.curr_tran_id += 1
def _abort_transaction(self):
result = self.trk.TrkRecordCancelTransaction(self.rechandle)
def bug_from_bug_id(self, bug_id):
rec_id = c_int(bug_id)
rec_type = c_int(self.config.RECORD_TYPE_SCR)
result = self.trk.TrkGetSingleRecord(self.rechandle, rec_id, rec_type)
if result:
raise error, catalog.msg(1408, ("TrkGetSingleRecord", str(bug_id), trk_error(result)))
return self._get_all_values()
def _update_field(self, field, value):
tr_fields = self.config.tr_fields
if not tr_fields.has_key(field):
self._set_string_value(field, value)
elif tr_fields[field] == "int":
self.set_int_value(field, value)
elif tr_fields[field] == "desc":
self._set_description_value(value)
elif tr_fields[field] == "note":
self._set_note_value(field, value)
else:
self._set_string_value(field, value)
def update_bug(self, dict, bug, user):
bug_id = bug['Id']
if dict:
if self.config.individual_login:
# Login as appropriate user
self.login(user, self.password)
if self.update_count % 50 == 0:
self._reset_login()
self.update_count += 1
self.bug_from_bug_id(int(bug_id))
self.log(1407, dict)
self._begin_transaction()
try:
for f in dict.keys():
self._update_field(f, dict[f])
except:
self._abort_transaction()
raise
self._commit_transaction()
def create_bug(self, dict, user):
if self.config.individual_login:
self.login(user, self.password)
rec_type = c_int(self.config.RECORD_TYPE_SCR)
result = self.trk.TrkNewRecordBegin(self.rechandle, rec_type)
if result:
raise error, catalog.msg(1400, ("TrkNewRecordBegin", trk_error(result)))
try:
for f in dict.keys():
self._update_field(f, dict[f])
tran_id = c_int(0)
except:
self._abort_transaction()
raise
result = self.trk.TrkNewRecordCommit(self.rechandle, byref(tran_id))
if result:
raise error, catalog.msg(1400, ("TrkNewRecordBegin", trk_error(result)))
bug_id = str(self._get_int_value('Id'))
return bug_id
def _fix_get_date_p4client(self, dict):
buf_len = c_int(0)
result = self.trk.TrkGetAssociationTextLength(self.assoc_handle, byref(buf_len))
if result:
raise error, catalog.msg(1400, ("TrkGetAssociationTextLength", trk_error(result)))
buf_len.value += 1
text_buf = c_char_p('\000' * buf_len.value)
data_left = c_int(0)
result = self.trk.TrkGetAssociationText(self.assoc_handle, buf_len, text_buf, byref(data_left))
if result:
raise error, catalog.msg(1400, ("TrkGetAssociationText", trk_error(result)))
desc = text_buf.value
fields = string.split(desc, "#")
dict['p4date'] = fields[0]
# Chop off everything from \r\n onwards
tmp = fields[1]
dict['client'] = tmp[:string.index(tmp, "\r\n")]
def _fix_get_changelist(self):
buf = create_string_buffer(TRK_MAX_STRING)
result = self.trk.TrkGetAssociationRevisionFound(self.assoc_handle, TRK_MAX_STRING, buf)
if result:
raise error, catalog.msg(1400, ("TrkGetAssociationRevisionFound", trk_error(result)))
return int(buf.value)
def _fix_get_user(self):
buf = create_string_buffer(TRK_MAX_STRING)
result = self.trk.TrkGetAssociationUser(self.assoc_handle, TRK_MAX_STRING, buf)
if result:
raise error, catalog.msg(1400, ("TrkGetAssociationUser", trk_error(result)))
return buf.value
def fixes_from_bug_id(self, bug_id):
rec_id = c_int(int(bug_id))
rec_type = c_int(self.config.RECORD_TYPE_SCR)
result = self.trk.TrkGetSingleRecord(self.rechandle, rec_id, rec_type)
if result:
raise error, catalog.msg(1408, ("TrkGetSingleRecord", str(bug_id), trk_error(result)))
result = self.trk.TrkInitAssociationList(self.assoc_handle)
if result:
raise error, catalog.msg(1400, ("TrkInitAssociationList", trk_error(result)))
fixes = []
buf = create_string_buffer(TRK_MAX_STRING)
while (0 == self.trk.TrkGetNextAssociation(self.assoc_handle)):
fix = {}
fix['Id'] = bug_id
result = self.trk.TrkGetAssociationModuleName(self.assoc_handle, TRK_MAX_STRING, buf)
if result:
raise error, catalog.msg(1400, ("TrkGetAssociationModuleName", trk_error(result)))
if buf.value == "fix":
fix['changelist'] = self._fix_get_changelist()
fix['user'] = self._fix_get_user()
result = self.trk.TrkGetAssociationRevisionFixed(self.assoc_handle, TRK_MAX_STRING, buf)
if result:
raise error, catalog.msg(1400, ("TrkGetAssociationRevisionFixed", trk_error(result)))
fix['status'] = buf.value
self._fix_get_date_p4client(fix)
fixes.append(fix)
return fixes
def _fix_find_issue(self, issueid):
rec_id = c_int(issueid)
rec_type = c_int(self.config.RECORD_TYPE_SCR)
result = self.trk.TrkGetSingleRecord(self.rechandle, rec_id, rec_type)
if result:
raise error, catalog.msg(1408, ("TrkGetSingleRecord", str(issueid), trk_error(result)))
def _fix_find_fix(self, changelist):
# Find appropriate fix record
result = self.trk.TrkInitAssociationList(self.assoc_handle)
if result:
raise error, catalog.msg(1400, ("TrkInitAssociationList", trk_error(result)))
buf = create_string_buffer(TRK_MAX_STRING)
while (0 == self.trk.TrkGetNextAssociation(self.assoc_handle)):
result = self.trk.TrkGetAssociationModuleName(self.assoc_handle, TRK_MAX_STRING, buf)
if result:
raise error, catalog.msg(1400, ("TrkGetAssociationModuleName", trk_error(result)))
if buf.value == "fix":
result = self.trk.TrkGetAssociationRevisionFound(self.assoc_handle, TRK_MAX_STRING, buf)
if result:
raise error, catalog.msg(1400, ("TrkGetAssociationRevisionFound", trk_error(result)))
if int(buf.value) == changelist:
return 1
return 0
def fix_update_fields(self, dict):
# Assumes positioned on appropriate fix record (or new one added if necessary)
result = self.trk.TrkSetAssociationModuleName(self.assoc_handle, "fix")
if result:
raise error, catalog.msg(1400, ("TrkSetAssociationModuleName", trk_error(result)))
if dict.has_key('changelist'):
result = self.trk.TrkSetAssociationRevisionFound(self.assoc_handle, str(dict['changelist']))
if result:
raise error, catalog.msg(1400, ("TrkSetAssociationRevisionFound", trk_error(result)))
else:
dict['changelist'] = self._fix_get_changelist()
if dict.has_key('status'):
result = self.trk.TrkSetAssociationRevisionFixed(self.assoc_handle, dict['status'])
if result:
raise error, catalog.msg(1400, ("TrkSetAssociationRevisionFixed", trk_error(result)))
if dict.has_key('user'):
result = self.trk.TrkSetAssociationUser(self.assoc_handle, dict['user'])
if result:
raise error, catalog.msg(1400, ("TrkSetAssociationUser", trk_error(result)))
# If we're provided with all values then just write them, otherwise read current value to
# rewrite
if not dict.has_key('p4date') or not dict.has_key('client'):
newdict = {}
self._fix_get_date_p4client(newdict)
if not dict.has_key('p4date'):
dict['p4date'] = newdict['p4date']
if not dict.has_key('client'):
dict['client'] = newdict['client']
if self.config.changelist_url <> None:
url = self.config.changelist_url % (dict['changelist'])
else:
url = ''
desc = "%s#%s\r\n%s" % (dict['p4date'], dict['client'], url)
buf_len = c_int(len(desc))
buf = c_char_p(desc)
result = self.trk.TrkSetAssociationText(self.assoc_handle, buf_len, buf, 0)
if result:
raise error, catalog.msg(1400, ("TrkSetAssociationText", trk_error(result)))
def add_fix(self, dict):
if self.config.individual_login:
self.login(dict['user'], self.password)
self._fix_find_issue(int(dict['Id']))
self._begin_transaction()
try:
result = self.trk.TrkAddNewAssociation(self.assoc_handle)
if result:
raise error, catalog.msg(1408, ("TrkAddNewAssociation", str(dict['Id']), trk_error(result)))
self.fix_update_fields(dict)
except:
self._abort_transaction()
raise
self._commit_transaction()
self.reset_assoc_handle()
def update_fix(self, dict, issueid, changelist):
if dict.has_key('user') and self.config.individual_login:
self.login(dict['user'], self.password)
else:
self._fix_find_issue(int(issueid))
if self._fix_find_fix(changelist):
new_user = self._fix_get_user()
if self.config.individual_login:
self.login(new_user, self.password)
self._fix_find_issue(int(issueid))
if self._fix_find_fix(changelist):
self._begin_transaction()
try:
self.fix_update_fields(dict)
except:
self._abort_transaction()
raise
self._commit_transaction()
self.reset_assoc_handle()
def delete_fix(self, dict):
if self.config.individual_login:
self.login(dict['user'], self.password)
self._fix_find_issue(int(dict['Id']))
if self._fix_find_fix(dict['changelist']):
self._begin_transaction()
try:
result = self.trk.TrkDeleteAssociation(self.assoc_handle)
if result:
raise error, catalog.msg(1408, ("TrkDeleteAssociation", str(dict['Id']), trk_error(result)))
except:
self._abort_transaction()
raise
self._commit_transaction()
self.reset_assoc_handle()
# A. REFERENCES
#
#
#
# B. DOCUMENT HISTORY
#
# 2003-11-20 RHGC Created.
#
#
# C. COPYRIGHT AND LICENCE
#
# This file is copyright (c) 2003 Vaccaperna Systems Ltd. All
# rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
#
#
# $Id: //info.ravenbrook.com/project/p4dti/version/1.5/code/replicator/teamtrack.py#3 $