2 Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
23 #include "compose.hpp"
24 #include "dcpomatic_assert.h"
25 #include "dcpomatic_log.h"
27 #include <dcp/raw_convert.h>
28 DCPOMATIC_DISABLE_WARNINGS
29 #include <libxml++/libxml++.h>
30 DCPOMATIC_ENABLE_WARNINGS
31 #include <boost/algorithm/string.hpp>
40 using boost::optional;
43 auto constexpr MEDIA_PATH_REQUIRED_MATCHES = 3;
46 Drive::Drive (string xml)
49 doc.read_string (xml);
50 _device = doc.string_child("Device");
51 for (auto i: doc.node_children("MountPoint")) {
52 _mount_points.push_back (i->content());
54 _size = doc.number_child<uint64_t>("Size");
55 _vendor = doc.optional_string_child("Vendor");
56 _model = doc.optional_string_child("Model");
61 Drive::as_xml () const
64 auto root = doc.create_root_node ("Drive");
65 root->add_child("Device")->add_child_text(_device);
66 for (auto i: _mount_points) {
67 root->add_child("MountPoint")->add_child_text(i.string());
69 root->add_child("Size")->add_child_text(dcp::raw_convert<string>(_size));
71 root->add_child("Vendor")->add_child_text(*_vendor);
74 root->add_child("Model")->add_child_text(*_model);
77 return doc.write_to_string("UTF-8");
82 Drive::description () const
85 snprintf(gb, 64, "%.1f", _size / 1000000000.0);
92 if (name.size() > 0) {
93 name += " " + *_model;
98 if (name.size() == 0) {
102 return String::compose(_("%1 (%2 GB) [%3]"), name, gb, _device);
107 Drive::log_summary () const
110 for (auto i: _mount_points) {
111 mp += i.string() + ",";
116 mp = mp.substr (0, mp.length() - 1);
119 return String::compose(
120 "Device %1 mounted on %2 size %3 vendor %4 model %5",
121 _device, mp, _size, _vendor.get_value_or("[none]"), _model.get_value_or("[none]")
127 /* This is in _common so we can use it in unit tests */
128 optional<OSXMediaPath>
129 analyse_osx_media_path (string path)
131 if (path.find("/IOHDIXController") != string::npos) {
132 /* This is a disk image, so we completely ignore it */
133 LOG_DISK_NC("Ignoring this as it seems to be a disk image");
138 vector<string> parts;
139 split(parts, path, boost::is_any_of("/"));
140 std::copy(parts.begin() + 1, parts.end(), back_inserter(mp.parts));
142 if (!parts.empty() && parts[0] == "IODeviceTree:") {
144 if (mp.parts.size() < MEDIA_PATH_REQUIRED_MATCHES) {
145 /* Later we expect at least MEDIA_PATH_REQUIRED_MATCHES parts in a IODeviceTree */
146 LOG_DISK_NC("Ignoring this as it has a strange media path");
149 } else if (!parts.empty() && parts[0] == "IOService:") {
159 /* Take soem OSXDisk objects, representing disks that `DARegisterDiskAppearedCallback` told us about,
160 * and find those drives that we could write a DCP to. The drives returned are "real" (not synthesized)
161 * and are whole disks (not partitions). They may be mounted, or contain mounted partitions, in which
162 * their mounted() method will return true.
165 osx_disks_to_drives (vector<OSXDisk> disks)
167 using namespace boost::algorithm;
169 /* Mark disks containing mounted partitions as themselves mounted */
170 for (auto& i: disks) {
174 for (auto& j: disks) {
175 if (!j.mount_points.empty() && starts_with(j.device, i.device)) {
176 LOG_DISK("Marking %1 as mounted because %2 is", i.device, j.device);
177 std::copy(j.mount_points.begin(), j.mount_points.end(), back_inserter(i.mount_points));
182 /* Mark containers of mounted synths as themselves mounted */
183 for (auto& i: disks) {
184 if (i.media_path.real) {
185 for (auto& j: disks) {
186 if (!j.media_path.real && !j.mount_points.empty()) {
187 /* i is real, j is a mounted synth; if we see the first MEDIA_PATH_REQUIRED_MATCHES parts
188 * of i anywhere in j we assume they are related and so i shares j's mount points.
190 bool one_missing = false;
192 DCPOMATIC_ASSERT (i.media_path.parts.size() >= MEDIA_PATH_REQUIRED_MATCHES);
193 for (auto k = 0; k < MEDIA_PATH_REQUIRED_MATCHES; ++k) {
194 if (find(j.media_path.parts.begin(), j.media_path.parts.end(), i.media_path.parts[k]) == j.media_path.parts.end()) {
197 all_parts += i.media_path.parts[k] + " ";
201 LOG_DISK("Marking %1 as mounted because %2 is (found %3)", i.device, j.device, all_parts);
202 std::copy(j.mount_points.begin(), j.mount_points.end(), back_inserter(i.mount_points));
209 vector<Drive> drives;
210 for (auto const& i: disks) {
211 if (i.whole && i.media_path.real) {
212 drives.push_back(Drive(i.device, i.mount_points, i.size, i.vendor, i.model));
213 LOG_DISK_NC(drives.back().log_summary());