ciderpress/app/VolumeCopyDialog.cpp
Andy McFadden edb7c13120 Fix diskimg OpenImage
The OpenImage method had an overload that took void*.  This turns out
to be a bad idea, because void* matches any pointer type that didn't
match something else.  So the WCHAR* filenames were going to the "open
from buffer" method rather than the "open from file" variant.

A less important issue is whether open-from-buffer should take a const
or non-const pointer.  If the "readOnly" boolean flag is not set, then
the contents can be altered and const is inappropriate.  The best course
seems to be to drop the boolean flag as an argument, and just have two
different methods.
2014-11-23 10:34:57 -08:00

834 lines
28 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Dialog that allows copying volumes or sub-volumes to and from files on
* disk.
*
* NOTE: we probably shouldn't allow copying volumes that start at block 0
* and are equal to the DiskImgLib limit on sizes (e.g. 8GB). We'd be
* copying a partial volume, which doesn't make sense.
*/
#include "stdafx.h"
#include "VolumeCopyDialog.h"
#include "HelpTopics.h"
#include "Main.h"
BEGIN_MESSAGE_MAP(VolumeCopyDialog, CDialog)
ON_COMMAND(IDHELP, OnHelp)
ON_COMMAND(IDC_VOLUEMCOPYSEL_TOFILE, OnCopyToFile)
ON_COMMAND(IDC_VOLUEMCOPYSEL_FROMFILE, OnCopyFromFile)
ON_NOTIFY(LVN_ITEMCHANGED, IDC_VOLUMECOPYSEL_LIST, OnListChange)
ON_MESSAGE(WMU_DIALOG_READY, OnDialogReady)
END_MESSAGE_MAP()
/*
* Sub-class the generic libutil CancelDialog class.
*/
class VolumeXferProgressDialog : public ProgressCancelDialog {
public:
BOOL Create(CWnd* pParentWnd = NULL) {
fAbortOperation = false;
return ProgressCancelDialog::Create(&fAbortOperation,
IDD_VOLUMECOPYPROG, IDC_VOLUMECOPYPROG_PROGRESS, pParentWnd);
}
void SetCurrentFiles(const WCHAR* fromName, const WCHAR* toName) {
CWnd* pWnd = GetDlgItem(IDC_VOLUMECOPYPROG_FROM);
ASSERT(pWnd != NULL);
pWnd->SetWindowText(fromName);
pWnd = GetDlgItem(IDC_VOLUMECOPYPROG_TO);
ASSERT(pWnd != NULL);
pWnd->SetWindowText(toName);
}
private:
void OnOK(void) override {
LOGI("Ignoring VolumeXferProgressDialog OnOK");
}
MainWindow* GetMainWindow(void) const {
return (MainWindow*)::AfxGetMainWnd();
}
bool fAbortOperation;
};
BOOL VolumeCopyDialog::OnInitDialog(void)
{
/*
* Scan the source image.
*/
CRect rect;
//this->GetWindowRect(&rect);
//LOGI("RECT is %d, %d, %d, %d", rect.left, rect.top, rect.bottom, rect.right);
ASSERT(fpDiskImg != NULL);
ScanDiskInfo(false);
CDialog::OnInitDialog(); // does DDX init
CButton* pButton;
pButton = (CButton*) GetDlgItem(IDC_VOLUEMCOPYSEL_FROMFILE);
pButton->EnableWindow(FALSE);
pButton = (CButton*) GetDlgItem(IDC_VOLUEMCOPYSEL_TOFILE);
pButton->EnableWindow(FALSE);
CString newTitle;
GetWindowText(newTitle);
newTitle += " - ";
newTitle += fPathName;
SetWindowText(newTitle);
/*
* Prep the listview control.
*
* Columns:
* [icon] Volume name | Format | Size (MB/GB) | Block count
*/
CListCtrl* pListView = (CListCtrl*) GetDlgItem(IDC_VOLUMECOPYSEL_LIST);
ASSERT(pListView != NULL);
ListView_SetExtendedListViewStyleEx(pListView->m_hWnd,
LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT);
int width1, width2, width3;
//CRect rect;
pListView->GetClientRect(&rect);
width1 = pListView->GetStringWidth(L"XXVolume NameXXmmmmm");
width2 = pListView->GetStringWidth(L"XXFormatXXmmmmmmmmmm");
width3 = pListView->GetStringWidth(L"XXSizeXXmmm");
//width4 = pListView->GetStringWidth("XXBlock CountXX");
pListView->InsertColumn(0, L"Volume Name", LVCFMT_LEFT, width1);
pListView->InsertColumn(1, L"Format", LVCFMT_LEFT, width2);
pListView->InsertColumn(2, L"Size", LVCFMT_LEFT, width3);
pListView->InsertColumn(3, L"Block Count", LVCFMT_LEFT,
rect.Width() - (width1+width2+width3)
- ::GetSystemMetrics(SM_CXVSCROLL) );
/* add images for list; this MUST be loaded before header images */
LoadListImages();
pListView->SetImageList(&fListImageList, LVSIL_SMALL);
LoadList();
CenterWindow();
int cc = PostMessage(WMU_DIALOG_READY, 0, 0);
ASSERT(cc != 0);
return TRUE;
}
void VolumeCopyDialog::OnOK(void)
{
/*
* We need to make sure we throw out the DiskFS we created before the modal
* dialog exits. This is necessary because we rely on an external DiskImg,
* and create DiskFS objects that point to it.
*/
Cleanup();
CDialog::OnOK();
}
void VolumeCopyDialog::OnCancel(void)
{
Cleanup();
CDialog::OnCancel();
}
void VolumeCopyDialog::Cleanup(void)
{
LOGD(" VolumeCopyDialog is done, cleaning up DiskFS");
delete fpDiskFS;
fpDiskFS = NULL;
}
void VolumeCopyDialog::OnListChange(NMHDR*, LRESULT* pResult)
{
//CRect rect;
//this->GetWindowRect(&rect);
//LOGI("RECT is %d, %d, %d, %d", rect.left, rect.top, rect.bottom, rect.right);
CListCtrl* pListView = (CListCtrl*) GetDlgItem(IDC_VOLUMECOPYSEL_LIST);
ASSERT(pListView != NULL);
CButton* pButton;
UINT selectedCount;
selectedCount = pListView->GetSelectedCount();
pButton = (CButton*) GetDlgItem(IDC_VOLUEMCOPYSEL_TOFILE);
pButton->EnableWindow(selectedCount != 0);
if (!fpDiskImg->GetReadOnly()) {
pButton = (CButton*) GetDlgItem(IDC_VOLUEMCOPYSEL_FROMFILE);
pButton->EnableWindow(selectedCount != 0);
}
*pResult = 0;
}
void VolumeCopyDialog::ScanDiskInfo(bool scanTop)
{
/*
* The top-level disk image should already have been analyzed and the
* format overridden (if necessary). We don't want to do it in here the
* first time around because the "override" dialog screws up placement
* of our dialog box. I guess opening windows from inside OnInitDialog
* isn't expected. Annoying. [Um, maybe we could call CenterWindow??
* Actually, now I'm a little concerned about modal dialogs coming and
* going while we're in OnInitDialog, because MainWindow is disabled and
* we're not yet enabled. ++ATM]
*/
const Preferences* pPreferences = GET_PREFERENCES();
MainWindow* pMain = (MainWindow*)::AfxGetMainWnd();
DIError dierr;
CString errMsg, failed;
assert(fpDiskImg != NULL);
assert(fpDiskFS == NULL);
if (scanTop) {
DiskImg::FSFormat oldFormat;
oldFormat = fpDiskImg->GetFSFormat();
/* check to see if the top-level FS has changed */
fpDiskImg->AnalyzeImageFS();
/*
* If requested (or necessary), verify the format. We only do this
* if we think the format has changed. This is possible, e.g. if
* somebody drops an MS-DOS volume into the first partition of a
* CFFA disk.
*/
if (oldFormat != fpDiskImg->GetFSFormat() &&
(fpDiskImg->GetFSFormat() == DiskImg::kFormatUnknown ||
fpDiskImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown ||
pPreferences->GetPrefBool(kPrQueryImageFormat)))
{
// ignore them if they hit "cancel"
(void) pMain->TryDiskImgOverride(fpDiskImg, fPathName,
DiskImg::kFormatUnknown, NULL, true, &errMsg);
if (!errMsg.IsEmpty()) {
ShowFailureMsg(this, errMsg, IDS_FAILED);
return;
}
}
}
/*
* Creating the "busy" window here is problematic, because we get called
* from OnInitDialog, at which point our window isn't yet established.
* Since we're modal, we disabled MainWindow, which means that when the
* "busy" box goes away there's no CiderPress window to take control. As
* a result we get a nasty flash.
*
* The only way around this is to defer the destruction of the modeless
* dialog until after we become visible.
*/
bool deferDestroy = false;
if (!IsWindowVisible() || !IsWindowEnabled()) {
LOGI(" Deferring destroy on wait dialog");
deferDestroy = true;
} else {
LOGI(" Not deferring destroy on wait dialog");
}
fpWaitDlg = new ExclusiveModelessDialog;
fpWaitDlg->Create(IDD_LOADING, this);
fpWaitDlg->CenterWindow(pMain);
pMain->PeekAndPump();
CWaitCursor waitc;
/*
* Create an appropriate DiskFS object. We only need to do this to get
* the sub-volume info, which is unfortunate since it can be slow.
*/
fpDiskFS = fpDiskImg->OpenAppropriateDiskFS(true);
if (fpDiskFS == NULL) {
LOGI("HEY: OpenAppropriateDiskFS failed!");
/* this is fatal, but there's no easy way to die */
/* (could we do a DestroyWindow from here?) */
/* at any rate, with "allowUnknown" set, this shouldn't happen */
} else {
fpDiskFS->SetScanForSubVolumes(DiskFS::kScanSubContainerOnly);
dierr = fpDiskFS->Initialize(fpDiskImg, DiskFS::kInitFull);
if (dierr != kDIErrNone) {
CString appName, msg;
appName.LoadString(IDS_MB_APP_NAME);
msg.Format(L"Warning: error during disk scan: %hs.",
DiskImgLib::DIStrError(dierr));
fpWaitDlg->MessageBox(msg, appName, MB_OK | MB_ICONEXCLAMATION);
/* keep going */
}
}
if (!deferDestroy && fpWaitDlg != NULL) {
fpWaitDlg->DestroyWindow();
fpWaitDlg = NULL;
}
return;
}
LONG VolumeCopyDialog::OnDialogReady(UINT, LONG)
{
if (fpWaitDlg != NULL) {
LOGI("OnDialogReady found active window, destroying");
fpWaitDlg->DestroyWindow();
fpWaitDlg = NULL;
}
return 0;
}
void VolumeCopyDialog::LoadList(void)
{
CListCtrl* pListView = (CListCtrl*) GetDlgItem(IDC_VOLUMECOPYSEL_LIST);
ASSERT(pListView != NULL);
int itemIndex = 0;
CString unknown = "(unknown)";
pListView->DeleteAllItems();
if (fpDiskFS == NULL) {
/* can only happen if imported volume is unrecognizeable */
return;
}
AddToList(pListView, fpDiskImg, fpDiskFS, &itemIndex);
DiskImgLib::DiskFS::SubVolume* pSubVolume;
pSubVolume = fpDiskFS->GetNextSubVolume(NULL);
while (pSubVolume != NULL) {
if (pSubVolume->GetDiskFS() == NULL) {
LOGI("WARNING: sub-volume DiskFS is NULL?!");
assert(false);
} else {
AddToList(pListView, pSubVolume->GetDiskImg(),
pSubVolume->GetDiskFS(), &itemIndex);
}
pSubVolume = fpDiskFS->GetNextSubVolume(pSubVolume);
}
}
void VolumeCopyDialog::AddToList(CListCtrl* pListView, DiskImg* pDiskImg,
DiskFS* pDiskFS, int* pIndex)
{
CString volName, format, sizeStr, blocksStr;
long numBlocks;
assert(pListView != NULL);
assert(pDiskImg != NULL);
assert(pDiskFS != NULL);
assert(pIndex != NULL);
numBlocks = pDiskImg->GetNumBlocks();
volName = pDiskFS->GetVolumeName();
format = DiskImg::ToString(pDiskImg->GetFSFormat());
blocksStr.Format(L"%ld", pDiskImg->GetNumBlocks());
if (numBlocks > 1024*1024*2)
sizeStr.Format(L"%.2fGB", (double) numBlocks / (1024.0*1024.0*2.0));
else if (numBlocks > 1024*2)
sizeStr.Format(L"%.2fMB", (double) numBlocks / (1024.0*2.0));
else
sizeStr.Format(L"%.2fKB", (double) numBlocks / 2.0);
/* add entry; first entry is the whole volume */
pListView->InsertItem(*pIndex, volName,
*pIndex == 0 ? kListIconVolume : kListIconSubVolume);
pListView->SetItemText(*pIndex, 1, format);
pListView->SetItemText(*pIndex, 2, sizeStr);
pListView->SetItemText(*pIndex, 3, blocksStr);
pListView->SetItemData(*pIndex, (DWORD) pDiskFS);
(*pIndex)++;
}
bool VolumeCopyDialog::GetSelectedDisk(DiskImg** ppDiskImg, DiskFS** ppDiskFS)
{
CListCtrl* pListView = (CListCtrl*) GetDlgItem(IDC_VOLUMECOPYSEL_LIST);
ASSERT(pListView != NULL);
ASSERT(ppDiskImg != NULL);
ASSERT(ppDiskFS != NULL);
if (pListView->GetSelectedCount() != 1)
return false;
POSITION posn;
posn = pListView->GetFirstSelectedItemPosition();
if (posn == NULL) {
ASSERT(false);
return false;
}
int num = pListView->GetNextSelectedItem(posn);
DWORD data = pListView->GetItemData(num);
*ppDiskFS = (DiskFS*) data;
assert(*ppDiskFS != NULL);
*ppDiskImg = (*ppDiskFS)->GetDiskImg();
return true;
}
void VolumeCopyDialog::OnHelp(void)
{
WinHelp(HELP_TOPIC_VOLUME_COPIER, HELP_CONTEXT);
}
void VolumeCopyDialog::OnCopyToFile(void)
{
VolumeXferProgressDialog* pProgressDialog = NULL;
Preferences* pPreferences = GET_PREFERENCES_WR();
MainWindow* pMain = (MainWindow*)::AfxGetMainWnd();
DiskImg::FSFormat originalFormat = DiskImg::kFormatUnknown;
DiskImg* pSrcImg = NULL;
DiskFS* pSrcFS = NULL;
DiskImg dstImg;
DIError dierr;
CString errMsg, saveName, msg, srcName;
int result;
result = GetSelectedDisk(&pSrcImg, &pSrcFS);
if (!result)
return;
assert(pSrcImg != NULL);
assert(pSrcFS != NULL);
srcName = pSrcFS->GetVolumeName();
/* force the format to be generic ProDOS-ordered blocks */
originalFormat = pSrcImg->GetFSFormat();
dierr = pSrcImg->OverrideFormat(pSrcImg->GetPhysicalFormat(),
DiskImg::kFormatGenericProDOSOrd, pSrcImg->GetSectorOrder());
if (dierr != kDIErrNone) {
errMsg.Format(L"Internal error: couldn't switch to generic ProDOS: %hs.",
DiskImgLib::DIStrError(dierr));
ShowFailureMsg(this, errMsg, IDS_FAILED);
goto bail;
}
LOGI("Logical volume '%ls' has %d 512-byte blocks",
(LPCWSTR) srcName, pSrcImg->GetNumBlocks());
/*
* Select file to write blocks to.
*/
{
CFileDialog saveDlg(FALSE, L"po", NULL,
OFN_OVERWRITEPROMPT|OFN_NOREADONLYRETURN|OFN_HIDEREADONLY,
L"All Files (*.*)|*.*||", this);
CString saveFolder;
saveDlg.m_ofn.lpstrTitle = L"New disk image (.po)";
saveDlg.m_ofn.lpstrInitialDir =
pPreferences->GetPrefString(kPrOpenArchiveFolder);
if (saveDlg.DoModal() != IDOK) {
LOGI(" User bailed out of image save dialog");
goto bail;
}
saveFolder = saveDlg.m_ofn.lpstrFile;
saveFolder = saveFolder.Left(saveDlg.m_ofn.nFileOffset);
pPreferences->SetPrefString(kPrOpenArchiveFolder, saveFolder);
saveName = saveDlg.GetPathName();
}
LOGI("File will be saved to '%ls'", (LPCWSTR) saveName);
/* DiskImgLib does not like it if file already exists */
errMsg = pMain->RemoveFile(saveName);
if (!errMsg.IsEmpty()) {
ShowFailureMsg(this, errMsg, IDS_FAILED);
goto bail;
}
/*
* Create a block image with the expected number of blocks.
*/
int dstNumBlocks;
dstNumBlocks = pSrcImg->GetNumBlocks();
{
ExclusiveModelessDialog* pWaitDlg = new ExclusiveModelessDialog;
pWaitDlg->Create(IDD_FORMATTING, this);
pWaitDlg->CenterWindow(pMain);
pMain->PeekAndPump(); // redraw
CWaitCursor waitc;
CStringA saveNameA(saveName);
dierr = dstImg.CreateImage(saveNameA, NULL,
DiskImg::kOuterFormatNone,
DiskImg::kFileFormatUnadorned,
DiskImg::kPhysicalFormatSectors,
NULL,
DiskImg::kSectorOrderProDOS,
DiskImg::kFormatGenericProDOSOrd,
dstNumBlocks,
true /* don't need to erase contents */);
pWaitDlg->DestroyWindow();
//pMain->PeekAndPump(); // redraw
}
if (dierr != kDIErrNone) {
errMsg.Format(L"Couldn't create disk image: %hs.",
DiskImgLib::DIStrError(dierr));
ShowFailureMsg(this, errMsg, IDS_FAILED);
goto bail;
}
/* initialize cancel dialog, and disable main window */
pProgressDialog = new VolumeXferProgressDialog;
EnableWindow(FALSE);
if (pProgressDialog->Create(this) == FALSE) {
LOGI("Progress dialog init failed?!");
ASSERT(false);
goto bail;
}
pProgressDialog->SetCurrentFiles(srcName, saveName);
time_t startWhen, endWhen;
startWhen = time(NULL);
/*
* Do the actual block copy.
*/
dierr = pMain->CopyDiskImage(&dstImg, pSrcImg, false, false, pProgressDialog);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled) {
errMsg.LoadString(IDS_OPERATION_CANCELLED);
ShowFailureMsg(pProgressDialog, errMsg, IDS_CANCELLED);
// remove the partially-written file
dstImg.CloseImage();
(void) _wunlink(saveName);
} else {
errMsg.Format(L"Copy failed: %hs.", DiskImgLib::DIStrError(dierr));
ShowFailureMsg(pProgressDialog, errMsg, IDS_FAILED);
}
goto bail;
}
dierr = dstImg.CloseImage();
if (dierr != kDIErrNone) {
errMsg.Format(L"ERROR: dstImg close failed (err=%d)\n", dierr);
ShowFailureMsg(pProgressDialog, errMsg, IDS_FAILED);
goto bail;
}
/* put elapsed time in the debug log */
endWhen = time(NULL);
float elapsed;
if (endWhen == startWhen)
elapsed = 1.0;
else
elapsed = (float) (endWhen - startWhen);
msg.Format(L"Copied %ld blocks in %ld seconds (%.2fKB/sec)",
pSrcImg->GetNumBlocks(), (long) (endWhen - startWhen),
(pSrcImg->GetNumBlocks() / 2.0) / elapsed);
LOGI("%ls", (LPCWSTR) msg);
#ifdef _DEBUG
pProgressDialog->MessageBox(msg, L"DEBUG: elapsed time", MB_OK);
#endif
pMain->SuccessBeep();
bail:
// restore the dialog window to prominence
EnableWindow(TRUE);
//SetActiveWindow();
if (pProgressDialog != NULL)
pProgressDialog->DestroyWindow();
/* un-override the source disk */
if (originalFormat != DiskImg::kFormatUnknown) {
dierr = pSrcImg->OverrideFormat(pSrcImg->GetPhysicalFormat(),
originalFormat, pSrcImg->GetSectorOrder());
if (dierr != kDIErrNone) {
LOGI("ERROR: couldn't un-override source image (dierr=%d)", dierr);
// not much else to do; should be okay
}
}
return;
}
void VolumeCopyDialog::OnCopyFromFile(void)
{
VolumeXferProgressDialog* pProgressDialog = NULL;
Preferences* pPreferences = GET_PREFERENCES_WR();
MainWindow* pMain = (MainWindow*)::AfxGetMainWnd();
//DiskImg::FSFormat originalFormat = DiskImg::kFormatUnknown;
CString openFilters;
CString loadName, targetName, errMsg, warning;
DiskImg* pDstImg = NULL;
DiskFS* pDstFS = NULL;
DiskImg srcImg;
DIError dierr;
int result;
bool needReload = false;
bool isPartial = false;
warning.LoadString(IDS_WARNING);
/*
* Get the DiskImg and DiskFS pointers for the selected partition out of
* the control. The storage for these is part of fpDiskFS, which holds
* the tree of subvolumes.
*/
result = GetSelectedDisk(&pDstImg, &pDstFS);
if (!result)
return;
// if (pDstFS == NULL)
// targetName = "the target volume";
// else
targetName = pDstFS->GetVolumeName();
/*
* Select the image to copy from.
*/
openFilters = MainWindow::kOpenDiskImage;
openFilters += MainWindow::kOpenAll;
openFilters += MainWindow::kOpenEnd;
CFileDialog dlg(TRUE, L"dsk", NULL, OFN_FILEMUSTEXIST, openFilters, this);
/* source file gets opened read-only */
dlg.m_ofn.Flags |= OFN_HIDEREADONLY;
dlg.m_ofn.lpstrTitle = L"Select image to copy from";
dlg.m_ofn.lpstrInitialDir = pPreferences->GetPrefString(kPrOpenArchiveFolder);
if (dlg.DoModal() != IDOK)
goto bail;
loadName = dlg.GetPathName();
{
CWaitCursor waitc;
CString saveFolder;
/* open the image file and analyze it */
CStringA loadNameA(loadName);
dierr = srcImg.OpenImage(loadNameA, PathProposal::kLocalFssep, true);
if (dierr != kDIErrNone) {
errMsg.Format(L"Unable to open disk image: %hs.",
DiskImgLib::DIStrError(dierr));
ShowFailureMsg(this, errMsg, IDS_FAILED);
goto bail;
}
if (srcImg.AnalyzeImage() != kDIErrNone) {
errMsg.Format(L"The file '%ls' doesn't seem to hold a valid disk image.",
(LPCWSTR) loadName);
ShowFailureMsg(this, errMsg, IDS_FAILED);
goto bail;
}
// save our folder choice in the preferences file
saveFolder = dlg.m_ofn.lpstrFile;
saveFolder = saveFolder.Left(dlg.m_ofn.nFileOffset);
pPreferences->SetPrefString(kPrOpenArchiveFolder, saveFolder);
}
/*
* Require that the input be block-addressable. This isn't really the
* right test, because it's conceivable that somebody would want to put
* a nibble image onto a disk volume. I can't think of a good reason
* to do this -- you can't just splat a fixed-track-length .NIB file
* onto a 5.25" disk, assuming you could get the drive to work on a PC
* in the first place -- so I'm going to take the simple way out. The
* right test is to verify that the EOF on the input is the same as the
* EOF on the output.
*/
if (!srcImg.GetHasBlocks()) {
errMsg = L"The disk image must be block-oriented. Nibble images"
L" cannot be copied.";
ShowFailureMsg(this, errMsg, IDS_FAILED);
goto bail;
}
/* force source volume to generic ProDOS blocks */
dierr = srcImg.OverrideFormat(srcImg.GetPhysicalFormat(),
DiskImg::kFormatGenericProDOSOrd, srcImg.GetSectorOrder());
if (dierr != kDIErrNone) {
errMsg.Format(L"Internal error: couldn't switch source to generic ProDOS: %hs.",
DiskImgLib::DIStrError(dierr));
ShowFailureMsg(this, errMsg, IDS_FAILED);
goto bail;
}
LOGI("Source image '%ls' has %d 512-byte blocks",
(LPCWSTR) loadName, srcImg.GetNumBlocks());
LOGI("Target volume has %d 512-byte blocks", pDstImg->GetNumBlocks());
if (srcImg.GetNumBlocks() > pDstImg->GetNumBlocks()) {
errMsg.Format(L"Error: the disk image file has %ld blocks, but the"
L" target volume holds %ld blocks. The target must"
L" have more space than the input file.",
srcImg.GetNumBlocks(), pDstImg->GetNumBlocks());
ShowFailureMsg(this, errMsg, IDS_FAILED);
goto bail;
}
if (pDstImg->GetNumBlocks() >= DiskImgLib::kVolumeMaxBlocks) {
// TODO: re-evaluate this limitation
errMsg.Format(L"Error: for safety reasons, copying disk images to"
L" larger volumes is not supported when the target"
L" is 8GB or larger.");
ShowFailureMsg(this, errMsg, IDS_FAILED);
goto bail;
}
if (srcImg.GetNumBlocks() != pDstImg->GetNumBlocks()) {
errMsg.LoadString(IDS_WARNING);
errMsg.Format(L"The disk image file has %ld blocks, but the target"
L" volume holds %ld blocks. The leftover space may be"
L" wasted, and non-ProDOS volumes may not be identified"
L" correctly. Do you wish to continue?",
srcImg.GetNumBlocks(), pDstImg->GetNumBlocks());
result = MessageBox(errMsg, warning, MB_OKCANCEL | MB_ICONQUESTION);
if (result != IDOK) {
LOGI("User chickened out of oversized disk copy");
goto bail;
}
isPartial = true;
}
errMsg.LoadString(IDS_WARNING); // TODO: what does this accomplish?
errMsg.Format(L"You are about to overwrite volume %ls with the"
L" contents of '%ls'. This will destroy all data on"
L" %ls. Are you sure you wish to continue?",
(LPCWSTR) targetName, (LPCWSTR) loadName, (LPCWSTR) targetName);
result = MessageBox(errMsg, warning, MB_OKCANCEL | MB_ICONEXCLAMATION);
if (result != IDOK) {
LOGI("User chickened out of disk copy");
goto bail;
}
/* force the target disk image to be generic ProDOS-ordered blocks */
dierr = pDstImg->OverrideFormat(pDstImg->GetPhysicalFormat(),
DiskImg::kFormatGenericProDOSOrd, pDstImg->GetSectorOrder());
if (dierr != kDIErrNone) {
errMsg.Format(L"Internal error: couldn't switch target to generic ProDOS: %hs.",
DiskImgLib::DIStrError(dierr));
ShowFailureMsg(this, errMsg, IDS_FAILED);
goto bail;
}
/* from here on out, before we exit we must re-analyze this volume */
needReload = true;
// redraw main to erase previous dialog
pMain->PeekAndPump();
/* initialize cancel dialog, and disable dialog */
pProgressDialog = new VolumeXferProgressDialog;
EnableWindow(FALSE);
if (pProgressDialog->Create(this) == FALSE) {
LOGI("Progress dialog init failed?!");
ASSERT(false);
return;
}
// if (pDstFS == NULL)
// pProgressDialog->SetCurrentFiles(loadName, "target");
// else
pProgressDialog->SetCurrentFiles(loadName, targetName);
/*
* We want to delete fpDiskFS now, but we can't because it's holding the
* storage for the DiskImg/DiskFS pointers in the subvolume list. We
* flush it to ensure that it won't try to write to the disk after the
* copy completes.
*/
fpDiskFS->Flush(DiskImg::kFlushAll);
time_t startWhen, endWhen;
startWhen = time(NULL);
/*
* Do the actual block copy.
*/
dierr = pMain->CopyDiskImage(pDstImg, &srcImg, false, isPartial,
pProgressDialog);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled) {
errMsg.LoadString(IDS_OPERATION_CANCELLED);
ShowFailureMsg(pProgressDialog, errMsg, IDS_CANCELLED);
} else {
errMsg.Format(L"Copy failed: %hs.", DiskImgLib::DIStrError(dierr));
ShowFailureMsg(pProgressDialog, errMsg, IDS_FAILED);
}
goto bail;
}
dierr = srcImg.CloseImage();
if (dierr != kDIErrNone) {
errMsg.Format(L"ERROR: srcImg close failed (err=%d)\n", dierr);
ShowFailureMsg(pProgressDialog, errMsg, IDS_FAILED);
goto bail;
}
endWhen = time(NULL);
float elapsed;
if (endWhen == startWhen)
elapsed = 1.0;
else
elapsed = (float) (endWhen - startWhen);
errMsg.Format(L"Copied %ld blocks in %ld seconds (%.2fKB/sec)",
srcImg.GetNumBlocks(), (long) (endWhen - startWhen),
(srcImg.GetNumBlocks() / 2.0) / elapsed);
LOGI("%ls", (LPCWSTR) errMsg);
#ifdef _DEBUG
pProgressDialog->MessageBox(errMsg, L"DEBUG: elapsed time", MB_OK);
#endif
pMain->SuccessBeep();
/*
* If a DiskFS insists on privately caching stuff (e.g. libhfs), we could
* end up corrupting the image we just wrote. We use SetAllReadOnly() to
* ensure that nothing will be written before we delete the DiskFS.
*/
assert(!fpDiskImg->GetReadOnly());
fpDiskFS->SetAllReadOnly(true);
delete fpDiskFS;
fpDiskFS = NULL;
assert(fpDiskImg->GetReadOnly());
fpDiskImg->SetReadOnly(false);
bail:
// restore the dialog window to prominence
EnableWindow(TRUE);
//SetActiveWindow();
if (pProgressDialog != NULL)
pProgressDialog->DestroyWindow();
/*
* Force a reload. We need to reload the disk information, then reload
* the list contents.
*
* By design, anything that would require un-overriding the format of
* the target DiskImg requires reloading it completely. Sort of heavy-
* handed, but it's reliable.
*/
if (needReload) {
LOGI("RELOAD dialog");
ScanDiskInfo(true); // reopens fpDiskFS
LoadList();
/* will we need to reopen the currently-open file list archive? */
if (pMain->IsOpenPathName(fPathName))
pMain->SetReopenFlag();
}
return;
}