00001 /* Bugzilla C# Proxy Library 00002 Copyright (C) 2006, Dansk BiblioteksCenter A/S 00003 Mads Bondo Dydensborg, <mbd@dbc.dk> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Lesser General Public 00007 License as published by the Free Software Foundation; either 00008 version 2.1 of the License, or (at your option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Lesser General Public License for more details. 00014 00015 You should have received a copy of the GNU Lesser General Public 00016 License along with this library; if not, write to the Free Software 00017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00018 */ 00019 00020 00021 /*! \file 00022 \brief Encapsulation of the Bugzilla server. 00023 00024 This class implements the server-related web services exposed by Bugzilla. 00025 00026 In order to use a %Server object, some properties will have to be set, 00027 such as Hostname, Port, and so on. 00028 00029 Tracing of the requests and responses, can be achieved by assigning 00030 a TextWriter to the TraceWriter property. 00031 00032 Methods exists to get some general information about the server, 00033 handle authentification, and get products and bugs. 00034 00035 Authentication by Bugzilla is handled using cookies. In order to 00036 obtain a set of cookies, you call the Login method. The cookies are 00037 handled automatically by the xml-rpc.net assembly. In order to store 00038 the cookies between sessions, methods to obtain and set the cookies 00039 are provided. 00040 00041 All methods are synchronous, and either succeed, or throw some kind 00042 of exception on error. 00043 00044 */ 00045 00046 using System; 00047 using System.IO; 00048 using System.Net; 00049 using System.Runtime.Serialization; 00050 using System.Runtime.Serialization.Formatters.Binary; 00051 using CookComputing.XmlRpc; 00052 using Bugzproxy.ProxyStructs; 00053 00054 namespace Bugzproxy { 00055 00056 /// <summary>This class encapsulates a Bugzilla server.</summary> 00057 /// <remarks> 00058 /// <para>Access is done through http/xmlrpc.</para> 00059 /// <para>Properties reflect settings that will not cause network traffic, while 00060 /// methods typically involves the server on the other end.</para> 00061 /// </remarks> 00062 public class Server { 00063 00064 //////////////////////////////////////////////////////////////////////////////// 00065 // Internal tracer class 00066 /// <summary>Trace data sent and received using the XML-RPC framework</summary> 00067 private class TextWriterTracer : XmlRpcLogger { 00068 // Stream to dump to 00069 private TextWriter writer; 00070 00071 /// <summary> 00072 /// Get or set a <c>TextWriter</c> stream to dump to. 00073 /// </summary> 00074 /// <value>A <c>TextWriter</c> object</value> 00075 public TextWriter Writer { 00076 get { 00077 return writer; 00078 } 00079 set { 00080 writer = value; 00081 } 00082 } 00083 00084 /// <summary>Called when a request is made</summary> 00085 /// <param name="sender">The <c>XmlRpcClientProtocol</c> from the XML-RPC 00086 /// library, who raised this event.</param> 00087 /// <param name="e">The Event arguments</param> 00088 protected override void OnRequest(object sender, XmlRpcRequestEventArgs e) { 00089 if (Writer != null) { 00090 Writer.WriteLine("Sending =====>"); 00091 } 00092 DumpStream(e.RequestStream); 00093 if (Writer != null) { 00094 Writer.WriteLine("====="); 00095 Writer.Flush(); 00096 } 00097 } 00098 00099 /// <summary>Called when a response is received</summary> 00100 /// <param name="sender">The <c>XmlRpcClientProtocol</c> from the XML-RPC 00101 /// library, who raised this event.</param> 00102 /// <param name="e">The Event arguments</param> 00103 protected override void OnResponse(object sender, XmlRpcResponseEventArgs e) { 00104 if (Writer != null) 00105 Writer.WriteLine( "Receiving <=====" ); 00106 DumpStream(e.ResponseStream); 00107 if (Writer != null) { 00108 Writer.WriteLine( "=====" ); 00109 Writer.Flush(); 00110 } 00111 } 00112 00113 private void DumpStream(Stream stm) { 00114 if (Writer != null) { 00115 stm.Position = 0; 00116 TextReader trdr = new StreamReader(stm); 00117 String s = trdr.ReadLine(); 00118 while (s != null) { 00119 Writer.WriteLine(s); 00120 s = trdr.ReadLine(); 00121 } 00122 } 00123 } 00124 } // class TextWriterTracer 00125 ////////////////////////////////////////////////////////////////////// 00126 00127 00128 /// <summary>This is where we direct our xmlrpc request. This could 00129 /// be an option, but for now it is hardcoded, as I do not expect 00130 /// the Bugzilla server to change this.</summary> 00131 private const string rpcname = "xmlrpc.cgi"; 00132 00133 /// <summary>Our xml-rpc.net proxy instance</summary> 00134 private IProxy bugzillaProxy; 00135 00136 /// <summary>Assembly members can access the bugzillaProxy</summary> 00137 /// <value>The internal <see cref="IProxy"/> object</value> 00138 internal IProxy Proxy { 00139 get { 00140 return bugzillaProxy; 00141 } 00142 private set { 00143 bugzillaProxy = value; 00144 } 00145 } 00146 00147 /// <summary>Get or set PreAuthenticate</summary> 00148 /// <value><b>true</b> or <b>false</b>.</value> 00149 /// <remarks>This property exposes the <b>PreAuthenticate</b> property of the 00150 /// underlying XML-RPC library, in case it may be of use to you.</remarks> 00151 public bool PreAuthenticate { 00152 get { 00153 return Proxy.PreAuthenticate; 00154 } 00155 set { 00156 Proxy.PreAuthenticate = value; 00157 } 00158 } 00159 00160 /// <summary> 00161 /// Get or set credentials. 00162 /// </summary> 00163 /// <value>An <b>ICredentials</b> implementation.</value> 00164 /// <remarks>This property exposes the <b>Credentials</b> property of the underlying 00165 /// XML-RPC library, in case it may be of use to you.</remarks> 00166 public ICredentials Credentials { 00167 get { 00168 return Proxy.Credentials; 00169 } 00170 set { 00171 Proxy.Credentials = value; 00172 } 00173 } 00174 00175 /// <summary>Our Tracer.</summary> 00176 private TextWriterTracer tracer; 00177 00178 /// <summary>The hostname.</summary> 00179 private string hostname; 00180 00181 /// <summary>The port to use on the server.</summary> 00182 private uint port; 00183 00184 /// <summary>The path to use on the server.</summary> 00185 private string path; 00186 00187 /// <summary>Whether or not to connect via SSL.</summary> 00188 private bool ssl; 00189 00190 /// <summary>Wheter we are logged in or not.</summary> 00191 private bool loggedIn; 00192 00193 00194 /* You can construct a Server instance, supplying optional 00195 information about hostname, port, path, ssl support, and 00196 tracer. */ 00197 00198 00199 /*! \name Constructors */ 00200 //@{ 00201 00202 ////////////////////////////////////////////////////////////////////// 00203 /// <summary>Initialize a new instance of the <see cref="Server"/> class 00204 /// with the specified parameters.</summary> 00205 /// <param name="hostname">The name of the server to use</param> 00206 /// <param name="port">The port to use</param> 00207 /// <param name="path">The path to use</param> 00208 /// <param name="ssl">If <b>true</b>, use https for connections, otherwise use http</param> 00209 /// <param name="traceWriter">A <b>TextWriter</b> instance to trace to</param> 00210 /// <remarks>The <paramref name="path"/> paremeter denotes the path to Bugzilla 00211 /// on the server. E.g. if Bugzilla is installed on <c>http://example.com/bugs</c>, 00212 /// then <paramref name="path"/> is <c>"bugs"</c>.</remarks> 00213 public Server(string hostname, uint port, string path, bool ssl, TextWriter traceWriter) { 00214 // Create the bugzillaproxy instance, associate it with our tracer 00215 this.Proxy = XmlRpcProxyGen.Create<IProxy>(); 00216 this.tracer = new TextWriterTracer(); 00217 this.tracer.Attach(Proxy); 00218 this.Hostname = hostname; 00219 this.Port = port; 00220 this.Path = path; 00221 this.ssl = ssl; 00222 this.tracer.Writer = traceWriter; 00223 LoggedIn = false; 00224 UpdateUrl(); 00225 } 00226 00227 /*! \overload */ 00228 /// <summary>Initialize a new instance of the <see cref="Server"/> class 00229 /// with the specified host name, port and path.</summary> 00230 /// <remarks>This constructs with a <b>null</b> <see cref="TraceWriter"/>, 00231 /// empty <see cref="Path"/>, and http as protocol (scheme).</remarks> 00232 /// <param name="hostname">The name of the server to use</param> 00233 /// <param name="port">The port to use</param> 00234 /// <param name="path">The path to use</param> 00235 public Server(string hostname, uint port, string path) 00236 : this( hostname, port, path, false, null ) {} 00237 00238 /*! \overload */ 00239 /// <summary>Initialize a new instance of the <see cref="Server"/> class 00240 /// with the specified parameters.</summary> 00241 /// <remarks>This constructs with a <b>null</b> <see cref="TraceWriter"/> and 00242 /// empty <see cref="Path"/>.</remarks> 00243 /// <param name="hostname">The name of the server to use</param> 00244 /// <param name="port">The port to use</param> 00245 /// <param name="ssl">If <b>true</b>, use https for connections, otherwise use 00246 /// http</param> 00247 public Server(string hostname, uint port, bool ssl) 00248 : this(hostname, port, String.Empty, ssl, null) { 00249 } 00250 00251 /*! \overload */ 00252 /// <summary>Initialize a new instance of the <see cref="Server"/> class 00253 /// with the specified host name and port number.</summary> 00254 /// <remarks>This constructs with a <b>null</b> <see cref="TraceWriter"/>, 00255 /// empty <see cref="Path"/>, and http as protocol (scheme).</remarks> 00256 /// <param name="hostname">The name of the server to use</param> 00257 /// <param name="port">The port to use</param> 00258 public Server(string hostname, uint port) 00259 : this(hostname, port, String.Empty, false, null) { 00260 } 00261 00262 /*! \overload */ 00263 /// <summary>Initialize a new instance of the <see cref="Server"/> class 00264 /// with the specified host name and path.</summary> 00265 /// <remarks>This constructs with a <b>null</b> <see cref="TraceWriter"/> at 00266 /// port 80, with empty <see cref="Path"/> and http as protocol (scheme)</remarks> 00267 /// <param name="hostname">The name of the server to use</param> 00268 /// <param name="path">The path to use</param> 00269 public Server(string hostname, string path) 00270 : this(hostname, 80, path, false, null) { 00271 } 00272 00273 /*! \overload */ 00274 /// <summary>Initialize a new instance of the <see cref="Server"/> class 00275 /// with the specified host name and protocol.</summary> 00276 /// <remarks>This constructs with a <b>null</b> <see cref="TraceWriter"/>, 00277 /// and empty <see cref="Path"/>. Depending on the setting of ssl, the port 00278 /// will be either 80 (<b>false</b>) or 443 (<b>true</b>).</remarks> 00279 /// <param name="hostname">The name of the server to use</param> 00280 /// <param name="ssl">If <b>true</b>, use https for connections at port 443, 00281 /// otherwise use http at port 80.</param> 00282 public Server(string hostname, bool ssl) 00283 : this(hostname, (ssl ? 443u : 80u), String.Empty, ssl, null) { 00284 } 00285 00286 /*! \overload */ 00287 /// <summary>Initialize a new instance of the <see cref="Server"/> class 00288 /// with the specified host name</summary> 00289 /// <remarks>This constructs using port 80, with an empty <see cref="Path"/>, 00290 /// with a <b>null</b> <see cref="TraceWriter"/>, and using http.</remarks> 00291 /// <param name="hostname">The name of the server to use</param> 00292 public Server(string hostname) 00293 : this(hostname, 80, String.Empty, false, null) { 00294 } 00295 00296 00297 00298 /*! \overload */ 00299 /// <summary>Initialize a new instance of the <see cref="Server"/> class</summary> 00300 /// <remarks>This constructs using an empty <see cref="Hostname"/>, an empty 00301 /// <see cref="Path"/>, port 80, using http, and with a <b>null</b> <see cref="TraceWriter"/>. 00302 /// </remarks> 00303 public Server() 00304 : this(String.Empty, 80, String.Empty, false, null) { 00305 } 00306 00307 //@} 00308 00309 ////////////////////////////////////////////////////////////////////// 00310 /// <summary>Update the URL on the proxy object from hostname and 00311 /// port</summary> 00312 private void UpdateUrl() { 00313 if (Path != String.Empty) { 00314 Proxy.Url 00315 = (ssl ? "https://" : "http://") + Hostname + ":" + Port + "/" + Path + "/" + rpcname; 00316 } 00317 else { 00318 Proxy.Url 00319 = (ssl ? "https://" : "http://") + Hostname + ":" +Port + "/" + rpcname; 00320 } 00321 } 00322 00323 ////////////////////////////////////////////////////////////////////// 00324 // Properties 00325 ////////////////////////////////////////////////////////////////////// 00326 /// <summary>Get or set the server's host name.</summary> 00327 /// <remarks>If you set it while logged in, an exception will be thrown</remarks> 00328 /// <exception cref="InvalidOperationException">This exception will be 00329 /// thrown if trying to set the host name while logged in.</exception> 00330 public string Hostname { 00331 get { 00332 return hostname; 00333 } 00334 set { 00335 if (LoggedIn) { 00336 throw new InvalidOperationException("Bugzilla.Hostname: Tried to change the hostname while logged in"); 00337 } 00338 hostname = value; 00339 UpdateUrl(); 00340 } 00341 } 00342 00343 /// <summary>Get or set the port of the web server.</summary> 00344 /// <remarks>If you set the port while logged in, an exception will be thrown. 00345 /// By default the port will be set to 80 for HTTP connections (the standard http 00346 /// port), and 443 for HTTPS connections.</remarks> 00347 /// <exception cref="InvalidOperationException">This exception will be 00348 /// thrown if trying to set the property while logged in.</exception> 00349 public uint Port { 00350 get { 00351 return port; 00352 } 00353 set { 00354 if (LoggedIn) { 00355 throw new InvalidOperationException("Bugzilla.Port: Tried to change the port while logged in"); 00356 } 00357 port = value; 00358 UpdateUrl(); 00359 } 00360 } 00361 00362 /// <summary>Get or set the path.</summary> 00363 /// <remarks> 00364 /// <para>Denotes the path to Bugzilla on the server. E.g. if Bugzilla 00365 /// is installed on <c>http://example.com/bugs</c>, then the path is <c>"bugs"</c>.</para> 00366 /// <para>If you set the path while logged in, an exception will be thrown. 00367 /// By default the path will be set to the empty string.</para> 00368 /// </remarks> 00369 /// <exception cref="System.InvalidOperationException">This exception will be 00370 /// thrown if trying to set the property while logged in</exception> 00371 public string Path { 00372 get { 00373 return path; 00374 } 00375 set { 00376 if (LoggedIn) { 00377 throw new InvalidOperationException("Bugzilla.Path: Tried to change the path while logged in"); 00378 } 00379 path = value; 00380 UpdateUrl(); 00381 } 00382 } 00383 00384 /// <summary>Get the login status.</summary> 00385 /// <remarks>If the user is logged in, returns <b>true</b>. Otherwise, return 00386 /// <b>false</b>.</remarks> 00387 public bool LoggedIn { 00388 get { 00389 return loggedIn; 00390 } 00391 private set { 00392 loggedIn = value; 00393 } 00394 } 00395 00396 /// <summary>Get or set a <b>TextWriter</b> for debug 00397 /// traces.</summary> 00398 /// <remarks>If set, the HTTP request and response will be 00399 /// written to this <b>TextWriter</b></remarks> 00400 public TextWriter TraceWriter { 00401 get { 00402 return tracer.Writer; 00403 } 00404 set { 00405 tracer.Writer = value; 00406 } 00407 } 00408 00409 ////////////////////////////////////////////////////////////////////// 00410 // Server Methods 00411 ////////////////////////////////////////////////////////////////////// 00412 00413 /*! \name General Methods 00414 Methods to get information about the server, and log in/out.*/ 00415 //@{ 00416 00417 /*! \example ServerInfo.cs 00418 * This is an example on how to use the Bugproxy.Server.GetVersion and 00419 * Bugzproxy.Server.GetTimezone call */ 00420 00421 /// <summary>Get the version of the server.</summary> 00422 /// <remarks>This requires that at least the <see cref="Hostname"/> has been 00423 /// set. <b>GetVersion</b> can be called without beeing logged in to the Bugzilla 00424 /// server.</remarks> 00425 /// <exception cref="InvalidOperationException">This 00426 /// exception will be thrown if <see cref="Hostname"/> is empty</exception> 00427 public string GetVersion() { 00428 if (Hostname == String.Empty) { 00429 throw new InvalidOperationException("GetVersion: Hostname is not set"); 00430 } 00431 return Proxy.GetVersion().version; 00432 } 00433 00434 /// <summary>Get the server's time zone.</summary> 00435 /// <remarks>This returns the servers timezone as a 00436 /// <b>string</b> in (+/-)XXXX (RFC 2822) format. All time/dates returned 00437 /// from the server will be in this time zone.</remarks> 00438 /// <returns>The server's time zone as a <b>string</b></returns> 00439 public string GetTimezone() { 00440 if (Hostname == String.Empty) { 00441 throw new InvalidOperationException("GetTimezone: Hostname is not set"); 00442 } 00443 return Proxy.GetTimezone().timeZone; 00444 } 00445 //@} 00446 00447 ////////////////////////////////////////////////////////////////////// 00448 // Methods to login and out, get/set cookies 00449 ////////////////////////////////////////////////////////////////////// 00450 00451 /*! \name Authentication Handling 00452 Methods to log in/out, and store/set credentials. */ 00453 //@{ 00454 00455 /*! \example Login.cs 00456 * This is an example on how to use the Bugzproxy.Server.Login call, which can also be used 00457 * to test if your login works with a given server. */ 00458 00459 /// <summary>Login to the server.</summary> 00460 /// <remarks> 00461 /// <para>Most servers require you to log in before you can retrieve information 00462 /// other than the version, let alone work with bugs, products or components.</para> 00463 /// <para>Currently, the <paramref name="remember"/> parameter is ignored by 00464 /// Bugzproxy.</para> 00465 /// </remarks> 00466 /// <param name="username">The Bugzilla username to use</param> 00467 /// <param name="password">The Bugzilla password to use</param> 00468 /// <param name="remember">Same meaning as the remember checkbox of the web 00469 /// interface.</param> 00470 /// <returns>The server's internal ID number for this user.</returns> 00471 /// <exception cref="InvalidOperationException">This exception will be thrown 00472 /// if trying to login, while already logged in.</exception> 00473 //when remember is fixed, we should copy some words about it from the bugzilla 00474 //API documentation. 00475 public int Login(string username, string password, bool remember) { 00476 if (LoggedIn) { 00477 throw new InvalidOperationException("Login: Already logged in"); 00478 } 00479 LoginParam param = new LoginParam(); 00480 param.login = username; 00481 param.password = password; 00482 // param.remember = remember; 00483 int res = Proxy.Login(param).id; 00484 LoggedIn = true; // prev statement will throw if failed. 00485 return res; // I have no idea what the user want to do with that. 00486 } 00487 00488 /// <summary>Logout of the server.</summary> 00489 /// <remarks>You must be logged in to call this function. This will invalidate 00490 /// the cookies set by Bugzilla.</remarks> 00491 /// <exception cref="InvalidOperationException">This exception will be thrown 00492 /// if trying to logout, without being logged in.</exception> 00493 public void Logout() { 00494 if (!LoggedIn) { 00495 throw new InvalidOperationException("Logout: Not logged in"); 00496 } 00497 Proxy.Logout(); 00498 LoggedIn = false; 00499 } 00500 00501 /// <summary>Get or set the cookies that are currently used as 00502 /// credentials.</summary> 00503 /// <value>A <b>CookieCollection</b> object with the currently used credetials 00504 /// cookies.</value> 00505 /// <remarks>By obtaining the cookies, you can store them somewhere, and use 00506 /// them instead of a login during a new session.</remarks> 00507 /// <exception cref="InvalidOperationException">This exception will be 00508 /// thrown if trying to set the cookies while logged in, or trying 00509 /// to get the cookies without beeing logged in.</exception> 00510 public CookieCollection Cookies { 00511 get { 00512 if (!LoggedIn) { 00513 throw new InvalidOperationException("cookies.get: Not logged in"); 00514 } 00515 return Proxy.CookieContainer.GetCookies(new Uri((ssl ? "https://" : "http://") + Hostname)); 00516 } 00517 set { 00518 if (LoggedIn) { 00519 throw new InvalidOperationException("cookies.set: Already logged in"); 00520 } 00521 foreach (Cookie c in value) { 00522 Proxy.CookieContainer.Add(c); 00523 } 00524 } 00525 } 00526 00527 /// <summary>Write the currently used cookies to a 00528 /// stream</summary> 00529 /// <param name="stream">The stream to write the cookies to.</param> 00530 /// <remarks>By obtaining the cookies, you can 00531 /// store them somewhere, and use them instead of a login during a 00532 /// new session.</remarks> 00533 /// <exception cref="InvalidOperationException">This exception will be thrown 00534 /// if trying to set the cookies, without being logged in.</exception> 00535 public void WriteCookies(Stream stream) { 00536 CookieCollection cc = Cookies; 00537 BinaryFormatter b = new BinaryFormatter(); 00538 b.Serialize(stream, cc); 00539 } 00540 00541 /// <summary>Read cookies from a stream.</summary> 00542 /// <param name="stream">The stream to read the cookies from.</param> 00543 /// <remarks> 00544 /// <para>By calling this method with a stored set of cookies, you do not 00545 /// need to perform a login.</para> 00546 /// <para>This method expects the stream to contain the cookies as they are written 00547 /// by <see cref="WriteCookies"/>.</para> 00548 /// </remarks> 00549 /// <exception cref="InvalidOperationException">This exception will be 00550 /// thrown if trying to get the Cookies, while being logged 00551 /// in</exception> 00552 public void ReadCookies(Stream stream) { 00553 BinaryFormatter b = new BinaryFormatter(); 00554 CookieCollection cc = (CookieCollection)b.Deserialize(stream); 00555 Cookies = cc; 00556 } 00557 00558 //@} 00559 ////////////////////////////////////////////////////////////////////// 00560 // Product related methods 00561 ////////////////////////////////////////////////////////////////////// 00562 00563 /*! \name Product Access 00564 Methods to get information about the products that the server knows 00565 00566 A user may not have access to all products. A user may have 00567 access to search and view bugs from some products, and 00568 additionally, to enter bugs against some other products. Methods 00569 for this are exposed here. Additionally, general methods to 00570 retrieve one or more bugs are made avaiable as well. 00571 */ 00572 //@{ 00573 /// <summary>Get a list of the products a user can search</summary> 00574 /// <returns>List of product ids that the user can search against</returns> 00575 public int[] GetSelectableProductIds() { 00576 return Proxy.GetSelectableProducts().ids; 00577 } 00578 00579 /// <summary>Get a list of the products a user can file bugs against</summary> 00580 /// <returns>List of product ids that the user can file bugs against</returns> 00581 public int[] GetEnterableProductIds() { 00582 return Proxy.GetEnterableProducts().ids; 00583 } 00584 00585 /// <summary>Get a list of the products an user can search or file bugs against</summary> 00586 /// <remarks>This is effectively a union of <see cref="GetSelectableProductIds"/> 00587 /// and <see cref="GetEnterableProductIds"/>.</remarks> 00588 /// <returns>List of product ids that the user can search or file bugs against</returns> 00589 public int[] GetAccessibleProductIds() { 00590 return Proxy.GetAccessibleProducts().ids; 00591 } 00592 00593 /*! \example ListProducts.cs 00594 * This is an example on how to use the Bugzproxy.Server.GetProducts call */ 00595 00596 /// <summary>Get a list of existing products</summary> 00597 /// <remarks>This returns an array of products, matching the ids 00598 /// supplied as argument. Note, however, that if the user has 00599 /// specified an id for a product that the user for some reason 00600 /// can not access, this is silently ignored.</remarks> 00601 /// <param name="ids">List of product ids</param> 00602 /// <returns>List of products</returns> 00603 public Product[] GetProducts(int[] ids) { 00604 ProductIds param; 00605 param.ids = ids; 00606 ProductInfo[] pis = Proxy.GetProducts(param).products; 00607 Product[] res = new Product[pis.Length]; 00608 for (int i = 0; i < pis.Length; ++i) { 00609 res[i] = new Product(this, pis[i]); 00610 } 00611 return res; 00612 } 00613 00614 /// <summary>Get a single existing product</summary> 00615 /// <remarks>This returns a single existing product from the id</remarks> 00616 /// <param name="id">The id of the product to get</param> 00617 /// <returns>A <see cref="Product"/> object.</returns> 00618 /// <exception cref="ArgumentOutOfRangeException">This exception will 00619 /// be thrown, if the id does not exists, or can not be accessed</exception> 00620 public Product GetProduct(int id) { 00621 int[] ids = new int[] { id }; 00622 Product[] ps = GetProducts(ids); 00623 if (ps.Length != 1) { 00624 throw new ArgumentOutOfRangeException("id", id, "No product was returned" 00625 + " from the server. You probably specified an ID of a non-existing product," 00626 + " or a product you can not access"); 00627 } 00628 return ps[0]; 00629 } 00630 //@} 00631 00632 ////////////////////////////////////////////////////////////////////// 00633 // Bug handling methods 00634 ////////////////////////////////////////////////////////////////////// 00635 /*! \name Bug Access 00636 Methods to get information about the bugs that the server knows. 00637 00638 Note: There is currently no search support in the Bugzilla 00639 WebService. When this is implemented, the search facilities 00640 should complement these services nicely. 00641 00642 As for products, a user may not have read/write access to all bugs. 00643 */ 00644 //@{ 00645 /*! \example ListBug.cs 00646 * This is an example on how to use the Bugzproxy.Server.GetBug call */ 00647 00648 /// <summary>Get a list of bugs</summary> 00649 /// <remarks>This returns an array of bugs, matching the ids 00650 /// supplied as arguments. Note, that if the user has specified 00651 /// an id for a bug that does not exist, or that the user can not 00652 /// access (read), an exception will be thrown. This is different 00653 /// from <see cref="GetProducts"/>.</remarks> 00654 /// <param name="ids">List of bug ids</param> 00655 /// <returns>Array of <see cref="Bug"/> objects</returns> 00656 /*! \todo Document exception, when known. */ 00657 public Bug[] GetBugs(int[] ids) { 00658 BugIds param; 00659 param.ids = ids; 00660 BugInfo[] bis = Proxy.GetBugs(param).bugs; 00661 Bug[] res = new Bug[bis.Length]; 00662 for (int i = 0; i < bis.Length; ++i) { 00663 res[i] = new Bug(this, bis[i]); 00664 } 00665 return res; 00666 } 00667 00668 /*! \example ListBugs.cs 00669 * This is an example on how to use the Bugzproxy.Server.GetBugs call */ 00670 00671 /// <summary>Get a bug</summary> 00672 /// <remarks>This return a single existing bug, from the id</remarks> 00673 /// <param name="id">The id of a bug</param> 00674 /// <returns>A bug</returns> 00675 /// <exception cref="ArgumentOutOfRangeException">This exception will 00676 /// be thrown, if the id does not exists, or can not be accessed</exception> 00677 public Bug GetBug(int id) { 00678 int[] ids = new int[1]; 00679 ids[0] = id; 00680 Bug[] bs = GetBugs(ids); 00681 if (bs.Length != 1) { 00682 throw new ArgumentOutOfRangeException("id", id, "No bug was returned from" 00683 + " the server. You probably specified an id of a non-existing bug, or" 00684 + " a bug you can not access"); 00685 } 00686 return bs[0]; 00687 } 00688 00689 /// <summary> 00690 /// Represents the <c>op_sys</c> field of a bug 00691 /// </summary> 00692 /// <remarks>See <see cref="GetLegalFieldValues"/> for details.</remarks> 00693 public const string OperatingSystem = "operatingSystem"; 00694 00695 /// <summary> 00696 /// Represetns the <c>assigned_to</c> field of a bug 00697 /// </summary> 00698 /// <remarks>See <see cref="GetLegalFieldValues"/> for details.</remarks> 00699 public const string AssignedTo = "assignedTo"; 00700 00701 /// <summary> 00702 /// Represents the <c>qa_contact</c> field of a bug 00703 /// </summary> 00704 /// <remarks>See <see cref="GetLegalFieldValues"/> for details.</remarks> 00705 public const string QaContact = "qaContact"; 00706 00707 /// <summary> 00708 /// Represents the <c>target_milestone</c> field of a bug 00709 /// </summary> 00710 /// <remarks>See <see cref="GetLegalFieldValues"/> for details.</remarks> 00711 public const string TargetMilestone = "targetMilestone"; 00712 00713 /// <summary>Get a list of legal values for a non-product specific 00714 /// bug field.</summary> 00715 /// <remarks>This can be used to retrieve a list of legal values 00716 /// for non-product specific fields of a bug, such as status, 00717 /// severity, and so on. When applicable, you should prefer using one of <see cref="OperatingSystem"/>, 00718 /// <see cref="AssignedTo"/>, <see cref="QaContact"/> or <see cref="TargetMilestone"/>. 00719 /// For other fields, including your own custom fields, you may use the Bugzilla 00720 /// original naming (such as <c>op_sys</c>). Note, that in order to retrieve 00721 /// values for product specific fields (such as component), you must use the 00722 /// <see cref="Product.GetLegalFieldValues"/> method.</remarks> 00723 /// <returns>A list of legal values for the field</returns> 00724 /// <param name="fieldName">The name of a field.</param> 00725 public string[] GetLegalFieldValues(string fieldName) { 00726 return GetLegalFieldValues(fieldName, -1); 00727 } 00728 00729 //@} 00730 // Private method, used internally, also by Product. 00731 internal string[] GetLegalFieldValues(string field, int productId) { 00732 GetLegalValuesForBugFieldParam param; 00733 // Translate names used by us to bugzilla names 00734 switch (field) { 00735 case OperatingSystem: 00736 field = "op_sys"; 00737 break; 00738 case AssignedTo: 00739 field = "assigned_to"; 00740 break; 00741 case QaContact: 00742 field = "qa_contact"; 00743 break; 00744 case TargetMilestone: 00745 field = "target_milestone"; 00746 break; 00747 } 00748 // Setup parameters 00749 param.field = field; 00750 param.productId = productId; // Ignored by server if not needed. 00751 return Proxy.GetLegalValuesForBugField(param).values; 00752 } 00753 00754 00755 /*! \name Experimental 00756 00757 * These methods are experimental, and will change/move, as the API 00758 * stabilizes. */ 00759 00760 //@{ 00761 ////////////////////////////////////////////////////////////////////// 00762 /// <summary>Create a bug - experimental</summary> 00763 /// <param name="product">Product name</param> 00764 /// <param name="component">Component name</param> 00765 /// <param name="summary">Bug summary</param> 00766 /// <param name="description">Bug initial description</param> 00767 /// <returns>the number -1.</returns> 00768 /// <remarks>This method is obsulete and does nothing. You should use 00769 /// <see cref="Product.CreateBug"/> instead.</remarks> 00770 [Obsolete("Use Product.CreateBug() instead.", true)] 00771 public int CreateBug(string product, string component, 00772 string summary, string description) { 00773 return -1; 00774 } 00775 00776 /// <summary> 00777 /// Change the resolution of a bug. 00778 /// </summary> 00779 /// <param name="bugId">The Bug number</param> 00780 /// <param name="resolution">The new resolution</param> 00781 /// <returns>?</returns> 00782 /// <remarks>This method is not supported by current versions of Bugzilla, and 00783 /// will require a custom patch to work.</remarks> 00784 public string SetBugResolution(int bugId, string resolution) { 00785 SetBugResolutionParam parameters; 00786 parameters.bugId = bugId; 00787 parameters.resolution = resolution; 00788 return Proxy.SetBugResolution(parameters); 00789 } 00790 00791 //@} 00792 00793 } // class Server 00794 00795 } // namespace Bugzproxy