| 1 | #!/usr/bin/python |
|---|
| 2 | # -*- coding: utf-8 -*- |
|---|
| 3 | import gtk |
|---|
| 4 | import gobject |
|---|
| 5 | import urllib |
|---|
| 6 | from bug import * |
|---|
| 7 | from mechanize import Browser |
|---|
| 8 | from BeautifulSoup import BeautifulSoup |
|---|
| 9 | # You have to change this if you have another storage |
|---|
| 10 | #from auth_dummy import user |
|---|
| 11 | |
|---|
| 12 | baseurl = "https://staging.launchpad.net/" |
|---|
| 13 | #baseurl = "https://launchpad.net/" |
|---|
| 14 | |
|---|
| 15 | class protocol: |
|---|
| 16 | |
|---|
| 17 | # LAUNCHPAD protocol using the web : suboptimal |
|---|
| 18 | |
|---|
| 19 | # Available informations for authentification are : |
|---|
| 20 | # - user.login() -> string that contains the login |
|---|
| 21 | # - user.password() -> string that contains the password |
|---|
| 22 | |
|---|
| 23 | def __init__(self,manager) : |
|---|
| 24 | self.user = manager |
|---|
| 25 | |
|---|
| 26 | ### Launchpad only functions #### |
|---|
| 27 | |
|---|
| 28 | def htmlify(self, string) : |
|---|
| 29 | s1 = string.strip().replace(" "," ") |
|---|
| 30 | s2 = s1.strip().replace(">",">") |
|---|
| 31 | s3 = s2.strip().replace("<","<") |
|---|
| 32 | return s3 |
|---|
| 33 | |
|---|
| 34 | ################INFORMATION ABOUT THE BTS ################################ |
|---|
| 35 | |
|---|
| 36 | # Send back the name of the protocol as a simple string |
|---|
| 37 | |
|---|
| 38 | @staticmethod |
|---|
| 39 | def Name(): |
|---|
| 40 | return "launchpadweb" |
|---|
| 41 | |
|---|
| 42 | # send the name of the bts this protocol will talk too. |
|---|
| 43 | # this is intended for future multi-protocol support |
|---|
| 44 | # and will enable bookmarks sharing between differents protocols |
|---|
| 45 | # that speaks to the same bts |
|---|
| 46 | def btsName(self): |
|---|
| 47 | return "launchpad" |
|---|
| 48 | |
|---|
| 49 | ################BUG RETRIEVING IN THE BTS ################################# |
|---|
| 50 | |
|---|
| 51 | # Functions related to bugs in your BTS |
|---|
| 52 | # Functions return an object "bug" (see bug.py) |
|---|
| 53 | # |
|---|
| 54 | # You have to implement the following functions : |
|---|
| 55 | # - retrieveBug(int) |
|---|
| 56 | # - getBugUrl(int) |
|---|
| 57 | |
|---|
| 58 | # Retrieve bug take an int, the bug number, and construct a bug object |
|---|
| 59 | # this bug object is then returned |
|---|
| 60 | def retrieveBug(self,nbr): |
|---|
| 61 | zebug = bug(nbr) |
|---|
| 62 | #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|---|
| 63 | # this is what you want to modify to support your own protocol |
|---|
| 64 | zeurl="%sbugs/%s" %(baseurl,str(nbr)) |
|---|
| 65 | f= urllib.urlopen(zeurl) |
|---|
| 66 | # ici on a le corps principal |
|---|
| 67 | # faudrait faire un extract pour soulager la mémoire |
|---|
| 68 | soup1 = BeautifulSoup(f).body.contents[5] |
|---|
| 69 | # no bug of this number here |
|---|
| 70 | if soup1.div.h1.contents[0] == "Page not found" : |
|---|
| 71 | # a negative number means that the bug doesn't exist |
|---|
| 72 | zebug.setNbr(-1) |
|---|
| 73 | else : |
|---|
| 74 | soup = soup1.div.div |
|---|
| 75 | title = soup.h1.contents[0] |
|---|
| 76 | tbody = soup.findAll('tbody')[0].findAll('td') |
|---|
| 77 | product = tbody[0].a.contents[0] |
|---|
| 78 | status = tbody[1].contents[0] |
|---|
| 79 | importance = tbody[2].contents[0] |
|---|
| 80 | string_tmp = tbody[3].a |
|---|
| 81 | # Here we look if the bug is assigned to someone or not |
|---|
| 82 | if string_tmp != None : |
|---|
| 83 | assigned = string_tmp.contents[2].strip() |
|---|
| 84 | else : |
|---|
| 85 | #Assigned to nobody |
|---|
| 86 | assigned = "Nobody yet" |
|---|
| 87 | #need to prettify content |
|---|
| 88 | content='' |
|---|
| 89 | p = soup.findAll('div', recursive=0)[1].div.findAll('p', recursive=0) |
|---|
| 90 | for i in p: |
|---|
| 91 | for j in i.contents: |
|---|
| 92 | jj = j.string |
|---|
| 93 | # we get rid of <br /> tags |
|---|
| 94 | if jj != None and jj != "<br />" : |
|---|
| 95 | # here we remove manually all   |
|---|
| 96 | newj = self.htmlify(jj) |
|---|
| 97 | #newj = jj.strip().replace(" "," ") |
|---|
| 98 | content = "%s\n%s"%(content,newj) |
|---|
| 99 | content = "%s\n"%content |
|---|
| 100 | |
|---|
| 101 | zebug.setTitle(title) |
|---|
| 102 | zebug.setStatus(status) |
|---|
| 103 | zebug.setPackage(product) |
|---|
| 104 | zebug.setImportance(importance) |
|---|
| 105 | zebug.setDescription(content) |
|---|
| 106 | zebug.setAssignee(assigned) |
|---|
| 107 | |
|---|
| 108 | array_com = soup.findAll('div', recursive=0)[1].findAll('div','boardComment') |
|---|
| 109 | j = 0 |
|---|
| 110 | com_counter = 0 |
|---|
| 111 | for i in array_com : |
|---|
| 112 | details = i.contents[1].findAll('a') |
|---|
| 113 | title = details[0].strong.contents[0] |
|---|
| 114 | poster = details[1].contents[0] |
|---|
| 115 | body = i.contents[3].findAll('p') |
|---|
| 116 | com_counter += 1 |
|---|
| 117 | content="" |
|---|
| 118 | for z in body : |
|---|
| 119 | phrase = '' |
|---|
| 120 | for ligne in z.contents : |
|---|
| 121 | if ligne.string != None : |
|---|
| 122 | phrase += ligne.string.strip() |
|---|
| 123 | content += self.htmlify(phrase) |
|---|
| 124 | content += "\n\n" |
|---|
| 125 | newcom = comment(com_counter,content,poster,title,"00/00/00") |
|---|
| 126 | zebug.addComment(newcom) |
|---|
| 127 | |
|---|
| 128 | #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|---|
| 129 | return zebug |
|---|
| 130 | |
|---|
| 131 | # getBugUrl takes a int, the bug number, and return the URL of |
|---|
| 132 | # the bug in order to open it in a browser |
|---|
| 133 | def getBugUrl(self,nbr) : |
|---|
| 134 | bugnbr = str(nbr) |
|---|
| 135 | return "%sbugs/%s" %(baseurl,bugnbr) |
|---|
| 136 | |
|---|
| 137 | |
|---|
| 138 | |
|---|
| 139 | ################SEARCH IN THE BTS ################################# |
|---|
| 140 | |
|---|
| 141 | # The functions Search will perform a search |
|---|
| 142 | # in the BTS |
|---|
| 143 | # All results matching the search are added to a |
|---|
| 144 | # gtk.ListStore with attributes in the following orders : |
|---|
| 145 | # gobject.TYPE_INT : number of the bug |
|---|
| 146 | # gobject.TYPE_STRING : package |
|---|
| 147 | # gobject.TYPE_STRING : title of the bug |
|---|
| 148 | # gobject.TYPE_STRING : importance |
|---|
| 149 | # gobject.TYPE_STRING : status |
|---|
| 150 | # |
|---|
| 151 | # You have to implement the following search methods for your BTS : |
|---|
| 152 | # - genericSearch(string) |
|---|
| 153 | # - packageSearch(string,string) |
|---|
| 154 | # - advancedSearch |
|---|
| 155 | # - packageExist(string) (return a boolean, not a listStore) |
|---|
| 156 | |
|---|
| 157 | |
|---|
| 158 | #private function to create a new result gtk.ListStore |
|---|
| 159 | def __newResult(self) : |
|---|
| 160 | return gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING) |
|---|
| 161 | |
|---|
| 162 | # content is an array of bug numbers |
|---|
| 163 | def __populateResult(self, results, content, typeofsearch, arg_package): |
|---|
| 164 | parse = content.body.findAll('tbody') |
|---|
| 165 | # if no results |
|---|
| 166 | if len(parse) == 0 : |
|---|
| 167 | return results |
|---|
| 168 | else : |
|---|
| 169 | table = parse[1].findAll('tr') |
|---|
| 170 | for tr in table : |
|---|
| 171 | row = tr.findAll('td') |
|---|
| 172 | nbr = int(row[1].contents[0]) |
|---|
| 173 | title = row[2].a.contents[0] |
|---|
| 174 | if typeofsearch == 2 : |
|---|
| 175 | package = row[3].contents[0] |
|---|
| 176 | importance = row[4].contents[0] |
|---|
| 177 | status = row[5].contents[0] |
|---|
| 178 | elif typeofsearch == 3 : |
|---|
| 179 | package = arg_package |
|---|
| 180 | importance = row[3].contents[0] |
|---|
| 181 | status = row[4].contents[0] |
|---|
| 182 | else : |
|---|
| 183 | package = '' |
|---|
| 184 | importance = '' |
|---|
| 185 | status = '' |
|---|
| 186 | results.insert_before(None, [nbr, package, title, importance, status ]) |
|---|
| 187 | return results |
|---|
| 188 | |
|---|
| 189 | |
|---|
| 190 | # genericSearch on a given string search_str. |
|---|
| 191 | def genericSearch(self, search_str) : |
|---|
| 192 | #creating an empty result first |
|---|
| 193 | results= self.__newResult() |
|---|
| 194 | #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|---|
| 195 | # this is what you want to modify to support your own protocol |
|---|
| 196 | #inserting dummies bugs |
|---|
| 197 | plus = search_str.replace(" ","+") |
|---|
| 198 | zeurl="%sdistros/ubuntu/+bugs?field.searchtext=%s" %(baseurl,plus) |
|---|
| 199 | f= urllib.urlopen(zeurl) |
|---|
| 200 | content=BeautifulSoup(f) |
|---|
| 201 | return self.__populateResult(results, content, 2, None) |
|---|
| 202 | |
|---|
| 203 | |
|---|
| 204 | #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|---|
| 205 | #returning the results |
|---|
| 206 | #return results |
|---|
| 207 | |
|---|
| 208 | # search on a given string search_str but only in bugs of |
|---|
| 209 | # a given package |
|---|
| 210 | def packageSearch(self, package, search_str): |
|---|
| 211 | #creating an empty result first |
|---|
| 212 | results= self.__newResult() |
|---|
| 213 | #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|---|
| 214 | # this is what you want to modify to support your own protocol |
|---|
| 215 | #inserting dummies bugs |
|---|
| 216 | # your package search must support a Null search_str |
|---|
| 217 | if search_str != None : |
|---|
| 218 | plus = search_str.replace(" ","+") |
|---|
| 219 | zeurl="%sdistros/ubuntu/+source/%s/+bugs?field.searchtext=%s" %(baseurl,package,plus) |
|---|
| 220 | else : |
|---|
| 221 | zeurl="%sdistros/ubuntu/+source/%s/+bugs" %(baseurl,package) |
|---|
| 222 | f= urllib.urlopen(zeurl) |
|---|
| 223 | content=BeautifulSoup(f) |
|---|
| 224 | return self.__populateResult(results, content, 3, package) |
|---|
| 225 | |
|---|
| 226 | #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|---|
| 227 | #returning the results |
|---|
| 228 | #return results |
|---|
| 229 | |
|---|
| 230 | |
|---|
| 231 | # Return true is the package exist in the BTS, false if not |
|---|
| 232 | def packageExist(self, package): |
|---|
| 233 | #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|---|
| 234 | # this is what you want to modify to support your own protocol |
|---|
| 235 | zeurl="%sproducts/%s" %(baseurl,package) |
|---|
| 236 | f= urllib.urlopen(zeurl) |
|---|
| 237 | #ugly launchpad hack ! |
|---|
| 238 | title=BeautifulSoup(f).html.head.title.contents[0] |
|---|
| 239 | return title != "Error: Page not found" |
|---|
| 240 | #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|---|
| 241 | |
|---|
| 242 | ################MODIFYING A BUG ################################# |
|---|
| 243 | |
|---|
| 244 | # The functions that will allow us to modify a bug |
|---|
| 245 | # |
|---|
| 246 | # You have to implement the following search modify for your BTS : |
|---|
| 247 | # - postComment(string,string,string) |
|---|
| 248 | |
|---|
| 249 | #private function that log the user into Launchpad |
|---|
| 250 | # return a mechanize Browser object |
|---|
| 251 | def __login(self): |
|---|
| 252 | urllog="%s+login" %baseurl |
|---|
| 253 | br = Browser() |
|---|
| 254 | br.set_handle_robots(False) |
|---|
| 255 | br.open(urllog) |
|---|
| 256 | br.select_form(name="login") |
|---|
| 257 | br["loginpage_email"]=self.user.login |
|---|
| 258 | br["loginpage_password"]=self.user.password |
|---|
| 259 | response = br.submit() |
|---|
| 260 | return br |
|---|
| 261 | |
|---|
| 262 | def postComment(self,bugnbr,title,comment) : |
|---|
| 263 | urlcom="%sbugs/%s" %(baseurl,bugnbr) |
|---|
| 264 | br = self.__login() |
|---|
| 265 | br.open(urlcom) |
|---|
| 266 | br.select_form(nr=2) |
|---|
| 267 | # TODO : set the title |
|---|
| 268 | br["field.comment"]= comment |
|---|
| 269 | br.submit() |
|---|
| 270 | |
|---|
| 271 | |
|---|
| 272 | |
|---|
| 273 | |
|---|
| 274 | |
|---|
| 275 | |
|---|