]> git.lyx.org Git - lyx.git/blob - src/graphics/GraphicsConverter.C
Get rid of lyxstring, remove usage of STRCONV.
[lyx.git] / src / graphics / GraphicsConverter.C
1 /**
2  * \file GraphicsConverter.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "GraphicsConverter.h"
14
15 #include "converter.h"
16 #include "debug.h"
17 #include "format.h"
18
19 #include "support/filetools.h"
20 #include "support/forkedcallqueue.h"
21 #include "support/tostr.h"
22 #include "support/lstrings.h"
23 #include "support/lyxlib.h"
24
25 #include <boost/bind.hpp>
26
27 #include "support/std_sstream.h"
28 #include <fstream>
29
30 namespace support = lyx::support;
31
32 using support::ChangeExtension;
33 using support::Forkedcall;
34 using support::ForkedCallQueue;
35 using support::LibFileSearch;
36 using support::LibScriptSearch;
37 using support::OnlyPath;
38 using support::OnlyFilename;
39 using support::QuoteName;
40 using support::subst;
41 using support::tempName;
42 using support::unlink;
43
44 using std::endl;
45 using std::ostream;
46 using std::ostringstream;
47
48
49 namespace lyx {
50 namespace graphics {
51
52 struct Converter::Impl : public boost::signals::trackable {
53         ///
54         Impl(string const &, string const &, string const &, string const &);
55
56         ///
57         void startConversion();
58
59         /** This method is connected to a signal passed to the forked call
60          *  class, passing control back here when the conversion is completed.
61          *  Cleans-up the temporary files, emits the finishedConversion
62          *  signal and removes the Converter from the list of all processes.
63          */
64         void converted(pid_t pid, int retval);
65
66         /** At the end of the conversion process inform the outside world
67          *  by emitting a signal.
68          */
69         typedef boost::signal1<void, bool> SignalType;
70         ///
71         SignalType finishedConversion;
72
73         ///
74         string script_command_;
75         ///
76         string script_file_;
77         ///
78         string to_file_;
79         ///
80         bool valid_process_;
81         ///
82         bool finished_;
83 };
84
85
86 bool Converter::isReachable(string const & from_format_name,
87                             string const & to_format_name)
88 {
89         return converters.isReachable(from_format_name, to_format_name);
90 }
91
92
93 Converter::Converter(string const & from_file,   string const & to_file_base,
94                      string const & from_format, string const & to_format)
95         : pimpl_(new Impl(from_file, to_file_base, from_format, to_format))
96 {}
97
98
99 // Empty d-tor out-of-line to keep boost::scoped_ptr happy.
100 Converter::~Converter()
101 {}
102
103
104 void Converter::startConversion() const
105 {
106         pimpl_->startConversion();
107 }
108
109
110 boost::signals::connection Converter::connect(slot_type const & slot) const
111 {
112         return pimpl_->finishedConversion.connect(slot);
113 }
114
115
116 string const & Converter::convertedFile() const
117 {
118         static string const empty;
119         return pimpl_->finished_ ? pimpl_->to_file_ : empty;
120 }
121
122 } // namespace graphics
123 } // namespace lyx
124
125
126 //------------------------------
127 // Implementation details follow
128 //------------------------------
129
130 namespace {
131
132 /** Build the conversion script, returning true if able to build it.
133  *  The script is output to the ostringstream 'script'.
134  */
135 bool build_script(string const & from_file, string const & to_file_base,
136                   string const & from_format, string const & to_format,
137                   ostream & script);
138
139 } // namespace anon
140
141
142 namespace lyx {
143 namespace graphics {
144
145 Converter::Impl::Impl(string const & from_file,   string const & to_file_base,
146                       string const & from_format, string const & to_format)
147         : valid_process_(false), finished_(false)
148 {
149         lyxerr[Debug::GRAPHICS] << "Converter c-tor:\n"
150                 << "\tfrom_file:      " << from_file
151                 << "\n\tto_file_base: " << to_file_base
152                 << "\n\tfrom_format:  " << from_format
153                 << "\n\tto_format:    " << to_format << endl;
154
155         // The converted image is to be stored in this file (we do not
156         // use ChangeExtension because this is a basename which may
157         // nevertheless contain a '.')
158         to_file_ = to_file_base + '.' +  formats.extension(to_format);
159
160         // The conversion commands are stored in a stringstream
161         ostringstream script;
162         script << "#!/bin/sh\n";
163         bool const success = build_script(from_file, to_file_base,
164                                           from_format, to_format, script);
165
166         if (!success) {
167                 script_command_ =
168                         "sh " + LibFileSearch("scripts", "convertDefault.sh") +
169                         ' ' + from_format + ':' + from_file + ' ' +
170                         to_format + ':' + to_file_;
171
172                 lyxerr[Debug::GRAPHICS]
173                         << "\tNo converter defined! I use convertDefault.sh\n\t"
174                         << script_command_ << endl;
175
176         } else {
177
178                 lyxerr[Debug::GRAPHICS] << "\tConversion script:"
179                         << "\n--------------------------------------\n"
180                         << script.str()
181                         << "\n--------------------------------------\n";
182
183                 // Output the script to file.
184                 static int counter = 0;
185                 script_file_ = OnlyPath(to_file_base) + "lyxconvert" +
186                         tostr(counter++) + ".sh";
187
188                 std::ofstream fs(script_file_.c_str());
189                 if (!fs.good())
190                         return;
191
192                 fs << script.str();
193                 fs.close();
194
195                 // The command needed to run the conversion process
196                 // We create a dummy command for ease of understanding of the
197                 // list of forked processes.
198                 // Note: 'sh ' is absolutely essential, or execvp will fail.
199                 script_command_ = "sh " + script_file_ + ' ' +
200                         OnlyFilename(from_file) + ' ' + to_format;
201         }
202         // All is ready to go
203         valid_process_ = true;
204 }
205
206
207 void Converter::Impl::startConversion()
208 {
209         if (!valid_process_) {
210                 converted(0, 1);
211                 return;
212         }
213
214         Forkedcall::SignalTypePtr
215                 ptr = ForkedCallQueue::get().add(script_command_);
216
217         ptr->connect(boost::bind(&Impl::converted, this, _1, _2));
218
219 }
220
221 void Converter::Impl::converted(pid_t /* pid */, int retval)
222 {
223         if (finished_)
224                 // We're done already!
225                 return;
226
227         finished_ = true;
228         // Clean-up behind ourselves
229         unlink(script_file_);
230
231         if (retval > 0) {
232                 unlink(to_file_);
233                 to_file_.erase();
234                 finishedConversion(false);
235         } else {
236                 finishedConversion(true);
237         }
238 }
239
240 } // namespace graphics
241 } // namespace lyx
242
243 namespace {
244
245 string const move_file(string const & from_file, string const & to_file)
246 {
247         if (from_file == to_file)
248                 return string();
249
250         ostringstream command;
251         command << "fromfile=" << from_file << "\n"
252                 << "tofile="   << to_file << "\n\n"
253                 << "'mv' -f ${fromfile} ${tofile} ||\n"
254                 << "{\n"
255                 << "\t'cp' -f ${fromfile} ${tofile} ||\n"
256                 << "\t{\n"
257                 << "\t\texit 1\n"
258                 << "\t}\n"
259                 << "\t'rm' -f ${fromfile}\n"
260                 << "}\n";
261
262         return command.str();
263 }
264
265
266 bool build_script(string const & from_file,
267                   string const & to_file_base,
268                   string const & from_format,
269                   string const & to_format,
270                   ostream & script)
271 {
272         lyxerr[Debug::GRAPHICS] << "build_script ... ";
273         typedef Converters::EdgePath EdgePath;
274
275         // we do not use ChangeExtension because this is a basename
276         // which may nevertheless contain a '.'
277         string const to_file = to_file_base + '.'
278                 + formats.extension(to_format);
279
280         if (from_format == to_format) {
281                 script << move_file(QuoteName(from_file), QuoteName(to_file));
282                 lyxerr[Debug::GRAPHICS] << "ready (from == to)" << endl;
283                 return true;
284         }
285
286         EdgePath edgepath = converters.getPath(from_format, to_format);
287
288         if (edgepath.empty()) {
289                 lyxerr[Debug::GRAPHICS] << "ready (edgepath.empty())" << endl;
290                 return false;
291         }
292
293         // Create a temporary base file-name for all intermediate steps.
294         // Remember to remove the temp file because we only want the name...
295         static int counter = 0;
296         string const tmp = "gconvert" + tostr(counter++);
297         string const to_base = tempName(string(), tmp);
298         unlink(to_base);
299
300         string outfile = from_file;
301
302         // The conversion commands may contain these tokens that need to be
303         // changed to infile, infile_base, outfile respectively.
304         string const token_from("$$i");
305         string const token_base("$$b");
306         string const token_to("$$o");
307
308         EdgePath::const_iterator it  = edgepath.begin();
309         EdgePath::const_iterator end = edgepath.end();
310
311         for (; it != end; ++it) {
312                 ::Converter const & conv = converters.get(*it);
313
314                 // Build the conversion command
315                 string const infile      = outfile;
316                 string const infile_base = ChangeExtension(infile, string());
317                 outfile = ChangeExtension(to_base, conv.To->extension());
318
319                 // Store these names in the shell script
320                 script << "infile="      << QuoteName(infile) << '\n'
321                        << "infile_base=" << QuoteName(infile_base) << '\n'
322                        << "outfile="     << QuoteName(outfile) << '\n';
323
324                 string command = conv.command;
325                 command = subst(command, token_from, "${infile}");
326                 command = subst(command, token_base, "${infile_base}");
327                 command = subst(command, token_to,   "${outfile}");
328                 command = LibScriptSearch(command);
329
330                 // Store in the shell script
331                 script << "\n" << command << " ||\n";
332
333                 // Test that this was successful. If not, remove
334                 // ${outfile} and exit the shell script
335                 script << "{\n"
336                        << "\t'rm' -f ${outfile}\n"
337                        << "\texit 1\n"
338                        << "}\n\n";
339
340                 // Test that the outfile exists.
341                 // ImageMagick's convert will often create ${outfile}.0,
342                 // ${outfile}.1.
343                 // If this occurs, move ${outfile}.0 to ${outfile}
344                 // and delete ${outfile}.?
345                 script << "if [ ! -f ${outfile} ]; then\n"
346                        << "\tif [ -f ${outfile}.0 ]; then\n"
347                        << "\t\t'mv' -f ${outfile}.0 ${outfile}\n"
348                        << "\t\t'rm' -f ${outfile}.?\n"
349                        << "\telse\n"
350                        << "\t\texit 1\n"
351                        << "\tfi\n"
352                        << "fi\n\n";
353
354                 // Delete the infile, if it isn't the original, from_file.
355                 if (infile != from_file) {
356                         script << "'rm' -f ${infile}\n\n";
357                 }
358         }
359
360         // Move the final outfile to to_file
361         script << move_file("${outfile}", QuoteName(to_file));
362         lyxerr[Debug::GRAPHICS] << "ready!" << endl;
363
364         return true;
365 }
366
367 } // namespace anon