001/*
002 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved.
003 * 
004 * http://www.izforge.com/izpack/
005 * http://developer.berlios.de/projects/izpack/
006 * 
007 * Copyright 2003 Marc Eppelmann
008 * 
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *     http://www.apache.org/licenses/LICENSE-2.0
014 *     
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 */
021
022/*
023 * This represents a Implementation of the KDE/GNOME DesktopEntry.
024 * which is standard from
025 * "Desktop Entry Standard"
026 *  "The format of .desktop files, supported by KDE and GNOME."
027 *  http://www.freedesktop.org/standards/desktop-entry-spec/
028 * 
029 *  [Desktop Entry]
030 //  Comment=$Comment
031 //  Comment[de]=
032 //  Encoding=$UTF-8
033 //  Exec=$'/home/marc/CPS/tomcat/bin/catalina.sh' run
034 //  GenericName=$
035 //  GenericName[de]=$
036 //  Icon=$inetd
037 //  MimeType=$
038 //  Name=$Start Tomcat
039 //  Name[de]=$Start Tomcat
040 //  Path=$/home/marc/CPS/tomcat/bin/
041 //  ServiceTypes=$
042 //  SwallowExec=$
043 //  SwallowTitle=$
044 //  Terminal=$true
045 //  TerminalOptions=$
046 //  Type=$Application
047 //  X-KDE-SubstituteUID=$false
048 //  X-KDE-Username=$
049 *
050 */
051package com.izforge.izpack.util.os;
052
053import java.io.BufferedReader;
054import java.io.BufferedWriter;
055import java.io.File;
056import java.io.FileReader;
057import java.io.FileWriter;
058import java.io.IOException;
059import java.util.Enumeration;
060import java.util.Properties;
061import java.util.StringTokenizer;
062import java.util.Vector;
063
064import com.izforge.izpack.util.FileExecutor;
065import com.izforge.izpack.util.OsVersion;
066import com.izforge.izpack.util.StringTool;
067
068/**
069 * This is the Implementation of the RFC-Based Desktop-Link. Used in KDE and GNOME.
070 * 
071 * @author marc.eppelmann@reddot.des
072 */
073public class Unix_Shortcut extends Shortcut implements Unix_ShortcutConstants
074{
075
076    //~ Static fields/initializers
077    // *******************************************************************************************************************************
078
079    /** version = "$Id: Unix_Shortcut.java,v 1.9 2005/07/27 08:57:07 jponge Exp $" */
080    private static String version = "$Id: Unix_Shortcut.java,v 1.9 2005/07/27 08:57:07 jponge Exp $";
081
082    /** rev = "$Revision: 1.9 $" */
083    private static String rev = "$Revision: 1.9 $";
084
085    /** DESKTOP_EXT = ".desktop" */
086    private static String DESKTOP_EXT = ".desktop";
087
088    /** template = "" */
089    private static String template = "";
090
091    /** N = "\n" */
092    private final static String N = "\n";
093
094    /** H = "#" */
095    private final static String H = "#";
096
097    /** S = " " */
098    private final static String S = " ";
099
100    /** C = Comment = H+S = "# " */
101    private final static String C = H + S;
102    
103    /** QM = "\"" : <b>Q</b>uotation<b>M</b>ark */
104    private final static String QM = "\"";
105
106    //~ Instance fields
107    // ******************************************************************************************************************************************
108
109    /** internal String createdDirectory */
110    private String createdDirectory;
111
112    /** internal int itsShow */
113    private int itsShow;
114
115    /** internal int itsUserType */
116    private int itsUserType;
117
118    /** internal int itsType */
119    private int itsType;
120
121    /** internal int itsIconIndex */
122    private int itsIconIndex;
123
124    /** internal String itsWorkingDirectory */
125    private String itsWorkingDirectory;
126
127    /** internal String itsGroupName */
128    private String itsGroupName;
129
130    /** internal String itsTargetPath */
131    private String itsTargetPath;
132
133    /** internal String itsIconPath */
134    private String itsIconPath;
135
136    /** internal String itsDescription */
137    private String itsDescription;
138
139    /** internal String itsArguments */
140    private String itsArguments;
141
142    /** internal String itsName */
143    private String itsName;
144
145    /** internal String itsFileName */
146    private String itsFileName;
147
148    /** internal String itsApplnkFolder = "applnk" */
149    private String itsApplnkFolder = "applnk";
150
151    /** internal Properties Set */
152    private Properties props;
153
154    /** forAll = new Boolean(false): A flag to indicate that this should created for all users. */
155    private Boolean forAll = new Boolean(false);
156
157    //~ Constructors
158    // *********************************************************************************************************************************************
159
160    /**
161     * Creates a new Unix_Shortcut object.
162     */
163    public Unix_Shortcut()
164    {
165        StringBuffer hlp = new StringBuffer();
166
167        String userLanguage = System.getProperty("user.language", "en");
168
169        hlp.append("[Desktop Entry]" + N);
170
171        hlp.append("Comment=" + $Comment + N);
172        hlp.append("Comment[" + userLanguage + "]=" + $Comment + N);
173        hlp.append("Encoding=" + $Encoding + N);
174        
175        hlp.append("Exec="+ $E_QUOT + $Exec + $E_QUOT + S + $Arguments + N);
176        hlp.append("GenericName=" + $GenericName + N);
177
178        hlp.append("GenericName[" + userLanguage + "]=" + $GenericName + N);
179        hlp.append("Icon=" + $Icon + N);
180        hlp.append("MimeType=" + $MimeType + N);
181        hlp.append("Name=" + $Name + N);
182        hlp.append("Name[" + userLanguage + "]=" + $Name + N);
183
184        hlp.append("Path="+ $P_QUOT + $Path + $P_QUOT + N);
185        hlp.append("ServiceTypes=" + $ServiceTypes + N);
186        hlp.append("SwallowExec=" + $SwallowExec + N);
187        hlp.append("SwallowTitle=" + $SwallowTitle + N);
188        hlp.append("Terminal=" + $Terminal + N);
189
190        hlp.append("TerminalOptions=" + $Options_For_Terminal + N);
191        hlp.append("Type=" + $Type + N);
192        hlp.append("URL=" + $URL + N);
193        hlp.append("X-KDE-SubstituteUID=" + $X_KDE_SubstituteUID + N);
194        hlp.append("X-KDE-Username=" + $X_KDE_Username + N);
195        hlp.append(N);
196        hlp.append(C + "created by" + S + getClass().getName() + S + rev + N );
197        hlp.append(C + version );
198
199        template = hlp.toString();
200
201        props = new Properties();
202
203        initProps();
204    }
205
206    //~ Methods
207    // **************************************************************************************************************************************************
208
209    /**
210     * This initialisizes all Properties Values with null.
211     */
212    private void initProps()
213    {
214        String[] propsArray = { $Comment, $$LANG_Comment, $Encoding, $Exec, $Arguments,
215                $GenericName, $$LANG_GenericName, $MimeType, $Name, $$LANG_Name, $Path,
216                $ServiceTypes, $SwallowExec, $SwallowTitle, $Terminal, $Options_For_Terminal,
217                $Type, $X_KDE_SubstituteUID, $X_KDE_Username, $Icon, $URL, $E_QUOT, $P_QUOT };
218
219        for (int i = 0; i < propsArray.length; i++)
220        {
221            props.put(propsArray[i], "");
222        }
223    }
224
225    /**
226     * Overridden Method
227     * 
228     * @see com.izforge.izpack.util.os.Shortcut#initialize(int, java.lang.String)
229     */
230    public void initialize(int aType, String aName) throws Exception
231    {
232        this.itsType = aType;
233        this.itsName = aName;
234        props.put($Name, aName);
235    }
236
237    /**
238     * This indicates that Unix will be supported.
239     * 
240     * @see com.izforge.izpack.util.os.Shortcut#supported()
241     */
242    public boolean supported()
243    {
244        return true;
245    }
246
247    /**
248     * Dummy
249     * 
250     * @see com.izforge.izpack.util.os.Shortcut#getDirectoryCreated()
251     */
252    public String getDirectoryCreated()
253    {
254        return this.createdDirectory; //while not stored...
255    }
256
257    /**
258     * Dummy
259     * 
260     * @see com.izforge.izpack.util.os.Shortcut#getFileName()
261     */
262    public String getFileName()
263    {
264        return (this.itsFileName);
265    }
266
267    /**
268     * Overridden compatibility method. Returns all directories in $USER/.kde/share/applink.
269     * 
270     * @see com.izforge.izpack.util.os.Shortcut#getProgramGroups(int)
271     */
272    public Vector getProgramGroups(int userType)
273    {
274        Vector groups = new Vector();
275
276        File kdeShareApplnk = getKdeShareApplnkFolder(userType);
277
278        try
279        {
280            File[] listing = kdeShareApplnk.listFiles();
281
282            for (int i = 0; i < listing.length; i++)
283            {
284                if (listing[i].isDirectory())
285                {
286                    groups.add(listing[i].getName());
287                }
288            }
289        }
290        catch (Exception e)
291        {
292            // ignore and return an empty vector.
293        }
294
295        return (groups);
296    }
297
298    /**
299     * Gets the Programsfolder for the given User (non-Javadoc).
300     * 
301     * @see com.izforge.izpack.util.os.Shortcut#getProgramsFolder(int)
302     */
303    public String getProgramsFolder(int current_user)
304    {
305        String result = new String();
306
307        // 
308        result = getKdeShareApplnkFolder(current_user).toString();
309
310        return result;
311    }
312
313    /**
314     * Gets the kde/share/applink - Folder for the given user and for the currently known and
315     * supported distribution.
316     * 
317     * @param userType to get for.
318     * 
319     * @return the users or the systems kde share/applink(-redhat/-mdk)
320     */
321    private File getKdeShareApplnkFolder(int userType)
322    {
323        File kdeBase = getKdeBase(userType);
324
325        File result = new File(kdeBase + File.separator + "share" + File.separator
326                + getKdeApplinkFolderName());
327
328        return result;
329    }
330
331    /**
332     * Gets the name of the applink folder for the currently used distribution. Currently
333     * "applnk-redhat for RedHat, "applnk-mdk" for Mandrake, and simply "applnk" for all others.
334     * 
335     * @return result
336     */
337    private String getKdeApplinkFolderName()
338    {
339        String applinkFolderName = "applnk";
340
341        if (OsVersion.IS_REDHAT_LINUX)
342        {
343            applinkFolderName = "applnk-redhat";
344        }
345
346        if (OsVersion.IS_MANDRAKE_LINUX)
347        {
348            applinkFolderName = "applnk-mdk";
349        }
350
351        return applinkFolderName;
352    }
353
354    /**
355     * Gets the KDEBasedir for the given User.
356     * 
357     * @param userType one of root or regular user
358     * 
359     * @return the basedir
360     */
361    private File getKdeBase(int userType)
362    {
363        File result = null;
364
365        if (userType == Shortcut.ALL_USERS)
366        {
367            FileExecutor fe = new FileExecutor();
368
369            String[] execOut = new String[2];
370
371            int execResult = fe.executeCommand(new String[] { "/usr/bin/env", "kde-config",
372                    "--prefix"}, execOut);
373
374            result = new File(execOut[0].trim());
375        }
376        else
377        {
378            result = new File(System.getProperty("user.home").toString() + File.separator + ".kde");
379        }
380        return result;
381    }
382
383    /**
384     * overridden method
385     * 
386     * @return true
387     * 
388     * @see com.izforge.izpack.util.os.Shortcut#multipleUsers()
389     */
390    public boolean multipleUsers()
391    {
392        // EVER true for UNIXes ;-)
393        return (true);
394    }
395
396    /**
397     * Creates and stores the shortcut-files.
398     * 
399     * @see com.izforge.izpack.util.os.Shortcut#save()
400     */
401    public void save() throws Exception
402    {
403        String FS = File.separator;
404        String target = null;
405
406        String shortCutDef = this.replace();
407
408        boolean rootUser4All = this.getUserType() == Shortcut.ALL_USERS;
409        boolean create4All = this.getCreateForAll().booleanValue();
410        
411        if ("".equals(this.itsGroupName) && this.getLinkType() == Shortcut.DESKTOP)
412        {
413            target = System.getProperty("user.home") + FS + "Desktop" + FS + this.itsName
414                    + DESKTOP_EXT;
415            this.itsFileName = target;
416
417            File source = writeShortCut(target, shortCutDef);
418
419            if (rootUser4All && create4All)
420            {
421                File dest = null;
422                File[] userHomesList = new File(FS + "home" + FS).listFiles();
423
424                File aHomePath = null;
425
426                if (userHomesList != null)
427                {
428                    for (int idx = 0; idx < userHomesList.length; idx++)
429                    {
430                        if (userHomesList[idx].isDirectory())
431                        {
432
433                            try
434                            {
435                                aHomePath = userHomesList[idx];
436                                dest = new File(aHomePath.toString() + FS + "Desktop" + FS
437                                        + source.getName());
438
439                                copyTo(source, dest);
440                            }
441                            catch (Exception rex)
442                            {
443                                /* ignore */// most distros does not allow root to access any user
444                                          // home (ls -la /home/user drwx------)
445                                // But try it anyway...
446                            }
447
448                            try
449                            {
450                                String[] output = new String[2];
451                                FileExecutor fe = new FileExecutor();
452                                int result = fe.executeCommand(new String[] { "/bin/chown",
453                                        aHomePath.getName(), aHomePath.toString()}, output);
454                                if (result != 0)
455                                {}
456                            }
457                            catch (RuntimeException rexx)
458                            {}
459                        }
460                    }
461                }
462            }
463        }
464        else
465        {
466            File kdeHomeShareApplnk = getKdeShareApplnkFolder(this.getUserType());
467            target = kdeHomeShareApplnk.toString() + FS + this.itsGroupName + FS + this.itsName
468                    + DESKTOP_EXT;
469            this.itsFileName = target;
470
471            if (rootUser4All && !create4All) { return; }
472            writeShortCut(target, shortCutDef);
473        }
474    }
475
476    /**
477     * Copies the inFile file to outFile using cbuff as buffer.
478     * 
479     * @param inFile The File to read from.
480     * @param outFile The targetFile to write to.
481     * 
482     * @throws IOException If an IO Error occurs
483     */
484    public static void copyTo(File inFile, File outFile) throws IOException
485    {
486        char[] cbuff = new char[32768];
487        BufferedReader reader = new BufferedReader(new FileReader(inFile));
488        BufferedWriter writer = new BufferedWriter(new FileWriter(outFile));
489
490        int readedBytes = 0;
491        long absWrittenBytes = 0;
492
493        while ((readedBytes = reader.read(cbuff, 0, cbuff.length)) != -1)
494        {
495            writer.write(cbuff, 0, readedBytes);
496            absWrittenBytes += readedBytes;
497        }
498
499        reader.close();
500        writer.close();
501    }
502
503    /**
504     * Writes the given Shortcutdefinition to the given Target.
505     * Returns the written File.  
506     * 
507     * @param target
508     * @param shortCutDef
509     * 
510     * @return the File of the written shortcut. 
511     */
512    private File writeShortCut(String target, String shortCutDef)
513    {
514        File targetPath = new File(target.toString().substring(0,
515                target.toString().lastIndexOf(File.separatorChar)));
516
517        if (!targetPath.exists())
518        {
519            targetPath.mkdirs();
520            this.createdDirectory = targetPath.toString();
521        }
522
523        File targetFileName = new File( target );
524        File backupFile = new File( targetPath + File.separator + "." + targetFileName.getName() + System.currentTimeMillis() );
525        if( targetFileName.exists() )
526        {
527          try
528          {
529            // create a hidden backup.file of the existing shortcut with a timestamp name.           
530            copyTo( targetFileName, backupFile  );// + System.e );
531            targetFileName.delete();
532          }
533          catch (IOException e3)
534          {
535            System.out.println("cannot create backup file " + backupFile + " of " + targetFileName );//  e3.printStackTrace();
536          }
537        }
538        FileWriter fileWriter = null;
539
540        try
541        {
542            fileWriter = new FileWriter( targetFileName );
543        }
544        catch (IOException e1)
545        {
546            System.out.println( e1.getMessage() );
547        }
548
549        try
550        {
551            fileWriter.write(shortCutDef);
552        }
553        catch (IOException e)
554        {
555            e.printStackTrace();
556        }
557
558        try
559        {
560            fileWriter.close();
561        }
562        catch (IOException e2)
563        {
564            e2.printStackTrace();
565        }
566
567        return targetFileName;
568    }
569
570    /**
571     * Set the Commandline Arguments
572     * 
573     * @see com.izforge.izpack.util.os.Shortcut#setArguments(java.lang.String)
574     */
575    public void setArguments(String args)
576    {
577        this.itsArguments = args;
578        props.put($Arguments, args);
579    }
580
581    /**
582     * Sets the Description
583     * 
584     * @see com.izforge.izpack.util.os.Shortcut#setDescription(java.lang.String)
585     */
586    public void setDescription(String description)
587    {
588        this.itsDescription = description;
589        props.put($Comment, description);
590    }
591
592    /**
593     * Sets The Icon Path
594     * 
595     * @see com.izforge.izpack.util.os.Shortcut#setIconLocation(java.lang.String, int)
596     */
597    public void setIconLocation(String path, int index)
598    {
599        this.itsIconPath = path;
600        this.itsIconIndex = index;
601        props.put($Icon, path);
602
603        //
604    }
605
606    /**
607     * Sets the Name of this Shortcut
608     * 
609     * @see com.izforge.izpack.util.os.Shortcut#setLinkName(java.lang.String)
610     */
611    public void setLinkName(String aName)
612    {
613        this.itsName = aName;
614        props.put($Name, aName);
615    }
616
617    /**
618     * Sets the type of this Shortcut
619     * 
620     * @see com.izforge.izpack.util.os.Shortcut#setLinkType(int)
621     */
622    public void setLinkType(int aType) throws IllegalArgumentException
623    {
624        this.itsType = aType;
625    }
626
627    /**
628     * Sets the ProgramGroup
629     * 
630     * @see com.izforge.izpack.util.os.Shortcut#setProgramGroup(java.lang.String)
631     */
632    public void setProgramGroup(String aGroupName)
633    {
634        this.itsGroupName = aGroupName;
635    }
636
637    /**
638     * Sets the ShowMode
639     * 
640     * @see com.izforge.izpack.util.os.Shortcut#setShowCommand(int)
641     */
642    public void setShowCommand(int show)
643    {
644        this.itsShow = show;
645    }
646
647    /**
648     * Sets The TargetPath
649     * 
650     * @see com.izforge.izpack.util.os.Shortcut#setTargetPath(java.lang.String)
651     */
652    public void setTargetPath(String aPath)
653    {
654        this.itsTargetPath = aPath;
655        
656        StringTokenizer whiteSpaceTester = new StringTokenizer( aPath );
657        
658        if( whiteSpaceTester.countTokens() > 1 )
659          props.put( $E_QUOT,QM );
660
661        props.put($Exec, aPath);
662    }
663
664    /**
665     * Sets the usertype.
666     * 
667     * @see com.izforge.izpack.util.os.Shortcut#setUserType(int)
668     */
669    public void setUserType(int aUserType)
670    {
671        this.itsUserType = aUserType;
672    }
673
674    /**
675     * Sets the working-directory
676     * 
677     * @see com.izforge.izpack.util.os.Shortcut#setWorkingDirectory(java.lang.String)
678     */
679    public void setWorkingDirectory(String aDirectory)
680    {
681        this.itsWorkingDirectory = aDirectory;
682        
683        StringTokenizer whiteSpaceTester = new StringTokenizer( aDirectory );
684        
685        if( whiteSpaceTester.countTokens() > 1 )
686          props.put( $P_QUOT,QM );
687
688        props.put($Path, aDirectory);
689    }
690
691    /**
692     * Dumps the Name to console.
693     * 
694     * @see java.lang.Object#toString()
695     */
696    public String toString()
697    {
698        return this.itsName + N + template;
699    }
700
701    /**
702     * Creates the Shortcut String which will be stored as File.
703     * 
704     * @return contents of the shortcut file
705     */
706    public String replace()
707    {
708        String result = template;
709        Enumeration enumeration = props.keys();
710
711        while (enumeration.hasMoreElements())
712        {
713            String key = (String) enumeration.nextElement();
714
715            result = StringTool.replace(result, key, props.getProperty(key));
716        }
717
718        return result;
719    }
720
721    /**
722     * Test Method
723     * 
724     * @param args
725     */
726    public static void main(String[] args)
727    {
728        Unix_Shortcut aSample = new Unix_Shortcut();
729
730        try
731        {
732            aSample.initialize(APPLICATIONS, "Start Tomcat");
733        }
734        catch (Exception exc)
735        {
736            System.err.println("Could not init Unix_Shourtcut");
737        }
738
739        aSample.replace();
740        System.out.println(aSample);
741
742        File targetFileName = new File(System.getProperty("user.home") + File.separator
743                + "Start Tomcat" + DESKTOP_EXT);
744        FileWriter fileWriter = null;
745
746        try
747        {
748            fileWriter = new FileWriter(targetFileName);
749        }
750        catch (IOException e1)
751        {
752            e1.printStackTrace();
753        }
754
755        try
756        {
757            fileWriter.write(template);
758        }
759        catch (IOException e)
760        {
761            e.printStackTrace();
762        }
763
764        try
765        {
766            fileWriter.close();
767        }
768        catch (IOException e2)
769        {
770            e2.printStackTrace();
771        }
772    }
773
774    /**
775     * Sets The Encoding
776     * 
777     * @see com.izforge.izpack.util.os.Shortcut#setEncoding(java.lang.String)
778     */
779    public void setEncoding(String aEncoding)
780    {
781        props.put($Encoding, aEncoding);
782    }
783
784    /**
785     * Sets The KDE Specific subst UID property
786     * 
787     * @see com.izforge.izpack.util.os.Shortcut#setKdeSubstUID(java.lang.String)
788     */
789    public void setKdeSubstUID(String aKDESubstUID)
790    {
791        props.put($X_KDE_SubstituteUID, aKDESubstUID);
792    }
793
794    /**
795     * Sets the MimeType
796     * 
797     * @see com.izforge.izpack.util.os.Shortcut#setMimetype(java.lang.String)
798     */
799    public void setMimetype(String aMimetype)
800    {
801        props.put($MimeType, aMimetype);
802    }
803
804    /**
805     * Sets the terminal
806     * 
807     * @see com.izforge.izpack.util.os.Shortcut#setTerminal(java.lang.String)
808     */
809    public void setTerminal(String trueFalseOrNothing)
810    {
811        props.put($Terminal, trueFalseOrNothing);
812    }
813
814    /**
815     * Sets the terminal options
816     * 
817     * @see com.izforge.izpack.util.os.Shortcut#setTerminalOptions(java.lang.String)
818     */
819    public void setTerminalOptions(String someTerminalOptions)
820    {
821        props.put($Options_For_Terminal, someTerminalOptions);
822    }
823
824    /**
825     * Sets the Shortcut type (one of Application, Link or Device)
826     * 
827     * @see com.izforge.izpack.util.os.Shortcut#setType(java.lang.String)
828     */
829    public void setType(String aType)
830    {
831        props.put($Type, aType);
832    }
833
834    /**
835     * Sets the Url for type Link. Can be also a apsolute file/path
836     * 
837     * @see com.izforge.izpack.util.os.Shortcut#setURL(java.lang.String)
838     */
839    public void setURL(String anUrl)
840    {
841        props.put($URL, anUrl);
842    }
843
844    /**
845     * Gets the Usertype of the Shortcut.
846     * 
847     * @see com.izforge.izpack.util.os.Shortcut#getUserType()
848     */
849    public int getUserType()
850    {
851        return itsUserType;
852    }
853}