I am using GDAA in my application for managing my application files in google drive. All the below listed operations work fine like
google sign-in (scope is added for AppData Folder)
download file from AppData Folder
upload file to AppData Folder
delete file from AppData Folder
but when I try to overwrite a file in AppData Folder i am getting the following error in the onResult() callback.
Status Message : Failed to commit changes.
Status Code : INTERNAL_ERROR (8)
I am unable to understand why this is happening. Please find below my code for reference
public void overwrite(String strLocalFilePath, String strDriveId, String strGoogleDriveFileMimeType, String strGoogleDriveFileTitle){
final DriveId driveId = DriveId.decodeFromString(strDriveId);
DriveFile file = driveId.asDriveFile();
file.open(mGoogleApiClient, DriveFile.MODE_WRITE_ONLY, null).setResultCallback(new ResultCallback<DriveApi.DriveContentsResult>() {
#Override
public void onResult(DriveApi.DriveContentsResult result) {
if (!result.getStatus().isSuccess()) {
Log.e(TAG,"Error");
return;
}
DriveContents driveContents = result.getDriveContents();
OutputStream outputStream = driveContents.getOutputStream();
boolean isSuccess = writeFileToStream(outputStream, strLocalFilePath);
if (isSuccess) {
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle(strGoogleDriveFileTitle)
.setMimeType(strGoogleDriveFileMimeType)
.build();
ExecutionOptions executionOptions = new ExecutionOptions.Builder()
.setNotifyOnCompletion(true)
.setTrackingTag("SAMPLE_TRACKING_TAG")
.build();
driveContents.commit(mGoogleApiClient, changeSet, executionOptions).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
// Handle the response status
if (!status.getStatus().isSuccess()) {
Log.e(TAG, "Error while trying to overwrite file. Message : "+status.getStatus().getStatusMessage() + " Status code : "+status.getStatus().getStatusCode());
return;
}else{
Log.d(TAG,"File overwritten successfully!!");
}
}
});
} else {
Log.e(TAG, "File I/O Error occurred : "+ strGoogleDriveFileTitle);
}
}
});
}
private boolean writeFileToStream (OutputStream oos, String filePath){
if (oos != null) {
InputStream is = null;
try {
Log.d(TAG, "Started writing file : "+filePath);
is = new FileInputStream(filePath);
byte[] buf = new byte[4096];
int c;
while ((c = is.read(buf, 0, buf.length)) > 0) {
oos.write(buf, 0, c);
oos.flush();
}
Log.d(TAG, "Finished writing file : "+filePath);
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
try {
if(oos != null) {
oos.close();
}
if(is != null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}
return false;
}
According to this thread, if you get error code 8 (INTERNAL_ERROR), please double check your app registration in dev console. Note that every registered Android client is uniquely identified by the (package name, Android Signing Certificate SHA-1) pair. If you have multiple package names / signing certificate for your debug and production environments, make sure to register every pair of them.
To verify:
Open the Credentials page and select your project
Make sure every pair has an Android typed OAuth 2.0 client IDs.
To create a new OAuth 2.0 client ID for your Android client, select New Credentials->OAuth2 Client ID from the dropdown, select Android and input your Package name / Signing-certificate fingerprint there.
To get your signing key certificate SHA-1:
Standard Debug Key
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
Other (Custom) Key
keytool -list -v -keystore $YOUR_KEYSTORE_LOCATION
Here's a reference which might also help:
Error : ConnectionResult{statusCode=INTERNAL_ERROR, resolution=null}
If you are getting this bug even after 1) making sure that you have registered the package name with its corresponding certificate fingerprint, and 2) are (re)using an already existing project, then you should check that this project has an product name and an email address (double check that one specially) associated with it, both to be found in the "consent screen" section.
Related
I want to sign the pdf using pdf digest. I have created the hash using below code,
byte[] buffer = new byte[1024];
int numOfBytesRead =0;
MessageDigest md = null;
md = MessageDigest.getInstance("SHA256","BC");
while((numOfBytesRead = content.read(buffer)) != -1 ){
md.update(buffer, 0, numOfBytesRead);
}
byte[] digest = md.digest();
At the end I need to attach this signature to my pdf. I have found one solution Create pkcs7 signature from file digest, but the algorithm used in the link is SHA256withRSA. My privatekey is genearted using EC algorithm and I need to use SHA256withECDSA.Is it possible to just sign the Hash using SHA256withECDSA and attach the signature to the pdf using PDFBox ExternalSigning Interface.
There are several situations in which Adobe calls a signer's certificate invalid even though apparently it is valid; in the case at hand in particular:
Key usage or Extended key usage values not appropriate
PAdES signature misses an ESS signing-certificate-v2 attribute
Key usage or Extended key usage values not appropriate
This is based on the information the OP first published as an answer
I tried below code still the pdf says Signature is invalid. Can you please check the code,
[...]
I have attached the pdf . Pdf file created
Indeed, Adobe Reader says the signature is invalid, but look more closely:
It says "Document has not been modified since this signature was applied" - This means that the signature is mathematically correct!
The issue is that the "Signer's certificate is invalid", and the reason for this can be seen when digging into the signature property dialogues:
Thus, the problem is that your signer certificate is Not valid for usage.
This is due to the highlighted attribute, while the Key Usage Digital Signature is ok, the "Extended key usage" 1.3.6.1.5.5.8.2.2 (OID for IPSEC Protection) is not!
According to the Adobe Digital Signatures Guide for IT, Adobe Acrobat accepts only
one or more of the following Key usage values (if any)
nonRepudiation
signTransaction (11.0.09 only)
digitalSignature (11.0.10 and later)
and one or more of the following Extended key usage values (if any)
emailProtection
codeSigning
anyExtendedKeyUsage
1.2.840.113583.1.1.5 (Adobe Authentic Documents Trust)
Due to its IPSEC Protection extended key usage value, therefore, your certificate is not considered valid for signing PDF documents.
This probably only is an issue in legacy ISO 32000-1 signatures, probably not in PAdES signatures.
PAdES signature misses an ESS signing-certificate-v2 attribute
This is based on the information the OP first published as an answer
I have created 2 pdf files, PDFA is signed using the digest of the pdf content with below code,
[...]
PDFA
PDFB is created with the same private key and certificate, but instead of digest I am using pdf document content directly which gives me valid signed pdf, PDFB code below,
[...]
PDFB
I think something is missing in the signing part of PDFA which I couldn't figure out.
Here the main difference is not whether you explicitly calculate the hash yourself or allow it to be calculated implicitly, the main difference is that the signature in PDFB includes an ESS signing-certificate-v2 attribute while the one in PDFA does not. This attribute is generated between
//PAdES - PDF Advanced Electronic Signature
and
//PAdES-end
As the comments already hint, this is only necessary for PAdES signatures, not for legacy ISO 32000-1 ones. The answer the OP took his original code from referred to creating a legacy ISO 32000-1 signature (and, therefore, works alright) while the OP creates a PAdES signature.
The presence of an ESS signing certificate attribute is required by the PAdES specification ETSI EN 319 142-1:
e) Generators shall use either the signing certificate or the signing-certificate v2 attribute, depending on the hash function, in accordance with ETSI EN 319 122-1.
(ETSI EN 319 142-1, section 6.3 PAdES baseline signatures)
It references the CAdES specification ETSI EN 319 122-1 which in turn requires
h) Requirement for SPO: ESS signing-certificate. The ESS signing-certificate attribute shall be used if the SHA-1 hash algorithm is used.
i) Requirement for SPO: ESS signing-certificate-v2. The ESS signing-certificate-v2 attribute shall be used when another hash algorithms than SHA-1 is used.
(ETSI EN 319 122-1, section 6.3 Requirements on components and services)
I tried below code still the pdf says Signature is invalid. Can you please check the code,
System.out.println("Hash Signing started");
List<Certificate> certList = getFormatCertificate(strCertificate);
PrivateKey privateKey;
CMSSignedData s = null;
Security.addProvider(new BouncyCastleProvider());
byte[] signature = null;
try {
privateKey = loadPrivateKey(strPrivatekey);
byte[] buffer = new byte[1024];
int numOfBytesRead =0;
MessageDigest md = null;
md = MessageDigest.getInstance("SHA256","BC");
while((numOfBytesRead = content.read(buffer)) != -1 ){
md.update(buffer, 0, numOfBytesRead);
}
byte[] digest = md.digest();
// Separate signature container creation step
JcaCertStore certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Attribute attr = new Attribute(CMSAttributes.messageDigest,
new DERSet(new DEROctetString(digest)));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
//AlgorithmIdentifier sha256withECDSA = new DefaultSignatureAlgorithmIdentifierFinder().find(signerAlgorithm);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(certList.get(certList.size()-1).getEncoded());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
gen.addSignerInfoGenerator(builder.build(
new JcaContentSignerBuilder(signerAlgorithm).build(privateKey),
new JcaX509CertificateHolder(cert)));
//DErse
// gen.addSignerInfoGenerator(builder.build(
// new JcaContentSignerBuilder(sha256withRSA,
// new DefaultDigestAlgorithmIdentifierFinder().find(sha256withRSA))
// .build(PrivateKeyFactory.createKey(privateKey.getEncoded())),
// new JcaX509CertificateHolder(cert)));
gen.addCertificates(certs);
s = gen.generate(new CMSAbsentContent(), false);
System.out.println("Hash sign completed");
signature = s.getEncoded();// ConstructEcdsaSigValue(s.getEncoded());
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("GeneralSecurityException ::"+e.toString());
} catch (OperatorCreationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("OperatorCreationException ::"+e.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("IOException ::"+e.toString());
} catch (CMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("CMSException ::"+e.toString());
}finally{
return signature;
}
I have attached the pdf . Pdf file created
#Mkl/Tilman : I have created 2 pdf files, PDFA is signed using the digest of the pdf content with below code,
System.out.println("Hash Signing started");
List<Certificate> certList = getFormatCertificate(strCertificate);
PrivateKey privateKey;
CMSSignedData s = null;
Security.addProvider(new BouncyCastleProvider());
byte[] signature = null;
try {
privateKey = loadPrivateKey(strPrivatekey);
/*byte[] buffer = new byte[1024];
int numOfBytesRead =0;
MessageDigest md = null;
//md = MessageDigest.getInstance("SHA256","BC");
md = MessageDigest.getInstance("SHA-256");
while((numOfBytesRead = content.read(buffer)) != -1 ){
md.update(buffer, 0, numOfBytesRead);
}
byte[] digest = md.digest();*/
MessageDigest md = MessageDigest.getInstance("SHA256", "BC");
byte[] digest = md.digest(IOUtils.toByteArray(content));
// Separate signature container creation step
JcaCertStore certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
Attribute attr = new Attribute(CMSAttributes.messageDigest,
new DERSet(new DEROctetString(digest)));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));
//AlgorithmIdentifier sha256withECDSA = new DefaultSignatureAlgorithmIdentifierFinder().find(signerAlgorithm);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(certList.get(certList.size()-1).getEncoded());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
gen.addSignerInfoGenerator(builder.build(
new JcaContentSignerBuilder(signerAlgorithm).build(privateKey),
new JcaX509CertificateHolder(cert)));
//DErse
// gen.addSignerInfoGenerator(builder.build(
// new JcaContentSignerBuilder(sha256withRSA,
// new DefaultDigestAlgorithmIdentifierFinder().find(sha256withRSA))
// .build(PrivateKeyFactory.createKey(privateKey.getEncoded())),
// new JcaX509CertificateHolder(cert)));
gen.addCertificates(certs);
s = gen.generate(new CMSAbsentContent(), false);
System.out.println("Hash sign completed");
signature = s.getEncoded();// ConstructEcdsaSigValue(s.getEncoded());
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("GeneralSecurityException ::"+e.toString());
} catch (OperatorCreationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("OperatorCreationException ::"+e.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("IOException ::"+e.toString());
} catch (CMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("CMSException ::"+e.toString());
}finally{
return signature;
}
PDFA
PDFB is created with the same private key and certificate, but instead of digest I am using pdf document content directly which gives me valid signed pdf, PDFB code below,
SignatureInterface signatureInterface = new SignatureInterface() {
#SuppressWarnings("rawtypes")
#Override
public byte[] sign(InputStream content) throws IOException {
try {
byte[] certificateByte = null;
Store certs = new JcaCertStore(certificates);
//PAdES - PDF Advanced Electronic Signature
//ESS - Enhanced Security Services
//ASN1 - Abstract Syntax Notation One-standard interface description language for defining data structures that can be serialized and deserialized in a cross-platform way
// Generating certificate hash
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(certificates.get(certificates.size()-1).getEncoded());
byte[] certHash = md.digest();
// Generating certificate hash ends
System.out.println("Cert hash generated");
//ESSCertIDv2 identifies the certificate from the hash
ESSCertIDv2 essCert1 =
new ESSCertIDv2(new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256), certHash);
ESSCertIDv2[] essCert1Arr =
{
essCert1
};
SigningCertificateV2 scv2 = new SigningCertificateV2(essCert1Arr);
Attribute certHAttribute =
new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new DERSet(scv2));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(certHAttribute);
AttributeTable at = new AttributeTable(v);
//Create a standard attribute table from the passed in parameters - certhash
CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(at){
protected Hashtable createStandardAttributeTable(Map parameters)
{
Hashtable result = super.createStandardAttributeTable(parameters);
result.remove(CMSAttributes.signingTime);
return result;
}
};
//PAdES-end
System.out.println("CMSAttributeTableGenerator generated");
SignerInfoGeneratorBuilder genBuild =
new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider());
genBuild.setSignedAttributeGenerator(attrGen);
//Get single certificate
org.spongycastle.asn1.x509.Certificate certas1 = org.spongycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(certificates.get(certificates.size()-1).getEncoded()));
// ContentSigner interface creates SHA256withECDSA signer using PvtKey
ContentSigner sha1Signer = new JcaContentSignerBuilder(signerAlgorithm).build(privateKey);
//Creates SignerInfoGenerator using X.509 cert and ContentSigner
SignerInfoGenerator sifGen = genBuild.build(sha1Signer, new X509CertificateHolder(certas1));
// CMSSignedDataGenerator generates a pkcs7-signature message
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
gen.addCertificates(certs);
gen.addSignerInfoGenerator(sifGen);
//Creates CMS message from PDF
CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
//Generate a CMS Signed Data object which can be carrying a detached CMS signature
//msg - content to be signed
CMSSignedData signedData = gen.generate(msg, false);
System.out.println("CMSSignedData is done");
return signedData.getEncoded();
} catch (GeneralSecurityException e) {
throw new IOException(e);
} catch (CMSException e) {
throw new IOException(e);
} catch (OperatorCreationException e) {
throw new IOException(e);
}
}
};
System.out.println("CMSSignedData is done2");
PDDocument pdDocument = PDDocument.load(inputfile);
System.out.println("pdDocument loaded");
pdDocument.addSignature(signature, signatureInterface);
PDFB
I think something is missing in the signing part of PDFA which I couldn't figure out.
I installed a X509 certificate into andorid keychain using following code:
Intent installIntent = KeyChain.createInstallIntent();
installIntent.putExtra(KeyChain.EXTRA_NAME, "My certificate");
installIntent.putExtra(KeyChain.EXTRA_CERTIFICATE, certificate.getEncoded());
startActivityForResult(installIntent, 2);
I get an toast mentioning "My Certificate is installed". Now when I am trying to fetch it back using following code:
try {
KeyStore ks = KeyStore.getInstance("AndroidCAStore");
if (ks != null) {
ks.load(null, null);
Enumeration aliases = ks.aliases();
List<String> alliasesNames = Collections.list(aliases);
for (String name : alliasesNames) {
if (ks.getCertificate(name) instanceof X509Certificate) {
X509Certificate certificate = (X509Certificate) ks.getCertificate(name);
if (certificate.getIssuerDN().getName().contains("My Certificate")) {
Log.d("CERTEXIST", "**********User Cert " + certificate.getIssuerDN().getName());
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (java.security.cert.CertificateException e) {
e.printStackTrace();
}
I am not able to find that installed x509 in this. Also I could see the installed x509 cert in User certificate in security settings of the device.
Also when I prompt user to choose a cert for server communication using:
KeyChain.choosePrivateKeyAlias(loginActivity, this,
new String[]{}, null, null, -1, null);
}
The prompt doesn't show my certificate. I am new to this certificates and key chain in Android.
I would like to know how to retrive the saved x509 cert and prompt that to user to select that certificate.
Any help is appreciated.
KeyChain.choosePrivateKeyAlias launches an antivity to prompt user to select the alias for a private key, but you have installed a certificate, not a private key, so your certificate will not be there.
KeyChain.createInstallIntent() can be used to install X509 certificates or PKCS#12 files, containing both private key and certificates. If you need to install a private key+certificate for authentication you need to provide a p12 file.
byte pkcs12Data[] = ...
installIntent.putExtra(KeyChain._PKCS12, pkcs12Data);
Currently, I'm using Google Drive Android API, to store my Android app data, to Google Drive App Folder.
This is what I'm doing when saving my application data
Generate a checksum for the current local zip file.
Search in Google Drive App Folder, to see whether there is an existing App Folder zip file.
If there is, overwrite the content of existing App Folder zip file, with current local zip files. Also, we will rename existing App Folder zip filename, with the latest checksum.
If there isn't existing App Folder zip file, generate a new App Folder zip file, with local zip file's content. We will use the latest checksum as App Folder zip filename.
Here's the code which performs the above-mentioned operations.
Generate new App Folder zip file, or update existing App Folder zip file
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();
}
}
}
Search for existing App Folder zip file
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;
}
The problem occurs, during loading application data. Imagine the following operations
Upload zip data to Google Drive App Folder for the first time. The checksum is 12345. The filename being used is ...checksum=12345...zip
Search for zip data from Google Drive App Folder. Able to find the file with filename ...checksum=12345...zip. Download the content. Verify the checksum of content is 12345 too.
Overwrite new zip data to existing Google Drive App Folder file. New zip data checksum is 67890. The existing app folder zip file is renamed to ...checksum=67890...zip
Search for zip data from Google Drive App Folder. Able to find the file with filename ...checksum=67890...zip. However, after downloading the content, the checksum of the content is still old 12345!
Download App Folder zip file
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();
}
}
First, I thought
Status status = Drive.DriveApi.requestSync(googleApiClient).await()
doesn't do the job well. It fails in most of the situation, with error message Sync request rate limit exceeded. In fact, the hard limit imposed in requestSync, make that API not particularly useful - Android Google Play / Drive Api
However, even when requestSync success, loadFromGoogleDrive still can only get the latest filename, but outdated checksum content.
I'm 100% sure loadFromGoogleDrive is returning me a cached data content, with the following observations.
I install a DownloadProgressListener in driveFile.open, bytesDownloaded is 0 and bytesExpected is -1.
If I use Google Drive Rest API, with the following desktop code, I can find the latest filename with correct checksum content.
If I uninstall my Android app and re-install again, loadFromGoogleDrive will able to get the latest filename with correct checksum content.
Is there any robust way, to avoid from always loading cached app data from Google Drive?
I manage to produce a demo. Here are the steps to reproduce this problem.
Step 1: Download source code
https://github.com/yccheok/google-drive-bug
Step 2 : Setup in API console
Step 3: Press button SAVE "123.TXT" WITH CONTENT "123"
A file with filename "123.TXT", content "123" will create in the app folder.
Step 4: Press button 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.
I'm trying to upload ZIP files of size 2MB to FTPServer from Android App. After authentication, I create the necessary folders in server if not exist, later I try to upload the zip file but ended up failing with following error.
ftpUpload: libcore.io.IoBridge.maybeThrowAfterRecvfrom(IoBridge.java:588)
libcore.io.IoBridge.recvfrom(IoBridge.java:552)
java.net.PlainSocketImpl.read(PlainSocketImpl.java:481)
java.net.PlainSocketImpl.-wrap0(PlainSocketImpl.java)
java.net.PlainSocketImpl$PlainSocketInputStream.read(PlainSocketImpl.java:237)
java.io.InputStreamReader.read(InputStreamReader.java:233)
java.io.BufferedReader.fillBuf(BufferedReader.java:145)
java.io.BufferedReader.readLine(BufferedReader.java:397)
org.apache.commons.net.ftp.FTP.__getReply(FTP.java:294)
org.apache.commons.net.ftp.FTP.sendCommand(FTP.java:490)
org.apache.commons.net.ftp.FTP.sendCommand(FTP.java:534)
org.apache.commons.net.ftp.FTP.port(FTP.java:862)
org.apache.commons.net.ftp.FTPClient._openDataConnection_(FTPClient.java:463)
org.apache.commons.net.ftp.FTPClient.__storeFile(FTPClient.java:374)
org.apache.commons.net.ftp.FTPClient.storeFile(FTPClient.java:1379)
com.inspection.servertransaction.CaseUploader.ftpUpload(CaseUploader.java:775)
com.inspection.servertransaction.CaseUploader.zipMedia(CaseUploader.java:591)
com.inspection.servertransaction.CaseUploader.uploadCase(CaseUploader.java:194)
com.inspection.services.UploadCasesThread$WorkerThread.processCommand(UploadCasesThread.java:103)
com.inspection.services.UploadCasesThread$WorkerThread.run(UploadCasesThread.java:51)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
java.lang.Thread.run(Thread.java:818)
FTPUpload function
private boolean ftpUpload(File localFilePath, String remoteFilePath) {
FTPClient ftpclient = new FTPClient();
FileInputStream fis;
boolean result;
try {
ftpclient.connect(InetAddress.getByName(context.getString(R.string.ftp_hostname)));
boolean isLoggedIn = ftpclient.login(context.getString(R.string.ftp_username), context.getString(R.string.ftp_password));
if (isLoggedIn) {
Log.i(this.getClass().getSimpleName(), "FTP authenticated");
// On Successful login
ftpclient.setFileType(FTP.BINARY_FILE_TYPE);
// Ignore the actual file name from remote path, and get only directory path.
int index = remoteFilePath.lastIndexOf("/");
String remoteDir = null;
if (index != -1)
remoteDir = remoteFilePath.substring(0, index);
if (remoteDir != null)
/**
* changeWorkingDirectory returns whether the specified directory exist or not.
* If not exist in remote, create folders
*/
if (!ftpclient.changeWorkingDirectory(remoteDir)) {
if (makeDirectories(ftpclient, remoteDir))
ftpclient.changeWorkingDirectory(remoteFilePath);
else
Log.e(this.getClass().getSimpleName(), "remote path is not available");
} else
ftpclient.changeWorkingDirectory(remoteFilePath);
// Get the stream of the file
String testName = localFilePath.getName();
fis = new FileInputStream(localFilePath);
// Upload file to the ftp server
result = ftpclient.storeFile(testName, fis);
Log.i(this.getClass().getSimpleName(), "FTP Store Result " + result);
if (result) {
Log.i(this.getClass().getSimpleName(), "File is uploaded successfully");
result = true;
} else {
Log.e(this.getClass().getSimpleName(), "File uploading failed");
result = false;
}
} else {
Log.e(this.getClass().getSimpleName(), "Login Fail!");
result = false;
}
// logout from ftp server
ftpclient.logout();
} catch (Exception e) {
Log.e(this.getClass().getSimpleName(), "FTP Exception!");
EvApp.mErrorLogger.writeLog(ExceptionStackTrace.printStackTraceException(e),
this.getClass().getSimpleName() + ": ftpUpload", null, "Error");
result = false;
}
It was working fine, suddenly facing this issue. So I doubt that it's might be Server side issue. But what might be the problem? Any help will be appreciated.
i want to create a comic reader project on android. In my database, i only save path to the chapter directory which was taken from folderID on google drive. When user request to read comic, i want to through google client api to browse folder and get all file inside it.
i've seen guilde on https://developers.google.com/drive/v2/reference/files/list
but i still do not understand how it works, specials parameter Drive, how can i get it?
Thanks for any supports.
private static List<File> retrieveAllFiles(Drive service) throws IOException {
List<File> result = new ArrayList<File>();
Files.List request = service.files().list();
do {
try {
FileList files = request.execute();
result.addAll(files.getItems());
request.setPageToken(files.getNextPageToken());
} catch (IOException e) {
System.out.println("An error occurred: " + e);
request.setPageToken(null);
}
} while (request.getPageToken() != null &&
request.getPageToken().length() > 0);
return result;
}