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 "PresenterTimer.hxx"
21 
22 #include <com/sun/star/lang/XMultiComponentFactory.hpp>
23 #include <com/sun/star/frame/Desktop.hpp>
24 #include <com/sun/star/frame/XTerminateListener.hpp>
25 
26 #include <osl/thread.hxx>
27 #include <osl/conditn.hxx>
28 
29 #include <algorithm>
30 #include <memory>
31 #include <mutex>
32 #include <set>
33 
34 using namespace ::com::sun::star;
35 using namespace ::com::sun::star::uno;
36 
37 namespace sdext::presenter {
38 
39 namespace {
40 class TimerTask
41 {
42 public:
43     TimerTask (
44         const PresenterTimer::Task& rTask,
45         const TimeValue& rDueTime,
46         const sal_Int64 nRepeatInterval,
47         const sal_Int32 nTaskId);
48 
49     PresenterTimer::Task maTask;
50     TimeValue maDueTime;
51     const sal_Int64 mnRepeatInterval;
52     const sal_Int32 mnTaskId;
53     bool mbIsCanceled;
54 };
55 
56 typedef std::shared_ptr<TimerTask> SharedTimerTask;
57 
58 class TimerTaskComparator
59 {
60 public:
operator ()(const SharedTimerTask & rpTask1,const SharedTimerTask & rpTask2) const61     bool operator() (const SharedTimerTask& rpTask1, const SharedTimerTask& rpTask2) const
62     {
63         return rpTask1->maDueTime.Seconds < rpTask2->maDueTime.Seconds
64             || (rpTask1->maDueTime.Seconds == rpTask2->maDueTime.Seconds
65                 && rpTask1->maDueTime.Nanosec < rpTask2->maDueTime.Nanosec);
66     }
67 };
68 
69 /** Queue all scheduled tasks and process them when their time has come.
70 */
71 class TimerScheduler
72     : public std::enable_shared_from_this<TimerScheduler>,
73       public ::osl::Thread
74 {
75 public:
76     static std::shared_ptr<TimerScheduler> Instance(
77         uno::Reference<uno::XComponentContext> const& xContext);
78     static SharedTimerTask CreateTimerTask (
79         const PresenterTimer::Task& rTask,
80         const TimeValue& rDueTime,
81         const sal_Int64 nRepeatInterval);
82 
83     void ScheduleTask (const SharedTimerTask& rpTask);
84     void CancelTask (const sal_Int32 nTaskId);
85 
86     static bool GetCurrentTime (TimeValue& rCurrentTime);
87     static sal_Int64 GetTimeDifference (
88         const TimeValue& rTargetTime,
89         const TimeValue& rCurrentTime);
90     static void ConvertToTimeValue (
91         TimeValue& rTimeValue,
92         const sal_Int64 nTimeDifference);
93     static sal_Int64 ConvertFromTimeValue (
94         const TimeValue& rTimeValue);
95 
96     static void NotifyTermination();
97 #if !defined NDEBUG
HasInstance()98     static bool HasInstance() { return mpInstance != nullptr; }
99 #endif
100 
101 private:
102     static std::shared_ptr<TimerScheduler> mpInstance;
103     static std::mutex maInstanceMutex;
104     std::shared_ptr<TimerScheduler> mpLateDestroy; // for clean exit
105     static sal_Int32 mnTaskId;
106 
107     std::mutex maTaskContainerMutex;
108     typedef ::std::set<SharedTimerTask,TimerTaskComparator> TaskContainer;
109     TaskContainer maScheduledTasks;
110     std::mutex maCurrentTaskMutex;
111     SharedTimerTask mpCurrentTask;
112     ::osl::Condition m_Shutdown;
113 
114     TimerScheduler(
115         uno::Reference<uno::XComponentContext> const& xContext);
116 public:
117     virtual void SAL_CALL run() override;
onTerminated()118     virtual void SAL_CALL onTerminated() override { mpLateDestroy.reset(); }
119 };
120 
121 class TerminateListener
122     : public ::cppu::WeakImplHelper<frame::XTerminateListener>
123 {
~TerminateListener()124     virtual ~TerminateListener() override
125     {
126         assert(!TimerScheduler::HasInstance());
127     }
128 
disposing(lang::EventObject const &)129     virtual void SAL_CALL disposing(lang::EventObject const&) override
130     {
131     }
132 
queryTermination(lang::EventObject const &)133     virtual void SAL_CALL queryTermination(lang::EventObject const&) override
134     {
135     }
136 
notifyTermination(lang::EventObject const &)137     virtual void SAL_CALL notifyTermination(lang::EventObject const&) override
138     {
139         TimerScheduler::NotifyTermination();
140     }
141 };
142 
143 } // end of anonymous namespace
144 
145 //===== PresenterTimer ========================================================
146 
ScheduleRepeatedTask(const uno::Reference<uno::XComponentContext> & xContext,const Task & rTask,const sal_Int64 nDelay,const sal_Int64 nInterval)147 sal_Int32 PresenterTimer::ScheduleRepeatedTask (
148     const uno::Reference<uno::XComponentContext>& xContext,
149     const Task& rTask,
150     const sal_Int64 nDelay,
151     const sal_Int64 nInterval)
152 {
153     assert(xContext.is());
154     TimeValue aCurrentTime;
155     if (TimerScheduler::GetCurrentTime(aCurrentTime))
156     {
157         TimeValue aDueTime;
158         TimerScheduler::ConvertToTimeValue(
159             aDueTime,
160             TimerScheduler::ConvertFromTimeValue (aCurrentTime) + nDelay);
161         SharedTimerTask pTask (TimerScheduler::CreateTimerTask(rTask, aDueTime, nInterval));
162         TimerScheduler::Instance(xContext)->ScheduleTask(pTask);
163         return pTask->mnTaskId;
164     }
165 
166     return NotAValidTaskId;
167 }
168 
CancelTask(const sal_Int32 nTaskId)169 void PresenterTimer::CancelTask (const sal_Int32 nTaskId)
170 {
171     auto const pInstance(TimerScheduler::Instance(nullptr));
172     if (pInstance)
173     {
174         pInstance->CancelTask(nTaskId);
175     }
176 }
177 
178 //===== TimerScheduler ========================================================
179 
180 std::shared_ptr<TimerScheduler> TimerScheduler::mpInstance;
181 std::mutex TimerScheduler::maInstanceMutex;
182 sal_Int32 TimerScheduler::mnTaskId = PresenterTimer::NotAValidTaskId;
183 
Instance(uno::Reference<uno::XComponentContext> const & xContext)184 std::shared_ptr<TimerScheduler> TimerScheduler::Instance(
185     uno::Reference<uno::XComponentContext> const& xContext)
186 {
187     std::lock_guard aGuard (maInstanceMutex);
188     if (mpInstance == nullptr)
189     {
190         if (!xContext.is())
191             return nullptr;
192         mpInstance.reset(new TimerScheduler(xContext));
193         mpInstance->create();
194     }
195     return mpInstance;
196 }
197 
TimerScheduler(uno::Reference<uno::XComponentContext> const & xContext)198 TimerScheduler::TimerScheduler(
199         uno::Reference<uno::XComponentContext> const& xContext)
200     : maTaskContainerMutex(),
201       maScheduledTasks(),
202       maCurrentTaskMutex(),
203       mpCurrentTask()
204 {
205     uno::Reference<frame::XDesktop> const xDesktop(
206             frame::Desktop::create(xContext));
207     uno::Reference<frame::XTerminateListener> const xListener(
208             new TerminateListener);
209     // assuming the desktop can take ownership
210     xDesktop->addTerminateListener(xListener);
211 }
212 
CreateTimerTask(const PresenterTimer::Task & rTask,const TimeValue & rDueTime,const sal_Int64 nRepeatInterval)213 SharedTimerTask TimerScheduler::CreateTimerTask (
214     const PresenterTimer::Task& rTask,
215     const TimeValue& rDueTime,
216     const sal_Int64 nRepeatInterval)
217 {
218     return std::make_shared<TimerTask>(rTask, rDueTime, nRepeatInterval, ++mnTaskId);
219 }
220 
ScheduleTask(const SharedTimerTask & rpTask)221 void TimerScheduler::ScheduleTask (const SharedTimerTask& rpTask)
222 {
223     if (!rpTask)
224         return;
225     if (rpTask->mbIsCanceled)
226         return;
227 
228     {
229         std::lock_guard aTaskGuard (maTaskContainerMutex);
230         maScheduledTasks.insert(rpTask);
231     }
232 }
233 
CancelTask(const sal_Int32 nTaskId)234 void TimerScheduler::CancelTask (const sal_Int32 nTaskId)
235 {
236     // Set of scheduled tasks is sorted after their due times, not their
237     // task ids.  Therefore we have to do a linear search for the task to
238     // cancel.
239     {
240         std::lock_guard aGuard (maTaskContainerMutex);
241         auto iTask = std::find_if(maScheduledTasks.begin(), maScheduledTasks.end(),
242             [nTaskId](const SharedTimerTask& rxTask) { return rxTask->mnTaskId == nTaskId; });
243         if (iTask != maScheduledTasks.end())
244             maScheduledTasks.erase(iTask);
245     }
246 
247     // The task that is to be canceled may be currently about to be
248     // processed.  Mark it with a flag that a) prevents a repeating task
249     // from being scheduled again and b) tries to prevent its execution.
250     {
251         std::lock_guard aGuard (maCurrentTaskMutex);
252         if (mpCurrentTask
253             && mpCurrentTask->mnTaskId == nTaskId)
254             mpCurrentTask->mbIsCanceled = true;
255     }
256 
257     // Let the main-loop cleanup in its own time
258 }
259 
NotifyTermination()260 void TimerScheduler::NotifyTermination()
261 {
262     std::shared_ptr<TimerScheduler> const pInstance(TimerScheduler::mpInstance);
263     if (!pInstance)
264     {
265         return;
266     }
267 
268     {
269         std::lock_guard aGuard(pInstance->maTaskContainerMutex);
270         pInstance->maScheduledTasks.clear();
271     }
272 
273     {
274         std::lock_guard aGuard(pInstance->maCurrentTaskMutex);
275         if (pInstance->mpCurrentTask)
276         {
277             pInstance->mpCurrentTask->mbIsCanceled = true;
278         }
279     }
280 
281     pInstance->m_Shutdown.set();
282 
283     // rhbz#1425304 join thread before shutdown
284     pInstance->join();
285 }
286 
run()287 void SAL_CALL TimerScheduler::run()
288 {
289     osl_setThreadName("sdext::presenter::TimerScheduler");
290 
291     while (true)
292     {
293         // Get the current time.
294         TimeValue aCurrentTime;
295         if ( ! GetCurrentTime(aCurrentTime))
296         {
297             // We can not get the current time and thus can not schedule anything.
298             break;
299         }
300 
301         // Restrict access to the maScheduledTasks member to one, mutex
302         // guarded, block.
303         SharedTimerTask pTask;
304         sal_Int64 nDifference = 0;
305         {
306             std::lock_guard aGuard (maTaskContainerMutex);
307 
308             // There are no more scheduled task.  Leave this loop, function and
309             // live of the TimerScheduler.
310             if (maScheduledTasks.empty())
311                 break;
312 
313             nDifference = GetTimeDifference(
314                 (*maScheduledTasks.begin())->maDueTime,
315                 aCurrentTime);
316             if (nDifference <= 0)
317             {
318                 pTask = *maScheduledTasks.begin();
319                 maScheduledTasks.erase(maScheduledTasks.begin());
320             }
321         }
322 
323         // Acquire a reference to the current task.
324         {
325             std::lock_guard aGuard (maCurrentTaskMutex);
326             mpCurrentTask = pTask;
327         }
328 
329         if (!pTask)
330         {
331             // Wait until the first task becomes due.
332             TimeValue aTimeValue;
333             ConvertToTimeValue(aTimeValue, nDifference);
334             // wait on condition variable, so the thread can be stopped
335             m_Shutdown.wait(&aTimeValue);
336         }
337         else
338         {
339             // Execute task.
340             if (pTask->maTask && !pTask->mbIsCanceled)
341             {
342                 pTask->maTask(aCurrentTime);
343 
344                 // Re-schedule repeating tasks.
345                 if (pTask->mnRepeatInterval > 0)
346                 {
347                     ConvertToTimeValue(
348                         pTask->maDueTime,
349                         ConvertFromTimeValue(pTask->maDueTime)
350                             + pTask->mnRepeatInterval);
351                     ScheduleTask(pTask);
352                 }
353             }
354 
355         }
356 
357         // Release reference to the current task.
358         {
359             std::lock_guard aGuard (maCurrentTaskMutex);
360             mpCurrentTask.reset();
361         }
362     }
363 
364     // While holding maInstanceMutex
365     std::lock_guard aInstance( maInstanceMutex );
366     mpLateDestroy = mpInstance;
367     mpInstance.reset();
368 }
369 
GetCurrentTime(TimeValue & rCurrentTime)370 bool TimerScheduler::GetCurrentTime (TimeValue& rCurrentTime)
371 {
372     TimeValue aSystemTime;
373     if (osl_getSystemTime(&aSystemTime))
374         return osl_getLocalTimeFromSystemTime(&aSystemTime, &rCurrentTime);
375     return false;
376 }
377 
GetTimeDifference(const TimeValue & rTargetTime,const TimeValue & rCurrentTime)378 sal_Int64 TimerScheduler::GetTimeDifference (
379     const TimeValue& rTargetTime,
380     const TimeValue& rCurrentTime)
381 {
382     return ConvertFromTimeValue(rTargetTime) - ConvertFromTimeValue(rCurrentTime);
383 }
384 
ConvertToTimeValue(TimeValue & rTimeValue,const sal_Int64 nTimeDifference)385 void TimerScheduler::ConvertToTimeValue (
386     TimeValue& rTimeValue,
387     const sal_Int64 nTimeDifference)
388 {
389     rTimeValue.Seconds = sal::static_int_cast<sal_Int32>(nTimeDifference / 1000000000L);
390     rTimeValue.Nanosec = sal::static_int_cast<sal_Int32>(nTimeDifference % 1000000000L);
391 }
392 
ConvertFromTimeValue(const TimeValue & rTimeValue)393 sal_Int64 TimerScheduler::ConvertFromTimeValue (
394     const TimeValue& rTimeValue)
395 {
396     return sal_Int64(rTimeValue.Seconds) * 1000000000L + rTimeValue.Nanosec;
397 }
398 
399 //===== TimerTask =============================================================
400 
401 namespace {
402 
TimerTask(const PresenterTimer::Task & rTask,const TimeValue & rDueTime,const sal_Int64 nRepeatInterval,const sal_Int32 nTaskId)403 TimerTask::TimerTask (
404     const PresenterTimer::Task& rTask,
405     const TimeValue& rDueTime,
406     const sal_Int64 nRepeatInterval,
407     const sal_Int32 nTaskId)
408     : maTask(rTask),
409       maDueTime(rDueTime),
410       mnRepeatInterval(nRepeatInterval),
411       mnTaskId(nTaskId),
412       mbIsCanceled(false)
413 {
414 }
415 
416 } // end of anonymous namespace
417 
418 //===== PresenterTimer ========================================================
419 
420 ::rtl::Reference<PresenterClockTimer> PresenterClockTimer::mpInstance;
421 
Instance(const css::uno::Reference<css::uno::XComponentContext> & rxContext)422 ::rtl::Reference<PresenterClockTimer> PresenterClockTimer::Instance (
423     const css::uno::Reference<css::uno::XComponentContext>& rxContext)
424 {
425     ::osl::MutexGuard aSolarGuard (::osl::Mutex::getGlobalMutex());
426 
427     ::rtl::Reference<PresenterClockTimer> pTimer;
428     if (mpInstance.is())
429     {
430         pTimer = mpInstance;
431     }
432     if ( ! pTimer.is())
433     {
434         pTimer.set(new PresenterClockTimer(rxContext));
435         mpInstance = pTimer;
436     }
437     return pTimer;
438 }
439 
PresenterClockTimer(const Reference<XComponentContext> & rxContext)440 PresenterClockTimer::PresenterClockTimer (const Reference<XComponentContext>& rxContext)
441     : PresenterClockTimerInterfaceBase(m_aMutex),
442       maListeners(),
443       maDateTime(),
444       mnTimerTaskId(PresenterTimer::NotAValidTaskId),
445       mbIsCallbackPending(false),
446       mxRequestCallback()
447     , m_xContext(rxContext)
448 {
449     assert(m_xContext.is());
450     Reference<lang::XMultiComponentFactory> xFactory =
451         rxContext->getServiceManager();
452     if (xFactory.is())
453         mxRequestCallback.set(
454             xFactory->createInstanceWithContext(
455                 "com.sun.star.awt.AsyncCallback",
456                 rxContext),
457             UNO_QUERY_THROW);
458 }
459 
~PresenterClockTimer()460 PresenterClockTimer::~PresenterClockTimer()
461 {
462     if (mnTimerTaskId != PresenterTimer::NotAValidTaskId)
463     {
464         PresenterTimer::CancelTask(mnTimerTaskId);
465         mnTimerTaskId = PresenterTimer::NotAValidTaskId;
466     }
467 
468     Reference<lang::XComponent> xComponent (mxRequestCallback, UNO_QUERY);
469     if (xComponent.is())
470         xComponent->dispose();
471     mxRequestCallback = nullptr;
472 }
473 
AddListener(const SharedListener & rListener)474 void PresenterClockTimer::AddListener (const SharedListener& rListener)
475 {
476     osl::MutexGuard aGuard (maMutex);
477 
478     maListeners.push_back(rListener);
479 
480     // Create a timer task when the first listener is added.
481     if (mnTimerTaskId==PresenterTimer::NotAValidTaskId)
482     {
483         mnTimerTaskId = PresenterTimer::ScheduleRepeatedTask(
484             m_xContext,
485             [this] (TimeValue const& rTime) { return this->CheckCurrentTime(rTime); },
486             0,
487             250000000 /*ns*/);
488     }
489 }
490 
RemoveListener(const SharedListener & rListener)491 void PresenterClockTimer::RemoveListener (const SharedListener& rListener)
492 {
493     osl::MutexGuard aGuard (maMutex);
494 
495     ListenerContainer::iterator iListener (::std::find(
496         maListeners.begin(),
497         maListeners.end(),
498         rListener));
499     if (iListener != maListeners.end())
500         maListeners.erase(iListener);
501     if (maListeners.empty())
502     {
503         // We have no more clients and therefore are not interested in time changes.
504         if (mnTimerTaskId != PresenterTimer::NotAValidTaskId)
505         {
506             PresenterTimer::CancelTask(mnTimerTaskId);
507             mnTimerTaskId = PresenterTimer::NotAValidTaskId;
508         }
509         mpInstance = nullptr;
510     }
511 }
512 
GetCurrentTime()513 oslDateTime PresenterClockTimer::GetCurrentTime()
514 {
515     TimeValue aCurrentTime;
516     TimerScheduler::GetCurrentTime(aCurrentTime);
517     oslDateTime aDateTime;
518     osl_getDateTimeFromTimeValue(&aCurrentTime, &aDateTime);
519     return aDateTime;
520 }
521 
CheckCurrentTime(const TimeValue & rCurrentTime)522 void PresenterClockTimer::CheckCurrentTime (const TimeValue& rCurrentTime)
523 {
524     css::uno::Reference<css::awt::XRequestCallback> xRequestCallback;
525     css::uno::Reference<css::awt::XCallback> xCallback;
526     {
527         osl::MutexGuard aGuard (maMutex);
528 
529         TimeValue aCurrentTime (rCurrentTime);
530         oslDateTime aDateTime;
531         if (osl_getDateTimeFromTimeValue(&aCurrentTime, &aDateTime))
532         {
533             if (aDateTime.Seconds != maDateTime.Seconds
534                 || aDateTime.Minutes != maDateTime.Minutes
535                 || aDateTime.Hours != maDateTime.Hours)
536             {
537                 // The displayed part of the current time has changed.
538                 // Prepare to call the listeners.
539                 maDateTime = aDateTime;
540 
541                 // Schedule notification of listeners.
542                 if (mxRequestCallback.is() && ! mbIsCallbackPending)
543                 {
544                     mbIsCallbackPending = true;
545                     xRequestCallback = mxRequestCallback;
546                     xCallback = this;
547                 }
548             }
549         }
550     }
551     if (xRequestCallback.is() && xCallback.is())
552         xRequestCallback->addCallback(xCallback, Any());
553 }
554 
555 //----- XCallback -------------------------------------------------------------
556 
notify(const css::uno::Any &)557 void SAL_CALL PresenterClockTimer::notify (const css::uno::Any&)
558 {
559     ListenerContainer aListenerCopy;
560 
561     {
562         osl::MutexGuard aGuard (maMutex);
563 
564         mbIsCallbackPending = false;
565 
566         aListenerCopy = maListeners;
567     }
568 
569     for (const auto& rxListener : aListenerCopy)
570     {
571         rxListener->TimeHasChanged(maDateTime);
572     }
573 }
574 
575 } // end of namespace ::sdext::presenter
576 
577 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
578