Add disk writer tool.
authorCarl Hetherington <cth@carlh.net>
Sun, 15 Mar 2020 23:44:31 +0000 (00:44 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 6 Apr 2020 13:57:14 +0000 (15:57 +0200)
77 files changed:
DEVELOP.md
cscript
debian/rules
graphics/linux/128/dcpomatic2_disk.png [new file with mode: 0644]
graphics/linux/16/dcpomatic2_disk.png [new file with mode: 0644]
graphics/linux/22/dcpomatic2_disk.png [new file with mode: 0644]
graphics/linux/256/dcpomatic2_disk.png [new file with mode: 0644]
graphics/linux/32/dcpomatic2_disk.png [new file with mode: 0644]
graphics/linux/48/dcpomatic2_disk.png [new file with mode: 0644]
graphics/linux/512/dcpomatic2_disk.png [new file with mode: 0644]
graphics/linux/64/dcpomatic2_disk.png [new file with mode: 0644]
graphics/osx/dcpomatic2.icns
graphics/osx/dcpomatic2_batch.icns
graphics/osx/dcpomatic2_disk.icns [new file with mode: 0644]
graphics/osx/dcpomatic2_disk.iconset/icon_128x128.png [new file with mode: 0644]
graphics/osx/dcpomatic2_disk.iconset/icon_128x128@2x.png [new file with mode: 0644]
graphics/osx/dcpomatic2_disk.iconset/icon_16x16.png [new file with mode: 0644]
graphics/osx/dcpomatic2_disk.iconset/icon_16x16@2x.png [new file with mode: 0644]
graphics/osx/dcpomatic2_disk.iconset/icon_256x256.png [new file with mode: 0644]
graphics/osx/dcpomatic2_disk.iconset/icon_256x256@2x.png [new file with mode: 0644]
graphics/osx/dcpomatic2_disk.iconset/icon_32x32.png [new file with mode: 0644]
graphics/osx/dcpomatic2_disk.iconset/icon_32x32@2x.png [new file with mode: 0644]
graphics/osx/dcpomatic2_disk.iconset/icon_512x512.png [new file with mode: 0644]
graphics/osx/dcpomatic2_disk.iconset/icon_512x512@2x.png [new file with mode: 0644]
graphics/osx/dcpomatic2_kdm.icns
graphics/osx/dcpomatic2_player.icns
graphics/osx/dcpomatic2_playlist.icns
graphics/osx/dcpomatic2_server.icns
graphics/src/dcpomatic2_disk.svg [new file with mode: 0644]
graphics/update
graphics/web/favicon-128x128.png
graphics/web/favicon-16x16.png
graphics/web/favicon-32x32.png
graphics/web/favicon-64x64.png
graphics/windows/dcpomatic2_disk.ico [new file with mode: 0644]
platform/osx/dcpomatic2_disk.Info.plist.in [new file with mode: 0644]
platform/osx/make_dmg.sh
platform/osx/set_paths.sh [new file with mode: 0644]
platform/osx/wscript
platform/windows/copy_deps.sh [new file with mode: 0644]
platform/windows/dcpomatic2_disk_debug.bat [new file with mode: 0644]
platform/windows/dcpomatic2_disk_writer.exe.manifest [new file with mode: 0644]
platform/windows/dcpomatic_disk.rc [new file with mode: 0644]
platform/windows/dcpomatic_disk_writer.rc [new file with mode: 0644]
platform/windows/set_paths.bat [new file with mode: 0644]
platform/windows/wscript
run/dcpomatic_disk [new file with mode: 0755]
run/dcpomatic_disk_writer [new file with mode: 0755]
src/lib/copy_to_drive_job.cc [new file with mode: 0644]
src/lib/copy_to_drive_job.h [new file with mode: 0644]
src/lib/cross.cc [deleted file]
src/lib/cross.h
src/lib/cross_common.cc [new file with mode: 0644]
src/lib/cross_linux.cc [new file with mode: 0644]
src/lib/cross_osx.cc [new file with mode: 0644]
src/lib/cross_windows.cc [new file with mode: 0644]
src/lib/dcpomatic_log.h
src/lib/disk_writer_messages.h [new file with mode: 0644]
src/lib/exceptions.cc
src/lib/exceptions.h
src/lib/file_log.cc
src/lib/file_log.h
src/lib/log.h
src/lib/log_entry.cc
src/lib/log_entry.h
src/lib/nanomsg.cc [new file with mode: 0644]
src/lib/nanomsg.h [new file with mode: 0644]
src/lib/state.cc
src/lib/wscript
src/tools/dcpomatic_disk.cc [new file with mode: 0644]
src/tools/dcpomatic_disk_writer.cc [new file with mode: 0644]
src/tools/wscript
src/wx/drive_wipe_warning_dialog.cc [new file with mode: 0644]
src/wx/drive_wipe_warning_dialog.h [new file with mode: 0644]
src/wx/wscript
test/wscript
wscript

index 6896900dd35950464b680d3576b29c92d6554537..4d2b0fca8b420c5e79c7d041124a1ec3fc0453fc 100644 (file)
@@ -47,7 +47,7 @@ The script can be run using something like
 to load a script file called `stress` and start executing it.
 
 
 to load a script file called `stress` and start executing it.
 
 
-Adding a new language
+## Adding a new language
 
 - Edit src/wx/config_dialog.cc to add the language to languages.
 - Add to platform/windows/wscript, platform/osx/make_dmg.sh, cscript.
 
 - Edit src/wx/config_dialog.cc to add the language to languages.
 - Add to platform/windows/wscript, platform/osx/make_dmg.sh, cscript.
diff --git a/cscript b/cscript
index eb7a817eb88683aa8e385793d444ad6ef8b0cfac..1f2ec62a92f2346896399274a85cb7386ca958e9 100644 (file)
--- a/cscript
+++ b/cscript
@@ -1,6 +1,6 @@
 # -*- mode: python -*-
 #
 # -*- mode: python -*-
 #
-#    Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
+#    Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
 #
 #    This file is part of DCP-o-matic.
 #
 #
 #    This file is part of DCP-o-matic.
 #
@@ -196,6 +196,15 @@ deb_depends['unstable'].extend(['libboost-filesystem1.67.0',
                                 'libx264-155',
                                 'libcurl4'])
 
                                 'libx264-155',
                                 'libcurl4'])
 
+def can_build_disk(target):
+    # We can build dcpomatic2_disk on platforms that have Boost process and can build the lwext4
+    # library.  For now, just whitelist good ones here.
+    #
+    # - Lots of Linux distros don't have a new enough boost (1.64 or above)
+    # - On Centos 6 we can't build lwext4 because it needs a new CMake which Centos 6's g++ is not new enough to build.
+    # - On Centos 7 there is a build error in lwext4 related to __unused
+    return target.platform == 'windows' or target.platform == 'osx' or (target.platform == 'linux' and target.distro == 'ubuntu' and target.version in ['18.04', '18.10', '19.04'])
+
 def packages(name, packages, f):
     s = '%s: ' % name
     for p in packages:
 def packages(name, packages, f):
     s = '%s: ' % name
     for p in packages:
@@ -245,6 +254,7 @@ def make_control(debian_version, bits, filename, debug, gui):
 
 def make_spec(filename, version, target, options, requires=None):
     """Make a .spec file for a RPM build"""
 
 def make_spec(filename, version, target, options, requires=None):
     """Make a .spec file for a RPM build"""
+    tools = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(filename))), "src/tools")
     f = open(filename, 'w')
     print('Summary:A program that generates Digital Cinema Packages (DCPs) from video and audio files', file=f)
     print('Name:dcpomatic2', file=f)
     f = open(filename, 'w')
     print('Summary:A program that generates Digital Cinema Packages (DCPs) from video and audio files', file=f)
     print('Name:dcpomatic2', file=f)
@@ -275,6 +285,9 @@ def make_spec(filename, version, target, options, requires=None):
     if options['variant'] == 'swaroop-studio':
         print('%{_bindir}/dcpomatic2_ecinema', file=f)
         print('%{_bindir}/dcpomatic2_uuid', file=f)
     if options['variant'] == 'swaroop-studio':
         print('%{_bindir}/dcpomatic2_ecinema', file=f)
         print('%{_bindir}/dcpomatic2_uuid', file=f)
+    if os.path.exists(os.path.join(tools, "dcpomatic2_disk")):
+        print('%{_bindir}/dcpomatic2_disk', file=f)
+        print('%{_bindir}/dcpomatic2_disk_writer', file=f)
     print('%{_datadir}/applications/dcpomatic2.desktop', file=f)
     print('%{_datadir}/applications/dcpomatic2_batch.desktop', file=f)
     print('%{_datadir}/applications/dcpomatic2_server.desktop', file=f)
     print('%{_datadir}/applications/dcpomatic2.desktop', file=f)
     print('%{_datadir}/applications/dcpomatic2_batch.desktop', file=f)
     print('%{_datadir}/applications/dcpomatic2_server.desktop', file=f)
@@ -333,7 +346,7 @@ def make_spec(filename, version, target, options, requires=None):
     print('%posttrans', file=f)
     print('/usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :', file=f)
 
     print('%posttrans', file=f)
     print('/usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :', file=f)
 
-def dependencies(target):
+def dependencies(target, options):
 
     if target.platform == 'linux':
         ffmpeg_options = { 'shared': False }
 
     if target.platform == 'linux':
         ffmpeg_options = { 'shared': False }
@@ -348,19 +361,23 @@ def dependencies(target):
         # Use distro-provided FFmpeg on Arch
         deps = []
 
         # Use distro-provided FFmpeg on Arch
         deps = []
 
-    deps.append(('libdcp', None))
-    deps.append(('libsub', None))
+    # Let's use C++11 mode if we can
+    cpp_lib_options = {'force-cpp11': True} if target.platform == 'osx' and target.bits == 64 else {}
+
+    deps.append(('libdcp', None, cpp_lib_options))
+    deps.append(('libsub', None, cpp_lib_options))
     deps.append(('rtaudio', 'carl'))
     # We get our OpenSSL libraries from the environment, but we
     # also need a patched openssl binary to make certificates.
     # This dependency is to get that binary, which is added into
     # the appropriate place later
     deps.append(('openssl', 'carl'))
     deps.append(('rtaudio', 'carl'))
     # We get our OpenSSL libraries from the environment, but we
     # also need a patched openssl binary to make certificates.
     # This dependency is to get that binary, which is added into
     # the appropriate place later
     deps.append(('openssl', 'carl'))
+    if can_build_disk(target):
+        deps.append(('lwext4', 'carl2'))
 
     return deps
 
 
     return deps
 
-def option_defaults():
-    return { "gui": True, "variant": None }
+option_defaults = { "gui": True, "variant": None, "disk": False }
 
 def configure_options(target, options):
     opt = ''
 
 def configure_options(target, options):
     opt = ''
@@ -385,6 +402,8 @@ def configure_options(target, options):
                 # I worry that this will cause ABI problems but I don't have
                 # a better solution.
                 opt += ' --force-cpp11'
                 # I worry that this will cause ABI problems but I don't have
                 # a better solution.
                 opt += ' --force-cpp11'
+    elif target.platform == 'osx' and target.bits == 64:
+        opt += ' --force-cpp11'
 
     if not options['gui']:
         opt += ' --disable-gui'
 
     if not options['gui']:
         opt += ' --disable-gui'
@@ -396,6 +415,9 @@ def configure_options(target, options):
     if target.debug and target.platform == 'windows':
         opt += ' --static-dcpomatic'
 
     if target.debug and target.platform == 'windows':
         opt += ' --static-dcpomatic'
 
+    if can_build_disk(target) and options['disk']:
+       opt += ' --enable-disk'
+
     return opt
 
 def build(target, options):
     return opt
 
 def build(target, options):
index 84cf3ffd08b4cdc493f8abb52ce1c6bcd34c9112..c45682033fa53335ec2c6607335210efdcca611c 100755 (executable)
@@ -50,3 +50,4 @@ override_dh_shlibdeps:
 
 override_dh_fixperms:
        dh_fixperms --exclude usr/bin/dcpomatic2_uuid
 
 override_dh_fixperms:
        dh_fixperms --exclude usr/bin/dcpomatic2_uuid
+       dh_fixperms --exclude usr/bin/dcpomatic2_disk_writer
diff --git a/graphics/linux/128/dcpomatic2_disk.png b/graphics/linux/128/dcpomatic2_disk.png
new file mode 100644 (file)
index 0000000..eea4e23
Binary files /dev/null and b/graphics/linux/128/dcpomatic2_disk.png differ
diff --git a/graphics/linux/16/dcpomatic2_disk.png b/graphics/linux/16/dcpomatic2_disk.png
new file mode 100644 (file)
index 0000000..9184f84
Binary files /dev/null and b/graphics/linux/16/dcpomatic2_disk.png differ
diff --git a/graphics/linux/22/dcpomatic2_disk.png b/graphics/linux/22/dcpomatic2_disk.png
new file mode 100644 (file)
index 0000000..99f74a7
Binary files /dev/null and b/graphics/linux/22/dcpomatic2_disk.png differ
diff --git a/graphics/linux/256/dcpomatic2_disk.png b/graphics/linux/256/dcpomatic2_disk.png
new file mode 100644 (file)
index 0000000..d049cc9
Binary files /dev/null and b/graphics/linux/256/dcpomatic2_disk.png differ
diff --git a/graphics/linux/32/dcpomatic2_disk.png b/graphics/linux/32/dcpomatic2_disk.png
new file mode 100644 (file)
index 0000000..6f0605c
Binary files /dev/null and b/graphics/linux/32/dcpomatic2_disk.png differ
diff --git a/graphics/linux/48/dcpomatic2_disk.png b/graphics/linux/48/dcpomatic2_disk.png
new file mode 100644 (file)
index 0000000..b3d63e0
Binary files /dev/null and b/graphics/linux/48/dcpomatic2_disk.png differ
diff --git a/graphics/linux/512/dcpomatic2_disk.png b/graphics/linux/512/dcpomatic2_disk.png
new file mode 100644 (file)
index 0000000..4dd1d65
Binary files /dev/null and b/graphics/linux/512/dcpomatic2_disk.png differ
diff --git a/graphics/linux/64/dcpomatic2_disk.png b/graphics/linux/64/dcpomatic2_disk.png
new file mode 100644 (file)
index 0000000..d0a57a3
Binary files /dev/null and b/graphics/linux/64/dcpomatic2_disk.png differ
index fdfa66155a681ccbe569e2907348203000a52ecb..e202cb09b5cbaf746b7056e923ce059ad1253f8b 100644 (file)
Binary files a/graphics/osx/dcpomatic2.icns and b/graphics/osx/dcpomatic2.icns differ
index 17a140579b2f4b27810423202ef896e1461a7533..73481f96fa490f9f1efa7f9d9673f36d408fd0a1 100644 (file)
Binary files a/graphics/osx/dcpomatic2_batch.icns and b/graphics/osx/dcpomatic2_batch.icns differ
diff --git a/graphics/osx/dcpomatic2_disk.icns b/graphics/osx/dcpomatic2_disk.icns
new file mode 100644 (file)
index 0000000..1d6beef
Binary files /dev/null and b/graphics/osx/dcpomatic2_disk.icns differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_128x128.png b/graphics/osx/dcpomatic2_disk.iconset/icon_128x128.png
new file mode 100644 (file)
index 0000000..eea4e23
Binary files /dev/null and b/graphics/osx/dcpomatic2_disk.iconset/icon_128x128.png differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_128x128@2x.png b/graphics/osx/dcpomatic2_disk.iconset/icon_128x128@2x.png
new file mode 100644 (file)
index 0000000..eea4e23
Binary files /dev/null and b/graphics/osx/dcpomatic2_disk.iconset/icon_128x128@2x.png differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_16x16.png b/graphics/osx/dcpomatic2_disk.iconset/icon_16x16.png
new file mode 100644 (file)
index 0000000..9184f84
Binary files /dev/null and b/graphics/osx/dcpomatic2_disk.iconset/icon_16x16.png differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_16x16@2x.png b/graphics/osx/dcpomatic2_disk.iconset/icon_16x16@2x.png
new file mode 100644 (file)
index 0000000..9184f84
Binary files /dev/null and b/graphics/osx/dcpomatic2_disk.iconset/icon_16x16@2x.png differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_256x256.png b/graphics/osx/dcpomatic2_disk.iconset/icon_256x256.png
new file mode 100644 (file)
index 0000000..d049cc9
Binary files /dev/null and b/graphics/osx/dcpomatic2_disk.iconset/icon_256x256.png differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_256x256@2x.png b/graphics/osx/dcpomatic2_disk.iconset/icon_256x256@2x.png
new file mode 100644 (file)
index 0000000..d049cc9
Binary files /dev/null and b/graphics/osx/dcpomatic2_disk.iconset/icon_256x256@2x.png differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_32x32.png b/graphics/osx/dcpomatic2_disk.iconset/icon_32x32.png
new file mode 100644 (file)
index 0000000..6f0605c
Binary files /dev/null and b/graphics/osx/dcpomatic2_disk.iconset/icon_32x32.png differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_32x32@2x.png b/graphics/osx/dcpomatic2_disk.iconset/icon_32x32@2x.png
new file mode 100644 (file)
index 0000000..6f0605c
Binary files /dev/null and b/graphics/osx/dcpomatic2_disk.iconset/icon_32x32@2x.png differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_512x512.png b/graphics/osx/dcpomatic2_disk.iconset/icon_512x512.png
new file mode 100644 (file)
index 0000000..4dd1d65
Binary files /dev/null and b/graphics/osx/dcpomatic2_disk.iconset/icon_512x512.png differ
diff --git a/graphics/osx/dcpomatic2_disk.iconset/icon_512x512@2x.png b/graphics/osx/dcpomatic2_disk.iconset/icon_512x512@2x.png
new file mode 100644 (file)
index 0000000..4dd1d65
Binary files /dev/null and b/graphics/osx/dcpomatic2_disk.iconset/icon_512x512@2x.png differ
index ac265cffef5f183a92f23003b4819e5f858eaf0e..3cde61af3e27e8a72620f9fbc96ff814cc27616a 100644 (file)
Binary files a/graphics/osx/dcpomatic2_kdm.icns and b/graphics/osx/dcpomatic2_kdm.icns differ
index dcdf416eced460cfc15a16ceab81aca389d84ff6..f57bd7eb3cc38908dab59cefb06b8a45e1583b93 100644 (file)
Binary files a/graphics/osx/dcpomatic2_player.icns and b/graphics/osx/dcpomatic2_player.icns differ
index f0d6e698a3f8a1ded1378288fa2051d02e181b74..64e9719027256caafba5ebefd41414ceaab6ea10 100644 (file)
Binary files a/graphics/osx/dcpomatic2_playlist.icns and b/graphics/osx/dcpomatic2_playlist.icns differ
index e73a63cd42525e67100933c977197e9b45af31ca..6e1cba7f2c635dc2fee43aaaeab1e61a6d955778 100644 (file)
Binary files a/graphics/osx/dcpomatic2_server.icns and b/graphics/osx/dcpomatic2_server.icns differ
diff --git a/graphics/src/dcpomatic2_disk.svg b/graphics/src/dcpomatic2_disk.svg
new file mode 100644 (file)
index 0000000..d93c8c1
--- /dev/null
@@ -0,0 +1,299 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="1066.6666"
+   height="1066.6666"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"
+   sodipodi:docname="dcpomatic2_disk.svg"
+   viewBox="0 0 1000 1000">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.061741495"
+     inkscape:cx="-2231.4864"
+     inkscape:cy="438.2963"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1680"
+     inkscape:window-height="995"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:document-rotation="0"
+     inkscape:snap-global="true"
+     inkscape:snap-bbox="true" />
+  <defs
+     id="defs4">
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient979">
+      <stop
+         style="stop-color:#255a54;stop-opacity:1;"
+         offset="0"
+         id="stop975" />
+      <stop
+         style="stop-color:#133430;stop-opacity:1"
+         offset="1"
+         id="stop977" />
+    </linearGradient>
+    <linearGradient
+       x1="403.63"
+       y1="448.35001"
+       gradientTransform="matrix(0.70711,-0.70711,0.70711,0.70711,-203.97741,756.21351)"
+       x2="382.89999"
+       gradientUnits="userSpaceOnUse"
+       y2="448.35001"
+       id="linearGradient855">
+      <stop
+         offset="0"
+         stop-opacity=".39216"
+         stop-color="#7f7f7f"
+         id="stop893"
+         style="stop-color:#7f7f7f;stop-opacity:1" />
+      <stop
+         offset="1"
+         stop-opacity=".39216"
+         stop-color="#6f6f6f"
+         id="stop895"
+         style="stop-color:#6f6f6f;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient855-3"
+       y2="448.35001"
+       gradientUnits="userSpaceOnUse"
+       x2="382.89999"
+       gradientTransform="matrix(0.70711,-0.70711,0.70711,0.70711,-203.97741,756.21351)"
+       y1="448.35001"
+       x1="403.63">
+      <stop
+         style="stop-color:#7f7f7f;stop-opacity:1"
+         id="stop851"
+         stop-color="#7f7f7f"
+         stop-opacity=".39216"
+         offset="0" />
+      <stop
+         style="stop-color:#6f6f6f;stop-opacity:0"
+         id="stop853"
+         stop-color="#6f6f6f"
+         stop-opacity=".39216"
+         offset="1" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="573.17725 : 70.615423 : 0"
+       inkscape:vp_y="0 : 1048.4799 : 0"
+       inkscape:vp_z="1196.4109 : -308.5085 : 0"
+       inkscape:persp3d-origin="531.47195 : 173.77218 : 1"
+       id="perspective4530" />
+    <linearGradient
+       x1="-772.01001"
+       y1="742.5"
+       gradientTransform="matrix(-0.84033,-0.84033,-0.84033,0.84033,136.32259,-691.39649)"
+       x2="-886.76001"
+       gradientUnits="userSpaceOnUse"
+       y2="742.5"
+       id="linearGradient3594">
+      <stop
+         offset="0"
+         stop-color="#ffffff"
+         id="stop4687" />
+      <stop
+         offset="1"
+         stop-opacity="0"
+         stop-color="#ffffff"
+         id="stop4689" />
+    </linearGradient>
+    <linearGradient
+       x1="386.39001"
+       y1="63.870998"
+       gradientTransform="matrix(0.70711,-0.70711,0.70711,0.70711,-203.97741,756.21351)"
+       x2="385.04001"
+       gradientUnits="userSpaceOnUse"
+       y2="613.94"
+       id="linearGradient3601">
+      <stop
+         offset="0"
+         stop-color="#7f7f7f"
+         id="stop3797" />
+      <stop
+         offset="1"
+         stop-color="#6f6f6f"
+         id="stop3799" />
+    </linearGradient>
+    <linearGradient
+       gradientTransform="translate(-77.797413,384.00351)"
+       x1="409.38"
+       y1="358.29999"
+       x2="212.92999"
+       gradientUnits="userSpaceOnUse"
+       y2="161.84"
+       id="linearGradient3609">
+      <stop
+         offset="0"
+         stop-color="#6f6f6f"
+         id="stop4034" />
+      <stop
+         offset=".5"
+         stop-color="#6f6f6f"
+         id="stop3374" />
+      <stop
+         offset="1"
+         stop-color="#6f6f6f"
+         id="stop3376" />
+    </linearGradient>
+    <linearGradient
+       x1="403.63"
+       y1="448.35001"
+       gradientTransform="matrix(0.70711,-0.70711,0.70711,0.70711,-203.97741,756.21351)"
+       x2="382.89999"
+       gradientUnits="userSpaceOnUse"
+       y2="448.35001"
+       id="linearGradient3632">
+      <stop
+         offset="0"
+         stop-opacity=".39216"
+         stop-color="#7f7f7f"
+         id="stop3636"
+         style="stop-color:#7f7f7f;stop-opacity:1" />
+      <stop
+         offset="1"
+         stop-opacity=".39216"
+         stop-color="#6f6f6f"
+         id="stop3638"
+         style="stop-color:#6f6f6f;stop-opacity:0.01941748" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient979"
+       id="linearGradient981"
+       x1="258.13272"
+       y1="581.64954"
+       x2="280.90915"
+       y2="487.69684"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.2956992,0,0,1.1173853,-70.442976,-75.323286)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient979"
+       id="linearGradient981-5"
+       x1="258.13272"
+       y1="581.64954"
+       x2="280.90915"
+       y2="487.69684"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.2956992,0,0,1.1173853,346.73201,-93.640862)" />
+  </defs>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-52.362188)">
+    <rect
+       style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:#ececec;stroke-width:15.00000095;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
+       id="rect945"
+       width="1167.6385"
+       height="1151.5332"
+       x="1217.4148"
+       y="1347.8682"
+       rx="33.75"
+       ry="33.75" />
+    <image
+       sodipodi:absref="/home/carl/src/dcpomatic/graphics/src/dcpomatic.png"
+       xlink:href="dcpomatic.png"
+       style="stroke:#000000;stroke-opacity:1;stroke-width:2.5000001;stroke-miterlimit:4;stroke-dasharray:none;opacity:1"
+       id="image4358"
+       preserveAspectRatio="none"
+       height="960.00006"
+       width="960.00006"
+       x="10.670144"
+       y="80.386467" />
+    <rect
+       style="opacity:1;vector-effect:none;fill:url(#linearGradient981);fill-opacity:1;stroke:none;stroke-width:15.00000095;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
+       id="rect958"
+       width="95.643623"
+       height="189.7067"
+       x="238.2251"
+       y="451.96884"
+       rx="43.729847"
+       ry="36.004051" />
+    <rect
+       style="opacity:1;vector-effect:none;fill:url(#linearGradient981-5);fill-opacity:1;stroke:none;stroke-width:15.00000095;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
+       id="rect958-6"
+       width="95.643623"
+       height="189.70668"
+       x="655.40009"
+       y="433.65125"
+       rx="43.729847"
+       ry="36.004051" />
+    <path
+       style="fill:#808080;stroke:#ffffff;stroke-width:15.00000095;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 635.01872,349.90824 85.16099,283.02659 H 263.60389 l 87.18344,-281.69086 c 3.82184,-13.96906 23.00671,-28.47528 36.85535,-28.47528 l 213.6276,-0.35249 c 17.09674,0.72772 28.6937,11.26128 33.74844,27.49204 z"
+       id="path947"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccccccc" />
+    <rect
+       style="opacity:1;vector-effect:none;fill:#999999;fill-opacity:1;stroke:#ececec;stroke-width:30.00000191;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
+       id="rect943"
+       width="456.57584"
+       height="105.73843"
+       x="263.60385"
+       y="632.93481"
+       ry="17.726633"
+       rx="17.773174" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.50000191px;line-height:1.20000005;font-family:'Bitstream Vera Sans Mono';-inkscape-font-specification:'Bitstream Vera Sans Mono';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.9375;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
+       x="-1064.7976"
+       y="826.49603"
+       id="text1027"><tspan
+         sodipodi:role="line"
+         id="tspan1025"
+         x="-1064.7976"
+         y="846.88104"
+         style="stroke-width:0.9375;-inkscape-font-specification:'Bitstream Vera Sans Mono';font-family:'Bitstream Vera Sans Mono';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;" /></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:128.48579407px;line-height:1.20000005;font-family:'Latin Modern Roman';-inkscape-font-specification:'Latin Modern Roman,  Bold';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.93749988;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       x="394.93756"
+       y="729.72614"
+       id="text1035"><tspan
+         sodipodi:role="line"
+         id="tspan1033"
+         x="394.93756"
+         y="729.72614"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Latin Modern Roman';-inkscape-font-specification:'Latin Modern Roman,  Bold';stroke-width:0.93749988">DCP</tspan></text>
+    <path
+       style="opacity:1;vector-effect:none;fill:#999999;fill-opacity:1;stroke:#ececec;stroke-width:30.00000191;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-end:none;paint-order:stroke fill markers"
+       d="m 292.58069,687.62725 h 37.57918"
+       id="path1037"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
index 2e5bccd17d63aee71b49f59b156cc7c6cccf3f0e..9b75493dbd94a0e5480d92540241f06a2d2ad6f9 100755 (executable)
@@ -12,9 +12,11 @@ if [ `basename $pwd` != "graphics" ]; then
     exit 1
 fi
 
     exit 1
 fi
 
+svg_apps="dcpomatic2_kdm dcpomatic2_server dcpomatic2_batch dcpomatic2_player dcpomatic2_playlist dcpomatic2_disk"
+
 if [ `uname -s` == "Darwin" ]; then
     # Convert OS X icons using OS X-only iconutil
 if [ `uname -s` == "Darwin" ]; then
     # Convert OS X icons using OS X-only iconutil
-    for p in dcpomatic2 dcpomatic2_kdm dcpomatic2_server dcpomatic2_batch dcpomatic2_player dcpomatic2_playlist; do
+    for p in dcpomatic2 $svg_apps; do
        iconutil --convert icns --output osx/$p.icns osx/$p.iconset
     done
 else
        iconutil --convert icns --output osx/$p.icns osx/$p.iconset
     done
 else
@@ -36,7 +38,7 @@ else
            convert src/dcpomatic.png -resize $rx$r osx/$p.iconset/icon_${r}x${r}.png
            convert src/dcpomatic.png -resize $rx$r osx/$p.iconset/icon_${r}x${r}@2x.png
        done
            convert src/dcpomatic.png -resize $rx$r osx/$p.iconset/icon_${r}x${r}.png
            convert src/dcpomatic.png -resize $rx$r osx/$p.iconset/icon_${r}x${r}@2x.png
        done
-       for p in dcpomatic2_kdm dcpomatic2_server dcpomatic2_batch dcpomatic2_player dcpomatic2_playlist; do
+       for p in $svg_apps; do
            mkdir -p osx/$p.iconset
            $INKSCAPE osx/$p.iconset/icon_${r}x${r}.png -w $r -h $r src/$p.svg
            $INKSCAPE osx/$p.iconset/icon_${r}x${r}@2x.png -w $r -h $r src/$p.svg
            mkdir -p osx/$p.iconset
            $INKSCAPE osx/$p.iconset/icon_${r}x${r}.png -w $r -h $r src/$p.svg
            $INKSCAPE osx/$p.iconset/icon_${r}x${r}@2x.png -w $r -h $r src/$p.svg
@@ -48,14 +50,14 @@ else
     for r in 16 22 32 48 64 128 256 512; do
        mkdir -p linux/$r
        convert src/dcpomatic.png -resize $rx$r linux/$r/dcpomatic2.png
     for r in 16 22 32 48 64 128 256 512; do
        mkdir -p linux/$r
        convert src/dcpomatic.png -resize $rx$r linux/$r/dcpomatic2.png
-       for p in dcpomatic2_kdm dcpomatic2_server dcpomatic2_batch dcpomatic2_player dcpomatic2_playlist; do
+       for p in $svg_apps; do
            $INKSCAPE linux/$r/$p.png src/$p.svg -w $r -h $r
        done
     done
 
     # Windows application icons
     mkdir -p windows
            $INKSCAPE linux/$r/$p.png src/$p.svg -w $r -h $r
        done
     done
 
     # Windows application icons
     mkdir -p windows
-    for p in dcpomatic2 dcpomatic2_kdm dcpomatic2_server dcpomatic2_batch dcpomatic2_player dcpomatic2_playlist; do
+    for p in dcpomatic2 $svg_apps; do
        icotool -c -o windows/$p.ico linux/16/$p.png linux/32/$p.png linux/48/$p.png linux/64/$p.png linux/128/$p.png
     done
     convert src/dcpomatic.png -resize 400x400 windows/dcpomatic.bmp
        icotool -c -o windows/$p.ico linux/16/$p.png linux/32/$p.png linux/48/$p.png linux/64/$p.png linux/128/$p.png
     done
     convert src/dcpomatic.png -resize 400x400 windows/dcpomatic.bmp
index 658bf796e788cb6ab03a3877f5131da550e03a26..6214556419d2b3b204e3cb6afd2b6d10c5387eb2 100644 (file)
Binary files a/graphics/web/favicon-128x128.png and b/graphics/web/favicon-128x128.png differ
index 68e4f96c4e1e453b4b7e3a89cd731efa60784e0c..4c31c0258b920b7011bf47ae6e91059812748ffc 100644 (file)
Binary files a/graphics/web/favicon-16x16.png and b/graphics/web/favicon-16x16.png differ
index 75fd6ed7186bd620d05fabdaecca390ffed05a1e..847bc813a7a7e6c3206fd2bdc6e50cbcbdee54fe 100644 (file)
Binary files a/graphics/web/favicon-32x32.png and b/graphics/web/favicon-32x32.png differ
index f7e5ca6fc8435c618ec7e0416a50f067b7d1548b..e836814d1bb9b2fda9c4684643886de028795d96 100644 (file)
Binary files a/graphics/web/favicon-64x64.png and b/graphics/web/favicon-64x64.png differ
diff --git a/graphics/windows/dcpomatic2_disk.ico b/graphics/windows/dcpomatic2_disk.ico
new file mode 100644 (file)
index 0000000..3ec68e2
Binary files /dev/null and b/graphics/windows/dcpomatic2_disk.ico differ
diff --git a/platform/osx/dcpomatic2_disk.Info.plist.in b/platform/osx/dcpomatic2_disk.Info.plist.in
new file mode 100644 (file)
index 0000000..1771468
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>English</string>
+       <key>CFBundleExecutable</key>
+       <string>dcpomatic2_disk</string>
+       <key>CFBundleGetInfoString</key>
+       <string>DCP-o-matic 2 Disk Writer</string>
+       <key>CFBundleIconFile</key>
+       <string>dcpomatic2_disk.icns</string>
+       <key>CFBundleIdentifier</key>
+       <string>com.dcpomatic.disk</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundleName</key>
+       <string>DCP-o-matic 2 Disk Writer</string>
+       <key>CFBundlePackageType</key>
+       <string>APPL</string>
+       <key>CFBundleShortVersions</key>
+       <string>@VERSION@</string>
+       <key>CFBundleSignature</key>
+       <string>DOMC</string>
+       <key>CFBundleVersion</key>
+       <string>@VERSION@</string>
+       <key>CFBundleAllowMixedLocalizations</key>
+       <true/>
+       <key>LSUIElement</key>
+       <string>0</string>
+       <key>NSMainNibFile</key>
+       <string>MainMenu</string>
+       <key>NSPrincipalClass</key>
+       <string>NSApplication</string>
+</dict>
+</plist>
index 867200aa74fbc4e73490b618f5670e5ccaedce38..9099e5453eb9ed295960e6e9b68bce26093b7c13 100644 (file)
@@ -122,6 +122,8 @@ function copy_libs {
     copy_lib_root libswscale "$dest"
     copy_lib_root libpostproc "$dest"
     copy_lib_root libswresample "$dest"
     copy_lib_root libswscale "$dest"
     copy_lib_root libpostproc "$dest"
     copy_lib_root libswresample "$dest"
+    copy_lib_root liblwext4 "$dest"
+    copy_lib_root libblockdev "$dest"
     copy $ROOT src/dcpomatic/build/src/lib/libdcpomatic2.dylib "$dest"
     copy $ROOT src/dcpomatic/build/src/wx/libdcpomatic2-wx.dylib "$dest"
     copy_lib_env libboost_system "$dest"
     copy $ROOT src/dcpomatic/build/src/lib/libdcpomatic2.dylib "$dest"
     copy $ROOT src/dcpomatic/build/src/wx/libdcpomatic2-wx.dylib "$dest"
     copy_lib_env libboost_system "$dest"
@@ -187,6 +189,7 @@ function copy_resources {
     cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_player.icns "$dest"
     cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_batch.icns "$dest"
     cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_playlist.icns "$dest"
     cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_player.icns "$dest"
     cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_batch.icns "$dest"
     cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_playlist.icns "$dest"
+    cp $prefix/src/dcpomatic/graphics/osx/dcpomatic2_disk.icns "$dest"
     cp $prefix/src/dcpomatic/graphics/osx/preferences/colour_conversions.png "$dest"
     cp $prefix/src/dcpomatic/graphics/osx/preferences/defaults.png "$dest"
     cp $prefix/src/dcpomatic/graphics/osx/preferences/kdm_email.png "$dest"
     cp $prefix/src/dcpomatic/graphics/osx/preferences/colour_conversions.png "$dest"
     cp $prefix/src/dcpomatic/graphics/osx/preferences/defaults.png "$dest"
     cp $prefix/src/dcpomatic/graphics/osx/preferences/kdm_email.png "$dest"
@@ -227,7 +230,7 @@ function copy_resources {
 }
 
 # param $1 list of things that link to other things
 }
 
 # param $1 list of things that link to other things
-function relink {
+function relink_relative {
     to_relink=`echo $to_relink | sed -e "s/\+//g"`
     local linkers=("$@")
 
     to_relink=`echo $to_relink | sed -e "s/\+//g"`
     local linkers=("$@")
 
@@ -250,25 +253,61 @@ function relink {
     done
 }
 
     done
 }
 
+# param $1 directory things should be relinked into
+#       $2 list of things that link to other things
+function relink_absolute {
+    to_relink=`echo $to_relink | sed -e "s/\+//g"`
+    target=$1
+    shift
+    local linkers=("$@")
+
+    for obj in "${linkers[@]}"; do
+       deps=`otool -L "$obj" | awk '{print $1}' | egrep "($to_relink)" | egrep "($ENV|$ROOT|boost|libicu)"`
+       for dep in $deps; do
+           base=`basename $dep`
+            install_name_tool -change "$dep" "$target"/$base -id `basename "$obj"` "$obj"
+       done
+    done
+}
+
+function sign {
+    codesign --deep --force --verify --verbose --options runtime --sign "Developer ID Application: Carl Hetherington (R82DXSR997)" "$1"
+    if [ "$?" != "0" ]; then
+       echo "Failed to sign $1"
+       exit 1
+    fi
+}
+
+
 # @param #1 .app directory
 # @param #1 .app directory
-# @param #2 full name e.g. DCP-o-matic Batch Converter
-# @param #3 bundle id e.g. com.dcpomatic.batch
+# @param #2 .pkg or ""
+# @param #3 full name e.g. DCP-o-matic Batch Converter
+# @param #4 bundle id e.g. com.dcpomatic.batch
 function make_dmg {
     local appdir="$1"
 function make_dmg {
     local appdir="$1"
-    local full_name="$2"
-    local bundle_id="$3"
+    local pkg="$2"
+    local full_name="$3"
+    local bundle_id="$4"
     tmp_dmg=dcpomatic_tmp.dmg
     dmg="$full_name $version.dmg"
     vol_name=DCP-o-matic-$version
 
     tmp_dmg=dcpomatic_tmp.dmg
     dmg="$full_name $version.dmg"
     vol_name=DCP-o-matic-$version
 
-    codesign --deep --force --verify --verbose --options runtime --sign "Developer ID Application: Carl Hetherington (R82DXSR997)" "$appdir"
-    if [ "$?" != "0" ]; then
-       echo "Failed to sign .app"
-       exit 1
+    sign "$appdir"
+
+    if [ "$pkg" != "" ]; then
+       productsign --sign "Developer ID Installer: Carl Hetherington (R82DXSR997)" "$pkg" "signed_temp.pkg"
+       if [ "$?" != "0" ]; then
+           echo "Failed to sign .pkg"
+           exit 1
+       fi
+       mv signed_temp.pkg "$pkg"
     fi
 
     mkdir -p $vol_name
     cp -a "$appdir" $vol_name
     fi
 
     mkdir -p $vol_name
     cp -a "$appdir" $vol_name
+    if [ "$pkg" != "" ]; then
+        cp -a "$pkg" $vol_name
+    fi
     ln -s /Applications "$vol_name/Applications"
     cat<<EOF > "$vol_name/READ ME.txt"
 Welcome to DCP-o-matic!  The first time you run the program there may be
     ln -s /Applications "$vol_name/Applications"
     cat<<EOF > "$vol_name/READ ME.txt"
 Welcome to DCP-o-matic!  The first time you run the program there may be
@@ -285,6 +324,22 @@ DCP-o-matic Anwendungen ab, bei weiteren Programmstarts wird sie nicht
 mehr auftreten.
 EOF
 
 mehr auftreten.
 EOF
 
+    if [ "$pkg" != "" ]; then
+        cat<<EOF > "$vol_name/READ ME.txt"
+
+To run this software successfully you must install $pkg before running
+the .app
+EOF
+    fi
+
+    if [ "$pkg" != "" ]; then
+        cat<<EOF > "$vol_name/READ ME.de_DE.txt"
+
+To run this software successfully you must install $pkg before running
+the .app
+EOF
+
+    fi
     rm -f $tmp_dmg "$dmg"
     hdiutil create -srcfolder $vol_name -volname $vol_name -fs HFS+ -fsargs "-c c=64,a=16,e=16" -format UDRW -size $DMG_SIZE $tmp_dmg
     attach=$(hdiutil attach -readwrite -noverify -noautoopen $tmp_dmg)
     rm -f $tmp_dmg "$dmg"
     hdiutil create -srcfolder $vol_name -volname $vol_name -fs HFS+ -fsargs "-c c=64,a=16,e=16" -format UDRW -size $DMG_SIZE $tmp_dmg
     attach=$(hdiutil attach -readwrite -noverify -noautoopen $tmp_dmg)
@@ -298,7 +353,9 @@ EOF
            set current view of container window to icon view
            set toolbar visible of container window to false
            set statusbar visible of container window to false
            set current view of container window to icon view
            set toolbar visible of container window to false
            set statusbar visible of container window to false
-           set the bounds of container window to {400, 200, 940, 300}
+           set the bounds of container window to {400, 200, 1160, 600}
+           set the bounds of container window to {400, 200, 1160, 600}
+           set the bounds of container window to {400, 200, 1160, 600}
            set theViewOptions to the icon view options of container window
            set arrangement of theViewOptions to not arranged
            set icon size of theViewOptions to 64
            set theViewOptions to the icon view options of container window
            set arrangement of theViewOptions to not arranged
            set icon size of theViewOptions to 64
@@ -306,6 +363,7 @@ EOF
            set position of item "Applications" of container window to {265, 80}
            set position of item "READ ME.txt" of container window to {430, 80}
            set position of item "READ ME.de_DE.txt" of container window to {595, 80}
            set position of item "Applications" of container window to {265, 80}
            set position of item "READ ME.txt" of container window to {430, 80}
            set position of item "READ ME.de_DE.txt" of container window to {595, 80}
+           set position of item "DCP-o-matic Disk Writer.pkg" of container window to {90, 255}
            close
            open
            update without registering applications
            close
            open
            update without registering applications
@@ -395,8 +453,8 @@ copy $ROOT bin/ffprobe "$approot/MacOS"
 copy $ROOT src/openssl/apps/openssl "$approot/MacOS"
 cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2.Info.plist "$approot/Info.plist"
 rl=("$approot/MacOS/dcpomatic2" "$approot/MacOS/dcpomatic2_cli" "$approot/MacOS/dcpomatic2_create" "$approot/MacOS/ffprobe" "$approot/Frameworks/"*.dylib)
 copy $ROOT src/openssl/apps/openssl "$approot/MacOS"
 cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2.Info.plist "$approot/Info.plist"
 rl=("$approot/MacOS/dcpomatic2" "$approot/MacOS/dcpomatic2_cli" "$approot/MacOS/dcpomatic2_create" "$approot/MacOS/ffprobe" "$approot/Frameworks/"*.dylib)
-relink "${rl[@]}"
-make_dmg "$appdir" "DCP-o-matic" com.dcpomatic
+relink_relative "${rl[@]}"
+make_dmg "$appdir" "" "DCP-o-matic" com.dcpomatic
 
 # DCP-o-matic KDM Creator
 setup "DCP-o-matic 2 KDM Creator.app"
 
 # DCP-o-matic KDM Creator
 setup "DCP-o-matic 2 KDM Creator.app"
@@ -405,8 +463,8 @@ copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_kdm_cli "$approot/MacOS"
 copy $ROOT src/openssl/apps/openssl "$approot/MacOS"
 cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_kdm.Info.plist "$approot/Info.plist"
 rl=("$approot/MacOS/dcpomatic2_kdm" "$approot/MacOS/dcpomatic2_kdm_cli" "$approot/Frameworks/"*.dylib)
 copy $ROOT src/openssl/apps/openssl "$approot/MacOS"
 cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_kdm.Info.plist "$approot/Info.plist"
 rl=("$approot/MacOS/dcpomatic2_kdm" "$approot/MacOS/dcpomatic2_kdm_cli" "$approot/Frameworks/"*.dylib)
-relink "${rl[@]}"
-make_dmg "$appdir" "DCP-o-matic KDM Creator" com.dcpomatic.kdm
+relink_relative "${rl[@]}"
+make_dmg "$appdir" "" "DCP-o-matic KDM Creator" com.dcpomatic.kdm
 
 # DCP-o-matic Encode Server
 setup "DCP-o-matic 2 Encode Server.app"
 
 # DCP-o-matic Encode Server
 setup "DCP-o-matic 2 Encode Server.app"
@@ -415,8 +473,8 @@ copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_server_cli "$approot/MacOS"
 copy $ROOT src/openssl/apps/openssl "$approot/MacOS"
 cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_server.Info.plist "$approot/Info.plist"
 rl=("$approot/MacOS/dcpomatic2_server" "$approot/MacOS/dcpomatic2_server_cli" "$approot/Frameworks/"*.dylib)
 copy $ROOT src/openssl/apps/openssl "$approot/MacOS"
 cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_server.Info.plist "$approot/Info.plist"
 rl=("$approot/MacOS/dcpomatic2_server" "$approot/MacOS/dcpomatic2_server_cli" "$approot/Frameworks/"*.dylib)
-relink "${rl[@]}"
-make_dmg "$appdir" "DCP-o-matic Encode Server" com.dcpomatic.server
+relink_relative "${rl[@]}"
+make_dmg "$appdir" "" "DCP-o-matic Encode Server" com.dcpomatic.server
 
 # DCP-o-matic Batch Converter
 setup "DCP-o-matic 2 Batch converter.app"
 
 # DCP-o-matic Batch Converter
 setup "DCP-o-matic 2 Batch converter.app"
@@ -424,8 +482,8 @@ copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_batch "$approot/MacOS"
 copy $ROOT src/openssl/apps/openssl "$approot/MacOS"
 cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_batch.Info.plist "$approot/Info.plist"
 rl=("$approot/MacOS/dcpomatic2_batch" "$approot/Frameworks/"*.dylib)
 copy $ROOT src/openssl/apps/openssl "$approot/MacOS"
 cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_batch.Info.plist "$approot/Info.plist"
 rl=("$approot/MacOS/dcpomatic2_batch" "$approot/Frameworks/"*.dylib)
-relink "${rl[@]}"
-make_dmg "$appdir" "DCP-o-matic Batch Converter" com.dcpomatic.batch
+relink_relative "${rl[@]}"
+make_dmg "$appdir" "" "DCP-o-matic Batch Converter" com.dcpomatic.batch
 
 # DCP-o-matic Player
 setup "DCP-o-matic 2 Player.app"
 
 # DCP-o-matic Player
 setup "DCP-o-matic 2 Player.app"
@@ -433,8 +491,8 @@ copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_player "$approot/MacOS"
 copy $ROOT src/openssl/apps/openssl "$approot/MacOS"
 cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_player.Info.plist "$approot/Info.plist"
 rl=("$approot/MacOS/dcpomatic2_player" "$approot/Frameworks/"*.dylib)
 copy $ROOT src/openssl/apps/openssl "$approot/MacOS"
 cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_player.Info.plist "$approot/Info.plist"
 rl=("$approot/MacOS/dcpomatic2_player" "$approot/Frameworks/"*.dylib)
-relink "${rl[@]}"
-make_dmg "$appdir" "DCP-o-matic Player" com.dcpomatic.player
+relink_relative "${rl[@]}"
+make_dmg "$appdir" "" "DCP-o-matic Player" com.dcpomatic.player
 
 # DCP-o-matic Playlist Editor
 setup "DCP-o-matic 2 Playlist Editor.app"
 
 # DCP-o-matic Playlist Editor
 setup "DCP-o-matic 2 Playlist Editor.app"
@@ -442,5 +500,80 @@ copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_playlist "$approot/MacOS"
 copy $ROOT src/openssl/apps/openssl "$approot/MacOS"
 cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_playlist.Info.plist "$approot/Info.plist"
 rl=("$approot/MacOS/dcpomatic2_playlist" "$approot/Frameworks/"*.dylib)
 copy $ROOT src/openssl/apps/openssl "$approot/MacOS"
 cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_playlist.Info.plist "$approot/Info.plist"
 rl=("$approot/MacOS/dcpomatic2_playlist" "$approot/Frameworks/"*.dylib)
-relink "${rl[@]}"
-make_dmg "$appdir" "DCP-o-matic Playlist Editor" com.dcpomatic.playlist
+relink_relative "${rl[@]}"
+make_dmg "$appdir" "" "DCP-o-matic Playlist Editor" com.dcpomatic.playlist
+
+# DCP-o-matic Disk Writer .app
+setup "DCP-o-matic 2 Disk Writer.app"
+copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_disk "$approot/MacOS"
+cp $prefix/src/dcpomatic/build/platform/osx/dcpomatic2_disk.Info.plist "$approot/Info.plist"
+rl=("$approot/MacOS/dcpomatic2_disk" "$approot/Frameworks/"*.dylib)
+relink_relative "${rl[@]}"
+
+# DCP-o-matic Disk Writer daemon .pkg
+
+pkgbase=tmp-disk-writer
+rm -rf $pkgbase
+mkdir $pkgbase
+pkgbin=$pkgbase/bin
+pkgroot=$pkgbase/root
+
+mkdir -p $pkgroot/Library/LaunchDaemons
+cat > $pkgroot/Library/LaunchDaemons/com.dcpomatic.disk.writer.plist <<EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>Label</key>
+    <string>com.dcpomatic.disk.writer</string>
+    <key>ProgramArguments</key>
+    <array>
+        <string>/Library/Application Support/com.dcpomatic/dcpomatic2_disk_writer</string>
+    </array>
+    <key>EnvironmentVariables</key>
+    <dict>
+        <key>DYLD_LIBRARY_PATH</key>
+        <string><![CDATA[/Library/Application Support/com.dcpomatic]]></string>
+    </dict>
+    <key>KeepAlive</key>
+    <true/>
+    <key>RunAtLoad</key>
+    <true/>
+    <key>Debug</key>
+    <true/>
+    <key>StandardOutPath</key>
+    <string>/Users/carl/damon.out.log</string>
+    <key>StandardErrorPath</key>
+    <string>/Users/carl/damon.err.log</string>
+</dict>
+</plist>
+EOF
+
+# Get the binaries together in $pkgbin then move them to the
+# place with spaces in the filename to avoid some of the pain of escaping
+
+mkdir $pkgbin
+copy $ROOT src/dcpomatic/build/src/tools/dcpomatic2_disk_writer "$pkgbin"
+copy_libs "$pkgbin"
+
+rl=("$pkgbin/dcpomatic2_disk_writer" "$pkgbin/"*.dylib)
+relink_absolute "/Library/Application Support/com.dcpomatic" "${rl[@]}"
+
+mkdir $pkgbase/scripts
+cat > $pkgbase/scripts/postinstall <<EOF
+#!/bin/sh
+/bin/launchctl load "/Library/LaunchDaemons/com.dcpomatic.disk.writer.plist"
+exit 0
+EOF
+chmod gou+x $pkgbase/scripts/postinstall
+
+find "$pkgbin" -iname "*.dylib" -print0 | while IFS= read -r -d '' f; do
+    sign "$f"
+done
+sign "$pkgbin/dcpomatic2_disk_writer"
+
+mkdir -p "$pkgroot/Library/Application Support/com.dcpomatic"
+mv $pkgbin/* "$pkgroot/Library/Application Support/com.dcpomatic/"
+pkgbuild --root $pkgroot --identifier com.dcpomatic.disk.writer --scripts $pkgbase/scripts "DCP-o-matic Disk Writer.pkg"
+
+make_dmg "$appdir" "DCP-o-matic Disk Writer.pkg" "DCP-o-matic Disk Writer" com.dcpomatic.disk
diff --git a/platform/osx/set_paths.sh b/platform/osx/set_paths.sh
new file mode 100644 (file)
index 0000000..f5e1bd8
--- /dev/null
@@ -0,0 +1,12 @@
+base=$HOME/dcpomatic
+env=$HOME/osx-environment
+sdk=$HOME/SDK
+
+export CPPFLAGS= LDFLAGS="-L$base/lib -L$env/lib -isysroot $sdk/MacOSX10.9.sdk -arch x86_64"
+export LINKFLAGS="-L$base/lib -L$env/lib -isysroot $sdk/MacOSX10.9.sdk -arch x86_64"
+export MACOSX_DEPLOYMENT_TARGET=10.9
+export CXXFLAGS="-I$base/include -I$env/include -isysroot $sdk/MacOSX10.9.sdk -arch x86_64"
+export CFLAGS="-I$base/include -I$env/include -isysroot $sdk/MacOSX10.9.sdk -arch x86_64"
+export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$base/lib:$env/64/lib
+export PATH=$env/64/bin:$PATH
+export PKG_CONFIG_PATH=$env/64/lib/pkgconfig:$base/lib/pkgconfig
index c1db9256b0b8fc2033951b64c376d065badb8c05..da20065f5622e297b223e0167451c3a99a06268c 100644 (file)
@@ -5,3 +5,4 @@ def build(bld):
     obj = bld(features='subst', source='dcpomatic2_batch.Info.plist.in', target='dcpomatic2_batch.Info.plist', version=bld.env.VERSION)
     obj = bld(features='subst', source='dcpomatic2_player.Info.plist.in', target='dcpomatic2_player.Info.plist', version=bld.env.VERSION)
     obj = bld(features='subst', source='dcpomatic2_playlist.Info.plist.in', target='dcpomatic2_playlist.Info.plist', version=bld.env.VERSION)
     obj = bld(features='subst', source='dcpomatic2_batch.Info.plist.in', target='dcpomatic2_batch.Info.plist', version=bld.env.VERSION)
     obj = bld(features='subst', source='dcpomatic2_player.Info.plist.in', target='dcpomatic2_player.Info.plist', version=bld.env.VERSION)
     obj = bld(features='subst', source='dcpomatic2_playlist.Info.plist.in', target='dcpomatic2_playlist.Info.plist', version=bld.env.VERSION)
+    obj = bld(features='subst', source='dcpomatic2_disk.Info.plist.in', target='dcpomatic2_disk.Info.plist', version=bld.env.VERSION)
diff --git a/platform/windows/copy_deps.sh b/platform/windows/copy_deps.sh
new file mode 100644 (file)
index 0000000..5cb6a01
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/bash
+
+MXE=/opt/mxe/usr/x86_64-w64-mingw32.shared/bin
+
+dir=$1
+if [ "$dir" == "" ]; then
+    echo "Syntax: $0 <dir>"
+    exit 1
+fi
+
+cp $MXE/libgcc_s_seh-1.dll $dir
+cp $MXE/libstdc++-6.dll $dir
+cp $MXE/libboost*.dll $dir
+cp $MXE/libglibmm*.dll $dir
+cp $MXE/libcrypto*.dll $dir
+cp $MXE/libwinpthread*.dll $dir
+cp $MXE/wxbase*.dll $dir
+cp $MXE/wxmsw*.dll $dir
+cp $MXE/libcurl*.dll $dir
+cp $MXE/libxml*.dll $dir
+cp $MXE/libjpeg*.dll $dir
+cp $MXE/zlib*.dll $dir
+cp $MXE/libpng*.dll $dir
+cp $MXE/libtiff*.dll $dir
+cp $MXE/libssh*.dll $dir
+cp $MXE/libidn*.dll $dir
+cp $MXE/liblzma*.dll $dir
+cp $MXE/libiconv*.dll $dir
+cp $MXE/libxslt*.dll $dir
+cp $MXE/libltdl*.dll $dir
+cp $MXE/libintl*.dll $dir
+cp $MXE/libunistring*.dll $dir
+cp $MXE/libwebp*.dll $dir
+cp $MXE/libgcrypt*.dll $dir
+cp $MXE/libdl*.dll $dir
+cp $MXE/libgpg*.dll $dir
+cp $MXE/libcairo*.dll $dir
+cp $MXE/libfontconfig*.dll $dir
+cp $MXE/libglib*.dll $dir      
+cp $MXE/icu*.dll $dir
+cp $MXE/libnettle*.dll $dir
+cp $MXE/libpango*.dll $dir
+cp $MXE/libsamplerate*.dll $dir
+cp $MXE/libzip*.dll $dir
+cp $MXE/libgmodule*.dll $dir
+cp $MXE/libgobject*.dll $dir
+cp $MXE/libsigc*.dll $dir
+cp $MXE/libpcre*.dll $dir
+cp $MXE/libx264*.dll $dir
+cp $MXE/libbz2*.dll $dir
+cp $MXE/libexpat*.dll $dir
+cp $MXE/libfreetype*.dll $dir
+cp $MXE/libffi*.dll $dir
+cp $MXE/libharfbuzz*.dll $dir
+cp $MXE/libpixman*.dll $dir
+cp $MXE/libnanomsg*.dll $dir
diff --git a/platform/windows/dcpomatic2_disk_debug.bat b/platform/windows/dcpomatic2_disk_debug.bat
new file mode 100644 (file)
index 0000000..f89a06a
--- /dev/null
@@ -0,0 +1 @@
+gdb.exe -x gdb_script dcpomatic2_disk.exe > %HOMEPATH%/Documents/dcpomatic_debug_log.txt
diff --git a/platform/windows/dcpomatic2_disk_writer.exe.manifest b/platform/windows/dcpomatic2_disk_writer.exe.manifest
new file mode 100644 (file)
index 0000000..7d922a0
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">\r
+    <assemblyIdentity version="1.0.0.0" processorArchitecture="X86" name="dcpomatic2_disk_writer" type="win32"/> \r
+    <description>DCP-o-matic Disk Writer</description> \r
+    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">\r
+           <security>\r
+                   <requestedPrivileges>\r
+                           <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>\r
+                   </requestedPrivileges>\r
+           </security>\r
+    </trustInfo>\r
+</assembly>\r
diff --git a/platform/windows/dcpomatic_disk.rc b/platform/windows/dcpomatic_disk.rc
new file mode 100644 (file)
index 0000000..e10e89e
--- /dev/null
@@ -0,0 +1,2 @@
+id ICON "../../graphics/windows/dcpomatic2_disk.ico"
+#include "wx-3.0/wx/msw/wx.rc"
diff --git a/platform/windows/dcpomatic_disk_writer.rc b/platform/windows/dcpomatic_disk_writer.rc
new file mode 100644 (file)
index 0000000..70138ed
--- /dev/null
@@ -0,0 +1,2 @@
+#include "winuser.h"\r
+1 RT_MANIFEST "dcpomatic2_disk_writer.exe.manifest"\r
diff --git a/platform/windows/set_paths.bat b/platform/windows/set_paths.bat
new file mode 100644 (file)
index 0000000..3a023fd
--- /dev/null
@@ -0,0 +1,2 @@
+set DCPOMATIC=z:
+set PATH=%PATH%;%DCPOMATIC%\lib;%DCPOMATIC%\bin;%DCPOMATIC%\src\dcpomatic\build\src\lib;%DCPOMATIC%\src\dcpomatic\build\src\wx
index db19c750ef315ea265e25d3250c7c8a01ad227ca..77f9bd0ccc7bfafad1dde19851b7849fe010d400 100644 (file)
@@ -1,7 +1,7 @@
 from __future__ import print_function
 import os
 
 from __future__ import print_function
 import os
 
-def write_installer(bits, windows_version, dcpomatic_version, debug, variant):
+def write_installer(bits, windows_version, dcpomatic_version, debug, variant, disk):
     try:
         os.makedirs('build/platform/windows')
     except:
     try:
         os.makedirs('build/platform/windows')
     except:
@@ -75,11 +75,10 @@ File "%static_deps%/bin/libintl-8.dll"
 File "%static_deps%/bin/libboost_chrono-mt.dll"
 File "%static_deps%/bin/libboost_filesystem-mt.dll"
 File "%static_deps%/bin/libboost_system-mt.dll"
 File "%static_deps%/bin/libboost_chrono-mt.dll"
 File "%static_deps%/bin/libboost_filesystem-mt.dll"
 File "%static_deps%/bin/libboost_system-mt.dll"
-File "%static_deps%/bin/libboost_thread_win32-mt.dll"
+File "%static_deps%/bin/libboost_thread-mt.dll"
 File "%static_deps%/bin/libboost_date_time-mt.dll"
 File "%static_deps%/bin/libboost_locale-mt.dll"
 File "%static_deps%/bin/libboost_regex-mt.dll"
 File "%static_deps%/bin/libboost_date_time-mt.dll"
 File "%static_deps%/bin/libboost_locale-mt.dll"
 File "%static_deps%/bin/libboost_regex-mt.dll"
-File "%static_deps%/bin/libeay32.dll"
 """, file=f)
 
     if bits == 32:
 """, file=f)
 
     if bits == 32:
@@ -129,15 +128,14 @@ File "%static_deps%/bin/libxslt-1.dll"
 File "%static_deps%/bin/libffi-6.dll"
 File "%static_deps%/bin/openssl.exe"
 File "%static_deps%/bin/libcurl-4.dll"
 File "%static_deps%/bin/libffi-6.dll"
 File "%static_deps%/bin/openssl.exe"
 File "%static_deps%/bin/libcurl-4.dll"
-File "%static_deps%/bin/ssleay32.dll"
-File "%static_deps%/bin/libzip-4.dll"
+File "%static_deps%/bin/libzip.dll"
 File "%static_deps%/bin/libcairomm-1.0-1.dll"
 File "%static_deps%/bin/libpangomm-1.4-1.dll"
 File "%static_deps%/bin/libsamplerate-0.dll"
 File "%static_deps%/bin/libcairomm-1.0-1.dll"
 File "%static_deps%/bin/libpangomm-1.4-1.dll"
 File "%static_deps%/bin/libsamplerate-0.dll"
-File "%static_deps%/bin/libnettle-6.dll"
-File "%static_deps%/bin/icuuc56.dll"
-File "%static_deps%/bin/icudt56.dll"
-File "%static_deps%/bin/icuin56.dll"
+File "%static_deps%/bin/libnettle-7.dll"
+File "%static_deps%/bin/icuuc65.dll"
+File "%static_deps%/bin/icudt65.dll"
+File "%static_deps%/bin/icuin65.dll"
 File "%static_deps%/bin/liblzma-5.dll"
 File "%static_deps%/bin/libpcre-1.dll"
 File "%static_deps%/bin/libharfbuzz-0.dll"
 File "%static_deps%/bin/liblzma-5.dll"
 File "%static_deps%/bin/libpcre-1.dll"
 File "%static_deps%/bin/libharfbuzz-0.dll"
@@ -146,26 +144,29 @@ File "%static_deps%/bin/liblcms2-2.dll"
 File "%static_deps%/bin/libwinpthread-1.dll"
 File "%static_deps%/bin/libgnutls-30.dll"
 File "%static_deps%/bin/libgmp-10.dll"
 File "%static_deps%/bin/libwinpthread-1.dll"
 File "%static_deps%/bin/libgnutls-30.dll"
 File "%static_deps%/bin/libgmp-10.dll"
-File "%static_deps%/bin/libhogweed-4.dll"
+File "%static_deps%/bin/libhogweed-5.dll"
 File "%static_deps%/bin/libidn2-0.dll"
 File "%static_deps%/bin/libunistring-2.dll"
 File "%static_deps%/bin/libssh2-1.dll"
 File "%static_deps%/bin/libgcrypt-20.dll"
 File "%static_deps%/bin/libidn2-0.dll"
 File "%static_deps%/bin/libunistring-2.dll"
 File "%static_deps%/bin/libssh2-1.dll"
 File "%static_deps%/bin/libgcrypt-20.dll"
-""", file=f)
-
-    if bits == 32:
-        print('File "%static_deps%/bin/libgpg-error-0.dll"', file=f)
-    else:
-        print('File "%static_deps%/bin/libgpg-error6-0.dll"', file=f)
-
-    print("""
+File "%static_deps%/bin/libgpg-error-0.dll"
 File "%static_deps%/bin/libpangoft2-1.0-0.dll"
 File "%static_deps%/bin/libpangoft2-1.0-0.dll"
-File "%static_deps%/bin/libx264-152.dll"
-
+File "%static_deps%/bin/libx264-155.dll"
+File "%static_deps%/bin/libwebp-7.dll"
+File "%static_deps%/bin/libcrypto-1_1-x64.dll"
+File "%static_deps%/bin/libltdl-7.dll"
+File "%static_deps%/bin/libdl.dll"
 File "%cdist_deps%/bin/asdcp-carl.dll"
 File "%cdist_deps%/bin/kumu-carl.dll"
     """, file=f)
 
 File "%cdist_deps%/bin/asdcp-carl.dll"
 File "%cdist_deps%/bin/kumu-carl.dll"
     """, file=f)
 
+    if disk:
+        print("""
+File "%static_deps%/bin/libnanomsg.dll"
+File "%cdist_deps%/lib/libblockdev.dll"
+File "%cdist_deps%/lib/liblwext4.dll"
+        """, file=f)
+
     if windows_version == 'xp':
         print("""
 File "%cdist_deps%/bin/avcodec-57.dll"
     if windows_version == 'xp':
         print("""
 File "%cdist_deps%/bin/avcodec-57.dll"
@@ -208,6 +209,8 @@ File "%cdist_deps%/src/openssl/apps/openssl.exe"
         print('File "%resources%/dcpomatic2_batch_debug.bat"', file=f)
         print('File "%resources%/dcpomatic2_kdm_debug.bat"', file=f)
         print('File "%resources%/dcpomatic2_player_debug.bat"', file=f)
         print('File "%resources%/dcpomatic2_batch_debug.bat"', file=f)
         print('File "%resources%/dcpomatic2_kdm_debug.bat"', file=f)
         print('File "%resources%/dcpomatic2_player_debug.bat"', file=f)
+        if disk:
+            print('File "%resources%/dcpomatic2_disk_debug.bat"', file=f)
         print('File "%mingw%/bin/gdb.exe"', file=f)
     else:
         print('File "%binaries%/src/wx/dcpomatic2-wx.dll"', file=f)
         print('File "%mingw%/bin/gdb.exe"', file=f)
     else:
         print('File "%binaries%/src/wx/dcpomatic2-wx.dll"', file=f)
@@ -338,6 +341,13 @@ File "%binaries%/src/tools/dcpomatic2_kdm.exe"
 File "%binaries%/src/tools/dcpomatic2_kdm_cli.exe"
     """, file=f)
 
 File "%binaries%/src/tools/dcpomatic2_kdm_cli.exe"
     """, file=f)
 
+    if disk:
+        print("""
+File "%binaries%/src/tools/dcpomatic2_disk.exe"
+File "%binaries%/src/tools/dcpomatic2_disk_writer.exe"
+File "%resources%/dcpomatic2_disk_writer.exe.manifest"
+    """, file=f)
+
     print("""
 File "%binaries%/src/tools/dcpomatic2_player.exe"
 File "%binaries%/src/tools/dcpomatic2_playlist.exe"
     print("""
 File "%binaries%/src/tools/dcpomatic2_player.exe"
 File "%binaries%/src/tools/dcpomatic2_playlist.exe"
@@ -348,6 +358,8 @@ File "%binaries%/src/tools/dcpomatic2_playlist.exe"
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\DCP-o-matic 2 debug.lnk" "$INSTDIR\\bin\\dcpomatic2_debug.bat"', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\DCP-o-matic 2 Batch Converter debug.lnk" "$INSTDIR\\bin\\dcpomatic2_batch_debug.bat" ""', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\DCP-o-matic 2 KDM Creator debug.lnk" "$INSTDIR\\bin\\dcpomatic2_kdm_debug.bat" ""', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\DCP-o-matic 2 debug.lnk" "$INSTDIR\\bin\\dcpomatic2_debug.bat"', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\DCP-o-matic 2 Batch Converter debug.lnk" "$INSTDIR\\bin\\dcpomatic2_batch_debug.bat" ""', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\DCP-o-matic 2 KDM Creator debug.lnk" "$INSTDIR\\bin\\dcpomatic2_kdm_debug.bat" ""', file=f)
+            if disk:
+                print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\DCP-o-matic 2 Disk Writer debug.lnk" "$INSTDIR\\bin\\dcpomatic2_disk_debug.bat" ""', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\Uninstall DCP-o-matic 2 debug.lnk" "$INSTDIR\\Uninstall.exe"', file=f)
         print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\DCP-o-matic 2 Player debug.lnk" "$INSTDIR\\bin\\dcpomatic2_player_debug.bat" ""', file=f)
         print('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic 2 debug" "DisplayName" "DCP-o-matic 2 debug (remove only)"', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\Uninstall DCP-o-matic 2 debug.lnk" "$INSTDIR\\Uninstall.exe"', file=f)
         print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2 debug\\DCP-o-matic 2 Player debug.lnk" "$INSTDIR\\bin\\dcpomatic2_player_debug.bat" ""', file=f)
         print('WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\DCP-o-matic 2 debug" "DisplayName" "DCP-o-matic 2 debug (remove only)"', file=f)
@@ -357,6 +369,8 @@ File "%binaries%/src/tools/dcpomatic2_playlist.exe"
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2.lnk" "$INSTDIR\\bin\\dcpomatic2.exe"', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2 Batch Converter.lnk" "$INSTDIR\\bin\\dcpomatic2_batch.exe"', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2 KDM Creator.lnk" "$INSTDIR\\bin\\dcpomatic2_kdm.exe"', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2.lnk" "$INSTDIR\\bin\\dcpomatic2.exe"', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2 Batch Converter.lnk" "$INSTDIR\\bin\\dcpomatic2_batch.exe"', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2 KDM Creator.lnk" "$INSTDIR\\bin\\dcpomatic2_kdm.exe"', file=f)
+            if disk:
+                print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2 Disk Writer.lnk" "$INSTDIR\\bin\\dcpomatic2_disk.exe"', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\Uninstall DCP-o-matic 2.lnk" "$INSTDIR\\Uninstall.exe"', file=f)
         print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2 Player.lnk" "$INSTDIR\\bin\\dcpomatic2_player.exe"', file=f)
         print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2 Playlist Editor.lnk" "$INSTDIR\\bin\\dcpomatic2_playlist.exe"', file=f)
             print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\Uninstall DCP-o-matic 2.lnk" "$INSTDIR\\Uninstall.exe"', file=f)
         print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2 Player.lnk" "$INSTDIR\\bin\\dcpomatic2_player.exe"', file=f)
         print('CreateShortCut "$SMPROGRAMS\\DCP-o-matic 2\\DCP-o-matic 2 Playlist Editor.lnk" "$INSTDIR\\bin\\dcpomatic2_playlist.exe"', file=f)
@@ -372,6 +386,8 @@ File "%binaries%/src/tools/dcpomatic2_playlist.exe"
             print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 debug.lnk" "$INSTDIR\\bin\\dcpomatic2_debug.bat" ""', file=f)
             print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 Batch Converter debug.lnk" "$INSTDIR\\bin\\dcpomatic2_batch_debug.bat" ""', file=f)
             print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 KDM Creator debug.lnk" "$INSTDIR\\bin\\dcpomatic2_kdm_debug.bat" ""', file=f)
             print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 debug.lnk" "$INSTDIR\\bin\\dcpomatic2_debug.bat" ""', file=f)
             print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 Batch Converter debug.lnk" "$INSTDIR\\bin\\dcpomatic2_batch_debug.bat" ""', file=f)
             print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 KDM Creator debug.lnk" "$INSTDIR\\bin\\dcpomatic2_kdm_debug.bat" ""', file=f)
+            if disk:
+                print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 Disk Writer debug.lnk" "$INSTDIR\\bin\\dcpomatic2_disk_debug.bat" ""', file=f)
     else:
         print('Section "DCP-o-matic 2 desktop shortcuts" SEC_MASTER_DESKTOP', file=f)
         print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 Player.lnk" "$INSTDIR\\bin\\dcpomatic2_player.exe"', file=f)
     else:
         print('Section "DCP-o-matic 2 desktop shortcuts" SEC_MASTER_DESKTOP', file=f)
         print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 Player.lnk" "$INSTDIR\\bin\\dcpomatic2_player.exe"', file=f)
@@ -379,6 +395,8 @@ File "%binaries%/src/tools/dcpomatic2_playlist.exe"
             print('CreateShortCut "$DESKTOP\\DCP-o-matic 2.lnk" "$INSTDIR\\bin\\dcpomatic2.exe" ""', file=f)
             print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 Batch Converter.lnk" "$INSTDIR\\bin\\dcpomatic2_batch.exe"', file=f)
             print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 KDM Creator.lnk" "$INSTDIR\\bin\\dcpomatic2_kdm.exe"', file=f)
             print('CreateShortCut "$DESKTOP\\DCP-o-matic 2.lnk" "$INSTDIR\\bin\\dcpomatic2.exe" ""', file=f)
             print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 Batch Converter.lnk" "$INSTDIR\\bin\\dcpomatic2_batch.exe"', file=f)
             print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 KDM Creator.lnk" "$INSTDIR\\bin\\dcpomatic2_kdm.exe"', file=f)
+            if disk:
+                print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 Disk Writer.lnk" "$INSTDIR\\bin\\dcpomatic2_disk.exe"', file=f)
 
     print("SectionEnd", file=f)
 
 
     print("SectionEnd", file=f)
 
@@ -396,7 +414,7 @@ SectionEnd
 
         print("""
 Section "Encode server desktop shortcuts" SEC_SERVER_DESKTOP
 
         print("""
 Section "Encode server desktop shortcuts" SEC_SERVER_DESKTOP
-CreateShortCut "$DESKTOP\\DCP-o-matic 2 encode server.lnk" "$INSTDIR\\bin\\dcpomatic2_server.exe" ""
+CreateShortCut "$DESKTOP\\DCP-o-matic 2 Encode Server.lnk" "$INSTDIR\\bin\\dcpomatic2_server.exe" ""
 SectionEnd
     """, file=f)
 
 SectionEnd
     """, file=f)
 
@@ -407,8 +425,8 @@ SectionEnd
         else:
             print('LangString DESC_SEC_MASTER ${LANG_ENGLISH} "DCP-o-matic 2"', file=f)
             print('LangString DESC_SEC_MASTER_DESKTOP ${LANG_ENGLISH} "DCP-o-matic 2 desktop shortcuts"', file=f)
         else:
             print('LangString DESC_SEC_MASTER ${LANG_ENGLISH} "DCP-o-matic 2"', file=f)
             print('LangString DESC_SEC_MASTER_DESKTOP ${LANG_ENGLISH} "DCP-o-matic 2 desktop shortcuts"', file=f)
-            print('LangString DESC_SEC_SERVER ${LANG_ENGLISH} "DCP-o-matic 2 encode server"', file=f)
-            print('LangString DESC_SEC_SERVER_DESKTOP ${LANG_ENGLISH} "DCP-o-matic 2 encode server desktop shortcuts"', file=f)
+            print('LangString DESC_SEC_SERVER ${LANG_ENGLISH} "DCP-o-matic 2 Encode Server"', file=f)
+            print('LangString DESC_SEC_SERVER_DESKTOP ${LANG_ENGLISH} "DCP-o-matic 2 Encode Server desktop shortcuts"', file=f)
 
     print("""
 !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
 
     print("""
 !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
@@ -436,6 +454,7 @@ RMDir "$INSTDIR"
 Delete "$DESKTOP\\DCP-o-matic 2 debug.lnk"
 Delete "$DESKTOP\\DCP-o-matic 2 Batch Converter debug.lnk"
 Delete "$DESKTOP\\DCP-o-matic 2 KDM Creator debug.lnk"
 Delete "$DESKTOP\\DCP-o-matic 2 debug.lnk"
 Delete "$DESKTOP\\DCP-o-matic 2 Batch Converter debug.lnk"
 Delete "$DESKTOP\\DCP-o-matic 2 KDM Creator debug.lnk"
+Delete "$DESKTOP\\DCP-o-matic 2 Disk Writer debug.lnk"
 Delete "$SMPROGRAMS\\DCP-o-matic 2 debug\\*.*"
 RmDir  "$SMPROGRAMS\\DCP-o-matic 2 debug"
 DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\\DCP-o-matic 2 debug"
 Delete "$SMPROGRAMS\\DCP-o-matic 2 debug\\*.*"
 RmDir  "$SMPROGRAMS\\DCP-o-matic 2 debug"
 DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\\DCP-o-matic 2 debug"
@@ -451,6 +470,7 @@ Delete "$DESKTOP\\DCP-o-matic 2.lnk"
 Delete "$DESKTOP\\DCP-o-matic 2 Batch Converter.lnk"
 Delete "$DESKTOP\\DCP-o-matic 2 Encode Server.lnk"
 Delete "$DESKTOP\\DCP-o-matic 2 KDM creator.lnk"
 Delete "$DESKTOP\\DCP-o-matic 2 Batch Converter.lnk"
 Delete "$DESKTOP\\DCP-o-matic 2 Encode Server.lnk"
 Delete "$DESKTOP\\DCP-o-matic 2 KDM creator.lnk"
+Delete "$DESKTOP\\DCP-o-matic 2 Disk Writer.lnk"
 Delete "$SMPROGRAMS\\DCP-o-matic 2\\*.*"
 RmDir  "$SMPROGRAMS\\DCP-o-matic 2"
 DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\\DCP-o-matic 2"
 Delete "$SMPROGRAMS\\DCP-o-matic 2\\*.*"
 RmDir  "$SMPROGRAMS\\DCP-o-matic 2"
 DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\\DCP-o-matic 2"
@@ -460,7 +480,7 @@ DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\U
 
 
 def build(bld):
 
 
 def build(bld):
-    write_installer(32, None, bld.env.VERSION, bld.env.DEBUG, bld.env.VARIANT)
-    write_installer(64, None, bld.env.VERSION, bld.env.DEBUG, bld.env.VARIANT)
-    write_installer(32, 'xp', bld.env.VERSION, bld.env.DEBUG, bld.env.VARIANT)
-    write_installer(64, 'xp', bld.env.VERSION, bld.env.DEBUG, bld.env.VARIANT)
+    write_installer(32, None, bld.env.VERSION, bld.env.DEBUG, bld.env.VARIANT, bld.env.ENABLE_DISK)
+    write_installer(64, None, bld.env.VERSION, bld.env.DEBUG, bld.env.VARIANT, bld.env.ENABLE_DISK)
+    write_installer(32, 'xp', bld.env.VERSION, bld.env.DEBUG, bld.env.VARIANT, bld.env.ENABLE_DISK)
+    write_installer(64, 'xp', bld.env.VERSION, bld.env.DEBUG, bld.env.VARIANT, bld.env.ENABLE_DISK)
diff --git a/run/dcpomatic_disk b/run/dcpomatic_disk
new file mode 100755 (executable)
index 0000000..9908004
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+export LD_LIBRARY_PATH=build/src/lib:build/src/wx:build/src/asdcplib/src:$LD_LIBRARY_PATH
+export DYLD_LIBRARY_PATH=build/src/lib:build/src/wx:build/src/asdcplib/src:/Users/carl/Environments/dcpomatic/64/lib
+if [ "$1" == "--debug" ]; then
+    shift
+    gdb --args build/src/tools/dcpomatic2_disk $*
+elif [ "$1" == "--valgrind" ]; then
+    shift
+    valgrind --tool="memcheck" --suppressions=suppressions --track-fds=yes --show-leak-kinds=all --leak-check=full build/src/tools/dcpomatic2_disk $*
+elif [ "$1" == "--callgrind" ]; then
+    shift
+    valgrind --tool="callgrind" build/src/tools/dcpomatic2_disk $*
+elif [ "$1" == "--massif" ]; then
+    shift
+    valgrind --tool="massif" build/src/tools/dcpomatic2_disk $*
+elif [ "$1" == "--i18n" ]; then
+    shift
+    LANGUAGE=de_DE.UTF8 LANG=de_DE.UTF8 LC_ALL=de_DE.UTF8 build/src/tools/dcpomatic2_disk "$*"
+elif [ "$1" == "--perf" ]; then
+    shift
+    perf record build/src/tools/dcpomatic2_disk $*
+elif [ "$1" == "--scaled" ]; then
+    shift
+    ~/src/run_scaled/run_scaled --sleep=5 --scale=0.5 build/src/tools/dcpomatic2_disk $*
+else
+    build/src/tools/dcpomatic2_disk $*
+fi
diff --git a/run/dcpomatic_disk_writer b/run/dcpomatic_disk_writer
new file mode 100755 (executable)
index 0000000..58164e0
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+export LD_LIBRARY_PATH=build/src/lib:build/src/wx:build/src/asdcplib/src:$LD_LIBRARY_PATH
+export DYLD_LIBRARY_PATH=build/src/lib:build/src/wx:build/src/asdcplib/src:/Users/carl/Environments/dcpomatic/64/lib
+exe=build/src/tools/dcpomatic2_disk_writer
+sudo chown root:root $exe
+sudo chmod 4755 $exe
+if [ "$1" == "--debug" ]; then
+    shift
+    gdb --args $exe $*
+elif [ "$1" == "--valgrind" ]; then
+    shift
+    valgrind --tool="memcheck" --suppressions=suppressions --track-fds=yes --show-leak-kinds=all --leak-check=full $exe $*
+elif [ "$1" == "--callgrind" ]; then
+    shift
+    valgrind --tool="callgrind" $exe $*
+elif [ "$1" == "--massif" ]; then
+    shift
+    valgrind --tool="massif" $exe $*
+elif [ "$1" == "--i18n" ]; then
+    shift
+    LANGUAGE=de_DE.UTF8 LANG=de_DE.UTF8 LC_ALL=de_DE.UTF8 $exe "$*"
+elif [ "$1" == "--perf" ]; then
+    shift
+    perf record $exe $*
+elif [ "$1" == "--scaled" ]; then
+    shift
+    ~/src/run_scaled/run_scaled --sleep=5 --scale=0.5 $exe $*
+else
+    $exe $*
+fi
diff --git a/src/lib/copy_to_drive_job.cc b/src/lib/copy_to_drive_job.cc
new file mode 100644 (file)
index 0000000..b7bb5a6
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+    Copyright (C) 2019-2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "disk_writer_messages.h"
+#include "copy_to_drive_job.h"
+#include "compose.hpp"
+#include "exceptions.h"
+#include <dcp/raw_convert.h>
+#include <nanomsg/nn.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <iostream>
+
+#include "i18n.h"
+
+using std::string;
+using std::cout;
+using std::min;
+using boost::shared_ptr;
+using dcp::raw_convert;
+
+CopyToDriveJob::CopyToDriveJob (boost::filesystem::path dcp, Drive drive, Nanomsg& nanomsg)
+       : Job (shared_ptr<Film>())
+       , _dcp (dcp)
+       , _drive (drive)
+       , _nanomsg (nanomsg)
+{
+
+}
+
+string
+CopyToDriveJob::name () const
+{
+       return String::compose (_("Copying %1 to %2"), _dcp.filename().string(), _drive.description());
+}
+
+string
+CopyToDriveJob::json_name () const
+{
+       return N_("copy");
+}
+
+void
+CopyToDriveJob::run ()
+{
+       if (!_nanomsg.nonblocking_send(String::compose("W\n%1\n%2\n", _dcp.string(), _drive.internal_name()))) {
+               throw CopyError ("Could not communicate with writer process", 0);
+       }
+
+       bool formatting = false;
+       while (true) {
+               string s = _nanomsg.blocking_get ();
+               if (s == DISK_WRITER_OK) {
+                       set_state (FINISHED_OK);
+                       return;
+               } else if (s == DISK_WRITER_ERROR) {
+                       string const m = _nanomsg.blocking_get ();
+                       string const n = _nanomsg.blocking_get ();
+                       throw CopyError (m, raw_convert<int>(n));
+               } else if (s == DISK_WRITER_FORMATTING) {
+                       sub ("Formatting drive");
+                       set_progress_unknown ();
+                       formatting = true;
+               } else if (s == DISK_WRITER_PROGRESS) {
+                       if (formatting) {
+                               sub ("Copying DCP");
+                               formatting = false;
+                       }
+                       set_progress (raw_convert<float>(_nanomsg.blocking_get()));
+               }
+       }
+}
diff --git a/src/lib/copy_to_drive_job.h b/src/lib/copy_to_drive_job.h
new file mode 100644 (file)
index 0000000..1a1a99f
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+    Copyright (C) 2019-2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "cross.h"
+#include "job.h"
+#include "nanomsg.h"
+
+class CopyToDriveJob : public Job
+{
+public:
+       CopyToDriveJob (boost::filesystem::path dcp, Drive drive, Nanomsg& nanomsg);
+
+       std::string name () const;
+       std::string json_name () const;
+       void run ();
+
+private:
+       void count (boost::filesystem::path dir, uint64_t& total_bytes);
+       void copy (boost::filesystem::path from, boost::filesystem::path to, uint64_t& total_remaining, uint64_t total);
+       boost::filesystem::path _dcp;
+       Drive _drive;
+       Nanomsg& _nanomsg;
+};
diff --git a/src/lib/cross.cc b/src/lib/cross.cc
deleted file mode 100644 (file)
index 5d35d5a..0000000
+++ /dev/null
@@ -1,538 +0,0 @@
-/*
-    Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
-
-    This file is part of DCP-o-matic.
-
-    DCP-o-matic is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    DCP-o-matic 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 General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "cross.h"
-#include "compose.hpp"
-#include "log.h"
-#include "dcpomatic_log.h"
-#include "config.h"
-#include "exceptions.h"
-extern "C" {
-#include <libavformat/avio.h>
-}
-#include <boost/algorithm/string.hpp>
-#ifdef DCPOMATIC_LINUX
-#include <unistd.h>
-#include <mntent.h>
-#endif
-#ifdef DCPOMATIC_WINDOWS
-#include <windows.h>
-#undef DATADIR
-#include <shlwapi.h>
-#include <shellapi.h>
-#include <fcntl.h>
-#endif
-#ifdef DCPOMATIC_OSX
-#include <sys/sysctl.h>
-#include <mach-o/dyld.h>
-#include <IOKit/pwr_mgt/IOPMLib.h>
-#endif
-#ifdef DCPOMATIC_POSIX
-#include <sys/types.h>
-#include <ifaddrs.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#endif
-#include <fstream>
-
-#include "i18n.h"
-
-using std::pair;
-using std::list;
-using std::ifstream;
-using std::string;
-using std::wstring;
-using std::make_pair;
-using std::runtime_error;
-using boost::shared_ptr;
-
-/** @param s Number of seconds to sleep for */
-void
-dcpomatic_sleep_seconds (int s)
-{
-#ifdef DCPOMATIC_POSIX
-       sleep (s);
-#endif
-#ifdef DCPOMATIC_WINDOWS
-       Sleep (s * 1000);
-#endif
-}
-
-void
-dcpomatic_sleep_milliseconds (int ms)
-{
-#ifdef DCPOMATIC_POSIX
-       usleep (ms * 1000);
-#endif
-#ifdef DCPOMATIC_WINDOWS
-       Sleep (ms);
-#endif
-}
-
-/** @return A string of CPU information (model name etc.) */
-string
-cpu_info ()
-{
-       string info;
-
-#ifdef DCPOMATIC_LINUX
-       /* This use of ifstream is ok; the filename can never
-          be non-Latin
-       */
-       ifstream f ("/proc/cpuinfo");
-       while (f.good ()) {
-               string l;
-               getline (f, l);
-               if (boost::algorithm::starts_with (l, "model name")) {
-                       string::size_type const c = l.find (':');
-                       if (c != string::npos) {
-                               info = l.substr (c + 2);
-                       }
-               }
-       }
-#endif
-
-#ifdef DCPOMATIC_OSX
-       char buffer[64];
-       size_t N = sizeof (buffer);
-       if (sysctlbyname ("machdep.cpu.brand_string", buffer, &N, 0, 0) == 0) {
-               info = buffer;
-       }
-#endif
-
-#ifdef DCPOMATIC_WINDOWS
-       HKEY key;
-       if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key) != ERROR_SUCCESS) {
-               return info;
-       }
-
-       DWORD type;
-       DWORD data;
-       if (RegQueryValueEx (key, L"ProcessorNameString", 0, &type, 0, &data) != ERROR_SUCCESS) {
-               return info;
-       }
-
-       if (type != REG_SZ) {
-               return info;
-       }
-
-       wstring value (data / sizeof (wchar_t), L'\0');
-       if (RegQueryValueEx (key, L"ProcessorNameString", 0, 0, reinterpret_cast<LPBYTE> (&value[0]), &data) != ERROR_SUCCESS) {
-               RegCloseKey (key);
-               return info;
-       }
-
-       info = string (value.begin(), value.end());
-
-       RegCloseKey (key);
-
-#endif
-
-       return info;
-}
-
-#ifdef DCPOMATIC_OSX
-/** @return Path of the Contents directory in the .app */
-boost::filesystem::path
-app_contents ()
-{
-       uint32_t size = 1024;
-       char buffer[size];
-       if (_NSGetExecutablePath (buffer, &size)) {
-               throw runtime_error ("_NSGetExecutablePath failed");
-       }
-
-       boost::filesystem::path path (buffer);
-       path = boost::filesystem::canonical (path);
-       path = path.parent_path ();
-       path = path.parent_path ();
-       return path;
-}
-#endif
-
-boost::filesystem::path
-shared_path ()
-{
-#ifdef DCPOMATIC_LINUX
-       char const * p = getenv ("DCPOMATIC_LINUX_SHARE_PREFIX");
-       if (p) {
-               return p;
-       }
-       return boost::filesystem::canonical (LINUX_SHARE_PREFIX);
-#endif
-#ifdef DCPOMATIC_WINDOWS
-       wchar_t dir[512];
-       GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
-       PathRemoveFileSpec (dir);
-       boost::filesystem::path path = dir;
-       return path.parent_path();
-#endif
-#ifdef DCPOMATIC_OSX
-       return app_contents() / "Resources";
-#endif
-}
-
-void
-run_ffprobe (boost::filesystem::path content, boost::filesystem::path out)
-{
-#ifdef DCPOMATIC_WINDOWS
-       SECURITY_ATTRIBUTES security;
-       security.nLength = sizeof (security);
-       security.bInheritHandle = TRUE;
-       security.lpSecurityDescriptor = 0;
-
-       HANDLE child_stderr_read;
-       HANDLE child_stderr_write;
-       if (!CreatePipe (&child_stderr_read, &child_stderr_write, &security, 0)) {
-               LOG_ERROR_NC ("ffprobe call failed (could not CreatePipe)");
-               return;
-       }
-
-       wchar_t dir[512];
-       GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
-       PathRemoveFileSpec (dir);
-       SetCurrentDirectory (dir);
-
-       STARTUPINFO startup_info;
-       ZeroMemory (&startup_info, sizeof (startup_info));
-       startup_info.cb = sizeof (startup_info);
-       startup_info.hStdError = child_stderr_write;
-       startup_info.dwFlags |= STARTF_USESTDHANDLES;
-
-       wchar_t command[512];
-       wcscpy (command, L"ffprobe.exe \"");
-
-       wchar_t file[512];
-       MultiByteToWideChar (CP_UTF8, 0, content.string().c_str(), -1, file, sizeof(file));
-       wcscat (command, file);
-
-       wcscat (command, L"\"");
-
-       PROCESS_INFORMATION process_info;
-       ZeroMemory (&process_info, sizeof (process_info));
-       if (!CreateProcess (0, command, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
-               LOG_ERROR_NC (N_("ffprobe call failed (could not CreateProcess)"));
-               return;
-       }
-
-       FILE* o = fopen_boost (out, "w");
-       if (!o) {
-               LOG_ERROR_NC (N_("ffprobe call failed (could not create output file)"));
-               return;
-       }
-
-       CloseHandle (child_stderr_write);
-
-       while (true) {
-               char buffer[512];
-               DWORD read;
-               if (!ReadFile(child_stderr_read, buffer, sizeof(buffer), &read, 0) || read == 0) {
-                       break;
-               }
-               fwrite (buffer, read, 1, o);
-       }
-
-       fclose (o);
-
-       WaitForSingleObject (process_info.hProcess, INFINITE);
-       CloseHandle (process_info.hProcess);
-       CloseHandle (process_info.hThread);
-       CloseHandle (child_stderr_read);
-#endif
-
-#ifdef DCPOMATIC_LINUX
-       string ffprobe = "ffprobe \"" + content.string() + "\" 2> \"" + out.string() + "\"";
-       LOG_GENERAL (N_("Probing with %1"), ffprobe);
-        system (ffprobe.c_str ());
-#endif
-
-#ifdef DCPOMATIC_OSX
-       boost::filesystem::path path = app_contents();
-       path /= "MacOS";
-       path /= "ffprobe";
-
-       string ffprobe = "\"" + path.string() + "\" \"" + content.string() + "\" 2> \"" + out.string() + "\"";
-       LOG_GENERAL (N_("Probing with %1"), ffprobe);
-       system (ffprobe.c_str ());
-#endif
-}
-
-list<pair<string, string> >
-mount_info ()
-{
-       list<pair<string, string> > m;
-
-#ifdef DCPOMATIC_LINUX
-       FILE* f = setmntent ("/etc/mtab", "r");
-       if (!f) {
-               return m;
-       }
-
-       while (true) {
-               struct mntent* mnt = getmntent (f);
-               if (!mnt) {
-                       break;
-               }
-
-               m.push_back (make_pair (mnt->mnt_dir, mnt->mnt_type));
-       }
-
-       endmntent (f);
-#endif
-
-       return m;
-}
-
-boost::filesystem::path
-openssl_path ()
-{
-#ifdef DCPOMATIC_WINDOWS
-       wchar_t dir[512];
-       GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
-       PathRemoveFileSpec (dir);
-
-       boost::filesystem::path path = dir;
-       path /= "openssl.exe";
-       return path;
-#endif
-
-#ifdef DCPOMATIC_OSX
-       boost::filesystem::path path = app_contents();
-       path /= "MacOS";
-       path /= "openssl";
-       return path;
-#endif
-
-#ifdef DCPOMATIC_LINUX
-       return "dcpomatic2_openssl";
-#endif
-
-}
-
-/* Apparently there is no way to create an ofstream using a UTF-8
-   filename under Windows.  We are hence reduced to using fopen
-   with this wrapper.
-*/
-FILE *
-fopen_boost (boost::filesystem::path p, string t)
-{
-#ifdef DCPOMATIC_WINDOWS
-        wstring w (t.begin(), t.end());
-       /* c_str() here should give a UTF-16 string */
-        return _wfopen (p.c_str(), w.c_str ());
-#else
-        return fopen (p.c_str(), t.c_str ());
-#endif
-}
-
-int
-dcpomatic_fseek (FILE* stream, int64_t offset, int whence)
-{
-#ifdef DCPOMATIC_WINDOWS
-       return _fseeki64 (stream, offset, whence);
-#else
-       return fseek (stream, offset, whence);
-#endif
-}
-
-void
-Waker::nudge ()
-{
-#ifdef DCPOMATIC_WINDOWS
-       boost::mutex::scoped_lock lm (_mutex);
-       SetThreadExecutionState (ES_SYSTEM_REQUIRED);
-#endif
-}
-
-Waker::Waker ()
-{
-#ifdef DCPOMATIC_OSX
-       boost::mutex::scoped_lock lm (_mutex);
-       /* We should use this */
-        // IOPMAssertionCreateWithName (kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR ("Encoding DCP"), &_assertion_id);
-       /* but it's not available on 10.5, so we use this */
-        IOPMAssertionCreate (kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &_assertion_id);
-#endif
-}
-
-Waker::~Waker ()
-{
-#ifdef DCPOMATIC_OSX
-       boost::mutex::scoped_lock lm (_mutex);
-       IOPMAssertionRelease (_assertion_id);
-#endif
-}
-
-void
-start_tool (boost::filesystem::path dcpomatic, string executable,
-#ifdef DCPOMATIC_OSX
-           string app
-#else
-           string
-#endif
-       )
-{
-#if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_WINDOWS)
-       boost::filesystem::path batch = dcpomatic.parent_path() / executable;
-#endif
-
-#ifdef DCPOMATIC_OSX
-       boost::filesystem::path batch = dcpomatic.parent_path ();
-       batch = batch.parent_path (); // MacOS
-       batch = batch.parent_path (); // Contents
-       batch = batch.parent_path (); // DCP-o-matic.app
-       batch = batch.parent_path (); // Applications
-       batch /= app;
-       batch /= "Contents";
-       batch /= "MacOS";
-       batch /= executable;
-#endif
-
-#if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_OSX)
-       pid_t pid = fork ();
-       if (pid == 0) {
-               int const r = system (batch.string().c_str());
-               exit (WEXITSTATUS (r));
-       }
-#endif
-
-#ifdef DCPOMATIC_WINDOWS
-       STARTUPINFO startup_info;
-       ZeroMemory (&startup_info, sizeof (startup_info));
-       startup_info.cb = sizeof (startup_info);
-
-       PROCESS_INFORMATION process_info;
-       ZeroMemory (&process_info, sizeof (process_info));
-
-       wchar_t cmd[512];
-       MultiByteToWideChar (CP_UTF8, 0, batch.string().c_str(), -1, cmd, sizeof(cmd));
-       CreateProcess (0, cmd, 0, 0, FALSE, 0, 0, 0, &startup_info, &process_info);
-#endif
-}
-
-void
-start_batch_converter (boost::filesystem::path dcpomatic)
-{
-       start_tool (dcpomatic, "dcpomatic2_batch", "DCP-o-matic\\ 2\\ Batch\\ Converter.app");
-}
-
-void
-start_player (boost::filesystem::path dcpomatic)
-{
-       start_tool (dcpomatic, "dcpomatic2_player", "DCP-o-matic\\ 2\\ Player.app");
-}
-
-uint64_t
-thread_id ()
-{
-#ifdef DCPOMATIC_WINDOWS
-       return (uint64_t) GetCurrentThreadId ();
-#else
-       return (uint64_t) pthread_self ();
-#endif
-}
-
-int
-avio_open_boost (AVIOContext** s, boost::filesystem::path file, int flags)
-{
-#ifdef DCPOMATIC_WINDOWS
-       int const length = (file.string().length() + 1) * 2;
-       char* utf8 = new char[length];
-       WideCharToMultiByte (CP_UTF8, 0, file.c_str(), -1, utf8, length, 0, 0);
-       int const r = avio_open (s, utf8, flags);
-       delete[] utf8;
-       return r;
-#else
-       return avio_open (s, file.c_str(), flags);
-#endif
-}
-
-#ifdef DCPOMATIC_WINDOWS
-void
-maybe_open_console ()
-{
-       if (Config::instance()->win32_console ()) {
-               AllocConsole();
-
-               HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
-               int hCrt = _open_osfhandle((intptr_t) handle_out, _O_TEXT);
-               FILE* hf_out = _fdopen(hCrt, "w");
-               setvbuf(hf_out, NULL, _IONBF, 1);
-               *stdout = *hf_out;
-
-               HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE);
-               hCrt = _open_osfhandle((intptr_t) handle_in, _O_TEXT);
-               FILE* hf_in = _fdopen(hCrt, "r");
-               setvbuf(hf_in, NULL, _IONBF, 128);
-               *stdin = *hf_in;
-       }
-}
-#endif
-
-boost::filesystem::path
-home_directory ()
-{
-#if defined(DCPOMATIC_LINUX) || defined(DCPOMATIC_OSX)
-               return getenv("HOME");
-#endif
-#ifdef DCPOMATIC_WINDOWS
-               return boost::filesystem::path(getenv("HOMEDRIVE")) / boost::filesystem::path(getenv("HOMEPATH"));
-#endif
-}
-
-string
-command_and_read (string cmd)
-{
-#ifdef DCPOMATIC_LINUX
-       FILE* pipe = popen (cmd.c_str(), "r");
-       if (!pipe) {
-               throw runtime_error ("popen failed");
-       }
-
-       string result;
-       char buffer[128];
-       try {
-               while (fgets(buffer, sizeof(buffer), pipe)) {
-                       result += buffer;
-               }
-       } catch (...) {
-               pclose (pipe);
-               throw;
-       }
-
-       pclose (pipe);
-       return result;
-#endif
-
-       return "";
-}
-
-/** @return true if this process is a 32-bit one running on a 64-bit-capable OS */
-bool
-running_32_on_64 ()
-{
-#ifdef DCPOMATIC_WINDOWS
-       BOOL p;
-       IsWow64Process (GetCurrentProcess(), &p);
-       return p;
-#endif
-       /* XXX: assuming nobody does this on Linux / OS X */
-       return false;
-}
index 584fa2b426a7f2fab9460205edbd47cd2395bf8f..20bab38a29e7296f8a52130cfd204aa56a417efd 100644 (file)
@@ -1,5 +1,5 @@
 /*
 /*
-    Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 
     This file is part of DCP-o-matic.
 
@@ -30,6 +30,7 @@
 #endif
 #include <boost/filesystem.hpp>
 #include <boost/thread/mutex.hpp>
 #endif
 #include <boost/filesystem.hpp>
 #include <boost/thread/mutex.hpp>
+#include <boost/optional.hpp>
 
 #ifdef DCPOMATIC_WINDOWS
 #define WEXITSTATUS(w) (w)
 
 #ifdef DCPOMATIC_WINDOWS
 #define WEXITSTATUS(w) (w)
@@ -44,6 +45,7 @@ extern std::string cpu_info ();
 extern void run_ffprobe (boost::filesystem::path, boost::filesystem::path);
 extern std::list<std::pair<std::string, std::string> > mount_info ();
 extern boost::filesystem::path openssl_path ();
 extern void run_ffprobe (boost::filesystem::path, boost::filesystem::path);
 extern std::list<std::pair<std::string, std::string> > mount_info ();
 extern boost::filesystem::path openssl_path ();
+extern boost::filesystem::path disk_writer_path ();
 #ifdef DCPOMATIC_OSX
 extern boost::filesystem::path app_contents ();
 #endif
 #ifdef DCPOMATIC_OSX
 extern boost::filesystem::path app_contents ();
 #endif
@@ -60,6 +62,15 @@ extern int avio_open_boost (AVIOContext** s, boost::filesystem::path file, int f
 extern boost::filesystem::path home_directory ();
 extern std::string command_and_read (std::string cmd);
 extern bool running_32_on_64 ();
 extern boost::filesystem::path home_directory ();
 extern std::string command_and_read (std::string cmd);
 extern bool running_32_on_64 ();
+extern void unprivileged ();
+extern boost::filesystem::path config_path ();
+
+class PrivilegeEscalator
+{
+public:
+       PrivilegeEscalator ();
+       ~PrivilegeEscalator ();
+};
 
 /** @class Waker
  *  @brief A class which tries to keep the computer awake on various operating systems.
 
 /** @class Waker
  *  @brief A class which tries to keep the computer awake on various operating systems.
@@ -82,4 +93,34 @@ private:
 #endif
 };
 
 #endif
 };
 
+class Drive
+{
+public:
+       Drive (std::string internal_name, uint64_t size, bool mounted, boost::optional<std::string> vendor, boost::optional<std::string> model)
+               : _internal_name(internal_name)
+               , _size(size)
+               , _mounted(mounted)
+               , _vendor(vendor)
+               , _model(model)
+       {}
+
+       std::string description () const;
+       std::string internal_name () const {
+               return _internal_name;
+       }
+       bool mounted () const {
+               return _mounted;
+       }
+
+private:
+       std::string _internal_name;
+       /** size in bytes */
+       uint64_t _size;
+       bool _mounted;
+       boost::optional<std::string> _vendor;
+       boost::optional<std::string> _model;
+};
+
+std::vector<Drive> get_drives ();
+
 #endif
 #endif
diff --git a/src/lib/cross_common.cc b/src/lib/cross_common.cc
new file mode 100644 (file)
index 0000000..b3a3940
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+    Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "cross.h"
+#include "compose.hpp"
+
+#include "i18n.h"
+
+using std::string;
+
+string
+Drive::description () const
+{
+       char gb[64];
+       snprintf(gb, 64, "%.1f", _size / 1000000000.0);
+
+       string name;
+       if (_vendor) {
+               name += *_vendor;
+       }
+       if (_model) {
+               if (name.size() > 0) {
+                       name += " " + *_model;
+               } else {
+                       name = *_model;
+               }
+       }
+       if (name.size() == 0) {
+               name = _("Unknown");
+       }
+
+       return String::compose("%1 (%2 GB) [%3]", name, gb, _internal_name);
+}
+
diff --git a/src/lib/cross_linux.cc b/src/lib/cross_linux.cc
new file mode 100644 (file)
index 0000000..4060879
--- /dev/null
@@ -0,0 +1,340 @@
+/*
+    Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "cross.h"
+#include "compose.hpp"
+#include "log.h"
+#include "dcpomatic_log.h"
+#include "config.h"
+#include "exceptions.h"
+#include <dcp/raw_convert.h>
+#include <glib.h>
+extern "C" {
+#include <libavformat/avio.h>
+}
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/dll/runtime_symbol_info.hpp>
+#include <unistd.h>
+#include <mntent.h>
+#include <sys/types.h>
+#include <ifaddrs.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fstream>
+
+#include "i18n.h"
+
+using std::pair;
+using std::list;
+using std::ifstream;
+using std::string;
+using std::wstring;
+using std::make_pair;
+using std::vector;
+using std::cerr;
+using std::cout;
+using std::runtime_error;
+using boost::shared_ptr;
+using boost::optional;
+
+/** @param s Number of seconds to sleep for */
+void
+dcpomatic_sleep_seconds (int s)
+{
+       sleep (s);
+}
+
+void
+dcpomatic_sleep_milliseconds (int ms)
+{
+       usleep (ms * 1000);
+}
+
+/** @return A string of CPU information (model name etc.) */
+string
+cpu_info ()
+{
+       string info;
+
+       /* This use of ifstream is ok; the filename can never
+          be non-Latin
+       */
+       ifstream f ("/proc/cpuinfo");
+       while (f.good ()) {
+               string l;
+               getline (f, l);
+               if (boost::algorithm::starts_with (l, "model name")) {
+                       string::size_type const c = l.find (':');
+                       if (c != string::npos) {
+                               info = l.substr (c + 2);
+                       }
+               }
+       }
+
+       return info;
+}
+
+boost::filesystem::path
+shared_path ()
+{
+       char const * p = getenv ("DCPOMATIC_LINUX_SHARE_PREFIX");
+       if (p) {
+               return p;
+       }
+       return boost::filesystem::canonical (LINUX_SHARE_PREFIX);
+}
+
+void
+run_ffprobe (boost::filesystem::path content, boost::filesystem::path out)
+{
+       string ffprobe = "ffprobe \"" + content.string() + "\" 2> \"" + out.string() + "\"";
+       LOG_GENERAL (N_("Probing with %1"), ffprobe);
+        system (ffprobe.c_str ());
+}
+
+list<pair<string, string> >
+mount_info ()
+{
+       list<pair<string, string> > m;
+
+       FILE* f = setmntent ("/etc/mtab", "r");
+       if (!f) {
+               return m;
+       }
+
+       while (true) {
+               struct mntent* mnt = getmntent (f);
+               if (!mnt) {
+                       break;
+               }
+
+               m.push_back (make_pair (mnt->mnt_dir, mnt->mnt_type));
+       }
+
+       endmntent (f);
+
+       return m;
+}
+
+boost::filesystem::path
+openssl_path ()
+{
+       return "dcpomatic2_openssl";
+}
+
+boost::filesystem::path
+disk_writer_path ()
+{
+       return boost::dll::program_location().parent_path() / "dcpomatic2_disk_writer";
+}
+
+/* Apparently there is no way to create an ofstream using a UTF-8
+   filename under Windows.  We are hence reduced to using fopen
+   with this wrapper.
+*/
+FILE *
+fopen_boost (boost::filesystem::path p, string t)
+{
+        return fopen (p.c_str(), t.c_str ());
+}
+
+int
+dcpomatic_fseek (FILE* stream, int64_t offset, int whence)
+{
+       return fseek (stream, offset, whence);
+}
+
+void
+Waker::nudge ()
+{
+
+}
+
+Waker::Waker ()
+{
+
+}
+
+Waker::~Waker ()
+{
+
+}
+
+void
+start_tool (boost::filesystem::path dcpomatic, string executable, string)
+{
+       boost::filesystem::path batch = dcpomatic.parent_path() / executable;
+
+       pid_t pid = fork ();
+       if (pid == 0) {
+               int const r = system (batch.string().c_str());
+               exit (WEXITSTATUS (r));
+       }
+}
+
+void
+start_batch_converter (boost::filesystem::path dcpomatic)
+{
+       start_tool (dcpomatic, "dcpomatic2_batch", "DCP-o-matic\\ 2\\ Batch\\ Converter.app");
+}
+
+void
+start_player (boost::filesystem::path dcpomatic)
+{
+       start_tool (dcpomatic, "dcpomatic2_player", "DCP-o-matic\\ 2\\ Player.app");
+}
+
+uint64_t
+thread_id ()
+{
+       return (uint64_t) pthread_self ();
+}
+
+int
+avio_open_boost (AVIOContext** s, boost::filesystem::path file, int flags)
+{
+       return avio_open (s, file.c_str(), flags);
+}
+
+
+boost::filesystem::path
+home_directory ()
+{
+               return getenv("HOME");
+}
+
+string
+command_and_read (string cmd)
+{
+       FILE* pipe = popen (cmd.c_str(), "r");
+       if (!pipe) {
+               throw runtime_error ("popen failed");
+       }
+
+       string result;
+       char buffer[128];
+       try {
+               while (fgets(buffer, sizeof(buffer), pipe)) {
+                       result += buffer;
+               }
+       } catch (...) {
+               pclose (pipe);
+               throw;
+       }
+
+       pclose (pipe);
+       return result;
+}
+
+/** @return true if this process is a 32-bit one running on a 64-bit-capable OS */
+bool
+running_32_on_64 ()
+{
+       /* I'm assuming nobody does this on Linux */
+       return false;
+}
+
+vector<Drive>
+get_drives ()
+{
+       vector<Drive> drives;
+
+       using namespace boost::filesystem;
+       list<string> mounted_devices;
+       std::ifstream f("/proc/mounts");
+       string line;
+       while (f.good()) {
+               getline(f, line);
+               vector<string> bits;
+               boost::algorithm::split (bits, line, boost::is_any_of(" "));
+               if (bits.size() > 0 && boost::algorithm::starts_with(bits[0], "/dev/")) {
+                       mounted_devices.push_back(bits[0]);
+                       LOG_DISK("Mounted device %1", bits[0]);
+               }
+       }
+
+       for (directory_iterator i = directory_iterator("/sys/block"); i != directory_iterator(); ++i) {
+               string const name = i->path().filename().string();
+               path device_type_file("/sys/block/" + name + "/device/type");
+               optional<string> device_type;
+               if (exists(device_type_file)) {
+                       device_type = dcp::file_to_string (device_type_file);
+                       boost::trim(*device_type);
+               }
+               /* Device type 5 is "SCSI_TYPE_ROM" in blkdev.h; seems usually to be a CD/DVD drive */
+               if (!boost::algorithm::starts_with(name, "loop") && (!device_type || *device_type != "5")) {
+                       uint64_t const size = dcp::raw_convert<uint64_t>(dcp::file_to_string(*i / "size")) * 512;
+                       if (size == 0) {
+                               continue;
+                       }
+                       bool mounted = false;
+                       optional<string> vendor;
+                       try {
+                               vendor = dcp::file_to_string("/sys/block/" + name + "/device/vendor");
+                               boost::trim(*vendor);
+                       } catch (...) {}
+                       optional<string> model;
+                       try {
+                               model = dcp::file_to_string("/sys/block/" + name + "/device/model");
+                               boost::trim(*model);
+                       } catch (...) {}
+                       BOOST_FOREACH (string j, mounted_devices) {
+                               if (boost::algorithm::starts_with(j, "/dev/" + name)) {
+                                       mounted = true;
+                               }
+                       }
+                       drives.push_back(Drive("/dev/" + i->path().filename().string(), size, mounted, vendor, model));
+                       LOG_DISK("Block device %1 size %2 %3 vendor %4 model %5", name, size, mounted ? "mounted" : "not mounted", vendor.get_value_or("[none]"), model.get_value_or("[none]"));
+               }
+       }
+
+       return drives;
+}
+
+void
+unprivileged ()
+{
+       uid_t ruid, euid, suid;
+       if (getresuid(&ruid, &euid, &suid) == -1) {
+               cerr << "getresuid() failed.\n";
+               exit (EXIT_FAILURE);
+       }
+       seteuid (ruid);
+}
+
+PrivilegeEscalator::~PrivilegeEscalator ()
+{
+       unprivileged ();
+}
+
+PrivilegeEscalator::PrivilegeEscalator ()
+{
+       seteuid (0);
+}
+
+boost::filesystem::path
+config_path ()
+{
+       boost::filesystem::path p;
+       p /= g_get_user_config_dir ();
+       p /= "dcpomatic2";
+       return p;
+}
diff --git a/src/lib/cross_osx.cc b/src/lib/cross_osx.cc
new file mode 100644 (file)
index 0000000..fa12fb3
--- /dev/null
@@ -0,0 +1,472 @@
+/*
+    Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "cross.h"
+#include "compose.hpp"
+#include "log.h"
+#include "dcpomatic_log.h"
+#include "config.h"
+#include "exceptions.h"
+#include <dcp/raw_convert.h>
+#include <glib.h>
+extern "C" {
+#include <libavformat/avio.h>
+}
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/dll/runtime_symbol_info.hpp>
+#include <boost/regex.hpp>
+#include <sys/sysctl.h>
+#include <mach-o/dyld.h>
+#include <IOKit/pwr_mgt/IOPMLib.h>
+#include <IOKit/storage/IOMedia.h>
+#include <DiskArbitration/DADisk.h>
+#include <DiskArbitration/DiskArbitration.h>
+#include <CoreFoundation/CFURL.h>
+#include <sys/types.h>
+#include <ifaddrs.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fstream>
+#include <cstring>
+
+#include "i18n.h"
+
+using std::pair;
+using std::list;
+using std::ifstream;
+using std::string;
+using std::wstring;
+using std::make_pair;
+using std::vector;
+using std::cerr;
+using std::cout;
+using std::runtime_error;
+using boost::shared_ptr;
+using boost::optional;
+
+/** @param s Number of seconds to sleep for */
+void
+dcpomatic_sleep_seconds (int s)
+{
+       sleep (s);
+}
+
+void
+dcpomatic_sleep_milliseconds (int ms)
+{
+       usleep (ms * 1000);
+}
+
+/** @return A string of CPU information (model name etc.) */
+string
+cpu_info ()
+{
+       string info;
+
+       char buffer[64];
+       size_t N = sizeof (buffer);
+       if (sysctlbyname ("machdep.cpu.brand_string", buffer, &N, 0, 0) == 0) {
+               info = buffer;
+       }
+
+       return info;
+}
+
+/** @return Path of the Contents directory in the .app */
+boost::filesystem::path
+app_contents ()
+{
+       return boost::dll::program_location().parent_path().parent_path();
+}
+
+boost::filesystem::path
+shared_path ()
+{
+       return app_contents() / "Resources";
+}
+
+void
+run_ffprobe (boost::filesystem::path content, boost::filesystem::path out)
+{
+       boost::filesystem::path path = app_contents();
+       path /= "MacOS";
+       path /= "ffprobe";
+
+       string ffprobe = "\"" + path.string() + "\" \"" + content.string() + "\" 2> \"" + out.string() + "\"";
+       LOG_GENERAL (N_("Probing with %1"), ffprobe);
+       system (ffprobe.c_str ());
+}
+
+list<pair<string, string> >
+mount_info ()
+{
+       list<pair<string, string> > m;
+       return m;
+}
+
+boost::filesystem::path
+openssl_path ()
+{
+       boost::filesystem::path path = app_contents();
+       path /= "MacOS";
+       path /= "openssl";
+       return path;
+}
+
+boost::filesystem::path
+disk_writer_path ()
+{
+       boost::filesystem::path path = app_contents();
+       path /= "MacOS";
+       path /= "dcpomatic2_disk_writer";
+       return path;
+}
+
+/* Apparently there is no way to create an ofstream using a UTF-8
+   filename under Windows.  We are hence reduced to using fopen
+   with this wrapper.
+*/
+FILE *
+fopen_boost (boost::filesystem::path p, string t)
+{
+        return fopen (p.c_str(), t.c_str ());
+}
+
+int
+dcpomatic_fseek (FILE* stream, int64_t offset, int whence)
+{
+       return fseek (stream, offset, whence);
+}
+
+void
+Waker::nudge ()
+{
+
+}
+
+Waker::Waker ()
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       /* We should use this */
+        // IOPMAssertionCreateWithName (kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, CFSTR ("Encoding DCP"), &_assertion_id);
+       /* but it's not available on 10.5, so we use this */
+        IOPMAssertionCreate (kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, &_assertion_id);
+}
+
+Waker::~Waker ()
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       IOPMAssertionRelease (_assertion_id);
+}
+
+void
+start_tool (boost::filesystem::path dcpomatic, string executable, string app)
+{
+       boost::filesystem::path batch = dcpomatic.parent_path ();
+       batch = batch.parent_path (); // MacOS
+       batch = batch.parent_path (); // Contents
+       batch = batch.parent_path (); // DCP-o-matic.app
+       batch = batch.parent_path (); // Applications
+       batch /= app;
+       batch /= "Contents";
+       batch /= "MacOS";
+       batch /= executable;
+
+       pid_t pid = fork ();
+       if (pid == 0) {
+               int const r = system (batch.string().c_str());
+               exit (WEXITSTATUS (r));
+       }
+}
+
+void
+start_batch_converter (boost::filesystem::path dcpomatic)
+{
+       start_tool (dcpomatic, "dcpomatic2_batch", "DCP-o-matic\\ 2\\ Batch\\ Converter.app");
+}
+
+void
+start_player (boost::filesystem::path dcpomatic)
+{
+       start_tool (dcpomatic, "dcpomatic2_player", "DCP-o-matic\\ 2\\ Player.app");
+}
+
+uint64_t
+thread_id ()
+{
+       return (uint64_t) pthread_self ();
+}
+
+int
+avio_open_boost (AVIOContext** s, boost::filesystem::path file, int flags)
+{
+       return avio_open (s, file.c_str(), flags);
+}
+
+boost::filesystem::path
+home_directory ()
+{
+               return getenv("HOME");
+}
+
+string
+command_and_read (string cmd)
+{
+       return "";
+}
+
+/** @return true if this process is a 32-bit one running on a 64-bit-capable OS */
+bool
+running_32_on_64 ()
+{
+       /* I'm assuming nobody does this on OS X */
+       return false;
+}
+
+static optional<string>
+get_vendor (CFDictionaryRef& description)
+{
+       void const* str = CFDictionaryGetValue (description, kDADiskDescriptionDeviceVendorKey);
+       if (!str) {
+               return optional<string>();
+       }
+
+       string s = CFStringGetCStringPtr ((CFStringRef) str, kCFStringEncodingUTF8);
+       boost::algorithm::trim (s);
+       return s;
+}
+
+static optional<string>
+get_model (CFDictionaryRef& description)
+{
+       void const* str = CFDictionaryGetValue (description, kDADiskDescriptionDeviceModelKey);
+       if (!str) {
+               return optional<string>();
+       }
+
+       string s = CFStringGetCStringPtr ((CFStringRef) str, kCFStringEncodingUTF8);
+       boost::algorithm::trim (s);
+       return s;
+}
+
+struct MediaPath
+{
+       bool real;       ///< true for a "real" disk, false for a synthesized APFS one
+       std::string prt; ///< "PRT" entry from the media path
+};
+
+static optional<MediaPath>
+analyse_media_path (CFDictionaryRef& description)
+{
+       using namespace boost::algorithm;
+
+       void const* str = CFDictionaryGetValue (description, kDADiskDescriptionMediaPathKey);
+       if (!str) {
+               return optional<MediaPath>();
+       }
+
+       string path(CFStringGetCStringPtr((CFStringRef) str, kCFStringEncodingUTF8));
+       MediaPath mp;
+       if (starts_with(path, "IODeviceTree:")) {
+               mp.real = true;
+       } else if (starts_with(path, "IOService:")) {
+               mp.real = false;
+       } else {
+               return optional<MediaPath>();
+       }
+
+       vector<string> bits;
+       split(bits, path, boost::is_any_of("/"));
+       BOOST_FOREACH (string i, bits) {
+               if (starts_with(i, "PRT")) {
+                       mp.prt = i;
+               }
+       }
+
+       return mp;
+}
+
+static bool
+is_whole_drive (DADiskRef& disk)
+{
+       io_service_t service = DADiskCopyIOMedia (disk);
+        CFTypeRef whole_media_ref = IORegistryEntryCreateCFProperty (service, CFSTR(kIOMediaWholeKey), kCFAllocatorDefault, 0);
+       bool whole_media = false;
+        if (whole_media_ref) {
+               whole_media = CFBooleanGetValue((CFBooleanRef) whole_media_ref);
+                CFRelease (whole_media_ref);
+        }
+       IOObjectRelease (service);
+       return whole_media;
+}
+
+static bool
+is_mounted (CFDictionaryRef& description)
+{
+       CFURLRef volume_path_key = (CFURLRef) CFDictionaryGetValue (description, kDADiskDescriptionVolumePathKey);
+       char mount_path_buffer[1024];
+       return CFURLGetFileSystemRepresentation(volume_path_key, false, (UInt8 *) mount_path_buffer, sizeof(mount_path_buffer));
+}
+
+/* Here follows some rather intricate and (probably) fragile code to find the list of available
+ * "real" drives on macOS that we might want to write a DCP to.
+ *
+ * We use the Disk Arbitration framework to give us a series of devices (/dev/disk0, /dev/disk1,
+ * /dev/disk1s1 and so on) and we use the API to gather useful information about these devices into
+ * a vector of Disk structs.
+ *
+ * Then we read the Disks that we found and try to derive a list of drives that we should offer to the
+ * user, with details of whether those drives are currently mounted or not.
+ *
+ * At the basic level we find the "disk"-level devices, looking at whether any of their partitions are mounted.
+ *
+ * This is complicated enormously by recent-ish macOS versions' habit of making `synthesized' volumes which
+ * reflect data in `real' partitions.  So, for example, we might have a real (physical) drive /dev/disk2 with
+ * a partition /dev/disk2s2 whose content is made into a synthesized /dev/disk3, itself containing some partitions
+ * which are mounted.  /dev/disk2s2 is not considered to be mounted, in this case.  So we need to know that
+ * disk2s2 is related to disk3 so we can consider disk2s2 as mounted if any parts of disk3 are.  In order to do
+ * this I am picking out what looks like a suitable identifier prefixed with PRT from the MediaContentKey.
+ * If disk2s2 and disk3 have the same PRT code I am assuming they are linked.
+ *
+ * Lots of this is guesswork and may be broken.  In my defence the documentation that I have been able to
+ * unearth is, to put it impolitely, crap.
+ */
+
+struct Disk
+{
+       string device;
+       optional<string> vendor;
+       optional<string> model;
+       bool real;
+       string prt;
+       bool whole;
+       bool mounted;
+       unsigned long size;
+};
+
+static void
+disk_appeared (DADiskRef disk, void* context)
+{
+       const char* bsd_name = DADiskGetBSDName (disk);
+       if (!bsd_name) {
+               return;
+       }
+       LOG_DISK("%1 appeared", bsd_name);
+
+       Disk this_disk;
+
+       this_disk.device = string("/dev/") + bsd_name;
+
+       CFDictionaryRef description = DADiskCopyDescription (disk);
+
+       this_disk.vendor = get_vendor (description);
+       this_disk.model = get_model (description);
+       LOG_DISK("Vendor/model: %1 %2", this_disk.vendor.get_value_or("[none]"), this_disk.model.get_value_or("[none]"));
+
+       optional<MediaPath> media_path = analyse_media_path (description);
+       if (!media_path) {
+               LOG_DISK("Finding media path for %1 failed", bsd_name);
+               return;
+       }
+
+       this_disk.real = media_path->real;
+       this_disk.prt = media_path->prt;
+       this_disk.whole = is_whole_drive (disk);
+       this_disk.mounted = is_mounted (description);
+       LOG_DISK("%1 prt %2 whole %3 mounted %4", this_disk.real ? "Real" : "Synth", this_disk.prt, this_disk.whole ? "whole" : "part", this_disk.mounted ? "mounted" : "unmounted");
+
+       CFNumberGetValue ((CFNumberRef) CFDictionaryGetValue (description, kDADiskDescriptionMediaSizeKey), kCFNumberLongType, &this_disk.size);
+       CFRelease (description);
+
+       reinterpret_cast<vector<Disk>*>(context)->push_back(this_disk);
+}
+
+vector<Drive>
+get_drives ()
+{
+       using namespace boost::algorithm;
+       vector<Disk> disks;
+
+       DASessionRef session = DASessionCreate(kCFAllocatorDefault);
+       if (!session) {
+               return vector<Drive>();
+       }
+
+       DARegisterDiskAppearedCallback (session, NULL, disk_appeared, &disks);
+       CFRunLoopRef run_loop = CFRunLoopGetCurrent ();
+       DASessionScheduleWithRunLoop (session, run_loop, kCFRunLoopDefaultMode);
+       CFRunLoopStop (run_loop);
+       CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.05, 0);
+       DAUnregisterCallback(session, (void *) disk_appeared, &disks);
+       CFRelease(session);
+
+       /* Mark disks containing mounted partitions as themselves mounted */
+       BOOST_FOREACH (Disk& i, disks) {
+               if (!i.whole) {
+                       continue;
+               }
+               BOOST_FOREACH (Disk& j, disks) {
+                       if (j.mounted && starts_with(j.device, i.device)) {
+                               LOG_DISK("Marking %1 as mounted because %2 is", i.device, j.device);
+                               i.mounted = true;
+                       }
+               }
+       }
+
+       /* Make a list of the PRT codes of mounted, synthesized disks */
+       vector<string> mounted_synths;
+       BOOST_FOREACH (Disk& i, disks) {
+               if (!i.real && i.mounted) {
+                       LOG_DISK("Found a mounted synth %1 with %2", i.device, i.prt);
+                       mounted_synths.push_back (i.prt);
+               }
+       }
+
+       /* Mark containers of those mounted synths as themselves mounted */
+       BOOST_FOREACH (Disk& i, disks) {
+               if (i.real && find(mounted_synths.begin(), mounted_synths.end(), i.prt) != mounted_synths.end()) {
+                       LOG_DISK("Marking %1 (%2) as mounted because it contains a mounted synth", i.device, i.prt);
+                       i.mounted = true;
+               }
+       }
+
+       vector<Drive> drives;
+       BOOST_FOREACH (Disk& i, disks) {
+               if (i.whole) {
+                       /* A whole disk that is not a container for a mounted synth */
+                       LOG_DISK("Adding drive: %1 %2 %3 %4 %5", i.device, i.size, i.mounted ? "mounted" : "unmounted", i.vendor.get_value_or("[none]"), i.model.get_value_or("[none]"));
+                       drives.push_back(Drive(i.device, i.size, i.mounted, i.vendor, i.model));
+               }
+       }
+       return drives;
+}
+
+boost::filesystem::path
+config_path ()
+{
+       boost::filesystem::path p;
+       p /= g_get_home_dir ();
+       p /= "Library";
+       p /= "Preferences";
+       p /= "com.dcpomatic";
+       p /= "2";
+       return p;
+}
diff --git a/src/lib/cross_windows.cc b/src/lib/cross_windows.cc
new file mode 100644 (file)
index 0000000..169435a
--- /dev/null
@@ -0,0 +1,568 @@
+/*
+    Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "cross.h"
+#include "compose.hpp"
+#include "log.h"
+#include "dcpomatic_log.h"
+#include "config.h"
+#include "exceptions.h"
+#include "dcpomatic_assert.h"
+#include <dcp/raw_convert.h>
+#include <glib.h>
+extern "C" {
+#include <libavformat/avio.h>
+}
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/dll/runtime_symbol_info.hpp>
+#include <windows.h>
+#include <winternl.h>
+#include <winioctl.h>
+#include <ntdddisk.h>
+#include <setupapi.h>
+#undef DATADIR
+#include <shlwapi.h>
+#include <shellapi.h>
+#include <fcntl.h>
+#include <fstream>
+
+#include "i18n.h"
+
+using std::pair;
+using std::list;
+using std::ifstream;
+using std::string;
+using std::wstring;
+using std::make_pair;
+using std::vector;
+using std::cerr;
+using std::cout;
+using std::runtime_error;
+using boost::shared_ptr;
+using boost::optional;
+
+/** @param s Number of seconds to sleep for */
+void
+dcpomatic_sleep_seconds (int s)
+{
+       Sleep (s * 1000);
+}
+
+void
+dcpomatic_sleep_milliseconds (int ms)
+{
+       Sleep (ms);
+}
+
+/** @return A string of CPU information (model name etc.) */
+string
+cpu_info ()
+{
+       string info;
+
+       HKEY key;
+       if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &key) != ERROR_SUCCESS) {
+               return info;
+       }
+
+       DWORD type;
+       DWORD data;
+       if (RegQueryValueEx (key, L"ProcessorNameString", 0, &type, 0, &data) != ERROR_SUCCESS) {
+               return info;
+       }
+
+       if (type != REG_SZ) {
+               return info;
+       }
+
+       wstring value (data / sizeof (wchar_t), L'\0');
+       if (RegQueryValueEx (key, L"ProcessorNameString", 0, 0, reinterpret_cast<LPBYTE> (&value[0]), &data) != ERROR_SUCCESS) {
+               RegCloseKey (key);
+               return info;
+       }
+
+       info = string (value.begin(), value.end());
+
+       RegCloseKey (key);
+
+       return info;
+}
+
+void
+run_ffprobe (boost::filesystem::path content, boost::filesystem::path out)
+{
+       SECURITY_ATTRIBUTES security;
+       security.nLength = sizeof (security);
+       security.bInheritHandle = TRUE;
+       security.lpSecurityDescriptor = 0;
+
+       HANDLE child_stderr_read;
+       HANDLE child_stderr_write;
+       if (!CreatePipe (&child_stderr_read, &child_stderr_write, &security, 0)) {
+               LOG_ERROR_NC ("ffprobe call failed (could not CreatePipe)");
+               return;
+       }
+
+       wchar_t dir[512];
+       GetModuleFileName (GetModuleHandle (0), dir, sizeof (dir));
+       PathRemoveFileSpec (dir);
+       SetCurrentDirectory (dir);
+
+       STARTUPINFO startup_info;
+       ZeroMemory (&startup_info, sizeof (startup_info));
+       startup_info.cb = sizeof (startup_info);
+       startup_info.hStdError = child_stderr_write;
+       startup_info.dwFlags |= STARTF_USESTDHANDLES;
+
+       wchar_t command[512];
+       wcscpy (command, L"ffprobe.exe \"");
+
+       wchar_t file[512];
+       MultiByteToWideChar (CP_UTF8, 0, content.string().c_str(), -1, file, sizeof(file));
+       wcscat (command, file);
+
+       wcscat (command, L"\"");
+
+       PROCESS_INFORMATION process_info;
+       ZeroMemory (&process_info, sizeof (process_info));
+       if (!CreateProcess (0, command, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
+               LOG_ERROR_NC (N_("ffprobe call failed (could not CreateProcess)"));
+               return;
+       }
+
+       FILE* o = fopen_boost (out, "w");
+       if (!o) {
+               LOG_ERROR_NC (N_("ffprobe call failed (could not create output file)"));
+               return;
+       }
+
+       CloseHandle (child_stderr_write);
+
+       while (true) {
+               char buffer[512];
+               DWORD read;
+               if (!ReadFile(child_stderr_read, buffer, sizeof(buffer), &read, 0) || read == 0) {
+                       break;
+               }
+               fwrite (buffer, read, 1, o);
+       }
+
+       fclose (o);
+
+       WaitForSingleObject (process_info.hProcess, INFINITE);
+       CloseHandle (process_info.hProcess);
+       CloseHandle (process_info.hThread);
+       CloseHandle (child_stderr_read);
+}
+
+list<pair<string, string> >
+mount_info ()
+{
+       list<pair<string, string> > m;
+       return m;
+}
+
+static boost::filesystem::path
+executable_path ()
+{
+       return boost::dll::program_location().parent_path();
+}
+
+boost::filesystem::path
+shared_path ()
+{
+       return executable_path().parent_path();
+}
+
+boost::filesystem::path
+openssl_path ()
+{
+       return executable_path() / "openssl.exe";
+}
+
+boost::filesystem::path
+disk_writer_path ()
+{
+       return executable_path() / "dcpomatic2_disk_writer.exe";
+}
+
+/* Apparently there is no way to create an ofstream using a UTF-8
+   filename under Windows.  We are hence reduced to using fopen
+   with this wrapper.
+*/
+FILE *
+fopen_boost (boost::filesystem::path p, string t)
+{
+        wstring w (t.begin(), t.end());
+       /* c_str() here should give a UTF-16 string */
+        return _wfopen (p.c_str(), w.c_str ());
+}
+
+int
+dcpomatic_fseek (FILE* stream, int64_t offset, int whence)
+{
+       return _fseeki64 (stream, offset, whence);
+}
+
+void
+Waker::nudge ()
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       SetThreadExecutionState (ES_SYSTEM_REQUIRED);
+}
+
+Waker::Waker ()
+{
+
+}
+
+Waker::~Waker ()
+{
+
+}
+
+void
+start_tool (boost::filesystem::path dcpomatic, string executable, string)
+{
+       boost::filesystem::path batch = dcpomatic.parent_path() / executable;
+
+       STARTUPINFO startup_info;
+       ZeroMemory (&startup_info, sizeof (startup_info));
+       startup_info.cb = sizeof (startup_info);
+
+       PROCESS_INFORMATION process_info;
+       ZeroMemory (&process_info, sizeof (process_info));
+
+       wchar_t cmd[512];
+       MultiByteToWideChar (CP_UTF8, 0, batch.string().c_str(), -1, cmd, sizeof(cmd));
+       CreateProcess (0, cmd, 0, 0, FALSE, 0, 0, 0, &startup_info, &process_info);
+}
+
+void
+start_batch_converter (boost::filesystem::path dcpomatic)
+{
+       start_tool (dcpomatic, "dcpomatic2_batch", "DCP-o-matic\\ 2\\ Batch\\ Converter.app");
+}
+
+void
+start_player (boost::filesystem::path dcpomatic)
+{
+       start_tool (dcpomatic, "dcpomatic2_player", "DCP-o-matic\\ 2\\ Player.app");
+}
+
+uint64_t
+thread_id ()
+{
+       return (uint64_t) GetCurrentThreadId ();
+}
+
+static string
+wchar_to_utf8 (wchar_t const * s)
+{
+       int const length = (wcslen(s) + 1) * 2;
+       char* utf8 = new char[length];
+       WideCharToMultiByte (CP_UTF8, 0, s, -1, utf8, length, 0, 0);
+       string u (utf8);
+       delete[] utf8;
+       return u;
+}
+
+int
+avio_open_boost (AVIOContext** s, boost::filesystem::path file, int flags)
+{
+       return avio_open (s, wchar_to_utf8(file.c_str()).c_str(), flags);
+}
+
+void
+maybe_open_console ()
+{
+       if (Config::instance()->win32_console ()) {
+               AllocConsole();
+
+               HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
+               int hCrt = _open_osfhandle((intptr_t) handle_out, _O_TEXT);
+               FILE* hf_out = _fdopen(hCrt, "w");
+               setvbuf(hf_out, NULL, _IONBF, 1);
+               *stdout = *hf_out;
+
+               HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE);
+               hCrt = _open_osfhandle((intptr_t) handle_in, _O_TEXT);
+               FILE* hf_in = _fdopen(hCrt, "r");
+               setvbuf(hf_in, NULL, _IONBF, 128);
+               *stdin = *hf_in;
+       }
+}
+
+boost::filesystem::path
+home_directory ()
+{
+       return boost::filesystem::path(getenv("HOMEDRIVE")) / boost::filesystem::path(getenv("HOMEPATH"));
+}
+
+string
+command_and_read (string)
+{
+       return "";
+}
+
+/** @return true if this process is a 32-bit one running on a 64-bit-capable OS */
+bool
+running_32_on_64 ()
+{
+       BOOL p;
+       IsWow64Process (GetCurrentProcess(), &p);
+       return p;
+}
+
+static optional<string>
+get_friendly_name (HDEVINFO device_info, SP_DEVINFO_DATA* device_info_data)
+{
+       wchar_t buffer[MAX_PATH];
+       ZeroMemory (&buffer, sizeof(buffer));
+       bool r = SetupDiGetDeviceRegistryPropertyW (
+                       device_info, device_info_data, SPDRP_FRIENDLYNAME, 0, reinterpret_cast<PBYTE>(buffer), sizeof(buffer), 0
+                       );
+       if (!r) {
+               return optional<string>();
+       }
+       return wchar_to_utf8 (buffer);
+}
+
+static const GUID GUID_DEVICE_INTERFACE_DISK = {
+       0x53F56307L, 0xB6BF, 0x11D0, { 0x94, 0xF2, 0x00, 0xA0, 0xC9, 0x1E, 0xFB, 0x8B }
+};
+
+static optional<int>
+get_device_number (HDEVINFO device_info, SP_DEVINFO_DATA* device_info_data)
+{
+       /* Find the Windows path to the device */
+
+       SP_DEVICE_INTERFACE_DATA device_interface_data;
+       device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
+
+       BOOL r = SetupDiEnumDeviceInterfaces (device_info, device_info_data, &GUID_DEVICE_INTERFACE_DISK, 0, &device_interface_data);
+       if (!r) {
+               LOG_DISK("SetupDiEnumDeviceInterfaces failed (%1)", GetLastError());
+               return optional<int>();
+       }
+
+       /* Find out how much space we need for our SP_DEVICE_INTERFACE_DETAIL_DATA_W */
+       DWORD size;
+       r = SetupDiGetDeviceInterfaceDetailW(device_info, &device_interface_data, 0, 0, &size, 0);
+       PSP_DEVICE_INTERFACE_DETAIL_DATA_W device_detail_data = static_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA_W> (malloc(size));
+       if (!device_detail_data) {
+               LOG_DISK_NC("malloc failed");
+               return optional<int>();
+       }
+
+       device_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
+
+       /* And get the path */
+       r = SetupDiGetDeviceInterfaceDetailW (device_info, &device_interface_data, device_detail_data, size, &size, 0);
+       if (!r) {
+               LOG_DISK_NC("SetupDiGetDeviceInterfaceDetailW failed");
+               free (device_detail_data);
+               return optional<int>();
+       }
+
+       /* Open it.  We would not be allowed GENERIC_READ access here but specifying 0 for
+          dwDesiredAccess allows us to query some metadata.
+       */
+       HANDLE device = CreateFileW (
+                       device_detail_data->DevicePath, 0,
+                       FILE_SHARE_READ | FILE_SHARE_WRITE, 0,
+                       OPEN_EXISTING, 0, 0
+                       );
+
+       free (device_detail_data);
+
+       if (device == INVALID_HANDLE_VALUE) {
+               LOG_DISK("CreateFileW failed with %1", GetLastError());
+               return optional<int>();
+       }
+
+       /* Get the device number */
+       STORAGE_DEVICE_NUMBER device_number;
+       r = DeviceIoControl (
+                       device, IOCTL_STORAGE_GET_DEVICE_NUMBER, 0, 0,
+                       &device_number, sizeof(device_number), &size, 0
+                       );
+
+       CloseHandle (device);
+
+       if (!r) {
+               return optional<int>();
+       }
+
+       return device_number.DeviceNumber;
+}
+
+/** Take a volume path (with a trailing \) and add any disk numbers related to that volume
+ *  to @ref disks.
+ */
+static void
+add_volume_disk_number (wchar_t* volume, vector<int>& disks)
+{
+       /* Strip trailing \ */
+       size_t const len = wcslen (volume);
+       DCPOMATIC_ASSERT (len > 0);
+       volume[len - 1] = L'\0';
+
+       HANDLE handle = CreateFileW (
+                       volume, 0,
+                       FILE_SHARE_READ | FILE_SHARE_WRITE, 0,
+                       OPEN_EXISTING, 0, 0
+                       );
+
+       DCPOMATIC_ASSERT (handle != INVALID_HANDLE_VALUE);
+
+       VOLUME_DISK_EXTENTS extents;
+       DWORD size;
+       BOOL r = DeviceIoControl (handle, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, 0, 0, &extents, sizeof(extents), &size, 0);
+       CloseHandle (handle);
+       if (!r) {
+               return;
+       }
+       DCPOMATIC_ASSERT (extents.NumberOfDiskExtents == 1);
+       return disks.push_back (extents.Extents[0].DiskNumber);
+}
+
+/* Return a list of disk numbers that contain volumes; i.e. a list of disk numbers that should
+ * not be offered as targets to write to as they are "mounted" (whatever that means on Windows).
+ */
+vector<int>
+disk_numbers_with_volumes ()
+{
+       vector<int> disks;
+
+       wchar_t volume_name[512];
+       HANDLE volume = FindFirstVolumeW (volume_name, sizeof(volume_name) / sizeof(wchar_t));
+       if (volume == INVALID_HANDLE_VALUE) {
+               return disks;
+       }
+
+       add_volume_disk_number (volume_name, disks);
+       while (true) {
+               if (!FindNextVolumeW(volume, volume_name, sizeof(volume_name) / sizeof(wchar_t))) {
+                       break;
+               }
+               add_volume_disk_number (volume_name, disks);
+       }
+       FindVolumeClose (volume);
+
+       return disks;
+}
+
+vector<Drive>
+get_drives ()
+{
+       vector<Drive> drives;
+
+       vector<int> disks_to_ignore = disk_numbers_with_volumes ();
+
+       /* Get a `device information set' containing information about all disks */
+       HDEVINFO device_info = SetupDiGetClassDevsA (&GUID_DEVICE_INTERFACE_DISK, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+       if (device_info == INVALID_HANDLE_VALUE) {
+               LOG_DISK_NC ("SetupDiClassDevsA failed");
+               return drives;
+       }
+
+       int i = 0;
+       while (true) {
+               /* Find out about the next disk */
+               SP_DEVINFO_DATA device_info_data;
+               device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
+               if (!SetupDiEnumDeviceInfo(device_info, i, &device_info_data)) {
+                       DWORD e = GetLastError();
+                       if (e != ERROR_NO_MORE_ITEMS) {
+                               LOG_DISK ("SetupDiEnumDeviceInfo failed (%1)", GetLastError());
+                       }
+                       break;
+               }
+               ++i;
+
+               optional<string> const friendly_name = get_friendly_name (device_info, &device_info_data);
+               optional<int> device_number = get_device_number (device_info, &device_info_data);
+               if (!device_number) {
+                       continue;
+               }
+
+               string const physical_drive = String::compose("\\\\.\\PHYSICALDRIVE%1", *device_number);
+
+               HANDLE device = CreateFileA (
+                               physical_drive.c_str(), 0,
+                               FILE_SHARE_READ | FILE_SHARE_WRITE, 0,
+                               OPEN_EXISTING, 0, 0
+                               );
+
+               if (device == INVALID_HANDLE_VALUE) {
+                       LOG_DISK_NC("Could not open PHYSICALDRIVE");
+                       continue;
+               }
+
+               DISK_GEOMETRY geom;
+               DWORD returned;
+               BOOL r = DeviceIoControl (
+                               device, IOCTL_DISK_GET_DRIVE_GEOMETRY, 0, 0,
+                               &geom, sizeof(geom), &returned, 0
+                               );
+
+               if (r && find(disks_to_ignore.begin(), disks_to_ignore.end(), *device_number) == disks_to_ignore.end()) {
+                       uint64_t const disk_size = geom.Cylinders.QuadPart * geom.TracksPerCylinder * geom.SectorsPerTrack * geom.BytesPerSector;
+                       drives.push_back (Drive(physical_drive, disk_size, false, friendly_name, optional<string>()));
+               }
+
+               CloseHandle (device);
+       }
+
+       return drives;
+}
+
+string
+Drive::description () const
+{
+       char gb[64];
+       snprintf(gb, 64, "%.1f", _size / 1000000000.0);
+
+       string name;
+       if (_vendor) {
+               name += *_vendor;
+       }
+       if (_model) {
+               if (name.size() > 0) {
+                       name += " " + *_model;
+               }
+       }
+       if (name.size() == 0) {
+               name = _("Unknown");
+       }
+
+       return String::compose("%1 (%2 GB) [%3]", name, gb, _internal_name);
+}
+
+boost::filesystem::path
+config_path ()
+{
+       boost::filesystem::path p;
+       p /= g_get_user_config_dir ();
+       p /= "dcpomatic2";
+       return p;
+}
index dbc850036681be89a101736366f03a7784276acd..382a586d430b5bee8687ffcac517b980c917af50 100644 (file)
@@ -36,3 +36,5 @@ extern boost::shared_ptr<Log> dcpomatic_log;
 #define LOG_DEBUG_PLAYER(...) dcpomatic_log->log(String::compose(__VA_ARGS__), LogEntry::TYPE_DEBUG_PLAYER);
 #define LOG_DEBUG_THREED(...) dcpomatic_log->log(String::compose(__VA_ARGS__), LogEntry::TYPE_DEBUG_THREED);
 #define LOG_DEBUG_THREED_NC(...) dcpomatic_log->log(__VA_ARGS__, LogEntry::TYPE_DEBUG_THREED);
 #define LOG_DEBUG_PLAYER(...) dcpomatic_log->log(String::compose(__VA_ARGS__), LogEntry::TYPE_DEBUG_PLAYER);
 #define LOG_DEBUG_THREED(...) dcpomatic_log->log(String::compose(__VA_ARGS__), LogEntry::TYPE_DEBUG_THREED);
 #define LOG_DEBUG_THREED_NC(...) dcpomatic_log->log(__VA_ARGS__, LogEntry::TYPE_DEBUG_THREED);
+#define LOG_DISK(...)         dcpomatic_log->log(String::compose(__VA_ARGS__), LogEntry::TYPE_DISK);
+#define LOG_DISK_NC(...)      dcpomatic_log->log(__VA_ARGS__, LogEntry::TYPE_DISK);
diff --git a/src/lib/disk_writer_messages.h b/src/lib/disk_writer_messages.h
new file mode 100644 (file)
index 0000000..61cdbcf
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+    Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+/* dcpomatic_disk_writer receives
+
+DCP pathname\n
+Internal name of drive to write to\n
+
+   Then responds with one of the following.
+*/
+
+/** Write finished and everything was OK, e.g.
+
+D\n
+
+*/
+#define DISK_WRITER_OK "D"
+
+/** There was an error.  Following this will come
+
+error message\n
+error number\n
+
+e.g.
+
+E\n
+Disc full\n
+42\n
+
+*/
+#define DISK_WRITER_ERROR "E"
+
+/** The disk writer is formatting the drive.  It is not possible
+ *  to give progress reports on this so the writer just tells us
+ *  it's happening.  This is finished when DISK_WRITER_PROGRESS
+ *  messages start arriving
+ */
+#define DISK_WRITER_FORMATTING "F"
+
+/** Some progress has been made in the main "copy" part of the task.
+ *  Following this will come
+
+progress as a float from 0 to 1\n
+
+e.g.
+
+P\n
+0.3\n
+
+*/
+#define DISK_WRITER_PROGRESS "P"
+
+/** dcpomatic_disk_writer may also receive
+
+Q\n
+
+as a request to quit.
+*/
+#define DISK_WRITER_QUIT "Q"
+
index ba3d4a05cd68bacbefd4d446fa4e2b3fbfaa87c0..d394ad4b2c4e41e1c8285e31b21efc3d1374e9c2 100644 (file)
@@ -114,3 +114,20 @@ GLError::GLError (char const * last, int e)
 {
 
 }
 {
 
 }
+
+CopyError::CopyError (string m, int n)
+       : runtime_error (String::compose("%1 (%2)", m, n))
+       , _message (m)
+       , _number (n)
+{
+
+}
+
+VerifyError::VerifyError (string m, int n)
+       : runtime_error (String::compose("%1 (%2)", m, n))
+       , _message (m)
+       , _number (n)
+{
+
+}
+
index 73b8cc85a7876259c594f5d1fee92acd9cd85897..0f8a2eda27a6b9fa7a42dab355331953ea51a691 100644 (file)
@@ -314,5 +314,47 @@ public:
        GLError (char const * last, int e);
 };
 
        GLError (char const * last, int e);
 };
 
+/** @class CopyError
+ *  @brief An error which occurs when copying a DCP to a distribution drive.
+ */
+class CopyError : public std::runtime_error
+{
+public:
+       CopyError (std::string s, int n);
+       virtual ~CopyError () throw () {}
+
+       std::string message () const {
+               return _message;
+       }
+
+       int number () const {
+               return _number;
+       }
+
+private:
+       std::string _message;
+       int _number;
+};
 
 
+/** @class VerifyError
+ *  @brief An error which occurs when verifying a DCP that we copied to a distribution drive.
+ */
+class VerifyError : public std::runtime_error
+{
+public:
+       VerifyError (std::string s, int n);
+       virtual ~VerifyError () throw () {}
+
+       std::string message () const {
+               return _message;
+       }
+
+       int number () const {
+               return _number;
+       }
+
+private:
+       std::string _message;
+       int _number;
+};
 #endif
 #endif
index f6eaa58f8ebaf85b4e02630fee9ab869f91115c2..b9aa84c3d9ff2aa957f84f01714fb1cbc1def2f9 100644 (file)
@@ -23,6 +23,7 @@
 #include "config.h"
 #include <cstdio>
 #include <iostream>
 #include "config.h"
 #include <cstdio>
 #include <iostream>
+#include <cerrno>
 
 using std::cout;
 using std::string;
 
 using std::cout;
 using std::string;
@@ -36,12 +37,18 @@ FileLog::FileLog (boost::filesystem::path file)
        set_types (Config::instance()->log_types());
 }
 
        set_types (Config::instance()->log_types());
 }
 
+FileLog::FileLog (boost::filesystem::path file, int types)
+       : _file (file)
+{
+       set_types (types);
+}
+
 void
 FileLog::do_log (shared_ptr<const LogEntry> entry)
 {
        FILE* f = fopen_boost (_file, "a");
        if (!f) {
 void
 FileLog::do_log (shared_ptr<const LogEntry> entry)
 {
        FILE* f = fopen_boost (_file, "a");
        if (!f) {
-               cout << "(could not log to " << _file.string() << "): " << entry.get() << "\n";
+               cout << "(could not log to " << _file.string() << " error " << errno << "): " << entry->get() << "\n";
                return;
        }
 
                return;
        }
 
index 53fbe4f765308a391a0cf1c591b323c960f255b8..613dd0939df990c1e0acc1f699cd4faeb15a9ea6 100644 (file)
@@ -24,6 +24,7 @@ class FileLog : public Log
 {
 public:
        explicit FileLog (boost::filesystem::path file);
 {
 public:
        explicit FileLog (boost::filesystem::path file);
+       FileLog (boost::filesystem::path file, int types);
 
        std::string head_and_tail (int amount = 1024) const;
 
 
        std::string head_and_tail (int amount = 1024) const;
 
index b102a2d653983c3368bf04d8669f5f440e05df19..416f4259d248399ed88cbff3f7d682ba2e3c0bf9 100644 (file)
@@ -46,6 +46,9 @@ public:
        void dcp_log (dcp::NoteType type, std::string message);
 
        void set_types (int types);
        void dcp_log (dcp::NoteType type, std::string message);
 
        void set_types (int types);
+       int types () const {
+               return _types;
+       }
 
        /** @param amount Approximate number of bytes to return; the returned value
         *  may be shorter or longer than this.
 
        /** @param amount Approximate number of bytes to return; the returned value
         *  may be shorter or longer than this.
index 54f9bfc53db3bc7e4a428c72cbdf20786239dc93..4aff47c73a035fe58b518a2e59612fa92cefc0c7 100644 (file)
 
 #include "i18n.h"
 
 
 #include "i18n.h"
 
-int const LogEntry::TYPE_GENERAL      = 0x1;
-int const LogEntry::TYPE_WARNING      = 0x2;
-int const LogEntry::TYPE_ERROR        = 0x4;
-int const LogEntry::TYPE_DEBUG_THREED = 0x8;
-int const LogEntry::TYPE_DEBUG_ENCODE = 0x10;
-int const LogEntry::TYPE_TIMING       = 0x20;
-int const LogEntry::TYPE_DEBUG_EMAIL  = 0x40;
-int const LogEntry::TYPE_DEBUG_PLAYER = 0x80;
+int const LogEntry::TYPE_GENERAL      = 0x001;
+int const LogEntry::TYPE_WARNING      = 0x002;
+int const LogEntry::TYPE_ERROR        = 0x004;
+int const LogEntry::TYPE_DEBUG_THREED = 0x008;
+int const LogEntry::TYPE_DEBUG_ENCODE = 0x010;
+int const LogEntry::TYPE_TIMING       = 0x020;
+int const LogEntry::TYPE_DEBUG_EMAIL  = 0x040;
+int const LogEntry::TYPE_DEBUG_PLAYER = 0x080;
+int const LogEntry::TYPE_DISK         = 0x100;
 
 using std::string;
 
 
 using std::string;
 
index c15a006d13e1f9b66ce5bb4d66da6aad4648216f..d4992de86d999b5ce94ae3ed8996c193302a85b8 100644 (file)
@@ -1,5 +1,5 @@
 /*
 /*
-    Copyright (C) 2015 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2015-2020 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 
     This file is part of DCP-o-matic.
 
@@ -36,6 +36,7 @@ public:
        static const int TYPE_TIMING;
        static const int TYPE_DEBUG_EMAIL;
        static const int TYPE_DEBUG_PLAYER;
        static const int TYPE_TIMING;
        static const int TYPE_DEBUG_EMAIL;
        static const int TYPE_DEBUG_PLAYER;
+       static const int TYPE_DISK;
 
        explicit LogEntry (int type);
        virtual ~LogEntry () {}
 
        explicit LogEntry (int type);
        virtual ~LogEntry () {}
diff --git a/src/lib/nanomsg.cc b/src/lib/nanomsg.cc
new file mode 100644 (file)
index 0000000..57220cd
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+    Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "nanomsg.h"
+#include "dcpomatic_log.h"
+#include <nanomsg/nn.h>
+#include <nanomsg/pair.h>
+#include <stdexcept>
+#include <cerrno>
+
+using std::string;
+using std::runtime_error;
+using boost::optional;
+
+#define NANOMSG_URL "ipc:///tmp/dcpomatic.ipc"
+
+Nanomsg::Nanomsg (bool server)
+{
+       _socket = nn_socket (AF_SP, NN_PAIR);
+       if (_socket < 0) {
+               throw runtime_error("Could not set up nanomsg socket");
+       }
+       if (server) {
+               if (nn_bind(_socket, NANOMSG_URL) < 0) {
+                       throw runtime_error(String::compose("Could not bind nanomsg socket (%1)", errno));
+               }
+       } else {
+               if (nn_connect(_socket, NANOMSG_URL) < 0) {
+                       throw runtime_error(String::compose("Could not connect nanomsg socket (%1)", errno));
+               }
+       }
+}
+
+void
+Nanomsg::blocking_send (string s)
+{
+       int const r = nn_send (_socket, s.c_str(), s.length(), 0);
+       if (r < 0) {
+               throw runtime_error(String::compose("Could not send to nanomsg socket (%1)", errno));
+       } else if (r != int(s.length())) {
+               throw runtime_error("Could not send to nanomsg socket (message too big)");
+       }
+}
+
+bool
+Nanomsg::nonblocking_send (string s)
+{
+       int const r = nn_send (_socket, s.c_str(), s.length(), NN_DONTWAIT);
+       if (r < 0) {
+               if (errno == EAGAIN) {
+                       return false;
+               }
+               throw runtime_error(String::compose("Could not send to nanomsg socket (%1)", errno));
+       } else if (r != int(s.length())) {
+               throw runtime_error("Could not send to nanomsg socket (message too big)");
+       }
+
+       return true;
+}
+
+optional<string>
+Nanomsg::get_from_pending ()
+{
+       if (_pending.empty()) {
+               return optional<string>();
+       }
+
+       string const l = _pending.back();
+       _pending.pop_back();
+       return l;
+}
+
+void
+Nanomsg::recv_and_parse (bool blocking)
+{
+       char* buf = 0;
+       int const received = nn_recv (_socket, &buf, NN_MSG, blocking ? 0 : NN_DONTWAIT);
+       if (received < 0)
+       {
+               if (!blocking && errno == EAGAIN) {
+                       return;
+               }
+
+               throw runtime_error ("Could not communicate with subprocess");
+       }
+
+       char* p = buf;
+       for (int i = 0; i < received; ++i) {
+               if (*p == '\n') {
+                       _pending.push_front (_current);
+                       _current = "";
+               } else {
+                       _current += *p;
+               }
+               ++p;
+       }
+       nn_freemsg (buf);
+}
+
+string
+Nanomsg::blocking_get ()
+{
+       optional<string> l = get_from_pending ();
+       if (l) {
+               return *l;
+       }
+
+       recv_and_parse (true);
+
+       l = get_from_pending ();
+       if (!l) {
+               throw runtime_error ("Could not communicate with subprocess");
+       }
+
+       return *l;
+}
+
+optional<string>
+Nanomsg::nonblocking_get ()
+{
+       optional<string> l = get_from_pending ();
+       if (l) {
+               return *l;
+       }
+
+       recv_and_parse (false);
+       return get_from_pending ();
+}
diff --git a/src/lib/nanomsg.h b/src/lib/nanomsg.h
new file mode 100644 (file)
index 0000000..dc84a6c
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+    Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include <string>
+#include <list>
+#include <boost/optional.hpp>
+#include <boost/noncopyable.hpp>
+
+class Nanomsg : public boost::noncopyable
+{
+public:
+       explicit Nanomsg (bool server);
+
+       void blocking_send (std::string s);
+       /** Try to send a message, returning true if successful, false
+        *  if we should try again (EAGAIN) or throwing an exception on any other
+        *  error.
+        */
+       bool nonblocking_send (std::string s);
+       std::string blocking_get ();
+       boost::optional<std::string> nonblocking_get ();
+
+private:
+       boost::optional<std::string> get_from_pending ();
+       void recv_and_parse (bool blocking);
+
+       int _socket;
+       std::list<std::string> _pending;
+       std::string _current;
+};
+
index abb1976951e9f5b62daccb8e7fe2674da17b45d1..dd48516f43fc05a384089c543a8b60a12896ea5f 100644 (file)
@@ -1,5 +1,5 @@
 /*
 /*
-    Copyright (C) 2018 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2018-2020 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 
     This file is part of DCP-o-matic.
 
@@ -19,6 +19,7 @@
 */
 
 #include "state.h"
 */
 
 #include "state.h"
+#include "cross.h"
 #include <glib.h>
 
 using std::string;
 #include <glib.h>
 
 using std::string;
@@ -34,16 +35,7 @@ State::path (string file, bool create_directories)
        if (override_path) {
                p = *override_path;
        } else {
        if (override_path) {
                p = *override_path;
        } else {
-#ifdef DCPOMATIC_OSX
-               p /= g_get_home_dir ();
-               p /= "Library";
-               p /= "Preferences";
-               p /= "com.dcpomatic";
-               p /= "2";
-#else
-               p /= g_get_user_config_dir ();
-               p /= "dcpomatic2";
-#endif
+               p = config_path ();
        }
        boost::system::error_code ec;
        if (create_directories) {
        }
        boost::system::error_code ec;
        if (create_directories) {
index a37d873a8052b974cbc6c2146dcc1ced9e3e5996..ca6786ef23440b617a8fd833bcb3030f40fa8f82 100644 (file)
@@ -53,7 +53,7 @@ sources = """
           content.cc
           content_factory.cc
           create_cli.cc
           content.cc
           content_factory.cc
           create_cli.cc
-          cross.cc
+          cross_common.cc
           crypto.cc
           curl_uploader.cc
           datasat_ap2x.cc
           crypto.cc
           curl_uploader.cc
           datasat_ap2x.cc
@@ -197,15 +197,26 @@ def build(bld):
                  """
 
     if bld.env.TARGET_OSX:
                  """
 
     if bld.env.TARGET_OSX:
-        obj.framework = ['IOKit', 'Foundation']
+        obj.framework = ['IOKit', 'Foundation', 'DiskArbitration']
 
     obj.source = sources + ' version.cc'
 
     if bld.env.VARIANT == 'swaroop-theater' or bld.env.VARIANT == 'swaroop-studio':
         obj.source += ' swaroop_spl.cc swaroop_spl_entry.cc'
 
 
     obj.source = sources + ' version.cc'
 
     if bld.env.VARIANT == 'swaroop-theater' or bld.env.VARIANT == 'swaroop-studio':
         obj.source += ' swaroop_spl.cc swaroop_spl_entry.cc'
 
+    if bld.env.ENABLE_DISK:
+        obj.source += ' copy_to_drive_job.cc nanomsg.cc'
+        obj.uselib += ' LWEXT4 NANOMSG'
+        if bld.env.TARGET_LINUX:
+            obj.uselib += ' POLKIT'
+
     if bld.env.TARGET_WINDOWS:
     if bld.env.TARGET_WINDOWS:
-        obj.uselib += ' WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE'
+        obj.uselib += ' WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE SETUPAPI'
+        obj.source += ' cross_windows.cc'
+    if bld.env.TARGET_OSX:
+        obj.source += ' cross_osx.cc'
+    if bld.env.TARGET_LINUX:
+        obj.source += ' cross_linux.cc'
     if bld.env.STATIC_DCPOMATIC:
         obj.uselib += ' XMLPP'
 
     if bld.env.STATIC_DCPOMATIC:
         obj.uselib += ' XMLPP'
 
diff --git a/src/tools/dcpomatic_disk.cc b/src/tools/dcpomatic_disk.cc
new file mode 100644 (file)
index 0000000..baccdbc
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+    Copyright (C) 2019-2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "wx/wx_signal_manager.h"
+#include "wx/wx_util.h"
+#include "wx/job_manager_view.h"
+#include "wx/drive_wipe_warning_dialog.h"
+#include "lib/file_log.h"
+#include "lib/dcpomatic_log.h"
+#include "lib/util.h"
+#include "lib/config.h"
+#include "lib/signal_manager.h"
+#include "lib/cross.h"
+#include "lib/copy_to_drive_job.h"
+#include "lib/job_manager.h"
+#include "lib/disk_writer_messages.h"
+#include <wx/wx.h>
+#include <boost/process.hpp>
+#ifdef DCPOMATIC_WINDOWS
+#include <boost/process/windows.hpp>
+#endif
+#ifdef DCPOMATIC_OSX
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+
+using std::string;
+using std::exception;
+using std::cout;
+using std::cerr;
+using std::runtime_error;
+using boost::shared_ptr;
+
+class DOMFrame : public wxFrame
+{
+public:
+       explicit DOMFrame (wxString const & title)
+               : wxFrame (0, -1, title)
+               , _nanomsg (true)
+               , _sizer (new wxBoxSizer(wxVERTICAL))
+       {
+               /* Use a panel as the only child of the Frame so that we avoid
+                  the dark-grey background on Windows.
+               */
+               wxPanel* overall_panel = new wxPanel (this);
+               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               s->Add (overall_panel, 1, wxEXPAND);
+               SetSizer (s);
+
+               wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+
+               int r = 0;
+               add_label_to_sizer (grid, overall_panel, _("DCP"), true, wxGBPosition(r, 0));
+               wxBoxSizer* dcp_name_sizer = new wxBoxSizer (wxHORIZONTAL);
+               _dcp_name = new wxStaticText (overall_panel, wxID_ANY, wxEmptyString);
+               dcp_name_sizer->Add (_dcp_name, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
+               _dcp_open = new wxButton (overall_panel, wxID_ANY, _("Open..."));
+               dcp_name_sizer->Add (_dcp_open, 0);
+               grid->Add (dcp_name_sizer, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
+               ++r;
+
+               add_label_to_sizer (grid, overall_panel, _("Drive"), true, wxGBPosition(r, 0));
+               wxBoxSizer* drive_sizer = new wxBoxSizer (wxHORIZONTAL);
+               _drive = new wxChoice (overall_panel, wxID_ANY);
+               drive_sizer->Add (_drive, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
+               _drive_refresh = new wxButton (overall_panel, wxID_ANY, _("Refresh"));
+               drive_sizer->Add (_drive_refresh, 0);
+               grid->Add (drive_sizer, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
+               ++r;
+
+               _jobs = new JobManagerView (overall_panel, false);
+               grid->Add (_jobs, wxGBPosition(r, 0), wxGBSpan(6, 2), wxEXPAND);
+               r += 6;
+
+               _copy = new wxButton (overall_panel, wxID_ANY, _("Copy DCP"));
+               grid->Add (_copy, wxGBPosition(r, 0), wxGBSpan(1, 2), wxEXPAND);
+               ++r;
+
+               grid->AddGrowableCol (1);
+
+               _dcp_open->Bind (wxEVT_BUTTON, boost::bind(&DOMFrame::open, this));
+               _copy->Bind (wxEVT_BUTTON, boost::bind(&DOMFrame::copy, this));
+               _drive->Bind (wxEVT_CHOICE, boost::bind(&DOMFrame::setup_sensitivity, this));
+               _drive_refresh->Bind (wxEVT_BUTTON, boost::bind(&DOMFrame::drive_refresh, this));
+
+               _sizer->Add (grid, 1, wxALL | wxEXPAND, DCPOMATIC_DIALOG_BORDER);
+               overall_panel->SetSizer (_sizer);
+               Fit ();
+               SetSize (768, GetSize().GetHeight() + 32);
+
+               /* XXX: this is a hack, but I expect we'll need logs and I'm not sure if there's
+                * a better place to put them.
+                */
+               dcpomatic_log.reset(new FileLog(config_path() / "disk.log"));
+               dcpomatic_log->set_types (dcpomatic_log->types() | LogEntry::TYPE_DISK);
+               LOG_DISK_NC("dcpomatic_disk started");
+
+               drive_refresh ();
+
+               Bind (wxEVT_SIZE, boost::bind (&DOMFrame::sized, this, _1));
+
+               JobManager::instance()->ActiveJobsChanged.connect(boost::bind(&DOMFrame::setup_sensitivity, this));
+
+#ifdef DCPOMATIC_WINDOWS
+               /* We must use ::shell here, it seems, to avoid error code 740 (related to privilege escalation) */
+               LOG_DISK("Starting writer process %1", disk_writer_path().string());
+               _writer = new boost::process::child (disk_writer_path(), boost::process::shell, boost::process::windows::hide);
+#endif
+
+#ifdef DCPOMATIC_LINUX
+               LOG_DISK("Starting writer process %1", disk_writer_path().string());
+               _writer = new boost::process::child (disk_writer_path());
+#endif
+
+               /* _writer is always running on macOS at the moment */
+       }
+
+       ~DOMFrame ()
+       {
+               _nanomsg.blocking_send(DISK_WRITER_QUIT "\n");
+       }
+
+private:
+       void sized (wxSizeEvent& ev)
+       {
+               _sizer->Layout ();
+               ev.Skip ();
+       }
+
+       void open ()
+       {
+               wxDirDialog* d = new wxDirDialog (this, _("Choose a DCP folder"), wxT(""), wxDD_DIR_MUST_EXIST);
+               int r = d->ShowModal ();
+               boost::filesystem::path const path (wx_to_std(d->GetPath()));
+               d->Destroy ();
+
+               if (r != wxID_OK) {
+                       return;
+               }
+
+               _dcp_path = path;
+               _dcp_name->SetLabel (std_to_wx(_dcp_path->filename().string()));
+               setup_sensitivity ();
+       }
+
+       void copy ()
+       {
+               DCPOMATIC_ASSERT (_drive->GetSelection() != wxNOT_FOUND);
+               DCPOMATIC_ASSERT (static_cast<bool>(_dcp_path));
+               DriveWipeWarningDialog* d = new DriveWipeWarningDialog (this, _drive->GetString(_drive->GetSelection()));
+               int const r = d->ShowModal ();
+               bool ok = r == wxID_OK && d->confirmed();
+               d->Destroy ();
+
+               if (!ok) {
+                       return;
+               }
+
+               JobManager::instance()->add(shared_ptr<Job>(new CopyToDriveJob(*_dcp_path, _drives[_drive->GetSelection()], _nanomsg)));
+               setup_sensitivity ();
+       }
+
+       void drive_refresh ()
+       {
+               int const sel = _drive->GetSelection ();
+               wxString current;
+               if (sel != wxNOT_FOUND) {
+                       current = _drive->GetString (sel);
+               }
+               _drive->Clear ();
+               int re_select = wxNOT_FOUND;
+               int j = 0;
+               _drives.clear ();
+               BOOST_FOREACH (Drive i, get_drives()) {
+                       if (!i.mounted()) {
+                               _drives.push_back (i);
+                       }
+               }
+               BOOST_FOREACH (Drive i, _drives) {
+                       wxString const s = std_to_wx(i.description());
+                       if (s == current) {
+                               re_select = j;
+                       }
+                       _drive->Append(s);
+                       ++j;
+               }
+               _drive->SetSelection (re_select);
+               setup_sensitivity ();
+       }
+
+       void setup_sensitivity ()
+       {
+               _copy->Enable (static_cast<bool>(_dcp_path) && _drive->GetSelection() != wxNOT_FOUND && !JobManager::instance()->work_to_do());
+       }
+
+       wxStaticText* _dcp_name;
+       wxButton* _dcp_open;
+       wxChoice* _drive;
+       wxButton* _drive_refresh;
+       wxButton* _copy;
+       JobManagerView* _jobs;
+       boost::optional<boost::filesystem::path> _dcp_path;
+       std::vector<Drive> _drives;
+       boost::process::child* _writer;
+       Nanomsg _nanomsg;
+       wxSizer* _sizer;
+};
+
+class App : public wxApp
+{
+public:
+       App ()
+               : _frame (0)
+       {}
+
+       bool OnInit ()
+       {
+               try {
+                       Config::FailedToLoad.connect (boost::bind (&App::config_failed_to_load, this));
+                       Config::Warning.connect (boost::bind (&App::config_warning, this, _1));
+
+                       SetAppName (_("DCP-o-matic Disk Writer"));
+
+                       if (!wxApp::OnInit()) {
+                               return false;
+                       }
+
+#ifdef DCPOMATIC_LINUX
+                       unsetenv ("UBUNTU_MENUPROXY");
+#endif
+
+#ifdef __WXOSX__
+                       ProcessSerialNumber serial;
+                       GetCurrentProcess (&serial);
+                       TransformProcessType (&serial, kProcessTransformToForegroundApplication);
+#endif
+
+                       dcpomatic_setup_path_encoding ();
+
+                       /* Enable i18n; this will create a Config object
+                          to look for a force-configured language.  This Config
+                          object will be wrong, however, because dcpomatic_setup
+                          hasn't yet been called and there aren't any filters etc.
+                          set up yet.
+                       */
+                       dcpomatic_setup_i18n ();
+
+                       /* Set things up, including filters etc.
+                          which will now be internationalised correctly.
+                       */
+                       dcpomatic_setup ();
+
+                       /* Force the configuration to be re-loaded correctly next
+                          time it is needed.
+                       */
+                       Config::drop ();
+
+                       _frame = new DOMFrame (_("DCP-o-matic Disk Writer"));
+                       SetTopWindow (_frame);
+
+                       _frame->Show ();
+
+                       signal_manager = new wxSignalManager (this);
+                       Bind (wxEVT_IDLE, boost::bind (&App::idle, this, _1));
+               }
+               catch (exception& e)
+               {
+                       error_dialog (0, wxString::Format ("DCP-o-matic could not start."), std_to_wx(e.what()));
+               }
+
+               return true;
+       }
+
+       void config_failed_to_load ()
+       {
+               message_dialog (_frame, _("The existing configuration failed to load.  Default values will be used instead.  These may take a short time to create."));
+       }
+
+       void config_warning (string m)
+       {
+               message_dialog (_frame, std_to_wx(m));
+       }
+
+       void idle (wxIdleEvent& ev)
+       {
+               signal_manager->ui_idle ();
+               ev.Skip ();
+       }
+
+       DOMFrame* _frame;
+};
+
+IMPLEMENT_APP (App)
diff --git a/src/tools/dcpomatic_disk_writer.cc b/src/tools/dcpomatic_disk_writer.cc
new file mode 100644 (file)
index 0000000..0bd5dcb
--- /dev/null
@@ -0,0 +1,474 @@
+/*
+    Copyright (C) 2019-2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "lib/disk_writer_messages.h"
+#include "lib/compose.hpp"
+#include "lib/exceptions.h"
+#include "lib/cross.h"
+#include "lib/digester.h"
+#include "lib/file_log.h"
+#include "lib/dcpomatic_log.h"
+#include "lib/nanomsg.h"
+extern "C" {
+#include <lwext4/ext4_mbr.h>
+#include <lwext4/ext4_fs.h>
+#include <lwext4/ext4_mkfs.h>
+#include <lwext4/ext4_errno.h>
+#include <lwext4/ext4_debug.h>
+#include <lwext4/ext4.h>
+}
+
+#ifdef DCPOMATIC_POSIX
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#endif
+
+#ifdef DCPOMATIC_OSX
+#undef nil
+extern "C" {
+#include <lwext4/file_dev.h>
+}
+#endif
+
+#ifdef DCPOMATIC_LINUX
+#include <linux/fs.h>
+#include <polkit/polkit.h>
+extern "C" {
+#include <lwext4/file_dev.h>
+}
+#include <poll.h>
+#endif
+
+#ifdef DCPOMATIC_WINDOWS
+extern "C" {
+#include <lwext4/file_windows.h>
+}
+#endif
+
+#include <glibmm.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <boost/filesystem.hpp>
+#include <boost/algorithm/string.hpp>
+#include <iostream>
+
+using std::cin;
+using std::min;
+using std::string;
+using std::runtime_error;
+using std::exception;
+using boost::optional;
+
+#ifdef DCPOMATIC_LINUX
+static PolkitAuthority* polkit_authority = 0;
+#endif
+static boost::filesystem::path dcp_path;
+static std::string device;
+static uint64_t const block_size = 4096;
+static Nanomsg* nanomsg = 0;
+
+static
+void
+count (boost::filesystem::path dir, uint64_t& total_bytes)
+{
+       using namespace boost::filesystem;
+       for (directory_iterator i = directory_iterator(dir); i != directory_iterator(); ++i) {
+               if (is_directory(*i)) {
+                       count (*i, total_bytes);
+               } else {
+                       total_bytes += file_size (*i);
+               }
+       }
+}
+
+static
+string
+write (boost::filesystem::path from, boost::filesystem::path to, uint64_t& total_remaining, uint64_t total)
+{
+       ext4_file out;
+       int r = ext4_fopen(&out, to.generic_string().c_str(), "wb");
+       if (r != EOK) {
+               throw CopyError (String::compose("Failed to open file %1", to.generic_string()), r);
+       }
+
+       FILE* in = fopen_boost (from, "rb");
+       if (!in) {
+               ext4_fclose (&out);
+               throw CopyError (String::compose("Failed to open file %1", from.string()), 0);
+       }
+
+       uint8_t* buffer = new uint8_t[block_size];
+       Digester digester;
+
+       uint64_t remaining = file_size (from);
+       while (remaining > 0) {
+               uint64_t const this_time = min(remaining, block_size);
+               size_t read = fread (buffer, 1, this_time, in);
+               if (read != this_time) {
+                       fclose (in);
+                       ext4_fclose (&out);
+                       delete[] buffer;
+                       throw CopyError (String::compose("Short read; expected %1 but read %2", this_time, read), 0);
+               }
+
+               digester.add (buffer, this_time);
+
+               size_t written;
+               r = ext4_fwrite (&out, buffer, this_time, &written);
+               if (r != EOK) {
+                       fclose (in);
+                       ext4_fclose (&out);
+                       delete[] buffer;
+                       throw CopyError ("Write failed", r);
+               }
+               if (written != this_time) {
+                       fclose (in);
+                       ext4_fclose (&out);
+                       delete[] buffer;
+                       throw CopyError (String::compose("Short write; expected %1 but wrote %2", this_time, written), 0);
+               }
+               remaining -= this_time;
+               total_remaining -= this_time;
+               nanomsg->blocking_send(String::compose(DISK_WRITER_PROGRESS "\n%1\n", (1 - float(total_remaining) / total)));
+       }
+
+       fclose (in);
+       ext4_fclose (&out);
+       delete[] buffer;
+
+       return digester.get ();
+}
+
+static
+string
+read (boost::filesystem::path from, boost::filesystem::path to, uint64_t& total_remaining, uint64_t total)
+{
+       ext4_file in;
+       LOG_DISK("Opening %1 for read", to.generic_string());
+       int r = ext4_fopen(&in, to.generic_string().c_str(), "rb");
+       if (r != EOK) {
+               throw VerifyError (String::compose("Failed to open file %1", to.generic_string()), r);
+       }
+       LOG_DISK("Opened %1 for read", to.generic_string());
+
+       uint8_t* buffer = new uint8_t[block_size];
+       Digester digester;
+
+       uint64_t remaining = file_size (from);
+       while (remaining > 0) {
+               uint64_t const this_time = min(remaining, block_size);
+               size_t read;
+               r = ext4_fread (&in, buffer, this_time, &read);
+               if (read != this_time) {
+                       ext4_fclose (&in);
+                       delete[] buffer;
+                       throw VerifyError (String::compose("Short read; expected %1 but read %2", this_time, read), 0);
+               }
+
+               digester.add (buffer, this_time);
+               remaining -= this_time;
+               total_remaining -= this_time;
+               nanomsg->blocking_send(String::compose(DISK_WRITER_PROGRESS "\n%1\n", (1 - float(total_remaining) / total)));
+       }
+
+       ext4_fclose (&in);
+       delete[] buffer;
+
+       return digester.get ();
+}
+
+
+/** @param from File to copy from.
+ *  @param to Directory to copy to.
+ */
+static
+void
+copy (boost::filesystem::path from, boost::filesystem::path to, uint64_t& total_remaining, uint64_t total)
+{
+       LOG_DISK ("Copy %1 -> %2", from.string(), to.generic_string());
+
+       using namespace boost::filesystem;
+
+       path const cr = to / from.filename();
+
+       if (is_directory(from)) {
+               int r = ext4_dir_mk (cr.generic_string().c_str());
+               if (r != EOK) {
+                       throw CopyError (String::compose("Failed to create directory %1", cr.generic_string()), r);
+               }
+
+               for (directory_iterator i = directory_iterator(from); i != directory_iterator(); ++i) {
+                       copy (i->path(), cr, total_remaining, total);
+               }
+       } else {
+               string const write_digest = write (from, cr, total_remaining, total);
+               LOG_DISK ("Wrote %1 %2 with %3", from.string(), cr.generic_string(), write_digest);
+               string const read_digest = read (from, cr, total_remaining, total);
+               LOG_DISK ("Read %1 %2 with %3", from.string(), cr.generic_string(), write_digest);
+               if (write_digest != read_digest) {
+                       throw VerifyError ("Hash of written data is incorrect", 0);
+               }
+       }
+}
+
+static
+void
+write ()
+try
+{
+//     ext4_dmask_set (DEBUG_ALL);
+
+       /* We rely on static initialization for these */
+       static struct ext4_fs fs;
+       static struct ext4_mkfs_info info;
+       info.block_size = 1024;
+       info.inode_size = 128;
+       info.journal = false;
+
+#ifdef WIN32
+       file_windows_name_set(device.c_str());
+       struct ext4_blockdev* bd = file_windows_dev_get();
+#else
+       file_dev_name_set (device.c_str());
+       struct ext4_blockdev* bd = file_dev_get ();
+#endif
+
+       if (!bd) {
+               throw CopyError ("Failed to open drive", 0);
+       }
+       LOG_DISK_NC ("Opened drive");
+
+       struct ext4_mbr_parts parts;
+       parts.division[0] = 100;
+       parts.division[1] = 0;
+       parts.division[2] = 0;
+       parts.division[3] = 0;
+
+#ifdef DCPOMATIC_LINUX
+       PrivilegeEscalator e;
+#endif
+
+       /* XXX: not sure if disk_id matters */
+       int r = ext4_mbr_write (bd, &parts, 0);
+
+       if (r) {
+               throw CopyError ("Failed to write MBR", r);
+       }
+       LOG_DISK_NC ("Wrote MBR");
+
+#ifdef DCPOMATIC_WINDOWS
+       struct ext4_mbr_bdevs bdevs;
+       r = ext4_mbr_scan (bd, &bdevs);
+       if (r != EOK) {
+               throw CopyError ("Failed to read MBR", r);
+       }
+
+       file_windows_partition_set (bdevs.partitions[0].part_offset, bdevs.partitions[0].part_size);
+#endif
+
+#ifdef DCPOMATIC_LINUX
+       /* Re-read the partition table */
+       int fd = open(device.c_str(), O_RDONLY);
+       ioctl(fd, BLKRRPART, NULL);
+       close(fd);
+#endif
+
+#ifdef DCPOMATIC_LINUX
+       string partition = device;
+       /* XXX: don't know if this logic is sensible */
+       if (partition.size() > 0 && isdigit(partition[partition.length() - 1])) {
+               partition += "p1";
+       } else {
+               partition += "1";
+       }
+       file_dev_name_set (partition.c_str());
+       bd = file_dev_get ();
+#endif
+
+#ifdef DCPOMATIC_OSX
+       string partition = device + "s1";
+       file_dev_name_set (partition.c_str());
+       bd = file_dev_get ();
+#endif
+
+       if (!bd) {
+               throw CopyError ("Failed to open partition", 0);
+       }
+       LOG_DISK_NC ("Opened partition");
+
+       nanomsg->blocking_send(DISK_WRITER_FORMATTING "\n");
+
+       r = ext4_mkfs(&fs, bd, &info, F_SET_EXT4);
+       if (r != EOK) {
+               throw CopyError ("Failed to make filesystem", r);
+       }
+       LOG_DISK_NC ("Made filesystem");
+
+       r = ext4_device_register(bd, "ext4_fs");
+       if (r != EOK) {
+               throw CopyError ("Failed to register device", r);
+       }
+       LOG_DISK_NC ("Registered device");
+
+       r = ext4_mount("ext4_fs", "/mp/", false);
+       if (r != EOK) {
+               throw CopyError ("Failed to mount device", r);
+       }
+       LOG_DISK_NC ("Mounted device");
+
+       uint64_t total_bytes = 0;
+       count (dcp_path, total_bytes);
+
+       /* XXX: this is a hack.  We are going to "treat" every byte twice; write it, and then verify it.  Double the
+        * bytes totals so that progress works itself out (assuming write is the same speed as read).
+        */
+       total_bytes *= 2;
+       copy (dcp_path, "/mp", total_bytes, total_bytes);
+
+       r = ext4_umount("/mp/");
+       if (r != EOK) {
+               throw CopyError ("Failed to unmount device", r);
+       }
+
+       ext4_device_unregister("ext4_fs");
+       nanomsg->blocking_send(DISK_WRITER_OK "\n");
+} catch (CopyError& e) {
+       LOG_DISK("CopyError: %1 %2", e.message(), e.number());
+       nanomsg->blocking_send(String::compose(DISK_WRITER_ERROR "\n%1\n%2\n", e.message(), e.number()));
+} catch (VerifyError& e) {
+       LOG_DISK("VerifyError: %1 %2", e.message(), e.number());
+       nanomsg->blocking_send(String::compose(DISK_WRITER_ERROR "\n%1\n%2\n", e.message(), e.number()));
+} catch (exception& e) {
+       LOG_DISK("Exception: %1", e.what());
+       nanomsg->blocking_send(String::compose(DISK_WRITER_ERROR "\n%1\n0\n", e.what()));
+}
+
+#ifdef DCPOMATIC_LINUX
+static
+void
+polkit_callback (GObject *, GAsyncResult* res, gpointer)
+{
+       PolkitAuthorizationResult* result = polkit_authority_check_authorization_finish (polkit_authority, res, 0);
+       if (result && polkit_authorization_result_get_is_authorized(result)) {
+               write ();
+       }
+       if (result) {
+               g_object_unref (result);
+       }
+}
+#endif
+
+bool
+idle ()
+{
+       using namespace boost::algorithm;
+
+       optional<string> s = nanomsg->nonblocking_get ();
+       if (!s) {
+               return true;
+       }
+
+       if (*s == "Q") {
+               exit (EXIT_SUCCESS);
+       } else if (*s == "W") {
+               dcp_path = nanomsg->blocking_get();
+               device = nanomsg->blocking_get();
+
+               /* Do some basic sanity checks; this is a bit belt-and-braces but it can't hurt... */
+
+#ifdef DCPOMATIC_OSX
+               if (!starts_with(device, "/dev/disk")) {
+                       LOG_DISK ("Will not write to %1", device);
+                       nanomsg->blocking_send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n");
+                       return true;
+               }
+#endif
+#ifdef DCPOMATIC_LINUX
+               if (!starts_with(device, "/dev/sd") && !starts_with(device, "/dev/hd")) {
+                       LOG_DISK ("Will not write to %1", device);
+                       nanomsg->blocking_send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n");
+                       return true;
+               }
+#endif
+#ifdef DCPOMATIC_WINDOWS
+               if (!starts_with(device, "\\\\.\\PHYSICALDRIVE")) {
+                       LOG_DISK ("Will not write to %1", device);
+                       nanomsg->blocking_send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n");
+                       return true;
+               }
+#endif
+
+               bool on_drive_list = false;
+               bool mounted = false;
+               for (auto const& i: get_drives()) {
+                       if (i.internal_name() == device) {
+                               on_drive_list = true;
+                               mounted = i.mounted();
+                       }
+               }
+
+               if (!on_drive_list) {
+                       LOG_DISK ("Will not write to %1 as it's not recognised as a drive", device);
+                       nanomsg->blocking_send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n");
+                       return true;
+               }
+               if (mounted) {
+                       LOG_DISK ("Will not write to %1 as it's mounted", device);
+                       nanomsg->blocking_send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n");
+                       return true;
+               }
+
+               LOG_DISK ("Here we go writing %1 to %2", dcp_path, device);
+
+#ifdef DCPOMATIC_LINUX
+               polkit_authority = polkit_authority_get_sync (0, 0);
+               PolkitSubject* subject = polkit_unix_process_new (getppid());
+               polkit_authority_check_authorization (
+                               polkit_authority, subject, "com.dcpomatic.write-drive", 0, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, 0, polkit_callback, 0
+                               );
+#else
+               write ();
+#endif
+       }
+
+       return true;
+}
+
+int
+main ()
+{
+       /* XXX: this is a hack, but I expect we'll need logs and I'm not sure if there's
+        * a better place to put them.
+        */
+       dcpomatic_log.reset(new FileLog(config_path() / "disk_writer.log", LogEntry::TYPE_DISK));
+       LOG_DISK_NC("dcpomatic_disk_writer started");
+
+       try {
+               nanomsg = new Nanomsg (false);
+       } catch (runtime_error& e) {
+               LOG_DISK_NC("Could not set up nanomsg socket");
+               exit (EXIT_FAILURE);
+       }
+
+       Glib::RefPtr<Glib::MainLoop> ml = Glib::MainLoop::create ();
+       Glib::signal_timeout().connect(sigc::ptr_fun(&idle), 500);
+       ml->run ();
+}
index 8af9e7589f1fe744f68ac96c740cde5cdd4b3fde..c7c953a318b04eb3534679f8bbfef97e6ea871a7 100644 (file)
@@ -32,8 +32,15 @@ def build(bld):
     uselib += 'AVUTIL SWSCALE SWRESAMPLE POSTPROC CURL BOOST_FILESYSTEM SSH ZIP CAIROMM FONTCONFIG PANGOMM SUB '
     uselib += 'SNDFILE SAMPLERATE BOOST_REGEX ICU NETTLE RTAUDIO PNG '
 
     uselib += 'AVUTIL SWSCALE SWRESAMPLE POSTPROC CURL BOOST_FILESYSTEM SSH ZIP CAIROMM FONTCONFIG PANGOMM SUB '
     uselib += 'SNDFILE SAMPLERATE BOOST_REGEX ICU NETTLE RTAUDIO PNG '
 
+    if bld.env.ENABLE_DISK:
+        if bld.env.TARGET_LINUX:
+            uselib += 'POLKIT '
+        uselib += 'LWEXT4 NANOMSG '
+
     if bld.env.TARGET_WINDOWS:
         uselib += 'WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE WINSOCK2 OLE32 DSOUND WINMM KSUSER '
     if bld.env.TARGET_WINDOWS:
         uselib += 'WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE WINSOCK2 OLE32 DSOUND WINMM KSUSER '
+    if bld.env.TARGET_LINUX:
+        uselib += 'DL '
 
     cli_tools = []
     if bld.env.VARIANT == 'swaroop-theater':
 
     cli_tools = []
     if bld.env.VARIANT == 'swaroop-theater':
@@ -42,6 +49,8 @@ def build(bld):
         cli_tools = ['dcpomatic_cli', 'dcpomatic_server_cli', 'server_test', 'dcpomatic_kdm_cli', 'dcpomatic_create', 'swaroop_dcpomatic_ecinema', 'swaroop_dcpomatic_uuid']
     else:
         cli_tools = ['dcpomatic_cli', 'dcpomatic_server_cli', 'server_test', 'dcpomatic_kdm_cli', 'dcpomatic_create']
         cli_tools = ['dcpomatic_cli', 'dcpomatic_server_cli', 'server_test', 'dcpomatic_kdm_cli', 'dcpomatic_create', 'swaroop_dcpomatic_ecinema', 'swaroop_dcpomatic_uuid']
     else:
         cli_tools = ['dcpomatic_cli', 'dcpomatic_server_cli', 'server_test', 'dcpomatic_kdm_cli', 'dcpomatic_create']
+        if bld.env.ENABLE_DISK:
+            cli_tools.append('dcpomatic_disk_writer')
 
     for t in cli_tools:
         obj = bld(features='cxx cxxprogram')
 
     for t in cli_tools:
         obj = bld(features='cxx cxxprogram')
@@ -49,6 +58,10 @@ def build(bld):
         obj.includes = ['..']
         obj.use    = ['libdcpomatic2']
         obj.source = '%s.cc' % t
         obj.includes = ['..']
         obj.use    = ['libdcpomatic2']
         obj.source = '%s.cc' % t
+        if bld.env.TARGET_WINDOWS and t == 'dcpomatic_disk_writer':
+            obj.source += ' ../../platform/windows/%s.rc' % t
+            # Prevent a console window opening when we start dcpomatic2_disk_writer
+            bld.env.LINKFLAGS.append('-Wl,-subsystem,windows')
         obj.target = t.replace('dcpomatic', 'dcpomatic2').replace('swaroop_', '')
         if t == 'server_test':
             obj.install_path = None
         obj.target = t.replace('dcpomatic', 'dcpomatic2').replace('swaroop_', '')
         if t == 'server_test':
             obj.install_path = None
@@ -61,6 +74,8 @@ def build(bld):
             gui_tools = ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server', 'dcpomatic_kdm', 'dcpomatic_player', 'swaroop_dcpomatic_playlist']
         else:
             gui_tools = ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server', 'dcpomatic_kdm', 'dcpomatic_player', 'dcpomatic_playlist']
             gui_tools = ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server', 'dcpomatic_kdm', 'dcpomatic_player', 'swaroop_dcpomatic_playlist']
         else:
             gui_tools = ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server', 'dcpomatic_kdm', 'dcpomatic_player', 'dcpomatic_playlist']
+            if bld.env.ENABLE_DISK:
+                gui_tools.append('dcpomatic_disk')
 
     for t in gui_tools:
         obj = bld(features='cxx cxxprogram')
 
     for t in gui_tools:
         obj = bld(features='cxx cxxprogram')
diff --git a/src/wx/drive_wipe_warning_dialog.cc b/src/wx/drive_wipe_warning_dialog.cc
new file mode 100644 (file)
index 0000000..2eb0b9d
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+    Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "drive_wipe_warning_dialog.h"
+#include "static_text.h"
+#include "wx_util.h"
+
+DriveWipeWarningDialog::DriveWipeWarningDialog (wxWindow* parent, wxString drive)
+       : wxDialog (parent, wxID_ANY, _("Important notice"))
+{
+       wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+       wxStaticText* text = new StaticText (this, wxEmptyString, wxDefaultPosition, wxSize(400, 300));
+       sizer->Add (text, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+       _yes = new wxTextCtrl (this, wxID_ANY);
+       sizer->Add (_yes, 0, wxALL, DCPOMATIC_DIALOG_BORDER);
+
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL);
+       if (buttons) {
+               sizer->Add(buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
+
+       SetSizer (sizer);
+       sizer->Layout ();
+       sizer->SetSizeHints (this);
+
+       text->SetLabelMarkup (
+               wxString::Format(
+                       _("If you continue with this operation <span weight=\"bold\" size=\"larger\">ALL DATA</span> "
+                         "on the drive %s will be <span weight=\"bold\" size=\"larger\">PERMANENTLY DESTROYED</span>.\n\n"
+                         "If you are sure you want to continue please type \"yes\" into the box below, then click OK."), drive
+                       )
+               );
+}
+
+bool
+DriveWipeWarningDialog::confirmed () const
+{
+       return _yes->GetValue() == "yes";
+}
diff --git a/src/wx/drive_wipe_warning_dialog.h b/src/wx/drive_wipe_warning_dialog.h
new file mode 100644 (file)
index 0000000..1043e7f
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+    Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include <wx/wx.h>
+
+class DriveWipeWarningDialog : public wxDialog
+{
+public:
+       DriveWipeWarningDialog (wxWindow* parent, wxString drive);
+
+       bool confirmed () const;
+
+private:
+       wxTextCtrl* _yes;
+};
index 66c88f8d00e298a7b6239616318ac700f05be30a..87ae38aac6cc6bb9758856b70cae01c4b677ba2e 100644 (file)
@@ -51,6 +51,7 @@ sources = """
           credentials_download_certificate_panel.cc
           dcp_panel.cc
           dcpomatic_button.cc
           credentials_download_certificate_panel.cc
           dcp_panel.cc
           dcpomatic_button.cc
+          drive_wipe_warning_dialog.cc
           email_dialog.cc
           image_sequence_dialog.cc
           isdcf_metadata_dialog.cc
           email_dialog.cc
           image_sequence_dialog.cc
           isdcf_metadata_dialog.cc
index 0e777c803ddb3e0c83de588bcbfd91b106ad6adc..a1a78eaea01dffb1d88de1cefddce6e877fb9364 100644 (file)
@@ -39,6 +39,8 @@ def build(bld):
     obj.uselib += 'AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE SWRESAMPLE POSTPROC CXML SUB GLIB CURL SSH XMLSEC BOOST_REGEX ICU NETTLE MAGICK PNG '
     if bld.env.TARGET_WINDOWS:
         obj.uselib += 'WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE '
     obj.uselib += 'AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE SWRESAMPLE POSTPROC CXML SUB GLIB CURL SSH XMLSEC BOOST_REGEX ICU NETTLE MAGICK PNG '
     if bld.env.TARGET_WINDOWS:
         obj.uselib += 'WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE '
+    if bld.env.TARGET_LINUX:
+        obj.uselib += 'DL '
     obj.use    = 'libdcpomatic2'
     obj.source = """
                  4k_test.cc
     obj.use    = 'libdcpomatic2'
     obj.source = """
                  4k_test.cc
diff --git a/wscript b/wscript
index fe14313160623cc5f167b0aebd30d3c2c1ca7580..a94fee8bae830ee8985275671cbcfbb5d96785dd 100644 (file)
--- a/wscript
+++ b/wscript
@@ -74,6 +74,7 @@ def options(opt):
     opt.add_option('--force-cpp11',       action='store_true', default=False, help='force use of C++11')
     opt.add_option('--variant',           help='build variant (swaroop-studio, swaroop-theater)', choices=['swaroop-studio', 'swaroop-theater'])
     opt.add_option('--use-lld',           action='store_true', default=False, help='use lld linker')
     opt.add_option('--force-cpp11',       action='store_true', default=False, help='force use of C++11')
     opt.add_option('--variant',           help='build variant (swaroop-studio, swaroop-theater)', choices=['swaroop-studio', 'swaroop-theater'])
     opt.add_option('--use-lld',           action='store_true', default=False, help='use lld linker')
+    opt.add_option('--enable-disk',       action='store_true', default=False, help='build dcpomatic2_disk tool; requires Boost process and lwext4 libraries')
 
 def configure(conf):
     conf.load('compiler_cxx')
 
 def configure(conf):
     conf.load('compiler_cxx')
@@ -90,6 +91,7 @@ def configure(conf):
     conf.env.VERSION = VERSION
     conf.env.DEBUG = conf.options.enable_debug
     conf.env.STATIC_DCPOMATIC = conf.options.static_dcpomatic
     conf.env.VERSION = VERSION
     conf.env.DEBUG = conf.options.enable_debug
     conf.env.STATIC_DCPOMATIC = conf.options.static_dcpomatic
+    conf.env.ENABLE_DISK = conf.options.enable_disk
     if conf.options.install_prefix is None:
         conf.env.INSTALL_PREFIX = conf.env.PREFIX
     else:
     if conf.options.install_prefix is None:
         conf.env.INSTALL_PREFIX = conf.env.PREFIX
     else:
@@ -167,8 +169,9 @@ def configure(conf):
         conf.check(lib='dsound', uselib_store='DSOUND', msg="Checking for library dsound")
         conf.check(lib='winmm', uselib_store='WINMM', msg="Checking for library winmm")
         conf.check(lib='ksuser', uselib_store='KSUSER', msg="Checking for library ksuser")
         conf.check(lib='dsound', uselib_store='DSOUND', msg="Checking for library dsound")
         conf.check(lib='winmm', uselib_store='WINMM', msg="Checking for library winmm")
         conf.check(lib='ksuser', uselib_store='KSUSER', msg="Checking for library ksuser")
+        conf.check(lib='setupapi', uselib_store='SETUPAPI', msg="Checking for library setupapi")
         boost_lib_suffix = '-mt'
         boost_lib_suffix = '-mt'
-        boost_thread = 'boost_thread_win32-mt'
+        boost_thread = 'boost_thread-mt'
         conf.check_cxx(fragment="""
                                #include <boost/locale.hpp>\n
                                int main() { std::locale::global (boost::locale::generator().generate ("")); }\n
         conf.check_cxx(fragment="""
                                #include <boost/locale.hpp>\n
                                int main() { std::locale::global (boost::locale::generator().generate ("")); }\n
@@ -192,6 +195,7 @@ def configure(conf):
         conf.env.append_value('CXXFLAGS', '-DLINUX_SHARE_PREFIX="%s/share/dcpomatic2"' % conf.env['INSTALL_PREFIX'])
         conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_LINUX')
         conf.env.append_value('CXXFLAGS', ['-Wlogical-op', '-Wcast-align'])
         conf.env.append_value('CXXFLAGS', '-DLINUX_SHARE_PREFIX="%s/share/dcpomatic2"' % conf.env['INSTALL_PREFIX'])
         conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_LINUX')
         conf.env.append_value('CXXFLAGS', ['-Wlogical-op', '-Wcast-align'])
+        conf.check(lib='dl', uselib_store='DL', msg='Checking for library dl')
         if not conf.env.DISABLE_GUI:
             conf.check_cfg(package='gtk+-2.0', args='--cflags --libs', uselib_store='GTK', mandatory=True)
 
         if not conf.env.DISABLE_GUI:
             conf.check_cfg(package='gtk+-2.0', args='--cflags --libs', uselib_store='GTK', mandatory=True)
 
@@ -366,6 +370,25 @@ def configure(conf):
     # libpng
     conf.check_cfg(package='libpng', args='--cflags --libs', uselib_store='PNG', mandatory=True)
 
     # libpng
     conf.check_cfg(package='libpng', args='--cflags --libs', uselib_store='PNG', mandatory=True)
 
+    # lwext4
+    if conf.options.enable_disk:
+        conf.check_cxx(fragment="""
+                                #include <lwext4/ext4.h>\n
+                                int main() { ext4_mount("ext4_fs", "/mp/", false); }\n
+                                """,
+                                msg='Checking for lwext4 library',
+                                libpath='/usr/local/lib',
+                                lib=['lwext4', 'blockdev'],
+                                uselib_store='LWEXT4')
+
+    if conf.env.TARGET_LINUX and conf.options.enable_disk:
+        conf.check_cfg(package='polkit-gobject-1', args='--cflags --libs', uselib_store='POLKIT', mandatory=True)
+
+    # nanomsg
+    if conf.options.enable_disk:
+        if conf.check_cfg(package='nanomsg', args='--cflags --libs', uselib_store='NANOMSG', mandatory=False) is None:
+            conf.check_cfg(package='libnanomsg', args='--cflags --libs', uselib_store='NANOMSG', mandatory=True)
+
     # FFmpeg
     if conf.options.static_ffmpeg:
         names = ['avformat', 'avfilter', 'avcodec', 'avutil', 'swscale', 'postproc', 'swresample']
     # FFmpeg
     if conf.options.static_ffmpeg:
         names = ['avformat', 'avfilter', 'avcodec', 'avutil', 'swscale', 'postproc', 'swresample']
@@ -510,6 +533,21 @@ def configure(conf):
                        lib=['boost_regex%s' % boost_lib_suffix],
                        uselib_store='BOOST_REGEX')
 
                        lib=['boost_regex%s' % boost_lib_suffix],
                        uselib_store='BOOST_REGEX')
 
+        # Really just checking for the header here (there's no associated library) but the test
+        # program has to link with boost_system so I'm doing it this way.
+        if conf.options.enable_disk:
+            deps = ['boost_system%s' % boost_lib_suffix]
+            if conf.env.TARGET_WINDOWS:
+                deps.append('ws2_32')
+                deps.append('boost_filesystem%s' % boost_lib_suffix)
+            conf.check_cxx(fragment="""
+                                #include <boost/process.hpp>\n
+                                int main() { boost::process::child* c = new boost::process::child("foo"); }\n
+                                """,
+                           msg='Checking for boost process library',
+                           lib=deps,
+                           uselib_store='BOOST_PROCESS')
+
     # libxml++ requires glibmm and versions of glibmm 2.45.31 and later
     # must be built with -std=c++11 as they use c++11
     # features and c++11 is not (yet) the default in gcc.
     # libxml++ requires glibmm and versions of glibmm 2.45.31 and later
     # must be built with -std=c++11 as they use c++11
     # features and c++11 is not (yet) the default in gcc.
@@ -657,14 +695,16 @@ def create_version_cc(version, cxx_flags):
 def post(ctx):
     if ctx.cmd == 'install' and ctx.env.TARGET_LINUX:
         ctx.exec_command('/sbin/ldconfig')
 def post(ctx):
     if ctx.cmd == 'install' and ctx.env.TARGET_LINUX:
         ctx.exec_command('/sbin/ldconfig')
-        # I can't find anything which tells me where things have been installed to,
-        # so here's some nasty hacks to guess.
-        debian = os.path.join(ctx.out_dir, '../debian/dcpomatic/usr/bin/dcpomatic2_uuid')
-        prefix = os.path.join(ctx.env['INSTALL_PREFIX'], 'bin/dcpomatic2_uuid')
-        if os.path.exists(debian):
-            os.chmod(debian, 0o4755)
-        if os.path.exists(prefix):
-            os.chmod(prefix, 0o4755)
+        # setuid root executables
+        for e in ['dcpomatic2_uuid', 'dcpomatic2_disk_writer']:
+            # I can't find anything which tells me where things have been installed to,
+            # so here's some nasty hacks to guess.
+            debian = os.path.join(ctx.out_dir, '../debian/dcpomatic/usr/bin/%s' % e)
+            prefix = os.path.join(ctx.env['INSTALL_PREFIX'], 'bin/%s' % e)
+            if os.path.exists(debian):
+                os.chmod(debian, 0o4755)
+            if os.path.exists(prefix):
+                os.chmod(prefix, 0o4755)
 
 def pot(bld):
     bld.recurse('src')
 
 def pot(bld):
     bld.recurse('src')