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 // #ifdef includeSmsFeature
229 public byte[] getByteFromImage(Image img){
230 int w = img.getWidth();
231 int h = img.getHeight();
232 int data_int[] = new int[ w * h ];
233 // img.getRGB( data_int, 0, w, 0, 0, w, h );
234 byte[] data_byte = new byte[ w * h * 3 ];
235 for ( int i = 0; i < w * h; ++i )
236 {
237 int color = data_int[ i ];
238 int offset = i * 3;
239 data_byte[ offset ] = ( byte ) ( ( color & 0xff0000 ) >> 16 );
240 data_byte[ offset +
241 1 ] = ( byte ) ( ( color & 0xff00 ) >> 8 );
242 data_byte[ offset + 2 ] = ( byte ) ( ( color & 0xff ) );
243 }
244 return data_byte;
245 }
246
247
248 public void addImageData(String photoname, Image imgdata, String albumname)
249 throws InvalidImageDataException, PersistenceMechanismException {
250
251 try {
252 imageRS = RecordStore
253 .openRecordStore(ALBUM_LABEL + albumname, true);
254 imageInfoRS = RecordStore.openRecordStore(INFO_LABEL + albumname,
255 true);
256 int rid; // new record ID for Image (bytes)
257 int rid2; // new record ID for ImageData (metadata)
258
259
260
261 ImageUtil converter = new ImageUtil();
262
263 // NOTE: For some Siemen's phone, all images have to be less than
264 // 16K
265 // May have to check for this, or try to convert to a lesser format
266 // for display on Siemen's phones (Could put this in an Aspect)
267
268 // Add Tucan
269 byte[] data1 = getByteFromImage(imgdata);
270 rid = imageRS.addRecord(data1, 0, data1.length);
271 ImageData ii = new ImageData(rid, ImageAccessor.ALBUM_LABEL
272 + albumname, photoname);
273 rid2 = imageInfoRS.getNextRecordID();
274 ii.setRecordId(rid2);
275 data1 = converter.getBytesFromImageInfo(ii);
276 imageInfoRS.addRecord(data1, 0, data1.length);
277
278 imageRS.closeRecordStore();
279
280 imageInfoRS.closeRecordStore();
281 } catch (RecordStoreException e) {
282 throw new PersistenceMechanismException();
283 }
284 }
285 //#endif
286
287
288 // #ifdef includeCopyPhoto
289 /**
290 * @param photoname
291 * @param imageData
292 * @param albumname
293 * @throws InvalidImageDataException
294 * @throws PersistenceMechanismException
295 */
296 public void addImageData(String photoname, ImageData imageData, String albumname) throws InvalidImageDataException, PersistenceMechanismException {
297 try {
298 imageRS = RecordStore.openRecordStore(ALBUM_LABEL + albumname, true);
299 imageInfoRS = RecordStore.openRecordStore(INFO_LABEL + albumname, true);
300 int rid2; // new record ID for ImageData (metadata)
301 ImageUtil converter = new ImageUtil();
302 rid2 = imageInfoRS.getNextRecordID();
303 imageData.setRecordId(rid2);
304 byte[] data1 = converter.getBytesFromImageInfo(imageData);
305 imageInfoRS.addRecord(data1, 0, data1.length);
306 } catch (RecordStoreException e) {
307 throw new PersistenceMechanismException();
308 }finally{
309 try {
310 imageRS.closeRecordStore();
311 imageInfoRS.closeRecordStore();
312 } catch (RecordStoreNotOpenException e) {
313 // TODO Auto-generated catch block
314 e.printStackTrace();
315 } catch (RecordStoreException e) {
316 // TODO Auto-generated catch block
317 e.printStackTrace();
318 }
319 }
320 }
321 // #endif
322
323 /**
324 * This will populate the imageInfo hashtable with the ImageInfo object,
325 * referenced by label name and populate the imageTable hashtable with Image
326 * objects referenced by the RMS record Id
327 *
328 * @throws PersistenceMechanismException
329 */
330 public ImageData[] loadImageDataFromRMS(String recordName)
331 throws PersistenceMechanismException, InvalidImageDataException {
332
333 Vector imagesVector = new Vector();
334
335 try {
336
337 // [EF] not used String storeName = ImageAccessor.ALBUM_LABEL + recordName;
338 String infoStoreName = ImageAccessor.INFO_LABEL + recordName;
339
340 RecordStore infoStore = RecordStore.openRecordStore(infoStoreName,
341 false);
342 RecordEnumeration isEnum = infoStore.enumerateRecords(null, null,
343 false);
344
345 while (isEnum.hasNextElement()) {
346 // Get next record
347 int currentId = isEnum.nextRecordId();
348 byte[] data = infoStore.getRecord(currentId);
349
350 // Convert the data from a byte array into our ImageData
351 // (metadata) object
352 ImageUtil converter = new ImageUtil();
353 ImageData iiObject = converter.getImageInfoFromBytes(data);
354
355 // Add the info to the metadata hashtable
356 String label = iiObject.getImageLabel();
357 imagesVector.addElement(iiObject);
358 model.getImageInfoTable().put(label, iiObject);
359
360 }
361
362 infoStore.closeRecordStore();
363
364 }catch (RecordStoreException rse) {
365 throw new PersistenceMechanismException(rse);
366 }
367
368 // Re-copy the contents into a smaller array
369 ImageData[] labelArray = new ImageData[imagesVector.size()];
370 imagesVector.copyInto(labelArray);
371 return labelArray;
372 }
373
374 /**
375 * Update the Image metadata associated with this named photo
376 * @throws InvalidImageDataException
377 * @throws PersistenceMechanismException
378 */
379 public boolean updateImageInfo(ImageData oldData, ImageData newData) throws InvalidImageDataException, PersistenceMechanismException {
380
381 boolean success = false;
382 RecordStore infoStore = null;
383 try {
384
385 // Parse the Data store name to get the Info store name
386 String infoStoreName = oldData.getParentAlbumName();
387 infoStoreName = ImageAccessor.INFO_LABEL + infoStoreName.substring(ImageAccessor.ALBUM_LABEL.length());
388 infoStore = RecordStore.openRecordStore(infoStoreName, false);
389
390 ImageUtil converter = new ImageUtil();
391 byte[] imageDataBytes = converter.getBytesFromImageInfo(newData);
392
393 infoStore.setRecord(oldData.getRecordId(), imageDataBytes, 0, imageDataBytes.length);
394
395 } catch (RecordStoreException rse) {
396 throw new PersistenceMechanismException(rse);
397 }
398
399 // Update the Hashtable 'cache'
400 setImageInfo(oldData.getImageLabel(), newData);
401
402 try {
403 infoStore.closeRecordStore();
404 } catch (RecordStoreNotOpenException e) {
405 //No problem if the RecordStore is not Open
406 } catch (RecordStoreException e) {
407 throw new PersistenceMechanismException(e);
408 }
409
410 return success;
411 }
412
413 /**
414 * Retrieve the metadata associated with a specified image (by name)
415 * @throws ImageNotFoundException
416 * @throws NullAlbumDataReference
417 */
418 public ImageData getImageInfo(String imageName) throws ImageNotFoundException, NullAlbumDataReference {
419
420 if (model == null)
421 throw new NullAlbumDataReference("Null reference to the Album data");
422
423 ImageData ii = (ImageData) model.getImageInfoTable().get(imageName);
424
425 if (ii == null)
426 throw new ImageNotFoundException(imageName +" was NULL in ImageAccessor Hashtable.");
427
428
429 return ii;
430
431 }
432
433 /**
434 * Update the hashtable with new ImageInfo data
435 */
436 public void setImageInfo(String imageName, ImageData newData) {
437
438 model.getImageInfoTable().put(newData.getImageLabel(), newData);
439
440 }
441
442 /**
443 * Fetch a single image from the Record Store This should be used for
444 * loading images on-demand (only when they are viewed or sent via SMS etc.)
445 * to reduce startup time by loading them all at once.
446 * @throws PersistenceMechanismException
447 */
448 public Image loadSingleImageFromRMS(String recordName, String imageName,
449 int recordId) throws PersistenceMechanismException {
450
451 Image img = null;
452 byte[] imageData = loadImageBytesFromRMS(recordName, imageName,
453 recordId);
454 img = Image.createImage(imageData, 0, imageData.length);
455 return img;
456 }
457
458 /**
459 * Get the data for an Image as a byte array. This is useful for sending
460 * images via SMS or HTTP
461 * @throws PersistenceMechanismException
462 */
463 public byte[] loadImageBytesFromRMS(String recordName, String imageName,
464 int recordId) throws PersistenceMechanismException {
465
466 byte[] imageData = null;
467
468 try {
469
470 RecordStore albumStore = RecordStore.openRecordStore(recordName,
471 false);
472 imageData = albumStore.getRecord(recordId);
473 albumStore.closeRecordStore();
474
475 } catch (RecordStoreException rse) {
476 throw new PersistenceMechanismException(rse);
477 }
478
479 return imageData;
480 }
481
482 /**
483 * Delete a single (specified) image from the (specified) record store. This
484 * will permanently delete the image data and metadata from the device.
485 * @throws PersistenceMechanismException
486 * @throws NullAlbumDataReference
487 * @throws ImageNotFoundException
488 */
489 public boolean deleteSingleImageFromRMS(String storeName, String imageName) throws PersistenceMechanismException, ImageNotFoundException, NullAlbumDataReference {
490
491 boolean success = false;
492
493 // Open the record stores containing the byte data and the meta data
494 // (info)
495 try {
496
497 // Verify storeName is name without pre-fix
498 imageRS = RecordStore
499 .openRecordStore(ALBUM_LABEL + storeName, true);
500 imageInfoRS = RecordStore.openRecordStore(INFO_LABEL + storeName,
501 true);
502
503 ImageData imageData = getImageInfo(imageName);
504 int rid = imageData.getForeignRecordId();
505
506 imageRS.deleteRecord(rid);
507 imageInfoRS.deleteRecord(rid);
508
509 imageRS.closeRecordStore();
510 imageInfoRS.closeRecordStore();
511
512 } catch (RecordStoreException rse) {
513 throw new PersistenceMechanismException(rse);
514 }
515
516 // TODO: It's not clear from the API whether the record store needs to
517 // be closed or not...
518
519 return success;
520 }
521
522 /**
523 * Define a new photo album for mobile photo users. This creates a new
524 * record store to store photos for the album.
525 * @throws PersistenceMechanismException
526 * @throws InvalidPhotoAlbumNameException
527 */
528 public void createNewPhotoAlbum(String albumName) throws PersistenceMechanismException, InvalidPhotoAlbumNameException {
529
530 RecordStore newAlbumRS = null;
531 RecordStore newAlbumInfoRS = null;
532 if (albumName.equals("")){
533 throw new InvalidPhotoAlbumNameException();
534 }
535 String[] names = getAlbumNames();
536 for (int i = 0; i < names.length; i++) {
537 if (names[i].equals(albumName))
538 throw new InvalidPhotoAlbumNameException();
539 }
540
541 try {
542 newAlbumRS = RecordStore.openRecordStore(ALBUM_LABEL + albumName,
543 true);
544 newAlbumInfoRS = RecordStore.openRecordStore(
545 INFO_LABEL + albumName, true);
546 newAlbumRS.closeRecordStore();
547 newAlbumInfoRS.closeRecordStore();
548 } catch (RecordStoreException rse) {
549 throw new PersistenceMechanismException(rse);
550 }
551
552 }
553
554 public void deletePhotoAlbum(String albumName) throws PersistenceMechanismException {
555
556 try {
557 RecordStore.deleteRecordStore(ALBUM_LABEL + albumName);
558 RecordStore.deleteRecordStore(INFO_LABEL + albumName);
559 } catch (RecordStoreException rse) {
560 throw new PersistenceMechanismException(rse);
561 }
562
563 }
564
565 /**
566 * Get the list of photo album names currently loaded.
567 *
568 * @return Returns the albumNames.
569 */
570 public String[] getAlbumNames() {
571 return albumNames;
572 }
573 }
|