os-js / osjs-client

OS.js Client Module
https://manual.os-js.org/
Other
31 stars 31 forks source link

Accessing to host OS files window in OSJS application #147

Closed mahsashadi closed 3 years ago

mahsashadi commented 3 years ago

Hi I need to select some files from my host OS in one form of my application. So How can I open host OS file explorer window in onClick property of browse buttons . Afterwards these files are needed to be uploaded in OSJS file system.

Could you help me with this scenario?


export default function MyComponent (props) {
    const [file, setFile] = React.useState({});
    const myArray = ['file1', 'file2', 'file3'];

    //useEffect( () => { }, [] );

    const onBrowse = item => () => {
        // create select file window of host OS
        // to put the file path in its texbox by selecting
      }

    const onUpload =  () => {
        //  upload selected files to osjs filesystem
      }

    return(
        <>
            {myArray.map( (item) =>(
                 <div>
                    <label>{item}:</label>
                    <input key= {item} value= {file[item]}></input>
                    <button onClick={onBrowse(item)}>Browse</button>
                </div>
            ))}
            <button onClick={onUpload}>Upload</button>
       </>
    )
}
andersevenrud commented 3 years ago

By "host" do you mean via the browser file selection dialog in this case ?

andersevenrud commented 3 years ago

If so, try something like this:

export default function MyComponent (props) {
  let currentItem
  const input = React.createRef()
  const [selected, setSelected] = React.useState({});
  const selection = ['file1', 'file2', 'file3']

  const onBrowse = item => {
    currentItem = item
    input.current.click()
  }

  const onUpload = async () => {
    const items = Object.keys(selected)
    for (let i = 0; i < items.length; i++) {
      await core.make('osjs/vfs')
        .writefile(`home:/${items[i]}`, selected[i])
    }
  }

  const onChange = (ev) => {
    setSelected({
      ...selected,
      [currentItem]: ev.target.files[0]
    })
  }

  React.useEffect(() => {
    input.current.addEventListener('change', onChange)

    return () => {
      input.current.removeEeventListener('change', onChange)
    }
  }, [input])

  return (
    <div style={{ overflow: 'hidden', position: 'relative' }}>
      {selection.map((item) =>(
        <div>
          <label>{item}:</label>
          <input key={item} value= {file[item]}></input>
          <button onClick={onBrowse(item)}>Browse</button>
        </div>
      ))}

      <input
        type="file"
        ref={input}
        style={{ visibility: 'hidden', position: 'absolute', top: '-10000px', left: '-10000px' }}
      />

      <button onClick={onUpload}>Upload</button>
    </div>
  )
}
mahsashadi commented 3 years ago

yes, Thank you, I forgot about input with file type !

I already have another problem after uploading in osjs (not related to osjs itself), if you have time to take a look.

I need to upload these files remotely with a post request. In api curl command I see -F option for each file, for example one as below

-F "keyfile = @somefile.key; type=application/x-iwork-keynote-sffkey"

I first createReadStream for each file (with their realPaths) and then I use below formdata in axios data, but it does not work.

formdata.append('keyfile', filestreams['keyfile]);
formdata.append('keyfile', type=application/x-iwork-keynote-sffkey);

Can you help me where the problem is? I guess the problem is in the way I use formdata.append()

andersevenrud commented 3 years ago

This is how you compose a file upload with form data:

const blob = new Blob(binary, { type: 'application/x-iwork-keynote-sffkey' })
formData.append('keyfile', blob, 'somefile.key')
mahsashadi commented 3 years ago

you mean that I do not need using createReadStream first?

andersevenrud commented 3 years ago

You can use a stream, but that does not allow you to set the mime type yourself:

formdata.append('keyfile', filestreams['keyfile]);

Ideally this should work though.

you mean that I do not need using createReadStream first?

I can't really answer this without knowing a bit about the code. You can use a Blob, File or ReadableStream. But what is best for you, I dunno.

mahsashadi commented 3 years ago

I have below function client-side:

async function uploadKeys(){
    let paths={};
    //upload in osjs
    const a = Object.keys(keyFiles).map(async item => {
        await vfs.writeFile(`home:/${item}`, keyFiles[item])
        paths[item] = `home:/${item}`
    })
    await Prmise.all(a);
    //call my API
    await axios.post(proc.resource('uploadkeys'), {filesPath:paths})
    .then(res=>{})
    .catch(err=>{}
}

and below functions server-side in some service-provider

async uploadKeys(req,vfs){
    return new Promise(async (resolve, reject)=>{
        const formdata = new formdata();
        let paths = req.body.filespath

        let fileStreams = await this.getFileData(paths, req.session.user, vfs);

        formdata.append('keyfile', fileStreams['keyfile'])
        // formdata.append() for other files

        await axios.post(myendpoint, formdata, {
            maxContentLength: Infinity,
            maxBodyLength: Infinity,
            headers:{'accept':'application/json','content-type':'multipart/form-data'}
        }).then(res=>{})
        .catch(err=>{})
    })
}

async getFileData(paths, user, vfs){
    let realPaths = {};
    let fileStreams = {}
   //getting real paths
    const a = Object.keys(paths).map(async item => {
        realPaths[item] = await vfs.realPath(paths[item], user)
    })
    await Prmise.all(a)
   //getting streams
    Object.keys(realPaths).map(item => {
        fileStreams[item] = await vfs.realPath(realPaths[item])
    })
    return fileStreams
}

My API is something like this: curl -x POST "myendpoint -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "keyfile=@myfile.key;type=sometype"

But my response is not what I expected. By the way I did not copy and paste the code if there is a mistake.

andersevenrud commented 3 years ago

That implementation looks a bit strange, and it looks like you're not actually creating streams :thinking:

async uploadKeys(req, vfs) {
  const formData = new FormData()
  const streams = await this.getFileData(req.body.filespath, req.session.user, vfs)

  if (!streams.keyfile) throw new Error('No keyfile stored')

  formData.append('keyfile', fs.createReadStream(streams.keyfile))

  return axios.post(url, formData, {
    maxContentLength: Infinity,
    maxBodyLength: Infinity,
    headers:{ accept: 'application/json', 'content-type':'multipart/form-data' }
  })
}

// Convert virtual path map to real path map
async getFileData(paths, user, vfs) {
  const promises = Object
    .entries(paths)
    .map(([k, v]) => vfs.realPath(v, user).then(r => ([k, r]))

  const result = await Promise.all(promises)
  return Object.fromEntries(result)
}
mahsashadi commented 3 years ago

That implementation looks a bit strange, and it looks like you're not actually creating streams

Oh, since I could not copy the code,I made a mistake in typing here.

Object.keys(realPaths).map(item => {
        fileStreams[item] = await vfs.realPath(realPaths[item])
    })

It is a typing error, In code, I truly get the stream as below:

fileStreams[item] = fs.createReadStream( realPaths[item])
andersevenrud commented 3 years ago

So the problem still persists ?

mahsashadi commented 3 years ago

Yes, I think the logic of your code and mine are the same.

andersevenrud commented 3 years ago

What's the actual error/result you're getting on the other side of this ?

mahsashadi commented 3 years ago

What's the actual error/result you're getting on the other side of this ?

It does not get the response code 200 with true file input, as the corresponding curl command does.

This is how you compose a file upload with form data:

How can I change the code I tell you in previous comments with Blob?

andersevenrud commented 3 years ago

I guess you're using something like this right ? https://www.npmjs.com/package/form-data

Looks like you maybe can do this:

.append('field', stream | buffer, { contentType: 'foo/bar' })
mahsashadi commented 3 years ago
 headers:{'accept':'application/json','content-type':'multipart/form-data'}

The problem was only about headers. Instead of using 'content-type':'multipart/form-data' , I use formData.getHeaders() and now the response is what I expected.

Thanks a lot :+1: