Alchete
Alchete

Reputation: 1649

Android Google DriveFile metadata is cached and not fetched from the cloud

Burcu Dogan wrote some example code showing how to sync a local preferences file to the user's Google Drive appfolder, found here: https://github.com/googledrive/appdatapreferences-android

I've converted this example to use the current Drive SDK, now shipping with Google Play Services.

If I update the cloud Drive file with device 1, and then run the following code on device 2, I'm getting a stale "modified" timestamp from the metadata. I'm assuming this is because the results are from a local cache of the Drive file:

Step 1. Look up the preferences file by name, with a query:

    /**
     * Retrieves the preferences file from the appdata folder.
     * @return Retrieved preferences file or {@code null}.
     * @throws IOException
     */
    public DriveFile getPreferencesFile() throws IOException
    {
        if (mDriveFile != null)
            return mDriveFile;

        GoogleApiClient googleApiClient = getGoogleApiClient();

        if (!googleApiClient.isConnected())
            LOGW(TAG, "getPreferencesFile -- Google API not connected");
        else
            LOGD(TAG, "getPreferencesFile -- Google API CONNECTED");

        Query query = new Query.Builder()
                .addFilter(Filters.contains(SearchableField.TITLE, FILE_NAME))
                .build();

        DriveApi.MetadataBufferResult metadataBufferResult =
                Drive.DriveApi.query(getGoogleApiClient(), query).await();

        if (!metadataBufferResult.getStatus().isSuccess()) {
            LOGE(TAG, "Problem while retrieving files");
            return null;
        }

        MetadataBuffer buffer = metadataBufferResult.getMetadataBuffer();

        LOGD(TAG, "Preference files found on Drive: " +
                buffer.getCount());

        if (buffer.getCount() == 0)
        {
            // return null to indicate the preference file doesn't exist
            mDriveFile = null;

            // create a new preferences file
//            mDriveFile = insertPreferencesFile("{}");
        }
        else
            mDriveFile = Drive.DriveApi.getFile(
                    getGoogleApiClient(),
                    buffer.get(0).getDriveId());

        // Release the metadata buffer
        buffer.release();

        return mDriveFile;
    }

Step 2. Get the metadata for the file:

// Get the metadata
DriveFile file;
DriveResource.MetadataResult result = file.getMetadata(getGoogleApiClient()).await();
Metadata metadata = result.getMetadata();
// Get the modified dates
metadata.getModifiedDate();

More curiously, after running the code below (which just lists the appdatafolder files and their content) the metadata modified date, fetched above, becomes correct!! Why???

/**
 *
 * Simple debug activity that lists all files currently in Drive AppFolder and their contents
 *
 */
public class ActivityViewFilesInAppFolder extends BaseActivity {

    private static final String TAG = "ActivityViewFilesInAppFolder";

    private TextView mLogArea;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Add a text view to the window
        ScrollView layout = new ScrollView(this);
        setContentView(layout);

        mLogArea = new TextView(this);

        layout.addView(mLogArea);

        ApiClientAsyncTask<Void, Void, String> task = new ApiClientAsyncTask<Void, Void, String>(this) {
            @Override
            protected String doInBackgroundConnected(Void[] params) {
                StringBuffer result = new StringBuffer();

                MetadataBuffer buffer = Drive.DriveApi.getAppFolder(getGoogleApiClient())
                        .listChildren(getGoogleApiClient()).await().getMetadataBuffer();

                result.append("found " + buffer.getCount() + " files:\n");

                for (Metadata m: buffer) {
                    DriveId id = m.getDriveId();
                    DriveFile file = Drive.DriveApi.getFile(getGoogleApiClient(), id);

                    DriveContents contents = file.open( getGoogleApiClient(),
                            DriveFile.MODE_READ_ONLY, null).await().getDriveContents();

                    FileInputStream is = new FileInputStream(contents.getParcelFileDescriptor()
                            .getFileDescriptor());
                    try {
                        BufferedReader bf = new BufferedReader(new InputStreamReader(is, Charsets.UTF_8));
                        String line=null; StringBuffer sb=new StringBuffer();
                        while ((line=bf.readLine()) != null ) {
                            sb.append(line);
                        }
                        contents.discard(getGoogleApiClient());
                        result.append("*** " + m.getTitle() + "/" + id + "/"
                                + m.getFileSize() + "B:\n   [" + sb.toString() + "]\n\n");
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
                buffer.release();
                return result.toString();
            }

            @Override
            protected void onPostExecute(String s) {
                if (mLogArea != null) {
                    mLogArea.append(s);

                    Map<String, ?> values = PreferenceManager
                            .getDefaultSharedPreferences(ActivityViewFilesInAppFolder.this).getAll();
                    String localJson = new GsonBuilder().create().toJson(values);

                    LOGD(TAG, "Local: " + localJson);
                    LOGD(TAG, "File:  " + s);
                }
            }
        };
        task.execute();
    }
}

Is the metadata reading from a cached local copy, unless something kicks it?

Does anyone know how to force these APIs to always pull the results from the remote Drive file?

Upvotes: 2

Views: 2052

Answers (2)

Alchete
Alchete

Reputation: 1649

Well, I just found the secret sauce to trick the new Drive API into reading metadata from the remote file instead of the local cache.

Even though reading metadata doesn't require the file to be opened, it turns out that the file needs to be opened!

So the working code to read the latest metadata from the cloud is as follows:

DriveFile file;

// Trick Google Drive into fetching the remote file
// which has the latest metadata
file.open( getGoogleApiClient(), DriveFile.MODE_READ_ONLY, null).await();

DriveResource.MetadataResult result = file.getMetadata(getGoogleApiClient()).await();
Metadata metadata = result.getMetadata();

// Get the modified date
metadata.getModifiedDate();

Question for Google -- is this working as intended? The metadata is cached unless you first open the file read_only?

Upvotes: 1

seanpj
seanpj

Reputation: 6755

I have an answer to your question. Well 'kind of answer', and I'm sure you will not be happy with it.

I had used RESTful API in my app before switching to GDAA. And after I did, I realized that GDAA, another layer with timing delays I have no control over, is causing issues in an app that attempts to keep multiple Android devices synchronized. See SO 22980497 22382099, 22515028, 23073474 and just grep for 'requestSync'.

I was hoping that GDAA implemented some kind of GCM logic to synchronize 'behind-the-scenes'. Especially when there is the 'addChangeListener()' method that seems to be designed for that. It does not look to be the case (at least not around Sept 2014). So, I backed off to a true-and-tested scheme of using RESTful API to talk to Google Drive with DataProvider and SyncAdapter logic behind it (much like shown in the UDACITY Class here).

What I'm not happy about, is somewhat ambiguous documentation of GDAA using terms like 'synchronize' not telling us if it is 'local' of 'network' synchronization. And not answering questions like the SO 23073474 mentioned above.

It appears (and I am not a Google insider) that GDAA has been designed for apps that do not immediately synchronize between devices. Unfortunately this has not been mentioned here or here - see 1:59, costing me a lot of time and frustration.

Now the question is: Should I (we) wait until we get 'real time' synchronization from GDAA, or should we go ahead and work on home-grown GCM based sync on top of RESTful-DataProvider-SyncAdapter?

Well, I personally will start working on GCM sync and will maintain an easy-to-use miniapp that will test GDAA behavior as new versions of Google Play Services come out. I will update this answer as soon as I have the 'test miniapp' ready and up in GitHub. Sorry I did not help much with the problem itself.

Upvotes: 4

Related Questions