source: air-manager/trunk/fuentes/python3-air-manager/airmanager/airmanager.py @ 7386

Last change on this file since 7386 was 7386, checked in by Juanma, 2 years ago

Fix default app after installing an air

File size: 18.4 KB
Line 
1#!/usr/bin/env python3
2import os
3import stat
4import datetime
5import subprocess
6import sys
7import shutil
8import tempfile
9import zipfile
10import urllib.request as url
11import glob
12from gi.repository import GdkPixbuf
13
14LOG='/tmp/air_manager.log'
15
16class AirManager():
17        def __init__(self):
18                self.dbg=True
19                self.default_icon="/usr/share/air-installer/rsrc/air-installer_icon.png"
20                self.adobeair_folder="/opt/AdobeAirApp/"
21                self.adobeairsdk_folder="/opt/adobe-air-sdk/"
22                self.adobeair_pkgname="adobeair"
23        #def __init__
24
25        def _debug(self,msg):
26                if self.dbg:
27                        print("airinstaller: %s"%msg)
28                        self._log(msg)
29        #def _debug
30
31        def _log(self,msg):
32                f=open(LOG,'a')
33                f.write("%s"%msg)
34                f.close()
35        #def _log
36
37        def set_default_icon(self,icon):
38                self.default_icon=icon
39
40        def install(self,air_file,icon=None):
41                sw_err=0
42                sw_install_sdk=False
43                self._check_adobeair()
44                if not icon:
45                        icon=self.default_icon
46                self._debug("Procced with file: %s"%air_file)
47                file_name=os.path.basename(air_file)
48                if self._check_file_is_air(air_file):
49                        basedir_name=file_name.replace(".air","")
50                        self._debug("Installing %s"%air_file)
51                        sw_err=self._install_air_package(air_file)
52                        if sw_err:
53                                self._debug("Trying rebuild...")
54                                modified_air_file=self._recompile_for_certificate_issue(air_file)
55                                self._debug("Installing %s"%modified_air_file)
56                                sw_err=self._install_air_package(modified_air_file)
57                        if sw_err:
58                                self._debug("Failed to install code: %s"%sw_err)
59                                self._debug("Going with sdk installation")
60                                sw_err=self._install_air_package_sdk(air_file,icon)
61                                sw_install_sdk=True
62
63                        if not sw_err and sw_install_sdk:
64                                #Copy icon to hicolor
65                                sw_installed=self._generate_desktop(file_name)
66                                if sw_installed:
67                                        hicolor_icon='/usr/share/icons/hicolor/48x48/apps/%s.png'%basedir_name
68                                        shutil.copyfile (icon,hicolor_icon)
69                                        self._debug("Installed in %s"%(basedir_name))
70                                else:
71                                        self._debug("%s Not Installed!!!"%(basedir_name))
72#                       elif not sw_err and icon!=self.default_icon:
73                        elif not sw_err:
74                                #Modify desktop with icon
75                                hicolor_icon='/usr/share/icons/hicolor/48x48/apps/%s.png'%basedir_name
76                                shutil.copyfile (icon,hicolor_icon)
77                                icon_new=os.path.basename(hicolor_icon)
78                                self._modify_desktop(air_file,icon_name=icon_new)
79                #Remove adobeair mime association
80                if os.path.isfile('/usr/share/mime/application/vnd.adobe.air-application-installer-package+zip.xml'):
81                        os.remove('/usr/share/mime/application/vnd.adobe.air-application-installer-package+zip.xml')
82                        my_env=os.environ.copy()
83                        my_env["DISPLAY"]=":0"
84                        subprocess.check_output(["xdg-mime","default","/usr/share/applications/air-installer.desktop","/usr/share/mime/packages/x-air-installer.xml"],input=b"",env=my_env)
85        #def install
86
87        def _modify_desktop(self,air_file,icon_name=None):
88                self._debug("Modify desktop %s"%air_file)
89                air_info=self.get_air_info(air_file)
90                sw_modify_icon=False
91                if 'name' in air_info.keys():
92                        cwd=os.getcwd()
93                        os.chdir('/usr/share/applications')
94                        desktop_list=glob.glob(air_info['name']+"*desktop")
95                        if desktop_list==[]:
96                                desktop_list=glob.glob(air_info['name'].lower()+"*desktop")
97                        if desktop_list:
98                                #First file must be the desktop but for sure...
99                                sw_modify_icon=False
100                                for desktop_file in desktop_list:
101                                        self._debug("Testing file %s"%desktop_file)
102                                        f=open(desktop_file,'r')
103                                        flines=f.readlines()
104                                        self._debug("Looking for %s"%self.adobeair_folder)
105                                        for fline in flines:
106                                                self._debug(fline)
107                                                self._debug(type(fline))
108                                                if '/opt/AdobeAirApp' in fline:
109                                                        self._debug("Match")
110                                                        sw_modify_icon=True
111                                        f.close()
112                        if sw_modify_icon:
113                                self._debug("Setting icon")
114                                new_desktop=[]
115                                for fline in flines:
116                                        if fline.startswith('Icon'):
117                                                self._debug("Before: %s"%fline)
118                                                nline='Icon=%s\n'%icon_name
119                                                self._debug("After: %s"%nline)
120                                                new_desktop.append(nline)
121                                        else:
122                                                new_desktop.append(fline)
123                                self._debug("Writing desktop %s"%desktop_file)
124                                f=open(desktop_file,'w')
125                                f.writelines(new_desktop)
126                                f.close()
127                        os.chdir(cwd)
128        #def _modify_desktop
129
130        def _check_adobeair(self):
131                sw_install_adobe=False
132                sw_download=False
133                try:
134                        res=subprocess.check_output(["dpkg-query","-W","-f='${Status}'",self.adobeair_pkgname])
135                        if "not" in str(res):
136                                self._debug("adobeair not installed")
137                                sw_install_adobe=True
138                except Exception as e:
139                        self._debug("dpkg-query failed: %s"%e)
140                        sw_install_adobe=True
141                finally:
142                        if sw_install_adobe:
143                                sw_download=self._install_adobeair()
144
145                if sw_download==False:
146                        if sw_install_adobe:
147                                self._debug("Adobeair failed to install")
148                        else:
149                                self._debug("Adobeair already installed")
150                #Now install the sdk
151                if not os.path.isdir(self.adobeair_folder):
152                        os.makedirs(self.adobeair_folder)
153                self._install_adobeair_sdk()
154
155        def _install_air_package(self,air_file):
156                sw_err=1
157                my_env=os.environ.copy()
158                my_env["DISPLAY"]=":0"
159                try:
160                        subprocess.check_output(["/usr/bin/Adobe AIR Application Installer","-silent","-eulaAccepted","-location","/opt/AdobeAirApp",air_file],env=my_env)
161                        sw_err=0
162                except Exception as e:
163                        self._debug("Install Error: %s"%e)
164                return sw_err
165        #def _install_air_package
166
167        def _install_air_package_sdk(self,air_file,icon=None):
168                sw_err=0
169                if not icon:
170                        icon=self.default_icon
171                file_name=os.path.basename(air_file)
172                basedir_name=file_name.replace('.air','')
173                wrkdir=self.adobeair_folder+basedir_name
174                if os.path.isdir(wrkdir):
175                        try:
176                                shutil.rmtree(wrkdir)
177                        except Exception as e:
178                                sw_err=3
179                                self._debug(e)
180                try:
181                        os.makedirs(wrkdir)
182                except Exception as e:
183                        sw_err=4
184                        self._debug(e)
185                if sw_err==0:
186                        try:
187                                shutil.copyfile (air_file,wrkdir+"/"+file_name)
188                        except:
189                                sw_err=1
190                #Copy icon to hicolor
191                if sw_err==0:
192                        hicolor_icon='/usr/share/icons/hicolor/48x48/apps/%s.png'%basedir_name
193                        try:
194                                shutil.copyfile (icon,hicolor_icon)
195                        except:
196                                sw_err=2
197
198                self._generate_desktop_sdk(file_name)
199                self._debug("Installed in %s/%s"%(wrkdir,air_file))
200                return sw_err
201        #def _install_air_package_sdk
202
203        def _generate_desktop(self,file_name):
204                basedir_name=file_name.replace('.air','')
205                desktop="/usr/share/applications/%s.desktop"%basedir_name
206                exec_file=self._get_air_bin_file(basedir_name)
207                self._debug("Exec: %s"%exec_file)
208                if exec_file:
209                        f=open(desktop,'w')
210                        f.write("[Desktop Entry]\n\
211Encoding=UTF-8\n\
212Version=1.0\n\
213Type=Application\n\
214Exec=\""+exec_file+"\"\n\
215Icon="+basedir_name+".png\n\
216Terminal=false\n\
217Name="+basedir_name+"\n\
218Comment=Application from AdobeAir "+basedir_name+"\n\
219MimeType=application/x-scratch-project\n\
220Categories=Application;Education;Development;ComputerScience;\n\
221")
222                        f.close()
223                        return True
224                else:
225                        return False
226#chmod +x $NEW_ICON_FILE
227        #def _generate_desktop
228
229        def _get_air_bin_file(self,basedir_name):
230                target_bin=''
231                for folder in os.listdir(self.adobeair_folder):
232                        target_folder=''
233                        if basedir_name.lower() in folder.lower() or basedir_name.lower==folder.lower():
234                                target_folder=os.listdir(self.adobeair_folder+folder)
235                        else:
236                                split_name=''
237                                if '-' in basedir_name.lower():
238                                        split_name=basedir_name.lower().split('-')[0]
239                                elif ' ' in basedir_name.lower():
240                                        split_name=basedir_name.lower().split(' ')[0]
241                                elif '.' in basedir_name.lower():
242                                        split_name=basedir_name.lower().split('.')[0]
243                                if split_name!='' and split_name in folder.lower():
244                                        target_folder=os.listdir(self.adobeair_folder+folder)
245                        if target_folder:
246                                if 'bin' in target_folder:
247                                        candidate_list=os.listdir(self.adobeair_folder+folder+'/bin')
248                                        for candidate_file in candidate_list:
249                                                test_file=self.adobeair_folder+folder+'/bin/'+candidate_file
250                                                self._debug("Testing %s"%test_file)
251                                                if os.access(test_file,os.X_OK):
252                                                        target_bin=test_file
253                                                        self._debug("Test OK for %s"%target_bin)
254                                                        break
255                return(target_bin)
256
257        def _generate_desktop_sdk(self,file_name):
258                basedir_name=file_name.replace('.air','')
259                desktop="/usr/share/applications/%s.desktop"%basedir_name
260                f=open(desktop,'w')
261                f.write("[Desktop Entry]\n\
262Encoding=UTF-8\n\
263Version=1.0\n\
264Type=Application\n\
265Exec=/opt/adobe-air-sdk/adobe-air/adobe-air "+self.adobeair_folder+basedir_name+"/"+file_name+"\n\
266Icon="+basedir_name+".png\n\
267Terminal=false\n\
268Name="+basedir_name+"\n\
269Comment=Application from AdobeAir "+basedir_name+"\n\
270MimeType=application/x-scratch-project\n\
271Categories=Application;Education;Development;ComputerScience;\n\
272")
273                f.close()
274        #def _generate_desktop_sdk
275
276        def _install_adobeair_sdk(self):
277                if os.path.isfile(self.adobeairsdk_folder+'adobe-air/adobe-air'):
278                        return
279                self._install_adobeair_depends()
280                self._debug("Installing Adobe Air SDK")
281                adobeair_urls=["http://lliurex.net/recursos-edu/misc/AdobeAIRSDK.tbz2","http://lliurex.net/recursos-edu/misc/adobe-air.tar.gz"]
282                for adobeair_url in adobeair_urls:
283                        req=url.Request(adobeair_url,headers={'User-Agent':'Mozilla/5.0'})
284                        try:
285                                adobeair_file=url.urlopen(req)
286                        except Exception as e:
287                                self._debug(e)
288                                return False
289                        (tmpfile,tmpfile_name)=tempfile.mkstemp()
290                        os.close(tmpfile)
291                        self._debug("Download %s"%tmpfile_name)
292                        with open(tmpfile_name,'wb') as output:
293                                output.write(adobeair_file.read())
294                        try:
295                                os.makedirs ("/opt/adobe-air-sdk")
296                        except:
297                                pass
298                        if adobeair_url.endswith(".tar.gz"):
299                                subprocess.call(["tar","zxf",tmpfile_name,"-C","/opt/adobe-air-sdk"])
300                        else:
301                                subprocess.call(["tar","jxf",tmpfile_name,"-C","/opt/adobe-air-sdk"])
302                st=os.stat("/opt/adobe-air-sdk/adobe-air/adobe-air")
303                os.chmod("/opt/adobe-air-sdk/adobe-air/adobe-air",st.st_mode | 0o111)
304
305        #def _install_adobeair_sdk
306
307        def _install_adobeair(self):
308                if self._install_adobeair_depends():
309                        self._debug("Installing Adobe Air")
310                        adobeair_url="http://airdownload.adobe.com/air/lin/download/2.6/AdobeAIRInstaller.bin"
311                        req=url.Request(adobeair_url,headers={'User-Agent':'Mozilla/5.0'})
312                        try:
313                                adobeair_file=url.urlopen(req)
314                        except Exception as e:
315                                self._debug('Donwload err: %s'%e)
316                                return False
317                        (tmpfile,tmpfile_name)=tempfile.mkstemp()
318                        os.close(tmpfile)
319                        with open(tmpfile_name,'wb') as output:
320                                output.write(adobeair_file.read())
321                        st=os.stat(tmpfile_name)
322                        os.chmod(tmpfile_name,st.st_mode | 0o111)
323#                       subprocess.call([tmpfile_name,"-silent","-eulaAccepted","-pingbackAllowed"])
324                        os.system("DISPLAY=:0 " + tmpfile_name + " -silent -eulaAccepted -pingbackAllowed")
325                        os.remove(tmpfile_name)
326                        #Remove symlinks
327                        if os.path.isfile("/usr/lib/libgnome-keyring.so.0"):
328                                os.remove("/usr/lib/libgnome-keyring.so.0")
329                        if os.path.isfile("/usr/lib/libgnome-keyring.so.0.2.0"):
330                                os.remove("/usr/lib/libgnome-keyring.so.0.2.0")
331                        return True
332                else:
333                        return False
334        #def _install_adobeair
335
336        def _install_adobeair_depends(self):
337                subprocess.call(["apt-get","-q","update"])
338                lib_folder='x86_64-linux-gnu'
339                if os.uname().machine=='x86_64':
340                        self._debug("Installing i386 libs")
341                        ret=subprocess.call(["apt-get","-q","-y","install","libgtk2.0-0:i386","libstdc++6:i386","libxml2:i386","libxslt1.1:i386","libcanberra-gtk-module:i386","gtk2-engines-murrine:i386","libqt4-qt3support:i386","libgnome-keyring0:i386","libnss-mdns:i386","libnss3:i386","libatk-adaptor:i386","libgail-common:i386"])
342                        if ret!=0:
343                                ret=subprocess.call(["dpkg","--add-architecture","i386"])
344                                ret=subprocess.call(["apt-get","-q","-y","install","libgtk2.0-0:i386","libstdc++6:i386","libxml2:i386","libxslt1.1:i386","libcanberra-gtk-module:i386","gtk2-engines-murrine:i386","libqt4-qt3support:i386","libgnome-keyring0:i386","libnss-mdns:i386","libnss3:i386","libatk-adaptor:i386","libgail-common:i386"])
345                        if ret!=0:
346                                return False
347                               
348                else:
349                        lib_folder='i386-linux-gnu'
350                        subprocess.call(["apt-get","-q","-y","install","libgtk2.0-0","libxslt1.1","libxml2","libnss3","libxaw7","libgnome-keyring0","libatk-adaptor","libgail-common"])
351                self._debug("Linking libs")
352                try:
353                        if os.path.isfile("/usr/lib/libgnome-keyring.so.0"):
354                                os.remove("/usr/lib/libgnome-keyring.so.0")
355                        if os.path.isfile("/usr/lib/libgnome-keyring.so.0.2.0"):
356                                os.remove("/usr/lib/libgnome-keyring.so.0.2.0")
357                        os.symlink("/usr/lib/"+lib_folder+"/libgnome-keyring.so.0","/usr/lib/libgnome-keyring.so.0")
358                        os.symlink("/usr/lib/"+lib_folder+"/libgnome-keyring.so.0.2.0","/usr/lib/libgnome-keyring.so.0.2.0")
359                except Exception as e:
360                        self._debug(e)
361                return True
362        #def _install_adobeair_depends
363
364        def _recompile_for_certificate_issue(self,air_file):
365                self._debug("Rebuilding package %s"%air_file)
366                new_air_file=''
367                tmpdir=self._unzip_air_file(air_file)
368                cwd=os.getcwd()
369                os.chdir(tmpdir)
370                air_xml=''
371                for xml_file in os.listdir("META-INF/AIR"):
372                        if xml_file.endswith(".xml"):
373                                air_xml=xml_file
374                                break
375                if air_xml:
376                        shutil.move("META-INF/AIR/"+air_xml,air_xml)
377                        shutil.rmtree("META-INF/",ignore_errors=True)
378                        os.remove("mimetype")
379                        self._debug("Generating new cert")
380                        subprocess.call(["/opt/adobe-air-sdk/bin/adt","-certificate","-cn","lliurex","2048-RSA","lliurex.p12","lliurex"])
381                        new_air_file=os.path.basename(air_file)
382                        my_env=os.environ.copy()
383                        my_env["DISPLAY"]=":0"
384                        try:
385                                subprocess.check_output(["/opt/adobe-air-sdk/bin/adt","-package","-tsa","none","-storetype","pkcs12","-keystore","lliurex.p12",new_air_file,air_xml,"."],input=b"lliurex",env=my_env)
386                        except Exception as e:
387                                self._debug(e)
388                os.chdir(cwd)
389                return tmpdir+'/'+new_air_file
390        #def _recompile_for_certificate_issue
391
392        def _unzip_air_file(self,air_file):
393                cwd=os.getcwd()
394                tmpdir=tempfile.mkdtemp()
395                self._debug("Extracting %s to %s"%(air_file,tmpdir))
396                os.chdir(tmpdir)
397                air_pkg=zipfile.ZipFile(air_file,'r')
398                for file_to_unzip in air_pkg.infolist():
399                        try:
400                                air_pkg.extract(file_to_unzip)
401                        except:
402                                pass
403                air_pkg.close()
404                os.chdir(cwd)
405                return (tmpdir)
406
407        def get_installed_apps(self):
408                installed_apps={}
409                if os.path.isdir(self.adobeair_folder):
410                        for app_dir in os.listdir(self.adobeair_folder):
411                                self._debug("Testing %s"%app_dir)
412                                app_desktop=''
413                                if os.path.isdir(self.adobeair_folder+app_dir+'/bin') or os.path.isfile(self.adobeair_folder+app_dir+'/'+app_dir+'.air'):
414                                        #Search the desktop of the app
415                                        self._debug("Searching desktop %s"%'/usr/share/applications/'+app_dir+'.desktop')
416                                        sw_desktop=False
417                                        if os.path.isdir(self.adobeair_folder+app_dir+'/share/META-INF/AIR'):
418                                                for pkg_file in os.listdir(self.adobeair_folder+app_dir+'/share/META-INF/AIR'):
419                                                        if pkg_file.endswith('.desktop'):
420                                                                app_desktop='/usr/share/applications/'+pkg_file
421                                                                sw_desktop=True
422                                                                break
423                                        if sw_desktop==False:
424                                                if os.path.isfile('/usr/share/applications/'+app_dir+'.desktop'):
425                                                        app_desktop='/usr/share/applications/'+app_dir+'.desktop'
426                                                elif os.path.isfile('/usr/share/applications/'+app_dir.lower()+'.desktop'):
427                                                        app_desktop='/usr/share/applications/'+app_dir.lower()+'.desktop'
428                                        #Get the app_id
429                                        app_id=''
430                                        self._debug("Searching id %s"%self.adobeair_folder+app_dir+'/share/application.xml')
431                                        if os.path.isfile(self.adobeair_folder+app_dir+'/share/application.xml'):
432                                                f=open(self.adobeair_folder+app_dir+'/share/application.xml','r')
433                                                flines=f.readlines()
434                                                app_id=''
435                                                for fline in flines:
436                                                        fline=fline.strip()
437                                                        if fline.startswith('<id>'):
438                                                                app_id=fline
439                                                                app_id=app_id.replace('<id>','')
440                                                                app_id=app_id.replace('</id>','')
441                                                                break
442                                                f.close()
443                                        elif os.path.isfile(self.adobeair_folder+app_dir+'/'+app_dir+'.air'):
444                                                app_id=app_dir+'.air'
445                                        installed_apps[app_dir]={'desktop':app_desktop,'air_id':app_id}
446                return installed_apps
447        #def get_installed_apps
448
449        def remove_air_app(self,*kwarg):
450                sw_err=1
451                my_env=os.environ.copy()
452                my_env["DISPLAY"]=":0"
453                air_dict=kwarg[0]
454                sw_uninstall_err=False
455                if 'air_id' in air_dict.keys():
456                        self._debug("Removing %s"%air_dict['air_id'])
457                        if air_dict['air_id'].endswith('.air'):
458                                air_file=self.adobeair_folder+air_dict['air_id'].replace('.air','')+'/'+air_dict['air_id']
459                                self._debug("SDK app detected %s"%air_file)
460                                if os.path.isfile(air_file):
461                                        try:
462                                                shutil.rmtree(os.path.dirname(air_file))
463                                                sw_err=0
464                                        except Exception as e:
465                                                self._debug(e)
466                        else:
467                                try:
468                                        #Let's try with supercow's power
469                                        pkgname=subprocess.check_output(["apt-cache","search",air_dict['air_id']],env=my_env,universal_newlines=True)
470                                        pkglist=pkgname.split(' ')
471                                        for pkg in pkglist:
472                                                self._debug("Testing %s"%pkg)
473                                                if air_dict['air_id'].lower() in pkg.lower():
474                                                        try:
475                                                                self._debug("Uninstalling %s"%pkg)
476                                                                sw_uninstall_err=subprocess.check_output(["apt-get","-y","remove",pkg],universal_newlines=True)
477                                                                self._debug("Uninstalled OK %s"%pkg)
478                                                                sw_err=0
479                                                        except Exception as e:
480                                                                self._debug(e)
481                                                        break
482                                except Exception as e:
483                                                sw_uninstall_err=True
484                               
485                                if sw_err:
486                                        try:
487                                                sw_uninstall_err=subprocess.check_output(["/usr/bin/Adobe AIR Application Installer","-silent","-uninstall","-location",self.adobeair_folder,air_dict['air_id']],env=my_env)
488                                                sw_err=0
489                                        except Exception as e:
490                                                self._debug(e)
491
492                if 'desktop' in air_dict.keys():
493                        if os.path.isfile(air_dict['desktop']):
494                                try:
495                                        os.remove(air_dict['desktop'])
496                                except Exception as e:
497                                        self._debug(e)
498                return sw_err
499
500        def get_air_info(self,air_file):
501                air_info={}
502                self._debug("Info for %s"%air_file)
503                tmpdir=self._unzip_air_file(air_file)
504                cwd=os.getcwd()
505                os.chdir(tmpdir+'/META-INF/AIR')
506                icon_line=''
507                name_line=''
508                if os.path.isfile('application.xml'):
509                        f=open('application.xml','r')
510                        flines=f.readlines()
511                        for fline in flines:
512                                fline=fline.strip()
513                                if fline.startswith('<filename>'):
514                                        name_line=fline
515                                if fline.startswith('<image48x48>'):
516                                        if fline!='<image48x48></image48x48>' and icon_line=='':
517                                                icon_line=fline
518                                                self._debug(fline)
519                        if icon_line:
520                                self._debug("ICON: %s"%icon_line)
521                                icon=icon_line.replace('<image48x48>','')
522                                icon=icon.replace('</image48x48>','')
523                                if icon!='':
524                                        icon=tmpdir+'/'+icon
525                                        air_info['pb']=GdkPixbuf.Pixbuf.new_from_file_at_scale(icon,64,-1,True)
526                        if name_line:
527                                name=name_line.replace('<filename>','')
528                                air_info['name']=name.replace('</filename>','')
529                else:
530                        air_info['name']=os.path.basename(air_file)
531                        air_info['name']=air_info['name'].replace('.air','')
532                        os.chdir(tmpdir)
533                        icon_files=glob("*/*48*png")
534                        if not icon_files:
535                                icon_files=glob("*48*png")
536                        if icon_files:
537                                air_info['pb']=GdkPixbuf.Pixbuf.new_from_file_at_scale(icon_files[0],64,-1,True)
538
539                return air_info
540        #def _get_air_info
541
542        def _check_file_is_air(self,air_file):
543                retval=False
544                if air_file.endswith(".air"):
545                        retval=True
546                return retval
547        #def _check_file_is_air
548               
Note: See TracBrowser for help on using the repository browser.