LibreOffice Module dbaccess (master) 1
dbdocrecovery.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
21#include <sdbcoretools.hxx>
22#include "storagetextstream.hxx"
24#include "subcomponents.hxx"
25
26#include <com/sun/star/sdb/application/XDatabaseDocumentUI.hpp>
27#include <com/sun/star/embed/ElementModes.hpp>
28#include <com/sun/star/io/TextInputStream.hpp>
29#include <com/sun/star/util/XModifiable.hpp>
30
31#include <o3tl/string_view.hxx>
32#include <rtl/ustrbuf.hxx>
33#include <sal/log.hxx>
35
36namespace dbaccess
37{
38
39 using css::uno::Reference;
40 using css::uno::UNO_QUERY;
41 using css::uno::UNO_QUERY_THROW;
42 using css::uno::UNO_SET_THROW;
43 using css::uno::Exception;
44 using css::uno::Sequence;
45 using css::uno::XComponentContext;
46 using css::embed::XStorage;
47 using css::frame::XController;
48 using css::sdb::application::XDatabaseDocumentUI;
49 using css::lang::XComponent;
50 using css::io::XStream;
51 using css::io::TextInputStream;
52 using css::io::XTextInputStream2;
53 using css::util::XModifiable;
54
55 namespace ElementModes = css::embed::ElementModes;
56
57 // helpers
58 namespace
59 {
60 void lcl_getPersistentRepresentation( const MapStringToCompDesc::value_type& i_rComponentDesc, OUStringBuffer& o_rBuffer )
61 {
62 o_rBuffer.append( i_rComponentDesc.first );
63 o_rBuffer.append( '=' );
64 o_rBuffer.append( i_rComponentDesc.second.sName );
65 o_rBuffer.append( ',' );
66 o_rBuffer.append( sal_Unicode( i_rComponentDesc.second.bForEditing ? '1' : '0' ) );
67 }
68
69 bool lcl_extractCompDesc( std::u16string_view i_rIniLine, OUString& o_rStorName, SubComponentDescriptor& o_rCompDesc )
70 {
71 const size_t nEqualSignPos = i_rIniLine.find( '=' );
72 if ( nEqualSignPos == 0 || nEqualSignPos == std::u16string_view::npos )
73 {
74 OSL_FAIL( "lcl_extractCompDesc: invalid map file entry - unexpected pos of '='" );
75 return false;
76 }
77 o_rStorName = i_rIniLine.substr( 0, nEqualSignPos );
78
79 const size_t nCommaPos = i_rIniLine.rfind( ',' );
80 if ( nCommaPos != i_rIniLine.size() - 2 )
81 {
82 OSL_FAIL( "lcl_extractCompDesc: invalid map file entry - unexpected pos of ','" );
83 return false;
84 }
85 o_rCompDesc.sName = i_rIniLine.substr( nEqualSignPos + 1, nCommaPos - nEqualSignPos - 1 );
86 o_rCompDesc.bForEditing = ( i_rIniLine[ nCommaPos + 1 ] == '1' );
87 return true;
88 }
89
90 constexpr OUStringLiteral sRecoveryDataSubStorageName = u"recovery";
91
92 constexpr OUStringLiteral sObjectMapStreamName = u"storage-component-map.ini";
93
94 void lcl_writeObjectMap_throw( const Reference<XComponentContext> & i_rContext, const Reference< XStorage >& i_rStorage,
95 const MapStringToCompDesc& i_mapStorageToCompDesc )
96 {
97 if ( i_mapStorageToCompDesc.empty() )
98 // nothing to do
99 return;
100
101 StorageTextOutputStream aTextOutput( i_rContext, i_rStorage, sObjectMapStreamName );
102
103 aTextOutput.writeLine( "[storages]" );
104
105 for (auto const& elem : i_mapStorageToCompDesc)
106 {
107 OUStringBuffer aLine;
108 lcl_getPersistentRepresentation(elem, aLine);
109
110 aTextOutput.writeLine( aLine.makeStringAndClear() );
111 }
112
113 aTextOutput.writeLine();
114 }
115
116 bool lcl_isSectionStart( std::u16string_view i_rIniLine, OUString& o_rSectionName )
117 {
118 const size_t nLen = i_rIniLine.size();
119 if ( o3tl::starts_with(i_rIniLine, u"[") && o3tl::ends_with(i_rIniLine, u"]") )
120 {
121 o_rSectionName = i_rIniLine.substr( 1, nLen -2 );
122 return true;
123 }
124 return false;
125 }
126
127 void lcl_stripTrailingLineFeed( OUString& io_rLine )
128 {
129 const sal_Int32 nLen = io_rLine.getLength();
130 if ( io_rLine.endsWith("\n") )
131 io_rLine = io_rLine.copy( 0, nLen - 1 );
132 }
133
134 void lcl_readObjectMap_throw( const Reference<XComponentContext> & i_rxContext, const Reference< XStorage >& i_rStorage,
135 MapStringToCompDesc& o_mapStorageToObjectName )
136 {
137 ENSURE_OR_THROW( i_rStorage.is(), "invalid storage" );
138 if ( !i_rStorage->hasByName( sObjectMapStreamName ) )
139 { // nothing to do, though suspicious
140 OSL_FAIL( "lcl_readObjectMap_throw: if there's no map file, then there's expected to be no storage, too!" );
141 return;
142 }
143
144 Reference< XStream > xIniStream( i_rStorage->openStreamElement(
145 sObjectMapStreamName, ElementModes::READ ), UNO_SET_THROW );
146
147 Reference< XTextInputStream2 > xTextInput = TextInputStream::create( i_rxContext );
148 xTextInput->setEncoding( "UTF-8" );
149 xTextInput->setInputStream( xIniStream->getInputStream() );
150
151 OUString sCurrentSection;
152 bool bCurrentSectionIsKnownToBeUnsupported = true;
153 while ( !xTextInput->isEOF() )
154 {
155 OUString sLine = xTextInput->readLine();
156 lcl_stripTrailingLineFeed( sLine );
157
158 if ( sLine.isEmpty() )
159 continue;
160
161 if ( lcl_isSectionStart( sLine, sCurrentSection ) )
162 {
163 bCurrentSectionIsKnownToBeUnsupported = false;
164 continue;
165 }
166
167 if ( bCurrentSectionIsKnownToBeUnsupported )
168 continue;
169
170 // the only section we support so far is "storages"
171 if ( sCurrentSection != "storages" )
172 {
173 bCurrentSectionIsKnownToBeUnsupported = true;
174 continue;
175 }
176
177 OUString sStorageName;
178 SubComponentDescriptor aCompDesc;
179 if ( !lcl_extractCompDesc( sLine, sStorageName, aCompDesc ) )
180 continue;
181 o_mapStorageToObjectName[ sStorageName ] = aCompDesc;
182 }
183 }
184
185 void lcl_markModified( const Reference< XComponent >& i_rSubComponent )
186 {
187 const Reference< XModifiable > xModify( i_rSubComponent, UNO_QUERY );
188 if ( !xModify.is() )
189 {
190 OSL_FAIL( "lcl_markModified: unhandled case!" );
191 return;
192 }
193
194 xModify->setModified( true );
195 }
196 }
197
198 // DatabaseDocumentRecovery
199 DatabaseDocumentRecovery::DatabaseDocumentRecovery( const Reference<XComponentContext> & i_rContext )
200 : mxContext( i_rContext )
201 {
202 }
203
205 {
206 }
207
208 void DatabaseDocumentRecovery::saveModifiedSubComponents( const Reference< XStorage >& i_rTargetStorage,
209 const std::vector< Reference< XController > >& i_rControllers )
210 {
211 ENSURE_OR_THROW( i_rTargetStorage.is(), "invalid document storage" );
212
213 // create a sub storage for recovery data
214 if ( i_rTargetStorage->hasByName( sRecoveryDataSubStorageName ) )
215 i_rTargetStorage->removeElement( sRecoveryDataSubStorageName );
216 Reference< XStorage > xRecoveryStorage = i_rTargetStorage->openStorageElement( sRecoveryDataSubStorageName, ElementModes::READWRITE );
217
218 // store recovery data for open sub components of the given controller(s)
219 if ( !i_rControllers.empty() )
220 {
221 ENSURE_OR_THROW( i_rControllers.size() == 1, "can't handle more than one controller" );
222 // At the moment, there can be only one view to a database document. If we ever allow for more than this,
223 // then we need a concept for sub documents opened from different controllers (i.e. two document views,
224 // and the user opens the very same form in both views). And depending on this, we need a concept for
225 // how those are saved to the recovery file.
226
227 MapCompTypeToCompDescs aMapCompDescs;
228
229 for (auto const& controller : i_rControllers)
230 {
231 Reference< XDatabaseDocumentUI > xDatabaseUI(controller, UNO_QUERY_THROW);
232 const Sequence< Reference< XComponent > > aComponents( xDatabaseUI->getSubComponents() );
233
234 for ( auto const & component : aComponents )
235 {
236 SubComponentRecovery aComponentRecovery( mxContext, xDatabaseUI, component );
237 aComponentRecovery.saveToRecoveryStorage( xRecoveryStorage, aMapCompDescs );
238 }
239 }
240
241 for (auto const& elem : aMapCompDescs)
242 {
243 Reference< XStorage > xComponentsStor( xRecoveryStorage->openStorageElement(
244 SubComponentRecovery::getComponentsStorageName( elem.first ), ElementModes::WRITE | ElementModes::NOCREATE ) );
245 lcl_writeObjectMap_throw( mxContext, xComponentsStor, elem.second );
246 tools::stor::commitStorageIfWriteable( xComponentsStor );
247 }
248 }
249
250 // commit the recovery storage
251 tools::stor::commitStorageIfWriteable( xRecoveryStorage );
252 }
253
254 void DatabaseDocumentRecovery::recoverSubDocuments( const Reference< XStorage >& i_rDocumentStorage,
255 const Reference< XController >& i_rTargetController )
256 {
257 ENSURE_OR_THROW( i_rDocumentStorage.is(), "illegal document storage" );
258 Reference< XDatabaseDocumentUI > xDocumentUI( i_rTargetController, UNO_QUERY_THROW );
259
260 if ( !i_rDocumentStorage->hasByName( sRecoveryDataSubStorageName ) )
261 // that's allowed
262 return;
263
264 // the "recovery" sub storage
265 Reference< XStorage > xRecoveryStorage = i_rDocumentStorage->openStorageElement( sRecoveryDataSubStorageName, ElementModes::READ );
266
267 // read the map from sub storages to object names
268 MapCompTypeToCompDescs aMapCompDescs;
269 const SubComponentType aKnownTypes[] = { TABLE, QUERY, FORM, REPORT, RELATION_DESIGN };
270 for (SubComponentType aKnownType : aKnownTypes)
271 {
272 if ( !xRecoveryStorage->hasByName( SubComponentRecovery::getComponentsStorageName( aKnownType ) ) )
273 continue;
274
275 Reference< XStorage > xComponentsStor( xRecoveryStorage->openStorageElement(
276 SubComponentRecovery::getComponentsStorageName( aKnownType ), ElementModes::READ ) );
277 lcl_readObjectMap_throw( mxContext, xComponentsStor, aMapCompDescs[ aKnownType ] );
278 xComponentsStor->dispose();
279 }
280
281 // recover all sub components as indicated by the map
282 for (auto const& elemMapCompDescs : aMapCompDescs)
283 {
284 const SubComponentType eComponentType = elemMapCompDescs.first;
285
286 // the storage for all components of the current type
287 Reference< XStorage > xComponentsStor( xRecoveryStorage->openStorageElement(
288 SubComponentRecovery::getComponentsStorageName( eComponentType ), ElementModes::READ ), UNO_SET_THROW );
289
290 // loop through all components of this type
291 for (auto const& elem : elemMapCompDescs.second)
292 {
293 const OUString sComponentName(elem.second.sName);
294 if ( !xComponentsStor->hasByName(elem.first) )
295 {
296 SAL_WARN( "dbaccess",
297 "DatabaseDocumentRecovery::recoverSubDocuments: inconsistent recovery storage: storage '" <<
298 elem.first <<
299 "' not found in '" <<
301 "', but required per map file!" );
302 continue;
303 }
304
305 // the controller needs to have a connection to be able to open sub components
306 if ( !xDocumentUI->isConnected() )
307 xDocumentUI->connect();
308
309 // recover the single component
310 Reference< XStorage > xCompStor( xComponentsStor->openStorageElement( elem.first, ElementModes::READ ) );
311 SubComponentRecovery aComponentRecovery( mxContext, xDocumentUI, eComponentType );
312 Reference< XComponent > xSubComponent( aComponentRecovery.recoverFromStorage( xCompStor, sComponentName, elem.second.bForEditing ) );
313
314 // at the moment, we only store, during session save, sub components which are modified. So, set this
315 // recovered sub component to "modified", too.
316 lcl_markModified( xSubComponent );
317 }
318
319 xComponentsStor->dispose();
320 }
321
322 xRecoveryStorage->dispose();
323
324 // now that we successfully recovered, removed the "recovery" sub storage
325 try
326 {
327 i_rDocumentStorage->removeElement( sRecoveryDataSubStorageName );
328 }
329 catch( const Exception& )
330 {
331 DBG_UNHANDLED_EXCEPTION("dbaccess");
332 }
333 }
334
335} // namespace dbaccess
336
337/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
void recoverSubDocuments(const css::uno::Reference< css::embed::XStorage > &i_rDocumentStorage, const css::uno::Reference< css::frame::XController > &i_rTargetController)
recovery sub components from the given document storage, if applicable
DatabaseDocumentRecovery(const css::uno::Reference< css::uno::XComponentContext > &i_rContext)
void saveModifiedSubComponents(const css::uno::Reference< css::embed::XStorage > &i_rTargetStorage, const std::vector< css::uno::Reference< css::frame::XController > > &i_rControllers)
saves the modified sub components of the given controller(s) to the "recovery" sub storage of the doc...
css::uno::Reference< css::uno::XComponentContext > mxContext
css::uno::Reference< css::lang::XComponent > recoverFromStorage(const css::uno::Reference< css::embed::XStorage > &i_rRecoveryStorage, const OUString &i_rComponentName, const bool i_bForEditing)
static OUString getComponentsStorageName(const SubComponentType i_eType)
void saveToRecoveryStorage(const css::uno::Reference< css::embed::XStorage > &i_rRecoveryStorage, MapCompTypeToCompDescs &io_mapCompDescs)
#define ENSURE_OR_THROW(c, m)
#define DBG_UNHANDLED_EXCEPTION(...)
uno::Reference< uno::XComponentContext > mxContext
float u
#define SAL_WARN(area, stream)
@ Exception
bool commitStorageIfWriteable(const css::uno::Reference< css::embed::XStorage > &_rxStorage)
commits a given storage if it's not readonly
std::unordered_map< OUString, SubComponentDescriptor > MapStringToCompDesc
std::map< SubComponentType, MapStringToCompDesc > MapCompTypeToCompDescs
constexpr bool ends_with(std::basic_string_view< charT, traits > sv, std::basic_string_view< charT, traits > x) noexcept
constexpr bool starts_with(std::basic_string_view< charT, traits > sv, std::basic_string_view< charT, traits > x) noexcept
sal_uInt16 sal_Unicode