mirror of
https://github.com/fadden/ciderpress.git
synced 2024-12-22 05:29:43 +00:00
d42b9c6dc0
The static analyzer was annoyed that the return value from calls to CString::LoadString() was being ignored. This adds a wrapper function that checks the value and logs a failure message if the string can't be found.
828 lines
28 KiB
C++
828 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"
|
|
|
|
|
|
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 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::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) {
|
|
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, targetName, 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;
|
|
|
|
// 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);
|
|
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;
|
|
}
|