-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathprsam
More file actions
executable file
·415 lines (371 loc) · 14.9 KB
/
Copy pathprsam
File metadata and controls
executable file
·415 lines (371 loc) · 14.9 KB
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
#!/usr/bin/env python
"""
prsam [init|config|add|import|remove|ls|sync|clean]
The PRS Annotation Manager (prsam) manages a library of PDF files for
you to annotate on your PRS-T1 reader. When you add a file to the
library, prsam will copy it over to your reader. If you desire, it can
"dice" the PDF file, splitting each page into several for better reading.
(This is particularly useful for multi-column PDFs.) After you annotate
the file on your reader, prsam will, when your run the "sync" command,
copy the annotated file back to your computer, placing it next to the
original.
prsam consists of a group of subcommands, listed above. For more
information on any of them, run 'prsam <subcommand> --help'.
~~
prsam init [options] <mount>
Initialize a library for a reader mounted at <mount>.
~~
prsam config [options]
Set the configuration options for the library. With no options
specified, display the current configuration.
~~
prsam add [options] <file>
Add the specified file to the library.
~~
prsam import [options] <reader file> <path on computer>
or: prsam import [options] --all <directory on computer>
Add the file on the reader to the library, syncing to the specified
path on the computer.
~~
prsam remove [options] <file>
Remove the specified file from the library. Either the file on the
computer or on the reader may be specified. A file on the reader may
be specified with its path from the mount point.
~~
prsam ls
List the files on the reader currently in the library.
~~
prsam sync [options]
Update all annotated PDFs.
~~
prsam clean
Remove files from the library that are no longer on the reader.
"""
# Copyright 2012-2013 Robert Schroll
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
import os
import sys
import tempfile
from optparse import OptionParser
from prsannots import __version__
from prsannots.manager import Manager, NotMountedError
from prsannots.pdfdice import UNITS
from prsannots.openfile import open_file
from prsannots.misc import u_print, u_argv
# Work around https://bugzilla.gnome.org/show_bug.cgi?id=687697
import logging
logging.disable(logging.ERROR)
try:
from gi.repository import Notify
if not Notify.init('PRS Annotation Manager'):
raise ImportError
except ImportError:
notify = lambda *args: None
else:
notification = Notify.Notification.new("PRS Annotation Manager", None, None)
notification.set_hint_int32('transient', 1)
def notify(message):
notification.update("PRS Annotation Manager", message, None)
notification.show()
USAGE = __doc__.strip().split('\n~~\n')
def print_err_exit(message):
u_print(message, sys.stderr)
sys.exit(1)
def get_manager(mount=None):
m = Manager()
if mount is None:
if m.load_mounted_reader():
return m
else:
if m.load_mount(mount):
return m
print_err_exit("Could not find reader.\n"
"Make sure it is mounted and that you have run 'prsam init <mount>'.")
def config_options(options):
config = {}
if options.infix is not None:
config['infix'] = options.infix
if options.readerdir is not None:
config['reader_dir'] = options.readerdir
if options.gs is not None:
config['gs'] = options.gs
if options.fake_highlight is not None:
config['fake_highlight'] = options.fake_highlight
return config
def do_init(args, options):
mount = os.path.abspath(args[0])
m = Manager()
if not options.force and m.load_mount(mount):
print_err_exit("Library already exists for %s.\n" % mount +
"Run with -f to force the creation of a new library.")
try:
m.new(mount, **config_options(options))
except NotMountedError, e:
print_err_exit(e)
m.save()
def do_config(args, options):
m = get_manager(options.mount)
config = config_options(options)
updated = False
if config:
m.update_settings(**config)
updated = True
if options.update_mount:
m.update_mount_setting()
updated = True
if updated:
m.save()
else:
u_print("Library Settings:")
for k,v in m.settings.items():
u_print(" %15s %s" % (k, v))
def do_add(args, options):
m = get_manager(options.mount)
if options.crop is not None:
unit = 1
crop = options.crop
for k in UNITS.keys():
if crop.endswith(k):
unit = UNITS[k]
crop = crop[:-len(k)]
try:
crop = [float(c) * unit for c in crop.split('x')]
if len(crop) not in (1,2,4):
raise ValueError, "Wrong crop length"
except ValueError:
print_err_exit("Could not understand crop setting: %s\n" % options.crop +
'Crop setting must be of form "<c>", "<h>x<v>", or "<l>x<b>x<r>x<t>".')
else:
crop = 0
if options.dice is not None:
try:
parts = options.dice.split('+', 1)
ncols, nrows = map(int, parts[0].split('x', 1))
if len(parts) == 2:
overlap = map(float, parts[1].split('x', 1))
dice = (ncols, nrows, crop, overlap)
else:
dice = (ncols, nrows, crop)
except ValueError:
print_err_exit("Could not understand dice setting: %s\n" % options.dice +
'Dice setting must be of form "<c>x<r>", "<c>x<r>+<o>", or "<c>x<r>+<o>x<o>".')
else:
dice = (1, 1, crop)
if options.preview:
fh, preview = tempfile.mkstemp()
os.close(fh)
else:
preview = None
if dice != (1, 1, 0):
func = lambda **kw: m.add_diced_pdf(args[0], dice, **kw)
else:
func = lambda **kw: m.add_pdf(args[0], **kw)
try:
fn = func(title=options.title, author=options.author, infix=options.infix,
reader_dir=options.readerdir, gs=options.gs, allow_dups=options.force,
preview=preview)
except IOError:
print_err_exit("Could not open file %s" % args[0])
else:
if fn is None:
print_err_exit("File already on reader. Use --force to add it again.\n"
"(You probably also want to set --infix, so both don't sync to the same place.)")
if preview:
if not open_file(preview):
print_err_exit("Could not start your default PDF viewer.\n"
"The preview file is saved as %s" % preview)
else:
m.save()
def do_import(args, options):
m = get_manager(options.mount)
if options.all:
try:
m.import_all(args[0], options.infix, options.copy)
except IOError, e:
print_err_exit("Could not import files: " + str(e))
else:
try:
m.import_pdf(args[0], args[1], options.infix, options.copy)
except IOError, e:
print_err_exit("Could not import file: " + str(e))
m.save()
def do_remove(args, options):
m = get_manager(options.mount)
if not m.delete(args[0], options.delete):
print_err_exit("The file %s is not currently in the library." % args[0])
m.save()
def do_ls(args, options):
m = get_manager(options.mount)
for k in m.library:
u_print(k)
def do_sync(args, options):
m = get_manager(options.mount)
if options.list:
for fn in m.needing_sync:
u_print("Sync %s" % m.library[fn]['filename'])
if not m.needing_sync:
u_print("No files need to be synced")
if options.clean:
u_print("Clean library")
return
if options.notify:
notify("Beginning sync")
need_sync = m.needing_sync
for fn in need_sync:
if options.verbose:
u_print("Syncing %s ..." % m.library[fn]['filename'])
try:
m.sync_pdf(fn)
except Exception, e:
msg = "Error syncing %s: %s" % (fn, e)
u_print(msg)
if options.notify:
notify(msg)
num = len(need_sync)
if options.notify:
if num == 0:
notify("Already up-to-date")
elif num == 1:
notify("Updated 1 annotated file")
else:
notify("Updated %i annotated files" % num)
if options.clean:
if options.verbose:
u_print("Cleaning library ...")
m.clean()
m.save()
def do_clean(args, options):
m = get_manager(options.mount)
m.clean()
m.save()
def main(args):
if len(args) > 0 and args[0][0] != '-':
command = args[0]
args = args[1:]
else:
command = ''
parser = OptionParser()
parser.add_option('-m', '--mount', metavar='DIR', help='the mount point of the reader')
def set_usage_description(string):
parts = string.split('\n\n', 1)
parser.set_usage(parts[0])
if len(parts) == 2:
parser.set_description(parts[1])
def add_gs_options():
parser.add_option('--gs', action='store_true', dest='gs',
help='run PDFs through Ghostscript before loading on reader')
parser.add_option('--no-gs', action='store_false', dest='gs',
help="don't run PDFs through Ghostscript")
def add_config_options():
parser.add_option('--infix', help='annotated PDFs are named filename.INFIX.pdf')
parser.add_option('--readerdir', metavar="DIR", help='directory on reader to store PDFs')
add_gs_options()
def add_highlight_options():
parser.add_option('--fake-highlight-on', action='store_true', dest='fake_highlight',
help='fake highlight annotations. (For Evince and family.)')
parser.add_option('--fake-highlight-off', action='store_false', dest='fake_highlight',
help='use real highlight annotations')
if command == 'init':
set_usage_description(USAGE[1])
parser.remove_option('--mount')
parser.add_option('-f', '--force', action='store_true', default=False,
help='initialize a new library even if one already exists')
add_config_options()
add_highlight_options()
function = do_init
nargs = 1
elif command == 'config':
set_usage_description(USAGE[2])
add_config_options()
parser.add_option('--update-mount', action='store_true', default=False,
help='update the stored mount point to that specified by --mount')
add_highlight_options()
function = do_config
nargs = 0
elif command == 'add':
set_usage_description(USAGE[3])
parser.add_option('-d', '--dice', help='instructions on dicing the PDF into subpages. '
'Should be in the form "<c>x<r>[+<o>]" for <c> columns and <r> rows. '
'The overlap <o> is in percentage of the page size, and may be of the '
'form "<h>x<v>" for different horizontal and vertical overlaps.')
parser.add_option('-c', '--crop', help='crop this amount off of the page. Should be of '
'the form <crop>[<unit>], where <crop> is a single number, the crop '
'amount on all sides, of the form <h>x<v>, for different horizontal and '
'vertical crops, or of the form <l>x<b>x<r>x<t>, specifying all four '
'amounts. <unit> should be one of %s. If not specified, the unit is '
'Postscript points.' % ', '.join(UNITS.keys()))
parser.add_option('-t', '--title', help='the title of the PDF')
parser.add_option('-a', '--author', help='the author of the PDF')
parser.add_option('-f', '--force', action='store_true', default=False,
help='add this file even if it already exists in the library')
parser.add_option('-p', '--preview', action='store_true', default=False,
help='preview the file, instead of adding it to the reader')
add_config_options()
function = do_add
nargs = 1
elif command == 'import':
set_usage_description(USAGE[4])
parser.add_option('--infix', help='annotated PDFs are named filename.INFIX.pdf')
parser.add_option('-c', '--copy', action='store_true', default=False,
help='copy the un-annotated PDF to the computer')
parser.add_option('-a', '--all', action='store_true', default=False,
help='import all annotated PDFs on the reader. Do not specify <reader path>.')
function = do_import
# nargs depends on --all, so set below
elif command == 'remove':
set_usage_description(USAGE[5])
parser.add_option('-d', '--delete', action='store_true', default=False,
help='delete file from reader')
function = do_remove
nargs = 1
elif command == 'ls':
set_usage_description(USAGE[6])
function = do_ls
nargs = 0
elif command == 'sync':
set_usage_description(USAGE[7])
parser.add_option('-c', '--clean', action='store_true', default=False,
help='clean files from library')
parser.add_option('-q', '--quiet', action='store_false', default=True, dest='notify',
help="don't notify when sync is finished")
parser.add_option('-v', '--verbose', action='store_true', default=False,
help="output the file names as sync occurs.")
parser.add_option('-l', '--list', action='store_true', default=False,
help="list the files that would have been synced, but don't sync")
function = do_sync
nargs = 0
elif command == 'clean':
set_usage_description(USAGE[8])
function = do_clean
nargs = 0
else:
set_usage_description(USAGE[0])
parser.version = "%prog " + __version__
parser.add_option('--version', action='version', help="show program's version number and exit")
parser.remove_option('--mount')
nargs = -1 # Always go to print_usage below.
options, args = parser.parse_args(args)
if command == 'import':
if options.all:
nargs = 1
else:
nargs = 2
if len(args) != nargs:
parser.print_usage()
sys.exit(1)
function(args, options)
if __name__ == '__main__':
main(u_argv[1:])