openMSX
DiskChanger.cc
Go to the documentation of this file.
1 #include "DiskChanger.hh"
2 #include "DiskFactory.hh"
3 #include "DummyDisk.hh"
4 #include "RamDSKDiskImage.hh"
5 #include "DirAsDSK.hh"
6 #include "CommandController.hh"
7 #include "RecordedCommand.hh"
9 #include "Scheduler.hh"
10 #include "FilePool.hh"
11 #include "File.hh"
12 #include "MSXMotherBoard.hh"
13 #include "Reactor.hh"
14 #include "DiskManipulator.hh"
15 #include "FileContext.hh"
16 #include "FileOperations.hh"
17 #include "FileException.hh"
18 #include "CommandException.hh"
19 #include "CliComm.hh"
20 #include "TclObject.hh"
21 #include "EmuTime.hh"
22 #include "serialize.hh"
23 #include "serialize_stl.hh"
24 #include "serialize_constr.hh"
25 #include "strCat.hh"
26 #include "view.hh"
27 #include <functional>
28 #include <memory>
29 #include <utility>
30 
31 using std::string;
32 using std::vector;
33 
34 namespace openmsx {
35 
36 class DiskCommand final : public Command // TODO RecordedCommand
37 {
38 public:
39  DiskCommand(CommandController& commandController,
40  DiskChanger& diskChanger);
41  void execute(span<const TclObject> tokens,
42  TclObject& result) override;
43  [[nodiscard]] string help(const vector<string>& tokens) const override;
44  void tabCompletion(vector<string>& tokens) const override;
45  [[nodiscard]] bool needRecord(span<const TclObject> tokens) const /*override*/;
46 private:
47  DiskChanger& diskChanger;
48 };
49 
51  string driveName_,
52  bool createCmd,
53  bool doubleSidedDrive_,
54  std::function<void()> preChangeCallback_)
55  : reactor(board.getReactor())
56  , controller(board.getCommandController())
57  , stateChangeDistributor(&board.getStateChangeDistributor())
58  , scheduler(&board.getScheduler())
59  , preChangeCallback(std::move(preChangeCallback_))
60  , driveName(std::move(driveName_))
61  , doubleSidedDrive(doubleSidedDrive_)
62 {
63  init(tmpStrCat(board.getMachineID(), "::"), createCmd);
64 }
65 
66 DiskChanger::DiskChanger(Reactor& reactor_, string driveName_)
67  : reactor(reactor_)
68  , controller(reactor.getCommandController())
69  , stateChangeDistributor(nullptr)
70  , scheduler(nullptr)
71  , driveName(std::move(driveName_))
72  , doubleSidedDrive(true) // irrelevant, but needs a value
73 {
74  init({}, true);
75 }
76 
77 void DiskChanger::init(std::string_view prefix, bool createCmd)
78 {
79  if (createCmd) createCommand();
80  ejectDisk();
81  auto& manipulator = reactor.getDiskManipulator();
82  manipulator.registerDrive(*this, prefix);
83  if (stateChangeDistributor) {
84  stateChangeDistributor->registerListener(*this);
85  }
86 }
87 
89 {
90  if (diskCommand) return;
91  diskCommand = std::make_unique<DiskCommand>(controller, *this);
92 }
93 
95 {
96  if (stateChangeDistributor) {
97  stateChangeDistributor->unregisterListener(*this);
98  }
99  auto& manipulator = reactor.getDiskManipulator();
100  manipulator.unregisterDrive(*this);
101 }
102 
104 {
105  return disk->getName();
106 }
107 
109 {
110  bool ret = diskChangedFlag;
111  diskChangedFlag = false;
112  return ret;
113 }
114 
116 {
117  if (dynamic_cast<DummyDisk*>(disk.get())) {
118  return nullptr;
119  }
120  return dynamic_cast<SectorAccessibleDisk*>(disk.get());
121 }
122 
123 std::string_view DiskChanger::getContainerName() const
124 {
125  return getDriveName();
126 }
127 
128 void DiskChanger::sendChangeDiskEvent(span<string> args)
129 {
130  // note: might throw MSXException
131  if (stateChangeDistributor) {
132  stateChangeDistributor->distributeNew(
133  std::make_shared<MSXCommandEvent>(
134  args, scheduler->getCurrentTime()));
135  } else {
136  signalStateChange(std::make_shared<MSXCommandEvent>(
137  args, EmuTime::zero()));
138  }
139 }
140 
141 void DiskChanger::signalStateChange(const std::shared_ptr<StateChange>& event)
142 {
143  auto* commandEvent = dynamic_cast<MSXCommandEvent*>(event.get());
144  if (!commandEvent) return;
145 
146  const auto& tokens = commandEvent->getTokens();
147  if (tokens[0] == getDriveName()) {
148  if (tokens[1] == "eject") {
149  ejectDisk();
150  } else {
151  insertDisk(tokens); // might throw
152  }
153  }
154 }
155 
156 void DiskChanger::stopReplay(EmuTime::param /*time*/) noexcept
157 {
158  // nothing
159 }
160 
161 int DiskChanger::insertDisk(const std::string& filename)
162 {
163  TclObject args[] = { TclObject("dummy"), TclObject(filename) };
164  try {
165  insertDisk(args);
166  return 0;
167  } catch (MSXException&) {
168  return -1;
169  }
170 }
171 
173 {
174  string diskImage = FileOperations::getConventionalPath(string(args[1].getString()));
175  auto& diskFactory = reactor.getDiskFactory();
176  std::unique_ptr<Disk> newDisk(diskFactory.createDisk(diskImage, *this));
177  for (const auto& arg : view::drop(args, 2)) {
178  newDisk->applyPatch(Filename(
179  arg.getString(), userFileContext()));
180  }
181 
182  // no errors, only now replace original disk
183  changeDisk(std::move(newDisk));
184 }
185 
186 void DiskChanger::ejectDisk()
187 {
188  changeDisk(std::make_unique<DummyDisk>());
189 }
190 
191 void DiskChanger::changeDisk(std::unique_ptr<Disk> newDisk)
192 {
193  if (preChangeCallback) preChangeCallback();
194  disk = std::move(newDisk);
195  diskChangedFlag = true;
197  getDiskName().getResolved());
198 }
199 
200 
201 // class DiskCommand
202 
204  DiskChanger& diskChanger_)
205  : Command(commandController_, diskChanger_.driveName)
206  , diskChanger(diskChanger_)
207 {
208 }
209 
211 {
212  if (tokens.size() == 1) {
213  result.addListElement(tmpStrCat(diskChanger.getDriveName(), ':'),
214  diskChanger.getDiskName().getResolved());
215 
216  TclObject options;
217  if (dynamic_cast<DummyDisk*>(diskChanger.disk.get())) {
218  options.addListElement("empty");
219  } else if (dynamic_cast<DirAsDSK*>(diskChanger.disk.get())) {
220  options.addListElement("dirasdisk");
221  } else if (dynamic_cast<RamDSKDiskImage*>(diskChanger.disk.get())) {
222  options.addListElement("ramdsk");
223  }
224  if (diskChanger.disk->isWriteProtected()) {
225  options.addListElement("readonly");
226  }
227  if (options.getListLength(getInterpreter()) != 0) {
228  result.addListElement(options);
229  }
230 
231  } else if (tokens[1] == "ramdsk") {
232  string args[] = {
233  diskChanger.getDriveName(), string(tokens[1].getString())
234  };
235  diskChanger.sendChangeDiskEvent(args);
236  } else if (tokens[1] == "-ramdsk") {
237  string args[] = {diskChanger.getDriveName(), "ramdsk"};
238  diskChanger.sendChangeDiskEvent(args);
239  result = "Warning: use of '-ramdsk' is deprecated, instead use the 'ramdsk' subcommand";
240  } else if (tokens[1] == "-eject") {
241  string args[] = {diskChanger.getDriveName(), "eject"};
242  diskChanger.sendChangeDiskEvent(args);
243  result = "Warning: use of '-eject' is deprecated, instead use the 'eject' subcommand";
244  } else if (tokens[1] == "eject") {
245  string args[] = {diskChanger.getDriveName(), "eject"};
246  diskChanger.sendChangeDiskEvent(args);
247  } else {
248  int firstFileToken = 1;
249  if (tokens[1] == "insert") {
250  if (tokens.size() > 2) {
251  firstFileToken = 2; // skip this subcommand as filearg
252  } else {
253  throw CommandException("Missing argument to insert subcommand");
254  }
255  }
256  try {
257  vector<string> args = { diskChanger.getDriveName() };
258  for (size_t i = firstFileToken; i < tokens.size(); ++i) { // 'i' changes in loop
259  std::string_view option = tokens[i].getString();
260  if (option == "-ips") {
261  if (++i == tokens.size()) {
262  throw MSXException(
263  "Missing argument for option \"", option, '\"');
264  }
265  args.emplace_back(tokens[i].getString());
266  } else {
267  // backwards compatibility
268  args.emplace_back(option);
269  }
270  }
271  diskChanger.sendChangeDiskEvent(args);
272  } catch (FileException& e) {
273  throw CommandException(std::move(e).getMessage());
274  }
275  }
276 }
277 
278 string DiskCommand::help(const vector<string>& /*tokens*/) const
279 {
280  const string& driveName = diskChanger.getDriveName();
281  return strCat(
282  driveName, " eject : remove disk from virtual drive\n",
283  driveName, " ramdsk : create a virtual disk in RAM\n",
284  driveName, " insert <filename> : change the disk file\n",
285  driveName, " <filename> : change the disk file\n",
286  driveName, " : show which disk image is in drive\n"
287  "The following options are supported when inserting a disk image:\n"
288  "-ips <filename> : apply the given IPS patch to the disk image");
289 }
290 
291 void DiskCommand::tabCompletion(vector<string>& tokens) const
292 {
293  if (tokens.size() >= 2) {
294  using namespace std::literals;
295  static constexpr std::array extra = {
296  "eject"sv, "ramdsk"sv, "insert"sv,
297  };
298  completeFileName(tokens, userFileContext(), extra);
299  }
300 }
301 
303 {
304  return tokens.size() > 1;
305 }
306 
307 static string calcSha1(SectorAccessibleDisk* disk, FilePool& filePool)
308 {
309  return disk ? disk->getSha1Sum(filePool).toString() : string{};
310 }
311 
312 // version 1: initial version
313 // version 2: replaced Filename with DiskName
314 template<typename Archive>
315 void DiskChanger::serialize(Archive& ar, unsigned version)
316 {
317  DiskName diskname = disk->getName();
318  if (ar.versionBelow(version, 2)) {
319  // there was no DiskName yet, just a plain Filename
321  ar.serialize("disk", filename);
322  if (filename.getOriginal() == "ramdisk") {
323  diskname = DiskName(Filename(), "ramdisk");
324  } else {
325  diskname = DiskName(filename, {});
326  }
327  } else {
328  ar.serialize("disk", diskname);
329  }
330 
331  vector<Filename> patches;
332  if constexpr (!Archive::IS_LOADER) {
333  patches = disk->getPatches();
334  }
335  ar.serialize("patches", patches);
336 
337  auto& filePool = reactor.getFilePool();
338  string oldChecksum;
339  if constexpr (!Archive::IS_LOADER) {
340  oldChecksum = calcSha1(getSectorAccessibleDisk(), filePool);
341  }
342  ar.serialize("checksum", oldChecksum);
343 
344  if constexpr (Archive::IS_LOADER) {
345  diskname.updateAfterLoadState();
346  string name = diskname.getResolved(); // TODO use Filename
347  if (!name.empty()) {
348  // Only when the original file doesn't exist on this
349  // system, try to search by sha1sum. This means we
350  // prefer the original file over a file with a matching
351  // sha1sum (the original file may have changed). An
352  // alternative is to prefer the exact sha1sum match.
353  // I'm not sure which alternative is better.
354  if (!FileOperations::exists(name)) {
355  assert(!oldChecksum.empty());
356  auto file = filePool.getFile(
357  FileType::DISK, Sha1Sum(oldChecksum));
358  if (file.is_open()) {
359  name = file.getURL();
360  }
361  }
362  vector<TclObject> args =
363  { TclObject("dummy"), TclObject(name) };
364  for (auto& p : patches) {
365  p.updateAfterLoadState();
366  args.emplace_back(p.getResolved()); // TODO
367  }
368 
369  try {
370  insertDisk(args);
371  } catch (MSXException& e) {
372  throw MSXException(
373  "Couldn't reinsert disk in drive ",
374  getDriveName(), ": ", e.getMessage());
375  // Alternative: Print warning and continue
376  // without diskimage. Is this better?
377  }
378  }
379 
380  string newChecksum = calcSha1(getSectorAccessibleDisk(), filePool);
381  if (oldChecksum != newChecksum) {
382  controller.getCliComm().printWarning(
383  "The content of the diskimage ",
384  diskname.getResolved(),
385  " has changed since the time this savestate was "
386  "created. This might result in emulation problems "
387  "or even diskcorruption. To prevent the latter, "
388  "the disk is now write-protected (eject and "
389  "reinsert the disk if you want to override this).");
390  disk->forceWriteProtect();
391  }
392  }
393 
394  // This should only be restored after disk is inserted
395  ar.serialize("diskChanged", diskChangedFlag);
396 }
397 
398 // extra (local) constructor arguments for polymorphic de-serialization
400 {
401  using type = std::tuple<std::string>;
402 
403  template<typename Archive>
404  void save(Archive& ar, const DiskChanger& changer)
405  {
406  ar.serialize("driveName", changer.getDriveName());
407  }
408 
409  template<typename Archive> type load(Archive& ar, unsigned /*version*/)
410  {
411  string driveName;
412  ar.serialize("driveName", driveName);
413  return std::tuple(driveName);
414  }
415 };
416 
419  std::reference_wrapper<MSXMotherBoard>);
420 
421 } // namespace openmsx
virtual void update(UpdateType type, std::string_view name, std::string_view value)=0
Interpreter & getInterpreter() const final
Definition: Command.cc:41
virtual CliComm & getCliComm()=0
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:144
const DiskName & getDiskName() const
Definition: DiskChanger.cc:103
~DiskChanger() override
Definition: DiskChanger.cc:94
int insertDisk(const std::string &filename) override
Definition: DiskChanger.cc:161
const std::string & getDriveName() const
Definition: DiskChanger.hh:38
std::string_view getContainerName() const override
Definition: DiskChanger.cc:123
void serialize(Archive &ar, unsigned version)
Definition: DiskChanger.cc:315
DiskChanger(MSXMotherBoard &board, std::string driveName, bool createCmd=true, bool doubleSidedDrive=true, std::function< void()> preChangeCallback={})
Definition: DiskChanger.cc:50
void changeDisk(std::unique_ptr< Disk > newDisk)
Definition: DiskChanger.cc:191
SectorAccessibleDisk * getSectorAccessibleDisk() override
Definition: DiskChanger.cc:115
bool diskChanged() override
Definition: DiskChanger.cc:108
bool needRecord(span< const TclObject > tokens) const
Definition: DiskChanger.cc:302
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: DiskChanger.cc:210
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: DiskChanger.cc:291
DiskCommand(CommandController &commandController, DiskChanger &diskChanger)
Definition: DiskChanger.cc:203
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: DiskChanger.cc:278
void unregisterDrive(DiskContainer &drive)
void registerDrive(DiskContainer &drive, std::string_view prefix)
std::string getResolved() const
Definition: DiskName.cc:24
void updateAfterLoadState()
Definition: DiskName.cc:29
File getFile(FileType fileType, const Sha1Sum &sha1sum)
Search file with the given sha1sum.
Definition: FilePool.cc:68
const std::string & getURL() const
Returns the URL of this file object.
Definition: File.cc:138
This class represents a filename.
Definition: Filename.hh:18
const std::string & getMessage() const &
Definition: MSXException.hh:23
std::string_view getMachineID() const
Contains the main loop of openMSX.
Definition: Reactor.hh:67
DiskFactory & getDiskFactory()
Definition: Reactor.hh:87
DiskManipulator & getDiskManipulator()
Definition: Reactor.hh:88
EmuTime::param getCurrentTime() const
Get the current scheduler time.
Definition: Scheduler.cc:84
Sha1Sum getSha1Sum(FilePool &filepool)
Calculate SHA1 of the content of this disk.
std::vector< Filename > getPatches() const
This class represents the result of a sha1 calculation (a 160-bit value).
Definition: sha1.hh:22
std::string toString() const
Definition: utils/sha1.cc:234
void registerListener(StateChangeListener &listener)
(Un)registers the given object to receive state change events.
void distributeNew(const EventPtr &event)
Deliver the event to all registered listeners MSX input devices should call the distributeNew() versi...
void unregisterListener(StateChangeListener &listener)
unsigned getListLength(Interpreter &interp) const
Definition: TclObject.cc:125
void addListElement(const T &t)
Definition: TclObject.hh:130
Definition: span.hh:126
constexpr index_type size() const noexcept
Definition: span.hh:296
bool exists(zstring_view filename)
Does this file (directory) exists?
const std::string & getConventionalPath(const std::string &path)
Returns the path in conventional path-delimiter.
This file implemented 3 utility functions:
Definition: Autofire.cc:5
constexpr const char *const filename
REGISTER_POLYMORPHIC_CLASS_1(DiskContainer, DiskChanger, "DiskChanger", std::reference_wrapper< MSXMotherBoard >)
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:172
constexpr auto drop(Range &&range, size_t n)
Definition: view.hh:288
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:983
TemporaryString tmpStrCat(Ts &&... ts)
Definition: strCat.hh:659
std::string strCat(Ts &&...ts)
Definition: strCat.hh:591
void save(Archive &ar, const DiskChanger &changer)
Definition: DiskChanger.cc:404
Serialize (local) constructor arguments.