source: devtools/desktop_to_xml/desktop_to_xml.sh @ 4967

Last change on this file since 4967 was 4967, checked in by Juanma, 3 years ago

addedf Lliurex as category when parsing zomandos

  • Property svn:executable set to *
File size: 17.4 KB
Line 
1#!/bin/bash
2#Script that generates xml info file from a desktop file
3#Usage:
4#Download lliurex-artwork-default from svn
5#Download the sources of the package(s)
6#Execute this script passing the dirs of sources to analyze as parameters
7#Profit
8
9msg_need_svn="In order to add the right icons you need to download lliurex-artwork-default from svn to the same dir as the source packages"
10msg_dir_not_found="dir not found"
11msg_icons_dir_error="Can't access %s or %s.\nSome icons may be missing.\n"
12msg_select_file="Select one install file:\n"
13msg_selected_file="\nSelected file: %s\n\n"
14msg_select_dir="\nSelect the dir containing the desktop file:\n"
15msg_gen_metainfo="Generating metainfo data path %s\n"
16msg_parsing_desktop="Parsing %s\n"
17msg_metainfo_generated="\nMetainfo file generated.\n"
18msg_desktop_updated="\nDesktop file updated.\n"
19msg_icon_found="Icon %s found.\nGenerating llx-resources dir\n"
20msg_icon_not_found="\n********** WARNING!!!! **********\nNo icon named %s found on dirs %s, %s or %s\nRemember that the icon must be in SVG format and no in PNG or other image format\n *************************** \n"
21msg_icon_location_info="\nPlease add %s as svg to %s and relaunch this program or use the llxsrc debhelper. Until then the app will not be included in our software store.\n"
22msg_icon_exists="\nIcon package found on the right location. Checking if llxsrc helper exists in rules\n"
23msg_work_done="\n----------\n\nWork done. If the process has no errors please check that the appfile.xml is right and that the rules file has the llxsrc helper. Also check that the icon is present in llx-resources.\nIn other case correct the errors and relaunch the script.\n\nRemember that the generated appfile.xml isn't a full one and is missing some fields like the screenshots. Take a cup of coffee and fulfill the empty fields following the specs at https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Application.html\n\n"
24msg_install_not_found="Install file not found in debian dir. Aborting.\n"
25msg_select_workdir="Select the workdir\n"
26msg_debhelper_enabled="package type llxsrc detected. Setting llx-resources as workdir\n"
27msg_rules_old_format="\n********** WARNING!!!! **********\nrules file has an old format.\nIt's HIGHLY recommended to update it to the new rules format.\n *************************** \n"
28msg_select_pkg="\nSelect the name of the package to process\n"
29msg_desktop_not_found="\nDesktop file not found at %s. Looking at llx-resources\n"
30msg_desktop_not_present="\nDesktop file was not found at %s. Operation aborted\n"
31msg_searching_img="\nSearching screenshots in %s for %s\n"
32msg_image_found="\nScreenshoot %s found for app %s\n"
33function usage_help
34{
35        printf "Usage:\n"
36        printf "$0 [svn_package_dir1] [svn_package_dir2]\n\n"
37        printf "$msg_need_svn\n\n"
38        exit 1
39}
40
41function analyze_zmd_dir
42{
43        printf "Analyzing $1\n"
44        if [ ! -d $lliurexArtworkDir ] || [ ! -d $vibrancyArtworkDir ]
45        then
46                printf "$msg_icons_dir_error" $lliurexArtworkDir $vibrancyArtworkDir
47        fi
48        wrkDir=$rootDir"/"$srcDir
49        cd $wrkDir
50        baseMetainfoDir=${wrkDir}"/llx-resources/"
51        zmdFiles=$(find . -name "*.app")
52        for i in ${zmdFiles}
53        do
54                zmdName=$(basename $i)
55                zmdName=${zmdName/.app/}
56                zmdDir=$(dirname $i)
57                cd $zmdDir
58                metainfoDir=${baseMetainfoDir}"/"${zmdName}"/metainfo"
59                fakeDesktopDir=${baseMetainfoDir}"/"${zmdName}"/desktops"
60                mkdir $metainfoDir -p
61                mkdir $fakeDesktopDir -p
62                fakeDesktop=${fakeDesktopDir}"/"${zmdName}".desktop"
63                no_copy_desktop=1
64                sw_zomando=1
65                parse_desktop $metainfoDir $fakeDesktop ${zmdName}".app"
66                cd $wrkDir
67        done
68        add_llxsrc_helper
69}
70
71function analyze_desktop_dir
72{
73        printf "Analyzing $1\n"
74        if [ ! -d $lliurexArtworkDir ] || [ ! -d $vibrancyArtworkDir ]
75        then
76                printf "$msg_icons_dir_error" $lliurexArtworkDir $vibrancyArtworkDir
77        fi
78       
79        cd $debianDir
80        #Set the package name
81        set_appName
82        #Get the install file. If there're many choose one...
83        declare -a installFiles
84        count=0
85        for i in *install
86        do
87                installFiles[$count]=$i
88                let count++
89        done
90        index=0
91        let count--
92        if [[ ${installFiles[0]} = '*install' ]]
93        then
94                printf "$msg_install_not_found"
95                get_workdir
96        else
97                if [ $count -gt 0 ]
98                then
99                        printf "$msg_select_file"
100                        for i in `seq 0 ${count}`
101                        do
102                                printf "${i}) ${installFiles[$i]}\n"
103                        done
104                        printf "Select file [0]: "
105                        read index
106                        [ -z $index ] && index=0
107                        installFile=${installFiles[$index]}
108                else
109                        installFile=${installFiles[0]}
110                fi
111                printf "$msg_selected_file" $installFile
112                process_installFile ${installFile}
113        fi
114        add_llxsrc_helper
115}
116
117function set_appName
118{
119        appName=$(basename $pkgDir)
120        declare -a pkgsArray
121        count=0
122        for pkg in `grep "Package:\ .*" control | cut -f2 -d ' '`
123        do
124                pkgsArray[$count]=$pkg
125                let count++
126        done
127        let count--
128        if [ $count -gt 0 ]
129        then
130                printf "$msg_select_pkg"
131                for i in `seq 0 ${count}`
132                do
133                        printf "$i) ${pkgsArray[$i]}\n"
134                done
135                printf "Select pkg name [0]: "
136                read index
137                [ -z $index ] && index=0
138                appName=${pkgsArray[$index]}
139        else
140                installFile=${pkgsArray[0]}
141        fi
142}
143
144function get_workdir
145{
146                cd $rootDir
147                cd $srcDir
148                if [ -d llx-resources ]
149                then
150                        printf "$msg_debhelper_enabled"
151                        installDir="llx-resources"
152                else
153                        printf "$msg_select_workdir"
154                        count=0
155                        declare -a dirArray
156                        for directory in *
157                        do
158                                dirArray[$count]=$directory
159                                let count++
160                        done
161                        printf "Selected Dir [0]: "
162                        read index
163                        installDir=${dirArray[$index]}
164                fi
165                process_pkg $installDir
166}
167
168function process_installFile
169{
170        installFile=$1
171        if [ `wc -l $installFile  | cut -f1 -d ' '` -gt 1 ]
172        then 
173                printf "$msg_select_dir"
174                count=0
175                declare -a fileLines
176                while read -r dirLine
177                do
178                        fileLines[$count]=$dirLine
179                        printf "$count) $dirLine\n"
180                        let count++
181                done < ${installFile}
182                printf "Selected file [0]: "
183                read index
184                installDir=${fileLines[$index]}
185        else 
186                installDir=`head -n1 $installFile`
187        fi
188        [ -z $index ] && index=0
189        installDir=${installDir/\**/}
190        [ -z "$installDir" ] && installDir='.'
191        process_pkg $installDir
192}
193
194function process_pkg
195{
196        cd $rootDir"/"$srcDir
197        wrkDir=`realpath $1 2>/dev/null`
198        if [ $? -ne 0 ]
199        then
200                #Dir doesn't seems to exists. Find the desktop on $svnDir and ask about the action
201                wrkDir='.'
202                installDir='.'
203        fi
204        printf "Entering %s\n" $wrkDir
205        if [ -d $wrkDir ]
206        then
207                cd $wrkDir
208        else
209                wrkDir=`dirname $wrkDir`
210                installDir=$wrkDir
211                cd $wrkDir
212        fi
213        #Find the desktop file of the application
214        desktopFiles=$(find . -name "*.desktop")
215        if [[ ! $desktopFiles ]]
216        then
217                printf "$msg_desktop_not_found" $wrkDir
218                cd $rootDir"/"$srcDir
219                wrkDir='.'
220                installDir='.'
221                desktopFiles=$(find . -name "*.desktop")
222                if [[ ! $desktopFiles ]]
223                then
224                        printf "$msg_desktop_not_present" $srcDir
225                        exit 1
226                fi
227        fi
228        for desktopFile in $desktopFiles
229        do
230                cd $rootDir"/"$srcDir
231                printf "Entering $wrkDir\n"
232                cd $wrkDir
233                #If workdir != $1 then it means that the install file has a file and not a dir
234                #In this case we assume that the install is putting the desktop file so metainfoDir becames llx-resources directly
235                if [[ $wrkDir != `realpath $1` ]]
236                then
237                        metainfoDir=${rootDir}"/"${srcDir}"/llx-resources/"${appName}
238                else
239                        metainfoDir=`dirname $desktopFile`
240                        metainfoDir=`dirname $metainfoDir`
241                fi
242                fakeDesktop=""
243                metainfoDir=$metainfoDir"/metainfo"
244                printf "$msg_gen_metainfo" $metainfoDir
245                mkdir $metainfoDir -p
246                parse_desktop $metainfoDir $fakeDesktop $desktopFile 
247                iconName=$(grep ^Icon= $desktopFile)
248                iconName=${iconName/*=/}
249                iconName=${iconName/.*/}
250                get_icon $iconName
251        done
252}
253
254function parse_desktop
255{
256        metainfoDir=$1
257        shift
258        fakeDesktop=$1
259        shift
260        for desktopFile in $@
261        do
262                printf "$msg_parsing_desktop" $desktopFile
263                item=`basename $desktopFile`
264                get_screenshot $item
265                if [ $sw_zomando -eq 1 ]
266                then
267                #generate a fake desktop
268                        printf "generating fake desktop %s for zmd" $fakeDesktop
269                fi
270
271                awk -v processFile=$item -v metainfoDir=$metainfoDir -v screenshot=$imageFound -v zomando=$sw_zomando -v fakeDesktop=$fakeDesktop -F '=' '
272                BEGIN{
273                        split(processFile,array,".",seps)
274        #               revDomainName="net.lliurex."array[1]
275                        outFile="/tmp/"processFile
276                        printf("") > outFile
277                        xmlFile=metainfoDir"/"array[1]".appdata.xml"
278                        tagId="<id>"array[1]"</id>"
279                        split(array[1],arrayKey,"-",seps)
280                        for (nameIndex in arrayKey)
281                        {
282                                if (length(arrayKey[nameIndex])>=3)
283                                {
284                                        if (tagKeywords!~">"arrayKey[nameIndex]"<")
285                                                tagKeywords=tagKeywords"<keyword>"arrayKey[nameIndex]"</keyword>\n";
286                                }
287                        }
288                        execPrinted=0
289                        commentArrayIndex=1
290                        nameArrayIndex=1
291                        noGenerate=0
292                        process=1
293                        if (zomando==1)
294                        {
295                                print "[Desktop Entry]">fakeDesktop
296                                print "Type=zomando">>fakeDesktop
297                                fakeIcon=fakeDesktop
298                                gsub(".desktop", ".png", fakeIcon)
299                                n=split(fakeIcon,array,"/")
300                                print "Icon=/usr/share/banners/lliurex-neu/"array[n] >> fakeDesktop
301                                print "NoDisplay=true" >> fakeDesktop
302
303                        }
304                }
305                {
306                        if ($0~/^\[.*\]/)
307                        {
308                                if ($0~/\[Desktop.*/)
309                                {
310                                        process=1;
311                                } else {
312                                        process=0;
313                                }               
314                        }
315                       
316                        if (process==1)
317                        {
318                                if ($1~/^Name/)
319                                {
320                                        if ($1=="Name")
321                                        {
322                                                tagName="<name>"
323                                                lang=""
324                                                if (zomando==1)
325                                                {
326                                                        print $0>>fakeDesktop
327                                                }
328                                        } else {
329                                                lang=$1
330                                                split(lang,array,"[",seps)
331                                                lang=substr(array[2], 1, length(array[2])-1)
332                                                tagName="<name xml:lang=\""lang"\">"
333                                        }
334                                        tagName=tagName""$2"</name>"
335                                        nameArray[nameArrayIndex]=tagName
336                                        nameArrayIndex++;
337                                        split($2,array," ",seps)
338                                        if ( lang != "")
339                                        {
340                                                tagKeywords=tagKeywords"</keywords>\n<keywords xml:lang=\""lang"\">\n";
341                                        }
342                                        for (nameIndex in array)
343                                        {
344                                                if (length(array[nameIndex])>=3)
345                                                {
346                                                        if (tagKeywords!~">"array[nameIndex]"<")
347                                                                tagKeywords=tagKeywords"<keyword>"array[nameIndex]"</keyword>\n";
348                                                }
349                                        }
350                                        if (zomando)
351                                                tagKeywords=tagKeywords"<keyword>Zomando</keyword>\n";
352                                } else if ($1~"Comment") {
353                                        if ($1=="Comment")
354                                        {
355                                                tagSum="<summary>"
356                                                tagDes="<p>"
357                                        } else {
358                                                lang=$1
359                                                split(lang,array,"[",seps)
360                                                lang=substr(array[2], 1, length(array[2])-1)
361                                                tagSum="<summary xml:lang=\""lang"\">"
362                                                tagDes="<p xml:lang=\""lang"\">"
363                                        }
364                                        sumario=$2
365                                        split(sumario,array,".",seps)
366                                        $0=$1"="array[1]
367                                        summaryArray[commentArrayIndex]=tagSum""array[1]"</summary>"
368                                        descriptionArray[commentArrayIndex]=tagDes""sumario"</p>"
369                                        commentArrayIndex++
370                                } else if ($1=="Categories") {
371                                        if (zomando==1)
372                                        {
373                                                print $0>>fakeDesktop
374                                        }
375                                        customCat=0
376                                        countCat=0
377                                        split($2,array,";",seps)
378                                        for (catIndex in array)
379                                        {
380                                                if (array[catIndex]!="")
381                                                {
382                                                        if (array[catIndex]~"-" && array[catIndex]!~"^X-")
383                                                        {
384                                                                customCat=1
385                                                                split(array[catIndex],lliurexCats,"-",seps)
386                                                                for (lliurexCatIndex in lliurexCats)
387                                                                {
388                                                                        lliurexCat="<category>"lliurexCats[lliurexCatIndex]"</category>\n"lliurexCat
389                                                                }
390                                                                categoryArray[catIndex]=lliurexCat
391                                                        } else {
392                                                                categoryArray[catIndex]="<category>"array[catIndex]"</category>"
393                                                        }
394                                                        countCat++
395                                                }
396                                        }
397
398
399                                        if (customCat==1 && countCat==1)
400                                        {
401                                                if (substr($0,length($0),1)==";")
402                                                        $0=$0"GTK"
403                                                else
404                                                        $0=$0;"GTK"
405                                                catIndex++
406                                        }
407                                } else  if ($1=="Icon") {
408                                        arrayItemNames=split($2,array,"/",seps)
409                                        arrayItems=split(array[arrayItemNames],array,".",seps)
410                                        iconBaseName=array[1]
411                                        $0=$1"="iconBaseName
412                                        if (zomando)
413                                        {
414                                                sub("zero-lliurex-", "",iconBaseName);
415                                                tagIcon="<icon type=\"cached\">"iconBaseName"</icon>";
416                                        } else {
417                                                tagIcon="<icon type=\"stock\">"iconBaseName"</icon>";
418                                        }
419                                } else if ($1=="Exec") {
420                                        if (execPrinted==0)
421                                        {
422                                                split($2,array," ",seps)
423                                                tagExec="<provides><binary>"array[1]"</binary></provides>"
424                                                execPrinted=1
425                                        }
426                                } else if ($1=="NoDisplay" || $1=="Terminal") {
427                                        if ($2 == "true" || $2=="TRUE" || $2=="True")
428                                        {
429                                                noGenerate=1
430                                        }
431                                }
432                        }
433                        print $0>>outFile
434                }
435                END{
436                        if (noGenerate==1)
437                        {
438                                print xmlFile" not generated as is a terminal app"
439                                exit 1
440                        }
441                        print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" > xmlFile
442                        print "<component type=\"desktop-application\">" >> xmlFile
443                        print tagId >> xmlFile
444                        print "<metadata_license>CC0-1.0</metadata_license>" >> xmlFile
445                        for (nameIndex in nameArray)
446                                print nameArray[nameIndex] >> xmlFile
447                        for (summaryIndex in summaryArray)
448                                print summaryArray[summaryIndex] >> xmlFile;
449                        print "<description>" >> xmlFile
450                        for (descriptionIndex in descriptionArray)
451                                print descriptionArray[descriptionIndex] >> xmlFile;
452                        print "</description>" >> xmlFile
453                        print "<categories>" >> xmlFile
454                        for (categoryIndex in categoryArray)
455                        {
456                                print categoryArray[categoryIndex] >> xmlFile;
457                        }
458                        if (zomando)
459                        {
460                                print "<category>Lliurex</category>" >> xmlFile
461                                print "<category>Zomando</category>" >> xmlFile
462                        }
463                        if (categoryIndex==0)
464                                print "<category>Utility</category>" >> xmlFile
465                        print "</categories>" >> xmlFile
466                        print tagIcon >> xmlFile
467                        print tagExec >> xmlFile
468                        print "<keywords>" >> xmlFile
469                        print tagKeywords >> xmlFile
470                        print "</keywords>" >> xmlFile
471                        if ( screenshot != 0 )
472                        {
473                                print "<screenshots>" >> xmlFile
474                                print "<screenshot type=\"default\">" >> xmlFile
475                                print "<caption>Main window.</caption>" >> xmlFile
476                                print "<image type=\"source\" width=\"800\" height=\"450\">"screenshot"</image>" >> xmlFile
477                                print "</screenshot>" >> xmlFile
478                                print "</screenshots>" >> xmlFile
479                        }
480#                       print "<developer_name>Lliurex Team</developer_name>" >> xmlFile
481                        print "</component>" >> xmlFile
482                }
483                ' $desktopFile
484                [ $? -eq 0 ] && printf "$msg_metainfo_generated"
485                if [ -z $no_copy_desktop ]
486                then
487                        cp /tmp/${item} $desktopFile
488                        printf "$msg_desktop_updated"
489                fi
490        done
491}
492
493function get_screenshot
494{
495        printf "$msg_searching_img" "debian" $1
496        imageFound=0
497        if [ $sw_zomando -ne 1 ]
498        then
499                pkgName=${1/.desktop/}
500        else
501                pkgName=${1/.app/}
502                pkgName=${pkgName/*-/}
503        fi
504        url="https://screenshots.debian.net/packages?page=1&search=${pkgName}&utf8=✓"
505        get_screenshot_from $url $pkgName
506#       if [[ $imageFound == 0 ]]
507#       then
508#               printf "$msg_searching_img" "ubuntu"
509#               url="http://screenshots.ubuntu.com/packages?page=1&search=${pkgName}&utf8=✓"
510#               get_screenshot_from $url $pkgName
511#       fi
512        if [[ $imageFound ==  'https://screenshots.debian.net/' || $imageFound == ' https://screenshots.ubuntu.com/' ]]
513        then
514                printf "Discarting image...\n"
515                imageFound=0
516        fi
517
518}
519
520function get_screenshot_from
521{
522        url=$1
523        baseUrl=${url/packages*/}
524        pkgName=$2
525        outFile=$(mktemp)
526        wget $url -t 2 -T 10 -o /dev/null -O $outFile
527        if [ $? -eq 0 ]
528        then
529                searchResult=$(grep  -P -o  "href=\"\/package\/".*?\" $outFile | head -n1)
530                if [ $searchResult ]
531                then
532                        searchResult=${searchResult/href=\"\//}
533                        searchResult=${searchResult/\"/}
534                        url=${baseUrl}${searchResult}
535                        wget $url -t 2 -T 10 -o /tmp/wget_desktop.log -O $outFile
536                        if [ $? -eq 0 ]
537                        then
538                                imageFound=$(grep  -P -o  "href=\"\/screenshots\/".*?\" $outFile | head -n1)
539                                imageFound=${imageFound/href=\"\//}
540                                imageFound=${imageFound/\"/}
541                                imageFound=${baseUrl}${imageFound}
542                                printf "$msg_image_found" $imageFound $pkgName
543                        fi
544                fi
545        fi
546}
547
548function get_icon
549{
550        [ -z ${1} ] && echo "No icon selected" && return
551        iconName=$(basename $1)
552        #Check if a svg icon exists
553        workDir=$PWD
554        printf "\nSearching for icon ${iconName}.svg in our package\n"
555        #First we'll check the dir for resources
556        resourcesDir=llx-resources/
557        cd $rootDir
558        cd $srcDir
559        iconFile=$(find ${resourcesDir} -name "$iconName.svg" | grep icons)
560        if [[ ! $iconFile ]]
561        then
562                cd $workDir
563                printf "Entering $workDir\n"
564                iconPath=${srcDir}"/"${installDir}
565                iconFile=$(find . -name "$iconName.svg")
566                if [[ ! $iconFile ]]
567                then
568                        printf "Accesing %s" `realpath $lliurexArtworkDir`
569                        #Look for the svg in lliurex-theme
570                        cd $rootDir
571                        iconPath=$lliurexArtworkDir
572                        cd $lliurexArtworkDir
573                        iconFile=$(find . -name "${iconName}.svg")
574                fi
575                if [[ $iconFile ]]
576                then
577                        iconFile=$(realpath $iconFile)
578                        cd $rootDir
579                        printf "$msg_icon_found" $iconFile
580                        cd $srcDir
581                        resourcesDir="llx-resources/"${appName}"/icons/apps/"
582                        mkdir $resourcesDir -p
583                        cd $OLDPWD
584                        cp ${iconFile} ${srcDir}"/"${resourcesDir}
585                        cd $OLDPWD
586                else
587                        printf "$msg_icon_not_found" ${iconName} "$lliurexArtworkDir" "$resourcesDir" "$installDir"
588                        printf "$msg_icon_location_info" ${iconName} "$lliurexArtworkDir" 
589                fi
590        else
591                printf "$msg_icon_exists"
592        fi
593}
594
595function add_llxsrc_helper
596{
597        cd $rootDir
598        cd $debianDir
599        printf "Adding llxsrc to rules"
600        if [[ ! `grep 'llxsrc' rules` ]]
601        then
602                if [[ `grep 'dh \$@' rules` ]]
603                then
604                        if [[ `grep '\-\-with' rules` ]]
605                        then 
606                                sed -i 's/\(dh $@\ --with\ \)/\0llxsrc,/' rules
607                        else
608                                sed -i 's/\(dh $@\ \)/\0 --with\ llxsrc\ /' rules
609                        fi
610                else
611                        printf "$msg_rules_old_format"
612                        sed -i 's/\tdh_installdeb/\tdh_llxsrcinstall\n\tdh_installdeb/' rules
613                fi
614        fi
615
616        if [[ ! `grep 'llxsrchelper' control` ]]
617        then
618                sed -i 's/\(Build-Depends:\ \)/\0llxsrchelper,/' control
619        fi
620}
621
622function get_package_type
623{
624        cd $srcDir
625        ls *zmds 1>/dev/null 2>&1
626        if [ $? -eq 0 ]
627        then
628                echo "deb"
629        else
630                echo "zmd"
631        fi
632}
633
634### MAIN PROGRAM ###
635
636[[ $@ ]] || usage_help
637
638for parm in $@
639do
640        if [ ! -d $parm ]
641        then
642                printf "\n${parm}: $msg_dir_not_found\n"
643                usage_help
644                exit 1
645        fi
646done
647
648launchDir=`realpath $PWD`
649
650for parm in $@
651do
652        cd $launchDir
653        rootDir=$launchDir
654        svnDir=`realpath $parm`
655        pkgDir=`realpath $parm`
656        srcDir=${parm}"/trunk/fuentes"
657        debianDir=${parm}"/trunk/fuentes/debian"
658        lliurexArtworkDir=${svnDir}"/../vibrancy-colors/trunk/fuentes/vibrancy-lliurex/apps"
659        no_copy_desktop=""
660        sw_zomando=0
661        get_package_type == 'zmd'  && analyze_zmd_dir $parm || analyze_desktop_dir $parm
662done
663
664printf "$msg_work_done"
665
666exit 0
667
Note: See TracBrowser for help on using the repository browser.