source: devtools/desktop_to_xml/desktop_to_xml.sh @ 5655

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

Added zmd suffix to the desktop name when parsing zomandos

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