/* * 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; }