LibreOffice Module sdext (master)  1
PresenterTimer.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  * Licensed to the Apache Software Foundation (ASF) under one or more
12  * contributor license agreements. See the NOTICE file distributed
13  * with this work for additional information regarding copyright
14  * ownership. The ASF licenses this file to you under the Apache
15  * License, Version 2.0 (the "License"); you may not use this file
16  * except in compliance with the License. You may obtain a copy of
17  * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
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 <iterator>
31 #include <memory>
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 
50  TimeValue maDueTime;
51  const sal_Int64 mnRepeatInterval;
52  const sal_Int32 mnTaskId;
54 };
55 
56 typedef std::shared_ptr<TimerTask> SharedTimerTask;
57 
58 class TimerTaskComparator
59 {
60 public:
61  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 
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
98  static bool HasInstance() { return mpInstance != nullptr; }
99 #endif
100 
101 private:
102  static std::shared_ptr<TimerScheduler> mpInstance;
103  static ::osl::Mutex maInstanceMutex;
104  std::shared_ptr<TimerScheduler> mpLateDestroy; // for clean exit
105  static sal_Int32 mnTaskId;
106 
107  ::osl::Mutex maTaskContainerMutex;
108  typedef ::std::set<SharedTimerTask,TimerTaskComparator> TaskContainer;
109  TaskContainer maScheduledTasks;
110  ::osl::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;
118  virtual void SAL_CALL onTerminated() override { mpLateDestroy.reset(); }
119 };
120 
121 class TerminateListener
122  : public ::cppu::WeakImplHelper<frame::XTerminateListener>
123 {
124  virtual ~TerminateListener() override
125  {
126  assert(!TimerScheduler::HasInstance());
127  }
128 
129  virtual void SAL_CALL disposing(lang::EventObject const&) override
130  {
131  }
132 
133  virtual void SAL_CALL queryTermination(lang::EventObject const&) override
134  {
135  }
136 
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 
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 
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;
183 
184 std::shared_ptr<TimerScheduler> TimerScheduler::Instance(
185  uno::Reference<uno::XComponentContext> const& xContext)
186 {
187  ::osl::MutexGuard 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 
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 
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 
221 void TimerScheduler::ScheduleTask (const SharedTimerTask& rpTask)
222 {
223  if (rpTask.get() == nullptr)
224  return;
225  if (rpTask->mbIsCanceled)
226  return;
227 
228  {
229  osl::MutexGuard aTaskGuard (maTaskContainerMutex);
230  maScheduledTasks.insert(rpTask);
231  }
232 }
233 
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  ::osl::MutexGuard 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  ::osl::MutexGuard aGuard (maCurrentTaskMutex);
252  if (mpCurrentTask.get() != nullptr
253  && mpCurrentTask->mnTaskId == nTaskId)
254  mpCurrentTask->mbIsCanceled = true;
255  }
256 
257  // Let the main-loop cleanup in its own time
258 }
259 
260 void TimerScheduler::NotifyTermination()
261 {
262  std::shared_ptr<TimerScheduler> const pInstance(TimerScheduler::mpInstance);
263  if (!pInstance)
264  {
265  return;
266  }
267 
268  {
269  ::osl::MutexGuard aGuard(pInstance->maTaskContainerMutex);
270  pInstance->maScheduledTasks.clear();
271  }
272 
273  {
274  ::osl::MutexGuard 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 
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  ::osl::MutexGuard 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  ::osl::MutexGuard aGuard (maCurrentTaskMutex);
326  mpCurrentTask = pTask;
327  }
328 
329  if (pTask.get() == nullptr)
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  ::osl::MutexGuard aGuard (maCurrentTaskMutex);
360  mpCurrentTask.reset();
361  }
362  }
363 
364  // While holding maInstanceMutex
365  osl::Guard< osl::Mutex > aInstance( maInstanceMutex );
366  mpLateDestroy = mpInstance;
367  mpInstance.reset();
368 }
369 
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 
378 sal_Int64 TimerScheduler::GetTimeDifference (
379  const TimeValue& rTargetTime,
380  const TimeValue& rCurrentTime)
381 {
382  return ConvertFromTimeValue(rTargetTime) - ConvertFromTimeValue(rCurrentTime);
383 }
384 
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 
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 
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 
421 
423  const css::uno::Reference<css::uno::XComponentContext>& rxContext)
424 {
425  ::osl::MutexGuard aSolarGuard (::osl::Mutex::getGlobalMutex());
426 
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 
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 
461 {
463  {
466  }
467 
469  if (xComponent.is())
470  xComponent->dispose();
471  mxRequestCallback = nullptr;
472 }
473 
475 {
476  osl::MutexGuard aGuard (maMutex);
477 
478  maListeners.push_back(rListener);
479 
480  // Create a timer task when the first listener is added.
482  {
484  m_xContext,
485  [this] (TimeValue const& rTime) { return this->CheckCurrentTime(rTime); },
486  0,
487  250000000 /*ns*/);
488  }
489 }
490 
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.
505  {
508  }
509  mpInstance = nullptr;
510  }
511 }
512 
514 {
515  TimeValue aCurrentTime;
516  TimerScheduler::GetCurrentTime(aCurrentTime);
517  oslDateTime aDateTime;
518  osl_getDateTimeFromTimeValue(&aCurrentTime, &aDateTime);
519  return aDateTime;
520 }
521 
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.
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 
557 void SAL_CALL PresenterClockTimer::notify (const css::uno::Any&)
558 {
559  ListenerContainer aListenerCopy (maListeners);
560 
561  {
562  osl::MutexGuard aGuard (maMutex);
563 
564  mbIsCallbackPending = false;
565 
566  ::std::copy(
567  maListeners.begin(),
568  maListeners.end(),
569  ::std::back_inserter(aListenerCopy));
570  }
571 
572  for (const auto& rxListener : aListenerCopy)
573  {
574  rxListener->TimeHasChanged(maDateTime);
575  }
576 }
577 
578 } // end of namespace ::sdext::presenter
579 
580 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
TaskContainer maScheduledTasks
SharedTimerTask mpCurrentTask
static sal_Int32 ScheduleRepeatedTask(const css::uno::Reference< css::uno::XComponentContext > &xContext, const Task &rTask, const sal_Int64 nFirst, const sal_Int64 nInterval)
Schedule a task to be executed repeatedly.
TimeValue maDueTime
osl::Mutex m_aMutex
void AddListener(const SharedListener &rListener)
::osl::Mutex maInstanceMutex
void CheckCurrentTime(const TimeValue &rCurrentTime)
const sal_Int32 mnTaskId
bool mbIsCanceled
virtual void SAL_CALL notify(const css::uno::Any &rUserData) override
static::rtl::Reference< PresenterClockTimer > Instance(const css::uno::Reference< css::uno::XComponentContext > &rxContext)
def run
::osl::Mutex maCurrentTaskMutex
css::uno::Reference< css::lang::XComponent > xComponent
const css::uno::Reference< css::uno::XComponentContext > m_xContext
PresenterClockTimer(const css::uno::Reference< css::uno::XComponentContext > &rxContext)
static void CancelTask(const sal_Int32 nTaskId)
std::shared_ptr< TimerScheduler > mpLateDestroy
PresenterTimer::Task maTask
DateTime maDateTime
::osl::Mutex maTaskContainerMutex
const sal_Int64 mnRepeatInterval
::osl::Condition m_Shutdown
css::uno::Reference< css::awt::XRequestCallback > mxRequestCallback
static std::shared_ptr< TimerScheduler > mpInstance
std::shared_ptr< Listener > SharedListener
::std::vector< SharedListener > ListenerContainer
void RemoveListener(const SharedListener &rListener)
static const sal_Int32 NotAValidTaskId
cppu::WeakComponentImplHelper< css::awt::XCallback > PresenterClockTimerInterfaceBase
Reference< XSingleServiceFactory > xFactory
The timer allows tasks to be scheduled for execution at a specified time in the future.
::std::function< void(const TimeValue &)> Task
A task is called with the current time.
static::rtl::Reference< PresenterClockTimer > mpInstance
const uno::Reference< uno::XComponentContext > m_xContext
Definition: wrapper.cxx:144