xref: /core/desktop/source/app/dispatchwatcher.cxx (revision 90eb9ea8)
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 
20 #include <sal/config.h>
21 
22 #include <sal/log.hxx>
23 #include <sfx2/docfilt.hxx>
24 #include <sfx2/fcontnr.hxx>
25 #include <svl/fstathelper.hxx>
26 
27 #include <app.hxx>
28 #include "dispatchwatcher.hxx"
29 #include "officeipcthread.hxx"
30 #include <rtl/ustring.hxx>
31 #include <comphelper/processfactory.hxx>
32 #include <comphelper/synchronousdispatch.hxx>
33 #include <com/sun/star/io/IOException.hpp>
34 #include <com/sun/star/util/XCloseable.hpp>
35 #include <com/sun/star/util/CloseVetoException.hpp>
36 #include <com/sun/star/task/InteractionHandler.hpp>
37 #include <com/sun/star/util/URL.hpp>
38 #include <com/sun/star/frame/Desktop.hpp>
39 #include <com/sun/star/container/XContainerQuery.hpp>
40 #include <com/sun/star/container/XEnumeration.hpp>
41 #include <com/sun/star/frame/XDispatch.hpp>
42 #include <com/sun/star/frame/XNotifyingDispatch.hpp>
43 #include <com/sun/star/beans/PropertyValue.hpp>
44 #include <com/sun/star/view/XPrintable.hpp>
45 #include <com/sun/star/util/URLTransformer.hpp>
46 #include <com/sun/star/util/XURLTransformer.hpp>
47 #include <com/sun/star/document/MacroExecMode.hpp>
48 #include <com/sun/star/document/XTypeDetection.hpp>
49 #include <com/sun/star/document/UpdateDocMode.hpp>
50 #include <com/sun/star/frame/XStorable.hpp>
51 #include <com/sun/star/script/XLibraryContainer2.hpp>
52 #include <com/sun/star/document/XEmbeddedScripts.hpp>
53 
54 #include <comphelper/propertyvalue.hxx>
55 #include <comphelper/sequence.hxx>
56 #include <comphelper/diagnose_ex.hxx>
57 #include <tools/urlobj.hxx>
58 #include <unotools/mediadescriptor.hxx>
59 #include <unotools/tempfile.hxx>
60 
61 #include <osl/thread.hxx>
62 #include <osl/file.hxx>
63 #include <iostream>
64 #include <string_view>
65 #include <utility>
66 
67 using namespace ::osl;
68 using namespace ::com::sun::star::uno;
69 using namespace ::com::sun::star::util;
70 using namespace ::com::sun::star::lang;
71 using namespace ::com::sun::star::frame;
72 using namespace ::com::sun::star::container;
73 using namespace ::com::sun::star::beans;
74 using namespace ::com::sun::star::view;
75 using namespace ::com::sun::star::task;
76 using namespace ::com::sun::star::document;
77 
78 namespace document = ::com::sun::star::document;
79 
80 namespace desktop
81 {
82 
83 namespace {
84 
85 struct DispatchHolder
86 {
DispatchHolderdesktop::__anon328ace6d0111::DispatchHolder87     DispatchHolder( URL _aURL, Reference< XDispatch > const & rDispatch ) :
88         aURL(std::move( _aURL )), xDispatch( rDispatch ) {}
89 
90     URL aURL;
91     Reference< XDispatch > xDispatch;
92 };
93 
impl_lookupExportFilterForUrl(std::u16string_view rUrl,std::u16string_view rFactory)94 std::shared_ptr<const SfxFilter> impl_lookupExportFilterForUrl( std::u16string_view rUrl, std::u16string_view rFactory )
95 {
96     // create the list of filters
97     OUString sQuery = "getSortedFilterList()"
98         ":module=" +
99         OUString::Concat(rFactory) + // use long name here !
100         ":iflags=" +
101         OUString::number(static_cast<sal_Int32>(SfxFilterFlags::EXPORT)) +
102         ":eflags=" +
103         OUString::number(static_cast<int>(SFX_FILTER_NOTINSTALLED));
104 
105     const Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
106     const Reference< XContainerQuery > xFilterFactory(
107             xContext->getServiceManager()->createInstanceWithContext( u"com.sun.star.document.FilterFactory"_ustr, xContext ),
108             UNO_QUERY_THROW );
109 
110     std::shared_ptr<const SfxFilter> pBestMatch;
111 
112     const Reference< XEnumeration > xFilterEnum(
113             xFilterFactory->createSubSetEnumerationByQuery( sQuery ), UNO_SET_THROW );
114     while ( xFilterEnum->hasMoreElements() )
115     {
116         comphelper::SequenceAsHashMap aFilterProps( xFilterEnum->nextElement() );
117         const OUString aName( aFilterProps.getUnpackedValueOrDefault( u"Name"_ustr, OUString() ) );
118         if ( !aName.isEmpty() )
119         {
120             std::shared_ptr<const SfxFilter> pFilter( SfxFilter::GetFilterByName( aName ) );
121             if ( pFilter && pFilter->CanExport() && pFilter->GetWildcard().Matches( rUrl ) )
122             {
123                 if ( !pBestMatch || ( SfxFilterFlags::PREFERED & pFilter->GetFilterFlags() ) )
124                     pBestMatch = pFilter;
125             }
126         }
127     }
128 
129     return pBestMatch;
130 }
131 
impl_getExportFilterFromUrl(const OUString & rUrl,const OUString & rFactory)132 std::shared_ptr<const SfxFilter> impl_getExportFilterFromUrl(
133         const OUString& rUrl, const OUString& rFactory)
134 {
135     try
136     {
137         const Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() );
138         const Reference< document::XTypeDetection > xTypeDetector(
139             xContext->getServiceManager()->createInstanceWithContext( u"com.sun.star.document.TypeDetection"_ustr, xContext ),
140             UNO_QUERY_THROW );
141         const OUString aTypeName( xTypeDetector->queryTypeByURL( rUrl ) );
142 
143         std::shared_ptr<const SfxFilter> pFilter( SfxFilterMatcher( rFactory ).GetFilter4EA( aTypeName, SfxFilterFlags::EXPORT ) );
144         if ( !pFilter )
145             pFilter = impl_lookupExportFilterForUrl( rUrl, rFactory );
146         if ( !pFilter )
147         {
148             OUString aTempName;
149             FileBase::getSystemPathFromFileURL( rUrl, aTempName );
150             OString aSource = OUStringToOString ( aTempName, osl_getThreadTextEncoding() );
151             std::cerr << "Error: no export filter for " << aSource << " found, aborting." << std::endl;
152         }
153 
154         return pFilter;
155     }
156     catch ( const Exception& )
157     {
158         return nullptr;
159     }
160 }
161 
impl_GuessFilter(const OUString & rUrlOut,const OUString & rDocService)162 OUString impl_GuessFilter( const OUString& rUrlOut, const OUString& rDocService )
163 {
164     OUString aOutFilter;
165     std::shared_ptr<const SfxFilter> pOutFilter = impl_getExportFilterFromUrl( rUrlOut, rDocService );
166     if (pOutFilter)
167         aOutFilter = pOutFilter->GetFilterName();
168 
169     return aOutFilter;
170 }
171 
172 /// dump scripts in a document to the console.
scriptCat(const Reference<XModel> & xDoc)173 void scriptCat(const Reference< XModel >& xDoc )
174 {
175     Reference< XEmbeddedScripts > xScriptAccess( xDoc, UNO_QUERY );
176     if (!xScriptAccess)
177     {
178         std::cout << "No script access\n";
179         return;
180     }
181 
182     // ignore xScriptAccess->getDialogLibraries() for now
183     Reference< css::script::XLibraryContainer2 > xLibraries(
184         xScriptAccess->getBasicLibraries() );
185 
186     if ( !xLibraries.is() )
187     {
188         std::cout << "No script libraries\n";
189         return;
190     }
191 
192     const Sequence< OUString > aLibNames = xLibraries->getElementNames();
193     std::cout << "Libraries: " << aLibNames.getLength() << "\n";
194     for (OUString const & libName : aLibNames)
195     {
196         std::cout << "Library: '" << libName << "' children: ";
197         Reference< XNameContainer > xContainer;
198         try {
199             if (!xLibraries->isLibraryLoaded( libName ))
200                 xLibraries->loadLibrary( libName );
201             xContainer = Reference< XNameContainer >(
202                 xLibraries->getByName( libName ), UNO_QUERY );
203         }
204         catch (const css::uno::Exception &e)
205         {
206             std::cout << "[" << libName << "] - failed to load library: " << e.Message << "\n";
207             continue;
208         }
209         if( !xContainer.is() )
210             std::cout << "0\n";
211         else
212         {
213             Sequence< OUString > aObjectNames = xContainer->getElementNames();
214 
215             std::cout << aObjectNames.getLength() << "\n\n";
216             for ( sal_Int32 j = 0 ; j < aObjectNames.getLength() ; ++j )
217             {
218                 const OUString &rObjectName = aObjectNames[j];
219 
220                 try
221                 {
222                     Any aCode = xContainer->getByName( rObjectName );
223                     OUString aCodeString;
224 
225                     if (! (aCode >>= aCodeString ) )
226                         std::cout << "[" << rObjectName << "] - error fetching code\n";
227                     else
228                         std::cout << "[" << rObjectName << "]\n"
229                                   << aCodeString.trim()
230                                   << "\n[/" << rObjectName << "]\n";
231                 }
232                 catch (const css::uno::Exception &e)
233                 {
234                     std::cout << "[" << rObjectName << "] - exception " << e.Message << " fetching code\n";
235                 }
236 
237                 if (j < aObjectNames.getLength() - 1)
238                     std::cout << "\n----------------------------------------------------------\n";
239                 std::cout << "\n";
240             }
241         }
242     }
243 }
244 
245 // Perform batch print
batchPrint(std::u16string_view rPrinterName,const Reference<XPrintable> & xDoc,const INetURLObject & aObj,const OUString & aName)246 void batchPrint( std::u16string_view rPrinterName, const Reference< XPrintable > &xDoc,
247                  const INetURLObject &aObj, const OUString &aName )
248 {
249     OUString aFilterOut;
250     OUString aPrinterName;
251     size_t nPathIndex =  rPrinterName.rfind( ';' );
252     if( nPathIndex != std::u16string_view::npos )
253         aFilterOut=rPrinterName.substr( nPathIndex+1 );
254     if( nPathIndex != 0 )
255         aPrinterName=rPrinterName.substr( 0, nPathIndex );
256 
257     INetURLObject aOutFilename( aObj );
258     aOutFilename.SetExtension( u"pdf" );
259     FileBase::getFileURLFromSystemPath( aFilterOut, aFilterOut );
260     OUString aOutFile = aFilterOut + "/" + aOutFilename.getName();
261 
262     OUString aTempName;
263     FileBase::getSystemPathFromFileURL( aName, aTempName );
264     OString aSource8 = OUStringToOString ( aTempName, osl_getThreadTextEncoding() );
265     FileBase::getSystemPathFromFileURL( aOutFile, aTempName );
266     OString aTargetURL8 = OUStringToOString(aTempName, osl_getThreadTextEncoding() );
267 
268     std::cout << "print " << aSource8 << " -> " << aTargetURL8;
269     std::cout << " using " << (aPrinterName.isEmpty() ? "<default_printer>"_ostr : OUStringToOString( aPrinterName, osl_getThreadTextEncoding() ));
270     std::cout << std::endl;
271 
272     // create the custom printer, if given
273     Sequence < PropertyValue > aPrinterArgs;
274     if( !aPrinterName.isEmpty() )
275     {
276         aPrinterArgs = { comphelper::makePropertyValue(u"Name"_ustr, aPrinterName) };
277         xDoc->setPrinter( aPrinterArgs );
278     }
279 
280     // print ( also without user interaction )
281     aPrinterArgs = { comphelper::makePropertyValue(u"FileName"_ustr, aOutFile),
282                      comphelper::makePropertyValue(u"Wait"_ustr, true) };
283     xDoc->print( aPrinterArgs );
284 }
285 
286 // Get xDoc module name
getName(const Reference<XInterface> & xDoc)287 OUString getName(const Reference< XInterface > & xDoc)
288 {
289     Reference< XModel > xModel( xDoc, UNO_QUERY );
290     if (!xModel)
291         return OUString();
292     utl::MediaDescriptor aMediaDesc( xModel->getArgs() );
293     OUString aDocService = aMediaDesc.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_DOCUMENTSERVICE, OUString() );
294     if (aDocService == "com.sun.star.text.TextDocument")
295         return u"Writer"_ustr;
296     else if (aDocService == "com.sun.star.text.GlobalDocument")
297         return u"Writer master"_ustr;
298     else if (aDocService == "com.sun.star.text.WebDocument")
299         return u"Writer/Web"_ustr;
300     else if (aDocService == "com.sun.star.drawing.DrawingDocument")
301         return u"Draw"_ustr;
302     else if (aDocService == "com.sun.star.presentation.PresentationDocument")
303         return u"Impress"_ustr;
304     else if (aDocService == "com.sun.star.sheet.SpreadsheetDocument")
305         return u"Calc"_ustr;
306     else if (aDocService == "com.sun.star.script.BasicIDE")
307         return u"Basic"_ustr;
308     else if (aDocService == "com.sun.star.formula.FormulaProperties")
309         return u"Math"_ustr;
310     else if (aDocService == "com.sun.star.sdb.RelationDesign")
311         return u"Relation Design"_ustr;
312     else if (aDocService == "com.sun.star.sdb.QueryDesign")
313         return u"Query Design"_ustr;
314     else if (aDocService == "com.sun.star.sdb.TableDesign")
315         return u"Table Design"_ustr;
316     else if (aDocService == "com.sun.star.sdb.DataSourceBrowser")
317         return u"Data Source Browser"_ustr;
318     else if (aDocService == "com.sun.star.sdb.DatabaseDocument")
319         return u"Database"_ustr;
320 
321     return OUString();
322 }
323 
324 } // anonymous namespace
325 
DispatchWatcher()326 DispatchWatcher::DispatchWatcher()
327     : m_nRequestCount(0)
328 {
329 }
330 
331 
~DispatchWatcher()332 DispatchWatcher::~DispatchWatcher()
333 {
334 }
335 
336 
executeDispatchRequests(const std::vector<DispatchRequest> & aDispatchRequestsList,bool bNoTerminate)337 bool DispatchWatcher::executeDispatchRequests( const std::vector<DispatchRequest>& aDispatchRequestsList, bool bNoTerminate )
338 {
339     Reference< XDesktop2 > xDesktop = css::frame::Desktop::create( ::comphelper::getProcessComponentContext() );
340 
341     std::vector< DispatchHolder >   aDispatches;
342     bool                     bSetInputFilter = false;
343     OUString                 aForcedInputFilter;
344 
345     for (auto const & aDispatchRequest: aDispatchRequestsList)
346     {
347         // Set Input Filter
348         if ( aDispatchRequest.aRequestType == REQUEST_INFILTER )
349         {
350             bSetInputFilter = true;
351             aForcedInputFilter = aDispatchRequest.aURL;
352             RequestHandler::RequestsCompleted();
353             continue;
354         }
355 
356         // create parameter array
357         std::vector<PropertyValue> aArgs;
358 
359         // mark request as user interaction from outside
360         aArgs.emplace_back("Referer", 0, Any(u"private:OpenEvent"_ustr),
361                            PropertyState_DIRECT_VALUE);
362 
363         OUString aTarget(u"_default"_ustr);
364 
365         if ( aDispatchRequest.aRequestType == REQUEST_PRINT ||
366              aDispatchRequest.aRequestType == REQUEST_PRINTTO ||
367              aDispatchRequest.aRequestType == REQUEST_BATCHPRINT ||
368              aDispatchRequest.aRequestType == REQUEST_CONVERSION ||
369              aDispatchRequest.aRequestType == REQUEST_CAT ||
370              aDispatchRequest.aRequestType == REQUEST_SCRIPT_CAT)
371         {
372             // documents opened for printing are opened readonly because they must be opened as a
373             // new document and this document could be open already
374             aArgs.emplace_back("ReadOnly", 0, Any(true), PropertyState_DIRECT_VALUE);
375             // always open a new document for printing, because it must be disposed afterwards
376             aArgs.emplace_back("OpenNewView", 0, Any(true), PropertyState_DIRECT_VALUE);
377             // printing is done in a hidden view
378             aArgs.emplace_back("Hidden", 0, Any(true), PropertyState_DIRECT_VALUE);
379             // load document for printing without user interaction
380             aArgs.emplace_back("Silent", 0, Any(true), PropertyState_DIRECT_VALUE);
381 
382             // hidden documents should never be put into open tasks
383             aTarget = "_blank";
384         }
385         else
386         {
387             Reference < XInteractionHandler2 > xInteraction(
388                 InteractionHandler::createWithParent(::comphelper::getProcessComponentContext(), nullptr) );
389 
390             aArgs.emplace_back("InteractionHandler", 0, Any(xInteraction),
391                                PropertyState_DIRECT_VALUE);
392 
393             aArgs.emplace_back("MacroExecutionMode", 0,
394                                Any(css::document::MacroExecMode::USE_CONFIG),
395                                PropertyState_DIRECT_VALUE);
396 
397             aArgs.emplace_back("UpdateDocMode", 0,
398                                Any(css::document::UpdateDocMode::ACCORDING_TO_CONFIG),
399                                PropertyState_DIRECT_VALUE);
400         }
401 
402         if ( !aDispatchRequest.aPreselectedFactory.isEmpty() )
403         {
404             aArgs.emplace_back(utl::MediaDescriptor::PROP_DOCUMENTSERVICE, 0,
405                                Any(aDispatchRequest.aPreselectedFactory),
406                                PropertyState_DIRECT_VALUE);
407         }
408 
409         OUString aName( GetURL_Impl( aDispatchRequest.aURL, aDispatchRequest.aCwdUrl ) );
410 
411         // load the document ... if they are loadable!
412         // Otherwise try to dispatch it ...
413         Reference < XPrintable > xDoc;
414         if(
415             ( aName.startsWith( ".uno" ) )  ||
416             ( aName.startsWith( "slot:" ) )  ||
417             ( aName.startsWith( "macro:" ) )  ||
418             ( aName.startsWith("vnd.sun.star.script") )
419           )
420         {
421             // Attention: URL must be parsed full. Otherwise some detections on it will fail!
422             // It doesn't matter, if parser isn't available. Because; We try loading of URL then ...
423             URL             aURL ;
424             aURL.Complete = aName;
425 
426             Reference < XDispatch >         xDispatcher ;
427             Reference < XURLTransformer >   xParser     ( URLTransformer::create(::comphelper::getProcessComponentContext()) );
428 
429             if( xParser.is() )
430                 xParser->parseStrict( aURL );
431 
432             xDispatcher = xDesktop->queryDispatch( aURL, OUString(), 0 );
433             SAL_WARN_IF(
434                 !xDispatcher.is(), "desktop.app",
435                 "unsupported dispatch request <" << aName << ">");
436             if( xDispatcher.is() )
437             {
438                 // Remember request so we can find it in statusChanged!
439                 m_nRequestCount++;
440 
441                 // Use local vector to store dispatcher because we have to fill our request container before
442                 // we can dispatch. Otherwise it would be possible that statusChanged is called before we dispatched all requests!!
443                 aDispatches.emplace_back( aURL, xDispatcher );
444             }
445         }
446         else if ( aName.startsWith( "service:" ) )
447         {
448             // TODO: the dispatch has to be done for loadComponentFromURL as well.
449             URL             aURL ;
450             aURL.Complete = aName;
451 
452             Reference < XDispatch >         xDispatcher ;
453             Reference < XURLTransformer >   xParser     ( URLTransformer::create(::comphelper::getProcessComponentContext()) );
454 
455             if( xParser.is() )
456                 xParser->parseStrict( aURL );
457 
458             xDispatcher = xDesktop->queryDispatch( aURL, OUString(), 0 );
459 
460             if( xDispatcher.is() )
461             {
462                 try
463                 {
464                     // We have to be listener to catch errors during dispatching URLs.
465                     // Otherwise it would be possible to have an office running without an open
466                     // window!!
467                     Sequence < PropertyValue > aArgs2{ comphelper::makePropertyValue(u"SynchronMode"_ustr,
468                                                                                      true) };
469                     Reference < XNotifyingDispatch > xDisp( xDispatcher, UNO_QUERY );
470                     if ( xDisp.is() )
471                         xDisp->dispatchWithNotification( aURL, aArgs2, this );
472                     else
473                         xDispatcher->dispatch( aURL, aArgs2 );
474                 }
475                 catch (const css::uno::Exception&)
476                 {
477                     TOOLS_WARN_EXCEPTION(
478                         "desktop.app",
479                         "Desktop::OpenDefault() ignoring Exception while calling XNotifyingDispatch");
480                 }
481             }
482         }
483         else
484         {
485             INetURLObject aObj( aName );
486             if ( aObj.GetProtocol() == INetProtocol::PrivSoffice )
487                 aTarget = "_default";
488 
489             // Set "AsTemplate" argument according to request type
490             if ( aDispatchRequest.aRequestType == REQUEST_FORCENEW ||
491                  aDispatchRequest.aRequestType == REQUEST_FORCEOPEN     )
492             {
493                 aArgs.emplace_back("AsTemplate", 0,
494                                    Any(aDispatchRequest.aRequestType == REQUEST_FORCENEW),
495                                    PropertyState_DIRECT_VALUE);
496             }
497 
498             // if we are called in viewmode, open document read-only
499             if(aDispatchRequest.aRequestType == REQUEST_VIEW) {
500                 aArgs.emplace_back("ReadOnly", 0, Any(true), PropertyState_DIRECT_VALUE);
501             }
502 
503             // if we are called with --show set Start in mediadescriptor
504             if(aDispatchRequest.aRequestType == REQUEST_START) {
505                 const sal_Int32 nStartingSlide = aDispatchRequest.aParam.toInt32();
506                 const sal_uInt16 nSlide = nStartingSlide > 0 ? nStartingSlide : 1;
507                 aArgs.emplace_back("StartPresentation", 0, Any(nSlide), PropertyState_DIRECT_VALUE);
508             }
509 
510             // Force input filter, if possible
511             if( bSetInputFilter )
512             {
513                 sal_Int32 nFilterOptionsIndex = 0;
514                 aArgs.emplace_back("FilterName", 0,
515                                    Any(aForcedInputFilter.getToken(0, ':', nFilterOptionsIndex)),
516                                    PropertyState_DIRECT_VALUE);
517 
518                 if (0 < nFilterOptionsIndex)
519                 {
520                     aArgs.emplace_back("FilterOptions", 0,
521                                        Any(aForcedInputFilter.copy(nFilterOptionsIndex)),
522                                        PropertyState_DIRECT_VALUE);
523                 }
524             }
525 
526             // This is a synchron loading of a component so we don't have to deal with our statusChanged listener mechanism.
527             try
528             {
529                 xDoc.set(comphelper::SynchronousDispatch::dispatch(
530                              xDesktop, aName, aTarget, comphelper::containerToSequence(aArgs)),
531                          UNO_QUERY);
532             }
533             catch (const css::lang::IllegalArgumentException&)
534             {
535                 TOOLS_WARN_EXCEPTION(
536                     "desktop.app",
537                     "Dispatchwatcher IllegalArgumentException while calling loadComponentFromURL");
538             }
539             catch (const css::io::IOException&)
540             {
541                 TOOLS_WARN_EXCEPTION(
542                     "desktop.app",
543                     "Dispatchwatcher IOException while calling loadComponentFromURL");
544             }
545             if ( aDispatchRequest.aRequestType == REQUEST_OPEN ||
546                  aDispatchRequest.aRequestType == REQUEST_VIEW ||
547                  aDispatchRequest.aRequestType == REQUEST_START ||
548                  aDispatchRequest.aRequestType == REQUEST_FORCEOPEN ||
549                  aDispatchRequest.aRequestType == REQUEST_FORCENEW      )
550             {
551                 // request is completed
552                 RequestHandler::RequestsCompleted();
553             }
554             else if ( aDispatchRequest.aRequestType == REQUEST_PRINT ||
555                       aDispatchRequest.aRequestType == REQUEST_PRINTTO ||
556                       aDispatchRequest.aRequestType == REQUEST_BATCHPRINT ||
557                       aDispatchRequest.aRequestType == REQUEST_CONVERSION ||
558                       aDispatchRequest.aRequestType == REQUEST_CAT ||
559                       aDispatchRequest.aRequestType == REQUEST_SCRIPT_CAT )
560             {
561                 if ( xDoc.is() )
562                 {
563                     // Do we need to save the document in a different format?
564                     if ( aDispatchRequest.aRequestType == REQUEST_CONVERSION ||
565                          aDispatchRequest.aRequestType == REQUEST_CAT )
566                     {
567 // FIXME: factor out into a method ...
568                         Reference< XStorable > xStorable( xDoc, UNO_QUERY );
569                         if ( xStorable.is() ) {
570                             const OUString& aParam = aDispatchRequest.aParam;
571                             sal_Int32 nPathIndex =  aParam.lastIndexOf( ';' );
572                             sal_Int32 nFilterIndex = aParam.indexOf( ':' );
573                             sal_Int32 nImgFilterIndex = aParam.lastIndexOf( '|' );
574                             if( nPathIndex < nFilterIndex )
575                                 nFilterIndex = -1;
576 
577                             OUString aFilterOut;
578                             OUString aImgOut;
579                             OUString aFilter;
580                             OUString aFilterExt;
581                             bool bGuess = false;
582 
583                             if( nFilterIndex >= 0 )
584                             {
585                                 aFilter = aParam.copy( nFilterIndex+1, nPathIndex-nFilterIndex-1 );
586                                 aFilterExt = aParam.copy( 0, nFilterIndex );
587                             }
588                             else
589                             {
590                                 // Guess
591                                 bGuess = true;
592                                 aFilterExt = aParam.copy( 0, nPathIndex );
593                             }
594 
595                             if( nImgFilterIndex >= 0 )
596                             {
597                                 aImgOut = aParam.copy( nImgFilterIndex+1 );
598                                 aFilterOut = aParam.copy( nPathIndex+1, nImgFilterIndex-nPathIndex-1 );
599                             }
600                             else
601                                 aFilterOut = aParam.copy( nPathIndex+1 );
602 
603                             FileBase::getFileURLFromSystemPath( aFilterOut, aFilterOut );
604                             INetURLObject aOutFilename(aFilterOut);
605                             aOutFilename.Append(aObj.getName(INetURLObject::LAST_SEGMENT, true,
606                                                              INetURLObject::DecodeMechanism::NONE));
607                             aOutFilename.SetExtension(aFilterExt);
608                             OUString aOutFile
609                                 = aOutFilename.GetMainURL(INetURLObject::DecodeMechanism::NONE);
610 
611                             std::unique_ptr<utl::TempFileNamed> fileForCat;
612                             if( aDispatchRequest.aRequestType == REQUEST_CAT )
613                             {
614                                 fileForCat = std::make_unique<utl::TempFileNamed>();
615                                 if (fileForCat->IsValid())
616                                     fileForCat->EnableKillingFile();
617                                 else
618                                     std::cerr << "Error: Cannot create temporary file..." << std::endl ;
619                                 aOutFile = fileForCat->GetURL();
620                             }
621 
622                             if ( bGuess )
623                             {
624                                 OUString aDocService;
625                                 Reference< XModel > xModel( xDoc, UNO_QUERY );
626                                 if ( xModel.is() )
627                                 {
628                                     utl::MediaDescriptor aMediaDesc( xModel->getArgs() );
629                                     aDocService = aMediaDesc.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_DOCUMENTSERVICE, OUString() );
630                                 }
631                                 aFilter = impl_GuessFilter( aOutFile, aDocService );
632                             }
633 
634                             bool bMultiFileTarget = false;
635 
636                             if (aFilter.isEmpty())
637                             {
638                                 std::cerr << "Error: no export filter" << std::endl;
639                             }
640                             else
641                             {
642                                 sal_Int32 nFilterOptionsIndex = aFilter.indexOf(':');
643                                 sal_Int32 nProps = ( 0 < nFilterOptionsIndex ) ? 4 : 3;
644 
645                                 if ( !aImgOut.isEmpty() )
646                                     nProps +=1;
647                                 Sequence<PropertyValue> conversionProperties( nProps );
648                                 auto pconversionProperties = conversionProperties.getArray();
649                                 pconversionProperties[0].Name = "ConversionRequestOrigin";
650                                 pconversionProperties[0].Value <<= u"CommandLine"_ustr;
651                                 pconversionProperties[1].Name = "Overwrite";
652                                 pconversionProperties[1].Value <<= true;
653 
654                                 pconversionProperties[2].Name = "FilterName";
655                                 if( 0 < nFilterOptionsIndex )
656                                 {
657                                     OUString sFilterName = aFilter.copy(0, nFilterOptionsIndex);
658                                     OUString sFilterOptions = aFilter.copy(nFilterOptionsIndex + 1);
659 
660                                     if (sFilterName == "Text - txt - csv (StarCalc)")
661                                     {
662                                         sal_Int32 nIdx(0);
663                                         // If the 11th token is '-1' then we export a file
664                                         // per sheet where the file name is based on the suggested
665                                         // output filename concatenated with the sheet name, so adjust
666                                         // the output and overwrite messages
667                                         // If the 11th token is not present or numeric 0 then the
668                                         // default sheet is exported with the output filename. If it
669                                         // is numeric >0 then that sheet (1-based) with the output
670                                         // filename concatenated with the sheet name. So even if
671                                         // that is a single file, the multi file target mechanism is
672                                         // used.
673                                         const OUString aTok(sFilterOptions.getToken(11, ',', nIdx));
674                                         // Actual validity is checked in Calc, here just check for
675                                         // presence of numeric value at start.
676                                         bMultiFileTarget = (!aTok.isEmpty() && aTok.toInt32() != 0);
677                                     }
678 
679                                     pconversionProperties[2].Value <<= sFilterName;
680 
681                                     pconversionProperties[3].Name = "FilterOptions";
682                                     pconversionProperties[3].Value <<= sFilterOptions;
683                                 }
684                                 else
685                                 {
686                                     pconversionProperties[2].Value <<= aFilter;
687                                 }
688 
689                                 if ( !aImgOut.isEmpty() )
690                                 {
691                                     assert(conversionProperties[nProps-1].Name.isEmpty());
692                                     pconversionProperties[nProps-1].Name = "ImageFilter";
693                                     pconversionProperties[nProps-1].Value <<= aImgOut;
694                                 }
695 
696                                 OUString aTempName;
697                                 FileBase::getSystemPathFromFileURL(aName, aTempName);
698                                 OString aSource8 = OUStringToOString(aTempName, osl_getThreadTextEncoding());
699                                 FileBase::getSystemPathFromFileURL(aOutFile, aTempName);
700                                 OString aTargetURL8 = OUStringToOString(aTempName, osl_getThreadTextEncoding());
701                                 if (aDispatchRequest.aRequestType != REQUEST_CAT)
702                                 {
703                                     OUString name=getName(xDoc);
704                                     std::cout << "convert " << aSource8;
705                                     if (!name.isEmpty())
706                                         std::cout << " as a " << name <<" document";
707                                     if (!bMultiFileTarget)
708                                         std::cout << " -> " << aTargetURL8;
709                                     std::cout << " using filter : " << OUStringToOString(aFilter, osl_getThreadTextEncoding()) << std::endl;
710                                     if (!bMultiFileTarget && FStatHelper::IsDocument(aOutFile))
711                                         std::cout << "Overwriting: " << OUStringToOString(aTempName, osl_getThreadTextEncoding()) << std::endl ;
712                                 }
713                                 try
714                                 {
715                                     xStorable->storeToURL(aOutFile, conversionProperties);
716                                 }
717                                 catch (const Exception& rException)
718                                 {
719                                     std::cerr << "Error: Please verify input parameters...";
720                                     if (!rException.Message.isEmpty())
721                                         std::cerr << " (" << rException.Message << ")";
722                                     std::cerr << std::endl;
723                                 }
724 
725                                 if (fileForCat && fileForCat->IsValid())
726                                 {
727                                     SvStream* aStream = fileForCat->GetStream(StreamMode::STD_READ);
728                                     while (aStream->good())
729                                     {
730                                         OString aStr;
731                                         aStream->ReadLine(aStr, SAL_MAX_INT32);
732                                         for (sal_Int32 i = 0; i < aStr.getLength(); ++i)
733                                         {
734                                             std::cout << aStr[i];
735                                         }
736                                         std::cout << std::endl;
737                                     }
738                                 }
739                             }
740                         }
741                     }
742                     else if ( aDispatchRequest.aRequestType == REQUEST_SCRIPT_CAT )
743                     {
744                         Reference< XModel > xModel( xDoc, UNO_QUERY );
745                         if( xModel.is() )
746                             scriptCat( xModel );
747                     }
748                     else if ( aDispatchRequest.aRequestType == REQUEST_BATCHPRINT )
749                     {
750                         batchPrint(aDispatchRequest.aParam, xDoc, aObj, aName);
751                     }
752                     else
753                     {
754                         if ( aDispatchRequest.aRequestType == REQUEST_PRINTTO )
755                         {
756                             // create the printer
757                             Sequence < PropertyValue > aPrinterArgs{ comphelper::makePropertyValue(
758                                 u"Name"_ustr, aDispatchRequest.aParam) };
759                             xDoc->setPrinter( aPrinterArgs );
760                         }
761 
762                         // print ( also without user interaction )
763                         Sequence < PropertyValue > aPrinterArgs{ comphelper::makePropertyValue(u"Wait"_ustr,
764                                                                                                true) };
765                         xDoc->print( aPrinterArgs );
766                     }
767                 }
768                 else
769                 {
770                     std::cerr << "Error: source file could not be loaded" << std::endl;
771                 }
772 
773                 // remove the document
774                 try
775                 {
776                     Reference < XCloseable > xClose( xDoc, UNO_QUERY );
777                     if ( xClose.is() )
778                         xClose->close( true );
779                     else
780                     {
781                         Reference < XComponent > xComp( xDoc, UNO_QUERY );
782                         if ( xComp.is() )
783                             xComp->dispose();
784                     }
785                 }
786                 catch (const css::util::CloseVetoException&)
787                 {
788                 }
789 
790                 // request is completed
791                 RequestHandler::RequestsCompleted();
792             }
793         }
794     }
795 
796     if ( !aDispatches.empty() )
797     {
798         // Execute all asynchronous dispatches now after we placed them into our request container!
799         Sequence < PropertyValue > aArgs{
800             comphelper::makePropertyValue(u"Referer"_ustr, u"private:OpenEvent"_ustr),
801             comphelper::makePropertyValue(u"SynchronMode"_ustr, true)
802         };
803 
804         for (const DispatchHolder & aDispatche : aDispatches)
805         {
806             Reference< XDispatch > xDispatch = aDispatche.xDispatch;
807             Reference < XNotifyingDispatch > xDisp( xDispatch, UNO_QUERY );
808             if ( xDisp.is() )
809                 xDisp->dispatchWithNotification( aDispatche.aURL, aArgs, this );
810             else
811             {
812                 m_nRequestCount--;
813                 xDispatch->dispatch( aDispatche.aURL, aArgs );
814             }
815         }
816     }
817 
818     bool bEmpty = (m_nRequestCount == 0);
819 
820     // No more asynchronous requests?
821     // The requests are removed from the request container after they called back to this
822     // implementation via statusChanged!!
823     if ( bEmpty && !bNoTerminate /*m_aRequestContainer.empty()*/ )
824     {
825         // We have to check if we have an open task otherwise we have to shutdown the office.
826         Reference< XElementAccess > xList = xDesktop->getFrames();
827 
828         if ( !xList->hasElements() )
829         {
830             // We don't have any task open so we have to shutdown ourself!!
831             return xDesktop->terminate();
832         }
833     }
834 
835     return false;
836 }
837 
838 
disposing(const css::lang::EventObject &)839 void SAL_CALL DispatchWatcher::disposing( const css::lang::EventObject& )
840 {
841 }
842 
843 
dispatchFinished(const DispatchResultEvent &)844 void SAL_CALL DispatchWatcher::dispatchFinished( const DispatchResultEvent& )
845 {
846     int nCount = --m_nRequestCount;
847     RequestHandler::RequestsCompleted();
848     if ( !nCount && !RequestHandler::AreRequestsPending() )
849     {
850         // We have to check if we have an open task otherwise we have to shutdown the office.
851         Reference< XDesktop2 > xDesktop = css::frame::Desktop::create( ::comphelper::getProcessComponentContext() );
852         Reference< XElementAccess > xList = xDesktop->getFrames();
853 
854         if ( !xList->hasElements() )
855         {
856             // We don't have any task open so we have to shutdown ourself!!
857             xDesktop->terminate();
858         }
859     }
860 }
861 
862 } // namespace desktop
863 
864 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
865