# Perforce Defect Tracking Integration Project # # # 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 $