001    /*
002    Copyright (c) 1996-2012, Damon Hart-Davis
003    All rights reserved.
004    
005    Redistribution and use in source and binary forms, with or without
006    modification, are permitted provided that the following conditions are
007    met:
008    
009      * Redistributions of source code must retain the above copyright
010        notice, this list of conditions and the following disclaimer.
011    
012      * Redistributions in binary form must reproduce the above copyright
013        notice, this list of conditions and the following disclaimer in the
014        documentation and/or other materials provided with the
015        distribution.
016    
017    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018    IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028    */
029    
030    package org.hd.d.pg2k.clApp.uploader;
031    
032    import java.io.ByteArrayOutputStream;
033    import java.io.File;
034    import java.io.IOException;
035    import java.io.InputStream;
036    import java.util.Properties;
037    
038    import org.hd.d.pg2k.svrCore.ExhibitName;
039    
040    
041    /**Holder for uploader mutable persistent properties, preferences, etc.
042     * Holds a small amount of structured information
043     * that can be persisted from one uploader run to the next.
044     * <p>
045     * Is thread-safe, though using multiple threads at once,
046     * especially mutators, may be confusing.
047     * <p>
048     * Not designed to be Serialisable,
049     * but instead can be stored in the plain-text Java properties format.
050     * <p>
051     * Is designed to be GUI-free.
052     * <p>
053     * Package visible since need be seen only by the main GUI class.
054     * <p>
055     * TODO: ADD locale
056     * <p>
057     * TODO: ADD upload category / main words / number-in-series / type / description
058     */
059    final class UploaderProps
060        {
061        /**Maximum length (in bytes) expected necessary to persist this data; strictly positive. */
062        public static final int MAX_PERS_BYTES = 8192;
063    
064    
065        /**Internal flag to note if any flags have changed.
066         * Volatile for thread-safe access without a lock.
067         * <p>
068         * Because of possible races this cannot be guaranteed to catch all changes,
069         * so a final "save-on-exit" and/or periodic save would be valuable.
070         */
071        private volatile boolean changed;
072    
073    
074        /**User ID (author initials), initially null; null or valid author initials.
075         * Would usually be the last set of initials (ie user ID) used.
076         * <p>
077         * Volatile for thread-safe access without a lock.
078         */
079        private volatile String userID;
080    
081        /**Get the user ID (author initials), initially null; null or valid author initials.
082         * Usually be the last set of initials (ie user ID) used
083         * to attempt a server connection.
084         */
085        public String getUserID() { return(userID); }
086    
087        /**Set the user ID (author initials), null or valid author initials.
088         * An attempt to set with an invalid value is treated as if null.
089         */
090        public void setUserID(final String authInitials)
091            {
092            if(!ExhibitName.validAuthorSyntax(authInitials))
093                {
094                if(userID == null) { return; } // Nothing to do...
095                changed = true;
096                userID = null;
097                }
098            else
099                {
100                if(authInitials.equals(userID)) { return; } // Nothing to do...
101                changed = true;
102                userID = authInitials;
103                }
104            }
105    
106    
107    
108        /**File chooser directory hint, or null.
109         * The last directory that a user looked in for files, eg to upload.
110         * <p>
111         * Volatile for thread-safe access without a lock.
112         */
113        private volatile File fileChooserPathHint;
114    
115        /**Get the file chooser directory hint, or null.
116         * The last directory that a user looked in for files, eg to upload.
117         */
118        public File getFileChooserPathHint() { return(fileChooserPathHint); }
119    
120        /**Set the file chooser directory hint, or null.
121         */
122        public void setFileChooserPathHint(final File pathHint)
123            {
124            if(pathHint == null)
125                {
126                if(fileChooserPathHint == null) { return; } // Nothing to do...
127                changed = true;
128                fileChooserPathHint = null;
129                }
130            else
131                {
132                if(pathHint.equals(fileChooserPathHint)) { return; } // Nothing to do...
133                changed = true;
134                fileChooserPathHint = pathHint;
135                }
136            }
137    
138    
139    
140    
141        /**Get data in properties-file format byte[]; never null. */
142        public byte[] getPersistentData()
143            {
144            final Properties props = new Properties();
145    
146            final String u = userID;
147            if(u != null) { props.put("userID", u); }
148            final File ph = fileChooserPathHint;
149            if(ph != null) { props.put("fileChooserPathHint", ph.getPath()); }
150    
151            final ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
152            try { props.store(baos, null); }
153            catch(final IOException e) { throw new Error("INTERNAL ERROR"); }
154    
155            return(baos.toByteArray());
156            }
157    
158        /**Get data in properties file if properties have changes, else null.
159         * The changed flag is cleared if this routine returns a non-null value,
160         * thus it can be used to save incremental updates asynchronously.
161         */
162        public byte[] getPersistentDataIfChanged()
163            {
164            if(!changed) { return(null); }
165    
166            // Clear changed flag *before* getting data to help reduce race effects.
167            changed = false;
168            return(getPersistentData());
169            }
170    
171        /**Load from previously-generated persistent data format.
172         * Any invalid data is simply ignored.
173         * <p>
174         * All updates are done as if through the setXXX() members
175         * to validate/coerce them, concurrently with other use of this instance.
176         */
177        public void loadFromPersistentData(final InputStream is)
178            throws IOException
179            {
180            if(is == null) { throw new IllegalArgumentException(); }
181            final Properties props = new Properties();
182            props.load(is);
183    
184            // Retrieve the values from the properties set.
185            setUserID(props.getProperty("userID"));
186            final String ph = props.getProperty("fileChooserPathHint");
187            setFileChooserPathHint((ph == null) ? null : new File(ph));
188            }
189        }