1 /*
2 * Created on Sep 13, 2004
3 *
4 */
5 package ubc.midp.mobilephoto.core.ui.datamodel;
6
7 import java.util.Vector;
8
9 import javax.microedition.lcdui.Image;
10 import javax.microedition.rms.RecordEnumeration;
11 import javax.microedition.rms.RecordStore;
12 import javax.microedition.rms.RecordStoreException;
13 import javax.microedition.rms.RecordStoreNotOpenException;
14
15 import lancs.midp.mobilephoto.lib.exceptions.ImageNotFoundException;
16 import lancs.midp.mobilephoto.lib.exceptions.ImagePathNotValidException;
17 import lancs.midp.mobilephoto.lib.exceptions.InvalidImageDataException;
18 import lancs.midp.mobilephoto.lib.exceptions.InvalidImageFormatException;
19 import lancs.midp.mobilephoto.lib.exceptions.InvalidPhotoAlbumNameException;
20 import lancs.midp.mobilephoto.lib.exceptions.NullAlbumDataReference;
21 import lancs.midp.mobilephoto.lib.exceptions.PersistenceMechanismException;
22 import ubc.midp.mobilephoto.core.util.ImageUtil;
23
24 /**
25 * @author trevor
26 *
27 * This is the main data access class. It handles all the connectivity with the
28 * RMS record stores to fetch and save data associated with MobilePhoto TODO:
29 * Refactor into stable interface for future updates. We may want to access data
30 * from RMS, or eventually direct from the 'file system' on devices that support
31 * the FileConnection optional API.
32 *
33 */
34 public class ImageAccessor {
35
36 // Note: Our midlet only ever has access to Record Stores it created
37 // For now, use naming convention to create record stores used by
38 // MobilePhoto
39 public static final String ALBUM_LABEL = "mpa-"; // "mpa- all album names
40 // are prefixed with
41 // this label
42 public static final String INFO_LABEL = "mpi-"; // "mpi- all album info
43 // stores are prefixed with
44 // this label
45 public static final String DEFAULT_ALBUM_NAME = "My Photo Album"; // default
46 // album
47 // name
48
49 public static final String IMAGE_LABEL = "ImageList"; // RecordStore name
50 // prefixed
51
52 protected String[] albumNames; // User defined names of photo albums
53
54 protected AlbumData model;
55
56 // Record Stores
57 private RecordStore imageRS = null;
58 private RecordStore imageInfoRS = null;
59
60 /*
61 * Constructor
62 */
63 public ImageAccessor(AlbumData mod) {
64 model = mod;
65 }
66
67 /**
68 * Load all existing photo albums that are defined in the record store.
69 *
70
71 * @throws InvalidImageDataException
72 * @throws PersistenceMechanismException
73 */
74 public void loadAlbums() throws InvalidImageDataException,
75 PersistenceMechanismException {
76
77 // Try to find any existing Albums (record stores)
78
79 String[] currentStores = RecordStore.listRecordStores();
80
81 if (currentStores != null) {
82 System.out.println("ImageAccessor::loadAlbums: Found: " + currentStores.length + " existing record stores");
83 model.existingRecords = true;
84 String[] temp = new String[currentStores.length];
85 int count = 0;
86
87 // Only use record stores that follow the naming convention defined
88 for (int i = 0; i < currentStores.length; i++) {
89 String curr = currentStores[i];
90
91 // If this record store is a photo album...
92 if (curr.startsWith(ALBUM_LABEL)) {
93
94 // Strip out the mpa- identifier
95 curr = curr.substring(4);
96 // Add the album name to the array
97 temp[i] = curr;
98 count++;
99 }
100 }
101
102 // Re-copy the contents into a smaller array now that we know the
103 // size
104 albumNames = new String[count];
105 int count2 = 0;
106 for (int i = 0; i < temp.length; i++) {
107 if (temp[i] != null) {
108 albumNames[count2] = temp[i];
109 count2++;
110 }
111 }
112 } else {
113 System.out.println("ImageAccessor::loadAlbums: 0 record stores exist. Creating default one.");
114 resetImageRecordStore();
115 loadAlbums();
116 }
117 // System.out.println("ImageAccessor::loadAlbums: Finished ok! ");
118 }
119
120 /**
121 * Reset the album data for MobilePhoto. This will delete all existing photo
122 * data from the record store and re-create the default album and photos.
123 *
124 * @throws InvalidImageFormatException
125 * @throws ImagePathNotValidException
126 * @throws InvalidImageDataException
127 * @throws PersistenceMechanismException
128 *
129 */
130 public void resetImageRecordStore() throws InvalidImageDataException, PersistenceMechanismException {
131
132 String storeName = null;
133 String infoStoreName = null;
134
135 // remove any existing album stores...
136 if (albumNames != null) {
137 for (int i = 0; i < albumNames.length; i++) {
138 try {
139 // Delete all existing stores containing Image objects as
140 // well as the associated ImageInfo objects
141 // Add the prefixes labels to the info store
142
143 storeName = ALBUM_LABEL + albumNames[i];
144 infoStoreName = INFO_LABEL + albumNames[i];
145
146 System.out.println("<* ImageAccessor.resetImageRecordStore() *> delete "+storeName);
147
148 RecordStore.deleteRecordStore(storeName);
149 RecordStore.deleteRecordStore(infoStoreName);
150
151 } catch (RecordStoreException e) {
152 System.out.println("No record store named " + storeName
153 + " to delete.");
154 System.out.println("...or...No record store named "
155 + infoStoreName + " to delete.");
156 System.out.println("Ignoring Exception: " + e);
157 // ignore any errors...
158 }
159 }
160 } else {
161 // Do nothing for now
162 System.out
163 .println("ImageAccessor::resetImageRecordStore: albumNames array was null. Nothing to delete.");
164 }
165
166 // Now, create a new default album for testing
167 addImageData("Tucan Sam", "/images/Tucan.png",
168 ImageAccessor.DEFAULT_ALBUM_NAME);
169 // Add Penguin
170 addImageData("Linux Penguin", "/images/Penguin.png",
171 ImageAccessor.DEFAULT_ALBUM_NAME);
172 // Add Duke
173 addImageData("Duke (Sun)", "/images/Duke1.PNG",
174 ImageAccessor.DEFAULT_ALBUM_NAME);
175 addImageData("UBC Logo", "/images/ubcLogo.PNG",
176 ImageAccessor.DEFAULT_ALBUM_NAME);
177 // Add Gail
178 addImageData("Gail", "/images/Gail1.PNG",
179 ImageAccessor.DEFAULT_ALBUM_NAME);
180 // Add JG
181 addImageData("J. Gosling", "/images/Gosling1.PNG",
182 ImageAccessor.DEFAULT_ALBUM_NAME);
183 // Add GK
184 addImageData("Gregor", "/images/Gregor1.PNG",
185 ImageAccessor.DEFAULT_ALBUM_NAME);
186 // Add KDV
187 addImageData("Kris", "/images/Kdvolder1.PNG",
188 ImageAccessor.DEFAULT_ALBUM_NAME);
189
190 }
191
192 public void addImageData(String photoname, String path, String albumname)
193 throws InvalidImageDataException, PersistenceMechanismException {
194
195 try {
196 imageRS = RecordStore
197 .openRecordStore(ALBUM_LABEL + albumname, true);
198 imageInfoRS = RecordStore.openRecordStore(INFO_LABEL + albumname,
199 true);
200
201 int rid; // new record ID for Image (bytes)
202 int rid2; // new record ID for ImageData (metadata)
203
204 ImageUtil converter = new ImageUtil();
205
206 // NOTE: For some Siemen's phone, all images have to be less than
207 // 16K
208 // May have to check for this, or try to convert to a lesser format
209 // for display on Siemen's phones (Could put this in an Aspect)
210
211 // Add Tucan
212 byte[] data1 = converter.readImageAsByteArray(path);
213 rid = imageRS.addRecord(data1, 0, data1.length);
214 ImageData ii = new ImageData(rid, ImageAccessor.ALBUM_LABEL
215 + albumname, photoname);
216 rid2 = imageInfoRS.getNextRecordID();
217 ii.setRecordId(rid2);
218 data1 = converter.getBytesFromImageInfo(ii);
219 imageInfoRS.addRecord(data1, 0, data1.length);
220
221 imageRS.closeRecordStore();
222
223 imageInfoRS.closeRecordStore();
224 } catch (RecordStoreException e) {
225 throw new PersistenceMechanismException();
226 }
227 }
228 public byte[] getByteFromImage(Image img){
229 int w = img.getWidth();
230 int h = img.getHeight();
231 int data_int[] = new int[ w * h ];
232 // img.getRGB( data_int, 0, w, 0, 0, w, h );
233 byte[] data_byte = new byte[ w * h * 3 ];
234 for ( int i = 0; i < w * h; ++i )
235 {
236 int color = data_int[ i ];
237 int offset = i * 3;
238 data_byte[ offset ] = ( byte ) ( ( color & 0xff0000 ) >> 16 );
239 data_byte[ offset +
240 1 ] = ( byte ) ( ( color & 0xff00 ) >> 8 );
241 data_byte[ offset + 2 ] = ( byte ) ( ( color & 0xff ) );
242 }
243 return data_byte;
244 }
245
246
247 public void addImageData(String photoname, Image imgdata, String albumname)
248 throws InvalidImageDataException, PersistenceMechanismException {
249
250 try {
251 imageRS = RecordStore
252 .openRecordStore(ALBUM_LABEL + albumname, true);
253 imageInfoRS = RecordStore.openRecordStore(INFO_LABEL + albumname,
254 true);
255 int rid; // new record ID for Image (bytes)
256 int rid2; // new record ID for ImageData (metadata)
257
258
259
260 ImageUtil converter = new ImageUtil();
261
262 // NOTE: For some Siemen's phone, all images have to be less than
263 // 16K
264 // May have to check for this, or try to convert to a lesser format
265 // for display on Siemen's phones (Could put this in an Aspect)
266
267 // Add Tucan
268 byte[] data1 = getByteFromImage(imgdata);
269 rid = imageRS.addRecord(data1, 0, data1.length);
270 ImageData ii = new ImageData(rid, ImageAccessor.ALBUM_LABEL
271 + albumname, photoname);
272 rid2 = imageInfoRS.getNextRecordID();
273 ii.setRecordId(rid2);
274 data1 = converter.getBytesFromImageInfo(ii);
275 imageInfoRS.addRecord(data1, 0, data1.length);
276
277 imageRS.closeRecordStore();
278
279 imageInfoRS.closeRecordStore();
280 } catch (RecordStoreException e) {
281 throw new PersistenceMechanismException();
282 }
283 }
284
285
286 /**
287 * @param photoname
288 * @param imageData
289 * @param albumname
290 * @throws InvalidImageDataException
291 * @throws PersistenceMechanismException
292 */
293 public void addImageData(String photoname, ImageData imageData, String albumname) throws InvalidImageDataException, PersistenceMechanismException {
294 try {
295 imageRS = RecordStore.openRecordStore(ALBUM_LABEL + albumname, true);
296 imageInfoRS = RecordStore.openRecordStore(INFO_LABEL + albumname, true);
297 int rid2; // new record ID for ImageData (metadata)
298 ImageUtil converter = new ImageUtil();
299 rid2 = imageInfoRS.getNextRecordID();
300 imageData.setRecordId(rid2);
301 byte[] data1 = converter.getBytesFromImageInfo(imageData);
302 imageInfoRS.addRecord(data1, 0, data1.length);
303 } catch (RecordStoreException e) {
304 throw new PersistenceMechanismException();
305 }finally{
306 try {
307 imageRS.closeRecordStore();
308 imageInfoRS.closeRecordStore();
309 } catch (RecordStoreNotOpenException e) {
310 // TODO Auto-generated catch block
311 e.printStackTrace();
312 } catch (RecordStoreException e) {
313 // TODO Auto-generated catch block
314 e.printStackTrace();
315 }
316 }
317 }
318
319 /**
320 * This will populate the imageInfo hashtable with the ImageInfo object,
321 * referenced by label name and populate the imageTable hashtable with Image
322 * objects referenced by the RMS record Id
323 *
324 * @throws PersistenceMechanismException
325 */
326 public ImageData[] loadImageDataFromRMS(String recordName)
327 throws PersistenceMechanismException, InvalidImageDataException {
328
329 Vector imagesVector = new Vector();
330
331 try {
332
333 // [EF] not used String storeName = ImageAccessor.ALBUM_LABEL + recordName;
334 String infoStoreName = ImageAccessor.INFO_LABEL + recordName;
335
336 RecordStore infoStore = RecordStore.openRecordStore(infoStoreName,
337 false);
338 RecordEnumeration isEnum = infoStore.enumerateRecords(null, null,
339 false);
340
341 while (isEnum.hasNextElement()) {
342 // Get next record
343 int currentId = isEnum.nextRecordId();
344 byte[] data = infoStore.getRecord(currentId);
345
346 // Convert the data from a byte array into our ImageData
347 // (metadata) object
348 ImageUtil converter = new ImageUtil();
349 ImageData iiObject = converter.getImageInfoFromBytes(data);
350
351 // Add the info to the metadata hashtable
352 String label = iiObject.getImageLabel();
353 imagesVector.addElement(iiObject);
354 model.getImageInfoTable().put(label, iiObject);
355
356 }
357
358 infoStore.closeRecordStore();
359
360 }catch (RecordStoreException rse) {
361 throw new PersistenceMechanismException(rse);
362 }
363
364 // Re-copy the contents into a smaller array
365 ImageData[] labelArray = new ImageData[imagesVector.size()];
366 imagesVector.copyInto(labelArray);
367 return labelArray;
368 }
369
370 /**
371 * Update the Image metadata associated with this named photo
372 * @throws InvalidImageDataException
373 * @throws PersistenceMechanismException
374 */
375 public boolean updateImageInfo(ImageData oldData, ImageData newData) throws InvalidImageDataException, PersistenceMechanismException {
376
377 boolean success = false;
378 RecordStore infoStore = null;
379 try {
380
381 // Parse the Data store name to get the Info store name
382 String infoStoreName = oldData.getParentAlbumName();
383 infoStoreName = ImageAccessor.INFO_LABEL + infoStoreName.substring(ImageAccessor.ALBUM_LABEL.length());
384 infoStore = RecordStore.openRecordStore(infoStoreName, false);
385
386 ImageUtil converter = new ImageUtil();
387 byte[] imageDataBytes = converter.getBytesFromImageInfo(newData);
388
389 infoStore.setRecord(oldData.getRecordId(), imageDataBytes, 0, imageDataBytes.length);
390
391 } catch (RecordStoreException rse) {
392 throw new PersistenceMechanismException(rse);
393 }
394
395 // Update the Hashtable 'cache'
396 setImageInfo(oldData.getImageLabel(), newData);
397
398 try {
399 infoStore.closeRecordStore();
400 } catch (RecordStoreNotOpenException e) {
401 //No problem if the RecordStore is not Open
402 } catch (RecordStoreException e) {
403 throw new PersistenceMechanismException(e);
404 }
405
406 return success;
407 }
408
409 /**
410 * Retrieve the metadata associated with a specified image (by name)
411 * @throws ImageNotFoundException
412 * @throws NullAlbumDataReference
413 */
414 public ImageData getImageInfo(String imageName) throws ImageNotFoundException, NullAlbumDataReference {
415
416 if (model == null)
417 throw new NullAlbumDataReference("Null reference to the Album data");
418
419 ImageData ii = (ImageData) model.getImageInfoTable().get(imageName);
420
421 if (ii == null)
422 throw new ImageNotFoundException(imageName +" was NULL in ImageAccessor Hashtable.");
423
424
425 return ii;
426
427 }
428
429 /**
430 * Update the hashtable with new ImageInfo data
431 */
432 public void setImageInfo(String imageName, ImageData newData) {
433
434 model.getImageInfoTable().put(newData.getImageLabel(), newData);
435
436 }
437
438 /**
439 * Fetch a single image from the Record Store This should be used for
440 * loading images on-demand (only when they are viewed or sent via SMS etc.)
441 * to reduce startup time by loading them all at once.
442 * @throws PersistenceMechanismException
443 */
444 public Image loadSingleImageFromRMS(String recordName, String imageName,
445 int recordId) throws PersistenceMechanismException {
446
447 Image img = null;
448 byte[] imageData = loadImageBytesFromRMS(recordName, imageName,
449 recordId);
450 img = Image.createImage(imageData, 0, imageData.length);
451 return img;
452 }
453
454 /**
455 * Get the data for an Image as a byte array. This is useful for sending
456 * images via SMS or HTTP
457 * @throws PersistenceMechanismException
458 */
459 public byte[] loadImageBytesFromRMS(String recordName, String imageName,
460 int recordId) throws PersistenceMechanismException {
461
462 byte[] imageData = null;
463
464 try {
465
466 RecordStore albumStore = RecordStore.openRecordStore(recordName,
467 false);
468 imageData = albumStore.getRecord(recordId);
469 albumStore.closeRecordStore();
470
471 } catch (RecordStoreException rse) {
472 throw new PersistenceMechanismException(rse);
473 }
474
475 return imageData;
476 }
477
478 /**
479 * Delete a single (specified) image from the (specified) record store. This
480 * will permanently delete the image data and metadata from the device.
481 * @throws PersistenceMechanismException
482 * @throws NullAlbumDataReference
483 * @throws ImageNotFoundException
484 */
485 public boolean deleteSingleImageFromRMS(String storeName, String imageName) throws PersistenceMechanismException, ImageNotFoundException, NullAlbumDataReference {
486
487 boolean success = false;
488
489 // Open the record stores containing the byte data and the meta data
490 // (info)
491 try {
492
493 // Verify storeName is name without pre-fix
494 imageRS = RecordStore
495 .openRecordStore(ALBUM_LABEL + storeName, true);
496 imageInfoRS = RecordStore.openRecordStore(INFO_LABEL + storeName,
497 true);
498
499 ImageData imageData = getImageInfo(imageName);
500 int rid = imageData.getForeignRecordId();
501
502 imageRS.deleteRecord(rid);
503 imageInfoRS.deleteRecord(rid);
504
505 imageRS.closeRecordStore();
506 imageInfoRS.closeRecordStore();
507
508 } catch (RecordStoreException rse) {
509 throw new PersistenceMechanismException(rse);
510 }
511
512 // TODO: It's not clear from the API whether the record store needs to
513 // be closed or not...
514
515 return success;
516 }
517
518 /**
519 * Define a new photo album for mobile photo users. This creates a new
520 * record store to store photos for the album.
521 * @throws PersistenceMechanismException
522 * @throws InvalidPhotoAlbumNameException
523 */
524 public void createNewPhotoAlbum(String albumName) throws PersistenceMechanismException, InvalidPhotoAlbumNameException {
525
526 RecordStore newAlbumRS = null;
527 RecordStore newAlbumInfoRS = null;
528 if (albumName.equals("")){
529 throw new InvalidPhotoAlbumNameException();
530 }
531 String[] names = getAlbumNames();
532 for (int i = 0; i < names.length; i++) {
533 if (names[i].equals(albumName))
534 throw new InvalidPhotoAlbumNameException();
535 }
536
537 try {
538 newAlbumRS = RecordStore.openRecordStore(ALBUM_LABEL + albumName,
539 true);
540 newAlbumInfoRS = RecordStore.openRecordStore(
541 INFO_LABEL + albumName, true);
542 newAlbumRS.closeRecordStore();
543 newAlbumInfoRS.closeRecordStore();
544 } catch (RecordStoreException rse) {
545 throw new PersistenceMechanismException(rse);
546 }
547
548 }
549
550 public void deletePhotoAlbum(String albumName) throws PersistenceMechanismException {
551
552 try {
553 RecordStore.deleteRecordStore(ALBUM_LABEL + albumName);
554 RecordStore.deleteRecordStore(INFO_LABEL + albumName);
555 } catch (RecordStoreException rse) {
556 throw new PersistenceMechanismException(rse);
557 }
558
559 }
560
561 /**
562 * Get the list of photo album names currently loaded.
563 *
564 * @return Returns the albumNames.
565 */
566 public String[] getAlbumNames() {
567 return albumNames;
568 }
569 }
|