/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace svx::DocRecovery { using namespace ::osl; #define COLUMN_STANDARDIMAGE -1 #define COLUMN_DISPLAYNAME 0 #define COLUMN_STATUSIMAGE 1 #define COLUMN_STATUSTEXT 2 RecoveryCore::RecoveryCore(css::uno::Reference< css::uno::XComponentContext > xContext, bool bUsedForSaving) : m_xContext (std::move( xContext )) , m_pListener ( nullptr ) , m_bListenForSaving(bUsedForSaving) { impl_startListening(); } RecoveryCore::~RecoveryCore() { impl_stopListening(); } const css::uno::Reference< css::uno::XComponentContext >& RecoveryCore::getComponentContext() const { return m_xContext; } TURLList& RecoveryCore::getURLListAccess() { return m_lURLs; } bool RecoveryCore::isBrokenTempEntry(const TURLInfo& rInfo) { if (rInfo.TempURL.isEmpty()) return false; // Note: If the original files was recovery ... but a temp file // exists ... an error inside the temp file exists! if ( (rInfo.RecoveryState != E_RECOVERY_FAILED ) && (rInfo.RecoveryState != E_ORIGINAL_DOCUMENT_RECOVERED) ) return false; return true; } void RecoveryCore::saveBrokenTempEntries(const OUString& rPath) { if (rPath.isEmpty()) return; if (!m_xRealCore.is()) return; // prepare all needed parameters for the following dispatch() request. css::util::URL aCopyURL = impl_getParsedURL(RECOVERY_CMD_DO_ENTRY_BACKUP); css::uno::Sequence< css::beans::PropertyValue > lCopyArgs(3); auto plCopyArgs = lCopyArgs.getArray(); plCopyArgs[0].Name = PROP_DISPATCHASYNCHRON; plCopyArgs[0].Value <<= false; plCopyArgs[1].Name = PROP_SAVEPATH; plCopyArgs[1].Value <<= rPath; plCopyArgs[2].Name = PROP_ENTRYID; // lCopyArgs[2].Value will be changed during next loop... // work on a copied list only... // Reason: We will get notifications from the core for every // changed or removed element. And that will change our m_lURLs list. // That's not a good idea, if we use a stl iterator inbetween .-) TURLList lURLs = m_lURLs; for (const TURLInfo& rInfo : lURLs) { if (!RecoveryCore::isBrokenTempEntry(rInfo)) continue; plCopyArgs[2].Value <<= rInfo.ID; m_xRealCore->dispatch(aCopyURL, lCopyArgs); } } void RecoveryCore::saveAllTempEntries(const OUString& rPath) { if (rPath.isEmpty()) return; if (!m_xRealCore.is()) return; // prepare all needed parameters for the following dispatch() request. css::util::URL aCopyURL = impl_getParsedURL(RECOVERY_CMD_DO_ENTRY_BACKUP); css::uno::Sequence< css::beans::PropertyValue > lCopyArgs(3); auto plCopyArgs = lCopyArgs.getArray(); plCopyArgs[0].Name = PROP_DISPATCHASYNCHRON; plCopyArgs[0].Value <<= false; plCopyArgs[1].Name = PROP_SAVEPATH; plCopyArgs[1].Value <<= rPath; plCopyArgs[2].Name = PROP_ENTRYID; // lCopyArgs[2].Value will be changed during next loop ... // work on a copied list only ... // Reason: We will get notifications from the core for every // changed or removed element. And that will change our m_lURLs list. // That's not a good idea, if we use a stl iterator inbetween .-) TURLList lURLs = m_lURLs; for (const TURLInfo& rInfo : lURLs) { if (rInfo.TempURL.isEmpty()) continue; plCopyArgs[2].Value <<= rInfo.ID; m_xRealCore->dispatch(aCopyURL, lCopyArgs); } } void RecoveryCore::forgetBrokenTempEntries() { if (!m_xRealCore.is()) return; css::util::URL aRemoveURL = impl_getParsedURL(RECOVERY_CMD_DO_ENTRY_CLEANUP); css::uno::Sequence< css::beans::PropertyValue > lRemoveArgs(2); auto plRemoveArgs = lRemoveArgs.getArray(); plRemoveArgs[0].Name = PROP_DISPATCHASYNCHRON; plRemoveArgs[0].Value <<= false; plRemoveArgs[1].Name = PROP_ENTRYID; // lRemoveArgs[1].Value will be changed during next loop ... // work on a copied list only ... // Reason: We will get notifications from the core for every // changed or removed element. And that will change our m_lURLs list. // That's not a good idea, if we use a stl iterator inbetween .-) TURLList lURLs = m_lURLs; for (const TURLInfo& rInfo : lURLs) { if (!RecoveryCore::isBrokenTempEntry(rInfo)) continue; plRemoveArgs[1].Value <<= rInfo.ID; m_xRealCore->dispatch(aRemoveURL, lRemoveArgs); } } // should only be called with valid m_xRealCore void RecoveryCore::forgetAllRecoveryEntriesMarkedForDiscard() { assert(m_xRealCore); // potential to move in a separate function css::util::URL aRemoveURL = impl_getParsedURL(RECOVERY_CMD_DO_ENTRY_CLEANUP); css::uno::Sequence lRemoveArgs(2); auto plRemoveArgs = lRemoveArgs.getArray(); plRemoveArgs[0].Name = PROP_DISPATCHASYNCHRON; plRemoveArgs[0].Value <<= false; plRemoveArgs[1].Name = PROP_ENTRYID; // work on a copied list only ... // Reason: We will get notifications from the core for every // changed or removed element. And that will change our m_lURLs list. // That's not a good idea, if we use a stl iterator inbetween .-) TURLList lURLs = m_lURLs; for (const TURLInfo& rInfo : lURLs) { if (!rInfo.ShouldDiscard) continue; plRemoveArgs[1].Value <<= rInfo.ID; m_xRealCore->dispatch(aRemoveURL, lRemoveArgs); } } void RecoveryCore::forgetAllRecoveryEntries() { if (!m_xRealCore.is()) return; css::util::URL aRemoveURL = impl_getParsedURL(RECOVERY_CMD_DO_ENTRY_CLEANUP); css::uno::Sequence< css::beans::PropertyValue > lRemoveArgs(2); auto plRemoveArgs = lRemoveArgs.getArray(); plRemoveArgs[0].Name = PROP_DISPATCHASYNCHRON; plRemoveArgs[0].Value <<= false; plRemoveArgs[1].Name = PROP_ENTRYID; // lRemoveArgs[1].Value will be changed during next loop ... // work on a copied list only ... // Reason: We will get notifications from the core for every // changed or removed element. And that will change our m_lURLs list. // That's not a good idea, if we use a stl iterator inbetween .-) TURLList lURLs = m_lURLs; for (const TURLInfo& rInfo : lURLs) { plRemoveArgs[1].Value <<= rInfo.ID; m_xRealCore->dispatch(aRemoveURL, lRemoveArgs); } } void RecoveryCore::forgetBrokenRecoveryEntries() { if (!m_xRealCore.is()) return; css::util::URL aRemoveURL = impl_getParsedURL(RECOVERY_CMD_DO_ENTRY_CLEANUP); css::uno::Sequence< css::beans::PropertyValue > lRemoveArgs(2); auto plRemoveArgs = lRemoveArgs.getArray(); plRemoveArgs[0].Name = PROP_DISPATCHASYNCHRON; plRemoveArgs[0].Value <<= false; plRemoveArgs[1].Name = PROP_ENTRYID; // lRemoveArgs[1].Value will be changed during next loop ... // work on a copied list only ... // Reason: We will get notifications from the core for every // changed or removed element. And that will change our m_lURLs list. // That's not a good idea, if we use a stl iterator inbetween .-) TURLList lURLs = m_lURLs; for (const TURLInfo& rInfo : lURLs) { if (!RecoveryCore::isBrokenTempEntry(rInfo)) continue; plRemoveArgs[1].Value <<= rInfo.ID; m_xRealCore->dispatch(aRemoveURL, lRemoveArgs); } } void RecoveryCore::setProgressHandler(const css::uno::Reference< css::task::XStatusIndicator >& xProgress) { m_xProgress = xProgress; } void RecoveryCore::setUpdateListener(IRecoveryUpdateListener* pListener) { m_pListener = pListener; } void RecoveryCore::doEmergencySavePrepare() { if (!m_xRealCore.is()) return; css::util::URL aURL = impl_getParsedURL(RECOVERY_CMD_DO_PREPARE_EMERGENCY_SAVE); css::uno::Sequence< css::beans::PropertyValue > lArgs{ comphelper::makePropertyValue( PROP_DISPATCHASYNCHRON, false) }; m_xRealCore->dispatch(aURL, lArgs); } void RecoveryCore::doEmergencySave() { if (!m_xRealCore.is()) return; css::util::URL aURL = impl_getParsedURL(RECOVERY_CMD_DO_EMERGENCY_SAVE); css::uno::Sequence< css::beans::PropertyValue > lArgs{ comphelper::makePropertyValue(PROP_STATUSINDICATOR, m_xProgress), comphelper::makePropertyValue(PROP_DISPATCHASYNCHRON, true) }; m_xRealCore->dispatch(aURL, lArgs); } void RecoveryCore::doRecovery() { if (!m_xRealCore.is()) return; forgetAllRecoveryEntriesMarkedForDiscard(); css::util::URL aURL = impl_getParsedURL(RECOVERY_CMD_DO_RECOVERY); css::uno::Sequence< css::beans::PropertyValue > lArgs{ comphelper::makePropertyValue(PROP_STATUSINDICATOR, m_xProgress), comphelper::makePropertyValue(PROP_DISPATCHASYNCHRON, true) }; m_xRealCore->dispatch(aURL, lArgs); } ERecoveryState RecoveryCore::mapDocState2RecoverState(EDocStates eDocState) { // ??? ERecoveryState eRecState = E_NOT_RECOVERED_YET; /* Attention: Some of the following states can occur at the same time. So we have to check for the "worst case" first! DAMAGED -> INCOMPLETE -> HANDLED */ // running ... if ( (eDocState & EDocStates::TryLoadBackup ) || (eDocState & EDocStates::TryLoadOriginal) ) eRecState = E_RECOVERY_IS_IN_PROGRESS; // red else if (eDocState & EDocStates::Damaged) eRecState = E_RECOVERY_FAILED; // yellow else if (eDocState & EDocStates::Incomplete) eRecState = E_ORIGINAL_DOCUMENT_RECOVERED; // green else if (eDocState & EDocStates::Succeeded) eRecState = E_SUCCESSFULLY_RECOVERED; return eRecState; } void SAL_CALL RecoveryCore::statusChanged(const css::frame::FeatureStateEvent& aEvent) { // a) special notification about start/stop async dispatch! // FeatureDescriptor = "start" || "stop" if (aEvent.FeatureDescriptor == RECOVERY_OPERATIONSTATE_START) { return; } if (aEvent.FeatureDescriptor == RECOVERY_OPERATIONSTATE_STOP) { if (m_pListener) m_pListener->end(); return; } // b) normal notification about changed items // FeatureDescriptor = "Update" // State = List of information [seq< NamedValue >] if (aEvent.FeatureDescriptor != RECOVERY_OPERATIONSTATE_UPDATE) return; ::comphelper::SequenceAsHashMap lInfo(aEvent.State); TURLInfo aNew; aNew.ID = lInfo.getUnpackedValueOrDefault(STATEPROP_ID , sal_Int32(0) ); aNew.DocState = static_cast(lInfo.getUnpackedValueOrDefault(STATEPROP_STATE , sal_Int32(0) )); aNew.OrgURL = lInfo.getUnpackedValueOrDefault(STATEPROP_ORGURL , OUString()); aNew.TempURL = lInfo.getUnpackedValueOrDefault(STATEPROP_TEMPURL , OUString()); aNew.FactoryURL = lInfo.getUnpackedValueOrDefault(STATEPROP_FACTORYURL , OUString()); aNew.TemplateURL = lInfo.getUnpackedValueOrDefault(STATEPROP_TEMPLATEURL, OUString()); aNew.DisplayName = lInfo.getUnpackedValueOrDefault(STATEPROP_TITLE , OUString()); aNew.Module = lInfo.getUnpackedValueOrDefault(STATEPROP_MODULE , OUString()); if (aNew.OrgURL.isEmpty()) { // If there is no file URL, the window title is used for the display name. // Remove any unwanted elements such as " - LibreOffice Writer". sal_Int32 i = aNew.DisplayName.indexOf(" - "); if (i > 0) aNew.DisplayName = aNew.DisplayName.copy(0, i); } else { // If there is a file URL, parse out the filename part as the display name. INetURLObject aOrgURL(aNew.OrgURL); aNew.DisplayName = aOrgURL.getName(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset); } // search for already existing items and update her nState value ... for (TURLInfo& aOld : m_lURLs) { if (aOld.ID == aNew.ID) { // change existing aOld.DocState = aNew.DocState; aOld.RecoveryState = RecoveryCore::mapDocState2RecoverState(aOld.DocState); if (m_pListener) { m_pListener->updateItems(); m_pListener->stepNext(&aOld); } return; } } // append as new one // TODO think about matching Module name to a corresponding icon OUString sURL = aNew.OrgURL; if (sURL.isEmpty()) sURL = aNew.FactoryURL; if (sURL.isEmpty()) sURL = aNew.TempURL; if (sURL.isEmpty()) sURL = aNew.TemplateURL; INetURLObject aURL(sURL); aNew.StandardImageId = SvFileInformationManager::GetFileImageId(aURL); /* set the right UI state for this item to NOT_RECOVERED_YET... because nDocState shows the state of the last emergency save operation before and is interesting for the used recovery core service only... for now! But if there is a further notification for this item (see lines above!) we must map the doc state to an UI state. */ aNew.RecoveryState = E_NOT_RECOVERED_YET; m_lURLs.push_back(aNew); if (m_pListener) m_pListener->updateItems(); } void SAL_CALL RecoveryCore::disposing(const css::lang::EventObject& /*aEvent*/) { m_xRealCore.clear(); } void RecoveryCore::impl_startListening() { // listening already initialized ? if (m_xRealCore.is()) return; m_xRealCore = css::frame::theAutoRecovery::get(m_xContext); css::util::URL aURL; if (m_bListenForSaving) aURL.Complete = RECOVERY_CMD_DO_EMERGENCY_SAVE; else aURL.Complete = RECOVERY_CMD_DO_RECOVERY; css::uno::Reference< css::util::XURLTransformer > xParser(css::util::URLTransformer::create(m_xContext)); xParser->parseStrict(aURL); /* Note: addStatusListener() call us synchronous back ... so we will get the complete list of currently open documents! */ m_xRealCore->addStatusListener(static_cast< css::frame::XStatusListener* >(this), aURL); } void RecoveryCore::impl_stopListening() { // Ignore it, if this instance doesn't listen currently if (!m_xRealCore.is()) return; css::util::URL aURL; if (m_bListenForSaving) aURL.Complete = RECOVERY_CMD_DO_EMERGENCY_SAVE; else aURL.Complete = RECOVERY_CMD_DO_RECOVERY; css::uno::Reference< css::util::XURLTransformer > xParser(css::util::URLTransformer::create(m_xContext)); xParser->parseStrict(aURL); m_xRealCore->removeStatusListener(static_cast< css::frame::XStatusListener* >(this), aURL); m_xRealCore.clear(); } css::util::URL RecoveryCore::impl_getParsedURL(const OUString& sURL) { css::util::URL aURL; aURL.Complete = sURL; css::uno::Reference< css::util::XURLTransformer > xParser(css::util::URLTransformer::create(m_xContext)); xParser->parseStrict(aURL); return aURL; } PluginProgress::PluginProgress(weld::ProgressBar* pProgressBar) : m_pProgressBar(pProgressBar) , m_nRange(100) { } PluginProgress::~PluginProgress() { } void SAL_CALL PluginProgress::dispose() { m_pProgressBar = nullptr; } void SAL_CALL PluginProgress::addEventListener(const css::uno::Reference< css::lang::XEventListener >& ) { } void SAL_CALL PluginProgress::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& ) { } void SAL_CALL PluginProgress::start(const OUString&, sal_Int32 nRange) { m_nRange = nRange; if (m_pProgressBar) m_pProgressBar->set_percentage(0); } void SAL_CALL PluginProgress::end() { if (m_pProgressBar) m_pProgressBar->set_percentage(m_nRange); } void SAL_CALL PluginProgress::setText(const OUString& rText) { if (m_pProgressBar) m_pProgressBar->set_text(rText); } void SAL_CALL PluginProgress::setValue(sal_Int32 nValue) { if (m_pProgressBar) m_pProgressBar->set_percentage((nValue * 100) / m_nRange); } void SAL_CALL PluginProgress::reset() { if (m_pProgressBar) m_pProgressBar->set_percentage(0); } SaveDialog::SaveDialog(weld::Window* pParent, RecoveryCore* pCore) : GenericDialogController(pParent, u"svx/ui/docrecoverysavedialog.ui"_ustr, u"DocRecoverySaveDialog"_ustr) , m_pCore(pCore) , m_xFileListLB(m_xBuilder->weld_tree_view(u"filelist"_ustr)) , m_xOkBtn(m_xBuilder->weld_button(u"ok"_ustr)) { m_xFileListLB->set_size_request(-1, m_xFileListLB->get_height_rows(10)); // Prepare the office for the following crash save step. // E.g. hide all open windows so the user can't influence our // operation .-) m_pCore->doEmergencySavePrepare(); m_xOkBtn->connect_clicked(LINK(this, SaveDialog, OKButtonHdl)); // fill listbox with current open documents TURLList& rURLs = m_pCore->getURLListAccess(); for (const TURLInfo& rInfo : rURLs) { m_xFileListLB->append(u""_ustr, rInfo.DisplayName, rInfo.StandardImageId); } } SaveDialog::~SaveDialog() { } IMPL_LINK_NOARG(SaveDialog, OKButtonHdl, weld::Button&, void) { // start crash-save with progress std::unique_ptr xProgress(new SaveProgressDialog(m_xDialog.get(), m_pCore)); short nResult = xProgress->run(); xProgress.reset(); // if "CANCEL" => return "CANCEL" // if "OK" => request a restart always! if (nResult == DLG_RET_OK) nResult = DLG_RET_OK_AUTOLAUNCH; m_xDialog->response(nResult); } SaveProgressDialog::SaveProgressDialog(weld::Window* pParent, RecoveryCore* pCore) : GenericDialogController(pParent, u"svx/ui/docrecoveryprogressdialog.ui"_ustr, u"DocRecoveryProgressDialog"_ustr) , m_pCore(pCore) , m_xProgressBar(m_xBuilder->weld_progress_bar(u"progress"_ustr)) { m_xProgressBar->set_size_request(m_xProgressBar->get_approximate_digit_width() * 50, -1); m_xProgress = new PluginProgress(m_xProgressBar.get()); } SaveProgressDialog::~SaveProgressDialog() { if (m_xProgress) m_xProgress->dispose(); } short SaveProgressDialog::run() { ::SolarMutexGuard aLock; m_pCore->setProgressHandler(m_xProgress); m_pCore->setUpdateListener(this); m_pCore->doEmergencySave(); short nRet = DialogController::run(); m_pCore->setUpdateListener(nullptr); return nRet; } void SaveProgressDialog::updateItems() { } void SaveProgressDialog::stepNext(TURLInfo* ) { /* TODO if m_pCore would have a member m_mCurrentItem, you could see, who is current, who is next ... You can show this information in progress report FixText */ } void SaveProgressDialog::end() { m_xDialog->response(DLG_RET_OK); } static short impl_askUserForWizardCancel(weld::Widget* pParent, TranslateId pRes) { std::unique_ptr xQuery(Application::CreateMessageDialog(pParent, VclMessageType::Question, VclButtonsType::YesNo, SvxResId(pRes))); if (xQuery->run() == RET_YES) return DLG_RET_OK; else return DLG_RET_CANCEL; } RecoveryDialog::RecoveryDialog(weld::Window* pParent, RecoveryCore* pCore) : GenericDialogController(pParent, u"svx/ui/docrecoveryrecoverdialog.ui"_ustr, u"DocRecoveryRecoverDialog"_ustr) , m_aTitleRecoveryInProgress(SvxResId(RID_SVXSTR_RECOVERY_INPROGRESS)) , m_aRecoveryOnlyFinish (SvxResId(RID_SVXSTR_RECOVERYONLY_FINISH)) , m_aRecoveryOnlyFinishDescr(SvxResId(RID_SVXSTR_RECOVERYONLY_FINISH_DESCR)) , m_pCore(pCore) , m_eRecoveryState(RecoveryDialog::E_RECOVERY_PREPARED) , m_bWaitForCore(false) , m_bWasRecoveryStarted(false) // , m_aColumnOffset(0) , m_aToggleCount(0) , m_aSuccessRecovStr(SvxResId(RID_SVXSTR_SUCCESSRECOV)) , m_aOrigDocRecovStr(SvxResId(RID_SVXSTR_ORIGDOCRECOV)) , m_aRecovFailedStr(SvxResId(RID_SVXSTR_RECOVFAILED)) , m_aRecovInProgrStr(SvxResId(RID_SVXSTR_RECOVINPROGR)) , m_aNotRecovYetStr(SvxResId(RID_SVXSTR_NOTRECOVYET)) , m_aWillBeDiscStr(SvxResId(RID_SVXSTR_WILLDISCARD)) , m_xDescrFT(m_xBuilder->weld_label(u"desc"_ustr)) , m_xProgressBar(m_xBuilder->weld_progress_bar(u"progress"_ustr)) , m_xFileListLB(m_xBuilder->weld_tree_view(u"filelist"_ustr)) , m_xNextBtn(m_xBuilder->weld_button(u"next"_ustr)) , m_xCancelBtn(m_xBuilder->weld_button(u"cancel"_ustr)) { const auto nWidth = m_xFileListLB->get_approximate_digit_width() * 80; m_xFileListLB->set_size_request(nWidth, m_xFileListLB->get_height_rows(10)); m_xProgressBar->set_size_request(m_xProgressBar->get_approximate_digit_width() * 50, -1); m_xProgress = new PluginProgress(m_xProgressBar.get()); std::vector aWidths; aWidths.push_back(60 * nWidth / 100); aWidths.push_back(5 * nWidth / 100); m_xFileListLB->set_column_fixed_widths(aWidths); m_xFileListLB->enable_toggle_buttons(weld::ColumnToggleType::Check); m_xFileListLB->connect_toggled( LINK(this, RecoveryDialog, ToggleRowHdl) ); m_xNextBtn->set_sensitive(true); m_xNextBtn->connect_clicked( LINK( this, RecoveryDialog, NextButtonHdl ) ); m_xCancelBtn->connect_clicked( LINK( this, RecoveryDialog, CancelButtonHdl ) ); // fill list box first time TURLList& rURLList = m_pCore->getURLListAccess(); for (size_t i = 0, nCount = rURLList.size(); i < nCount; ++i) { const TURLInfo& rInfo = rURLList[i]; m_xFileListLB->append(); m_xFileListLB->set_toggle(i, TRISTATE_TRUE); m_xFileListLB->set_id(i, weld::toId(&rInfo)); m_xFileListLB->set_image(i, rInfo.StandardImageId, COLUMN_STANDARDIMAGE); m_xFileListLB->set_text(i, rInfo.DisplayName, COLUMN_DISPLAYNAME); m_xFileListLB->set_image(i, impl_getStatusImage(rInfo), COLUMN_STATUSIMAGE); m_xFileListLB->set_text(i, impl_getStatusString(rInfo), COLUMN_STATUSTEXT); m_aToggleCount++; } // mark first item if (m_xFileListLB->n_children()) m_xFileListLB->set_cursor(0); } RecoveryDialog::~RecoveryDialog() { if (m_xProgress) m_xProgress->dispose(); } bool RecoveryDialog::allSuccessfullyRecovered() { const int c = m_xFileListLB->n_children(); for (int i = 0; i < c; ++i) { TURLInfo* pInfo = weld::fromId(m_xFileListLB->get_id(i)); if (!pInfo) continue; if (pInfo->RecoveryState != E_SUCCESSFULLY_RECOVERED) return false; } return true; } short RecoveryDialog::execute() { ::SolarMutexGuard aSolarLock; switch (m_eRecoveryState) { case RecoveryDialog::E_RECOVERY_IN_PROGRESS : { // user decided to start recovery ... m_bWasRecoveryStarted = true; // do it asynchronous (to allow repaints) // and wait for this asynchronous operation. m_xDescrFT->set_label( m_aTitleRecoveryInProgress ); m_xNextBtn->set_sensitive(false); m_xCancelBtn->set_sensitive(false); m_pCore->setProgressHandler(m_xProgress); m_pCore->setUpdateListener(this); m_pCore->doRecovery(); m_bWaitForCore = true; while(m_bWaitForCore && !Application::IsQuit()) Application::Yield(); m_pCore->setUpdateListener(nullptr); // Skip FINISH button if everything was successfully recovered if (allSuccessfullyRecovered()) m_eRecoveryState = RecoveryDialog::E_RECOVERY_DONE; else m_eRecoveryState = RecoveryDialog::E_RECOVERY_CORE_DONE; return execute(); } case RecoveryDialog::E_RECOVERY_CORE_DONE : { // the core finished it's task. // let the user decide the next step. m_xDescrFT->set_label(m_aRecoveryOnlyFinishDescr); m_xNextBtn->set_label(m_aRecoveryOnlyFinish); m_xNextBtn->set_sensitive(true); m_xCancelBtn->set_sensitive(false); return 0; } case RecoveryDialog::E_RECOVERY_DONE : { // All documents were recovered. // User decided to step to the "next" wizard page. // Do it ... but check first, if there exist some // failed recovery documents. They must be saved to // a user selected directory. short nRet = DLG_RET_UNKNOWN; BrokenRecoveryDialog aBrokenRecoveryDialog(m_xDialog.get(), m_pCore, !m_bWasRecoveryStarted); OUString sSaveDir = aBrokenRecoveryDialog.getSaveDirURL(); // get the default dir if (aBrokenRecoveryDialog.isExecutionNeeded()) { nRet = aBrokenRecoveryDialog.run(); sSaveDir = aBrokenRecoveryDialog.getSaveDirURL(); } switch(nRet) { // no broken temp files exists // step to the next wizard page case DLG_RET_UNKNOWN : { m_eRecoveryState = RecoveryDialog::E_RECOVERY_HANDLED; return DLG_RET_OK; } // user decided to save the broken temp files // do and forget it // step to the next wizard page case DLG_RET_OK : { m_pCore->saveBrokenTempEntries(sSaveDir); m_pCore->forgetBrokenTempEntries(); m_eRecoveryState = RecoveryDialog::E_RECOVERY_HANDLED; return DLG_RET_OK; } // user decided to ignore broken temp files. // Ask it again ... may be this decision was wrong. // Results: // IGNORE => remove broken temp files // => step to the next wizard page // CANCEL => step back to the recovery page case DLG_RET_CANCEL : { // TODO ask user ... m_pCore->forgetBrokenTempEntries(); m_eRecoveryState = RecoveryDialog::E_RECOVERY_HANDLED; return DLG_RET_OK; } } m_eRecoveryState = RecoveryDialog::E_RECOVERY_HANDLED; return DLG_RET_OK; } case RecoveryDialog::E_RECOVERY_CANCELED : { // "YES" => break recovery // But there exist different states, where "cancel" can be called. // Handle it different. if (m_bWasRecoveryStarted) m_eRecoveryState = RecoveryDialog::E_RECOVERY_CANCELED_AFTERWARDS; else m_eRecoveryState = RecoveryDialog::E_RECOVERY_CANCELED_BEFORE; return execute(); } case RecoveryDialog::E_RECOVERY_CANCELED_BEFORE : case RecoveryDialog::E_RECOVERY_CANCELED_AFTERWARDS : { // We have to check if there exists some temp. files. // They should be saved to a user defined location. // If no temp files exists or user decided to ignore it ... // we have to remove all recovery/session data anyway! short nRet = DLG_RET_UNKNOWN; BrokenRecoveryDialog aBrokenRecoveryDialog(m_xDialog.get(), m_pCore, !m_bWasRecoveryStarted); OUString sSaveDir = aBrokenRecoveryDialog.getSaveDirURL(); // get the default save location // dialog itself checks if there is a need to copy files for this mode. // It uses the information m_bWasRecoveryStarted doing so. if (aBrokenRecoveryDialog.isExecutionNeeded()) { nRet = aBrokenRecoveryDialog.run(); sSaveDir = aBrokenRecoveryDialog.getSaveDirURL(); } // Possible states: // a) nRet == DLG_RET_UNKNOWN // dialog was not shown ... // because there exists no temp file for copy. // => remove all recovery data // b) nRet == DLG_RET_OK // dialog was shown ... // user decided to save temp files // => save all OR broken temp files (depends from the time, where cancel was called) // => remove all recovery data // c) nRet == DLG_RET_CANCEL // dialog was shown ... // user decided to ignore temp files // => remove all recovery data // => a)/c) are the same ... b) has one additional operation // b) if (nRet == DLG_RET_OK) { if (m_bWasRecoveryStarted) m_pCore->saveBrokenTempEntries(sSaveDir); else m_pCore->saveAllTempEntries(sSaveDir); } // a,b,c) if (m_bWasRecoveryStarted) m_pCore->forgetBrokenRecoveryEntries(); else m_pCore->forgetAllRecoveryEntries(); m_eRecoveryState = RecoveryDialog::E_RECOVERY_HANDLED; // THERE IS NO WAY BACK. see impl_askUserForWizardCancel()! return DLG_RET_CANCEL; } } // should never be reached .-) OSL_FAIL("Should never be reached!"); return DLG_RET_OK; } void RecoveryDialog::updateItems() { int c = m_xFileListLB->n_children(); for (int i = 0; i < c; ++i) { TURLInfo* pInfo = weld::fromId(m_xFileListLB->get_id(i)); if ( !pInfo ) continue; m_xFileListLB->set_image(i, impl_getStatusImage(*pInfo), COLUMN_STATUSIMAGE); OUString sStatus = impl_getStatusString( *pInfo ); if (!sStatus.isEmpty()) m_xFileListLB->set_text(i, sStatus, COLUMN_STATUSTEXT); } } void RecoveryDialog::stepNext(TURLInfo* pItem) { int c = m_xFileListLB->n_children(); for (int i=0; i < c; ++i) { TURLInfo* pInfo = weld::fromId(m_xFileListLB->get_id(i)); if (pInfo->ID != pItem->ID) continue; m_xFileListLB->set_cursor(i); m_xFileListLB->scroll_to_row(i); break; } } void RecoveryDialog::end() { m_bWaitForCore = false; } IMPL_LINK_NOARG(RecoveryDialog, NextButtonHdl, weld::Button&, void) { switch (m_eRecoveryState) { case RecoveryDialog::E_RECOVERY_PREPARED: m_eRecoveryState = RecoveryDialog::E_RECOVERY_IN_PROGRESS; execute(); break; case RecoveryDialog::E_RECOVERY_CORE_DONE: m_eRecoveryState = RecoveryDialog::E_RECOVERY_DONE; execute(); break; } if (m_eRecoveryState == RecoveryDialog::E_RECOVERY_HANDLED) { m_xDialog->response(DLG_RET_OK); } } IMPL_LINK_NOARG(RecoveryDialog, CancelButtonHdl, weld::Button&, void) { switch (m_eRecoveryState) { case RecoveryDialog::E_RECOVERY_PREPARED: if (impl_askUserForWizardCancel(m_xDialog.get(), RID_SVXSTR_QUERY_EXIT_RECOVERY) != DLG_RET_CANCEL) { m_eRecoveryState = RecoveryDialog::E_RECOVERY_CANCELED; execute(); } break; case RecoveryDialog::E_RECOVERY_CORE_DONE: m_eRecoveryState = RecoveryDialog::E_RECOVERY_CANCELED; execute(); break; } if (m_eRecoveryState == RecoveryDialog::E_RECOVERY_HANDLED) { m_xDialog->response(RET_CANCEL); } } IMPL_LINK_NOARG(RecoveryDialog, ToggleRowHdl, const weld::TreeView::iter_col&, void) { int aIndex = m_xFileListLB->get_selected_index(); TriState eState = m_xFileListLB->get_toggle(aIndex); if (m_bWasRecoveryStarted) { switch (eState) { case TRISTATE_FALSE: eState = TRISTATE_TRUE; break; case TRISTATE_TRUE: eState = TRISTATE_FALSE; break; default: // should never happen assert(false); break; } // revert toggle m_xFileListLB->set_toggle(aIndex, eState); } else { impl_updateItemDescription(aIndex, eState); switch (eState) { case TRISTATE_FALSE: m_aToggleCount--; break; case TRISTATE_TRUE: m_aToggleCount++; break; default: // should never happen assert(false); break; } m_xNextBtn->set_sensitive(m_aToggleCount != 0); } } OUString RecoveryDialog::impl_getStatusString( const TURLInfo& rInfo ) const { OUString sStatus; switch ( rInfo.RecoveryState ) { case E_SUCCESSFULLY_RECOVERED : sStatus = m_aSuccessRecovStr; break; case E_ORIGINAL_DOCUMENT_RECOVERED : sStatus = m_aOrigDocRecovStr; break; case E_RECOVERY_FAILED : sStatus = m_aRecovFailedStr; break; case E_RECOVERY_IS_IN_PROGRESS : sStatus = m_aRecovInProgrStr; break; case E_NOT_RECOVERED_YET : sStatus = m_aNotRecovYetStr; break; case E_WILL_BE_DISCARDED: sStatus = m_aWillBeDiscStr; break; default: break; } return sStatus; } OUString RecoveryDialog::impl_getStatusImage( const TURLInfo& rInfo ) { OUString sStatus; switch ( rInfo.RecoveryState ) { case E_SUCCESSFULLY_RECOVERED : sStatus = RID_SVXBMP_GREENCHECK; break; case E_ORIGINAL_DOCUMENT_RECOVERED : sStatus = RID_SVXBMP_YELLOWCHECK; break; case E_RECOVERY_FAILED : sStatus = RID_SVXBMP_REDCROSS; break; default: break; } return sStatus; } void RecoveryDialog::impl_updateItemDescription(int row, const TriState& rState) { TURLInfo* pInfo = reinterpret_cast(m_xFileListLB->get_id(row).toInt64()); if (!pInfo) return; switch (rState) { case TRISTATE_FALSE: pInfo->RecoveryState = ERecoveryState::E_WILL_BE_DISCARDED; pInfo->ShouldDiscard = true; break; case TRISTATE_TRUE: pInfo->RecoveryState = ERecoveryState::E_NOT_RECOVERED_YET; pInfo->ShouldDiscard = false; break; default: // should never happen assert(false); break; } OUString sStatus = impl_getStatusString(*pInfo); if (!sStatus.isEmpty()) m_xFileListLB->set_text(row, sStatus, COLUMN_STATUSTEXT); } BrokenRecoveryDialog::BrokenRecoveryDialog(weld::Window* pParent, RecoveryCore* pCore, bool bBeforeRecovery) : GenericDialogController(pParent, u"svx/ui/docrecoverybrokendialog.ui"_ustr, u"DocRecoveryBrokenDialog"_ustr) , m_pCore(pCore) , m_bBeforeRecovery(bBeforeRecovery) , m_bExecutionNeeded(false) , m_xFileListLB(m_xBuilder->weld_tree_view(u"filelist"_ustr)) , m_xSaveDirED(m_xBuilder->weld_entry(u"savedir"_ustr)) , m_xSaveDirBtn(m_xBuilder->weld_button(u"change"_ustr)) , m_xOkBtn(m_xBuilder->weld_button(u"ok"_ustr)) , m_xCancelBtn(m_xBuilder->weld_button(u"cancel"_ustr)) { m_xSaveDirBtn->connect_clicked( LINK( this, BrokenRecoveryDialog, SaveButtonHdl ) ); m_xOkBtn->connect_clicked( LINK( this, BrokenRecoveryDialog, OkButtonHdl ) ); m_xCancelBtn->connect_clicked( LINK( this, BrokenRecoveryDialog, CancelButtonHdl ) ); m_sSavePath = SvtPathOptions().GetWorkPath(); INetURLObject aObj( m_sSavePath ); OUString sPath; osl::FileBase::getSystemPathFromFileURL(aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), sPath); m_xSaveDirED->set_text(sPath); impl_refresh(); } BrokenRecoveryDialog::~BrokenRecoveryDialog() { } void BrokenRecoveryDialog::impl_refresh() { m_bExecutionNeeded = false; TURLList& rURLList = m_pCore->getURLListAccess(); for (const TURLInfo& rInfo : rURLList) { if (m_bBeforeRecovery) { // "Cancel" before recovery -> // search for any temp files! if (rInfo.TempURL.isEmpty()) continue; } else { // "Cancel" after recovery -> // search for broken temp files if (!RecoveryCore::isBrokenTempEntry(rInfo)) continue; } m_bExecutionNeeded = true; m_xFileListLB->append(weld::toId(&rInfo), rInfo.DisplayName, rInfo.StandardImageId); } m_sSavePath.clear(); m_xOkBtn->grab_focus(); } bool BrokenRecoveryDialog::isExecutionNeeded() const { return m_bExecutionNeeded; } const OUString& BrokenRecoveryDialog::getSaveDirURL() const { return m_sSavePath; } IMPL_LINK_NOARG(BrokenRecoveryDialog, OkButtonHdl, weld::Button&, void) { OUString sPhysicalPath = comphelper::string::strip(m_xSaveDirED->get_text(), ' '); OUString sURL; osl::FileBase::getFileURLFromSystemPath( sPhysicalPath, sURL ); m_sSavePath = sURL; while (m_sSavePath.isEmpty()) impl_askForSavePath(); m_xDialog->response(DLG_RET_OK); } IMPL_LINK_NOARG(BrokenRecoveryDialog, CancelButtonHdl, weld::Button&, void) { m_xDialog->response(RET_CANCEL); } IMPL_LINK_NOARG(BrokenRecoveryDialog, SaveButtonHdl, weld::Button&, void) { impl_askForSavePath(); } void BrokenRecoveryDialog::impl_askForSavePath() { css::uno::Reference< css::ui::dialogs::XFolderPicker2 > xFolderPicker = sfx2::createFolderPicker(m_pCore->getComponentContext(), m_xDialog.get()); INetURLObject aURL(m_sSavePath, INetProtocol::File); xFolderPicker->setDisplayDirectory(aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE)); short nRet = xFolderPicker->execute(); if (nRet == css::ui::dialogs::ExecutableDialogResults::OK) { m_sSavePath = xFolderPicker->getDirectory(); OUString sPath; osl::FileBase::getSystemPathFromFileURL(m_sSavePath, sPath); m_xSaveDirED->set_text(sPath); } } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */