From: Carl Hetherington Date: Sun, 15 Mar 2020 23:44:31 +0000 (+0100) Subject: Add disk writer tool. X-Git-Tag: v2.15.48~3 X-Git-Url: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=a1f7bf2d9e5610075fbd898cdf52f4f8373741f2 Add disk writer tool. --- diff --git a/DEVELOP.md b/DEVELOP.md index 6896900dd..4d2b0fca8 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -47,7 +47,7 @@ The script can be run using something like 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. diff --git a/cscript b/cscript index eb7a817eb..1f2ec62a9 100644 --- a/cscript +++ b/cscript @@ -1,6 +1,6 @@ # -*- mode: python -*- # -# Copyright (C) 2012-2019 Carl Hetherington +# Copyright (C) 2012-2020 Carl Hetherington # # This file is part of DCP-o-matic. # @@ -196,6 +196,15 @@ deb_depends['unstable'].extend(['libboost-filesystem1.67.0', '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: @@ -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""" + 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) @@ -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 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) @@ -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) -def dependencies(target): +def dependencies(target, options): if target.platform == 'linux': ffmpeg_options = { 'shared': False } @@ -348,19 +361,23 @@ def dependencies(target): # 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')) + if can_build_disk(target): + deps.append(('lwext4', 'carl2')) return deps -def option_defaults(): - return { "gui": True, "variant": None } +option_defaults = { "gui": True, "variant": None, "disk": False } 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' + elif target.platform == 'osx' and target.bits == 64: + opt += ' --force-cpp11' 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 can_build_disk(target) and options['disk']: + opt += ' --enable-disk' + return opt def build(target, options): diff --git a/debian/rules b/debian/rules index 84cf3ffd0..c45682033 100755 --- a/debian/rules +++ b/debian/rules @@ -50,3 +50,4 @@ override_dh_shlibdeps: 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 index 000000000..eea4e234b 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 index 000000000..9184f8488 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 index 000000000..99f74a789 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 index 000000000..d049cc967 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 index 000000000..6f0605c52 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 index 000000000..b3d63e0af 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 index 000000000..4dd1d6598 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 index 000000000..d0a57a3f1 Binary files /dev/null and b/graphics/linux/64/dcpomatic2_disk.png differ diff --git a/graphics/osx/dcpomatic2.icns b/graphics/osx/dcpomatic2.icns index fdfa66155..e202cb09b 100644 Binary files a/graphics/osx/dcpomatic2.icns and b/graphics/osx/dcpomatic2.icns differ diff --git a/graphics/osx/dcpomatic2_batch.icns b/graphics/osx/dcpomatic2_batch.icns index 17a140579..73481f96f 100644 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 index 000000000..1d6beef3c 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 index 000000000..eea4e234b 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 index 000000000..eea4e234b 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 index 000000000..9184f8488 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 index 000000000..9184f8488 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 index 000000000..d049cc967 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 index 000000000..d049cc967 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 index 000000000..6f0605c52 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 index 000000000..6f0605c52 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 index 000000000..4dd1d6598 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 index 000000000..4dd1d6598 Binary files /dev/null and b/graphics/osx/dcpomatic2_disk.iconset/icon_512x512@2x.png differ diff --git a/graphics/osx/dcpomatic2_kdm.icns b/graphics/osx/dcpomatic2_kdm.icns index ac265cffe..3cde61af3 100644 Binary files a/graphics/osx/dcpomatic2_kdm.icns and b/graphics/osx/dcpomatic2_kdm.icns differ diff --git a/graphics/osx/dcpomatic2_player.icns b/graphics/osx/dcpomatic2_player.icns index dcdf416ec..f57bd7eb3 100644 Binary files a/graphics/osx/dcpomatic2_player.icns and b/graphics/osx/dcpomatic2_player.icns differ diff --git a/graphics/osx/dcpomatic2_playlist.icns b/graphics/osx/dcpomatic2_playlist.icns index f0d6e698a..64e971902 100644 Binary files a/graphics/osx/dcpomatic2_playlist.icns and b/graphics/osx/dcpomatic2_playlist.icns differ diff --git a/graphics/osx/dcpomatic2_server.icns b/graphics/osx/dcpomatic2_server.icns index e73a63cd4..6e1cba7f2 100644 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 index 000000000..d93c8c1f5 --- /dev/null +++ b/graphics/src/dcpomatic2_disk.svg @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + DCP + + + diff --git a/graphics/update b/graphics/update index 2e5bccd17..9b75493db 100755 --- a/graphics/update +++ b/graphics/update @@ -12,9 +12,11 @@ if [ `basename $pwd` != "graphics" ]; then 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 - 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 @@ -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 - 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 @@ -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 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 - 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 diff --git a/graphics/web/favicon-128x128.png b/graphics/web/favicon-128x128.png index 658bf796e..621455641 100644 Binary files a/graphics/web/favicon-128x128.png and b/graphics/web/favicon-128x128.png differ diff --git a/graphics/web/favicon-16x16.png b/graphics/web/favicon-16x16.png index 68e4f96c4..4c31c0258 100644 Binary files a/graphics/web/favicon-16x16.png and b/graphics/web/favicon-16x16.png differ diff --git a/graphics/web/favicon-32x32.png b/graphics/web/favicon-32x32.png index 75fd6ed71..847bc813a 100644 Binary files a/graphics/web/favicon-32x32.png and b/graphics/web/favicon-32x32.png differ diff --git a/graphics/web/favicon-64x64.png b/graphics/web/favicon-64x64.png index f7e5ca6fc..e836814d1 100644 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 index 000000000..3ec68e254 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 index 000000000..177146894 --- /dev/null +++ b/platform/osx/dcpomatic2_disk.Info.plist.in @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + dcpomatic2_disk + CFBundleGetInfoString + DCP-o-matic 2 Disk Writer + CFBundleIconFile + dcpomatic2_disk.icns + CFBundleIdentifier + com.dcpomatic.disk + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + DCP-o-matic 2 Disk Writer + CFBundlePackageType + APPL + CFBundleShortVersions + @VERSION@ + CFBundleSignature + DOMC + CFBundleVersion + @VERSION@ + CFBundleAllowMixedLocalizations + + LSUIElement + 0 + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/platform/osx/make_dmg.sh b/platform/osx/make_dmg.sh index 867200aa7..9099e5453 100644 --- a/platform/osx/make_dmg.sh +++ b/platform/osx/make_dmg.sh @@ -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 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" @@ -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_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" @@ -227,7 +230,7 @@ function copy_resources { } # 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=("$@") @@ -250,25 +253,61 @@ function relink { 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 #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" - 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 - 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 + if [ "$pkg" != "" ]; then + cp -a "$pkg" $vol_name + fi ln -s /Applications "$vol_name/Applications" cat< "$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 + if [ "$pkg" != "" ]; then + cat< "$vol_name/READ ME.txt" + +To run this software successfully you must install $pkg before running +the .app +EOF + fi + + if [ "$pkg" != "" ]; then + cat< "$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) @@ -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 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 @@ -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 "DCP-o-matic Disk Writer.pkg" of container window to {90, 255} 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) -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" @@ -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) -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" @@ -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) -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" @@ -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) -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" @@ -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) -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" @@ -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) -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 < + + + + Label + com.dcpomatic.disk.writer + ProgramArguments + + /Library/Application Support/com.dcpomatic/dcpomatic2_disk_writer + + EnvironmentVariables + + DYLD_LIBRARY_PATH + + + KeepAlive + + RunAtLoad + + Debug + + StandardOutPath + /Users/carl/damon.out.log + StandardErrorPath + /Users/carl/damon.err.log + + +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 <" + 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 index 000000000..f89a06add --- /dev/null +++ b/platform/windows/dcpomatic2_disk_debug.bat @@ -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 index 000000000..7d922a0db --- /dev/null +++ b/platform/windows/dcpomatic2_disk_writer.exe.manifest @@ -0,0 +1,12 @@ + + + + DCP-o-matic Disk Writer + + + + + + + + diff --git a/platform/windows/dcpomatic_disk.rc b/platform/windows/dcpomatic_disk.rc new file mode 100644 index 000000000..e10e89e2f --- /dev/null +++ b/platform/windows/dcpomatic_disk.rc @@ -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 index 000000000..70138ed12 --- /dev/null +++ b/platform/windows/dcpomatic_disk_writer.rc @@ -0,0 +1,2 @@ +#include "winuser.h" +1 RT_MANIFEST "dcpomatic2_disk_writer.exe.manifest" diff --git a/platform/windows/set_paths.bat b/platform/windows/set_paths.bat new file mode 100644 index 000000000..3a023fdd7 --- /dev/null +++ b/platform/windows/set_paths.bat @@ -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 diff --git a/platform/windows/wscript b/platform/windows/wscript index db19c750e..77f9bd0cc 100644 --- a/platform/windows/wscript +++ b/platform/windows/wscript @@ -1,7 +1,7 @@ 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: @@ -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_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/libeay32.dll" """, 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/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/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" @@ -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/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=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/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) + 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" @@ -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) + 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) @@ -338,6 +341,13 @@ File "%binaries%/src/tools/dcpomatic2_kdm.exe" 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" @@ -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) + 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) @@ -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) + 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) @@ -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) + 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) @@ -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) + if disk: + print('CreateShortCut "$DESKTOP\\DCP-o-matic 2 Disk Writer.lnk" "$INSTDIR\\bin\\dcpomatic2_disk.exe"', file=f) print("SectionEnd", file=f) @@ -396,7 +414,7 @@ SectionEnd 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) @@ -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) - 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 @@ -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 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" @@ -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 Disk Writer.lnk" 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): - 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 index 000000000..990800461 --- /dev/null +++ b/run/dcpomatic_disk @@ -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 index 000000000..58164e088 --- /dev/null +++ b/run/dcpomatic_disk_writer @@ -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 index 000000000..b7bb5a60d --- /dev/null +++ b/src/lib/copy_to_drive_job.cc @@ -0,0 +1,92 @@ +/* + Copyright (C) 2019-2020 Carl Hetherington + + 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 . + +*/ + +#include "disk_writer_messages.h" +#include "copy_to_drive_job.h" +#include "compose.hpp" +#include "exceptions.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#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()) + , _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(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(_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 index 000000000..1a1a99f44 --- /dev/null +++ b/src/lib/copy_to_drive_job.h @@ -0,0 +1,40 @@ +/* + Copyright (C) 2019-2020 Carl Hetherington + + 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 . + +*/ + +#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 index 5d35d5a4b..000000000 --- a/src/lib/cross.cc +++ /dev/null @@ -1,538 +0,0 @@ -/* - Copyright (C) 2012-2018 Carl Hetherington - - 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 . - -*/ - -#include "cross.h" -#include "compose.hpp" -#include "log.h" -#include "dcpomatic_log.h" -#include "config.h" -#include "exceptions.h" -extern "C" { -#include -} -#include -#ifdef DCPOMATIC_LINUX -#include -#include -#endif -#ifdef DCPOMATIC_WINDOWS -#include -#undef DATADIR -#include -#include -#include -#endif -#ifdef DCPOMATIC_OSX -#include -#include -#include -#endif -#ifdef DCPOMATIC_POSIX -#include -#include -#include -#include -#endif -#include - -#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 (&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 > -mount_info () -{ - list > 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; -} diff --git a/src/lib/cross.h b/src/lib/cross.h index 584fa2b42..20bab38a2 100644 --- a/src/lib/cross.h +++ b/src/lib/cross.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2018 Carl Hetherington + Copyright (C) 2012-2020 Carl Hetherington This file is part of DCP-o-matic. @@ -30,6 +30,7 @@ #endif #include #include +#include #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 > 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 @@ -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 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. @@ -82,4 +93,34 @@ private: #endif }; +class Drive +{ +public: + Drive (std::string internal_name, uint64_t size, bool mounted, boost::optional vendor, boost::optional 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 _vendor; + boost::optional _model; +}; + +std::vector get_drives (); + #endif diff --git a/src/lib/cross_common.cc b/src/lib/cross_common.cc new file mode 100644 index 000000000..b3a39402a --- /dev/null +++ b/src/lib/cross_common.cc @@ -0,0 +1,51 @@ +/* + Copyright (C) 2012-2020 Carl Hetherington + + 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 . + +*/ + +#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 index 000000000..406087917 --- /dev/null +++ b/src/lib/cross_linux.cc @@ -0,0 +1,340 @@ +/* + Copyright (C) 2012-2020 Carl Hetherington + + 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 . + +*/ + +#include "cross.h" +#include "compose.hpp" +#include "log.h" +#include "dcpomatic_log.h" +#include "config.h" +#include "exceptions.h" +#include +#include +extern "C" { +#include +} +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 > +mount_info () +{ + list > 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 +get_drives () +{ + vector drives; + + using namespace boost::filesystem; + list mounted_devices; + std::ifstream f("/proc/mounts"); + string line; + while (f.good()) { + getline(f, line); + vector 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 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(dcp::file_to_string(*i / "size")) * 512; + if (size == 0) { + continue; + } + bool mounted = false; + optional vendor; + try { + vendor = dcp::file_to_string("/sys/block/" + name + "/device/vendor"); + boost::trim(*vendor); + } catch (...) {} + optional 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 index 000000000..fa12fb380 --- /dev/null +++ b/src/lib/cross_osx.cc @@ -0,0 +1,472 @@ +/* + Copyright (C) 2012-2020 Carl Hetherington + + 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 . + +*/ + +#include "cross.h" +#include "compose.hpp" +#include "log.h" +#include "dcpomatic_log.h" +#include "config.h" +#include "exceptions.h" +#include +#include +extern "C" { +#include +} +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 > +mount_info () +{ + list > 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 +get_vendor (CFDictionaryRef& description) +{ + void const* str = CFDictionaryGetValue (description, kDADiskDescriptionDeviceVendorKey); + if (!str) { + return optional(); + } + + string s = CFStringGetCStringPtr ((CFStringRef) str, kCFStringEncodingUTF8); + boost::algorithm::trim (s); + return s; +} + +static optional +get_model (CFDictionaryRef& description) +{ + void const* str = CFDictionaryGetValue (description, kDADiskDescriptionDeviceModelKey); + if (!str) { + return optional(); + } + + 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 +analyse_media_path (CFDictionaryRef& description) +{ + using namespace boost::algorithm; + + void const* str = CFDictionaryGetValue (description, kDADiskDescriptionMediaPathKey); + if (!str) { + return optional(); + } + + 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(); + } + + vector 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 vendor; + optional 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 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*>(context)->push_back(this_disk); +} + +vector +get_drives () +{ + using namespace boost::algorithm; + vector disks; + + DASessionRef session = DASessionCreate(kCFAllocatorDefault); + if (!session) { + return vector(); + } + + 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 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 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 index 000000000..169435a51 --- /dev/null +++ b/src/lib/cross_windows.cc @@ -0,0 +1,568 @@ +/* + Copyright (C) 2012-2020 Carl Hetherington + + 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 . + +*/ + +#include "cross.h" +#include "compose.hpp" +#include "log.h" +#include "dcpomatic_log.h" +#include "config.h" +#include "exceptions.h" +#include "dcpomatic_assert.h" +#include +#include +extern "C" { +#include +} +#include +#include +#include +#include +#include +#include +#include +#include +#undef DATADIR +#include +#include +#include +#include + +#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 (&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 > +mount_info () +{ + list > 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 +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(buffer), sizeof(buffer), 0 + ); + if (!r) { + return optional(); + } + 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 +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(); + } + + /* 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 (malloc(size)); + if (!device_detail_data) { + LOG_DISK_NC("malloc failed"); + return optional(); + } + + 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(); + } + + /* 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(); + } + + /* 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(); + } + + 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& 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 +disk_numbers_with_volumes () +{ + vector 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 +get_drives () +{ + vector drives; + + vector 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 const friendly_name = get_friendly_name (device_info, &device_info_data); + optional 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())); + } + + 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; +} diff --git a/src/lib/dcpomatic_log.h b/src/lib/dcpomatic_log.h index dbc850036..382a586d4 100644 --- a/src/lib/dcpomatic_log.h +++ b/src/lib/dcpomatic_log.h @@ -36,3 +36,5 @@ extern boost::shared_ptr 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_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 index 000000000..61cdbcfbd --- /dev/null +++ b/src/lib/disk_writer_messages.h @@ -0,0 +1,77 @@ +/* + Copyright (C) 2020 Carl Hetherington + + 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 . + +*/ + +/* 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" + diff --git a/src/lib/exceptions.cc b/src/lib/exceptions.cc index ba3d4a05c..d394ad4b2 100644 --- a/src/lib/exceptions.cc +++ b/src/lib/exceptions.cc @@ -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) +{ + +} + diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h index 73b8cc85a..0f8a2eda2 100644 --- a/src/lib/exceptions.h +++ b/src/lib/exceptions.h @@ -314,5 +314,47 @@ public: 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 diff --git a/src/lib/file_log.cc b/src/lib/file_log.cc index f6eaa58f8..b9aa84c3d 100644 --- a/src/lib/file_log.cc +++ b/src/lib/file_log.cc @@ -23,6 +23,7 @@ #include "config.h" #include #include +#include using std::cout; using std::string; @@ -36,12 +37,18 @@ FileLog::FileLog (boost::filesystem::path file) 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 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; } diff --git a/src/lib/file_log.h b/src/lib/file_log.h index 53fbe4f76..613dd0939 100644 --- a/src/lib/file_log.h +++ b/src/lib/file_log.h @@ -24,6 +24,7 @@ class FileLog : public Log { public: explicit FileLog (boost::filesystem::path file); + FileLog (boost::filesystem::path file, int types); std::string head_and_tail (int amount = 1024) const; diff --git a/src/lib/log.h b/src/lib/log.h index b102a2d65..416f4259d 100644 --- a/src/lib/log.h +++ b/src/lib/log.h @@ -46,6 +46,9 @@ public: 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. diff --git a/src/lib/log_entry.cc b/src/lib/log_entry.cc index 54f9bfc53..4aff47c73 100644 --- a/src/lib/log_entry.cc +++ b/src/lib/log_entry.cc @@ -24,14 +24,15 @@ #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; diff --git a/src/lib/log_entry.h b/src/lib/log_entry.h index c15a006d1..d4992de86 100644 --- a/src/lib/log_entry.h +++ b/src/lib/log_entry.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2015 Carl Hetherington + Copyright (C) 2015-2020 Carl Hetherington 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_DISK; explicit LogEntry (int type); virtual ~LogEntry () {} diff --git a/src/lib/nanomsg.cc b/src/lib/nanomsg.cc new file mode 100644 index 000000000..57220cd54 --- /dev/null +++ b/src/lib/nanomsg.cc @@ -0,0 +1,145 @@ +/* + Copyright (C) 2020 Carl Hetherington + + 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 . + +*/ + +#include "nanomsg.h" +#include "dcpomatic_log.h" +#include +#include +#include +#include + +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 +Nanomsg::get_from_pending () +{ + if (_pending.empty()) { + return optional(); + } + + 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 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 +Nanomsg::nonblocking_get () +{ + optional 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 index 000000000..dc84a6ce7 --- /dev/null +++ b/src/lib/nanomsg.h @@ -0,0 +1,48 @@ +/* + Copyright (C) 2020 Carl Hetherington + + 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 . + +*/ + +#include +#include +#include +#include + +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 nonblocking_get (); + +private: + boost::optional get_from_pending (); + void recv_and_parse (bool blocking); + + int _socket; + std::list _pending; + std::string _current; +}; + diff --git a/src/lib/state.cc b/src/lib/state.cc index abb197695..dd48516f4 100644 --- a/src/lib/state.cc +++ b/src/lib/state.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2018 Carl Hetherington + Copyright (C) 2018-2020 Carl Hetherington This file is part of DCP-o-matic. @@ -19,6 +19,7 @@ */ #include "state.h" +#include "cross.h" #include using std::string; @@ -34,16 +35,7 @@ State::path (string file, bool create_directories) 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) { diff --git a/src/lib/wscript b/src/lib/wscript index a37d873a8..ca6786ef2 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -53,7 +53,7 @@ sources = """ content.cc content_factory.cc create_cli.cc - cross.cc + cross_common.cc crypto.cc curl_uploader.cc datasat_ap2x.cc @@ -197,15 +197,26 @@ def build(bld): """ 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' + 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: - 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' diff --git a/src/tools/dcpomatic_disk.cc b/src/tools/dcpomatic_disk.cc new file mode 100644 index 000000000..baccdbce3 --- /dev/null +++ b/src/tools/dcpomatic_disk.cc @@ -0,0 +1,309 @@ +/* + Copyright (C) 2019-2020 Carl Hetherington + + 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 . + +*/ + +#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 +#include +#ifdef DCPOMATIC_WINDOWS +#include +#endif +#ifdef DCPOMATIC_OSX +#include +#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(_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(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(_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 _dcp_path; + std::vector _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 index 000000000..0bd5dcbc4 --- /dev/null +++ b/src/tools/dcpomatic_disk_writer.cc @@ -0,0 +1,474 @@ +/* + Copyright (C) 2019-2020 Carl Hetherington + + 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 . + +*/ + +#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 +#include +#include +#include +#include +#include +} + +#ifdef DCPOMATIC_POSIX +#include +#include +#include +#endif + +#ifdef DCPOMATIC_OSX +#undef nil +extern "C" { +#include +} +#endif + +#ifdef DCPOMATIC_LINUX +#include +#include +extern "C" { +#include +} +#include +#endif + +#ifdef DCPOMATIC_WINDOWS +extern "C" { +#include +} +#endif + +#include +#include +#include +#include +#include +#include + +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 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 ml = Glib::MainLoop::create (); + Glib::signal_timeout().connect(sigc::ptr_fun(&idle), 500); + ml->run (); +} diff --git a/src/tools/wscript b/src/tools/wscript index 8af9e7589..c7c953a31 100644 --- a/src/tools/wscript +++ b/src/tools/wscript @@ -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 ' + 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_LINUX: + uselib += 'DL ' 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'] + if bld.env.ENABLE_DISK: + cli_tools.append('dcpomatic_disk_writer') 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 + 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 @@ -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'] + if bld.env.ENABLE_DISK: + gui_tools.append('dcpomatic_disk') 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 index 000000000..2eb0b9d8c --- /dev/null +++ b/src/wx/drive_wipe_warning_dialog.cc @@ -0,0 +1,56 @@ +/* + Copyright (C) 2019 Carl Hetherington + + 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 . + +*/ + +#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 ALL DATA " + "on the drive %s will be PERMANENTLY DESTROYED.\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 index 000000000..1043e7f1b --- /dev/null +++ b/src/wx/drive_wipe_warning_dialog.h @@ -0,0 +1,32 @@ +/* + Copyright (C) 2019 Carl Hetherington + + 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 . + +*/ + +#include + +class DriveWipeWarningDialog : public wxDialog +{ +public: + DriveWipeWarningDialog (wxWindow* parent, wxString drive); + + bool confirmed () const; + +private: + wxTextCtrl* _yes; +}; diff --git a/src/wx/wscript b/src/wx/wscript index 66c88f8d0..87ae38aac 100644 --- a/src/wx/wscript +++ b/src/wx/wscript @@ -51,6 +51,7 @@ sources = """ 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 diff --git a/test/wscript b/test/wscript index 0e777c803..a1a78eaea 100644 --- a/test/wscript +++ b/test/wscript @@ -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 ' + if bld.env.TARGET_LINUX: + obj.uselib += 'DL ' obj.use = 'libdcpomatic2' obj.source = """ 4k_test.cc diff --git a/wscript b/wscript index fe1431316..a94fee8ba 100644 --- 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('--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') @@ -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.ENABLE_DISK = conf.options.enable_disk 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='setupapi', uselib_store='SETUPAPI', msg="Checking for library setupapi") boost_lib_suffix = '-mt' - boost_thread = 'boost_thread_win32-mt' + boost_thread = 'boost_thread-mt' conf.check_cxx(fragment=""" #include \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.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) @@ -366,6 +370,25 @@ def configure(conf): # 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 \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'] @@ -510,6 +533,21 @@ def configure(conf): 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 \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. @@ -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') - # 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')