ciderpress/app/VolumeCopyDialog.cpp

825 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 "Main.h"
#include "../reformat/Charset.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;
CheckedLoadString(&appName, 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 sizeStr, blocksStr;
assert(pListView != NULL);
assert(pDiskImg != NULL);
assert(pDiskFS != NULL);
assert(pIndex != NULL);
long numBlocks = pDiskImg->GetNumBlocks();
CString volName(Charset::ConvertMORToUNI(pDiskFS->GetVolumeName()));
CString 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::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;
int result;
result = GetSelectedDisk(&pSrcImg, &pSrcFS);
if (!result)
return;
assert(pSrcImg != NULL);
assert(pSrcFS != NULL);
CString srcName(Charset::ConvertMORToUNI(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) {
CheckedLoadString(&errMsg, 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, errMsg, warning;
DiskImg* pDstImg = NULL;
DiskFS* pDstFS = NULL;
DiskImg srcImg;
DIError dierr;
int result;
bool needReload = false;
bool isPartial = false;
CheckedLoadString(&warning, 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;
CString targetName(Charset::ConvertMORToUNI(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);
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) {
CheckedLoadString(&errMsg, 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;
}