unoconv 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/unoconv.rb +3 -0
- data/lib/unoconv/listener.rb +202 -0
- data/readme.md +20 -0
- data/unoconv.gemspec +16 -0
- metadata +47 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1272b2327db9be111afc5c9e2a96bcde453103eadc370f64e27ed52a83819145
|
4
|
+
data.tar.gz: 68bef81465573f6c2aae6df730360836d9018447ac5420a91304e6ce1527299f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3695d6732e179cf3c3fe5a6b250af43c38edbbf6fdcae983373f5086d1392f97fd661ef6fe492c988d889dfd4164c5738ba86981c91eef19659c0320276c015d
|
7
|
+
data.tar.gz: 283a7ee550711592221571ea398828cc1efa3ccbf74a396618f66ba7f29c6b9c740afae513f6c3cbb612e05a852a85fa366dc13785b00e3ea198fc5b97b6029c
|
data/lib/unoconv.rb
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'shellwords'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
# Usage:
|
6
|
+
# Unoconv::Listener.open do |unoconv_listener|
|
7
|
+
# begin
|
8
|
+
# unoconv_listener.generate_pdf(doc_path) do |tmp_pdf_path|
|
9
|
+
# # Copy or move tmp_pdf_path to where you have to store it.
|
10
|
+
# # tmp_pdf_path is deleted after this block is executed.
|
11
|
+
# end
|
12
|
+
# rescue DocumentNotFound, FailedToCreatePDF => e
|
13
|
+
# # handle exception
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
|
17
|
+
class Unoconv::Listener
|
18
|
+
# errors during generated_pdf
|
19
|
+
class DocumentNotFound < StandardError; end
|
20
|
+
class FailedToCreatePDF < StandardError; end
|
21
|
+
class NotStarted < StandardError; end
|
22
|
+
# errors during open / start
|
23
|
+
class LibreOfficeAlreadyOpen < StandardError; end
|
24
|
+
class UnoconvAlreadyOpen < StandardError; end
|
25
|
+
|
26
|
+
STARTUP_SLEEP = 20.0
|
27
|
+
OUTDIR = 'tmp'
|
28
|
+
|
29
|
+
def self.open(&block)
|
30
|
+
instance = new
|
31
|
+
instance.start
|
32
|
+
block.call(instance)
|
33
|
+
ensure
|
34
|
+
instance&.stop
|
35
|
+
end
|
36
|
+
|
37
|
+
def generate_pdf(doc_path, output_format, &block)
|
38
|
+
tmp_pdf_path = nil
|
39
|
+
|
40
|
+
unless started?
|
41
|
+
raise NotStarted, "Call #{self.class}#start before calling #{__method__}"
|
42
|
+
end
|
43
|
+
|
44
|
+
unless File.exists?(doc_path)
|
45
|
+
raise DocumentNotFound, "File does not exist: #{doc_path}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Libre Office and unoconv can both crash,
|
49
|
+
# so we need to wrap it in a timeout and restart the services if they time out
|
50
|
+
Timeout::timeout(30) do
|
51
|
+
begin
|
52
|
+
tmp_pdf_path = unoconv_generate_pdf(doc_path, output_format)
|
53
|
+
block.call(tmp_pdf_path)
|
54
|
+
rescue Timeout::Error
|
55
|
+
restart
|
56
|
+
convert_to_pdf(doc_path, &block)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
ensure
|
61
|
+
File.delete(tmp_pdf_path) if tmp_pdf_path && File.exists?(tmp_pdf_path)
|
62
|
+
end
|
63
|
+
|
64
|
+
def start
|
65
|
+
# If the unoconv process is already running, starting a new process will
|
66
|
+
# make it freeze. The output is not returned when the process is frozen
|
67
|
+
# but the output is shown in the terminal as:
|
68
|
+
# LibreOffice crashed - exit code: 0
|
69
|
+
#
|
70
|
+
# To avoid this I try to raise an error if unoconv is already open.
|
71
|
+
raise_if_already_open
|
72
|
+
|
73
|
+
puts "Starting: unoconv --listener"
|
74
|
+
@unoconv_pid = Process.spawn("#{unoconv_cmd} --listener")
|
75
|
+
|
76
|
+
puts "Waiting #{STARTUP_SLEEP} sec for Libre Office and unoconv to start..."
|
77
|
+
sleep(STARTUP_SLEEP)
|
78
|
+
puts "Expecting Libre Office and unoconv to be open and ready now!"
|
79
|
+
end
|
80
|
+
|
81
|
+
def stop
|
82
|
+
puts "Stopping: unoconv --listener"
|
83
|
+
kill_soffice
|
84
|
+
kill_unoconv
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def started?
|
90
|
+
@unoconv_pid.present?
|
91
|
+
end
|
92
|
+
|
93
|
+
def unoconv_generate_pdf(doc_path, output_format)
|
94
|
+
out_path = File.join(OUTDIR, "#{SecureRandom.uuid}.pdf")
|
95
|
+
options = [
|
96
|
+
'--no-launch',
|
97
|
+
"-f #{output_format}",
|
98
|
+
'-P PaperFormat=A4',
|
99
|
+
"-o #{Shellwords.escape(out_path)}",
|
100
|
+
Shellwords.escape(doc_path)
|
101
|
+
]
|
102
|
+
unless system("#{unoconv_cmd} #{options.join(' ')}")
|
103
|
+
raise FailedToCreatePDF, "might be unable to convert: #{doc_path}"
|
104
|
+
end
|
105
|
+
|
106
|
+
out_path
|
107
|
+
end
|
108
|
+
|
109
|
+
def restart
|
110
|
+
puts "Restarting #{self.class}"
|
111
|
+
stop
|
112
|
+
start
|
113
|
+
end
|
114
|
+
|
115
|
+
def kill_unoconv
|
116
|
+
puts "Stopping: #{unoconv_pname} (unoconv)"
|
117
|
+
kill_pid(@unoconv_pid)
|
118
|
+
kill_pid(find_unoconv_pid)
|
119
|
+
@unoconv_pid = nil
|
120
|
+
end
|
121
|
+
|
122
|
+
# Kill Libre Office
|
123
|
+
def kill_soffice
|
124
|
+
puts "Stopping: #{soffice_pname} (Libre Office)"
|
125
|
+
kill_pid(find_soffice_pid)
|
126
|
+
end
|
127
|
+
|
128
|
+
def kill_pid(pid)
|
129
|
+
return unless pid.present?
|
130
|
+
Process.kill("TERM", pid)
|
131
|
+
Process.wait
|
132
|
+
rescue Errno::ESRCH
|
133
|
+
# No such process
|
134
|
+
# In this case simply return without re-raising.
|
135
|
+
rescue Errno::ECHILD
|
136
|
+
# No child processes
|
137
|
+
# Nothing to wait for. In this case simply return without re-raising.
|
138
|
+
end
|
139
|
+
|
140
|
+
# PID for Libre Office
|
141
|
+
def find_soffice_pid
|
142
|
+
%x(pgrep #{soffice_pname}).presence&.to_i
|
143
|
+
end
|
144
|
+
|
145
|
+
# PID for unoconv
|
146
|
+
def find_unoconv_pid
|
147
|
+
%x(pgrep #{unoconv_pname}).presence&.to_i
|
148
|
+
end
|
149
|
+
|
150
|
+
# Process name for Libre Office
|
151
|
+
def soffice_pname
|
152
|
+
'soffice'
|
153
|
+
end
|
154
|
+
|
155
|
+
# Process name for unoconv
|
156
|
+
def unoconv_pname
|
157
|
+
'LibreOffi'
|
158
|
+
end
|
159
|
+
|
160
|
+
def raise_if_already_open
|
161
|
+
raise UnoconvAlreadyOpen, unoconv_error if find_unoconv_pid.present?
|
162
|
+
raise LibreOfficeAlreadyOpen, soffice_error if find_soffice_pid.present?
|
163
|
+
end
|
164
|
+
|
165
|
+
def unoconv_error
|
166
|
+
"unoconv is already running. #{error_advice}"
|
167
|
+
end
|
168
|
+
|
169
|
+
def soffice_error
|
170
|
+
"Libre Office is already open. #{error_advice}"
|
171
|
+
end
|
172
|
+
|
173
|
+
def error_advice
|
174
|
+
"Make sure it's only spawned once. To reset manually run: #{killall_cmd}"
|
175
|
+
end
|
176
|
+
|
177
|
+
# Depends on unoconv (and Libre Office) being installed
|
178
|
+
def unoconv_cmd
|
179
|
+
# Workaround for Mac users with newer versions of Libre Office
|
180
|
+
@unoconv_cmd ||=
|
181
|
+
if use_mac_workaround?
|
182
|
+
"#{mac_python_path} $(which unoconv)"
|
183
|
+
else
|
184
|
+
"unoconv"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def mac_python_path
|
189
|
+
@mac_python_path ||=
|
190
|
+
Dir["/Applications/LibreOffice.app/Contents/*/python"].first
|
191
|
+
end
|
192
|
+
|
193
|
+
def use_mac_workaround?
|
194
|
+
return @use_mac_workaround unless @use_mac_workaround.nil?
|
195
|
+
@use_mac_workaround = RUBY_PLATFORM.include?("darwin")
|
196
|
+
end
|
197
|
+
|
198
|
+
# Command to kill both Libre Office and unoconv
|
199
|
+
def killall_cmd
|
200
|
+
"kill -9 `pgrep #{soffice_pname} #{unoconv_pname}`"
|
201
|
+
end
|
202
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Ruby interface for unoconv
|
2
|
+
|
3
|
+
# Prerequisites
|
4
|
+
|
5
|
+
* Install unoconv compatible version of Libre Office
|
6
|
+
* Install unoconv
|
7
|
+
|
8
|
+
## Usage:
|
9
|
+
```
|
10
|
+
Unoconv::Listener.open do |unoconv_listener|
|
11
|
+
begin
|
12
|
+
unoconv_listener.generate_pdf(doc_path) do |tmp_pdf_path|
|
13
|
+
# Copy or move tmp_pdf_path to where you have to store it.
|
14
|
+
# tmp_pdf_path is deleted after this block is executed.
|
15
|
+
end
|
16
|
+
rescue Unoconv::Listener::DocumentNotFound, Unoconv::Listener::FailedToCreatePDF => e
|
17
|
+
# handle exception
|
18
|
+
end
|
19
|
+
end
|
20
|
+
```
|
data/unoconv.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'unoconv'
|
5
|
+
spec.version = '0.0.4'
|
6
|
+
|
7
|
+
spec.authors = ['Sofus']
|
8
|
+
spec.required_ruby_version = '>= 2.3.1'
|
9
|
+
spec.description = 'Unoconv document conversion interface for Ruby'
|
10
|
+
spec.summary = <<-SUM
|
11
|
+
Use Unoconv::Listener to handle converting multiple documents to pdf in
|
12
|
+
Libre Office.
|
13
|
+
SUM
|
14
|
+
spec.files = `git ls-files`.split("\n")
|
15
|
+
spec.require_paths = ['lib']
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: unoconv
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sofus
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-11-23 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Unoconv document conversion interface for Ruby
|
14
|
+
email:
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/unoconv.rb
|
20
|
+
- lib/unoconv/listener.rb
|
21
|
+
- readme.md
|
22
|
+
- unoconv.gemspec
|
23
|
+
homepage:
|
24
|
+
licenses: []
|
25
|
+
metadata: {}
|
26
|
+
post_install_message:
|
27
|
+
rdoc_options: []
|
28
|
+
require_paths:
|
29
|
+
- lib
|
30
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 2.3.1
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
requirements: []
|
41
|
+
rubyforge_project:
|
42
|
+
rubygems_version: 2.7.7
|
43
|
+
signing_key:
|
44
|
+
specification_version: 4
|
45
|
+
summary: Use Unoconv::Listener to handle converting multiple documents to pdf in Libre
|
46
|
+
Office.
|
47
|
+
test_files: []
|