How to replace file in Google Drive?

2019-05-14 15:01发布

问题:

Using the following code, which is take from android-quickstart, this code can produce multiple files with same name if you take multiple pictures. How can it be modified to ensure the file with the same name is replaced?

public class MainActivity extends Activity implements ConnectionCallbacks,
        OnConnectionFailedListener {

    private static final String TAG = "android-drive-quickstart";
    private static final int REQUEST_CODE_CAPTURE_IMAGE = 1;
    private static final int REQUEST_CODE_CREATOR = 2;
    private static final int REQUEST_CODE_RESOLUTION = 3;

    private GoogleApiClient mGoogleApiClient;
    private Bitmap mBitmapToSave;

    /**
     * Create a new file and save it to Drive.
     */
    private void saveFileToDrive() {
        // Start by creating a new contents, and setting a callback.
        Log.i(TAG, "Creating new contents.");
        final Bitmap image = mBitmapToSave;
        Drive.DriveApi.newContents(mGoogleApiClient).setResultCallback(new ResultCallback<ContentsResult>() {

            @Override
            public void onResult(ContentsResult result) {
                // If the operation was not successful, we cannot do anything
                // and must
                // fail.
                if (!result.getStatus().isSuccess()) {
                    Log.i(TAG, "Failed to create new contents.");
                    return;
                }
                // Otherwise, we can write our data to the new contents.
                Log.i(TAG, "New contents created.");
                // Get an output stream for the contents.
                OutputStream outputStream = result.getContents().getOutputStream();
                // Write the bitmap data from it.
                ByteArrayOutputStream bitmapStream = new ByteArrayOutputStream();
                image.compress(Bitmap.CompressFormat.PNG, 100, bitmapStream);
                try {
                    outputStream.write(bitmapStream.toByteArray());
                } catch (IOException e1) {
                    Log.i(TAG, "Unable to write file contents.");
                }
                // Create the initial metadata - MIME type and title.
                // Note that the user will be able to change the title later.
                MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder()
                        .setMimeType("image/jpeg")
                        .setTitle("Android Photo.png")
                        .build();
                // Create an intent for the file chooser, and start it.
                IntentSender intentSender = Drive.DriveApi
                        .newCreateFileActivityBuilder()
                        .setInitialMetadata(metadataChangeSet)
                        .setInitialContents(result.getContents())
                        .build(mGoogleApiClient);
                try {
                    startIntentSenderForResult(
                            intentSender, REQUEST_CODE_CREATOR, null, 0, 0, 0);
                } catch (SendIntentException e) {
                    Log.i(TAG, "Failed to launch file chooser.");
                }
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mGoogleApiClient == null) {
            // Create the API client and bind it to an instance variable.
            // We use this instance as the callback for connection and connection
            // failures.
            // Since no account name is passed, the user is prompted to choose.
            mGoogleApiClient = new GoogleApiClient.Builder(this)
                    .addApi(Drive.API)
                    .addScope(Drive.SCOPE_FILE)
                    .addConnectionCallbacks(this)
                    .addOnConnectionFailedListener(this)
                    .build();
        }
        // Connect the client. Once connected, the camera is launched.
        mGoogleApiClient.connect();
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Drive.API)
                .addScope(Drive.SCOPE_FILE)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();
    }
    @Override
    protected void onStart() {
        super.onStart();
        mGoogleApiClient.connect();
    }
    @Override
    protected void onPause() {
        if (mGoogleApiClient != null) {
            mGoogleApiClient.disconnect();
        }
        super.onPause();
    }

    @Override
    protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
        switch (requestCode) {
            case REQUEST_CODE_CAPTURE_IMAGE:
                // Called after a photo has been taken.
                if (resultCode == Activity.RESULT_OK) {
                    // Store the image data as a bitmap for writing later.
                    mBitmapToSave = (Bitmap) data.getExtras().get("data");
                }
                break;
            case REQUEST_CODE_CREATOR:
                // Called after a file is saved to Drive.
                if (resultCode == RESULT_OK) {
                    Log.i(TAG, "Image successfully saved.");
                    mBitmapToSave = null;
                    // Just start the camera again for another photo.
                    startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
                            REQUEST_CODE_CAPTURE_IMAGE);
                }
                break;
        }
    }

    @Override
    public void onConnectionFailed(ConnectionResult result) {
        // Called whenever the API client fails to connect.
        Log.i(TAG, "GoogleApiClient connection failed: " + result.toString());
        if (!result.hasResolution()) {
            // show the localized error dialog.
            GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), this, 0).show();
            return;
        }
        // The failure has a resolution. Resolve it.
        // Called typically when the app is not yet authorized, and an
        // authorization
        // dialog is displayed to the user.
        try {
            result.startResolutionForResult(this, REQUEST_CODE_RESOLUTION);
        } catch (SendIntentException e) {
            Log.e(TAG, "Exception while starting resolution activity", e);
        }
    }

    @Override
    public void onConnected(Bundle connectionHint) {
        Log.i(TAG, "API client connected.");
        if (mBitmapToSave == null) {
            // This activity has no UI of its own. Just start the camera.
            startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
                    REQUEST_CODE_CAPTURE_IMAGE);
            return;
        }
        saveFileToDrive();
    }

    @Override
    public void onConnectionSuspended(int cause) {
        Log.i(TAG, "GoogleApiClient connection suspended");
    }
}

回答1:

I open the drive file for writing as follows:

writeDriveFile(DriveFile file, Handler handler){
    //see query task below to get a drive file by its name. Be careful you can get multiple data elements in the MetadataBuffer below if you have uploaded multiple files with the same name.
    Task<DriveContents> openFileTask = myDriveResourceClient.openFile(file, DriveFile.MODE_WRITE_ONLY);

Then with that task write some object bytes to the stream someObject.getBytes(), no magic here. Then commit the results.

openFileTask
            .continueWithTask(task -> {
                DriveContents contents = task.getResult();

                // Process contents...
                try (OutputStream writer = contents.getOutputStream()) {
                    writer.write(someObject.getBytes());
                    writer.close();
                }

                //Add whatever metadata you want here
                MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
                        .setLastViewedByMeDate(new Date())
                        .build();

                //commit the file to Google Drive
                Task<Void> commitTask = myDriveResourceClient.commitContents(contents, changeSet);
                handler.onWriteResults();
                return commitTask;
            })
            .addOnFailureListener(e -> {
                // Handle failure
                Log.e(TAG, "Unable to read contents", e);
                handler.onDriveError(e);
            });

The handler is just an interface I defined for errors or results that I want to process after Drive API execution. You could also add an addOnCompleteListener() to process something once the writing was complete.

file is an instance of DriveFile that can be acquired by a query task. The metadata that come to you in the task code block has a getDriveId() method which has a asDrivefFile() method to get the Drive file you need above.

Query query = new Query.Builder()
            .addFilter(Filters.eq(SearchableField.TITLE, "file name"))
            .build();
Task<MetadataBuffer> queryTask = mDriveResourceClient.query(query);

Then process the MetadataBuffer

queryTask.continueWithTask(
                    task -> {
                        MetadataBuffer metadataBuffer = task.getResult() ;
                        //I have this loop because I wanted to know if there were other versions of the file on the drive
                        for(Metadata data : metadataBuffer) {
                            Log.d(TAG, "******************* metadataBuffer title is " + data.getTitle());

                            if(data.getTitle().equals("file name")){
                                //this is just a method I defined that encapsulates the drive writing code above.
                                writeDriveFile(data.getDriveId().asDriveFile(), jsonContent, handler);
                            }
                        }
                        return task;
                    })
            .addOnCompleteListener(task -> {
                //some complete tasks
                }
            })
            .addOnFailureListener(e -> {
                Log.e(TAG, "************************** Error searching for " + fileName, e);
                handler.onDriveError(e);
            });

This code was all for writing to the App folder but you just need to set the right scope when you create you resource client.

Deleting a drive file can be done as follows.

 public void deleteDriveFile(DriveResource file){
    Task<Void> deleteTask = mDriveResourceClient.delete(file);
    deleteTask
            .continueWith(task -> {
                Log.d(TAG, "************* Deleted drive file: " + file.getDriveId().toInvariantString());
                return task;
            })
            .addOnFailureListener(e -> {
                //log some sort of error for yourself
            });
}


回答2:

We can use the Google Drive API to:
- Find the existing file
- Get its ID
- Delete the file
- Write a new file