/** * This method will return you all locally-cached photos for the requested team. It will then * spawn a new background thread, and if Drive is available, it will sync the photos for the * requested team only and then notify the requesting activity that it has new photos. * * <p>Since syncing photos with Drive can take a few seconds, FileUtils.loadTeamPhotos() will * immediately call the PhotoRequester's updatePhotos(Bitmap[]) with whatever photos are locally * cached for that team, and if FileUtils is able to connect to Drive then it will call it again * after performing the sync. * * @param teamNumber The team whos photos we want to load. * @param requester The activity that is requesting the photos. This activity's * .updatePhotos(Bitmap[]) will be called with the loaded photos. * @return It will call requester.updatePhotos(Bitmap[]) with an array of Bitmaps containing all * photos for that team, or a zero-length array if no photos were found for that team. */ public void getTeamPhotos(int teamNumber, PhotoRequester requester) { // check for STORAGE permission if (!canWriteToStorage()) return; /* First, return the requester any photos we have on the local drive */ File photosDir = new File(mLocalTeamPhotosFilePath + "/" + teamNumber); // check if that folder exists if (!photosDir.isDirectory()) { // we have no photos for this team requester.updatePhotos(new Bitmap[0]); return; } File[] listOfFiles = photosDir.listFiles(); ArrayList<Bitmap> arrBitmaps = new ArrayList<Bitmap>(); for (int i = 0; i < listOfFiles.length; i++) { if (listOfFiles[i].isFile()) { // BitmapFactory will return `null` if the file cannot be parsed as an image, so no // error-checking needed. Bitmap bitmap = BitmapFactory.decodeFile(listOfFiles[i].getPath()); if (bitmap != null) arrBitmaps.add(bitmap); } // else: if it's not a file, then what is it???? .... skip I guess } requester.updatePhotos(arrBitmaps.toArray(new Bitmap[arrBitmaps.size()])); /* Now, attempt to sync with Drive */ if (canConnectToDrive()) { (new TeamPhotoSyncerThread(teamNumber, requester)).start(); } }
private void syncPhotosForTeam(GoogleApiClient googleApiClient, int teamNumber) { if (mTeamNumber >= 0) { Log.i( mActivity.getResources().getString(R.string.app_name), "Beginning photo sync for team " + teamNumber); // get the list of local files File photosDir = new File(mLocalTeamPhotosFilePath + "/" + teamNumber); /** **** get the list of local files ***** */ // check if that folder exists if (!photosDir.isDirectory()) { // we have no photos for this team if (mRequester != null) mRequester.updatePhotos(new Bitmap[0]); googleApiClient.disconnect(); return; } File[] listOfFiles = photosDir.listFiles(); ArrayList<String> arrLocalFiles = new ArrayList<String>(); for (File file : listOfFiles) { // make sure it's an image file Bitmap bitmap = BitmapFactory.decodeFile(file.getPath()); if (bitmap != null) { arrLocalFiles.add(file.getPath()); } // else: if it's not a file, then what is it???? .... skip I guess } // Navigate to the correct folder - there has to be a more efficient way to do this DriveFolder rootFolder = Drive.DriveApi.getFolder(googleApiClient, mDriveIdTeamPhotosFolder); Log.i( mActivity.getResources().getString(R.string.app_name), "Local Files: " + arrLocalFiles); Query query = new Query.Builder() .addFilter(Filters.eq(SearchableField.TITLE, "" + teamNumber)) .build(); DriveApi.MetadataBufferResult result = rootFolder.queryChildren(googleApiClient, query).await(); DriveFolder teamPhotosFolder = null; for (Metadata m : result.getMetadataBuffer()) { teamPhotosFolder = m.getDriveId().asDriveFolder(); } result.getMetadataBuffer().close(); ArrayList<PathAndDriveId> arrRemoteFiles = new ArrayList<PathAndDriveId>(); if (teamPhotosFolder != null) { query = new Query.Builder() .addFilter( Filters.eq(SearchableField.MIME_TYPE, "application/vnd.google-apps.photo")) .build(); result = teamPhotosFolder.queryChildren(googleApiClient, query).await(); for (Metadata m : result.getMetadataBuffer()) { arrRemoteFiles.add( new PathAndDriveId( mRemoteToplevelFolderName + "/" + mRemoteTeamFolderName + "/" + mRemoteTeamPhotosFolderName + "/" + mTeamNumber + "/" + m.getTitle(), m.getDriveId(), m.getTitle())); } result.getMetadataBuffer().close(); } Log.i( mActivity.getResources().getString(R.string.app_name), "Remote Files: " + arrRemoteFiles); // Remove files that are in both lists - these don't need to be synced. for (PathAndDriveId remoteFile : arrRemoteFiles) { String remotePath = remoteFile.path; if (arrLocalFiles.contains(remotePath)) { arrRemoteFiles.remove(remoteFile); arrLocalFiles.remove(remotePath); } } /** **** Download any Files we're missing locally ***** */ for (PathAndDriveId remoteFile : arrRemoteFiles) { // the Drive API actually makes a local cache of the file, so let's copy the contents into // our app's file structure DriveApi.DriveContentsResult fileResult = remoteFile .driveId .asDriveFile() .open(googleApiClient, DriveFile.MODE_READ_ONLY, null) .await(); if (!fileResult.getStatus().isSuccess()) { // file can't be opened continue; } String localFileToCreate = mLocalTeamPhotosFilePath + "/" + teamNumber + "/" + remoteFile.title; DriveContents contents = null; InputStream in = null; FileOutputStream fout = null; try { // DriveContents object contains pointers to the actual byte stream, which we will // manually copy contents = fileResult.getDriveContents(); in = contents.getInputStream(); fout = new FileOutputStream(localFileToCreate); // read bytes from source file and write to destination file byte[] b = new byte[1024]; int noOfBytes = 0; while ((noOfBytes = in.read(b)) != -1) fout.write(b, 0, noOfBytes); in.close(); fout.close(); contents.discard(googleApiClient); } catch (FileNotFoundException e) { // something went wrong, delete the file we were trying to create (new File(localFileToCreate)).delete(); } catch (IOException e) { // something went wrong, delete the file we were trying to create (new File(localFileToCreate)).delete(); } } /** **** Upload any files that are missing remotely ***** */ for (String localFile : arrLocalFiles) { // if the remote folder does not exist, create it. This should only need to be called // once. if (teamPhotosFolder == null) { MetadataChangeSet changeSet = new MetadataChangeSet.Builder().setTitle("" + teamNumber).build(); DriveFolder.DriveFolderResult folderResult = rootFolder.createFolder(googleApiClient, changeSet).await(); teamPhotosFolder = folderResult.getDriveFolder(); } DriveContents contents = null; try { FileInputStream in = new FileInputStream(localFile); // create a new file in Drive. This works a little different than normal file IO it that // you create the file first, // then tell it at the end which folder it's part of. Think of "folders" in drive more // like "tags" or "labels". DriveApi.DriveContentsResult contentResult = Drive.DriveApi.newDriveContents(googleApiClient).await(); contents = contentResult.getDriveContents(); OutputStream out = contents.getOutputStream(); // read bytes from source file and write to destination file byte[] b = new byte[1024]; int noOfBytes = 0; while ((noOfBytes = in.read(b)) != -1) out.write(b, 0, noOfBytes); in.close(); out.close(); } catch (FileNotFoundException e) { // something went wrong, discard the changes to the Drive file if (contents != null) contents.discard(googleApiClient); } catch (IOException e) { // something went wrong, discard the changes to the Drive file if (contents != null) contents.discard(googleApiClient); } if (contents != null) { String[] splitFilename = localFile.split("/"); String filename = splitFilename[splitFilename.length - 1]; MetadataChangeSet changeSet = new MetadataChangeSet.Builder() .setTitle(filename) .setMimeType(URLConnection.guessContentTypeFromName(filename)) .build(); teamPhotosFolder.createFile(googleApiClient, changeSet, contents).await(); } } Log.i( mActivity.getResources().getString(R.string.app_name), "Finished photo sync for team " + teamNumber); googleApiClient.disconnect(); // hand the photos back to the PhotoRequester if (mRequester != null) { // first, get the list of image files listOfFiles = photosDir.listFiles(); ArrayList<Bitmap> arrLocalBitmaps = new ArrayList<Bitmap>(); for (File file : listOfFiles) { // make sure it's an image file Bitmap bitmap = BitmapFactory.decodeFile(file.getPath()); if (bitmap != null) { arrLocalBitmaps.add(bitmap); } // else: if it's not a file, then what is it???? .... skip I guess } Bitmap[] bitmaps = new Bitmap[arrLocalBitmaps.size()]; // TODO: commented out because it crashes the app with the error: // "Only the original thread that created a view hierarchy can touch its views." // This will need a re-jigging - maybe a loop in the PhotoRequester that checks for // updates every // few seconds and re-draws if there has been one. // mRequester.updatePhotos(arrLocalBitmaps.toArray(bitmaps)); } } }