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