如何避免始终从Google云端硬盘加载缓存的应用数据 [英] How to avoid from always loading cached app data from Google Drive

查看:319
本文介绍了如何避免始终从Google云端硬盘加载缓存的应用数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目前,我使用



第3步:按下保存123.TXT的内容123





文件名为123.TXT,内容为123的文件将在应用程序文件夹中创建。



步骤4:按内容456





上一个文件将被重命名为456.TXT ,内容更新为456步骤5:按下按钮LOAD LAST SAVED FILE



找到文件名为456.TXT的文件,但读取了之前缓存的内容123。我期待内容456。



请注意,如果我们



  1. 按下LOAD LAST SAVED FILE,文件名为456。
  2. 重新安装演示应用程序。 TXT和内容456。

我已正式提交问题报告 - https://code.google.com/a/google.com/ p / apps-api-issues / issues / detail?id = 4727






其他信息



这就是它在我的设备下的样子 - http:// youtu。 be / kuIHoi4A1c0



我意识到,并非所有的用户都会遇到这个问题。例如,我已经测试了另一款Nexus 6,Google Play服务9.4.52(440-127739847)。这个问题没有出现。



我编译了一个用于测试目的的APK - https://github.com/yccheok/google-drive-bug/releases/download/1.0/demo.apk p>

解决方案


  1. Google云端硬盘上的搜索速度很慢。
    为什么不使用基文件夹的属性来存储压缩文件的ID?
    https://developers.google.com/drive/v2/web/properties

  2. Google云端硬盘上的文件名称不是唯一的,您可以上传多个具有相同名称的文件。但是,Google返回的文件ID是唯一的。


Currently, I'm using Google Drive Android API, to store my Android app data, to Google Drive App Folder.

This is what I'm doing when saving my application data

  1. Generate a checksum for the current local zip file.
  2. Search in Google Drive App Folder, to see whether there is an existing App Folder zip file.
  3. If there is, overwrite the content of existing App Folder zip file, with current local zip files. Also, we will rename existing App Folder zip filename, with the latest checksum.
  4. If there isn't existing App Folder zip file, generate a new App Folder zip file, with local zip file's content. We will use the latest checksum as App Folder zip filename.

Here's the code which performs the above-mentioned operations.

Generate new App Folder zip file, or update existing App Folder zip file

public static boolean saveToGoogleDrive(GoogleApiClient googleApiClient, File file, HandleStatusable h, PublishProgressable p) {
    // Should we new or replace?

    GoogleCloudFile googleCloudFile = searchFromGoogleDrive(googleApiClient, h, p);

    try {
        p.publishProgress(JStockApplication.instance().getString(R.string.uploading));

        final long checksum = org.yccheok.jstock.gui.Utils.getChecksum(file);
        final long date = new Date().getTime();
        final int version = org.yccheok.jstock.gui.Utils.getCloudFileVersionID();
        final String title = getGoogleDriveTitle(checksum, date, version);

        DriveContents driveContents;
        DriveFile driveFile = null;

        if (googleCloudFile == null) {
            DriveApi.DriveContentsResult driveContentsResult = Drive.DriveApi.newDriveContents(googleApiClient).await();

            if (driveContentsResult == null) {
                return false;
            }

            Status status = driveContentsResult.getStatus();
            if (!status.isSuccess()) {
                h.handleStatus(status);
                return false;
            }

            driveContents = driveContentsResult.getDriveContents();

        } else {
            driveFile = googleCloudFile.metadata.getDriveId().asDriveFile();
            DriveApi.DriveContentsResult driveContentsResult = driveFile.open(googleApiClient, DriveFile.MODE_WRITE_ONLY, null).await();

            if (driveContentsResult == null) {
                return false;
            }

            Status status = driveContentsResult.getStatus();
            if (!status.isSuccess()) {
                h.handleStatus(status);
                return false;
            }

            driveContents = driveContentsResult.getDriveContents();
        }

        OutputStream outputStream = driveContents.getOutputStream();
        InputStream inputStream = null;

        byte[] buf = new byte[8192];

        try {
            inputStream = new FileInputStream(file);
            int c;

            while ((c = inputStream.read(buf, 0, buf.length)) > 0) {
                outputStream.write(buf, 0, c);
            }

        } catch (IOException e) {
            Log.e(TAG, "", e);
            return false;
        } finally {
            org.yccheok.jstock.file.Utils.close(outputStream);
            org.yccheok.jstock.file.Utils.close(inputStream);
        }

        if (googleCloudFile == null) {
            // Create the metadata for the new file including title and MIME
            // type.
            MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder()
                    .setTitle(title)
                    .setMimeType("application/zip").build();

            DriveFolder driveFolder = Drive.DriveApi.getAppFolder(googleApiClient);
            DriveFolder.DriveFileResult driveFileResult = driveFolder.createFile(googleApiClient, metadataChangeSet, driveContents).await();

            if (driveFileResult == null) {
                return false;
            }

            Status status = driveFileResult.getStatus();
            if (!status.isSuccess()) {
                h.handleStatus(status);
                return false;
            }
        } else {
            MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder()
                    .setTitle(title).build();

            DriveResource.MetadataResult metadataResult = driveFile.updateMetadata(googleApiClient, metadataChangeSet).await();
            Status status = metadataResult.getStatus();
            if (!status.isSuccess()) {
                h.handleStatus(status);
                return false;
            }
        }

        Status status;
        try {
            status = driveContents.commit(googleApiClient, null).await();
        } catch (java.lang.IllegalStateException e) {
            // java.lang.IllegalStateException: DriveContents already closed.
            Log.e(TAG, "", e);
            return false;
        }

        if (!status.isSuccess()) {
            h.handleStatus(status);
            return false;
        }

        status = Drive.DriveApi.requestSync(googleApiClient).await();
        if (!status.isSuccess()) {
            // Sync request rate limit exceeded.
            //
            //h.handleStatus(status);
            //return false;
        }

        return true;
    } finally {
        if (googleCloudFile != null) {
            googleCloudFile.metadataBuffer.release();
        }
    }
}

Search for existing App Folder zip file

private static String getGoogleDriveTitle(long checksum, long date, int version) {
    return "jstock-" + org.yccheok.jstock.gui.Utils.getJStockUUID() + "-checksum=" + checksum + "-date=" + date + "-version=" + version + ".zip";
}

// https://stackoverflow.com/questions/1360113/is-java-regex-thread-safe
private static final Pattern googleDocTitlePattern = Pattern.compile("jstock-" + org.yccheok.jstock.gui.Utils.getJStockUUID() + "-checksum=([0-9]+)-date=([0-9]+)-version=([0-9]+)\\.zip", Pattern.CASE_INSENSITIVE);

private static GoogleCloudFile searchFromGoogleDrive(GoogleApiClient googleApiClient, HandleStatusable h, PublishProgressable p) {
    DriveFolder driveFolder = Drive.DriveApi.getAppFolder(googleApiClient);

    // https://stackoverflow.com/questions/34705929/filters-ownedbyme-doesnt-work-in-drive-api-for-android-but-works-correctly-i
    final String titleName = ("jstock-" + org.yccheok.jstock.gui.Utils.getJStockUUID() + "-checksum=");
    Query query = new Query.Builder()
            .addFilter(Filters.and(
                Filters.contains(SearchableField.TITLE, titleName),
                Filters.eq(SearchableField.TRASHED, false)
            ))
            .build();

    DriveApi.MetadataBufferResult metadataBufferResult = driveFolder.queryChildren(googleApiClient, query).await();

    if (metadataBufferResult == null) {
        return null;
    }

    Status status = metadataBufferResult.getStatus();

    if (!status.isSuccess()) {
        h.handleStatus(status);
        return null;
    }

    MetadataBuffer metadataBuffer = null;
    boolean needToReleaseMetadataBuffer = true;

    try {
        metadataBuffer = metadataBufferResult.getMetadataBuffer();
        if (metadataBuffer != null ) {
            long checksum = 0;
            long date = 0;
            int version = 0;
            Metadata metadata = null;

            for (Metadata md : metadataBuffer) {
                if (p.isCancelled()) {
                    return null;
                }

                if (md == null || !md.isDataValid()) {
                    continue;
                }

                final String title = md.getTitle();

                // Retrieve checksum, date and version information from filename.
                final Matcher matcher = googleDocTitlePattern.matcher(title);
                String _checksum = null;
                String _date = null;
                String _version = null;
                if (matcher.find()){
                    if (matcher.groupCount() == 3) {
                        _checksum = matcher.group(1);
                        _date = matcher.group(2);
                        _version = matcher.group(3);
                    }
                }
                if (_checksum == null || _date == null || _version == null) {
                    continue;
                }

                try {
                    checksum = Long.parseLong(_checksum);
                    date = Long.parseLong(_date);
                    version = Integer.parseInt(_version);
                } catch (NumberFormatException ex) {
                    Log.e(TAG, "", ex);
                    continue;
                }

                metadata = md;

                break;

            }   // for

            if (metadata != null) {
                // Caller will be responsible to release the resource. If release too early,
                // metadata will not readable.
                needToReleaseMetadataBuffer = false;
                return GoogleCloudFile.newInstance(metadataBuffer, metadata, checksum, date, version);
            }
        }   // if
    } finally {
        if (needToReleaseMetadataBuffer) {
            if (metadataBuffer != null) {
                metadataBuffer.release();
            }
        }
    }

    return null;
}


The problem occurs, during loading application data. Imagine the following operations

  1. Upload zip data to Google Drive App Folder for the first time. The checksum is 12345. The filename being used is ...checksum=12345...zip
  2. Search for zip data from Google Drive App Folder. Able to find the file with filename ...checksum=12345...zip. Download the content. Verify the checksum of content is 12345 too.
  3. Overwrite new zip data to existing Google Drive App Folder file. New zip data checksum is 67890. The existing app folder zip file is renamed to ...checksum=67890...zip
  4. Search for zip data from Google Drive App Folder. Able to find the file with filename ...checksum=67890...zip. However, after downloading the content, the checksum of the content is still old 12345!

Download App Folder zip file

public static CloudFile loadFromGoogleDrive(GoogleApiClient googleApiClient, HandleStatusable h, PublishProgressable p) {
    final java.io.File directory = JStockApplication.instance().getExternalCacheDir();
    if (directory == null) {
        org.yccheok.jstock.gui.Utils.showLongToast(R.string.unable_to_access_external_storage);
        return null;
    }

    Status status = Drive.DriveApi.requestSync(googleApiClient).await();
    if (!status.isSuccess()) {
        // Sync request rate limit exceeded.
        //
        //h.handleStatus(status);
        //return null;
    }

    GoogleCloudFile googleCloudFile = searchFromGoogleDrive(googleApiClient, h, p);

    if (googleCloudFile == null) {
        return null;
    }

    try {
        DriveFile driveFile = googleCloudFile.metadata.getDriveId().asDriveFile();
        DriveApi.DriveContentsResult driveContentsResult = driveFile.open(googleApiClient, DriveFile.MODE_READ_ONLY, null).await();

        if (driveContentsResult == null) {
            return null;
        }

        status = driveContentsResult.getStatus();
        if (!status.isSuccess()) {
            h.handleStatus(status);
            return null;
        }

        final long checksum = googleCloudFile.checksum;
        final long date = googleCloudFile.date;
        final int version = googleCloudFile.version;

        p.publishProgress(JStockApplication.instance().getString(R.string.downloading));

        final DriveContents driveContents = driveContentsResult.getDriveContents();

        InputStream inputStream = null;
        java.io.File outputFile = null;
        OutputStream outputStream = null;

        try {
            inputStream = driveContents.getInputStream();
            outputFile = java.io.File.createTempFile(org.yccheok.jstock.gui.Utils.getJStockUUID(), ".zip", directory);
            outputFile.deleteOnExit();
            outputStream = new FileOutputStream(outputFile);

            int read = 0;
            byte[] bytes = new byte[1024];

            while ((read = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, read);
            }
        } catch (IOException ex) {
            Log.e(TAG, "", ex);
        } finally {
            org.yccheok.jstock.file.Utils.close(outputStream);
            org.yccheok.jstock.file.Utils.close(inputStream);
            driveContents.discard(googleApiClient);
        }

        if (outputFile == null) {
            return null;
        }

        return CloudFile.newInstance(outputFile, checksum, date, version);
    } finally {
        googleCloudFile.metadataBuffer.release();
    }
}

First, I thought

Status status = Drive.DriveApi.requestSync(googleApiClient).await()

doesn't do the job well. It fails in most of the situation, with error message Sync request rate limit exceeded. In fact, the hard limit imposed in requestSync, make that API not particularly useful - Android Google Play / Drive Api


However, even when requestSync success, loadFromGoogleDrive still can only get the latest filename, but outdated checksum content.

I'm 100% sure loadFromGoogleDrive is returning me a cached data content, with the following observations.

  1. I install a DownloadProgressListener in driveFile.open, bytesDownloaded is 0 and bytesExpected is -1.
  2. If I use Google Drive Rest API, with the following desktop code, I can find the latest filename with correct checksum content.
  3. If I uninstall my Android app and re-install again, loadFromGoogleDrive will able to get the latest filename with correct checksum content.

Is there any robust way, to avoid from always loading cached app data from Google Drive?


I manage to produce a demo. Here are the steps to reproduce this problem.

Step 1: Download source code

https://github.com/yccheok/google-drive-bug

Step 2 : Setup in API console

Step 3: Press button SAVE "123.TXT" WITH CONTENT "123"

A file with filename "123.TXT", content "123" will create in the app folder.

Step 4: Press button SAVE "456.TXT" WITH CONTENT "456"

The previous file will be renamed to "456.TXT", with content updated to "456"

Step 5: Press button LOAD LAST SAVED FILE

File with filename "456.TXT" was found, but the previous cached content "123" is read. I was expecting content "456".

Take note that, if we

  1. Uninstall demo app.
  2. Re-install demo app.
  3. Press button LOAD LAST SAVED FILE, file with filename "456.TXT" and content "456" is found.

I had submitted issues report officially - https://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=4727


Other info

This is how it looks like under my device - http://youtu.be/kuIHoi4A1c0

I realise, not all users will hit with this problem. For instance, I had tested with another Nexus 6, Google Play Services 9.4.52 (440-127739847). The problem doesn't appear.

I had compiled an APK for testing purpose - https://github.com/yccheok/google-drive-bug/releases/download/1.0/demo.apk

解决方案

  1. Search on Google Drive is slow. Why not use properties of the base folder to store id of the zip file? https://developers.google.com/drive/v2/web/properties
  2. File names on Google Drive are not unique, you can upload multiple files with same names. The File ID returned by Google, however, is unique.

这篇关于如何避免始终从Google云端硬盘加载缓存的应用数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆