Reputation: 770
I have a database backup stored in app folder in the drive. Below is the code I have written.
public void startRestore(View view)
{
int EXTERNAL_WRITE_PERMISSION = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.M)
{
if(EXTERNAL_WRITE_PERMISSION != PackageManager.PERMISSION_GRANTED)
{
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE))
{
Snackbar.make(mLayout, "Write permission is required",
Snackbar.LENGTH_INDEFINITE).setAction("OK", new View.OnClickListener() {
@Override
public void onClick(View view) {
// Request the permission
ActivityCompat.requestPermissions(BackupActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSION_REQUEST_STORAGE);
}
}).show();
} else {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSION_REQUEST_STORAGE);
}
}
}
if (backupExists())
{
Log.d("RESTORE: ", "Started restore");
final String driveFileID = sharedPreferences.getString("dbBackupDriveFileID", "");
final DriveFile driveFile = DriveId.decodeFromString(driveFileID).asDriveFile();
Log.d("RESTORE_FileID: ", driveFileID);
final Task<DriveContents> openFileTask = mDriveResourceClient.openFile(driveFile, DriveFile.MODE_READ_ONLY);
openFileTask.continueWithTask(new Continuation<DriveContents, Task<Void>>()
{
@Override
public Task<Void> then(@NonNull Task<DriveContents> task) throws Exception
{
Log.d("RESTORE: ", "open File task");
DriveContents driveContents = task.getResult();
//TODO download file an add to database
InputStream inputStream = driveContents.getInputStream();
byte[] buf = new byte[8192];
int c = 0;
String baseDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
String fileName = DatabaseHelper.DATABASE_NAME;
Log.d("RESTORE: ", baseDir + "/" +fileName);
File f = new File(baseDir+File.pathSeparator+fileName);
if(f.canWrite())
{
Log.d("RESTORE: ", "File writable");
OutputStream outputStream = new FileOutputStream(f);
while ((c = inputStream.read(buf, 0, buf.length)) > 0)
{
outputStream.write(buf, 0, c);
outputStream.flush();
}
outputStream.close();
}
else
{
Log.d("RESTORE: ", "File not writable");
}
return mDriveResourceClient.discardContents(driveContents);
}
})
.addOnFailureListener(new OnFailureListener()
{
@Override
public void onFailure(@NonNull Exception e)
{
}
});
}
else
{
Toast.makeText(this, "Backup does not exists", Toast.LENGTH_SHORT).show();
}
}
In the above code the the control always reaches to Log.d("RESTORE: ", "File not writable");
. I have the write permissions defined in the manifest and also runtime permission is granted. Also there is no error in the log.
Below is the backup function for reference.
public void startBackup(View view)
{
final ProgressDialog progressDialog = new ProgressDialog(this);
final File currentDB = this.getDatabasePath(DatabaseHelper.DATABASE_NAME);
Log.d("DATABASE: ", currentDB.getAbsolutePath());
Log.d("DATABASE: ", currentDB.getName());
progressDialog.setMessage("Backing Up!!!!");
progressDialog.show();
final Task<DriveFolder> appFolderTask = mDriveResourceClient.getAppFolder();
final Task<DriveContents> createContentsTask = mDriveResourceClient.createContents();
Tasks.whenAll(appFolderTask, createContentsTask)
.continueWithTask(new Continuation<Void, Task<DriveFile>>()
{
@Override
public Task<DriveFile> then(@NonNull Task<Void> task) throws Exception
{
DriveFolder parent = appFolderTask.getResult();
DriveContents contents = createContentsTask.getResult();
InputStream inputStream = null;
try
{
inputStream = new FileInputStream(currentDB);
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
OutputStream outputStream = contents.getOutputStream();
int c = 0;
byte[] buf = new byte[8192];
if (inputStream != null)
{
while ((c = inputStream.read(buf, 0, buf.length)) > 0)
{
outputStream.write(buf, 0, c);
outputStream.flush();
}
outputStream.close();
}
else
{
Toast.makeText(BackupActivity.this, "Some error occurred", Toast.LENGTH_SHORT).show();
}
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setMimeType("application/x-sqlite3")
.setTitle(currentDB.getName())
.build();
return mDriveResourceClient.createFile(parent, changeSet, contents);
}
})
.addOnSuccessListener(this, new OnSuccessListener<DriveFile>() {
@Override
public void onSuccess(DriveFile driveFile)
{
progressDialog.dismiss();
String driveFileID = driveFile.getDriveId().encodeToString();
String dateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date());
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("dbBackupDriveFileID", driveFileID);
editor.putString("lastDbBackupTime", dateTime);
editor.apply();
Log.d("DRIVE_FILE", driveFileID);
String d = getString(R.string.last_backup) + dateTime;
textView.setText(d);
Toast.makeText(BackupActivity.this, "Backup Successful. File "+driveFile.getDriveId()
.encodeToString(), Toast.LENGTH_LONG).show();
}
})
.addOnFailureListener(this, new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e)
{
progressDialog.dismiss();
Log.e("DRIVE ", "Unable to create file", e);
Toast.makeText(BackupActivity.this, "Unable to backup", Toast.LENGTH_SHORT).show();
}
});
}
Upvotes: 0
Views: 569
Reputation: 770
Instead of using the File f = new File(baseDir+File.pathSeparator+fileName);
I replaced the File usage with an FileOutputStream
.
Modified part of the restore function:
DriveContents driveContents = task.getResult();
//TODO download file an add to database
InputStream inputStream = driveContents.getInputStream();
byte[] buf = new byte[8192];
int c = 0;
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
{
Log.d("RESTORE: ", "External DIR mounted");
String baseDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
String fileName = DatabaseHelper.DATABASE_NAME;
String fileFullName = baseDir + File.separator + fileName;
Log.d("RESTORE: ", fileFullName);
FileOutputStream outputStream;
outputStream = new FileOutputStream(fileFullName, false);
while ((c = inputStream.read(buf, 0, buf.length)) > 0) {
outputStream.write(buf, 0, c);
outputStream.flush();
}
outputStream.close();
}
else
{
Log.d("RESTORE: ", "External DIR not mounted");
}
This solved my issue.
Upvotes: 1
Reputation: 57043
The answer could be as simple as adding f.mkdirs()
immediately after File f = new File(baseDir+File.pathSeparator+fileName);
and before if(f.canWrite())
.
However there are numerous reasons why canWrite
can return false, So you should check the STATE (probably before you try canWrite
)
Personally I utilise the following rather long winded code :-
class StoreData {
private String directory; //Note built internally and includes subdirectory
private String subdirectory;
private String filename;
private boolean mounted;
private boolean inerror;
private boolean fileexists;
private boolean direxists;
private long errorcode;
private ArrayList<String> errorlist = new ArrayList<>();
private ArrayList<File> otherfilesindirectory = new ArrayList<>();
// Need to be aware of the API
@SuppressWarnings("unused")
public static final int API_VERSION = Build.VERSION.SDK_INT;
private static final long UNMOUNTED = 1;
private static final long FILEIOERR = 2;
private static final long READERR = 4;
private static final String NEWLINE = "\r\n";
/**
* Sole Constructor for a StoreData object
* Note instantiating creates but the deletes a file, assuming that
* no prior errors left the instance in an unusable state (as initially set)
* Note instantiating, if existcheck (3rd param) is true, does not create
* and delete the file, rather it checks that the file exists
* typically for reading an existing file.
*
* @param subdirectory - Sub directory in which to create file
* @param filename - the file name where actual data will be stored
* @param existcheck - whether or not to check for the existence of the file
*
* Note!! existcheck, if true, will not try to create the file
*/
public StoreData(String subdirectory, @SuppressWarnings("SameParameterValue") String filename, boolean existcheck) {
fileexists = false;
direxists = false;
mounted = false;
inerror = false;
errorcode = 0;
this.directory = "";
this.subdirectory = subdirectory;
this.filename = filename;
// External Storage must be mounted.
String chkmnt = Environment.getExternalStorageState();
if(!(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))) {
switch (Environment.getExternalStorageState()) {
case Environment.MEDIA_SHARED : {
errorlist.add(
"Although External Storage is present." +
" It cannot be used as it's in use via USB." +
"\nDisconnect the USB cable and then try again."
);
break;
}
case Environment.MEDIA_REMOVED : {
errorlist.add(
"External Storage is not present." +
"\nInsert an SD Card."
);
break;
}
case Environment.MEDIA_EJECTING : {
errorlist.add(
"External Storage is being ejected." +
"\nRe-insert the SD Card."
);
break;
}
case Environment.MEDIA_NOFS : {
errorlist.add(
"External Storage is blank or does not have the correct" +
" filesystem present." +
"\nUse a valid SDCard."
);
break;
}
case Environment.MEDIA_BAD_REMOVAL : {
errorlist.add(
"External Storage was removed incorrectly." +
"\nRe-insert the SD Card, if this fails then" +
" try restarting the device."
);
break;
}
case Environment.MEDIA_CHECKING : {
errorlist.add(
"External Storage is unavailable as it is being checked." +
"\nTry again."
);
}
case Environment.MEDIA_MOUNTED_READ_ONLY : {
errorlist.add(
"External Storage is READ ONLY." +
"\nInsert an SD card that is not protected."
);
}
case Environment.MEDIA_UNKNOWN : {
errorlist.add(
"External Storage state is UNKNOWN." +
"\ntry a different SD Card."
);
}
case Environment.MEDIA_UNMOUNTABLE : {
errorlist.add(
"External Storage cannot be mounted." +
"\nTry re-inserting the SD Card or using a different SD Card."
);
}
case Environment.MEDIA_UNMOUNTED : {
errorlist.add(
"External Storage is not mounted." +
"\nTry re-inserting the SD Card or using a different SD Card."
);
}
default: {
errorlist.add(
"Undefined Error"
);
}
}
this.errorcode = UNMOUNTED;
return;
} else {
this.mounted = true;
}
// Get the required directory and specified sub directory
File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),subdirectory);
this.directory = dir.getPath();
// If existcheck is true check that the directories exist
if (existcheck && dir.exists()) {
direxists = true;
}
// If the directories do not exist try to create them and redo check
// Note! existcheck is more for file level so always try to create
// directories
else {
boolean x = dir.mkdirs();
if(dir.exists()) {
direxists = true;
}
}
if(direxists) {
refreshOtherFilesInDirectory();
}
// File level
File f = new File(directory,filename);
// Check if the file exists if requested and return if it does
if (existcheck) {
if (f.exists()) {
fileexists = true;
}
return;
}
try {
boolean x = f.createNewFile();
}
catch (IOException e) {
e.printStackTrace();
this.errorcode = FILEIOERR ;
errorlist.add(
"File Error " + e.getMessage()
);
return;
}
boolean x = f.delete();
}
@SuppressWarnings({"ConstantConditions", "UnusedReturnValue"})
public boolean refreshOtherFilesInDirectory() {
boolean rv = true;
File dir = new File(directory);
File[] dirlist = dir.listFiles();
if((dirlist.length) > 0) {
// Sort the list
Arrays.sort(dirlist, new Comparator<File>() {
@Override
public int compare(File object1, File object2) {
return object1.getName().compareTo(object2.getName());
}
});
otherfilesindirectory.clear();
for (File aDirlist : dirlist) {
if (!(aDirlist.getName().equals(this.filename))) {
otherfilesindirectory.add(aDirlist);
}
}
}
return rv;
}
/**
* writeData - Write data to the file from String Arraylist passed
* Note!! a linefeed is added to each string
* @param datatowrite - strng ArrayList holding data to write
* @return result flag
*/
@SuppressWarnings("unused")
public boolean writeData(ArrayList<String> datatowrite) {
// Check that this instance is OK
if (!this.isOK()) {
this.errorlist.add(
"\nError prior to call to writeData method."
);
return false;
}
// Prepare to write
this.errorlist.clear();
File f = new File(this.directory,File.separator + this.filename);
try {
boolean x = f.createNewFile();
FileOutputStream fos = new FileOutputStream(f);
OutputStreamWriter osw = new OutputStreamWriter(fos);
for (int i = 0; i < datatowrite.size(); i++) {
osw.write(datatowrite.get(i) + NEWLINE);
}
osw.flush();
osw.close();
fos.flush();
fos.close();
this.fileexists = true;
}
catch (IOException e) {
e.printStackTrace();
this.errorcode = FILEIOERR;
errorlist.add(
"File Error " + e.getMessage()
);
return false;
}
return true;
}
/**
* readData - Populate a String ArrayList from the data in the file
* Note! Assumes linefeeds in the file separate strings of data
* @return - result flag
*/
@SuppressWarnings("unused")
public ArrayList<String> readData() {
ArrayList<String> rv = new ArrayList<>();
if(!this.isOKandExists()) {
this.errorlist.add(
"\nError prior to call to readData method or the file doesn't exist."
);
this.errorcode = READERR;
return rv;
}
this.errorlist.clear();
File f = new File(this.directory,File.separator + this.filename);
try {
FileInputStream fis = new FileInputStream(f);
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
String line;
while((line = br.readLine()) != null) {
rv.add(line);
}
}
catch (IOException e) {
e.printStackTrace();
this.errorcode = READERR;
errorlist.add(
"File Read Error" + e.getMessage()
);
return rv;
}
return rv;
}
/**
* isOK - Check if object is usable
* @return true if OK else false
*/
public boolean isOK() {
return !(errorcode != 0 || !mounted || inerror);
}
/**
* exists = Check if the file exists
* @return - Result of check
*/
@SuppressWarnings("unused")
public boolean exists() {
return this.fileexists;
}
public boolean isOKandExists() {
return this.isOK() && this.fileexists;
}
/**
* Return a string displaying the instances details
* @return string displaying object's members
*/
public String Display() {
String rv;
rv = "Directory path=" + directory + "\n" +
"SubDirectory=" + subdirectory + "\n" +
"Filename=" + filename + "\n" +
"Mounted =" + Boolean.toString(mounted) + "\n" +
"Directory Exists=" + Boolean.toString(this.direxists) + "\n" +
"File Exists=" + Boolean.toString(this.fileexists) + "\n" +
"In Error=" + Boolean.toString(inerror) + "\n" +
"Last Error Code=" + Long.toString(errorcode);
return rv;
}
@SuppressWarnings("unused")
public String DisplayWithOtherFiles() {
String rv;
rv = this.Display() + "\nOther Files in Directory (" + this.directory + ") ";
for(int i = 0; i < otherfilesindirectory.size(); i++) {
rv = rv + "\n\t" + otherfilesindirectory.get(i).getName();
}
return rv;
}
/**
* Retrieve generated error messages. if any
* @return sting comprised of all error messages generated
*/
@SuppressWarnings("unused")
public String getErrorMessages() {
String rv = "";
for(int i = 0; i < errorlist.size(); i++) {
rv = rv + errorlist.get(i);
}
return rv;
}
/**
* Method: getDirectory - get the backup directory as a String
* @return Directory as a String
*/
public String getDirectory() {
return this.directory;
}
/**
* Method: getFilename - get the filename of the object as a String
* @return Filename as a String
*/
@SuppressWarnings("unused")
public String getFilename() {
return this.filename;
}
/**
* Method: getSubDirectory - get the sub-directory as a string
* @return Sub-Directory as a String
*/
@SuppressWarnings("unused")
public String getSubDirectory() {
return this.subdirectory;
}
/**
* Method: getFilesInDirectory - return an ArrayList of type File
* @return List of files in the directory as an ArrayList<File>
*/
public ArrayList<File> getFilesInDirectory() {
return this.otherfilesindirectory;
}
}
Upvotes: 0