/* -*- 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 .
*/
bool RecoveryCore::isBrokenTempEntry(const TURLInfo& rInfo)
{ if (rInfo.TempURL.isEmpty()) returnfalse;
// 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)
) returnfalse;
returntrue;
}
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;
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;
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;
// 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<css::beans::PropertyValue> 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;
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;
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;
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;
/* 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;
// 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();
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
*/
}
// 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()
{ constint c = m_xFileListLB->n_children(); for (int i = 0; i < c; ++i)
{
TURLInfo* pInfo = weld::fromId<TURLInfo*>(m_xFileListLB->get_id(i)); if (!pInfo) continue;
if (pInfo->RecoveryState != E_SUCCESSFULLY_RECOVERED) returnfalse;
} returntrue;
}
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();
// 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;
}
}
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<TURLInfo*>(m_xFileListLB->get_id(i)); if ( !pInfo ) continue;
void RecoveryDialog::stepNext(TURLInfo* pItem)
{ int c = m_xFileListLB->n_children(); for (int i=0; i < c; ++i)
{
TURLInfo* pInfo = weld::fromId<TURLInfo*>(m_xFileListLB->get_id(i)); if (pInfo->ID != pItem->ID) continue;
switch (eState)
{ case TRISTATE_FALSE:
m_aToggleCount--; break; case TRISTATE_TRUE:
m_aToggleCount++; break; default: // should never happen
assert(false); break;
}