Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

file.read or file.readSync error: EACCES (Permission denied) #17

Open
xpalacincreditoh opened this issue Nov 29, 2021 · 8 comments
Open

Comments

@xpalacincreditoh
Copy link

xpalacincreditoh commented Nov 29, 2021

Good morning,

My system details:

  • Ubuntu 20.04.3 LTS
  • NativeScript CLI 8.1.4
  • NVM 0.35.3
  • Node v16.13.0
  • NPM 8.1.0
  • Android 11 API Level 30
  • Xiaomi Redmi Note 9S MIUI 12.5.2
  • "@nativescript/core": "~8.1.1",
  • "@nativescript-community/ui-document-picker": "^1.1.7",
  • "@nativescript/android": "8.1.1",

When Open filepicker and select a PDF file from downloads folder the file.readSync sayas error:

Error: java.io.FileNotFoundException: /storage/emulated/0/Download/Test.pdf: open failed: EACCES (Permission denied)

And when call file.read says error:

Error: ReadTask returns no result.

This just happens only when targetSdkVersion is set to 30 and not in 29, but now to publish and app in Google App Store needs targetSdkVersion 30.

Thanks

@gdsoftdev
Copy link

Hi,

I've exactly the same problem. From the Download directory I can read a JPG file but not a PDF file (permission denied)
@xpalacincreditoh Did you find a solution ?

Thanks

@xpalacincreditoh
Copy link
Author

I'm waiting for you to merge the PR pending fix NativeScript/NativeScript#9661

@nikoTM
Copy link

nikoTM commented Jan 12, 2022

// REVIEW: Not sure why DocumentsContact is not there yet
type ProviderWithDocumentsContact = typeof android.provider & {
  DocumentsContract: any;
};

// Per https://stackoverflow.com/questions/17546101/get-real-path-for-uri-android
export function getPathFromURI(uri: android.net.Uri) {
  const getMediaFilePathForN = (uri: android.net.Uri, context: any) => {
    let returnUri = uri;
    let returnCursor = context
      .getContentResolver()
      .query(returnUri, null, null, null, null);
    /*
     * Get the column indexes of the data in the Cursor,
     *     * move to the first row in the Cursor, get the data,
     *     * and display it.
     * */
    let nameIndex = returnCursor.getColumnIndex(
      android.provider.OpenableColumns.DISPLAY_NAME
    );
    let sizeIndex = returnCursor.getColumnIndex(
      android.provider.OpenableColumns.SIZE
    );
    returnCursor.moveToFirst();
    let name = returnCursor.getString(nameIndex);
    let size = java.lang.Long.toString(returnCursor.getLong(sizeIndex));
    let file = new java.io.File(context.getFilesDir(), name);
    try {
      let inputStream = context.getContentResolver().openInputStream(uri);
      let outputStream = new java.io.FileOutputStream(file);
      let read = 0;
      let maxBufferSize = 1 * 1024 * 1024;
      let bytesAvailable = inputStream.available();

      //int bufferSize = 1024;
      let bufferSize = Math.min(bytesAvailable, maxBufferSize);
      let buffers = java.lang.reflect.Array.newInstance(
        java.lang.Byte.class.getField('TYPE').get(null),
        bufferSize
      );
      while ((read = inputStream.read(buffers)) != -1) {
        outputStream.write(buffers, 0, read);
      }
      inputStream.close();
      outputStream.close();
      console.log('File Path', 'Path ' + file.getPath());
      console.log('File Size', 'Size ' + file.length());
    } catch (e) {
      console.log('Exception', e);
    }
    return file.getPath();
  };

  const getDriveFilePath = (uri: android.net.Uri, context: any) => {
    let returnUri = uri;
    let returnCursor = context
      .getContentResolver()
      .query(returnUri, null, null, null, null);
    /*
     * Get the column indexes of the data in the Cursor,
     *     * move to the first row in the Cursor, get the data,
     *     * and display it.
     * */
    let nameIndex = returnCursor.getColumnIndex(
      android.provider.OpenableColumns.DISPLAY_NAME
    );
    returnCursor.moveToFirst();
    let name = returnCursor.getString(nameIndex);
    let file = new java.io.File(context.getCacheDir(), name);
    try {
      let inputStream = context.getContentResolver().openInputStream(uri);
      let outputStream = new java.io.FileOutputStream(file);
      let read = 0;
      let maxBufferSize = 1 * 1024 * 1024;
      let bytesAvailable = inputStream.available();

      //int bufferSize = 1024;
      let bufferSize = Math.min(bytesAvailable, maxBufferSize);

      let buffers = java.lang.reflect.Array.newInstance(
        java.lang.Byte.class.getField('TYPE').get(null),
        bufferSize
      );
      while ((read = inputStream.read(buffers)) != -1) {
        outputStream.write(buffers, 0, read);
      }
      inputStream.close();
      outputStream.close();
      console.log('File Path', 'Path ' + file.getPath());
      console.log('File Size', 'Size ' + file.length());
    } catch (e) {
      console.log('Exception', e);
    }
    return file.getPath();
  };

  const getDataColumn = (
    context: any,
    uri: android.net.Uri,
    selection: any,
    selectionArgs: any
  ) => {
    let cursor = null;
    const column = '_data';
    const projection = [column];

    try {
      cursor = context
        .getContentResolver()
        .query(uri, projection, selection, selectionArgs, null);
      if (cursor != null && cursor.moveToFirst()) {
        let column_index = cursor.getColumnIndexOrThrow(column);
        return cursor.getString(column_index);
      }
    } catch (e) {
      return getMediaFilePathForN(uri, context);
    } finally {
      if (cursor != null) cursor.close();
    }
    return null;
  };

  const isExternalStorageDocument = (uri: android.net.Uri) => {
    return 'com.android.externalstorage.documents' === uri.getAuthority();
  };

  const isDownloadsDocument = (uri: android.net.Uri) => {
    return 'com.android.providers.downloads.documents' === uri.getAuthority();
  };

  const isMediaDocument = (uri: android.net.Uri) => {
    return 'com.android.providers.media.documents' === uri.getAuthority();
  };

  const isGooglePhotosUri = (uri: android.net.Uri) => {
    return 'com.google.android.apps.photos.content' === uri.getAuthority();
  };
  const isGoogleDriveUri = (uri: android.net.Uri) => {
    return (
      'com.google.android.apps.docs.storage' === uri.getAuthority() ||
      'com.google.android.apps.docs.storage.legacy' === uri.getAuthority()
    );
  };
  const activity =
    Application.android.startActivity || Application.android.foregroundActivity;
  const context = activity.getApplicationContext();
  const isKitKat = parseInt(Device.sdkVersion, 10) >= 19;

  if (typeof uri === 'string') {
    uri = android.net.Uri.parse(uri);
  }
  // DocumentProvider
  if (
    isKitKat &&
    (<ProviderWithDocumentsContact>(
      android.provider
    )).DocumentsContract.isDocumentUri(context, uri)
  ) {
    // ExternalStorageProvider
    if (isExternalStorageDocument(uri)) {
      const docId: string = (<ProviderWithDocumentsContact>(
        android.provider
      )).DocumentsContract.getDocumentId(uri);
      const split = docId.split(':');
      const type: string = split[0].toLowerCase();
      if ('primary' === type) {
        return (
          android.os.Environment.getExternalStorageDirectory() + '/' + split[1]
        );
      } else {
        // https://stackoverflow.com/questions/44226029/how-get-a-file-path-by-uri-which-authority-is-com-android-externalstorage-docum
        let external = context.getExternalMediaDirs();

        if (external.length > 0) {
          let filePath = external[0].getAbsolutePath();
          filePath =
            filePath.substring(0, filePath.indexOf('Android')) + split[1];
          return filePath;
        }
        return uri.getPath();
      }
    }
    // DownloadsProvider
    else if (isDownloadsDocument(uri)) {
      if (parseInt(Device.sdkVersion, 10) >= 23) {
        let cursor = null;
        try {
          cursor = context
            .getContentResolver()
            .query(
              uri,
              [android.provider.MediaStore.MediaColumns.DISPLAY_NAME],
              null,
              null,
              null
            );
          if (cursor != null && cursor.moveToFirst()) {
            let fileName = cursor.getString(0);
            let path =
              android.os.Environment.getExternalStorageDirectory().toString() +
              '/Download/' +
              fileName;
            if (!android.text.TextUtils.isEmpty(path)) {
              return path;
            }
          }
        } finally {
          if (cursor != null) cursor.close();
        }
        const id = (<ProviderWithDocumentsContact>(
          android.provider
        )).DocumentsContract.getDocumentId(uri);
        if (!android.text.TextUtils.isEmpty(id)) {
          if (id.startsWith('raw:')) {
            return id.replace(/^raw:/, '');
          }
          const contentUriPrefixesToTry = [
            'content://downloads/public_downloads',
            'content://downloads/my_downloads'
          ];
          for (let contentUriPrefix of contentUriPrefixesToTry) {
            try {
              let contentUri = android.content.ContentUris.withAppendedId(
                android.net.Uri.parse(contentUriPrefix),
                 java.lang.Long.valueOf(id) as any
              );

              /*   final Uri contentUri = ContentUris.withAppendedId(
                          Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));*/

              return getDataColumn(context, contentUri, null, null);
            } catch (e) {
              //In Android 8 and Android P the id is not a number
              return uri
                .getPath()
                .replace(/^\/document\/raw:/, '')
                .replace(/^raw:/, '');
            }
          }
        }
      } else {
        const id: string = (<ProviderWithDocumentsContact>(
          android.provider
        )).DocumentsContract.getDocumentId(uri);
        if (id.startsWith('raw:')) {
          return id.slice(4);
        } else {
          const contentUri = android.content.ContentUris.withAppendedId(
            android.net.Uri.parse('content://downloads/public_downloads'),
            java.lang.Long.valueOf(id) as any
          );

          return getDataColumn(context, contentUri, null, null);
        }
      }
    }
    // MediaProvider
    else if (isMediaDocument(uri)) {
      const docId = (<ProviderWithDocumentsContact>(
        android.provider
      )).DocumentsContract.getDocumentId(uri);
      const split = docId.split(':');
      const type = split[0];

      let contentUri = null;
      if ('image' === type) {
        contentUri =
          android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
      } else if ('video' === type) {
        contentUri =
          android.provider.MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
      } else if ('audio' === type) {
        contentUri =
          android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
      } else {
        return getMediaFilePathForN(uri, context);
      }

      const selection = '_id=?';
      const selectionArgs = [split[1]]; // js Array?

      return getDataColumn(context, contentUri, selection, selectionArgs);
    } else if (isGoogleDriveUri(uri)) {
      return getDriveFilePath(uri, context);
    }
  }
  // MediaStore (and general)
  else if ('content' === uri.getScheme().toLowerCase()) {
    if (isGooglePhotosUri(uri)) {
      return uri.getLastPathSegment();
    }

    if (isGoogleDriveUri(uri)) {
      return getDriveFilePath(uri, context);
    }
    if (parseInt(Device.sdkVersion) === 24) {
      // return getFilePathFromURI(context,uri);
      return getMediaFilePathForN(uri, context);
      // return getRealPathFromURI(context,uri);
    } else {
      return getDataColumn(context, uri, null, null);
    }
  }
  // File
  else if ('file' === uri.getScheme().toLowerCase()) {
    return uri.getPath();
  }

  return null;
}

@xpalacincreditoh I use this monstrosity for reading files for now. You might only need getMediaFilePathForN from there. But it seems to handle most of things.

@xpalacincreditoh
Copy link
Author

When I call:
openFilePicker(isIOS ? this.optionsFile.ios : this.optionsFile.android).then((res) => { ...
Where do I have to use your method getPathFromURI?

@nikoTM
Copy link

nikoTM commented Jan 12, 2022

@xpalacincreditoh res.android

@xpalacincreditoh
Copy link
Author

xpalacincreditoh commented Jan 13, 2022

@xpalacincreditoh res.android

When I use your code I get an error with word type:

Module parse failed: Unexpected token (279:5)
File was processed with these loaders:
 * ./node_modules/@nativescript/webpack/dist/loaders/nativescript-worker-loader/index.js
You may need an additional loader to handle the result of these loaders.
| 
| // REVIEW: Not sure why DocumentsContact is not there yet
> type ProviderWithDocumentsContact = typeof android.provider & {
|     DocumentsContract: any;
| };

@nikoTM
Copy link

nikoTM commented Jan 13, 2022

@xpalacincreditoh seems like you are using typescript code in a JS file?

@xpalacincreditoh
Copy link
Author

xpalacincreditoh commented Jan 13, 2022

@xpalacincreditoh seems like you are using typescript code in a JS file?

yes I use your solution of yesterday #17 (comment) I just realized it's TS and my code is JS.
Error:
JS: TypeError: getUtils(...).getBytes is not a function

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants