Escape OS X batch converter path.
[dcpomatic.git] / src / lib / cross.cc
1 /*
2     Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include "cross.h"
21 #include "compose.hpp"
22 #include "log.h"
23 #include "exceptions.h"
24 #include <boost/algorithm/string.hpp>
25 #ifdef DCPOMATIC_LINUX
26 #include <unistd.h>
27 #include <mntent.h>
28 #endif
29 #ifdef DCPOMATIC_WINDOWS
30 #include <windows.h>
31 #undef DATADIR
32 #include <shlwapi.h>
33 #endif
34 #ifdef DCPOMATIC_OSX
35 #include <sys/sysctl.h>
36 #include <mach-o/dyld.h>
37 #include <IOKit/pwr_mgt/IOPMLib.h>
38 #endif
39 #ifdef DCPOMATIC_POSIX
40 #include <sys/types.h>
41 #include <ifaddrs.h>
42 #include <netinet/in.h>
43 #include <arpa/inet.h>
44 #endif
45 #include <fstream>
46
47 #include "i18n.h"
48
49 #define LOG_GENERAL(...) log->log (String::compose (__VA_ARGS__), LogEntry::TYPE_GENERAL);
50 #define LOG_ERROR(...) log->log (String::compose (__VA_ARGS__), LogEntry::TYPE_ERROR);
51 #define LOG_ERROR_NC(...) log->log (__VA_ARGS__, LogEntry::TYPE_ERROR);
52
53 using std::pair;
54 using std::list;
55 using std::ifstream;
56 using std::string;
57 using std::wstring;
58 using std::make_pair;
59 using std::runtime_error;
60 using boost::shared_ptr;
61
62 /** @param s Number of seconds to sleep for */
63 void
64 dcpomatic_sleep (int s)
65 {
66 #ifdef DCPOMATIC_POSIX
67         sleep (s);
68 #endif
69 #ifdef DCPOMATIC_WINDOWS
70         Sleep (s * 1000);
71 #endif
72 }
73
74 /** @return A string of CPU information (model name etc.) */
75 string
76 cpu_info ()
77 {
78         string info;
79
80 #ifdef DCPOMATIC_LINUX
81         /* This use of ifstream is ok; the filename can never
82            be non-Latin
83         */
84         ifstream f ("/proc/cpuinfo");
85         while (f.good ()) {
86                 string l;
87                 getline (f, l);
88                 if (boost::algorithm::starts_with (l, "model name")) {
89                         string::size_type const c = l.find (':');
90                         if (c != string::npos) {
91                                 info = l.substr (c + 2);
92                         }
93                 }
94         }
95 #endif
96
97 #ifdef DCPOMATIC_OSX
98         char buffer[64];
99         size_t N = sizeof (buffer);
100         if (sysctlbyname ("machdep.cpu.brand_string", buffer, &N, 0, 0) == 0) {
101                 info = buffer;
102         }
103 #endif
104
105 #ifdef DCPOMATIC_WINDOWS
106         HKEY key;
107         if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key) != ERROR_SUCCESS) {
108                 return info;
109         }
110
111         DWORD type;
112         DWORD data;
113         if (RegQueryValueEx (key, L"ProcessorNameString", 0, &type, 0, &data) != ERROR_SUCCESS) {
114                 return info;
115         }
116
117         if (type != REG_SZ) {
118                 return info;
119         }
120
121         wstring value (data / sizeof (wchar_t), L'\0');
122         if (RegQueryValueEx (key, L"ProcessorNameString", 0, 0, reinterpret_cast<LPBYTE> (&value[0]), &data) != ERROR_SUCCESS) {
123                 RegCloseKey (key);
124                 return info;
125         }
126
127         info = string (value.begin(), value.end());
128
129         RegCloseKey (key);
130
131 #endif
132
133         return info;
134 }
135
136 #ifdef DCPOMATIC_OSX
137 /** @return Path of the Contents directory in the .app */
138 boost::filesystem::path
139 app_contents ()
140 {
141         uint32_t size = 1024;
142         char buffer[size];
143         if (_NSGetExecutablePath (buffer, &size)) {
144                 throw runtime_error ("_NSGetExecutablePath failed");
145         }
146
147         boost::filesystem::path path (buffer);
148         path = boost::filesystem::canonical (path);
149         path = path.parent_path ();
150         path = path.parent_path ();
151         return path;
152 }
153 #endif
154
155 boost::filesystem::path
156 shared_path ()
157 {
158 #ifdef DCPOMATIC_LINUX
159         char const * p = getenv ("DCPOMATIC_LINUX_SHARE_PREFIX");
160         if (p) {
161                 return p;
162         }
163         return boost::filesystem::canonical (LINUX_SHARE_PREFIX);
164 #endif
165 #ifdef DCPOMATIC_WINDOWS
166         wchar_t dir[512];
167         GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
168         PathRemoveFileSpec (dir);
169         boost::filesystem::path path = dir;
170         return path.parent_path();
171 #endif
172 #ifdef DCPOMATIC_OSX
173         return app_contents() / "Resources";
174 #endif
175 }
176
177 void
178 run_ffprobe (boost::filesystem::path content, boost::filesystem::path out, shared_ptr<Log> log)
179 {
180 #ifdef DCPOMATIC_WINDOWS
181         SECURITY_ATTRIBUTES security;
182         security.nLength = sizeof (security);
183         security.bInheritHandle = TRUE;
184         security.lpSecurityDescriptor = 0;
185
186         HANDLE child_stderr_read;
187         HANDLE child_stderr_write;
188         if (!CreatePipe (&child_stderr_read, &child_stderr_write, &security, 0)) {
189                 LOG_ERROR_NC ("ffprobe call failed (could not CreatePipe)");
190                 return;
191         }
192
193         wchar_t dir[512];
194         GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
195         PathRemoveFileSpec (dir);
196         SetCurrentDirectory (dir);
197
198         STARTUPINFO startup_info;
199         ZeroMemory (&startup_info, sizeof (startup_info));
200         startup_info.cb = sizeof (startup_info);
201         startup_info.hStdError = child_stderr_write;
202         startup_info.dwFlags |= STARTF_USESTDHANDLES;
203
204         wchar_t command[512];
205         wcscpy (command, L"ffprobe.exe \"");
206
207         wchar_t file[512];
208         MultiByteToWideChar (CP_UTF8, 0, content.string().c_str(), -1, file, sizeof(file));
209         wcscat (command, file);
210
211         wcscat (command, L"\"");
212
213         PROCESS_INFORMATION process_info;
214         ZeroMemory (&process_info, sizeof (process_info));
215         if (!CreateProcess (0, command, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
216                 LOG_ERROR_NC (N_("ffprobe call failed (could not CreateProcess)"));
217                 return;
218         }
219
220         FILE* o = fopen_boost (out, "w");
221         if (!o) {
222                 LOG_ERROR_NC (N_("ffprobe call failed (could not create output file)"));
223                 return;
224         }
225
226         CloseHandle (child_stderr_write);
227
228         while (true) {
229                 char buffer[512];
230                 DWORD read;
231                 if (!ReadFile(child_stderr_read, buffer, sizeof(buffer), &read, 0) || read == 0) {
232                         break;
233                 }
234                 fwrite (buffer, read, 1, o);
235         }
236
237         fclose (o);
238
239         WaitForSingleObject (process_info.hProcess, INFINITE);
240         CloseHandle (process_info.hProcess);
241         CloseHandle (process_info.hThread);
242         CloseHandle (child_stderr_read);
243 #endif
244
245 #ifdef DCPOMATIC_LINUX
246         string ffprobe = "ffprobe \"" + content.string() + "\" 2> \"" + out.string() + "\"";
247         LOG_GENERAL (N_("Probing with %1"), ffprobe);
248         system (ffprobe.c_str ());
249 #endif
250
251 #ifdef DCPOMATIC_OSX
252         boost::filesystem::path path = app_contents();
253         path /= "MacOS";
254         path /= "ffprobe";
255
256         string ffprobe = "\"" + path.string() + "\" \"" + content.string() + "\" 2> \"" + out.string() + "\"";
257         LOG_GENERAL (N_("Probing with %1"), ffprobe);
258         system (ffprobe.c_str ());
259 #endif
260 }
261
262 list<pair<string, string> >
263 mount_info ()
264 {
265         list<pair<string, string> > m;
266
267 #ifdef DCPOMATIC_LINUX
268         FILE* f = setmntent ("/etc/mtab", "r");
269         if (!f) {
270                 return m;
271         }
272
273         while (true) {
274                 struct mntent* mnt = getmntent (f);
275                 if (!mnt) {
276                         break;
277                 }
278
279                 m.push_back (make_pair (mnt->mnt_dir, mnt->mnt_type));
280         }
281
282         endmntent (f);
283 #endif
284
285         return m;
286 }
287
288 boost::filesystem::path
289 openssl_path ()
290 {
291 #ifdef DCPOMATIC_WINDOWS
292         wchar_t dir[512];
293         GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
294         PathRemoveFileSpec (dir);
295
296         boost::filesystem::path path = dir;
297         path /= "openssl.exe";
298         return path;
299 #else
300         /* We assume that it's on the path for Linux and OS X */
301         return "openssl";
302 #endif
303
304 }
305
306 /* Apparently there is no way to create an ofstream using a UTF-8
307    filename under Windows.  We are hence reduced to using fopen
308    with this wrapper.
309 */
310 FILE *
311 fopen_boost (boost::filesystem::path p, string t)
312 {
313 #ifdef DCPOMATIC_WINDOWS
314         wstring w (t.begin(), t.end());
315         /* c_str() here should give a UTF-16 string */
316         return _wfopen (p.c_str(), w.c_str ());
317 #else
318         return fopen (p.c_str(), t.c_str ());
319 #endif
320 }
321
322 int
323 dcpomatic_fseek (FILE* stream, int64_t offset, int whence)
324 {
325 #ifdef DCPOMATIC_WINDOWS
326         return _fseeki64 (stream, offset, whence);
327 #else
328         return fseek (stream, offset, whence);
329 #endif
330 }
331
332 void
333 Waker::nudge ()
334 {
335 #ifdef DCPOMATIC_WINDOWS
336         SetThreadExecutionState (ES_SYSTEM_REQUIRED);
337 #endif
338 }
339
340 Waker::Waker ()
341 {
342 #ifdef DCPOMATIC_OSX
343         /* We should use this */
344         // IOPMAssertionCreateWithName (kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR ("Encoding DCP"), &_assertion_id);
345         /* but it's not available on 10.5, so we use this */
346         IOPMAssertionCreate (kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &_assertion_id);
347 #endif
348 }
349
350 Waker::~Waker ()
351 {
352 #ifdef DCPOMATIC_OSX
353         IOPMAssertionRelease (_assertion_id);
354 #endif
355 }
356
357 void
358 start_batch_converter (boost::filesystem::path dcpomatic)
359 {
360 #if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_WINDOWS)
361         boost::filesystem::path batch = dcpomatic.parent_path() / "dcpomatic2_batch";
362 #endif
363
364 #ifdef DCPOMATIC_OSX
365         boost::filesystem::path batch = dcpomatic.parent_path ();
366         batch = batch.parent_path (); // MacOS
367         batch = batch.parent_path (); // Contents
368         batch = batch.parent_path (); // DCP-o-matic.app
369         batch = batch.parent_path (); // Applications
370         batch /= "DCP-o-matic\\ 2\\ Batch\\ Converter.app";
371         batch /= "Contents";
372         batch /= "MacOS";
373         batch /= "dcpomatic2_batch";
374 #endif
375
376 #if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_OSX)
377         pid_t pid = fork ();
378         if (pid == 0) {
379                 std::cout << "start " << batch << "\n";
380                 int const r = system (batch.string().c_str());
381                 exit (WEXITSTATUS (r));
382         }
383 #endif
384
385 #ifdef DCPOMATIC_WINDOWS
386         STARTUPINFO startup_info;
387         ZeroMemory (&startup_info, sizeof (startup_info));
388         startup_info.cb = sizeof (startup_info);
389
390         PROCESS_INFORMATION process_info;
391         ZeroMemory (&process_info, sizeof (process_info));
392
393         wchar_t cmd[512];
394         MultiByteToWideChar (CP_UTF8, 0, batch.string().c_str(), -1, cmd, sizeof(cmd));
395         CreateProcess (0, cmd, 0, 0, FALSE, 0, 0, 0, &startup_info, &process_info);
396 #endif
397 }