Fix some spelling mistakes (mostly in comments).
[dcpomatic.git] / src / lib / cross_common.cc
1 /*
2     Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
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.
10
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.
15
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/>.
18
19 */
20
21
22 #include "cross.h"
23 #include "compose.hpp"
24 #include "dcpomatic_assert.h"
25 #include "dcpomatic_log.h"
26 #include <dcp/raw_convert.h>
27 #include <dcp/warnings.h>
28 LIBDCP_DISABLE_WARNINGS
29 #include <libxml++/libxml++.h>
30 LIBDCP_ENABLE_WARNINGS
31 #include <boost/algorithm/string.hpp>
32 #include <iostream>
33
34 #include "i18n.h"
35
36
37 using std::map;
38 using std::string;
39 using std::vector;
40 using boost::optional;
41
42
43 auto constexpr MEDIA_PATH_REQUIRED_MATCHES = 3;
44
45
46 Drive::Drive (string xml)
47 {
48         cxml::Document doc;
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());
53         }
54         _size = doc.number_child<uint64_t>("Size");
55         _vendor = doc.optional_string_child("Vendor");
56         _model = doc.optional_string_child("Model");
57 }
58
59
60 string
61 Drive::as_xml () const
62 {
63         xmlpp::Document doc;
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());
68         }
69         root->add_child("Size")->add_child_text(dcp::raw_convert<string>(_size));
70         if (_vendor) {
71                 root->add_child("Vendor")->add_child_text(*_vendor);
72         }
73         if (_model) {
74                 root->add_child("Model")->add_child_text(*_model);
75         }
76
77         return doc.write_to_string("UTF-8");
78 }
79
80
81 string
82 Drive::description () const
83 {
84         char gb[64];
85         snprintf(gb, 64, "%.1f", _size / 1000000000.0);
86
87         string name;
88         if (_vendor) {
89                 name += *_vendor;
90         }
91         if (_model) {
92                 if (name.size() > 0) {
93                         name += " " + *_model;
94                 } else {
95                         name = *_model;
96                 }
97         }
98         if (name.size() == 0) {
99                 name = _("Unknown");
100         }
101
102         return String::compose(_("%1 (%2 GB) [%3]"), name, gb, _device);
103 }
104
105
106 string
107 Drive::log_summary () const
108 {
109         string mp;
110         for (auto i: _mount_points) {
111                 mp += i.string() + ",";
112         }
113         if (mp.empty()) {
114                 mp = "[none]";
115         } else {
116                 mp = mp.substr (0, mp.length() - 1);
117         }
118
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]")
122                         );
123 }
124
125
126
127 /* This is in _common so we can use it in unit tests */
128 optional<OSXMediaPath>
129 analyse_osx_media_path (string path)
130 {
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");
134                 return {};
135         }
136
137         OSXMediaPath mp;
138         vector<string> parts;
139         split(parts, path, boost::is_any_of("/"));
140         std::copy(parts.begin() + 1, parts.end(), back_inserter(mp.parts));
141
142         if (!parts.empty() && parts[0] == "IODeviceTree:") {
143                 mp.real = true;
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");
147                         return {};
148                 }
149         } else if (!parts.empty() && parts[0] == "IOService:") {
150                 mp.real = false;
151         } else {
152                 return {};
153         }
154
155         return mp;
156 }
157
158
159 /* Take some 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.
163  */
164 vector<Drive>
165 osx_disks_to_drives (vector<OSXDisk> disks)
166 {
167         using namespace boost::algorithm;
168
169         /* Mark disks containing mounted partitions as themselves mounted */
170         for (auto& i: disks) {
171                 if (!i.whole) {
172                         continue;
173                 }
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));
178                         }
179                 }
180         }
181
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.
189                                          */
190                                         bool one_missing = false;
191                                         string all_parts;
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()) {
195                                                         one_missing = true;
196                                                 }
197                                                 all_parts += i.media_path.parts[k] + " ";
198                                         }
199
200                                         if (!one_missing) {
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));
203                                         }
204                                 }
205                         }
206                 }
207         }
208
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());
214                 }
215         }
216
217         return drives;
218 }