Program Club

Google 드라이브에서 항상 캐시 된 앱 데이터를로드하지 않는 방법

proclub 2020. 12. 13. 09:53
반응형

Google 드라이브에서 항상 캐시 된 앱 데이터를로드하지 않는 방법


현재 저는 Google Drive Android API를 사용하여 Android 앱 데이터를 Google Drive App Folder에 저장하고 있습니다.

이것이 내 응용 프로그램 데이터를 저장할 때 수행하는 작업입니다.

  1. 현재 로컬 zip 파일에 대한 체크섬을 생성합니다.
  2. Google Drive 앱 폴더 에서 검색 하여 기존 앱 폴더 zip 파일이 있는지 확인합니다.
  3. 있는 경우 기존 앱 폴더 zip 파일의 내용을 현재 로컬 zip 파일로 덮어 씁니다. 또한 기존 앱 폴더 zip 파일 이름을 최신 체크섬으로 변경합니다.
  4. 기존 앱 폴더 zip 파일이없는 경우 로컬 zip 파일의 콘텐츠와 함께 새 앱 폴더 zip 파일을 생성합니다. 최신 체크섬을 앱 폴더 zip 파일 이름으로 사용합니다.

위에서 언급 한 작업을 수행하는 코드는 다음과 같습니다.

새 앱 폴더 zip 파일 생성 또는 기존 앱 폴더 zip 파일 업데이트

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();
        }
    }
}

기존 앱 폴더 zip 파일 검색

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;
}

응용 프로그램 데이터를로드하는 동안 문제가 발생합니다. 다음 작업을 상상해보십시오.

  1. 처음으로 zip 데이터를 Google 드라이브 앱 폴더업로드 합니다. 체크섬은 12345입니다. 사용되는 파일 이름은 다음과 같습니다....checksum=12345...zip
  2. Google 드라이브 앱 폴더 에서 zip 데이터를 검색합니다 . filename으로 파일을 찾을 수 ...checksum=12345...zip있습니다. 콘텐츠를 다운로드하십시오. 콘텐츠의 체크섬도 확인하십시오 12345.
  3. 새 zip 데이터를 기존 Google 드라이브 앱 폴더 파일에 덮어 씁니다 . 새 zip 데이터 체크섬은 67890. 기존 앱 폴더 zip 파일의 이름이...checksum=67890...zip
  4. Google 드라이브 앱 폴더 에서 zip 데이터를 검색합니다 . filename으로 파일을 찾을 수 ...checksum=67890...zip있습니다. 그러나 콘텐츠를 다운로드 한 후에도 콘텐츠의 체크섬은 여전히 ​​오래되었습니다 12345!

앱 폴더 zip 파일 다운로드

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();
    }
}

먼저

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

일을 잘하지 않습니다. 대부분의 상황에서 실패하고 오류 메시지가 표시됩니다 Sync request rate limit exceeded.. 실제로에 부과 된 하드 제한으로 requestSync인해 해당 API가 특히 유용하지 않습니다.- Android Google Play / Drive Api


그러나 requestSync성공 하더라도 loadFromGoogleDrive최신 파일 이름 만 가져올 수 있지만 체크섬 내용은 오래되었습니다.

loadFromGoogleDrive다음과 같은 관찰과 함께 캐시 된 데이터 콘텐츠를 반환하고 있다고 100 % 확신 합니다.

  1. DownloadProgressListenerin을 설치하고 driveFile.openbytesDownloaded는 0이고 bytesExpected는 -1입니다.
  2. 내가 사용하는 경우 Google 드라이브 나머지 API를 다음과 같이, 데스크탑 코드 , 내가 올바른 체크섬 내용으로 최신 파일 이름을 찾을 수 있습니다.
  3. Android 앱을 제거하고 다시 설치 loadFromGoogleDrive하면 올바른 체크섬 콘텐츠가 포함 된 최신 파일 이름을 얻을 수 있습니다.

Google 드라이브에서 캐시 된 앱 데이터를 항상로드하지 않도록하는 강력한 방법이 있습니까?


저는 데모를 제작합니다. 이 문제를 재현하는 단계는 다음과 같습니다.

1 단계 : 소스 코드 다운로드

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

2 단계 : API 콘솔에서 설정

여기에 이미지 설명 입력

3 단계 : SAVE "123.TXT"WITH CONTENT "123"버튼을 누릅니다.

여기에 이미지 설명 입력

파일 이름이 "123.TXT"이고 콘텐츠가 "123"인 파일이 앱 폴더에 생성됩니다.

4 단계 : 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.

참고URL : https://stackoverflow.com/questions/38934769/how-to-avoid-from-always-loading-cached-app-data-from-google-drive

반응형