001/*
002 * Copyright (c) 2004-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: welker $'
006 * '$Date: 2010-05-06 05:21:26 +0000 (Thu, 06 May 2010) $' 
007 * '$Revision: 24234 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.actor.io;
031
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.kepler.io.DirectoryListing;
035import org.kepler.io.FileInfo;
036import org.kepler.ssh.ExecException;
037
038import ptolemy.actor.TypedAtomicActor;
039import ptolemy.actor.TypedIOPort;
040import ptolemy.actor.parameters.PortParameter;
041import ptolemy.data.ArrayToken;
042import ptolemy.data.BooleanToken;
043import ptolemy.data.LongToken;
044import ptolemy.data.RecordToken;
045import ptolemy.data.StringToken;
046import ptolemy.data.Token;
047import ptolemy.data.expr.Parameter;
048import ptolemy.data.type.ArrayType;
049import ptolemy.data.type.BaseType;
050import ptolemy.data.type.RecordType;
051import ptolemy.data.type.Type;
052import ptolemy.kernel.CompositeEntity;
053import ptolemy.kernel.util.IllegalActionException;
054import ptolemy.kernel.util.NameDuplicationException;
055
056//////////////////////////////////////////////////////////////////////////
057//// SshDirectoryList
058
059/**
060 * <p>
061 * <b>Obsolete actor.</b> Use SshDirectoryList-v1.1 instead.
062 * </p>
063 * <p>
064 * Actor for directory listing.
065 * <ul>
066 * <li>local or remote dir (using ssh)</li>
067 * <li>get new files from previous call</li>
068 * </ul>
069 * 
070 * </p>
071 * <p>
072 * This actor uses org.kepler.io.DirectoryListing class to get file listing
073 * 
074 * </p>
075 * <p>
076 * The initial input should be:
077 * <ul>
078 * <li>the target machine, either null, "" or "local" to denote the local
079 * machine to be used by Java I/O commands, OR "[user@]host[:port]" to denote a
080 * remote machine to be used by an ssh connection.</li>
081 * <li>the directory to be listed as String</li>
082 * <li>the file mask for listing files according to the pattern only, "" means
083 * all files</li>
084 * </ul>
085 * 
086 * </p>
087 * <p>
088 * The actor takes the target and directory arguments only once! After that it
089 * works with that setting at each fire. The mask can be reset to something else
090 * for each firing but in such a case, the previous listings are deleted (as if
091 * starting fresh).
092 * 
093 * </p>
094 * <p>
095 * Depending on the boolean parameter 'newFilesOnly' there are two different
096 * behavior: <br/>
097 * If 'newFilesOnly' is set (default), for each trigger, the file list is
098 * updated, and the array of 'new' files will be the output. 'New' means the
099 * difference between the current and the previous listings. <br/>
100 * If 'newFilesOnly' is not set, for each trigger, the whole list is returned.
101 * This can be used for watching a specific file and look for changes in size or
102 * access time of the file.
103 * 
104 * </p>
105 * <p>
106 * Depending on the boolean parameter 'checkSizeAndDate' the meaning 'new' is
107 * handled as following: <br/>
108 * If 'checkSizeAndDate' is set, modified files are also listed besides brand
109 * new files. I.e., a new file is that is not found in previous listing OR the
110 * size or date of the file has changed between the previous and current
111 * listings. <br/>
112 * If 'checkSizeAndDate' is not set (default), only brand new files are listed.
113 * I.e. only the file names are checked, and file modifications have no effect.
114 * 
115 * </p>
116 * <p>
117 * The files are listed in the following format: Each element in the array will
118 * be a RecordToken: {name=String, size=long, date=long}, where the size is
119 * given in bytes, the date is given in UTC seconds. The resolution depends on
120 * the 'ls -l' output, i.e. at most a minute, and for old files a day. In the
121 * local case, it depends on the resolution of Java and the local OS, usually
122 * less than a millisecond.
123 * 
124 * </p>
125 * <p>
126 * On error, an empty string is returned.
127 * 
128 * </p>
129 * <p>
130 * Note: even if you pass a token to the mask port parameter, you must pass a
131 * token to the trigger port as well anyway.
132 * </p>
133 * 
134 * @author Norbert Podhorszki
135 * @version $Id: SshDirectoryList.java 24234 2010-05-06 05:21:26Z welker $
136 * @since Ptolemy II 5.0.1
137 */
138public class SshDirectoryList extends TypedAtomicActor {
139        /**
140         * Construct an actor with the given container and name.
141         * 
142         * @param container
143         *            The container.
144         * @param name
145         *            The name of this actor.
146         * @exception IllegalActionException
147         *                If the actor cannot be contained by the proposed
148         *                container.
149         * @exception NameDuplicationException
150         *                If the container already has an actor with this name.
151         */
152        public SshDirectoryList(CompositeEntity container, String name)
153                        throws NameDuplicationException, IllegalActionException {
154                super(container, name);
155
156                // Uncomment the next line to see debugging statements
157                // addDebugListener(new ptolemy.kernel.util.StreamListener());
158
159                // target selects the machine where the directory is to be accessed
160                target = new PortParameter(this, "target", new StringToken(
161                                "[ local | [user@]host[:port] ]"));
162                new Parameter(target.getPort(), "_showName", BooleanToken.TRUE);
163
164                // dir is the path to the directory to be listed on the target machine
165                dir = new PortParameter(this, "dir", new StringToken("/path/to/dir"));
166                new Parameter(dir.getPort(), "_showName", BooleanToken.TRUE);
167
168                // mask is the file mask for the files to be listed
169                mask = new PortParameter(this, "mask", new StringToken("*"));
170                new Parameter(mask.getPort(), "_showName", BooleanToken.TRUE);
171
172                // a trigger to read it again
173                trigger = new TypedIOPort(this, "trigger", true, false);
174                trigger.setTypeEquals(BaseType.UNKNOWN);
175                new Parameter(trigger, "_showName", BooleanToken.FALSE);
176
177                // new files only or all?
178                newFilesOnly = new Parameter(this, "newFilesOnly", new BooleanToken(
179                                true));
180                newFilesOnly.setTypeEquals(BaseType.BOOLEAN);
181
182                // new files only or all?
183                checkSizeAndDate = new Parameter(this, "checkSizeAndDate",
184                                new BooleanToken(false));
185                checkSizeAndDate.setTypeEquals(BaseType.BOOLEAN);
186
187                // the output: an array of filenames
188                newFiles = new TypedIOPort(this, "newFiles", false, true);
189                String[] labels = { "name", "size", "date" };
190                Type[] ctypes = { BaseType.STRING, BaseType.LONG, BaseType.LONG };
191                _etype = new RecordType(labels, ctypes);
192                newFiles.setTypeEquals(new ArrayType(_etype));
193                // newFiles.setTypeAtMost(new RecordType(new String[0], new Type[0]));
194                // newFiles.setTypeEquals(new ArrayType(BaseType.UNKNOWN));
195                // newFiles.setTypeEquals(new BaseType.UNKNOWN);
196                new Parameter(newFiles, "_showName", BooleanToken.FALSE);
197        }
198
199        /***********************************************************
200         * ports and parameters
201         */
202
203        /**
204         * The machine to be used at job submission. It should be null, "" or
205         * "local" for the local machine or [user@]host[:port] to denote a remote
206         * machine accessible with ssh.
207         * 
208         * This parameter is read once at initialize.
209         */
210        public PortParameter target;
211
212        /**
213         * The path to the directory to be read on the target machines. This
214         * parameter is read once at initialize.
215         */
216        public PortParameter dir;
217
218        /**
219         * The file mask for listing only such files. This parameter is read once at
220         * initialize.
221         */
222        public PortParameter mask;
223
224        /**
225         * The trigger port to do the reading. This port is an output port of type
226         * ArrayToken.
227         */
228        public TypedIOPort trigger;
229
230        /**
231         * Specifying whether the output should contain only new files or all. 'New'
232         * means the difference in the current and previous listing.
233         */
234        public Parameter newFilesOnly;
235
236        /**
237         * Specifying whether the output should contain modified files as well, or
238         * only brand new files.
239         */
240        public Parameter checkSizeAndDate;
241
242        /**
243         * The output: array of new files since the previous read. This port is an
244         * output port of type ArrayToken. Each element is a RecordToken of
245         * {name=String, size=long, date=long}
246         */
247        public TypedIOPort newFiles;
248
249        /***********************************************************
250         * public methods
251         */
252
253        /**
254         * initialize() runs once before first exec
255         * 
256         * @exception IllegalActionException
257         *                If the parent class throws it.
258         */
259        public void initialize() throws IllegalActionException {
260                super.initialize();
261                _firstFire = true;
262        }
263
264        /**
265         * fire
266         * 
267         * @exception IllegalActionException
268         */
269        public void fire() throws IllegalActionException {
270                super.fire();
271
272                // consume the trigger token
273                trigger.get(0);
274
275                FileInfo[] _newFiles = null;
276                if (_firstFire) {
277                        // update PortParameters
278                        target.update();
279                        dir.update();
280                        mask.update();
281
282                        _target = ((StringToken) target.getToken()).stringValue();
283                        _dir = ((StringToken) dir.getToken()).stringValue();
284                        _mask = ((StringToken) mask.getToken()).stringValue();
285                        _prevmask = _mask;
286                        _newOnly = ((BooleanToken) newFilesOnly.getToken()).booleanValue();
287                        _checkModifications = ((BooleanToken) checkSizeAndDate.getToken())
288                                        .booleanValue();
289
290                        if (isDebugging)
291                                log.debug("Create DirectoryListing object: " + "target = "
292                                                + _target + "; dir = " + _dir + "; filemask = " + _mask
293                                                + "; newFilesOnly = " + _newOnly
294                                                + "; checkSizeAndDate = " + _checkModifications);
295
296                        String masks[] = new String[1];
297                        masks[0] = _mask;
298                        _dl = new DirectoryListing(_target, _dir, masks);
299
300                        _firstFire = false;
301                } else {
302                        // mask can be reset
303                        mask.update();
304                        _mask = ((StringToken) mask.getToken()).stringValue();
305                        if (!_mask.equals(_prevmask)) {
306                                String masks[] = new String[1];
307                                masks[0] = _mask;
308                                _dl.setMask(masks);
309                                _prevmask = _mask;
310                        }
311                }
312
313                try {
314                        // list directory
315                        int n = _dl.list();
316                        if (isDebugging)
317                                log.debug("Number of total files = " + n);
318                        if (n > 0) {
319                                // get new files
320                                if (_newOnly)
321                                        _newFiles = _dl.getNewFiles(_checkModifications);
322                                else
323                                        _newFiles = _dl.getList();
324
325                                if (isDebugging || _newFiles.length > 0)
326                                        log.info("Number of " + (_newOnly ? "NEW" : "")
327                                                        + " files = " + _newFiles.length);
328                        }
329
330                } catch (ExecException ex) {
331                        log.error("SshDirectoryList error at remote directory reading. "
332                                        + ex);
333                }
334
335                // create the result
336                ArrayToken at;
337                if (_newFiles != null && _newFiles.length > 0) {
338                        RecordToken[] rt = new RecordToken[_newFiles.length];
339                        for (int i = 0; i < _newFiles.length; i++) {
340                                rt[i] = createRecordToken(_newFiles[i]);
341                                // newFiles.send(0, rt[i]);
342                        }
343                        at = new ArrayToken(_etype, rt);
344                }
345                // else at = ArrayToken.NIL;
346                else
347                        at = new ArrayToken(_etype, new RecordToken[0]);
348
349                newFiles.send(0, at);
350        }
351
352        /*
353         * Create one RecordToken of format {name=String, size=long, date=long} from
354         * the FileInfo struct.
355         */
356        private RecordToken createRecordToken(FileInfo fi) {
357                String[] labels = { "name", "size", "date" };
358                Token[] values = new Token[3];
359                values[0] = new StringToken(fi.getName());
360                values[1] = new LongToken(fi.getSize());
361                values[2] = new LongToken(fi.getDate());
362                RecordToken rt = null;
363                try {
364                        rt = new RecordToken(labels, values);
365                } catch (IllegalActionException ex) {
366                        log
367                                        .error("SshDirectoryList: Error at creating a record token for fileinfo: "
368                                                        + fi
369                                                        + "\nlabels = "
370                                                        + labels
371                                                        + "\nvalues = "
372                                                        + values);
373                }
374                return rt;
375        }
376
377        private boolean _firstFire;
378        private String _target;
379        private String _dir;
380        private String _mask;
381        private String _prevmask;
382        private boolean _newOnly;
383        private boolean _checkModifications;
384        private DirectoryListing _dl;
385        private Type _etype;
386
387        private static final Log log = LogFactory.getLog(SshDirectoryList.class
388                        .getName());
389        private static final boolean isDebugging = log.isDebugEnabled();
390
391}