1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_desktop.hxx"
30 
31 
32 
33 
34 #include "sal/config.h"
35 
36 #include <cstddef>
37 
38 #include "com/sun/star/beans/PropertyValue.hpp"
39 #include "com/sun/star/beans/NamedValue.hpp"
40 
41 #include "com/sun/star/deployment/DependencyException.hpp"
42 #include "com/sun/star/deployment/LicenseException.hpp"
43 #include "com/sun/star/deployment/VersionException.hpp"
44 #include "com/sun/star/deployment/InstallException.hpp"
45 #include "com/sun/star/deployment/PlatformException.hpp"
46 
47 #include "com/sun/star/deployment/ui/LicenseDialog.hpp"
48 #include "com/sun/star/deployment/DeploymentException.hpp"
49 #include "com/sun/star/deployment/UpdateInformationProvider.hpp"
50 #include "com/sun/star/deployment/XPackage.hpp"
51 
52 #include "com/sun/star/task/XAbortChannel.hpp"
53 #include "com/sun/star/task/XInteractionAbort.hpp"
54 #include "com/sun/star/task/XInteractionApprove.hpp"
55 
56 #include "com/sun/star/ucb/CommandAbortedException.hpp"
57 #include "com/sun/star/ucb/CommandFailedException.hpp"
58 #include "com/sun/star/ucb/XCommandEnvironment.hpp"
59 
60 #include "com/sun/star/ui/dialogs/ExecutableDialogResults.hpp"
61 
62 #include "com/sun/star/uno/Reference.hxx"
63 #include "com/sun/star/uno/RuntimeException.hpp"
64 #include "com/sun/star/uno/Sequence.hxx"
65 #include "com/sun/star/uno/XInterface.hpp"
66 #include "com/sun/star/uno/TypeClass.hpp"
67 #include "osl/diagnose.h"
68 #include "osl/mutex.hxx"
69 #include "rtl/ref.hxx"
70 #include "rtl/ustring.h"
71 #include "rtl/ustring.hxx"
72 #include "sal/types.h"
73 #include "ucbhelper/content.hxx"
74 #include "cppuhelper/exc_hlp.hxx"
75 #include "cppuhelper/implbase3.hxx"
76 #include "comphelper/anytostring.hxx"
77 #include "vcl/msgbox.hxx"
78 #include "toolkit/helper/vclunohelper.hxx"
79 #include "comphelper/processfactory.hxx"
80 
81 #include "dp_gui.h"
82 #include "dp_gui_thread.hxx"
83 #include "dp_gui_extensioncmdqueue.hxx"
84 #include "dp_gui_dependencydialog.hxx"
85 #include "dp_gui_dialog2.hxx"
86 #include "dp_gui_shared.hxx"
87 #include "dp_gui_theextmgr.hxx"
88 #include "dp_gui_updatedialog.hxx"
89 #include "dp_gui_updateinstalldialog.hxx"
90 #include "dp_dependencies.hxx"
91 #include "dp_identifier.hxx"
92 #include "dp_version.hxx"
93 
94 #include <queue>
95 #include <boost/shared_ptr.hpp>
96 
97 #if (defined(_MSC_VER) && (_MSC_VER < 1400))
98 #define _WIN32_WINNT 0x0400
99 #endif
100 
101 #ifdef WNT
102 #include "tools/prewin.h"
103 #include <objbase.h>
104 #include "tools/postwin.h"
105 #endif
106 
107 
108 using namespace ::com::sun::star;
109 using ::rtl::OUString;
110 
111 namespace {
112 
113 OUString getVersion( OUString const & sVersion )
114 {
115     return ( sVersion.getLength() == 0 ) ? OUString( RTL_CONSTASCII_USTRINGPARAM( "0" ) ) : sVersion;
116 }
117 
118 OUString getVersion( const uno::Reference< deployment::XPackage > &rPackage )
119 {
120     return getVersion( rPackage->getVersion());
121 }
122 }
123 
124 
125 namespace dp_gui {
126 
127 //==============================================================================
128 
129 class ProgressCmdEnv
130     : public ::cppu::WeakImplHelper3< ucb::XCommandEnvironment,
131                                       task::XInteractionHandler,
132                                       ucb::XProgressHandler >
133 {
134     uno::Reference< task::XInteractionHandler> m_xHandler;
135     uno::Reference< uno::XComponentContext > m_xContext;
136     uno::Reference< task::XAbortChannel> m_xAbortChannel;
137 
138     DialogHelper   *m_pDialogHelper;
139     OUString        m_sTitle;
140     bool            m_bAborted;
141     bool            m_bWarnUser;
142     sal_Int32       m_nCurrentProgress;
143 
144     void updateProgress();
145 
146     void update_( uno::Any const & Status ) throw ( uno::RuntimeException );
147 
148 public:
149     virtual ~ProgressCmdEnv();
150 
151     /** When param bAskWhenInstalling = true, then the user is asked if he
152     agrees to install this extension. In case this extension is already installed
153     then the user is also notified and asked if he wants to replace that existing
154     extension. In first case an interaction request with an InstallException
155     will be handled and in the second case a VersionException will be handled.
156     */
157 
158     ProgressCmdEnv( const uno::Reference< uno::XComponentContext > rContext,
159                     DialogHelper *pDialogHelper,
160                     const OUString &rTitle )
161         :   m_xContext( rContext ),
162             m_pDialogHelper( pDialogHelper ),
163             m_sTitle( rTitle ),
164             m_bAborted( false ),
165             m_bWarnUser( false )
166     {}
167 
168     Dialog * activeDialog() { return m_pDialogHelper ? m_pDialogHelper->getWindow() : NULL; }
169 
170     void setTitle( const OUString& rNewTitle ) { m_sTitle = rNewTitle; }
171     void startProgress();
172     void stopProgress();
173     void progressSection( const OUString &rText,
174                           const uno::Reference< task::XAbortChannel > &xAbortChannel = 0 );
175     inline bool isAborted() const { return m_bAborted; }
176     inline void setWarnUser( bool bNewVal ) { m_bWarnUser = bNewVal; }
177 
178     // XCommandEnvironment
179     virtual uno::Reference< task::XInteractionHandler > SAL_CALL getInteractionHandler()
180         throw ( uno::RuntimeException );
181     virtual uno::Reference< ucb::XProgressHandler > SAL_CALL getProgressHandler()
182         throw ( uno::RuntimeException );
183 
184     // XInteractionHandler
185     virtual void SAL_CALL handle( uno::Reference< task::XInteractionRequest > const & xRequest )
186         throw ( uno::RuntimeException );
187 
188     // XProgressHandler
189     virtual void SAL_CALL push( uno::Any const & Status )
190         throw ( uno::RuntimeException );
191     virtual void SAL_CALL update( uno::Any const & Status )
192         throw ( uno::RuntimeException );
193     virtual void SAL_CALL pop() throw ( uno::RuntimeException );
194 };
195 
196 //------------------------------------------------------------------------------
197 struct ExtensionCmd
198 {
199     enum E_CMD_TYPE { ADD, ENABLE, DISABLE, REMOVE, CHECK_FOR_UPDATES, ACCEPT_LICENSE };
200 
201     E_CMD_TYPE  m_eCmdType;
202     bool        m_bWarnUser;
203     OUString    m_sExtensionURL;
204     OUString    m_sRepository;
205     uno::Reference< deployment::XPackage > m_xPackage;
206     std::vector< uno::Reference< deployment::XPackage > >        m_vExtensionList;
207 
208     ExtensionCmd( const E_CMD_TYPE eCommand,
209                   const OUString &rExtensionURL,
210                   const OUString &rRepository,
211                   const bool bWarnUser )
212         : m_eCmdType( eCommand ),
213           m_bWarnUser( bWarnUser ),
214           m_sExtensionURL( rExtensionURL ),
215           m_sRepository( rRepository ) {};
216     ExtensionCmd( const E_CMD_TYPE eCommand,
217                   const uno::Reference< deployment::XPackage > &rPackage )
218         : m_eCmdType( eCommand ),
219           m_bWarnUser( false ),
220           m_xPackage( rPackage ) {};
221     ExtensionCmd( const E_CMD_TYPE eCommand,
222                   const std::vector<uno::Reference<deployment::XPackage > > &vExtensionList )
223         : m_eCmdType( eCommand ),
224           m_bWarnUser( false ),
225           m_vExtensionList( vExtensionList ) {};
226 };
227 
228 typedef ::boost::shared_ptr< ExtensionCmd > TExtensionCmd;
229 
230 //------------------------------------------------------------------------------
231 class ExtensionCmdQueue::Thread: public dp_gui::Thread
232 {
233 public:
234     Thread( DialogHelper *pDialogHelper,
235             TheExtensionManager *pManager,
236             const uno::Reference< uno::XComponentContext > & rContext );
237 
238     void addExtension( const OUString &rExtensionURL,
239                        const OUString &rRepository,
240                        const bool bWarnUser );
241     void removeExtension( const uno::Reference< deployment::XPackage > &rPackage );
242     void enableExtension( const uno::Reference< deployment::XPackage > &rPackage,
243                           const bool bEnable );
244     void checkForUpdates( const std::vector<uno::Reference<deployment::XPackage > > &vExtensionList );
245     void acceptLicense( const uno::Reference< deployment::XPackage > &rPackage );
246     void stop();
247     bool isBusy();
248 
249     static OUString searchAndReplaceAll( const OUString &rSource,
250                                          const OUString &rWhat,
251                                          const OUString &rWith );
252 private:
253     Thread( Thread & ); // not defined
254     void operator =( Thread & ); // not defined
255 
256     virtual ~Thread();
257 
258     virtual void execute();
259     virtual void SAL_CALL onTerminated();
260 
261     void _addExtension( ::rtl::Reference< ProgressCmdEnv > &rCmdEnv,
262                         const OUString &rPackageURL,
263                         const OUString &rRepository,
264                         const bool bWarnUser );
265     void _removeExtension( ::rtl::Reference< ProgressCmdEnv > &rCmdEnv,
266                            const uno::Reference< deployment::XPackage > &xPackage );
267     void _enableExtension( ::rtl::Reference< ProgressCmdEnv > &rCmdEnv,
268                            const uno::Reference< deployment::XPackage > &xPackage );
269     void _disableExtension( ::rtl::Reference< ProgressCmdEnv > &rCmdEnv,
270                             const uno::Reference< deployment::XPackage > &xPackage );
271     void _checkForUpdates( const std::vector<uno::Reference<deployment::XPackage > > &vExtensionList );
272     void _acceptLicense( ::rtl::Reference< ProgressCmdEnv > &rCmdEnv,
273                            const uno::Reference< deployment::XPackage > &xPackage );
274 
275     enum Input { NONE, START, STOP };
276 
277     uno::Reference< uno::XComponentContext > m_xContext;
278     std::queue< TExtensionCmd >              m_queue;
279 
280     DialogHelper *m_pDialogHelper;
281     TheExtensionManager *m_pManager;
282 
283     const OUString   m_sEnablingPackages;
284     const OUString   m_sDisablingPackages;
285     const OUString   m_sAddingPackages;
286     const OUString   m_sRemovingPackages;
287     const OUString   m_sDefaultCmd;
288     const OUString   m_sAcceptLicense;
289     osl::Condition   m_wakeup;
290     osl::Mutex       m_mutex;
291     Input            m_eInput;
292     bool             m_bTerminated;
293     bool             m_bStopped;
294     bool             m_bWorking;
295 };
296 
297 //------------------------------------------------------------------------------
298 void ProgressCmdEnv::startProgress()
299 {
300     m_nCurrentProgress = 0;
301 
302     if ( m_pDialogHelper )
303         m_pDialogHelper->showProgress( true );
304 }
305 
306 //------------------------------------------------------------------------------
307 void ProgressCmdEnv::stopProgress()
308 {
309     if ( m_pDialogHelper )
310         m_pDialogHelper->showProgress( false );
311 }
312 
313 //------------------------------------------------------------------------------
314 void ProgressCmdEnv::progressSection( const OUString &rText,
315                                       const uno::Reference< task::XAbortChannel > &xAbortChannel )
316 {
317     m_xAbortChannel = xAbortChannel;
318     if (! m_bAborted)
319     {
320         m_nCurrentProgress = 0;
321         if ( m_pDialogHelper )
322         {
323             m_pDialogHelper->updateProgress( rText, xAbortChannel );
324             m_pDialogHelper->updateProgress( 5 );
325         }
326     }
327 }
328 
329 //------------------------------------------------------------------------------
330 void ProgressCmdEnv::updateProgress()
331 {
332     if ( ! m_bAborted )
333     {
334         long nProgress = ((m_nCurrentProgress*5) % 100) + 5;
335         if ( m_pDialogHelper )
336             m_pDialogHelper->updateProgress( nProgress );
337     }
338 }
339 
340 //------------------------------------------------------------------------------
341 ProgressCmdEnv::~ProgressCmdEnv()
342 {
343     // TODO: stop all threads and wait
344 }
345 
346 
347 //------------------------------------------------------------------------------
348 // XCommandEnvironment
349 //------------------------------------------------------------------------------
350 uno::Reference< task::XInteractionHandler > ProgressCmdEnv::getInteractionHandler()
351     throw ( uno::RuntimeException )
352 {
353     return this;
354 }
355 
356 //------------------------------------------------------------------------------
357 uno::Reference< ucb::XProgressHandler > ProgressCmdEnv::getProgressHandler()
358     throw ( uno::RuntimeException )
359 {
360     return this;
361 }
362 
363 //------------------------------------------------------------------------------
364 // XInteractionHandler
365 //------------------------------------------------------------------------------
366 void ProgressCmdEnv::handle( uno::Reference< task::XInteractionRequest > const & xRequest )
367     throw ( uno::RuntimeException )
368 {
369     uno::Any request( xRequest->getRequest() );
370     OSL_ASSERT( request.getValueTypeClass() == uno::TypeClass_EXCEPTION );
371     dp_misc::TRACE( OUSTR("[dp_gui_cmdenv.cxx] incoming request:\n")
372         + ::comphelper::anyToString(request) + OUSTR("\n"));
373 
374     lang::WrappedTargetException wtExc;
375     deployment::DependencyException depExc;
376 	deployment::LicenseException licExc;
377     deployment::VersionException verExc;
378 	deployment::InstallException instExc;
379     deployment::PlatformException platExc;
380 
381     // selections:
382     bool approve = false;
383     bool abort = false;
384 
385     if (request >>= wtExc) {
386         // handable deployment error signalled, e.g.
387         // bundle item registration failed, notify cause only:
388         uno::Any cause;
389         deployment::DeploymentException dpExc;
390         if (wtExc.TargetException >>= dpExc)
391             cause = dpExc.Cause;
392         else {
393             ucb::CommandFailedException cfExc;
394             if (wtExc.TargetException >>= cfExc)
395                 cause = cfExc.Reason;
396             else
397                 cause = wtExc.TargetException;
398         }
399         update_( cause );
400 
401         // ignore intermediate errors of legacy packages, i.e.
402         // former pkgchk behaviour:
403         const uno::Reference< deployment::XPackage > xPackage( wtExc.Context, uno::UNO_QUERY );
404         OSL_ASSERT( xPackage.is() );
405         if ( xPackage.is() )
406         {
407             const uno::Reference< deployment::XPackageTypeInfo > xPackageType( xPackage->getPackageType() );
408             OSL_ASSERT( xPackageType.is() );
409             if (xPackageType.is())
410             {
411                 approve = ( xPackage->isBundle() &&
412                             xPackageType->getMediaType().matchAsciiL(
413                                 RTL_CONSTASCII_STRINGPARAM(
414                                     "application/"
415                                     "vnd.sun.star.legacy-package-bundle") ));
416             }
417         }
418         abort = !approve;
419     }
420     else if (request >>= depExc)
421     {
422         std::vector< rtl::OUString > deps;
423         for (sal_Int32 i = 0; i < depExc.UnsatisfiedDependencies.getLength();
424              ++i)
425         {
426             deps.push_back(
427                 dp_misc::Dependencies::getErrorText( depExc.UnsatisfiedDependencies[i]) );
428         }
429         {
430             vos::OGuard guard(Application::GetSolarMutex());
431             short n = DependencyDialog( m_pDialogHelper? m_pDialogHelper->getWindow() : NULL, deps ).Execute();
432             // Distinguish between closing the dialog and programatically
433             // canceling the dialog (headless VCL):
434             approve = n == RET_OK
435                 || (n == RET_CANCEL && !Application::IsDialogCancelEnabled());
436         }
437     }
438 	else if (request >>= licExc)
439     {
440         uno::Reference< ui::dialogs::XExecutableDialog > xDialog(
441             deployment::ui::LicenseDialog::create(
442             m_xContext, VCLUnoHelper::GetInterface( m_pDialogHelper? m_pDialogHelper->getWindow() : NULL ),
443             licExc.ExtensionName, licExc.Text ) );
444         sal_Int16 res = xDialog->execute();
445         if ( res == ui::dialogs::ExecutableDialogResults::CANCEL )
446             abort = true;
447         else if ( res == ui::dialogs::ExecutableDialogResults::OK )
448             approve = true;
449         else
450         {
451             OSL_ASSERT(0);
452         }
453 	}
454     else if (request >>= verExc)
455     {
456         sal_uInt32 id;
457         switch (dp_misc::compareVersions(
458                     verExc.NewVersion, verExc.Deployed->getVersion() ))
459         {
460         case dp_misc::LESS:
461             id = RID_WARNINGBOX_VERSION_LESS;
462             break;
463         case dp_misc::EQUAL:
464             id = RID_WARNINGBOX_VERSION_EQUAL;
465             break;
466         default: // dp_misc::GREATER
467             id = RID_WARNINGBOX_VERSION_GREATER;
468             break;
469         }
470         OSL_ASSERT( verExc.Deployed.is() );
471         bool bEqualNames = verExc.NewDisplayName.equals(
472             verExc.Deployed->getDisplayName());
473         {
474             vos::OGuard guard(Application::GetSolarMutex());
475             WarningBox box( m_pDialogHelper? m_pDialogHelper->getWindow() : NULL, ResId(id, *DeploymentGuiResMgr::get()));
476             String s;
477             if (bEqualNames)
478             {
479                 s = box.GetMessText();
480             }
481             else if (id == RID_WARNINGBOX_VERSION_EQUAL)
482             {
483                 //hypothetical: requires two instances of an extension with the same
484                 //version to have different display names. Probably the developer forgot
485                 //to change the version.
486                 s = String(ResId(RID_STR_WARNINGBOX_VERSION_EQUAL_DIFFERENT_NAMES, *DeploymentGuiResMgr::get()));
487             }
488             else if (id == RID_WARNINGBOX_VERSION_LESS)
489             {
490                 s = String(ResId(RID_STR_WARNINGBOX_VERSION_LESS_DIFFERENT_NAMES, *DeploymentGuiResMgr::get()));
491             }
492             else if (id == RID_WARNINGBOX_VERSION_GREATER)
493             {
494                s = String(ResId(RID_STR_WARNINGBOX_VERSION_GREATER_DIFFERENT_NAMES, *DeploymentGuiResMgr::get()));
495             }
496             s.SearchAndReplaceAllAscii( "$NAME", verExc.NewDisplayName);
497             s.SearchAndReplaceAllAscii( "$OLDNAME", verExc.Deployed->getDisplayName());
498             s.SearchAndReplaceAllAscii( "$NEW", getVersion(verExc.NewVersion) );
499             s.SearchAndReplaceAllAscii( "$DEPLOYED", getVersion(verExc.Deployed) );
500             box.SetMessText(s);
501             approve = box.Execute() == RET_OK;
502             abort = !approve;
503         }
504     }
505 	else if (request >>= instExc)
506 	{
507         if ( ! m_bWarnUser )
508         {
509             approve = true;
510         }
511         else
512         {
513             if ( m_pDialogHelper )
514             {
515                 vos::OGuard guard(Application::GetSolarMutex());
516 
517                 approve = m_pDialogHelper->installExtensionWarn( instExc.displayName );
518             }
519             else
520                 approve = false;
521             abort = !approve;
522         }
523 	}
524     else if (request >>= platExc)
525     {
526         vos::OGuard guard( Application::GetSolarMutex() );
527         String sMsg( ResId( RID_STR_UNSUPPORTED_PLATFORM, *DeploymentGuiResMgr::get() ) );
528         sMsg.SearchAndReplaceAllAscii( "%Name", platExc.package->getDisplayName() );
529         ErrorBox box( m_pDialogHelper? m_pDialogHelper->getWindow() : NULL, WB_OK, sMsg );
530         box.Execute();
531         approve = true;
532     }
533 
534 	if (approve == false && abort == false)
535     {
536         // forward to UUI handler:
537         if (! m_xHandler.is()) {
538             // late init:
539             uno::Sequence< uno::Any > handlerArgs( 1 );
540             handlerArgs[ 0 ] <<= beans::PropertyValue(
541                 OUSTR("Context"), -1, uno::Any( m_sTitle ),
542                 beans::PropertyState_DIRECT_VALUE );
543              m_xHandler.set( m_xContext->getServiceManager()
544                             ->createInstanceWithArgumentsAndContext(
545                                 OUSTR("com.sun.star.uui.InteractionHandler"),
546                                 handlerArgs, m_xContext ), uno::UNO_QUERY_THROW );
547         }
548         m_xHandler->handle( xRequest );
549     }
550 	else
551 	{
552         // select:
553         uno::Sequence< uno::Reference< task::XInteractionContinuation > > conts(
554             xRequest->getContinuations() );
555         uno::Reference< task::XInteractionContinuation > const * pConts = conts.getConstArray();
556         sal_Int32 len = conts.getLength();
557         for ( sal_Int32 pos = 0; pos < len; ++pos )
558         {
559             if (approve) {
560                 uno::Reference< task::XInteractionApprove > xInteractionApprove( pConts[ pos ], uno::UNO_QUERY );
561                 if (xInteractionApprove.is()) {
562                     xInteractionApprove->select();
563                     // don't query again for ongoing continuations:
564                     approve = false;
565                 }
566             }
567             else if (abort) {
568                 uno::Reference< task::XInteractionAbort > xInteractionAbort( pConts[ pos ], uno::UNO_QUERY );
569                 if (xInteractionAbort.is()) {
570                     xInteractionAbort->select();
571                     // don't query again for ongoing continuations:
572                     abort = false;
573                 }
574             }
575         }
576 	}
577 }
578 
579 //------------------------------------------------------------------------------
580 // XProgressHandler
581 //------------------------------------------------------------------------------
582 void ProgressCmdEnv::push( uno::Any const & rStatus )
583     throw( uno::RuntimeException )
584 {
585     update_( rStatus );
586 }
587 
588 //------------------------------------------------------------------------------
589 void ProgressCmdEnv::update_( uno::Any const & rStatus )
590     throw( uno::RuntimeException )
591 {
592     OUString text;
593     if ( rStatus.hasValue() && !( rStatus >>= text) )
594     {
595         if ( rStatus.getValueTypeClass() == uno::TypeClass_EXCEPTION )
596             text = static_cast< uno::Exception const *>( rStatus.getValue() )->Message;
597         if ( text.getLength() == 0 )
598             text = ::comphelper::anyToString( rStatus ); // fallback
599 
600         const ::vos::OGuard aGuard( Application::GetSolarMutex() );
601         const ::std::auto_ptr< ErrorBox > aBox( new ErrorBox( m_pDialogHelper? m_pDialogHelper->getWindow() : NULL, WB_OK, text ) );
602         aBox->Execute();
603     }
604     ++m_nCurrentProgress;
605     updateProgress();
606 }
607 
608 //------------------------------------------------------------------------------
609 void ProgressCmdEnv::update( uno::Any const & rStatus )
610     throw( uno::RuntimeException )
611 {
612     update_( rStatus );
613 }
614 
615 //------------------------------------------------------------------------------
616 void ProgressCmdEnv::pop()
617     throw( uno::RuntimeException )
618 {
619     update_( uno::Any() ); // no message
620 }
621 
622 //------------------------------------------------------------------------------
623 ExtensionCmdQueue::Thread::Thread( DialogHelper *pDialogHelper,
624                                    TheExtensionManager *pManager,
625                                    const uno::Reference< uno::XComponentContext > & rContext ) :
626     m_xContext( rContext ),
627     m_pDialogHelper( pDialogHelper ),
628     m_pManager( pManager ),
629     m_sEnablingPackages( DialogHelper::getResourceString( RID_STR_ENABLING_PACKAGES ) ),
630     m_sDisablingPackages( DialogHelper::getResourceString( RID_STR_DISABLING_PACKAGES ) ),
631     m_sAddingPackages( DialogHelper::getResourceString( RID_STR_ADDING_PACKAGES ) ),
632     m_sRemovingPackages( DialogHelper::getResourceString( RID_STR_REMOVING_PACKAGES ) ),
633     m_sDefaultCmd( DialogHelper::getResourceString( RID_STR_ADD_PACKAGES ) ),
634     m_sAcceptLicense( DialogHelper::getResourceString( RID_STR_ACCEPT_LICENSE ) ),
635     m_eInput( NONE ),
636     m_bTerminated( false ),
637     m_bStopped( false ),
638     m_bWorking( false )
639 {
640     OSL_ASSERT( pDialogHelper );
641 }
642 
643 //------------------------------------------------------------------------------
644 void ExtensionCmdQueue::Thread::addExtension( const ::rtl::OUString &rExtensionURL,
645                                               const ::rtl::OUString &rRepository,
646                                               const bool bWarnUser )
647 {
648     ::osl::MutexGuard aGuard( m_mutex );
649 
650     //If someone called stop then we do not add the extension -> game over!
651     if ( m_bStopped )
652         return;
653 
654     if ( rExtensionURL.getLength() )
655     {
656         TExtensionCmd pEntry( new ExtensionCmd( ExtensionCmd::ADD, rExtensionURL, rRepository, bWarnUser ) );
657 
658         m_queue.push( pEntry );
659         m_eInput = START;
660         m_wakeup.set();
661     }
662 }
663 
664 //------------------------------------------------------------------------------
665 void ExtensionCmdQueue::Thread::removeExtension( const uno::Reference< deployment::XPackage > &rPackage )
666 {
667     ::osl::MutexGuard aGuard( m_mutex );
668 
669     //If someone called stop then we do not remove the extension -> game over!
670     if ( m_bStopped )
671         return;
672 
673     if ( rPackage.is() )
674     {
675         TExtensionCmd pEntry( new ExtensionCmd( ExtensionCmd::REMOVE, rPackage ) );
676 
677         m_queue.push( pEntry );
678         m_eInput = START;
679         m_wakeup.set();
680     }
681 }
682 
683 //------------------------------------------------------------------------------
684 void ExtensionCmdQueue::Thread::acceptLicense( const uno::Reference< deployment::XPackage > &rPackage )
685 {
686     ::osl::MutexGuard aGuard( m_mutex );
687 
688     //If someone called stop then we do not remove the extension -> game over!
689     if ( m_bStopped )
690         return;
691 
692     if ( rPackage.is() )
693     {
694         TExtensionCmd pEntry( new ExtensionCmd( ExtensionCmd::ACCEPT_LICENSE, rPackage ) );
695 
696         m_queue.push( pEntry );
697         m_eInput = START;
698         m_wakeup.set();
699     }
700 }
701 
702 //------------------------------------------------------------------------------
703 void ExtensionCmdQueue::Thread::enableExtension( const uno::Reference< deployment::XPackage > &rPackage,
704                                                  const bool bEnable )
705 {
706     ::osl::MutexGuard aGuard( m_mutex );
707 
708     //If someone called stop then we do not remove the extension -> game over!
709     if ( m_bStopped )
710         return;
711 
712     if ( rPackage.is() )
713     {
714         TExtensionCmd pEntry( new ExtensionCmd( bEnable ? ExtensionCmd::ENABLE :
715                                                           ExtensionCmd::DISABLE,
716                                                 rPackage ) );
717         m_queue.push( pEntry );
718         m_eInput = START;
719         m_wakeup.set();
720     }
721 }
722 
723 //------------------------------------------------------------------------------
724 void ExtensionCmdQueue::Thread::checkForUpdates(
725     const std::vector<uno::Reference<deployment::XPackage > > &vExtensionList )
726 {
727     ::osl::MutexGuard aGuard( m_mutex );
728 
729     //If someone called stop then we do not update the extension -> game over!
730     if ( m_bStopped )
731         return;
732 
733     TExtensionCmd pEntry( new ExtensionCmd( ExtensionCmd::CHECK_FOR_UPDATES, vExtensionList ) );
734     m_queue.push( pEntry );
735     m_eInput = START;
736     m_wakeup.set();
737 }
738 
739 //------------------------------------------------------------------------------
740 //Stopping this thread will not abort the installation of extensions.
741 void ExtensionCmdQueue::Thread::stop()
742 {
743     osl::MutexGuard aGuard( m_mutex );
744     m_bStopped = true;
745     m_eInput = STOP;
746     m_wakeup.set();
747 }
748 
749 //------------------------------------------------------------------------------
750 bool ExtensionCmdQueue::Thread::isBusy()
751 {
752     osl::MutexGuard aGuard( m_mutex );
753     return m_bWorking;
754 }
755 
756 //------------------------------------------------------------------------------
757 ExtensionCmdQueue::Thread::~Thread() {}
758 
759 //------------------------------------------------------------------------------
760 void ExtensionCmdQueue::Thread::execute()
761 {
762 #ifdef WNT
763     //Needed for use of the service "com.sun.star.system.SystemShellExecute" in
764     //DialogHelper::openWebBrowser
765     CoUninitialize();
766     HRESULT r = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
767 #endif
768     for (;;)
769     {
770         if ( m_wakeup.wait() != osl::Condition::result_ok )
771         {
772             dp_misc::TRACE( "dp_gui::ExtensionCmdQueue::Thread::run: ignored "
773                        "osl::Condition::wait failure\n" );
774         }
775         m_wakeup.reset();
776 
777         int nSize;
778         Input eInput;
779         {
780             osl::MutexGuard aGuard( m_mutex );
781             eInput = m_eInput;
782             m_eInput = NONE;
783             nSize = m_queue.size();
784             m_bWorking = false;
785         }
786 
787         // If this thread has been woken up by anything else except start, stop
788         // then input is NONE and we wait again.
789         // We only install the extension which are currently in the queue.
790         // The progressbar will be set to show the progress of the current number
791         // of extensions. If we allowed to add extensions now then the progressbar may
792         // have reached the end while we still install newly added extensions.
793         if ( ( eInput == NONE ) || ( nSize == 0 ) )
794             continue;
795         if ( eInput == STOP )
796             break;
797 
798         ::rtl::Reference< ProgressCmdEnv > currentCmdEnv( new ProgressCmdEnv( m_xContext, m_pDialogHelper, m_sDefaultCmd ) );
799 
800         // Do not lock the following part with addExtension. addExtension may be called in the main thread.
801         // If the message box "Do you want to install the extension (or similar)" is shown and then
802         // addExtension is called, which then blocks the main thread, then we deadlock.
803         bool bStartProgress = true;
804 
805         while ( !currentCmdEnv->isAborted() && --nSize >= 0 )
806         {
807             {
808                 osl::MutexGuard aGuard( m_mutex );
809                 m_bWorking = true;
810             }
811 
812             try
813             {
814                 TExtensionCmd pEntry;
815                 {
816                     ::osl::MutexGuard queueGuard( m_mutex );
817                     pEntry = m_queue.front();
818                     m_queue.pop();
819                 }
820 
821                 if ( bStartProgress && ( pEntry->m_eCmdType != ExtensionCmd::CHECK_FOR_UPDATES ) )
822                 {
823                     currentCmdEnv->startProgress();
824                     bStartProgress = false;
825                 }
826 
827                 switch ( pEntry->m_eCmdType ) {
828                 case ExtensionCmd::ADD :
829                     _addExtension( currentCmdEnv, pEntry->m_sExtensionURL, pEntry->m_sRepository, pEntry->m_bWarnUser );
830                     break;
831                 case ExtensionCmd::REMOVE :
832                     _removeExtension( currentCmdEnv, pEntry->m_xPackage );
833                     break;
834                 case ExtensionCmd::ENABLE :
835                     _enableExtension( currentCmdEnv, pEntry->m_xPackage );
836                     break;
837                 case ExtensionCmd::DISABLE :
838                     _disableExtension( currentCmdEnv, pEntry->m_xPackage );
839                     break;
840                 case ExtensionCmd::CHECK_FOR_UPDATES :
841                     _checkForUpdates( pEntry->m_vExtensionList );
842                     break;
843                 case ExtensionCmd::ACCEPT_LICENSE :
844                     _acceptLicense( currentCmdEnv, pEntry->m_xPackage );
845                     break;
846                 }
847             }
848             //catch ( deployment::DeploymentException &)
849             //{
850             //}
851             //catch ( lang::IllegalArgumentException &)
852             //{
853             //}
854             catch ( ucb::CommandAbortedException & )
855             {
856                 //This exception is thrown when the user clicks cancel on the progressbar.
857                 //Then we cancel the installation of all extensions and remove them from
858                 //the queue.
859                 {
860                     ::osl::MutexGuard queueGuard2(m_mutex);
861                     while ( --nSize >= 0 )
862                         m_queue.pop();
863                 }
864                 break;
865             }
866             catch ( ucb::CommandFailedException & )
867             {
868                 //This exception is thrown when a user clicked cancel in the messagebox which was
869                 //startet by the interaction handler. For example the user will be asked if he/she
870                 //really wants to install the extension.
871                 //These interaction are run for exectly one extension at a time. Therefore we continue
872                 //with installing the remaining extensions.
873                 continue;
874             }
875             catch ( uno::Exception & )
876             {
877                 //Todo display the user an error
878                 //see also DialogImpl::SyncPushButton::Click()
879                 uno::Any exc( ::cppu::getCaughtException() );
880                 OUString msg;
881                 deployment::DeploymentException dpExc;
882                 if ((exc >>= dpExc) &&
883                     dpExc.Cause.getValueTypeClass() == uno::TypeClass_EXCEPTION)
884                 {
885                     // notify error cause only:
886                     msg = reinterpret_cast< uno::Exception const * >( dpExc.Cause.getValue() )->Message;
887                 }
888                 if (msg.getLength() == 0) // fallback for debugging purposes
889                     msg = ::comphelper::anyToString(exc);
890 
891                 const ::vos::OGuard guard( Application::GetSolarMutex() );
892                 ::std::auto_ptr<ErrorBox> box(
893                     new ErrorBox( currentCmdEnv->activeDialog(), WB_OK, msg ) );
894                 if ( m_pDialogHelper )
895                     box->SetText( m_pDialogHelper->getWindow()->GetText() );
896                 box->Execute();
897                     //Continue with installation of the remaining extensions
898             }
899             {
900                 osl::MutexGuard aGuard( m_mutex );
901                 m_bWorking = false;
902             }
903         }
904 
905         {
906             // when leaving the while loop with break, we should set working to false, too
907 			osl::MutexGuard aGuard( m_mutex );
908             m_bWorking = false;
909         }
910 
911 		if ( !bStartProgress )
912             currentCmdEnv->stopProgress();
913     }
914     //end for
915     //enable all buttons
916 //     m_pDialog->m_bAddingExtensions = false;
917 //     m_pDialog->updateButtonStates();
918 #ifdef WNT
919     CoUninitialize();
920 #endif
921 }
922 
923 //------------------------------------------------------------------------------
924 void ExtensionCmdQueue::Thread::_addExtension( ::rtl::Reference< ProgressCmdEnv > &rCmdEnv,
925                                                const OUString &rPackageURL,
926                                                const OUString &rRepository,
927                                                const bool bWarnUser )
928 {
929     //check if we have a string in anyTitle. For example "unopkg gui \" caused anyTitle to be void
930     //and anyTitle.get<OUString> throws as RuntimeException.
931     uno::Any anyTitle;
932 	try
933 	{
934 		anyTitle = ::ucbhelper::Content( rPackageURL, rCmdEnv.get() ).getPropertyValue( OUSTR("Title") );
935 	}
936 	catch ( uno::Exception & )
937 	{
938 		return;
939 	}
940 
941     OUString sName;
942     if ( ! (anyTitle >>= sName) )
943     {
944         OSL_ENSURE(0, "Could not get file name for extension.");
945         return;
946     }
947 
948     rCmdEnv->setWarnUser( bWarnUser );
949     uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
950     uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
951     OUString sTitle = searchAndReplaceAll( m_sAddingPackages, OUSTR("%EXTENSION_NAME"), sName );
952     rCmdEnv->progressSection( sTitle, xAbortChannel );
953 
954     try
955     {
956 		xExtMgr->addExtension(rPackageURL, uno::Sequence<beans::NamedValue>(),
957                               rRepository, xAbortChannel, rCmdEnv.get() );
958     }
959     catch ( ucb::CommandFailedException & )
960     {
961         // When the extension is already installed we'll get a dialog asking if we want to overwrite. If we then press
962         // cancel this exception is thrown.
963     }
964     catch ( ucb::CommandAbortedException & )
965     {
966         // User clicked the cancel button
967         // TODO: handle cancel
968     }
969     rCmdEnv->setWarnUser( false );
970 }
971 
972 //------------------------------------------------------------------------------
973 void ExtensionCmdQueue::Thread::_removeExtension( ::rtl::Reference< ProgressCmdEnv > &rCmdEnv,
974                                                   const uno::Reference< deployment::XPackage > &xPackage )
975 {
976     uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
977     uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
978     OUString sTitle = searchAndReplaceAll( m_sRemovingPackages, OUSTR("%EXTENSION_NAME"), xPackage->getDisplayName() );
979     rCmdEnv->progressSection( sTitle, xAbortChannel );
980 
981     OUString id( dp_misc::getIdentifier( xPackage ) );
982     try
983     {
984         xExtMgr->removeExtension( id, xPackage->getName(), xPackage->getRepositoryName(), xAbortChannel, rCmdEnv.get() );
985     }
986     catch ( deployment::DeploymentException & )
987     {}
988     catch ( ucb::CommandFailedException & )
989     {}
990     catch ( ucb::CommandAbortedException & )
991     {}
992 
993     // Check, if there are still updates to be notified via menu bar icon
994     uno::Sequence< uno::Sequence< rtl::OUString > > aItemList;
995     UpdateDialog::createNotifyJob( false, aItemList );
996 }
997 
998 //------------------------------------------------------------------------------
999 void ExtensionCmdQueue::Thread::_checkForUpdates(
1000     const std::vector<uno::Reference<deployment::XPackage > > &vExtensionList )
1001 {
1002     UpdateDialog* pUpdateDialog;
1003     std::vector< UpdateData > vData;
1004 
1005     const ::vos::OGuard guard( Application::GetSolarMutex() );
1006 
1007     pUpdateDialog = new UpdateDialog( m_xContext, m_pDialogHelper? m_pDialogHelper->getWindow() : NULL, vExtensionList, &vData );
1008 
1009     pUpdateDialog->notifyMenubar( true, false ); // prepare the checking, if there updates to be notified via menu bar icon
1010 
1011     if ( ( pUpdateDialog->Execute() == RET_OK ) && !vData.empty() )
1012     {
1013         // If there is at least one directly downloadable dialog then we
1014         // open the install dialog.
1015         ::std::vector< UpdateData > dataDownload;
1016         int countWebsiteDownload = 0;
1017         typedef std::vector< dp_gui::UpdateData >::const_iterator cit;
1018 
1019         for ( cit i = vData.begin(); i < vData.end(); i++ )
1020         {
1021             if ( i->sWebsiteURL.getLength() > 0 )
1022                 countWebsiteDownload ++;
1023             else
1024                 dataDownload.push_back( *i );
1025         }
1026 
1027         short nDialogResult = RET_OK;
1028         if ( !dataDownload.empty() )
1029         {
1030             nDialogResult = UpdateInstallDialog( m_pDialogHelper? m_pDialogHelper->getWindow() : NULL, dataDownload, m_xContext ).Execute();
1031             pUpdateDialog->notifyMenubar( false, true ); // Check, if there are still pending updates to be notified via menu bar icon
1032         }
1033         else
1034             pUpdateDialog->notifyMenubar( false, false ); // Check, if there are pending updates to be notified via menu bar icon
1035 
1036         //Now start the webbrowser and navigate to the websites where we get the updates
1037         if ( RET_OK == nDialogResult )
1038         {
1039             for ( cit i = vData.begin(); i < vData.end(); i++ )
1040             {
1041                 if ( m_pDialogHelper && ( i->sWebsiteURL.getLength() > 0 ) )
1042                     m_pDialogHelper->openWebBrowser( i->sWebsiteURL, m_pDialogHelper->getWindow()->GetText() );
1043             }
1044         }
1045     }
1046     else
1047         pUpdateDialog->notifyMenubar( false, false ); // check if there updates to be notified via menu bar icon
1048 
1049     delete pUpdateDialog;
1050 }
1051 
1052 //------------------------------------------------------------------------------
1053 void ExtensionCmdQueue::Thread::_enableExtension( ::rtl::Reference< ProgressCmdEnv > &rCmdEnv,
1054                                                   const uno::Reference< deployment::XPackage > &xPackage )
1055 {
1056     if ( !xPackage.is() )
1057         return;
1058 
1059     uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
1060     uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
1061     OUString sTitle = searchAndReplaceAll( m_sEnablingPackages, OUSTR("%EXTENSION_NAME"), xPackage->getDisplayName() );
1062     rCmdEnv->progressSection( sTitle, xAbortChannel );
1063 
1064     try
1065     {
1066         xExtMgr->enableExtension( xPackage, xAbortChannel, rCmdEnv.get() );
1067         if ( m_pDialogHelper )
1068             m_pDialogHelper->updatePackageInfo( xPackage );
1069     }
1070     catch ( ::ucb::CommandAbortedException & )
1071     {}
1072 }
1073 
1074 //------------------------------------------------------------------------------
1075 void ExtensionCmdQueue::Thread::_disableExtension( ::rtl::Reference< ProgressCmdEnv > &rCmdEnv,
1076                                                    const uno::Reference< deployment::XPackage > &xPackage )
1077 {
1078     if ( !xPackage.is() )
1079         return;
1080 
1081     uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
1082     uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
1083     OUString sTitle = searchAndReplaceAll( m_sDisablingPackages, OUSTR("%EXTENSION_NAME"), xPackage->getDisplayName() );
1084     rCmdEnv->progressSection( sTitle, xAbortChannel );
1085 
1086     try
1087     {
1088         xExtMgr->disableExtension( xPackage, xAbortChannel, rCmdEnv.get() );
1089         if ( m_pDialogHelper )
1090             m_pDialogHelper->updatePackageInfo( xPackage );
1091     }
1092     catch ( ::ucb::CommandAbortedException & )
1093     {}
1094 }
1095 
1096 //------------------------------------------------------------------------------
1097 void ExtensionCmdQueue::Thread::_acceptLicense( ::rtl::Reference< ProgressCmdEnv > &rCmdEnv,
1098                                                 const uno::Reference< deployment::XPackage > &xPackage )
1099 {
1100     if ( !xPackage.is() )
1101         return;
1102 
1103     uno::Reference< deployment::XExtensionManager > xExtMgr = m_pManager->getExtensionManager();
1104     uno::Reference< task::XAbortChannel > xAbortChannel( xExtMgr->createAbortChannel() );
1105     OUString sTitle = searchAndReplaceAll( m_sAcceptLicense, OUSTR("%EXTENSION_NAME"), xPackage->getDisplayName() );
1106     rCmdEnv->progressSection( sTitle, xAbortChannel );
1107 
1108     try
1109     {
1110         xExtMgr->checkPrerequisitesAndEnable( xPackage, xAbortChannel, rCmdEnv.get() );
1111         if ( m_pDialogHelper )
1112             m_pDialogHelper->updatePackageInfo( xPackage );
1113     }
1114     catch ( ::ucb::CommandAbortedException & )
1115     {}
1116 }
1117 
1118 //------------------------------------------------------------------------------
1119 void ExtensionCmdQueue::Thread::onTerminated()
1120 {
1121     ::osl::MutexGuard g(m_mutex);
1122     m_bTerminated = true;
1123 }
1124 
1125 //------------------------------------------------------------------------------
1126 OUString ExtensionCmdQueue::Thread::searchAndReplaceAll( const OUString &rSource,
1127                                                          const OUString &rWhat,
1128                                                          const OUString &rWith )
1129 {
1130     OUString aRet( rSource );
1131     sal_Int32 nLen = rWhat.getLength();
1132 
1133     if ( !nLen )
1134         return aRet;
1135 
1136     sal_Int32 nIndex = rSource.indexOf( rWhat );
1137     while ( nIndex != -1 )
1138     {
1139         aRet = aRet.replaceAt( nIndex, nLen, rWith );
1140         nIndex = aRet.indexOf( rWhat, nIndex + rWith.getLength() );
1141     }
1142     return aRet;
1143 }
1144 
1145 
1146 //------------------------------------------------------------------------------
1147 //------------------------------------------------------------------------------
1148 //------------------------------------------------------------------------------
1149 ExtensionCmdQueue::ExtensionCmdQueue( DialogHelper * pDialogHelper,
1150                                       TheExtensionManager *pManager,
1151                                       const uno::Reference< uno::XComponentContext > &rContext )
1152   : m_thread( new Thread( pDialogHelper, pManager, rContext ) )
1153 {
1154     m_thread->launch();
1155 }
1156 
1157 ExtensionCmdQueue::~ExtensionCmdQueue() {
1158     stop();
1159 }
1160 
1161 void ExtensionCmdQueue::addExtension( const ::rtl::OUString & extensionURL,
1162                                       const ::rtl::OUString & repository,
1163                                       const bool bWarnUser )
1164 {
1165     m_thread->addExtension( extensionURL, repository, bWarnUser );
1166 }
1167 
1168 void ExtensionCmdQueue::removeExtension( const uno::Reference< deployment::XPackage > &rPackage )
1169 {
1170     m_thread->removeExtension( rPackage );
1171 }
1172 
1173 void ExtensionCmdQueue::enableExtension( const uno::Reference< deployment::XPackage > &rPackage,
1174                                          const bool bEnable )
1175 {
1176     m_thread->enableExtension( rPackage, bEnable );
1177 }
1178 
1179 void ExtensionCmdQueue::checkForUpdates( const std::vector<uno::Reference<deployment::XPackage > > &vExtensionList )
1180 {
1181     m_thread->checkForUpdates( vExtensionList );
1182 }
1183 
1184 void ExtensionCmdQueue::acceptLicense( const uno::Reference< deployment::XPackage > &rPackage )
1185 {
1186     m_thread->acceptLicense( rPackage );
1187 }
1188 
1189 void ExtensionCmdQueue::syncRepositories( const uno::Reference< uno::XComponentContext > &xContext )
1190 {
1191     dp_misc::syncRepositories( new ProgressCmdEnv( xContext, NULL, OUSTR("Extension Manager") ) );
1192 }
1193 
1194 void ExtensionCmdQueue::stop()
1195 {
1196     m_thread->stop();
1197 }
1198 
1199 bool ExtensionCmdQueue::isBusy()
1200 {
1201     return m_thread->isBusy();
1202 }
1203 
1204 void handleInteractionRequest( const uno::Reference< uno::XComponentContext > & xContext,
1205                                const uno::Reference< task::XInteractionRequest > & xRequest )
1206 {
1207     ::rtl::Reference< ProgressCmdEnv > xCmdEnv( new ProgressCmdEnv( xContext, NULL, OUSTR("Extension Manager") ) );
1208     xCmdEnv->handle( xRequest );
1209 }
1210 
1211 } //namespace dp_gui
1212 
1213