/* Hey Emacs, this is -*- LPC -*- ! */ /* $Id: errors_base.c,v 1.5 2001/06/02 03:28:17 presto Exp $ */ /** * This is the low level error database inheritable. It handles all such * annoying things as connecting to the server and sending the queries. * There are 2 levels of functions, you can do your own queries or * leave the details to this object. * The errors database contains the following fields in the errors table: * <UL> * <LI>Id - The unique identifier for a report * <LI>DirEntryDate - The date the report was entered into the directory * (changes when forwarded) * <LI>EntryDate - The date the report was made in time() format * <LI>FixDate - The date the report was fixed or marked otherwise in time() * format * <LI>Directory - The directory the report belongs to * <LI>Filename - The filename of the object the report was made on * <LI>Category - The category the report belongs to, one of 'ROOM', 'OBJECT', * 'RITUAL', 'SPELL', 'HELP', or 'COMMAND' * <LI>Type - The type of the report, one of 'TYPO', 'BUG' or 'IDEA' * <LI>Name - The name of the object * <LI>Reporter - The name of the person who made the report * <LI>Fixer - The name of the person who fixed the report or otherwise marked * it * <LI>Status - The status of the report, one of 'OPEN', 'FIXING', * 'CONSIDERING', 'DENIED', or 'FIXED' * <LI>Report - The text of the report as typed by the reporter * <LI>Runtime - The runtime error that may or may not(!) relate to the * report * </UL> * There are is also a separate table for forwards, which has the following * fields: * <UL> * <LI>Id - The bug report id the forward belongs to * <LI>ForwardDate - The date the report was forwarder in time() format * <LI>Forwarder - The name of the person who forwarded the report * <LI>OldDirectory - The directory the report belonged to before forwarding * </UL> * Finally, a table for comments exists with the following fields: * <UL> * <LI>Id - The bug report id the comment belongs to * <LI>CommentDate - The date someone made a comment on the report in time() * format * <LI>Commenter - The name of the person who commented on the report * <LI>Comment - The comment about the report * </UL> * @author Turrican */ #include <db.h> #include <log.h> #include <config.h> /** @ignore yes */ private class report { int row; string newstatus; string newdir; string newtype; int changed; } /** @ignore yes */ private class bugs { //int fd; mixed* errors; string user; int changed; } private nosave mapping _globvars; protected varargs mixed get_row(mixed key, int row, int nomap); /** * This method initializes some state variables and connects to the errors * database. It doesn't do very much now we do not use the * internal database methods. * @param key the unique key to use for the global variables * @param user the username used to connect to the database * @param replace set to 1 if an existing key should be replaced * @return 0 for succes, an error string for failure * @see save_changes() * @see finish_errors() */ protected varargs string init_errors(mixed key, string user, int replace) { string ret; if (!mapp(_globvars)) { _globvars = ([ ]); } else { if (!(replace || undefinedp(_globvars[key]))) { return "key already in use"; } } _globvars[key] = new(class bugs); _globvars[key]->user = user; _globvars[key]->errors = ([ ]); return 0; } /* init_errors() */ /** * This method ends access to the database. It doesn't save any pending * changes. * @param key the unique key for the global variables * @return 0 for succes, an error string for failure * @see init_errors() * @see save_changes() */ protected string finish_errors(mixed key) { string ret; if (!classp(_globvars[key])) { return "unknown key"; } map_delete(_globvars, key); return ret; } /* finish_errors() */ /** * This method is called when the request finishes. * @param key the key of the thingy which finished */ void event_finished_get_fields(string key) { } /* event_finished_get_fiels() */ private void finished_get_fields(mixed key, int type, mixed* data) { _globvars[key]->errors = data; event_finished_get_fields(key); } /* finished_get_fields() */ /** * This method fetches the specified fields from the database. You should * not call this too often as this is a very expensive operation. Typically * it's called once, at the beginning of your object. This will finish * immeditately, a function called 'event_finished_get_fields' will be * called that you can do whatever you want with. * @param key the unique key for the global variables * @param directory the directory to get the reports from * @param fields a comma separated list of fields you wish to fetch * @return the number of reports that were found or an error string * @see get_bug() * @see get_forwards() * @see get_comments() */ protected void get_fields(mixed key, string directory, string fields, string type) { string query; string temp; mixed ret; if (!strlen(type)) { type = "OPEN"; } _globvars[key]->errors = ({ }); fields += ", Directory"; query = sprintf("SELECT %s FROM errors WHERE Directory = '%s' AND " "Status = '%s' ORDER BY DirEntryDate;", fields, directory, type); MYSQL_HANDLER->make_sql_request("errors", USER, "", query, (: finished_get_fields($(key), $1, $2) :)); } /* get_fields() */ void event_finish_get_forwards(mixed key, int row) { } /* event_finish_get_forwards() */ protected void finish_get_forwards(mixed key, int row, int type, mixed* data) { _globvars[key]->forwards[row] = data; event_finish_get_forwards(key, row); } /* finish_get_forwards() */ /** * This method gets the forwarding info for a report in the database. * The info is given in a mapping indexed by column name where the * values are arrays of the values for the columns, ordered by * ForwardDate. * @param key the unique key for the global variables * @param row the row number to get the forwards for * @return a mapping with the forwards or an error string * @see get_fields() * @see get_comments() */ protected void get_forwards(mixed key, int row) { string query, err; mixed ret; mapping forwards; ret = _globvars[key]->errors[row]; if (_globvars[key]->forwards[row]) { return _globvars[key]->forwards[row]; } query = sprintf("SELECT ForwardDate, Forwarder, OldDirectory FROM forwards " "WHERE Id = %d ORDER BY ForwardDate;", ret["Id"]); MYSQL_HANDLER->make_sql_request("errors", USER, "", query, (: finished_get_forwards($(key), $(row), $1, $2) :)); /* if (ret) { err = catch { int *dates, i; string *forwarders, *dirs; mixed *vals; dates = allocate(ret); forwarders = allocate(ret); dirs = allocate(ret); for (i = 0; i < ret; i++) { vals = db_fetch(fd, i + 1); dates[i] = vals[0]; forwarders[i] = vals[1]; dirs[i] = vals[2]; } forwards = allocate_mapping(3); forwards["ForwardDate"] = dates; forwards["Forwarder"] = forwarders; forwards["OldDirectory"] = dirs; }; if (err) { //catch(db_close(fd)); return err; } } //catch(db_close(fd)); return forwards; */ } /* get_forwards() */ void event_finish_get_comments(mixed key, int row) { } /* event_finish_get_comments() */ protected void finish_get_comments(mixed key, int row, int type, mixed* data) { _globvars[key]->comments[row] = data; event_finish_get_comments(key, row); } /* finish_get_comments() */ /** * This method gets the comments about a report in the database. * The info is given in a mapping indexed by column name where the * values are arrays of the values for the columns, ordered by * CommentDate. * @param key the unique key for the global variables * @param row the row number to get the comments for * @return a mapping with the comments or an error string * @see get_fields() * @see get_forwards() */ protected void get_comments(mixed key, int row) { string query, err; mixed ret; mapping comments; int fd; ret = _globvars[key]->errors[row]; if (_globvars[key]->comments[row]) { return _globvars[key]->comments[row]; } query = sprintf("SELECT CommentDate, Commenter, Comment FROM comments " "WHERE Id = %d ORDER BY CommentDate;", ret["Id"]); MYSQL_HANDLER->make_sql_request("errors", USER, "", query, (: finished_get_comments($(key), $(row), $1, $2) :)); } /* get_comments() */ /** * This method returns a row of information from the database. Note that * you get the original, not a copy, so any changes you make to the row * will also be reflected in a later get_row(). If you don't want this, * just make a copy(). The row consists of a mapping with the column names * being the keys. * @param key the unique key for the global variables * @param number the row number of the bug report * @return a row from the database or an error string * @see get_fields() * @see efun::copy() */ protected void get_row(mixed key, int row) { mixed *res; string ret, *keys; mapping rowvals; return _globvars[key]->errors[row]; } /* get_row() */ /** * This method changes the status of a bug report. In the old system * it would delete the bug. * @param key the unique key for the global variables * @param row the row number to change the status of * @param status the new status of the report * @return 1 for succes, 0 for failure */ protected int set_status(mixed key, int row, string status) { int realrow = ROWS[row]; /* Currently, this can't fail because of missing commit/rollback facilities in MySQL. We only actually save this later on. */ if (!classp(_globvars[key]->changes[row])) { _globvars[key]->changes[row] = new(class _report, row : realrow); } _globvars[key]->changes[row]->newstatus = status; _globvars[key]->changes[row]->changed = 1; return 1; } /* set_status() */ /** * This method changes the type of a bug report. * Possible types are 'IDEA', 'BUG' and 'TYPO'. * @param key the unique key for the global variables * @param row the row number to change the type of * @param type the new type of the report * @return 1 for succes, 0 for failure */ protected int set_type(mixed key, int row, string type) { /* Currently, this can't fail because of missing commit/rollback facilities in MySQL. We only actually save this later on. */ if (!classp(_globvars[key]->changes[row])) { _globvars[key]->changes[row] = new(class _report, row : realrow); } _globvars[key]->changes[row]->newtype = status; _globvars[key]->changes[row]->changed = 1; return 1; } /* set_type() */ /** * This method forwards the bug report to a different directory. * @param key the unique key for the global variables * @param row the row number of the bug to forward * @param directory the name of the new directory * @return 1 for succes, 0 for failure */ protected int forward_bug(mixed key, int row, string directory) { int realrow = ROWS[row]; /* Currently, this can't fail because of missing commit/rollback facilities in MySQL. We only actually save this later on. */ while (directory[<1] == '/') { directory = directory[0..<2]; } if (!classp(_globvars[key]->changes[row])) { _globvars[key]->changes[row] = new(class _report, row : realrow); } _globvars[key]->changes[row]->newdir = directory; _globvars[key]->changes[row]->changed = 1; return 1; } /* forward_bug() */ /** * This method stores a comment about the report. * Unlike most other methods, this is immediately saved in the * comments table! * @param key the unique key for the global variables * @param row the row number of the bug to forward * @param who the name of the commenter * @param comment the text of the comment * @return 0 for succes, an error string for failure */ protected string comment_bug(mixed key, int row, string who, string comment) { string query, err; int fd; mixed ret; query = sprintf("INSERT LOW_PRIORITY INTO comments VALUES " "(%d, %d, '%s', '%s');", ret["Id"], time(), who, comment); MYSQL_HANDLER->make_sql_request("errors", USER, "", query); return 0; } /* comment_bug() */ /** @ignore yes */ private string save_status(mixed key, int *ids, string user, string status) { string query, ret, err; mixed res; ret = ""; if (sizeof(ids)) { if (sizeof(ids) == 1) { query = sprintf("UPDATE LOW_PRIORITY errors SET Status = '%s', " "FixDate = %d, Fixer = '%s' WHERE Id = %d;", status, time(), user, ids[0]); } else { query = sprintf("UPDATE LOW_PRIORITY errors SET Status = '%s', " "FixDate = %d, Fixer = '%s' WHERE Id IN (%s);", status, time(), user, implode(ids, (: "" + $1 + ", " + $2 :))); } MYSQL_HANDLER->make_sql_request("errors", USER, "", query); } return ret; } /** * This method saves any pending changes into the database. * @param key the unique key for the global variables * @param user the name of the person who made the changes * @return 0 for succes, an error string for failure * @see finish_errors() * @see init_errors() */ protected string save_changes(mixed key, string user) { class _report *reports, report; string query, *queries, ret, err; int *ids_fixed, *ids_denied, *ids_fixing, *ids_considering, *ids_open, ftime; mixed res, row; if (!CHANGED) { return 0; } reports = filter(values(ERRORS), (: ((class _report)$1)->changed :)); ids_fixed = ids_denied = ids_fixing = ids_considering = ids_open = queries = ({ }); ret = ""; foreach (report in reports) { row = get_row(key, report->row, 1); if (stringp(row)) { ret += row; continue; } switch (report->newstatus) { case "FIXED": ids_fixed += ({ row["Id"] }); break; case "DENIED": ids_denied += ({ row["Id"] }); break; case "FIXING": ids_fixing += ({ row["Id"] }); break; case "CONSIDERING": ids_considering += ({ row["Id"] }); break; case "OPEN": ids_open += ({ row["Id"] }); break; case 0: case "": if (report->newdir) { ftime = time(); query = sprintf("UPDATE LOW_PRIORITY errors SET Directory = '%s', " "DirEntryDate = %d WHERE Id = %d;", report->newdir, ftime, row["Id"]); queries += ({ query }); query = sprintf("INSERT LOW_PRIORITY INTO forwards VALUES " "(%d, %d, '%s', '%s');", row["Id"], ftime, user, row["Directory"]); queries += ({ query }); } if (report->newtype) { query = sprintf("UPDATE LOW_PRIORITY errors SET Type = '%s' " "WHERE Id = %d;", report->newtype, row["Id"]); queries += ({ query }); } break; default: ret += sprintf("Unknown report status: %s\n", report->newstatus); break; } } ret += save_status(key, ids_fixed, user, "FIXED"); ret += save_status(key, ids_denied, user, "DENIED"); ret += save_status(key, ids_fixing, user, "FIXING"); ret += save_status(key, ids_considering, user, "CONSIDERING"); ret += save_status(key, ids_open, user, "OPEN" ); foreach (query in queries) { err = catch(db_exec(FD, query)); if (err) { ret += err; } if (stringp(res)) { ret += res; } } if (ret == "") { ret = 0; } return ret; } /* save_changes() */