Google 드라이브에서 항상 캐시 된 앱 데이터를로드하지 않는 방법
현재 저는 Google Drive Android API를 사용하여 Android 앱 데이터를 Google Drive App Folder에 저장하고 있습니다.
이것이 내 응용 프로그램 데이터를 저장할 때 수행하는 작업입니다.
- 현재 로컬 zip 파일에 대한 체크섬을 생성합니다.
- Google Drive 앱 폴더 에서 검색 하여 기존 앱 폴더 zip 파일이 있는지 확인합니다.
- 있는 경우 기존 앱 폴더 zip 파일의 내용을 현재 로컬 zip 파일로 덮어 씁니다. 또한 기존 앱 폴더 zip 파일 이름을 최신 체크섬으로 변경합니다.
- 기존 앱 폴더 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;
}
응용 프로그램 데이터를로드하는 동안 문제가 발생합니다. 다음 작업을 상상해보십시오.
- 처음으로 zip 데이터를 Google 드라이브 앱 폴더 에 업로드 합니다. 체크섬은
12345입니다. 사용되는 파일 이름은 다음과 같습니다....checksum=12345...zip - Google 드라이브 앱 폴더 에서 zip 데이터를 검색합니다 . filename으로 파일을 찾을 수
...checksum=12345...zip있습니다. 콘텐츠를 다운로드하십시오. 콘텐츠의 체크섬도 확인하십시오12345. - 새 zip 데이터를 기존 Google 드라이브 앱 폴더 파일에 덮어 씁니다 . 새 zip 데이터 체크섬은
67890. 기존 앱 폴더 zip 파일의 이름이...checksum=67890...zip - 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 % 확신 합니다.
DownloadProgressListenerin을 설치하고driveFile.openbytesDownloaded는 0이고 bytesExpected는 -1입니다.- 내가 사용하는 경우 Google 드라이브 나머지 API를 다음과 같이, 데스크탑 코드 , 내가 올바른 체크섬 내용으로 최신 파일 이름을 찾을 수 있습니다.
- 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
- Uninstall demo app.
- Re-install demo app.
- 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
- 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
- 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.
'Program Club' 카테고리의 다른 글
| PEM_read_bio_PrivateKey ()는 ECB 모드에서만 NULL을 반환합니다. (0) | 2020.12.13 |
|---|---|
| Data.Acid를 사용할 때 이벤트 구현 변경을 처리하는 방법 (0) | 2020.12.13 |
| Android 프로파일 러를 사용할 때 앱이 계속 충돌합니다. (0) | 2020.12.13 |
| PHP에서 템플릿 시스템을 사용해야하는 이유는 무엇입니까? (0) | 2020.12.12 |
| Perl에 열거 형이 있습니까? (0) | 2020.12.12 |



