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.IOException;
033 import java.util.ArrayList;
034 import java.util.HashSet;
035 import java.util.Set;
036
037 import org.hd.d.pg2k.svrCore.ExhibitName;
038 import org.hd.d.pg2k.svrCore.ROByteArray;
039
040 /**Database of selected files.
041 * Package-visible for direct manipulation by GUI classes.
042 * <p>
043 * Thread-safe.
044 * <p>
045 * A lock can be held on an instance to make compound atomic operations.
046 */
047 final class SelectedFilesDB
048 {
049 /**Ordered random-access list of proposed exhibits to be uploaded; never null.
050 */
051 private final ArrayList<SelectedFileDetails> files = new ArrayList<SelectedFileDetails>();
052
053 /**Set of all local file names; never null. */
054 private final Set<String> localFilenames = new HashSet<String>();
055
056 /**Set of all putative short exhibit names; never null.
057 * FIXME: consider converting to Name.ExhibitFull (or ExhibitShort) for smaller memory footprint.
058 */
059 private final Set<String> shortNames = new HashSet<String>();
060
061 /**Set of all MD5 hashes; never null. */
062 private final Set<ROByteArray> hashesMD5 = new HashSet<ROByteArray>();
063
064
065 /**Add item, silently dropping duplicate file entries if possible.
066 * Duplicate entries for a given local file or exhibit (short) name
067 * will be ignored.
068 *
069 * @param newFile new file to add; must be null
070 */
071 synchronized void add(final SelectedFileDetails newFile)
072 {
073 if(newFile == null) { throw new IllegalArgumentException(); }
074
075 final String shortNameAsString = newFile.getEsa().getExhibitFullName().getShortName().toString();
076
077 if(localFilenames.contains(newFile.getLocalFilename()) ||
078 shortNames.contains(shortNameAsString) ||
079 hashesMD5.contains(newFile.getHashMD5()))
080 { return; /* Already present. */ }
081
082 // Add to all internal tables...
083 localFilenames.add(newFile.getLocalFilename());
084 shortNames.add(shortNameAsString);
085 hashesMD5.add(newFile.getHashMD5());
086 files.add(newFile);
087
088 changed = true;
089 }
090
091 /**Remove the indicated item (shuffling any following elements down).
092 * Silently ignores out-of-range requests.
093 */
094 synchronized void remove(final int index)
095 {
096 if((index < 0) || (index >= files.size())) { return; }
097
098 // Remove from all internal tables.
099 final SelectedFileDetails selectedFileDetails = files.get(index);
100 localFilenames.remove(selectedFileDetails.getLocalFilename());
101 shortNames.remove(selectedFileDetails.getEsa().getExhibitFullName().getShortName().toString());
102 hashesMD5.remove(selectedFileDetails.getHashMD5());
103 files.remove(index);
104
105 changed = true;
106 }
107
108 /**Remove the indicated item (shuffling any following elements down).
109 * Silently ignores requests where the items is not present.
110 */
111 synchronized boolean remove(final SelectedFileDetails selectedFileDetails)
112 {
113 if(selectedFileDetails == null) { throw new IllegalArgumentException(); }
114
115 // Remove from all internal tables.
116 localFilenames.remove(selectedFileDetails.getLocalFilename());
117 shortNames.remove(selectedFileDetails.getEsa().getExhibitFullName().getShortName().toString());
118 hashesMD5.remove(selectedFileDetails.getHashMD5());
119 final boolean removed = files.remove(selectedFileDetails);
120
121 if(removed) { changed = true; }
122 return(removed);
123 }
124
125 /**Change the putative exhibit name to another new syntatically-valid one.
126 * Attempting to set it to an invalid or existing (in this DB) name
127 * (ignoring the directory component) will be rejected with an exception.
128 * @param index entry to be replaced
129 * @param newFullName new unique (in this DB) valid exhibit name
130 */
131 synchronized void setExhibitName(final int index, final String newFullName)
132 {
133 if(!ExhibitName.validNameSyntax(newFullName)) { throw new IllegalArgumentException(); }
134
135 final String newShortName = ExhibitName.getFileComponent(newFullName).toString();
136
137 // Atomically replace old details with new.
138 if(shortNames.contains(newShortName))
139 { throw new IllegalArgumentException("duplicate name"); }
140
141 try
142 {
143 final SelectedFileDetails oldSFD = files.get(index);
144 final SelectedFileDetails newSFD =
145 new SelectedFileDetails(oldSFD.getFc(), newFullName, oldSFD.getDescription(), oldSFD.getHashMD5());
146 files.set(index, newSFD);
147 final String oldShortName = oldSFD.getEsa().getExhibitFullName().getShortName().toString();
148 shortNames.remove(oldShortName);
149 shortNames.add(newShortName);
150
151 changed = true;
152 }
153 catch(final IOException e) { throw new Error(e); } // Should not happen.
154 }
155
156 /**Change the putative exhibit description.
157 * @param index entry to be replaced
158 * @param newDescription new description; never null
159 */
160 synchronized void setDescription(final int index, final String newDescription)
161 {
162 if(newDescription == null) { throw new IllegalArgumentException(); }
163
164 try
165 {
166 final SelectedFileDetails oldSFD = files.get(index);
167 if(oldSFD.getDescription().equals(newDescription))
168 { return; /* No change; leave untouched. */ }
169 final SelectedFileDetails newSFD =
170 new SelectedFileDetails(oldSFD.getFc(), oldSFD.getEsa().getFilePath(), newDescription, oldSFD.getHashMD5());
171 files.set(index, newSFD);
172
173 changed = true;
174 }
175 catch(final IOException e) { throw new Error(e); } // Should not happen.
176 }
177
178 /**Clear the whole DB. */
179 synchronized void clear()
180 {
181 localFilenames.clear();
182 shortNames.clear();
183 hashesMD5.clear();
184 files.clear();
185
186 changed = true;
187 }
188
189 /**Get number of entries in DB; non-negative. */
190 synchronized int size()
191 {
192 return(files.size());
193 }
194
195 /**Get given numbered entry by index; never null. */
196 synchronized SelectedFileDetails get(final int index)
197 {
198 return(files.get(index));
199 }
200
201 /**Returns true if an exhibit in this DB already has this short name.
202 * @param shortName never null
203 */
204 synchronized boolean shortNameInUse(final String shortName)
205 {
206 return(shortNames.contains(shortName));
207 }
208
209 /**Returns true if this local name is already in this DB.
210 * @param localFilename never null
211 */
212 synchronized boolean localFilenameInUse(final String localFilename)
213 {
214 return(localFilenames.contains(localFilename));
215 }
216
217
218 /**Flag set true when the table is changed. */
219 private boolean changed;
220
221 /**Returns the "changed" flag value and clears it.
222 * Can be used to arrange redrawing of a view of this data after an update.
223 */
224 synchronized boolean testAndClearChangedFlag()
225 {
226 final boolean result = changed;
227 changed = false;
228 return(result);
229 }
230 }