57 votes

Comment éviter de charger systématiquement les données d'application en cache à partir de Google Drive ?

Actuellement, j'utilise API Android de Google Drive pour stocker les données de mes applications Android, pour Dossier d'applications Google Drive .

Voici ce que je fais lorsque je sauvegarde les données de mon application

  1. Génère une somme de contrôle pour le fichier zip local actuel.
  2. Rechercher dans Dossier d'applications Google Drive pour voir s'il y a un fichier zip App Folder existant.
  3. Si c'est le cas, écrasez le contenu du fichier zip du Dossier des Applications existant, avec les fichiers zip locaux actuels. De même, nous renommerons le nom du fichier zip du Dossier des Applis existant, avec la dernière somme de contrôle.
  4. S'il n'y a pas de fichier zip App Folder existant, générez un nouveau fichier zip App Folder, avec le contenu du fichier zip local. Nous utiliserons la dernière somme de contrôle comme nom de fichier zip de l'App Folder.

Voici le code qui effectue les opérations susmentionnées.

Générer un nouveau fichier zip du dossier d'application, ou mettre à jour le fichier zip du dossier d'application existant.

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

Recherchez le fichier zip du dossier d'applications existant

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

Le problème survient lors du chargement des données de l'application. Imaginez les opérations suivantes

  1. Télécharger les données zip vers Dossier d'applications Google Drive pour la première fois. La somme de contrôle est 12345 . Le nom de fichier utilisé est ...checksum=12345...zip
  2. Recherche de données zip à partir de Dossier d'applications Google Drive . Capable de trouver le fichier avec le nom de fichier ...checksum=12345...zip . Téléchargez le contenu. Vérifiez que la somme de contrôle du contenu est 12345 aussi.
  3. Ecraser les nouvelles données zip sur les données existantes Dossier d'applications Google Drive fichier. La somme de contrôle des nouvelles données zip est 67890 . Le fichier zip du dossier d'applications existant est renommé en ...checksum=67890...zip
  4. Recherche de données zip à partir de Dossier d'applications Google Drive . Capable de trouver le fichier avec le nom de fichier ...checksum=67890...zip . Cependant, après avoir téléchargé le contenu, la somme de contrôle du contenu est toujours ancienne. 12345 !

Télécharger le fichier zip du dossier de l'application

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

D'abord, j'ai pensé

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

ne fait pas bien le travail. Il échoue dans la plupart des situations, avec un message d'erreur Sync request rate limit exceeded. En fait, la limite dure imposée dans requestSync Cette API n'est donc pas particulièrement utile. Android Google Play / Drive Api


Cependant, même lorsque requestSync succès, loadFromGoogleDrive ne peut toujours obtenir que le dernier nom de fichier, mais le contenu de la somme de contrôle est périmé.

Je suis sûr à 100% loadFromGoogleDrive me renvoie un contenu de données en cache, avec les observations suivantes.

  1. J'installe un DownloadProgressListener sur driveFile.open , bytesDownloaded est 0 et bytesExpected est -1.
  2. Si j'utilise Google Drive Rest API avec les éléments suivants code de bureau Je peux trouver le dernier nom de fichier avec le contenu correct de la somme de contrôle.
  3. Si je désinstalle mon application Android et la réinstalle à nouveau, loadFromGoogleDrive sera capable d'obtenir le dernier nom de fichier avec le contenu correct de la somme de contrôle.

Existe-t-il un moyen robuste d'éviter de toujours charger les données de l'application en cache depuis Google Drive ?


J'ai réussi à produire une démo. Voici les étapes pour reproduire ce problème.

Étape 1 : Télécharger le code source

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

Étape 2 : Configuration dans la console API

enter image description here

Étape 3 : Appuyez sur le bouton SAUVEGARDER "123.TXT" AVEC LE CONTENU "123".

enter image description here

Un fichier portant le nom "123.TXT" et le contenu "123" sera créé dans le dossier de l'application.

Étape 4 : Appuyez sur le bouton SAUVEGARDER "456.TXT" AVEC LE CONTENU "456".

enter image description here

Le fichier précédent sera renommé en "456.TXT", avec un contenu mis à jour en "456".

Étape 5 : Appuyez sur le bouton CHARGER LE DERNIER FICHIER SAUVEGARDÉ

enter image description here

Le fichier dont le nom est "456.TXT" a été trouvé, mais le contenu précédent en cache "123" est lu. J'attendais le contenu "456".

Notez que, si nous

  1. Désinstallez l'application de démonstration.
  2. Réinstallez l'application de démonstration.
  3. Appuyez sur le bouton LOAD LAST SAVED FILE, un fichier portant le nom "456.TXT" et le contenu "456" est trouvé.

J'avais soumis un rapport officiel sur les problèmes - https://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=4727


Autres informations

Voici comment cela se présente sous mon appareil - http://youtu.be/kuIHoi4A1c0

Je réalise que tous les utilisateurs ne rencontreront pas ce problème. Par exemple, j'avais testé avec un autre Nexus 6, Google Play Services 9.4.52 (440-127739847). Le problème n'apparaît pas.

J'avais compilé un APK pour le tester - https://github.com/yccheok/google-drive-bug/releases/download/1.0/demo.apk

1voto

ashishb Points 159
  1. La recherche sur Google Drive est lente. Pourquoi ne pas utiliser les propriétés du dossier de base pour stocker l'identifiant du fichier zip ? https://developers.google.com/drive/v2/web/properties
  2. Les noms de fichiers sur Google Drive ne sont pas uniques, vous pouvez télécharger plusieurs fichiers avec les mêmes noms. L'ID du fichier renvoyé par Google, cependant, est unique.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X