2

I am opening files using the following commands in python :

file = os.path.normpath(file_path)
phandler = subprocess.Popen(['open', '-W', file])

I am polling

is_opened = phandler.poll() == None

to see if the process has been terminated. It is working if the process is terminated using cmd+Q. However, this is not working if I close the file using cmd+w. What is the foolproof way to make sure that the file is not open anymore.

PS. From phandler, I can also get the process Id using phandler.pid if needed.

If there is no other way to do this, I might have to use a library like watchdog, which is a wrapper over iNotify, but it will be preferable if I can achieve this using a library like psutils.

Pradeep Vairamani
  • 4,004
  • 3
  • 36
  • 59
  • Technically, if you are using Mac OS X (as implied by the `open` command and use of "cmd" in a keyboard shortcut), `watchdog` is a wrapper around either `FSEvents` or `kqueue`, since `inotify` is specific to the Linux kernel. – chepner Jul 17 '14 at 12:19
  • 1
    I wouldn't be surprised if there were *no* fool proof way of achieving that... Especially if you want a cross platform solution. – Bakuriu Jul 17 '14 at 12:24
  • You aren't passing `file` to the `open` command. How are `file` and `self.key` related? – chepner Jul 17 '14 at 12:26
  • Check the `lsof` command, which can be used to list all files opened by a particular process. You can then check if your file is on the list of the process that opens it. However, you'll need some way of knowing which application actually open the file, as `open` does not open the file itself; it just passes the file to the appropriate application. – chepner Jul 17 '14 at 12:28
  • you can check `/proc/$PID/fd` directory – Vor Jul 17 '14 at 12:53
  • @chepner: Sorry, corrected the mistakes in the code. I was actually passing file. Even with watchdog, we have access only to FileModifiedEvent and not the FileClose events and it doesn't work well with editors like VIM, which is a concern. Also, because I have a way to get the pid, I thought we could leverage that. – Pradeep Vairamani Jul 17 '14 at 12:53
  • @Vor Mac OS X does not have `/proc`. – chepner Jul 17 '14 at 13:04
  • 1
    Note the `vim` only opens the file long enough to create the `swp` file, then closes it. You can use `lsof` to see *all* processes that open your file. – chepner Jul 17 '14 at 13:08
  • @chepner: 1- the process that opens the file may be just a launcher. It may exit while the document stays open i.e., you can't use the fact whether a process is alive to know whether the file is closed in the general case 2- As you said, many editors work with a temporary copy and therefore the original file is updated using `rename` i.e., even if the original file is closed; the user may still continue to edit the document -- you can't rely on `lsof` in the general case. There are no solutions that would work for any file type but it might be easy to find a workaround for a specific case. – jfs Feb 26 '16 at 10:48

1 Answers1

0

Is what you're looking for something like this:

from __future__ import print_function  # If in Python 2.x, import print function from Python 3.x

import os
import subprocess
import time

import psutil

def get_proc_status(pid):
    """Get the status of the process which has the specified process id."""

    proc_status = None
    try:
        proc_status = psutil.Process(pid).status()
    except psutil.NoSuchProcess as no_proc_exc:
        print(no_proc_exc)
    except psutil.ZombieProcess as zombie_proc_exc:  
        # For Python 3.0+ in Linux (and MacOS?).
        print(zombie_proc_exc)
    return proc_status

def open_file(app, file_path):
    """Open the specified file with the specified app, and wait for the file to be closed. """

    my_file = os.path.normpath(file_path)
    # phandler = subprocess.Popen(['open', '-W', my_file])
    # Must be for Macs. Doesn't work in Windows or Linux.
    phandler = subprocess.Popen([app, my_file])
    pid = phandler.pid

    pstatus = get_proc_status(pid)
    while pstatus is not None and pstatus != "zombie":
        # The allowed set of statuses might differ on your system.
        print("subprocess %s, current process status: %s." % (pid, pstatus))
        time.sleep(1)  # Wait 1 second.
        pstatus = get_proc_status(pid)
        # Get process status to check in 'while' clause at start of next loop iteration.

    if os.name == 'posix' and pstatus == "zombie":   # Handle zombie processes in Linux (and MacOS?).
        print("subprocess %s, near-final process status: %s." % (pid, pstatus))
        phandler.communicate()

    pstatus = get_proc_status(pid)
    print("subprocess %s, final process status: %s.\n" % (pid, pstatus))

open_file(r"Evince\bin\Evince.exe", "fuzzed_file.pdf")
open_file(r"Evince\bin\Evince.exe", "unfuzzed_file.pdf")

According to Python: Non-Blocking + Non defunct process and how to kill (or avoid) zombie processes with subprocess module, it seems that the communicate() call is needed to handle "zombie" processes in Linux (and also, I think, in MacOS).

(See StackOverflow's Tag Info for zombie-process tag.)

This generates output such as:

subprocess 6844, current process status: running.
subprocess 6844, current process status: running.
subprocess 6844, current process status: running.
subprocess 6844, current process status: running.
psutil.NoSuchProcess no process found with pid 6844
psutil.NoSuchProcess no process found with pid 6844
subprocess 6844, final process status: None.

subprocess 5460, current process status: running.
subprocess 5460, current process status: running.
subprocess 5460, current process status: running.
subprocess 5460, current process status: running.
subprocess 5460, current process status: running.
subprocess 5460, current process status: running.
psutil.NoSuchProcess no process found with pid 5460
psutil.NoSuchProcess no process found with pid 5460
subprocess 5460, final process status: None.
Community
  • 1
  • 1
Russell J.
  • 67
  • 1
  • 8