-
Notifications
You must be signed in to change notification settings - Fork 0
/
pdfdog.py
153 lines (132 loc) · 5.31 KB
/
pdfdog.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"""PDF Dog. Show a pdf and track changes.
Usage:
pdfdog [-q|-l] <filename>
pdfdog (-h | --help)
pdfdog --version
Options:
-h --help Show this screen.
-q Quiet. Don't print informative messages. The default.
-l Log informative messages to stdout.
--version Show version.
"""
from __future__ import print_function
import os
import time
import sys
import tempfile
import subprocess
import shlex
import shutil
import datetime
from docopt import docopt
from version import __VERSION__
POLL_INTERVAL = 0.100 # how often to poll for pdf changes
def log(*args):
"""Simple logging routine that can be silenced by cli argument."""
global QUIET
if not QUIET:
print(*args)
def copy_file(filename):
"""Copy the given file (a pdf hopefully), to a temporary file so it can
be opened by the pdf viewer without locking the source pdf file. Return
the name (full path) of the temporary file."""
with open(filename, 'r') as infile, \
tempfile.NamedTemporaryFile(suffix='.pdf', prefix='pdfdog_',
delete=False, mode='w') as outfile:
shutil.copyfileobj(infile, outfile, -1)
return outfile.name # temp file name
def get_launch_cmd():
"""Get the executable spec for the default viewer for pdfs. This varies by
platform. Currently only Windows."""
# TODO see http://stackoverflow.com/questions/434597/open-document-with-default-application-in-python
# this is for Windows
# Get the file type associated with file extension .pdf, returns
# something like '.pdf=AcroExch.Document.11\r\n'
a = subprocess.check_output('assoc .pdf', shell=True)
b = a.strip().split('=')[1] # get file type, like 'AcroExch.Document.11'
# Get the executable with full path for that file type, returns like
# 'AcroExch.Document.11="C:\\Program Files (x86)\\Adobe\\Reader 11.0\\Reader\\AcroRd32.exe" "%1"\r\n'
c = subprocess.check_output('ftype %s' % b, shell=True)
d = c.split('=')[1] # get the string to the right of '='
launch_cmd = shlex.split(d)[0] # get just executable with full path
log("Command: %s" % launch_cmd)
return launch_cmd
def terminate(viewer, temp_file):
# If viewer currently showing a file, end it
if viewer:
log("Terminate.")
viewer.terminate()
# if existing temp file, delete it
#TODO find some clever way to not let this loop forever
while True:
try:
# if the temp file exists, delete it
if temp_file and os.path.isfile(temp_file):
os.remove(temp_file)
break # temp file deleted
except WindowsError:
# keep trying to kill the viewer
viewer.terminate()
viewer.kill()
continue # try again to delete temp file
def launch(cmd, filename):
"""Launches the pdf viewer (given by cmd) on the given file name. Returns
the Popen object for the pdf viewer process."""
log("View: %s" % filename)
viewer = subprocess.Popen((cmd, filename))
log("Viewer PID: %s" % viewer.pid)
return viewer
def poll(filename, old_mtime):
"""Polls the given filename and waits for it to come into existence and for
the modification time to advance
Return:
Modification time if file is ready to view
None if file does not exist or not ready to view
"""
if os.path.isfile(filename):
mtime = datetime.datetime.fromtimestamp(os.stat(filename).st_mtime)
if mtime > old_mtime:
return True, mtime
time.sleep(POLL_INTERVAL)
return False, old_mtime
def main(arguments):
"""The main loop."""
# extract file name
filename = arguments['<filename>']
# make sure we got something resembling a file name string
if not filename:
log("***Must specify a valid file name, got: %s" % filename)
sys.exit(1)
log("Filename: %s" % filename)
# determine the executable to use to launch/view the pdf (platform specific)
cmd = get_launch_cmd()
# set modification time to the far past to guarantee good comparison
mtime = datetime.datetime.min
temp_file = None # no temp file to view initially
viewer = None # no pdf viewer initially active
while True:
try:
# poll for changes in the pdf file or wait for it to appear
view, mtime = poll(filename, mtime)
# if ready to view (file updated, came into existence)
if view:
# terminate existing viewer, if any, and delete temp file
terminate(viewer, temp_file)
# copy pdf file to temp file
temp_file = copy_file(filename)
# show the file in the pdf viewer
viewer = launch(cmd, temp_file)
except KeyboardInterrupt:
# we got a ctrl-c, this indicates normal termination
terminate(viewer, temp_file) # get rid of viewer and temp file
break
except:
# something unexpected happened, clean up and pass the error along
terminate(viewer, temp_file) # get rid of viewer and temp file
raise
if __name__ == '__main__':
# parse command line args
arguments = docopt(__doc__, version='PDF Dog %s' % __VERSION__)
QUIET = arguments['-q'] or not arguments['-l']
log("Args:", arguments)
main(arguments)