3

If a file is currently opened via native application(.pdf -> pdf viewer) how do I track down its open status?

I am creating a file syncing application using electron.js. So if the user wants to remove a file I wanna check is it open or not. If open I want to show a warning.

File is still open. Please close it first.

Debotos Das
  • 519
  • 1
  • 6
  • 17
  • If a file descriptor is opened for writing, that file will be locked and you can catch the error on second open (this may vary slightly by OS). However, the file descriptor won't still be open. The PDF viewer reads the file into memory and then closes the descriptor. You'll have to implement your own tracker. Assuming you are the one opening the PDFs that shouldn't be too hard. If you're not the one opening the PDF's you cannot do this. – leitning Jun 23 '21 at 05:27
  • Hi, Thanks for the comment. As I am opening the PDF/image I can set a state like open: true. But then I also need to know when it's closed. So that I can update that state. – Debotos Das Jun 23 '21 at 05:32
  • How are you opening it? Listen for the close event on the child process or the browser window. – leitning Jun 23 '21 at 05:42
  • I am opening the file via `shell.openPath`. It has no close event. – Debotos Das Jun 23 '21 at 05:52
  • Does this answer your question? [Node js check if a file is open before copy](https://stackoverflow.com/questions/28588707/node-js-check-if-a-file-is-open-before-copy) – pushkin Jun 25 '21 at 16:02
  • Hi, I tried that but didn't work. Instead, this package - https://github.com/ronomon/opened is working perfectly with just a promise wrapper. – Debotos Das Jun 26 '21 at 02:11

3 Answers3

2

For now, the below snippet is working for me(Tested in macOS) -

In the main process -

import util from 'util'
const exec = util.promisify(require('child_process').exec)

const checkFileIsOpen = async (fileName: string): Promise<boolean> => {
    try {
        const filePath = myAppDocsPath + '/' + fileName.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')

        const { stdout } = await exec('lsof ' + filePath)
        console.log('stdout:', stdout)

        if (stdout.length === 0) {
            // Not in use | File is not open
            return false
        } else {
            // In use | File is open
            return true
        }
    } catch (error) {
        console.log('Check file is open error:\n', error)
        return false
    }
}
Debotos Das
  • 519
  • 1
  • 6
  • 17
0

Using the spawn function you receive a reference to the child process that you can listen to the exit event on. Here's a simple example

const {spawn} = require('child_process');
const PDF_BIN = '/path/to/pdf-viewer';

const fileTracker = {};

const openFile = (fileName) => {
  if(fileTracker[fileName])
    throw new Error(`${fileName} already open`);
  let args = ['-foo','--b','ar',fileName];
  let cp = spawn(PDF_BIN, args);
  fileTracker[fileName] = cp;
  cp.once('error',(err) => {
    console.error(err);
    console.log(`Error opening ${fileName}`);
    delete(fileTracker[fileName]);
  });
  cp.once('exit',(code,signal) => {
    if(signal)
      code = signal;
    if(code != 0)
      console.error(`Recieved exit code ${code} on file ${fileName}`);
    delete(fileTracker[fileName]);
  })  
}
leitning
  • 1,081
  • 1
  • 6
  • 10
  • Thanks. In my case file type can be anything like - Image, Docx, PDF, Excel, Presentation, Text, CSV, etc. And I am opening through the system native application for each type of file. Specifically, I don't know where is the `bin` binary of that opening application. – Debotos Das Jun 24 '21 at 02:01
0

I found a better solution with the help of a package (https://github.com/ronomon/opened) -

const checkFileIsOpen = async (fileName: string): Promise<boolean> => {
    try {
        // myAppDocsPath is a variable of my application specific path
        const filePath = myAppDocsPath + '/' + fileName
        const paths = [filePath]
        return new Promise((resolve, reject) => {
            Opened.files(paths, function (error: any, hashTable: Record<string, boolean>) {
                console.log(hashTable)
                if (error) throw reject(false)
                resolve(hashTable[paths[0]])
            })
        })
    } catch (error) {
        console.log('Check file is open error:\n', error)
        return false
    }
}

For a list of file -

type FileListOpenStatus = Record<string, boolean> | false

const checkFileListOpenStatus = async (fileNameList: string[]): Promise<FileListOpenStatus> => {
    try {
        // myAppDocsPath is a variable of my application specific path
        const paths = fileNameList.map((fileName) => `${myAppDocsPath}/${fileName}`)
        return new Promise((resolve, reject) => {
            Opened.files(paths, function (error: any, hashTable: Record<string, boolean>) {
                console.log(hashTable)
                if (error) throw reject(false)
                const results: Record<string, boolean> = {}
                for (const [filePath, value] of Object.entries(hashTable)) {
                    const fileName = path.basename(filePath)
                    results[fileName] = value
                }
                resolve(results)
            })
        })
    } catch (error) {
        console.log('Check file list open status error:\n', error)
        return false
    }
}
Debotos Das
  • 519
  • 1
  • 6
  • 17