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.
@@ -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
@@ -0,0 +1,3 @@
1
+ module Unoconv
2
+ require 'unoconv/listener'
3
+ end
@@ -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
@@ -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
+ ```
@@ -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: []