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 <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 
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 std::mutex maInstanceMutex;
104  std::shared_ptr<TimerScheduler> mpLateDestroy; // for clean exit
105  static sal_Int32 mnTaskId;
106 
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;
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  std::scoped_lock 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 {
201  uno::Reference<frame::XDesktop> const xDesktop(
202  frame::Desktop::create(xContext));
203  uno::Reference<frame::XTerminateListener> const xListener(
204  new TerminateListener);
205  // assuming the desktop can take ownership
206  xDesktop->addTerminateListener(xListener);
207 }
208 
209 SharedTimerTask TimerScheduler::CreateTimerTask (
210  const PresenterTimer::Task& rTask,
211  const TimeValue& rDueTime,
212  const sal_Int64 nRepeatInterval)
213 {
214  return std::make_shared<TimerTask>(rTask, rDueTime, nRepeatInterval, ++mnTaskId);
215 }
216 
217 void TimerScheduler::ScheduleTask (const SharedTimerTask& rpTask)
218 {
219  if (!rpTask)
220  return;
221  if (rpTask->mbIsCanceled)
222  return;
223 
224  {
225  std::scoped_lock aTaskGuard (maTaskContainerMutex);
226  maScheduledTasks.insert(rpTask);
227  }
228 }
229 
230 void TimerScheduler::CancelTask (const sal_Int32 nTaskId)
231 {
232  // Set of scheduled tasks is sorted after their due times, not their
233  // task ids. Therefore we have to do a linear search for the task to
234  // cancel.
235  {
236  std::scoped_lock aGuard (maTaskContainerMutex);
237  auto iTask = std::find_if(maScheduledTasks.begin(), maScheduledTasks.end(),
238  [nTaskId](const SharedTimerTask& rxTask) { return rxTask->mnTaskId == nTaskId; });
239  if (iTask != maScheduledTasks.end())
240  maScheduledTasks.erase(iTask);
241  }
242 
243  // The task that is to be canceled may be currently about to be
244  // processed. Mark it with a flag that a) prevents a repeating task
245  // from being scheduled again and b) tries to prevent its execution.
246  {
247  std::scoped_lock aGuard (maCurrentTaskMutex);
248  if (mpCurrentTask
249  && mpCurrentTask->mnTaskId == nTaskId)
250  mpCurrentTask->mbIsCanceled = true;
251  }
252 
253  // Let the main-loop cleanup in its own time
254 }
255 
256 void TimerScheduler::NotifyTermination()
257 {
258  std::shared_ptr<TimerScheduler> const pInstance(TimerScheduler::mpInstance);
259  if (!pInstance)
260  {
261  return;
262  }
263 
264  {
265  std::scoped_lock aGuard(pInstance->maTaskContainerMutex);
266  pInstance->maScheduledTasks.clear();
267  }
268 
269  {
270  std::scoped_lock aGuard(pInstance->maCurrentTaskMutex);
271  if (pInstance->mpCurrentTask)
272  {
273  pInstance->mpCurrentTask->mbIsCanceled = true;
274  }
275  }
276 
277  pInstance->m_Shutdown.set();
278 
279  // rhbz#1425304 join thread before shutdown
280  pInstance->join();
281 }
282 
283 void SAL_CALL TimerScheduler::run()
284 {
285  osl_setThreadName("sdext::presenter::TimerScheduler");
286 
287  while (true)
288  {
289  // Get the current time.
290  TimeValue aCurrentTime;
291  if ( ! GetCurrentTime(aCurrentTime))
292  {
293  // We can not get the current time and thus can not schedule anything.
294  break;
295  }
296 
297  // Restrict access to the maScheduledTasks member to one, mutex
298  // guarded, block.
299  SharedTimerTask pTask;
300  sal_Int64 nDifference = 0;
301  {
302  std::scoped_lock aGuard (maTaskContainerMutex);
303 
304  // There are no more scheduled task. Leave this loop, function and
305  // live of the TimerScheduler.
306  if (maScheduledTasks.empty())
307  break;
308 
309  nDifference = GetTimeDifference(
310  (*maScheduledTasks.begin())->maDueTime,
311  aCurrentTime);
312  if (nDifference <= 0)
313  {
314  pTask = *maScheduledTasks.begin();
315  maScheduledTasks.erase(maScheduledTasks.begin());
316  }
317  }
318 
319  // Acquire a reference to the current task.
320  {
321  std::scoped_lock aGuard (maCurrentTaskMutex);
322  mpCurrentTask = pTask;
323  }
324 
325  if (!pTask)
326  {
327  // Wait until the first task becomes due.
328  TimeValue aTimeValue;
329  ConvertToTimeValue(aTimeValue, nDifference);
330  // wait on condition variable, so the thread can be stopped
331  m_Shutdown.wait(&aTimeValue);
332  }
333  else
334  {
335  // Execute task.
336  if (pTask->maTask && !pTask->mbIsCanceled)
337  {
338  pTask->maTask(aCurrentTime);
339 
340  // Re-schedule repeating tasks.
341  if (pTask->mnRepeatInterval > 0)
342  {
343  ConvertToTimeValue(
344  pTask->maDueTime,
345  ConvertFromTimeValue(pTask->maDueTime)
346  + pTask->mnRepeatInterval);
347  ScheduleTask(pTask);
348  }
349  }
350 
351  }
352 
353  // Release reference to the current task.
354  {
355  std::scoped_lock aGuard (maCurrentTaskMutex);
356  mpCurrentTask.reset();
357  }
358  }
359 
360  // While holding maInstanceMutex
361  std::scoped_lock aInstance( maInstanceMutex );
362  mpLateDestroy = mpInstance;
363  mpInstance.reset();
364 }
365 
366 bool TimerScheduler::GetCurrentTime (TimeValue& rCurrentTime)
367 {
368  TimeValue aSystemTime;
369  if (osl_getSystemTime(&aSystemTime))
370  return osl_getLocalTimeFromSystemTime(&aSystemTime, &rCurrentTime);
371  return false;
372 }
373 
374 sal_Int64 TimerScheduler::GetTimeDifference (
375  const TimeValue& rTargetTime,
376  const TimeValue& rCurrentTime)
377 {
378  return ConvertFromTimeValue(rTargetTime) - ConvertFromTimeValue(rCurrentTime);
379 }
380 
381 void TimerScheduler::ConvertToTimeValue (
382  TimeValue& rTimeValue,
383  const sal_Int64 nTimeDifference)
384 {
385  rTimeValue.Seconds = sal::static_int_cast<sal_Int32>(nTimeDifference / 1000000000L);
386  rTimeValue.Nanosec = sal::static_int_cast<sal_Int32>(nTimeDifference % 1000000000L);
387 }
388 
389 sal_Int64 TimerScheduler::ConvertFromTimeValue (
390  const TimeValue& rTimeValue)
391 {
392  return sal_Int64(rTimeValue.Seconds) * 1000000000L + rTimeValue.Nanosec;
393 }
394 
395 //===== TimerTask =============================================================
396 
397 namespace {
398 
399 TimerTask::TimerTask (
400  const PresenterTimer::Task& rTask,
401  const TimeValue& rDueTime,
402  const sal_Int64 nRepeatInterval,
403  const sal_Int32 nTaskId)
404  : maTask(rTask),
405  maDueTime(rDueTime),
406  mnRepeatInterval(nRepeatInterval),
407  mnTaskId(nTaskId),
408  mbIsCanceled(false)
409 {
410 }
411 
412 } // end of anonymous namespace
413 
414 //===== PresenterTimer ========================================================
415 
417 
419  const css::uno::Reference<css::uno::XComponentContext>& rxContext)
420 {
421  ::osl::MutexGuard aSolarGuard (::osl::Mutex::getGlobalMutex());
422 
424  if (mpInstance.is())
425  {
426  pTimer = mpInstance;
427  }
428  if ( ! pTimer.is())
429  {
430  pTimer.set(new PresenterClockTimer(rxContext));
431  mpInstance = pTimer;
432  }
433  return pTimer;
434 }
435 
438  maDateTime(),
439  mnTimerTaskId(PresenterTimer::NotAValidTaskId),
440  mbIsCallbackPending(false),
441  m_xContext(rxContext)
442 {
443  assert(m_xContext.is());
444  Reference<lang::XMultiComponentFactory> xFactory =
445  rxContext->getServiceManager();
446  if (xFactory.is())
447  mxRequestCallback.set(
448  xFactory->createInstanceWithContext(
449  "com.sun.star.awt.AsyncCallback",
450  rxContext),
451  UNO_QUERY_THROW);
452 }
453 
455 {
457  {
460  }
461 
462  Reference<lang::XComponent> xComponent (mxRequestCallback, UNO_QUERY);
463  if (xComponent.is())
464  xComponent->dispose();
465  mxRequestCallback = nullptr;
466 }
467 
469 {
470  osl::MutexGuard aGuard (maMutex);
471 
472  maListeners.push_back(rListener);
473 
474  // Create a timer task when the first listener is added.
476  {
478  m_xContext,
479  [this] (TimeValue const& rTime) { return this->CheckCurrentTime(rTime); },
480  0,
481  250000000 /*ns*/);
482  }
483 }
484 
486 {
487  osl::MutexGuard aGuard (maMutex);
488 
489  ListenerContainer::iterator iListener (::std::find(
490  maListeners.begin(),
491  maListeners.end(),
492  rListener));
493  if (iListener != maListeners.end())
494  maListeners.erase(iListener);
495  if (maListeners.empty())
496  {
497  // We have no more clients and therefore are not interested in time changes.
499  {
502  }
503  mpInstance = nullptr;
504  }
505 }
506 
508 {
509  TimeValue aCurrentTime;
510  TimerScheduler::GetCurrentTime(aCurrentTime);
511  oslDateTime aDateTime;
512  osl_getDateTimeFromTimeValue(&aCurrentTime, &aDateTime);
513  return aDateTime;
514 }
515 
516 void PresenterClockTimer::CheckCurrentTime (const TimeValue& rCurrentTime)
517 {
518  css::uno::Reference<css::awt::XRequestCallback> xRequestCallback;
519  css::uno::Reference<css::awt::XCallback> xCallback;
520  {
521  osl::MutexGuard aGuard (maMutex);
522 
523  TimeValue aCurrentTime (rCurrentTime);
524  oslDateTime aDateTime;
525  if (osl_getDateTimeFromTimeValue(&aCurrentTime, &aDateTime))
526  {
527  if (aDateTime.Seconds != maDateTime.Seconds
528  || aDateTime.Minutes != maDateTime.Minutes
529  || aDateTime.Hours != maDateTime.Hours)
530  {
531  // The displayed part of the current time has changed.
532  // Prepare to call the listeners.
533  maDateTime = aDateTime;
534 
535  // Schedule notification of listeners.
537  {
538  mbIsCallbackPending = true;
539  xRequestCallback = mxRequestCallback;
540  xCallback = this;
541  }
542  }
543  }
544  }
545  if (xRequestCallback.is() && xCallback.is())
546  xRequestCallback->addCallback(xCallback, Any());
547 }
548 
549 //----- XCallback -------------------------------------------------------------
550 
551 void SAL_CALL PresenterClockTimer::notify (const css::uno::Any&)
552 {
553  ListenerContainer aListenerCopy;
554 
555  {
556  osl::MutexGuard aGuard (maMutex);
557 
558  mbIsCallbackPending = false;
559 
560  aListenerCopy = maListeners;
561  }
562 
563  for (const auto& rxListener : aListenerCopy)
564  {
565  rxListener->TimeHasChanged(maDateTime);
566  }
567 }
568 
569 } // end of namespace ::sdext::presenter
570 
571 /* 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
void AddListener(const SharedListener &rListener)
std::mutex maCurrentTaskMutex
static std::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)
std::mutex m_aMutex
def run
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
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::mutex maTaskContainerMutex
::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:150