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 const std::string& 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);
152  }
153  }
154 }
155 
156 void DiskChanger::stopReplay(EmuTime::param /*time*/)
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  static constexpr const char* const extra[] = {
295  "eject", "ramdsk", "insert",
296  };
297  completeFileName(tokens, userFileContext(), extra);
298  }
299 }
300 
302 {
303  return tokens.size() > 1;
304 }
305 
306 static string calcSha1(SectorAccessibleDisk* disk, FilePool& filePool)
307 {
308  return disk ? disk->getSha1Sum(filePool).toString() : string{};
309 }
310 
311 // version 1: initial version
312 // version 2: replaced Filename with DiskName
313 template<typename Archive>
314 void DiskChanger::serialize(Archive& ar, unsigned version)
315 {
316  DiskName diskname = disk->getName();
317  if (ar.versionBelow(version, 2)) {
318  // there was no DiskName yet, just a plain Filename
320  ar.serialize("disk", filename);
321  if (filename.getOriginal() == "ramdisk") {
322  diskname = DiskName(Filename(), "ramdisk");
323  } else {
324  diskname = DiskName(filename, {});
325  }
326  } else {
327  ar.serialize("disk", diskname);
328  }
329 
330  vector<Filename> patches;
331  if (!ar.isLoader()) {
332  patches = disk->getPatches();
333  }
334  ar.serialize("patches", patches);
335 
336  auto& filePool = reactor.getFilePool();
337  string oldChecksum;
338  if (!ar.isLoader()) {
339  oldChecksum = calcSha1(getSectorAccessibleDisk(), filePool);
340  }
341  ar.serialize("checksum", oldChecksum);
342 
343  if (ar.isLoader()) {
344  diskname.updateAfterLoadState();
345  string name = diskname.getResolved(); // TODO use Filename
346  if (!name.empty()) {
347  // Only when the original file doesn't exist on this
348  // system, try to search by sha1sum. This means we
349  // prefer the original file over a file with a matching
350  // sha1sum (the original file may have changed). An
351  // alternative is to prefer the exact sha1sum match.
352  // I'm not sure which alternative is better.
353  if (!FileOperations::exists(name)) {
354  assert(!oldChecksum.empty());
355  auto file = filePool.getFile(
356  FileType::DISK, Sha1Sum(oldChecksum));
357  if (file.is_open()) {
358  name = file.getURL();
359  }
360  }
361  vector<TclObject> args =
362  { TclObject("dummy"), TclObject(name) };
363  for (auto& p : patches) {
364  p.updateAfterLoadState();
365  args.emplace_back(p.getResolved()); // TODO
366  }
367 
368  try {
369  insertDisk(args);
370  } catch (MSXException& e) {
371  throw MSXException(
372  "Couldn't reinsert disk in drive ",
373  getDriveName(), ": ", e.getMessage());
374  // Alternative: Print warning and continue
375  // without diskimage. Is this better?
376  }
377  }
378 
379  string newChecksum = calcSha1(getSectorAccessibleDisk(), filePool);
380  if (oldChecksum != newChecksum) {
381  controller.getCliComm().printWarning(
382  "The content of the diskimage ",
383  diskname.getResolved(),
384  " has changed since the time this savestate was "
385  "created. This might result in emulation problems "
386  "or even diskcorruption. To prevent the latter, "
387  "the disk is now write-protected (eject and "
388  "reinsert the disk if you want to override this).");
389  disk->forceWriteProtect();
390  }
391  }
392 
393  // This should only be restored after disk is inserted
394  ar.serialize("diskChanged", diskChangedFlag);
395 }
396 
397 // extra (local) constructor arguments for polymorphic de-serialization
399 {
400  using type = std::tuple<std::string>;
401 
402  template<typename Archive>
403  void save(Archive& ar, const DiskChanger& changer)
404  {
405  ar.serialize("driveName", changer.getDriveName());
406  }
407 
408  template<typename Archive> type load(Archive& ar, unsigned /*version*/)
409  {
410  string driveName;
411  ar.serialize("driveName", driveName);
412  return std::tuple(driveName);
413  }
414 };
415 
418  std::reference_wrapper<MSXMotherBoard>);
419 
420 } // namespace openmsx
openmsx::CommandException
Definition: CommandException.hh:9
DiskManipulator.hh
FileException.hh
openmsx::SectorAccessibleDisk
Definition: SectorAccessibleDisk.hh:16
openmsx::DiskCommand::DiskCommand
DiskCommand(CommandController &commandController, DiskChanger &diskChanger)
Definition: DiskChanger.cc:203
openmsx::File::getURL
const std::string & getURL() const
Returns the URL of this file object.
Definition: File.cc:138
openmsx::DiskChanger::insertDisk
int insertDisk(const std::string &filename) override
Definition: DiskChanger.cc:161
openmsx::SectorAccessibleDisk::forceWriteProtect
void forceWriteProtect()
Definition: SectorAccessibleDisk.cc:130
openmsx::CommandController
Definition: CommandController.hh:19
serialize.hh
openmsx::Completer::completeFileName
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:139
openmsx::Sha1Sum::toString
std::string toString() const
Definition: utils/sha1.cc:234
openmsx::DiskChanger::serialize
void serialize(Archive &ar, unsigned version)
Definition: DiskChanger.cc:314
openmsx::DiskContainer
Definition: DiskContainer.hh:15
openmsx::DirAsDSK
Definition: DirAsDSK.hh:17
DiskFactory.hh
openmsx::Reactor::getDiskManipulator
DiskManipulator & getDiskManipulator()
Definition: Reactor.hh:88
TclObject.hh
openmsx::StateChangeDistributor::registerListener
void registerListener(StateChangeListener &listener)
(Un)registers the given object to receive state change events.
Definition: StateChangeDistributor.cc:19
openmsx::DiskChanger::getSectorAccessibleDisk
SectorAccessibleDisk * getSectorAccessibleDisk() override
Definition: DiskChanger.cc:115
DiskChanger.hh
openmsx::StateChangeDistributor::distributeNew
void distributeNew(const EventPtr &event)
Deliver the event to all registered listeners MSX input devices should call the distributeNew() versi...
Definition: StateChangeDistributor.cc:43
openmsx::DiskName
DiskName
Definition: DiskName.cc:45
openmsx::DiskChanger
Definition: DiskChanger.hh:25
openmsx::DiskChanger::changeDisk
void changeDisk(std::unique_ptr< Disk > newDisk)
Definition: DiskChanger.cc:191
openmsx::FileOperations::exists
bool exists(zstring_view filename)
Does this file (directory) exists?
Definition: FileOperations.cc:553
openmsx::SerializeConstructorArgs< DiskChanger >::type
std::tuple< std::string > type
Definition: DiskChanger.cc:400
openmsx::DummyDisk
Definition: DummyDisk.hh:9
openmsx::Reactor::getFilePool
FilePool & getFilePool()
Definition: Reactor.hh:90
DummyDisk.hh
openmsx::CliComm::update
virtual void update(UpdateType type, std::string_view name, std::string_view value)=0
openmsx::MSXException
Definition: MSXException.hh:10
openmsx::userFileContext
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:172
openmsx::REGISTER_POLYMORPHIC_CLASS_1
REGISTER_POLYMORPHIC_CLASS_1(DiskContainer, DiskChanger, "DiskChanger", std::reference_wrapper< MSXMotherBoard >)
DirAsDSK.hh
openmsx::DiskName::getResolved
std::string getResolved() const
Definition: DiskName.cc:24
openmsx::TclObject::addListElement
void addListElement(const T &t)
Definition: TclObject.hh:130
RamDSKDiskImage.hh
StateChangeDistributor.hh
span
Definition: span.hh:126
openmsx::Reactor
Contains the main loop of openMSX.
Definition: Reactor.hh:67
openmsx::DiskChanger::getDiskName
const DiskName & getDiskName() const
Definition: DiskChanger.cc:103
Reactor.hh
CommandController.hh
view::drop
auto drop(Range &&range, size_t n)
Definition: view.hh:288
openmsx::CliComm::printWarning
void printWarning(std::string_view message)
Definition: CliComm.cc:10
RecordedCommand.hh
openmsx::DiskName
Definition: DiskName.hh:9
openmsx::RamDSKDiskImage
Definition: RamDSKDiskImage.hh:10
openmsx::CommandController::getCliComm
virtual CliComm & getCliComm()=0
openmsx::MSXMotherBoard::getMachineID
std::string_view getMachineID() const
Definition: MSXMotherBoard.hh:69
openmsx::DiskChanger::DiskChanger
DiskChanger(MSXMotherBoard &board, std::string driveName, bool createCmd=true, bool doubleSidedDrive=true, std::function< void()> preChangeCallback={})
Definition: DiskChanger.cc:50
File.hh
openmsx::FilePool::getFile
File getFile(FileType fileType, const Sha1Sum &sha1sum)
Search file with the given sha1sum.
Definition: FilePool.cc:68
openmsx::DiskChanger::createCommand
void createCommand()
Definition: DiskChanger.cc:88
openmsx::CliComm::MEDIA
@ MEDIA
Definition: CliComm.hh:26
openmsx::DiskName::updateAfterLoadState
void updateAfterLoadState()
Definition: DiskName.cc:29
openmsx::MSXMotherBoard
Definition: MSXMotherBoard.hh:61
openmsx::filename
constexpr const char *const filename
Definition: FirmwareSwitch.cc:10
INSTANTIATE_SERIALIZE_METHODS
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:983
openmsx::FileException
Definition: FileException.hh:9
openmsx::SerializeConstructorArgs< DiskChanger >::load
type load(Archive &ar, unsigned)
Definition: DiskChanger.cc:408
serialize_stl.hh
openmsx::DiskChanger::getDriveName
const std::string & getDriveName() const
Definition: DiskChanger.hh:38
view.hh
openmsx::Sha1Sum
This class represents the result of a sha1 calculation (a 160-bit value).
Definition: sha1.hh:22
FilePool.hh
Scheduler.hh
FileContext.hh
openmsx::DiskCommand
Definition: DiskChanger.cc:37
tmpStrCat
TemporaryString tmpStrCat(Ts &&... ts)
Definition: strCat.hh:659
openmsx::DiskCommand::help
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: DiskChanger.cc:278
FileOperations.hh
openmsx::DiskManipulator::registerDrive
void registerDrive(DiskContainer &drive, std::string_view prefix)
Definition: DiskManipulator.cc:53
openmsx::DiskChanger::getContainerName
const std::string & getContainerName() const override
Definition: DiskChanger.cc:123
span::size
constexpr index_type size() const noexcept
Definition: span.hh:296
openmsx::Reactor::getDiskFactory
DiskFactory & getDiskFactory()
Definition: Reactor.hh:87
EmuTime.hh
openmsx::Filename
Filename
Definition: Filename.cc:35
openmsx::DiskCommand::execute
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: DiskChanger.cc:210
strCat.hh
openmsx::FileOperations::getConventionalPath
const std::string & getConventionalPath(const std::string &path)
Returns the path in conventional path-delimiter.
Definition: FileOperations.hh:155
openmsx::SerializeConstructorArgs< DiskChanger >::save
void save(Archive &ar, const DiskChanger &changer)
Definition: DiskChanger.cc:403
openmsx::Command
Definition: Command.hh:41
openmsx::DiskChanger::diskChanged
bool diskChanged() override
Definition: DiskChanger.cc:108
openmsx::StateChangeDistributor::unregisterListener
void unregisterListener(StateChangeListener &listener)
Definition: StateChangeDistributor.cc:25
openmsx::DiskManipulator::unregisterDrive
void unregisterDrive(DiskContainer &drive)
Definition: DiskManipulator.cc:67
openmsx::FilePool
Definition: FilePool.hh:17
openmsx::TclObject
Definition: TclObject.hh:24
openmsx::SectorAccessibleDisk::getSha1Sum
Sha1Sum getSha1Sum(FilePool &filepool)
Calculate SHA1 of the content of this disk.
Definition: SectorAccessibleDisk.cc:72
openmsx::TclObject::getListLength
unsigned getListLength(Interpreter &interp) const
Definition: TclObject.cc:125
CliComm.hh
openmsx::Filename
This class represents a filename.
Definition: Filename.hh:18
openmsx::MSXException::getMessage
const std::string & getMessage() const &
Definition: MSXException.hh:23
openmsx::SerializeConstructorArgs
Serialize (local) constructor arguments.
Definition: serialize_constr.hh:32
openmsx::DiskChanger::~DiskChanger
~DiskChanger() override
Definition: DiskChanger.cc:94
strCat
std::string strCat(Ts &&...ts)
Definition: strCat.hh:591
CommandException.hh
openmsx::Scheduler::getCurrentTime
EmuTime::param getCurrentTime() const
Get the current scheduler time.
Definition: Scheduler.cc:84
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
openmsx::SectorAccessibleDisk::getPatches
std::vector< Filename > getPatches() const
Definition: SectorAccessibleDisk.cc:62
MSXMotherBoard.hh
serialize_constr.hh
openmsx::CommandCompleter::getInterpreter
Interpreter & getInterpreter() const final
Definition: Command.cc:41
openmsx::DiskCommand::tabCompletion
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: DiskChanger.cc:291
openmsx::DiskCommand::needRecord
bool needRecord(span< const TclObject > tokens) const
Definition: DiskChanger.cc:301