From 76475937edc2d61a559dba48ef14727ce3387664 Mon Sep 17 00:00:00 2001 From: Lars Op den Kamp Date: Thu, 20 Jan 2011 11:33:54 +0100 Subject: [PATCH] sync xbmc-antiquated/Dharma-pvr with xbmc/Dharma --- .gitignore | 12 + Makefile.in | 50 +- XBMC.xcodeproj/project.pbxproj | 178 + addons/library.xbmc.addon/dlfcn-win32.cpp | 263 + addons/library.xbmc.addon/dlfcn-win32.h | 46 + addons/library.xbmc.addon/libXBMC_addon.h | 150 + addons/library.xbmc.gui/libXBMC_gui.h | 325 + addons/library.xbmc.pvr/libXBMC_pvr.h | 165 + addons/pvr.hts/addon.xml | 22 + addons/pvr.hts/icon.png | Bin 0 -> 21031 bytes addons/pvr.hts/pthreadVC2.dll | Bin 0 -> 30720 bytes addons/pvr.hts/pthreadVC2d.dll | Bin 0 -> 79360 bytes .../resources/language/Dutch/strings.xml | 11 + .../resources/language/English/strings.xml | 11 + .../resources/language/German/strings.xml | 9 + addons/pvr.hts/resources/settings.xml | 10 + addons/pvr.mythtv/addon.xml | 23 + .../resources/language/English/strings.xml | 7 + addons/pvr.mythtv/resources/settings.xml | 6 + .../pvr.team-mediaportal.tvserver/addon.xml | 22 + addons/pvr.team-mediaportal.tvserver/icon.jpg | Bin 0 -> 9461 bytes .../resources/language/Dutch/strings.xml | 14 + .../resources/language/English/strings.xml | 14 + .../resources/language/German/strings.xml | 14 + .../resources/settings.xml | 13 + addons/pvr.vdr.streamdev/addon.xml | 23 + addons/pvr.vdr.streamdev/icon.jpg | Bin 0 -> 14359 bytes addons/pvr.vdr.streamdev/pthreadVC2.dll | Bin 0 -> 30720 bytes addons/pvr.vdr.streamdev/pthreadVC2d.dll | Bin 0 -> 79360 bytes .../resources/data/noSignal.mpg | Bin 0 -> 25207 bytes .../resources/language/Dutch/strings.xml | 15 + .../resources/language/English/strings.xml | 15 + .../resources/language/German/strings.xml | 15 + .../pvr.vdr.streamdev/resources/settings.xml | 14 + addons/pvr.vdr.vnsi/addon.xml | 23 + addons/pvr.vdr.vnsi/icon.jpg | Bin 0 -> 17133 bytes addons/pvr.vdr.vnsi/pthreadVC2.dll | Bin 0 -> 30720 bytes addons/pvr.vdr.vnsi/pthreadVC2d.dll | Bin 0 -> 79360 bytes .../resources/language/Dutch/strings.xml | 48 + .../resources/language/English/strings.xml | 48 + .../resources/language/German/strings.xml | 48 + addons/pvr.vdr.vnsi/resources/settings.xml | 11 + .../skins/Confluence/720p/ChannelScan.xml | 733 + .../720p/DialogFullScreenInfo.xml | 59 +- .../720p/DialogPVRChannelManager.xml | 604 + .../720p/DialogPVRChannelsOSD.xml | 263 + .../720p/DialogPVRGroupManager.xml | 435 + .../720p/DialogPVRGuideInfo.xml | 274 + .../720p/DialogPVRGuideOSD.xml | 266 + .../720p/DialogPVRGuideSearch.xml | 461 + .../720p/DialogPVRRecordingInfo.xml | 261 + .../720p/DialogPVRTimerSettings.xml | 159 + .../720p/DialogPVRUpdateProgressBar.xml | 48 + addons/skin.confluence/720p/Home.xml | 240 +- addons/skin.confluence/720p/MyTV.xml | 2813 +++ .../skin.confluence/720p/PlayerControls.xml | 144 +- addons/skin.confluence/720p/Settings.xml | 18 +- .../720p/SettingsSystemInfo.xml | 15 + .../skin.confluence/720p/VideoFullScreen.xml | 249 +- addons/skin.confluence/720p/VideoOSD.xml | 294 +- .../720p/custom_SkinSetting_1111.xml | 67 + addons/skin.confluence/720p/defaults.xml | 1 + addons/skin.confluence/720p/includes.xml | 30 +- addons/skin.confluence/backgrounds/tv.jpg | Bin 0 -> 576461 bytes .../language/English/strings.xml | 20 +- .../language/German/strings.xml | 17 +- addons/skin.confluence/media/Makefile | 1 + .../media/OSDChannelDownFO.png | Bin 0 -> 4693 bytes .../media/OSDChannelDownNF.png | Bin 0 -> 3686 bytes .../media/OSDChannelListFO.png | Bin 0 -> 4766 bytes .../media/OSDChannelListNF.png | Bin 0 -> 3654 bytes .../skin.confluence/media/OSDChannelUPFO.png | Bin 0 -> 4643 bytes .../skin.confluence/media/OSDChannelUPNF.png | Bin 0 -> 3698 bytes addons/skin.confluence/media/OSDRecordNF2.png | Bin 0 -> 1883 bytes .../skin.confluence/media/OSDTeleTextFO.png | Bin 0 -> 4838 bytes .../skin.confluence/media/OSDTeleTextNF.png | Bin 0 -> 3654 bytes addons/skin.confluence/media/OSDepgFO.png | Bin 0 -> 4842 bytes addons/skin.confluence/media/OSDepgNF.png | Bin 0 -> 3648 bytes addons/skin.confluence/media/PVR-HasTimer.png | Bin 0 -> 3138 bytes .../skin.confluence/media/PVR-IsRecording.png | Bin 0 -> 3655 bytes addons/skin.confluence/media/StackNF.png | Bin 2894 -> 2897 bytes .../media/genre-a-moviedrama.png | Bin 0 -> 2849 bytes addons/skin.confluence/media/genre-b-news.png | Bin 0 -> 2846 bytes addons/skin.confluence/media/genre-c-show.png | Bin 0 -> 2849 bytes .../skin.confluence/media/genre-d-sports.png | Bin 0 -> 2849 bytes .../skin.confluence/media/genre-e-child.png | Bin 0 -> 2849 bytes .../skin.confluence/media/genre-f-music.png | Bin 0 -> 2849 bytes addons/skin.confluence/media/genre-g-arts.png | Bin 0 -> 2849 bytes .../skin.confluence/media/genre-h-social.png | Bin 0 -> 2849 bytes .../skin.confluence/media/genre-i-science.png | Bin 0 -> 2849 bytes .../skin.confluence/media/genre-j-hobby.png | Bin 0 -> 2849 bytes .../skin.confluence/media/genre-k-special.png | Bin 0 -> 2849 bytes .../skin.confluence/media/genre-l-unknown.png | Bin 0 -> 2849 bytes configure.in | 5 + guilib/GUIControl.h | 1 + guilib/GUIControlFactory.cpp | 13 + guilib/GUIEPGGridContainer.cpp | 1745 ++ guilib/GUIEPGGridContainer.h | 215 + guilib/GUIEditControl.cpp | 16 +- guilib/GUIEditControl.h | 1 + guilib/GUILabelControl.cpp | 7 + guilib/GUILabelControl.h | 1 + guilib/GUIListGroup.cpp | 44 + guilib/GUIListGroup.h | 2 + guilib/GUIListItemLayout.cpp | 14 + guilib/GUIListItemLayout.h | 2 + guilib/GUIListLabel.cpp | 12 + guilib/GUIListLabel.h | 1 + guilib/Key.h | 28 +- guilib/Makefile.in | 1 + guilib/system.h | 1 + language/Dutch/strings.xml | 533 +- language/English/strings.xml | 422 +- language/German/strings.xml | 410 + lib/addons/library.xbmc.addon/Makefile.in | 27 + .../library.xbmc.addon/libXBMC_addon.cpp | 128 + .../project/VS2008Express/libXBMC_addon.sln | 20 + .../VS2008Express/libXBMC_addon.vcproj | 177 + lib/addons/library.xbmc.gui/Makefile.in | 27 + lib/addons/library.xbmc.gui/libXBMC_gui.cpp | 555 + .../project/VS2008Express/libXBMC_gui.sln | 20 + .../project/VS2008Express/libXBMC_gui.vcproj | 183 + lib/addons/library.xbmc.pvr/Makefile.in | 27 + lib/addons/library.xbmc.pvr/libXBMC_pvr.cpp | 149 + .../project/VS2008Express/libXBMC_pvr.sln | 20 + .../project/VS2008Express/libXBMC_pvr.vcproj | 183 + project/VS2008Express/XBMC for Windows.sln | 87 +- project/VS2008Express/XBMC.vcproj | 264 + project/VS2008Express/guilib.vcproj | 8 + system/keymaps/keyboard.xml | 40 + system/keymaps/remote.xml | 56 +- system/playercorefactory.xml | 3 + tools/Linux/packaging/debian/xbmc-bin.install | 1 + xbmc/AddonDatabase.cpp | 63 + xbmc/AddonDatabase.h | 24 +- xbmc/AdvancedSettings.cpp | 17 + xbmc/AdvancedSettings.h | 5 + xbmc/Application.cpp | 198 +- xbmc/Application.h | 2 + xbmc/ApplicationMessenger.cpp | 10 + xbmc/ApplicationMessenger.h | 5 +- xbmc/ButtonTranslator.cpp | 10 +- xbmc/DateTime.cpp | 93 +- xbmc/DateTime.h | 3 +- xbmc/FileItem.cpp | 278 +- xbmc/FileItem.h | 66 + xbmc/FileSystem/AddonsDirectory.cpp | 5 + xbmc/FileSystem/DllLibCMyth.h | 20 + xbmc/FileSystem/FactoryDirectory.cpp | 6 + xbmc/FileSystem/FileFactory.cpp | 6 + xbmc/FileSystem/ILiveTV.h | 4 +- xbmc/FileSystem/Makefile.in | 2 + xbmc/FileSystem/MythFile.cpp | 4 +- xbmc/FileSystem/MythFile.h | 4 +- xbmc/FileSystem/PVRDirectory.cpp | 120 + xbmc/FileSystem/PVRDirectory.h | 46 + xbmc/FileSystem/PVRFile.cpp | 325 + xbmc/FileSystem/PVRFile.h | 79 + xbmc/FileSystem/VTPFile.cpp | 4 +- xbmc/FileSystem/VTPFile.h | 4 +- xbmc/GUIDialogAddonInfo.cpp | 13 +- xbmc/GUIDialogContextMenu.h | 19 +- xbmc/GUIDialogMediaSource.cpp | 9 + xbmc/GUIDialogNumeric.cpp | 20 +- xbmc/GUIDialogNumeric.h | 4 +- xbmc/GUIDialogPVRChannelManager.cpp | 870 + xbmc/GUIDialogPVRChannelManager.h | 56 + xbmc/GUIDialogPVRChannelsOSD.cpp | 196 + xbmc/GUIDialogPVRChannelsOSD.h | 46 + xbmc/GUIDialogPVRCutterOSD.cpp | 61 + xbmc/GUIDialogPVRCutterOSD.h | 34 + xbmc/GUIDialogPVRDirectorOSD.cpp | 68 + xbmc/GUIDialogPVRDirectorOSD.h | 34 + xbmc/GUIDialogPVRGroupManager.cpp | 292 + xbmc/GUIDialogPVRGroupManager.h | 56 + xbmc/GUIDialogPVRGuideInfo.cpp | 154 + xbmc/GUIDialogPVRGuideInfo.h | 41 + xbmc/GUIDialogPVRGuideOSD.cpp | 152 + xbmc/GUIDialogPVRGuideOSD.h | 46 + xbmc/GUIDialogPVRGuideSearch.cpp | 372 + xbmc/GUIDialogPVRGuideSearch.h | 46 + xbmc/GUIDialogPVRRecordingInfo.cpp | 60 + xbmc/GUIDialogPVRRecordingInfo.h | 39 + xbmc/GUIDialogPVRTimerSettings.cpp | 383 + xbmc/GUIDialogPVRTimerSettings.h | 53 + xbmc/GUIDialogPVRUpdateProgressBar.cpp | 103 + xbmc/GUIDialogPVRUpdateProgressBar.h | 44 + xbmc/GUIDialogSeekBar.cpp | 2 + xbmc/GUIDialogSettings.cpp | 130 +- xbmc/GUIDialogSettings.h | 10 +- xbmc/GUIDialogVideoSettings.cpp | 1 + xbmc/GUIMediaWindow.cpp | 10 +- xbmc/GUISettings.cpp | 46 + xbmc/GUISettings.h | 9 + xbmc/GUIViewControl.cpp | 5 +- xbmc/GUIViewState.cpp | 4 + xbmc/GUIViewStateTV.cpp | 72 + xbmc/GUIViewStateTV.h | 36 + xbmc/GUIViewStateVideo.cpp | 1 + xbmc/GUIWindowFullScreen.cpp | 233 +- xbmc/GUIWindowFullScreen.h | 4 + xbmc/GUIWindowOSD.cpp | 12 + xbmc/GUIWindowSettingsCategory.cpp | 78 +- xbmc/GUIWindowSystemInfo.cpp | 20 +- xbmc/GUIWindowTV.cpp | 1892 ++ xbmc/GUIWindowTV.h | 100 + xbmc/GUIWindowVideoBase.cpp | 57 + xbmc/Makefile.in | 15 + xbmc/PVRManager.cpp | 2034 ++ xbmc/PVRManager.h | 409 + xbmc/Settings.cpp | 4 +- xbmc/SortFileItem.cpp | 20 +- xbmc/SortFileItem.h | 2 + xbmc/TVDatabase.cpp | 1611 ++ xbmc/TVDatabase.h | 109 + xbmc/URL.cpp | 3 + xbmc/Util.cpp | 18 + xbmc/Util.h | 2 + xbmc/XBIRRemote.h | 2 + xbmc/addons/Addon.cpp | 6 +- xbmc/addons/Addon.h | 17 +- xbmc/addons/AddonDll.h | 13 +- xbmc/addons/AddonHelpers_Addon.cpp | 236 + xbmc/addons/AddonHelpers_Addon.h | 50 + xbmc/addons/AddonHelpers_GUI.cpp | 1539 ++ xbmc/addons/AddonHelpers_GUI.h | 180 + xbmc/addons/AddonHelpers_PVR.cpp | 319 + xbmc/addons/AddonHelpers_PVR.h | 55 + xbmc/addons/AddonHelpers_local.cpp | 141 + xbmc/addons/AddonHelpers_local.h | 258 + xbmc/addons/AddonManager.cpp | 33 +- xbmc/addons/AddonManager.h | 1 + xbmc/addons/DllPVRClient.h | 30 + xbmc/addons/Makefile | 7 +- xbmc/addons/PVRClient.cpp | 1003 + xbmc/addons/PVRClient.h | 130 + xbmc/addons/Skin.cpp | 1 + xbmc/addons/include/xbmc_pvr_dll.h | 178 + xbmc/addons/include/xbmc_pvr_types.h | 426 + xbmc/cores/DllLoader/DllLoaderContainer.cpp | 2 +- xbmc/cores/DllLoader/Win32DllLoader.cpp | 3 + .../cores/dvdplayer/DVDCodecs/Video/VDPAU.cpp | 17 +- xbmc/cores/dvdplayer/DVDDemuxers/DVDDemux.h | 14 +- .../dvdplayer/DVDDemuxers/DVDDemuxFFmpeg.cpp | 1 + .../DVDDemuxers/DVDDemuxPVRClient.cpp | 261 + .../dvdplayer/DVDDemuxers/DVDDemuxPVRClient.h | 91 + .../dvdplayer/DVDDemuxers/DVDDemuxPacket.h | 37 + .../dvdplayer/DVDDemuxers/DVDDemuxUtils.h | 2 +- .../DVDDemuxers/DVDFactoryDemuxer.cpp | 21 + xbmc/cores/dvdplayer/DVDDemuxers/Makefile.in | 1 + .../DVDInputStreams/DVDFactoryInputStream.cpp | 3 + .../DVDInputStreams/DVDInputStream.h | 15 +- .../DVDInputStreams/DVDInputStreamHTSP.cpp | 32 +- .../DVDInputStreams/DVDInputStreamHTSP.h | 12 +- .../DVDInputStreams/DVDInputStreamMMS.cpp | 3 + .../DVDInputStreamPVRManager.cpp | 282 + .../DVDInputStreamPVRManager.h | 106 + .../DVDInputStreams/DVDInputStreamTV.cpp | 4 +- .../DVDInputStreams/DVDInputStreamTV.h | 5 +- xbmc/cores/dvdplayer/DVDInputStreams/Makefile | 3 +- xbmc/cores/dvdplayer/DVDPlayer.cpp | 174 +- xbmc/cores/dvdplayer/DVDPlayer.h | 1 + xbmc/cores/paplayer/CodecFactory.cpp | 2 + xbmc/lib/libhts/Win32/include/stdint.h | 2 +- xbmc/pvrclients/MediaPortal/Makefile | 67 + xbmc/pvrclients/MediaPortal/README | 150 + xbmc/pvrclients/MediaPortal/Socket.cpp | 638 + xbmc/pvrclients/MediaPortal/Socket.h | 299 + xbmc/pvrclients/MediaPortal/StdString.h | 4329 ++++ xbmc/pvrclients/MediaPortal/channels.cpp | 67 + xbmc/pvrclients/MediaPortal/channels.h | 49 + xbmc/pvrclients/MediaPortal/client.cpp | 672 + xbmc/pvrclients/MediaPortal/client.h | 65 + xbmc/pvrclients/MediaPortal/epg.cpp | 284 + xbmc/pvrclients/MediaPortal/epg.h | 75 + .../linux/pvrclient-mediaportal_os_posix.h | 81 + .../project/VS2008Express/XBMC_MPTV.sln | 19 + .../project/VS2008Express/XBMC_MPTV.vcproj | 294 + .../MediaPortal/pvrclient-mediaportal.cpp | 1014 + .../MediaPortal/pvrclient-mediaportal.h | 139 + .../MediaPortal/pvrclient-mediaportal_os.h | 47 + xbmc/pvrclients/MediaPortal/recordings.cpp | 130 + xbmc/pvrclients/MediaPortal/recordings.h | 68 + xbmc/pvrclients/MediaPortal/timers.cpp | 132 + xbmc/pvrclients/MediaPortal/timers.h | 64 + xbmc/pvrclients/MediaPortal/utils.cpp | 244 + xbmc/pvrclients/MediaPortal/utils.h | 65 + .../pvrclient-mediaportal_os_windows.h | 87 + xbmc/pvrclients/mythtv/Makefile.in | 70 + xbmc/pvrclients/mythtv/MythXml.cpp | 144 + xbmc/pvrclients/mythtv/MythXml.h | 34 + xbmc/pvrclients/mythtv/StdString.h | 4333 ++++ xbmc/pvrclients/mythtv/client.cpp | 542 + xbmc/pvrclients/mythtv/client.h | 48 + .../mythtv/libmythxml/GetChannelListCommand.h | 25 + .../libmythxml/GetChannelListParameters.h | 19 + .../libmythxml/GetChannelListResult.cpp | 53 + .../mythtv/libmythxml/GetChannelListResult.h | 17 + .../mythtv/libmythxml/GetNumChannelsCommand.h | 24 + .../libmythxml/GetNumChannelsParameters.h | 19 + .../libmythxml/GetNumChannelsResult.cpp | 54 + .../mythtv/libmythxml/GetNumChannelsResult.h | 23 + .../libmythxml/GetProgramGuideCommand.h | 18 + .../libmythxml/GetProgramGuideParameters.cpp | 43 + .../libmythxml/GetProgramGuideParameters.h | 38 + .../libmythxml/GetProgramGuideResult.cpp | 331 + .../mythtv/libmythxml/GetProgramGuideResult.h | 25 + xbmc/pvrclients/mythtv/libmythxml/Makefile | 16 + .../mythtv/libmythxml/MythXmlCommand.cpp | 42 + .../mythtv/libmythxml/MythXmlCommand.h | 49 + .../libmythxml/MythXmlCommandParameters.cpp | 33 + .../libmythxml/MythXmlCommandParameters.h | 43 + .../mythtv/libmythxml/MythXmlCommandResult.h | 35 + xbmc/pvrclients/mythtv/libmythxml/SChannel.h | 18 + xbmc/pvrclients/mythtv/libmythxml/SEpg.h | 29 + .../mythtv/linux/pvrclient-mythtv_os_posix.h | 108 + xbmc/pvrclients/mythtv/pvrclient-mythtv_os.h | 44 + xbmc/pvrclients/tvheadend/HTSPData.cpp | 567 + xbmc/pvrclients/tvheadend/HTSPData.h | 82 + xbmc/pvrclients/tvheadend/HTSPDemux.cpp | 578 + xbmc/pvrclients/tvheadend/HTSPDemux.h | 62 + xbmc/pvrclients/tvheadend/HTSPSession.cpp | 657 + xbmc/pvrclients/tvheadend/HTSPSession.h | 280 + xbmc/pvrclients/tvheadend/Makefile | 70 + xbmc/pvrclients/tvheadend/StdString.h | 4333 ++++ xbmc/pvrclients/tvheadend/client.cpp | 521 + xbmc/pvrclients/tvheadend/client.h | 52 + xbmc/pvrclients/tvheadend/linux/os_posix.h | 93 + .../hts-tvheadend-2.10-AddSignalQuality.diff | 66 + ...-2.11-01-add-CAID-to-channel-in-HTSP.patch | 108 + .../project/VS2008Express/XBMC_hts.vcproj | 268 + .../tvheadend/pthread_win32/pthread.h | 1368 ++ .../tvheadend/pthread_win32/pthreadVC2.lib | Bin 0 -> 29052 bytes .../tvheadend/pthread_win32/pthreadVC2d.lib | Bin 0 -> 29172 bytes .../tvheadend/pthread_win32/sched.h | 178 + .../tvheadend/pthread_win32/semaphore.h | 166 + .../tvheadend/pvrclient-tvheadend_os.h | 44 + xbmc/pvrclients/tvheadend/thread.cpp | 408 + xbmc/pvrclients/tvheadend/thread.h | 166 + xbmc/pvrclients/tvheadend/tools.cpp | 94 + xbmc/pvrclients/tvheadend/tools.h | 65 + xbmc/pvrclients/tvheadend/windows/dirent.cpp | 159 + xbmc/pvrclients/tvheadend/windows/dirent.h | 103 + .../tvheadend/windows/os_windows.cpp | 43 + .../pvrclients/tvheadend/windows/os_windows.h | 365 + xbmc/pvrclients/vdr-streamdev/Makefile.in | 68 + xbmc/pvrclients/vdr-streamdev/README | 34 + xbmc/pvrclients/vdr-streamdev/StdString.h | 4333 ++++ xbmc/pvrclients/vdr-streamdev/channels.cpp | 391 + xbmc/pvrclients/vdr-streamdev/channels.h | 326 + xbmc/pvrclients/vdr-streamdev/channelscan.cpp | 37 + xbmc/pvrclients/vdr-streamdev/channelscan.h | 33 + xbmc/pvrclients/vdr-streamdev/client.cpp | 1225 + xbmc/pvrclients/vdr-streamdev/client.h | 62 + xbmc/pvrclients/vdr-streamdev/epg.cpp | 164 + xbmc/pvrclients/vdr-streamdev/epg.h | 76 + .../linux/pvrclient-vdr_os_posix.h | 108 + ...-cvs100210-ReplaceRecordingStreaming.patch | 581 + .../streamdev-cvs221109-AddCallbackMsg.diff | 120 + .../streamdev-cvs221109-AddFemonV1.diff | 210 + .../patches/vdr-1.7.10_extensionsAndXBMC.diff | 19905 ++++++++++++++++ .../vdr-1.7.7-ExtendetStatusMessage.diff | 113 + .../patches/vdr-1.7.7-GenreToFromEpgDat.diff | 46 + .../project/VS2008Express/XBMC_VDR.sln | 19 + .../project/VS2008Express/XBMC_VDR.vcproj | 334 + .../project/VS2008Express/inttypes.h | 305 + .../project/VS2008Express/stdint.h | 247 + .../vdr-streamdev/pthread_win32/pthread.h | 1368 ++ .../pthread_win32/pthreadVC2.lib | Bin 0 -> 29052 bytes .../pthread_win32/pthreadVC2d.lib | Bin 0 -> 29172 bytes .../vdr-streamdev/pthread_win32/sched.h | 178 + .../vdr-streamdev/pthread_win32/semaphore.h | 166 + .../vdr-streamdev/pvrclient-vdr_os.h | 44 + xbmc/pvrclients/vdr-streamdev/recordings.cpp | 312 + xbmc/pvrclients/vdr-streamdev/recordings.h | 101 + xbmc/pvrclients/vdr-streamdev/ringbuffer.cpp | 385 + xbmc/pvrclients/vdr-streamdev/ringbuffer.h | 115 + xbmc/pvrclients/vdr-streamdev/select.cpp | 80 + xbmc/pvrclients/vdr-streamdev/select.h | 101 + xbmc/pvrclients/vdr-streamdev/thread.cpp | 409 + xbmc/pvrclients/vdr-streamdev/thread.h | 166 + xbmc/pvrclients/vdr-streamdev/timers.cpp | 394 + xbmc/pvrclients/vdr-streamdev/timers.h | 100 + xbmc/pvrclients/vdr-streamdev/tools.cpp | 715 + xbmc/pvrclients/vdr-streamdev/tools.h | 214 + .../vdr-streamdev/vtptransceiver.cpp | 1433 ++ .../pvrclients/vdr-streamdev/vtptransceiver.h | 104 + .../vdr-streamdev/windows/dirent.cpp | 161 + .../pvrclients/vdr-streamdev/windows/dirent.h | 103 + .../vdr-streamdev/windows/getline.cpp | 149 + .../vdr-streamdev/windows/getline.h | 12 + .../windows/pvrclient-vdr_os_windows.cpp | 43 + .../windows/pvrclient-vdr_os_windows.h | 365 + xbmc/pvrclients/vdr-vnsi/COPYING | 340 + xbmc/pvrclients/vdr-vnsi/Makefile | 70 + xbmc/pvrclients/vdr-vnsi/README | 71 + xbmc/pvrclients/vdr-vnsi/StdString.h | 4333 ++++ xbmc/pvrclients/vdr-vnsi/VNSIChannelScan.cpp | 711 + xbmc/pvrclients/vdr-vnsi/VNSIChannelScan.h | 104 + xbmc/pvrclients/vdr-vnsi/VNSIData.cpp | 1050 + xbmc/pvrclients/vdr-vnsi/VNSIData.h | 91 + xbmc/pvrclients/vdr-vnsi/VNSIDemux.cpp | 481 + xbmc/pvrclients/vdr-vnsi/VNSIDemux.h | 68 + xbmc/pvrclients/vdr-vnsi/VNSIRecording.cpp | 170 + xbmc/pvrclients/vdr-vnsi/VNSIRecording.h | 49 + xbmc/pvrclients/vdr-vnsi/VNSISession.cpp | 482 + xbmc/pvrclients/vdr-vnsi/VNSISession.h | 63 + xbmc/pvrclients/vdr-vnsi/client.cpp | 675 + xbmc/pvrclients/vdr-vnsi/client.h | 59 + xbmc/pvrclients/vdr-vnsi/linux/os_posix.h | 93 + .../vdr-vnsi/pvrclient-vdrVNSI_os.h | 44 + xbmc/pvrclients/vdr-vnsi/recordings.cpp | 312 + xbmc/pvrclients/vdr-vnsi/recordings.h | 101 + xbmc/pvrclients/vdr-vnsi/requestpacket.cpp | 150 + xbmc/pvrclients/vdr-vnsi/requestpacket.h | 69 + xbmc/pvrclients/vdr-vnsi/responsepacket.cpp | 142 + xbmc/pvrclients/vdr-vnsi/responsepacket.h | 83 + xbmc/pvrclients/vdr-vnsi/thread.cpp | 409 + xbmc/pvrclients/vdr-vnsi/thread.h | 166 + xbmc/pvrclients/vdr-vnsi/tools.cpp | 740 + xbmc/pvrclients/vdr-vnsi/tools.h | 218 + .../vdr-vnsi/vdr-plugin-vnsiserver/COPYING | 340 + .../vdr-vnsi/vdr-plugin-vnsiserver/HISTORY | 6 + .../vdr-vnsi/vdr-plugin-vnsiserver/Makefile | 123 + .../vdr-vnsi/vdr-plugin-vnsiserver/README | 43 + .../vdr-plugin-vnsiserver/bitstream.c | 128 + .../vdr-plugin-vnsiserver/bitstream.h | 47 + .../vdr-plugin-vnsiserver/cmdcontrol.c | 1675 ++ .../vdr-plugin-vnsiserver/cmdcontrol.h | 111 + .../vdr-vnsi/vdr-plugin-vnsiserver/config.c | 91 + .../vdr-vnsi/vdr-plugin-vnsiserver/config.h | 73 + .../vdr-plugin-vnsiserver/connection.c | 451 + .../vdr-plugin-vnsiserver/connection.h | 98 + .../vdr-vnsi/vdr-plugin-vnsiserver/cxsocket.c | 505 + .../vdr-vnsi/vdr-plugin-vnsiserver/cxsocket.h | 210 + .../vdr-vnsi/vdr-plugin-vnsiserver/demuxer.c | 380 + .../vdr-vnsi/vdr-plugin-vnsiserver/demuxer.h | 293 + .../vdr-plugin-vnsiserver/demuxer_AAC.c | 278 + .../vdr-plugin-vnsiserver/demuxer_AAC.h | 59 + .../vdr-plugin-vnsiserver/demuxer_AC3.c | 368 + .../vdr-plugin-vnsiserver/demuxer_AC3.h | 76 + .../vdr-plugin-vnsiserver/demuxer_DTS.c | 42 + .../vdr-plugin-vnsiserver/demuxer_DTS.h | 42 + .../vdr-plugin-vnsiserver/demuxer_MPEGAudio.c | 393 + .../vdr-plugin-vnsiserver/demuxer_MPEGAudio.h | 115 + .../vdr-plugin-vnsiserver/demuxer_MPEGVideo.c | 387 + .../vdr-plugin-vnsiserver/demuxer_MPEGVideo.h | 65 + .../vdr-plugin-vnsiserver/demuxer_Subtitle.c | 136 + .../vdr-plugin-vnsiserver/demuxer_Subtitle.h | 50 + .../vdr-plugin-vnsiserver/demuxer_Teletext.c | 110 + .../vdr-plugin-vnsiserver/demuxer_Teletext.h | 47 + .../vdr-plugin-vnsiserver/demuxer_h264.c | 451 + .../vdr-plugin-vnsiserver/demuxer_h264.h | 97 + .../vdr-vnsi/vdr-plugin-vnsiserver/global.h | 128 + ...scan-0.0.5-pre11e-AddServiceInterface.diff | 426 + .../project/VNSI Server.cbp | 118 + .../project/VNSI Server.layout | 76 + .../vdr-vnsi/vdr-plugin-vnsiserver/receiver.c | 1279 + .../vdr-vnsi/vdr-plugin-vnsiserver/receiver.h | 95 + .../vdr-plugin-vnsiserver/recplayer.c | 292 + .../vdr-plugin-vnsiserver/recplayer.h | 75 + .../vdr-plugin-vnsiserver/requestpacket.c | 118 + .../vdr-plugin-vnsiserver/requestpacket.h | 72 + .../vdr-plugin-vnsiserver/responsepacket.c | 214 + .../vdr-plugin-vnsiserver/responsepacket.h | 70 + .../vdr-vnsi/vdr-plugin-vnsiserver/server.c | 220 + .../vdr-vnsi/vdr-plugin-vnsiserver/server.h | 56 + .../vdr-vnsi/vdr-plugin-vnsiserver/suspend.c | 57 + .../vdr-plugin-vnsiserver/suspend.dat | 1206 + .../vdr-vnsi/vdr-plugin-vnsiserver/suspend.h | 36 + .../vdr-vnsi/vdr-plugin-vnsiserver/tools.c | 48 + .../vdr-vnsi/vdr-plugin-vnsiserver/tools.h | 32 + .../vdr-plugin-vnsiserver/vdrcommand.h | 121 + .../vdr-plugin-vnsiserver/vnsiserver.c | 138 + .../vnsiserver/allowed_hosts.conf | 13 + .../vnsiserver/noSignal.mpg | Bin 0 -> 25207 bytes .../vdr-plugin-vnsiserver/wirbelscanservice.h | 57 + xbmc/pvrclients/vdr-vnsi/vdrcommand.h | 122 + xbmc/settings/VideoSettings.h | 1 + xbmc/utils/GUIInfoManager.cpp | 468 +- xbmc/utils/GUIInfoManager.h | 107 +- xbmc/utils/Makefile | 5 + xbmc/utils/PVRChannels.cpp | 1447 ++ xbmc/utils/PVRChannels.h | 312 + xbmc/utils/PVREpg.cpp | 1452 ++ xbmc/utils/PVREpg.h | 222 + xbmc/utils/PVRRecordings.cpp | 499 + xbmc/utils/PVRRecordings.h | 112 + xbmc/utils/PVRTimers.cpp | 709 + xbmc/utils/PVRTimers.h | 151 + xbmc/utils/TextSearch.cpp | 265 + xbmc/utils/TextSearch.h | 48 + xbmc/win32/stdbool.h | 53 + 493 files changed, 123350 insertions(+), 299 deletions(-) create mode 100644 addons/library.xbmc.addon/dlfcn-win32.cpp create mode 100644 addons/library.xbmc.addon/dlfcn-win32.h create mode 100644 addons/library.xbmc.addon/libXBMC_addon.h create mode 100644 addons/library.xbmc.gui/libXBMC_gui.h create mode 100644 addons/library.xbmc.pvr/libXBMC_pvr.h create mode 100644 addons/pvr.hts/addon.xml create mode 100644 addons/pvr.hts/icon.png create mode 100644 addons/pvr.hts/pthreadVC2.dll create mode 100644 addons/pvr.hts/pthreadVC2d.dll create mode 100644 addons/pvr.hts/resources/language/Dutch/strings.xml create mode 100644 addons/pvr.hts/resources/language/English/strings.xml create mode 100644 addons/pvr.hts/resources/language/German/strings.xml create mode 100644 addons/pvr.hts/resources/settings.xml create mode 100644 addons/pvr.mythtv/addon.xml create mode 100644 addons/pvr.mythtv/resources/language/English/strings.xml create mode 100644 addons/pvr.mythtv/resources/settings.xml create mode 100644 addons/pvr.team-mediaportal.tvserver/addon.xml create mode 100644 addons/pvr.team-mediaportal.tvserver/icon.jpg create mode 100644 addons/pvr.team-mediaportal.tvserver/resources/language/Dutch/strings.xml create mode 100644 addons/pvr.team-mediaportal.tvserver/resources/language/English/strings.xml create mode 100644 addons/pvr.team-mediaportal.tvserver/resources/language/German/strings.xml create mode 100644 addons/pvr.team-mediaportal.tvserver/resources/settings.xml create mode 100644 addons/pvr.vdr.streamdev/addon.xml create mode 100644 addons/pvr.vdr.streamdev/icon.jpg create mode 100644 addons/pvr.vdr.streamdev/pthreadVC2.dll create mode 100644 addons/pvr.vdr.streamdev/pthreadVC2d.dll create mode 100644 addons/pvr.vdr.streamdev/resources/data/noSignal.mpg create mode 100644 addons/pvr.vdr.streamdev/resources/language/Dutch/strings.xml create mode 100644 addons/pvr.vdr.streamdev/resources/language/English/strings.xml create mode 100644 addons/pvr.vdr.streamdev/resources/language/German/strings.xml create mode 100644 addons/pvr.vdr.streamdev/resources/settings.xml create mode 100644 addons/pvr.vdr.vnsi/addon.xml create mode 100644 addons/pvr.vdr.vnsi/icon.jpg create mode 100644 addons/pvr.vdr.vnsi/pthreadVC2.dll create mode 100644 addons/pvr.vdr.vnsi/pthreadVC2d.dll create mode 100644 addons/pvr.vdr.vnsi/resources/language/Dutch/strings.xml create mode 100644 addons/pvr.vdr.vnsi/resources/language/English/strings.xml create mode 100644 addons/pvr.vdr.vnsi/resources/language/German/strings.xml create mode 100644 addons/pvr.vdr.vnsi/resources/settings.xml create mode 100644 addons/pvr.vdr.vnsi/resources/skins/Confluence/720p/ChannelScan.xml create mode 100644 addons/skin.confluence/720p/DialogPVRChannelManager.xml create mode 100644 addons/skin.confluence/720p/DialogPVRChannelsOSD.xml create mode 100644 addons/skin.confluence/720p/DialogPVRGroupManager.xml create mode 100644 addons/skin.confluence/720p/DialogPVRGuideInfo.xml create mode 100644 addons/skin.confluence/720p/DialogPVRGuideOSD.xml create mode 100644 addons/skin.confluence/720p/DialogPVRGuideSearch.xml create mode 100644 addons/skin.confluence/720p/DialogPVRRecordingInfo.xml create mode 100644 addons/skin.confluence/720p/DialogPVRTimerSettings.xml create mode 100644 addons/skin.confluence/720p/DialogPVRUpdateProgressBar.xml create mode 100644 addons/skin.confluence/720p/MyTV.xml create mode 100644 addons/skin.confluence/backgrounds/tv.jpg create mode 100644 addons/skin.confluence/media/OSDChannelDownFO.png create mode 100644 addons/skin.confluence/media/OSDChannelDownNF.png create mode 100644 addons/skin.confluence/media/OSDChannelListFO.png create mode 100644 addons/skin.confluence/media/OSDChannelListNF.png create mode 100644 addons/skin.confluence/media/OSDChannelUPFO.png create mode 100644 addons/skin.confluence/media/OSDChannelUPNF.png create mode 100644 addons/skin.confluence/media/OSDRecordNF2.png create mode 100644 addons/skin.confluence/media/OSDTeleTextFO.png create mode 100644 addons/skin.confluence/media/OSDTeleTextNF.png create mode 100644 addons/skin.confluence/media/OSDepgFO.png create mode 100644 addons/skin.confluence/media/OSDepgNF.png create mode 100644 addons/skin.confluence/media/PVR-HasTimer.png create mode 100644 addons/skin.confluence/media/PVR-IsRecording.png create mode 100644 addons/skin.confluence/media/genre-a-moviedrama.png create mode 100644 addons/skin.confluence/media/genre-b-news.png create mode 100644 addons/skin.confluence/media/genre-c-show.png create mode 100644 addons/skin.confluence/media/genre-d-sports.png create mode 100644 addons/skin.confluence/media/genre-e-child.png create mode 100644 addons/skin.confluence/media/genre-f-music.png create mode 100644 addons/skin.confluence/media/genre-g-arts.png create mode 100644 addons/skin.confluence/media/genre-h-social.png create mode 100644 addons/skin.confluence/media/genre-i-science.png create mode 100644 addons/skin.confluence/media/genre-j-hobby.png create mode 100644 addons/skin.confluence/media/genre-k-special.png create mode 100644 addons/skin.confluence/media/genre-l-unknown.png create mode 100644 guilib/GUIEPGGridContainer.cpp create mode 100644 guilib/GUIEPGGridContainer.h create mode 100644 lib/addons/library.xbmc.addon/Makefile.in create mode 100644 lib/addons/library.xbmc.addon/libXBMC_addon.cpp create mode 100644 lib/addons/library.xbmc.addon/project/VS2008Express/libXBMC_addon.sln create mode 100644 lib/addons/library.xbmc.addon/project/VS2008Express/libXBMC_addon.vcproj create mode 100644 lib/addons/library.xbmc.gui/Makefile.in create mode 100644 lib/addons/library.xbmc.gui/libXBMC_gui.cpp create mode 100644 lib/addons/library.xbmc.gui/project/VS2008Express/libXBMC_gui.sln create mode 100644 lib/addons/library.xbmc.gui/project/VS2008Express/libXBMC_gui.vcproj create mode 100644 lib/addons/library.xbmc.pvr/Makefile.in create mode 100644 lib/addons/library.xbmc.pvr/libXBMC_pvr.cpp create mode 100644 lib/addons/library.xbmc.pvr/project/VS2008Express/libXBMC_pvr.sln create mode 100644 lib/addons/library.xbmc.pvr/project/VS2008Express/libXBMC_pvr.vcproj create mode 100644 xbmc/FileSystem/PVRDirectory.cpp create mode 100644 xbmc/FileSystem/PVRDirectory.h create mode 100644 xbmc/FileSystem/PVRFile.cpp create mode 100644 xbmc/FileSystem/PVRFile.h create mode 100644 xbmc/GUIDialogPVRChannelManager.cpp create mode 100644 xbmc/GUIDialogPVRChannelManager.h create mode 100644 xbmc/GUIDialogPVRChannelsOSD.cpp create mode 100644 xbmc/GUIDialogPVRChannelsOSD.h create mode 100644 xbmc/GUIDialogPVRCutterOSD.cpp create mode 100644 xbmc/GUIDialogPVRCutterOSD.h create mode 100644 xbmc/GUIDialogPVRDirectorOSD.cpp create mode 100644 xbmc/GUIDialogPVRDirectorOSD.h create mode 100644 xbmc/GUIDialogPVRGroupManager.cpp create mode 100644 xbmc/GUIDialogPVRGroupManager.h create mode 100644 xbmc/GUIDialogPVRGuideInfo.cpp create mode 100644 xbmc/GUIDialogPVRGuideInfo.h create mode 100644 xbmc/GUIDialogPVRGuideOSD.cpp create mode 100644 xbmc/GUIDialogPVRGuideOSD.h create mode 100644 xbmc/GUIDialogPVRGuideSearch.cpp create mode 100644 xbmc/GUIDialogPVRGuideSearch.h create mode 100644 xbmc/GUIDialogPVRRecordingInfo.cpp create mode 100644 xbmc/GUIDialogPVRRecordingInfo.h create mode 100644 xbmc/GUIDialogPVRTimerSettings.cpp create mode 100644 xbmc/GUIDialogPVRTimerSettings.h create mode 100644 xbmc/GUIDialogPVRUpdateProgressBar.cpp create mode 100644 xbmc/GUIDialogPVRUpdateProgressBar.h create mode 100644 xbmc/GUIViewStateTV.cpp create mode 100644 xbmc/GUIViewStateTV.h create mode 100644 xbmc/GUIWindowTV.cpp create mode 100644 xbmc/GUIWindowTV.h create mode 100644 xbmc/PVRManager.cpp create mode 100644 xbmc/PVRManager.h create mode 100644 xbmc/TVDatabase.cpp create mode 100644 xbmc/TVDatabase.h create mode 100644 xbmc/addons/AddonHelpers_Addon.cpp create mode 100644 xbmc/addons/AddonHelpers_Addon.h create mode 100644 xbmc/addons/AddonHelpers_GUI.cpp create mode 100644 xbmc/addons/AddonHelpers_GUI.h create mode 100644 xbmc/addons/AddonHelpers_PVR.cpp create mode 100644 xbmc/addons/AddonHelpers_PVR.h create mode 100644 xbmc/addons/AddonHelpers_local.cpp create mode 100644 xbmc/addons/AddonHelpers_local.h create mode 100644 xbmc/addons/DllPVRClient.h create mode 100644 xbmc/addons/PVRClient.cpp create mode 100644 xbmc/addons/PVRClient.h create mode 100644 xbmc/addons/include/xbmc_pvr_dll.h create mode 100644 xbmc/addons/include/xbmc_pvr_types.h create mode 100644 xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxPVRClient.cpp create mode 100644 xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxPVRClient.h create mode 100644 xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxPacket.h create mode 100644 xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamPVRManager.cpp create mode 100644 xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamPVRManager.h create mode 100644 xbmc/pvrclients/MediaPortal/Makefile create mode 100644 xbmc/pvrclients/MediaPortal/README create mode 100644 xbmc/pvrclients/MediaPortal/Socket.cpp create mode 100644 xbmc/pvrclients/MediaPortal/Socket.h create mode 100644 xbmc/pvrclients/MediaPortal/StdString.h create mode 100644 xbmc/pvrclients/MediaPortal/channels.cpp create mode 100644 xbmc/pvrclients/MediaPortal/channels.h create mode 100644 xbmc/pvrclients/MediaPortal/client.cpp create mode 100644 xbmc/pvrclients/MediaPortal/client.h create mode 100644 xbmc/pvrclients/MediaPortal/epg.cpp create mode 100644 xbmc/pvrclients/MediaPortal/epg.h create mode 100644 xbmc/pvrclients/MediaPortal/linux/pvrclient-mediaportal_os_posix.h create mode 100644 xbmc/pvrclients/MediaPortal/project/VS2008Express/XBMC_MPTV.sln create mode 100644 xbmc/pvrclients/MediaPortal/project/VS2008Express/XBMC_MPTV.vcproj create mode 100644 xbmc/pvrclients/MediaPortal/pvrclient-mediaportal.cpp create mode 100644 xbmc/pvrclients/MediaPortal/pvrclient-mediaportal.h create mode 100644 xbmc/pvrclients/MediaPortal/pvrclient-mediaportal_os.h create mode 100644 xbmc/pvrclients/MediaPortal/recordings.cpp create mode 100644 xbmc/pvrclients/MediaPortal/recordings.h create mode 100644 xbmc/pvrclients/MediaPortal/timers.cpp create mode 100644 xbmc/pvrclients/MediaPortal/timers.h create mode 100644 xbmc/pvrclients/MediaPortal/utils.cpp create mode 100644 xbmc/pvrclients/MediaPortal/utils.h create mode 100644 xbmc/pvrclients/MediaPortal/windows/pvrclient-mediaportal_os_windows.h create mode 100644 xbmc/pvrclients/mythtv/Makefile.in create mode 100644 xbmc/pvrclients/mythtv/MythXml.cpp create mode 100644 xbmc/pvrclients/mythtv/MythXml.h create mode 100644 xbmc/pvrclients/mythtv/StdString.h create mode 100644 xbmc/pvrclients/mythtv/client.cpp create mode 100644 xbmc/pvrclients/mythtv/client.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/GetChannelListCommand.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/GetChannelListParameters.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/GetChannelListResult.cpp create mode 100644 xbmc/pvrclients/mythtv/libmythxml/GetChannelListResult.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsCommand.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsParameters.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsResult.cpp create mode 100644 xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsResult.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideCommand.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideParameters.cpp create mode 100644 xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideParameters.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideResult.cpp create mode 100644 xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideResult.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/Makefile create mode 100644 xbmc/pvrclients/mythtv/libmythxml/MythXmlCommand.cpp create mode 100644 xbmc/pvrclients/mythtv/libmythxml/MythXmlCommand.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/MythXmlCommandParameters.cpp create mode 100644 xbmc/pvrclients/mythtv/libmythxml/MythXmlCommandParameters.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/MythXmlCommandResult.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/SChannel.h create mode 100644 xbmc/pvrclients/mythtv/libmythxml/SEpg.h create mode 100644 xbmc/pvrclients/mythtv/linux/pvrclient-mythtv_os_posix.h create mode 100644 xbmc/pvrclients/mythtv/pvrclient-mythtv_os.h create mode 100644 xbmc/pvrclients/tvheadend/HTSPData.cpp create mode 100644 xbmc/pvrclients/tvheadend/HTSPData.h create mode 100644 xbmc/pvrclients/tvheadend/HTSPDemux.cpp create mode 100644 xbmc/pvrclients/tvheadend/HTSPDemux.h create mode 100644 xbmc/pvrclients/tvheadend/HTSPSession.cpp create mode 100644 xbmc/pvrclients/tvheadend/HTSPSession.h create mode 100644 xbmc/pvrclients/tvheadend/Makefile create mode 100644 xbmc/pvrclients/tvheadend/StdString.h create mode 100644 xbmc/pvrclients/tvheadend/client.cpp create mode 100644 xbmc/pvrclients/tvheadend/client.h create mode 100644 xbmc/pvrclients/tvheadend/linux/os_posix.h create mode 100644 xbmc/pvrclients/tvheadend/patches/hts-tvheadend-2.10-AddSignalQuality.diff create mode 100644 xbmc/pvrclients/tvheadend/patches/hts-tvheadend-2.11-01-add-CAID-to-channel-in-HTSP.patch create mode 100644 xbmc/pvrclients/tvheadend/project/VS2008Express/XBMC_hts.vcproj create mode 100644 xbmc/pvrclients/tvheadend/pthread_win32/pthread.h create mode 100644 xbmc/pvrclients/tvheadend/pthread_win32/pthreadVC2.lib create mode 100644 xbmc/pvrclients/tvheadend/pthread_win32/pthreadVC2d.lib create mode 100644 xbmc/pvrclients/tvheadend/pthread_win32/sched.h create mode 100644 xbmc/pvrclients/tvheadend/pthread_win32/semaphore.h create mode 100644 xbmc/pvrclients/tvheadend/pvrclient-tvheadend_os.h create mode 100644 xbmc/pvrclients/tvheadend/thread.cpp create mode 100644 xbmc/pvrclients/tvheadend/thread.h create mode 100644 xbmc/pvrclients/tvheadend/tools.cpp create mode 100644 xbmc/pvrclients/tvheadend/tools.h create mode 100644 xbmc/pvrclients/tvheadend/windows/dirent.cpp create mode 100644 xbmc/pvrclients/tvheadend/windows/dirent.h create mode 100644 xbmc/pvrclients/tvheadend/windows/os_windows.cpp create mode 100644 xbmc/pvrclients/tvheadend/windows/os_windows.h create mode 100644 xbmc/pvrclients/vdr-streamdev/Makefile.in create mode 100644 xbmc/pvrclients/vdr-streamdev/README create mode 100644 xbmc/pvrclients/vdr-streamdev/StdString.h create mode 100644 xbmc/pvrclients/vdr-streamdev/channels.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/channels.h create mode 100644 xbmc/pvrclients/vdr-streamdev/channelscan.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/channelscan.h create mode 100644 xbmc/pvrclients/vdr-streamdev/client.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/client.h create mode 100644 xbmc/pvrclients/vdr-streamdev/epg.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/epg.h create mode 100644 xbmc/pvrclients/vdr-streamdev/linux/pvrclient-vdr_os_posix.h create mode 100644 xbmc/pvrclients/vdr-streamdev/patches/streamdev-cvs100210-ReplaceRecordingStreaming.patch create mode 100644 xbmc/pvrclients/vdr-streamdev/patches/streamdev-cvs221109-AddCallbackMsg.diff create mode 100644 xbmc/pvrclients/vdr-streamdev/patches/streamdev-cvs221109-AddFemonV1.diff create mode 100644 xbmc/pvrclients/vdr-streamdev/patches/vdr-1.7.10_extensionsAndXBMC.diff create mode 100644 xbmc/pvrclients/vdr-streamdev/patches/vdr-1.7.7-ExtendetStatusMessage.diff create mode 100644 xbmc/pvrclients/vdr-streamdev/patches/vdr-1.7.7-GenreToFromEpgDat.diff create mode 100644 xbmc/pvrclients/vdr-streamdev/project/VS2008Express/XBMC_VDR.sln create mode 100644 xbmc/pvrclients/vdr-streamdev/project/VS2008Express/XBMC_VDR.vcproj create mode 100644 xbmc/pvrclients/vdr-streamdev/project/VS2008Express/inttypes.h create mode 100644 xbmc/pvrclients/vdr-streamdev/project/VS2008Express/stdint.h create mode 100644 xbmc/pvrclients/vdr-streamdev/pthread_win32/pthread.h create mode 100644 xbmc/pvrclients/vdr-streamdev/pthread_win32/pthreadVC2.lib create mode 100644 xbmc/pvrclients/vdr-streamdev/pthread_win32/pthreadVC2d.lib create mode 100644 xbmc/pvrclients/vdr-streamdev/pthread_win32/sched.h create mode 100644 xbmc/pvrclients/vdr-streamdev/pthread_win32/semaphore.h create mode 100644 xbmc/pvrclients/vdr-streamdev/pvrclient-vdr_os.h create mode 100644 xbmc/pvrclients/vdr-streamdev/recordings.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/recordings.h create mode 100644 xbmc/pvrclients/vdr-streamdev/ringbuffer.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/ringbuffer.h create mode 100644 xbmc/pvrclients/vdr-streamdev/select.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/select.h create mode 100644 xbmc/pvrclients/vdr-streamdev/thread.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/thread.h create mode 100644 xbmc/pvrclients/vdr-streamdev/timers.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/timers.h create mode 100644 xbmc/pvrclients/vdr-streamdev/tools.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/tools.h create mode 100644 xbmc/pvrclients/vdr-streamdev/vtptransceiver.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/vtptransceiver.h create mode 100644 xbmc/pvrclients/vdr-streamdev/windows/dirent.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/windows/dirent.h create mode 100644 xbmc/pvrclients/vdr-streamdev/windows/getline.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/windows/getline.h create mode 100644 xbmc/pvrclients/vdr-streamdev/windows/pvrclient-vdr_os_windows.cpp create mode 100644 xbmc/pvrclients/vdr-streamdev/windows/pvrclient-vdr_os_windows.h create mode 100644 xbmc/pvrclients/vdr-vnsi/COPYING create mode 100644 xbmc/pvrclients/vdr-vnsi/Makefile create mode 100644 xbmc/pvrclients/vdr-vnsi/README create mode 100644 xbmc/pvrclients/vdr-vnsi/StdString.h create mode 100644 xbmc/pvrclients/vdr-vnsi/VNSIChannelScan.cpp create mode 100644 xbmc/pvrclients/vdr-vnsi/VNSIChannelScan.h create mode 100644 xbmc/pvrclients/vdr-vnsi/VNSIData.cpp create mode 100644 xbmc/pvrclients/vdr-vnsi/VNSIData.h create mode 100644 xbmc/pvrclients/vdr-vnsi/VNSIDemux.cpp create mode 100644 xbmc/pvrclients/vdr-vnsi/VNSIDemux.h create mode 100644 xbmc/pvrclients/vdr-vnsi/VNSIRecording.cpp create mode 100644 xbmc/pvrclients/vdr-vnsi/VNSIRecording.h create mode 100644 xbmc/pvrclients/vdr-vnsi/VNSISession.cpp create mode 100644 xbmc/pvrclients/vdr-vnsi/VNSISession.h create mode 100644 xbmc/pvrclients/vdr-vnsi/client.cpp create mode 100644 xbmc/pvrclients/vdr-vnsi/client.h create mode 100644 xbmc/pvrclients/vdr-vnsi/linux/os_posix.h create mode 100644 xbmc/pvrclients/vdr-vnsi/pvrclient-vdrVNSI_os.h create mode 100644 xbmc/pvrclients/vdr-vnsi/recordings.cpp create mode 100644 xbmc/pvrclients/vdr-vnsi/recordings.h create mode 100644 xbmc/pvrclients/vdr-vnsi/requestpacket.cpp create mode 100644 xbmc/pvrclients/vdr-vnsi/requestpacket.h create mode 100644 xbmc/pvrclients/vdr-vnsi/responsepacket.cpp create mode 100644 xbmc/pvrclients/vdr-vnsi/responsepacket.h create mode 100644 xbmc/pvrclients/vdr-vnsi/thread.cpp create mode 100644 xbmc/pvrclients/vdr-vnsi/thread.h create mode 100644 xbmc/pvrclients/vdr-vnsi/tools.cpp create mode 100644 xbmc/pvrclients/vdr-vnsi/tools.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/COPYING create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/HISTORY create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/Makefile create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/README create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/bitstream.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/bitstream.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cmdcontrol.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cmdcontrol.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/config.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/config.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/connection.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/connection.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cxsocket.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cxsocket.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AAC.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AAC.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AC3.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AC3.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_DTS.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_DTS.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGAudio.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGAudio.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGVideo.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGVideo.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Subtitle.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Subtitle.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Teletext.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Teletext.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_h264.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_h264.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/global.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/patches/vdr-wirbelscan-0.0.5-pre11e-AddServiceInterface.diff create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/project/VNSI Server.cbp create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/project/VNSI Server.layout create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/receiver.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/receiver.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/recplayer.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/recplayer.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/requestpacket.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/requestpacket.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/responsepacket.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/responsepacket.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/server.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/server.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/suspend.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/suspend.dat create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/suspend.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/tools.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/tools.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vdrcommand.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vnsiserver.c create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vnsiserver/allowed_hosts.conf create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vnsiserver/noSignal.mpg create mode 100644 xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/wirbelscanservice.h create mode 100644 xbmc/pvrclients/vdr-vnsi/vdrcommand.h create mode 100644 xbmc/utils/PVRChannels.cpp create mode 100644 xbmc/utils/PVRChannels.h create mode 100644 xbmc/utils/PVREpg.cpp create mode 100644 xbmc/utils/PVREpg.h create mode 100644 xbmc/utils/PVRRecordings.cpp create mode 100644 xbmc/utils/PVRRecordings.h create mode 100644 xbmc/utils/PVRTimers.cpp create mode 100644 xbmc/utils/PVRTimers.h create mode 100644 xbmc/utils/TextSearch.cpp create mode 100644 xbmc/utils/TextSearch.h create mode 100644 xbmc/win32/stdbool.h diff --git a/.gitignore b/.gitignore index 1870274b60..7b28c7b407 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ config.log /addons/visualization.waveform/Waveform_win32.vis /addons/script.module.pil/ /addons/script.module.pysqlite/ +/addons/pvr.*/*.pvr # /guilib/ /guilib/Makefile @@ -106,6 +107,11 @@ config.log # /guilib/tinyXML/ +# /lib/addons/ +/lib/addons/library.xbmc.addon/Makefile +/lib/addons/library.xbmc.gui/Makefile +/lib/addons/library.xbmc.pvr/Makefile + # /lib/cpluff/ /lib/cpluff/ABOUT-NLS /lib/cpluff/aclocal.m4 @@ -1148,6 +1154,11 @@ config.log /xbmc/lib/libXDAAP/libXDAAP_win32/Debug /xbmc/lib/libXDAAP/libXDAAP_win32/Release +# /xbmc/pvrclients +/xbmc/pvrclients/*/.dependencies +/xbmc/pvrclients/mythtv/Makefile +/xbmc/pvrclients/vdr-streamdev/Makefile + /xbmc/screensavers/Makefile /xbmc/screensavers/rsxs-0.9/Makefile @@ -1204,3 +1215,4 @@ config.log /xbmc/visualizations/XBMCProjectM/libprojectM/config.inp /xbmc/win32/svn_rev.h + diff --git a/Makefile.in b/Makefile.in index 436156bd46..31bd370b0f 100644 --- a/Makefile.in +++ b/Makefile.in @@ -28,6 +28,7 @@ BIN_DIRS= \ xbmc/FileSystem/MusicDatabaseDirectory \ xbmc/FileSystem/VideoDatabaseDirectory \ xbmc/karaoke \ + xbmc/addons \ xbmc/lib/cmyth/libcmyth \ xbmc/lib/cmyth/librefmem \ xbmc/lib/libhts \ @@ -45,6 +46,7 @@ BIN_DIRS= \ xbmc/lib/libsquish \ xbmc/visualizations \ xbmc/screensavers \ + xbmc/pvrclients \ xbmc/utils \ xbmc/settings \ xbmc/linux \ @@ -118,6 +120,17 @@ ifneq (@BUILD_GOOM@,1) VIS_DIRS+=xbmc/visualizations/Goom endif +PVR_DIRS=\ + xbmc/pvrclients/MediaPortal \ + xbmc/pvrclients/mythtv \ + xbmc/pvrclients/vdr-streamdev \ + xbmc/pvrclients/vdr-vnsi \ + xbmc/pvrclients/tvheadend + +LIBADDON_DIRS=\ + lib/addons/library.xbmc.addon \ + lib/addons/library.xbmc.pvr \ + lib/addons/library.xbmc.gui \ CONFLUENCE_MEDIA=addons/skin.confluence/media @@ -128,7 +141,8 @@ LIVE_DIRS=\ tools/XBMCLive DIRS= $(BIN_DIRS) $(EC_DIRS) $(XBMCTEX_DIRS) $(DVDPCODECS_DIRS) $(PAPCODECS_DIRS) \ - $(LIB_DIRS) $(SS_DIRS) $(VIS_DIRS) $(SKIN_DIRS) $(LIVE_DIRS) + $(LIB_DIRS) $(SS_DIRS) $(VIS_DIRS) $(PVR_DIRS) $(LIBADDON_DIRS) $(SKIN_DIRS) \ + $(LIVE_DIRS) LIBS=@LIBS@ CFLAGS=@CFLAGS@ @@ -149,8 +163,8 @@ all : Makefile externals xbmc.bin xbmc-xrandr skins include Makefile.include -.PHONY : dllloader exports visualizations screensavers eventclients papcodecs \ - dvdpcodecs imagelib codecs externals force skins +.PHONY : dllloader exports pvrclients visualizations screensavers eventclients \ + papcodecs dvdpcodecs imagelib codecs externals force libaddon skins # hack targets to keep build system up to date Makefile : config.status $(addsuffix .in, $(AUTOGENERATED_MAKEFILES)) @@ -269,6 +283,8 @@ xbmc/settings/settings.a: force $(MAKE) -C xbmc/settings xbmc/utils/utils.a: force $(MAKE) -C xbmc/utils +xbmc/pvrclients/pvrclient.a: force + $(MAKE) -C xbmc/pvrclients xbmc/osx/osx.a: force $(MAKE) -C xbmc/osx xbmc/lib/libapetag/.libs/libapetag.a: force @@ -318,10 +334,20 @@ ifeq ($(or $(findstring powerpc-linux,$(ARCH)),$(findstring powerpc64-linux,$(AR endif endif endif +pvrclients: exports + $(MAKE) -C xbmc/pvrclients/MediaPortal + $(MAKE) -C xbmc/pvrclients/mythtv + $(MAKE) -C xbmc/pvrclients/vdr-streamdev + $(MAKE) -C xbmc/pvrclients/vdr-vnsi + $(MAKE) -C xbmc/pvrclients/tvheadend screensavers: exports ifneq (arm, $(ARCH)) $(MAKE) -C xbmc/screensavers/rsxs-0.9/xbmc endif +libaddon: exports + $(MAKE) -C lib/addons/library.xbmc.addon + $(MAKE) -C lib/addons/library.xbmc.gui + $(MAKE) -C lib/addons/library.xbmc.pvr libpython: dllloader $(MAKE) -C xbmc/lib/libPython $(MAKE) -C xbmc/lib/libPython/xbmcmodule @@ -375,10 +401,10 @@ libs: libhdhomerun libid3tag imagelib libexif python system/libcpluff-$(ARCH).so else libs: libhdhomerun libid3tag imagelib libexif python system/libcpluff-$(ARCH).so endif -externals: codecs libs python visualizations screensavers +externals: libaddon codecs libs python pvrclients visualizations screensavers xcode_depends: \ - codecs libs python visualizations screensavers eventclients skins \ + codecs libs python pvrclients visualizations screensavers eventclients skins \ xbmc/lib/libsquish/libsquish-@ARCH@.a \ xbmc/lib/libapetag/.libs/libapetag.a \ xbmc/lib/cmyth/libcmyth/libcmyth.a \ @@ -518,7 +544,7 @@ endif install-arch: @# Arch dependent files - @find system addons -regextype posix-extended -type f -not -iregex ".*svn.*|.*script\.module\..*" -iregex ".*$(ARCH).*|.*\.vis|.*\.xbs|.*python.*\.zip" -exec install -D "{}" $(DESTDIR)$(libdir)/xbmc/"{}" \; -printf " -- %-75.75f\r" + @find system addons -regextype posix-extended -type f -not -iregex ".*svn.*|.*script\.module\..*" -iregex ".*$(ARCH).*|.*\.pvr|.*\.vis|.*\.xbs|.*\.so|.*python.*\.zip" -exec install -D "{}" $(DESTDIR)$(libdir)/xbmc/"{}" \; -printf " -- %-75.75f\r" @cp -r addons/script.module.pil $(DESTDIR)$(libdir)/xbmc/addons/ @cp -r addons/script.module.pysqlite $(DESTDIR)$(libdir)/xbmc/addons/ @@ -535,7 +561,7 @@ install-datas: install-scripts @echo "Done!" @echo "Copying system files to $(DESTDIR)$(datarootdir)/xbmc" @# Arch independent files - @find addons language media sounds userdata system -regextype posix-extended -type f -not -iregex ".*script\.module\..*|.*$(ARCH).*|.*\.vis|.*\.xbs|.*svn.*|.*\.so|.*\.dll|.*\.pyd|.*python.*\.zip" -exec install -D -m 0644 "{}" $(DESTDIR)$(datarootdir)/xbmc/"{}" \; -printf " -- %-75.75f\r" + @find addons language media sounds userdata system -regextype posix-extended -type f -not -iregex ".*script\.module\..*|.*$(ARCH).*|.*\.pvr|.*\.vis|.*\.xbs|.*svn.*|.*\.so|.*\.dll|.*\.pyd|.*python.*\.zip" -exec install -D -m 0644 "{}" $(DESTDIR)$(datarootdir)/xbmc/"{}" \; -printf " -- %-75.75f\r" @# Icons and links @mkdir -p $(DESTDIR)$(datarootdir)/applications @cp -a tools/Linux/xbmc.desktop $(DESTDIR)$(datarootdir)/applications/ @@ -557,6 +583,8 @@ uninstall: @rm -rf $(DESTDIR)$(datarootdir)/xbmc $(DESTDIR)$(bindir)/xbmc @rm -rf $(DESTDIR)$(bindir)/xbmc-standalone @rm -rf $(DESTDIR)$(datarootdir)/xsessions/XBMC.desktop + @rm -rf $(libdir)/libXBMC_* + @rm -rf $(prefix)/include/xbmc @echo "Done!" reallyclean: @@ -603,10 +631,12 @@ clean-screensavers: for d in $(SS_DIRS); do if test -f $$d/Makefile; then $(MAKE) -C $$d clean; fi; done clean-visualisations: for d in $(VIS_DIRS); do if test -f $$d/Makefile; then $(MAKE) -C $$d clean; fi; done +clean-pvrclients: + for d in $(PVR_DIRS); do if test -f $$d/Makefile; then $(MAKE) -C $$d clean; fi; done +clean-libaddons: + for d in $(LIBADDON_DIRS); do if test -f $$d/Makefile; then $(MAKE) -C $$d clean; fi; done clean-codecs: clean-dvdpcodecs clean-papcodecs clean-externals: clean-codecs clean-eventclients clean-xbmctex clean-libs \ - clean-screensavers clean-visualisations - - + clean-pvrclients clean-screensavers clean-visualisations clean-libaddons diff --git a/XBMC.xcodeproj/project.pbxproj b/XBMC.xcodeproj/project.pbxproj index bf60b5c221..129e726d93 100644 --- a/XBMC.xcodeproj/project.pbxproj +++ b/XBMC.xcodeproj/project.pbxproj @@ -270,6 +270,36 @@ 810C9FA90D67D1FB0095F5DD /* MythDirectory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 810C9FA50D67D1FB0095F5DD /* MythDirectory.cpp */; }; 810C9FAA0D67D1FB0095F5DD /* MythFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 810C9FA70D67D1FB0095F5DD /* MythFile.cpp */; }; 815EE6350E17F1DC009FBE3C /* DVDInputStreamRTMP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 815EE6330E17F1DC009FBE3C /* DVDInputStreamRTMP.cpp */; }; + 832F2C46120D8B5E00026B38 /* PVRManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C44120D8B5E00026B38 /* PVRManager.cpp */; }; + 832F2C4F120D8B7F00026B38 /* PVRChannels.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C47120D8B7F00026B38 /* PVRChannels.cpp */; }; + 832F2C50120D8B7F00026B38 /* PVREpg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C49120D8B7F00026B38 /* PVREpg.cpp */; }; + 832F2C51120D8B7F00026B38 /* PVRRecordings.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C4B120D8B7F00026B38 /* PVRRecordings.cpp */; }; + 832F2C52120D8B7F00026B38 /* PVRTimers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C4D120D8B7F00026B38 /* PVRTimers.cpp */; }; + 832F2C69120D8BA900026B38 /* GUIDialogPVRChannelManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C53120D8BA800026B38 /* GUIDialogPVRChannelManager.cpp */; }; + 832F2C6A120D8BA900026B38 /* GUIDialogPVRChannelsOSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C55120D8BA800026B38 /* GUIDialogPVRChannelsOSD.cpp */; }; + 832F2C6B120D8BA900026B38 /* GUIDialogPVRCutterOSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C57120D8BA800026B38 /* GUIDialogPVRCutterOSD.cpp */; }; + 832F2C6C120D8BA900026B38 /* GUIDialogPVRDirectorOSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C59120D8BA800026B38 /* GUIDialogPVRDirectorOSD.cpp */; }; + 832F2C6D120D8BA900026B38 /* GUIDialogPVRGroupManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C5B120D8BA800026B38 /* GUIDialogPVRGroupManager.cpp */; }; + 832F2C6E120D8BA900026B38 /* GUIDialogPVRGuideInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C5D120D8BA900026B38 /* GUIDialogPVRGuideInfo.cpp */; }; + 832F2C6F120D8BA900026B38 /* GUIDialogPVRGuideOSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C5F120D8BA900026B38 /* GUIDialogPVRGuideOSD.cpp */; }; + 832F2C70120D8BA900026B38 /* GUIDialogPVRGuideSearch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C61120D8BA900026B38 /* GUIDialogPVRGuideSearch.cpp */; }; + 832F2C71120D8BA900026B38 /* GUIDialogPVRRecordingInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C63120D8BA900026B38 /* GUIDialogPVRRecordingInfo.cpp */; }; + 832F2C72120D8BA900026B38 /* GUIDialogPVRTimerSettings.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C65120D8BA900026B38 /* GUIDialogPVRTimerSettings.cpp */; }; + 832F2C73120D8BA900026B38 /* GUIDialogPVRUpdateProgressBar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C67120D8BA900026B38 /* GUIDialogPVRUpdateProgressBar.cpp */; }; + 832F2C7E120D8C0300026B38 /* AddonHelpers_Addon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C76120D8C0300026B38 /* AddonHelpers_Addon.cpp */; }; + 832F2C7F120D8C0300026B38 /* AddonHelpers_GUI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C78120D8C0300026B38 /* AddonHelpers_GUI.cpp */; }; + 832F2C80120D8C0300026B38 /* AddonHelpers_local.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C7A120D8C0300026B38 /* AddonHelpers_local.cpp */; }; + 832F2C81120D8C0300026B38 /* AddonHelpers_PVR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C7C120D8C0300026B38 /* AddonHelpers_PVR.cpp */; }; + 832F2C84120D8C1000026B38 /* PVRClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C82120D8C0F00026B38 /* PVRClient.cpp */; }; + 832F2C87120D8C5300026B38 /* TVDatabase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C85120D8C5300026B38 /* TVDatabase.cpp */; }; + 832F2C8A120D8C9300026B38 /* GUIEPGGridContainer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C88120D8C9300026B38 /* GUIEPGGridContainer.cpp */; }; + 832F2C8F120D8CB500026B38 /* PVRDirectory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C8B120D8CB400026B38 /* PVRDirectory.cpp */; }; + 832F2C90120D8CB500026B38 /* PVRFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C8D120D8CB500026B38 /* PVRFile.cpp */; }; + 832F2C95120D8CF600026B38 /* TextSearch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C93120D8CF600026B38 /* TextSearch.cpp */; }; + 832F2C98120D8D0B00026B38 /* DVDDemuxPVRClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C96120D8D0B00026B38 /* DVDDemuxPVRClient.cpp */; }; + 832F2C9B120D8D3000026B38 /* GUIWindowTV.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C99120D8D3000026B38 /* GUIWindowTV.cpp */; }; + 832F2CA0120D8D5C00026B38 /* DVDInputStreamPVRManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2C9E120D8D5C00026B38 /* DVDInputStreamPVRManager.cpp */; }; + 832F2CA3120D8D7600026B38 /* GUIViewStateTV.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 832F2CA1120D8D7600026B38 /* GUIViewStateTV.cpp */; }; 83A72B910FBC8DB000171871 /* CoreAudioRenderer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83A72B8E0FBC8DB000171871 /* CoreAudioRenderer.cpp */; }; 83A72B940FBC8DFF00171871 /* CoreAudio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83A72B920FBC8DFF00171871 /* CoreAudio.cpp */; }; 83A72B970FBC8E3B00171871 /* LockFree.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83A72B950FBC8E3B00171871 /* LockFree.cpp */; settings = {COMPILER_FLAGS = "-O0"; }; }; @@ -2149,6 +2179,70 @@ 810CA0080D683DEF0095F5DD /* libSDL_mixer-x86-osx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libSDL_mixer-x86-osx.a"; path = "lib/libSDL-OSX/libSDL_mixer-x86-osx.a"; sourceTree = ""; }; 815EE6330E17F1DC009FBE3C /* DVDInputStreamRTMP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DVDInputStreamRTMP.cpp; sourceTree = ""; }; 815EE6340E17F1DC009FBE3C /* DVDInputStreamRTMP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DVDInputStreamRTMP.h; sourceTree = ""; }; + 832F2C44120D8B5E00026B38 /* PVRManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PVRManager.cpp; path = xbmc/PVRManager.cpp; sourceTree = SOURCE_ROOT; }; + 832F2C45120D8B5E00026B38 /* PVRManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PVRManager.h; path = xbmc/PVRManager.h; sourceTree = SOURCE_ROOT; }; + 832F2C47120D8B7F00026B38 /* PVRChannels.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PVRChannels.cpp; path = xbmc/utils/PVRChannels.cpp; sourceTree = ""; }; + 832F2C48120D8B7F00026B38 /* PVRChannels.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PVRChannels.h; path = xbmc/utils/PVRChannels.h; sourceTree = ""; }; + 832F2C49120D8B7F00026B38 /* PVREpg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PVREpg.cpp; path = xbmc/utils/PVREpg.cpp; sourceTree = ""; }; + 832F2C4A120D8B7F00026B38 /* PVREpg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PVREpg.h; path = xbmc/utils/PVREpg.h; sourceTree = ""; }; + 832F2C4B120D8B7F00026B38 /* PVRRecordings.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PVRRecordings.cpp; path = xbmc/utils/PVRRecordings.cpp; sourceTree = ""; }; + 832F2C4C120D8B7F00026B38 /* PVRRecordings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PVRRecordings.h; path = xbmc/utils/PVRRecordings.h; sourceTree = ""; }; + 832F2C4D120D8B7F00026B38 /* PVRTimers.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PVRTimers.cpp; path = xbmc/utils/PVRTimers.cpp; sourceTree = ""; }; + 832F2C4E120D8B7F00026B38 /* PVRTimers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PVRTimers.h; path = xbmc/utils/PVRTimers.h; sourceTree = ""; }; + 832F2C53120D8BA800026B38 /* GUIDialogPVRChannelManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogPVRChannelManager.cpp; sourceTree = ""; }; + 832F2C54120D8BA800026B38 /* GUIDialogPVRChannelManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogPVRChannelManager.h; sourceTree = ""; }; + 832F2C55120D8BA800026B38 /* GUIDialogPVRChannelsOSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogPVRChannelsOSD.cpp; sourceTree = ""; }; + 832F2C56120D8BA800026B38 /* GUIDialogPVRChannelsOSD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogPVRChannelsOSD.h; sourceTree = ""; }; + 832F2C57120D8BA800026B38 /* GUIDialogPVRCutterOSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogPVRCutterOSD.cpp; sourceTree = ""; }; + 832F2C58120D8BA800026B38 /* GUIDialogPVRCutterOSD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogPVRCutterOSD.h; sourceTree = ""; }; + 832F2C59120D8BA800026B38 /* GUIDialogPVRDirectorOSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogPVRDirectorOSD.cpp; sourceTree = ""; }; + 832F2C5A120D8BA800026B38 /* GUIDialogPVRDirectorOSD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogPVRDirectorOSD.h; sourceTree = ""; }; + 832F2C5B120D8BA800026B38 /* GUIDialogPVRGroupManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogPVRGroupManager.cpp; sourceTree = ""; }; + 832F2C5C120D8BA900026B38 /* GUIDialogPVRGroupManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogPVRGroupManager.h; sourceTree = ""; }; + 832F2C5D120D8BA900026B38 /* GUIDialogPVRGuideInfo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogPVRGuideInfo.cpp; sourceTree = ""; }; + 832F2C5E120D8BA900026B38 /* GUIDialogPVRGuideInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogPVRGuideInfo.h; sourceTree = ""; }; + 832F2C5F120D8BA900026B38 /* GUIDialogPVRGuideOSD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogPVRGuideOSD.cpp; sourceTree = ""; }; + 832F2C60120D8BA900026B38 /* GUIDialogPVRGuideOSD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogPVRGuideOSD.h; sourceTree = ""; }; + 832F2C61120D8BA900026B38 /* GUIDialogPVRGuideSearch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogPVRGuideSearch.cpp; sourceTree = ""; }; + 832F2C62120D8BA900026B38 /* GUIDialogPVRGuideSearch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogPVRGuideSearch.h; sourceTree = ""; }; + 832F2C63120D8BA900026B38 /* GUIDialogPVRRecordingInfo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogPVRRecordingInfo.cpp; sourceTree = ""; }; + 832F2C64120D8BA900026B38 /* GUIDialogPVRRecordingInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogPVRRecordingInfo.h; sourceTree = ""; }; + 832F2C65120D8BA900026B38 /* GUIDialogPVRTimerSettings.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogPVRTimerSettings.cpp; sourceTree = ""; }; + 832F2C66120D8BA900026B38 /* GUIDialogPVRTimerSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogPVRTimerSettings.h; sourceTree = ""; }; + 832F2C67120D8BA900026B38 /* GUIDialogPVRUpdateProgressBar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogPVRUpdateProgressBar.cpp; sourceTree = ""; }; + 832F2C68120D8BA900026B38 /* GUIDialogPVRUpdateProgressBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogPVRUpdateProgressBar.h; sourceTree = ""; }; + 832F2C76120D8C0300026B38 /* AddonHelpers_Addon.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AddonHelpers_Addon.cpp; path = addons/AddonHelpers_Addon.cpp; sourceTree = ""; }; + 832F2C77120D8C0300026B38 /* AddonHelpers_Addon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AddonHelpers_Addon.h; path = addons/AddonHelpers_Addon.h; sourceTree = ""; }; + 832F2C78120D8C0300026B38 /* AddonHelpers_GUI.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AddonHelpers_GUI.cpp; path = addons/AddonHelpers_GUI.cpp; sourceTree = ""; }; + 832F2C79120D8C0300026B38 /* AddonHelpers_GUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AddonHelpers_GUI.h; path = addons/AddonHelpers_GUI.h; sourceTree = ""; }; + 832F2C7A120D8C0300026B38 /* AddonHelpers_local.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AddonHelpers_local.cpp; path = addons/AddonHelpers_local.cpp; sourceTree = ""; }; + 832F2C7B120D8C0300026B38 /* AddonHelpers_local.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AddonHelpers_local.h; path = addons/AddonHelpers_local.h; sourceTree = ""; }; + 832F2C7C120D8C0300026B38 /* AddonHelpers_PVR.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AddonHelpers_PVR.cpp; path = addons/AddonHelpers_PVR.cpp; sourceTree = ""; }; + 832F2C7D120D8C0300026B38 /* AddonHelpers_PVR.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AddonHelpers_PVR.h; path = addons/AddonHelpers_PVR.h; sourceTree = ""; }; + 832F2C82120D8C0F00026B38 /* PVRClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PVRClient.cpp; path = addons/PVRClient.cpp; sourceTree = ""; }; + 832F2C83120D8C1000026B38 /* PVRClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PVRClient.h; path = addons/PVRClient.h; sourceTree = ""; }; + 832F2C85120D8C5300026B38 /* TVDatabase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TVDatabase.cpp; path = xbmc/TVDatabase.cpp; sourceTree = SOURCE_ROOT; }; + 832F2C86120D8C5300026B38 /* TVDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TVDatabase.h; path = xbmc/TVDatabase.h; sourceTree = SOURCE_ROOT; }; + 832F2C88120D8C9300026B38 /* GUIEPGGridContainer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIEPGGridContainer.cpp; sourceTree = ""; }; + 832F2C89120D8C9300026B38 /* GUIEPGGridContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIEPGGridContainer.h; sourceTree = ""; }; + 832F2C8B120D8CB400026B38 /* PVRDirectory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PVRDirectory.cpp; path = xbmc/FileSystem/PVRDirectory.cpp; sourceTree = ""; }; + 832F2C8C120D8CB500026B38 /* PVRDirectory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PVRDirectory.h; path = xbmc/FileSystem/PVRDirectory.h; sourceTree = ""; }; + 832F2C8D120D8CB500026B38 /* PVRFile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PVRFile.cpp; path = xbmc/FileSystem/PVRFile.cpp; sourceTree = ""; }; + 832F2C8E120D8CB500026B38 /* PVRFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PVRFile.h; path = xbmc/FileSystem/PVRFile.h; sourceTree = ""; }; + 832F2C93120D8CF600026B38 /* TextSearch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TextSearch.cpp; path = xbmc/utils/TextSearch.cpp; sourceTree = SOURCE_ROOT; }; + 832F2C94120D8CF600026B38 /* TextSearch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextSearch.h; path = xbmc/utils/TextSearch.h; sourceTree = SOURCE_ROOT; }; + 832F2C96120D8D0B00026B38 /* DVDDemuxPVRClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DVDDemuxPVRClient.cpp; sourceTree = ""; }; + 832F2C97120D8D0B00026B38 /* DVDDemuxPVRClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DVDDemuxPVRClient.h; sourceTree = ""; }; + 832F2C99120D8D3000026B38 /* GUIWindowTV.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIWindowTV.cpp; sourceTree = ""; }; + 832F2C9A120D8D3000026B38 /* GUIWindowTV.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIWindowTV.h; sourceTree = ""; }; + 832F2C9E120D8D5C00026B38 /* DVDInputStreamPVRManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DVDInputStreamPVRManager.cpp; sourceTree = ""; }; + 832F2C9F120D8D5C00026B38 /* DVDInputStreamPVRManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DVDInputStreamPVRManager.h; sourceTree = ""; }; + 832F2CA1120D8D7600026B38 /* GUIViewStateTV.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIViewStateTV.cpp; sourceTree = ""; }; + 832F2CA2120D8D7600026B38 /* GUIViewStateTV.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIViewStateTV.h; sourceTree = ""; }; + 83840880116E243C009B5115 /* Addon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Addon.h; path = xbmc/addons/Addon.h; sourceTree = SOURCE_ROOT; }; + 83840881116E243C009B5115 /* AddonDll.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AddonDll.h; path = xbmc/addons/AddonDll.h; sourceTree = SOURCE_ROOT; }; + 8398EE72116F1297001227BB /* DllAddon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DllAddon.h; path = xbmc/addons/DllAddon.h; sourceTree = SOURCE_ROOT; }; + 8398EE73116F1297001227BB /* DllPVRClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DllPVRClient.h; path = xbmc/addons/DllPVRClient.h; sourceTree = SOURCE_ROOT; }; 83A72B8E0FBC8DB000171871 /* CoreAudioRenderer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CoreAudioRenderer.cpp; path = AudioRenderers/CoreAudioRenderer.cpp; sourceTree = ""; }; 83A72B8F0FBC8DB000171871 /* CoreAudioRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CoreAudioRenderer.h; path = AudioRenderers/CoreAudioRenderer.h; sourceTree = ""; }; 83A72B900FBC8DB000171871 /* IAudioRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IAudioRenderer.h; path = AudioRenderers/IAudioRenderer.h; sourceTree = ""; }; @@ -3992,6 +4086,18 @@ E38E238E0D2626E600618676 /* CoreAudio.framework */, E38E238F0D2626E600618676 /* CoreServices.framework */, F59879070FBAA0C3008EF4FB /* QuartzCore.framework */, + 832F2C47120D8B7F00026B38 /* PVRChannels.cpp */, + 832F2C48120D8B7F00026B38 /* PVRChannels.h */, + 832F2C8B120D8CB400026B38 /* PVRDirectory.cpp */, + 832F2C8C120D8CB500026B38 /* PVRDirectory.h */, + 832F2C8D120D8CB500026B38 /* PVRFile.cpp */, + 832F2C8E120D8CB500026B38 /* PVRFile.h */, + 832F2C49120D8B7F00026B38 /* PVREpg.cpp */, + 832F2C4A120D8B7F00026B38 /* PVREpg.h */, + 832F2C4B120D8B7F00026B38 /* PVRRecordings.cpp */, + 832F2C4C120D8B7F00026B38 /* PVRRecordings.h */, + 832F2C4D120D8B7F00026B38 /* PVRTimers.cpp */, + 832F2C4E120D8B7F00026B38 /* PVRTimers.h */, E38E23900D2626E600618676 /* Foundation.framework */, E38E23910D2626E600618676 /* OpenGL.framework */, 09AB6884FE841BABC02AAC07 /* CoreFoundation.framework */, @@ -4083,6 +4189,8 @@ 7CD9742711BC6EAA00649E31 /* GUITextureGLES.cpp */, 7CD9742811BC6EAA00649E31 /* GUITextureGLES.h */, 7C779DA0104A4B2E00F444C4 /* Texture.cpp */, + 832F2C93120D8CF600026B38 /* TextSearch.cpp */, + 832F2C94120D8CF600026B38 /* TextSearch.h */, 7C779DA1104A4B2E00F444C4 /* Texture.h */, 7C779DA2104A4B2E00F444C4 /* TextureDX.cpp */, 7C779DA3104A4B2E00F444C4 /* TextureDX.h */, @@ -4160,6 +4268,8 @@ E38E13E10D25F9F900618676 /* GUIEditControl.h */, E38E13E20D25F9F900618676 /* GUIFadeLabelControl.cpp */, E38E13E30D25F9F900618676 /* GUIFadeLabelControl.h */, + 832F2C88120D8C9300026B38 /* GUIEPGGridContainer.cpp */, + 832F2C89120D8C9300026B38 /* GUIEPGGridContainer.h */, 7C779D9B104A4B2E00F444C4 /* GUIImage.cpp */, 7C779D9C104A4B2E00F444C4 /* GUIImage.h */, E38E13F20D25F9F900618676 /* GUILabelControl.cpp */, @@ -4605,6 +4715,28 @@ F5E55B65107412DE006E788A /* GUIDialogTeletext.cpp */, F5E55B64107412DE006E788A /* GUIDialogTeletext.h */, E38E17E00D25F9FA00618676 /* GUIDialogVideoBookmarks.cpp */, + 832F2C53120D8BA800026B38 /* GUIDialogPVRChannelManager.cpp */, + 832F2C54120D8BA800026B38 /* GUIDialogPVRChannelManager.h */, + 832F2C55120D8BA800026B38 /* GUIDialogPVRChannelsOSD.cpp */, + 832F2C56120D8BA800026B38 /* GUIDialogPVRChannelsOSD.h */, + 832F2C57120D8BA800026B38 /* GUIDialogPVRCutterOSD.cpp */, + 832F2C58120D8BA800026B38 /* GUIDialogPVRCutterOSD.h */, + 832F2C59120D8BA800026B38 /* GUIDialogPVRDirectorOSD.cpp */, + 832F2C5A120D8BA800026B38 /* GUIDialogPVRDirectorOSD.h */, + 832F2C5B120D8BA800026B38 /* GUIDialogPVRGroupManager.cpp */, + 832F2C5C120D8BA900026B38 /* GUIDialogPVRGroupManager.h */, + 832F2C5D120D8BA900026B38 /* GUIDialogPVRGuideInfo.cpp */, + 832F2C5E120D8BA900026B38 /* GUIDialogPVRGuideInfo.h */, + 832F2C5F120D8BA900026B38 /* GUIDialogPVRGuideOSD.cpp */, + 832F2C60120D8BA900026B38 /* GUIDialogPVRGuideOSD.h */, + 832F2C61120D8BA900026B38 /* GUIDialogPVRGuideSearch.cpp */, + 832F2C62120D8BA900026B38 /* GUIDialogPVRGuideSearch.h */, + 832F2C63120D8BA900026B38 /* GUIDialogPVRRecordingInfo.cpp */, + 832F2C64120D8BA900026B38 /* GUIDialogPVRRecordingInfo.h */, + 832F2C65120D8BA900026B38 /* GUIDialogPVRTimerSettings.cpp */, + 832F2C66120D8BA900026B38 /* GUIDialogPVRTimerSettings.h */, + 832F2C67120D8BA900026B38 /* GUIDialogPVRUpdateProgressBar.cpp */, + 832F2C68120D8BA900026B38 /* GUIDialogPVRUpdateProgressBar.h */, E38E17E10D25F9FA00618676 /* GUIDialogVideoBookmarks.h */, E38E17E20D25F9FA00618676 /* GUIDialogVideoScan.cpp */, E38E17E30D25F9FA00618676 /* GUIDialogVideoScan.h */, @@ -4632,6 +4764,8 @@ E38E17FB0D25F9FA00618676 /* GUIViewStateMusic.cpp */, E38E17FC0D25F9FA00618676 /* GUIViewStateMusic.h */, E38E17FF0D25F9FA00618676 /* GUIViewStateVideo.cpp */, + 832F2CA1120D8D7600026B38 /* GUIViewStateTV.cpp */, + 832F2CA2120D8D7600026B38 /* GUIViewStateTV.h */, E38E18000D25F9FA00618676 /* GUIViewStateVideo.h */, F5A7B433113CBC6A0059D6AA /* GUIWindowAddonBrowser.cpp */, F5A7B432113CBC6A0059D6AA /* GUIWindowAddonBrowser.h */, @@ -4683,6 +4817,8 @@ E38E183A0D25F9FA00618676 /* GUIWindowSystemInfo.h */, F5F95D9F0E4E203700C3FA5C /* GUIWindowTestPattern.cpp */, F5F95D9E0E4E203700C3FA5C /* GUIWindowTestPattern.h */, + 832F2C99120D8D3000026B38 /* GUIWindowTV.cpp */, + 832F2C9A120D8D3000026B38 /* GUIWindowTV.h */, 7C779E50104A58F900F444C4 /* GUIWindowTestPatternGL.cpp */, 7C779E51104A58F900F444C4 /* GUIWindowTestPatternGL.h */, F50FDC59119B4B2C00C8B8CD /* GUIDialogTextViewer.cpp */, @@ -5204,6 +5340,8 @@ E38E15490D25F9F900618676 /* DVDDemux.cpp */, E38E154A0D25F9F900618676 /* DVDDemux.h */, E38E154D0D25F9F900618676 /* DVDDemuxShoutcast.cpp */, + 832F2C96120D8D0B00026B38 /* DVDDemuxPVRClient.cpp */, + 832F2C97120D8D0B00026B38 /* DVDDemuxPVRClient.h */, E38E154E0D25F9F900618676 /* DVDDemuxShoutcast.h */, E38E154F0D25F9F900618676 /* DVDDemuxUtils.cpp */, E38E15500D25F9F900618676 /* DVDDemuxUtils.h */, @@ -5238,6 +5376,8 @@ 815EE6330E17F1DC009FBE3C /* DVDInputStreamRTMP.cpp */, 815EE6340E17F1DC009FBE3C /* DVDInputStreamRTMP.h */, E33979940D62FD47004ECDDA /* DVDInputStreamTV.cpp */, + 832F2C9E120D8D5C00026B38 /* DVDInputStreamPVRManager.cpp */, + 832F2C9F120D8D5C00026B38 /* DVDInputStreamPVRManager.h */, E33979950D62FD47004ECDDA /* DVDInputStreamTV.h */, E38E15670D25F9FA00618676 /* dvdnav */, E38E15740D25F9FA00618676 /* DVDStateSerializer.cpp */, @@ -5320,6 +5460,8 @@ E38E15E90D25F9FA00618676 /* CodecFactory.h */, E38E15EE0D25F9FA00618676 /* DllAc3codec.h */, E38E15EF0D25F9FA00618676 /* DllAdpcm.h */, + 8398EE72116F1297001227BB /* DllAddon.h */, + 8398EE73116F1297001227BB /* DllPVRClient.h */, E38E15F20D25F9FA00618676 /* DllDCACodec.h */, E38E15F50D25F9FA00618676 /* DllLibFlac.h */, E38E15FA0D25F9FA00618676 /* DllNosefart.h */, @@ -5447,6 +5589,8 @@ 889B4D8D0E0EF86C00FAD25E /* RSSDirectory.h */, F5A7B42A113CBB950059D6AA /* AddonsDirectory.h */, F5A7B42B113CBB950059D6AA /* AddonsDirectory.cpp */, + 83840880116E243C009B5115 /* Addon.h */, + 83840881116E243C009B5115 /* AddonDll.h */, 88ACB0190DCF40800083CFDF /* ASAPFileDirectory.cpp */, 88ACB01A0DCF40800083CFDF /* ASAPFileDirectory.h */, 880DBE530DC224A100E26B71 /* MusicFileDirectory.cpp */, @@ -5714,6 +5858,8 @@ E38E17840D25F9FA00618676 /* DirectoryNodeYear.cpp */, E38E17850D25F9FA00618676 /* DirectoryNodeYear.h */, E38E17880D25F9FA00618676 /* QueryParams.cpp */, + 832F2C44120D8B5E00026B38 /* PVRManager.cpp */, + 832F2C45120D8B5E00026B38 /* PVRManager.h */, E38E17890D25F9FA00618676 /* QueryParams.h */, ); path = VideoDatabaseDirectory; @@ -6753,6 +6899,8 @@ 7CCF7FC8106A0DF500992676 /* TimeUtils.h */, E38E1E890D25F9FD00618676 /* TuxBoxUtil.cpp */, E38E1E8A0D25F9FD00618676 /* TuxBoxUtil.h */, + 832F2C85120D8C5300026B38 /* TVDatabase.cpp */, + 832F2C86120D8C5300026B38 /* TVDatabase.h */, E38E1E8B0D25F9FD00618676 /* UdpClient.cpp */, E38E1E8C0D25F9FD00618676 /* UdpClient.h */, 7CF1FD67123E049300B2CBCB /* Variant.cpp */, @@ -8062,6 +8210,36 @@ 7CDFC2AE12021E9E00E182BC /* AutoPtrHandle.cpp in Sources */, F5E728D01227527D00B152C1 /* DVDInputStreamBluray.cpp in Sources */, 7CF1FD6A123E049300B2CBCB /* Variant.cpp in Sources */, + 832F2C46120D8B5E00026B38 /* PVRManager.cpp in Sources */, + 832F2C4F120D8B7F00026B38 /* PVRChannels.cpp in Sources */, + 832F2C50120D8B7F00026B38 /* PVREpg.cpp in Sources */, + 832F2C51120D8B7F00026B38 /* PVRRecordings.cpp in Sources */, + 832F2C52120D8B7F00026B38 /* PVRTimers.cpp in Sources */, + 832F2C69120D8BA900026B38 /* GUIDialogPVRChannelManager.cpp in Sources */, + 832F2C6A120D8BA900026B38 /* GUIDialogPVRChannelsOSD.cpp in Sources */, + 832F2C6B120D8BA900026B38 /* GUIDialogPVRCutterOSD.cpp in Sources */, + 832F2C6C120D8BA900026B38 /* GUIDialogPVRDirectorOSD.cpp in Sources */, + 832F2C6D120D8BA900026B38 /* GUIDialogPVRGroupManager.cpp in Sources */, + 832F2C6E120D8BA900026B38 /* GUIDialogPVRGuideInfo.cpp in Sources */, + 832F2C6F120D8BA900026B38 /* GUIDialogPVRGuideOSD.cpp in Sources */, + 832F2C70120D8BA900026B38 /* GUIDialogPVRGuideSearch.cpp in Sources */, + 832F2C71120D8BA900026B38 /* GUIDialogPVRRecordingInfo.cpp in Sources */, + 832F2C72120D8BA900026B38 /* GUIDialogPVRTimerSettings.cpp in Sources */, + 832F2C73120D8BA900026B38 /* GUIDialogPVRUpdateProgressBar.cpp in Sources */, + 832F2C7E120D8C0300026B38 /* AddonHelpers_Addon.cpp in Sources */, + 832F2C7F120D8C0300026B38 /* AddonHelpers_GUI.cpp in Sources */, + 832F2C80120D8C0300026B38 /* AddonHelpers_local.cpp in Sources */, + 832F2C81120D8C0300026B38 /* AddonHelpers_PVR.cpp in Sources */, + 832F2C84120D8C1000026B38 /* PVRClient.cpp in Sources */, + 832F2C87120D8C5300026B38 /* TVDatabase.cpp in Sources */, + 832F2C8A120D8C9300026B38 /* GUIEPGGridContainer.cpp in Sources */, + 832F2C8F120D8CB500026B38 /* PVRDirectory.cpp in Sources */, + 832F2C90120D8CB500026B38 /* PVRFile.cpp in Sources */, + 832F2C95120D8CF600026B38 /* TextSearch.cpp in Sources */, + 832F2C98120D8D0B00026B38 /* DVDDemuxPVRClient.cpp in Sources */, + 832F2C9B120D8D3000026B38 /* GUIWindowTV.cpp in Sources */, + 832F2CA0120D8D5C00026B38 /* DVDInputStreamPVRManager.cpp in Sources */, + 832F2CA3120D8D7600026B38 /* GUIViewStateTV.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/addons/library.xbmc.addon/dlfcn-win32.cpp b/addons/library.xbmc.addon/dlfcn-win32.cpp new file mode 100644 index 0000000000..a6cf2c28ff --- /dev/null +++ b/addons/library.xbmc.addon/dlfcn-win32.cpp @@ -0,0 +1,263 @@ +/* + * dlfcn-win32 + * Copyright (c) 2007 Ramiro Polla + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include "dlfcn-win32.h" + +/* Note: + * MSDN says these functions are not thread-safe. We make no efforts to have + * any kind of thread safety. + */ + +/* I have no special reason to have set MAX_GLOBAL_OBJECTS to this value. Any + * comments are welcome. + */ +#define MAX_OBJECTS 255 + +static HMODULE global_objects[MAX_OBJECTS]; + +/* This function adds an object to the list of global objects. + * The implementation is very simple and slow. + * TODO: should failing this function be enough to fail the call to dlopen( )? + */ +static void global_object_add( HMODULE hModule ) +{ + int i; + + for( i = 0 ; i < MAX_OBJECTS ; i++ ) + { + if( !global_objects[i] ) + { + global_objects[i] = hModule; + break; + } + } +} + +static void global_object_rem( HMODULE hModule ) +{ + int i; + + for( i = 0 ; i < MAX_OBJECTS ; i++ ) + { + if( global_objects[i] == hModule ) + { + global_objects[i] = 0; + break; + } + } +} + +/* Argument to last function. Used in dlerror( ) */ +static char last_name[MAX_PATH]; + +static int copy_string( char *dest, int dest_size, const char *src ) +{ + int i = 0; + + if( src && dest ) + { + for( i = 0 ; i < dest_size-1 ; i++ ) + { + if( !src[i] ) + break; + else + dest[i] = src[i]; + } + } + dest[i] = '\0'; + + return i; +} + +void *dlopen( const char *file, int mode ) +{ + HMODULE hModule; + UINT uMode; + + /* Do not let Windows display the critical-error-handler message box */ + uMode = SetErrorMode( SEM_FAILCRITICALERRORS ); + + if( file == 0 ) + { + /* Save NULL pointer for error message */ + _snprintf_s( last_name, MAX_PATH, MAX_PATH, "0x%p", file ); + + /* POSIX says that if the value of file is 0, a handle on a global + * symbol object must be provided. That object must be able to access + * all symbols from the original program file, and any objects loaded + * with the RTLD_GLOBAL flag. + * The return value from GetModuleHandle( ) allows us to retrieve + * symbols only from the original program file. For objects loaded with + * the RTLD_GLOBAL flag, we create our own list later on. + */ + hModule = GetModuleHandle( NULL ); + } + else + { + char lpFileName[MAX_PATH]; + int i; + + /* MSDN says backslashes *must* be used instead of forward slashes. */ + for( i = 0 ; i < sizeof(lpFileName)-1 ; i++ ) + { + if( !file[i] ) + break; + else if( file[i] == '/' ) + lpFileName[i] = '\\'; + else + lpFileName[i] = file[i]; + } + lpFileName[i] = '\0'; + + /* Save file name for error message */ + copy_string( last_name, sizeof(last_name), lpFileName ); + + /* POSIX says the search path is implementation-defined. + * LOAD_WITH_ALTERED_SEARCH_PATH is used to make it behave more closely + * to UNIX's search paths (start with system folders instead of current + * folder). + */ + hModule = LoadLibraryEx( (LPSTR) lpFileName, NULL, + LOAD_WITH_ALTERED_SEARCH_PATH ); + /* If the object was loaded with RTLD_GLOBAL, add it to list of global + * objects, so that its symbols may be retrieved even if the handle for + * the original program file is passed. POSIX says that if the same + * file is specified in multiple invocations, and any of them are + * RTLD_GLOBAL, even if any further invocations use RTLD_LOCAL, the + * symbols will remain global. + */ + + if( hModule && (mode & RTLD_GLOBAL) ) + global_object_add( hModule ); + } + + /* Return to previous state of the error-mode bit flags. */ + SetErrorMode( uMode ); + + return (void *) hModule; +} + +int dlclose( void *handle ) +{ + HMODULE hModule = (HMODULE) handle; + BOOL ret; + + /* Save handle for error message */ + _snprintf_s( last_name, MAX_PATH, MAX_PATH, "0x%p", handle ); + + ret = FreeLibrary( hModule ); + + /* If the object was loaded with RTLD_GLOBAL, remove it from list of global + * objects. + */ + if( ret ) + global_object_rem( hModule ); + + /* dlclose's return value in inverted in relation to FreeLibrary's. */ + ret = !ret; + + return (int) ret; +} + +void *dlsym( void *handle, const char *name ) +{ + FARPROC symbol; + HMODULE myhandle = (HMODULE) handle; + + /* Save symbol name for error message */ + copy_string( last_name, sizeof(last_name), name ); + + symbol = GetProcAddress( myhandle, name ); +#if 0 + if( symbol == NULL ) + { + HMODULE hModule; + + /* If the handle for the original program file is passed, also search + * in all globally loaded objects. + */ + + hModule = GetModuleHandle( NULL ); + + if( hModule == handle ) + { + int i; + + for( i = 0 ; i < MAX_OBJECTS ; i++ ) + { + if( global_objects[i] != 0 ) + { + symbol = GetProcAddress( global_objects[i], name ); + if( symbol != NULL ) + break; + } + } + } + + + CloseHandle( hModule ); + } +#endif + return (void*) symbol; +} + +char *dlerror( void ) +{ + DWORD dwMessageId; + /* POSIX says this function doesn't have to be thread-safe, so we use one + * static buffer. + * MSDN says the buffer cannot be larger than 64K bytes, so we set it to + * the limit. + */ + static char lpBuffer[65535]; + DWORD ret; + + dwMessageId = GetLastError( ); + + if( dwMessageId == 0 ) + return NULL; + + /* Format error message to: + * "": + */ + ret = copy_string( lpBuffer, sizeof(lpBuffer), "\"" ); + ret += copy_string( lpBuffer+ret, sizeof(lpBuffer)-ret, last_name ); + ret += copy_string( lpBuffer+ret, sizeof(lpBuffer)-ret, "\": " ); + ret += FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwMessageId, + MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), + lpBuffer+ret, sizeof(lpBuffer)-ret, NULL ); + + if( ret > 1 ) + { + /* POSIX says the string must not have trailing */ + if( lpBuffer[ret-2] == '\r' && lpBuffer[ret-1] == '\n' ) + lpBuffer[ret-2] = '\0'; + } + + /* POSIX says that invoking dlerror( ) a second time, immediately following + * a prior invocation, shall result in NULL being returned. + */ + SetLastError(0); + + return lpBuffer; +} + diff --git a/addons/library.xbmc.addon/dlfcn-win32.h b/addons/library.xbmc.addon/dlfcn-win32.h new file mode 100644 index 0000000000..f906343a96 --- /dev/null +++ b/addons/library.xbmc.addon/dlfcn-win32.h @@ -0,0 +1,46 @@ +#pragma once +/* + * dlfcn-win32 + * Copyright (c) 2007 Ramiro Polla + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DLFCN_H +#define DLFCN_H + +/* POSIX says these are implementation-defined. + * To simplify use with Windows API, we treat them the same way. + */ + +#define RTLD_LAZY 0 +#define RTLD_NOW 0 + +#define RTLD_GLOBAL (1 << 1) +#define RTLD_LOCAL (1 << 2) + +/* These two were added in The Open Group Base Specifications Issue 6. + * Note: All other RTLD_* flags in any dlfcn.h are not standard compliant. + */ + +#define RTLD_DEFAULT 0 +#define RTLD_NEXT 0 + +void *dlopen ( const char *file, int mode ); +int dlclose( void *handle ); +void *dlsym ( void *handle, const char *name ); +char *dlerror( void ); + +#endif /* DLFCN-WIN32_H */ diff --git a/addons/library.xbmc.addon/libXBMC_addon.h b/addons/library.xbmc.addon/libXBMC_addon.h new file mode 100644 index 0000000000..7af0d1cf7d --- /dev/null +++ b/addons/library.xbmc.addon/libXBMC_addon.h @@ -0,0 +1,150 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include +#include +#include + +#ifndef _LINUX +#include "dlfcn-win32.h" +#define ADDON_DLL "\\library.xbmc.addon\\libXBMC_addon.dll" +#else +#include +#if defined(__APPLE__) +#if defined(__POWERPC__) +#define ADDON_DLL "/library.xbmc.addon/libXBMC_addon-powerpc-osx.so" +#else +#define ADDON_DLL "/library.xbmc.addon/libXBMC_addon-x86-osx.so" +#endif +#elif defined(__x86_64__) +#define ADDON_DLL "/library.xbmc.addon/libXBMC_addon-x86_64-linux.so" +#elif defined(_POWERPC) +#define ADDON_DLL "/library.xbmc.addon/libXBMC_addon-powerpc-linux.so" +#elif defined(_POWERPC64) +#define ADDON_DLL "/library.xbmc.addon/libXBMC_addon-powerpc64-linux.so" +#else /* !__x86_64__ && !__powerpc__ */ +#define ADDON_DLL "/library.xbmc.addon/libXBMC_addon-i486-linux.so" +#endif /* __x86_64__ */ +#endif /* _LINUX */ + +typedef enum addon_log { + LOG_DEBUG, + LOG_INFO, + LOG_NOTICE, + LOG_ERROR +} addon_log_t; + +typedef enum queue_msg { + QUEUE_INFO, + QUEUE_WARNING, + QUEUE_ERROR +} queue_msg_t; + +class cHelper_libXBMC_addon +{ +public: + cHelper_libXBMC_addon() + { + m_libXBMC_addon = NULL; + m_Handle = NULL; + } + + ~cHelper_libXBMC_addon() + { + if (m_libXBMC_addon) + { + XBMC_unregister_me(); + dlclose(m_libXBMC_addon); + } + } + + bool RegisterMe(void *Handle) + { + m_Handle = Handle; + + std::string libBasePath; + libBasePath = ((cb_array*)m_Handle)->libPath; + libBasePath += ADDON_DLL; + + m_libXBMC_addon = dlopen(libBasePath.c_str(), RTLD_LAZY); + if (m_libXBMC_addon == NULL) + { + fprintf(stderr, "Unable to load %s\n", dlerror()); + return false; + } + + XBMC_register_me = (int (*)(void *HANDLE)) + dlsym(m_libXBMC_addon, "XBMC_register_me"); + if (XBMC_register_me == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + XBMC_unregister_me = (void (*)()) + dlsym(m_libXBMC_addon, "XBMC_unregister_me"); + if (XBMC_unregister_me == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + Log = (void (*)(const addon_log_t loglevel, const char *format, ... )) + dlsym(m_libXBMC_addon, "XBMC_log"); + if (Log == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + GetSetting = (bool (*)(std::string settingName, void *settingValue)) + dlsym(m_libXBMC_addon, "XBMC_get_setting"); + if (GetSetting == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + QueueNotification = (void (*)(const queue_msg_t loglevel, const char *format, ... )) + dlsym(m_libXBMC_addon, "XBMC_queue_notification"); + if (QueueNotification == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + UnknownToUTF8 = (void (*)(std::string &str)) + dlsym(m_libXBMC_addon, "XBMC_unknown_to_utf8"); + if (UnknownToUTF8 == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + GetLocalizedString = (std::string (*)(int dwCode)) + dlsym(m_libXBMC_addon, "XBMC_get_localized_string"); + if (GetLocalizedString == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + GetDVDMenuLanguage = (std::string (*)()) + dlsym(m_libXBMC_addon, "XBMC_get_dvd_menu_language"); + if (GetDVDMenuLanguage == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + return XBMC_register_me(m_Handle) > 0; + } + + void (*Log)(const addon_log_t loglevel, const char *format, ... ); + bool (*GetSetting)(std::string settingName, void *settingValue); + void (*QueueNotification)(const queue_msg_t type, const char *format, ... ); + void (*UnknownToUTF8)(std::string &str); + std::string (*GetLocalizedString)(int dwCode); + std::string (*GetDVDMenuLanguage)(); + +protected: + int (*XBMC_register_me)(void *HANDLE); + void (*XBMC_unregister_me)(); + +private: + void *m_libXBMC_addon; + void *m_Handle; + struct cb_array + { + const char* libPath; + }; +}; diff --git a/addons/library.xbmc.gui/libXBMC_gui.h b/addons/library.xbmc.gui/libXBMC_gui.h new file mode 100644 index 0000000000..8d9c33d032 --- /dev/null +++ b/addons/library.xbmc.gui/libXBMC_gui.h @@ -0,0 +1,325 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include +#include +#include + +typedef void* GUIHANDLE; + +#ifndef _LINUX +#include "../library.xbmc.addon/dlfcn-win32.h" +#define GUI_HELPER_DLL "\\library.xbmc.gui\\libXBMC_gui.dll" +#else +#include +#if defined(__APPLE__) +#if defined(__POWERPC__) +#define GUI_HELPER_DLL "/library.xbmc.gui/libXBMC_gui-powerpc-osx.so" +#else +#define GUI_HELPER_DLL "/library.xbmc.gui/libXBMC_gui-x86-osx.so" +#endif +#elif defined(__x86_64__) +#define GUI_HELPER_DLL "/library.xbmc.gui/libXBMC_gui-x86_64-linux.so" +#elif defined(_POWERPC) +#define GUI_HELPER_DLL "/library.xbmc.gui/libXBMC_gui-powerpc-linux.so" +#elif defined(_POWERPC64) +#define GUI_HELPER_DLL "/library.xbmc.gui/libXBMC_gui-powerpc64-linux.so" +#else /* !__x86_64__ && !__powerpc__ */ +#define GUI_HELPER_DLL "/library.xbmc.gui/libXBMC_gui-i486-linux.so" +#endif /* __x86_64__ */ +#endif /* _LINUX */ + +#define ADDON_ACTION_PREVIOUS_MENU 10 +#define ADDON_ACTION_CLOSE_DIALOG 51 + +class cGUIWindow; +class cGUISpinControl; +class cGUIRadioButton; +class cGUIProgressControl; +class cListItem; + +class cHelper_libXBMC_gui +{ +public: + cHelper_libXBMC_gui() + { + m_libXBMC_gui = NULL; + m_Handle = NULL; + } + + ~cHelper_libXBMC_gui() + { + if (m_libXBMC_gui) + { + GUI_unregister_me(); + dlclose(m_libXBMC_gui); + } + } + + bool RegisterMe(void *Handle) + { + m_Handle = Handle; + + std::string libBasePath; + libBasePath = ((cb_array*)m_Handle)->libPath; + libBasePath += GUI_HELPER_DLL; + + m_libXBMC_gui = dlopen(libBasePath.c_str(), RTLD_LAZY); + if (m_libXBMC_gui == NULL) + { + fprintf(stderr, "Unable to load %s\n", dlerror()); + return false; + } + + GUI_register_me = (int (*)(void *HANDLE)) + dlsym(m_libXBMC_gui, "GUI_register_me"); + if (GUI_register_me == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + GUI_unregister_me = (void (*)()) + dlsym(m_libXBMC_gui, "GUI_unregister_me"); + if (GUI_unregister_me == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + Lock = (void (*)()) + dlsym(m_libXBMC_gui, "GUI_lock"); + if (Lock == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + Unlock = (void (*)()) + dlsym(m_libXBMC_gui, "GUI_unlock"); + if (Unlock == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + GetScreenHeight = (int (*)()) + dlsym(m_libXBMC_gui, "GUI_get_screen_height"); + if (GetScreenHeight == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + GetScreenWidth = (int (*)()) + dlsym(m_libXBMC_gui, "GUI_get_screen_width"); + if (GetScreenWidth == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + GetVideoResolution = (int (*)()) + dlsym(m_libXBMC_gui, "GUI_get_video_resolution"); + if (GetVideoResolution == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + Window_create = (cGUIWindow* (*)(const char *xmlFilename, const char *defaultSkin, bool forceFallback, bool asDialog)) + dlsym(m_libXBMC_gui, "GUI_Window_create"); + if (Window_create == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + Window_destroy = (void (*)(cGUIWindow* p)) + dlsym(m_libXBMC_gui, "GUI_Window_destroy"); + if (Window_destroy == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + Control_getSpin = (cGUISpinControl* (*)(cGUIWindow *window, int controlId)) + dlsym(m_libXBMC_gui, "GUI_control_get_spin"); + if (Control_getSpin == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + Control_releaseSpin = (void (*)(cGUISpinControl* p)) + dlsym(m_libXBMC_gui, "GUI_control_release_spin"); + if (Control_releaseSpin == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + Control_getRadioButton = (cGUIRadioButton* (*)(cGUIWindow *window, int controlId)) + dlsym(m_libXBMC_gui, "GUI_control_get_radiobutton"); + if (Control_getRadioButton == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + Control_releaseRadioButton = (void (*)(cGUIRadioButton* p)) + dlsym(m_libXBMC_gui, "GUI_control_release_radiobutton"); + if (Control_releaseRadioButton == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + Control_getProgress = (cGUIProgressControl* (*)(cGUIWindow *window, int controlId)) + dlsym(m_libXBMC_gui, "GUI_control_get_progress"); + if (Control_getProgress == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + Control_releaseProgress = (void (*)(cGUIProgressControl* p)) + dlsym(m_libXBMC_gui, "GUI_control_release_progress"); + if (Control_releaseProgress == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + ListItem_create = (cListItem* (*)(const char *label, const char *label2, const char *iconImage, const char *thumbnailImage, const char *path)) + dlsym(m_libXBMC_gui, "GUI_ListItem_create"); + if (ListItem_create == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + ListItem_destroy = (void (*)(cListItem* p)) + dlsym(m_libXBMC_gui, "GUI_ListItem_destroy"); + if (ListItem_destroy == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + + return GUI_register_me(m_Handle) > 0; + } + + void (*Lock)(); + void (*Unlock)(); + int (*GetScreenHeight)(); + int (*GetScreenWidth)(); + int (*GetVideoResolution)(); + cGUIWindow* (*Window_create)(const char *xmlFilename, const char *defaultSkin, bool forceFallback, bool asDialog); + void (*Window_destroy)(cGUIWindow* p); + cGUISpinControl* (*Control_getSpin)(cGUIWindow *window, int controlId); + void (*Control_releaseSpin)(cGUISpinControl* p); + cGUIRadioButton* (*Control_getRadioButton)(cGUIWindow *window, int controlId); + void (*Control_releaseRadioButton)(cGUIRadioButton* p); + cGUIProgressControl* (*Control_getProgress)(cGUIWindow *window, int controlId); + void (*Control_releaseProgress)(cGUIProgressControl* p); + cListItem* (*ListItem_create)(const char *label, const char *label2, const char *iconImage, const char *thumbnailImage, const char *path); + void (*ListItem_destroy)(cListItem* p); + +protected: + int (*GUI_register_me)(void *HANDLE); + void (*GUI_unregister_me)(); + +private: + void *m_libXBMC_gui; + void *m_Handle; + struct cb_array + { + const char* libPath; + }; +}; + +class cGUISpinControl +{ +public: + cGUISpinControl(cGUIWindow *window, int controlId); + virtual ~cGUISpinControl(void) {} + + virtual void SetVisible(bool yesNo); + virtual void SetText(const char *label); + virtual void Clear(); + virtual void AddLabel(const char *label, int iValue); + virtual int GetValue(); + virtual void SetValue(int iValue); + +private: + cGUIWindow *m_Window; + int m_ControlId; + GUIHANDLE m_SpinHandle; +}; + +class cGUIRadioButton +{ +public: + cGUIRadioButton(cGUIWindow *window, int controlId); + ~cGUIRadioButton() {} + + virtual void SetVisible(bool yesNo); + virtual void SetText(const char *label); + virtual void SetSelected(bool yesNo); + virtual bool IsSelected(); + +private: + cGUIWindow *m_Window; + int m_ControlId; + GUIHANDLE m_ButtonHandle; +}; + +class cGUIProgressControl +{ +public: + cGUIProgressControl(cGUIWindow *window, int controlId); + virtual ~cGUIProgressControl(void) {} + + virtual void SetPercentage(float fPercent); + virtual float GetPercentage() const; + virtual void SetInfo(int iInfo); + virtual int GetInfo() const; + virtual std::string GetDescription() const; + +private: + cGUIWindow *m_Window; + int m_ControlId; + GUIHANDLE m_ProgressHandle; +}; + +class cListItem +{ +friend class cGUIWindow; + +public: + cListItem(const char *label, const char *label2, const char *iconImage, const char *thumbnailImage, const char *path); + virtual ~cListItem(void) {} + + virtual const char *GetLabel(); + virtual void SetLabel(const char *label); + virtual const char *GetLabel2(); + virtual void SetLabel2(const char *label); + virtual void SetIconImage(const char *image); + virtual void SetThumbnailImage(const char *image); + virtual void SetInfo(const char *Info); + virtual void SetProperty(const char *key, const char *value); + virtual const char *GetProperty(const char *key) const; + virtual void SetPath(const char *Path); + +// {(char*)"select(); +// {(char*)"isSelected(); +protected: + GUIHANDLE m_ListItemHandle; +}; + +class cGUIWindow +{ +friend class cGUISpinControl; +friend class cGUIRadioButton; +friend class cGUIProgressControl; + +public: + cGUIWindow(const char *xmlFilename, const char *defaultSkin, bool forceFallback, bool asDialog); + ~cGUIWindow(); + + virtual bool Show(); + virtual void Close(); + virtual void DoModal(); + virtual bool SetFocusId(int iControlId); + virtual int GetFocusId(); + virtual bool SetCoordinateResolution(int res); + virtual void SetProperty(const char *key, const char *value); + virtual void SetPropertyInt(const char *key, int value); + virtual void SetPropertyBool(const char *key, bool value); + virtual void SetPropertyDouble(const char *key, double value); + virtual const char *GetProperty(const char *key) const; + virtual int GetPropertyInt(const char *key) const; + virtual bool GetPropertyBool(const char *key) const; + virtual double GetPropertyDouble(const char *key) const; + virtual void ClearProperties(); + virtual int GetListSize(); + virtual void ClearList(); + virtual GUIHANDLE AddStringItem(const char *name, int itemPosition = -1); + virtual void AddItem(GUIHANDLE item, int itemPosition = -1); + virtual void AddItem(cListItem *item, int itemPosition = -1); + virtual void RemoveItem(int itemPosition); + virtual GUIHANDLE GetListItem(int listPos); + virtual void SetCurrentListPosition(int listPos); + virtual int GetCurrentListPosition(); + virtual void SetControlLabel(int controlId, const char *label); + + virtual bool OnClick(int controlId); + virtual bool OnFocus(int controlId); + virtual bool OnInit(); + virtual bool OnAction(int actionId); + + GUIHANDLE m_cbhdl; + bool (*CBOnInit)(GUIHANDLE cbhdl); + bool (*CBOnFocus)(GUIHANDLE cbhdl, int controlId); + bool (*CBOnClick)(GUIHANDLE cbhdl, int controlId); + bool (*CBOnAction)(GUIHANDLE cbhdl, int actionId); + +protected: + GUIHANDLE m_WindowHandle; +}; + diff --git a/addons/library.xbmc.pvr/libXBMC_pvr.h b/addons/library.xbmc.pvr/libXBMC_pvr.h new file mode 100644 index 0000000000..69c531bf6a --- /dev/null +++ b/addons/library.xbmc.pvr/libXBMC_pvr.h @@ -0,0 +1,165 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include +#include +#include +#include "xbmc_pvr_types.h" + +#ifndef _LINUX +#include "../library.xbmc.addon/dlfcn-win32.h" +#define PVR_HELPER_DLL "\\library.xbmc.pvr\\libXBMC_pvr.dll" +#else +#include +#if defined(__APPLE__) +#if defined(__POWERPC__) +#define PVR_HELPER_DLL "/library.xbmc.pvr/libXBMC_pvr-powerpc-osx.so" +#else +#define PVR_HELPER_DLL "/library.xbmc.pvr/libXBMC_pvr-x86-osx.so" +#endif +#elif defined(__x86_64__) +#define PVR_HELPER_DLL "/library.xbmc.pvr/libXBMC_pvr-x86_64-linux.so" +#elif defined(_POWERPC) +#define PVR_HELPER_DLL "/library.xbmc.pvr/libXBMC_pvr-powerpc-linux.so" +#elif defined(_POWERPC64) +#define PVR_HELPER_DLL "/library.xbmc.pvr/libXBMC_pvr-powerpc64-linux.so" +#else /* !__x86_64__ && !__powerpc__ */ +#define PVR_HELPER_DLL "/library.xbmc.pvr/libXBMC_pvr-i486-linux.so" +#endif /* __x86_64__ */ +#endif /* _LINUX */ + +#define DVD_TIME_BASE 1000000 +#define DVD_NOPTS_VALUE (-1LL<<52) // should be possible to represent in both double and __int64 + +class cHelper_libXBMC_pvr +{ +public: + cHelper_libXBMC_pvr() + { + m_libXBMC_pvr = NULL; + m_Handle = NULL; + } + + ~cHelper_libXBMC_pvr() + { + if (m_libXBMC_pvr) + { + PVR_unregister_me(); + dlclose(m_libXBMC_pvr); + } + } + + bool RegisterMe(void *Handle) + { + m_Handle = Handle; + + std::string libBasePath; + libBasePath = ((cb_array*)m_Handle)->libPath; + libBasePath += PVR_HELPER_DLL; + + m_libXBMC_pvr = dlopen(libBasePath.c_str(), RTLD_LAZY); + if (m_libXBMC_pvr == NULL) + { + fprintf(stderr, "Unable to load %s\n", dlerror()); + return false; + } + + PVR_register_me = (int (*)(void *HANDLE)) + dlsym(m_libXBMC_pvr, "PVR_register_me"); + if (PVR_register_me == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + PVR_unregister_me = (void (*)()) + dlsym(m_libXBMC_pvr, "PVR_unregister_me"); + if (PVR_unregister_me == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + TransferEpgEntry = (void (*)(const PVRHANDLE handle, const PVR_PROGINFO *epgentry)) + dlsym(m_libXBMC_pvr, "PVR_transfer_epg_entry"); + if (TransferEpgEntry == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + TransferChannelEntry = (void (*)(const PVRHANDLE handle, const PVR_CHANNEL *chan)) + dlsym(m_libXBMC_pvr, "PVR_transfer_channel_entry"); + if (TransferChannelEntry == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + TransferTimerEntry = (void (*)(const PVRHANDLE handle, const PVR_TIMERINFO *timer)) + dlsym(m_libXBMC_pvr, "PVR_transfer_timer_entry"); + if (TransferTimerEntry == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + TransferRecordingEntry = (void (*)(const PVRHANDLE handle, const PVR_RECORDINGINFO *recording)) + dlsym(m_libXBMC_pvr, "PVR_transfer_recording_entry"); + if (TransferRecordingEntry == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + AddMenuHook = (void (*)(PVR_MENUHOOK *hook)) + dlsym(m_libXBMC_pvr, "PVR_add_menu_hook"); + if (AddMenuHook == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + Recording = (void (*)(const char *Name, const char *FileName, bool On)) + dlsym(m_libXBMC_pvr, "PVR_recording"); + if (Recording == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + TriggerTimerUpdate = (void (*)()) + dlsym(m_libXBMC_pvr, "PVR_trigger_timer_update"); + if (TriggerTimerUpdate == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + TriggerRecordingUpdate = (void (*)()) + dlsym(m_libXBMC_pvr, "PVR_trigger_recording_update"); + if (TriggerRecordingUpdate == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + +#ifdef USE_DEMUX + FreeDemuxPacket = (void (*)(DemuxPacket* pPacket)) + dlsym(m_libXBMC_pvr, "PVR_free_demux_packet"); + if (FreeDemuxPacket == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } + + AllocateDemuxPacket = (DemuxPacket* (*)(int iDataSize)) + dlsym(m_libXBMC_pvr, "PVR_allocate_demux_packet"); + if (AllocateDemuxPacket == NULL) { fprintf(stderr, "Unable to assign function %s\n", dlerror()); return false; } +#endif + + return PVR_register_me(m_Handle) > 0; + } + + void (*TransferEpgEntry)(const PVRHANDLE handle, const PVR_PROGINFO *epgentry); + void (*TransferChannelEntry)(const PVRHANDLE handle, const PVR_CHANNEL *chan); + void (*TransferTimerEntry)(const PVRHANDLE handle, const PVR_TIMERINFO *timer); + void (*TransferRecordingEntry)(const PVRHANDLE handle, const PVR_RECORDINGINFO *recording); + void (*AddMenuHook)(PVR_MENUHOOK *hook); + void (*Recording)(const char *Name, const char *FileName, bool On); + void (*TriggerTimerUpdate)(); + void (*TriggerRecordingUpdate)(); +#ifdef USE_DEMUX + void (*FreeDemuxPacket)(DemuxPacket* pPacket); + DemuxPacket* (*AllocateDemuxPacket)(int iDataSize); +#endif + +protected: + int (*PVR_register_me)(void *HANDLE); + void (*PVR_unregister_me)(); + +private: + void *m_libXBMC_pvr; + void *m_Handle; + struct cb_array + { + const char* libPath; + }; +}; diff --git a/addons/pvr.hts/addon.xml b/addons/pvr.hts/addon.xml new file mode 100644 index 0000000000..66cf72b0da --- /dev/null +++ b/addons/pvr.hts/addon.xml @@ -0,0 +1,22 @@ + + + + + + + + XBMC's frontend for Tvheadend + Tvheadend frontend; supporting streaming of Live TV & Recordings, EPG, Timers + This is unstable software! The authors are in no way responsible for failed recordings, incorrect timers, wasted hours, or any other undesirable effects.. + all + + diff --git a/addons/pvr.hts/icon.png b/addons/pvr.hts/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..874edccfecb02eba716e446aeebd93fa39451cb1 GIT binary patch literal 21031 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τP)pc3z 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 literal 0 HcmV?d00001 diff --git a/addons/pvr.hts/pthreadVC2d.dll b/addons/pvr.hts/pthreadVC2d.dll new file mode 100644 index 0000000000000000000000000000000000000000..6fffdc45d43ece6ae81e87977dd33d96df72620a GIT binary patch literal 79360 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%0D!ioIT#c*)tF;Yikp> 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 literal 0 HcmV?d00001 diff --git a/addons/pvr.hts/resources/language/Dutch/strings.xml b/addons/pvr.hts/resources/language/Dutch/strings.xml new file mode 100644 index 0000000000..840e485c94 --- /dev/null +++ b/addons/pvr.hts/resources/language/Dutch/strings.xml @@ -0,0 +1,11 @@ + + + + Tvheadend server naam of IP + HTTP Poort + HTSP Poort + Gebruikersnaam + Wachtwoord + Sla eerste I-frame over + Gids tijdscorrectie + diff --git a/addons/pvr.hts/resources/language/English/strings.xml b/addons/pvr.hts/resources/language/English/strings.xml new file mode 100644 index 0000000000..17c523d05a --- /dev/null +++ b/addons/pvr.hts/resources/language/English/strings.xml @@ -0,0 +1,11 @@ + + + + Tvheadend Hostname or IP + HTTP Port + HTSP Port + Username + Password + Skip First I-frame + EPG offset correction + diff --git a/addons/pvr.hts/resources/language/German/strings.xml b/addons/pvr.hts/resources/language/German/strings.xml new file mode 100644 index 0000000000..31d407162f --- /dev/null +++ b/addons/pvr.hts/resources/language/German/strings.xml @@ -0,0 +1,9 @@ + + + + Tvheadend Hostname oder IP + HTTP Port + HTSP Port + Benutzername + Passwort + diff --git a/addons/pvr.hts/resources/settings.xml b/addons/pvr.hts/resources/settings.xml new file mode 100644 index 0000000000..539b988f59 --- /dev/null +++ b/addons/pvr.hts/resources/settings.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/addons/pvr.mythtv/addon.xml b/addons/pvr.mythtv/addon.xml new file mode 100644 index 0000000000..dac8f16356 --- /dev/null +++ b/addons/pvr.mythtv/addon.xml @@ -0,0 +1,23 @@ + + + + + + + + XBMC's frontend for MythTV + MythTV frontend; supporting streaming of Live TV & Recordings, EPG, Timers + This is unstable software! The authors are in no way responsible for failed recordings, incorrect timers, wasted hours, or any other undesirable effects.. + all + + + diff --git a/addons/pvr.mythtv/resources/language/English/strings.xml b/addons/pvr.mythtv/resources/language/English/strings.xml new file mode 100644 index 0000000000..bb3167c523 --- /dev/null +++ b/addons/pvr.mythtv/resources/language/English/strings.xml @@ -0,0 +1,7 @@ + + + + MythTV Backend Hostname or IP + MythXML Port + + diff --git a/addons/pvr.mythtv/resources/settings.xml b/addons/pvr.mythtv/resources/settings.xml new file mode 100644 index 0000000000..f50e749cfa --- /dev/null +++ b/addons/pvr.mythtv/resources/settings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/addons/pvr.team-mediaportal.tvserver/addon.xml b/addons/pvr.team-mediaportal.tvserver/addon.xml new file mode 100644 index 0000000000..cd5593ce53 --- /dev/null +++ b/addons/pvr.team-mediaportal.tvserver/addon.xml @@ -0,0 +1,22 @@ + + + + + + + + XBMC frontend for the MediaPortal TV Server + MediaPortal TV Server frontend; supporting streaming of Live TV & Recordings, EPG, Timers. + This is unstable software! The authors are in no way responsible for failed recordings, incorrect timers, wasted hours, or any other undesirable effects.. + all + + diff --git a/addons/pvr.team-mediaportal.tvserver/icon.jpg b/addons/pvr.team-mediaportal.tvserver/icon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dbc561e6ff7d256d1c53a3c9d17ea9b87fc513fa GIT binary patch literal 9461 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 + + + Mediaportal hostnaam + Mediaportal XBMC plugin poort + Alleen vrij te ontvangen kanalen + Toon radio + Tekenset conversie (UTF-8) + Verbindingstimeout (s) + Importeer alleen TV kanalen uit groep + Importeer alleen radio kanalen uit groep + Converteer hostnaam naar IP adres + EPG: Genre tekst inlezen (traag) + diff --git a/addons/pvr.team-mediaportal.tvserver/resources/language/English/strings.xml b/addons/pvr.team-mediaportal.tvserver/resources/language/English/strings.xml new file mode 100644 index 0000000000..4c681c13f4 --- /dev/null +++ b/addons/pvr.team-mediaportal.tvserver/resources/language/English/strings.xml @@ -0,0 +1,14 @@ + + + + Mediaportal Hostname + Mediaportal XBMC plugin Port + Free-to-air only + Include Radio + Character Set Conversion + Connect timeout (s) + Import only TV Channels from group + Import only Radio Channels from group + Convert hostname to IP-adress + EPG: Read genre strings (slow) + diff --git a/addons/pvr.team-mediaportal.tvserver/resources/language/German/strings.xml b/addons/pvr.team-mediaportal.tvserver/resources/language/German/strings.xml new file mode 100644 index 0000000000..d995e187a7 --- /dev/null +++ b/addons/pvr.team-mediaportal.tvserver/resources/language/German/strings.xml @@ -0,0 +1,14 @@ + + + + Mediaportal Hostname oder IP + Mediaportal Port + Nur frei empfangbare Kanäle + Zeige Radiokanäle + Textkonvertierung (UTF-8) + Verbindungszeitüberlauf (s) + Importiere nur TV Kanäle aus Gruppe + Importiere nur Radiokanäle aus Gruppe + Konvertiere Hostname nach IP-Adresse + EPG: Genre Texte hochladen (langsam) + diff --git a/addons/pvr.team-mediaportal.tvserver/resources/settings.xml b/addons/pvr.team-mediaportal.tvserver/resources/settings.xml new file mode 100644 index 0000000000..523da8fab4 --- /dev/null +++ b/addons/pvr.team-mediaportal.tvserver/resources/settings.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/addons/pvr.vdr.streamdev/addon.xml b/addons/pvr.vdr.streamdev/addon.xml new file mode 100644 index 0000000000..3367c23844 --- /dev/null +++ b/addons/pvr.vdr.streamdev/addon.xml @@ -0,0 +1,23 @@ + + + + + + + + PVR client to connect VDR to XBMC over the Streamdev interface + VDR frontend; supporting streaming of Live TV & Recordings, EPG, Timers over the VNSI plugin + Erlaubt das wiedergeben von Live TV und Aufnahmen mittels VDR auf XBMC. Des weiteren werden EPG, Kanalsuche und Timer unterstützt. + This is unstable software! The authors are in no way responsible for failed recordings, incorrect timers, wasted hours, or any other undesirable effects.. + all + + diff --git a/addons/pvr.vdr.streamdev/icon.jpg b/addons/pvr.vdr.streamdev/icon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..37be1c9240a41d4551fd9bbbaea82b3c6110c897 GIT binary patch literal 14359 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 literal 0 HcmV?d00001 diff --git a/addons/pvr.vdr.streamdev/pthreadVC2.dll b/addons/pvr.vdr.streamdev/pthreadVC2.dll new file mode 100644 index 0000000000000000000000000000000000000000..fdea6760d6cecac7d1d00ddde0e50c627551bbf1 GIT binary patch literal 30720 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 literal 0 HcmV?d00001 diff --git a/addons/pvr.vdr.streamdev/pthreadVC2d.dll b/addons/pvr.vdr.streamdev/pthreadVC2d.dll new file mode 100644 index 0000000000000000000000000000000000000000..6fffdc45d43ece6ae81e87977dd33d96df72620a GIT binary patch literal 79360 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%0D!ioIT#c*)tF;Yikp> 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 literal 0 HcmV?d00001 diff --git a/addons/pvr.vdr.streamdev/resources/data/noSignal.mpg b/addons/pvr.vdr.streamdev/resources/data/noSignal.mpg new file mode 100644 index 0000000000000000000000000000000000000000..ebf1ff1140e3ae372cb3e9d34461cf43dc36ec2b GIT binary patch literal 25207 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 literal 0 HcmV?d00001 diff --git a/addons/pvr.vdr.streamdev/resources/language/Dutch/strings.xml b/addons/pvr.vdr.streamdev/resources/language/Dutch/strings.xml new file mode 100644 index 0000000000..cb648d0c35 --- /dev/null +++ b/addons/pvr.vdr.streamdev/resources/language/Dutch/strings.xml @@ -0,0 +1,15 @@ + + + + VDR Hostnaam + Streamdev Poort + Alleen Free-to-air + Radio zenders laten zien + Karakter set conversie + Connectie timeout (s) + Negeer kanalen zonder VPID and APID + Berichten vanuit VDR toestaan + Prioriteit + Lees opnames van directory + VDR opname directory + diff --git a/addons/pvr.vdr.streamdev/resources/language/English/strings.xml b/addons/pvr.vdr.streamdev/resources/language/English/strings.xml new file mode 100644 index 0000000000..11b6614a8d --- /dev/null +++ b/addons/pvr.vdr.streamdev/resources/language/English/strings.xml @@ -0,0 +1,15 @@ + + + + VDR Hostname + Streamdev Port + Free-to-air only + Include Radio + Character Set Conversion + Connect timeout (s) + Ignore Channels without VPID and APID + Allow VDR Messages + Priority + Read recordings from directory + VDR recordings directory + diff --git a/addons/pvr.vdr.streamdev/resources/language/German/strings.xml b/addons/pvr.vdr.streamdev/resources/language/German/strings.xml new file mode 100644 index 0000000000..cbea98d42e --- /dev/null +++ b/addons/pvr.vdr.streamdev/resources/language/German/strings.xml @@ -0,0 +1,15 @@ + + + + VDR Hostname oder IP + Streamdev Port + Nur frei empfangbare Kanäle + Zeige Radiokanäle + Textkonvertierung (UTF-8) + Verbindungszeitüberlauf (s) + Ignoriere Känale ohne VPID und APID + VDR Nachrichten erlauben + Priorität + Aufnahmen aus Ordner lesen + VDR Aufnahmeordner + diff --git a/addons/pvr.vdr.streamdev/resources/settings.xml b/addons/pvr.vdr.streamdev/resources/settings.xml new file mode 100644 index 0000000000..15162922cb --- /dev/null +++ b/addons/pvr.vdr.streamdev/resources/settings.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/addons/pvr.vdr.vnsi/addon.xml b/addons/pvr.vdr.vnsi/addon.xml new file mode 100644 index 0000000000..a0b1e0383c --- /dev/null +++ b/addons/pvr.vdr.vnsi/addon.xml @@ -0,0 +1,23 @@ + + + + + + + + PVR client to connect VDR to XBMC over the VNSI interface + VDR frontend; supporting streaming of Live TV & Recordings, EPG, Timers over the VNSI plugin + Erlaubt das wiedergeben von Live TV und Aufnahmen mittels VDR auf XBMC. Des weiteren werden EPG, Kanalsuche und Timer unterstützt. + This is unstable software! The authors are in no way responsible for failed recordings, incorrect timers, wasted hours, or any other undesirable effects.. + all + + diff --git a/addons/pvr.vdr.vnsi/icon.jpg b/addons/pvr.vdr.vnsi/icon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d0211608745d83785e09a6b445fcff80a9e06769 GIT binary patch literal 17133 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 GBmdc;ft?8e literal 0 HcmV?d00001 diff --git a/addons/pvr.vdr.vnsi/pthreadVC2.dll b/addons/pvr.vdr.vnsi/pthreadVC2.dll new file mode 100644 index 0000000000000000000000000000000000000000..fdea6760d6cecac7d1d00ddde0e50c627551bbf1 GIT binary patch literal 30720 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 literal 0 HcmV?d00001 diff --git a/addons/pvr.vdr.vnsi/pthreadVC2d.dll b/addons/pvr.vdr.vnsi/pthreadVC2d.dll new file mode 100644 index 0000000000000000000000000000000000000000..6fffdc45d43ece6ae81e87977dd33d96df72620a GIT binary patch literal 79360 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%0D!ioIT#c*)tF;Yikp> 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 literal 0 HcmV?d00001 diff --git a/addons/pvr.vdr.vnsi/resources/language/Dutch/strings.xml b/addons/pvr.vdr.vnsi/resources/language/Dutch/strings.xml new file mode 100644 index 0000000000..7a8883148a --- /dev/null +++ b/addons/pvr.vdr.vnsi/resources/language/Dutch/strings.xml @@ -0,0 +1,48 @@ + + + + VDR hostnaam of IP adres + VNSI Poort + Prioriteit + Character Set Conversie + Connectie timeout (s) + Berichten vanuit VDR toestaan + Lees opnames van directory^M + VDR opname directory^M + Kanalen scannen + Kanalen scan - opties + kanalen scan starten + Bron Type + TV kanalen + Radio kanalen + FTA kanalen + Gecodeerde kanalen + HD kanalen + Land + Kabel Inversion + Kabel Symbolrate + Kabel modulation + Antenne Inversion + Sateliet + ATSC type + Terug + Kanalen zoeken - bezig... %i %% + Type: + Device: + Scan: %i + Signaal: %i %% + Nieuwe kanalen: %i + Alle kanalen: %i + Analoge TV + Analoge Radio + Transponder: + Nieuwe kanalen + Kanalen zoeken - Klaar + Geen device beschikbaar - exiting + Geen DVB-S2 apparaat beschikbaar - We vallen terug op DVB-S + Bezig + Gestopt + Klaar + Kanalen scan - Geanuleerd + Kanalen scan - Fout + diff --git a/addons/pvr.vdr.vnsi/resources/language/English/strings.xml b/addons/pvr.vdr.vnsi/resources/language/English/strings.xml new file mode 100644 index 0000000000..19006dc8c3 --- /dev/null +++ b/addons/pvr.vdr.vnsi/resources/language/English/strings.xml @@ -0,0 +1,48 @@ + + + + VDR Hostname or IP + VNSI Port + Priority + Character Set Conversion + Connect timeout (s) + Allow VDR Messages + Read recordings from directory + VDR recordings directory + Channel search + Channel search - Settings + Start Channel search + Source Type + TV channels + Radio channels + FTA channels + Scrambled channels + HD channels + Country + Cable Inversion + Cable Symbolrate + Cable modulation + Terr Inversion + Satellite + ATSC Type + Back + Channel search - running... %i %% + Type: + Device: + Scan: %i + Signal: %i %% + New channels: %i + All channels: %i + Analog TV + Analog Radio + Transponder: + New channels + Channel search - Finished + No device available - exiting + No DVB-S2 device available - trying fallback to DVB-S + Running + Stopped + Finished + Channel search - Canceled + Channel search - Error + diff --git a/addons/pvr.vdr.vnsi/resources/language/German/strings.xml b/addons/pvr.vdr.vnsi/resources/language/German/strings.xml new file mode 100644 index 0000000000..99f1435c6a --- /dev/null +++ b/addons/pvr.vdr.vnsi/resources/language/German/strings.xml @@ -0,0 +1,48 @@ + + + + VDR Hostname oder IP + VNSI Port + Priorität + Textkonvertierung (UTF-8) + Verbindungszeitüberlauf (s) + VDR Nachrichten erlauben + Aufnahmen aus Ordner lesen + VDR Aufnahmeordner + Kanalsuche + Kanalsuche - Einstellungen + Kanalsuche starten + Empfangsart + TV Kanäle + Radio Kanäle + Frei empfangbare Kanäle + Verschlüsselte Kanäle + HD Kanäle + Land + Kabel Inversion + Kabel Symbolrate + Kabel Modulation + Terrestrisch Inversion + Satellit + ATSC Type + Zurück + Kanalsuche - läuft... %i %% + Empfangsart: + Gerät: + Fortschritt: %i + Signal: %i %% + Neue Kanäle: %i + Alle Kanäle: %i + Analog TV + Analog Radio + Transponder: + Neue Kanäle + Kanalsuche - Abgeschlossen + Kein Empfangsgerät verfügbar + Kein DVB-S2 Empfangsgerät verfügbar - versuche DVB-S + Läuft... + Angehalten + Fertig + Kanalsuche - Abgebrochen + Kanalsuche - Fehler + diff --git a/addons/pvr.vdr.vnsi/resources/settings.xml b/addons/pvr.vdr.vnsi/resources/settings.xml new file mode 100644 index 0000000000..d557cab22d --- /dev/null +++ b/addons/pvr.vdr.vnsi/resources/settings.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/addons/pvr.vdr.vnsi/resources/skins/Confluence/720p/ChannelScan.xml b/addons/pvr.vdr.vnsi/resources/skins/Confluence/720p/ChannelScan.xml new file mode 100644 index 0000000000..e4f6338255 --- /dev/null +++ b/addons/pvr.vdr.vnsi/resources/skins/Confluence/720p/ChannelScan.xml @@ -0,0 +1,733 @@ + + 5 + no + 2 + + CommonSettingsBackground + CommonMediaPlayingBackground + + 90 + 50 + + + + + + + + + + 0 + 0 + 1100 + 640 + DialogBack.png + + + LOGO + 30 + 15 + 220 + 80 + keep + Confluence_Logo.png + + + 268 + 10 + 790 + 618 + black-back2.png + + + 268 + 10 + 804 + 70 + stretch + GlassTitleBar.png + + + header label + 300 + 20 + 740 + 30 + font16caps + + left + center + white + black + + + Start/Stop Channel search + 10 + 90 + 260 + 60 + 13 + + font13_title + right + center + MenuItemNF.png + MenuItemFO.png + 2 + 10 + 6 + 6 + + + Cancel + 10 + 150 + 260 + 60 + 13 + + font13_title + right + center + MenuItemNF.png + MenuItemFO.png + 10 + 10 + 5 + 5 + IsEmpty(Window.Property(Scanning)) + + + IsEmpty(Window.Property(Scanning)) + + Source Type + 268 + 80 + 790 + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + center + + 5 + 5 + 22 + 11 + + + Default RadioButton + 268 + 120 + 790 + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + + 5 + 5 + 10 + 12 + Conditional + + + Default RadioButton + 268 + 160 + 790 + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + + 5 + 5 + 11 + 13 + Conditional + Conditional + + + Default RadioButton + 268 + 200 + 790 + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + + 5 + 5 + 12 + 14 + Conditional + Conditional + Conditional + + + Default RadioButton + 268 + 240 + 790 + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + + 5 + 5 + 13 + 15 + Conditional + Conditional + Conditional + Conditional + + + Default RadioButton + 268 + 280 + 790 + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + + 5 + 5 + 14 + 16 + Conditional + Conditional + Conditional + Conditional + Conditional + + + Country selection + 268 + 320 + 790 + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + center + + 5 + 5 + 15 + 17 + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + + + Satellite selection + 268 + 360 + 790 + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + center + + 5 + 5 + 16 + 18 + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + + + DVB-C Inversion + 268 + 400 + 790 + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + center + + 5 + 5 + 17 + 29 + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + + + DVB-C Symbolrate + 268 + 440 + 790 + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + center + + 5 + 5 + 18 + 20 + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + + + DVB-C QAM + 268 + 480 + 790 + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + center + + 5 + 5 + 29 + 21 + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + + + DVB-T Inversion + 268 + 520 + 790 + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + center + + 5 + 5 + 20 + 22 + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + + + ATSC Type + 268 + 560 + 790 + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + center + + 5 + 5 + 21 + 10 + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + Conditional + + + + !IsEmpty(Window.Property(Scanning)) + + Progressbar + 275 + 60 + 780 + 14 + + + type label + 275 + 85 + 250 + 30 + font13 + + left + center + grey + black + + + type value + 1040 + 85 + 500 + 30 + font13 + + right + center + white + black + + + device label + 275 + 115 + 250 + 30 + font13 + + left + center + white + black + + + device value + 1040 + 115 + 500 + 30 + font13 + + right + center + white + black + + + transponder label + 275 + 145 + 250 + 30 + font13 + + left + center + white + black + + + transponder value + 1040 + 145 + 500 + 30 + font13 + + right + center + white + black + + + Progressbar + 30 + 160 + 220 + 50 + + + Signal label + 40 + 168 + 250 + 30 + font13 + + left + center + white + black + + + 215 + 170 + 30 + 30 + stretch + amt-overlay-watched.png + !IsEmpty(Window.Property(Locked)) + + + 290 + 180 + 750 + 400 + 2 + 2 + 10 + 60 + 60 + 200 + + + 0 + 0 + 750 + 40 + stretch + MenuItemNF.png + + + 710 + 5 + 40 + 30 + stretch + OverlayLocked.png + !IsEmpty(ListItem.Property(IsEncrypted)) + + + 690 + 7 + 60 + 25 + stretch + OverlayHD.png + Conditional + !IsEmpty(ListItem.Property(IsHD)) + + + 0 + 2 + 36 + 36 + stretch + DefaultVideoCover.png + IsEmpty(ListItem.Property(IsRadio)) + + + 0 + 2 + 36 + 36 + stretch + DefaultAlbumCover.png + !IsEmpty(ListItem.Property(IsRadio)) + + + 45 + 0 + 500 + 40 + font14 + left + center + grey2 + selected + ListItem.Label + + + + + 0 + 0 + 750 + 40 + stretch + MenuItemNF.png + !Control.HasFocus(2) + VisibleFadeEffect + + + 0 + 0 + 750 + 40 + stretch + MenuItemFO.png + Control.HasFocus(2) + VisibleFadeEffect + + + 710 + 5 + 40 + 30 + stretch + OverlayLocked.png + !IsEmpty(ListItem.Property(IsEncrypted)) + + + 690 + 7 + 60 + 25 + stretch + OverlayHD.png + Conditional + !IsEmpty(ListItem.Property(IsHD)) + + + 0 + 2 + 36 + 36 + stretch + DefaultVideoCover.png + IsEmpty(ListItem.Property(IsRadio)) + + + 0 + 2 + 36 + 36 + stretch + DefaultAlbumCover.png + !IsEmpty(ListItem.Property(IsRadio)) + + + 45 + 0 + 500 + 40 + font14 + left + center + grey2 + selected + ListItem.Label + + + + + 1060 + 180 + 25 + 410 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 2 + 10 + true + vertical + + + Page Count Label + 1040 + 600 + 500 + 20 + font12 + grey + false + right + center + + + + Status Label + 275 + 590 + 500 + 20 + font14 + yellow + false + left + center + + + + + BehindDialogFadeOut + + 60 + 0 + WindowClose + WindowOpen + + 0 + 0 + 250 + 35 + header.png + + + WindowTitleCommons + 220 + + + + WindowTitleHomeButton + Clock + + diff --git a/addons/skin.confluence/720p/DialogFullScreenInfo.xml b/addons/skin.confluence/720p/DialogFullScreenInfo.xml index 806364688e..cd2ebfa783 100644 --- a/addons/skin.confluence/720p/DialogFullScreenInfo.xml +++ b/addons/skin.confluence/720p/DialogFullScreenInfo.xml @@ -33,7 +33,7 @@ keep ThumbShadow.png 8 - !VideoPlayer.Content(Movies) + !VideoPlayer.Content(Movies) + !VideoPlayer.Content(LiveTV) Movie cover image @@ -48,6 +48,17 @@ 8 VideoPlayer.Content(Movies) + + Live TV Channel Logo image + 30 + 290r + 280 + 260 + 200 + $INFO[VideoPlayer.Cover] + keep + VideoPlayer.Content(LiveTV) + Progressbar 10 @@ -158,6 +169,19 @@ black VideoPlayer.Content(Episodes) + + 10 + 35 + 880 + 30 + + left + center + font13 + grey + black + VideoPlayer.Content(LiveTV) + 10 35 @@ -198,6 +222,7 @@ grey2 black Player.ChapterCount + !VideoPlayer.Content(LiveTV) Chapter Name @@ -211,6 +236,21 @@ font12_title grey2 black + !VideoPlayer.Content(LiveTV) + + + Live TV Channel Group + 0 + 110 + 300 + 20 + + left + center + font12_title + grey2 + black + VideoPlayer.Content(LiveTV) Player Times @@ -237,9 +277,24 @@ font12_title grey2 black + !VideoPlayer.Content(LiveTV) + + + Next Live TV Name + 890 + 150 + 890 + 20 + + right + center + font12_title + grey2 + black + VideoPlayer.Content(LiveTV) Clock - \ No newline at end of file + diff --git a/addons/skin.confluence/720p/DialogPVRChannelManager.xml b/addons/skin.confluence/720p/DialogPVRChannelManager.xml new file mode 100644 index 0000000000..5eae1f2cb7 --- /dev/null +++ b/addons/skin.confluence/720p/DialogPVRChannelManager.xml @@ -0,0 +1,604 @@ + + 6 + no + + CommonSettingsBackground + CommonMediaPlayingBackground + + 90 + 50 + + + + + + + + + + 0 + 0 + 1100 + 640 + DialogBack.png + + + LOGO + 30 + 15 + 220 + 80 + keep + Confluence_Logo.png + + + OK Button + 10 + 90 + 260 + 60 + 13 + + font13_title + right + top + MenuItemNF.png + MenuItemFO.png + 60 + 7 + 6 + 5 + + + Apply changes Button + 10 + 150 + 260 + 60 + 13 + + font13_title + right + top + MenuItemNF.png + MenuItemFO.png + 60 + 7 + 4 + 6 + + + Cancel Button + 10 + 210 + 260 + 60 + 13 + + font13_title + right + top + MenuItemNF.png + MenuItemFO.png + 60 + 7 + 5 + 4 + + + Channel activated + 268 + 80 + 380 + 35 + font12 + white + white + black + left + center + button-focus.png + button-nofocus.png + no + + 4 + 20 + 33 + 8 + + + Channel name + 268 + 115 + 380 + 35 + font12 + white + white + black + button-focus.png + button-nofocus.png + + 20 + 4 + 7 + 9 + + + Channellogo Button + 268 + 150 + 380 + 35 + font12 + button-focus.png + button-nofocus.png + + 4 + 20 + 8 + 11 + + + LOGO + 613 + 150 + 35 + 35 + keep + ListItem.Property(Icon) + + + Group selection + 268 + 185 + 380 + 35 + font12 + button-focus.png + button-nofocus.png + + 20 + 4 + 9 + 12 + + + EPG activated + 268 + 220 + 380 + 35 + font12 + white + white + black + left + center + button-focus.png + button-nofocus.png + no + + 4 + 20 + 11 + 13 + + + EPG source + 268 + 255 + 380 + 35 + font12 + button-focus.png + button-nofocus.png + + 20 + 4 + 12 + 30 + + + Group Manager Button + 268 + 425 + 190 + 35 + font12 + button-focus.png + button-nofocus.png + center + + 4 + 34 + 13 + 31 + + + TV/Radio Button + 458 + 425 + 190 + 35 + font12 + IsEmpty(Window.Property(IsRadio)) + button-focus.png + button-nofocus.png + center + + 30 + 20 + 13 + 31 + + + TV/Radio Button + 458 + 425 + 190 + 35 + font12 + !IsEmpty(Window.Property(IsRadio)) + button-focus.png + button-nofocus.png + center + + 30 + 20 + 13 + 31 + + + Edit channel Button + 268 + 460 + 380 + 35 + font12 + button-focus.png + button-nofocus.png + center + + 4 + 20 + 30 + 32 + + + Delete channel Button + 268 + 495 + 380 + 35 + font12 + button-focus.png + button-nofocus.png + center + + 4 + 20 + 31 + 33 + + + New channel Button + 268 + 530 + 380 + 35 + font12 + button-focus.png + button-nofocus.png + center + + 4 + 20 + 32 + 7 + + + 670 + 60 + 380 + 530 + ContentPanel.png + + + 680 + 70 + 350 + 500 + 20 + 20 + 7 + 60 + 60 + 200 + + + 0 + 0 + 350 + 40 + MenuItemNF.png + + + 0 + 5 + 30 + 30 + ListItem.Property(Icon) + + + 0 + 5 + 30 + 30 + black-back2.png + !ListItem.Property(ActiveChannel) + + + 320 + 5 + 30 + 30 + amt-overlay-saved.png + ListItem.Property(Changed) + + + 35 + 0 + 50 + 35 + font12 + left + center + gray + selected + ListItem.Property(Number) + !ListItem.Property(ActiveChannel) + + + 35 + 0 + 50 + 35 + font12 + left + center + white + selected + ListItem.Property(Number) + ListItem.Property(ActiveChannel) + + + 85 + 0 + 190 + 35 + font12 + left + center + gray + selected + ListItem.Property(Name) + !ListItem.Property(ActiveChannel) + + + 85 + 0 + 190 + 35 + font12 + left + center + white + selected + ListItem.Property(Name) + ListItem.Property(ActiveChannel) + + + + + 0 + 0 + 350 + 60 + MenuItemNF.png + !Control.HasFocus(20) + VisibleFadeEffect + + + 0 + 0 + 350 + 60 + MenuItemFO.png + Control.HasFocus(20) + VisibleFadeEffect + + + 0 + 5 + 30 + 30 + ListItem.Property(Icon) + + + 320 + 5 + 30 + 30 + amt-overlay-saved.png + ListItem.Property(Changed) + + + 35 + 0 + 50 + 35 + font12 + left + center + gray + selected + ListItem.Property(Number) + !ListItem.Property(ActiveChannel) + + + 35 + 0 + 50 + 35 + font12 + left + center + white + selected + ListItem.Property(Number) + ListItem.Property(ActiveChannel) + + + 85 + 0 + 190 + 35 + font12 + left + center + gray + selected + ListItem.Property(Name) + !ListItem.Property(ActiveChannel) + + + 85 + 0 + 190 + 35 + font12 + left + center + white + selected + ListItem.Property(Name) + ListItem.Property(ActiveChannel) + + + 0 + 30 + 350 + 35 + font10 + left + center + grey2 + selected + + + + 0 + 0 + 350 + 60 + black-back2.png + !ListItem.Property(ActiveChannel) + + + + + 1060 + 60 + 25 + 530 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 20 + 4 + false + vertical + + + Page Count Label + 1040 + 600 + 500 + 20 + font12 + grey + false + right + center + + + + 268 + 10 + 790 + 618 + black-back2.png + + + 268 + 10 + 804 + 70 + stretch + GlassTitleBar.png + + + header label + 300 + 20 + 740 + 30 + font16caps + + left + center + white + black + IsEmpty(Window.Property(IsRadio)) + + + header label + 300 + 20 + 740 + 30 + font16caps + + left + center + white + black + !IsEmpty(Window.Property(IsRadio)) + + + BehindDialogFadeOut + + 60 + 0 + WindowClose + WindowOpen + + 0 + 0 + 250 + 35 + header.png + + + WindowTitleCommons + 220 + + + + WindowTitleHomeButton + Clock + + diff --git a/addons/skin.confluence/720p/DialogPVRChannelsOSD.xml b/addons/skin.confluence/720p/DialogPVRChannelsOSD.xml new file mode 100644 index 0000000000..be9987e49e --- /dev/null +++ b/addons/skin.confluence/720p/DialogPVRChannelsOSD.xml @@ -0,0 +1,263 @@ + + 11 + + 1 + 780 + 30 + + dialogeffect + + + + background image + 0 + 0 + 480 + 660 + EEFFFFFF + DialogBack.png + + + Close Window button + 400 + 9 + 64 + 32 + + - + PreviousMenu + DialogCloseButton-focus.png + DialogCloseButton.png + 2 + 2 + 2 + 2 + system.getbool(input.enablemouse) + + + 40 + 10 + 430 + 90 + stretch + GlassTitleBar.png + + + header label + 40 + 18 + 430 + 30 + font12_title + + center + center + white + black + pvr.IsPlayingTV + + + header label + 40 + 18 + 430 + 30 + font12_title + + center + center + white + black + pvr.IsPlayingRadio + + + 40 + 64 + 390 + 541 + 60 + 60 + 11 + 11 + list + 60 + 200 + + + 0 + 0 + 390 + 61 + MenuItemNF.png + VisibleFadeEffect + + + 5 + -4 + 40 + 35 + font10 + left + center + grey + grey + ListItem.ChannelNumber + + + 50 + 0 + 270 + 30 + font13 + white + selected + left + center + + + + 50 + 30 + 330 + 30 + font12 + grey + grey + left + center + IsEmpty(Listitem.Icon) + + + + 50 + 30 + 270 + 30 + font12 + grey + grey + left + center + !IsEmpty(Listitem.Icon) + + + + 330 + 4 + 50 + 50 + $INFO[ListItem.Icon] + + + + + 0 + 0 + 390 + 61 + MenuItemNF.png + !Control.HasFocus(11) + VisibleFadeEffect + + + 0 + 0 + 390 + 61 + MenuItemFO.png + Control.HasFocus(11) + VisibleFadeEffect + + + 5 + -4 + 40 + 35 + font10 + left + center + grey + grey + ListItem.ChannelNumber + + + 50 + 0 + 270 + 30 + font13 + white + selected + left + center + + + + 50 + 30 + 330 + 30 + font12 + grey + grey + left + center + IsEmpty(Listitem.Icon) + + + + 50 + 30 + 270 + 30 + font12 + grey + grey + left + center + !IsEmpty(Listitem.Icon) + + + + 330 + 4 + 50 + 50 + $INFO[ListItem.Icon] + + + + + 435 + 65 + 25 + 541 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 11 + 11 + 61 + 61 + false + vertical + + + Page Count Label + 450 + 615 + 400 + 20 + font12 + grey + false + right + center + + Window_OpenClose_Animation + + + + \ No newline at end of file diff --git a/addons/skin.confluence/720p/DialogPVRGroupManager.xml b/addons/skin.confluence/720p/DialogPVRGroupManager.xml new file mode 100644 index 0000000000..7c81edec3b --- /dev/null +++ b/addons/skin.confluence/720p/DialogPVRGroupManager.xml @@ -0,0 +1,435 @@ + + 29 + + + !Window.IsVisible(FileBrowser) + WindowOpen + WindowClose + + 130 + 0 + 1150 + 720 + MediaBladeSub.png + + + Close Window button + 180 + 0 + 64 + 32 + + - + PreviousMenu + DialogCloseButton-focus.png + DialogCloseButton.png + 9000 + 9000 + 9000 + 9000 + system.getbool(input.enablemouse) + + + WindowOpen + WindowClose + + header label + 160 + 50 + 1080 + 30 + font24_title + + center + center + white + black + + + Group list + 160 + 90 + + name label + 0 + 0 + 340 + 70 + font13_title + + center + center + blue + + + 0 + 75 + 340 + 460 + button-nofocus.png + + + 5 + 85 + 330 + 440 + 13 + 13 + 29 + 73 + 73 + 200 + + + 0 + 0 + 330 + 41 + MenuItemNF.png + + + 10 + 0 + 310 + 40 + font12 + left + center + grey2 + selected + ListItem.Label + + + + + 0 + 0 + 330 + 41 + !Control.HasFocus(13) + MenuItemNF.png + + + 0 + 0 + 330 + 41 + Control.HasFocus(13) + MenuItemFO.png + + + 10 + 0 + 310 + 40 + font12 + left + center + white + selected + ListItem.Label + + + + + 340 + 75 + 25 + 460 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 13 + 11 + 73 + 73 + false + vertical + + + + Channels list + 525 + 90 + + name label + 0 + 0 + 340 + 70 + font13_title + + center + center + blue + + + 0 + 75 + 340 + 460 + button-nofocus.png + + + 5 + 85 + 330 + 440 + 11 + 11 + 73 + 71 + 71 + 200 + + + 0 + 0 + 330 + 41 + MenuItemNF.png + + + 32 + 32 + 5 + 4 + $INFO[ListItem.Icon] + + + 40 + 0 + 280 + 40 + font12 + left + center + grey2 + selected + + + + + + 0 + 0 + 330 + 41 + !Control.HasFocus(11) + MenuItemNF.png + + + 0 + 0 + 330 + 41 + Control.HasFocus(11) + MenuItemFO.png + + + 32 + 32 + 5 + 4 + $INFO[ListItem.Icon] + + + 40 + 0 + 280 + 40 + font12 + left + center + white + selected + + + + + + 340 + 75 + 25 + 460 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 11 + 12 + 71 + 71 + false + vertical + + + + Grouped Channels list + 890 + 90 + + name label + 0 + 0 + 340 + 70 + font13_title + + center + center + blue + + + 0 + 75 + 340 + 460 + button-nofocus.png + + + 5 + 85 + 330 + 440 + 12 + 12 + 71 + 72 + 72 + 200 + + + 0 + 0 + 330 + 41 + MenuItemNF.png + + + 32 + 32 + 5 + 4 + $INFO[ListItem.Icon] + + + 40 + 0 + 280 + 40 + font12 + left + center + grey2 + selected + + + + + + 0 + 0 + 330 + 41 + !Control.HasFocus(12) + MenuItemNF.png + + + 0 + 0 + 330 + 41 + Control.HasFocus(12) + MenuItemFO.png + + + 32 + 32 + 5 + 4 + $INFO[ListItem.Icon] + + + 40 + 0 + 280 + 40 + font12 + left + center + white + selected + + + + + + 340 + 75 + 25 + 460 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 12 + 26 + 72 + 72 + false + vertical + + + + 160 + 660 + 1080 + 40 + 2 + center + horizontal + 72 + 13 + 9000 + 9000 + + Add Group + 210 + ButtonInfoDialogsCommonValues + + + + Rename Group + 210 + ButtonInfoDialogsCommonValues + + + + Delete Group + 210 + ButtonInfoDialogsCommonValues + + + + OK + 210 + ButtonInfoDialogsCommonValues + + + + + + SideBladeRight + Clock + + + Fake Label used to pass on name label + false + + + \ No newline at end of file diff --git a/addons/skin.confluence/720p/DialogPVRGuideInfo.xml b/addons/skin.confluence/720p/DialogPVRGuideInfo.xml new file mode 100644 index 0000000000..e07c2e26b2 --- /dev/null +++ b/addons/skin.confluence/720p/DialogPVRGuideInfo.xml @@ -0,0 +1,274 @@ + + 7 + + 1 + 20 + 30 + ![Window.IsVisible(FullscreenVideo) | Window.IsVisible(Visualisation)] + + dialogeffect + + + + background image + 0 + 0 + 730 + 660 + EEFFFFFF + DialogBack.png + + + Close Window button + 650 + 9 + 64 + 32 + + - + PreviousMenu + DialogCloseButton-focus.png + DialogCloseButton.png + 2 + 2 + 2 + 2 + system.getbool(input.enablemouse) + + + 80 + 10 + 570 + 90 + stretch + GlassTitleBar.png + + + header label + 40 + 18 + 650 + 30 + font13_title + + center + center + white + black + + + Title label + 40 + 70 + 650 + 30 + font13caps + + center + center + white + black + + + + 40 + 140 + + Start Date + 170 + 0 + 170 + 25 + right + center + font13_title + blue + + + + Start date value + 180 + 0 + 470 + 25 + left + center + font13 + white + + + + Start time + 170 + 35 + 170 + 25 + right + center + font13_title + blue + + + + Start Time value + 180 + 35 + 470 + 25 + left + center + font13 + white + + + + Channel Name + 170 + 70 + 170 + 25 + right + center + font13_title + blue + + + + Channel Value + 180 + 70 + 470 + 25 + left + center + font13 + white + + + + Duration + 170 + 105 + 170 + 25 + right + center + font13_title + blue + + + + Duration value + 180 + 105 + 470 + + left + font13 + true + + + Genre + 170 + 140 + 170 + 25 + right + center + font13_title + blue + + + + Genre value + 180 + 140 + 470 + + left + font13 + true + + + Subtitle value + 0 + 185 + 650 + + center + font13caps + blue + true + !IsEmpty(ListItem.PlotOutline) + + + + 610 + 370 + 400 + 30 + font13_title + grey2 + black + true + right + center + + + + Next page button + 620 + 375 + page + - + 60 + 60 + 9000 + 9000 + - + true + + + PLOT + 40 + 400 + 650 + 180 + font12 + justify + 60 + + + + 40 + 590 + 650 + 40 + 5 + center + horizontal + 9000 + 9000 + 60 + 60 + + Switch to Channel + ButtonInfoDialogsCommonValues + + + + Record + ButtonInfoDialogsCommonValues + + + + OK + ButtonInfoDialogsCommonValues + + + + + + SideBladeRight + + \ No newline at end of file diff --git a/addons/skin.confluence/720p/DialogPVRGuideOSD.xml b/addons/skin.confluence/720p/DialogPVRGuideOSD.xml new file mode 100644 index 0000000000..f6c45afe9a --- /dev/null +++ b/addons/skin.confluence/720p/DialogPVRGuideOSD.xml @@ -0,0 +1,266 @@ + + 11 + + 1 + 780 + 30 + + dialogeffect + + + + background image + 0 + 0 + 480 + 660 + EEFFFFFF + DialogBack.png + + + Close Window button + 400 + 9 + 64 + 32 + + - + PreviousMenu + DialogCloseButton-focus.png + DialogCloseButton.png + 2 + 2 + 2 + 2 + system.getbool(input.enablemouse) + + + 40 + 10 + 430 + 90 + stretch + GlassTitleBar.png + + + header label + 40 + 18 + 430 + 30 + font12_title + + center + center + white + black + + + 40 + 64 + 390 + 541 + 60 + 60 + 11 + 11 + list + 60 + 200 + + + 0 + 0 + 390 + 61 + MenuItemNF.png + VisibleFadeEffect + + + 5 + 0 + 200 + 30 + font13 + blue + selected + left + center + + + + 390 + 0 + 370 + 30 + font12 + grey2 + selected + right + center + + + + 5 + 30 + 390 + 30 + font12 + grey + selected + left + center + + ![ListItem.IsRecording | ListItem.HasTimer] + + + 5 + 30 + 350 + 30 + font12 + grey + selected + left + center + + ListItem.IsRecording | ListItem.HasTimer + + + 360 + 30 + 30 + 20 + PVR-IsRecording.png + ListItem.IsRecording + + + 370 + 30 + 20 + 20 + PVR-HasTimer.png + ListItem.HasTimer + + + + + 0 + 0 + 390 + 61 + MenuItemNF.png + !Control.HasFocus(11) + VisibleFadeEffect + + + 0 + 0 + 390 + 61 + MenuItemFO.png + Control.HasFocus(11) + VisibleFadeEffect + + + 5 + 0 + 200 + 30 + font13 + selected + selected + left + center + + + + 390 + 0 + 370 + 30 + font12 + selected + selected + right + center + + + + 5 + 30 + 390 + 30 + font12 + white + selected + left + center + + ![ListItem.IsRecording | ListItem.HasTimer] + + + 5 + 30 + 350 + 30 + font12 + white + selected + left + center + + ListItem.IsRecording | ListItem.HasTimer + + + 360 + 30 + 30 + 20 + PVR-IsRecording.png + ListItem.IsRecording + + + 370 + 30 + 20 + 20 + PVR-HasTimer.png + ListItem.HasTimer + + + + + 435 + 65 + 25 + 541 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 11 + 11 + 61 + 61 + false + vertical + + + Page Count Label + 450 + 615 + 400 + 20 + font12 + grey + false + right + center + + Window_OpenClose_Animation + + + + diff --git a/addons/skin.confluence/720p/DialogPVRGuideSearch.xml b/addons/skin.confluence/720p/DialogPVRGuideSearch.xml new file mode 100644 index 0000000000..8c7645dcc4 --- /dev/null +++ b/addons/skin.confluence/720p/DialogPVRGuideSearch.xml @@ -0,0 +1,461 @@ + + 9 + + 1 + 210 + 65 + + dialogeffect + + + background image + 0 + 0 + 865 + 605 + DialogBack.png + + + 80 + 10 + 700 + 90 + stretch + GlassTitleBar.png + + + header label + 80 + 18 + 700 + 30 + font13_title + + center + center + white + black + + + Close Window button + 770 + 9 + 64 + 32 + + - + PreviousMenu + DialogCloseButton-focus.png + DialogCloseButton.png + 3 + 3 + 3 + 3 + system.getbool(input.enablemouse) + + + Search string + 30 + 60 + 320 + 40 + font12caps + + left + center + blue + black + + + 30 + 100 + 800 + 50 + stretch + KeyboardEditArea.png + + + Search string + 35 + 105 + 790 + 40 + font13 + white + black + - + - + left + 9 + 9 + 26 + 10 + + + Search help + 30 + 155 + 800 + 70 + left + font12 + grey2 + + + + 30 + 230 + + Include Description + 0 + 0 + 35 + 400 + font12 + white + white + black + button-focus.png + button-nofocus.png + left + center + + 12 + 12 + 9 + 11 + + + Case Sensitive + 0 + 35 + 35 + 400 + font12 + white + white + black + button-focus.png + button-nofocus.png + left + center + + 13 + 13 + 10 + 14 + + + Start Date + 0 + 70 + 400 + 35 + font12 + white + white + black + button-focus.png + button-nofocus.png + + 16 + 16 + 11 + 15 + + + Stop Date + 0 + 105 + 400 + 35 + font12 + white + white + black + button-focus.png + button-nofocus.png + + 17 + 17 + 14 + 18 + + + Genre + 0 + 140 + 400 + 35 + font12 + button-focus.png + button-nofocus.png + + 19 + 19 + 15 + 20 + + + Include unknown Genres + 0 + 175 + 35 + 400 + font12 + white + white + black + left + center + button-focus.png + button-nofocus.png + no + + 21 + 21 + 18 + 22 + + + FTA only + 0 + 210 + 35 + 400 + font12 + white + white + black + left + center + button-focus.png + button-nofocus.png + no + + 23 + 23 + 20 + 24 + + + Ignore Timers + 0 + 245 + 35 + 400 + font12 + white + white + black + left + center + button-focus.png + button-nofocus.png + no + + 27 + 27 + 22 + 26 + + + + 440 + 230 + + Min Duration + 0 + 0 + 400 + 35 + font12 + button-focus.png + button-nofocus.png + + 10 + 10 + 9 + 13 + + + Max Duration + 0 + 35 + 400 + 35 + font12 + button-focus.png + button-nofocus.png + + 11 + 11 + 12 + 16 + + + Start time + 0 + 70 + 400 + 35 + font12 + white + white + black + button-focus.png + button-nofocus.png + + 14 + 14 + 13 + 17 + + + Stop time + 0 + 105 + 400 + 35 + font12 + white + white + black + button-focus.png + button-nofocus.png + + 15 + 15 + 16 + 19 + + + avoid repeats + 0 + 140 + 400 + 35 + font12 + white + white + black + left + center + button-focus.png + button-nofocus.png + no + + 18 + 18 + 17 + 21 + + + Groups + 0 + 175 + 400 + 35 + font12 + button-focus.png + button-nofocus.png + + 20 + 20 + 19 + 23 + + + Channels + 0 + 210 + 400 + 35 + font12 + button-focus.png + button-nofocus.png + + 22 + 22 + 21 + 27 + + + Ignore Recordings + 0 + 245 + 35 + 400 + font12 + white + white + black + left + center + button-focus.png + button-nofocus.png + no + + 24 + 24 + 23 + 26 + + + + 540 + 125 + + Defaults Button + 0 + 0 + 200 + 40 + center + center + MenuItemNF.png + button-focus.png + font12_title + + 26 + 25 + 24 + 9 + + + Cancel Button + 210 + 0 + 200 + 40 + center + center + MenuItemNF.png + button-focus.png + font12_title + + 28 + 26 + 27 + 9 + + + Search Button + 420 + 0 + 200 + 40 + center + center + MenuItemNF.png + button-focus.png + font12_title + + 25 + 28 + 27 + 9 + + + + \ No newline at end of file diff --git a/addons/skin.confluence/720p/DialogPVRRecordingInfo.xml b/addons/skin.confluence/720p/DialogPVRRecordingInfo.xml new file mode 100644 index 0000000000..3f90217b21 --- /dev/null +++ b/addons/skin.confluence/720p/DialogPVRRecordingInfo.xml @@ -0,0 +1,261 @@ + + 10 + + + 580 + WindowOpen + WindowClose + + 0 + 0 + 1100 + 720 + MediaBladeSub.png + + + Close Window button + 20 + 0 + 64 + 32 + + - + PreviousMenu + DialogCloseButton-focus.png + DialogCloseButton.png + 450 + 450 + 450 + 450 + system.getbool(input.enablemouse) + + + media info background image + 0 + 0 + 800 + 720 + VisOsdPanel.png + Window.IsVisible(124) + + + WindowOpen + WindowClose + + header label + 660 + 40 + 630 + 30 + font30_title + + right + center + white + black + + + Title label + 660 + 70 + 630 + 30 + font13caps + + right + center + white + black + + + 0 + 140 + + Start Date + 190 + 0 + 160 + 25 + right + center + font13_title + blue + + + + Start date value + 200 + 0 + 450 + 25 + left + center + font13 + white + + + + Start time + 190 + 35 + 160 + 25 + right + center + font13_title + blue + + + + Start Time value + 200 + 35 + 450 + 25 + left + center + font13 + white + + + + Channel Name + 190 + 70 + 160 + 25 + right + center + font13_title + blue + + + + Channel Value + 200 + 70 + 450 + 25 + left + center + font13 + white + + + + Duration + 190 + 105 + 160 + 25 + right + center + font13_title + blue + + + + Duration value + 200 + 105 + 450 + + left + font13 + true + + + Genre + 190 + 140 + 160 + 25 + right + center + font13_title + blue + + + + Genre value + 200 + 140 + 450 + + left + font13 + true + + + Subtitle value + 40 + 185 + 610 + + center + font13caps + blue + true + !IsEmpty(ListItem.PlotOutline) + + + + 570 + 370 + 400 + 30 + font13_title + grey2 + black + true + right + center + + + + Next page button + 580 + 375 + page + - + 7 + 5 + 9000 + 9000 + - + true + + + PLOT + 40 + 400 + 610 + 220 + font12 + justify + 60 + + + + 40 + 660 + 620 + 40 + 5 + center + horizontal + 60 + 60 + 9000 + 9000 + + OK + ButtonInfoDialogsCommonValues + + + + + + SideBladeRight + + \ No newline at end of file diff --git a/addons/skin.confluence/720p/DialogPVRTimerSettings.xml b/addons/skin.confluence/720p/DialogPVRTimerSettings.xml new file mode 100644 index 0000000000..f34296e7f6 --- /dev/null +++ b/addons/skin.confluence/720p/DialogPVRTimerSettings.xml @@ -0,0 +1,159 @@ + + 29 + + 1 + 240 + 55 + + dialogeffect + + + background image + 0 + 0 + 800 + 620 + DialogBack.png + + + 80 + 10 + 640 + 90 + stretch + GlassTitleBar.png + + + Close Window button + 710 + 9 + 64 + 32 + + - + PreviousMenu + DialogCloseButton-focus.png + DialogCloseButton.png + 2 + 2 + 2 + 2 + system.getbool(input.enablemouse) + + + header label + 20 + 18 + 760 + 30 + font13_title + + center + center + white + black + + + control area + 30 + 80 + 740 + 440 + -1 + 9001 + 9001 + 9001 + 9001 + + + Default Button + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + + + Default RadioButton + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + + + Default spincontrolex + 40 + grey2 + white + MenuItemFO.png + MenuItemNF.png + font13 + center + yes + + + Default Slider + 40 + MenuItemFO.png + MenuItemNF.png + font13 + grey2 + white + + + Default Seperator + 2 + separator2.png + + + Default Edit + 40 + font13 + grey2 + white + MenuItemFO.png + MenuItemNF.png + + + 200 + 550 + + OK Button + 0 + 0 + 200 + 40 + center + center + MenuItemNF.png + button-focus.png + font12_title + + 29 + 29 + 5 + 5 + + + Cancel Button + 200 + 0 + 200 + 40 + center + center + MenuItemNF.png + button-focus.png + font12_title + + 28 + 28 + 5 + 5 + + + + \ No newline at end of file diff --git a/addons/skin.confluence/720p/DialogPVRUpdateProgressBar.xml b/addons/skin.confluence/720p/DialogPVRUpdateProgressBar.xml new file mode 100644 index 0000000000..85d3ba9b50 --- /dev/null +++ b/addons/skin.confluence/720p/DialogPVRUpdateProgressBar.xml @@ -0,0 +1,48 @@ + + + WindowOpen + WindowClose + + + 720 + 0 + conditional + conditional + + 0 + -10 + 400 + 70 + InfoMessagePanel.png + + + Header Label + 15 + 4 + 370 + 18 + font10_title + selected + left + center + + + Title Label + 15 + 20 + 370 + 20 + font10 + left + center + + + progress control + 15 + 42 + 370 + 8 + + + + \ No newline at end of file diff --git a/addons/skin.confluence/720p/Home.xml b/addons/skin.confluence/720p/Home.xml index fc398f028d..efd4636a12 100644 --- a/addons/skin.confluence/720p/Home.xml +++ b/addons/skin.confluence/720p/Home.xml @@ -145,6 +145,158 @@ black + + + 490r + 40 + Container(9000).HasFocus(12) + [PVR.IsRecording | PVR.HasTimer] + + + + + + + + + + + + + + + + + conditional + + background + 0 + 0 + 480 + 135 + DDFFFFFF + OverlayDialogBackground.png + + + !PVR.IsRecording + PVR.HasTimer + + 435 + 15 + 25 + 25 + PVR-HasTimer.png + + + Next Timer Header label + 420 + 15 + 25 + 400 + + right + center + font12_title + blue + black + + + NextRecordingTitle + 460 + 40 + 30 + 440 + + right + center + font13 + black + true + + + NextRecordingChannel + 460 + 70 + 25 + 440 + + right + center + font12 + black + + + NextRecordingDateTime + 460 + 95 + 25 + 440 + + right + center + font12 + black + + + + PVR.IsRecording + + 423 + 15 + 37 + 25 + PVR-IsRecording.png + + + Is Recording Header label + 410 + 15 + 25 + 390 + + right + center + font12_title + blue + black + + + NextRecordingTitle + 460 + 40 + 30 + 440 + + right + center + font13 + black + true + + + NextRecordingChannel + 460 + 70 + 25 + 440 + + right + center + font12 + black + + + NextRecordingDateTime + 460 + 95 + 25 + 440 + + right + center + font12 + black + + + 0 @@ -161,7 +313,7 @@ HomeNowPlayingBack.png - !VideoPlayer.Content(Movies) + !VideoPlayer.Content(Episodes) + !VideoPlayer.Content(Movies) + !VideoPlayer.Content(Episodes) + !VideoPlayer.Content(LiveTV) Cover image 200r @@ -371,6 +523,85 @@ black + + VideoPlayer.Content(LiveTV) + + Cover image + 200r + 0 + 180 + 340 + keep + $INFO[VideoPlayer.Cover] + ThumbBorder.png + 5 + + + NowPlaying label + 210r + 210 + 30 + 660 + + right + center + font12_title + blue + black + + + Channel label + 210r + 235 + 30 + 660 + + right + center + font12_title + white + black + + + Title label + 210r + 260 + 30 + 660 + + right + center + font13_title + white + black + + + Next Label + 210r + 285 + 30 + 660 + + right + center + font12 + grey2 + black + + + Time Label + 210r + 310 + 30 + 325 + + right + center + font12 + white + black + + WindowClose @@ -829,6 +1060,13 @@ $INFO[Skin.String(Home_Custom_Back_Video_Folder)] !Skin.HasSetting(HomeMenuNoVideosButton) + ![Skin.HasSetting(HomeMenuNoMoviesButton) + Skin.HasSetting(HomeMenuNoTVShowsButton)] + + + ActivateWindow(TV) + special://skin/backgrounds/tv.jpg + $INFO[Skin.String(Home_Custom_Back_TV_Folder)] + System.GetBool(pvrmanager.enabled) + ActivateWindow(VideoFiles) diff --git a/addons/skin.confluence/720p/MyTV.xml b/addons/skin.confluence/720p/MyTV.xml new file mode 100644 index 0000000000..e2d21a93ad --- /dev/null +++ b/addons/skin.confluence/720p/MyTV.xml @@ -0,0 +1,2813 @@ + + 32 + no + + CommonTVBackground + + !Control.IsVisible(11) + !Control.IsVisible(12) + + 0 + 0 + 1280 + 720 + special://skin/backgrounds/media-overlay.png + Player.HasVideo + !Skin.HasSetting(ShowBackgroundVideo) + + + 0 + 0 + 1280 + 720 + Player.HasAudio + !Skin.HasSetting(ShowBackgroundVis) + + + 0 + 0 + 1280 + 720 + Player.HasVideo + !Skin.HasSetting(ShowBackgroundVideo) + + + + Window_OpenClose_Animation + + 0 + 0 + 1280 + 720 + black-back.png + VisibleFadeEffect + + + 0 + 128r + 1280 + 128 + floor.png + + + 55 + 60 + 1170 + 600 + ContentPanel.png + + + 55 + 645 + 1170 + 600 + keep + ContentPanel.png + + + + Small Media Window + Control.IsVisible(11) | Control.IsVisible(12) + VisibleFadeEffect + Window_OpenClose_Animation + + 80 + 80 + 710 + 400 + button-nofocus.png + + + 85 + 85 + 700 + 390 + $INFO[Skin.String(Home_Custom_Back_TV_Folder)] + VisibleFadeEffect + !Player.HasVideo + + + 85 + 85 + 700 + 390 + Player.HasVideo + + + + + 40 + 30r + 700 + 20 + + left + center + font12 + grey2 + black + Player.HasMedia + !Control.IsVisible(10) + VisibleFadeEffect + Window_OpenClose_Animation + + + Window_OpenClose_Animation + + TV Guide Channel + Control.IsVisible(15) + VisibleFadeEffect + + 80 + 60 + + Date Time label + 0 + 20 + 300 + 20 + font13_title + white + black + center + center + + + + Title + 300 + 20 + 600 + 20 + font13_title + white + black + center + center + + + + Status header label + 960 + 20 + 140 + 20 + font13_title + white + black + center + center + + + + separator image + 0 + 50 + 1100 + 1 + 88FFFFFF + separator2.png + + + 0 + 55 + 1100 + 520 + 15 + 15 + 31 + 75 + 75 + 200 + + + 0 + 0 + 1100 + 41 + MenuItemNF.png + + + 0 + 0 + 300 + 40 + 33FFFFFF + StackFO.png + + + 960 + 0 + 140 + 40 + 33FFFFFF + StackFO.png + + + 150 + 0 + 280 + 40 + font12 + center + center + grey2 + selected + ListItem.Label2 + + + 310 + 0 + 640 + 40 + font13 + left + center + grey2 + selected + ListItem.Label + + + 970 + 10 + 30 + 20 + PVR-IsRecording.png + ListItem.IsRecording + + + 1005 + 0 + 80 + 40 + font10 + left + center + grey2 + selected + + ListItem.IsRecording + + + 970 + 10 + 20 + 20 + PVR-HasTimer.png + ListItem.HasTimer + + + 1000 + 0 + 80 + 40 + font10 + left + center + grey2 + selected + + ListItem.HasTimer + + + + + 0 + 0 + 1100 + 41 + MenuItemNF.png + + + 0 + 0 + 300 + 40 + 33FFFFFF + StackFO.png + !Control.HasFocus(15) + + + 960 + 0 + 140 + 40 + 33FFFFFF + StackFO.png + !Control.HasFocus(15) + + + 0 + 0 + 300 + 40 + 88FFFFFF + StackFO.png + Control.HasFocus(15) + + + 960 + 0 + 140 + 40 + 88FFFFFF + StackFO.png + Control.HasFocus(15) + + + 150 + 0 + 280 + 40 + font12 + center + center + white + selected + ListItem.Label2 + + + 310 + 0 + 640 + 40 + font13 + left + center + white + selected + ListItem.Label + + + 970 + 10 + 30 + 20 + PVR-IsRecording.png + ListItem.IsRecording + + + 1005 + 0 + 80 + 40 + font10 + left + center + grey2 + selected + + ListItem.IsRecording + + + 970 + 10 + 20 + 20 + PVR-HasTimer.png + ListItem.HasTimer + + + 1000 + 0 + 80 + 40 + font10 + left + center + grey2 + selected + + ListItem.HasTimer + + + + + 1105 + 50 + 25 + 520 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 15 + 31 + 75 + 75 + false + vertical + Control.IsVisible(15) + + + + Page Count Label + 40r + 30r + 500 + 20 + font12 + grey + false + right + center + + + + + TV Guide Now/Next + Control.IsVisible(16) + VisibleFadeEffect + + 80 + 60 + + Time label + 0 + 20 + 100 + 20 + font13_title + white + black + center + center + + + + Channel label + 100 + 20 + 250 + 20 + font13_title + white + black + center + center + + + + Title + 350 + 20 + 550 + 20 + font13_title + white + black + center + center + + + + Status header label + 960 + 20 + 140 + 20 + font13_title + white + black + center + center + + + + separator image + 0 + 50 + 1100 + 1 + 88FFFFFF + separator2.png + + + 0 + 55 + 1100 + 520 + 16 + 16 + 31 + 76 + 76 + 200 + + + 0 + 0 + 1100 + 41 + MenuItemNF.png + + + 100 + 0 + 250 + 40 + 33FFFFFF + StackFO.png + + + 960 + 0 + 140 + 40 + 33FFFFFF + StackFO.png + + + 50 + 0 + 100 + 40 + font12 + center + center + grey2 + selected + ListItem.StartTime + + + 110 + 5 + 30 + 30 + ListItem.Icon + + + 150 + 0 + 190 + 35 + font12 + left + center + grey2 + selected + ListItem.ChannelName + + + 360 + 0 + 590 + 35 + font13 + left + center + grey2 + selected + ListItem.Label + + + 970 + 10 + 30 + 20 + PVR-IsRecording.png + ListItem.IsRecording + + + 1005 + 0 + 80 + 40 + font10 + left + center + grey2 + selected + + ListItem.IsRecording + + + 970 + 10 + 20 + 20 + PVR-HasTimer.png + ListItem.HasTimer + + + 1000 + 0 + 80 + 40 + font10 + left + center + grey2 + selected + + ListItem.HasTimer + + + + + 0 + 0 + 1100 + 41 + MenuItemNF.png + + + 100 + 0 + 250 + 40 + 33FFFFFF + StackFO.png + !Control.HasFocus(16) + + + 960 + 0 + 140 + 40 + 33FFFFFF + StackFO.png + !Control.HasFocus(16) + + + 100 + 0 + 250 + 40 + 88FFFFFF + StackFO.png + Control.HasFocus(16) + + + 960 + 0 + 140 + 40 + 88FFFFFF + StackFO.png + Control.HasFocus(16) + + + 50 + 0 + 100 + 40 + font12 + center + center + grey2 + selected + ListItem.StartTime + + + 110 + 5 + 30 + 30 + ListItem.Icon + + + 150 + 0 + 190 + 35 + font12 + left + center + grey2 + selected + ListItem.ChannelName + + + 360 + 0 + 590 + 35 + font13 + left + center + white + selected + ListItem.Label + + + 970 + 10 + 30 + 20 + PVR-IsRecording.png + ListItem.IsRecording + + + 1005 + 0 + 80 + 40 + font10 + left + center + grey2 + selected + + ListItem.IsRecording + + + 970 + 10 + 20 + 20 + PVR-HasTimer.png + ListItem.HasTimer + + + 1000 + 0 + 80 + 40 + font10 + left + center + grey2 + selected + + ListItem.HasTimer + + + + + 1105 + 50 + 25 + 520 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 16 + 31 + 76 + 76 + false + vertical + Control.IsVisible(16) + + + + Page Count Label + 40r + 30r + 500 + 20 + font12 + grey + false + right + center + + + + + TV Guide Timeline + Control.IsVisible(10) + VisibleFadeEffect + + EPG Grid + 80 + 81 + 1120 + 550 + 10 + 350 + 40 + 6 + 31 + 31 + 10 + 10 + + + 40 + 29 + 0 + 0 + button-nofocus.png + + + 10 + 0 + 34 + 29 + font12 + center + selected + left + + + + + UnFocus + + 0 + 0 + 220 + 52 + button-nofocus.png + + + 5 + 4 + 45 + 44 + $INFO[ListItem.Icon] + + + 54 + 0 + 160 + 52 + special12 + center + selected + left + + + + + OnFocus + + 0 + 0 + 220 + 52 + button-focus.png + + + 5 + 4 + 45 + 44 + $INFO[ListItem.Icon] + + + 54 + 0 + 160 + 52 + special12 + center + selected + left + + + + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),16) + genre-a-moviedrama.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),32) + genre-b-news.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),48) + genre-c-show.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),64) + genre-d-sports.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),80) + genre-e-child.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),96) + genre-f-music.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),112) + genre-g-arts.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),128) + genre-h-social.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),144) + genre-i-science.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),160) + genre-j-hobby.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),176) + genre-k-special.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),0) | stringcompare(ListItem.Property(GenreType),240) + genre-l-unknown.png + + + 6 + 3 + 30 + 25 + font12 + center + selected + left + ListItem.Label + + + 5 + 28 + 30 + 20 + PVR-IsRecording.png + ListItem.IsRecording + + + 5 + 28 + 20 + 20 + PVR-HasTimer.png + ListItem.HasTimer + + + + + 40 + 52 + 0 + 0 + folder-focus.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),16) + genre-a-moviedrama.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),32) + genre-b-news.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),48) + genre-c-show.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),64) + genre-d-sports.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),80) + genre-e-child.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),96) + genre-f-music.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),112) + genre-g-arts.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),128) + genre-h-social.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),144) + genre-i-science.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),160) + genre-j-hobby.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),176) + genre-k-special.png + + + 40 + 52 + 0 + 0 + stringcompare(ListItem.Property(GenreType),0) | stringcompare(ListItem.Property(GenreType),240) + genre-l-unknown.png + + + 6 + 3 + 30 + 25 + font12 + center + selected + left + ListItem.Label + + + 5 + 28 + 30 + 20 + PVR-IsRecording.png + ListItem.IsRecording + + + 5 + 28 + 20 + 20 + PVR-HasTimer.png + ListItem.HasTimer + + + + + 200 + 62r + + 0 + 0 + 15 + 15 + genre-b-news.png + + + News Genre + 20 + 0 + 225 + 15 + font10 + + left + center + white + + + 250 + 0 + 15 + 15 + genre-a-moviedrama.png + + + Movie Genre + 270 + 0 + 225 + 15 + font10 + + left + center + white + + + 500 + 0 + 15 + 15 + genre-k-special.png + + + Special Genre + 520 + 0 + 225 + 15 + font10 + + left + center + white + + + 750 + 0 + 15 + 15 + genre-c-show.png + + + News Genre + 770 + 0 + 225 + 15 + font10 + + left + center + white + + + + 0 + 19 + 15 + 15 + genre-d-sports.png + + + Sports Genre + 20 + 19 + 225 + 15 + font10 + + left + center + white + + + 250 + 19 + 15 + 15 + genre-e-child.png + + + Children Genre + 270 + 19 + 225 + 15 + font10 + + left + center + white + + + 500 + 19 + 15 + 15 + genre-i-science.png + + + Educational/Science Genre + 520 + 19 + 225 + 15 + font10 + + left + center + white + + + 750 + 19 + 15 + 15 + genre-f-music.png + + + Music Genre + 770 + 19 + 225 + 15 + font10 + + left + center + white + + + + 0 + 38 + 15 + 15 + genre-g-arts.png + + + Arts Genre + 20 + 38 + 225 + 15 + font10 + + left + center + white + + + 250 + 38 + 15 + 15 + genre-j-hobby.png + + + Leisure/Hobbies Genre + 270 + 38 + 225 + 15 + font10 + + left + center + white + + + 500 + 38 + 15 + 15 + genre-h-social.png + + + Social Genre + 520 + 38 + 225 + 15 + font10 + + left + center + white + + + 750 + 38 + 15 + 15 + genre-l-unknown.png + + + Other/Unknown Genre + 770 + 38 + 225 + 15 + font10 + + left + center + white + + + + + + + TV Channels group + Control.IsVisible(11) + VisibleFadeEffect + + 85 + 490 + + 0 + 0 + 700 + 30 + font13caps + white + black + true + center + center + + + + Plot Value for TVShow + 0 + 30 + 700 + 90 + font12 + justify + white + black + - + + true + + + + 800 + 85 + 390 + 541 + 32 + 70 + 11 + 11 + list + 70 + 200 + + + 0 + 0 + 390 + 61 + MenuItemNF.png + VisibleFadeEffect + + + 5 + -4 + 40 + 35 + font10 + left + center + grey + grey + ListItem.ChannelNumber + + + 50 + 0 + 270 + 30 + font13 + white + selected + left + center + + + + 50 + 30 + 330 + 30 + font12 + grey + grey + left + center + IsEmpty(Listitem.Icon) + + + + 50 + 30 + 270 + 30 + font12 + grey + grey + left + center + !IsEmpty(Listitem.Icon) + + + + 330 + 4 + 50 + 50 + $INFO[ListItem.Icon] + + + + + 0 + 0 + 390 + 61 + MenuItemNF.png + !Control.HasFocus(11) + VisibleFadeEffect + + + 0 + 0 + 390 + 61 + MenuItemFO.png + Control.HasFocus(11) + VisibleFadeEffect + + + 5 + -4 + 40 + 35 + font10 + left + center + grey + grey + ListItem.ChannelNumber + + + 50 + 0 + 270 + 30 + font13 + white + selected + left + center + + + + 50 + 30 + 330 + 30 + font12 + grey + grey + left + center + IsEmpty(Listitem.Icon) + + + + 50 + 30 + 270 + 30 + font12 + grey + grey + left + center + !IsEmpty(Listitem.Icon) + + + + 330 + 4 + 50 + 50 + $INFO[ListItem.Icon] + + + + + 1190 + 85 + 25 + 540 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 11 + 32 + 70 + 70 + false + vertical + Control.IsVisible(11) + + + Page Count Label + 40r + 30r + 500 + 20 + font12 + grey + false + right + center + + Window_OpenClose_Animation + + + + + Radio Channels group + Control.IsVisible(12) + VisibleFadeEffect + + 85 + 490 + + 0 + 0 + 700 + 30 + font13caps + white + black + true + center + center + + + + Plot Value for TVShow + 0 + 30 + 700 + 90 + font12 + justify + white + black + - + + true + + + + 800 + 85 + 390 + 541 + 33 + 71 + 12 + 12 + list + 71 + 200 + + + 0 + 0 + 390 + 61 + MenuItemNF.png + VisibleFadeEffect + + + 5 + -4 + 40 + 35 + font10 + left + center + grey + grey + ListItem.ChannelNumber + + + 50 + 0 + 270 + 30 + font13 + white + selected + left + center + + + + 50 + 30 + 330 + 30 + font12 + grey + grey + left + center + IsEmpty(Listitem.Icon) + + + + 50 + 30 + 270 + 30 + font12 + grey + grey + left + center + !IsEmpty(Listitem.Icon) + + + + 330 + 4 + 50 + 50 + $INFO[ListItem.Icon] + + + + + 0 + 0 + 390 + 61 + MenuItemNF.png + !Control.HasFocus(12) + VisibleFadeEffect + + + 0 + 0 + 390 + 61 + MenuItemFO.png + Control.HasFocus(12) + VisibleFadeEffect + + + 5 + -4 + 40 + 35 + font10 + left + center + grey + grey + ListItem.ChannelNumber + + + 50 + 0 + 270 + 30 + font13 + white + selected + left + center + + + + 50 + 30 + 330 + 30 + font12 + grey + grey + left + center + IsEmpty(Listitem.Icon) + + + + 50 + 30 + 270 + 30 + font12 + grey + grey + left + center + !IsEmpty(Listitem.Icon) + + + + 330 + 4 + 50 + 50 + $INFO[ListItem.Icon] + + + + + 1190 + 85 + 25 + 540 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 11 + 32 + 71 + 71 + false + vertical + Control.IsVisible(12) + + + Page Count Label + 40r + 30r + 500 + 20 + font12 + grey + false + right + center + + Window_OpenClose_Animation + + + + + Recordings group + Control.IsVisible(13) + VisibleFadeEffect + + 490 + 100 + 700 + 521 + 34 + 72 + 13 + 13 + list + 72 + 200 + + + 0 + 0 + 700 + 41 + MenuItemNF.png + VisibleFadeEffect + + + 10 + 5 + 30 + 30 + $INFO[ListItem.Icon] + + + 50 + 0 + 630 + 40 + font13 + grey2 + selected + left + center + + + + 690 + 0 + 500 + 40 + font12 + grey2 + selected + right + center + + + + + + 0 + 0 + 700 + 41 + MenuItemFO.png + Control.HasFocus(13) + VisibleFadeEffect + + + 0 + 0 + 700 + 41 + MenuItemNF.png + VisibleFadeEffect + !Control.HasFocus(13) + + + 500 + 5 + 200 + 31 + MediaItemDetailBG.png + Control.HasFocus(13) + !IsEmpty(ListItem.Date) + + + 10 + 5 + 30 + 30 + $INFO[ListItem.Icon] + + + 50 + 0 + 630 + 40 + font13 + white + selected + left + center + + + + 690 + 0 + 500 + 40 + font12 + grey2 + selected + right + center + + + + + + 1190 + 100 + 25 + 521 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 12 + 34 + 72 + 72 + false + vertical + Control.IsVisible(13) + + + Page Count Label + 40r + 30r + 500 + 20 + font12 + grey + false + right + center + + Window_OpenClose_Animation + + + 80 + 100 + + 0 + 0 + 380 + 270 + keep + IconCrossfadeTime + $INFO[Container(13).ListItem.Icon] + ThumbShadow.png + 8 + + + 0 + 263 + 380 + 80 + stretch + GlassTitleBar.png + AAFFFFFF + + + 0 + 290 + 380 + 25 + + true + center + center + font24_title + white + black + + + Plot Value for TVShow + 0 + 330 + 380 + 190 + font12 + justify + white + black + 9999999999 + + true + + + + + + Timers group + Control.IsVisible(14) + VisibleFadeEffect + + 80 + 60 + + Title header label + 0 + 20 + 300 + 20 + font13_title + white + black + center + center + + + + Schedule Time header label + 300 + 20 + 600 + 20 + font13_title + white + black + center + center + + + + Status header label + 900 + 20 + 200 + 20 + font13_title + white + black + center + center + + + + separator image + 0 + 50 + 1100 + 1 + 88FFFFFF + separator2.png + + + 0 + 55 + 1100 + 480 + 14 + 14 + 35 + 73 + 73 + 200 + + + 0 + 0 + 1100 + 41 + MenuItemNF.png + + + 0 + 0 + 300 + 40 + 33FFFFFF + StackFO.png + + + 900 + 0 + 200 + 40 + 33FFFFFF + StackFO.png + + + 150 + 0 + 290 + 40 + font12 + center + center + selected + ListItem.Label + + + 600 + 0 + 590 + 40 + font12 + center + center + selected + ListItem.Date + + + 1000 + 0 + 190 + 40 + font12 + center + center + selected + ListItem.Comment + + + + + 0 + 0 + 1100 + 41 + MenuItemNF.png + + + 0 + 0 + 300 + 40 + 33FFFFFF + StackFO.png + !Control.HasFocus(14) + + + 900 + 0 + 200 + 40 + 33FFFFFF + StackFO.png + !Control.HasFocus(14) + + + 0 + 0 + 300 + 40 + 88FFFFFF + StackFO.png + Control.HasFocus(14) + + + 900 + 0 + 200 + 40 + 88FFFFFF + StackFO.png + Control.HasFocus(14) + + + 150 + 0 + 290 + 40 + font12 + center + center + selected + ListItem.Label + + + 600 + 0 + 590 + 40 + font12 + center + center + selected + ListItem.Date + + + 1000 + 0 + 190 + 40 + font12 + center + center + selected + ListItem.Comment + + + + + 1105 + 50 + 25 + 480 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 11 + 35 + 73 + 73 + false + vertical + Control.IsVisible(14) + + + separator image + 55 + 540 + 1010 + 1 + 88FFFFFF + separator2.png + + + Next timer date + 55 + 545 + 1010 + 30 + font13 + center + center + true + white + + PVR.HasTimer + + + + Page Count Label + 40r + 30r + 500 + 20 + font12 + grey + false + right + center + + Window_OpenClose_Animation + + + + + TV Search group + Control.IsVisible(17) + VisibleFadeEffect + + 80 + 60 + + Time label + 0 + 20 + 300 + 20 + font13_title + white + black + center + center + + + + Channel label + 300 + 20 + 250 + 20 + font13_title + white + black + center + center + + + + Title + 550 + 20 + 350 + 20 + font13_title + white + black + center + center + + + + Status header label + 960 + 20 + 140 + 20 + font13_title + white + black + center + center + + + + separator image + 0 + 50 + 1100 + 1 + 88FFFFFF + separator2.png + + + 0 + 55 + 1100 + 520 + 17 + 17 + 36 + 77 + 77 + 200 + + + 0 + 0 + 1100 + 41 + MenuItemNF.png + + + 300 + 0 + 250 + 40 + 33FFFFFF + StackFO.png + + + 960 + 0 + 140 + 40 + 33FFFFFF + StackFO.png + + + 150 + 0 + 300 + 40 + font12 + center + center + grey2 + selected + ListItem.Date + + + 310 + 5 + 30 + 30 + ListItem.Icon + + + 350 + 0 + 190 + 35 + font12 + left + center + grey2 + selected + ListItem.ChannelName + + + 560 + 0 + 390 + 35 + font13 + left + center + grey2 + selected + ListItem.Label + + + 970 + 10 + 30 + 20 + PVR-IsRecording.png + ListItem.IsRecording + + + 1005 + 0 + 80 + 40 + font10 + left + center + grey2 + selected + + ListItem.IsRecording + + + 970 + 10 + 20 + 20 + PVR-HasTimer.png + ListItem.HasTimer + + + 1000 + 0 + 80 + 40 + font10 + left + center + grey2 + selected + + ListItem.HasTimer + + + + + 0 + 0 + 1100 + 41 + MenuItemNF.png + + + 300 + 0 + 250 + 40 + 33FFFFFF + StackFO.png + !Control.HasFocus(17) + + + 960 + 0 + 140 + 40 + 33FFFFFF + StackFO.png + !Control.HasFocus(17) + + + 300 + 0 + 250 + 40 + 88FFFFFF + StackFO.png + Control.HasFocus(17) + + + 960 + 0 + 140 + 40 + 88FFFFFF + StackFO.png + Control.HasFocus(17) + + + 150 + 0 + 300 + 40 + font12 + center + center + grey2 + selected + ListItem.Date + + + 310 + 5 + 30 + 30 + ListItem.Icon + + + 350 + 0 + 190 + 35 + font12 + left + center + grey2 + selected + ListItem.ChannelName + + + 560 + 0 + 390 + 35 + font13 + left + center + white + selected + ListItem.Label + + + 970 + 10 + 30 + 20 + PVR-IsRecording.png + ListItem.IsRecording + + + 1005 + 0 + 80 + 40 + font10 + left + center + grey2 + selected + + ListItem.IsRecording + + + 970 + 10 + 20 + 20 + PVR-HasTimer.png + ListItem.HasTimer + + + 1000 + 0 + 80 + 40 + font10 + left + center + grey2 + selected + + ListItem.HasTimer + + + + + 1105 + 50 + 25 + 520 + ScrollBarV.png + ScrollBarV_bar.png + ScrollBarV_bar_focus.png + ScrollBarNib.png + ScrollBarNib.png + 16 + 31 + 77 + 77 + false + vertical + Control.IsVisible(17) + + + + Page Count Label + 40r + 30r + 500 + 20 + font12 + grey + false + right + center + + + + + + 0 + 0 + 1280 + 720 + black-back.png + Visible + Hidden + Window.IsActive(FileBrowser) | Window.IsActive(601) | Window.IsActive(602) | Window.IsActive(603) | Window.IsActive(604) | Window.IsActive(606) + + + Conditional + WindowOpen + WindowClose + + Fake button for mouse control + -250 + 0 + 265 + 720 + + - + - + - + true + + SideBladeLeft + + LOGO + -230 + 50 + 220 + 80 + keep + Confluence_Logo.png + + + -250 + 130 + CommonNowPlaying + + TV Channels + 0 + 0 + ButtonCommonValues + + 11 + 11 + 611 + 33 + + + Radio Channels + 0 + 40 + ButtonCommonValues + + 12 + 12 + 32 + 31 + + + TV Guide + 0 + 80 + ButtonCommonValues + + 10 + 10 + 33 + 34 + + + Recordings + 0 + 120 + ButtonCommonValues + + 13 + 13 + 31 + 35 + + + Timers + 0 + 160 + ButtonCommonValues + + 14 + 14 + 34 + 36 + + + Search + 0 + 200 + ButtonCommonValues + + 17 + 17 + 35 + 610 + + + Fake Button to fix Player Controls Navigation + 36 + 603 + false + + + 0 + 260 + CommonNowPlaying_Controls + + + Fake Button to fix Player Controls Navigation + 608 + 32 + false + + + + + 440 + 0 + !IsEmpty(Control.GetLabel(30)) + [Control.IsVisible(10) | Control.IsVisible(11) | Control.IsVisible(12) | Control.IsVisible(15) | Control.IsVisible(16)] + VisibleFadeEffect + WindowClose + WindowOpen + + 0 + 0 + 250 + 35 + header.png + + + WindowTitleCommons + 220 + + + + 240 + 0 + WindowClose + WindowOpen + Conditional + + 0 + 0 + 250 + 35 + header.png + + + WindowTitleCommons + 220 + + + + 60 + 0 + WindowClose + WindowOpen + + 0 + 0 + 250 + 35 + header.png + + + WindowTitleCommons + 220 + + + + WindowTitleHomeButton + Clock + + \ No newline at end of file diff --git a/addons/skin.confluence/720p/PlayerControls.xml b/addons/skin.confluence/720p/PlayerControls.xml index fedacee00f..55596136bd 100644 --- a/addons/skin.confluence/720p/PlayerControls.xml +++ b/addons/skin.confluence/720p/PlayerControls.xml @@ -1,5 +1,5 @@ - 603 + 100 dialogeffect Player.HasMedia + Window.IsActive(PlayerControls) + !Window.IsActive(FullscreenVideo) + !Window.IsActive(Visualisation) @@ -18,6 +18,8 @@ 25 162 + 603 + !VideoPlayer.Content(LiveTV) 0 0 @@ -31,6 +33,7 @@ 300 200 XBMC.PlayerControl(Previous) + !VideoPlayer.Content(LiveTV) 40 @@ -45,6 +48,7 @@ 300 200 XBMC.PlayerControl(Rewind) + !VideoPlayer.Content(LiveTV) 80 @@ -62,6 +66,7 @@ 300 200 XBMC.PlayerControl(Play) + !VideoPlayer.Content(LiveTV) 120 @@ -77,6 +82,7 @@ 200 down XBMC.PlayerControl(Stop) + !VideoPlayer.Content(LiveTV) 160 @@ -91,6 +97,7 @@ 300 200 XBMC.PlayerControl(Forward) + !VideoPlayer.Content(LiveTV) 200 @@ -105,6 +112,7 @@ 300 200 XBMC.PlayerControl(Next) + !VideoPlayer.Content(LiveTV) 240 @@ -121,6 +129,7 @@ XBMC.PlayerControl(record) Player.CanRecord Conditional + !VideoPlayer.Content(LiveTV) 365 @@ -135,6 +144,7 @@ 608 100 100 + !VideoPlayer.Content(LiveTV) 365 @@ -144,6 +154,7 @@ OSDRepeatNF.png !Playlist.IsRepeat + !Playlist.IsRepeatOne !Control.HasFocus(607) + !VideoPlayer.Content(LiveTV) 365 @@ -153,6 +164,7 @@ OSDRepeatFO.png !Playlist.IsRepeat + !Playlist.IsRepeatOne Control.HasFocus(607) + !VideoPlayer.Content(LiveTV) 365 @@ -162,6 +174,7 @@ OSDRepeatOneNF.png Playlist.IsRepeatOne !Control.HasFocus(607) + !VideoPlayer.Content(LiveTV) 365 @@ -171,6 +184,7 @@ OSDRepeatOneFO.png Playlist.IsRepeatOne Control.HasFocus(607) + !VideoPlayer.Content(LiveTV) 365 @@ -180,6 +194,7 @@ OSDRepeatAllNF.png Playlist.IsRepeat !Control.HasFocus(607) + !VideoPlayer.Content(LiveTV) 365 @@ -189,6 +204,7 @@ OSDRepeatAllFO.png Playlist.IsRepeat Control.HasFocus(607) + !VideoPlayer.Content(LiveTV) 405 @@ -206,6 +222,130 @@ 600 100 100 + !VideoPlayer.Content(LiveTV) + + + + 25 + 162 + 700 + VideoPlayer.Content(LiveTV) + + 0 + 0 + 40 + 40 + + OSDRewindFO.png + OSDRewindNF.png + 706 + 702 + 300 + 200 + XBMC.PlayerControl(Rewind) + VideoPlayer.Content(LiveTV) + false + Conditional + + + 40 + 0 + 40 + 40 + + OSDStopFO.png + OSDStopNF.png + 701 + 703 + 300 + 200 + down + XBMC.PlayerControl(Stop) + VideoPlayer.Content(LiveTV) + + + 80 + 0 + 40 + 40 + + OSDPauseFO.png + OSDPauseNF.png + Player.Paused | Player.Forwarding | Player.Rewinding + OSDPlayFO.png + OSDPlayNF.png + 702 + 704 + 300 + 200 + XBMC.PlayerControl(Play) + VideoPlayer.Content(LiveTV) + false + Conditional + + + 120 + 0 + 40 + 40 + + OSDForwardFO.png + OSDForwardNF.png + 703 + 700 + 300 + 200 + XBMC.PlayerControl(Forward) + VideoPlayer.Content(LiveTV) + false + Conditional + + + 200 + 0 + 40 + 40 + + OSDChannelUPFO.png + OSDChannelUPNF.png + 704 + 705 + 300 + 200 + XBMC.PlayerControl(Previous) + VideoPlayer.Content(LiveTV) + + + 240 + 0 + 40 + 40 + + OSDChannelDownFO.png + OSDChannelDownNF.png + 700 + 706 + 300 + 200 + XBMC.PlayerControl(Next) + VideoPlayer.Content(LiveTV) + + + 280 + 0 + 40 + 40 + + OSDRecordFO.png + OSDRecordNF.png + 705 + 701 + 300 + 200 + XBMC.PlayerControl(record) + Player.CanRecord + Conditional + VideoPlayer.Content(LiveTV) @@ -229,4 +369,4 @@ SmallVideoInfo - \ No newline at end of file + diff --git a/addons/skin.confluence/720p/Settings.xml b/addons/skin.confluence/720p/Settings.xml index 42cf51c652..bb19989038 100644 --- a/addons/skin.confluence/720p/Settings.xml +++ b/addons/skin.confluence/720p/Settings.xml @@ -96,42 +96,48 @@ special://skin/backgrounds/videos.jpg + + 31409 + ActivateWindow(MyTVSettings) + special://skin/backgrounds/tv.jpg + + 31402 ActivateWindow(MusicSettings) special://skin/backgrounds/music.jpg - + 31403 ActivateWindow(PicturesSettings) special://skin/backgrounds/pictures.jpg - + 31404 ActivateWindow(WeatherSettings) special://skin/backgrounds/weather.jpg - + 31408 ActivateWindow(AddonBrowser) special://skin/backgrounds/addons.jpg - + 31405 ActivateWindow(NetworkSettings) special://skin/backgrounds/network.jpg - + 31406 ActivateWindow(SystemSettings) special://skin/backgrounds/system.jpg - + 31407 ActivateWindow(1111) diff --git a/addons/skin.confluence/720p/SettingsSystemInfo.xml b/addons/skin.confluence/720p/SettingsSystemInfo.xml index 9c5e570f4d..9a14617ec6 100644 --- a/addons/skin.confluence/720p/SettingsSystemInfo.xml +++ b/addons/skin.confluence/720p/SettingsSystemInfo.xml @@ -146,6 +146,21 @@ false + + Button PVR + 60 + 241 + 0 + right + center + font13_title + grey2 + white + MenuItemFO.png + MenuItemNF.png + false + + 268 diff --git a/addons/skin.confluence/720p/VideoFullScreen.xml b/addons/skin.confluence/720p/VideoFullScreen.xml index 4fd8089f42..20746fd339 100644 --- a/addons/skin.confluence/720p/VideoFullScreen.xml +++ b/addons/skin.confluence/720p/VideoFullScreen.xml @@ -37,5 +37,252 @@ font12 VisibleFadeEffect + + 440 + 100 + 400 + 100 + font13caps + TV Channel Group Select Button + OverlayDialogBackground.png + 503 + 503 + 500 + 500 + VisibleFadeEffect + + + Player.ShowCodec + VideoPlayer.Content(LiveTV) + system.getbool(pvrplayback.signalquality) + 160 + + media info background image + 0 + 0 + 1280 + 220 + black-back.png + + + Header + 50 + 5 + 1200 + 25 + + left + center + font13_title + blue + + + Backend + 50 + 40 + 165 + 25 + + left + center + font12 + grey2 + + + Backend value + 220 + 40 + 1000 + 25 + + left + center + font12 + white + + + Device + 50 + 65 + 165 + 25 + + left + center + font12 + grey2 + + + Device value + 220 + 65 + 1000 + 25 + + left + center + font12 + white + + + Status + 50 + 90 + 165 + 25 + + left + center + font12 + grey2 + + + Status value + 220 + 90 + 1000 + 25 + + left + center + font12 + white + + + Signal + 50 + 115 + 165 + 25 + + left + center + font12 + grey2 + + + Progressbar + 220 + 122 + 910 + 14 + PVR.ActStreamProgrSignal + + + Signal value + 1200 + 115 + 180 + 25 + + left + center + font12 + white + + + SNR + 50 + 140 + 165 + 25 + + left + center + font12 + grey2 + + + Progressbar + 220 + 147 + 910 + 14 + - + PVR.ActStreamProgrSNR + + + SNR value + 1200 + 140 + 180 + 25 + + left + center + font12 + white + + + BER + 50 + 165 + 165 + 25 + + left + center + font12 + grey2 + + + BER value + 220 + 165 + 1000 + 25 + + left + center + font12 + white + + + UNC + 430 + 165 + 165 + 25 + + left + center + font12 + grey2 + + + UNC value + 600 + 165 + 1000 + 25 + + left + center + font12 + white + + + Encryption + 50 + 190 + 165 + 25 + + left + center + font12 + grey2 + + + Encryption value + 220 + 190 + 1000 + 25 + + left + center + font12 + white + + - \ No newline at end of file + diff --git a/addons/skin.confluence/720p/VideoOSD.xml b/addons/skin.confluence/720p/VideoOSD.xml index 6709ad5057..33878c954a 100644 --- a/addons/skin.confluence/720p/VideoOSD.xml +++ b/addons/skin.confluence/720p/VideoOSD.xml @@ -4,7 +4,9 @@ WindowOpen WindowClose - Conditional + Conditional + + media info background image 0 @@ -13,6 +15,9 @@ 256 MediaInfoBackUpper.png + + + 417 4 @@ -30,6 +35,7 @@ 600 600 ActivateWindow(125) + !VideoPlayer.Content(LiveTV) 50 @@ -45,6 +51,7 @@ 600 600 ActivateWindow(124) + !VideoPlayer.Content(LiveTV) 100 @@ -60,8 +67,51 @@ 601 601 ActivateWindow(123) + !VideoPlayer.Content(LiveTV) + !VideoPlayer.Content(LiveTV) + + + 417 + 4 + + 0 + 0 + 45 + 45 + + - + OSDAudioFO.png + OSDAudioNF.png + 705 + 701 + 100 + 100 + ActivateWindow(124) + VideoPlayer.Content(LiveTV) + + + 50 + 0 + 45 + 45 + + - + OSDVideoFO.png + OSDVideoNF.png + 700 + 702 + 100 + 100 + ActivateWindow(123) + VideoPlayer.Content(LiveTV) + + VideoPlayer.Content(LiveTV) + + + + separator image 567 @@ -70,7 +120,22 @@ 1 66FFFFFF separator2.png + !VideoPlayer.Content(LiveTV) + + + + separator image + 517 + 25 + 200 + 1 + 66FFFFFF + separator2.png + VideoPlayer.Content(LiveTV) + + + 718 4 @@ -91,6 +156,7 @@ XBMC.RunScript($INFO[Skin.String(SubtitleScript_Path)]) Skin.HasSetting(SubtitleDownload_Enable) + !IsEmpty(Skin.String(SubtitleScript_Path)) Conditional + !VideoPlayer.Content(LiveTV) 50 @@ -108,6 +174,7 @@ PlayerControl(ShowVideoMenu) VideoPlayer.HasMenu Conditional + !VideoPlayer.Content(LiveTV) 100 @@ -125,8 +192,68 @@ XBMC.PlayerControl(record) Player.CanRecord Conditional + !VideoPlayer.Content(LiveTV) + !VideoPlayer.Content(LiveTV) + + + 718 + 4 + + 0 + 0 + 45 + 45 + + - + OSDChannelListFO.png + OSDChannelListNF.png + 702 + 704 + 602 + 602 + ActivateWindow(PVROSDChannels) + Dialog.Close(VideoOSD) + VideoPlayer.Content(LiveTV) + + + 50 + 0 + 45 + 45 + + - + OSDepgFO.png + OSDepgNF.png + 703 + 705 + 605 + 605 + ActivateWindow(PVROSDGuide) + VideoPlayer.Content(LiveTV) + + + 100 + 0 + 45 + 45 + + - + OSDTeleTextFO.png + OSDTeleTextNF.png + 704 + 700 + 606 + 606 + ActivateWindow(Teletext) + VideoPlayer.Content(LiveTV) + + VideoPlayer.Content(LiveTV) + + + + 490 40 @@ -144,6 +271,7 @@ 701 701 PlayerControl(Previous) + !VideoPlayer.Content(LiveTV) 50 @@ -159,6 +287,7 @@ 702 702 PlayerControl(Rewind) + !VideoPlayer.Content(LiveTV) 100 @@ -178,6 +307,7 @@ 702 702 PlayerControl(Play) + !VideoPlayer.Content(LiveTV) 150 @@ -193,6 +323,7 @@ 703 703 PlayerControl(Stop) + !VideoPlayer.Content(LiveTV) 200 @@ -208,6 +339,7 @@ 703 703 PlayerControl(Forward) + !VideoPlayer.Content(LiveTV) 250 @@ -223,8 +355,159 @@ 704 704 PlayerControl(Next) + !VideoPlayer.Content(LiveTV) + + !VideoPlayer.Content(LiveTV) + + + + 440 + 40 + + 0 + 0 + 50 + 50 + + - + OSDRewindFO.png + OSDRewindNF.png + 606 + 601 + 700 + 700 + PlayerControl(Rewind) + false + Conditional + VideoPlayer.Content(LiveTV) + + + 50 + 0 + 50 + 50 + + 208 + - + OSDPauseFO.png + OSDPauseNF.png + Player.Paused | Player.Forwarding | Player.Rewinding + OSDPlayFO.png + OSDPlayNF.png + 600 + 603 + 700 + 700 + PlayerControl(Play) + false + Conditional + VideoPlayer.Content(LiveTV) + + + 100 + 0 + 50 + 50 + + - + OSDStopFO.png + OSDStopNF.png + 601 + 604 + 700 + 700 + PlayerControl(Stop) + VideoPlayer.Content(LiveTV) + + + 150 + 0 + 50 + 50 + + - + OSDForwardFO.png + OSDForwardNF.png + 603 + 602 + 70 + 700 + PlayerControl(Forward) + false + Conditional + VideoPlayer.Content(LiveTV) + + + 250 + 0 + 50 + 50 + + - + OSDChannelUPFO.png + OSDChannelUPNF.png + 604 + 605 + 703 + 703 + PlayerControl(Previous) + VideoPlayer.Content(LiveTV) + + + 300 + 0 + 50 + 50 + + - + OSDChannelDownFO.png + OSDChannelDownNF.png + 602 + 606 + 704 + 704 + PlayerControl(Next) + VideoPlayer.Content(LiveTV) + + + 350 + 0 + 50 + 50 + + - + OSDRecordFO.png + OSDRecordNF.png + 605 + 600 + 705 + 705 + XBMC.PlayerControl(record) + Player.CanRecord + Conditional + VideoPlayer.Content(LiveTV) + Player.CanRecord + !Player.Recording + + + 350 + 0 + 50 + 50 + + - + OSDRecord2.png + OSDRecordNF2.png + 605 + 600 + 705 + 705 + XBMC.PlayerControl(record) + Conditional + VideoPlayer.Content(LiveTV) + Player.CanRecord + Player.Recording + VideoPlayer.Content(LiveTV) + + 20 60 @@ -239,6 +522,8 @@ black Window.IsTopmost(VideoOSD) + + 1260 60 @@ -252,6 +537,8 @@ grey black + + 0 0 @@ -270,7 +557,10 @@ + + Clock - \ No newline at end of file + + diff --git a/addons/skin.confluence/720p/custom_SkinSetting_1111.xml b/addons/skin.confluence/720p/custom_SkinSetting_1111.xml index 28304a0006..9fdaf7bb56 100644 --- a/addons/skin.confluence/720p/custom_SkinSetting_1111.xml +++ b/addons/skin.confluence/720p/custom_SkinSetting_1111.xml @@ -693,6 +693,13 @@ special://skin/backgrounds/videos.jpg $INFO[Skin.String(Home_Custom_Back_Video_Folder)] + + + - + special://skin/backgrounds/tv.jpg + $INFO[Skin.String(Home_Custom_Back_TV_Folder)] + System.GetBool(pvrmanager.enabled) + - @@ -1295,6 +1302,66 @@ 304 + + Container(9003).HasFocus(12) + + Single Image button + 5 + 0 + 180 + 40 + + font12_title + grey2 + white + center + button-nofocus.png + button-focus.png + Skin.SetImage(Home_Custom_Back_TV_Folder) + 302 + 301 + 304 + 304 + + + Multi Image button + 190 + 0 + 180 + 40 + + font12_title + grey2 + white + center + button-nofocus.png + button-focus.png + Skin.SetPath(Home_Custom_Back_TV_Folder) + 300 + 302 + 304 + 304 + + + Default Image button + 375 + 0 + 180 + 40 + + font12_title + grey2 + white + center + button-nofocus.png + button-focus.png + Skin.Reset(Home_Custom_Back_TV_Folder) + 301 + 300 + 304 + 304 + + 95 diff --git a/addons/skin.confluence/720p/defaults.xml b/addons/skin.confluence/720p/defaults.xml index 7d2ca4ad69..3c495b7549 100644 --- a/addons/skin.confluence/720p/defaults.xml +++ b/addons/skin.confluence/720p/defaults.xml @@ -132,6 +132,7 @@ grey3 7 center + no 490 diff --git a/addons/skin.confluence/720p/includes.xml b/addons/skin.confluence/720p/includes.xml index db9cf9b555..fbf9d704f6 100644 --- a/addons/skin.confluence/720p/includes.xml +++ b/addons/skin.confluence/720p/includes.xml @@ -70,6 +70,18 @@ [Container.Content(TVShows) | Container.Content(Seasons) |Container.Content(Episodes)] + [!IsEmpty(Skin.String(Home_Custom_Back_TVShow_Folder) + Skin.HasSetting(HomeMenuNoTVShowsButton)]) + + + 0 + 0 + 1280 + 720 + $INFO[Skin.String(Home_Custom_Back_TV_Folder)] + 10000 + true + 1000 + + 0 @@ -417,7 +429,7 @@ font12_title grey2 black - !videoplayer.content(episodes) + !videoplayer.content(musicvideos) + !videoplayer.content(episodes) + !videoplayer.content(musicvideos) + !videoplayer.content(LiveTV) TV Show Title label @@ -449,6 +461,22 @@ false 2000 + + Channel label + 160 + 20 + 30 + 325 + + left + center + font12_title + white + black + videoplayer.content(LiveTV) + false + 2000 + Title label 160 diff --git a/addons/skin.confluence/backgrounds/tv.jpg b/addons/skin.confluence/backgrounds/tv.jpg new file mode 100644 index 0000000000000000000000000000000000000000..94146905632f4c4e0404fb535ee3bda71e546fa8 GIT binary patch literal 576461 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
EK(65bO&S#VqTjWx2O;sqQSQNKpq{1{#vm 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
5UJ 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 z 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%0D~RMvz@? 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 z%0D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 z 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
ZS>qlxA5Yw9UA5-nQ; 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 literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/language/English/strings.xml b/addons/skin.confluence/language/English/strings.xml index 3514889c06..9277be8fcc 100644 --- a/addons/skin.confluence/language/English/strings.xml +++ b/addons/skin.confluence/language/English/strings.xml @@ -151,6 +151,20 @@ [B]CONFIGURE SYSTEM SETTINGS[/B][CR][CR]Setup and calibrate displays · Configure audio output · Setup remote controls[CR]Set power saving options · Enable debugging · Setup master lock [B]CONFIGURE SKIN SETTINGS[/B][CR][CR]Setup the Confluence skin · Add and remove home menu items[CR]Change skin backgrounds [B]CONFIGURE ADD-ONS[/B][CR][CR]Manage your installed Add-ons · Browse for and install Add-ons from xbmc.org[CR]Modify Add-on settings - - Select your XBMC user Profile[CR]to login and continue - + [B]CONFIGURE TV SETTINGS[/B][CR][CR]Change fullscreen info · Manage EPG data settings + + Select your XBMC user Profile[CR]to login and continue + + + Recording Timers + Scheduled Time + Live TV + Add Group + Rename Group + Delete Group + Available[CR]Groups + Ungrouped[CR]Channels + Channels in Group + Channel Group + Timer Set + diff --git a/addons/skin.confluence/language/German/strings.xml b/addons/skin.confluence/language/German/strings.xml index c7955bae45..005d2d92f3 100644 --- a/addons/skin.confluence/language/German/strings.xml +++ b/addons/skin.confluence/language/German/strings.xml @@ -138,6 +138,21 @@ [B]KONFIGURIERE NETZWERK EINSTELLUNGEN[/B][CR][CR]Einrichten der Steuerung von XBMC via UPnP und HTTP · Konfiguriere Datei Zugriff[CR]Setze Internet Zugriffs Optionen [B]KONFIGURIERE SYSTEM EINSTELLUNGEN[/B][CR][CR]Setze und kalibriere Displays · Konfiguriere Audioausgabe · Setze Fernbedienungs Einstellungen · Setze Energiespar Optionen · Aktiviere Debugging · Einrichten Master Sperre [B]KONFIGURIERE SKIN EINSTELLUNGEN[/B][CR][CR]Einrichten des Confluence Skin · Hinzufügen und entfernen der Home Menü Einträge[CR]Wechseln der Skin Hintergründe - [B]KONFIGURIERE ADD-ONS[/B][CR][CR]Organisiere die installierten Add-ons · Installiere Add-ons von xbmc.org[CR]Add-on Einstellungen anpassen + [B]KONFIGURIERE ERWEITERUNGEN[/B][CR][CR]Organisiere die installierten Add-ons · Installiere Add-ons von xbmc.org[CR]Add-on Einstellungen anpassen + [B]KONFIGURIERE TV EINSTELLUNGEN[/B][CR][CR]Verändere Vollbild Modus · Verwalte EPG Daten Einstellungen + Wähle Dein XBMC Benutzer Profil[CR]Zum Anmelden und Weitermachen + + + Recording Timers + Scheduled Time + Live TV + Gruppe hinzufügen + Gruppe umbenennen + Gruppe löschen + Verfügbare[CR]Gruppen + Ungruppierte[CR]Kanäle + Kanäle in Gruppe + Kanalgruppe + Timer Set diff --git a/addons/skin.confluence/media/Makefile b/addons/skin.confluence/media/Makefile index 4ee7af34d1..03d37436ac 100644 --- a/addons/skin.confluence/media/Makefile +++ b/addons/skin.confluence/media/Makefile @@ -259,6 +259,7 @@ IMAGES= \ OSDRandomOnFO.png \ OSDRandomOnNF.png \ OSDRecord2.png \ + OSDRecordNF2.png \ OSDRecordFO.png \ OSDRecordNF.png \ OSDRecordOff.png \ diff --git a/addons/skin.confluence/media/OSDChannelDownFO.png b/addons/skin.confluence/media/OSDChannelDownFO.png new file mode 100644 index 0000000000000000000000000000000000000000..5116c327ba5f1f9dabccd5586036159f58bac1b2 GIT binary patch literal 4693 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.ActivateWindow(MusicOSD) XBMC.ActivateWindow(MusicOSD) Info + XBMC.ActivateWindow(PVROSDGuide) + XBMC.ActivateWindow(PVROSDChannels) @@ -365,6 +375,15 @@ PreviousMenu + + + Red + Green + Yellow + Blue + PreviousMenu + + PreviousMenu @@ -508,4 +527,37 @@ Close + + + Close + Close + Close + Close + + + + + Close + Close + Close + Close + + + + + Close + Close + + + + + Close + Close + + + + + PreviousMenu + + diff --git a/system/playercorefactory.xml b/system/playercorefactory.xml index 66784de36f..6fc8c2085b 100644 --- a/system/playercorefactory.xml +++ b/system/playercorefactory.xml @@ -33,5 +33,8 @@ + + + diff --git a/tools/Linux/packaging/debian/xbmc-bin.install b/tools/Linux/packaging/debian/xbmc-bin.install index cddb90a986..a170bd7a60 100644 --- a/tools/Linux/packaging/debian/xbmc-bin.install +++ b/tools/Linux/packaging/debian/xbmc-bin.install @@ -1 +1,2 @@ usr/lib/xbmc +usr/share/xbmc/addons diff --git a/xbmc/AddonDatabase.cpp b/xbmc/AddonDatabase.cpp index 69de4d0ba4..3426ec8601 100644 --- a/xbmc/AddonDatabase.cpp +++ b/xbmc/AddonDatabase.cpp @@ -75,6 +75,10 @@ bool CAddonDatabase::CreateTables() m_pDS->exec("CREATE TABLE disabled (id integer primary key, addonID text)\n"); m_pDS->exec("CREATE UNIQUE INDEX idxDisabled ON disabled(addonID)"); + CLog::Log(LOGINFO, "create pvrenabled table"); + m_pDS->exec("CREATE TABLE pvrenabled (id integer primary key, addonID text)\n"); + m_pDS->exec("CREATE UNIQUE INDEX idxPVREnabled ON pvrenabled(addonID)"); + CLog::Log(LOGINFO, "create broken table"); m_pDS->exec("CREATE TABLE broken (id integer primary key, addonID text, reason text)\n"); m_pDS->exec("CREATE UNIQUE INDEX idxBroken ON broken(addonID)"); @@ -150,6 +154,11 @@ bool CAddonDatabase::UpdateOldVersion(int version) { m_pDS->exec("alter table addon add disclaimer text"); } + if (version < 13) + { + m_pDS->exec("CREATE TABLE pvrenabled (id integer primary key, addonID text)\n"); + m_pDS->exec("CREATE INDEX idxPVREnabled ON pvrenabled(addonID)"); + } } catch (...) { @@ -596,6 +605,40 @@ bool CAddonDatabase::DisableAddon(const CStdString &addonID, bool disable /* = t return false; } +bool CAddonDatabase::EnableSystemPVRAddon(const CStdString &addonID, bool enable) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + if (enable) + { + CStdString sql = PrepareSQL("select id from pvrenabled where addonID='%s'", addonID.c_str()); + m_pDS->query(sql.c_str()); + if (m_pDS->eof()) // not found + { + m_pDS->close(); + sql = PrepareSQL("insert into pvrenabled(id, addonID) values(NULL, '%s')", addonID.c_str()); + m_pDS->exec(sql); + return true; + } + return false; // already enabled or failed query + } + else + { + CStdString sql = PrepareSQL("delete from pvrenabled where addonID='%s'", addonID.c_str()); + m_pDS->exec(sql); + } + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed on addon '%s'", __FUNCTION__, addonID.c_str()); + } + return false; +} + bool CAddonDatabase::BreakAddon(const CStdString &addonID, bool broken /* = true */, const CStdString& reason) { try @@ -650,6 +693,26 @@ bool CAddonDatabase::IsAddonDisabled(const CStdString &addonID) return false; } +bool CAddonDatabase::IsSystemPVRAddonEnabled(const CStdString &addonID) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString sql = PrepareSQL("select id from pvrenabled where addonID='%s'", addonID.c_str()); + m_pDS->query(sql.c_str()); + bool ret = !m_pDS->eof(); // in the pvrenabled table -> enabled + m_pDS->close(); + return ret; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed on addon %s", __FUNCTION__, addonID.c_str()); + } + return false; +} + CStdString CAddonDatabase::IsAddonBroken(const CStdString &addonID) { try diff --git a/xbmc/AddonDatabase.h b/xbmc/AddonDatabase.h index 5fd182c85e..deb557d25f 100644 --- a/xbmc/AddonDatabase.h +++ b/xbmc/AddonDatabase.h @@ -57,9 +57,11 @@ public: \sa SetRepoTimestamp */ CDateTime GetRepoTimestamp(const CStdString& id); + bool GetSystemEnabled(const CStdString& id); + bool SetSystemEnabled(const CStdString& id, bool enabled); bool Search(const CStdString& search, ADDON::VECADDONS& items); bool SearchTitle(const CStdString& strSearch, ADDON::VECADDONS& items); - static void SetPropertiesFromAddon(const ADDON::AddonPtr& addon, CFileItemPtr& item); + static void SetPropertiesFromAddon(const ADDON::AddonPtr& addon, CFileItemPtr& item); /*! \brief Disable an addon. Sets a flag that this addon has been disabled. If disabled, it is usually still available on disk. @@ -80,11 +82,25 @@ public: \sa DisableAddon, IsAddonDisabled */ bool HasDisabledAddons(); + /*! \brief Enable an system PVR addon. + Sets a flag that this PVR addon has been enabled. If disabled, it is usually still available on disk. + \param addonID id of the addon to enable + \param disable whether to enable or disable. Defaults to false (disable) + \return true on success, false on failure + \sa IsSystemPVRAddonEnabled */ + bool EnableSystemPVRAddon(const CStdString &addonID, bool enable = false); + + /*! \brief Check whether an system PVR addon has been enabled via EnableSystemPVRAddon. + \param addonID id of the addon to check + \return true if the addon is disabled, false otherwise + \sa EnableSystemPVRAddon */ + bool IsSystemPVRAddonEnabled(const CStdString &addonID); + /*! \brief Mark an addon as broken - Sets a flag that this addon has been marked as broken in the repository. + Sets a flag that this addon has been marked as broken in the repository. \param addonID id of the addon to mark as broken \param broken whether to mark or not. Defaults to true - \param reason why it is broken. Defaults to blank + \param reason why it is broken. Defaults to blank \return true on success, false on failure \sa IsAddonBroken */ bool BreakAddon(const CStdString &addonID, bool broken = true, const CStdString& reason=""); @@ -98,7 +114,7 @@ public: protected: virtual bool CreateTables(); virtual bool UpdateOldVersion(int version); - virtual int GetMinVersion() const { return 12; } + virtual int GetMinVersion() const { return 13; } const char *GetDefaultDBName() const { return "Addons"; } }; diff --git a/xbmc/AdvancedSettings.cpp b/xbmc/AdvancedSettings.cpp index d132e9d89b..70a27935c3 100644 --- a/xbmc/AdvancedSettings.cpp +++ b/xbmc/AdvancedSettings.cpp @@ -270,6 +270,9 @@ void CAdvancedSettings::Initialize() m_bgInfoLoaderMaxThreads = 5; + m_bDisableEPGTimeCorrection = false; + m_iUserDefinedEPGTimeCorrection = 0; + m_measureRefreshrate = false; m_cacheMemBufferSize = (1048576 * 5); @@ -753,6 +756,9 @@ bool CAdvancedSettings::Load() XMLUtils::GetInt(pRootElement, "bginfoloadermaxthreads", m_bgInfoLoaderMaxThreads); m_bgInfoLoaderMaxThreads = std::max(1, m_bgInfoLoaderMaxThreads); + XMLUtils::GetBoolean(pRootElement, "noepgtimecorrection", m_bDisableEPGTimeCorrection); + XMLUtils::GetInt(pRootElement, "userepgtimecorrection", m_iUserDefinedEPGTimeCorrection, 0, 1440); + XMLUtils::GetBoolean(pRootElement, "measurerefreshrate", m_measureRefreshrate); TiXmlElement* pDatabase = pRootElement->FirstChildElement("videodatabase"); @@ -778,6 +784,17 @@ bool CAdvancedSettings::Load() XMLUtils::GetString(pDatabase, "name", m_databaseMusic.name); } + pDatabase = pRootElement->FirstChildElement("tvdatabase"); + if (pDatabase) + { + XMLUtils::GetString(pDatabase, "type", m_databaseTV.type); + XMLUtils::GetString(pDatabase, "host", m_databaseTV.host); + XMLUtils::GetString(pDatabase, "port", m_databaseTV.port); + XMLUtils::GetString(pDatabase, "user", m_databaseTV.user); + XMLUtils::GetString(pDatabase, "pass", m_databaseTV.pass); + XMLUtils::GetString(pDatabase, "name", m_databaseTV.name); + } + // load in the GUISettings overrides: g_guiSettings.LoadXML(pRootElement, true); // true to hide the settings we read in diff --git a/xbmc/AdvancedSettings.h b/xbmc/AdvancedSettings.h index 019f7d9ffb..ae181edafc 100644 --- a/xbmc/AdvancedSettings.h +++ b/xbmc/AdvancedSettings.h @@ -264,11 +264,16 @@ class CAdvancedSettings CStdString m_gpuTempCmd; int m_bgInfoLoaderMaxThreads; + /* PVR/TV related advanced settings */ + bool m_bDisableEPGTimeCorrection; + int m_iUserDefinedEPGTimeCorrection; + bool m_measureRefreshrate; //when true the videoreferenceclock will measure the refreshrate when direct3d is used //otherwise it will use the windows refreshrate DatabaseSettings m_databaseMusic; // advanced music database setup DatabaseSettings m_databaseVideo; // advanced video database setup + DatabaseSettings m_databaseTV; // advanced tv database setup unsigned int m_cacheMemBufferSize; }; diff --git a/xbmc/Application.cpp b/xbmc/Application.cpp index ab51b89e94..5625c22387 100644 --- a/xbmc/Application.cpp +++ b/xbmc/Application.cpp @@ -217,8 +217,24 @@ #include "GUIDialogAccessPoints.h" #endif #include "GUIDialogFullScreenInfo.h" -#include "GUIDialogTeletext.h" #include "GUIDialogSlider.h" + +/* PVR related include Files */ +#include "PVRManager.h" +#include "GUIWindowTV.h" +#include "GUIDialogPVRChannelManager.h" +#include "GUIDialogPVRChannelsOSD.h" +#include "GUIDialogPVRCutterOSD.h" +#include "GUIDialogPVRDirectorOSD.h" +#include "GUIDialogPVRGroupManager.h" +#include "GUIDialogPVRGuideInfo.h" +#include "GUIDialogPVRGuideOSD.h" +#include "GUIDialogPVRGuideSearch.h" +#include "GUIDialogPVRRecordingInfo.h" +#include "GUIDialogPVRTimerSettings.h" +#include "GUIDialogPVRUpdateProgressBar.h" +#include "GUIDialogTeletext.h" + #include "GUIControlFactory.h" #include "cores/dlgcache.h" @@ -1013,7 +1029,6 @@ bool CApplication::Initialize() #ifdef HAS_DX g_windowManager.Add(new CGUIWindowTestPatternDX); // window id = 8 #endif - g_windowManager.Add(new CGUIDialogTeletext); // window id = g_windowManager.Add(new CGUIWindowSettingsScreenCalibration); // window id = 11 g_windowManager.Add(new CGUIWindowSettingsCategory); // window id = 12 slideshow:window id 2007 g_windowManager.Add(new CGUIWindowVideoNav); // window id = 36 @@ -1060,7 +1075,6 @@ bool CApplication::Initialize() #ifdef HAS_LINUX_NETWORK g_windowManager.Add(new CGUIDialogAccessPoints); // window id = 141 #endif - g_windowManager.Add(new CGUIDialogLockSettings); // window id = 131 g_windowManager.Add(new CGUIDialogContentSettings); // window id = 132 @@ -1070,6 +1084,21 @@ bool CApplication::Initialize() g_windowManager.Add(new CGUIWindowMusicNav); // window id = 502 g_windowManager.Add(new CGUIWindowMusicPlaylistEditor); // window id = 503 + /* Load PVR related Windows and Dialogs */ + g_windowManager.Add(new CGUIWindowTV); // window id = 600 + g_windowManager.Add(new CGUIDialogPVRGuideInfo); // window id = 601 + g_windowManager.Add(new CGUIDialogPVRRecordingInfo); // window id = 602 + g_windowManager.Add(new CGUIDialogPVRTimerSettings); // window id = 603 + g_windowManager.Add(new CGUIDialogPVRGroupManager); // window id = 604 + g_windowManager.Add(new CGUIDialogPVRChannelManager); // window id = 605 + g_windowManager.Add(new CGUIDialogPVRGuideSearch); // window id = 606 + g_windowManager.Add(new CGUIDialogPVRUpdateProgressBar); // window id = 608 + g_windowManager.Add(new CGUIDialogPVRChannelsOSD); // window id = 609 + g_windowManager.Add(new CGUIDialogPVRGuideOSD); // window id = 610 + g_windowManager.Add(new CGUIDialogPVRDirectorOSD); // window id = 611 + g_windowManager.Add(new CGUIDialogPVRCutterOSD); // window id = 612 + g_windowManager.Add(new CGUIDialogTeletext); // window id = 613 + g_windowManager.Add(new CGUIDialogSelect); // window id = 2000 g_windowManager.Add(new CGUIWindowMusicInfo); // window id = 2001 g_windowManager.Add(new CGUIDialogOK); // window id = 2002 @@ -1102,6 +1131,8 @@ bool CApplication::Initialize() } } + StartPVRManager(); + SAFE_DELETE(m_splash); if (g_guiSettings.GetBool("masterlock.startuplock") && @@ -1417,6 +1448,21 @@ void CApplication::StopZeroconf() #endif } +void CApplication::StartPVRManager() +{ + if (g_guiSettings.GetBool("pvrmanager.enabled")) + { + CLog::Log(LOGINFO, "starting PVRManager"); + g_PVRManager.Start(); + } +} + +void CApplication::StopPVRManager() +{ + CLog::Log(LOGINFO, "stopping PVRManager"); + g_PVRManager.Stop(); +} + void CApplication::DimLCDOnPlayback(bool dim) { #ifdef HAS_LCD @@ -2490,81 +2536,84 @@ bool CApplication::OnAction(const CAction &action) if ( IsPlaying()) { - // pause : pauses current audio song - if (action.GetID() == ACTION_PAUSE && m_iPlaySpeed == 1) + if (!CurrentFileItem().IsLiveTV()) { - m_pPlayer->Pause(); + // pause : pauses current audio song + if (action.GetID() == ACTION_PAUSE && m_iPlaySpeed == 1) + { + m_pPlayer->Pause(); #ifdef HAS_KARAOKE - m_pKaraokeMgr->SetPaused( m_pPlayer->IsPaused() ); + m_pKaraokeMgr->SetPaused( m_pPlayer->IsPaused() ); #endif - if (!m_pPlayer->IsPaused()) - { // unpaused - set the playspeed back to normal - SetPlaySpeed(1); - } - g_audioManager.Enable(m_pPlayer->IsPaused() && !g_audioContext.IsPassthroughActive()); - return true; - } - if (!m_pPlayer->IsPaused()) - { - // if we do a FF/RW in my music then map PLAY action togo back to normal speed - // if we are playing at normal speed, then allow play to pause - if (action.GetID() == ACTION_PLAYER_PLAY || action.GetID() == ACTION_PAUSE) - { - if (m_iPlaySpeed != 1) - { + if (!m_pPlayer->IsPaused()) + { // unpaused - set the playspeed back to normal SetPlaySpeed(1); } - else - { - m_pPlayer->Pause(); - } + g_audioManager.Enable(m_pPlayer->IsPaused() && !g_audioContext.IsPassthroughActive()); return true; } - if (action.GetID() == ACTION_PLAYER_FORWARD || action.GetID() == ACTION_PLAYER_REWIND) + if (!m_pPlayer->IsPaused()) { - int iPlaySpeed = m_iPlaySpeed; - if (action.GetID() == ACTION_PLAYER_REWIND && iPlaySpeed == 1) // Enables Rewinding - iPlaySpeed *= -2; - else if (action.GetID() == ACTION_PLAYER_REWIND && iPlaySpeed > 1) //goes down a notch if you're FFing - iPlaySpeed /= 2; - else if (action.GetID() == ACTION_PLAYER_FORWARD && iPlaySpeed < 1) //goes up a notch if you're RWing - iPlaySpeed /= 2; - else - iPlaySpeed *= 2; + // if we do a FF/RW in my music then map PLAY action togo back to normal speed + // if we are playing at normal speed, then allow play to pause + if (action.GetID() == ACTION_PLAYER_PLAY || action.GetID() == ACTION_PAUSE) + { + if (m_iPlaySpeed != 1) + { + SetPlaySpeed(1); + } + else + { + m_pPlayer->Pause(); + } + return true; + } + if (action.GetID() == ACTION_PLAYER_FORWARD || action.GetID() == ACTION_PLAYER_REWIND) + { + int iPlaySpeed = m_iPlaySpeed; + if (action.GetID() == ACTION_PLAYER_REWIND && iPlaySpeed == 1) // Enables Rewinding + iPlaySpeed *= -2; + else if (action.GetID() == ACTION_PLAYER_REWIND && iPlaySpeed > 1) //goes down a notch if you're FFing + iPlaySpeed /= 2; + else if (action.GetID() == ACTION_PLAYER_FORWARD && iPlaySpeed < 1) //goes up a notch if you're RWing + iPlaySpeed /= 2; + else + iPlaySpeed *= 2; - if (action.GetID() == ACTION_PLAYER_FORWARD && iPlaySpeed == -1) //sets iSpeed back to 1 if -1 (didn't plan for a -1) - iPlaySpeed = 1; - if (iPlaySpeed > 32 || iPlaySpeed < -32) - iPlaySpeed = 1; + if (action.GetID() == ACTION_PLAYER_FORWARD && iPlaySpeed == -1) //sets iSpeed back to 1 if -1 (didn't plan for a -1) + iPlaySpeed = 1; + if (iPlaySpeed > 32 || iPlaySpeed < -32) + iPlaySpeed = 1; - SetPlaySpeed(iPlaySpeed); - return true; - } - else if ((action.GetAmount() || GetPlaySpeed() != 1) && (action.GetID() == ACTION_ANALOG_REWIND || action.GetID() == ACTION_ANALOG_FORWARD)) - { - // calculate the speed based on the amount the button is held down - int iPower = (int)(action.GetAmount() * MAX_FFWD_SPEED + 0.5f); - // returns 0 -> MAX_FFWD_SPEED - int iSpeed = 1 << iPower; - if (iSpeed != 1 && action.GetID() == ACTION_ANALOG_REWIND) - iSpeed = -iSpeed; - g_application.SetPlaySpeed(iSpeed); - if (iSpeed == 1) - CLog::Log(LOGDEBUG,"Resetting playspeed"); - return true; + SetPlaySpeed(iPlaySpeed); + return true; + } + else if ((action.GetAmount() || GetPlaySpeed() != 1) && (action.GetID() == ACTION_ANALOG_REWIND || action.GetID() == ACTION_ANALOG_FORWARD)) + { + // calculate the speed based on the amount the button is held down + int iPower = (int)(action.GetAmount() * MAX_FFWD_SPEED + 0.5f); + // returns 0 -> MAX_FFWD_SPEED + int iSpeed = 1 << iPower; + if (iSpeed != 1 && action.GetID() == ACTION_ANALOG_REWIND) + iSpeed = -iSpeed; + g_application.SetPlaySpeed(iSpeed); + if (iSpeed == 1) + CLog::Log(LOGDEBUG,"Resetting playspeed"); + return true; + } } - } - // allow play to unpause - else - { - if (action.GetID() == ACTION_PLAYER_PLAY) + // allow play to unpause + else { - // unpause, and set the playspeed back to normal - m_pPlayer->Pause(); - g_audioManager.Enable(m_pPlayer->IsPaused() && !g_audioContext.IsPassthroughActive()); + if (action.GetID() == ACTION_PLAYER_PLAY) + { + // unpause, and set the playspeed back to normal + m_pPlayer->Pause(); + g_audioManager.Enable(m_pPlayer->IsPaused() && !g_audioContext.IsPassthroughActive()); - g_application.SetPlaySpeed(1); - return true; + g_application.SetPlaySpeed(1); + return true; + } } } } @@ -3144,6 +3193,20 @@ bool CApplication::Cleanup() g_windowManager.Delete(WINDOW_DIALOG_ACCESS_POINTS); g_windowManager.Delete(WINDOW_DIALOG_SLIDER); + /* Delete PVR related windows and dialogs */ + g_windowManager.Delete(WINDOW_TV); + g_windowManager.Delete(WINDOW_DIALOG_PVR_GUIDE_INFO); + g_windowManager.Delete(WINDOW_DIALOG_PVR_RECORDING_INFO); + g_windowManager.Delete(WINDOW_DIALOG_PVR_TIMER_SETTING); + g_windowManager.Delete(WINDOW_DIALOG_PVR_GROUP_MANAGER); + g_windowManager.Delete(WINDOW_DIALOG_PVR_CHANNEL_MANAGER); + g_windowManager.Delete(WINDOW_DIALOG_PVR_GUIDE_SEARCH); + g_windowManager.Delete(WINDOW_DIALOG_PVR_CHANNEL_SCAN); + g_windowManager.Delete(WINDOW_DIALOG_PVR_UPDATE_PROGRESS); + g_windowManager.Delete(WINDOW_DIALOG_PVR_OSD_CHANNELS); + g_windowManager.Delete(WINDOW_DIALOG_PVR_OSD_GUIDE); + g_windowManager.Delete(WINDOW_DIALOG_PVR_OSD_DIRECTOR); + g_windowManager.Delete(WINDOW_DIALOG_PVR_OSD_CUTTER); g_windowManager.Delete(WINDOW_DIALOG_OSD_TELETEXT); g_windowManager.Delete(WINDOW_DIALOG_TEXT_VIEWER); @@ -3162,6 +3225,7 @@ bool CApplication::Cleanup() g_windowManager.Delete(WINDOW_MUSIC_OVERLAY); g_windowManager.Delete(WINDOW_VIDEO_OVERLAY); g_windowManager.Delete(WINDOW_SLIDESHOW); + g_windowManager.Delete(WINDOW_ADDON_BROWSER); g_windowManager.Delete(WINDOW_HOME); g_windowManager.Delete(WINDOW_PROGRAMS); @@ -3176,6 +3240,7 @@ bool CApplication::Cleanup() g_windowManager.Remove(WINDOW_SETTINGS_MYVIDEOS); g_windowManager.Remove(WINDOW_SETTINGS_NETWORK); g_windowManager.Remove(WINDOW_SETTINGS_APPEARANCE); + g_windowManager.Remove(WINDOW_SETTINGS_MYTV); g_windowManager.Remove(WINDOW_DIALOG_KAI_TOAST); g_windowManager.Remove(WINDOW_DIALOG_SEEK_BAR); @@ -3291,6 +3356,7 @@ void CApplication::Stop() m_applicationMessenger.Cleanup(); + StopPVRManager(); StopServices(); //Sleep(5000); @@ -4304,7 +4370,7 @@ void CApplication::ActivateScreenSaver(bool forceType /*= false */) if (!forceType) { // set to Dim in the case of a dialog on screen or playing video - if (g_windowManager.HasModalDialog() || (IsPlayingVideo() && g_guiSettings.GetBool("screensaver.usedimonpause"))) + if (g_windowManager.HasModalDialog() || (IsPlayingVideo() && g_guiSettings.GetBool("screensaver.usedimonpause")) || g_PVRManager.ChannelScanRunning()) { if (!CAddonMgr::Get().GetAddon("screensaver.xbmc.builtin.dim", m_screenSaver)) m_screenSaver.reset(new CScreenSaver("")); diff --git a/xbmc/Application.h b/xbmc/Application.h index bceff12cdf..4fe97792b0 100644 --- a/xbmc/Application.h +++ b/xbmc/Application.h @@ -115,6 +115,8 @@ public: void StopUPnPRenderer(); void StartUPnPServer(); void StopUPnPServer(); + void StartPVRManager(); + void StopPVRManager(); void StartEventServer(); bool StopEventServer(bool bWait, bool promptuser); void RefreshEventServer(); diff --git a/xbmc/ApplicationMessenger.cpp b/xbmc/ApplicationMessenger.cpp index 0a8a4fb333..f34a05f8eb 100644 --- a/xbmc/ApplicationMessenger.cpp +++ b/xbmc/ApplicationMessenger.cpp @@ -55,6 +55,7 @@ #include "SingleLock.h" #include "lib/libPython/xbmcmodule/GUIPythonWindowDialog.h" #include "lib/libPython/xbmcmodule/GUIPythonWindowXMLDialog.h" +#include "addons/AddonHelpers_GUI.h" #ifdef HAS_HTTPAPI #include "lib/libhttpapi/XBMChttp.h" @@ -586,6 +587,15 @@ case TMSG_POWERDOWN: } break; + case TMSG_GUI_ADDON_DIALOG: + { + if (pMsg->lpVoid) + { // TODO: This is ugly - really these python dialogs should just be normal XBMC dialogs + ((ADDON::CGUIAddonWindowDialog *)pMsg->lpVoid)->Show_Internal(pMsg->dwParam2 > 0); + } + } + break; + case TMSG_GUI_PYTHON_DIALOG: { if (pMsg->lpVoid) diff --git a/xbmc/ApplicationMessenger.h b/xbmc/ApplicationMessenger.h index ac1c7f053d..2247b79f67 100644 --- a/xbmc/ApplicationMessenger.h +++ b/xbmc/ApplicationMessenger.h @@ -78,8 +78,9 @@ class CGUIDialog; #define TMSG_GUI_PYTHON_DIALOG 605 #define TMSG_GUI_DIALOG_CLOSE 606 #define TMSG_GUI_ACTION 607 -#define TMSG_GUI_INFOLABEL 608 -#define TMSG_GUI_INFOBOOL 609 +#define TMSG_GUI_ADDON_DIALOG 608 +#define TMSG_GUI_INFOLABEL 609 +#define TMSG_GUI_INFOBOOL 610 #define TMSG_OPTICAL_MOUNT 700 #define TMSG_OPTICAL_UNMOUNT 701 diff --git a/xbmc/ButtonTranslator.cpp b/xbmc/ButtonTranslator.cpp index 28a581e400..2b9ba3920d 100644 --- a/xbmc/ButtonTranslator.cpp +++ b/xbmc/ButtonTranslator.cpp @@ -201,6 +201,7 @@ static const ActionMapping windows[] = {"music" , WINDOW_MUSIC}, {"video" , WINDOW_VIDEOS}, {"videos" , WINDOW_VIDEOS}, // backward compat + {"tv" , WINDOW_TV}, {"systeminfo" , WINDOW_SYSTEM_INFORMATION}, {"testpattern" , WINDOW_TEST_PATTERN}, {"screencalibration" , WINDOW_SCREEN_CALIBRATION}, @@ -213,6 +214,7 @@ static const ActionMapping windows[] = {"videossettings" , WINDOW_SETTINGS_MYVIDEOS}, {"networksettings" , WINDOW_SETTINGS_NETWORK}, {"appearancesettings" , WINDOW_SETTINGS_APPEARANCE}, + {"tvsettings" , WINDOW_SETTINGS_MYTV}, {"scripts" , WINDOW_PROGRAMS}, // backward compat {"videofiles" , WINDOW_VIDEO_FILES}, {"videolibrary" , WINDOW_VIDEO_NAV}, @@ -225,6 +227,10 @@ static const ActionMapping windows[] = {"virtualkeyboard" , WINDOW_DIALOG_KEYBOARD}, {"volumebar" , WINDOW_DIALOG_VOLUME_BAR}, {"submenu" , WINDOW_DIALOG_SUB_MENU}, + {"pvrosdchannels" , WINDOW_DIALOG_PVR_OSD_CHANNELS}, + {"pvrosdguide" , WINDOW_DIALOG_PVR_OSD_GUIDE}, + {"pvrosddirector" , WINDOW_DIALOG_PVR_OSD_DIRECTOR}, + {"pvrosdcutter" , WINDOW_DIALOG_PVR_OSD_CUTTER}, {"favourites" , WINDOW_DIALOG_FAVOURITES}, {"contextmenu" , WINDOW_DIALOG_CONTEXT_MENU}, {"infodialog" , WINDOW_DIALOG_KAI_TOAST}, @@ -1044,13 +1050,15 @@ uint32_t CButtonTranslator::TranslateRemoteString(const char *szButton) else if (strButton.Equals("pageminus")) buttonCode = XINPUT_IR_REMOTE_CHANNEL_MINUS; else if (strButton.Equals("mute")) buttonCode = XINPUT_IR_REMOTE_MUTE; else if (strButton.Equals("recordedtv")) buttonCode = XINPUT_IR_REMOTE_RECORDED_TV; - else if (strButton.Equals("guide")) buttonCode = XINPUT_IR_REMOTE_TITLE; // same as title + else if (strButton.Equals("guide")) buttonCode = XINPUT_IR_REMOTE_GUIDE; else if (strButton.Equals("livetv")) buttonCode = XINPUT_IR_REMOTE_LIVE_TV; else if (strButton.Equals("star")) buttonCode = XINPUT_IR_REMOTE_STAR; else if (strButton.Equals("hash")) buttonCode = XINPUT_IR_REMOTE_HASH; else if (strButton.Equals("clear")) buttonCode = XINPUT_IR_REMOTE_CLEAR; else if (strButton.Equals("enter")) buttonCode = XINPUT_IR_REMOTE_ENTER; else if (strButton.Equals("xbox")) buttonCode = XINPUT_IR_REMOTE_DISPLAY; // same as display + else if (strButton.Equals("playlist")) buttonCode = XINPUT_IR_REMOTE_PLAYLIST; + else if (strButton.Equals("guide")) buttonCode = XINPUT_IR_REMOTE_GUIDE; else if (strButton.Equals("teletext")) buttonCode = XINPUT_IR_REMOTE_TELETEXT; else if (strButton.Equals("red")) buttonCode = XINPUT_IR_REMOTE_RED; else if (strButton.Equals("green")) buttonCode = XINPUT_IR_REMOTE_GREEN; diff --git a/xbmc/DateTime.cpp b/xbmc/DateTime.cpp index 5ff12759dd..4c8c007246 100644 --- a/xbmc/DateTime.cpp +++ b/xbmc/DateTime.cpp @@ -897,6 +897,18 @@ void CDateTime::SetFromDBDate(const CStdString &date) SetDate(year, month, day); } +void CDateTime::SetFromDBTime(const CStdString &time) +{ + // assumes format: + // HH:MM:SS + int hour = 0, minute = 0, second = 0; + hour = atoi(time.Mid(0,2).c_str()); + minute = atoi(time.Mid(3,2).c_str()); + if (time.size() > 5) + second = atoi(time.Mid(6,2).c_str()); + SetTime(hour, minute, second); +} + CStdString CDateTime::GetAsLocalizedTime(const CStdString &format, bool withSeconds) const { CStdString strOut; @@ -1058,7 +1070,7 @@ CStdString CDateTime::GetAsLocalizedTime(const CStdString &format, bool withSeco return strOut; } -CStdString CDateTime::GetAsLocalizedDate(bool longDate/*=false*/) const +CStdString CDateTime::GetAsLocalizedDate(bool longDate/*=false*/, bool withShortNames/*=true*/) const { CStdString strOut; @@ -1120,15 +1132,31 @@ CStdString CDateTime::GetAsLocalizedDate(bool longDate/*=false*/) const str.Format("%02d", dateTime.wDay); else // Day of week string { - switch (dateTime.wDayOfWeek) + if (withShortNames) + { + switch (dateTime.wDayOfWeek) + { + case 1 : str = g_localizeStrings.Get(41); break; + case 2 : str = g_localizeStrings.Get(42); break; + case 3 : str = g_localizeStrings.Get(43); break; + case 4 : str = g_localizeStrings.Get(44); break; + case 5 : str = g_localizeStrings.Get(45); break; + case 6 : str = g_localizeStrings.Get(46); break; + default: str = g_localizeStrings.Get(47); break; + } + } + else { - case 1 : str = g_localizeStrings.Get(11); break; - case 2 : str = g_localizeStrings.Get(12); break; - case 3 : str = g_localizeStrings.Get(13); break; - case 4 : str = g_localizeStrings.Get(14); break; - case 5 : str = g_localizeStrings.Get(15); break; - case 6 : str = g_localizeStrings.Get(16); break; - default: str = g_localizeStrings.Get(17); break; + switch (dateTime.wDayOfWeek) + { + case 1 : str = g_localizeStrings.Get(11); break; + case 2 : str = g_localizeStrings.Get(12); break; + case 3 : str = g_localizeStrings.Get(13); break; + case 4 : str = g_localizeStrings.Get(14); break; + case 5 : str = g_localizeStrings.Get(15); break; + case 6 : str = g_localizeStrings.Get(16); break; + default: str = g_localizeStrings.Get(17); break; + } } } strOut+=str; @@ -1159,20 +1187,41 @@ CStdString CDateTime::GetAsLocalizedDate(bool longDate/*=false*/) const str.Format("%02d", dateTime.wMonth); else // Month string { - switch (dateTime.wMonth) + if (withShortNames) + { + switch (dateTime.wMonth) + { + case 1 : str = g_localizeStrings.Get(51); break; + case 2 : str = g_localizeStrings.Get(52); break; + case 3 : str = g_localizeStrings.Get(53); break; + case 4 : str = g_localizeStrings.Get(54); break; + case 5 : str = g_localizeStrings.Get(55); break; + case 6 : str = g_localizeStrings.Get(56); break; + case 7 : str = g_localizeStrings.Get(57); break; + case 8 : str = g_localizeStrings.Get(58); break; + case 9 : str = g_localizeStrings.Get(59); break; + case 10: str = g_localizeStrings.Get(60); break; + case 11: str = g_localizeStrings.Get(61); break; + default: str = g_localizeStrings.Get(62); break; + } + } + else { - case 1 : str = g_localizeStrings.Get(21); break; - case 2 : str = g_localizeStrings.Get(22); break; - case 3 : str = g_localizeStrings.Get(23); break; - case 4 : str = g_localizeStrings.Get(24); break; - case 5 : str = g_localizeStrings.Get(25); break; - case 6 : str = g_localizeStrings.Get(26); break; - case 7 : str = g_localizeStrings.Get(27); break; - case 8 : str = g_localizeStrings.Get(28); break; - case 9 : str = g_localizeStrings.Get(29); break; - case 10: str = g_localizeStrings.Get(30); break; - case 11: str = g_localizeStrings.Get(31); break; - default: str = g_localizeStrings.Get(32); break; + switch (dateTime.wMonth) + { + case 1 : str = g_localizeStrings.Get(21); break; + case 2 : str = g_localizeStrings.Get(22); break; + case 3 : str = g_localizeStrings.Get(23); break; + case 4 : str = g_localizeStrings.Get(24); break; + case 5 : str = g_localizeStrings.Get(25); break; + case 6 : str = g_localizeStrings.Get(26); break; + case 7 : str = g_localizeStrings.Get(27); break; + case 8 : str = g_localizeStrings.Get(28); break; + case 9 : str = g_localizeStrings.Get(29); break; + case 10: str = g_localizeStrings.Get(30); break; + case 11: str = g_localizeStrings.Get(31); break; + default: str = g_localizeStrings.Get(32); break; + } } } strOut+=str; diff --git a/xbmc/DateTime.h b/xbmc/DateTime.h index 5483ace239..aa98046f15 100644 --- a/xbmc/DateTime.h +++ b/xbmc/DateTime.h @@ -164,6 +164,7 @@ public: void SetDateTime(int year, int month, int day, int hour, int minute, int second); void SetDate(int year, int month, int day); void SetTime(int hour, int minute, int second); + void SetFromDBTime(const CStdString &time); void SetFromDBDate(const CStdString &date); /*! \brief set from a database datetime format YYYY-MM-DD HH:MM:SS @@ -179,7 +180,7 @@ public: CDateTime GetAsUTCDateTime() const; CStdString GetAsDBDateTime() const; CStdString GetAsDBDate() const; - CStdString GetAsLocalizedDate(bool longDate=false) const; + CStdString GetAsLocalizedDate(bool longDate=false, bool withShortNames=false) const; CStdString GetAsLocalizedTime(const CStdString &format, bool withSeconds=true) const; CStdString GetAsLocalizedDateTime(bool longDate=false, bool withSeconds=true) const; CStdString GetAsRFC1123DateTime() const; diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index b87eb6b01c..a1902bafb3 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -41,6 +41,10 @@ #include "SortFileItem.h" #include "utils/TuxBoxUtil.h" #include "VideoInfoTag.h" +#include "utils/PVREpg.h" +#include "utils/PVRChannels.h" +#include "utils/PVRRecordings.h" +#include "utils/PVRTimers.h" #include "utils/SingleLock.h" #include "MusicInfoTag.h" #include "PictureInfoTag.h" @@ -64,6 +68,10 @@ CFileItem::CFileItem(const CSong& song) { m_musicInfoTag = NULL; m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; m_pictureInfoTag = NULL; Reset(); SetLabel(song.strTitle); @@ -78,6 +86,10 @@ CFileItem::CFileItem(const CStdString &path, const CAlbum& album) { m_musicInfoTag = NULL; m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; m_pictureInfoTag = NULL; Reset(); SetLabel(album.strAlbum); @@ -98,6 +110,10 @@ CFileItem::CFileItem(const CVideoInfoTag& movie) { m_musicInfoTag = NULL; m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; m_pictureInfoTag = NULL; Reset(); SetLabel(movie.m_strTitle); @@ -117,10 +133,94 @@ CFileItem::CFileItem(const CVideoInfoTag& movie) SetCachedVideoThumb(); } +CFileItem::CFileItem(const cPVREPGInfoTag& programme) +{ + m_musicInfoTag = NULL; + m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; + m_pictureInfoTag = NULL; + Reset(); + m_strPath = programme.Path(); + m_bIsFolder = false; + *GetEPGInfoTag() = programme; + SetLabel(programme.Title()); + SetThumbnailImage(programme.Icon()); + //FillInDefaultIcon(); + //SetVideoThumb(); + SetInvalid(); +} + +CFileItem::CFileItem(const cPVRChannelInfoTag& channel) +{ + m_musicInfoTag = NULL; + m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; + m_pictureInfoTag = NULL; + Reset(); + m_strPath = channel.Path(); + m_bIsFolder = false; + *GetPVRChannelInfoTag() = channel; + SetLabel(channel.Name()); + m_strLabel2 = channel.NowTitle(); + SetThumbnailImage(channel.Icon()); + //FillInDefaultIcon(); + //SetVideoThumb(); + SetInvalid(); +} + +CFileItem::CFileItem(const cPVRRecordingInfoTag& record) +{ + m_musicInfoTag = NULL; + m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; + m_pictureInfoTag = NULL; + Reset(); + m_strPath = record.Path(); + m_bIsFolder = false; + *GetPVRRecordingInfoTag() = record; + SetLabel(record.m_strTitle); + //FillInDefaultIcon(); + //SetVideoThumb(); + SetInvalid(); +} + +CFileItem::CFileItem(const cPVRTimerInfoTag& timer) +{ + m_musicInfoTag = NULL; + m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; + m_pictureInfoTag = NULL; + Reset(); + m_strPath = timer.Path(); + m_bIsFolder = false; + *GetPVRTimerInfoTag() = timer; + SetLabel(timer.Title()); + m_strLabel2 = timer.Summary(); + //FillInDefaultIcon(); + //SetVideoThumb(); + SetInvalid(); +} + CFileItem::CFileItem(const CArtist& artist) { m_musicInfoTag = NULL; m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; m_pictureInfoTag = NULL; Reset(); SetLabel(artist.strArtist); @@ -134,6 +234,10 @@ CFileItem::CFileItem(const CGenre& genre) { m_musicInfoTag = NULL; m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; m_pictureInfoTag = NULL; Reset(); SetLabel(genre.strGenre); @@ -147,6 +251,10 @@ CFileItem::CFileItem(const CFileItem& item): CGUIListItem() { m_musicInfoTag = NULL; m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; m_pictureInfoTag = NULL; *this = item; } @@ -155,6 +263,10 @@ CFileItem::CFileItem(const CGUIListItem& item) { m_musicInfoTag = NULL; m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; m_pictureInfoTag = NULL; Reset(); // not particularly pretty, but it gets around the issue of Reset() defaulting @@ -166,6 +278,10 @@ CFileItem::CFileItem(void) { m_musicInfoTag = NULL; m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; m_pictureInfoTag = NULL; Reset(); } @@ -175,6 +291,10 @@ CFileItem::CFileItem(const CStdString& strLabel) { m_musicInfoTag = NULL; m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; m_pictureInfoTag = NULL; Reset(); SetLabel(strLabel); @@ -184,6 +304,10 @@ CFileItem::CFileItem(const CStdString& strPath, bool bIsFolder) { m_musicInfoTag = NULL; m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; m_pictureInfoTag = NULL; Reset(); m_strPath = strPath; @@ -197,6 +321,10 @@ CFileItem::CFileItem(const CMediaSource& share) { m_musicInfoTag = NULL; m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; m_pictureInfoTag = NULL; Reset(); m_bIsFolder = true; @@ -220,10 +348,18 @@ CFileItem::~CFileItem(void) { delete m_musicInfoTag; delete m_videoInfoTag; + delete m_epgInfoTag; + delete m_pvrChannelInfoTag; + delete m_pvrRecordingInfoTag; + delete m_pvrTimerInfoTag; delete m_pictureInfoTag; m_musicInfoTag = NULL; m_videoInfoTag = NULL; + m_epgInfoTag = NULL; + m_pvrChannelInfoTag = NULL; + m_pvrRecordingInfoTag = NULL; + m_pvrTimerInfoTag = NULL; m_pictureInfoTag = NULL; } @@ -263,6 +399,62 @@ const CFileItem& CFileItem::operator=(const CFileItem& item) m_videoInfoTag = NULL; } + if (item.HasEPGInfoTag()) + { + m_epgInfoTag = GetEPGInfoTag(); + if (m_epgInfoTag) + *m_epgInfoTag = *item.m_epgInfoTag; + } + else + { + if (m_epgInfoTag) + delete m_epgInfoTag; + + m_epgInfoTag = NULL; + } + + if (item.HasPVRChannelInfoTag()) + { + m_pvrChannelInfoTag = GetPVRChannelInfoTag(); + if (m_pvrChannelInfoTag) + *m_pvrChannelInfoTag = *item.m_pvrChannelInfoTag; + } + else + { + if (m_pvrChannelInfoTag) + delete m_pvrChannelInfoTag; + + m_pvrChannelInfoTag = NULL; + } + + if (item.HasPVRRecordingInfoTag()) + { + m_pvrRecordingInfoTag = GetPVRRecordingInfoTag(); + if (m_pvrRecordingInfoTag) + *m_pvrRecordingInfoTag = *item.m_pvrRecordingInfoTag; + } + else + { + if (m_pvrRecordingInfoTag) + delete m_pvrRecordingInfoTag; + + m_pvrRecordingInfoTag = NULL; + } + + if (item.HasPVRTimerInfoTag()) + { + m_pvrTimerInfoTag = GetPVRTimerInfoTag(); + if (m_pvrTimerInfoTag) + *m_pvrTimerInfoTag = *item.m_pvrTimerInfoTag; + } + else + { + if (m_pvrTimerInfoTag) + delete m_pvrTimerInfoTag; + + m_pvrTimerInfoTag = NULL; + } + if (item.HasPictureInfoTag()) { m_pictureInfoTag = GetPictureInfoTag(); @@ -324,6 +516,14 @@ void CFileItem::Reset() m_musicInfoTag=NULL; delete m_videoInfoTag; m_videoInfoTag=NULL; + delete m_epgInfoTag; + m_epgInfoTag=NULL; + delete m_pvrChannelInfoTag; + m_pvrChannelInfoTag=NULL; + delete m_pvrRecordingInfoTag; + m_pvrRecordingInfoTag=NULL; + delete m_pvrTimerInfoTag; + m_pvrTimerInfoTag=NULL; delete m_pictureInfoTag; m_pictureInfoTag=NULL; m_extrainfo.Empty(); @@ -518,9 +718,30 @@ bool CFileItem::IsAudio() const return false; extension.ToLower(); - if (g_settings.m_musicExtensions.Find(extension) != -1) - return true; + return (g_settings.m_musicExtensions.Find(extension) != -1); +} +bool CFileItem::IsEPG() const +{ + if (HasEPGInfoTag()) return true; /// is this enough? + return false; +} + +bool CFileItem::IsPVRChannel() const +{ + if (HasPVRChannelInfoTag()) return true; /// is this enough? + return false; +} + +bool CFileItem::IsPVRRecording() const +{ + if (HasPVRRecordingInfoTag()) return true; /// is this enough? + return false; +} + +bool CFileItem::IsPVRTimer() const +{ + if (HasPVRTimerInfoTag()) return true; /// is this enough? return false; } @@ -765,6 +986,11 @@ bool CFileItem::IsVTP() const return CUtil::IsVTP(m_strPath); } +bool CFileItem::IsPVR() const +{ + return CUtil::IsPVR(m_strPath); +} + bool CFileItem::IsLiveTV() const { return CUtil::IsLiveTV(m_strPath); @@ -829,6 +1055,11 @@ void CFileItem::FillInDefaultIcon() * in mind the complexity of the code behind the check in the * case of IsWhatater() returns false. */ + if ( IsLiveTV() ) + { + // Live TV Channel + return; + } if ( IsAudio() ) { // audio @@ -985,6 +1216,12 @@ void CFileItem::SetLabel(const CStdString &strLabel) CGUIListItem::SetLabel(strLabel); } +void CFileItem::SetLabel2(const CStdString &strLabel) +{ + m_strLabel2 = strLabel; +} + + void CFileItem::SetFileSizeLabel() { if( m_bIsFolder && m_dwSize == 0 ) @@ -1533,6 +1770,9 @@ void CFileItemList::Sort(SORT_METHOD sortMethod, SORT_ORDER sortOrder) case SORT_METHOD_LASTPLAYED: FillSortFields(SSortFileItem::ByLastPlayed); break; + case SORT_METHOD_CHANNEL: + FillSortFields(SSortFileItem::ByChannel); + break; default: break; } @@ -2285,6 +2525,8 @@ bool CFileItemList::AlwaysCache() const return CMusicDatabaseDirectory::CanCache(m_strPath); if (IsVideoDb()) return CVideoDatabaseDirectory::CanCache(m_strPath); +// if (IsEPG()) +// return true; // always cache return false; } @@ -2924,6 +3166,38 @@ CVideoInfoTag* CFileItem::GetVideoInfoTag() return m_videoInfoTag; } +cPVREPGInfoTag* CFileItem::GetEPGInfoTag() +{ + if (!m_epgInfoTag) + m_epgInfoTag = new cPVREPGInfoTag; + + return m_epgInfoTag; +} + +cPVRChannelInfoTag* CFileItem::GetPVRChannelInfoTag() +{ + if (!m_pvrChannelInfoTag) + m_pvrChannelInfoTag = new cPVRChannelInfoTag; + + return m_pvrChannelInfoTag; +} + +cPVRRecordingInfoTag* CFileItem::GetPVRRecordingInfoTag() +{ + if (!m_pvrRecordingInfoTag) + m_pvrRecordingInfoTag = new cPVRRecordingInfoTag; + + return m_pvrRecordingInfoTag; +} + +cPVRTimerInfoTag* CFileItem::GetPVRTimerInfoTag() +{ + if (!m_pvrTimerInfoTag) + m_pvrTimerInfoTag = new cPVRTimerInfoTag; + + return m_pvrTimerInfoTag; +} + CPictureInfoTag* CFileItem::GetPictureInfoTag() { if (!m_pictureInfoTag) diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index 03ee9238cd..7ee009a482 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -41,6 +41,10 @@ namespace MUSIC_INFO class CMusicInfoTag; } class CVideoInfoTag; +class cPVREPGInfoTag; +class cPVRChannelInfoTag; +class cPVRRecordingInfoTag; +class cPVRTimerInfoTag; class CPictureInfoTag; class CAlbum; @@ -73,6 +77,10 @@ public: CFileItem(const CArtist& artist); CFileItem(const CGenre& genre); CFileItem(const CVideoInfoTag& movie); + CFileItem(const cPVREPGInfoTag& programme); + CFileItem(const cPVRChannelInfoTag& channel); + CFileItem(const cPVRRecordingInfoTag& record); + CFileItem(const cPVRTimerInfoTag& timer); CFileItem(const CMediaSource& share); virtual ~CFileItem(void); virtual CGUIListItem *Clone() const { return new CFileItem(*this); }; @@ -120,6 +128,10 @@ public: bool IsMultiPath() const; bool IsMusicDb() const; bool IsVideoDb() const; + bool IsEPG() const; + bool IsPVRChannel() const; + bool IsPVRRecording() const; + bool IsPVRTimer() const; bool IsType(const char *ext) const; bool IsVirtualDirectoryRoot() const; bool IsReadOnly() const; @@ -132,6 +144,7 @@ public: bool IsMythTV() const; bool IsHDHomeRun() const; bool IsVTP() const; + bool IsPVR() const; bool IsLiveTV() const; bool IsRSS() const; @@ -141,6 +154,7 @@ public: void SetMusicThumb(bool alwaysCheckRemote = false); void SetFileSizeLabel(); virtual void SetLabel(const CStdString &strLabel); + virtual void SetLabel2(const CStdString &strLabel); CURL GetAsUrl() const; bool IsLabelPreformated() const { return m_bLabelPreformated; } void SetLabelPreformated(bool bYesNo) { m_bLabelPreformated=bYesNo; } @@ -172,6 +186,54 @@ public: return m_videoInfoTag; } + inline bool HasEPGInfoTag() const + { + return m_epgInfoTag != NULL; + } + + cPVREPGInfoTag* GetEPGInfoTag(); + + inline const cPVREPGInfoTag* GetEPGInfoTag() const + { + return m_epgInfoTag; + } + + inline bool HasPVRChannelInfoTag() const + { + return m_pvrChannelInfoTag != NULL; + } + + cPVRChannelInfoTag* GetPVRChannelInfoTag(); + + inline const cPVRChannelInfoTag* GetPVRChannelInfoTag() const + { + return m_pvrChannelInfoTag; + } + + inline bool HasPVRRecordingInfoTag() const + { + return m_pvrRecordingInfoTag != NULL; + } + + cPVRRecordingInfoTag* GetPVRRecordingInfoTag(); + + inline const cPVRRecordingInfoTag* GetPVRRecordingInfoTag() const + { + return m_pvrRecordingInfoTag; + } + + inline bool HasPVRTimerInfoTag() const + { + return m_pvrTimerInfoTag != NULL; + } + + cPVRTimerInfoTag* GetPVRTimerInfoTag(); + + inline const cPVRTimerInfoTag* GetPVRTimerInfoTag() const + { + return m_pvrTimerInfoTag; + } + inline bool HasPictureInfoTag() const { return m_pictureInfoTag != NULL; @@ -282,6 +344,10 @@ private: CStdString m_extrainfo; MUSIC_INFO::CMusicInfoTag* m_musicInfoTag; CVideoInfoTag* m_videoInfoTag; + cPVREPGInfoTag* m_epgInfoTag; + cPVRChannelInfoTag* m_pvrChannelInfoTag; + cPVRRecordingInfoTag* m_pvrRecordingInfoTag; + cPVRTimerInfoTag * m_pvrTimerInfoTag; CPictureInfoTag* m_pictureInfoTag; bool m_bIsAlbum; }; diff --git a/xbmc/FileSystem/AddonsDirectory.cpp b/xbmc/FileSystem/AddonsDirectory.cpp index ba88783308..d186ac269d 100644 --- a/xbmc/FileSystem/AddonsDirectory.cpp +++ b/xbmc/FileSystem/AddonsDirectory.cpp @@ -31,6 +31,7 @@ #include "StringUtils.h" #include "TextureManager.h" #include "File.h" +#include "SpecialProtocol.h" using namespace ADDON; @@ -173,6 +174,7 @@ bool CAddonsDirectory::GetDirectory(const CStdString& strPath, CFileItemList &it void CAddonsDirectory::GenerateListing(CURL &path, VECADDONS& addons, CFileItemList &items, bool reposAsFolders) { + CStdString xbmcPath = _P("special://xbmc/addons"); items.ClearItems(); for (unsigned i=0; i < addons.size(); i++) { @@ -185,6 +187,9 @@ void CAddonsDirectory::GenerateListing(CURL &path, VECADDONS& addons, CFileItemL AddonPtr addon2; if (CAddonMgr::Get().GetAddon(addon->ID(),addon2)) pItem->SetProperty("Addon.Status",g_localizeStrings.Get(305)); + else if (pItem->GetProperty("Addon.Path").Left(xbmcPath.size()).Equals(xbmcPath)) + pItem->SetProperty("Addon.Status",g_localizeStrings.Get(24095)); + if (!addon->Props().broken.IsEmpty()) pItem->SetProperty("Addon.Status",g_localizeStrings.Get(24098)); if (addon2 && addon2->Version() < addon->Version()) diff --git a/xbmc/FileSystem/DllLibCMyth.h b/xbmc/FileSystem/DllLibCMyth.h index 761fedf31f..3cf0542aae 100644 --- a/xbmc/FileSystem/DllLibCMyth.h +++ b/xbmc/FileSystem/DllLibCMyth.h @@ -38,11 +38,17 @@ public: virtual cmyth_recorder_t conn_get_free_recorder (cmyth_conn_t conn)=0; virtual cmyth_recorder_t conn_get_recorder_from_num(cmyth_conn_t conn, int num)=0; + virtual int conn_get_freespace (cmyth_conn_t control,long long *total, long long *used)=0; + virtual int conn_hung (cmyth_conn_t control)=0; virtual cmyth_event_t event_get (cmyth_conn_t conn, char * data, int len)=0; virtual int event_select (cmyth_conn_t conn, struct timeval *timeout)=0; virtual cmyth_proglist_t proglist_get_all_recorded(cmyth_conn_t control)=0; + virtual cmyth_proglist_t proglist_get_all_scheduled(cmyth_conn_t control)=0; + virtual cmyth_proglist_t proglist_get_all_pending (cmyth_conn_t control)=0; + virtual cmyth_proglist_t proglist_get_conflicting (cmyth_conn_t control)=0; + virtual int mysql_get_guide(cmyth_database_t db, cmyth_program_t **prog, time_t starttime, time_t endtime) = 0; virtual cmyth_proginfo_t proglist_get_item (cmyth_proglist_t pl, int index)=0; virtual int proglist_get_count (cmyth_proglist_t pl)=0; @@ -104,6 +110,7 @@ public: virtual cmyth_timestamp_t proginfo_rec_start (cmyth_proginfo_t prog)=0; virtual cmyth_timestamp_t proginfo_rec_end (cmyth_proginfo_t prog)=0; virtual cmyth_proginfo_rec_status_t proginfo_rec_status(cmyth_proginfo_t prog)=0; + virtual long proginfo_card_id (cmyth_proginfo_t prog)=0; virtual char* proginfo_prodyear (cmyth_proginfo_t prog)=0; virtual cmyth_proginfo_t proginfo_get_from_basename (cmyth_conn_t control, const char* basename)=0; virtual int proginfo_delete_recording(cmyth_conn_t control, cmyth_proginfo_t prog)=0; @@ -136,11 +143,17 @@ class DllLibCMyth : public DllDynamic, DllLibCMythInterface DEFINE_METHOD1(cmyth_recorder_t, conn_get_free_recorder, (cmyth_conn_t p1)) DEFINE_METHOD2(cmyth_recorder_t, conn_get_recorder_from_num,(cmyth_conn_t p1, int p2)) + DEFINE_METHOD3(int, conn_get_freespace, (cmyth_conn_t p1, long long *p2, long long *p3)) + DEFINE_METHOD1(int, conn_hung, (cmyth_conn_t p1)) DEFINE_METHOD3(cmyth_event_t, event_get, (cmyth_conn_t p1, char * p2, int p3)) DEFINE_METHOD2(int, event_select, (cmyth_conn_t p1, struct timeval *p2)) DEFINE_METHOD1(cmyth_proglist_t, proglist_get_all_recorded, (cmyth_conn_t p1)) + DEFINE_METHOD1(cmyth_proglist_t, proglist_get_all_scheduled, (cmyth_conn_t p1)) + DEFINE_METHOD1(cmyth_proglist_t, proglist_get_all_pending, (cmyth_conn_t p1)) + DEFINE_METHOD1(cmyth_proglist_t, proglist_get_conflicting, (cmyth_conn_t p1)) + DEFINE_METHOD4(int, mysql_get_guide, (cmyth_database_t p1, cmyth_program_t **p2, time_t p3, time_t p4)) DEFINE_METHOD2(cmyth_proginfo_t, proglist_get_item, (cmyth_proglist_t p1, int p2)) DEFINE_METHOD1(int, proglist_get_count, (cmyth_proglist_t p1)) @@ -200,6 +213,7 @@ class DllLibCMyth : public DllDynamic, DllLibCMythInterface DEFINE_METHOD1(cmyth_timestamp_t, proginfo_rec_start, (cmyth_proginfo_t p1)) DEFINE_METHOD1(cmyth_timestamp_t, proginfo_rec_end, (cmyth_proginfo_t p1)) DEFINE_METHOD1(cmyth_proginfo_rec_status_t, proginfo_rec_status, (cmyth_proginfo_t p1)) + DEFINE_METHOD1(long, proginfo_card_id, (cmyth_proginfo_t p1)) DEFINE_METHOD1(char*, proginfo_prodyear, (cmyth_proginfo_t p1)) DEFINE_METHOD2(cmyth_proginfo_t, proginfo_get_from_basename, (cmyth_conn_t p1, const char* p2)) DEFINE_METHOD2(int, proginfo_delete_recording, (cmyth_conn_t p1, cmyth_proginfo_t p2)) @@ -227,10 +241,15 @@ class DllLibCMyth : public DllDynamic, DllLibCMythInterface RESOLVE_METHOD_RENAME(cmyth_conn_connect_path, conn_connect_path) RESOLVE_METHOD_RENAME(cmyth_conn_get_free_recorder, conn_get_free_recorder) RESOLVE_METHOD_RENAME(cmyth_conn_get_recorder_from_num, conn_get_recorder_from_num) + RESOLVE_METHOD_RENAME(cmyth_conn_get_freespace, conn_get_freespace) + RESOLVE_METHOD_RENAME(cmyth_conn_hung, conn_hung) RESOLVE_METHOD_RENAME(cmyth_event_get, event_get) RESOLVE_METHOD_RENAME(cmyth_event_select, event_select) RESOLVE_METHOD_RENAME(cmyth_proglist_get_all_recorded, proglist_get_all_recorded) + RESOLVE_METHOD_RENAME(cmyth_proglist_get_all_scheduled, proglist_get_all_scheduled) + RESOLVE_METHOD_RENAME(cmyth_proglist_get_all_pending, proglist_get_all_pending) + RESOLVE_METHOD_RENAME(cmyth_proglist_get_conflicting, proglist_get_conflicting) RESOLVE_METHOD_RENAME(cmyth_mysql_get_guide, mysql_get_guide) RESOLVE_METHOD_RENAME(cmyth_proglist_get_item, proglist_get_item) RESOLVE_METHOD_RENAME(cmyth_proglist_get_count, proglist_get_count) @@ -290,6 +309,7 @@ class DllLibCMyth : public DllDynamic, DllLibCMythInterface RESOLVE_METHOD_RENAME(cmyth_proginfo_rec_start, proginfo_rec_start) RESOLVE_METHOD_RENAME(cmyth_proginfo_rec_end, proginfo_rec_end) RESOLVE_METHOD_RENAME(cmyth_proginfo_rec_status, proginfo_rec_status) + RESOLVE_METHOD_RENAME(cmyth_proginfo_card_id, proginfo_card_id) RESOLVE_METHOD_RENAME(cmyth_proginfo_prodyear, proginfo_prodyear) RESOLVE_METHOD_RENAME(cmyth_proginfo_get_from_basename, proginfo_get_from_basename) RESOLVE_METHOD_RENAME(cmyth_proginfo_delete_recording, proginfo_delete_recording) diff --git a/xbmc/FileSystem/FactoryDirectory.cpp b/xbmc/FileSystem/FactoryDirectory.cpp index 4e1fd8db54..f6cf7c99bd 100644 --- a/xbmc/FileSystem/FactoryDirectory.cpp +++ b/xbmc/FileSystem/FactoryDirectory.cpp @@ -79,6 +79,9 @@ #ifdef HAS_FILESYSTEM_HTSP #include "HTSPDirectory.h" #endif +#ifdef HAS_PVRCLIENTS +#include "PVRDirectory.h" +#endif #include "../utils/Network.h" #include "ZipDirectory.h" #ifdef HAS_FILESYSTEM_RAR @@ -185,6 +188,9 @@ IDirectory* CFactoryDirectory::Create(const CStdString& strPath) #ifdef HAS_FILESYSTEM_HTSP if (strProtocol == "htsp") return new CHTSPDirectory(); #endif +#ifdef HAS_PVRCLIENTS + if (strProtocol == "pvr") return new CPVRDirectory(); +#endif #ifdef HAS_ZEROCONF if (strProtocol == "zeroconf") return new CZeroconfDirectory(); #endif diff --git a/xbmc/FileSystem/FileFactory.cpp b/xbmc/FileSystem/FileFactory.cpp index 5590566430..a6acad7411 100644 --- a/xbmc/FileSystem/FileFactory.cpp +++ b/xbmc/FileSystem/FileFactory.cpp @@ -58,6 +58,9 @@ #ifdef HAS_FILESYSTEM_VTP #include "VTPFile.h" #endif +#ifdef HAS_PVRCLIENTS +#include "PVRFile.h" +#endif #include "FileZip.h" #ifdef HAS_FILESYSTEM_RAR #include "FileRar.h" @@ -157,6 +160,9 @@ IFile* CFileFactory::CreateLoader(const CURL& url) #endif #ifdef HAS_FILESYSTEM_VTP else if (strProtocol == "vtp") return new CVTPFile(); +#endif +#ifdef HAS_PVRCLIENTS + else if (strProtocol == "pvr") return new CPVRFile(); #endif } diff --git a/xbmc/FileSystem/ILiveTV.h b/xbmc/FileSystem/ILiveTV.h index 36ac07d01c..3b2277bd2c 100644 --- a/xbmc/FileSystem/ILiveTV.h +++ b/xbmc/FileSystem/ILiveTV.h @@ -28,8 +28,8 @@ class ILiveTVInterface { public: virtual ~ILiveTVInterface() {} - virtual bool NextChannel() = 0; - virtual bool PrevChannel() = 0; + virtual bool NextChannel(bool preview = false) = 0; + virtual bool PrevChannel(bool preview = false) = 0; virtual bool SelectChannel(unsigned int channel) = 0; virtual int GetTotalTime() = 0; diff --git a/xbmc/FileSystem/Makefile.in b/xbmc/FileSystem/Makefile.in index 8e69be4cf2..f93633f2cf 100644 --- a/xbmc/FileSystem/Makefile.in +++ b/xbmc/FileSystem/Makefile.in @@ -68,6 +68,8 @@ SRCS=AddonsDirectory.cpp \ PlaylistDirectory.cpp \ PlaylistFileDirectory.cpp \ PluginDirectory.cpp \ + PVRFile.cpp \ + PVRDirectory.cpp \ RSSDirectory.cpp \ RTVDirectory.cpp \ SAPDirectory.cpp \ diff --git a/xbmc/FileSystem/MythFile.cpp b/xbmc/FileSystem/MythFile.cpp index c47d1f2bff..6555d62d94 100644 --- a/xbmc/FileSystem/MythFile.cpp +++ b/xbmc/FileSystem/MythFile.cpp @@ -633,12 +633,12 @@ bool CMythFile::ChangeChannel(int direction, const CStdString &channel) return true; } -bool CMythFile::NextChannel() +bool CMythFile::NextChannel(bool preview) { return ChangeChannel(CHANNEL_DIRECTION_UP, ""); } -bool CMythFile::PrevChannel() +bool CMythFile::PrevChannel(bool preview) { return ChangeChannel(CHANNEL_DIRECTION_DOWN, ""); } diff --git a/xbmc/FileSystem/MythFile.h b/xbmc/FileSystem/MythFile.h index 92671872d2..2440500cca 100644 --- a/xbmc/FileSystem/MythFile.h +++ b/xbmc/FileSystem/MythFile.h @@ -62,8 +62,8 @@ public: virtual ILiveTVInterface* GetLiveTV() {return (ILiveTVInterface*)this;} - virtual bool NextChannel(); - virtual bool PrevChannel(); + virtual bool NextChannel(bool preview = false); + virtual bool PrevChannel(bool preview = false); virtual bool SelectChannel(unsigned int channel); virtual int GetTotalTime(); diff --git a/xbmc/FileSystem/PVRDirectory.cpp b/xbmc/FileSystem/PVRDirectory.cpp new file mode 100644 index 0000000000..25408977ba --- /dev/null +++ b/xbmc/FileSystem/PVRDirectory.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "PVRDirectory.h" +#include "FileItem.h" +#include "Util.h" +#include "URL.h" +#include "PVRManager.h" +#include "utils/log.h" +#include "utils/PVRChannels.h" +#include "utils/PVRRecordings.h" +#include "utils/PVRTimers.h" +#include "LocalizeStrings.h" + +using namespace std; +using namespace XFILE; + +CPVRDirectory::CPVRDirectory() +{ +} + +CPVRDirectory::~CPVRDirectory() +{ +} + +bool CPVRDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items) +{ + CStdString base(strPath); + CUtil::RemoveSlashAtEnd(base); + + CURL url(strPath); + CStdString fileName = url.GetFileName(); + CUtil::RemoveSlashAtEnd(fileName); + CLog::Log(LOGDEBUG, "CPVRDirectory::GetDirectory(%s)", base.c_str()); + + if (fileName == "") + { + CFileItemPtr item; + + item.reset(new CFileItem(base + "/channels/", true)); + item->SetLabel(g_localizeStrings.Get(19019)); + item->SetLabelPreformated(true); + items.Add(item); + + item.reset(new CFileItem(base + "/recordings/", true)); + item->SetLabel(g_localizeStrings.Get(19017)); + item->SetLabelPreformated(true); + items.Add(item); + + item.reset(new CFileItem(base + "/timers/", true)); + item->SetLabel(g_localizeStrings.Get(19040)); + item->SetLabelPreformated(true); + items.Add(item); + + item.reset(new CFileItem(base + "/guide/", true)); + item->SetLabel(g_localizeStrings.Get(19029)); + item->SetLabelPreformated(true); + items.Add(item); + + // Sort by name only. Labels are preformated. + items.AddSortMethod(SORT_METHOD_LABEL, 551 /* Name */, LABEL_MASKS("%L", "", "%L", "")); + + return true; + } + else if (fileName.Left(10) == "recordings") + { + return PVRRecordings.GetDirectory(strPath, items) > 0; + } + else if (fileName.Left(8) == "channels") + { + return cPVRChannels::GetDirectory(strPath, items) > 0; + } + else if (fileName.Left(6) == "timers") + { + return PVRTimers.GetDirectory(strPath, items) > 0; + } + + return false; +} + +bool CPVRDirectory::SupportsFileOperations(const CStdString& strPath) +{ + CURL url(strPath); + CStdString filename = url.GetFileName(); + CUtil::RemoveSlashAtEnd(filename); + + if (filename.Left(11) == "recordings/" && filename.Right(4) == ".pvr") + return true; + + return false; +} + +bool CPVRDirectory::IsLiveTV(const CStdString& strPath) +{ + CURL url(strPath); + return url.GetFileName().Left(9) == "channelstv/"; +} + +bool CPVRDirectory::HasRecordings() +{ + return PVRRecordings.GetNumRecordings() > 0; +} diff --git a/xbmc/FileSystem/PVRDirectory.h b/xbmc/FileSystem/PVRDirectory.h new file mode 100644 index 0000000000..90ce917e19 --- /dev/null +++ b/xbmc/FileSystem/PVRDirectory.h @@ -0,0 +1,46 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "IDirectory.h" + +class CPVRSession; + +namespace XFILE { + +class CPVRDirectory + : public IDirectory +{ +public: + CPVRDirectory(); + virtual ~CPVRDirectory(); + + virtual bool GetDirectory(const CStdString& strPath, CFileItemList &items); + virtual bool IsAllowed(const CStdString &strFile) const { return true; }; + + static bool SupportsFileOperations(const CStdString& strPath); + static bool IsLiveTV(const CStdString& strPath); + static bool HasRecordings(); + +private: +}; + +} diff --git a/xbmc/FileSystem/PVRFile.cpp b/xbmc/FileSystem/PVRFile.cpp new file mode 100644 index 0000000000..dc035891d7 --- /dev/null +++ b/xbmc/FileSystem/PVRFile.cpp @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "PVRFile.h" +#include "Util.h" +#include "PVRManager.h" +#include "utils/log.h" + +using namespace XFILE; +using namespace std; + +CPVRFile::CPVRFile() +{ + m_isPlayRecording = false; + m_playingItem = -1; +} + +CPVRFile::~CPVRFile() +{ +} + +bool CPVRFile::Open(const CURL& url) +{ + Close(); + + CStdString strURL = url.Get(); + + if (strURL.Left(18) == "pvr://channels/tv/") + { + cPVRChannelInfoTag *tag = cPVRChannels::GetByPath(strURL); + if (tag) + { + if (!g_PVRManager.OpenLiveStream(tag)) + return false; + + m_isPlayRecording = false; + CLog::Log(LOGDEBUG, "%s - TV Channel has started on filename %s", __FUNCTION__, strURL.c_str()); + } + else + { + CLog::Log(LOGERROR, "PVRFile - TV Channel not found with filename %s", strURL.c_str()); + return false; + } + } + else if (strURL.Left(21) == "pvr://channels/radio/") + { + cPVRChannelInfoTag *tag = cPVRChannels::GetByPath(strURL); + if (tag) + { + if (!g_PVRManager.OpenLiveStream(tag)) + return false; + + m_isPlayRecording = false; + CLog::Log(LOGDEBUG, "%s - Radio Channel has started on filename %s", __FUNCTION__, strURL.c_str()); + } + else + { + CLog::Log(LOGERROR, "PVRFile - Radio Channel not found with filename %s", strURL.c_str()); + return false; + } + } + else if (strURL.Left(17) == "pvr://recordings/") + { + cPVRRecordingInfoTag *tag = PVRRecordings.GetByPath(strURL); + if (tag) + { + if (!g_PVRManager.OpenRecordedStream(tag)) + return false; + + m_isPlayRecording = true; + CLog::Log(LOGDEBUG, "%s - Recording has started on filename %s", __FUNCTION__, strURL.c_str()); + } + else + { + CLog::Log(LOGERROR, "PVRFile - Recording not found with filename %s", strURL.c_str()); + return false; + } + } + else + { + CLog::Log(LOGERROR, "%s - invalid path specified %s", __FUNCTION__, strURL.c_str()); + return false; + } + + return true; +} + +void CPVRFile::Close() +{ + g_PVRManager.CloseStream(); +} + +unsigned int CPVRFile::Read(void* buffer, int64_t size) +{ + return g_PVRManager.ReadStream((BYTE*)buffer, size); +} + +int64_t CPVRFile::GetLength() +{ + return g_PVRManager.LengthStream(); +} + +int64_t CPVRFile::Seek(int64_t pos, int whence) +{ + if (whence == SEEK_POSSIBLE) + { + int64_t ret = g_PVRManager.SeekStream(pos, whence); + + if (ret >= 0) + { + return ret; + } + else + { + if (g_PVRManager.LengthStream() && g_PVRManager.SeekStream(0, SEEK_CUR) >= 0) + return 1; + else + return 0; + } + } + else + { + return g_PVRManager.SeekStream(pos, whence); + } + return 0; +} + +int64_t CPVRFile::GetPosition() +{ + return g_PVRManager.GetStreamPosition(); +} + +int CPVRFile::GetTotalTime() +{ + return g_PVRManager.GetTotalTime(); +} + +int CPVRFile::GetStartTime() +{ + return g_PVRManager.GetStartTime(); +} + +bool CPVRFile::NextChannel(bool preview/* = false*/) +{ + unsigned int newchannel; + + if (m_isPlayRecording) + { + /* We are inside a recording, skip channelswitch */ + return true; + } + + /* Do channel switch and save new channel number, it is not always + * increased by one in a case if next channel is encrypted or we + * on the beginning or end of the channel list! + */ + if (g_PVRManager.ChannelUp(&newchannel, preview)) + { + m_playingItem = newchannel; + return true; + } + else + { + return false; + } +} + +bool CPVRFile::PrevChannel(bool preview/* = false*/) +{ + unsigned int newchannel; + + if (m_isPlayRecording) + { + /* We are inside a recording, skip channelswitch */ + return true; + } + + /* Do channel switch and save new channel number, it is not always + * increased by one in a case if next channel is encrypted or we + * on the beginning or end of the channel list! + */ + if (g_PVRManager.ChannelDown(&newchannel, preview)) + { + m_playingItem = newchannel; + return true; + } + else + { + return false; + } +} + +bool CPVRFile::SelectChannel(unsigned int channel) +{ + if (m_isPlayRecording) + { + /* We are inside a recording, skip channelswitch */ + /** TODO: + ** Add support for cutting keys (functions becomes the numeric keys as integer) + **/ + return true; + } + + if (g_PVRManager.ChannelSwitch(channel)) + { + m_playingItem = channel; + return true; + } + else + { + return false; + } +} + +bool CPVRFile::UpdateItem(CFileItem& item) +{ + if (m_isPlayRecording) + { + /* We are inside a recording, skip item update */ + return true; + } + + return g_PVRManager.UpdateItem(item); +} + +CStdString CPVRFile::TranslatePVRFilename(const CStdString& pathFile) +{ + CStdString FileName = pathFile; + + if (FileName.substr(0, 14) == "pvr://channels") + { + cPVRChannelInfoTag *tag = cPVRChannels::GetByPath(FileName); + if (tag) + { + CStdString stream = tag->StreamURL(); + if(!stream.IsEmpty()) + { + if (stream.compare(6, 7, "stream/") == 0) + { + // pvr://stream + // This function was added to retrieve the stream URL for this item + // Is is used for the MediaPortal PVR addon + // see PVRManager.cpp + return g_PVRManager.GetLiveStreamURL(tag); + } + else + { + return stream; + } + } + } + } + return FileName; +} + +bool CPVRFile::CanRecord() +{ + if (m_isPlayRecording) + { + return false; + } + + return g_PVRManager.CanInstantRecording(); +} + +bool CPVRFile::IsRecording() +{ + return g_PVRManager.IsRecordingOnPlayingChannel(); +} + +bool CPVRFile::Record(bool bOnOff) +{ + return g_PVRManager.StartRecordingOnPlayingChannel(bOnOff); +} + +bool CPVRFile::Delete(const CURL& url) +{ + CStdString path(url.GetFileName()); + + if (path.Left(11) == "recordings/" && path[path.size()-1] != '/') + { + CStdString strURL = url.Get(); + cPVRRecordingInfoTag *tag = PVRRecordings.GetByPath(strURL); + if (tag) + return tag->Delete(); + } + return false; +} + +bool CPVRFile::Rename(const CURL& url, const CURL& urlnew) +{ + CStdString path(url.GetFileName()); + CStdString newname(urlnew.GetFileName()); + + size_t found = newname.find_last_of("/"); + if (found != CStdString::npos) + newname = newname.substr(found+1); + + if (path.Left(11) == "recordings/" && path[path.size()-1] != '/') + { + CStdString strURL = url.Get(); + cPVRRecordingInfoTag *tag = PVRRecordings.GetByPath(strURL); + if (tag) + return tag->Rename(newname); + } + return false; +} diff --git a/xbmc/FileSystem/PVRFile.h b/xbmc/FileSystem/PVRFile.h new file mode 100644 index 0000000000..d81637eff9 --- /dev/null +++ b/xbmc/FileSystem/PVRFile.h @@ -0,0 +1,79 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "IFile.h" +#include "ILiveTV.h" +#include "VideoInfoTag.h" + + +class CPVRSession; + +namespace XFILE { + +class CPVRFile + : public IFile + , ILiveTVInterface + , IRecordable +{ +public: + CPVRFile(); + virtual ~CPVRFile(); + virtual bool Open(const CURL& url); + virtual int64_t Seek(int64_t pos, int whence=SEEK_SET); + virtual int64_t GetPosition(); + virtual int64_t GetLength(); + virtual int Stat(const CURL& url, struct __stat64* buffer) { return -1; } + virtual void Close(); + virtual unsigned int Read(void* buffer, int64_t size); + virtual CStdString GetContent() { return ""; } + virtual bool SkipNext() { return true; } + + virtual bool Delete(const CURL& url); + virtual bool Rename(const CURL& url, const CURL& urlnew); + virtual bool Exists(const CURL& url) { return false; } + + virtual ILiveTVInterface* GetLiveTV() {return (ILiveTVInterface*)this;} + + virtual bool NextChannel(bool preview = false); + virtual bool PrevChannel(bool preview = false); + virtual bool SelectChannel(unsigned int channel); + + virtual int GetTotalTime(); + virtual int GetStartTime(); + virtual bool UpdateItem(CFileItem& item); + + virtual IRecordable* GetRecordable() {return (IRecordable*)this;} + + virtual bool CanRecord(); + virtual bool IsRecording(); + virtual bool Record(bool bOnOff); + + static CStdString TranslatePVRFilename(const CStdString& pathFile); + +protected: + bool m_isPlayRecording; + int m_playingItem; +}; + +} + + diff --git a/xbmc/FileSystem/VTPFile.cpp b/xbmc/FileSystem/VTPFile.cpp index 6194de0a18..9bbd40b243 100644 --- a/xbmc/FileSystem/VTPFile.cpp +++ b/xbmc/FileSystem/VTPFile.cpp @@ -147,7 +147,7 @@ int64_t CVTPFile::Seek(int64_t pos, int whence) return -1; } -bool CVTPFile::NextChannel() +bool CVTPFile::NextChannel(bool preview/* = false*/) { if(m_session == NULL) return false; @@ -173,7 +173,7 @@ bool CVTPFile::NextChannel() return false; } -bool CVTPFile::PrevChannel() +bool CVTPFile::PrevChannel(bool preview/* = false*/) { if(m_session == NULL) return false; diff --git a/xbmc/FileSystem/VTPFile.h b/xbmc/FileSystem/VTPFile.h index 42401ac15b..eb2c553f5b 100644 --- a/xbmc/FileSystem/VTPFile.h +++ b/xbmc/FileSystem/VTPFile.h @@ -51,8 +51,8 @@ public: virtual ILiveTVInterface* GetLiveTV() {return (ILiveTVInterface*)this;} - virtual bool NextChannel(); - virtual bool PrevChannel(); + virtual bool NextChannel(bool preview = false); + virtual bool PrevChannel(bool preview = false); virtual bool SelectChannel(unsigned int channel); virtual int GetTotalTime() { return 0; } diff --git a/xbmc/GUIDialogAddonInfo.cpp b/xbmc/GUIDialogAddonInfo.cpp index 57aaa4e85f..2da0856f35 100644 --- a/xbmc/GUIDialogAddonInfo.cpp +++ b/xbmc/GUIDialogAddonInfo.cpp @@ -34,6 +34,7 @@ #include "URL.h" #include "utils/JobManager.h" #include "utils/FileOperationJob.h" +#include "Application.h" #define CONTROL_BTN_INSTALL 6 #define CONTROL_BTN_ENABLE 7 @@ -126,7 +127,7 @@ void CGUIDialogAddonInfo::UpdateControls() bool isEnabled = isInstalled && m_item->GetProperty("Addon.Enabled").Equals("true"); bool isUpdatable = isInstalled && m_item->GetProperty("Addon.UpdateAvail").Equals("true"); // TODO: System addons should be able to be disabled - bool canDisable = isInstalled && !isSystem && !m_localAddon->IsInUse(); + bool canDisable = isInstalled && (!isSystem || m_localAddon->Type() == ADDON_PVRDLL) && !m_localAddon->IsInUse(); bool canInstall = !isInstalled && m_item->GetProperty("Addon.Broken").IsEmpty(); CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_INSTALL, canDisable || canInstall); @@ -176,9 +177,17 @@ void CGUIDialogAddonInfo::OnEnable(bool enable) if (!m_localAddon.get()) return; + CStdString xbmcPath = _P("special://xbmc/addons"); CAddonDatabase database; database.Open(); - database.DisableAddon(m_localAddon->ID(), !enable); + if (m_localAddon->Type() == ADDON_PVRDLL && m_localAddon->Path().Left(xbmcPath.size()).Equals(xbmcPath)) + database.EnableSystemPVRAddon(m_localAddon->ID(), enable); + else + database.DisableAddon(m_localAddon->ID(), !enable); + + if (m_localAddon->Type() == ADDON_PVRDLL && enable) + g_application.StartPVRManager(); + SetItem(m_item); UpdateControls(); g_windowManager.SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE); diff --git a/xbmc/GUIDialogContextMenu.h b/xbmc/GUIDialogContextMenu.h index 1ed9992906..381eaed373 100644 --- a/xbmc/GUIDialogContextMenu.h +++ b/xbmc/GUIDialogContextMenu.h @@ -99,8 +99,25 @@ enum CONTEXT_BUTTON { CONTEXT_BUTTON_CANCELLED = 0, CONTEXT_BUTTON_SCRIPT_SETTINGS, CONTEXT_BUTTON_LASTFM_UNLOVE_ITEM, CONTEXT_BUTTON_LASTFM_UNBAN_ITEM, + CONTEXT_BUTTON_HIDE, + CONTEXT_BUTTON_SHOW_HIDDEN, + CONTEXT_BUTTON_ADD, + CONTEXT_BUTTON_ACTIVATE, + CONTEXT_BUTTON_START_RECORD, + CONTEXT_BUTTON_STOP_RECORD, + CONTEXT_BUTTON_GROUP_MANAGER, + CONTEXT_BUTTON_CHANNEL_MANAGER, CONTEXT_BUTTON_SET_MOVIESET_THUMB, + CONTEXT_BUTTON_BEGIN, + CONTEXT_BUTTON_END, + CONTEXT_BUTTON_FIND, CONTEXT_BUTTON_DELETE_PLUGIN, + CONTEXT_BUTTON_SORTASC, + CONTEXT_BUTTON_SORTBY, + CONTEXT_BUTTON_SORTBY_CHANNEL, + CONTEXT_BUTTON_SORTBY_NAME, + CONTEXT_BUTTON_SORTBY_DATE, + CONTEXT_BUTTON_MENU_HOOKS, CONTEXT_BUTTON_USER1, CONTEXT_BUTTON_USER2, CONTEXT_BUTTON_USER3, @@ -141,7 +158,6 @@ public: */ static int ShowAndGetChoice(const CContextButtons &choices); -protected: void SetupButtons(); /*! \brief Position the context menu in the middle of the focused control. @@ -151,6 +167,7 @@ protected: float GetWidth(); float GetHeight(); +protected: virtual void OnInitWindow(); virtual void OnWindowLoaded(); virtual void OnWindowUnload(); diff --git a/xbmc/GUIDialogMediaSource.cpp b/xbmc/GUIDialogMediaSource.cpp index e90c0783b1..4e7ae3001d 100644 --- a/xbmc/GUIDialogMediaSource.cpp +++ b/xbmc/GUIDialogMediaSource.cpp @@ -27,6 +27,7 @@ #include "GUIWindowManager.h" #include "Util.h" #include "FileSystem/PluginDirectory.h" +#include "FileSystem/PVRDirectory.h" #include "GUIDialogYesNo.h" #include "FileSystem/File.h" #include "FileItem.h" @@ -301,6 +302,14 @@ void CGUIDialogMediaSource::OnPathBrowse(int item) share1.strPath = "zeroconf://"; share1.strName = "Zeroconf Browser"; extraShares.push_back(share1); + + // add the recordings dir as needed + if (CPVRDirectory::HasRecordings()) + { + share1.strPath = "pvr://recordings/"; + share1.strName = g_localizeStrings.Get(19017); // TV Recordings + extraShares.push_back(share1); + } } else if (m_type == "pictures") { diff --git a/xbmc/GUIDialogNumeric.cpp b/xbmc/GUIDialogNumeric.cpp index dd4c2430e0..aa29b1b5b3 100644 --- a/xbmc/GUIDialogNumeric.cpp +++ b/xbmc/GUIDialogNumeric.cpp @@ -42,6 +42,7 @@ CGUIDialogNumeric::CGUIDialogNumeric(void) { m_bConfirmed = false; m_bCanceled = false; + m_autoCloseTime = 0; m_mode = INPUT_PASSWORD; m_block = 0; @@ -55,6 +56,9 @@ CGUIDialogNumeric::~CGUIDialogNumeric(void) bool CGUIDialogNumeric::OnAction(const CAction &action) { + if (action.GetID() >= ACTION_MOVE_LEFT && action.GetID() <= ACTION_MOVE_DOWN) + m_autoClosing = false; + if (action.GetID() == ACTION_CLOSE_DIALOG || action.GetID() == ACTION_PREVIOUS_MENU) OnCancel(); else if (action.GetID() == ACTION_NEXT_ITEM) @@ -98,6 +102,7 @@ bool CGUIDialogNumeric::OnMessage(CGUIMessage& message) m_bConfirmed = false; m_bCanceled = false; m_dirty = false; + m_autoCloseTime = 0; return CGUIDialog::OnMessage(message); } break; @@ -278,6 +283,11 @@ void CGUIDialogNumeric::FrameMove() void CGUIDialogNumeric::OnNumber(unsigned int num) { + if (m_autoCloseTime) + { + SetAutoClose(m_autoCloseTime); + } + if (m_mode == INPUT_NUMBER || m_mode == INPUT_PASSWORD) { m_number += num + '0'; @@ -561,16 +571,22 @@ bool CGUIDialogNumeric::ShowAndGetIPAddress(CStdString &IPAddress, const CStdStr return true; } -bool CGUIDialogNumeric::ShowAndGetNumber(CStdString& strInput, const CStdString &strHeading) +bool CGUIDialogNumeric::ShowAndGetNumber(CStdString& strInput, const CStdString &strHeading, unsigned int autoCloseTime) { // Prompt user for password input CGUIDialogNumeric *pDialog = (CGUIDialogNumeric *)g_windowManager.GetWindow(WINDOW_DIALOG_NUMERIC); pDialog->SetHeading( strHeading ); pDialog->SetMode(INPUT_NUMBER, (void *)&strInput); + if (autoCloseTime) + { + pDialog->m_autoCloseTime = autoCloseTime; + pDialog->SetAutoClose(autoCloseTime); + } + pDialog->DoModal(); - if (!pDialog->IsConfirmed() || pDialog->IsCanceled()) + if (!autoCloseTime && (!pDialog->IsConfirmed() || pDialog->IsCanceled())) return false; pDialog->GetOutput(&strInput); return true; diff --git a/xbmc/GUIDialogNumeric.h b/xbmc/GUIDialogNumeric.h index ff962655e7..196b98b2f4 100644 --- a/xbmc/GUIDialogNumeric.h +++ b/xbmc/GUIDialogNumeric.h @@ -48,7 +48,7 @@ public: static bool ShowAndGetTime(SYSTEMTIME &time, const CStdString &heading); static bool ShowAndGetDate(SYSTEMTIME &date, const CStdString &heading); static bool ShowAndGetIPAddress(CStdString &IPAddress, const CStdString &heading); - static bool ShowAndGetNumber(CStdString& strInput, const CStdString &strHeading); + static bool ShowAndGetNumber(CStdString& strInput, const CStdString &strHeading, unsigned int autoCloseTime = 0); static bool ShowAndGetSeconds(CStdString& timeString, const CStdString &heading); protected: @@ -69,5 +69,7 @@ protected: unsigned int m_block; // for time, date, and IP methods. unsigned int m_lastblock; bool m_dirty; // true if the current block has been changed. + int m_autoCloseTime; + CStdString m_password; // for password input CStdString m_number; ///< for number or password input }; diff --git a/xbmc/GUIDialogPVRChannelManager.cpp b/xbmc/GUIDialogPVRChannelManager.cpp new file mode 100644 index 0000000000..b2179295ec --- /dev/null +++ b/xbmc/GUIDialogPVRChannelManager.cpp @@ -0,0 +1,870 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialogPVRChannelManager.h" +#include "Application.h" +#include "GUISettings.h" +#include "GUIWindowManager.h" +#include "LocalizeStrings.h" +#include "MediaManager.h" +#include "Picture.h" +#include "PVRManager.h" +#include "Settings.h" +#include "TVDatabase.h" +#include "utils/log.h" +#include "utils/PVRChannels.h" +#include "GUISpinControlEx.h" +#include "GUIEditControl.h" +#include "GUIRadioButtonControl.h" +#include "GUIImage.h" +#include "GUIDialogYesNo.h" +#include "GUIDialogFileBrowser.h" +#include "GUIDialogPVRGroupManager.h" +#include "GUIDialogProgress.h" +#include "GUIDialogSelect.h" +#include "GUIDialogOK.h" +#include "GUIDialogKeyboard.h" + +#define BUTTON_OK 4 +#define BUTTON_APPLY 5 +#define BUTTON_CANCEL 6 +#define RADIOBUTTON_ACTIVE 7 +#define EDIT_NAME 8 +#define BUTTON_CHANNEL_LOGO 9 +#define IMAGE_CHANNEL_LOGO 10 +#define SPIN_GROUP_SELECTION 11 +#define RADIOBUTTON_USEEPG 12 +#define SPIN_EPGSOURCE_SELECTION 13 +#define CONTROL_LIST_CHANNELS 20 +#define BUTTON_GROUP_MANAGER 30 +#define BUTTON_EDIT_CHANNEL 31 +#define BUTTON_DELETE_CHANNEL 32 +#define BUTTON_NEW_CHANNEL 33 +#define BUTTON_RADIO_TV 34 + +using namespace std; + +CGUIDialogPVRChannelManager::CGUIDialogPVRChannelManager() + : CGUIDialog(WINDOW_DIALOG_PVR_CHANNEL_MANAGER, "DialogPVRChannelManager.xml") +{ + m_channelItems = new CFileItemList; + m_bIsRadio = false; +} + +CGUIDialogPVRChannelManager::~CGUIDialogPVRChannelManager() +{ + delete m_channelItems; +} + +bool CGUIDialogPVRChannelManager::OnAction(const CAction& action) +{ + int actionID = action.GetID(); + if (actionID == ACTION_PREVIOUS_MENU || actionID == ACTION_CLOSE_DIALOG) + { + Close(); + return true; + } + else if (actionID == ACTION_MOVE_DOWN || actionID == ACTION_MOVE_UP || + actionID == ACTION_PAGE_DOWN || actionID == ACTION_PAGE_UP) + { + if (GetFocusedControlID() == CONTROL_LIST_CHANNELS) + { + if (!m_bMovingMode) + { + CGUIDialog::OnAction(action); + unsigned int iSelected = m_viewControl.GetSelectedItem(); + if (iSelected != m_iSelected) + { + m_iSelected = iSelected; + SetData(m_iSelected); + } + return true; + } + else + { + CStdString number; + CGUIDialog::OnAction(action); + if (actionID == ACTION_MOVE_UP) + { + unsigned int newSelect = m_iSelected == 0 ? m_channelItems->Size()-1 : m_iSelected == 0 ? m_iSelected : m_iSelected-1; + if (m_channelItems->Get(newSelect)->GetProperty("Number") != "-") + { + number.Format("%i", m_iSelected+1); + m_channelItems->Get(newSelect)->SetProperty("Number", number); + number.Format("%i", newSelect+1); + m_channelItems->Get(m_iSelected)->SetProperty("Number", number); + } + m_channelItems->Swap(newSelect, m_iSelected); + m_iSelected = newSelect; + } + else if (actionID == ACTION_MOVE_DOWN) + { + int newSelect = m_iSelected >= m_channelItems->Size()-1 ? 0 : m_iSelected+1; + if (m_channelItems->Get(newSelect)->GetProperty("Number") != "-") + { + number.Format("%i", m_iSelected+1); + m_channelItems->Get(newSelect)->SetProperty("Number", number); + number.Format("%i", newSelect+1); + m_channelItems->Get(m_iSelected)->SetProperty("Number", number); + } + m_channelItems->Swap(newSelect, m_iSelected); + m_iSelected = newSelect; + } + else if (actionID == ACTION_PAGE_UP) + { + unsigned int lines = m_iSelected-m_viewControl.GetSelectedItem(); + for (unsigned int i = 0; i < lines; i++) + { + unsigned int newSelect = m_iSelected == 0 ? m_channelItems->Size()-1 : m_iSelected == 0 ? m_iSelected : m_iSelected-1; + if (m_channelItems->Get(newSelect)->GetProperty("Number") != "-") + { + number.Format("%i", m_iSelected+1); + m_channelItems->Get(newSelect)->SetProperty("Number", number); + number.Format("%i", newSelect+1); + m_channelItems->Get(m_iSelected)->SetProperty("Number", number); + } + m_channelItems->Swap(newSelect, m_iSelected); + m_iSelected = newSelect; + } + } + else if (actionID == ACTION_PAGE_DOWN) + { + unsigned int lines = m_viewControl.GetSelectedItem()-m_iSelected; + for (unsigned int i = 0; i < lines; i++) + { + int newSelect = m_iSelected >= m_channelItems->Size()-1 ? 0 : m_iSelected+1; + if (m_channelItems->Get(newSelect)->GetProperty("Number") != "-") + { + number.Format("%i", m_iSelected+1); + m_channelItems->Get(newSelect)->SetProperty("Number", number); + number.Format("%i", newSelect+1); + m_channelItems->Get(m_iSelected)->SetProperty("Number", number); + } + m_channelItems->Swap(newSelect, m_iSelected); + m_iSelected = newSelect; + } + } + m_viewControl.SetItems(*m_channelItems); + m_viewControl.SetSelectedItem(m_iSelected); + return true; + } + } + } + + return CGUIDialog::OnAction(action); +} + +bool CGUIDialogPVRChannelManager::OnMessage(CGUIMessage& message) +{ + unsigned int iControl = 0; + unsigned int iMessage = message.GetMessage(); + + switch (iMessage) + { + case GUI_MSG_WINDOW_DEINIT: + { + Clear(); + } + break; + + case GUI_MSG_WINDOW_INIT: + { + CGUIWindow::OnMessage(message); + m_iSelected = 0; + m_bIsRadio = false; + m_bMovingMode = false; + m_bContainsChanges = false; + SetProperty("IsRadio", ""); + Update(); + SetData(m_iSelected); + return true; + } + break; + + case GUI_MSG_CLICKED: + { + iControl = message.GetSenderId(); + if (iControl == CONTROL_LIST_CHANNELS) + { + if (!m_bMovingMode) + { + int iAction = message.GetParam1(); + int iItem = m_viewControl.GetSelectedItem(); + + /* Check file item is in list range and get his pointer */ + if (iItem < 0 || iItem >= (int)m_channelItems->Size()) return true; + + CFileItemPtr pItem = m_channelItems->Get(iItem); + + /* Process actions */ + if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK) + { + /* Show Contextmenu */ + OnPopupMenu(iItem); + } + } + else + { + CFileItemPtr pItem = m_channelItems->Get(m_iSelected); + pItem->SetProperty("Changed", true); + pItem->Select(false); + m_bMovingMode = false; + m_bContainsChanges = true; + return true; + } + } + else if (iControl == BUTTON_OK) + { + SaveList(); + Close(); + return true; + } + else if (iControl == BUTTON_APPLY) + { + SaveList(); + return true; + } + else if (iControl == BUTTON_CANCEL) + { + Close(); + return true; + } + else if (iControl == BUTTON_RADIO_TV) + { + if (m_bContainsChanges) + { + // prompt user for confirmation of channel record + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + if (!pDialog) + return true; + + pDialog->SetHeading(20052); + pDialog->SetLine(0, ""); + pDialog->SetLine(1, 19212); + pDialog->SetLine(2, 20103); + pDialog->DoModal(); + + if (pDialog->IsConfirmed()) + SaveList(); + } + + m_iSelected = 0; + m_bMovingMode = false; + m_bContainsChanges = false; + m_bIsRadio = !m_bIsRadio; + SetProperty("IsRadio", m_bIsRadio ? "true" : ""); + Update(); + SetData(m_iSelected); + return true; + } + else if (iControl == RADIOBUTTON_ACTIVE) + { + CGUIRadioButtonControl *pRadioButton = (CGUIRadioButtonControl *)GetControl(RADIOBUTTON_ACTIVE); + if (pRadioButton) + { + CFileItemPtr pItem = m_channelItems->Get(m_iSelected); + pItem->SetProperty("Changed", true); + pItem->SetProperty("ActiveChannel", pRadioButton->IsSelected()); + m_bContainsChanges = true; + Renumber(); + } + } + else if (iControl == EDIT_NAME) + { + CGUIEditControl *pEdit = (CGUIEditControl *)GetControl(EDIT_NAME); + if (pEdit) + { + CFileItemPtr pItem = m_channelItems->Get(m_iSelected); + pItem->SetProperty("Changed", true); + pItem->SetProperty("Name", pEdit->GetLabel2()); + m_bContainsChanges = true; + } + } + else if (iControl == BUTTON_CHANNEL_LOGO) + { + CFileItemPtr pItem = m_channelItems->Get(m_iSelected); + + if (g_settings.GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked()) + return false; + else if (!g_passwordManager.IsMasterLockUnlocked(true)) + return false; + + // setup our thumb list + CFileItemList items; + + // add the current thumb, if available + if (!pItem->GetProperty("Icon").IsEmpty()) + { + CFileItemPtr current(new CFileItem("thumb://Current", false)); + current->SetThumbnailImage(pItem->GetPVRChannelInfoTag()->Icon()); + current->SetLabel(g_localizeStrings.Get(20016)); + items.Add(current); + } + else if (pItem->HasThumbnail()) + { // already have a thumb that the share doesn't know about - must be a local one, so we mayaswell reuse it. + CFileItemPtr current(new CFileItem("thumb://Current", false)); + current->SetThumbnailImage(pItem->GetThumbnailImage()); + current->SetLabel(g_localizeStrings.Get(20016)); + items.Add(current); + } + + // and add a "no thumb" entry as well + CFileItemPtr nothumb(new CFileItem("thumb://None", false)); + nothumb->SetIconImage(pItem->GetIconImage()); + nothumb->SetLabel(g_localizeStrings.Get(20018)); + items.Add(nothumb); + + CStdString strThumb; + VECSOURCES shares; + if (g_guiSettings.GetString("pvrmenu.iconpath") != "") + { + CMediaSource share1; + share1.strPath = g_guiSettings.GetString("pvrmenu.iconpath"); + share1.strName = g_localizeStrings.Get(19018); + shares.push_back(share1); + } + g_mediaManager.GetLocalDrives(shares); + if (!CGUIDialogFileBrowser::ShowAndGetImage(items, shares, g_localizeStrings.Get(1030), strThumb)) + return false; + + if (strThumb == "thumb://Current") + return true; + + if (strThumb == "thumb://None") + strThumb = ""; + + pItem->SetProperty("Icon", strThumb); + pItem->SetProperty("Changed", true); + m_bContainsChanges = true; + return true; + } + else if (iControl == SPIN_GROUP_SELECTION) + { + CGUISpinControlEx *pSpin = (CGUISpinControlEx *)GetControl(SPIN_GROUP_SELECTION); + if (pSpin) + { + if (!m_bIsRadio && PVRChannelGroupsTV.size() == 0) + return true; + else if (m_bIsRadio && PVRChannelGroupsRadio.size() == 0) + return true; + + CFileItemPtr pItem = m_channelItems->Get(m_iSelected); + pItem->SetProperty("GroupId", (int)pSpin->GetValue()); + pItem->SetProperty("Changed", true); + m_bContainsChanges = true; + return true; + } + } + else if (iControl == RADIOBUTTON_USEEPG) + { + CGUIRadioButtonControl *pRadioButton = (CGUIRadioButtonControl *)GetControl(RADIOBUTTON_USEEPG); + if (pRadioButton) + { + CFileItemPtr pItem = m_channelItems->Get(m_iSelected); + pItem->SetProperty("Changed", true); + pItem->SetProperty("UseEPG", pRadioButton->IsSelected()); + m_bContainsChanges = true; + } + } + else if (iControl == SPIN_EPGSOURCE_SELECTION) + { + /// TODO: Add EPG scraper support + return true; + CGUISpinControlEx *pSpin = (CGUISpinControlEx *)GetControl(SPIN_EPGSOURCE_SELECTION); + if (pSpin) + { + CFileItemPtr pItem = m_channelItems->Get(m_iSelected); + pItem->SetProperty("EPGSource", (int)0); + pItem->SetProperty("Changed", true); + m_bContainsChanges = true; + return true; + } + } + else if (iControl == BUTTON_GROUP_MANAGER) + { + /* Load group manager dialog */ + CGUIDialogPVRGroupManager* pDlgInfo = (CGUIDialogPVRGroupManager*)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_GROUP_MANAGER); + if (!pDlgInfo) + return false; + + pDlgInfo->SetRadio(m_bIsRadio); + + /* Open dialog window */ + pDlgInfo->DoModal(); + + CGUISpinControlEx *pSpin = (CGUISpinControlEx *)GetControl(SPIN_GROUP_SELECTION); + if (pSpin) + { + pSpin->Clear(); + pSpin->AddLabel(g_localizeStrings.Get(19140), -1); + if (!m_bIsRadio) + { + for (unsigned int i = 0; i < PVRChannelGroupsTV.size(); i++) + pSpin->AddLabel(PVRChannelGroupsTV[i].GroupName(), PVRChannelGroupsTV[i].GroupID()); + } + else + { + for (unsigned int i = 0; i < PVRChannelGroupsRadio.size(); i++) + pSpin->AddLabel(PVRChannelGroupsRadio[i].GroupName(), PVRChannelGroupsTV[i].GroupID()); + } + pSpin->SetValue(m_channelItems->Get(m_iSelected)->GetPropertyInt("GroupId")); + } + + return true; + } + else if (iControl == BUTTON_EDIT_CHANNEL) + { + CFileItemPtr pItem = m_channelItems->Get(m_iSelected); + if (pItem->GetPropertyBOOL("Virtual")) + { + CStdString strURL = pItem->GetProperty("StreamURL"); + if (CGUIDialogKeyboard::ShowAndGetInput(strURL, g_localizeStrings.Get(19214), false)) + pItem->SetProperty("StreamURL", strURL); + return true; + } + + CGUIDialogOK::ShowAndGetInput(19033,19038,0,0); + return true; + } + else if (iControl == BUTTON_DELETE_CHANNEL) + { + CFileItemPtr pItem = m_channelItems->Get(m_iSelected); + + // prompt user for confirmation of channel record + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + if (!pDialog) + return true; + + pDialog->SetHeading(19211); + pDialog->SetLine(0, ""); + pDialog->SetLine(1, 750); + pDialog->SetLine(2, ""); + pDialog->DoModal(); + + if (pDialog->IsConfirmed()) + { + if (pItem->GetPropertyBOOL("Virtual")) + { + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + database->Open(); + database->RemoveDBChannel(*pItem->GetPVRChannelInfoTag()); + database->Close(); + + m_channelItems->Remove(m_iSelected); + m_viewControl.SetItems(*m_channelItems); + Renumber(); + return true; + } + CGUIDialogOK::ShowAndGetInput(19033,19038,0,0); + } + return true; + } + else if (iControl == BUTTON_NEW_CHANNEL) + { + std::vector clients; + + CGUIDialogSelect* pDlgSelect = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT); + if (!pDlgSelect) + return false; + + pDlgSelect->SetHeading(19213); // Select Client + pDlgSelect->Add(g_localizeStrings.Get(19209)); + clients.push_back(999); + CLIENTMAPITR itr; + for (itr = g_PVRManager.Clients()->begin() ; itr != g_PVRManager.Clients()->end(); itr++) + { + CStdString strClient = (*itr).second->GetBackendName() + ":" + (*itr).second->GetConnectionString(); + clients.push_back((*itr).first); + pDlgSelect->Add(strClient); + } + + pDlgSelect->DoModal(); + + int selection = pDlgSelect->GetSelectedLabel(); + if (selection >= 0 && selection <= clients.size()) + { + int clientID = clients[selection]; + if (clientID == 999) + { + CStdString strURL = ""; + if (CGUIDialogKeyboard::ShowAndGetInput(strURL, g_localizeStrings.Get(19214), false)) + { + if (!strURL.IsEmpty()) + { + cPVRChannelInfoTag newchannel; + newchannel.Reset(); + newchannel.SetName(g_localizeStrings.Get(19204)); + newchannel.SetRadio(m_bIsRadio); + newchannel.SetGrabEpg(false); + newchannel.SetVirtual(true); + newchannel.SetStreamURL(strURL); + newchannel.SetClientID(999); + + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + database->Open(); + newchannel.SetChannelID(database->AddDBChannel(newchannel)); + database->Close(); + CFileItemPtr channel(new CFileItem(newchannel)); + + channel->SetProperty("ActiveChannel", true); + channel->SetProperty("Name", g_localizeStrings.Get(19204)); + channel->SetProperty("UseEPG", false); + channel->SetProperty("GroupId", (int)newchannel.GroupID()); + channel->SetProperty("Icon", newchannel.Icon()); + channel->SetProperty("EPGSource", (int)0); + channel->SetProperty("ClientName", g_localizeStrings.Get(19209)); + + m_channelItems->AddFront(channel, m_iSelected); + m_viewControl.SetItems(*m_channelItems); + Renumber(); + } + } + } + else + { + CGUIDialogOK::ShowAndGetInput(19033,19038,0,0); + } + } + return true; + } + } + break; + } + + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogPVRChannelManager::OnWindowLoaded() +{ + CGUIDialog::OnWindowLoaded(); + + m_viewControl.Reset(); + m_viewControl.SetParentWindow(GetID()); + m_viewControl.AddView(GetControl(CONTROL_LIST_CHANNELS)); +} + +void CGUIDialogPVRChannelManager::OnWindowUnload() +{ + CGUIDialog::OnWindowUnload(); + m_viewControl.Reset(); +} + +CFileItemPtr CGUIDialogPVRChannelManager::GetCurrentListItem(int offset) +{ + return m_channelItems->Get(m_iSelected); +} + +bool CGUIDialogPVRChannelManager::OnPopupMenu(int iItem) +{ + // popup the context menu + // grab our context menu + CContextButtons buttons; + + // mark the item + if (iItem >= 0 && iItem < m_channelItems->Size()) + m_channelItems->Get(iItem)->Select(true); + else + return false; + + CFileItemPtr pItem = m_channelItems->Get(iItem); + + buttons.Add(CONTEXT_BUTTON_MOVE, 116); /* Move channel up or down */ + if (pItem->GetPropertyBOOL("Virtual")) + buttons.Add(CONTEXT_BUTTON_EDIT_SOURCE, 1027); /* Edit virtual channel URL */ + + int choice = CGUIDialogContextMenu::ShowAndGetChoice(buttons); + + // deselect our item + if (iItem >= 0 && iItem < m_channelItems->Size()) + m_channelItems->Get(iItem)->Select(false); + + if (choice < 0) + return false; + + return OnContextButton(iItem, (CONTEXT_BUTTON)choice); +} + +bool CGUIDialogPVRChannelManager::OnContextButton(int itemNumber, CONTEXT_BUTTON button) +{ + /* Check file item is in list range and get his pointer */ + if (itemNumber < 0 || itemNumber >= (int)m_channelItems->Size()) return false; + + CFileItemPtr pItem = m_channelItems->Get(itemNumber); + + if (button == CONTEXT_BUTTON_MOVE) + { + m_bMovingMode = true; + pItem->Select(true); + } + else if (button == CONTEXT_BUTTON_EDIT_SOURCE) + { + CStdString strURL = pItem->GetProperty("StreamURL"); + if (CGUIDialogKeyboard::ShowAndGetInput(strURL, g_localizeStrings.Get(19214), false)) + pItem->SetProperty("StreamURL", strURL); + } + return true; +} + +void CGUIDialogPVRChannelManager::SetData(int iItem) +{ + CGUISpinControlEx *pSpin; + CGUIEditControl *pEdit; + CGUIRadioButtonControl *pRadioButton; + + /* Check file item is in list range and get his pointer */ + if (iItem < 0 || iItem >= (int)m_channelItems->Size()) return; + + CFileItemPtr pItem = m_channelItems->Get(iItem); +// cPVRChannelInfoTag *infotag = pItem->GetPVRChannelInfoTag(); + + pEdit = (CGUIEditControl *)GetControl(EDIT_NAME); + if (pEdit) + { + pEdit->SetLabel2(pItem->GetProperty("Name")); + pEdit->SetInputType(CGUIEditControl::INPUT_TYPE_TEXT, 19208); + } + + pRadioButton = (CGUIRadioButtonControl *)GetControl(RADIOBUTTON_ACTIVE); + if (pRadioButton) pRadioButton->SetSelected(pItem->GetPropertyBOOL("ActiveChannel")); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(RADIOBUTTON_USEEPG); + if (pRadioButton) pRadioButton->SetSelected(pItem->GetPropertyBOOL("UseEPG")); + + pSpin = (CGUISpinControlEx *)GetControl(SPIN_GROUP_SELECTION); + if (pSpin) pSpin->SetValue(pItem->GetPropertyInt("GroupId")); + + pSpin = (CGUISpinControlEx *)GetControl(SPIN_GROUP_SELECTION); + if (pSpin) pSpin->SetValue(pItem->GetPropertyInt("EPGSource")); + +} + +void CGUIDialogPVRChannelManager::Update() +{ + // lock our display, as this window is rendered from the player thread + g_graphicsContext.Lock(); + m_viewControl.SetCurrentView(CONTROL_LIST_CHANNELS); + + // empty the lists ready for population + Clear(); + + if (!m_bIsRadio) + { + for (unsigned int i = 0; i < PVRChannelsTV.size(); i++) + { + CFileItemPtr channel(new CFileItem(PVRChannelsTV[i])); + channel->SetProperty("ActiveChannel", (bool)!PVRChannelsTV[i].IsHidden()); + channel->SetProperty("Name", PVRChannelsTV[i].Name()); + channel->SetProperty("UseEPG", PVRChannelsTV[i].GrabEpg()); + channel->SetProperty("GroupId", (int)PVRChannelsTV[i].GroupID()); + channel->SetProperty("Icon", PVRChannelsTV[i].Icon()); + channel->SetProperty("EPGSource", (int)0); + CStdString number; number.Format("%i", PVRChannelsTV[i].Number()); + channel->SetProperty("Number", number); + + if (PVRChannelsTV[i].IsVirtual()) + { + channel->SetProperty("Virtual", true); + channel->SetProperty("StreamURL", PVRChannelsTV[i].StreamURL()); + } + + CStdString clientName; + if (PVRChannelsTV[i].ClientID() == 999) /* XBMC internal */ + clientName = g_localizeStrings.Get(19209); + else + clientName = g_PVRManager.Clients()->find(PVRChannelsTV[i].ClientID())->second->GetBackendName() + ":" + g_PVRManager.Clients()->find(PVRChannelsTV[i].ClientID())->second->GetConnectionString(); + channel->SetProperty("ClientName", clientName); + + m_channelItems->Add(channel); + } + + CGUISpinControlEx *pSpin = (CGUISpinControlEx *)GetControl(SPIN_GROUP_SELECTION); + if (pSpin) + { + pSpin->Clear(); + pSpin->AddLabel(g_localizeStrings.Get(19140), -1); + for (unsigned int i = 0; i < PVRChannelGroupsTV.size(); i++) + { + pSpin->AddLabel(PVRChannelGroupsTV[i].GroupName(), PVRChannelGroupsTV[i].GroupID()); + } + } + + pSpin = (CGUISpinControlEx *)GetControl(SPIN_EPGSOURCE_SELECTION); + if (pSpin) + { + pSpin->Clear(); + pSpin->AddLabel(g_localizeStrings.Get(19210), 0); + /// TODO: Add Labels for EPG scrapers here + } + } + else + { + for (unsigned int i = 0; i < PVRChannelsRadio.size(); i++) + { + CFileItemPtr channel(new CFileItem(PVRChannelsRadio[i])); + channel->SetProperty("ActiveChannel", (bool)!PVRChannelsRadio[i].IsHidden()); + channel->SetProperty("Name", PVRChannelsRadio[i].Name()); + channel->SetProperty("UseEPG", PVRChannelsRadio[i].GrabEpg()); + channel->SetProperty("GroupId", (int)PVRChannelsRadio[i].GroupID()); + channel->SetProperty("Icon", PVRChannelsRadio[i].Icon()); + channel->SetProperty("EPGSource", (int)0); + CStdString number; number.Format("%i", PVRChannelsRadio[i].Number()); + channel->SetProperty("Number", number); + + if (PVRChannelsRadio[i].IsVirtual()) + { + channel->SetProperty("Virtual", true); + channel->SetProperty("StreamURL", PVRChannelsRadio[i].StreamURL()); + } + + CStdString clientName; + if (PVRChannelsRadio[i].ClientID() == 999) /* XBMC internal */ + clientName = g_localizeStrings.Get(19209); + else + clientName = g_PVRManager.Clients()->find(PVRChannelsRadio[i].ClientID())->second->GetBackendName() + ":" + g_PVRManager.Clients()->find(PVRChannelsRadio[i].ClientID())->second->GetConnectionString(); + channel->SetProperty("ClientName", clientName); + + m_channelItems->Add(channel); + } + + CGUISpinControlEx *pSpin = (CGUISpinControlEx *)GetControl(SPIN_GROUP_SELECTION); + if (pSpin) + { + pSpin->Clear(); + pSpin->AddLabel(g_localizeStrings.Get(19140), -1); + for (unsigned int i = 0; i < PVRChannelGroupsRadio.size(); i++) + { + pSpin->AddLabel(PVRChannelGroupsRadio[i].GroupName(), PVRChannelGroupsRadio[i].GroupID()); + } + } + + pSpin = (CGUISpinControlEx *)GetControl(SPIN_EPGSOURCE_SELECTION); + if (pSpin) + { + pSpin->Clear(); + pSpin->AddLabel(g_localizeStrings.Get(19210), 0); + /// TODO: Add Labels for EPG scrapers here + } + } + + Renumber(); + m_viewControl.SetItems(*m_channelItems); + m_viewControl.SetSelectedItem(m_iSelected); + + g_graphicsContext.Unlock(); +} + +void CGUIDialogPVRChannelManager::Clear() +{ + m_viewControl.Clear(); + m_channelItems->Clear(); +} + +void CGUIDialogPVRChannelManager::SaveList() +{ + if (!m_bContainsChanges) + return; + + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + database->Open(); + + CGUIDialogProgress* pDlgProgress = (CGUIDialogProgress*)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS); + pDlgProgress->SetHeading(190); + pDlgProgress->SetLine(0, ""); + pDlgProgress->SetLine(1, 328); + pDlgProgress->SetLine(2, ""); + pDlgProgress->StartModal(); + pDlgProgress->Progress(); + pDlgProgress->SetPercentage(0); + + int activeChannels = 0; + for (int i = 0; i < m_channelItems->Size(); i++) + { + if (m_channelItems->Get(i)->GetPropertyBOOL("ActiveChannel")) + activeChannels++; + } + + for (int i = 0; i < m_channelItems->Size(); i++) + { + CFileItemPtr pItem = m_channelItems->Get(i); + cPVRChannelInfoTag *tag = pItem->GetPVRChannelInfoTag(); + if (!pItem->GetPropertyBOOL("ActiveChannel")) + tag->SetNumber(1+activeChannels++); + else + tag->SetNumber(atoi(pItem->GetProperty("Number"))); + tag->SetName(pItem->GetProperty("Name")); + tag->SetHidden(!pItem->GetPropertyBOOL("ActiveChannel")); + tag->SetIcon(pItem->GetProperty("Icon")); + tag->SetGroupID(pItem->GetPropertyInt("GroupId")); + + if (pItem->GetPropertyBOOL("Virtual")) + { + tag->SetStreamURL(pItem->GetProperty("StreamURL")); + } + + CStdString prevEPGSource = tag->Grabber(); + int epgSource = pItem->GetPropertyInt("EPGSource"); + if (epgSource == 0) + tag->SetGrabber("client"); + + if ((tag->GrabEpg() && !pItem->GetPropertyBOOL("UseEPG")) || prevEPGSource != tag->Grabber()) + { + database->EraseChannelEPG(tag->ChannelID()); + PVREpgs.ClearChannel(tag->ChannelID()); + } + tag->SetGrabEpg(pItem->GetPropertyBOOL("UseEPG")); + + database->UpdateDBChannel(*tag); + pItem->SetProperty("Changed", false); + pDlgProgress->SetPercentage(i * 100 / m_channelItems->Size()); + } + + database->Close(); + if (!m_bIsRadio) + { + PVRChannelsTV.Unload(); + PVRChannelsTV.Load(false); + } + else + { + PVRChannelsRadio.Unload(); + PVRChannelsRadio.Load(true); + } + m_bContainsChanges = false; + pDlgProgress->Close(); +} + +void CGUIDialogPVRChannelManager::Renumber() +{ + int number = 1; + CStdString strNumber; + CFileItemPtr pItem; + for (int i = 0; i < m_channelItems->Size(); i++) + { + pItem = m_channelItems->Get(i); + if (pItem->GetPropertyBOOL("ActiveChannel")) + { + strNumber.Format("%i", number++); + pItem->SetProperty("Number", strNumber); + } + else + pItem->SetProperty("Number", "-"); + } +} diff --git a/xbmc/GUIDialogPVRChannelManager.h b/xbmc/GUIDialogPVRChannelManager.h new file mode 100644 index 0000000000..235c3640c8 --- /dev/null +++ b/xbmc/GUIDialogPVRChannelManager.h @@ -0,0 +1,56 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialog.h" +#include "GUIViewControl.h" +#include "GUIDialogContextMenu.h" + +class CGUIDialogPVRChannelManager : public CGUIDialog +{ +public: + CGUIDialogPVRChannelManager(void); + virtual ~CGUIDialogPVRChannelManager(void); + virtual bool OnMessage(CGUIMessage& message); + virtual bool OnAction(const CAction& action); + virtual void OnWindowLoaded(); + virtual void OnWindowUnload(); + virtual bool HasListItems() const { return true; }; + virtual CFileItemPtr GetCurrentListItem(int offset = 0); + +protected: + virtual bool OnPopupMenu(int iItem); + virtual bool OnContextButton(int itemNumber, CONTEXT_BUTTON button); + +private: + void Clear(); + void Update(); + void SaveList(); + void Renumber(); + void SetData(int iItem); + bool m_bIsRadio; + bool m_bMovingMode; + bool m_bContainsChanges; + + unsigned int m_iSelected; + CFileItemList* m_channelItems; + CGUIViewControl m_viewControl; +}; diff --git a/xbmc/GUIDialogPVRChannelsOSD.cpp b/xbmc/GUIDialogPVRChannelsOSD.cpp new file mode 100644 index 0000000000..c17edef2aa --- /dev/null +++ b/xbmc/GUIDialogPVRChannelsOSD.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialogPVRChannelsOSD.h" +#include "Application.h" +#include "FileItem.h" +#include "GUIWindowManager.h" +#include "GUIDialogOK.h" +#include "GUIDialogPVRGuideInfo.h" +#include "PVRManager.h" +#include "ViewState.h" + +using namespace std; + +#define CONTROL_LIST 11 + +CGUIDialogPVRChannelsOSD::CGUIDialogPVRChannelsOSD() + : CGUIDialog(WINDOW_DIALOG_PVR_OSD_CHANNELS, "DialogPVRChannelsOSD.xml") +{ + m_vecItems = new CFileItemList; +} + +CGUIDialogPVRChannelsOSD::~CGUIDialogPVRChannelsOSD() +{ + delete m_vecItems; +} + +bool CGUIDialogPVRChannelsOSD::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_DEINIT: + { + Clear(); + } + break; + + case GUI_MSG_WINDOW_INIT: + { + /* Close dialog immediately if now TV or radio channel is playing */ + if (!g_PVRManager.IsPlayingTV() && !g_PVRManager.IsPlayingRadio()) + { + Close(); + return true; + } + CGUIWindow::OnMessage(message); + Update(); + return true; + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + + if (m_viewControl.HasControl(iControl)) // list/thumb control + { + int iItem = m_viewControl.GetSelectedItem(); + int iAction = message.GetParam1(); + + if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) + { + /* Switch to channel */ + GotoChannel(iItem); + return true; + } + else if (iAction == ACTION_SHOW_INFO || iAction == ACTION_MOUSE_RIGHT_CLICK) + { + /* Show information Dialog */ + ShowInfo(iItem); + return true; + } + } + } + break; + } + + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogPVRChannelsOSD::Update() +{ + // lock our display, as this window is rendered from the player thread + g_graphicsContext.Lock(); + m_viewControl.SetCurrentView(DEFAULT_VIEW_LIST); + + // empty the list ready for population + Clear(); + + bool RadioPlaying; + int CurrentChannel; + g_PVRManager.GetCurrentChannel(&CurrentChannel, &RadioPlaying); + + if (!RadioPlaying) + PVRChannelsTV.GetChannels(m_vecItems, g_PVRManager.GetPlayingGroup()); + else + PVRChannelsRadio.GetChannels(m_vecItems, g_PVRManager.GetPlayingGroup()); + + m_viewControl.SetItems(*m_vecItems); + m_viewControl.SetSelectedItem(CurrentChannel-1); + g_graphicsContext.Unlock(); +} + +void CGUIDialogPVRChannelsOSD::Clear() +{ + m_viewControl.Clear(); + m_vecItems->Clear(); +} + +void CGUIDialogPVRChannelsOSD::GotoChannel(int item) +{ + /* Check file item is in list range and get his pointer */ + if (item < 0 || item >= (int)m_vecItems->Size()) return; + + CFileItemPtr pItem = m_vecItems->Get(item); + + if (pItem->m_strPath == g_application.CurrentFile()) + { + Close(); + return; + } + + if (!g_application.PlayFile(*pItem)) + { + CGUIDialogOK::ShowAndGetInput(19033,0,19136,0); + return; + } + m_viewControl.SetItems(*m_vecItems); + m_viewControl.SetSelectedItem(item); +} + +void CGUIDialogPVRChannelsOSD::ShowInfo(int item) +{ + /* Check file item is in list range and get his pointer */ + if (item < 0 || item >= (int)m_vecItems->Size()) return; + + CFileItemPtr pItem = m_vecItems->Get(item); + if (pItem && pItem->IsPVRChannel()) + { + /* Get the current running show on this channel from the EPG storage */ + const cPVREPGInfoTag *epgnow = PVREpgs.GetEPG(pItem->GetPVRChannelInfoTag(), true)->GetInfoTagNow(); + CFileItem *itemNow = new CFileItem(*epgnow); + + /* Load programme info dialog */ + CGUIDialogPVRGuideInfo* pDlgInfo = (CGUIDialogPVRGuideInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_GUIDE_INFO); + if (!pDlgInfo) + return; + + /* inform dialog about the file item and open dialog window */ + pDlgInfo->SetProgInfo(itemNow); + pDlgInfo->DoModal(); + delete itemNow; /* delete previuosly created FileItem */ + } + + return; +} + +void CGUIDialogPVRChannelsOSD::OnWindowLoaded() +{ + CGUIDialog::OnWindowLoaded(); + m_viewControl.Reset(); + m_viewControl.SetParentWindow(GetID()); + m_viewControl.AddView(GetControl(CONTROL_LIST)); +} + +void CGUIDialogPVRChannelsOSD::OnWindowUnload() +{ + CGUIDialog::OnWindowUnload(); + m_viewControl.Reset(); +} + +CGUIControl *CGUIDialogPVRChannelsOSD::GetFirstFocusableControl(int id) +{ + if (m_viewControl.HasControl(id)) + id = m_viewControl.GetCurrentControl(); + + return CGUIWindow::GetFirstFocusableControl(id); +} diff --git a/xbmc/GUIDialogPVRChannelsOSD.h b/xbmc/GUIDialogPVRChannelsOSD.h new file mode 100644 index 0000000000..66a43e2d8e --- /dev/null +++ b/xbmc/GUIDialogPVRChannelsOSD.h @@ -0,0 +1,46 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialog.h" +#include "GUIViewControl.h" + +class CFileItemList; + +class CGUIDialogPVRChannelsOSD : public CGUIDialog +{ +public: + CGUIDialogPVRChannelsOSD(void); + virtual ~CGUIDialogPVRChannelsOSD(void); + virtual bool OnMessage(CGUIMessage& message); + virtual void OnWindowLoaded(); + virtual void OnWindowUnload(); + +protected: + void GotoChannel(int iItem); + void ShowInfo(int item); + void Clear(); + void Update(); + CGUIControl *GetFirstFocusableControl(int id); + + CFileItemList *m_vecItems; + CGUIViewControl m_viewControl; +}; diff --git a/xbmc/GUIDialogPVRCutterOSD.cpp b/xbmc/GUIDialogPVRCutterOSD.cpp new file mode 100644 index 0000000000..1b5a73326c --- /dev/null +++ b/xbmc/GUIDialogPVRCutterOSD.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialogPVRCutterOSD.h" +#include "utils/log.h" +#include "Application.h" + +using namespace std; + +CGUIDialogPVRCutterOSD::CGUIDialogPVRCutterOSD() + : CGUIDialog(WINDOW_DIALOG_PVR_OSD_CUTTER, "DialogPVRCutterOSD.xml") +{ +} + +CGUIDialogPVRCutterOSD::~CGUIDialogPVRCutterOSD() +{ +} + +bool CGUIDialogPVRCutterOSD::OnAction(const CAction& action) +{ + if (action.GetID() == ACTION_PREVIOUS_MENU || action.GetID() == ACTION_CLOSE_DIALOG) + { + Close(); + return true; + } + + return CGUIDialog::OnAction(action); +} + +bool CGUIDialogPVRCutterOSD::OnMessage(CGUIMessage& message) +{ + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogPVRCutterOSD::OnInitWindow() +{ + CGUIDialog::OnInitWindow(); +} + +void CGUIDialogPVRCutterOSD::OnDeinitWindow(int nextWindowID) +{ + CGUIDialog::OnDeinitWindow(nextWindowID); +} diff --git a/xbmc/GUIDialogPVRCutterOSD.h b/xbmc/GUIDialogPVRCutterOSD.h new file mode 100644 index 0000000000..35376d557a --- /dev/null +++ b/xbmc/GUIDialogPVRCutterOSD.h @@ -0,0 +1,34 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialog.h" + +class CGUIDialogPVRCutterOSD : public CGUIDialog +{ +public: + CGUIDialogPVRCutterOSD(void); + virtual ~CGUIDialogPVRCutterOSD(void); + virtual bool OnMessage(CGUIMessage& message); + virtual bool OnAction(const CAction& action); + virtual void OnInitWindow(); + virtual void OnDeinitWindow(int nextWindowID); +}; diff --git a/xbmc/GUIDialogPVRDirectorOSD.cpp b/xbmc/GUIDialogPVRDirectorOSD.cpp new file mode 100644 index 0000000000..e99c6896b8 --- /dev/null +++ b/xbmc/GUIDialogPVRDirectorOSD.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * DESCRIPTION: + * + * Used in Fullscreen view to control, multifeed channel groups. + * + */ + +#include "GUIDialogPVRDirectorOSD.h" +#include "utils/log.h" +#include "Application.h" + +using namespace std; + +CGUIDialogPVRDirectorOSD::CGUIDialogPVRDirectorOSD() + : CGUIDialog(WINDOW_DIALOG_PVR_OSD_DIRECTOR, "DialogPVRDirectorOSD.xml") +{ +} + +CGUIDialogPVRDirectorOSD::~CGUIDialogPVRDirectorOSD() +{ +} + +bool CGUIDialogPVRDirectorOSD::OnAction(const CAction& action) +{ + if (action.GetID() == ACTION_PREVIOUS_MENU || action.GetID() == ACTION_CLOSE_DIALOG) + { + Close(); + return true; + } + + return CGUIDialog::OnAction(action); +} + +bool CGUIDialogPVRDirectorOSD::OnMessage(CGUIMessage& message) +{ + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogPVRDirectorOSD::OnInitWindow() +{ + CGUIDialog::OnInitWindow(); +} + +void CGUIDialogPVRDirectorOSD::OnDeinitWindow(int nextWindowID) +{ + CGUIDialog::OnDeinitWindow(nextWindowID); +} diff --git a/xbmc/GUIDialogPVRDirectorOSD.h b/xbmc/GUIDialogPVRDirectorOSD.h new file mode 100644 index 0000000000..7cc527481a --- /dev/null +++ b/xbmc/GUIDialogPVRDirectorOSD.h @@ -0,0 +1,34 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialog.h" + +class CGUIDialogPVRDirectorOSD : public CGUIDialog +{ +public: + CGUIDialogPVRDirectorOSD(void); + virtual ~CGUIDialogPVRDirectorOSD(void); + virtual bool OnMessage(CGUIMessage& message); + virtual bool OnAction(const CAction& action); + virtual void OnInitWindow(); + virtual void OnDeinitWindow(int nextWindowID); +}; diff --git a/xbmc/GUIDialogPVRGroupManager.cpp b/xbmc/GUIDialogPVRGroupManager.cpp new file mode 100644 index 0000000000..bd6c431caf --- /dev/null +++ b/xbmc/GUIDialogPVRGroupManager.cpp @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialogPVRGroupManager.h" +#include "Application.h" +#include "FileItem.h" +#include "GUIDialogKeyboard.h" +#include "GUIDialogOK.h" +#include "GUIDialogYesNo.h" +#include "GUIWindowManager.h" +#include "LocalizeStrings.h" +#include "utils/PVRChannels.h" + +using namespace std; + +#define CONTROL_LIST_CHANNELS_LEFT 11 +#define CONTROL_LIST_CHANNELS_RIGHT 12 +#define CONTROL_LIST_CHANNEL_GROUPS 13 +#define CONTROL_CURRENT_GROUP_LABEL 20 +#define BUTTON_NEWGROUP 26 +#define BUTTON_RENAMEGROUP 27 +#define BUTTON_DELGROUP 28 +#define BUTTON_OK 29 + +CGUIDialogPVRGroupManager::CGUIDialogPVRGroupManager() + : CGUIDialog(WINDOW_DIALOG_PVR_GROUP_MANAGER, "DialogPVRGroupManager.xml") +{ + m_channelLeftItems = new CFileItemList; + m_channelRightItems = new CFileItemList; + m_channelGroupItems = new CFileItemList; +} + +CGUIDialogPVRGroupManager::~CGUIDialogPVRGroupManager() +{ + delete m_channelLeftItems; + delete m_channelRightItems; + delete m_channelGroupItems; +} + +bool CGUIDialogPVRGroupManager::OnMessage(CGUIMessage& message) +{ + unsigned int iControl = 0; + unsigned int iMessage = message.GetMessage(); + + switch (iMessage) + { + case GUI_MSG_WINDOW_DEINIT: + { + Clear(); + } + break; + + case GUI_MSG_WINDOW_INIT: + { + CGUIWindow::OnMessage(message); + m_iSelectedLeft = 0; + m_iSelectedRight = 0; + m_iSelectedGroup = 0; + Update(); + return true; + } + break; + + case GUI_MSG_CLICKED: + { + iControl = message.GetSenderId(); + + if (iControl == BUTTON_OK) + { + Close(); + } + else if (iControl == BUTTON_NEWGROUP) + { + CStdString strDescription = ""; + if (CGUIDialogKeyboard::ShowAndGetInput(strDescription, g_localizeStrings.Get(19139), false)) + { + if (strDescription != "") + { + if (!m_bIsRadio) + PVRChannelGroupsTV.AddGroup(strDescription); + else + PVRChannelGroupsRadio.AddGroup(strDescription); + Update(); + } + } + } + else if (iControl == BUTTON_DELGROUP && m_channelGroupItems->GetFileCount() != 0) + { + m_iSelectedGroup = m_viewControlGroup.GetSelectedItem(); + CFileItemPtr pItemGroup = m_channelGroupItems->Get(m_iSelectedGroup); + + // prompt user for confirmation of channel record + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + if (pDialog) + { + CStdString groupName; + if (!m_bIsRadio) + groupName = PVRChannelGroupsTV.GetGroupName(atoi(pItemGroup->m_strPath.c_str())); + else + groupName = PVRChannelGroupsRadio.GetGroupName(atoi(pItemGroup->m_strPath.c_str())); + + pDialog->SetHeading(117); + pDialog->SetLine(0, ""); + pDialog->SetLine(1, groupName); + pDialog->SetLine(2, ""); + pDialog->DoModal(); + + if (pDialog->IsConfirmed()) + { + if (!m_bIsRadio) + PVRChannelGroupsTV.DeleteGroup(atoi(pItemGroup->m_strPath.c_str())); + else + PVRChannelGroupsRadio.DeleteGroup(atoi(pItemGroup->m_strPath.c_str())); + Update(); + } + } + return true; + } + else if (iControl == BUTTON_RENAMEGROUP && m_channelGroupItems->GetFileCount() != 0) + { + if (CGUIDialogKeyboard::ShowAndGetInput(m_CurrentGroupName, g_localizeStrings.Get(19139), false)) + { + if (m_CurrentGroupName != "") + { + if (!m_bIsRadio) + PVRChannelGroupsTV.RenameGroup(atoi(m_channelGroupItems->Get(m_iSelectedGroup)->m_strPath.c_str()), m_CurrentGroupName); + else + PVRChannelGroupsRadio.RenameGroup(atoi(m_channelGroupItems->Get(m_iSelectedGroup)->m_strPath.c_str()), m_CurrentGroupName); + Update(); + } + } + } + else if (m_viewControlLeft.HasControl(iControl)) // list/thumb control + { + m_iSelectedLeft = m_viewControlLeft.GetSelectedItem(); + int iAction = message.GetParam1(); + + if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) + { + if (m_channelGroupItems->GetFileCount() == 0) + { + CGUIDialogOK::ShowAndGetInput(19033,19137,0,19138); + } + else if (m_channelLeftItems->GetFileCount() > 0) + { + CFileItemPtr pItemGroup = m_channelGroupItems->Get(m_iSelectedGroup); + CFileItemPtr pItemChannel = m_channelLeftItems->Get(m_iSelectedLeft); + if (!m_bIsRadio) + PVRChannelGroupsTV.ChannelToGroup(*pItemChannel->GetPVRChannelInfoTag(), atoi(pItemGroup->m_strPath.c_str())); + else + PVRChannelGroupsRadio.ChannelToGroup(*pItemChannel->GetPVRChannelInfoTag(), atoi(pItemGroup->m_strPath.c_str())); + Update(); + } + return true; + } + } + else if (m_viewControlRight.HasControl(iControl)) // list/thumb control + { + m_iSelectedRight = m_viewControlRight.GetSelectedItem(); + int iAction = message.GetParam1(); + + if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) + { + if (m_channelRightItems->GetFileCount() > 0) + { + CFileItemPtr pItemChannel = m_channelRightItems->Get(m_iSelectedRight); + if (!m_bIsRadio) + PVRChannelGroupsTV.ChannelToGroup(*pItemChannel->GetPVRChannelInfoTag(), 0); + else + PVRChannelGroupsRadio.ChannelToGroup(*pItemChannel->GetPVRChannelInfoTag(), 0); + Update(); + } + return true; + } + } + else if (m_viewControlGroup.HasControl(iControl)) // list/thumb control + { + int iAction = message.GetParam1(); + + if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) + { + m_iSelectedGroup = m_viewControlGroup.GetSelectedItem(); + Update(); + return true; + } + } + } + break; + } + + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogPVRGroupManager::OnWindowLoaded() +{ + CGUIDialog::OnWindowLoaded(); + + m_viewControlLeft.Reset(); + m_viewControlLeft.SetParentWindow(GetID()); + m_viewControlLeft.AddView(GetControl(CONTROL_LIST_CHANNELS_LEFT)); + + m_viewControlRight.Reset(); + m_viewControlRight.SetParentWindow(GetID()); + m_viewControlRight.AddView(GetControl(CONTROL_LIST_CHANNELS_RIGHT)); + + m_viewControlGroup.Reset(); + m_viewControlGroup.SetParentWindow(GetID()); + m_viewControlGroup.AddView(GetControl(CONTROL_LIST_CHANNEL_GROUPS)); +} + +void CGUIDialogPVRGroupManager::OnWindowUnload() +{ + CGUIDialog::OnWindowUnload(); + m_viewControlLeft.Reset(); + m_viewControlRight.Reset(); + m_viewControlGroup.Reset(); +} + +void CGUIDialogPVRGroupManager::Update() +{ + m_CurrentGroupName = ""; + + // lock our display, as this window is rendered from the player thread + g_graphicsContext.Lock(); + m_viewControlLeft.SetCurrentView(CONTROL_LIST_CHANNELS_LEFT); + m_viewControlRight.SetCurrentView(CONTROL_LIST_CHANNELS_RIGHT); + m_viewControlGroup.SetCurrentView(CONTROL_LIST_CHANNEL_GROUPS); + + // empty the lists ready for population + Clear(); + + int groups; + if (!m_bIsRadio) + groups = PVRChannelGroupsTV.GetGroupList(m_channelGroupItems); + else + groups = PVRChannelGroupsRadio.GetGroupList(m_channelGroupItems); + + m_viewControlGroup.SetItems(*m_channelGroupItems); + m_viewControlGroup.SetSelectedItem(m_iSelectedGroup); + + if (!m_bIsRadio) + PVRChannelsTV.GetChannels(m_channelLeftItems, 0); + else + PVRChannelsRadio.GetChannels(m_channelLeftItems, 0); + m_viewControlLeft.SetItems(*m_channelLeftItems); + m_viewControlLeft.SetSelectedItem(m_iSelectedLeft); + + if (groups > 0) + { + CFileItemPtr pItem = m_channelGroupItems->Get(m_viewControlGroup.GetSelectedItem()); + m_CurrentGroupName = pItem->m_strTitle; + SET_CONTROL_LABEL(CONTROL_CURRENT_GROUP_LABEL, m_CurrentGroupName); + + if (!m_bIsRadio) + PVRChannelsTV.GetChannels(m_channelRightItems, atoi(pItem->m_strPath.c_str())); + else + PVRChannelsRadio.GetChannels(m_channelRightItems, atoi(pItem->m_strPath.c_str())); + m_viewControlRight.SetItems(*m_channelRightItems); + m_viewControlRight.SetSelectedItem(m_iSelectedRight); + } + + g_graphicsContext.Unlock(); +} + +void CGUIDialogPVRGroupManager::Clear() +{ + m_viewControlLeft.Clear(); + m_viewControlRight.Clear(); + m_viewControlGroup.Clear(); + + m_channelLeftItems->Clear(); + m_channelRightItems->Clear(); + m_channelGroupItems->Clear(); +} diff --git a/xbmc/GUIDialogPVRGroupManager.h b/xbmc/GUIDialogPVRGroupManager.h new file mode 100644 index 0000000000..32b414b8fc --- /dev/null +++ b/xbmc/GUIDialogPVRGroupManager.h @@ -0,0 +1,56 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialog.h" +#include "GUIViewControl.h" + +class CFileItemList; + +class CGUIDialogPVRGroupManager : public CGUIDialog +{ +public: + CGUIDialogPVRGroupManager(void); + virtual ~CGUIDialogPVRGroupManager(void); + virtual bool OnMessage(CGUIMessage& message); + virtual void OnWindowLoaded(); + virtual void OnWindowUnload(); + void SetRadio(bool IsRadio) { m_bIsRadio = IsRadio; } + +protected: + void Clear(); + void Update(); + + CStdString m_CurrentGroupName; + bool m_bIsRadio; + + unsigned int m_iSelectedLeft; + unsigned int m_iSelectedRight; + unsigned int m_iSelectedGroup; + + CFileItemList *m_channelLeftItems; + CFileItemList *m_channelRightItems; + CFileItemList *m_channelGroupItems; + + CGUIViewControl m_viewControlLeft; + CGUIViewControl m_viewControlRight; + CGUIViewControl m_viewControlGroup; +}; diff --git a/xbmc/GUIDialogPVRGuideInfo.cpp b/xbmc/GUIDialogPVRGuideInfo.cpp new file mode 100644 index 0000000000..212a941983 --- /dev/null +++ b/xbmc/GUIDialogPVRGuideInfo.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialogPVRGuideInfo.h" +#include "Application.h" +#include "GUIWindowManager.h" +#include "GUIDialogOK.h" +#include "GUIDialogYesNo.h" +#include "utils/PVREpg.h" +#include "utils/PVRChannels.h" +#include "utils/PVRTimers.h" + +using namespace std; + +#define CONTROL_BTN_SWITCH 5 +#define CONTROL_BTN_RECORD 6 +#define CONTROL_BTN_OK 7 + +CGUIDialogPVRGuideInfo::CGUIDialogPVRGuideInfo(void) + : CGUIDialog(WINDOW_DIALOG_PVR_GUIDE_INFO, "DialogPVRGuideInfo.xml") + , m_progItem(new CFileItem) +{ +} + +CGUIDialogPVRGuideInfo::~CGUIDialogPVRGuideInfo(void) +{ +} + +bool CGUIDialogPVRGuideInfo::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_INIT: + { + CGUIDialog::OnMessage(message); + Update(); + break; + } + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_BTN_OK) + { + Close(); + return true; + } + else if (iControl == CONTROL_BTN_RECORD) + { + if (m_progItem->GetEPGInfoTag()->ChannelNumber() > 0) + { + if (m_progItem->GetEPGInfoTag()->Timer() == NULL) + { + // prompt user for confirmation of channel record + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + + if (pDialog) + { + pDialog->SetHeading(264); + pDialog->SetLine(0, ""); + pDialog->SetLine(1, m_progItem->GetEPGInfoTag()->Title()); + pDialog->SetLine(2, ""); + pDialog->DoModal(); + + if (pDialog->IsConfirmed()) + { + cPVRTimerInfoTag newtimer(*m_progItem.get()); + CFileItem *item = new CFileItem(newtimer); + cPVRTimers::AddTimer(*item); + } + } + } + else + { + CGUIDialogOK::ShowAndGetInput(19033,19067,0,0); + } + } + Close(); + return true; + } + else if (iControl == CONTROL_BTN_SWITCH) + { + Close(); + + cPVRChannels *channels; + if (!m_progItem->GetEPGInfoTag()->IsRadio()) + channels = &PVRChannelsTV; + else + channels = &PVRChannelsRadio; + + if (!g_application.PlayFile(CFileItem(channels->at(m_progItem->GetEPGInfoTag()->ChannelNumber()-1)))) + { + CGUIDialogOK::ShowAndGetInput(19033,0,19035,0); + return false; + } + return true; + } + } + } + + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogPVRGuideInfo::SetProgInfo(const CFileItem *item) +{ + *m_progItem = *item; +} + +CFileItemPtr CGUIDialogPVRGuideInfo::GetCurrentListItem(int offset) +{ + return m_progItem; +} + +void CGUIDialogPVRGuideInfo::Update() +{ + // set recording button label + cPVREPGInfoTag* tag = m_progItem->GetEPGInfoTag(); + if (tag->End() > CDateTime::GetCurrentDateTime()) + { + if (tag->Timer() == NULL) + { + if (tag->Start() < CDateTime::GetCurrentDateTime()) + SET_CONTROL_LABEL(CONTROL_BTN_RECORD, 264); + else + SET_CONTROL_LABEL(CONTROL_BTN_RECORD, 19061); + } + else + { + if (tag->Start() < CDateTime::GetCurrentDateTime()) + SET_CONTROL_LABEL(CONTROL_BTN_RECORD, 19059); + else + SET_CONTROL_LABEL(CONTROL_BTN_RECORD, 19060); + } + } + else + SET_CONTROL_HIDDEN(CONTROL_BTN_RECORD); +} diff --git a/xbmc/GUIDialogPVRGuideInfo.h b/xbmc/GUIDialogPVRGuideInfo.h new file mode 100644 index 0000000000..e3edd5d088 --- /dev/null +++ b/xbmc/GUIDialogPVRGuideInfo.h @@ -0,0 +1,41 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialog.h" + +class CGUIDialogPVRGuideInfo : public CGUIDialog +{ +public: + CGUIDialogPVRGuideInfo(void); + virtual ~CGUIDialogPVRGuideInfo(void); + virtual bool OnMessage(CGUIMessage& message); + virtual bool HasListItems() const { return true; }; + virtual CFileItemPtr GetCurrentListItem(int offset = 0); + + void SetProgInfo(const CFileItem *item); + +protected: + void Update(); + + CFileItemPtr m_progItem; +}; + diff --git a/xbmc/GUIDialogPVRGuideOSD.cpp b/xbmc/GUIDialogPVRGuideOSD.cpp new file mode 100644 index 0000000000..c4b398f2c3 --- /dev/null +++ b/xbmc/GUIDialogPVRGuideOSD.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialogPVRGuideOSD.h" +#include "Application.h" +#include "FileItem.h" +#include "GUIDialogPVRGuideInfo.h" +#include "GUIWindowManager.h" +#include "PVRManager.h" +#include "ViewState.h" + +using namespace std; + +#define CONTROL_LIST 11 + +CGUIDialogPVRGuideOSD::CGUIDialogPVRGuideOSD() + : CGUIDialog(WINDOW_DIALOG_PVR_OSD_GUIDE, "DialogPVRGuideOSD.xml") +{ + m_vecItems = new CFileItemList; +} + +CGUIDialogPVRGuideOSD::~CGUIDialogPVRGuideOSD() +{ + delete m_vecItems; +} + +bool CGUIDialogPVRGuideOSD::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_DEINIT: + { + Clear(); + } + break; + + case GUI_MSG_WINDOW_INIT: + { + /* Close dialog immediately if now TV or radio channel is playing */ + if (!g_PVRManager.IsPlayingTV() && !g_PVRManager.IsPlayingRadio()) + { + Close(); + return true; + } + CGUIWindow::OnMessage(message); + Update(); + return true; + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + + if (m_viewControl.HasControl(iControl)) // list/thumb control + { + int iItem = m_viewControl.GetSelectedItem(); + int iAction = message.GetParam1(); + + if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) + { + ShowInfo(iItem); + return true; + } + } + } + break; + } + + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogPVRGuideOSD::Update() +{ + // lock our display, as this window is rendered from the player thread + g_graphicsContext.Lock(); + m_viewControl.SetCurrentView(DEFAULT_VIEW_LIST); + + // empty the list ready for population + Clear(); + + bool RadioPlaying; + int CurrentChannel; + g_PVRManager.GetCurrentChannel(&CurrentChannel, &RadioPlaying); + PVREpgs.GetEPGChannel(CurrentChannel, m_vecItems, RadioPlaying); + + m_viewControl.SetItems(*m_vecItems); + g_graphicsContext.Unlock(); +} + +void CGUIDialogPVRGuideOSD::Clear() +{ + m_viewControl.Clear(); + m_vecItems->Clear(); +} + +void CGUIDialogPVRGuideOSD::ShowInfo(int item) +{ + /* Check file item is in list range and get his pointer */ + if (item < 0 || item >= (int)m_vecItems->Size()) return; + + CFileItemPtr pItem = m_vecItems->Get(item); + + /* Load programme info dialog */ + CGUIDialogPVRGuideInfo* pDlgInfo = (CGUIDialogPVRGuideInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_GUIDE_INFO); + if (!pDlgInfo) + return; + + /* inform dialog about the file item and open dialog window */ + pDlgInfo->SetProgInfo(pItem.get()); + pDlgInfo->DoModal(); +} + +void CGUIDialogPVRGuideOSD::OnWindowLoaded() +{ + CGUIDialog::OnWindowLoaded(); + m_viewControl.Reset(); + m_viewControl.SetParentWindow(GetID()); + m_viewControl.AddView(GetControl(CONTROL_LIST)); +} + +void CGUIDialogPVRGuideOSD::OnWindowUnload() +{ + CGUIDialog::OnWindowUnload(); + m_viewControl.Reset(); +} + +CGUIControl *CGUIDialogPVRGuideOSD::GetFirstFocusableControl(int id) +{ + if (m_viewControl.HasControl(id)) + id = m_viewControl.GetCurrentControl(); + + return CGUIWindow::GetFirstFocusableControl(id); +} diff --git a/xbmc/GUIDialogPVRGuideOSD.h b/xbmc/GUIDialogPVRGuideOSD.h new file mode 100644 index 0000000000..bbfab08463 --- /dev/null +++ b/xbmc/GUIDialogPVRGuideOSD.h @@ -0,0 +1,46 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialog.h" +#include "GUIViewControl.h" + +class CFileItemList; + +class CGUIDialogPVRGuideOSD : public CGUIDialog +{ +public: + CGUIDialogPVRGuideOSD(void); + virtual ~CGUIDialogPVRGuideOSD(void); + virtual bool OnMessage(CGUIMessage& message); + virtual void OnWindowLoaded(); + virtual void OnWindowUnload(); + +protected: + void ShowInfo(int iItem); + void Clear(); + void Update(); + + CGUIControl *GetFirstFocusableControl(int id); + + CFileItemList *m_vecItems; + CGUIViewControl m_viewControl; +}; diff --git a/xbmc/GUIDialogPVRGuideSearch.cpp b/xbmc/GUIDialogPVRGuideSearch.cpp new file mode 100644 index 0000000000..03d0c772df --- /dev/null +++ b/xbmc/GUIDialogPVRGuideSearch.cpp @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialogPVRGuideSearch.h" +#include "Application.h" +#include "LocalizeStrings.h" +#include "GUIEditControl.h" +#include "GUIRadioButtonControl.h" +#include "GUISpinControlEx.h" +#include "GUIWindowManager.h" +#include "utils/PVREpg.h" +#include "utils/PVRChannels.h" + +using namespace std; + +#define CONTROL_EDIT_SEARCH 9 +#define CONTROL_BTN_INC_DESC 10 +#define CONTROL_BTN_CASE_SENS 11 +#define CONTROL_SPIN_MIN_DURATION 12 +#define CONTROL_SPIN_MAX_DURATION 13 +#define CONTROL_EDIT_START_DATE 14 +#define CONTROL_EDIT_STOP_DATE 15 +#define CONTROL_EDIT_START_TIME 16 +#define CONTROL_EDIT_STOP_TIME 17 +#define CONTROL_SPIN_GENRE 18 +#define CONTROL_SPIN_NO_REPEATS 19 +#define CONTROL_BTN_UNK_GENRE 20 +#define CONTROL_SPIN_GROUPS 21 +#define CONTROL_BTN_FTA_ONLY 22 +#define CONTROL_SPIN_CHANNELS 23 +#define CONTROL_BTN_IGNORE_TMR 24 +#define CONTROL_BTN_CANCEL 25 +#define CONTROL_BTN_SEARCH 26 +#define CONTROL_BTN_IGNORE_REC 27 +#define CONTROL_BTN_DEFAULTS 28 + +CGUIDialogPVRGuideSearch::CGUIDialogPVRGuideSearch(void) + : CGUIDialog(WINDOW_DIALOG_PVR_GUIDE_SEARCH, "DialogPVRGuideSearch.xml") +{ + m_bConfirmed = false; + m_searchfilter = NULL; +} + +bool CGUIDialogPVRGuideSearch::OnMessage(CGUIMessage& message) +{ + CGUIDialog::OnMessage(message); + + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_INIT: + { + m_bConfirmed = false; + m_bCanceled = false; + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_BTN_SEARCH) + { + OnSearch(); + m_bCanceled = false; + Close(); + return true; + } + else if (iControl == CONTROL_BTN_CANCEL) + { + Close(); + m_bCanceled = true; + return true; + } + else if (iControl == CONTROL_BTN_DEFAULTS) + { + if (m_searchfilter) + { + m_searchfilter->SetDefaults(); + Update(); + } + + return true; + } + else if (iControl == CONTROL_SPIN_GROUPS) + { + /* Set Channel list spin */ + CGUISpinControlEx *pSpin = (CGUISpinControlEx *)GetControl(CONTROL_SPIN_CHANNELS); + if (pSpin) + { + CFileItemList channelslist_tv; + PVRChannelsTV.GetChannels(&channelslist_tv, m_searchfilter->m_Group); + + pSpin->Clear(); + pSpin->AddLabel(g_localizeStrings.Get(19140), -1); + pSpin->AddLabel(g_localizeStrings.Get(19023), -2); + pSpin->AddLabel(g_localizeStrings.Get(19024), -3); + + for (int i = 0; i < channelslist_tv.Size(); i++) + { + int chanNumber = channelslist_tv[i]->GetPVRChannelInfoTag()->Number(); + CStdString string; + string.Format("%i %s", chanNumber, channelslist_tv[i]->GetPVRChannelInfoTag()->Name().c_str()); + pSpin->AddLabel(string, chanNumber); + } + } + return true; + } + } + break; + } + + return false; +} + +void CGUIDialogPVRGuideSearch::OnWindowLoaded() +{ + Update(); + return CGUIDialog::OnWindowLoaded(); +} + +void CGUIDialogPVRGuideSearch::OnSearch() +{ + CGUISpinControlEx *pSpin; + CGUIEditControl *pEdit; + CGUIRadioButtonControl *pRadioButton; + CDateTime dateTime; + + if (!m_searchfilter) + return; + + pEdit = (CGUIEditControl *)GetControl(CONTROL_EDIT_SEARCH); + if (pEdit) m_searchfilter->m_SearchString = pEdit->GetLabel2(); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_BTN_INC_DESC); + if (pRadioButton) m_searchfilter->m_SearchDescription = pRadioButton->IsSelected(); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_BTN_CASE_SENS); + if (pRadioButton) m_searchfilter->m_CaseSensitive = pRadioButton->IsSelected(); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_BTN_FTA_ONLY); + if (pRadioButton) m_searchfilter->m_FTAOnly = pRadioButton->IsSelected(); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_BTN_UNK_GENRE); + if (pRadioButton) m_searchfilter->m_IncUnknGenres = pRadioButton->IsSelected(); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_BTN_IGNORE_REC); + if (pRadioButton) m_searchfilter->m_IgnPresentRecords = pRadioButton->IsSelected(); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_BTN_IGNORE_TMR); + if (pRadioButton) m_searchfilter->m_IgnPresentTimers = pRadioButton->IsSelected(); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_SPIN_NO_REPEATS); + if (pRadioButton) m_searchfilter->m_PreventRepeats = pRadioButton->IsSelected(); + + pSpin = (CGUISpinControlEx *)GetControl(CONTROL_SPIN_GENRE); + if (pSpin) m_searchfilter->m_GenreType = pSpin->GetValue(); + + pSpin = (CGUISpinControlEx *)GetControl(CONTROL_SPIN_MIN_DURATION); + if (pSpin) m_searchfilter->m_minDuration = pSpin->GetValue(); + + pSpin = (CGUISpinControlEx *)GetControl(CONTROL_SPIN_MAX_DURATION); + if (pSpin) m_searchfilter->m_maxDuration = pSpin->GetValue(); + + pSpin = (CGUISpinControlEx *)GetControl(CONTROL_SPIN_CHANNELS); + if (pSpin) m_searchfilter->m_ChannelNumber = pSpin->GetValue(); + + pSpin = (CGUISpinControlEx *)GetControl(CONTROL_SPIN_GROUPS); + if (pSpin) m_searchfilter->m_Group = pSpin->GetValue(); + + pEdit = (CGUIEditControl *)GetControl(CONTROL_EDIT_START_TIME); + if (pEdit) + { + dateTime.SetFromDBTime(pEdit->GetLabel2()); + dateTime.GetAsSystemTime(m_searchfilter->m_startTime); + } + + pEdit = (CGUIEditControl *)GetControl(CONTROL_EDIT_STOP_TIME); + if (pEdit) + { + dateTime.SetFromDBTime(pEdit->GetLabel2()); + dateTime.GetAsSystemTime(m_searchfilter->m_endTime); + } + + pEdit = (CGUIEditControl *)GetControl(CONTROL_EDIT_START_DATE); + if (pEdit) + { + dateTime.SetFromDBDate(pEdit->GetLabel2()); + dateTime.GetAsSystemTime(m_searchfilter->m_startDate); + } + + pEdit = (CGUIEditControl *)GetControl(CONTROL_EDIT_STOP_DATE); + if (pEdit) + { + dateTime.SetFromDBDate(pEdit->GetLabel2()); + dateTime.GetAsSystemTime(m_searchfilter->m_endDate); + } + + m_bConfirmed = true; +} + +void CGUIDialogPVRGuideSearch::Update() +{ + CGUISpinControlEx *pSpin; + CGUIEditControl *pEdit; + CGUIRadioButtonControl *pRadioButton; + + if (!m_searchfilter) + return; + + pEdit = (CGUIEditControl *)GetControl(CONTROL_EDIT_SEARCH); + if (pEdit) + { + pEdit->SetLabel2(m_searchfilter->m_SearchString); + pEdit->SetInputType(CGUIEditControl::INPUT_TYPE_TEXT, 16017); + } + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_BTN_CASE_SENS); + if (pRadioButton) pRadioButton->SetSelected(m_searchfilter->m_CaseSensitive); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_BTN_INC_DESC); + if (pRadioButton) pRadioButton->SetSelected(m_searchfilter->m_SearchDescription); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_BTN_FTA_ONLY); + if (pRadioButton) pRadioButton->SetSelected(m_searchfilter->m_FTAOnly); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_BTN_UNK_GENRE); + if (pRadioButton) pRadioButton->SetSelected(m_searchfilter->m_IncUnknGenres); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_BTN_IGNORE_REC); + if (pRadioButton) pRadioButton->SetSelected(m_searchfilter->m_IgnPresentRecords); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_BTN_IGNORE_TMR); + if (pRadioButton) pRadioButton->SetSelected(m_searchfilter->m_IgnPresentTimers); + + pRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_SPIN_NO_REPEATS); + if (pRadioButton) pRadioButton->SetSelected(m_searchfilter->m_PreventRepeats); + + /* Set duration list spin */ + pSpin = (CGUISpinControlEx *)GetControl(CONTROL_SPIN_MIN_DURATION); + if (pSpin) + { + pSpin->Clear(); + pSpin->AddLabel("-", -1); + for (int i = 1; i < 12*60/5; i++) + { + CStdString string; + string.Format(g_localizeStrings.Get(14044),i*5); + pSpin->AddLabel(string, i*5); + } + pSpin->SetValue(m_searchfilter->m_minDuration); + } + + pSpin = (CGUISpinControlEx *)GetControl(CONTROL_SPIN_MAX_DURATION); + if (pSpin) + { + pSpin->Clear(); + pSpin->AddLabel("-", -1); + for (int i = 1; i < 12*60/5; i++) + { + CStdString string; + string.Format(g_localizeStrings.Get(14044),i*5); + pSpin->AddLabel(string, i*5); + } + pSpin->SetValue(m_searchfilter->m_maxDuration); + } + + /* Set time fields */ + pEdit = (CGUIEditControl *)GetControl(CONTROL_EDIT_START_TIME); + if (pEdit) + { + CDateTime time = m_searchfilter->m_startTime; + pEdit->SetLabel2(time.GetAsLocalizedTime("", false)); + pEdit->SetInputType(CGUIEditControl::INPUT_TYPE_TIME, 14066); + } + pEdit = (CGUIEditControl *)GetControl(CONTROL_EDIT_STOP_TIME); + if (pEdit) + { + CDateTime time = m_searchfilter->m_endTime; + pEdit->SetLabel2(time.GetAsLocalizedTime("", false)); + pEdit->SetInputType(CGUIEditControl::INPUT_TYPE_TIME, 14066); + } + pEdit = (CGUIEditControl *)GetControl(CONTROL_EDIT_START_DATE); + if (pEdit) + { + CDateTime date = m_searchfilter->m_startDate; + pEdit->SetLabel2(date.GetAsDBDate()); + pEdit->SetInputType(CGUIEditControl::INPUT_TYPE_DATE, 14067); + } + pEdit = (CGUIEditControl *)GetControl(CONTROL_EDIT_STOP_DATE); + if (pEdit) + { + CDateTime date = m_searchfilter->m_endDate; + pEdit->SetLabel2(date.GetAsDBDate()); + pEdit->SetInputType(CGUIEditControl::INPUT_TYPE_DATE, 14067); + } + + /* Set Channel list spin */ + pSpin = (CGUISpinControlEx *)GetControl(CONTROL_SPIN_CHANNELS); + if (pSpin) + { + CFileItemList channelslist_tv; + PVRChannelsTV.GetChannels(&channelslist_tv, m_searchfilter->m_Group); + + pSpin->Clear(); + pSpin->AddLabel(g_localizeStrings.Get(19140), -1); + pSpin->AddLabel(g_localizeStrings.Get(19023), -2); + pSpin->AddLabel(g_localizeStrings.Get(19024), -3); + + for (int i = 0; i < channelslist_tv.Size(); i++) + { + int chanNumber = channelslist_tv[i]->GetPVRChannelInfoTag()->Number(); + CStdString string; + string.Format("%i %s", chanNumber, channelslist_tv[i]->GetPVRChannelInfoTag()->Name().c_str()); + pSpin->AddLabel(string, chanNumber); + } + pSpin->SetValue(m_searchfilter->m_ChannelNumber); + } + + /* Set Group list spin */ + pSpin = (CGUISpinControlEx *)GetControl(CONTROL_SPIN_GROUPS); + if (pSpin) + { + CFileItemList grouplist; + PVRChannelGroupsTV.GetGroupList(&grouplist); + + pSpin->Clear(); + pSpin->AddLabel(g_localizeStrings.Get(593), -1); + + for (int i = 0; i < grouplist.Size(); i++) + pSpin->AddLabel(grouplist[i]->GetLabel(), atoi(grouplist[i]->m_strPath)); + + pSpin->SetValue(m_searchfilter->m_Group); + } + + /* Set Genre list spin */ + pSpin = (CGUISpinControlEx *)GetControl(CONTROL_SPIN_GENRE); + if (pSpin) + { + pSpin->Clear(); + pSpin->AddLabel(g_localizeStrings.Get(593), -1); + pSpin->AddLabel(g_localizeStrings.Get(19500), EVCONTENTMASK_MOVIEDRAMA); + pSpin->AddLabel(g_localizeStrings.Get(19516), EVCONTENTMASK_NEWSCURRENTAFFAIRS); + pSpin->AddLabel(g_localizeStrings.Get(19532), EVCONTENTMASK_SHOW); + pSpin->AddLabel(g_localizeStrings.Get(19548), EVCONTENTMASK_SPORTS); + pSpin->AddLabel(g_localizeStrings.Get(19564), EVCONTENTMASK_CHILDRENYOUTH); + pSpin->AddLabel(g_localizeStrings.Get(19580), EVCONTENTMASK_MUSICBALLETDANCE); + pSpin->AddLabel(g_localizeStrings.Get(19596), EVCONTENTMASK_ARTSCULTURE); + pSpin->AddLabel(g_localizeStrings.Get(19612), EVCONTENTMASK_SOCIALPOLITICALECONOMICS); + pSpin->AddLabel(g_localizeStrings.Get(19628), EVCONTENTMASK_EDUCATIONALSCIENCE); + pSpin->AddLabel(g_localizeStrings.Get(19644), EVCONTENTMASK_LEISUREHOBBIES); + pSpin->AddLabel(g_localizeStrings.Get(19660), EVCONTENTMASK_SPECIAL); + pSpin->AddLabel(g_localizeStrings.Get(19499), EVCONTENTMASK_USERDEFINED); + pSpin->SetValue(m_searchfilter->m_GenreType); + } +} diff --git a/xbmc/GUIDialogPVRGuideSearch.h b/xbmc/GUIDialogPVRGuideSearch.h new file mode 100644 index 0000000000..803e004cec --- /dev/null +++ b/xbmc/GUIDialogPVRGuideSearch.h @@ -0,0 +1,46 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialog.h" + +struct EPGSearchFilter; + +class CGUIDialogPVRGuideSearch : public CGUIDialog +{ +public: + CGUIDialogPVRGuideSearch(void); + virtual ~CGUIDialogPVRGuideSearch(void) {} + virtual bool OnMessage(CGUIMessage& message); + virtual void OnWindowLoaded(); + + void SetFilterData(EPGSearchFilter *searchfilter) { m_searchfilter = searchfilter; } + bool IsConfirmed() const { return m_bConfirmed; } + bool IsCanceled() const { return m_bCanceled; } + void OnSearch(); + +protected: + void Update(); + + bool m_bConfirmed; + bool m_bCanceled; + EPGSearchFilter *m_searchfilter; +}; diff --git a/xbmc/GUIDialogPVRRecordingInfo.cpp b/xbmc/GUIDialogPVRRecordingInfo.cpp new file mode 100644 index 0000000000..0e9198a924 --- /dev/null +++ b/xbmc/GUIDialogPVRRecordingInfo.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialogPVRRecordingInfo.h" +#include "GUIWindowManager.h" +#include "utils/PVRRecordings.h" + +using namespace std; + +#define CONTROL_BTN_OK 10 + +CGUIDialogPVRRecordingInfo::CGUIDialogPVRRecordingInfo(void) + : CGUIDialog(WINDOW_DIALOG_PVR_RECORDING_INFO, "DialogPVRRecordingInfo.xml") + , m_recordItem(new CFileItem) +{ +} + +bool CGUIDialogPVRRecordingInfo::OnMessage(CGUIMessage& message) +{ + if (message.GetMessage() == GUI_MSG_CLICKED) + { + int iControl = message.GetSenderId(); + + if (iControl == CONTROL_BTN_OK) + { + Close(); + return true; + } + } + + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogPVRRecordingInfo::SetRecording(const CFileItem *item) +{ + *m_recordItem = *item; +} + +CFileItemPtr CGUIDialogPVRRecordingInfo::GetCurrentListItem(int offset) +{ + return m_recordItem; +} diff --git a/xbmc/GUIDialogPVRRecordingInfo.h b/xbmc/GUIDialogPVRRecordingInfo.h new file mode 100644 index 0000000000..376508f7ac --- /dev/null +++ b/xbmc/GUIDialogPVRRecordingInfo.h @@ -0,0 +1,39 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialog.h" + +class CGUIDialogPVRRecordingInfo : public CGUIDialog +{ +public: + CGUIDialogPVRRecordingInfo(void); + virtual ~CGUIDialogPVRRecordingInfo(void) {} + virtual bool OnMessage(CGUIMessage& message); + virtual bool HasListItems() const { return true; }; + virtual CFileItemPtr GetCurrentListItem(int offset = 0); + + void SetRecording(const CFileItem *item); + +protected: + CFileItemPtr m_recordItem; +}; + diff --git a/xbmc/GUIDialogPVRTimerSettings.cpp b/xbmc/GUIDialogPVRTimerSettings.cpp new file mode 100644 index 0000000000..ff27eb94e8 --- /dev/null +++ b/xbmc/GUIDialogPVRTimerSettings.cpp @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialogPVRTimerSettings.h" +#include "GUIDialogKeyboard.h" +#include "GUIDialogNumeric.h" +#include "GUISettings.h" +#include "LocalizeStrings.h" +#include "PVRManager.h" +#include "utils/PVRTimers.h" + +using namespace std; + +#define CONTROL_TMR_ACTIVE 20 +#define CONTROL_TMR_CHNAME_TV 21 +#define CONTROL_TMR_DAY 22 +#define CONTROL_TMR_BEGIN 23 +#define CONTROL_TMR_END 24 +#define CONTROL_TMR_PRIORITY 26 +#define CONTROL_TMR_LIFETIME 27 +#define CONTROL_TMR_FIRST_DAY 28 +#define CONTROL_TMR_NAME 29 +#define CONTROL_TMR_RADIO 50 +#define CONTROL_TMR_CHNAME_RADIO 51 + +CGUIDialogPVRTimerSettings::CGUIDialogPVRTimerSettings(void) + : CGUIDialogSettings(WINDOW_DIALOG_PVR_TIMER_SETTING, "DialogPVRTimerSettings.xml") +{ + m_cancelled = true; + m_tmp_day = 11; +} + +void CGUIDialogPVRTimerSettings::CreateSettings() +{ + cPVRTimerInfoTag* tag = m_timerItem->GetPVRTimerInfoTag(); + + // clear out any old settings + m_settings.clear(); + + // create our settings controls + AddBool(CONTROL_TMR_ACTIVE, 19074, &tag->m_Active); + AddButton(CONTROL_TMR_NAME, 19075, &tag->m_strTitle, true); + AddBool(CONTROL_TMR_RADIO, 19077, &tag->m_Radio); + + /// Channel names + { + // For TV + CFileItemList channelslist_tv; + SETTINGSTRINGS channelstrings_tv; + PVRChannelsTV.GetChannels(&channelslist_tv, -1); + + channelstrings_tv.push_back("0 dummy"); + + for (int i = 0; i < channelslist_tv.Size(); i++) + { + CStdString string; + CFileItemPtr item = channelslist_tv[i]; + string.Format("%i %s", item->GetPVRChannelInfoTag()->Number(), item->GetPVRChannelInfoTag()->Name().c_str()); + channelstrings_tv.push_back(string); + } + + AddSpin(CONTROL_TMR_CHNAME_TV, 19078, &tag->m_channelNum, channelstrings_tv.size(), channelstrings_tv); + EnableSettings(CONTROL_TMR_CHNAME_TV, !tag->m_Radio); + + // For Radio + CFileItemList channelslist_radio; + SETTINGSTRINGS channelstrings_radio; + PVRChannelsRadio.GetChannels(&channelslist_radio, -1); + + channelstrings_radio.push_back("0 dummy"); + + for (int i = 0; i < channelslist_radio.Size(); i++) + { + CStdString string; + CFileItemPtr item = channelslist_radio[i]; + string.Format("%i %s", item->GetPVRChannelInfoTag()->Number(), item->GetPVRChannelInfoTag()->Name().c_str()); + channelstrings_radio.push_back(string); + } + + AddSpin(CONTROL_TMR_CHNAME_RADIO, 19078, &tag->m_channelNum, channelstrings_radio.size(), channelstrings_radio); + EnableSettings(CONTROL_TMR_CHNAME_RADIO, tag->m_Radio); + } + + /// Day + { + SETTINGSTRINGS daystrings; + tm time_cur; + tm time_tmr; + + daystrings.push_back(g_localizeStrings.Get(19086)); + daystrings.push_back(g_localizeStrings.Get(19087)); + daystrings.push_back(g_localizeStrings.Get(19088)); + daystrings.push_back(g_localizeStrings.Get(19089)); + daystrings.push_back(g_localizeStrings.Get(19090)); + daystrings.push_back(g_localizeStrings.Get(19091)); + daystrings.push_back(g_localizeStrings.Get(19092)); + daystrings.push_back(g_localizeStrings.Get(19093)); + daystrings.push_back(g_localizeStrings.Get(19094)); + daystrings.push_back(g_localizeStrings.Get(19095)); + daystrings.push_back(g_localizeStrings.Get(19096)); + CDateTime time = CDateTime::GetCurrentDateTime(); + CDateTime timestart = tag->m_StartTime; + + /* get diffence of timer in days between today and timer start date */ + time.GetAsTm(time_cur); + timestart.GetAsTm(time_tmr); + + if (time_tmr.tm_yday - time_cur.tm_yday >= 0) + m_tmp_day += time_tmr.tm_yday - time_cur.tm_yday; + else + m_tmp_day += time_tmr.tm_yday - time_cur.tm_yday + 365; + + for (int i = 1; i < 365; ++i) + { + CStdString string = time.GetAsLocalizedDate(); + daystrings.push_back(string); + time += CDateTimeSpan(1, 0, 0, 0); + } + + if (tag->m_Repeat) + { + if (tag->m_Weekdays == 0x01) + m_tmp_day = 0; + else if (tag->m_Weekdays == 0x02) + m_tmp_day = 1; + else if (tag->m_Weekdays == 0x04) + m_tmp_day = 2; + else if (tag->m_Weekdays == 0x08) + m_tmp_day = 3; + else if (tag->m_Weekdays == 0x10) + m_tmp_day = 4; + else if (tag->m_Weekdays == 0x20) + m_tmp_day = 5; + else if (tag->m_Weekdays == 0x40) + m_tmp_day = 6; + else if (tag->m_Weekdays == 0x1F) + m_tmp_day = 7; + else if (tag->m_Weekdays == 0x3F) + m_tmp_day = 8; + else if (tag->m_Weekdays == 0x7F) + m_tmp_day = 9; + else if (tag->m_Weekdays == 0x60) + m_tmp_day = 10; + } + + AddSpin(CONTROL_TMR_DAY, 19079, &m_tmp_day, daystrings.size(), daystrings); + } + + AddButton(CONTROL_TMR_BEGIN, 19080, &timerStartTimeStr, true); + AddButton(CONTROL_TMR_END, 19081, &timerEndTimeStr, true); + AddSpin(CONTROL_TMR_PRIORITY, 19082, &tag->m_Priority, 0, 99); + AddSpin(CONTROL_TMR_LIFETIME, 19083, &tag->m_Lifetime, 0, 365); + + /// First day + { + SETTINGSTRINGS daystrings; + tm time_cur; + tm time_tmr; + + CDateTime time = CDateTime::GetCurrentDateTime(); + CDateTime timestart = tag->m_FirstDay; + + /* get diffence of timer in days between today and timer start date */ + if (time < timestart) + { + time.GetAsTm(time_cur); + timestart.GetAsTm(time_tmr); + + if (time_tmr.tm_yday - time_cur.tm_yday >= 0) + { + m_tmp_iFirstDay += time_tmr.tm_yday - time_cur.tm_yday + 1; + } + else + { + m_tmp_iFirstDay += time_tmr.tm_yday - time_cur.tm_yday + 365 + 1; + } + } + + daystrings.push_back(g_localizeStrings.Get(19030)); + + for (int i = 1; i < 365; ++i) + { + CStdString string = time.GetAsLocalizedDate(); + daystrings.push_back(string); + time += CDateTimeSpan(1, 0, 0, 0); + } + + AddSpin(CONTROL_TMR_FIRST_DAY, 19084, &m_tmp_iFirstDay, daystrings.size(), daystrings); + + if (tag->m_Repeat) + EnableSettings(CONTROL_TMR_FIRST_DAY, true); + else + EnableSettings(CONTROL_TMR_FIRST_DAY, false); + } +} + +void CGUIDialogPVRTimerSettings::OnSettingChanged(SettingInfo &setting) +{ + cPVRTimerInfoTag* tag = m_timerItem->GetPVRTimerInfoTag(); + + if (setting.id == CONTROL_TMR_NAME) + { + if (CGUIDialogKeyboard::ShowAndGetInput(tag->m_strTitle, g_localizeStrings.Get(19097), false)) + { + UpdateSetting(CONTROL_TMR_NAME); + } + } + else if (setting.id == CONTROL_TMR_RADIO) + { + cPVRChannelInfoTag* channeltag = NULL; + if (!tag->IsRadio()) + { + EnableSettings(CONTROL_TMR_CHNAME_TV, true); + EnableSettings(CONTROL_TMR_CHNAME_RADIO, false); + channeltag = PVRChannelsTV.GetByNumber(tag->Number()); + } + else + { + EnableSettings(CONTROL_TMR_CHNAME_TV, false); + EnableSettings(CONTROL_TMR_CHNAME_RADIO, true); + channeltag = PVRChannelsRadio.GetByNumber(tag->Number()); + } + + if (channeltag) + { + tag->SetClientNumber(channeltag->ClientNumber()); + tag->SetClientID(channeltag->ClientID()); + tag->SetRadio(channeltag->IsRadio()); + tag->SetNumber(channeltag->Number()); + } + } + else if (setting.id == CONTROL_TMR_CHNAME_TV || setting.id == CONTROL_TMR_CHNAME_RADIO) + { + cPVRChannelInfoTag* channeltag = NULL; + if (!tag->IsRadio()) + channeltag = PVRChannelsTV.GetByNumber(tag->Number()); + else + channeltag = PVRChannelsRadio.GetByNumber(tag->Number()); + + if (channeltag) + { + tag->SetClientNumber(channeltag->ClientNumber()); + tag->SetClientID(channeltag->ClientID()); + tag->SetRadio(channeltag->IsRadio()); + tag->SetNumber(channeltag->Number()); + } + } + else if (setting.id == CONTROL_TMR_DAY && m_tmp_day > 10) + { + CDateTime time = CDateTime::GetCurrentDateTime(); + CDateTime timestart = timerStartTime; + CDateTime timestop = timerEndTime; + int m_tmp_diff; + tm time_cur; + tm time_tmr; + + /* get diffence of timer in days between today and timer start date */ + time.GetAsTm(time_cur); + timestart.GetAsTm(time_tmr); + + if (time_tmr.tm_yday - time_cur.tm_yday >= 0) + m_tmp_diff = time_tmr.tm_yday - time_cur.tm_yday; + else + m_tmp_diff = time_tmr.tm_yday - time_cur.tm_yday + 365; + + tag->m_StartTime = timestart + CDateTimeSpan(m_tmp_day-11-m_tmp_diff, 0, 0, 0); + tag->m_StopTime = timestop + CDateTimeSpan(m_tmp_day-11-m_tmp_diff, 0, 0, 0); + + EnableSettings(CONTROL_TMR_FIRST_DAY, false); + + tag->m_Repeat = false; + tag->m_Weekdays = 0; + } + else if (setting.id == CONTROL_TMR_DAY && m_tmp_day <= 10) + { + EnableSettings(CONTROL_TMR_FIRST_DAY, true); + tag->m_Repeat = true; + + if (m_tmp_day == 0) + tag->m_Weekdays = 0x01; + else if (m_tmp_day == 1) + tag->m_Weekdays = 0x02; + else if (m_tmp_day == 2) + tag->m_Weekdays = 0x04; + else if (m_tmp_day == 3) + tag->m_Weekdays = 0x08; + else if (m_tmp_day == 4) + tag->m_Weekdays = 0x10; + else if (m_tmp_day == 5) + tag->m_Weekdays = 0x20; + else if (m_tmp_day == 6) + tag->m_Weekdays = 0x40; + else if (m_tmp_day == 7) + tag->m_Weekdays = 0x1F; + else if (m_tmp_day == 8) + tag->m_Weekdays = 0x3F; + else if (m_tmp_day == 9) + tag->m_Weekdays = 0x7F; + else if (m_tmp_day == 10) + tag->m_Weekdays = 0x60; + else + tag->m_Weekdays = 0; + } + else if (setting.id == CONTROL_TMR_BEGIN) + { + if (CGUIDialogNumeric::ShowAndGetTime(timerStartTime, g_localizeStrings.Get(14066))) + { + CDateTime timestart = timerStartTime; + int start_day = tag->m_StartTime.GetDay(); + int start_month = tag->m_StartTime.GetMonth(); + int start_year = tag->m_StartTime.GetYear(); + int start_hour = timestart.GetHour(); + int start_minute = timestart.GetMinute(); + tag->m_StartTime.SetDateTime(start_year, start_month, start_day, start_hour, start_minute, 0); + + timerStartTimeStr = tag->m_StartTime.GetAsLocalizedTime("", false); + UpdateSetting(CONTROL_TMR_BEGIN); + } + } + else if (setting.id == CONTROL_TMR_END) + { + if (CGUIDialogNumeric::ShowAndGetTime(timerEndTime, g_localizeStrings.Get(14066))) + { + CDateTime timestop = timerEndTime; + int start_day = tag->m_StopTime.GetDay(); + int start_month = tag->m_StopTime.GetMonth(); + int start_year = tag->m_StopTime.GetYear(); + int start_hour = timestop.GetHour(); + int start_minute = timestop.GetMinute(); + tag->m_StopTime.SetDateTime(start_year, start_month, start_day, start_hour, start_minute, 0); + + timerEndTimeStr = tag->m_StopTime.GetAsLocalizedTime("", false); + UpdateSetting(CONTROL_TMR_END); + } + } + else if (setting.id == CONTROL_TMR_FIRST_DAY && m_tmp_day <= 10) + { + if (m_tmp_iFirstDay > 0) + tag->m_FirstDay = CDateTime::GetCurrentDateTime() + CDateTimeSpan(m_tmp_iFirstDay-1, 0, 0, 0); + else + tag->m_FirstDay = NULL; + } +} + +void CGUIDialogPVRTimerSettings::SetTimer(CFileItem *item) +{ + m_timerItem = item; + m_cancelled = true; + + m_timerItem->GetPVRTimerInfoTag()->m_StartTime.GetAsSystemTime(timerStartTime); + m_timerItem->GetPVRTimerInfoTag()->m_StopTime.GetAsSystemTime(timerEndTime); + timerStartTimeStr = m_timerItem->GetPVRTimerInfoTag()->m_StartTime.GetAsLocalizedTime("", false); + timerEndTimeStr = m_timerItem->GetPVRTimerInfoTag()->m_StopTime.GetAsLocalizedTime("", false); + + m_tmp_iFirstDay = 0; + m_tmp_day = 11; +} + +void CGUIDialogPVRTimerSettings::OnOkay() +{ + m_cancelled = false; + cPVRTimerInfoTag* tag = m_timerItem->GetPVRTimerInfoTag(); + if (tag->Title() == g_localizeStrings.Get(19056)) + tag->SetTitle(cPVRChannels::GetByClientFromAll(tag->ClientNumber(), tag->ClientID())->Name()); +} diff --git a/xbmc/GUIDialogPVRTimerSettings.h b/xbmc/GUIDialogPVRTimerSettings.h new file mode 100644 index 0000000000..5ce738e36a --- /dev/null +++ b/xbmc/GUIDialogPVRTimerSettings.h @@ -0,0 +1,53 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "DateTime.h" +#include "GUIDialogSettings.h" +#include "GUIListItem.h" + +class CFileItem; + +class CGUIDialogPVRTimerSettings : public CGUIDialogSettings +{ +public: + CGUIDialogPVRTimerSettings(void); + virtual ~CGUIDialogPVRTimerSettings(void) {} + void SetTimer(CFileItem *item); + bool GetOK() { return !m_cancelled; } + +protected: + virtual void CreateSettings(); + virtual void OnSettingChanged(SettingInfo &setting); + virtual void OnOkay(); + virtual void OnCancel() { m_cancelled = true; } + + SYSTEMTIME timerStartTime; + SYSTEMTIME timerEndTime; + CStdString timerStartTimeStr; + CStdString timerEndTimeStr; + int m_tmp_iFirstDay;; + int m_tmp_day; + + CFileItem *m_timerItem; + bool m_cancelled; +}; + diff --git a/xbmc/GUIDialogPVRUpdateProgressBar.cpp b/xbmc/GUIDialogPVRUpdateProgressBar.cpp new file mode 100644 index 0000000000..4f5c905ce8 --- /dev/null +++ b/xbmc/GUIDialogPVRUpdateProgressBar.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialogPVRUpdateProgressBar.h" +#include "GUIProgressControl.h" +#include "GUISliderControl.h" +#include "utils/SingleLock.h" + +#define CONTROL_LABELHEADER 30 +#define CONTROL_LABELTITLE 31 +#define CONTROL_PROGRESS 32 + +CGUIDialogPVRUpdateProgressBar::CGUIDialogPVRUpdateProgressBar(void) + : CGUIDialog(WINDOW_DIALOG_EPG_SCAN, "DialogPVRUpdateProgressBar.xml") +{ + m_loadOnDemand = false; +} + +bool CGUIDialogPVRUpdateProgressBar::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_INIT: + { + CGUIDialog::OnMessage(message); + + m_strTitle.Empty(); + m_strHeader.Empty(); + m_fPercentDone = -1.0f; + + UpdateState(); + return true; + } + break; + } + + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogPVRUpdateProgressBar::Render() +{ + if (m_bRunning) + UpdateState(); + + CGUIDialog::Render(); +} + +void CGUIDialogPVRUpdateProgressBar::SetHeader(const CStdString& strHeader) +{ + CSingleLock lock (m_critical); + + m_strHeader = strHeader; +} + +void CGUIDialogPVRUpdateProgressBar::SetTitle(const CStdString& strTitle) +{ + CSingleLock lock (m_critical); + + m_strTitle = strTitle; +} + +void CGUIDialogPVRUpdateProgressBar::SetProgress(int currentItem, int itemCount) +{ + CSingleLock lock (m_critical); + + m_fPercentDone = (float)((currentItem*100)/itemCount); + if (m_fPercentDone > 100.0F) + m_fPercentDone = 100.0F; +} + +void CGUIDialogPVRUpdateProgressBar::UpdateState() +{ + CSingleLock lock (m_critical); + + SET_CONTROL_LABEL(CONTROL_LABELHEADER, m_strHeader); + SET_CONTROL_LABEL(CONTROL_LABELTITLE, m_strTitle); + + if (m_fPercentDone > -1.0f) + { + SET_CONTROL_VISIBLE(CONTROL_PROGRESS); + CGUIProgressControl* pProgressCtrl=(CGUIProgressControl*)GetControl(CONTROL_PROGRESS); + if (pProgressCtrl) pProgressCtrl->SetPercentage(m_fPercentDone); + } +} + diff --git a/xbmc/GUIDialogPVRUpdateProgressBar.h b/xbmc/GUIDialogPVRUpdateProgressBar.h new file mode 100644 index 0000000000..5218b1c959 --- /dev/null +++ b/xbmc/GUIDialogPVRUpdateProgressBar.h @@ -0,0 +1,44 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIDialog.h" + +class CGUIDialogPVRUpdateProgressBar : public CGUIDialog +{ +public: + CGUIDialogPVRUpdateProgressBar(void); + virtual ~CGUIDialogPVRUpdateProgressBar(void) {} + virtual bool OnMessage(CGUIMessage& message); + virtual void Render(); + void SetProgress(int currentItem, int itemCount); + void SetHeader(const CStdString& strHeader); + void SetTitle(const CStdString& strTitle); + void UpdateState(); + +protected: + CStdString m_strTitle; + CStdString m_strHeader; + CCriticalSection m_critical; + float m_fPercentDone; + int m_currentItem; + int m_itemCount; +}; diff --git a/xbmc/GUIDialogSeekBar.cpp b/xbmc/GUIDialogSeekBar.cpp index 095ac262a8..3335907c77 100644 --- a/xbmc/GUIDialogSeekBar.cpp +++ b/xbmc/GUIDialogSeekBar.cpp @@ -26,6 +26,8 @@ #include "utils/GUIInfoManager.h" #include "utils/TimeUtils.h" #include "StringUtils.h" +#include "FileItem.h" +#include "GUISettings.h" #define SEEK_BAR_DISPLAY_TIME 2000L #define SEEK_BAR_SEEK_TIME 500L diff --git a/xbmc/GUIDialogSettings.cpp b/xbmc/GUIDialogSettings.cpp index 53a8aefd9a..36d8f49554 100644 --- a/xbmc/GUIDialogSettings.cpp +++ b/xbmc/GUIDialogSettings.cpp @@ -23,6 +23,7 @@ #include "GUISpinControlEx.h" #include "GUIRadioButtonControl.h" #include "GUISettingsSliderControl.h" +#include "GUIEditControl.h" #include "GUIImage.h" #include "GUIControlGroupList.h" #include "LocalizeStrings.h" @@ -37,6 +38,8 @@ #define CONTROL_DEFAULT_SPIN 9 #define CONTROL_DEFAULT_SLIDER 10 #define CONTROL_DEFAULT_SEPARATOR 11 +#define CONTROL_DEFAULT_EDIT 12 +#define CONTROL_DEFAULT_EDIT_NUM 13 #define CONTROL_OKAY_BUTTON 28 #define CONTROL_CANCEL_BUTTON 29 #define CONTROL_START 30 @@ -47,6 +50,8 @@ using namespace std; CGUIDialogSettings::CGUIDialogSettings(int id, const char *xmlFile) : CGUIDialog(id, xmlFile) { + m_pOriginalEdit = NULL; + m_pOriginalEditNum = NULL; m_pOriginalSpin = NULL; m_pOriginalRadioButton = NULL; m_pOriginalSettingsButton = NULL; @@ -87,11 +92,15 @@ void CGUIDialogSettings::SetupPage() { // cleanup first, if necessary FreeControls(); + m_pOriginalEdit = (CGUIEditControl*)GetControl(CONTROL_DEFAULT_EDIT); + m_pOriginalEditNum = (CGUIEditControl*)GetControl(CONTROL_DEFAULT_EDIT_NUM); m_pOriginalSpin = (CGUISpinControlEx*)GetControl(CONTROL_DEFAULT_SPIN); m_pOriginalRadioButton = (CGUIRadioButtonControl *)GetControl(CONTROL_DEFAULT_RADIOBUTTON); m_pOriginalSettingsButton = (CGUIButtonControl *)GetControl(CONTROL_DEFAULT_BUTTON); m_pOriginalSlider = (CGUISettingsSliderControl *)GetControl(CONTROL_DEFAULT_SLIDER); m_pOriginalSeparator = (CGUIImage *)GetControl(CONTROL_DEFAULT_SEPARATOR); + if (m_pOriginalEdit) m_pOriginalEdit->SetVisible(false); + if (m_pOriginalEditNum) m_pOriginalEditNum->SetVisible(false); if (m_pOriginalSpin) m_pOriginalSpin->SetVisible(false); if (m_pOriginalRadioButton) m_pOriginalRadioButton->SetVisible(false); if (m_pOriginalSettingsButton) m_pOriginalSettingsButton->SetVisible(false); @@ -99,7 +108,14 @@ void CGUIDialogSettings::SetupPage() if (m_pOriginalSeparator) m_pOriginalSeparator->SetVisible(false); // update our settings label + if (GetID() == WINDOW_DIALOG_PVR_TIMER_SETTING) + { + SET_CONTROL_LABEL(CONTROL_SETTINGS_LABEL, g_localizeStrings.Get(19057)); + } + else + { SET_CONTROL_LABEL(CONTROL_SETTINGS_LABEL, g_localizeStrings.Get(13395 + GetID() - WINDOW_DIALOG_VIDEO_OSD_SETTINGS)); + } CGUIControlGroupList *group = (CGUIControlGroupList *)GetControl(CONTROL_GROUP_LIST); if (!group) @@ -184,11 +200,25 @@ void CGUIDialogSettings::UpdateSetting(unsigned int id) if (setting.formatFunction) pControl->SetTextValue(setting.formatFunction(value, setting.interval)); } } - else if (setting.type == SettingInfo::BUTTON) + else if (setting.type == SettingInfo::BUTTON_DIALOG) { SET_CONTROL_LABEL(controlID,setting.name); - if (m_usePopupSliders && setting.data && setting.formatFunction) - SET_CONTROL_LABEL2(controlID,setting.formatFunction(*(float *)setting.data, setting.interval)); + CGUIButtonControl *pControl = (CGUIButtonControl *)GetControl(controlID); + if (pControl && setting.data) pControl->SetLabel2(*(CStdString *)setting.data); + } + else if (setting.type == SettingInfo::EDIT) + { + CGUIEditControl *pControl = (CGUIEditControl *)GetControl(controlID); + if (pControl && setting.data) pControl->SetLabel2(*(CStdString *)setting.data); + } + else if (setting.type == SettingInfo::EDIT_NUM) + { + CGUIEditControl *pControl = (CGUIEditControl *)GetControl(controlID); + if (pControl && setting.data) { + CStdString strIndex; + strIndex.Format("%i", *(int *)setting.data); + pControl->SetLabel2(strIndex); + } } if (setting.enabled) @@ -234,6 +264,24 @@ void CGUIDialogSettings::OnClick(int iID) CGUISpinControlEx *pControl = (CGUISpinControlEx *)GetControl(iID); if (setting.data) *(int *)setting.data = pControl->GetValue(); } + else if (setting.type == SettingInfo::BUTTON_DIALOG) + { + CGUIButtonControl *pControl = (CGUIButtonControl *)GetControl(iID); + if (setting.data) *(CStdString *)setting.data = pControl->GetLabel2(); + } + else if (setting.type == SettingInfo::EDIT) + { + CGUIEditControl *pControl = (CGUIEditControl *)GetControl(iID); + if (setting.data) *(CStdString *)setting.data = pControl->GetLabel2(); + } + else if (setting.type == SettingInfo::EDIT_NUM) + { + CGUIEditControl *pControl = (CGUIEditControl *)GetControl(iID); + if (setting.data) { + CStdString strIndex = pControl->GetLabel2(); + *(int *)setting.data = atol(strIndex.c_str()); + } + } else if (setting.type == SettingInfo::CHECK) { CGUIRadioButtonControl *pControl = (CGUIRadioButtonControl *)GetControl(iID); @@ -273,7 +321,15 @@ void CGUIDialogSettings::FreeControls() void CGUIDialogSettings::AddSetting(SettingInfo &setting, float width, int iControlID) { CGUIControl *pControl = NULL; - if (setting.type == SettingInfo::BUTTON && m_pOriginalSettingsButton) + if (setting.type == SettingInfo::BUTTON_DIALOG && m_pOriginalSettingsButton) + { + pControl = new CGUIButtonControl(*m_pOriginalSettingsButton); + if (!pControl) return ; + ((CGUIButtonControl *)pControl)->SetLabel(setting.name); + pControl->SetWidth(width); + if (setting.data) ((CGUIButtonControl *)pControl)->SetLabel2(*(CStdString *)setting.data); + } + else if (setting.type == SettingInfo::BUTTON && m_pOriginalSettingsButton) { pControl = new CGUIButtonControl(*m_pOriginalSettingsButton); if (!pControl) return ; @@ -282,6 +338,27 @@ void CGUIDialogSettings::AddSetting(SettingInfo &setting, float width, int iCont ((CGUIButtonControl *)pControl)->SetLabel2(setting.formatFunction(*(float *)setting.data, setting.interval)); pControl->SetWidth(width); } + else if (setting.type == SettingInfo::EDIT && m_pOriginalEdit) + { + pControl = new CGUIEditControl(*m_pOriginalEdit); + if (!pControl) return ; + ((CGUIEditControl *)pControl)->SetLabel(setting.name); + pControl->SetWidth(width); + if (setting.data) ((CGUIEditControl *)pControl)->SetLabel2(*(CStdString *)setting.data); + } + else if (setting.type == SettingInfo::EDIT_NUM && m_pOriginalEditNum) + { + pControl = new CGUIEditControl(*m_pOriginalEditNum); + if (!pControl) return ; + ((CGUIEditControl *)pControl)->SetLabel(setting.name); + pControl->SetWidth(width); + ((CGUIEditControl *)pControl)->SetInputType(CGUIEditControl::INPUT_TYPE_NUMBER, 0); + if (setting.data) { + CStdString strIndex; + strIndex.Format("%i", *(int *)setting.data); + ((CGUIEditControl *)pControl)->SetLabel2(strIndex); + } + } else if (setting.type == SettingInfo::SEPARATOR && m_pOriginalSeparator) { pControl = new CGUIImage(*m_pOriginalSeparator); @@ -337,6 +414,28 @@ void CGUIDialogSettings::AddSetting(SettingInfo &setting, float width, int iCont delete pControl; } +void CGUIDialogSettings::AddEdit(unsigned int id, int label, CStdString *str, bool enabled) +{ + SettingInfo setting; + setting.id = id; + setting.name = g_localizeStrings.Get(label); + setting.type = SettingInfo::EDIT; + setting.enabled = enabled; + setting.data = str; + m_settings.push_back(setting); +} + +void CGUIDialogSettings::AddNumEdit(unsigned int id, int label, int *current, bool enabled) +{ + SettingInfo setting; + setting.id = id; + setting.name = g_localizeStrings.Get(label); + setting.type = SettingInfo::EDIT_NUM; + setting.enabled = enabled; + setting.data = current; + m_settings.push_back(setting); +} + void CGUIDialogSettings::AddButton(unsigned int id, int label, float *current, float min, float interval, float max, FORMATFUNCTION function) { SettingInfo setting; @@ -351,6 +450,17 @@ void CGUIDialogSettings::AddButton(unsigned int id, int label, float *current, f m_settings.push_back(setting); } +void CGUIDialogSettings::AddButton(unsigned int id, int label, CStdString *str, bool bOn) +{ + SettingInfo setting; + setting.id = id; + setting.name = g_localizeStrings.Get(label); + setting.type = SettingInfo::BUTTON_DIALOG; + setting.enabled = bOn; + setting.data = str; + m_settings.push_back(setting); +} + void CGUIDialogSettings::AddBool(unsigned int id, int label, bool *on, bool enabled) { SettingInfo setting; @@ -362,6 +472,18 @@ void CGUIDialogSettings::AddBool(unsigned int id, int label, bool *on, bool enab m_settings.push_back(setting); } +void CGUIDialogSettings::AddSpin(unsigned int id, int label, int *current, unsigned int max, const SETTINGSTRINGS &entries) +{ + SettingInfo setting; + setting.id = id; + setting.name = g_localizeStrings.Get(label); + setting.type = SettingInfo::SPIN; + setting.data = current; + for (unsigned int i = 0; i < max; i++) + setting.entry.push_back(make_pair(i, entries[i])); + m_settings.push_back(setting); +} + void CGUIDialogSettings::AddSpin(unsigned int id, int label, int *current, unsigned int max, const int *entries) { SettingInfo setting; diff --git a/xbmc/GUIDialogSettings.h b/xbmc/GUIDialogSettings.h index 678a8677a2..7df0cf9261 100644 --- a/xbmc/GUIDialogSettings.h +++ b/xbmc/GUIDialogSettings.h @@ -28,14 +28,16 @@ class CGUISpinControlEx; class CGUIButtonControl; class CGUIRadioButtonControl; class CGUISettingsSliderControl; +class CGUIEditControl; class CGUIImage; +typedef std::vector SETTINGSTRINGS; typedef CStdString (*FORMATFUNCTION) (float value, float min); class SettingInfo { public: - enum SETTING_TYPE { NONE=0, BUTTON, CHECK, CHECK_UCHAR, SPIN, SLIDER, SEPARATOR }; + enum SETTING_TYPE { NONE=0, EDIT, EDIT_NUM, BUTTON, BUTTON_DIALOG, CHECK, CHECK_UCHAR, SPIN, SLIDER, SEPARATOR }; SettingInfo() { id = 0; @@ -83,8 +85,12 @@ protected: void AddSetting(SettingInfo &setting, float width, int iControlID); + void AddEdit(unsigned int id, int label, CStdString *str, bool enabled = true); + void AddNumEdit(unsigned int id, int label, int *current, bool enabled = true); void AddButton(unsigned int id, int label, float *current = NULL, float min = 0, float interval = 0, float max = 0, FORMATFUNCTION function = NULL); + void AddButton(unsigned int it, int label, CStdString *str, bool bOn=true); void AddBool(unsigned int id, int label, bool *on, bool enabled = true); + void AddSpin(unsigned int id, int label, int *current, unsigned int max, const SETTINGSTRINGS &entries); void AddSpin(unsigned int id, int label, int *current, unsigned int max, const int *entries); void AddSpin(unsigned int id, int label, int *current, unsigned int min, unsigned int max, const char* minLabel = NULL); void AddSpin(unsigned int id, int label, int *current, std::vector > &values); @@ -92,6 +98,8 @@ protected: void AddSlider(unsigned int id, int label, float *current, float min, float interval, float max, FORMATFUNCTION formatFunction, bool allowPopup = true); void AddSeparator(unsigned int id); + CGUIEditControl *m_pOriginalEdit; + CGUIEditControl *m_pOriginalEditNum; CGUISpinControlEx *m_pOriginalSpin; CGUIRadioButtonControl *m_pOriginalRadioButton; CGUIButtonControl *m_pOriginalSettingsButton; diff --git a/xbmc/GUIDialogVideoSettings.cpp b/xbmc/GUIDialogVideoSettings.cpp index 291585d6b1..b74a57908d 100644 --- a/xbmc/GUIDialogVideoSettings.cpp +++ b/xbmc/GUIDialogVideoSettings.cpp @@ -88,6 +88,7 @@ void CGUIDialogVideoSettings::CreateSettings() entries.push_back(make_pair(VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL_HALF, 16318)); entries.push_back(make_pair(VS_INTERLACEMETHOD_VDPAU_TEMPORAL_HALF , 16317)); entries.push_back(make_pair(VS_INTERLACEMETHOD_VDPAU_INVERSE_TELECINE , 16314)); + entries.push_back(make_pair(VS_INTERLACEMETHOD_AUTO_ION , 16320)); /* remove unsupported methods */ for(vector >::iterator it = entries.begin(); it != entries.end();) diff --git a/xbmc/GUIMediaWindow.cpp b/xbmc/GUIMediaWindow.cpp index 62102b2676..f64aae656e 100644 --- a/xbmc/GUIMediaWindow.cpp +++ b/xbmc/GUIMediaWindow.cpp @@ -646,6 +646,12 @@ bool CGUIMediaWindow::GetDirectory(const CStdString &strDirectory, CFileItemList m_history.RemoveParentPath(); } + int iWindow = GetID(); + if (iWindow == WINDOW_TV && (items.m_strPath == "pvr://recordings/" || + items.m_strPath.Left(15) == "pvr://channels/" || + items.m_strPath.Left(13) == "pvr://timers/")) + return true; + if (m_guiState.get() && !m_guiState->HideParentDirItems() && !items.m_strPath.IsEmpty()) { CFileItemPtr pItem(new CFileItem("..")); @@ -655,7 +661,6 @@ bool CGUIMediaWindow::GetDirectory(const CStdString &strDirectory, CFileItemList items.AddFront(pItem, 0); } - int iWindow = GetID(); CStdStringArray regexps; if (iWindow == WINDOW_VIDEO_FILES) @@ -790,7 +795,8 @@ bool CGUIMediaWindow::Update(const CStdString &strDirectory) if (!bSelectedFound) m_viewControl.SetSelectedItem(0); - m_history.AddPath(m_vecItems->m_strPath); + if (iWindow != WINDOW_TV || (iWindow == WINDOW_TV && m_vecItems->m_strPath.Left(17) == "pvr://recordings/")) + m_history.AddPath(m_vecItems->m_strPath); //m_history.DumpPathHistory(); diff --git a/xbmc/GUISettings.cpp b/xbmc/GUISettings.cpp index 7bba5e9f3d..510dff5edb 100644 --- a/xbmc/GUISettings.cpp +++ b/xbmc/GUISettings.cpp @@ -48,11 +48,14 @@ using namespace std; using namespace ADDON; // String id's of the masks +#define MASK_DAYS 17999 +#define MASK_HOURS 17998 #define MASK_MINS 14044 #define MASK_SECS 14045 #define MASK_MS 14046 #define MASK_PERCENT 14047 #define MASK_KBPS 14048 +#define MASK_MB 17997 #define MASK_KB 14049 #define MASK_DB 14050 @@ -755,6 +758,49 @@ void CGUISettings::Initialize() AddInt(NULL, "window.height", 0, 480, 10, 1, INT_MAX, SPIN_CONTROL_INT); AddPath(NULL,"system.playlistspath",20006,"set default",BUTTON_CONTROL_PATH_INPUT,false); + + // tv settings (access over TV menu from home window) + AddGroup(8, 19180); + CSettingsCategory* pvr = AddCategory(8, "pvrmanager", 128); + AddBool(pvr, "pvrmanager.enabled", 449 , false); + AddString(pvr, "pvrmanager.channelmanager", 19199, "", BUTTON_CONTROL_STANDARD); + AddString(pvr, "pvrmanager.channelscan", 19117, "", BUTTON_CONTROL_STANDARD); + AddString(pvr, "pvrmanager.resetdb", 19185, "", BUTTON_CONTROL_STANDARD); + + CSettingsCategory* pvrm = AddCategory(8, "pvrmenu", 19181); + AddInt(pvrm, "pvrmenu.daystodisplay", 19182, 2, 1, 1, 14, SPIN_CONTROL_INT_PLUS, MASK_DAYS); + AddInt(pvrm, "pvrmenu.lingertime", 19183, 0, 0, 30, 960, SPIN_CONTROL_INT_PLUS, MASK_MINS); + AddBool(pvrm, "pvrmenu.infoswitch", 19178, true); + AddBool(pvrm, "pvrmenu.infotimeout", 19179, true); + AddInt(pvrm, "pvrmenu.infotime", 19184, 5, 1, 1, 10, SPIN_CONTROL_INT_PLUS, MASK_SECS); + AddBool(pvrm, "pvrmenu.hidevideolength", 19169, true); + AddSeparator(pvrm, "pvrmenu.sep1"); + AddString(pvrm, "pvrmenu.iconpath", 19018, "", BUTTON_CONTROL_PATH_INPUT, false, 657); + AddString(pvrm, "pvrmenu.searchicons", 19167, "", BUTTON_CONTROL_STANDARD); + AddSeparator(pvrm, "pvrmenu.sep2"); + AddInt(pvrm, "pvrmenu.defaultguideview", 19065, GUIDE_VIEW_CHANNEL, GUIDE_VIEW_CHANNEL, 1, GUIDE_VIEW_TIMELINE, SPIN_CONTROL_TEXT); + + CSettingsCategory* pvre = AddCategory(8, "pvrepg", 19069); + AddInt(pvre, "pvrepg.epgscan", 19070, 5, 1, 1, 24, SPIN_CONTROL_INT_PLUS, MASK_HOURS); + AddInt(pvre, "pvrepg.epgupdate", 19071, 120, 15, 15, 480, SPIN_CONTROL_INT_PLUS, MASK_MINS); + AddBool(pvre, "pvrepg.ignoredbforclient", 19072, false); + AddString(pvre, "pvrepg.resetepg", 19187, "", BUTTON_CONTROL_STANDARD); + + CSettingsCategory* pvrp = AddCategory(8, "pvrplayback", 19177); + AddBool(pvrp, "pvrplayback.switchautoclose", 19168, true); + AddBool(pvrp, "pvrplayback.playminimized", 19171, true); + AddInt(pvrp, "pvrplayback.scantime", 19170, 15, 1, 1, 60, SPIN_CONTROL_INT_PLUS, MASK_SECS); + AddInt(pvrp, "pvrplayback.channelentrytimeout", 19073, 0, 0, 250, 2000, SPIN_CONTROL_INT_PLUS, MASK_MS); + AddSeparator(pvrp, "pvrplayback.sep1"); + AddBool(pvrp, "pvrplayback.signalquality", 19037, true); + AddInt(pvrp, "pvrplayback.startlast", 19189, START_LAST_CHANNEL_OFF, START_LAST_CHANNEL_OFF, 1, START_LAST_CHANNEL_ON, SPIN_CONTROL_TEXT); + + CSettingsCategory* pvrr = AddCategory(8, "pvrrecord", 19043); + AddInt(pvrr, "pvrrecord.instantrecordtime", 19172, 180, 1, 1, 720, SPIN_CONTROL_INT_PLUS, MASK_MINS); + AddInt(pvrr, "pvrrecord.defaultpriority", 19173, 50, 1, 1, 100, SPIN_CONTROL_INT_PLUS); + AddInt(pvrr, "pvrrecord.defaultlifetime", 19174, 99, 1, 1, 365, SPIN_CONTROL_INT_PLUS, MASK_DAYS); + AddInt(pvrr, "pvrrecord.marginstart", 19175, 2, 1, 1, 60, SPIN_CONTROL_INT_PLUS, MASK_MINS); + AddInt(pvrr, "pvrrecord.marginstop", 19176, 10, 1, 1, 60, SPIN_CONTROL_INT_PLUS, MASK_MINS); } CGUISettings::~CGUISettings(void) diff --git a/xbmc/GUISettings.h b/xbmc/GUISettings.h index c4fa8f0324..c5dee14c6c 100644 --- a/xbmc/GUISettings.h +++ b/xbmc/GUISettings.h @@ -129,6 +129,15 @@ class TiXmlElement; #define APM_HIPOWER_STANDBY 2 #define APM_LOPOWER_STANDBY 3 +#define GUIDE_VIEW_CHANNEL 0 +#define GUIDE_VIEW_NOW 1 +#define GUIDE_VIEW_NEXT 2 +#define GUIDE_VIEW_TIMELINE 3 + +#define START_LAST_CHANNEL_OFF 0 +#define START_LAST_CHANNEL_MIN 1 +#define START_LAST_CHANNEL_ON 2 + #define SETTINGS_TYPE_BOOL 1 #define SETTINGS_TYPE_FLOAT 2 #define SETTINGS_TYPE_INT 3 diff --git a/xbmc/GUIViewControl.cpp b/xbmc/GUIViewControl.cpp index 63652813f7..755c50a79d 100644 --- a/xbmc/GUIViewControl.cpp +++ b/xbmc/GUIViewControl.cpp @@ -122,8 +122,9 @@ void CGUIViewControl::SetCurrentView(int viewMode) // Update it with the contents UpdateContents(pNewView, item); - // Update our view control - UpdateViewAsControl(((CGUIBaseContainer *)pNewView)->GetLabel()); + // Update our view control only if we are not in the TV Window + if (m_parentWindow != WINDOW_TV) + UpdateViewAsControl(((CGUIBaseContainer *)pNewView)->GetLabel()); } void CGUIViewControl::SetItems(CFileItemList &items) diff --git a/xbmc/GUIViewState.cpp b/xbmc/GUIViewState.cpp index b146143e3c..cc1bcbce68 100644 --- a/xbmc/GUIViewState.cpp +++ b/xbmc/GUIViewState.cpp @@ -25,6 +25,7 @@ #include "GUIViewStateVideo.h" #include "GUIViewStatePictures.h" #include "GUIViewStatePrograms.h" +#include "GUIViewStateTV.h" #include "PlayListPlayer.h" #include "Util.h" #include "URL.h" @@ -116,6 +117,9 @@ CGUIViewState* CGUIViewState::GetViewState(int windowId, const CFileItemList& it if (windowId==WINDOW_VIDEO_PLAYLIST) return new CGUIViewStateWindowVideoPlaylist(items); + if (windowId==WINDOW_TV) + return new CGUIViewStateWindowTV(items); + if (windowId==WINDOW_PICTURES) return new CGUIViewStateWindowPictures(items); diff --git a/xbmc/GUIViewStateTV.cpp b/xbmc/GUIViewStateTV.cpp new file mode 100644 index 0000000000..56781d5616 --- /dev/null +++ b/xbmc/GUIViewStateTV.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIViewStateTV.h" +#include "PlayListPlayer.h" +#include "GUIBaseContainer.h" // for VIEW_TYPE_* +#include "VideoDatabase.h" +#include "GUISettings.h" +#include "AdvancedSettings.h" +#include "Settings.h" +#include "FileItem.h" +#include "Key.h" +#include "Util.h" +#include "LocalizeStrings.h" +#include "utils/log.h" + +CGUIViewStateWindowTV::CGUIViewStateWindowTV(const CFileItemList& items) : CGUIViewState(items) +{ + if (items.m_strPath.Left(17) == "pvr://recordings/") + { + if (g_guiSettings.GetBool("filelists.ignorethewhensorting")) + AddSortMethod(SORT_METHOD_LABEL_IGNORE_THE, 551, LABEL_MASKS("%L", "%I", "%L", "")); // FileName, Size | Foldername, e + else + AddSortMethod(SORT_METHOD_LABEL, 551, LABEL_MASKS("%L", "%I", "%L", "")); // FileName, Size | Foldername, empty + AddSortMethod(SORT_METHOD_SIZE, 553, LABEL_MASKS("%L", "%I", "%L", "%I")); // FileName, Size | Foldername, Size + AddSortMethod(SORT_METHOD_DATE, 552, LABEL_MASKS("%L", "%J", "%L", "%J")); // FileName, Date | Foldername, Date + AddSortMethod(SORT_METHOD_FILE, 561, LABEL_MASKS("%L", "%I", "%L", "")); // Filename, Size | FolderName, empty + + SetSortMethod(g_settings.m_viewStateVideoFiles.m_sortMethod); + SetViewAsControl(g_settings.m_viewStateVideoFiles.m_viewMode); + SetSortOrder(g_settings.m_viewStateVideoFiles.m_sortOrder); + } + LoadViewState(items.m_strPath, WINDOW_TV); +} + +bool CGUIViewStateWindowTV::AutoPlayNextItem() +{ + return false; +} + +//CStdString CGUIViewStateWindowTV::GetExtensions() +//{ +// return ".pvr|.timer""; +//} + +bool CGUIViewStateWindowTV::HideParentDirItems() +{ + return false; +} + +void CGUIViewStateWindowTV::SaveViewState() +{ + +} diff --git a/xbmc/GUIViewStateTV.h b/xbmc/GUIViewStateTV.h new file mode 100644 index 0000000000..c36290576d --- /dev/null +++ b/xbmc/GUIViewStateTV.h @@ -0,0 +1,36 @@ +#pragma once + +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIViewState.h" + + +class CGUIViewStateWindowTV : public CGUIViewState +{ +public: + CGUIViewStateWindowTV(const CFileItemList& items); +protected: + virtual bool AutoPlayNextItem(); + virtual bool HideParentDirItems(); +// virtual CStdString GetExtensions(); + virtual void SaveViewState(); +}; diff --git a/xbmc/GUIViewStateVideo.cpp b/xbmc/GUIViewStateVideo.cpp index ff25178447..28bbace4e5 100644 --- a/xbmc/GUIViewStateVideo.cpp +++ b/xbmc/GUIViewStateVideo.cpp @@ -23,6 +23,7 @@ #include "PlayListPlayer.h" #include "FileSystem/VideoDatabaseDirectory.h" #include "FileSystem/PluginDirectory.h" +#include "FileSystem/PVRDirectory.h" #include "GUIBaseContainer.h" #include "VideoDatabase.h" #include "GUISettings.h" diff --git a/xbmc/GUIWindowFullScreen.cpp b/xbmc/GUIWindowFullScreen.cpp index 934e51f918..208f012581 100644 --- a/xbmc/GUIWindowFullScreen.cpp +++ b/xbmc/GUIWindowFullScreen.cpp @@ -35,11 +35,13 @@ #include "GUITextLayout.h" #include "GUIWindowManager.h" #include "GUIDialogFullScreenInfo.h" -#include "GUIDialogAudioSubtitleSettings.h" #include "GUIDialogNumeric.h" +#include "GUIDialogAudioSubtitleSettings.h" +#include "GUISelectButtonControl.h" #include "GUISliderControl.h" #include "Settings.h" #include "FileItem.h" +#include "PVRManager.h" #include "VideoReferenceClock.h" #include "AdvancedSettings.h" #include "CPUInfo.h" @@ -58,6 +60,7 @@ #define LABEL_ROW1 10 #define LABEL_ROW2 11 #define LABEL_ROW3 12 +#define CONTROL_GROUP_CHOOSER 503 #define BTN_OSD_VIDEO 13 #define BTN_OSD_AUDIO 14 @@ -119,6 +122,7 @@ CGUIWindowFullScreen::CGUIWindowFullScreen(void) m_dwShowViewModeTimeout = 0; m_bShowCurrentTime = false; m_subsLayout = NULL; + m_bGroupSelectShow = false; m_sliderAction = 0; // audio // - language @@ -200,36 +204,52 @@ bool CGUIWindowFullScreen::OnAction(const CAction &action) break; case ACTION_STEP_BACK: - if (m_timeCodePosition > 0) - SeekToTimeCodeStamp(SEEK_RELATIVE, SEEK_BACKWARD); + if (!g_application.CurrentFileItem().HasPVRChannelInfoTag()) + { + if (m_timeCodePosition > 0) + SeekToTimeCodeStamp(SEEK_RELATIVE, SEEK_BACKWARD); + else + g_application.m_pPlayer->Seek(false, false); + } else - g_application.m_pPlayer->Seek(false, false); + SeekTV(false, false); return true; - break; case ACTION_STEP_FORWARD: - if (m_timeCodePosition > 0) - SeekToTimeCodeStamp(SEEK_RELATIVE, SEEK_FORWARD); + if (!g_application.CurrentFileItem().HasPVRChannelInfoTag()) + { + if (m_timeCodePosition > 0) + SeekToTimeCodeStamp(SEEK_RELATIVE, SEEK_FORWARD); + else + g_application.m_pPlayer->Seek(true, false); + } else - g_application.m_pPlayer->Seek(true, false); + SeekTV(true, false); return true; - break; case ACTION_BIG_STEP_BACK: - if (m_timeCodePosition > 0) - SeekToTimeCodeStamp(SEEK_RELATIVE, SEEK_BACKWARD); + if (!g_application.CurrentFileItem().HasPVRChannelInfoTag()) + { + if (m_timeCodePosition > 0) + SeekToTimeCodeStamp(SEEK_RELATIVE, SEEK_BACKWARD); + else + g_application.m_pPlayer->Seek(false, true); + } else - g_application.m_pPlayer->Seek(false, true); + SeekTV(false, true); return true; - break; case ACTION_BIG_STEP_FORWARD: - if (m_timeCodePosition > 0) - SeekToTimeCodeStamp(SEEK_RELATIVE, SEEK_FORWARD); + if (!g_application.CurrentFileItem().HasPVRChannelInfoTag()) + { + if (m_timeCodePosition > 0) + SeekToTimeCodeStamp(SEEK_RELATIVE, SEEK_FORWARD); + else + g_application.m_pPlayer->Seek(true, true); + } else - g_application.m_pPlayer->Seek(true, true); + SeekTV(true, true); return true; - break; case ACTION_NEXT_SCENE: if (g_application.m_pPlayer->SeekScene(true)) @@ -271,6 +291,7 @@ bool CGUIWindowFullScreen::OnAction(const CAction &action) CGUIDialogFullScreenInfo* pDialog = (CGUIDialogFullScreenInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_FULLSCREEN_INFO); if (pDialog) { + CFileItem item(g_application.CurrentFileItem()); pDialog->DoModal(); return true; } @@ -401,13 +422,23 @@ bool CGUIWindowFullScreen::OnAction(const CAction &action) if (g_application.CurrentFileItem().IsLiveTV()) { int channelNr = -1; + int currentChannelNr; + g_PVRManager.GetCurrentChannel(¤tChannelNr, NULL); - CStdString strChannel; - strChannel.Format("%i", action.GetID() - REMOTE_0); - if (CGUIDialogNumeric::ShowAndGetNumber(strChannel, g_localizeStrings.Get(19000))) - channelNr = atoi(strChannel.c_str()); + if (action.GetID() == REMOTE_0) + { + channelNr = g_PVRManager.GetPreviousChannel(); + } + else + { + int autoCloseTime = g_guiSettings.GetBool("pvrplayback.switchautoclose") ? 1500 : 0; + CStdString strChannel; + strChannel.Format("%i", action.GetID() - REMOTE_0); + if (CGUIDialogNumeric::ShowAndGetNumber(strChannel, g_localizeStrings.Get(19000), autoCloseTime) || autoCloseTime) + channelNr = atoi(strChannel.c_str()); + } - if (channelNr > 0) + if (channelNr > 0 && channelNr != currentChannelNr) OnAction(CAction(ACTION_CHANNEL_SWITCH, (float)channelNr)); } else @@ -443,6 +474,18 @@ bool CGUIWindowFullScreen::OnAction(const CAction &action) } return true; break; + case ACTION_SHOW_PLAYLIST: + { + CFileItem item(g_application.CurrentFileItem()); + if (item.HasPVRChannelInfoTag()) + g_windowManager.ActivateWindow(WINDOW_DIALOG_PVR_OSD_CHANNELS); + else if (item.HasVideoInfoTag()) + g_windowManager.ActivateWindow(WINDOW_VIDEO_PLAYLIST); + else if (item.HasMusicInfoTag()) + g_windowManager.ActivateWindow(WINDOW_MUSIC_PLAYLIST); + } + return true; + break; case ACTION_ZOOM_IN: { g_settings.m_currentVideoSettings.m_CustomZoomAmount += 0.01f; @@ -490,6 +533,7 @@ bool CGUIWindowFullScreen::OnAction(const CAction &action) default: break; } + return CGUIWindow::OnAction(action); } @@ -524,6 +568,8 @@ void CGUIWindowFullScreen::OnWindowLoaded() pLabel->SetVisible(true); pLabel->SetLabel("$INFO(VIDEOPLAYER.TIME) / $INFO(VIDEOPLAYER.DURATION)"); } + + FillInTVGroups(); } bool CGUIWindowFullScreen::OnMessage(CGUIMessage& message) @@ -542,6 +588,7 @@ bool CGUIWindowFullScreen::OnMessage(CGUIMessage& message) g_infoManager.SetShowInfo(false); g_infoManager.SetShowCodec(false); m_bShowCurrentTime = false; + m_bGroupSelectShow = false; g_infoManager.SetDisplayAfterSeek(0); // Make sure display after seek is off. // switch resolution @@ -588,6 +635,14 @@ bool CGUIWindowFullScreen::OnMessage(CGUIMessage& message) if (pDialog) pDialog->Close(true); pDialog = (CGUIDialog *)g_windowManager.GetWindow(WINDOW_DIALOG_FULLSCREEN_INFO); if (pDialog) pDialog->Close(true); + pDialog = (CGUIDialog *)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_OSD_CHANNELS); + if (pDialog) pDialog->Close(true); + pDialog = (CGUIDialog *)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_OSD_GUIDE); + if (pDialog) pDialog->Close(true); + pDialog = (CGUIDialog *)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_OSD_DIRECTOR); + if (pDialog) pDialog->Close(true); + pDialog = (CGUIDialog *)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_OSD_CUTTER); + if (pDialog) pDialog->Close(true); FreeResources(true); @@ -611,6 +666,55 @@ bool CGUIWindowFullScreen::OnMessage(CGUIMessage& message) return true; } + case GUI_MSG_CLICKED: + { + unsigned int iControl = message.GetSenderId(); + if (iControl == CONTROL_GROUP_CHOOSER) + { + int iNewGroup = -1; // All Channels + + cPVRChannelGroups *groups; + if (!g_PVRManager.IsPlayingRadio()) + groups = &PVRChannelGroupsTV; + else + groups = &PVRChannelGroupsRadio; + + // Get the currently selected label of the Select button + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), iControl); + OnMessage(msg); + CStdString strLabel = msg.GetLabel(); + if (msg.GetParam1() != 0) + { + // Go with the currently selected Label String from the Select button + // thru all Group names, if one of this names match the label load + // the ID of this group, if no equal name is found the default group + // for all channels is used. + for (int i = 0; i < (int) groups->size(); ++i) + { + if (strLabel == groups->at(i).GroupName()) + { + iNewGroup = groups->at(i).GroupID(); + break; + } + } + } + + // Switch to the first channel of the new group if the new group ID is + // different from the current one. + if (iNewGroup != g_PVRManager.GetPlayingGroup()) + { + g_PVRManager.SetPlayingGroup(iNewGroup); + OnAction(CAction(ACTION_CHANNEL_SWITCH, (float) groups->GetFirstChannelForGroupID(iNewGroup))); + } + + // hide the control and reset focus + m_bGroupSelectShow = false; + SET_CONTROL_HIDDEN(CONTROL_GROUP_CHOOSER); +// SET_CONTROL_FOCUS(0, 0); + return true; + } + break; + } case GUI_MSG_SETFOCUS: case GUI_MSG_LOSTFOCUS: if (message.GetSenderId() != WINDOW_FULLSCREEN_VIDEO) return true; @@ -798,6 +902,7 @@ void CGUIWindowFullScreen::FrameMove() SET_CONTROL_VISIBLE(LABEL_ROW2); SET_CONTROL_VISIBLE(LABEL_ROW3); SET_CONTROL_VISIBLE(BLUE_BAR); + SET_CONTROL_HIDDEN(CONTROL_GROUP_CHOOSER); } else if (m_timeCodeShow) { @@ -805,6 +910,15 @@ void CGUIWindowFullScreen::FrameMove() SET_CONTROL_HIDDEN(LABEL_ROW2); SET_CONTROL_HIDDEN(LABEL_ROW3); SET_CONTROL_VISIBLE(BLUE_BAR); + SET_CONTROL_HIDDEN(CONTROL_GROUP_CHOOSER); + } + else if (m_bGroupSelectShow) + { + SET_CONTROL_HIDDEN(LABEL_ROW1); + SET_CONTROL_HIDDEN(LABEL_ROW2); + SET_CONTROL_HIDDEN(LABEL_ROW3); + SET_CONTROL_HIDDEN(BLUE_BAR); + SET_CONTROL_VISIBLE(CONTROL_GROUP_CHOOSER); } else { @@ -812,6 +926,7 @@ void CGUIWindowFullScreen::FrameMove() SET_CONTROL_HIDDEN(LABEL_ROW2); SET_CONTROL_HIDDEN(LABEL_ROW3); SET_CONTROL_HIDDEN(BLUE_BAR); + SET_CONTROL_HIDDEN(CONTROL_GROUP_CHOOSER); } } @@ -903,6 +1018,23 @@ void CGUIWindowFullScreen::SeekToTimeCodeStamp(SEEK_TYPE type, SEEK_DIRECTION di m_timeCodeShow = false; } +void CGUIWindowFullScreen::SeekTV(bool bPlus, bool bLargeStep) +{ + if (bLargeStep) + { + if (bPlus) + OnAction(CAction(ACTION_NEXT_ITEM)); + else + OnAction(CAction(ACTION_PREV_ITEM)); + return; + } + else if (!bLargeStep) + { + ChangetheTVGroup(bPlus); + return; + } +} + double CGUIWindowFullScreen::GetTimeCodeStamp() { // Convert the timestamp into an integer @@ -964,6 +1096,63 @@ void CGUIWindowFullScreen::OnSliderChange(void *data, CGUISliderControl *slider) } } +void CGUIWindowFullScreen::FillInTVGroups() +{ + CGUIMessage msgReset(GUI_MSG_LABEL_RESET, GetID(), CONTROL_GROUP_CHOOSER); + g_windowManager.SendMessage(msgReset); + + cPVRChannelGroups *groups; + if (!g_PVRManager.IsPlayingRadio()) + groups = &PVRChannelGroupsTV; + else + groups = &PVRChannelGroupsRadio; + + int iGroup = 0; + int iCurrentGroup = 0; + { + // First Group is All channels (ID = -1) + CGUIMessage msg(GUI_MSG_LABEL_ADD, GetID(), CONTROL_GROUP_CHOOSER, iGroup++); + msg.SetLabel(593); + g_windowManager.SendMessage(msg); + } + for (int i = 0; i < (int) groups->size(); ++i) + { + if (groups->at(i).GroupID() == g_PVRManager.GetPlayingGroup()) + iCurrentGroup = iGroup; + + CGUIMessage msg(GUI_MSG_LABEL_ADD, GetID(), CONTROL_GROUP_CHOOSER, iGroup++); + msg.SetLabel(groups->at(i).GroupName()); + g_windowManager.SendMessage(msg); + } + CGUIMessage msgSel(GUI_MSG_ITEM_SELECT, GetID(), CONTROL_GROUP_CHOOSER, iCurrentGroup); + g_windowManager.SendMessage(msgSel); +} + +void CGUIWindowFullScreen::ChangetheTVGroup(bool next) +{ + CGUISelectButtonControl* pButton = (CGUISelectButtonControl*)GetControl(CONTROL_GROUP_CHOOSER); + if (!pButton) + return; + + if (!m_bGroupSelectShow) + { + SET_CONTROL_VISIBLE(CONTROL_GROUP_CHOOSER); + SET_CONTROL_FOCUS(CONTROL_GROUP_CHOOSER, 0); + + // fire off an event that we've pressed this button... + OnAction(CAction(ACTION_SELECT_ITEM)); + + m_bGroupSelectShow = true; + } + else + { + if (next) + pButton->OnRight(); + else + pButton->OnLeft(); + } +} + void CGUIWindowFullScreen::ToggleOSD() { CGUIWindowOSD *pOSD = (CGUIWindowOSD *)g_windowManager.GetWindow(WINDOW_OSD); diff --git a/xbmc/GUIWindowFullScreen.h b/xbmc/GUIWindowFullScreen.h index da08905e4a..d73fa09531 100644 --- a/xbmc/GUIWindowFullScreen.h +++ b/xbmc/GUIWindowFullScreen.h @@ -41,6 +41,7 @@ public: virtual void Render(); virtual void OnWindowLoaded(); void ChangetheTimeCode(int remote); + void ChangetheTVGroup(bool next); virtual void OnSliderChange(void *data, CGUISliderControl *slider); protected: @@ -49,7 +50,9 @@ protected: private: void RenderTTFSubtitles(); + void SeekTV(bool bPlus, bool bLargeStep); void SeekChapter(int iChapter); + void FillInTVGroups(); void ToggleOSD(); enum SEEK_TYPE { SEEK_ABSOLUTE, SEEK_RELATIVE }; @@ -81,6 +84,7 @@ private: bool m_bShowCurrentTime; + bool m_bGroupSelectShow; bool m_timeCodeShow; unsigned int m_timeCodeTimeout; int m_timeCodeStamp[6]; diff --git a/xbmc/GUIWindowOSD.cpp b/xbmc/GUIWindowOSD.cpp index 28fcf1b1aa..6fa65ae568 100644 --- a/xbmc/GUIWindowOSD.cpp +++ b/xbmc/GUIWindowOSD.cpp @@ -48,6 +48,10 @@ void CGUIWindowOSD::FrameMove() if (g_Mouse.IsActive() || g_windowManager.IsWindowActive(WINDOW_DIALOG_AUDIO_OSD_SETTINGS) || g_windowManager.IsWindowActive(WINDOW_DIALOG_VIDEO_OSD_SETTINGS) || g_windowManager.IsWindowActive(WINDOW_DIALOG_VIDEO_BOOKMARKS) + || g_windowManager.IsWindowActive(WINDOW_DIALOG_PVR_OSD_CHANNELS) + || g_windowManager.IsWindowActive(WINDOW_DIALOG_PVR_OSD_GUIDE) + || g_windowManager.IsWindowActive(WINDOW_DIALOG_PVR_OSD_DIRECTOR) + || g_windowManager.IsWindowActive(WINDOW_DIALOG_PVR_OSD_CUTTER) || g_windowManager.IsWindowActive(WINDOW_DIALOG_OSD_TELETEXT)) SetAutoClose(100); // enough for 10fps } @@ -100,6 +104,14 @@ bool CGUIWindowOSD::OnMessage(CGUIMessage& message) if (pDialog && pDialog->IsDialogRunning()) pDialog->Close(true); pDialog = (CGUIDialog *)g_windowManager.GetWindow(WINDOW_DIALOG_VIDEO_BOOKMARKS); if (pDialog && pDialog->IsDialogRunning()) pDialog->Close(true); + pDialog = (CGUIDialog *)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_OSD_CHANNELS); + if (pDialog && pDialog->IsDialogRunning()) pDialog->Close(true); + pDialog = (CGUIDialog *)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_OSD_GUIDE); + if (pDialog && pDialog->IsDialogRunning()) pDialog->Close(true); + pDialog = (CGUIDialog *)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_OSD_DIRECTOR); + if (pDialog && pDialog->IsDialogRunning()) pDialog->Close(true); + pDialog = (CGUIDialog *)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_OSD_CUTTER); + if (pDialog && pDialog->IsDialogRunning()) pDialog->Close(true); pDialog = (CGUIDialog *)g_windowManager.GetWindow(WINDOW_DIALOG_OSD_TELETEXT); if (pDialog && pDialog->IsDialogRunning()) pDialog->Close(true); } diff --git a/xbmc/GUIWindowSettingsCategory.cpp b/xbmc/GUIWindowSettingsCategory.cpp index b27d66f4ad..cb19c58acd 100644 --- a/xbmc/GUIWindowSettingsCategory.cpp +++ b/xbmc/GUIWindowSettingsCategory.cpp @@ -56,6 +56,7 @@ #include "GUIDialogKeyboard.h" #include "GUIDialogYesNo.h" #include "GUIDialogOK.h" +#include "GUIDialogPVRChannelManager.h" #include "GUIWindowPrograms.h" #include "addons/Visualisation.h" #include "addons/AddonManager.h" @@ -81,6 +82,8 @@ #endif #include "GUIDialogAccessPoints.h" #include "FileSystem/Directory.h" +#include "utils/PVRChannels.h" +#include "PVRManager.h" #include "FileItem.h" #include "GUIToggleButtonControl.h" @@ -134,7 +137,7 @@ CGUIWindowSettingsCategory::CGUIWindowSettingsCategory(void) m_pOriginalImage = NULL; m_pOriginalEdit = NULL; // set the correct ID range... - m_idRange = 8; + m_idRange = 9; m_iScreen = 0; // set the network settings so that we don't reset them unnecessarily m_iNetworkAssignment = -1; @@ -602,6 +605,25 @@ void CGUIWindowSettingsCategory::CreateSettings() pControl->AddLabel(g_localizeStrings.Get(13509), RESAMPLE_REALLYHIGH); pControl->SetValue(pSettingInt->GetData()); } + else if (strSetting.Equals("pvrmenu.defaultguideview")) + { + CSettingInt *pSettingInt = (CSettingInt*)pSetting; + CGUISpinControlEx *pControl = (CGUISpinControlEx *)GetControl(GetSetting(strSetting)->GetID()); + pControl->AddLabel(g_localizeStrings.Get(19029), GUIDE_VIEW_CHANNEL); + pControl->AddLabel(g_localizeStrings.Get(19030), GUIDE_VIEW_NOW); + pControl->AddLabel(g_localizeStrings.Get(19031), GUIDE_VIEW_NEXT); + pControl->AddLabel(g_localizeStrings.Get(19032), GUIDE_VIEW_TIMELINE); + pControl->SetValue(pSettingInt->GetData()); + } + else if (strSetting.Equals("pvrplayback.startlast")) + { + CSettingInt *pSettingInt = (CSettingInt*)pSetting; + CGUISpinControlEx *pControl = (CGUISpinControlEx *)GetControl(GetSetting(strSetting)->GetID()); + pControl->AddLabel(g_localizeStrings.Get(106), START_LAST_CHANNEL_OFF); + pControl->AddLabel(g_localizeStrings.Get(19190), START_LAST_CHANNEL_MIN); + pControl->AddLabel(g_localizeStrings.Get(107), START_LAST_CHANNEL_ON); + pControl->SetValue(pSettingInt->GetData()); + } } if (m_vecSections[m_iSection]->m_strCategory == "network") @@ -760,6 +782,11 @@ void CGUIWindowSettingsCategory::UpdateSettings() CGUIControl *pControl = (CGUIControl *)GetControl(pSettingControl->GetID()); if (pControl) pControl->SetEnabled(g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE); } + else if (!strSetting.Equals("pvr.enabled") && strSetting.Left(4).Equals("pvrmanager.")) + { + CGUIControl *pControl = (CGUIControl *)GetControl(pSettingControl->GetID()); + if (pControl) pControl->SetEnabled(g_guiSettings.GetBool("pvrmanager.enabled")); + } else if (!strSetting.Equals("services.esenabled") && strSetting.Left(11).Equals("services.es")) { @@ -1005,6 +1032,13 @@ void CGUIWindowSettingsCategory::UpdateSettings() pControl->SetEnabled(addon->HasSettings()); } } + else if (!strSetting.Equals("pvrmanager.enabled") + && !strSetting.Equals("pvrmanager.resetdb") + && strSetting.Left(3).Equals("pvr")) + { + CGUIControl *pControl = (CGUIControl *)GetControl(pSettingControl->GetID()); + if (pControl) pControl->SetEnabled(g_guiSettings.GetBool("pvrmanager.enabled")); + } #if defined(_LINUX) && !defined(__APPLE__) else if (strSetting.Equals("audiooutput.custompassthrough")) { @@ -1575,7 +1609,7 @@ void CGUIWindowSettingsCategory::OnSettingChanged(CBaseSettingControl *pSettingC if (CAddonMgr::Get().GetAddon(g_guiSettings.GetString("screensaver.mode"), addon, ADDON_SCREENSAVER)) CGUIDialogAddonSettings::ShowAndGetInput(addon); } - else if (strSetting.Equals("debug.screenshotpath") || strSetting.Equals("audiocds.recordingpath") || strSetting.Equals("subtitles.custompath")) + else if (strSetting.Equals("debug.screenshotpath") || strSetting.Equals("audiocds.recordingpath") || strSetting.Equals("subtitles.custompath") || strSetting.Equals("pvrmenu.iconpath")) { CSettingString *pSettingString = (CSettingString *)pSettingControl->GetSetting(); CStdString path = g_guiSettings.GetString(strSetting,false); @@ -1587,7 +1621,11 @@ void CGUIWindowSettingsCategory::OnSettingChanged(CBaseSettingControl *pSettingC UpdateSettings(); bool bWriteOnly = true; - if (strSetting.Equals("subtitles.custompath")) + if (strSetting.Equals("pvrmenu.iconpath")) + { + bWriteOnly = false; + } + else if (strSetting.Equals("subtitles.custompath")) { bWriteOnly = false; shares = g_settings.m_videoSources; @@ -1725,6 +1763,13 @@ void CGUIWindowSettingsCategory::OnSettingChanged(CBaseSettingControl *pSettingC } #endif } + else if (strSetting.Equals("pvrmanager.enabled")) + { + if (g_guiSettings.GetBool("pvrmanager.enabled")) + g_application.StartPVRManager(); + else + g_application.StopPVRManager(); + } else if (strSetting.Equals("masterlock.lockcode")) { // Now Prompt User to enter the old and then the new MasterCode! @@ -1852,6 +1897,33 @@ void CGUIWindowSettingsCategory::OnSettingChanged(CBaseSettingControl *pSettingC { CUtil::DeleteVideoDatabaseDirectoryCache(); } + else if (strSetting.Equals("pvrmenu.searchicons")) + { + cPVRChannels::SearchMissingChannelIcons(); + } + else if (strSetting.Equals("pvrmanager.resetdb")) + { + if (CGUIDialogYesNo::ShowAndGetInput(19098, 19186, 750, 0)) + g_PVRManager.ResetDatabase(); + } + else if (strSetting.Equals("pvrepg.resetepg")) + { + if (CGUIDialogYesNo::ShowAndGetInput(19098, 19188, 750, 0)) + g_PVRManager.ResetEPG(); + } + else if (strSetting.Equals("pvrmanager.channelscan")) + { + if (CGUIDialogYesNo::ShowAndGetInput(19098, 19118, 19194, 0)) + g_PVRManager.StartChannelScan(); + } + else if (strSetting.Equals("pvrmanager.channelmanager")) + { + CGUIDialogPVRChannelManager *dialog = (CGUIDialogPVRChannelManager *)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_CHANNEL_MANAGER); + if (dialog) + { + dialog->DoModal(); + } + } UpdateSettings(); } diff --git a/xbmc/GUIWindowSystemInfo.cpp b/xbmc/GUIWindowSystemInfo.cpp index f8c3fd74cd..308106e2b2 100644 --- a/xbmc/GUIWindowSystemInfo.cpp +++ b/xbmc/GUIWindowSystemInfo.cpp @@ -35,9 +35,10 @@ #define CONTROL_BT_NETWORK 96 #define CONTROL_BT_VIDEO 97 #define CONTROL_BT_HARDWARE 98 +#define CONTROL_BT_PVR 99 #define CONTROL_START CONTROL_BT_STORAGE -#define CONTROL_END CONTROL_BT_HARDWARE +#define CONTROL_END CONTROL_BT_PVR CGUIWindowSystemInfo::CGUIWindowSystemInfo(void) :CGUIWindow(WINDOW_SYSTEM_INFORMATION, "SettingsSystemInfo.xml") @@ -159,6 +160,23 @@ void CGUIWindowSystemInfo::FrameMove() SetControlLabel(i++, "%s: %s", 22012, SYSTEM_TOTAL_MEMORY); SetControlLabel(i++, "%s: %s", 158, SYSTEM_FREE_MEMORY); } + else if(m_section == CONTROL_BT_PVR) + { + SET_CONTROL_LABEL(40,g_localizeStrings.Get(19166)); + int i = 2; + + SetControlLabel(i++, "%s: %s", 19120, PVR_BACKEND_NUMBER); + i++; // empty line + SetControlLabel(i++, "%s: %s", 19012, PVR_BACKEND_NAME); + SetControlLabel(i++, "%s: %s", 19114, PVR_BACKEND_VERSION); + SetControlLabel(i++, "%s: %s", 19115, PVR_BACKEND_HOST); + SetControlLabel(i++, "%s: %s", 19116, PVR_BACKEND_DISKSPACE); + SetControlLabel(i++, "%s: %s", 19019, PVR_BACKEND_CHANNELS); + SetControlLabel(i++, "%s: %s", 19163, PVR_BACKEND_RECORDINGS); + SetControlLabel(i++, "%s: %s", 19025, PVR_BACKEND_TIMERS); + } + SET_CONTROL_LABEL(52, "XBMC "+g_infoManager.GetLabel(SYSTEM_BUILD_VERSION)+" (Compiled : "+g_infoManager.GetLabel(SYSTEM_BUILD_DATE)+")"); + CGUIWindow::FrameMove(); } diff --git a/xbmc/GUIWindowTV.cpp b/xbmc/GUIWindowTV.cpp new file mode 100644 index 0000000000..5dfc625dc7 --- /dev/null +++ b/xbmc/GUIWindowTV.cpp @@ -0,0 +1,1892 @@ +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* Standart includes */ +#include "Application.h" +#include "FileSystem/File.h" +#include "FileSystem/StackDirectory.h" +#include "GUISettings.h" +#include "GUIWindowManager.h" +#include "LocalizeStrings.h" +#include "MediaManager.h" +#include "Picture.h" +#include "Settings.h" +#include "TextureCache.h" +#include "utils/log.h" +#include "URL.h" + +/* Dialog windows includes */ +#include "GUIDialogFileBrowser.h" +#include "GUIDialogProgress.h" +#include "GUIDialogYesNo.h" +#include "GUIDialogOK.h" +#include "GUIDialogNumeric.h" +#include "GUIDialogKeyboard.h" +#include "GUIDialogPVRGuideInfo.h" +#include "GUIDialogPVRRecordingInfo.h" +#include "GUIDialogPVRTimerSettings.h" +#include "GUIDialogPVRGroupManager.h" +#include "GUIDialogPVRGuideSearch.h" +#include "GUIUserMessages.h" +#include "GUIEPGGridContainer.h" + +/* self include */ +#include "GUIWindowTV.h" + +/* TV control */ +#include "PVRManager.h" + + +using namespace std; + +#define CONTROL_LIST_TIMELINE 10 +#define CONTROL_LIST_CHANNELS_TV 11 +#define CONTROL_LIST_CHANNELS_RADIO 12 +#define CONTROL_LIST_RECORDINGS 13 +#define CONTROL_LIST_TIMERS 14 +#define CONTROL_LIST_GUIDE_CHANNEL 15 +#define CONTROL_LIST_GUIDE_NOW_NEXT 16 +#define CONTROL_LIST_SEARCH 17 + +#define CONTROL_LABELHEADER 29 +#define CONTROL_LABELGROUP 30 + +#define CONTROL_BTNGUIDE 31 +#define CONTROL_BTNCHANNELS_TV 32 +#define CONTROL_BTNCHANNELS_RADIO 33 +#define CONTROL_BTNRECORDINGS 34 +#define CONTROL_BTNTIMERS 35 +#define CONTROL_BTNSEARCH 36 +#define CONTROL_BTNGUIDE_CHANNEL 37 +#define CONTROL_BTNGUIDE_NOW 38 +#define CONTROL_BTNGUIDE_NEXT 39 +#define CONTROL_BTNGUIDE_TIMELINE 40 + +/** + * \brief Class constructor + */ +CGUIWindowTV::CGUIWindowTV(void) : CGUIMediaWindow(WINDOW_TV, "MyTV.xml") +{ + m_iCurrSubTVWindow = TV_WINDOW_UNKNOWN; + m_iSavedSubTVWindow = TV_WINDOW_UNKNOWN; + m_iSelected_GUIDE = 0; + m_iSelected_CHANNELS_TV = 0; + m_iSelected_CHANNELS_RADIO = 0; + m_iSelected_RECORDINGS = 0; + m_iSelected_RECORDINGS_Path = "pvr://recordings/"; + m_iSelected_TIMERS = 0; + m_iSelected_SEARCH = 0; + m_iCurrentTVGroup = -1; + m_iCurrentRadioGroup = -1; + m_bShowHiddenChannels = false; + m_bSearchStarted = false; + m_bSearchConfirmed = false; + m_iGuideView = g_guiSettings.GetInt("pvrmenu.defaultguideview"); + m_guideGrid = NULL; + m_iSortOrder_SEARCH = SORT_ORDER_ASC; + m_iSortMethod_SEARCH = SORT_METHOD_DATE; + m_iSortOrder_TIMERS = SORT_ORDER_ASC; + m_iSortMethod_TIMERS = SORT_METHOD_DATE; +} + +CGUIWindowTV::~CGUIWindowTV() +{ +} + +bool CGUIWindowTV::OnMessage(CGUIMessage& message) +{ + unsigned int iControl = 0; + unsigned int iMessage = message.GetMessage(); + + if (iMessage == GUI_MSG_FOCUSED) + { + /* Get the focused control Identifier */ + iControl = message.GetControlId(); + + /* Process Identifier for focused Subwindow select buttons or list item. + * If a new conrol becomes highlighted load his subwindow data + */ + if (iControl == CONTROL_BTNGUIDE || m_iSavedSubTVWindow == TV_WINDOW_TV_PROGRAM) + { + if (m_iCurrSubTVWindow != TV_WINDOW_TV_PROGRAM) + UpdateGuide(); + else + m_iSelected_GUIDE = m_viewControl.GetSelectedItem(); + + m_iCurrSubTVWindow = TV_WINDOW_TV_PROGRAM; + } + else if (iControl == CONTROL_BTNCHANNELS_TV || m_iSavedSubTVWindow == TV_WINDOW_CHANNELS_TV) + { + if (m_iCurrSubTVWindow != TV_WINDOW_CHANNELS_TV) + UpdateChannelsTV(); + else + m_iSelected_CHANNELS_TV = m_viewControl.GetSelectedItem(); + + m_iCurrSubTVWindow = TV_WINDOW_CHANNELS_TV; + } + else if (iControl == CONTROL_BTNCHANNELS_RADIO || m_iSavedSubTVWindow == TV_WINDOW_CHANNELS_RADIO) + { + if (m_iCurrSubTVWindow != TV_WINDOW_CHANNELS_RADIO) + UpdateChannelsRadio(); + else + m_iSelected_CHANNELS_RADIO = m_viewControl.GetSelectedItem(); + + m_iCurrSubTVWindow = TV_WINDOW_CHANNELS_RADIO; + } + else if (iControl == CONTROL_BTNRECORDINGS || m_iSavedSubTVWindow == TV_WINDOW_RECORDINGS) + { + if (m_iCurrSubTVWindow != TV_WINDOW_RECORDINGS) + UpdateRecordings(); + else + { + m_iSelected_RECORDINGS_Path = m_vecItems->m_strPath; + m_iSelected_RECORDINGS = m_viewControl.GetSelectedItem(); + } + + m_iCurrSubTVWindow = TV_WINDOW_RECORDINGS; + } + else if (iControl == CONTROL_BTNTIMERS || m_iSavedSubTVWindow == TV_WINDOW_TIMERS) + { + if (m_iCurrSubTVWindow != TV_WINDOW_TIMERS) + UpdateTimers(); + else + m_iSelected_TIMERS = m_viewControl.GetSelectedItem(); + + m_iCurrSubTVWindow = TV_WINDOW_TIMERS; + } + else if (iControl == CONTROL_BTNSEARCH || m_iSavedSubTVWindow == TV_WINDOW_SEARCH) + { + if (m_iCurrSubTVWindow != TV_WINDOW_SEARCH) + UpdateSearch(); + else + m_iSelected_SEARCH = m_viewControl.GetSelectedItem(); + + m_iCurrSubTVWindow = TV_WINDOW_SEARCH; + } + + if (m_iSavedSubTVWindow != TV_WINDOW_UNKNOWN) + m_iSavedSubTVWindow = TV_WINDOW_UNKNOWN; + } + else if (iMessage == GUI_MSG_CLICKED) + { + iControl = message.GetSenderId(); + + if (iControl == CONTROL_BTNGUIDE) + { + m_iGuideView++; + + if (m_iGuideView > GUIDE_VIEW_TIMELINE) + { + m_iGuideView = 0; + } + + UpdateGuide(); + return true; + } + else if (iControl == CONTROL_BTNGUIDE_CHANNEL) + { + m_iGuideView = GUIDE_VIEW_CHANNEL; + UpdateGuide(); + return true; + } + else if (iControl == CONTROL_BTNGUIDE_NOW) + { + m_iGuideView = GUIDE_VIEW_NOW; + UpdateGuide(); + return true; + } + else if (iControl == CONTROL_BTNGUIDE_NEXT) + { + m_iGuideView = GUIDE_VIEW_NEXT; + UpdateGuide(); + return true; + } + else if (iControl == CONTROL_BTNGUIDE_TIMELINE) + { + m_iGuideView = GUIDE_VIEW_TIMELINE; + UpdateGuide(); + return true; + } + else if (iControl == CONTROL_BTNCHANNELS_TV) + { + m_iCurrentTVGroup = PVRChannelGroupsTV.GetNextGroupID(m_iCurrentTVGroup); + UpdateChannelsTV(); + return true; + } + else if (iControl == CONTROL_BTNCHANNELS_RADIO) + { + m_iCurrentRadioGroup = PVRChannelGroupsRadio.GetNextGroupID(m_iCurrentRadioGroup); + UpdateChannelsRadio(); + return true; + } + else if (iControl == CONTROL_BTNRECORDINGS) + { + g_PVRManager.TriggerRecordingsUpdate(); + UpdateRecordings(); + return true; + } + else if (iControl == CONTROL_BTNTIMERS) + { + PVRTimers.Update(); + UpdateTimers(); + return true; + } + else if (iControl == CONTROL_BTNSEARCH) + { + ShowSearchResults(); + } + else if (iControl == CONTROL_LIST_TIMELINE || + iControl == CONTROL_LIST_GUIDE_CHANNEL || + iControl == CONTROL_LIST_GUIDE_NOW_NEXT) + { + int iAction = message.GetParam1(); + int iItem = m_viewControl.GetSelectedItem(); + + /* Check file item is in list range and get his pointer */ + if (iItem < 0 || iItem >= (int)m_vecItems->Size()) return true; + + CFileItemPtr pItem = m_vecItems->Get(iItem); + + /* Process actions */ + if ((iAction == ACTION_SELECT_ITEM) || (iAction == ACTION_SHOW_INFO || iAction == ACTION_MOUSE_LEFT_CLICK)) + { + /* Show information Dialog */ + ShowEPGInfo(pItem.get()); + return true; + } + else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK) + { + /* Show Contextmenu */ + OnPopupMenu(iItem); + return true; + } + else if (iAction == ACTION_RECORD) + { + if (pItem->GetEPGInfoTag()->ChannelNumber() != -1) + { + if (pItem->GetEPGInfoTag()->Timer() == NULL) + { + // prompt user for confirmation of channel record + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + if (!pDialog) + return false; + + pDialog->SetHeading(264); + pDialog->SetLine(0, ""); + pDialog->SetLine(1, pItem->GetEPGInfoTag()->Title()); + pDialog->SetLine(2, ""); + pDialog->DoModal(); + + if (!pDialog->IsConfirmed()) + return true; + + cPVRTimerInfoTag newtimer(*pItem.get()); + CFileItem *item = new CFileItem(newtimer); + + if (cPVRTimers::AddTimer(*item)) + PVREpgs.SetVariableData(m_vecItems); + } + else + { + CGUIDialogOK::ShowAndGetInput(19033,19034,0,0); + } + } + } + else if (iAction == ACTION_PLAY) + { + cPVRChannels *channels; + if (!pItem->GetEPGInfoTag()->IsRadio()) + channels = &PVRChannelsTV; + else + channels = &PVRChannelsRadio; + + if (!g_application.PlayFile(CFileItem(channels->at(pItem->GetEPGInfoTag()->ChannelNumber()-1)))) + { + CGUIDialogOK::ShowAndGetInput(19033,0,19035,0); + return false; + } + return true; + } + } + else if ((iControl == CONTROL_LIST_CHANNELS_TV) || (iControl == CONTROL_LIST_CHANNELS_RADIO)) + { + /* Get currently performed action */ + int iAction = message.GetParam1(); + + /* Get currently selected item from file list */ + int iItem = m_viewControl.GetSelectedItem(); + + /* Check file item is in list range and get his pointer */ + if (iItem < 0 || iItem >= (int)m_vecItems->Size()) return true; + + CFileItemPtr pItem = m_vecItems->Get(iItem); + + /* Process actions */ + if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK || iAction == ACTION_PLAY) + { + /* Check if "Add channel..." entry is pressed by OK, if yes + create a new channel and open settings dialog, otherwise + open channel with player */ + if (pItem->m_strPath == "pvr://channels/.add.channel") + { + CGUIDialogOK::ShowAndGetInput(19033,0,19038,0); + } + else + { + if (iControl == CONTROL_LIST_CHANNELS_TV) + g_PVRManager.SetPlayingGroup(m_iCurrentTVGroup); + if (iControl == CONTROL_LIST_CHANNELS_RADIO) + g_PVRManager.SetPlayingGroup(m_iCurrentRadioGroup); + + /* Open tv channel by Player and return */ + return PlayFile(pItem.get(), g_guiSettings.GetBool("pvrplayback.playminimized")); + } + } + else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK) + { + //contextmenu + OnPopupMenu(iItem); + return true; + } + else if (iAction == ACTION_SHOW_INFO) + { + /* Show information Dialog */ + ShowEPGInfo(pItem.get()); + return true; + } + else if (iAction == ACTION_DELETE_ITEM) + { + /* Check if entry is a valid deleteable channel */ + int iChannel = pItem->GetPVRChannelInfoTag()->Number(); + + if (iChannel != -1) + { + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + + if (pDialog) + { + pDialog->SetHeading(19039); + pDialog->SetLine(0, ""); + pDialog->SetLine(1, pItem->GetPVRChannelInfoTag()->Name()); + pDialog->SetLine(2, ""); + pDialog->DoModal(); + + if (!pDialog->IsConfirmed()) return false; + + if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV) + { + PVRChannelsTV.HideChannel(pItem->GetPVRChannelInfoTag()->Number()); + UpdateChannelsTV(); + } + else if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO) + { + PVRChannelsRadio.HideChannel(pItem->GetPVRChannelInfoTag()->Number()); + UpdateChannelsRadio(); + } + } + return true; + } + } + } + else if (iControl == CONTROL_LIST_RECORDINGS) + { + /* Get currently performed action */ + int iAction = message.GetParam1(); + + /* Get currently selected item from file list */ + int iItem = m_viewControl.GetSelectedItem(); + + /* Check file item is in list range and get his pointer */ + if (iItem < 0 || iItem >= (int)m_vecItems->Size()) return true; + + CFileItemPtr pItem = m_vecItems->Get(iItem); + if (pItem->m_bIsFolder || pItem->IsParentFolder()) + return OnClick(iItem); + + /* Process actions */ + if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK || iAction == ACTION_PLAY) + { + /* Open recording with Player and return */ + return PlayFile(pItem.get(), false); + } + else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK) + { + //contextmenu + OnPopupMenu(iItem); + return true; + } + else if (iAction == ACTION_SHOW_INFO) + { + /* Show information Dialog */ + ShowRecordingInfo(pItem.get()); + return true; + } + else if (iAction == ACTION_DELETE_ITEM) + { + /* Check if entry is a valid deleteable record */ + if (pItem->GetPVRRecordingInfoTag()->ClientIndex() != -1) + { + // prompt user for confirmation of record deletion + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + if (!pDialog) + return false; + + pDialog->SetHeading(122); + pDialog->SetLine(0, 19043); + pDialog->SetLine(1, ""); + pDialog->SetLine(2, pItem->GetPVRRecordingInfoTag()->Title()); + pDialog->DoModal(); + + if (pDialog->IsConfirmed()) + { + if (cPVRRecordings::DeleteRecording(*pItem)) + { + PVRRecordings.Update(true); + UpdateRecordings(); + } + } + return true; + } + } + } + else if (iControl == CONTROL_LIST_TIMERS) + { + /* Get currently performed action */ + int iAction = message.GetParam1(); + + /* Get currently selected item from file list */ + int iItem = m_viewControl.GetSelectedItem(); + + /* Check file item is in list range and get his pointer */ + if (iItem < 0 || iItem >= (int)m_vecItems->Size()) return true; + + CFileItemPtr pItem = m_vecItems->Get(iItem); + + /* Process actions */ + if (iAction == ACTION_SHOW_INFO || iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) + { + /* Check if "Add timer..." entry is pressed by OK, if yes + create a new timer and open settings dialog, otherwise + open settings for selected timer entry */ + if (pItem->m_strPath == "pvr://timers/add.timer") + { + cPVRTimerInfoTag newtimer(true); + CFileItem *item = new CFileItem(newtimer); + + if (ShowTimerSettings(item)) + { + /* Add timer to backend */ + cPVRTimers::AddTimer(*item); + UpdateTimers(); + } + } + else + { + CFileItem fileitem(*pItem); + + if (ShowTimerSettings(&fileitem)) + { + /* Update timer on pvr backend */ + cPVRTimers::UpdateTimer(fileitem); + UpdateTimers(); + } + } + + return true; + } + else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK) + { + //contextmenu + OnPopupMenu(iItem); + return true; + } + else if (iAction == ACTION_DELETE_ITEM) + { + /* Check if entry is a valid deleteable timer */ + if (pItem->GetPVRTimerInfoTag()->ClientIndex() != -1) + { + // prompt user for confirmation of timer deletion + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + if (!pDialog) + return false; + + pDialog->SetHeading(122); + pDialog->SetLine(0, 19040); + pDialog->SetLine(1, ""); + pDialog->SetLine(2, pItem->GetPVRTimerInfoTag()->Title()); + pDialog->DoModal(); + + if (pDialog->IsConfirmed()) + { + cPVRTimers::DeleteTimer(*pItem); + UpdateTimers(); + } + return true; + } + } + } + else if (iControl == CONTROL_LIST_SEARCH) + { + /* Get currently performed action */ + int iAction = message.GetParam1(); + + /* Get currently selected item from file list */ + int iItem = m_viewControl.GetSelectedItem(); + + /* Check file item is in list range and get his pointer */ + if (iItem < 0 || iItem >= (int)m_vecItems->Size()) return true; + + CFileItemPtr pItem = m_vecItems->Get(iItem); + + /* Process actions */ + if (iAction == ACTION_SHOW_INFO || iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) + { + if (pItem->m_strPath == "pvr://guide/searchresults/empty.epg") + ShowSearchResults(); + else + ShowEPGInfo(pItem.get()); + + return true; + } + else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK) + { + /* Show context menu */ + OnPopupMenu(iItem); + return true; + } + else if (iAction == ACTION_RECORD) + { + if (pItem->GetEPGInfoTag()->ChannelNumber() != -1) + { + if (pItem->GetEPGInfoTag()->Timer() == NULL) + { + // prompt user for confirmation of channel record + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + if (!pDialog) + return false; + + pDialog->SetHeading(264); + pDialog->SetLine(0, ""); + pDialog->SetLine(1, pItem->GetEPGInfoTag()->Title()); + pDialog->SetLine(2, ""); + pDialog->DoModal(); + + if (!pDialog->IsConfirmed()) + return true; + + cPVRTimerInfoTag newtimer(*pItem.get()); + CFileItem *item = new CFileItem(newtimer); + + if (cPVRTimers::AddTimer(*item)) + PVREpgs.SetVariableData(m_vecItems); + } + else + CGUIDialogOK::ShowAndGetInput(19033,19034,0,0); + } + } + } + } + return CGUIMediaWindow::OnMessage(message); +} + +bool CGUIWindowTV::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_PREVIOUS_MENU) + { + g_windowManager.PreviousWindow(); + return true; + } + else if (action.GetID() == ACTION_PARENT_DIR) + { + if (m_iCurrSubTVWindow == TV_WINDOW_RECORDINGS && m_vecItems->m_strPath != "pvr://recordings/") + GoParentFolder(); + else + g_windowManager.PreviousWindow(); + + return true; + } + return CGUIMediaWindow::OnAction(action); +} + +void CGUIWindowTV::OnWindowLoaded() +{ + CGUIMediaWindow::OnWindowLoaded(); + m_viewControl.Reset(); + m_viewControl.SetParentWindow(GetID()); + m_viewControl.AddView(GetControl(CONTROL_LIST_TIMELINE)); + m_viewControl.AddView(GetControl(CONTROL_LIST_CHANNELS_TV)); + m_viewControl.AddView(GetControl(CONTROL_LIST_CHANNELS_RADIO)); + m_viewControl.AddView(GetControl(CONTROL_LIST_RECORDINGS)); + m_viewControl.AddView(GetControl(CONTROL_LIST_TIMERS)); + m_viewControl.AddView(GetControl(CONTROL_LIST_GUIDE_CHANNEL)); + m_viewControl.AddView(GetControl(CONTROL_LIST_GUIDE_NOW_NEXT)); + m_viewControl.AddView(GetControl(CONTROL_LIST_SEARCH)); +} + +void CGUIWindowTV::OnWindowUnload() +{ + /* Save current Subwindow selected list position */ + if (m_iCurrSubTVWindow == TV_WINDOW_TV_PROGRAM) + m_iSelected_GUIDE = m_viewControl.GetSelectedItem(); + else if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV) + m_iSelected_CHANNELS_TV = m_viewControl.GetSelectedItem(); + else if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO) + m_iSelected_CHANNELS_RADIO = m_viewControl.GetSelectedItem(); + else if (m_iCurrSubTVWindow == TV_WINDOW_RECORDINGS) + { + m_iSelected_RECORDINGS_Path = m_vecItems->m_strPath; + m_iSelected_RECORDINGS = m_viewControl.GetSelectedItem(); + } + else if (m_iCurrSubTVWindow == TV_WINDOW_TIMERS) + m_iSelected_TIMERS = m_viewControl.GetSelectedItem(); + else if (m_iCurrSubTVWindow == TV_WINDOW_SEARCH) + m_iSelected_SEARCH = m_viewControl.GetSelectedItem(); + + m_iSavedSubTVWindow = m_iCurrSubTVWindow; + m_iCurrSubTVWindow = TV_WINDOW_UNKNOWN; + + m_viewControl.Reset(); + CGUIMediaWindow::OnWindowUnload(); + return; +} + +void CGUIWindowTV::OnInitWindow() +{ + /* Make sure we have active running clients, otherwise return to + * Previous Window. + */ + if (!g_PVRManager.HaveActiveClients()) + { + g_windowManager.PreviousWindow(); + CGUIDialogOK::ShowAndGetInput(19033,0,19045,19044); + return; + } + + /* This is a bad way but the SetDefaults function use the first and last + * epg date which is not available on construction time, thats why we + * but it to Window initialization. + */ + if (!m_bSearchStarted) + { + m_bSearchStarted = true; + m_searchfilter.SetDefaults(); + } + + if (m_iSavedSubTVWindow == TV_WINDOW_TV_PROGRAM) + m_viewControl.SetCurrentView(CONTROL_LIST_GUIDE_CHANNEL); + else if (m_iSavedSubTVWindow == TV_WINDOW_CHANNELS_TV) + m_viewControl.SetCurrentView(CONTROL_LIST_CHANNELS_TV); + else if (m_iSavedSubTVWindow == TV_WINDOW_CHANNELS_RADIO) + m_viewControl.SetCurrentView(CONTROL_LIST_CHANNELS_RADIO); + else if (m_iSavedSubTVWindow == TV_WINDOW_RECORDINGS) + m_viewControl.SetCurrentView(CONTROL_LIST_RECORDINGS); + else if (m_iSavedSubTVWindow == TV_WINDOW_TIMERS) + m_viewControl.SetCurrentView(CONTROL_LIST_TIMERS); + else if (m_iSavedSubTVWindow == TV_WINDOW_SEARCH) + m_viewControl.SetCurrentView(CONTROL_LIST_SEARCH); + + CGUIMediaWindow::OnInitWindow(); + return; +} + +void CGUIWindowTV::GetContextButtons(int itemNumber, CContextButtons &buttons) +{ + /* Check file item is in list range and get his pointer */ + if (itemNumber < 0 || itemNumber >= (int)m_vecItems->Size()) return; + + CFileItemPtr pItem = m_vecItems->Get(itemNumber); + + if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV || m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO) + { + if (itemNumber < 0 || itemNumber >= m_vecItems->Size()) + return; + + /* check that file item is in list and get his pointer*/ + if (pItem->m_strPath == "pvr://channels/.add.channel") + { + /* If yes show only "New Channel" on context menu */ + buttons.Add(CONTEXT_BUTTON_ADD, 19046); /* Add new channel */ + } + else + { + buttons.Add(CONTEXT_BUTTON_INFO, 19047); /* Channel info button */ + buttons.Add(CONTEXT_BUTTON_FIND, 19003); /* Find similar program */ + buttons.Add(CONTEXT_BUTTON_PLAY_ITEM, 19000); /* switch to channel */ + buttons.Add(CONTEXT_BUTTON_SET_THUMB, 20019); /* Set icon */ + buttons.Add(CONTEXT_BUTTON_GROUP_MANAGER, 19048); /* Group managment */ + buttons.Add(CONTEXT_BUTTON_HIDE, m_bShowHiddenChannels ? 19049 : 19054); /* HIDE CHANNEL */ + + if (m_vecItems->Size() > 1 && !m_bShowHiddenChannels) + buttons.Add(CONTEXT_BUTTON_MOVE, 116); /* Move channel up or down */ + + if ((m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV && PVRChannelsTV.GetNumHiddenChannels() > 0) || + (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO && PVRChannelsRadio.GetNumHiddenChannels() > 0) || + m_bShowHiddenChannels) + buttons.Add(CONTEXT_BUTTON_SHOW_HIDDEN, m_bShowHiddenChannels ? 19050 : 19051); /* SHOW HIDDEN CHANNELS */ + + CGUIMediaWindow::GetContextButtons(itemNumber, buttons); + + if (g_PVRManager.HaveMenuHooks(pItem->GetPVRChannelInfoTag()->ClientID())) + buttons.Add(CONTEXT_BUTTON_MENU_HOOKS, 19195); /* PVR client specific action */ + } + } + else if (m_iCurrSubTVWindow == TV_WINDOW_RECORDINGS) /* Add recordings context buttons */ + { + buttons.Add(CONTEXT_BUTTON_INFO, 19053); /* Get Information of this recording */ + buttons.Add(CONTEXT_BUTTON_FIND, 19003); /* Find similar program */ + buttons.Add(CONTEXT_BUTTON_PLAY_ITEM, 12021); /* Play this recording */ +// buttons.Add(CONTEXT_BUTTON_RESUME_ITEM, 12022); + // Update sort by button +// if (m_guiState->GetSortMethod()!=SORT_METHOD_NONE) +// { +// CStdString sortLabel; +// sortLabel.Format(g_localizeStrings.Get(550).c_str(), g_localizeStrings.Get(m_guiState->GetSortMethodLabel()).c_str()); +// buttons.Add(CONTEXT_BUTTON_SORTBY, sortLabel); /* Sort method */ +// +// if (m_guiState->GetDisplaySortOrder()==SORT_ORDER_ASC) +// buttons.Add(CONTEXT_BUTTON_SORTASC, 584); /* Sort up or down */ +// else +// buttons.Add(CONTEXT_BUTTON_SORTASC, 585); /* Sort up or down */ +// } + + buttons.Add(CONTEXT_BUTTON_RENAME, 118); /* Rename this recording */ + buttons.Add(CONTEXT_BUTTON_DELETE, 117); /* Delete this recording */ + } + else if (m_iCurrSubTVWindow == TV_WINDOW_TIMERS) /* Add timer context buttons */ + { + /* Check for a empty file item list, means only a + file item with the name "Add timer..." is present */ + if (pItem->m_strPath == "pvr://timers/add.timer") + { + /* If yes show only "New Timer" on context menu */ + buttons.Add(CONTEXT_BUTTON_ADD, 19056); /* NEW TIMER */ + if (m_vecItems->Size() > 1) + { + buttons.Add(CONTEXT_BUTTON_SORTBY_NAME, 103); /* Sort by Name */ + buttons.Add(CONTEXT_BUTTON_SORTBY_DATE, 104); /* Sort by Date */ + } + } + else + { + /* If any timers are present show more */ + buttons.Add(CONTEXT_BUTTON_EDIT, 19057); /* Edit Timer */ + buttons.Add(CONTEXT_BUTTON_ADD, 19056); /* NEW TIMER */ + buttons.Add(CONTEXT_BUTTON_ACTIVATE, 19058); /* ON/OFF */ + buttons.Add(CONTEXT_BUTTON_RENAME, 118); /* Rename Timer */ + buttons.Add(CONTEXT_BUTTON_DELETE, 117); /* Delete Timer */ + buttons.Add(CONTEXT_BUTTON_SORTBY_NAME, 103); /* Sort by Name */ + buttons.Add(CONTEXT_BUTTON_SORTBY_DATE, 104); /* Sort by Date */ + if (g_PVRManager.HaveMenuHooks(pItem->GetPVRTimerInfoTag()->ClientID())) + buttons.Add(CONTEXT_BUTTON_MENU_HOOKS, 19195); /* PVR client specific action */ + } + } + else if (m_iCurrSubTVWindow == TV_WINDOW_TV_PROGRAM) + { + if (pItem->GetEPGInfoTag()->End() > CDateTime::GetCurrentDateTime()) + { + if (pItem->GetEPGInfoTag()->Timer() == NULL) + { + if (pItem->GetEPGInfoTag()->Start() < CDateTime::GetCurrentDateTime()) + { + buttons.Add(CONTEXT_BUTTON_START_RECORD, 264); /* RECORD programme */ + } + else + { + buttons.Add(CONTEXT_BUTTON_START_RECORD, 19061); + } + } + else + { + if (pItem->GetEPGInfoTag()->Start() < CDateTime::GetCurrentDateTime()) + { + buttons.Add(CONTEXT_BUTTON_STOP_RECORD, 19059); + } + else + { + buttons.Add(CONTEXT_BUTTON_STOP_RECORD, 19060); + } + } + } + + buttons.Add(CONTEXT_BUTTON_INFO, 658); /* Epg info button */ + buttons.Add(CONTEXT_BUTTON_PLAY_ITEM, 19000); /* Switch channel */ + buttons.Add(CONTEXT_BUTTON_FIND, 19003); /* Find similar program */ + if (m_iGuideView == GUIDE_VIEW_TIMELINE) + { + buttons.Add(CONTEXT_BUTTON_BEGIN, 19063); /* Go to begin */ + buttons.Add(CONTEXT_BUTTON_END, 19064); /* Go to end */ + } + if (g_PVRManager.HaveMenuHooks(pItem->GetEPGInfoTag()->ClientID())) + buttons.Add(CONTEXT_BUTTON_MENU_HOOKS, 19195); /* PVR client specific action */ + } + else if (m_iCurrSubTVWindow == TV_WINDOW_SEARCH) + { + if (pItem->GetLabel() != g_localizeStrings.Get(19027)) + { + if (pItem->GetEPGInfoTag()->End() > CDateTime::GetCurrentDateTime()) + { + if (pItem->GetEPGInfoTag()->Timer() == NULL) + { + if (pItem->GetEPGInfoTag()->Start() < CDateTime::GetCurrentDateTime()) + buttons.Add(CONTEXT_BUTTON_START_RECORD, 264); /* RECORD programme */ + else + buttons.Add(CONTEXT_BUTTON_START_RECORD, 19061); /* Create a Timer */ + } + else + { + if (pItem->GetEPGInfoTag()->Start() < CDateTime::GetCurrentDateTime()) + buttons.Add(CONTEXT_BUTTON_STOP_RECORD, 19059); /* Stop recording */ + else + buttons.Add(CONTEXT_BUTTON_STOP_RECORD, 19060); /* Delete Timer */ + } + } + + buttons.Add(CONTEXT_BUTTON_INFO, 658); /* Epg info button */ + buttons.Add(CONTEXT_BUTTON_SORTBY_CHANNEL, 19062); /* Sort by channel */ + buttons.Add(CONTEXT_BUTTON_SORTBY_NAME, 103); /* Sort by Name */ + buttons.Add(CONTEXT_BUTTON_SORTBY_DATE, 104); /* Sort by Date */ + buttons.Add(CONTEXT_BUTTON_CLEAR, 20375); /* Clear search results */ + if (g_PVRManager.HaveMenuHooks(pItem->GetEPGInfoTag()->ClientID())) + buttons.Add(CONTEXT_BUTTON_MENU_HOOKS, 19195); /* PVR client specific action */ + } + } +} + +bool CGUIWindowTV::OnContextButton(int itemNumber, CONTEXT_BUTTON button) +{ + /* Check file item is in list range and get his pointer */ + if (itemNumber < 0 || itemNumber >= (int)m_vecItems->Size()) return false; + + CFileItemPtr pItem = m_vecItems->Get(itemNumber); + + if (button == CONTEXT_BUTTON_PLAY_ITEM) + { + if (m_iCurrSubTVWindow == TV_WINDOW_RECORDINGS) + { + return PlayFile(pItem.get(), false); + } + + if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV || + m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO) + { + if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV) + g_PVRManager.SetPlayingGroup(m_iCurrentTVGroup); + if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO) + g_PVRManager.SetPlayingGroup(m_iCurrentRadioGroup); + + return PlayFile(pItem.get(), g_guiSettings.GetBool("pvrplayback.playminimized")); + } + else if (m_iCurrSubTVWindow == TV_WINDOW_TV_PROGRAM) + { + cPVRChannels *channels; + if (!pItem->GetEPGInfoTag()->IsRadio()) + channels = &PVRChannelsTV; + else + channels = &PVRChannelsRadio; + + if (!g_application.PlayFile(CFileItem(channels->at(pItem->GetEPGInfoTag()->ChannelNumber()-1)))) + { + CGUIDialogOK::ShowAndGetInput(19033,0,19035,0); + return false; + } + return true; + } + } + else if (button == CONTEXT_BUTTON_MOVE) + { + CStdString strIndex; + strIndex.Format("%i", pItem->GetPVRChannelInfoTag()->Number()); + CGUIDialogNumeric::ShowAndGetNumber(strIndex, g_localizeStrings.Get(19052)); + int newIndex = atoi(strIndex.c_str()); + + if (newIndex != pItem->GetPVRChannelInfoTag()->Number()) + { + if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV) + { + PVRChannelsTV.MoveChannel(pItem->GetPVRChannelInfoTag()->Number(), newIndex); + UpdateChannelsTV(); + } + else if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO) + { + PVRChannelsRadio.MoveChannel(pItem->GetPVRChannelInfoTag()->Number(), newIndex); + UpdateChannelsRadio(); + } + } + } + else if (button == CONTEXT_BUTTON_HIDE) + { + // prompt user for confirmation of channel hide + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + + if (pDialog) + { + pDialog->SetHeading(19039); + pDialog->SetLine(0, ""); + pDialog->SetLine(1, pItem->GetPVRChannelInfoTag()->Name()); + pDialog->SetLine(2, ""); + pDialog->DoModal(); + + if (!pDialog->IsConfirmed()) return false; + + if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV) + { + PVRChannelsTV.HideChannel(pItem->GetPVRChannelInfoTag()->Number()); + UpdateChannelsTV(); + } + else if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO) + { + PVRChannelsRadio.HideChannel(pItem->GetPVRChannelInfoTag()->Number()); + UpdateChannelsRadio(); + } + } + } + else if (button == CONTEXT_BUTTON_SHOW_HIDDEN) + { + if (m_bShowHiddenChannels) + m_bShowHiddenChannels = false; + else + m_bShowHiddenChannels = true; + + if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV) + UpdateChannelsTV(); + else if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO) + UpdateChannelsRadio(); + } + else if (button == CONTEXT_BUTTON_SET_THUMB) + { + if (g_settings.GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked()) + return false; + else if (!g_passwordManager.IsMasterLockUnlocked(true)) + return false; + + // setup our thumb list + CFileItemList items; + + // add the current thumb, if available + if (!pItem->GetPVRChannelInfoTag()->Icon().IsEmpty()) + { + CFileItemPtr current(new CFileItem("thumb://Current", false)); + current->SetThumbnailImage(pItem->GetPVRChannelInfoTag()->Icon()); + current->SetLabel(g_localizeStrings.Get(20016)); + items.Add(current); + } + else if (pItem->HasThumbnail()) + { // already have a thumb that the share doesn't know about - must be a local one, so we mayaswell reuse it. + CFileItemPtr current(new CFileItem("thumb://Current", false)); + current->SetThumbnailImage(pItem->GetThumbnailImage()); + current->SetLabel(g_localizeStrings.Get(20016)); + items.Add(current); + } + + // and add a "no thumb" entry as well + CFileItemPtr nothumb(new CFileItem("thumb://None", false)); + nothumb->SetIconImage(pItem->GetIconImage()); + nothumb->SetLabel(g_localizeStrings.Get(20018)); + items.Add(nothumb); + + CStdString strThumb; + VECSOURCES shares; + if (g_guiSettings.GetString("pvrmenu.iconpath") != "") + { + CMediaSource share1; + share1.strPath = g_guiSettings.GetString("pvrmenu.iconpath"); + share1.strName = g_localizeStrings.Get(19018); + shares.push_back(share1); + } + g_mediaManager.GetLocalDrives(shares); + if (!CGUIDialogFileBrowser::ShowAndGetImage(items, shares, g_localizeStrings.Get(1030), strThumb)) + return false; + + if (strThumb == "thumb://Current") + return true; + + if (strThumb == "thumb://None") + strThumb = ""; + + if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV) + { + PVRChannelsTV.SetChannelIcon(pItem->GetPVRChannelInfoTag()->Number(), strThumb); + UpdateChannelsTV(); + } + else if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO) + { + PVRChannelsRadio.SetChannelIcon(pItem->GetPVRChannelInfoTag()->Number(), strThumb); + UpdateChannelsRadio(); + } + + return true; + } + else if (button == CONTEXT_BUTTON_EDIT) + { + if (m_iCurrSubTVWindow == TV_WINDOW_TIMERS) + { + CFileItem fileitem(*pItem); + + if (ShowTimerSettings(&fileitem)) + { + cPVRTimers::UpdateTimer(fileitem); + UpdateTimers(); + } + } + + return true; + } + else if (button == CONTEXT_BUTTON_ADD) + { + if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO || + m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV) + { + CGUIDialogOK::ShowAndGetInput(19033,0,19038,0); + } + else if (m_iCurrSubTVWindow == TV_WINDOW_TIMERS) + { + cPVRTimerInfoTag newtimer(true); + CFileItem *item = new CFileItem(newtimer); + + if (ShowTimerSettings(item)) + { + cPVRTimers::AddTimer(*item); + UpdateTimers(); + } + + return true; + } + } + else if (button == CONTEXT_BUTTON_ACTIVATE) + { + int return_str_id; + + if (pItem->GetPVRTimerInfoTag()->Active() == true) + { + pItem->GetPVRTimerInfoTag()->SetActive(false); + return_str_id = 13106; + } + else + { + pItem->GetPVRTimerInfoTag()->SetActive(true); + return_str_id = 305; + } + + CGUIDialogOK::ShowAndGetInput(19033, 19040, 0, return_str_id); + + cPVRTimers::UpdateTimer(*pItem); + UpdateTimers(); /** Force list update **/ + return true; + } + else if (button == CONTEXT_BUTTON_RENAME) + { + if (m_iCurrSubTVWindow == TV_WINDOW_TIMERS) + { + CStdString strNewName = pItem->GetPVRTimerInfoTag()->Title(); + if (CGUIDialogKeyboard::ShowAndGetInput(strNewName, g_localizeStrings.Get(19042), false)) + { + cPVRTimers::RenameTimer(*pItem, strNewName); + UpdateTimers(); + } + + return true; + } + else if (m_iCurrSubTVWindow == TV_WINDOW_RECORDINGS) + { + CStdString strNewName = pItem->GetPVRRecordingInfoTag()->Title(); + if (CGUIDialogKeyboard::ShowAndGetInput(strNewName, g_localizeStrings.Get(19041), false)) + { + if (cPVRRecordings::RenameRecording(*pItem, strNewName)) + { + UpdateRecordings(); + } + } + } + } + else if (button == CONTEXT_BUTTON_DELETE) + { + if (m_iCurrSubTVWindow == TV_WINDOW_TIMERS) + { + // prompt user for confirmation of timer deletion + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + + if (pDialog) + { + pDialog->SetHeading(122); + pDialog->SetLine(0, 19040); + pDialog->SetLine(1, ""); + pDialog->SetLine(2, pItem->GetPVRTimerInfoTag()->Title()); + pDialog->DoModal(); + + if (!pDialog->IsConfirmed()) return false; + + cPVRTimers::DeleteTimer(*pItem); + + UpdateTimers(); + } + + return true; + } + else if (m_iCurrSubTVWindow == TV_WINDOW_RECORDINGS) + { + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + + if (pDialog) + { + pDialog->SetHeading(122); + pDialog->SetLine(0, 19043); + pDialog->SetLine(1, ""); + pDialog->SetLine(2, pItem->GetPVRRecordingInfoTag()->Title()); + pDialog->DoModal(); + + if (!pDialog->IsConfirmed()) return false; + + if (cPVRRecordings::DeleteRecording(*pItem)) + { + PVRRecordings.Update(true); + UpdateRecordings(); + } + } + + return true; + } + } + else if (button == CONTEXT_BUTTON_INFO) + { + if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV || + m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO || + m_iCurrSubTVWindow == TV_WINDOW_TV_PROGRAM || + m_iCurrSubTVWindow == TV_WINDOW_SEARCH) + { + ShowEPGInfo(pItem.get()); + } + else if (m_iCurrSubTVWindow == TV_WINDOW_RECORDINGS) + { + ShowRecordingInfo(pItem.get()); + } + } + else if (button == CONTEXT_BUTTON_START_RECORD) + { + if (m_iCurrSubTVWindow == TV_WINDOW_TV_PROGRAM || m_iCurrSubTVWindow == TV_WINDOW_SEARCH) + { + int iChannel = pItem->GetEPGInfoTag()->ChannelNumber(); + + if (iChannel != -1) + { + if (pItem->GetEPGInfoTag()->Timer() == NULL) + { + // prompt user for confirmation of channel record + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + + if (pDialog) + { + pDialog->SetHeading(264); + pDialog->SetLine(0, pItem->GetEPGInfoTag()->ChannelName()); + pDialog->SetLine(1, ""); + pDialog->SetLine(2, pItem->GetEPGInfoTag()->Title()); + pDialog->DoModal(); + + if (pDialog->IsConfirmed()) + { + cPVRTimerInfoTag newtimer(*pItem.get()); + CFileItem *item = new CFileItem(newtimer); + + if (cPVRTimers::AddTimer(*item)) + PVREpgs.SetVariableData(m_vecItems); + } + } + } + else + { + CGUIDialogOK::ShowAndGetInput(19033,19034,0,0); + } + } + } + } + else if (button == CONTEXT_BUTTON_STOP_RECORD) + { + if (m_iCurrSubTVWindow == TV_WINDOW_TV_PROGRAM || m_iCurrSubTVWindow == TV_WINDOW_SEARCH) + { + int iChannel = pItem->GetEPGInfoTag()->ChannelNumber(); + + if (iChannel != -1) + { + if (pItem->GetEPGInfoTag()->Timer() != NULL) + { + CFileItemList timerlist; + + if (PVRTimers.GetTimers(&timerlist) > 0) + { + for (int i = 0; i < timerlist.Size(); ++i) + { + if ((timerlist[i]->GetPVRTimerInfoTag()->Number() == pItem->GetEPGInfoTag()->ChannelNumber()) && + (timerlist[i]->GetPVRTimerInfoTag()->Start() <= pItem->GetEPGInfoTag()->Start()) && + (timerlist[i]->GetPVRTimerInfoTag()->Stop() >= pItem->GetEPGInfoTag()->End()) && + (timerlist[i]->GetPVRTimerInfoTag()->IsRepeating() != true)) + { + if (cPVRTimers::DeleteTimer(*timerlist[i])) + PVREpgs.SetVariableData(m_vecItems); + } + } + } + } + } + } + } + else if (button == CONTEXT_BUTTON_GROUP_MANAGER) + { + if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV) + ShowGroupManager(false); + else if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO) + ShowGroupManager(true); + } + else if (button == CONTEXT_BUTTON_RESUME_ITEM) + { + + } + else if (button == CONTEXT_BUTTON_CLEAR) + { + if (m_iCurrSubTVWindow == TV_WINDOW_SEARCH) + { + m_bSearchStarted = false; + m_bSearchConfirmed = false; + m_searchfilter.SetDefaults(); + UpdateSearch(); + } + } + else if (button == CONTEXT_BUTTON_SORTASC) // sort asc + { + if (m_guiState.get()) + m_guiState->SetNextSortOrder(); + UpdateFileList(); + return true; + } + else if (button == CONTEXT_BUTTON_SORTBY) // sort by + { + if (m_guiState.get()) + m_guiState->SetNextSortMethod(); + UpdateFileList(); + return true; + } + else if (button == CONTEXT_BUTTON_SORTBY_CHANNEL) + { + if (m_iCurrSubTVWindow == TV_WINDOW_SEARCH) + { + if (m_iSortMethod_SEARCH != SORT_METHOD_CHANNEL) + { + m_iSortMethod_SEARCH = SORT_METHOD_CHANNEL; + m_iSortOrder_SEARCH = SORT_ORDER_ASC; + } + else + { + m_iSortOrder_SEARCH = m_iSortOrder_SEARCH == SORT_ORDER_ASC ? SORT_ORDER_DESC : SORT_ORDER_ASC; + } + UpdateSearch(); + } + } + else if (button == CONTEXT_BUTTON_SORTBY_NAME) + { + if (m_iCurrSubTVWindow == TV_WINDOW_TIMERS) + { + if (m_iSortMethod_TIMERS != SORT_METHOD_LABEL) + { + m_iSortMethod_TIMERS = SORT_METHOD_LABEL; + m_iSortOrder_TIMERS = SORT_ORDER_ASC; + } + else + { + m_iSortOrder_TIMERS = m_iSortOrder_TIMERS == SORT_ORDER_ASC ? SORT_ORDER_DESC : SORT_ORDER_ASC; + } + UpdateTimers(); + } + else if (m_iCurrSubTVWindow == TV_WINDOW_SEARCH) + { + if (m_iSortMethod_SEARCH != SORT_METHOD_LABEL) + { + m_iSortMethod_SEARCH = SORT_METHOD_LABEL; + m_iSortOrder_SEARCH = SORT_ORDER_ASC; + } + else + { + m_iSortOrder_SEARCH = m_iSortOrder_SEARCH == SORT_ORDER_ASC ? SORT_ORDER_DESC : SORT_ORDER_ASC; + } + UpdateSearch(); + } + } + else if (button == CONTEXT_BUTTON_SORTBY_DATE) + { + if (m_iCurrSubTVWindow == TV_WINDOW_TIMERS) + { + if (m_iSortMethod_TIMERS != SORT_METHOD_DATE) + { + m_iSortMethod_TIMERS = SORT_METHOD_DATE; + m_iSortOrder_TIMERS = SORT_ORDER_ASC; + } + else + { + m_iSortOrder_TIMERS = m_iSortOrder_TIMERS == SORT_ORDER_ASC ? SORT_ORDER_DESC : SORT_ORDER_ASC; + } + UpdateTimers(); + } + else if (m_iCurrSubTVWindow == TV_WINDOW_SEARCH) + { + if (m_iSortMethod_SEARCH != SORT_METHOD_DATE) + { + m_iSortMethod_SEARCH = SORT_METHOD_DATE; + m_iSortOrder_SEARCH = SORT_ORDER_ASC; + } + else + { + m_iSortOrder_SEARCH = m_iSortOrder_SEARCH == SORT_ORDER_ASC ? SORT_ORDER_DESC : SORT_ORDER_ASC; + } + UpdateSearch(); + } + } + else if (button == CONTEXT_BUTTON_BEGIN) + { + m_guideGrid->GoToBegin(); + } + else if (button == CONTEXT_BUTTON_END) + { + m_guideGrid->GoToEnd(); + } + else if (button == CONTEXT_BUTTON_FIND) + { + m_searchfilter.SetDefaults(); + if (pItem->IsEPG()) + { + m_searchfilter.m_SearchString = "\"" + pItem->GetEPGInfoTag()->Title() + "\""; + } + else if (pItem->IsPVRChannel()) + { + m_searchfilter.m_SearchString = "\"" + pItem->GetPVRChannelInfoTag()->NowTitle() + "\""; + } + else if (pItem->IsPVRRecording()) + { + m_searchfilter.m_SearchString = "\"" + pItem->GetPVRRecordingInfoTag()->Title() + "\""; + } + m_bSearchConfirmed = true; + SET_CONTROL_FOCUS(CONTROL_BTNSEARCH, 0); + UpdateSearch(); + SET_CONTROL_FOCUS(CONTROL_LIST_SEARCH, 0); + } + else if (button == CONTEXT_BUTTON_MENU_HOOKS) + { + if (pItem->IsEPG()) + { + g_PVRManager.ProcessMenuHooks(pItem->GetEPGInfoTag()->ClientID()); + } + else if (pItem->IsPVRChannel()) + { + g_PVRManager.ProcessMenuHooks(pItem->GetPVRChannelInfoTag()->ClientID()); + } + else if (pItem->IsPVRRecording()) + { + g_PVRManager.ProcessMenuHooks(pItem->GetPVRRecordingInfoTag()->ClientID()); + } + else if (pItem->IsPVRTimer()) + { + g_PVRManager.ProcessMenuHooks(pItem->GetPVRTimerInfoTag()->ClientID()); + } + } + + return CGUIMediaWindow::OnContextButton(itemNumber, button); +} + +void CGUIWindowTV::ShowEPGInfo(CFileItem *item) +{ + /* Check item is TV epg or channel information tag */ + if (item->IsEPG()) + { + /* Load programme info dialog */ + CGUIDialogPVRGuideInfo* pDlgInfo = (CGUIDialogPVRGuideInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_GUIDE_INFO); + if (!pDlgInfo) + return; + + /* inform dialog about the file item */ + pDlgInfo->SetProgInfo(item); + PVREpgs.SetVariableData(m_vecItems); + + /* Open dialog window */ + pDlgInfo->DoModal(); + } + else if (item->IsPVRChannel()) + { + const cPVREPGInfoTag *epgnow = PVREpgs.GetEPG(item->GetPVRChannelInfoTag(), true)->GetInfoTagNow(); + if (!epgnow) + { + CGUIDialogOK::ShowAndGetInput(19033,0,19055,0); + return; + } + + CFileItem *itemNow = new CFileItem(*epgnow); + + /* Load programme info dialog */ + CGUIDialogPVRGuideInfo* pDlgInfo = (CGUIDialogPVRGuideInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_GUIDE_INFO); + if (!pDlgInfo) + return; + + /* inform dialog about the file item */ + pDlgInfo->SetProgInfo(itemNow); + + /* Open dialog window */ + pDlgInfo->DoModal(); + } + else + CLog::Log(LOGERROR, "CGUIWindowTV: Can't open programme info dialog, no epg or channel info tag!"); +} + +void CGUIWindowTV::ShowRecordingInfo(CFileItem *item) +{ + /* Check item is TV record information tag */ + if (!item->IsPVRRecording()) + { + CLog::Log(LOGERROR, "CGUIWindowTV: Can't open recording info dialog, no record info tag!"); + return; + } + + /* Load record info dialog */ + CGUIDialogPVRRecordingInfo* pDlgInfo = (CGUIDialogPVRRecordingInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_RECORDING_INFO); + + if (!pDlgInfo) + return; + + /* inform dialog about the file item */ + pDlgInfo->SetRecording(item); + + /* Open dialog window */ + pDlgInfo->DoModal(); + + /* Return to caller */ + return; +} + +bool CGUIWindowTV::ShowTimerSettings(CFileItem *item) +{ + /* Check item is TV timer information tag */ + if (!item->IsPVRTimer()) + { + CLog::Log(LOGERROR, "CGUIWindowTV: Can't open timer settings dialog, no timer info tag!"); + return false; + } + + /* Load timer settings dialog */ + CGUIDialogPVRTimerSettings* pDlgInfo = (CGUIDialogPVRTimerSettings*)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_TIMER_SETTING); + + if (!pDlgInfo) + return false; + + /* inform dialog about the file item */ + pDlgInfo->SetTimer(item); + + /* Open dialog window */ + pDlgInfo->DoModal(); + + /* Get modify flag from window and return it to caller */ + return pDlgInfo->GetOK(); +} + +void CGUIWindowTV::ShowGroupManager(bool IsRadio) +{ + /* Load group manager dialog */ + CGUIDialogPVRGroupManager* pDlgInfo = (CGUIDialogPVRGroupManager*)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_GROUP_MANAGER); + + if (!pDlgInfo) + return; + + pDlgInfo->SetRadio(IsRadio); + + /* Open dialog window */ + pDlgInfo->DoModal(); + + return; +} + +void CGUIWindowTV::ShowSearchResults() +{ + /* Load timer settings dialog */ + CGUIDialogPVRGuideSearch* pDlgInfo = (CGUIDialogPVRGuideSearch*)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_GUIDE_SEARCH); + + if (!pDlgInfo) + return; + + pDlgInfo->SetFilterData(&m_searchfilter); + + /* Open dialog window */ + pDlgInfo->DoModal(); + + if (pDlgInfo->IsConfirmed()) + { + m_bSearchConfirmed = true; + UpdateSearch(); + } + + return; +} + +void CGUIWindowTV::UpdateGuide() +{ + bool RadioPlaying; + int CurrentChannel; + g_PVRManager.GetCurrentChannel(&CurrentChannel, &RadioPlaying); + + m_vecItems->Clear(); + + if (m_iGuideView == GUIDE_VIEW_CHANNEL) + { + m_guideGrid = NULL; + m_viewControl.SetCurrentView(CONTROL_LIST_GUIDE_CHANNEL); + + CStdString strChannel; + if (!RadioPlaying) + strChannel = PVRChannelsTV.GetNameForChannel(CurrentChannel); + else + strChannel = PVRChannelsRadio.GetNameForChannel(CurrentChannel); + + SET_CONTROL_LABEL(CONTROL_BTNGUIDE, g_localizeStrings.Get(19029)); + SET_CONTROL_LABEL(CONTROL_LABELGROUP, strChannel); + + if (PVREpgs.GetEPGChannel(CurrentChannel, m_vecItems, RadioPlaying) == 0) + { + CFileItemPtr item; + item.reset(new CFileItem("pvr://guide/" + strChannel + "/empty.epg", false)); + item->SetLabel(g_localizeStrings.Get(19028)); + item->SetLabelPreformated(true); + m_vecItems->Add(item); + } + m_viewControl.SetItems(*m_vecItems); + } + else if (m_iGuideView == GUIDE_VIEW_NOW) + { + m_guideGrid = NULL; + + m_viewControl.SetCurrentView(CONTROL_LIST_GUIDE_NOW_NEXT); + + SET_CONTROL_LABEL(CONTROL_BTNGUIDE, g_localizeStrings.Get(19029) + ": " + g_localizeStrings.Get(19030)); + SET_CONTROL_LABEL(CONTROL_LABELGROUP, g_localizeStrings.Get(19030)); + + if (PVREpgs.GetEPGNow(m_vecItems, RadioPlaying) == 0) + { + CFileItemPtr item; + item.reset(new CFileItem("pvr://guide/now/empty.epg", false)); + item->SetLabel(g_localizeStrings.Get(19028)); + item->SetLabelPreformated(true); + m_vecItems->Add(item); + } + m_viewControl.SetItems(*m_vecItems); + } + else if (m_iGuideView == GUIDE_VIEW_NEXT) + { + m_guideGrid = NULL; + + m_viewControl.SetCurrentView(CONTROL_LIST_GUIDE_NOW_NEXT); + + SET_CONTROL_LABEL(CONTROL_BTNGUIDE, g_localizeStrings.Get(19029) + ": " + g_localizeStrings.Get(19031)); + SET_CONTROL_LABEL(CONTROL_LABELGROUP, g_localizeStrings.Get(19031)); + + if (PVREpgs.GetEPGNext(m_vecItems, RadioPlaying) == 0) + { + CFileItemPtr item; + item.reset(new CFileItem("pvr://guide/next/empty.epg", false)); + item->SetLabel(g_localizeStrings.Get(19028)); + item->SetLabelPreformated(true); + m_vecItems->Add(item); + } + m_viewControl.SetItems(*m_vecItems); + } + else if (m_iGuideView == GUIDE_VIEW_TIMELINE) + { + SET_CONTROL_LABEL(CONTROL_BTNGUIDE, g_localizeStrings.Get(19029) + ": " + g_localizeStrings.Get(19032)); + SET_CONTROL_LABEL(CONTROL_LABELGROUP, g_localizeStrings.Get(19032)); + + if (PVREpgs.GetEPGAll(m_vecItems, RadioPlaying) > 0) + { + CDateTime now = CDateTime::GetCurrentDateTime(); + CDateTime m_gridStart = now - CDateTimeSpan(0, 0, 0, (now.GetMinute() % 30) * 60 + now.GetSecond()) - CDateTimeSpan(0, g_guiSettings.GetInt("pvrmenu.lingertime") / 60, g_guiSettings.GetInt("pvrmenu.lingertime") % 60, 0); + CDateTime m_gridEnd = m_gridStart + CDateTimeSpan(g_guiSettings.GetInt("pvrmenu.daystodisplay"), 0, 0, 0); + m_guideGrid = (CGUIEPGGridContainer*)GetControl(CONTROL_LIST_TIMELINE); + m_guideGrid->SetStartEnd(m_gridStart, m_gridEnd); + m_viewControl.SetCurrentView(CONTROL_LIST_TIMELINE); + +// m_viewControl.SetSelectedItem(m_iSelected_GUIDE); + } + } + + SET_CONTROL_LABEL(CONTROL_LABELHEADER, g_localizeStrings.Get(19029)); +} + +void CGUIWindowTV::UpdateChannelsTV() +{ + m_vecItems->Clear(); + m_viewControl.SetCurrentView(CONTROL_LIST_CHANNELS_TV); + if (!m_bShowHiddenChannels) + m_vecItems->m_strPath = "pvr://channels/tv/" + PVRChannelGroupsTV.GetGroupName(m_iCurrentTVGroup) + "/"; + else + m_vecItems->m_strPath = "pvr://channels/tv/.hidden/"; + Update(m_vecItems->m_strPath); + + if (m_vecItems->Size() == 0) + { + if (m_bShowHiddenChannels) + { + m_bShowHiddenChannels = false; + UpdateChannelsTV(); + return; + } + else if (m_iCurrentTVGroup != -1) + { + m_iCurrentTVGroup = PVRChannelGroupsTV.GetNextGroupID(m_iCurrentTVGroup); + UpdateChannelsTV(); + return; + } + } + + m_viewControl.SetSelectedItem(m_iSelected_CHANNELS_TV); + + SET_CONTROL_LABEL(CONTROL_LABELHEADER, g_localizeStrings.Get(19023)); + if (m_bShowHiddenChannels) + SET_CONTROL_LABEL(CONTROL_LABELGROUP, g_localizeStrings.Get(19022)); + else + SET_CONTROL_LABEL(CONTROL_LABELGROUP, PVRChannelGroupsTV.GetGroupName(m_iCurrentTVGroup)); +} + +void CGUIWindowTV::UpdateChannelsRadio() +{ + m_vecItems->Clear(); + m_viewControl.SetCurrentView(CONTROL_LIST_CHANNELS_RADIO); + if (!m_bShowHiddenChannels) + m_vecItems->m_strPath = "pvr://channels/radio/" + PVRChannelGroupsRadio.GetGroupName(m_iCurrentRadioGroup) + "/"; + else + m_vecItems->m_strPath = "pvr://channels/radio/.hidden/"; + Update(m_vecItems->m_strPath); + + if (m_vecItems->Size() == 0) + { + if (m_bShowHiddenChannels) + { + m_bShowHiddenChannels = false; + UpdateChannelsRadio(); + return; + } + else if (m_iCurrentRadioGroup != -1) + { + m_iCurrentRadioGroup = PVRChannelGroupsRadio.GetNextGroupID(m_iCurrentRadioGroup); + UpdateChannelsRadio(); + return; + } + } + + m_viewControl.SetSelectedItem(m_iSelected_CHANNELS_RADIO); + + SET_CONTROL_LABEL(CONTROL_LABELHEADER, g_localizeStrings.Get(19024)); + if (m_bShowHiddenChannels) + SET_CONTROL_LABEL(CONTROL_LABELGROUP, g_localizeStrings.Get(19022)); + else + SET_CONTROL_LABEL(CONTROL_LABELGROUP, PVRChannelGroupsRadio.GetGroupName(m_iCurrentRadioGroup)); +} + +void CGUIWindowTV::UpdateRecordings() +{ + m_vecItems->Clear(); + m_viewControl.SetCurrentView(CONTROL_LIST_RECORDINGS); + m_vecItems->m_strPath = "pvr://recordings/"; + Update(m_iSelected_RECORDINGS_Path); + m_viewControl.SetSelectedItem(m_iSelected_RECORDINGS); + + SET_CONTROL_LABEL(CONTROL_LABELHEADER, g_localizeStrings.Get(19017)); + SET_CONTROL_LABEL(CONTROL_LABELGROUP, ""); +} + +void CGUIWindowTV::UpdateTimers() +{ + m_vecItems->Clear(); + m_viewControl.SetCurrentView(CONTROL_LIST_TIMERS); + m_vecItems->m_strPath = "pvr://timers/"; + Update(m_vecItems->m_strPath); + m_vecItems->Sort(m_iSortMethod_TIMERS, m_iSortOrder_TIMERS); + m_viewControl.SetItems(*m_vecItems); + m_viewControl.SetSelectedItem(m_iSelected_TIMERS); + + SET_CONTROL_LABEL(CONTROL_LABELHEADER, g_localizeStrings.Get(19025)); + SET_CONTROL_LABEL(CONTROL_LABELGROUP, ""); +} + +void CGUIWindowTV::UpdateSearch() +{ + m_vecItems->Clear(); + m_viewControl.SetCurrentView(CONTROL_LIST_SEARCH); + + if (m_bSearchConfirmed) + { + CGUIDialogProgress* dlgProgress = (CGUIDialogProgress*)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS); + if (dlgProgress) + { + dlgProgress->SetHeading(194); + dlgProgress->SetLine(0, m_searchfilter.m_SearchString); + dlgProgress->SetLine(1, ""); + dlgProgress->SetLine(2, ""); + dlgProgress->StartModal(); + dlgProgress->Progress(); + } + + PVREpgs.GetEPGSearch(m_vecItems, m_searchfilter); + if (dlgProgress) + dlgProgress->Close(); + + if (m_vecItems->Size() == 0) + { + CGUIDialogOK::ShowAndGetInput(194, 284, 0, 0); + m_bSearchConfirmed = false; + } + } + + if (m_vecItems->Size() == 0) + { + CFileItemPtr item; + item.reset(new CFileItem("pvr://guide/searchresults/empty.epg", false)); + item->SetLabel(g_localizeStrings.Get(19027)); + item->SetLabelPreformated(true); + m_vecItems->Add(item); + } + else + { + m_vecItems->Sort(m_iSortMethod_SEARCH, m_iSortOrder_SEARCH); + } + + m_viewControl.SetItems(*m_vecItems); + m_viewControl.SetSelectedItem(m_iSelected_SEARCH); + + SET_CONTROL_LABEL(CONTROL_LABELHEADER, g_localizeStrings.Get(283)); + SET_CONTROL_LABEL(CONTROL_LABELGROUP, ""); +} + +void CGUIWindowTV::UpdateButtons() +{ + if (m_iGuideView == GUIDE_VIEW_CHANNEL) + SET_CONTROL_LABEL(CONTROL_BTNGUIDE, g_localizeStrings.Get(19029)); + else if (m_iGuideView == GUIDE_VIEW_NOW) + SET_CONTROL_LABEL(CONTROL_BTNGUIDE, g_localizeStrings.Get(19029) + ": " + g_localizeStrings.Get(19030)); + else if (m_iGuideView == GUIDE_VIEW_NEXT) + SET_CONTROL_LABEL(CONTROL_BTNGUIDE, g_localizeStrings.Get(19029) + ": " + g_localizeStrings.Get(19031)); + else if (m_iGuideView == GUIDE_VIEW_TIMELINE) + SET_CONTROL_LABEL(CONTROL_BTNGUIDE, g_localizeStrings.Get(19029) + ": " + g_localizeStrings.Get(19032)); +} + +void CGUIWindowTV::UpdateData(TVWindow update) +{ + if (m_iCurrSubTVWindow == TV_WINDOW_TV_PROGRAM && update == TV_WINDOW_TV_PROGRAM) + { + + } + else if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_TV && update == TV_WINDOW_CHANNELS_TV) + UpdateChannelsTV(); + else if (m_iCurrSubTVWindow == TV_WINDOW_CHANNELS_RADIO && update == TV_WINDOW_CHANNELS_RADIO) + UpdateChannelsRadio(); + else if (m_iCurrSubTVWindow == TV_WINDOW_RECORDINGS && update == TV_WINDOW_RECORDINGS) + UpdateRecordings(); + else if (m_iCurrSubTVWindow == TV_WINDOW_TIMERS && update == TV_WINDOW_TIMERS) + UpdateTimers(); + + UpdateButtons(); +} + +bool CGUIWindowTV::PlayFile(CFileItem *item, bool playMinimized) +{ + if (playMinimized) + { + if (item->m_strPath == g_application.CurrentFile()) + { + CGUIMessage msg(GUI_MSG_FULLSCREEN, 0, GetID()); + g_windowManager.SendMessage(msg); + return true; + } + else + { + g_settings.m_bStartVideoWindowed = true; + } + } + + if (item->m_strPath.Left(17) == "pvr://recordings/") + { + /* For recordings we check here for a available stream URL */ + CStdString stream = item->GetPVRRecordingInfoTag()->StreamURL(); + if (stream != "") + { + /* Isolate the folder from the filename */ + size_t found = stream.find_last_of("/"); + if (found == CStdString::npos) + found = stream.find_last_of("\\"); + + if (found != CStdString::npos) + { + /* Check here for asterix at the begin of the filename */ + if (stream[found+1] == '*') + { + /* Create a "stack://" url with all files matching the extension */ + CStdString ext = CUtil::GetExtension(stream); + CStdString dir = stream.substr(0, found).c_str(); + + CFileItemList items; + CDirectory::GetDirectory(dir, items); + items.Sort(SORT_METHOD_FILE ,SORT_ORDER_ASC); + + vector stack; + for (int i = 0; i < items.Size(); ++i) + { + if (CUtil::GetExtension(items[i]->m_strPath) == ext) + stack.push_back(i); + } + + if (stack.size() > 0) + { + /* If we have a stack change the path of the item to it */ + CStackDirectory dir; + CStdString stackPath = dir.ConstructStackPath(items, stack); + item->m_strPath = stackPath; + } + } + else + { + /* If no asterix is present play only the given stream URL */ + item->m_strPath = stream; + } + } + else + { + CLog::Log(LOGERROR, "CGUIWindowTV: Can't open recording, no valid filename!"); + CGUIDialogOK::ShowAndGetInput(19033,0,19036,0); + return false; + } + } + + if (!g_application.PlayFile(*item, false)) + { + CGUIDialogOK::ShowAndGetInput(19033,0,19036,0); + return false; + } + } + else + { + /* Play Live TV */ + if (!g_application.PlayFile(*item, false)) + { + CGUIDialogOK::ShowAndGetInput(19033,0,19035,0); + return false; + } + } + + return true; +} diff --git a/xbmc/GUIWindowTV.h b/xbmc/GUIWindowTV.h new file mode 100644 index 0000000000..6aa36c12e3 --- /dev/null +++ b/xbmc/GUIWindowTV.h @@ -0,0 +1,100 @@ +#pragma once + +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "GUIMediaWindow.h" +#include "utils/PVREpg.h" + +class CGUIEPGGridContainer; + +enum TVWindow +{ + TV_WINDOW_UNKNOWN = 0, + TV_WINDOW_TV_PROGRAM = 1, + TV_WINDOW_CHANNELS_TV = 2, + TV_WINDOW_CHANNELS_RADIO = 3, + TV_WINDOW_RECORDINGS = 4, + TV_WINDOW_TIMERS = 5, + TV_WINDOW_SEARCH = 6 +}; + +class CGUIWindowTV : public CGUIMediaWindow +{ +public: + CGUIWindowTV(void); + virtual ~CGUIWindowTV(void); + virtual bool OnMessage(CGUIMessage& message); + virtual bool OnAction(const CAction &action); + virtual void OnWindowLoaded(); + virtual void OnWindowUnload(); + virtual void OnInitWindow(); + + void UpdateData(TVWindow update); + +protected: + virtual void GetContextButtons(int itemNumber, CContextButtons &buttons); + virtual bool OnContextButton(int itemNumber, CONTEXT_BUTTON button); + virtual void UpdateButtons(); + +private: + TVWindow m_iCurrSubTVWindow; /* Active subwindow */ + TVWindow m_iSavedSubTVWindow; /* Last subwindow, required if main window is shown again */ + bool m_bShowHiddenChannels; + bool m_bSearchStarted; + bool m_bSearchConfirmed; + EPGSearchFilter m_searchfilter; + + /* Selected item in associated list, required for subwindow change */ + int m_iSelected_GUIDE; + int m_iSelected_CHANNELS_TV; + int m_iSelected_CHANNELS_RADIO; + int m_iSelected_RECORDINGS; + CStdString m_iSelected_RECORDINGS_Path; + int m_iSelected_TIMERS; + int m_iSelected_SEARCH; + + SORT_ORDER m_iSortOrder_TIMERS; + SORT_METHOD m_iSortMethod_TIMERS; + SORT_ORDER m_iSortOrder_SEARCH; + SORT_METHOD m_iSortMethod_SEARCH; + + int m_iGuideView; + int m_iCurrentTVGroup; + int m_iCurrentRadioGroup; + + void ShowEPGInfo(CFileItem *item); + void ShowRecordingInfo(CFileItem *item); + bool ShowTimerSettings(CFileItem *item); + void ShowGroupManager(bool IsRadio); + void ShowSearchResults(); + + void UpdateGuide(); + void UpdateChannelsTV(); + void UpdateChannelsRadio(); + void UpdateRecordings(); + void UpdateTimers(); + void UpdateSearch(); + + bool PlayFile(CFileItem *item, bool playMinimized = false); + + CGUIEPGGridContainer *m_guideGrid; +}; diff --git a/xbmc/GUIWindowVideoBase.cpp b/xbmc/GUIWindowVideoBase.cpp index ff0f9dd25b..ffc535de29 100644 --- a/xbmc/GUIWindowVideoBase.cpp +++ b/xbmc/GUIWindowVideoBase.cpp @@ -62,6 +62,7 @@ #include "StringUtils.h" #include "utils/log.h" #include "utils/FileUtils.h" +#include "utils/PVRRecordings.h" #include "addons/Skin.h" #include "MediaManager.h" @@ -1332,6 +1333,62 @@ bool CGUIWindowVideoBase::OnPlayMedia(int iItem) item.SetProperty("original_listitem_url", pItem->m_strPath); } + if (item.m_strPath.Left(17) == "pvr://recordings/") + { + /* For recordings we check here for a available stream URL */ + cPVRRecordingInfoTag *tag = PVRRecordings.GetByPath(item.m_strPath); + if (tag && !tag->StreamURL().IsEmpty()) + { + CStdString stream = tag->StreamURL(); + + /* Isolate the folder from the filename */ + size_t found = stream.find_last_of("/"); + if (found == CStdString::npos) + found = stream.find_last_of("\\"); + + if (found != CStdString::npos) + { + /* Check here for asterix at the begin of the filename */ + if (stream[found+1] == '*') + { + /* Create a "stack://" url with all files matching the extension */ + CStdString ext = CUtil::GetExtension(stream); + CStdString dir = stream.substr(0, found).c_str(); + + CFileItemList items; + CDirectory::GetDirectory(dir, items); + items.Sort(SORT_METHOD_FILE ,SORT_ORDER_ASC); + + vector stack; + for (int i = 0; i < items.Size(); ++i) + { + if (CUtil::GetExtension(items[i]->m_strPath) == ext) + stack.push_back(i); + } + + if (stack.size() > 0) + { + /* If we have a stack change the path of the item to it */ + CStackDirectory dir; + CStdString stackPath = dir.ConstructStackPath(items, stack); + item.m_strPath = stackPath; + } + } + else + { + /* If no asterix is present play only the given stream URL */ + item.m_strPath = stream; + } + } + else + { + CLog::Log(LOGERROR, "CGUIWindowTV: Can't open recording, no valid filename!"); + CGUIDialogOK::ShowAndGetInput(19033,0,19036,0); + return false; + } + } + } + PlayMovie(&item); return true; diff --git a/xbmc/Makefile.in b/xbmc/Makefile.in index 70f7f3f289..53f32e105b 100644 --- a/xbmc/Makefile.in +++ b/xbmc/Makefile.in @@ -142,6 +142,17 @@ SRCS=Application.cpp \ GUIDialogSelect.cpp \ GUIDialogSettings.cpp \ GUIDialogSubMenu.cpp \ + GUIDialogPVRChannelManager.cpp \ + GUIDialogPVRChannelsOSD.cpp \ + GUIDialogPVRCutterOSD.cpp \ + GUIDialogPVRDirectorOSD.cpp \ + GUIDialogPVRGroupManager.cpp \ + GUIDialogPVRGuideInfo.cpp \ + GUIDialogPVRGuideOSD.cpp \ + GUIDialogPVRGuideSearch.cpp \ + GUIDialogPVRRecordingInfo.cpp \ + GUIDialogPVRTimerSettings.cpp \ + GUIDialogPVRUpdateProgressBar.cpp \ GUIDialogTeletext.cpp \ GUIDialogTextViewer.cpp \ GUIDialogVideoBookmarks.cpp \ @@ -161,6 +172,8 @@ SRCS=Application.cpp \ GUIWindowPrograms.cpp \ GUIWindowScreensaver.cpp \ GUIWindowSystemInfo.cpp \ + GUIViewStateTV.cpp \ + GUIWindowTV.cpp \ GUIWindowVisualisation.cpp \ GUIWindowWeather.cpp \ BackgroundInfoLoader.cpp \ @@ -202,6 +215,8 @@ SRCS=Application.cpp \ ZeroconfBrowser.cpp \ VideoReferenceClock.cpp \ DPMSSupport.cpp \ + PVRManager.cpp \ + TVDatabase.cpp \ GUIWindowTestPatternGL.cpp \ RenderSystem.cpp \ RenderSystemGL.cpp \ diff --git a/xbmc/PVRManager.cpp b/xbmc/PVRManager.cpp new file mode 100644 index 0000000000..cd62dbf56c --- /dev/null +++ b/xbmc/PVRManager.cpp @@ -0,0 +1,2034 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "Application.h" +#include "GUISettings.h" +#include "Util.h" +#include "GUIWindowTV.h" +#include "GUIWindowManager.h" +#include "utils/GUIInfoManager.h" +#include "PVRManager.h" +#ifdef HAS_VIDEO_PLAYBACK +#include "cores/VideoRenderers/RenderManager.h" +#endif +#include "utils/log.h" +#include "LocalizeStrings.h" +#include "FileSystem/File.h" +#include "StringUtils.h" +#include "utils/TimeUtils.h" +#include "MusicInfoTag.h" +#include "Settings.h" + +/* GUI Messages includes */ +#include "GUIDialogOK.h" +#include "GUIDialogProgress.h" +#include "GUIDialogSelect.h" + +#define CHANNELCHECKDELTA 600 // seconds before checking for changes inside channels list +#define TIMERCHECKDELTA 300 // seconds before checking for changes inside timers list +#define RECORDINGCHECKDELTA 450 // seconds before checking for changes inside recordings list +#define EPGCLEANUPCHECKDELTA 900 // seconds before checking for changes inside recordings list + +using namespace std; +using namespace XFILE; +using namespace MUSIC_INFO; +using namespace ADDON; + +/********************************************************************** + * BEGIN OF CLASS **___ CPVRManager __________________________________* + ** **/ + +/******************************************************************** + * CPVRManager constructor + * + * It creates the PVRManager, which mostly handle all PVR related + * operations for XBMC + ********************************************************************/ +CPVRManager::CPVRManager() +{ + InitializeCriticalSection(&m_critSection); + m_bFirstStart = true; + CLog::Log(LOGDEBUG,"PVR: created"); +} + +/******************************************************************** + * CPVRManager destructor + * + * Destroy this class + ********************************************************************/ +CPVRManager::~CPVRManager() +{ + DeleteCriticalSection(&m_critSection); + CLog::Log(LOGDEBUG,"PVR: destroyed"); +} + +/******************************************************************** + * CPVRManager Start + * + * PVRManager Startup + ********************************************************************/ +void CPVRManager::Start() +{ + /* First stop and remove any clients */ + if (!m_clients.empty()) + Stop(); + + /* Check if TV is enabled under Settings->Video->TV->Enable */ + if (!g_guiSettings.GetBool("pvrmanager.enabled")) + return; + + CLog::Log(LOGNOTICE, "PVR: PVRManager starting"); + + /* Reset Member variables and System Info swap counters */ + m_hasRecordings = false; + m_isRecording = false; + m_hasTimers = false; + m_CurrentGroupID = -1; + m_currentPlayingChannel = NULL; + m_currentPlayingRecording = NULL; + m_PreviousChannel[0] = -1; + m_PreviousChannel[1] = -1; + m_PreviousChannelIndex = 0; + m_infoToggleStart = NULL; + m_infoToggleCurrent = 0; + m_recordingToggleStart = NULL; + m_recordingToggleCurrent = 0; + m_LastChannel = 0; + m_bChannelScanRunning = false; + + /* Discover, load and create chosen Client add-on's. */ + CAddonMgr::Get().RegisterAddonMgrCallback(ADDON_PVRDLL, this); + if (!LoadClients()) + { + CLog::Log(LOGERROR, "PVR: couldn't load any clients"); + return; + } + + /* Create the supervisor thread to do all background activities */ + Create(); + SetName("XBMC PVR Supervisor"); + SetPriority(-15); + CLog::Log(LOGNOTICE, "PVR: PVRManager started. Clients loaded = %u", (unsigned int) m_clients.size()); + return; +} + +/******************************************************************** + * CPVRManager Stop + * + * PVRManager shutdown + ********************************************************************/ +void CPVRManager::Stop() +{ + CLog::Log(LOGNOTICE, "PVR: PVRManager stoping"); + StopThread(); + + m_clients.clear(); + m_clientsProps.clear(); + return; +} + +/******************************************************************** + * CPVRManager LoadClients + * + * Load the client drivers and doing the startup. + ********************************************************************/ +bool CPVRManager::LoadClients() +{ + /* Get all PVR Add on's */ + VECADDONS addons; + if (!CAddonMgr::Get().GetAddons(ADDON_PVRDLL, addons, true)) + return false; + + m_database.Open(); + + /* load the clients */ + for (unsigned i=0; i < addons.size(); i++) + { + const AddonPtr clientAddon = addons.at(i); + if (!clientAddon->Enabled()) + continue; + + /* Add client to TV-Database to identify different backend types, + * if client is already added his id is given. + */ + long clientID = m_database.AddClient(clientAddon->Name(), clientAddon->ID()); + if (clientID == -1) + { + CLog::Log(LOGERROR, "PVR: Can't Add/Get PVR Client '%s' to to TV Database", clientAddon->Name().c_str()); + continue; + } + + /* Load the Client libraries, and inside them into client list if + * success. Client initialization is also performed during loading. + */ + boost::shared_ptr addon = boost::dynamic_pointer_cast(clientAddon); + if (addon && addon->Create(clientID, this)) + { + m_clients.insert(std::make_pair(clientID, addon)); + } + } + + m_database.Close(); + + // Request each client's basic properties + GetClientProperties(); + + return !m_clients.empty(); +} + +/******************************************************************** + * CPVRManager GetClientProperties + * + * Load the client Properties for every client + ********************************************************************/ +void CPVRManager::GetClientProperties() +{ + m_clientsProps.clear(); + CLIENTMAPITR itr = m_clients.begin(); + while (itr != m_clients.end()) + { + GetClientProperties((*itr).first); + itr++; + } +} + +/******************************************************************** + * CPVRManager GetClientProperties + * + * Load the client Properties for the given client ID in the + * Properties list + ********************************************************************/ +void CPVRManager::GetClientProperties(long clientID) +{ + PVR_SERVERPROPS props; + if (m_clients[clientID]->GetProperties(&props) == PVR_ERROR_NO_ERROR) + { + // store the client's properties + m_clientsProps.insert(std::make_pair(clientID, props)); + } +} + +/******************************************************************** + * CPVRManager GetFirstClientID + * + * Returns the first loaded client ID + ********************************************************************/ +unsigned long CPVRManager::GetFirstClientID() +{ + CLIENTMAPITR itr = m_clients.begin(); + return m_clients[(*itr).first]->GetID(); +} + +/******************************************************************** + * CPVRManager OnClientMessage + * + * Callback function from Client driver to inform about changed + * timers, channels, recordings or epg. + ********************************************************************/ +void CPVRManager::OnClientMessage(const long clientID, const PVR_EVENT clientEvent, const char* msg) +{ + /* here the manager reacts to messages sent from any of the clients via the IPVRClientCallback */ + CStdString clientName = m_clients[clientID]->GetBackendName() + ":" + m_clients[clientID]->GetConnectionString(); + switch (clientEvent) + { + case PVR_EVENT_UNKNOWN: + CLog::Log(LOGDEBUG, "%s - PVR: client_%ld unknown event : %s", __FUNCTION__, clientID, msg); + break; + + case PVR_EVENT_TIMERS_CHANGE: + { + CLog::Log(LOGDEBUG, "%s - PVR: client_%ld timers changed", __FUNCTION__, clientID); + PVRTimers.Update(); + SyncInfo(); + + CGUIWindowTV *pTVWin = (CGUIWindowTV *)g_windowManager.GetWindow(WINDOW_TV); + if (pTVWin) + pTVWin->UpdateData(TV_WINDOW_TIMERS); + } + break; + + case PVR_EVENT_RECORDINGS_CHANGE: + { + CLog::Log(LOGDEBUG, "%s - PVR: client_%ld recording list changed", __FUNCTION__, clientID); + SyncInfo(); + + CGUIWindowTV *pTVWin = (CGUIWindowTV *)g_windowManager.GetWindow(WINDOW_TV); + if (pTVWin) + pTVWin->UpdateData(TV_WINDOW_RECORDINGS); + } + break; + + case PVR_EVENT_CHANNELS_CHANGE: + { + CLog::Log(LOGDEBUG, "%s - PVR: client_%ld channel list changed", __FUNCTION__, clientID); + SyncInfo(); + + CGUIWindowTV *pTVWin = (CGUIWindowTV *)g_windowManager.GetWindow(WINDOW_TV); + if (pTVWin) + { + pTVWin->UpdateData(TV_WINDOW_CHANNELS_TV); + pTVWin->UpdateData(TV_WINDOW_CHANNELS_RADIO); + } + } + break; + + default: + break; + } +} + +/******************************************************************** + * CPVRManager RequestRestart + * + * Restart a client driver + ********************************************************************/ +bool CPVRManager::RequestRestart(AddonPtr addon, bool datachanged) +{ + if (!addon) + return false; + + CLog::Log(LOGINFO, "PVR: requested restart of clientName:%s, clientGUID:%s", addon->Name().c_str(), addon->ID().c_str()); + CLIENTMAPITR itr = m_clients.begin(); + while (itr != m_clients.end()) + { + if (m_clients[(*itr).first]->ID() == addon->ID()) + { + if (m_clients[(*itr).first]->Name() == addon->Name()) + { + CLog::Log(LOGINFO, "PVR: restarting clientName:%s, clientGUID:%s", addon->Name().c_str(), addon->ID().c_str()); + StopThread(); + m_clients[(*itr).first]->ReCreate(); + Create(); + } + } + itr++; + } + return true; +} + +/******************************************************************** + * CPVRManager RequestRemoval + * + * Unload a client driver + ********************************************************************/ +bool CPVRManager::RequestRemoval(AddonPtr addon) +{ + if (!addon) + return false; + + CLog::Log(LOGINFO, "PVR: requested removal of clientName:%s, clientGUID:%s", addon->Name().c_str(), addon->ID().c_str()); + CLIENTMAPITR itr = m_clients.begin(); + while (itr != m_clients.end()) + { + if (m_clients[(*itr).first]->ID() == addon->ID()) + { + if (m_clients[(*itr).first]->Name() == addon->Name()) + { + CLog::Log(LOGINFO, "PVR: removing clientName:%s, clientGUID:%s", addon->Name().c_str(), addon->ID().c_str()); + StopThread(); + + m_clients[(*itr).first]->Destroy(); + m_clients.erase((*itr).first); + if (!m_clients.empty()) + Create(); + + return true; + } + } + itr++; + } + + return false; +} + + +/*************************************************************/ +/** INTERNAL FUNCTIONS **/ +/*************************************************************/ + +/*************************************************************/ +/** PVRManager Update and control thread **/ +/*************************************************************/ + +void CPVRManager::Process() +{ + /* Get TV Channels from Backends */ + PVRChannelsTV.Load(false); + + /* Get Radio Channels from Backends */ + PVRChannelsRadio.Load(true); + + /* Load the TV Channel group lists */ + PVRChannelGroupsTV.Load(false); + + /* Load the Radio Channel group lists */ + PVRChannelGroupsRadio.Load(true); + + /* Continue last watched channel after first startup */ + if (m_bFirstStart && g_guiSettings.GetInt("pvrplayback.startlast") != START_LAST_CHANNEL_OFF) + { + CLog::Log(LOGNOTICE,"PVR: Try to continue last channel"); + m_bFirstStart = false; + + m_database.Open(); + int lastChannel = m_database.GetLastChannel(); + if (lastChannel > 0) + { + cPVRChannelInfoTag *tag = cPVRChannels::GetByChannelIDFromAll(lastChannel); + if (tag) + { + cPVRChannels *channels; + if (!tag->IsRadio()) + channels = &PVRChannelsTV; + else + channels = &PVRChannelsRadio; + + if (g_guiSettings.GetInt("pvrplayback.startlast") == START_LAST_CHANNEL_MIN) + g_settings.m_bStartVideoWindowed = true; + + if (g_application.PlayFile(CFileItem(channels->at(tag->Number()-1)))) + CLog::Log(LOGNOTICE,"PVR: Continue playback of channel '%s'", tag->Name().c_str()); + else + CLog::Log(LOGERROR,"PVR: Channel '%s' can't continued", tag->Name().c_str()); + } + else + CLog::Log(LOGERROR,"PVR: Can't find channel (ID=%i) for continue on startup", lastChannel); + } + m_database.Close(); + } + + /* Get Timers from Backends */ + PVRTimers.Load(); + + /* Get Recordings from Backend */ + PVRRecordings.Load(); + + /* Get Epg's from Backend */ + PVREpgs.Load(); + + int Now = CTimeUtils::GetTimeMS()/1000; + int LastEPGCleanup = Now; + m_LastTVChannelCheck = Now; + m_LastRadioChannelCheck = Now+CHANNELCHECKDELTA/2; + m_LastRecordingsCheck = Now; + m_LastEPGUpdate = Now; + m_LastTimersCheck = Now; + /* Check the last EPG scan date if XBMC is restarted to prevent a rescan if + the time is not longer as one hour ago */ + m_database.Open(); + if (m_database.GetLastEPGScanTime() < CDateTime::GetCurrentDateTime()-CDateTimeSpan(0,1,0,0)) + m_LastEPGScan = Now-(g_guiSettings.GetInt("pvrepg.epgscan")*60*60)+120; + else + m_LastEPGScan = Now; + m_database.Close(); + + while (!m_bStop) + { + Now = CTimeUtils::GetTimeMS()/1000; + + /* Check for new or updated TV Channels */ + if (Now - m_LastTVChannelCheck > CHANNELCHECKDELTA) // don't do this too often + { + CLog::Log(LOGDEBUG,"PVR: Updating TV Channel list"); + PVRChannelsTV.Update(); + m_LastTVChannelCheck = Now; + } + + /* Check for new or updated Radio Channels */ + if (Now - m_LastRadioChannelCheck > CHANNELCHECKDELTA) // don't do this too often + { + CLog::Log(LOGDEBUG,"PVR: Updating Radio Channel list"); + PVRChannelsRadio.Update(); + m_LastRadioChannelCheck = Now; + } + + /* Check for new or updated Recordings */ + if (Now - m_LastRecordingsCheck > RECORDINGCHECKDELTA) // don't do this too often + { + CLog::Log(LOGDEBUG,"PVR: Updating Recordings list"); + PVRRecordings.Update(true); + m_LastRecordingsCheck = Now; + } + + /* Check for new or updated Timers */ + if (Now - m_LastTimersCheck > TIMERCHECKDELTA) // don't do this too often + { + CLog::Log(LOGDEBUG,"PVR: Updating Timers list"); + PVRTimers.Update(); + SyncInfo(); + CGUIWindowTV *pTVWin = (CGUIWindowTV *)g_windowManager.GetWindow(WINDOW_TV); + if (pTVWin) + pTVWin->UpdateData(TV_WINDOW_TIMERS); + m_LastTimersCheck = Now; + } + + /* Check for new or updated EPG entries */ + if (Now - m_LastEPGScan > g_guiSettings.GetInt("pvrepg.epgscan")*60*60) // don't do this too often + { + PVREpgs.Update(true); + m_LastEPGScan = Now; + m_LastEPGUpdate = Now; // Data is also updated during scan + LastEPGCleanup = Now; + } + else if (Now - m_LastEPGUpdate > g_guiSettings.GetInt("pvrepg.epgupdate")*60) // don't do this too often + { + PVREpgs.Update(false); + m_LastEPGUpdate = Now; + LastEPGCleanup = Now; + } + else if (Now - LastEPGCleanup > EPGCLEANUPCHECKDELTA) // don't do this too often + { + /* Cleanup EPG Data */ + PVREpgs.Cleanup(); + LastEPGCleanup = Now; + } + + EnterCriticalSection(&m_critSection); + + /* Get Signal information of the current playing channel */ + if (m_currentPlayingChannel && g_guiSettings.GetBool("pvrplayback.signalquality") && !m_currentPlayingChannel->GetPVRChannelInfoTag()->IsVirtual()) + { + m_clients[m_currentPlayingChannel->GetPVRChannelInfoTag()->ClientID()]->SignalQuality(m_qualityInfo); + } + LeaveCriticalSection(&m_critSection); + + Sleep(1000); + } + + /* if a channel or recording is playing stop it first */ + if (m_currentPlayingChannel || m_currentPlayingRecording) + g_application.StopPlaying(); + + /* Remove Epg's from Memory */ + PVREpgs.Unload(); + + /* Remove recordings from Memory */ + PVRRecordings.Unload(); + + /* Remove Timers from Memory */ + PVRTimers.Unload(); + + /* Remove TV Channel groups from Memory */ + PVRChannelGroupsTV.Unload(); + + /* Remove Radio Channel groups from Memory */ + PVRChannelGroupsRadio.Unload(); + + /* Remove Radio Channels from Memory */ + PVRChannelsRadio.Unload(); + + /* Remove TV Channels from Memory */ + PVRChannelsTV.Unload(); +} + + +/*************************************************************/ +/** GUIInfoManager FUNCTIONS **/ +/*************************************************************/ + +/******************************************************************** + * CPVRManager SyncInfo + * + * Synchronize InfoManager related stuff + ********************************************************************/ +void CPVRManager::SyncInfo() +{ + EnterCriticalSection(&m_critSection); + + PVRRecordings.GetNumRecordings() > 0 ? m_hasRecordings = true : m_hasRecordings = false; + PVRTimers.GetNumTimers() > 0 ? m_hasTimers = true : m_hasTimers = false; + m_isRecording = false; + cPVRTimerInfoTag *nextTimer = NULL; + + m_nowRecordingTitle.clear(); + m_nowRecordingChannel.clear(); + m_nowRecordingDateTime.clear(); + m_nextRecordingTitle.clear(); + m_nextRecordingChannel.clear(); + m_nextRecordingDateTime.clear(); + + if (m_hasTimers) + { + for (unsigned int i = 0; i < PVRTimers.size(); ++i) + { + if (PVRTimers[i].Active() && (PVRTimers[i].Start() < CDateTime::GetCurrentDateTime() && PVRTimers[i].Stop() > CDateTime::GetCurrentDateTime())) + { + m_nowRecordingTitle.push_back(PVRTimers[i].Title()); + m_nowRecordingChannel.push_back(PVRChannelsTV.GetNameForChannel(PVRTimers[i].Number())); + m_nowRecordingDateTime.push_back(PVRTimers[i].Start().GetAsLocalizedDateTime(false, false)); + m_isRecording = true; + } + else if (PVRTimers[i].Active()) + { + if (!nextTimer || nextTimer->Start() > PVRTimers[i].Start()) + nextTimer = &PVRTimers[i]; + } + } + + if (nextTimer) + { + m_nextRecordingTitle = nextTimer->Title(); + m_nextRecordingChannel = PVRChannelsTV.GetNameForChannel(nextTimer->Number()); + m_nextRecordingDateTime = nextTimer->Start().GetAsLocalizedDateTime(false, false); + } + } + + LeaveCriticalSection(&m_critSection); +} + +/******************************************************************** + * CPVRManager TranslateCharInfo + * + * Returns a GUIInfoManager Character String + ********************************************************************/ +#define INFO_TOGGLE_TIME 1500 +const char* CPVRManager::TranslateCharInfo(DWORD dwInfo) +{ + if (dwInfo == PVR_NOW_RECORDING_TITLE) + { + if (m_recordingToggleStart == 0) + { + SyncInfo(); + m_recordingToggleStart = CTimeUtils::GetTimeMS(); + m_recordingToggleCurrent = 0; + } + else + { + if (CTimeUtils::GetTimeMS() - m_recordingToggleStart > INFO_TOGGLE_TIME) + { + SyncInfo(); + if (m_nowRecordingTitle.size() > 0) + { + m_recordingToggleCurrent++; + if (m_recordingToggleCurrent > m_nowRecordingTitle.size()-1) + m_recordingToggleCurrent = 0; + + m_recordingToggleStart = CTimeUtils::GetTimeMS(); + } + } + } + + if (m_nowRecordingTitle.size() > 0) + return m_nowRecordingTitle[m_recordingToggleCurrent]; + else + return ""; + } + else if (dwInfo == PVR_NOW_RECORDING_CHANNEL) + { + if (m_nowRecordingChannel.size() > 0) + return m_nowRecordingChannel[m_recordingToggleCurrent]; + else + return ""; + } + else if (dwInfo == PVR_NOW_RECORDING_DATETIME) + { + if (m_nowRecordingDateTime.size() > 0) + return m_nowRecordingDateTime[m_recordingToggleCurrent]; + else + return ""; + } + else if (dwInfo == PVR_NEXT_RECORDING_TITLE) return m_nextRecordingTitle; + else if (dwInfo == PVR_NEXT_RECORDING_CHANNEL) return m_nextRecordingChannel; + else if (dwInfo == PVR_NEXT_RECORDING_DATETIME) return m_nextRecordingDateTime; + else if (dwInfo == PVR_BACKEND_NAME) return m_backendName; + else if (dwInfo == PVR_BACKEND_VERSION) return m_backendVersion; + else if (dwInfo == PVR_BACKEND_HOST) return m_backendHost; + else if (dwInfo == PVR_BACKEND_DISKSPACE) return m_backendDiskspace; + else if (dwInfo == PVR_BACKEND_CHANNELS) return m_backendChannels; + else if (dwInfo == PVR_BACKEND_TIMERS) return m_backendTimers; + else if (dwInfo == PVR_BACKEND_RECORDINGS) return m_backendRecordings; + else if (dwInfo == PVR_BACKEND_NUMBER) + { + if (m_infoToggleStart == 0) + { + m_infoToggleStart = CTimeUtils::GetTimeMS(); + m_infoToggleCurrent = 0; + } + else + { + if (CTimeUtils::GetTimeMS() - m_infoToggleStart > INFO_TOGGLE_TIME) + { + if (m_clients.size() > 0) + { + m_infoToggleCurrent++; + if (m_infoToggleCurrent > m_clients.size()-1) + m_infoToggleCurrent = 0; + + CLIENTMAPITR itr = m_clients.begin(); + for (unsigned int i = 0; i < m_infoToggleCurrent; i++) + itr++; + + long long kBTotal = 0; + long long kBUsed = 0; + if (m_clients[(*itr).first]->GetDriveSpace(&kBTotal, &kBUsed) == PVR_ERROR_NO_ERROR) + { + kBTotal /= 1024; // Convert to MBytes + kBUsed /= 1024; // Convert to MBytes + m_backendDiskspace.Format("%s %.1f GByte - %s: %.1f GByte", g_localizeStrings.Get(20161), (float) kBTotal / 1024, g_localizeStrings.Get(20162), (float) kBUsed / 1024); + } + else + { + m_backendDiskspace = g_localizeStrings.Get(19055); + } + + int NumChannels = m_clients[(*itr).first]->GetNumChannels(); + if (NumChannels >= 0) + m_backendChannels.Format("%i", NumChannels); + else + m_backendChannels = g_localizeStrings.Get(161); + + int NumTimers = m_clients[(*itr).first]->GetNumTimers(); + if (NumTimers >= 0) + m_backendTimers.Format("%i", NumTimers); + else + m_backendTimers = g_localizeStrings.Get(161); + + int NumRecordings = m_clients[(*itr).first]->GetNumRecordings(); + if (NumRecordings >= 0) + m_backendRecordings.Format("%i", NumRecordings); + else + m_backendRecordings = g_localizeStrings.Get(161); + + m_backendName = m_clients[(*itr).first]->GetBackendName(); + m_backendVersion = m_clients[(*itr).first]->GetBackendVersion(); + m_backendHost = m_clients[(*itr).first]->GetConnectionString(); + } + else + { + m_backendName = ""; + m_backendVersion = ""; + m_backendHost = ""; + m_backendDiskspace = ""; + m_backendTimers = ""; + m_backendRecordings = ""; + m_backendChannels = ""; + } + m_infoToggleStart = CTimeUtils::GetTimeMS(); + } + } + + static CStdString backendClients; + if (m_clients.size() > 0) + backendClients.Format("%u %s %u", m_infoToggleCurrent+1, g_localizeStrings.Get(20163), m_clients.size()); + else + backendClients = g_localizeStrings.Get(14023); + + return backendClients; + } + else if (dwInfo == PVR_TOTAL_DISKSPACE) + { + long long kBTotal = 0; + long long kBUsed = 0; + CLIENTMAPITR itr = m_clients.begin(); + while (itr != m_clients.end()) + { + long long clientKBTotal = 0; + long long clientKBUsed = 0; + + if (m_clients[(*itr).first]->GetDriveSpace(&clientKBTotal, &clientKBUsed) == PVR_ERROR_NO_ERROR) + { + kBTotal += clientKBTotal; + kBUsed += clientKBUsed; + } + itr++; + } + kBTotal /= 1024; // Convert to MBytes + kBUsed /= 1024; // Convert to MBytes + m_totalDiskspace.Format("%s %0.1f GByte - %s: %0.1f GByte", g_localizeStrings.Get(20161), (float) kBTotal / 1024, g_localizeStrings.Get(20162), (float) kBUsed / 1024); + return m_totalDiskspace; + } + else if (dwInfo == PVR_NEXT_TIMER) + { + cPVRTimerInfoTag *next = PVRTimers.GetNextActiveTimer(); + if (next != NULL) + { + m_nextTimer.Format("%s %s %s %s", g_localizeStrings.Get(19106) + , next->Start().GetAsLocalizedDate(true) + , g_localizeStrings.Get(19107) + , next->Start().GetAsLocalizedTime("HH:mm", false)); + return m_nextTimer; + } + } + else if (dwInfo == PVR_PLAYING_DURATION) + { + m_playingDuration = StringUtils::SecondsToTimeString(GetTotalTime()/1000, TIME_FORMAT_GUESS); + return m_playingDuration.c_str(); + } + else if (dwInfo == PVR_PLAYING_TIME) + { + m_playingTime = StringUtils::SecondsToTimeString(GetStartTime()/1000, TIME_FORMAT_GUESS); + return m_playingTime.c_str(); + } + else if (dwInfo == PVR_ACTUAL_STREAM_VIDEO_BR) + { + static CStdString strBitrate = ""; + if (m_qualityInfo.video_bitrate > 0) + strBitrate.Format("%.2f Mbit/s", m_qualityInfo.video_bitrate); + return strBitrate; + } + else if (dwInfo == PVR_ACTUAL_STREAM_AUDIO_BR) + { + CStdString strBitrate = ""; + if (m_qualityInfo.audio_bitrate > 0) + strBitrate.Format("%.0f kbit/s", m_qualityInfo.audio_bitrate); + return strBitrate; + } + else if (dwInfo == PVR_ACTUAL_STREAM_DOLBY_BR) + { + CStdString strBitrate = ""; + if (m_qualityInfo.dolby_bitrate > 0) + strBitrate.Format("%.0f kbit/s", m_qualityInfo.dolby_bitrate); + return strBitrate; + } + else if (dwInfo == PVR_ACTUAL_STREAM_SIG) + { + CStdString strSignal = ""; + if (m_qualityInfo.signal > 0) + strSignal.Format("%d %%", m_qualityInfo.signal / 655); + return strSignal; + } + else if (dwInfo == PVR_ACTUAL_STREAM_SNR) + { + CStdString strSNR = ""; + if (m_qualityInfo.snr > 0) + strSNR.Format("%d %%", m_qualityInfo.snr / 655); + return strSNR; + } + else if (dwInfo == PVR_ACTUAL_STREAM_BER) + { + CStdString strBER; + strBER.Format("%08X", m_qualityInfo.ber); + return strBER; + } + else if (dwInfo == PVR_ACTUAL_STREAM_UNC) + { + CStdString strUNC; + strUNC.Format("%08X", m_qualityInfo.unc); + return strUNC; + } + else if (dwInfo == PVR_ACTUAL_STREAM_CLIENT) + { + return m_playingClientName; + } + else if (dwInfo == PVR_ACTUAL_STREAM_DEVICE) + { + CStdString string = m_qualityInfo.frontend_name; + if (string == "") + return g_localizeStrings.Get(13205); + else + return string; + } + else if (dwInfo == PVR_ACTUAL_STREAM_STATUS) + { + CStdString string = m_qualityInfo.frontend_status; + if (string == "") + return g_localizeStrings.Get(13205); + else + return string; + } + else if (dwInfo == PVR_ACTUAL_STREAM_CRYPTION) + { + if (m_currentPlayingChannel) + return m_currentPlayingChannel->GetPVRChannelInfoTag()->EncryptionName(); + } + return ""; +} + +/******************************************************************** + * CPVRManager TranslateIntInfo + * + * Returns a GUIInfoManager integer value + ********************************************************************/ +int CPVRManager::TranslateIntInfo(DWORD dwInfo) +{ + if (dwInfo == PVR_PLAYING_PROGRESS) + { + return (float)(((float)GetStartTime() / GetTotalTime()) * 100); + } + else if (dwInfo == PVR_ACTUAL_STREAM_SIG_PROGR) + { + return (float)(((float)m_qualityInfo.signal / 0xFFFF) * 100); + } + else if (dwInfo == PVR_ACTUAL_STREAM_SNR_PROGR) + { + return (float)(((float)m_qualityInfo.snr / 0xFFFF) * 100); + } + return 0; +} + +/******************************************************************** + * CPVRManager TranslateBoolInfo + * + * Returns a GUIInfoManager boolean value + ********************************************************************/ +bool CPVRManager::TranslateBoolInfo(DWORD dwInfo) +{ + if (dwInfo == PVR_IS_RECORDING) + return m_isRecording; + else if (dwInfo == PVR_HAS_TIMER) + return m_hasTimers; + else if (dwInfo == PVR_IS_PLAYING_TV) + return IsPlayingTV(); + else if (dwInfo == PVR_IS_PLAYING_RADIO) + return IsPlayingRadio(); + else if (dwInfo == PVR_IS_PLAYING_RECORDING) + return IsPlayingRecording(); + else if (dwInfo == PVR_ACTUAL_STREAM_ENCRYPTED) + { + if (m_currentPlayingChannel) + return m_currentPlayingChannel->GetPVRChannelInfoTag()->IsEncrypted(); + } + + return false; +} + + +/*************************************************************/ +/** GENERAL FUNCTIONS **/ +/*************************************************************/ + +void CPVRManager::StartChannelScan() +{ + std::vector clients; + long scanningClientID = -1; + m_bChannelScanRunning = true; + + CLIENTMAPITR itr = m_clients.begin(); + while (itr != m_clients.end()) + { + if (m_clients[(*itr).first]->ReadyToUse() && GetClientProps(m_clients[(*itr).first]->GetID())->SupportChannelScan) + { + clients.push_back(m_clients[(*itr).first]->GetID()); + } + itr++; + } + + if (clients.size() > 1) + { + CGUIDialogSelect* pDialog= (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT); + + pDialog->Reset(); + pDialog->SetHeading(19119); + + for (unsigned int i = 0; i < clients.size(); i++) + { + pDialog->Add(m_clients[clients[i]]->GetBackendName() + ":" + m_clients[clients[i]]->GetConnectionString()); + } + + pDialog->DoModal(); + + int selection = pDialog->GetSelectedLabel(); + if (selection >= 0) + { + scanningClientID = clients[selection]; + } + + } + else if (clients.size() == 1) + { + scanningClientID = clients[0]; + } + + if (scanningClientID < 0) + { + CGUIDialogOK::ShowAndGetInput(19033,0,19192,0); + return; + } + + CLog::Log(LOGNOTICE,"PVR: Starting to scan for channels on client %s:%s", m_clients[scanningClientID]->GetBackendName().c_str(), m_clients[scanningClientID]->GetConnectionString().c_str()); + long perfCnt = CTimeUtils::GetTimeMS(); + + PVREpgs.InihibitUpdate(true); + + if (m_currentPlayingRecording || m_currentPlayingChannel) + { + CLog::Log(LOGNOTICE,"PVR: Is playing data, stopping playback"); + g_application.StopPlaying(); + } + /* Stop the supervisor thread */ + StopThread(); + + if (m_clients[scanningClientID]->StartChannelScan() != PVR_ERROR_NO_ERROR) + { + CGUIDialogOK::ShowAndGetInput(19111,0,19193,0); + } + + /* Create the supervisor thread again */ + Create(); + CLog::Log(LOGNOTICE, "PVR: Channel scan finished after %li.%li seconds", (CTimeUtils::GetTimeMS()-perfCnt)/1000, (CTimeUtils::GetTimeMS()-perfCnt)%1000); + m_bChannelScanRunning = false; +} + +void CPVRManager::ResetDatabase() +{ + CLog::Log(LOGNOTICE,"PVR: TV Database is now set to it's initial state"); + + CGUIDialogProgress* pDlgProgress = (CGUIDialogProgress*)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS); + pDlgProgress->SetLine(0, ""); + pDlgProgress->SetLine(1, g_localizeStrings.Get(19186)); + pDlgProgress->SetLine(2, ""); + pDlgProgress->StartModal(); + pDlgProgress->Progress(); + + PVREpgs.InihibitUpdate(true); + + if (m_currentPlayingRecording || m_currentPlayingChannel) + { + CLog::Log(LOGNOTICE,"PVR: Is playing data, stopping playback"); + g_application.StopPlaying(); + } + pDlgProgress->SetPercentage(10); + + Stop(); + pDlgProgress->SetPercentage(20); + + m_database.Open(); + m_database.EraseEPG(); + pDlgProgress->SetPercentage(30); + + m_database.EraseChannelLinkageMap(); + pDlgProgress->SetPercentage(40); + + m_database.EraseChannelGroups(); + pDlgProgress->SetPercentage(50); + + m_database.EraseRadioChannelGroups(); + pDlgProgress->SetPercentage(60); + + m_database.EraseChannels(); + pDlgProgress->SetPercentage(70); + + m_database.EraseChannelSettings(); + pDlgProgress->SetPercentage(80); + + m_database.EraseClients(); + pDlgProgress->SetPercentage(90); + + m_database.Close(); + CLog::Log(LOGNOTICE,"PVR: TV Database reset finished, starting PVR Subsystem again"); + Start(); + pDlgProgress->SetPercentage(100); + pDlgProgress->Close(); +} + +void CPVRManager::ResetEPG() +{ + CLog::Log(LOGNOTICE,"PVR: EPG is being erased"); + + CGUIDialogProgress* pDlgProgress = (CGUIDialogProgress*)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS); + pDlgProgress->SetLine(0, ""); + pDlgProgress->SetLine(1, g_localizeStrings.Get(19186)); + pDlgProgress->SetLine(2, ""); + pDlgProgress->StartModal(); + pDlgProgress->Progress(); + + PVREpgs.InihibitUpdate(true); + + if (m_currentPlayingRecording || m_currentPlayingChannel) + { + CLog::Log(LOGNOTICE,"PVR: Is playing data, stopping playback"); + g_application.StopPlaying(); + } + pDlgProgress->SetPercentage(10); + + Stop(); + pDlgProgress->SetPercentage(30); + + m_database.Open(); + pDlgProgress->SetPercentage(50); + + m_database.EraseEPG(); + pDlgProgress->SetPercentage(70); + + m_database.Close(); + CLog::Log(LOGNOTICE,"PVR: EPG reset finished, starting PVR Subsystem again"); + Start(); + pDlgProgress->SetPercentage(100); + pDlgProgress->Close(); +} + +bool CPVRManager::IsPlayingTV() +{ + if (!m_currentPlayingChannel) + return false; + + return !m_currentPlayingChannel->GetPVRChannelInfoTag()->IsRadio(); +} + +bool CPVRManager::IsPlayingRadio() +{ + if (!m_currentPlayingChannel) + return false; + + return m_currentPlayingChannel->GetPVRChannelInfoTag()->IsRadio(); +} + +bool CPVRManager::IsPlayingRecording() +{ + if (m_currentPlayingRecording) + return false; + else + return true; +} + +PVR_SERVERPROPS *CPVRManager::GetCurrentClientProps() +{ + if (m_currentPlayingChannel) + return &m_clientsProps[m_currentPlayingChannel->GetPVRChannelInfoTag()->ClientID()]; + else if (m_currentPlayingRecording) + return &m_clientsProps[m_currentPlayingRecording->GetPVRRecordingInfoTag()->ClientID()]; + else + return NULL; +} + +long CPVRManager::GetCurrentPlayingClientID() +{ + if (m_currentPlayingChannel) + return m_currentPlayingChannel->GetPVRChannelInfoTag()->ClientID(); + else if (m_currentPlayingRecording) + return m_currentPlayingRecording->GetPVRRecordingInfoTag()->ClientID(); + else + return -1; +} + +PVR_STREAMPROPS *CPVRManager::GetCurrentStreamProps() +{ + if (m_currentPlayingChannel) + { + int cid = m_currentPlayingChannel->GetPVRChannelInfoTag()->ClientID(); + m_clients[cid]->GetStreamProperties(&m_streamProps[cid]); + + return &m_streamProps[cid]; + } + else + return NULL; +} + +CFileItem *CPVRManager::GetCurrentPlayingItem() +{ + if (m_currentPlayingChannel) + return m_currentPlayingChannel; + else if (m_currentPlayingRecording) + return m_currentPlayingRecording; + else + return NULL; +} + +CStdString CPVRManager::GetCurrentInputFormat() +{ + if (m_currentPlayingChannel) + return m_currentPlayingChannel->GetPVRChannelInfoTag()->InputFormat(); + + return ""; +} + +bool CPVRManager::GetCurrentChannel(int *number, bool *radio) +{ + if (m_currentPlayingChannel) + { + if (number) + *number = m_currentPlayingChannel->GetPVRChannelInfoTag()->Number(); + if (radio) + *radio = m_currentPlayingChannel->GetPVRChannelInfoTag()->IsRadio(); + return true; + } + else + { + if (number) + *number = 1; + if (radio) + *radio = false; + return false; + } +} + +bool CPVRManager::HaveActiveClients() +{ + if (m_clients.empty()) + return false; + + int ready = 0; + CLIENTMAPITR itr = m_clients.begin(); + while (itr != m_clients.end()) + { + if (m_clients[(*itr).first]->ReadyToUse()) + ready++; + itr++; + } + return ready > 0 ? true : false; +} + +bool CPVRManager::HaveMenuHooks(long clientID) +{ + if (clientID < 0) + clientID = GetCurrentPlayingClientID(); + if (clientID < 0) + return false; + return m_clients[clientID]->HaveMenuHooks(); +} + +void CPVRManager::ProcessMenuHooks(long clientID) +{ + if (m_clients[clientID]->HaveMenuHooks()) + { + PVR_MENUHOOKS *hooks = m_clients[clientID]->GetMenuHooks(); + std::vector hookIDs; + + CGUIDialogSelect* pDialog = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT); + + pDialog->Reset(); + pDialog->SetHeading(19196); + + for (unsigned int i = 0; i < hooks->size(); i++) + { + pDialog->Add(m_clients[clientID]->GetString(hooks->at(i).string_id)); + } + + pDialog->DoModal(); + + int selection = pDialog->GetSelectedLabel(); + if (selection >= 0) + { + m_clients[clientID]->CallMenuHook(hooks->at(selection)); + } + } +} + +int CPVRManager::GetPreviousChannel() +{ + if (m_currentPlayingChannel == NULL) + return -1; + + int LastChannel = m_currentPlayingChannel->GetPVRChannelInfoTag()->Number(); + + if ((m_PreviousChannel[m_PreviousChannelIndex ^ 1] == LastChannel || LastChannel != m_PreviousChannel[0]) && LastChannel != m_PreviousChannel[1]) + m_PreviousChannelIndex ^= 1; + + return m_PreviousChannel[m_PreviousChannelIndex ^= 1]; +} + +bool CPVRManager::CanInstantRecording() +{ + if (!m_currentPlayingChannel) + return false; + + const cPVRChannelInfoTag* tag = m_currentPlayingChannel->GetPVRChannelInfoTag(); + if (m_clientsProps[tag->ClientID()].SupportTimers) + return true; + else + return false; +} + +bool CPVRManager::IsRecordingOnPlayingChannel() +{ + if (!m_currentPlayingChannel) + return false; + + const cPVRChannelInfoTag* tag = m_currentPlayingChannel->GetPVRChannelInfoTag(); + return tag->IsRecording(); +} + +bool CPVRManager::StartRecordingOnPlayingChannel(bool bOnOff) +{ + if (!m_currentPlayingChannel) + return false; + + cPVRChannelInfoTag* tag = m_currentPlayingChannel->GetPVRChannelInfoTag(); + if (m_clientsProps[tag->ClientID()].SupportTimers) + { + cPVRChannels *channels; + if (!m_currentPlayingChannel->GetPVRChannelInfoTag()->IsRadio()) + channels = &PVRChannelsTV; + else + channels = &PVRChannelsRadio; + + if (bOnOff && tag->IsRecording() == false) + { + cPVRTimerInfoTag newtimer(true); + newtimer.SetTitle(tag->Name()); + CFileItem *item = new CFileItem(newtimer); + + if (!cPVRTimers::AddTimer(*item)) + { + CGUIDialogOK::ShowAndGetInput(19033,0,19164,0); + return false; + } + + channels->at(tag->Number()-1).SetRecording(true); /* Set in channel list */ + tag->SetRecording(true); /* and also in current playing item */ + return true; + } + else if (tag->IsRecording() == true) + { + for (unsigned int i = 0; i < PVRTimers.size(); ++i) + { + if (!PVRTimers[i].IsRepeating() && PVRTimers[i].Active() && + (PVRTimers[i].Number() == tag->Number()) && + (PVRTimers[i].Start() <= CDateTime::GetCurrentDateTime()) && + (PVRTimers[i].Stop() >= CDateTime::GetCurrentDateTime())) + { + if (cPVRTimers::DeleteTimer(PVRTimers[i], true)) + { + channels->at(tag->Number()-1).SetRecording(false); /* Set in channel list */ + tag->SetRecording(false); /* and also in current playing item */ + return true; + } + } + } + } + } + return false; +} + +void CPVRManager::SaveCurrentChannelSettings() +{ + if (m_currentPlayingChannel) + { + // save video settings + if (g_settings.m_currentVideoSettings != g_settings.m_defaultVideoSettings) + { + m_database.Open(); + m_database.SetChannelSettings(m_currentPlayingChannel->GetPVRChannelInfoTag()->ChannelID(), g_settings.m_currentVideoSettings); + m_database.Close(); + } + } +} + +void CPVRManager::LoadCurrentChannelSettings() +{ + if (m_currentPlayingChannel) + { + CVideoSettings loadedChannelSettings; + + // Switch to default options + g_settings.m_currentVideoSettings = g_settings.m_defaultVideoSettings; + + m_database.Open(); + if (m_database.GetChannelSettings(m_currentPlayingChannel->GetPVRChannelInfoTag()->ChannelID(), loadedChannelSettings)) + { + if (loadedChannelSettings.m_AudioDelay != g_settings.m_currentVideoSettings.m_AudioDelay) + { + g_settings.m_currentVideoSettings.m_AudioDelay = loadedChannelSettings.m_AudioDelay; + + if (g_application.m_pPlayer) + g_application.m_pPlayer->SetAVDelay(g_settings.m_currentVideoSettings.m_AudioDelay); + } + + if (loadedChannelSettings.m_AudioStream != g_settings.m_currentVideoSettings.m_AudioStream) + { + g_settings.m_currentVideoSettings.m_AudioStream = loadedChannelSettings.m_AudioStream; + + // only change the audio stream if a different one has been asked for + if (g_application.m_pPlayer->GetAudioStream() != g_settings.m_currentVideoSettings.m_AudioStream) + { + g_application.m_pPlayer->SetAudioStream(g_settings.m_currentVideoSettings.m_AudioStream); // Set the audio stream to the one selected + } + } + + if (loadedChannelSettings.m_Brightness != g_settings.m_currentVideoSettings.m_Brightness) + { + g_settings.m_currentVideoSettings.m_Brightness = loadedChannelSettings.m_Brightness; + } + + if (loadedChannelSettings.m_Contrast != g_settings.m_currentVideoSettings.m_Contrast) + { + g_settings.m_currentVideoSettings.m_Contrast = loadedChannelSettings.m_Contrast; + } + + if (loadedChannelSettings.m_Gamma != g_settings.m_currentVideoSettings.m_Gamma) + { + g_settings.m_currentVideoSettings.m_Gamma = loadedChannelSettings.m_Gamma; + } + + if (loadedChannelSettings.m_Crop != g_settings.m_currentVideoSettings.m_Crop) + { + g_settings.m_currentVideoSettings.m_Crop = loadedChannelSettings.m_Crop; + // AutoCrop changes will get picked up automatically by dvdplayer + } + + if (loadedChannelSettings.m_CropLeft != g_settings.m_currentVideoSettings.m_CropLeft) + { + g_settings.m_currentVideoSettings.m_CropLeft = loadedChannelSettings.m_CropLeft; + } + + if (loadedChannelSettings.m_CropRight != g_settings.m_currentVideoSettings.m_CropRight) + { + g_settings.m_currentVideoSettings.m_CropRight = loadedChannelSettings.m_CropRight; + } + + if (loadedChannelSettings.m_CropTop != g_settings.m_currentVideoSettings.m_CropTop) + { + g_settings.m_currentVideoSettings.m_CropTop = loadedChannelSettings.m_CropTop; + } + + if (loadedChannelSettings.m_CropBottom != g_settings.m_currentVideoSettings.m_CropBottom) + { + g_settings.m_currentVideoSettings.m_CropBottom = loadedChannelSettings.m_CropBottom; + } + + if (loadedChannelSettings.m_VolumeAmplification != g_settings.m_currentVideoSettings.m_VolumeAmplification) + { + g_settings.m_currentVideoSettings.m_VolumeAmplification = loadedChannelSettings.m_VolumeAmplification; + + if (g_application.m_pPlayer) + g_application.m_pPlayer->SetDynamicRangeCompression((long)(g_settings.m_currentVideoSettings.m_VolumeAmplification * 100)); + } + + if (loadedChannelSettings.m_OutputToAllSpeakers != g_settings.m_currentVideoSettings.m_OutputToAllSpeakers) + { + g_settings.m_currentVideoSettings.m_OutputToAllSpeakers = loadedChannelSettings.m_OutputToAllSpeakers; + } + + if (loadedChannelSettings.m_ViewMode != g_settings.m_currentVideoSettings.m_ViewMode) + { + g_settings.m_currentVideoSettings.m_ViewMode = loadedChannelSettings.m_ViewMode; + + g_renderManager.SetViewMode(g_settings.m_currentVideoSettings.m_ViewMode); + g_settings.m_currentVideoSettings.m_CustomZoomAmount = g_settings.m_fZoomAmount; + g_settings.m_currentVideoSettings.m_CustomPixelRatio = g_settings.m_fPixelRatio; + } + + if (loadedChannelSettings.m_CustomPixelRatio != g_settings.m_currentVideoSettings.m_CustomPixelRatio) + { + g_settings.m_currentVideoSettings.m_CustomPixelRatio = loadedChannelSettings.m_CustomPixelRatio; + } + + if (loadedChannelSettings.m_CustomZoomAmount != g_settings.m_currentVideoSettings.m_CustomZoomAmount) + { + g_settings.m_currentVideoSettings.m_CustomZoomAmount = loadedChannelSettings.m_CustomZoomAmount; + } + + if (loadedChannelSettings.m_NoiseReduction != g_settings.m_currentVideoSettings.m_NoiseReduction) + { + g_settings.m_currentVideoSettings.m_NoiseReduction = loadedChannelSettings.m_NoiseReduction; + } + + if (loadedChannelSettings.m_Sharpness != g_settings.m_currentVideoSettings.m_Sharpness) + { + g_settings.m_currentVideoSettings.m_Sharpness = loadedChannelSettings.m_Sharpness; + } + + if (loadedChannelSettings.m_SubtitleDelay != g_settings.m_currentVideoSettings.m_SubtitleDelay) + { + g_settings.m_currentVideoSettings.m_SubtitleDelay = loadedChannelSettings.m_SubtitleDelay; + + g_application.m_pPlayer->SetSubTitleDelay(g_settings.m_currentVideoSettings.m_SubtitleDelay); + } + + if (loadedChannelSettings.m_SubtitleOn != g_settings.m_currentVideoSettings.m_SubtitleOn) + { + g_settings.m_currentVideoSettings.m_SubtitleOn = loadedChannelSettings.m_SubtitleOn; + + g_application.m_pPlayer->SetSubtitleVisible(g_settings.m_currentVideoSettings.m_SubtitleOn); + } + + if (loadedChannelSettings.m_SubtitleStream != g_settings.m_currentVideoSettings.m_SubtitleStream) + { + g_settings.m_currentVideoSettings.m_SubtitleStream = loadedChannelSettings.m_SubtitleStream; + + g_application.m_pPlayer->SetSubtitle(g_settings.m_currentVideoSettings.m_SubtitleStream); + } + + if (loadedChannelSettings.m_InterlaceMethod != g_settings.m_currentVideoSettings.m_InterlaceMethod) + { + g_settings.m_currentVideoSettings.m_InterlaceMethod = loadedChannelSettings.m_InterlaceMethod; + } + } + m_database.Close(); + } +} + +void CPVRManager::SetPlayingGroup(int GroupId) +{ + m_CurrentGroupID = GroupId; +} + +void CPVRManager::ResetQualityData() +{ + if (g_guiSettings.GetBool("pvrplayback.signalquality")) + { + strncpy(m_qualityInfo.frontend_name, g_localizeStrings.Get(13205).c_str(), 1024); + strncpy(m_qualityInfo.frontend_status, g_localizeStrings.Get(13205).c_str(), 1024); + } + else + { + strncpy(m_qualityInfo.frontend_name, g_localizeStrings.Get(13106).c_str(), 1024); + strncpy(m_qualityInfo.frontend_status, g_localizeStrings.Get(13106).c_str(), 1024); + } + m_qualityInfo.snr = 0; + m_qualityInfo.signal = 0; + m_qualityInfo.ber = 0; + m_qualityInfo.unc = 0; + m_qualityInfo.video_bitrate = 0; + m_qualityInfo.audio_bitrate = 0; + m_qualityInfo.dolby_bitrate = 0; +} + +int CPVRManager::GetPlayingGroup() +{ + return m_CurrentGroupID; +} + +void CPVRManager::TriggerRecordingsUpdate(bool force) +{ + m_LastRecordingsCheck = CTimeUtils::GetTimeMS()/1000-RECORDINGCHECKDELTA + (force ? 0 : 5); +} + +void CPVRManager::TriggerTimersUpdate(bool force) +{ + m_LastTimersCheck = CTimeUtils::GetTimeMS()/1000-TIMERCHECKDELTA + (force ? 0 : 5); +} + +bool CPVRManager::OpenLiveStream(const cPVRChannelInfoTag* tag) +{ + if (tag == NULL) + return false; + + EnterCriticalSection(&m_critSection); + + /* Check if a channel or recording is already opened and clear it if yes */ + if (m_currentPlayingChannel) + delete m_currentPlayingChannel; + if (m_currentPlayingRecording) + delete m_currentPlayingRecording; + + /* Set the new channel information */ + m_currentPlayingChannel = new CFileItem(*tag); + m_currentPlayingRecording = NULL; + m_scanStart = CTimeUtils::GetTimeMS(); /* Reset the stream scan timer */ + ResetQualityData(); + + /* Open the stream on the Client */ + if (tag->StreamURL().IsEmpty()) + { + if (!m_clientsProps[tag->ClientID()].HandleInputStream || + !m_clients[tag->ClientID()]->OpenLiveStream(*tag)) + { + delete m_currentPlayingChannel; + m_currentPlayingChannel = NULL; + LeaveCriticalSection(&m_critSection); + return false; + } + } + + /* Load now the new channel settings from Database */ + LoadCurrentChannelSettings(); + + LeaveCriticalSection(&m_critSection); + return true; +} + +bool CPVRManager::OpenRecordedStream(const cPVRRecordingInfoTag* tag) +{ + if (tag == NULL) + return false; + + EnterCriticalSection(&m_critSection); + + /* Check if a channel or recording is already opened and clear it if yes */ + if (m_currentPlayingChannel) + delete m_currentPlayingChannel; + if (m_currentPlayingRecording) + delete m_currentPlayingRecording; + + /* Set the new recording information */ + m_currentPlayingRecording = new CFileItem(*tag); + m_currentPlayingChannel = NULL; + m_scanStart = CTimeUtils::GetTimeMS(); /* Reset the stream scan timer */ + m_playingClientName = m_clients[tag->ClientID()]->GetBackendName() + ":" + m_clients[tag->ClientID()]->GetConnectionString(); + + /* Open the recording stream on the Client */ + bool ret = m_clients[tag->ClientID()]->OpenRecordedStream(*tag); + + LeaveCriticalSection(&m_critSection); + return ret; +} + +CStdString CPVRManager::GetLiveStreamURL(const cPVRChannelInfoTag* tag) +{ + CStdString stream_url; + + EnterCriticalSection(&m_critSection); + + /* Check if a channel or recording is already opened and clear it if yes */ + if (m_currentPlayingChannel) + delete m_currentPlayingChannel; + if (m_currentPlayingRecording) + delete m_currentPlayingRecording; + + /* Set the new channel information */ + m_currentPlayingChannel = new CFileItem(*tag); + m_currentPlayingRecording = NULL; + m_scanStart = CTimeUtils::GetTimeMS(); /* Reset the stream scan timer */ + ResetQualityData(); + + /* Retrieve the dynamily generated stream URL from the Client */ + stream_url = m_clients[tag->ClientID()]->GetLiveStreamURL(*tag); + if (stream_url.IsEmpty()) + { + delete m_currentPlayingChannel; + m_currentPlayingChannel = NULL; + LeaveCriticalSection(&m_critSection); + return ""; + } + + LeaveCriticalSection(&m_critSection); + + return stream_url; +} + +void CPVRManager::CloseStream() +{ + EnterCriticalSection(&m_critSection); + + if (m_currentPlayingChannel) + { + m_playingClientName = ""; + + /* Save channel number in database */ + m_database.Open(); + m_database.UpdateLastChannel(*m_currentPlayingChannel->GetPVRChannelInfoTag()); + m_database.Close(); + + /* Store current settings inside Database */ + SaveCurrentChannelSettings(); + + /* Set quality data to undefined defaults */ + ResetQualityData(); + + /* Close the Client connection */ + if (m_currentPlayingChannel->GetPVRChannelInfoTag()->StreamURL().IsEmpty()) + m_clients[m_currentPlayingChannel->GetPVRChannelInfoTag()->ClientID()]->CloseLiveStream(); + delete m_currentPlayingChannel; + m_currentPlayingChannel = NULL; + } + else if (m_currentPlayingRecording) + { + /* Close the Client connection */ + m_clients[m_currentPlayingRecording->GetPVRRecordingInfoTag()->ClientID()]->CloseRecordedStream(); + delete m_currentPlayingRecording; + m_currentPlayingRecording = NULL; + } + + LeaveCriticalSection(&m_critSection); + return; +} + +int CPVRManager::ReadStream(void* lpBuf, int64_t uiBufSize) +{ + EnterCriticalSection(&m_critSection); + + int bytesReaded = 0; + + /* Check stream for available video or audio data, if after the scantime no stream + is present playback is canceled and returns to the window */ + if (m_scanStart) + { + if (CTimeUtils::GetTimeMS() - m_scanStart > (unsigned int) g_guiSettings.GetInt("pvrplayback.scantime")*1000) + { + CLog::Log(LOGERROR,"PVR: No video or audio data available after %i seconds, playback stopped", g_guiSettings.GetInt("pvrplayback.scantime")); + LeaveCriticalSection(&m_critSection); + return 0; + } + else if (g_application.IsPlayingVideo() || g_application.IsPlayingAudio()) + m_scanStart = NULL; + } + + /* Process LiveTV Reading */ + if (m_currentPlayingChannel) + { + bytesReaded = m_clients[m_currentPlayingChannel->GetPVRChannelInfoTag()->ClientID()]->ReadLiveStream(lpBuf, uiBufSize); + } + /* Process Recording Reading */ + else if (m_currentPlayingRecording) + { + bytesReaded = m_clients[m_currentPlayingRecording->GetPVRRecordingInfoTag()->ClientID()]->ReadRecordedStream(lpBuf, uiBufSize); + } + + LeaveCriticalSection(&m_critSection); + return bytesReaded; +} + +void CPVRManager::DemuxReset() +{ + EnterCriticalSection(&m_critSection); + if (m_currentPlayingChannel) + m_clients[m_currentPlayingChannel->GetPVRChannelInfoTag()->ClientID()]->DemuxReset(); + LeaveCriticalSection(&m_critSection); +} + +void CPVRManager::DemuxAbort() +{ + EnterCriticalSection(&m_critSection); + if (m_currentPlayingChannel) + m_clients[m_currentPlayingChannel->GetPVRChannelInfoTag()->ClientID()]->DemuxAbort(); + LeaveCriticalSection(&m_critSection); +} + +void CPVRManager::DemuxFlush() +{ + EnterCriticalSection(&m_critSection); + if (m_currentPlayingChannel) + m_clients[m_currentPlayingChannel->GetPVRChannelInfoTag()->ClientID()]->DemuxFlush(); + LeaveCriticalSection(&m_critSection); +} + +DemuxPacket* CPVRManager::ReadDemuxStream() +{ + DemuxPacket* packet = NULL; + + EnterCriticalSection(&m_critSection); + if (m_currentPlayingChannel) + { + packet = m_clients[m_currentPlayingChannel->GetPVRChannelInfoTag()->ClientID()]->DemuxRead(); + } + LeaveCriticalSection(&m_critSection); + return packet; +} + +int64_t CPVRManager::LengthStream(void) +{ + int64_t streamLength = 0; + + EnterCriticalSection(&m_critSection); + + if (m_currentPlayingChannel) + { + streamLength = 0; + } + else if (m_currentPlayingRecording) + { + streamLength = m_clients[m_currentPlayingRecording->GetPVRRecordingInfoTag()->ClientID()]->LengthRecordedStream(); + } + + LeaveCriticalSection(&m_critSection); + return streamLength; +} + +int64_t CPVRManager::SeekStream(int64_t iFilePosition, int iWhence/* = SEEK_SET*/) +{ + int64_t streamNewPos = 0; + + EnterCriticalSection(&m_critSection); + + if (m_currentPlayingChannel) + { + streamNewPos = 0; + } + else if (m_currentPlayingRecording) + { + streamNewPos = m_clients[m_currentPlayingRecording->GetPVRRecordingInfoTag()->ClientID()]->SeekRecordedStream(iFilePosition, iWhence); + } + + LeaveCriticalSection(&m_critSection); + return streamNewPos; +} + +int64_t CPVRManager::GetStreamPosition() +{ + int64_t streamPos = 0; + + EnterCriticalSection(&m_critSection); + + if (m_currentPlayingChannel) + { + streamPos = 0; + } + else if (m_currentPlayingRecording) + { + streamPos = m_clients[m_currentPlayingRecording->GetPVRRecordingInfoTag()->ClientID()]->PositionRecordedStream(); + } + + LeaveCriticalSection(&m_critSection); + return streamPos; +} + +bool CPVRManager::UpdateItem(CFileItem& item) +{ + /* Don't update if a recording is played */ + if (item.IsPVRRecording()) + return false; + + if (!item.IsPVRChannel()) + { + CLog::Log(LOGERROR, "CPVRManager: UpdateItem no TVChannelTag given!"); + return false; + } + + g_application.CurrentFileItem() = *m_currentPlayingChannel; + g_infoManager.SetCurrentItem(*m_currentPlayingChannel); + + cPVRChannelInfoTag* tagNow = item.GetPVRChannelInfoTag(); + if (tagNow->IsRadio()) + { + CMusicInfoTag* musictag = item.GetMusicInfoTag(); + if (musictag) + { + musictag->SetURL(tagNow->Path()); + musictag->SetTitle(tagNow->NowTitle()); + musictag->SetArtist(tagNow->Name()); + // musictag->SetAlbum(tag->m_strBouquet); + musictag->SetAlbumArtist(tagNow->Name()); + musictag->SetGenre(tagNow->NowGenre()); + musictag->SetDuration(tagNow->NowDuration()); + musictag->SetLoaded(true); + musictag->SetComment(""); + musictag->SetLyrics(""); + } + } + + cPVRChannelInfoTag* tagPrev = item.GetPVRChannelInfoTag(); + if (tagPrev && tagPrev->Number() != m_LastChannel) + { + m_LastChannel = tagPrev->Number(); + m_LastChannelChanged = CTimeUtils::GetTimeMS(); + if (tagNow->ClientID() == 999) + m_playingClientName = g_localizeStrings.Get(19209); + else if (!tagNow->IsVirtual()) + m_playingClientName = m_clients[tagNow->ClientID()]->GetBackendName() + ":" + m_clients[tagNow->ClientID()]->GetConnectionString(); + else + m_playingClientName = g_localizeStrings.Get(13205); + } + if (CTimeUtils::GetTimeMS() - m_LastChannelChanged >= g_guiSettings.GetInt("pvrplayback.channelentrytimeout") && m_LastChannel != m_PreviousChannel[m_PreviousChannelIndex]) + m_PreviousChannel[m_PreviousChannelIndex ^= 1] = m_LastChannel; + + return false; +} + +bool CPVRManager::ChannelSwitch(unsigned int iChannel) +{ + if (!m_currentPlayingChannel) + return false; + + cPVRChannels *channels; + if (!m_currentPlayingChannel->GetPVRChannelInfoTag()->IsRadio()) + channels = &PVRChannelsTV; + else + channels = &PVRChannelsRadio; + + if (iChannel > channels->size()+1) + { + CGUIDialogOK::ShowAndGetInput(19033,19136,0,0); + return false; + } + + EnterCriticalSection(&m_critSection); + + const cPVRChannelInfoTag* tag = &channels->at(iChannel-1); + + /* Store current settings inside Database */ + SaveCurrentChannelSettings(); + + /* Perform Channelswitch */ + if (tag->StreamURL().IsEmpty() && !m_clients[tag->ClientID()]->SwitchChannel(*tag)) + { + CGUIDialogOK::ShowAndGetInput(19033,0,19136,0); + LeaveCriticalSection(&m_critSection); + return false; + } + + /* Update the Playing channel data and the current epg data if it was not previewed */ + delete m_currentPlayingChannel; + m_currentPlayingChannel = new CFileItem(*tag); + + /* Reset the Audio/Video detection counter */ + m_scanStart = CTimeUtils::GetTimeMS(); + + /* Load now the new channel settings from Database */ + LoadCurrentChannelSettings(); + + /* Set quality data to undefined defaults */ + ResetQualityData(); + + LeaveCriticalSection(&m_critSection); + return true; +} + +bool CPVRManager::ChannelUp(unsigned int *newchannel, bool preview/* = false*/) +{ + if (m_currentPlayingChannel) + { + cPVRChannels *channels; + if (!m_currentPlayingChannel->GetPVRChannelInfoTag()->IsRadio()) + channels = &PVRChannelsTV; + else + channels = &PVRChannelsRadio; + + EnterCriticalSection(&m_critSection); + + /* Store current settings inside Database */ + SaveCurrentChannelSettings(); + + unsigned int currentTVChannel = m_currentPlayingChannel->GetPVRChannelInfoTag()->Number(); + const cPVRChannelInfoTag* tag; + for (unsigned int i = 1; i < channels->size(); i++) + { + currentTVChannel += 1; + + if (currentTVChannel > channels->size()) + currentTVChannel = 1; + + tag = &channels->at(currentTVChannel-1); + + if ((m_CurrentGroupID != -1) && (m_CurrentGroupID != tag->GroupID())) + continue; + + /* Perform Channelswitch */ + if (preview) + { + /* Update the Playing channel data and the current epg data */ + delete m_currentPlayingChannel; + m_currentPlayingChannel = new CFileItem(*tag); + + /* Load now the new channel settings from Database */ + LoadCurrentChannelSettings(); + + *newchannel = currentTVChannel; + LeaveCriticalSection(&m_critSection); + return true; + } + else if (!tag->StreamURL().IsEmpty() || m_clients[tag->ClientID()]->SwitchChannel(*tag)) + { + /* Update the Playing channel data and the current epg data */ + delete m_currentPlayingChannel; + m_currentPlayingChannel = new CFileItem(*tag); + m_scanStart = CTimeUtils::GetTimeMS(); + + /* Load now the new channel settings from Database */ + LoadCurrentChannelSettings(); + + /* Set quality data to undefined defaults */ + ResetQualityData(); + + *newchannel = currentTVChannel; + LeaveCriticalSection(&m_critSection); + return true; + } + } + LeaveCriticalSection(&m_critSection); + } + + return false; +} + +bool CPVRManager::ChannelDown(unsigned int *newchannel, bool preview/* = false*/) +{ + if (m_currentPlayingChannel) + { + cPVRChannels *channels; + if (!m_currentPlayingChannel->GetPVRChannelInfoTag()->IsRadio()) + channels = &PVRChannelsTV; + else + channels = &PVRChannelsRadio; + + EnterCriticalSection(&m_critSection); + + /* Store current settings inside Database */ + SaveCurrentChannelSettings(); + + int currentTVChannel = m_currentPlayingChannel->GetPVRChannelInfoTag()->Number(); + const cPVRChannelInfoTag* tag; + for (unsigned int i = 1; i < channels->size(); i++) + { + currentTVChannel -= 1; + + if (currentTVChannel <= 0) + currentTVChannel = channels->size(); + + tag = &channels->at(currentTVChannel-1); + + if ((m_CurrentGroupID != -1) && (m_CurrentGroupID != tag->GroupID())) + continue; + + /* Perform Channelswitch */ + if (preview) + { + /* Update the Playing channel data and the current epg data */ + delete m_currentPlayingChannel; + m_currentPlayingChannel = new CFileItem(*tag); + + /* Load now the new channel settings from Database */ + LoadCurrentChannelSettings(); + + *newchannel = currentTVChannel; + LeaveCriticalSection(&m_critSection); + return true; + } + else if (!tag->StreamURL().IsEmpty() || m_clients[tag->ClientID()]->SwitchChannel(*tag)) + { + /* Update the Playing channel data and the current epg data */ + delete m_currentPlayingChannel; + m_currentPlayingChannel = new CFileItem(*tag); + m_scanStart = CTimeUtils::GetTimeMS(); + + /* Load now the new channel settings from Database */ + LoadCurrentChannelSettings(); + + /* Set quality data to undefined defaults */ + ResetQualityData(); + + *newchannel = currentTVChannel; + LeaveCriticalSection(&m_critSection); + return true; + } + } + LeaveCriticalSection(&m_critSection); + } + return false; +} + +int CPVRManager::GetTotalTime() +{ + if (m_currentPlayingChannel) + return m_currentPlayingChannel->GetPVRChannelInfoTag()->NowDuration() * 1000; + + return 0; +} + +int CPVRManager::GetStartTime() +{ + /* If it is called without a opened TV channel return with NULL */ + if (!m_currentPlayingChannel) + return 0; + + /* GetStartTime() is frequently called by DVDPlayer during playback of Live TV, so we + * check here if the end of the current running event is reached, if yes update the + * playing file item with the newest EPG data of the now running event. + */ + cPVRChannelInfoTag* tag = m_currentPlayingChannel->GetPVRChannelInfoTag(); + if (tag->NowEndTime() < CDateTime::GetCurrentDateTime() || tag->NowTitle() == g_localizeStrings.Get(19055)) + { + EnterCriticalSection(&m_critSection); + UpdateItem(*m_currentPlayingChannel); + LeaveCriticalSection(&m_critSection); + } + + /* Calculate here the position we have of the running live TV event. + * "position in ms" = ("current local time" - "event start local time") * 1000 + */ + CDateTimeSpan time = CDateTime::GetCurrentDateTime() - tag->NowStartTime(); + return time.GetDays() * 1000 * 60 * 60 * 24 + + time.GetHours() * 1000 * 60 * 60 + + time.GetMinutes() * 1000 * 60 + + time.GetSeconds() * 1000; +} + +CPVRManager g_PVRManager; diff --git a/xbmc/PVRManager.h b/xbmc/PVRManager.h new file mode 100644 index 0000000000..32253479d2 --- /dev/null +++ b/xbmc/PVRManager.h @@ -0,0 +1,409 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "FileItem.h" +#include "TVDatabase.h" +#include "addons/Addon.h" +#include "addons/PVRClient.h" +#include "addons/AddonManager.h" +#include "utils/Thread.h" +#include "utils/PVRChannels.h" +#include "utils/PVRRecordings.h" +#include "utils/PVRTimers.h" + +#include +#include + +typedef std::map< long, boost::shared_ptr > CLIENTMAP; +typedef std::map< long, boost::shared_ptr >::iterator CLIENTMAPITR; +typedef std::map< long, PVR_SERVERPROPS > CLIENTPROPS; +typedef std::map< long, PVR_STREAMPROPS > STREAMPROPS; + +class CPVRManager : IPVRClientCallback + , public ADDON::IAddonMgrCallback + , private CThread +{ +public: + CPVRManager(); + ~CPVRManager(); + + /*! \name Startup functions + */ + void Start(); + void Stop(); + + /*! \name External Client access functions + */ + unsigned long GetFirstClientID(); + CLIENTMAP* Clients() { return &m_clients; } + CTVDatabase *GetTVDatabase() { return &m_database; } + + /*! \name Addon related functions + */ + bool RequestRestart(ADDON::AddonPtr addon, bool datachanged); + bool RequestRemoval(ADDON::AddonPtr addon); + void OnClientMessage(const long clientID, const PVR_EVENT clientEvent, const char* msg); + + /*! \name GUIInfoManager functions + */ + void SyncInfo(); + const char* TranslateCharInfo(DWORD dwInfo); + int TranslateIntInfo(DWORD dwInfo); + bool TranslateBoolInfo(DWORD dwInfo); + + /*! \name General functions + */ + + /*! \brief Open a selection Dialog and start a channelscan on + selected Client. + */ + void StartChannelScan(); + + /*! \brief Get info about a running channel scan + \return true if scan is running + */ + bool ChannelScanRunning() { return m_bChannelScanRunning; } + + /*! \brief Set the TV Database to it's initial state and delete all + the data inside. + */ + void ResetDatabase(); + + /*! \brief Set the EPG data inside TV Database to it's initial state + and reload it from Clients. + */ + void ResetEPG(); + + /*! \brief Returns true if a tv channel is playing + \return true during TV playback + */ + bool IsPlayingTV(); + + /*! \brief Returns true if a radio channel is playing + \return true during radio playback + */ + bool IsPlayingRadio(); + + /*! \brief Returns true if a recording is playing over the client + \return true during recording playback + */ + bool IsPlayingRecording(); + + /*! \brief Returns the properties of the current playing client + \return pointer to properties (NULL if no stream is playing) + */ + PVR_SERVERPROPS *GetCurrentClientProps(); + + /*! \brief Return the current playing client identifier + \return the identifier or -1 if no playing item ist present + */ + long GetCurrentPlayingClientID(); + + /*! \brief Returns the properties of the given client identifier + \param clientID The identifier of the client + \return pointer to properties (NULL if no stream is playing) + */ + PVR_SERVERPROPS *GetClientProps(int clientID) { return &m_clientsProps[clientID]; } + + /*! \brief Returns the properties of the current playing stream content + \return pointer to properties (NULL if no stream is playing) + */ + PVR_STREAMPROPS *GetCurrentStreamProps(); + + /*! \brief Returns the current playing file item + \return pointer to file item class (NULL if no stream is playing) + */ + CFileItem *GetCurrentPlayingItem(); + + /*! \brief Get the input format name of the current playing channel + \return the name of the input format or empty if unknown + */ + CStdString GetCurrentInputFormat(); + + /*! \brief Returns the current playing channel number + \param number Address to integer value to write playing channel number + \param radio Address to boolean value to set it true if it is a radio channel + \return true if channel is playing + */ + bool GetCurrentChannel(int *number, bool *radio); + + /*! \brief Returns if a minimum one client is active + \return true if minimum one client is started + */ + bool HaveActiveClients(); + + /*! \brief Returns the presence of PVR specific Menu entries + \param clientID identifier of the client to ask or < 0 for playing channel + \return true if menu hooks are present + */ + bool HaveMenuHooks(long clientID); + + /*! \brief Open selection and progress pvr actions + \param clientID identifier to process + */ + void ProcessMenuHooks(long clientID); + + /*! \brief Returns the previous selected channel + \return the number of the previous channel or -1 if no channel was selected before + */ + int GetPreviousChannel(); + + /*! \brief Get the possibility to start a recording of the current playing + channel. + \return true if a recording can be started + */ + bool CanInstantRecording(); + + /*! \brief Get the presence of timers. + \return true if timers are present + */ + bool HasTimer() { return m_hasTimers; } + + /*! \brief Get the presence of a running recording. + \return true if a recording is running + */ + bool IsRecording() { return m_isRecording; } + + /*! \brief Get the presence of a running recording on current playing channel. + \return true if a recording is running + */ + bool IsRecordingOnPlayingChannel(); + + /*! \brief Start a instant recording on playing channel + \return true if it success + */ + bool StartRecordingOnPlayingChannel(bool bOnOff); + + /*! \brief Set the current playing group ID, used to load the right channel + lists. + */ + void SetPlayingGroup(int GroupId); + + /*! \brief Get the current playing group ID, used to load the + right channel lists + \return current playing group identifier + */ + int GetPlayingGroup(); + + /*! \brief Trigger a recordings list update + */ + void TriggerRecordingsUpdate(bool force=true); + + /*! \brief Trigger a timer list update + */ + void TriggerTimersUpdate(bool force=true); + + + /*! \name Stream reading functions + PVR Client internal input stream access, is used if + inside PVR_SERVERPROPS the HandleInputStream is true + */ + + /*! \brief Open the Channel stream on the given channel info tag + \return true if opening was succesfull + */ + bool OpenLiveStream(const cPVRChannelInfoTag* tag); + + /*! \brief Open a recording by a index number passed to this function. + \return true if opening was succesfull + */ + bool OpenRecordedStream(const cPVRRecordingInfoTag* tag); + + /*! \brief Returns runtime generated stream URL + Returns a during runtime generated stream URL from the PVR Client. + Backends like Mediaportal generates the URL for the RTSP streams + during opening. + \return Stream URL + */ + CStdString GetLiveStreamURL(const cPVRChannelInfoTag* tag); + + /*! \brief Close the stream on the PVR Client. + */ + void CloseStream(); + + /*! \brief Read the stream + Read the stream to the buffer pointer passed defined by "buf" and + a maximum site passed with "buf_size". + \return the amount of readed bytes is returned + */ + int ReadStream(void* lpBuf, int64_t uiBufSize); + + /*! \brief Reset the client demuxer + */ + void DemuxReset(); + + /*! \brief Aborts any internal reading that might be stalling main thread + * NOTICE - this can be called from another thread + */ + void DemuxAbort(); + + /*! \brief Flush the demuxer, if any data is kept in buffers, this should be freed now + */ + void DemuxFlush(); + + /*! \brief Read the stream from demuxer + Read the stream from pvr client own demuxer + \return a allocated demuxer packet + */ + DemuxPacket* ReadDemuxStream(); + + /*! \brief Return the filesize of the current running stream. + Limited to recordings playback at the moment. + \return the size of the actual running stream + */ + int64_t LengthStream(void); + + /*! \brief Seek to a position inside stream + It is currently limited to playback of recordings, no live tv seek is possible. + \param pos the position to seek to + \param whence how we want to seek ("new position=pos", or "new position=pos+actual postion" or "new position=filesize-pos + \return the new position inside stream + */ + int64_t SeekStream(int64_t iFilePosition, int iWhence = SEEK_SET); + + /*! \brief Get the current playing position in stream + \return the current position inside stream + */ + int64_t GetStreamPosition(void); + + /*! \brief Update the current running channel + It is called during a channel change to refresh the global file item. + \param item the file item with the current running item + \return true if success + */ + bool UpdateItem(CFileItem& item); + + /*! \brief Switch to a channel by the given number + Used only for live TV channels + \param channel the channel number to switch + \return true if success + */ + bool ChannelSwitch(unsigned int channel); + + /*! \brief Switch to the next channel in list + It switch to the next channel and return the new channel to + the pointer passed by this function, if preview is true no client + channel switch is performed, only the data of the new channel is + loaded, is used to show new event information in fast channel zapping. + \param newchannel pointer to store the new selected channel number + \param preview if set the channel is only switched inside XBMC without client action + \return true if success + */ + bool ChannelUp(unsigned int *newchannel, bool preview = false); + + /*! \brief Switch the to previous channel in list + It switch to the previous channel and return the new channel to + the pointer passed by this function, if preview is true no client + channel switch is performed, only the data of the new channel is + loaded, is used to show new event information in fast channel zapping. + \param newchannel pointer to store the new selected channel number + \param preview if set the channel is only switched inside XBMC without client action + \return true if success + */ + bool ChannelDown(unsigned int *newchannel, bool preview = false); + + /*! \brief Returns the duration of the current playing channel + Used only for live TV channels + \return duration in milliseconds or NULL if no channel is playing. + */ + int GetTotalTime(); + + /*! \brief Returns the current position from 0 in milliseconds + Used only for live TV channels + \return position in milliseconds or NULL if no channel is playing. + */ + int GetStartTime(); + +protected: + virtual void Process(); + +private: + /*--- Handling functions ---*/ + bool LoadClients(); + void GetClientProperties(); /*! \brief call GetClientProperties(long clientID) for each client connected */ + void GetClientProperties(long clientID); /*! \brief request the PVR_SERVERPROPS struct from each client */ + void SaveCurrentChannelSettings(); /*! \brief Write the current Video and Audio settings of + playing channel to the TV Database */ + void LoadCurrentChannelSettings(); /*! \brief Read and set the Video and Audio settings of + playing channel from the TV Database */ + void ResetQualityData(); /*! \brief Reset the Signal Quality data structure to initial values */ + + /*--- General PVRManager data ---*/ + CLIENTMAP m_clients; /* pointer to each enabled client's interface */ + CLIENTPROPS m_clientsProps; /* store the properties of each client locally */ + STREAMPROPS m_streamProps; + CTVDatabase m_database; + CRITICAL_SECTION m_critSection; + bool m_bFirstStart; /* Is set if this is first startup of PVRManager */ + bool m_bChannelScanRunning; /* Is set if a channel scan is running */ + + /*--- GUIInfoManager information data ---*/ + DWORD m_infoToggleStart; /* Time to toogle pvr infos like in System info */ + unsigned int m_infoToggleCurrent; /* The current item showed by the GUIInfoManager */ + DWORD m_recordingToggleStart; /* Time to toogle currently running pvr recordings */ + unsigned int m_recordingToggleCurrent; /* The current item showed by the GUIInfoManager */ + + CStdString m_nextRecordingDateTime; + CStdString m_nextRecordingChannel; + CStdString m_nextRecordingTitle; + std::vector m_nowRecordingDateTime; + std::vector m_nowRecordingChannel; + std::vector m_nowRecordingTitle; + CStdString m_backendName; + CStdString m_backendVersion; + CStdString m_backendHost; + CStdString m_backendDiskspace; + CStdString m_backendTimers; + CStdString m_backendRecordings; + CStdString m_backendChannels; + CStdString m_totalDiskspace; + CStdString m_nextTimer; + CStdString m_playingDuration; + CStdString m_playingTime; + CStdString m_playingClientName; + bool m_isRecording; + bool m_hasRecordings; + bool m_hasTimers; + + /*--- Thread Update Timers ---*/ + int m_LastTVChannelCheck; + int m_LastRadioChannelCheck; + int m_LastRecordingsCheck; + int m_LastTimersCheck; + int m_LastEPGUpdate; + int m_LastEPGScan; + + /*--- Previous Channel data ---*/ + int m_PreviousChannel[2]; + int m_PreviousChannelIndex; + int m_LastChannel; + long m_LastChannelChanged; + + /*--- Stream playback data ---*/ + CFileItem *m_currentPlayingChannel; /* The current playing channel or NULL */ + CFileItem *m_currentPlayingRecording; /* The current playing recording or NULL */ + int m_CurrentGroupID; /* The current selected Channel group list */ + DWORD m_scanStart; /* Scan start time to check for non present streams */ + PVR_SIGNALQUALITY m_qualityInfo; /* Stream quality information */ +}; + +extern CPVRManager g_PVRManager; diff --git a/xbmc/Settings.cpp b/xbmc/Settings.cpp index 7733800caf..a5ed3ab178 100644 --- a/xbmc/Settings.cpp +++ b/xbmc/Settings.cpp @@ -109,7 +109,7 @@ void CSettings::Initialize() m_pictureExtensions = ".png|.jpg|.jpeg|.bmp|.gif|.ico|.tif|.tiff|.tga|.pcx|.cbz|.zip|.cbr|.rar|.m3u|.dng|.nef|.cr2|.crw|.orf|.arw|.erf|.3fr|.dcr|.x3f|.mef|.raf|.mrw|.pef|.sr2|.rss"; m_musicExtensions = ".nsv|.m4a|.flac|.aac|.strm|.pls|.rm|.rma|.mpa|.wav|.wma|.ogg|.mp3|.mp2|.m3u|.mod|.amf|.669|.dmf|.dsm|.far|.gdm|.imf|.it|.m15|.med|.okt|.s3m|.stm|.sfx|.ult|.uni|.xm|.sid|.ac3|.dts|.cue|.aif|.aiff|.wpl|.ape|.mac|.mpc|.mp+|.mpp|.shn|.zip|.rar|.wv|.nsf|.spc|.gym|.adx|.dsp|.adp|.ymf|.ast|.afc|.hps|.xsp|.xwav|.waa|.wvs|.wam|.gcm|.idsp|.mpdsp|.mss|.spt|.rsd|.mid|.kar|.sap|.cmc|.cmr|.dmc|.mpt|.mpd|.rmt|.tmc|.tm8|.tm2|.oga|.url|.pxml|.tta|.rss|.cm3|.cms|.dlt|.brstm"; - m_videoExtensions = ".m4v|.3g2|.3gp|.nsv|.tp|.ts|.ty|.strm|.pls|.rm|.rmvb|.m3u|.ifo|.mov|.qt|.divx|.xvid|.bivx|.vob|.nrg|.img|.iso|.pva|.wmv|.asf|.asx|.ogm|.m2v|.avi|.bin|.dat|.mpg|.mpeg|.mp4|.mkv|.avc|.vp3|.svq3|.nuv|.viv|.dv|.fli|.flv|.rar|.001|.wpl|.zip|.vdr|.dvr-ms|.xsp|.mts|.m2t|.m2ts|.evo|.ogv|.sdp|.avs|.rec|.url|.pxml|.vc1|.h264|.rcv|.rss|.mpls|.webm|.bdmv"; + m_videoExtensions = ".m4v|.3g2|.3gp|.nsv|.tp|.ts|.ty|.strm|.pls|.rm|.rmvb|.m3u|.ifo|.mov|.qt|.divx|.xvid|.bivx|.vob|.nrg|.img|.iso|.pva|.wmv|.asf|.asx|.ogm|.m2v|.avi|.bin|.dat|.mpg|.mpeg|.mp4|.mkv|.avc|.vp3|.svq3|.nuv|.viv|.dv|.fli|.flv|.rar|.001|.wpl|.zip|.vdr|.dvr-ms|.xsp|.mts|.m2t|.m2ts|.evo|.ogv|.sdp|.avs|.rec|.url|.pxml|.vc1|.h264|.rcv|.rss|.pvr|.mpls|.webm|.bdmv"; // internal music extensions m_musicExtensions += "|.sidstream|.oggstream|.nsfstream|.asapstream|.cdda"; @@ -693,7 +693,7 @@ bool CSettings::LoadSettings(const CStdString& strSettingsFile) if (pElement) { int interlaceMethod; - GetInteger(pElement, "interlacemethod", interlaceMethod, VS_INTERLACEMETHOD_NONE, VS_INTERLACEMETHOD_NONE, VS_INTERLACEMETHOD_INVERSE_TELECINE); + GetInteger(pElement, "interlacemethod", interlaceMethod, VS_INTERLACEMETHOD_NONE, VS_INTERLACEMETHOD_NONE, VS_INTERLACEMETHOD_AUTO_ION); m_defaultVideoSettings.m_InterlaceMethod = (EINTERLACEMETHOD)interlaceMethod; int scalingMethod; GetInteger(pElement, "scalingmethod", scalingMethod, VS_SCALINGMETHOD_LINEAR, VS_SCALINGMETHOD_NEAREST, VS_SCALINGMETHOD_AUTO); diff --git a/xbmc/SortFileItem.cpp b/xbmc/SortFileItem.cpp index 0ebe08552d..9cf4c1d3e3 100644 --- a/xbmc/SortFileItem.cpp +++ b/xbmc/SortFileItem.cpp @@ -24,6 +24,8 @@ #include "StringUtils.h" #include "VideoInfoTag.h" #include "MusicInfoTag.h" +#include "utils/PVREpg.h" +#include "utils/PVRTimers.h" #include "FileItem.h" #include "URL.h" #include "utils/log.h" @@ -153,7 +155,13 @@ void SSortFileItem::ByDate(CFileItemPtr &item) if (!item) return; CStdString label; - label.Format("%s %s", item->m_dateTime.GetAsDBDateTime().c_str(), item->GetLabel().c_str()); + if (item->IsEPG()) + label.Format("%s %s", item->GetEPGInfoTag()->Start().GetAsDBDateTime().c_str(), item->GetLabel().c_str()); + else if (item->IsPVRTimer()) + label.Format("%s %s", item->GetPVRTimerInfoTag()->Start().GetAsDBDateTime().c_str(), item->GetLabel().c_str()); + else + label.Format("%s %s", item->m_dateTime.GetAsDBDateTime().c_str(), item->GetLabel().c_str()); + item->SetSortLabel(label); } @@ -470,6 +478,16 @@ void SSortFileItem::ByProductionCode(CFileItemPtr &item) item->SetSortLabel(item->GetVideoInfoTag()->m_strProductionCode); } +void SSortFileItem::ByChannel(CFileItemPtr &item) +{ + if (!item) return; + + if (item->IsEPG()) + item->SetSortLabel(item->GetEPGInfoTag()->ChannelName()); + else if (item->IsPVRChannel()) + item->SetSortLabel(item->GetPVRChannelInfoTag()->Name()); +} + void SSortFileItem::ByBitrate(CFileItemPtr &item) { if (!item) return; diff --git a/xbmc/SortFileItem.h b/xbmc/SortFileItem.h index f03c14fe3a..9fda33ea66 100644 --- a/xbmc/SortFileItem.h +++ b/xbmc/SortFileItem.h @@ -57,6 +57,7 @@ struct SSortFileItem static void BySongTrackNum(CFileItemPtr &item); static void BySongDuration(CFileItemPtr &item); static void BySongRating(CFileItemPtr &item); + static void ByChannel(CFileItemPtr &item); static void ByProgramCount(CFileItemPtr &item); @@ -116,6 +117,7 @@ typedef enum { SORT_METHOD_LABEL_IGNORE_FOLDERS, SORT_METHOD_LASTPLAYED, SORT_METHOD_UNSORTED, + SORT_METHOD_CHANNEL, SORT_METHOD_BITRATE, SORT_METHOD_MAX } SORT_METHOD; diff --git a/xbmc/TVDatabase.cpp b/xbmc/TVDatabase.cpp new file mode 100644 index 0000000000..920387ac46 --- /dev/null +++ b/xbmc/TVDatabase.cpp @@ -0,0 +1,1611 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "TVDatabase.h" +#include "utils/GUIInfoManager.h" +#include "AdvancedSettings.h" +#include "utils/log.h" + +using namespace std; + +using namespace dbiplus; + +CTVDatabase::CTVDatabase(void) +{ + oneWriteSQLString = ""; +} + +CTVDatabase::~CTVDatabase(void) +{ +} + +bool CTVDatabase::Open() +{ + return CDatabase::Open(g_advancedSettings.m_databaseTV); +} + +bool CTVDatabase::CreateTables() +{ + try + { + CDatabase::CreateTables(); + + CLog::Log(LOGINFO, "TV-Database: Creating tables"); + + CLog::Log(LOGINFO, "TV-Database: Creating Clients table"); + m_pDS->exec("CREATE TABLE Clients (idClient integer primary key, Name text, GUID text)\n"); + + CLog::Log(LOGINFO, "TV-Database: Creating LastChannel table"); + m_pDS->exec("CREATE TABLE LastChannel (idChannel integer primary key, Number integer, Name text)\n"); + + CLog::Log(LOGINFO, "TV-Database: Creating LastEPGScan table"); + m_pDS->exec("CREATE TABLE LastEPGScan (idScan integer primary key, ScanTime datetime)\n"); + + CLog::Log(LOGINFO, "TV-Database: Creating GuideData table"); + m_pDS->exec("CREATE TABLE GuideData (idDatabaseBroadcast integer primary key, idUniqueBroadcast integer, idChannel integer, StartTime datetime, " + "EndTime datetime, strTitle text, strPlotOutline text, strPlot text, GenreType integer, GenreSubType integer, " + "firstAired datetime, parentalRating integer, starRating integer, notify integer, seriesNum text, " + "episodeNum text, episodePart text, episodeName text)\n"); + m_pDS->exec("CREATE UNIQUE INDEX ix_GuideData on GuideData(idChannel, StartTime desc)\n"); /// pointless? + + CLog::Log(LOGINFO, "TV-Database: Creating Channels table"); + m_pDS->exec("CREATE TABLE Channels (idChannel integer primary key, Name text, Number integer, ClientName text, " + "ClientNumber integer, idClient integer, UniqueId integer, IconPath text, GroupID integer, countWatched integer, " + "timeWatched integer, lastTimeWatched datetime, encryption integer, radio bool, hide bool, grabEpg bool, EpgGrabber text, " + "lastGrabTime datetime, Virtual bool, strInputFormat text, strStreamURL text)\n"); + + CLog::Log(LOGINFO, "TV-Database: Creating ChannelLinkageMap table"); + m_pDS->exec("CREATE TABLE ChannelLinkageMap (idMapping integer primary key, idPortalChannel integer, idLinkedChannel integer)\n"); + + CLog::Log(LOGINFO, "TV-Database: Creating ChannelGroup table"); + m_pDS->exec("CREATE TABLE ChannelGroup (idGroup integer primary key, groupName text, sortOrder integer)\n"); + + CLog::Log(LOGINFO, "TV-Database: Creating RadioChannelGroup table"); + m_pDS->exec("CREATE TABLE RadioChannelGroup (idGroup integer primary key, groupName text, sortOrder integer)\n"); + + CLog::Log(LOGINFO, "TV-Database: Creating ChannelSettings table"); + m_pDS->exec("CREATE TABLE ChannelSettings ( idChannel integer primary key, Deinterlace integer," + "ViewMode integer,ZoomAmount float, PixelRatio float, AudioStream integer, SubtitleStream integer," + "SubtitleDelay float, SubtitlesOn bool, Brightness float, Contrast float, Gamma float," + "VolumeAmplification float, AudioDelay float, OutputToAllSpeakers bool, Crop bool, CropLeft integer," + "CropRight integer, CropTop integer, CropBottom integer, Sharpness float, NoiseReduction float)\n"); + m_pDS->exec("CREATE UNIQUE INDEX ix_ChannelSettings ON ChannelSettings (idChannel)\n"); + } + catch (...) + { + CLog::Log(LOGERROR, "%s unable to create TV tables:%i", __FUNCTION__, (int)GetLastError()); + return false; + } + + return true; +} + +bool CTVDatabase::UpdateOldVersion(int iVersion) +{ + BeginTransaction(); + + try + { + + } + catch (...) + { + CLog::Log(LOGERROR, "Error attempting to update the database version!"); + RollbackTransaction(); + return false; + } + CommitTransaction(); + return true; +} + +CDateTime CTVDatabase::GetLastEPGScanTime() +{ + try + { + if (NULL == m_pDB.get()) return -1; + if (NULL == m_pDS.get()) return -1; + + CStdString SQL=FormatSQL("select * from LastEPGScan"); + + m_pDS->query(SQL.c_str()); + + if (m_pDS->num_rows() > 0) + { + CDateTime lastTime; + lastTime.SetFromDBDateTime(m_pDS->fv("ScanTime").get_asString()); + m_pDS->close(); + return lastTime; + } + else + { + m_pDS->close(); + return NULL; + } + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + } + + return -1; +} + +bool CTVDatabase::UpdateLastEPGScan(const CDateTime lastScan) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString SQL=FormatSQL("select * from LastEPGScan"); + m_pDS->query(SQL.c_str()); + + if (m_pDS->num_rows() > 0) + { + m_pDS->close(); + // update the item + CStdString SQL=FormatSQL("update LastEPGScan set ScanTime='%s'", lastScan.GetAsDBDateTime().c_str()); + + m_pDS->exec(SQL.c_str()); + return true; + } + else // add the items + { + m_pDS->close(); + SQL=FormatSQL("insert into LastEPGScan ( ScanTime ) values ('%s')\n", lastScan.GetAsDBDateTime().c_str()); + m_pDS->exec(SQL.c_str()); + return true; + } + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } +} + +int CTVDatabase::GetLastChannel() +{ + try + { + if (NULL == m_pDB.get()) return -1; + if (NULL == m_pDS.get()) return -1; + + CStdString SQL=FormatSQL("select * from LastChannel"); + + m_pDS->query(SQL.c_str()); + + if (m_pDS->num_rows() > 0) + { + int channelId = m_pDS->fv("idChannel").get_asInt(); + + m_pDS->close(); + return channelId; + } + else + { + m_pDS->close(); + return -1; + } + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + } + + return -1; +} + +bool CTVDatabase::UpdateLastChannel(const cPVRChannelInfoTag &info) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + if (info.ChannelID() < 0) // no match found, update required + { + return false; + } + + CStdString SQL; + + SQL=FormatSQL("select * from LastChannel"); + m_pDS->query(SQL.c_str()); + + if (m_pDS->num_rows() > 0) + { + m_pDS->close(); + // update the item + CStdString SQL=FormatSQL("update LastChannel set Number=%i,Name='%s',idChannel=%i", info.Number(), info.Name().c_str(), info.ChannelID()); + + m_pDS->exec(SQL.c_str()); + return true; + } + else // add the items + { + m_pDS->close(); + SQL=FormatSQL("insert into LastChannel ( idChannel,Number,Name ) values ('%i','%i','%s')\n", info.ChannelID(), info.Number(), info.Name().c_str()); + m_pDS->exec(SQL.c_str()); + return true; + } + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%li) failed", __FUNCTION__, info.ChannelID()); + return false; + } +} + +bool CTVDatabase::EraseClients() +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString strSQL=FormatSQL("delete from Clients"); + m_pDS->exec(strSQL.c_str()); + strSQL=FormatSQL("delete from LastChannel"); + m_pDS->exec(strSQL.c_str()); + strSQL=FormatSQL("delete from LastEPGScan"); + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } +} + +long CTVDatabase::AddClient(const CStdString &client, const CStdString &guid) +{ + try + { + if (NULL == m_pDB.get()) return -1; + if (NULL == m_pDS.get()) return -1; + + long clientId = GetClientId(guid); + if (clientId < 0) + { + CStdString SQL=FormatSQL("insert into Clients (idClient, Name, GUID) values (NULL, '%s', '%s')\n", client.c_str(), guid.c_str()); + m_pDS->exec(SQL.c_str()); + clientId = (long)m_pDS->lastinsertid(); + + CLog::Log(LOGNOTICE, "TVDatabase: Added new PVR Client ID '%li' with Name '%s' and GUID '%s'", clientId, client.c_str(), guid.c_str()); + } + + return clientId; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (Name: %s, GUID: %s) failed", __FUNCTION__, client.c_str(), guid.c_str()); + } + + return -1; +} + +long CTVDatabase::GetClientId(const CStdString& guid) +{ + CStdString SQL; + + try + { + long clientId = -1; + + if (NULL == m_pDB.get()) return -1; + if (NULL == m_pDS.get()) return -1; + + SQL = FormatSQL("select idClient from Clients where GUID like '%s'", guid.c_str()); + + m_pDS->query(SQL.c_str()); + + if (!m_pDS->eof()) + clientId = m_pDS->fv("Clients.idClient").get_asInt(); + + m_pDS->close(); + + return clientId; + } + catch (...) + { + CLog::Log(LOGERROR, "%s unable to get ClientId (%s)", __FUNCTION__, SQL.c_str()); + } + + return -1; +} + +bool CTVDatabase::EraseEPG() +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString strSQL=FormatSQL("delete from GuideData"); + + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } +} + +bool CTVDatabase::EraseChannelEPG(long channelID) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString strSQL=FormatSQL("delete from GuideData WHERE GuideData.idChannel = '%u'", channelID); + + m_pDS->exec(strSQL.c_str()); + + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } +} + +bool CTVDatabase::EraseChannelEPGAfterTime(long channelID, CDateTime after) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString strSQL=FormatSQL("delete from GuideData WHERE GuideData.idChannel = '%u' AND GuideData.EndTime > '%s'", channelID, after.GetAsDBDateTime().c_str()); + + m_pDS->exec(strSQL.c_str()); + + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } +} + +bool CTVDatabase::EraseOldEPG() +{ + //delete programs from database that are more than 1 day old + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CDateTime yesterday = CDateTime::GetCurrentDateTime()-CDateTimeSpan(1, 0, 0, 0); + CStdString strSQL = FormatSQL("DELETE FROM GuideData WHERE EndTime < '%s'", yesterday.GetAsDBDateTime().c_str()); + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } +} + +long CTVDatabase::AddEPGEntry(const cPVREPGInfoTag &info, bool oneWrite, bool firstWrite, bool lastWrite) +{ + try + { + if (NULL == m_pDB.get()) return -1; + if (NULL == m_pDS.get()) return -1; + + if (!oneWrite && firstWrite) + m_pDS->insert(); + + if (info.GetUniqueBroadcastID() > 0) + { + CStdString SQL = FormatSQL("INSERT INTO GuideData (idDatabaseBroadcast, idChannel, StartTime, " + "EndTime, strTitle, strPlotOutline, strPlot, GenreType, GenreSubType, " + "firstAired, parentalRating, starRating, notify, seriesNum, " + "episodeNum, episodePart, episodeName, idUniqueBroadcast) " + "VALUES (NULL,'%i','%s','%s','%s','%s','%s','%i','%i','%s','%i','%i','%i','%s','%s','%s','%s','%i')\n", + info.ChannelID(), info.Start().GetAsDBDateTime().c_str(), info.End().GetAsDBDateTime().c_str(), + info.Title().c_str(), info.PlotOutline().c_str(), info.Plot().c_str(), info.GenreType(), info.GenreSubType(), + info.FirstAired().GetAsDBDateTime().c_str(), info.ParentalRating(), info.StarRating(), info.Notify(), + info.SeriesNum().c_str(), info.EpisodeNum().c_str(), info.EpisodePart().c_str(), info.EpisodeName().c_str(), + info.GetUniqueBroadcastID()); + + if (oneWrite) + { + m_pDS->exec(SQL.c_str()); + return (long)m_pDS->lastinsertid(); + } + else + m_pDS->add_insert_sql(SQL); + } + + if (!oneWrite && lastWrite) + { + m_pDS->post(); + m_pDS->clear_insert_sql(); + } + return 0; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, info.Title().c_str()); + } + + return -1; +} + +bool CTVDatabase::UpdateEPGEntry(const cPVREPGInfoTag &info, bool oneWrite, bool firstWrite, bool lastWrite) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + if (NULL == m_pDS2.get()) return false; + + if (!oneWrite && firstWrite) + m_pDS2->insert(); + + if (info.GetUniqueBroadcastID() > 0) + { + CStdString SQL = FormatSQL("select idDatabaseBroadcast from GuideData WHERE (GuideData.idUniqueBroadcast = '%u' OR GuideData.StartTime = '%s') AND GuideData.idChannel = '%u'", info.GetUniqueBroadcastID(), info.Start().GetAsDBDateTime().c_str(), info.ChannelID()); + m_pDS->query(SQL.c_str()); + + if (m_pDS->num_rows() > 0) + { + int id = m_pDS->fv("idDatabaseBroadcast").get_asInt(); + m_pDS->close(); + // update the item + SQL = FormatSQL("update GuideData set idChannel=%i, StartTime='%s', EndTime='%s', strTitle='%s', strPlotOutline='%s', " + "strPlot='%s', GenreType=%i, GenreSubType=%i, firstAired='%s', parentalRating=%i, starRating=%i, " + "notify=%i, seriesNum='%s', episodeNum='%s', episodePart='%s', episodeName='%s', " + "idUniqueBroadcast=%i WHERE idDatabaseBroadcast=%i", + info.ChannelID(), info.Start().GetAsDBDateTime().c_str(), info.End().GetAsDBDateTime().c_str(), + info.Title().c_str(), info.PlotOutline().c_str(), info.Plot().c_str(), info.GenreType(), info.GenreSubType(), + info.FirstAired().GetAsDBDateTime().c_str(), info.ParentalRating(), info.StarRating(), info.Notify(), + info.SeriesNum().c_str(), info.EpisodeNum().c_str(), info.EpisodePart().c_str(), info.EpisodeName().c_str(), + info.GetUniqueBroadcastID(), id); + + if (oneWrite) + m_pDS->exec(SQL.c_str()); + else + m_pDS2->add_insert_sql(SQL); + + return true; + } + else // add the items + { + m_pDS->close(); + SQL = FormatSQL("insert into GuideData (idDatabaseBroadcast, idChannel, StartTime, " + "EndTime, strTitle, strPlotOutline, strPlot, GenreType, GenreSubType, " + "firstAired, parentalRating, starRating, notify, seriesNum, " + "episodeNum, episodePart, episodeName, idUniqueBroadcast) " + "values (NULL,'%i','%s','%s','%s','%s','%s','%i','%i','%s','%i','%i','%i','%s','%s','%s','%s','%i')\n", + info.ChannelID(), info.Start().GetAsDBDateTime().c_str(), info.End().GetAsDBDateTime().c_str(), + info.Title().c_str(), info.PlotOutline().c_str(), info.Plot().c_str(), info.GenreType(), info.GenreSubType(), + info.FirstAired().GetAsDBDateTime().c_str(), info.ParentalRating(), info.StarRating(), info.Notify(), + info.SeriesNum().c_str(), info.EpisodeNum().c_str(), info.EpisodePart().c_str(), info.EpisodeName().c_str(), + info.GetUniqueBroadcastID()); + + if (oneWrite) + { + m_pDS->exec(SQL.c_str()); + } + else + { + if (firstWrite) + m_pDS2->insert(); + + m_pDS2->add_insert_sql(SQL); + } + + if (GetEPGDataEnd(info.ChannelID()) > info.End()) + { + CLog::Log(LOGNOTICE, "TV-Database: erasing epg data due to event change on channel %s", info.ChannelName().c_str()); + EraseChannelEPGAfterTime(info.ChannelID(), info.End()); + } + } + } + + if (!oneWrite && lastWrite) + { + m_pDS2->post(); + m_pDS2->clear_insert_sql(); + } + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%s on Channel %s) failed", __FUNCTION__, info.Title().c_str(), info.ChannelName().c_str()); + } + return false; +} + +bool CTVDatabase::RemoveEPGEntry(const cPVREPGInfoTag &info) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + + if (info.GetUniqueBroadcastID() < 0) // no match found, update required + return false; + + CStdString strSQL=FormatSQL("delete from GuideData WHERE GuideData.idUniqueBroadcast = '%u'", info.GetUniqueBroadcastID() ); + + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, info.Title().c_str()); + return false; + } +} + +bool CTVDatabase::RemoveEPGEntries(unsigned int channelID, const CDateTime &start, const CDateTime &end) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString strSQL; + if (channelID < 0) + { + strSQL=FormatSQL("delete from GuideData WHERE GuideData.StartTime < '%s' AND GuideData.EndTime > '%s'", + start.GetAsDBDateTime().c_str(), end.GetAsDBDateTime().c_str()); + } + else + { + strSQL=FormatSQL("delete from GuideData WHERE GuideData.idChannel = '%u' AND GuideData.StartTime < '%s' AND GuideData.EndTime > '%s'", + channelID, start.GetAsDBDateTime().c_str(), end.GetAsDBDateTime().c_str()); + } + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (ID=%i) failed", __FUNCTION__, channelID); + return false; + } +} + +bool CTVDatabase::GetEPGForChannel(const cPVRChannelInfoTag &channelinfo, cPVREpg *epg, const CDateTime &start, const CDateTime &end) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString SQL=FormatSQL("select * from GuideData WHERE GuideData.idChannel = '%u' AND GuideData.EndTime > '%s' " + "AND GuideData.StartTime < '%s' ORDER BY GuideData.StartTime;", channelinfo.ChannelID(), start.GetAsDBDateTime().c_str(), end.GetAsDBDateTime().c_str()); + m_pDS->query( SQL.c_str() ); + + if (m_pDS->num_rows() <= 0) + return false; + + while (!m_pDS->eof()) + { + PVR_PROGINFO broadcast; + CDateTime startTime, endTime; + time_t startTime_t, endTime_t; + broadcast.uid = m_pDS->fv("idUniqueBroadcast").get_asInt(); + broadcast.title = m_pDS->fv("strTitle").get_asString().c_str(); + broadcast.subtitle = m_pDS->fv("strPlotOutline").get_asString().c_str(); + broadcast.description = m_pDS->fv("strPlot").get_asString().c_str(); + broadcast.genre_type = m_pDS->fv("GenreType").get_asInt(); + broadcast.genre_sub_type = m_pDS->fv("GenreSubType").get_asInt(); + broadcast.parental_rating = m_pDS->fv("parentalRating").get_asInt(); + startTime.SetFromDBDateTime(m_pDS->fv("StartTime").get_asString()); + endTime.SetFromDBDateTime(m_pDS->fv("EndTime").get_asString()); + startTime.GetAsTime(startTime_t); + endTime.GetAsTime(endTime_t); + broadcast.starttime = startTime_t; + broadcast.endtime = endTime_t; + + cPVREpg::Add(&broadcast, epg); + + m_pDS->next(); + } + m_pDS->close(); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } + return false; +} + +CDateTime CTVDatabase::GetEPGDataStart(int channelID) +{ + CDateTime lastProgramme; + try + { + CStdString SQL; + + if (NULL == m_pDB.get()) return lastProgramme; + if (NULL == m_pDS.get()) return lastProgramme; + + if (channelID != -1) + { + SQL=FormatSQL("SELECT GuideData.StartTime FROM GuideData WHERE GuideData.idChannel = '%u' ORDER BY GuideData.StartTime DESC;", channelID); + } + else { + SQL=FormatSQL("SELECT GuideData.StartTime FROM GuideData ORDER BY GuideData.StartTime DESC;"); + } + m_pDS->query( SQL.c_str() ); + if (!m_pDS->eof()) + { + lastProgramme.SetFromDBDateTime(m_pDS->fv("StartTime").get_asString()); + } + m_pDS->close(); + + if (!lastProgramme.IsValid()) + return CDateTime::GetCurrentDateTime(); + else + return lastProgramme; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return lastProgramme; + } +} + +CDateTime CTVDatabase::GetEPGDataEnd(int channelID) +{ + CDateTime lastProgramme; + try + { + CStdString SQL; + + if (NULL == m_pDB.get()) return lastProgramme; + if (NULL == m_pDS.get()) return lastProgramme; + + if (channelID != -1) + { + SQL=FormatSQL("SELECT GuideData.EndTime FROM GuideData WHERE GuideData.idChannel = '%u' ORDER BY GuideData.EndTime DESC;", channelID); + } + else + { + SQL=FormatSQL("SELECT GuideData.EndTime FROM GuideData ORDER BY GuideData.EndTime DESC;"); + } + m_pDS->query( SQL.c_str() ); + if (!m_pDS->eof()) + { + lastProgramme.SetFromDBDateTime(m_pDS->fv("EndTime").get_asString()); + } + m_pDS->close(); + + if (!lastProgramme.IsValid()) + return CDateTime::GetCurrentDateTime(); + else + return lastProgramme; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return lastProgramme; + } +} + +bool CTVDatabase::EraseChannels() +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString strSQL=FormatSQL("delete from Channels"); + + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } +} + +bool CTVDatabase::EraseClientChannels(long clientID) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString strSQL=FormatSQL("delete from Channels WHERE Channels.idClient = '%u'", clientID); + + m_pDS->exec(strSQL.c_str()); + + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } +} + +long CTVDatabase::AddDBChannel(const cPVRChannelInfoTag &info, bool oneWrite, bool firstWrite, bool lastWrite) +{ + try + { + if (info.ClientID() < 0) return -1; + if (NULL == m_pDB.get()) return -1; + if (NULL == m_pDS.get()) return -1; + + long channelId = info.ChannelID(); + if (channelId < 0) + { + CStdString SQL = FormatSQL("insert into Channels (idChannel, idClient, Number, Name, ClientName, " + "ClientNumber, UniqueId, IconPath, GroupID, encryption, radio, " + "grabEpg, EpgGrabber, hide, Virtual, strInputFormat, strStreamURL) " + "values (NULL, '%i', '%i', '%s', '%s', '%i', '%i', '%s', '%i', '%i', '%i', '%i', '%s', '%i', '%i', '%s', '%s')\n", + info.ClientID(), info.Number(), info.Name().c_str(), info.ClientName().c_str(), + info.ClientNumber(), info.UniqueID(), info.m_IconPath.c_str(), info.m_iGroupID, + info.EncryptionSystem(), info.m_radio, info.m_grabEpg, info.m_grabber.c_str(), + info.m_hide, info.m_bIsVirtual, info.m_strInputFormat.c_str(), info.m_strStreamURL.c_str()); + + if (oneWrite) + { + m_pDS->exec(SQL.c_str()); + channelId = (long)m_pDS->lastinsertid(); + } + else + { + if (firstWrite) + m_pDS->insert(); + + m_pDS->add_insert_sql(SQL); + if (lastWrite) + m_pDS->post(); + } + } + return channelId; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, info.m_strChannel.c_str()); + } + + return -1; +} + +bool CTVDatabase::RemoveDBChannel(const cPVRChannelInfoTag &info) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + long channelId = info.ChannelID(); + long clientId = info.ClientID(); + + if (channelId < 0 || clientId < 0) // no match found, update required + { + return false; + } + + CStdString strSQL=FormatSQL("delete from Channels WHERE Channels.idChannel = '%u' AND Channels.idClient = '%u'", channelId, clientId); + + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, info.m_strChannel.c_str()); + return false; + } +} + +long CTVDatabase::UpdateDBChannel(const cPVRChannelInfoTag &info) +{ + try + { + if (NULL == m_pDB.get()) return -1; + if (NULL == m_pDS.get()) return -1; + + long channelId = info.m_iIdChannel; + + CStdString SQL; + SQL=FormatSQL("select * from Channels WHERE Channels.idChannel = '%u'", channelId); + m_pDS->query(SQL.c_str()); + + if (m_pDS->num_rows() > 0) + { + m_pDS->close(); + // update the item + SQL = FormatSQL("update Channels set idClient=%i,Number=%i,Name='%s',ClientName='%s'," + "ClientNumber=%i,UniqueId=%i,IconPath='%s',GroupID=%i,encryption=%i,radio=%i," + "hide=%i,grabEpg=%i,EpgGrabber='%s',Virtual=%i,strInputFormat='%s',strStreamURL='%s' where idChannel=%i", + info.ClientID(), info.Number(), info.Name().c_str(), info.ClientName().c_str(), + info.ClientNumber(), info.UniqueID(), info.m_IconPath.c_str(), info.m_iGroupID, + info.EncryptionSystem(), info.m_radio, info.m_hide, info.m_grabEpg, info.m_grabber.c_str(), + info.m_bIsVirtual, info.m_strInputFormat.c_str(), info.m_strStreamURL.c_str(), channelId); + + m_pDS->exec(SQL.c_str()); + return channelId; + } + else // add the items + { + m_pDS->close(); + SQL = FormatSQL("insert into Channels (idChannel, idClient, Number, Name, ClientName, " + "ClientNumber, UniqueId, IconPath, GroupID, encryption, radio, " + "grabEpg, EpgGrabber, hide, Virtual, strInputFormat, strStreamURL) " + "values (NULL, '%i', '%i', '%s', '%s', '%i', '%i', '%s', '%i', '%i', '%i', '%i', '%s', '%i', '%i', '%s', '%s')\n", + info.ClientID(), info.Number(), info.Name().c_str(), info.ClientName().c_str(), + info.ClientNumber(), info.UniqueID(), info.m_IconPath.c_str(), info.m_iGroupID, + info.EncryptionSystem(), info.m_radio, info.m_grabEpg, info.m_grabber.c_str(), + info.m_hide, info.m_bIsVirtual, info.m_strInputFormat.c_str(), info.m_strStreamURL.c_str()); + + m_pDS->exec(SQL.c_str()); + channelId = (long)m_pDS->lastinsertid(); + return channelId; + } + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, info.m_strChannel.c_str()); + return false; + } +} + +bool CTVDatabase::HasChannel(const cPVRChannelInfoTag &info) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString SQL=FormatSQL("select * from Channels WHERE Channels.Name = '%s' AND Channels.ClientNumber = '%i'", info.m_strChannel.c_str(), info.m_iClientNum); + + m_pDS->query(SQL.c_str()); + + int num = 0; + + num = m_pDS->num_rows(); + + m_pDS->close(); + + if (num != 0) + return true; + else + return false; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } +} + +int CTVDatabase::GetDBNumChannels(bool radio) +{ + try + { + if (NULL == m_pDB.get()) return 0; + if (NULL == m_pDS.get()) return 0; + + CStdString SQL=FormatSQL("select * from Channels WHERE Channels.radio=%u", radio); + + m_pDS->query(SQL.c_str()); + + int num = m_pDS->num_rows(); + + m_pDS->close(); + + return num; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return 0; + } +} + +int CTVDatabase::GetNumHiddenChannels() +{ + try + { + if (NULL == m_pDB.get()) return 0; + if (NULL == m_pDS.get()) return 0; + + CStdString SQL=FormatSQL("select * from Channels WHERE Channels.hide=%u", true); + + m_pDS->query(SQL.c_str()); + + int num = m_pDS->num_rows(); + + m_pDS->close(); + + return num; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return 0; + } +} + +bool CTVDatabase::GetDBChannelList(cPVRChannels &results, bool radio) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString SQL=FormatSQL("select * from Channels WHERE Channels.radio=%u ORDER BY Channels.Number", radio); + + m_pDS->query(SQL.c_str()); + + while (!m_pDS->eof()) + { + cPVRChannelInfoTag channel; + + channel.m_clientID = m_pDS->fv("idClient").get_asInt(); + channel.m_iIdChannel = m_pDS->fv("idChannel").get_asInt(); + channel.m_iChannelNum = m_pDS->fv("Number").get_asInt(); + channel.m_strChannel = m_pDS->fv("Name").get_asString(); + channel.m_strClientName = m_pDS->fv("ClientName").get_asString(); + channel.m_iClientNum = m_pDS->fv("ClientNumber").get_asInt(); + channel.m_iIdUnique = m_pDS->fv("UniqueId").get_asInt(); + channel.m_IconPath = m_pDS->fv("IconPath").get_asString(); + channel.m_iGroupID = m_pDS->fv("GroupID").get_asInt(); + channel.m_encryptionSystem = m_pDS->fv("encryption").get_asInt(); + channel.m_radio = m_pDS->fv("radio").get_asBool(); + channel.m_hide = m_pDS->fv("hide").get_asBool(); + channel.m_grabEpg = m_pDS->fv("grabEpg").get_asBool(); + channel.m_grabber = m_pDS->fv("EpgGrabber").get_asString(); + channel.m_strInputFormat = m_pDS->fv("strInputFormat").get_asString(); + channel.m_strStreamURL = m_pDS->fv("strStreamURL").get_asString(); + channel.m_countWatched = m_pDS->fv("countWatched").get_asInt(); + channel.m_secondsWatched = m_pDS->fv("timeWatched").get_asInt(); + channel.m_bIsVirtual = m_pDS->fv("Virtual").get_asBool(); + channel.m_lastTimeWatched.SetFromDBDateTime(m_pDS->fv("lastTimeWatched").get_asString()); + + results.push_back(channel); + m_pDS->next(); + } + + m_pDS->close(); + + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } + + return false; +} + +bool CTVDatabase::EraseChannelLinkageMap() +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString strSQL=FormatSQL("delete from ChannelLinkageMap"); + + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } +} + +long CTVDatabase::AddChannelLinkage(int PortalChannel, int LinkedChannel) +{ + try + { + long idMapping; + + if (PortalChannel < 0) return -1; + if (LinkedChannel < 0) return -1; + if (NULL == m_pDB.get()) return -1; + if (NULL == m_pDS.get()) return -1; + + CStdString SQL=FormatSQL("insert into ChannelLinkageMap (idMapping, idPortalChannel, idLinkedChannel) values (NULL, '%i', '%i')", PortalChannel, LinkedChannel); + m_pDS->exec(SQL.c_str()); + idMapping = (long)m_pDS->lastinsertid(); + + return idMapping; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%i-%i) failed", __FUNCTION__, PortalChannel, LinkedChannel); + } + return -1; +} + +bool CTVDatabase::DeleteChannelLinkage(unsigned int channelId) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + if (channelId < 0) // no match found, update required + return false; + + CStdString strSQL=FormatSQL("delete from ChannelLinkageMap WHERE idPortalChannel='%u' OR idLinkedChannel='%u'", channelId, channelId); + + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, channelId); + return false; + } +} + +bool CTVDatabase::GetChannelLinkageMap(cPVRChannelInfoTag &channel) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + channel.ClearChannelLinkage(); + + CStdString SQL = FormatSQL("select * from ChannelLinkageMap WHERE ChannelLinkageMap.idPortalChannel=%u ORDER BY ChannelLinkageMap.idLinkedChannel", channel.ChannelID()); + m_pDS->query(SQL.c_str()); + + while (!m_pDS->eof()) + { + channel.AddChannelLinkage(m_pDS->fv("idLinkedChannel").get_asInt()); + m_pDS->next(); + } + + m_pDS->close(); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } + return false; +} + +bool CTVDatabase::EraseChannelGroups() +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString strSQL=FormatSQL("delete from ChannelGroup"); + + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } +} + +long CTVDatabase::AddChannelGroup(const CStdString &groupName, int sortOrder) +{ + try + { + if (groupName == "") return -1; + if (NULL == m_pDB.get()) return -1; + if (NULL == m_pDS.get()) return -1; + + long groupId; + groupId = GetChannelGroupId(groupName); + if (groupId < 0) + { + CStdString SQL=FormatSQL("insert into ChannelGroup (idGroup, groupName, sortOrder) values (NULL, '%s', '%i')", groupName.c_str(), sortOrder); + m_pDS->exec(SQL.c_str()); + groupId = (long)m_pDS->lastinsertid(); + } + + return groupId; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, groupName.c_str()); + } + return -1; +} + +bool CTVDatabase::DeleteChannelGroup(unsigned int GroupId) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + if (GroupId < 0) // no match found, update required + return false; + + CStdString strSQL=FormatSQL("delete from ChannelGroup WHERE ChannelGroup.idGroup = '%u'", GroupId); + + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, GroupId); + return false; + } +} + +bool CTVDatabase::GetChannelGroupList(cPVRChannelGroups &results) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString SQL = FormatSQL("select * from ChannelGroup ORDER BY ChannelGroup.sortOrder"); + m_pDS->query(SQL.c_str()); + + while (!m_pDS->eof()) + { + cPVRChannelGroup data; + + data.SetGroupID(m_pDS->fv("idGroup").get_asInt()); + data.SetGroupName(m_pDS->fv("groupName").get_asString()); + data.SetSortOrder(m_pDS->fv("sortOrder").get_asInt()); + + results.push_back(data); + m_pDS->next(); + } + + m_pDS->close(); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } + return false; +} + +bool CTVDatabase::SetChannelGroupName(unsigned int GroupId, const CStdString &newname) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + if (GroupId < 0) // no match found, update required + { + return false; + } + + CStdString SQL; + + SQL=FormatSQL("select * from ChannelGroup WHERE ChannelGroup.idGroup = '%u'", GroupId); + m_pDS->query(SQL.c_str()); + + if (m_pDS->num_rows() > 0) + { + m_pDS->close(); + // update the item + CStdString SQL = FormatSQL("update ChannelGroup set groupName='%s' WHERE idGroup=%i", newname.c_str(), GroupId); + m_pDS->exec(SQL.c_str()); + return true; + } + return false; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, GroupId); + return false; + } +} + +bool CTVDatabase::SetChannelGroupSortOrder(unsigned int GroupId, int sortOrder) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + if (GroupId < 0) // no match found, update required + return false; + + CStdString SQL; + + SQL=FormatSQL("select * from ChannelGroup WHERE ChannelGroup.idGroup = '%u'", GroupId); + m_pDS->query(SQL.c_str()); + + if (m_pDS->num_rows() > 0) + { + m_pDS->close(); + // update the item + CStdString SQL = FormatSQL("update ChannelGroup set sortOrder='%i' WHERE idGroup=%i", sortOrder, GroupId); + m_pDS->exec(SQL.c_str()); + return true; + } + return false; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, GroupId); + } + return -1; +} + +long CTVDatabase::GetChannelGroupId(const CStdString &groupname) +{ + CStdString SQL; + + try + { + long lGroupId = -1; + if (NULL == m_pDB.get()) return lGroupId; + if (NULL == m_pDS.get()) return lGroupId; + + SQL=FormatSQL("select idGroup from ChannelGroup where groupName like '%s'", groupname.c_str()); + m_pDS->query(SQL.c_str()); + if (!m_pDS->eof()) + lGroupId = m_pDS->fv("ChannelGroup.idGroup").get_asInt(); + + m_pDS->close(); + return lGroupId; + } + catch (...) + { + CLog::Log(LOGERROR, "%s unable to get GroupId (%s)", __FUNCTION__, SQL.c_str()); + } + return -1; +} + +bool CTVDatabase::EraseRadioChannelGroups() +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString strSQL=FormatSQL("delete from RadioChannelGroup"); + + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } +} + +long CTVDatabase::AddRadioChannelGroup(const CStdString &groupName, int sortOrder) +{ + try + { + if (groupName == "") return -1; + if (NULL == m_pDB.get()) return -1; + if (NULL == m_pDS.get()) return -1; + + long groupId; + groupId = GetRadioChannelGroupId(groupName); + if (groupId < 0) + { + CStdString SQL=FormatSQL("insert into RadioChannelGroup (idGroup, groupName, sortOrder) values (NULL, '%s', '%i')", groupName.c_str(), sortOrder); + m_pDS->exec(SQL.c_str()); + groupId = (long)m_pDS->lastinsertid(); + } + + return groupId; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, groupName.c_str()); + } + return -1; +} + +bool CTVDatabase::DeleteRadioChannelGroup(unsigned int GroupId) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + if (GroupId < 0) // no match found, update required + return false; + + CStdString strSQL=FormatSQL("delete from RadioChannelGroup WHERE RadioChannelGroup.idGroup = '%u'", GroupId); + + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, GroupId); + return false; + } +} + +bool CTVDatabase::GetRadioChannelGroupList(cPVRChannelGroups &results) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString SQL = FormatSQL("select * from RadioChannelGroup ORDER BY RadioChannelGroup.sortOrder"); + m_pDS->query(SQL.c_str()); + + while (!m_pDS->eof()) + { + cPVRChannelGroup data; + + data.SetGroupID(m_pDS->fv("idGroup").get_asInt()); + data.SetGroupName(m_pDS->fv("groupName").get_asString()); + data.SetSortOrder(m_pDS->fv("sortOrder").get_asInt()); + + results.push_back(data); + m_pDS->next(); + } + + m_pDS->close(); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } + return false; +} + +bool CTVDatabase::SetRadioChannelGroupName(unsigned int GroupId, const CStdString &newname) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + if (GroupId < 0) // no match found, update required + { + return false; + } + + CStdString SQL; + + SQL=FormatSQL("select * from RadioChannelGroup WHERE RadioChannelGroup.idGroup = '%u'", GroupId); + m_pDS->query(SQL.c_str()); + + if (m_pDS->num_rows() > 0) + { + m_pDS->close(); + // update the item + CStdString SQL = FormatSQL("update RadioChannelGroup set groupName='%s' WHERE idGroup=%i", newname.c_str(), GroupId); + m_pDS->exec(SQL.c_str()); + return true; + } + return false; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, GroupId); + return false; + } +} + +bool CTVDatabase::SetRadioChannelGroupSortOrder(unsigned int GroupId, int sortOrder) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + if (GroupId < 0) // no match found, update required + return false; + + CStdString SQL; + + SQL=FormatSQL("select * from RadioChannelGroup WHERE RadioChannelGroup.idGroup = '%u'", GroupId); + m_pDS->query(SQL.c_str()); + + if (m_pDS->num_rows() > 0) + { + m_pDS->close(); + // update the item + CStdString SQL = FormatSQL("update RadioChannelGroup set sortOrder='%i' WHERE idGroup=%i", sortOrder, GroupId); + m_pDS->exec(SQL.c_str()); + return true; + } + return false; + } + catch (...) + { + CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, GroupId); + } + return -1; +} + +long CTVDatabase::GetRadioChannelGroupId(const CStdString &groupname) +{ + CStdString SQL; + + try + { + long lGroupId = -1; + if (NULL == m_pDB.get()) return lGroupId; + if (NULL == m_pDS.get()) return lGroupId; + + SQL=FormatSQL("select idGroup from RadioChannelGroup where groupName like '%s'", groupname.c_str()); + m_pDS->query(SQL.c_str()); + if (!m_pDS->eof()) + lGroupId = m_pDS->fv("RadioChannelGroup.idGroup").get_asInt(); + + m_pDS->close(); + return lGroupId; + } + catch (...) + { + CLog::Log(LOGERROR, "%s unable to get GroupId (%s)", __FUNCTION__, SQL.c_str()); + } + return -1; +} + +bool CTVDatabase::EraseChannelSettings() +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + CStdString strSQL=FormatSQL("delete from ChannelSettings"); + + m_pDS->exec(strSQL.c_str()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + return false; + } +} + +bool CTVDatabase::GetChannelSettings(unsigned int channelID, CVideoSettings &settings) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + if (channelID < 0) return false; + + CStdString strSQL=FormatSQL("select * from ChannelSettings where idChannel like '%u'", channelID); + + m_pDS->query( strSQL.c_str() ); + if (m_pDS->num_rows() > 0) + { // get the video settings info + settings.m_AudioDelay = m_pDS->fv("AudioDelay").get_asFloat(); + settings.m_AudioStream = m_pDS->fv("AudioStream").get_asInt(); + settings.m_Brightness = m_pDS->fv("Brightness").get_asFloat(); + settings.m_Contrast = m_pDS->fv("Contrast").get_asFloat(); + settings.m_CustomPixelRatio = m_pDS->fv("PixelRatio").get_asFloat(); + settings.m_NoiseReduction = m_pDS->fv("NoiseReduction").get_asFloat(); + settings.m_Sharpness = m_pDS->fv("Sharpness").get_asFloat(); + settings.m_CustomZoomAmount = m_pDS->fv("ZoomAmount").get_asFloat(); + settings.m_Gamma = m_pDS->fv("Gamma").get_asFloat(); + settings.m_SubtitleDelay = m_pDS->fv("SubtitleDelay").get_asFloat(); + settings.m_SubtitleOn = m_pDS->fv("SubtitlesOn").get_asBool(); + settings.m_SubtitleStream = m_pDS->fv("SubtitleStream").get_asInt(); + settings.m_ViewMode = m_pDS->fv("ViewMode").get_asInt(); + settings.m_Crop = m_pDS->fv("Crop").get_asBool(); + settings.m_CropLeft = m_pDS->fv("CropLeft").get_asInt(); + settings.m_CropRight = m_pDS->fv("CropRight").get_asInt(); + settings.m_CropTop = m_pDS->fv("CropTop").get_asInt(); + settings.m_CropBottom = m_pDS->fv("CropBottom").get_asInt(); + settings.m_InterlaceMethod = (EINTERLACEMETHOD)m_pDS->fv("Deinterlace").get_asInt(); + settings.m_VolumeAmplification = m_pDS->fv("VolumeAmplification").get_asFloat(); + settings.m_OutputToAllSpeakers = m_pDS->fv("OutputToAllSpeakers").get_asBool(); + settings.m_SubtitleCached = false; + m_pDS->close(); + return true; + } + m_pDS->close(); + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + } + return false; +} + +bool CTVDatabase::SetChannelSettings(unsigned int channelID, const CVideoSettings &settings) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + if (channelID < 0) + { + // no match found, update required + return false; + } + CStdString strSQL; + strSQL.Format("select * from ChannelSettings where idChannel=%i", channelID); + m_pDS->query( strSQL.c_str() ); + if (m_pDS->num_rows() > 0) + { + m_pDS->close(); + // update the item + strSQL=FormatSQL("update ChannelSettings set Deinterlace=%i,ViewMode=%i,ZoomAmount=%f,PixelRatio=%f," + "AudioStream=%i,SubtitleStream=%i,SubtitleDelay=%f,SubtitlesOn=%i,Brightness=%f,Contrast=%f,Gamma=%f," + "VolumeAmplification=%f,AudioDelay=%f,OutputToAllSpeakers=%i,Sharpness=%f,NoiseReduction=%f,", + settings.m_InterlaceMethod, settings.m_ViewMode, settings.m_CustomZoomAmount, settings.m_CustomPixelRatio, + settings.m_AudioStream, settings.m_SubtitleStream, settings.m_SubtitleDelay, settings.m_SubtitleOn, + settings.m_Brightness, settings.m_Contrast, settings.m_Gamma, settings.m_VolumeAmplification, settings.m_AudioDelay, + settings.m_OutputToAllSpeakers,settings.m_Sharpness,settings.m_NoiseReduction); + CStdString strSQL2; + strSQL2=FormatSQL("Crop=%i,CropLeft=%i,CropRight=%i,CropTop=%i,CropBottom=%i where idChannel=%i\n", settings.m_Crop, settings.m_CropLeft, settings.m_CropRight, settings.m_CropTop, settings.m_CropBottom, channelID); + strSQL += strSQL2; + m_pDS->exec(strSQL.c_str()); + return true; + } + else + { // add the items + m_pDS->close(); + strSQL=FormatSQL("insert into ChannelSettings ( idChannel,Deinterlace,ViewMode,ZoomAmount,PixelRatio," + "AudioStream,SubtitleStream,SubtitleDelay,SubtitlesOn,Brightness,Contrast,Gamma," + "VolumeAmplification,AudioDelay,OutputToAllSpeakers,Crop,CropLeft,CropRight,CropTop,CropBottom,Sharpness,NoiseReduction)" + " values (%i,%i,%i,%f,%f,%i,%i,%f,%i,%f,%f,%f,%f,%f,", + channelID, settings.m_InterlaceMethod, settings.m_ViewMode, settings.m_CustomZoomAmount, settings.m_CustomPixelRatio, + settings.m_AudioStream, settings.m_SubtitleStream, settings.m_SubtitleDelay, settings.m_SubtitleOn, + settings.m_Brightness, settings.m_Contrast, settings.m_Gamma, settings.m_VolumeAmplification, settings.m_AudioDelay); + CStdString strSQL2; + strSQL2=FormatSQL("%i,%i,%i,%i,%i,%i,%f,%f)\n", settings.m_OutputToAllSpeakers, settings.m_Crop, settings.m_CropLeft, settings.m_CropRight, + settings.m_CropTop, settings.m_CropBottom, settings.m_Sharpness, settings.m_NoiseReduction); + strSQL += strSQL2; + m_pDS->exec(strSQL.c_str()); + return true; + } + } + catch (...) + { + CLog::Log(LOGERROR, "%s (ID=%i) failed", __FUNCTION__, channelID); + return false; + } +} + diff --git a/xbmc/TVDatabase.h b/xbmc/TVDatabase.h new file mode 100644 index 0000000000..63dc8ed3b8 --- /dev/null +++ b/xbmc/TVDatabase.h @@ -0,0 +1,109 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "Database.h" +#include "DateTime.h" +#include "FileItem.h" +#include "settings/VideoSettings.h" +#include "utils/PVREpg.h" +#include "utils/PVRChannels.h" + +class CTVDatabase : public CDatabase +{ +public: + CTVDatabase(void); + virtual ~CTVDatabase(void); + + virtual bool Open(); + + virtual int GetMinVersion() const { return 4; }; + const char *GetDefaultDBName() const { return "MyTV4.db"; }; + + bool EraseClients(); + long AddClient(const CStdString &client, const CStdString &guid); + CDateTime GetLastEPGScanTime(); + bool UpdateLastEPGScan(const CDateTime lastScan); + int GetLastChannel(); + bool UpdateLastChannel(const cPVRChannelInfoTag &info); + + /* Database Epg handling */ + bool EraseEPG(); + bool EraseChannelEPG(long channelID); + bool EraseChannelEPGAfterTime(long channelID, CDateTime after); + bool EraseOldEPG(); + long AddEPGEntry(const cPVREPGInfoTag &info, bool oneWrite = true, bool firstWrite = false, bool lastWrite = false); + bool UpdateEPGEntry(const cPVREPGInfoTag &info, bool oneWrite = true, bool firstWrite = false, bool lastWrite = false); + bool RemoveEPGEntry(const cPVREPGInfoTag &info); + bool RemoveEPGEntries(unsigned int channelID, const CDateTime &start, const CDateTime &end); + bool GetEPGForChannel(const cPVRChannelInfoTag &channelinfo, cPVREpg *epg, const CDateTime &start, const CDateTime &end); + CDateTime GetEPGDataStart(int channelID); + CDateTime GetEPGDataEnd(int channelID); + + /* Database Channel handling */ + bool EraseChannels(); + bool EraseClientChannels(long clientID); + long AddDBChannel(const cPVRChannelInfoTag &info, bool oneWrite = true, bool firstWrite = false, bool lastWrite = false); + bool RemoveDBChannel(const cPVRChannelInfoTag &info); + long UpdateDBChannel(const cPVRChannelInfoTag &info); + int GetDBNumChannels(bool radio); + int GetNumHiddenChannels(); + bool HasChannel(const cPVRChannelInfoTag &info); + bool GetDBChannelList(cPVRChannels &results, bool radio); + + /* Database Channel Portal Linkage */ + bool EraseChannelLinkageMap(); + long AddChannelLinkage(int PortalChannel, int LinkedChannel); + bool DeleteChannelLinkage(unsigned int channelId); + bool GetChannelLinkageMap(cPVRChannelInfoTag &channel); + + /* Database Channel Group handling */ + bool EraseChannelGroups(); + long AddChannelGroup(const CStdString &groupName, int sortOrder); + bool DeleteChannelGroup(unsigned int GroupId); + bool GetChannelGroupList(cPVRChannelGroups &results); + bool SetChannelGroupName(unsigned int GroupId, const CStdString &newname); + bool SetChannelGroupSortOrder(unsigned int GroupId, int sortOrder); + + /* Database Radio Group handling */ + bool EraseRadioChannelGroups(); + long AddRadioChannelGroup(const CStdString &groupName, int sortOrder); + bool DeleteRadioChannelGroup(unsigned int GroupId); + bool GetRadioChannelGroupList(cPVRChannelGroups &results); + bool SetRadioChannelGroupName(unsigned int GroupId, const CStdString &newname); + bool SetRadioChannelGroupSortOrder(unsigned int GroupId, int sortOrder); + + /* Database channel settings storage */ + bool EraseChannelSettings(); + bool GetChannelSettings(unsigned int channelID, CVideoSettings &settings); + bool SetChannelSettings(unsigned int channelID, const CVideoSettings &settings); + +protected: + long GetClientId(const CStdString &guid); + long GetChannelGroupId(const CStdString &groupname); + long GetRadioChannelGroupId(const CStdString &groupname); + +private: + CStdString oneWriteSQLString; +// char *InsertSQLString(char *dest, const char *src); + virtual bool CreateTables(); + virtual bool UpdateOldVersion(int version); +}; diff --git a/xbmc/URL.cpp b/xbmc/URL.cpp index 47f876d610..e6a6fa7c1e 100644 --- a/xbmc/URL.cpp +++ b/xbmc/URL.cpp @@ -179,6 +179,7 @@ void CURL::Parse(const CStdString& strURL1) int iEnd = strURL.length(); const char* sep = NULL; + //TODO fix all Addon paths CStdString strProtocol2 = GetTranslatedProtocol(); if(m_strProtocol.Equals("rss") || m_strProtocol.Equals("addons")) @@ -187,6 +188,7 @@ void CURL::Parse(const CStdString& strURL1) if(strProtocol2.Equals("http") || strProtocol2.Equals("https") || strProtocol2.Equals("plugin") + || m_strProtocol.Equals("addon") || strProtocol2.Equals("hdhomerun") || strProtocol2.Equals("rtsp") || strProtocol2.Equals("zip")) @@ -310,6 +312,7 @@ void CURL::Parse(const CStdString& strURL1) || m_strProtocol.CompareNoCase("musicdb") == 0 || m_strProtocol.CompareNoCase("videodb") == 0 || m_strProtocol.CompareNoCase("lastfm") == 0 + || m_strProtocol.CompareNoCase("pvr") == 0 || m_strProtocol.Left(3).CompareNoCase("mem") == 0) { if (m_strHostName != "" && m_strFileName != "") diff --git a/xbmc/Util.cpp b/xbmc/Util.cpp index b65528c986..9222ecd338 100644 --- a/xbmc/Util.cpp +++ b/xbmc/Util.cpp @@ -45,6 +45,7 @@ #include "FileSystem/MultiPathDirectory.h" #include "FileSystem/DirectoryCache.h" #include "FileSystem/SpecialProtocol.h" +#include "FileSystem/PVRDirectory.h" #include "FileSystem/RSSDirectory.h" #include "ThumbnailCache.h" #ifdef HAS_FILESYSTEM_RAR @@ -1154,6 +1155,11 @@ bool CUtil::IsVTP(const CStdString& strFile) return strFile.Left(4).Equals("vtp:"); } +bool CUtil::IsPVR(const CStdString& strFile) +{ + return strFile.Left(4).Equals("pvr:"); +} + bool CUtil::IsHTSP(const CStdString& strFile) { return strFile.Left(5).Equals("htsp:"); @@ -1161,6 +1167,9 @@ bool CUtil::IsHTSP(const CStdString& strFile) bool CUtil::IsLiveTV(const CStdString& strFile) { + if (strFile.Left(14).Equals("pvr://channels")) + return true; + if(IsTuxBox(strFile) || IsVTP(strFile) || IsHDHomeRun(strFile) @@ -1174,6 +1183,11 @@ bool CUtil::IsLiveTV(const CStdString& strFile) return false; } +bool CUtil::IsTVRecording(const CStdString& strFile) +{ + return strFile.Left(15).Equals("pvr://recording"); +} + bool CUtil::IsMusicDb(const CStdString& strFile) { return strFile.Left(8).Equals("musicdb:"); @@ -3068,6 +3082,10 @@ bool CUtil::SupportsFileOperations(const CStdString& strPath) return true; if (IsSmb(strPath)) return true; + if (IsTVRecording(strPath)) + { + return CPVRDirectory::SupportsFileOperations(strPath); + } if (IsMythTV(strPath)) { /* diff --git a/xbmc/Util.h b/xbmc/Util.h index f74d32aa37..c268987e44 100644 --- a/xbmc/Util.h +++ b/xbmc/Util.h @@ -104,8 +104,10 @@ public: static bool IsMythTV(const CStdString& strFile); static bool IsHDHomeRun(const CStdString& strFile); static bool IsVTP(const CStdString& strFile); + static bool IsPVR(const CStdString& strFile); static bool IsHTSP(const CStdString& strFile); static bool IsLiveTV(const CStdString& strFile); + static bool IsTVRecording(const CStdString& strFile); static bool IsMusicDb(const CStdString& strFile); static bool IsVideoDb(const CStdString& strFile); static bool IsLastFM(const CStdString& strFile); diff --git a/xbmc/XBIRRemote.h b/xbmc/XBIRRemote.h index bc7437336f..0109f6ce9d 100644 --- a/xbmc/XBIRRemote.h +++ b/xbmc/XBIRRemote.h @@ -88,6 +88,8 @@ #define XINPUT_IR_REMOTE_GREEN 252 #define XINPUT_IR_REMOTE_YELLOW 253 #define XINPUT_IR_REMOTE_BLUE 254 +#define XINPUT_IR_REMOTE_PLAYLIST 255 +#define XINPUT_IR_REMOTE_GUIDE 256 typedef struct _XINPUT_IR_REMOTE { diff --git a/xbmc/addons/Addon.cpp b/xbmc/addons/Addon.cpp index db5ca157c6..1ae8425e01 100644 --- a/xbmc/addons/Addon.cpp +++ b/xbmc/addons/Addon.cpp @@ -149,7 +149,7 @@ static const TypeMapping types[] = {"xbmc.gui.skin", ADDON_SKIN, 166, "DefaultAddonSkin.png" }, {"xbmc.gui.webinterface", ADDON_WEB_INTERFACE, 199, "DefaultAddonWebSkin.png" }, {"xbmc.addon.repository", ADDON_REPOSITORY, 24011, "DefaultAddonRepository.png" }, - {"pvrclient", ADDON_PVRDLL, 0, "" }, + {"xbmc.pvrclient", ADDON_PVRDLL, 24018, ""}, {"xbmc.addon.video", ADDON_VIDEO, 1037, "DefaultAddonVideo.png" }, {"xbmc.addon.audio", ADDON_AUDIO, 1038, "DefaultAddonMusic.png" }, {"xbmc.addon.image", ADDON_IMAGE, 1039, "DefaultAddonPicture.png" }, @@ -359,6 +359,9 @@ void CAddon::BuildLibName(const cp_extension_t *extension) case ADDON_VIZ: ext = ADDON_VIS_EXT; break; + case ADDON_PVRDLL: + ext = ADDON_PVRDLL_EXT; + break; case ADDON_SCRIPT: case ADDON_SCRIPT_LIBRARY: case ADDON_SCRIPT_LYRICS: @@ -393,6 +396,7 @@ void CAddon::BuildLibName(const cp_extension_t *extension) case ADDON_SCRAPER_MUSICVIDEOS: case ADDON_SCRAPER_TVSHOWS: case ADDON_SCRAPER_LIBRARY: + case ADDON_PVRDLL: case ADDON_PLUGIN: { CStdString temp = CAddonMgr::Get().GetExtValue(extension->configuration, "@library"); diff --git a/xbmc/addons/Addon.h b/xbmc/addons/Addon.h index 9767253553..c761417867 100644 --- a/xbmc/addons/Addon.h +++ b/xbmc/addons/Addon.h @@ -29,6 +29,7 @@ class CURL; class TiXmlElement; +class CAddonHelpers_Addon; typedef struct cp_plugin_info_t cp_plugin_info_t; typedef struct cp_extension_t cp_extension_t; @@ -143,6 +144,12 @@ public: */ virtual CStdString GetSetting(const CStdString& key); + /*! \brief Load the default settings and override these with any previously configured user settings + \return true if settings exist, false otherwise + \sa LoadUserSettings, SaveSettings, HasSettings, HasUserSettings, GetSetting, UpdateSetting + */ + virtual bool LoadSettings(); + TiXmlElement* GetSettingsXML(); virtual CStdString GetString(uint32_t id); @@ -171,17 +178,13 @@ public: ADDONDEPS GetDeps(); protected: + friend class CAddonHelpers_Addon; + CAddon(const CAddon&); // protected as all copying is handled by Clone() CAddon(const CAddon&, const AddonPtr&); const AddonPtr Parent() const { return m_parent; } virtual void BuildLibName(const cp_extension_t *ext = NULL); - /*! \brief Load the default settings and override these with any previously configured user settings - \return true if settings exist, false otherwise - \sa LoadUserSettings, SaveSettings, HasSettings, HasUserSettings, GetSetting, UpdateSetting - */ - virtual bool LoadSettings(); - /*! \brief Load the user settings \return true if user settings exist, false otherwise \sa LoadSettings, SaveSettings, HasSettings, HasUserSettings, GetSetting, UpdateSetting @@ -208,7 +211,7 @@ protected: bool m_userSettingsLoaded; private: - friend class AddonMgr; + friend class CAddonMgr; AddonProps m_props; const AddonPtr m_parent; CStdString m_userSettingsPath; diff --git a/xbmc/addons/AddonDll.h b/xbmc/addons/AddonDll.h index 0e72ab817c..ce55f6716e 100644 --- a/xbmc/addons/AddonDll.h +++ b/xbmc/addons/AddonDll.h @@ -28,6 +28,7 @@ #include "FileSystem/File.h" #include "FileSystem/SpecialProtocol.h" #include "FileSystem/Directory.h" +#include "AddonHelpers_local.h" #include "log.h" using namespace XFILE; @@ -59,6 +60,7 @@ namespace ADDON virtual bool LoadSettings(); TheStruct* m_pStruct; TheProps* m_pInfo; + CAddonHelpers* m_pHelpers; private: TheDll* m_pDll; @@ -109,6 +111,7 @@ CAddonDll::CAddonDll(const AddonProps &props) m_initialized = false; m_pDll = NULL; m_pInfo = NULL; + m_pHelpers = NULL; } template @@ -185,9 +188,15 @@ bool CAddonDll::Create() if (!LoadDll()) return false; + /* Allocate the helper function class to allow crosstalk over + helper libraries */ + m_pHelpers = new CAddonHelpers(this); + + /* Call Create to make connections, initializing data or whatever is + needed to become the AddOn running */ try { - ADDON_STATUS status = m_pDll->Create(NULL, m_pInfo); + ADDON_STATUS status = m_pDll->Create(m_pHelpers->GetCallbacks(), m_pInfo); if (status == STATUS_OK) m_initialized = true; else if (status == STATUS_NEED_SETTINGS) @@ -242,6 +251,8 @@ void CAddonDll::Destroy() { HandleException(e, "m_pDll->Unload"); } + delete m_pHelpers; + m_pHelpers = NULL; delete m_pStruct; m_pStruct = NULL; delete m_pDll; diff --git a/xbmc/addons/AddonHelpers_Addon.cpp b/xbmc/addons/AddonHelpers_Addon.cpp new file mode 100644 index 0000000000..f8d16efd64 --- /dev/null +++ b/xbmc/addons/AddonHelpers_Addon.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "Application.h" +#include "Addon.h" +#include "AddonHelpers_Addon.h" +#include "log.h" +#include "LangInfo.h" + +namespace ADDON +{ + +CAddonHelpers_Addon::CAddonHelpers_Addon(CAddon* addon) +{ + m_addon = addon; + m_callbacks = new CB_AddOnLib; + + /* AddOn Helper functions */ + m_callbacks->Log = AddOnLog; + m_callbacks->QueueNotification = QueueNotification; + m_callbacks->GetSetting = GetAddonSetting; + m_callbacks->UnknownToUTF8 = UnknownToUTF8; + m_callbacks->GetLocalizedString = GetLocalizedString; + m_callbacks->GetDVDMenuLanguage = GetDVDMenuLanguage; +} + +CAddonHelpers_Addon::~CAddonHelpers_Addon() +{ + delete m_callbacks; +} + +void CAddonHelpers_Addon::AddOnLog(void *addonData, const addon_log_t loglevel, const char *msg) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return; + + CAddonHelpers_Addon* addonHelper = helper->GetHelperAddon(); + + try + { + CStdString xbmcMsg; + xbmcMsg.Format("AddOnLog: %s/%s: %s", TranslateType(addonHelper->m_addon->Type()).c_str(), addonHelper->m_addon->Name().c_str(), msg); + + int xbmclog; + switch (loglevel) + { + case LOG_ERROR: + xbmclog = LOGERROR; + break; + case LOG_INFO: + xbmclog = LOGINFO; + break; + case LOG_NOTICE: + xbmclog = LOGNOTICE; + break; + case LOG_DEBUG: + default: + xbmclog = LOGDEBUG; + break; + } + + /* finally write the logmessage */ + CLog::Log(xbmclog, "%s", xbmcMsg.c_str()); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "AddOnLog: %s/%s - exception '%s' during AddOnLogCallback occurred, contact Developer '%s' of this AddOn", TranslateType(addonHelper->m_addon->Type()).c_str(), addonHelper->m_addon->Name().c_str(), e.what(), addonHelper->m_addon->Author().c_str()); + return; + } +} + +void CAddonHelpers_Addon::QueueNotification(void *addonData, const queue_msg_t type, const char *msg) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return; + + CAddonHelpers_Addon* addonHelper = helper->GetHelperAddon(); + + try + { + switch (type) + { + case QUEUE_WARNING: + g_application.m_guiDialogKaiToast.QueueNotification(CGUIDialogKaiToast::Warning, addonHelper->m_addon->Name(), msg, 3000, true); + CLog::Log(LOGDEBUG, "%s: %s-%s - Warning Message : %s", __FUNCTION__, TranslateType(addonHelper->m_addon->Type()).c_str(), addonHelper->m_addon->Name().c_str(), msg); + break; + + case QUEUE_ERROR: + g_application.m_guiDialogKaiToast.QueueNotification(CGUIDialogKaiToast::Error, addonHelper->m_addon->Name(), msg, 3000, true); + CLog::Log(LOGDEBUG, "%s: %s-%s - Error Message : %s", __FUNCTION__, TranslateType(addonHelper->m_addon->Type()).c_str(), addonHelper->m_addon->Name().c_str(), msg); + break; + + case QUEUE_INFO: + default: + g_application.m_guiDialogKaiToast.QueueNotification(CGUIDialogKaiToast::Info, addonHelper->m_addon->Name(), msg, 3000, false); + CLog::Log(LOGDEBUG, "%s: %s-%s - Info Message : %s", __FUNCTION__, TranslateType(addonHelper->m_addon->Type()).c_str(), addonHelper->m_addon->Name().c_str(), msg); + break; + } + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "QueueNotification: %s/%s - exception '%s' during AddOnLogCallback occurred, contact Developer '%s' of this AddOn", TranslateType(addonHelper->m_addon->Type()).c_str(), addonHelper->m_addon->Name().c_str(), e.what(), addonHelper->m_addon->Author().c_str()); + return; + } +} + +bool CAddonHelpers_Addon::GetAddonSetting(void *addonData, const char* settingName, void *settingValue) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || settingName == NULL || settingValue == NULL) + return false; + + CAddonHelpers_Addon* addonHelper = helper->GetHelperAddon(); + + try + { + CLog::Log(LOGDEBUG, "CAddonHelpers_Addon: AddOn %s request Setting %s", addonHelper->m_addon->Name().c_str(), settingName); + + if (!addonHelper->m_addon->LoadSettings()) + { + CLog::Log(LOGERROR, "Could't get Settings for AddOn: %s", addonHelper->m_addon->Name().c_str()); + return false; + } + + TiXmlElement *setting = addonHelper->m_addon->GetSettingsXML()->FirstChildElement("setting"); + while (setting) + { + const char *id = setting->Attribute("id"); + const char *type = setting->Attribute("type"); + + if (strcmpi(id, settingName) == 0 && type) + { + if (strcmpi(type, "text") == 0 || strcmpi(type, "ipaddress") == 0 || + strcmpi(type, "folder") == 0 || strcmpi(type, "action") == 0 || + strcmpi(type, "music") == 0 || strcmpi(type, "pictures") == 0 || + strcmpi(type, "folder") == 0 || strcmpi(type, "programs") == 0 || + strcmpi(type, "files") == 0 || strcmpi(type, "fileenum") == 0) + { + strcpy((char*) settingValue, addonHelper->m_addon->GetSetting(id).c_str()); + return true; + } + else if (strcmpi(type, "integer") == 0 || strcmpi(type, "enum") == 0 || + strcmpi(type, "labelenum") == 0) + { + *(int*) settingValue = (int) atoi(addonHelper->m_addon->GetSetting(id)); + return true; + } + else if (strcmpi(type, "bool") == 0) + { + *(bool*) settingValue = (bool) (addonHelper->m_addon->GetSetting(id) == "true" ? true : false); + return true; + } + else + { + CLog::Log(LOGERROR, "Unknown setting type '%s' for id %s in %s", type, id, addonHelper->m_addon->Name().c_str()); + } + } + setting = setting->NextSiblingElement("setting"); + } + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s - exception '%s' during GetAddonSetting occurred, contact Developer '%s' of this AddOn", addonHelper->m_addon->Name().c_str(), e.what(), addonHelper->m_addon->Author().c_str()); + } + return false; +} + +char* CAddonHelpers_Addon::UnknownToUTF8(const char *sourceDest) +{ + CStdString string; + if (sourceDest != NULL) + g_charsetConverter.unknownToUTF8(sourceDest, string); + else + string = ""; + char *buffer = (char*) malloc (string.length()+1); + strcpy(buffer, string.c_str()); + return buffer; +} + +char* CAddonHelpers_Addon::GetLocalizedString(const void* addonData, long dwCode) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return NULL; + + CAddonHelpers_Addon* addonHelper = helper->GetHelperAddon(); + + CStdString string; + if (dwCode >= 30000 && dwCode <= 30999) + string = addonHelper->m_addon->GetString(dwCode).c_str(); + else if (dwCode >= 32000 && dwCode <= 32999) + string = addonHelper->m_addon->GetString(dwCode).c_str(); + else + string = g_localizeStrings.Get(dwCode).c_str(); + + char *buffer = (char*) malloc (string.length()+1); + strcpy(buffer, string.c_str()); + return buffer; +} + +char* CAddonHelpers_Addon::GetDVDMenuLanguage(const void* addonData) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return NULL; + + CStdString string = g_langInfo.GetDVDMenuLanguage(); + + char *buffer = (char*) malloc (string.length()+1); + strcpy(buffer, string.c_str()); + return buffer; +} + + + +}; /* namespace ADDON */ diff --git a/xbmc/addons/AddonHelpers_Addon.h b/xbmc/addons/AddonHelpers_Addon.h new file mode 100644 index 0000000000..f0cb01df8a --- /dev/null +++ b/xbmc/addons/AddonHelpers_Addon.h @@ -0,0 +1,50 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "AddonHelpers_local.h" + +namespace ADDON +{ + +class CAddonHelpers_Addon +{ +public: + CAddonHelpers_Addon(CAddon* addon); + ~CAddonHelpers_Addon(); + + /**! \name General Functions */ + CB_AddOnLib *GetCallbacks() { return m_callbacks; } + + /**! \name Callback functions */ + static void AddOnLog(void *addonData, const addon_log_t loglevel, const char *msg); + static void QueueNotification(void *addonData, const queue_msg_t type, const char *msg); + static bool GetAddonSetting(void *addonData, const char* settingName, void *settingValue); + static char* UnknownToUTF8(const char *sourceDest); + static char* GetLocalizedString(const void* addonData, long dwCode); + static char* GetDVDMenuLanguage(const void* addonData); + +private: + CB_AddOnLib *m_callbacks; + CAddon *m_addon; +}; + +}; /* namespace ADDON */ diff --git a/xbmc/addons/AddonHelpers_GUI.cpp b/xbmc/addons/AddonHelpers_GUI.cpp new file mode 100644 index 0000000000..3520e5da80 --- /dev/null +++ b/xbmc/addons/AddonHelpers_GUI.cpp @@ -0,0 +1,1539 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "Application.h" +#include "Addon.h" +#include "AddonHelpers_GUI.h" +#include "log.h" +#include "Skin.h" +#include "FileItem.h" +#include "FileSystem/File.h" +#include "GUIWindowManager.h" +#include "TextureManager.h" +#include "GUISettings.h" +#include "GUISpinControlEx.h" +#include "GUIRadioButtonControl.h" +#include "GUISettingsSliderControl.h" +#include "GUIEditControl.h" +#include "GUIProgressControl.h" + +#define CONTROL_BTNVIEWASICONS 2 +#define CONTROL_BTNSORTBY 3 +#define CONTROL_BTNSORTASC 4 +#define CONTROL_LABELFILES 12 + +using namespace std; + +namespace ADDON +{ + +static int iXBMCGUILockRef = 0; + +CAddonHelpers_GUI::CAddonHelpers_GUI(CAddon* addon) +{ + m_addon = addon; + m_callbacks = new CB_GUILib; + + /* GUI Helper functions */ + m_callbacks->Lock = CAddonHelpers_GUI::Lock; + m_callbacks->Unlock = CAddonHelpers_GUI::Unlock; + m_callbacks->GetScreenHeight = CAddonHelpers_GUI::GetScreenHeight; + m_callbacks->GetScreenWidth = CAddonHelpers_GUI::GetScreenWidth; + m_callbacks->GetVideoResolution = CAddonHelpers_GUI::GetVideoResolution; + m_callbacks->Window_New = CAddonHelpers_GUI::Window_New; + m_callbacks->Window_Delete = CAddonHelpers_GUI::Window_Delete; + m_callbacks->Window_SetCallbacks = CAddonHelpers_GUI::Window_SetCallbacks; + m_callbacks->Window_Show = CAddonHelpers_GUI::Window_Show; + m_callbacks->Window_Close = CAddonHelpers_GUI::Window_Close; + m_callbacks->Window_DoModal = CAddonHelpers_GUI::Window_DoModal; + m_callbacks->Window_SetFocusId = CAddonHelpers_GUI::Window_SetFocusId; + m_callbacks->Window_GetFocusId = CAddonHelpers_GUI::Window_GetFocusId; + m_callbacks->Window_SetCoordinateResolution = CAddonHelpers_GUI::Window_SetCoordinateResolution; + m_callbacks->Window_SetProperty = CAddonHelpers_GUI::Window_SetProperty; + m_callbacks->Window_SetPropertyInt = CAddonHelpers_GUI::Window_SetPropertyInt; + m_callbacks->Window_SetPropertyBool = CAddonHelpers_GUI::Window_SetPropertyBool; + m_callbacks->Window_SetPropertyDouble = CAddonHelpers_GUI::Window_SetPropertyDouble; + m_callbacks->Window_GetProperty = CAddonHelpers_GUI::Window_GetProperty; + m_callbacks->Window_GetPropertyInt = CAddonHelpers_GUI::Window_GetPropertyInt; + m_callbacks->Window_GetPropertyBool = CAddonHelpers_GUI::Window_GetPropertyBool; + m_callbacks->Window_GetPropertyDouble = CAddonHelpers_GUI::Window_GetPropertyDouble; + m_callbacks->Window_ClearProperties = CAddonHelpers_GUI::Window_ClearProperties; + + m_callbacks->Window_GetListSize = CAddonHelpers_GUI::Window_GetListSize; + m_callbacks->Window_ClearList = CAddonHelpers_GUI::Window_ClearList; + m_callbacks->Window_AddItem = CAddonHelpers_GUI::Window_AddItem; + m_callbacks->Window_AddStringItem = CAddonHelpers_GUI::Window_AddStringItem; + m_callbacks->Window_RemoveItem = CAddonHelpers_GUI::Window_RemoveItem; + m_callbacks->Window_GetListItem = CAddonHelpers_GUI::Window_GetListItem; + m_callbacks->Window_SetCurrentListPosition = CAddonHelpers_GUI::Window_SetCurrentListPosition; + m_callbacks->Window_GetCurrentListPosition = CAddonHelpers_GUI::Window_GetCurrentListPosition; + + m_callbacks->Window_GetControl_Spin = CAddonHelpers_GUI::Window_GetControl_Spin; + m_callbacks->Window_GetControl_Button = CAddonHelpers_GUI::Window_GetControl_Button; + m_callbacks->Window_GetControl_RadioButton = CAddonHelpers_GUI::Window_GetControl_RadioButton; + m_callbacks->Window_GetControl_Edit = CAddonHelpers_GUI::Window_GetControl_Edit; + m_callbacks->Window_GetControl_Progress = CAddonHelpers_GUI::Window_GetControl_Progress; + + m_callbacks->Window_SetControlLabel = CAddonHelpers_GUI::Window_SetControlLabel; + + m_callbacks->Control_Spin_SetVisible = CAddonHelpers_GUI::Control_Spin_SetVisible; + m_callbacks->Control_Spin_SetText = CAddonHelpers_GUI::Control_Spin_SetText; + m_callbacks->Control_Spin_Clear = CAddonHelpers_GUI::Control_Spin_Clear; + m_callbacks->Control_Spin_AddLabel = CAddonHelpers_GUI::Control_Spin_AddLabel; + m_callbacks->Control_Spin_GetValue = CAddonHelpers_GUI::Control_Spin_GetValue; + m_callbacks->Control_Spin_SetValue = CAddonHelpers_GUI::Control_Spin_SetValue; + + m_callbacks->Control_RadioButton_SetVisible = CAddonHelpers_GUI::Control_RadioButton_SetVisible; + m_callbacks->Control_RadioButton_SetText = CAddonHelpers_GUI::Control_RadioButton_SetText; + m_callbacks->Control_RadioButton_SetSelected= CAddonHelpers_GUI::Control_RadioButton_SetSelected; + m_callbacks->Control_RadioButton_IsSelected = CAddonHelpers_GUI::Control_RadioButton_IsSelected; + + m_callbacks->Control_Progress_SetPercentage = CAddonHelpers_GUI::Control_Progress_SetPercentage; + m_callbacks->Control_Progress_GetPercentage = CAddonHelpers_GUI::Control_Progress_GetPercentage; + m_callbacks->Control_Progress_SetInfo = CAddonHelpers_GUI::Control_Progress_SetInfo; + m_callbacks->Control_Progress_GetInfo = CAddonHelpers_GUI::Control_Progress_GetInfo; + m_callbacks->Control_Progress_GetDescription= CAddonHelpers_GUI::Control_Progress_GetDescription; + + m_callbacks->ListItem_Create = CAddonHelpers_GUI::ListItem_Create; + m_callbacks->ListItem_GetLabel = CAddonHelpers_GUI::ListItem_GetLabel; + m_callbacks->ListItem_SetLabel = CAddonHelpers_GUI::ListItem_SetLabel; + m_callbacks->ListItem_GetLabel2 = CAddonHelpers_GUI::ListItem_GetLabel2; + m_callbacks->ListItem_SetLabel2 = CAddonHelpers_GUI::ListItem_SetLabel2; + m_callbacks->ListItem_SetIconImage = CAddonHelpers_GUI::ListItem_SetIconImage; + m_callbacks->ListItem_SetThumbnailImage = CAddonHelpers_GUI::ListItem_SetThumbnailImage; + m_callbacks->ListItem_SetInfo = CAddonHelpers_GUI::ListItem_SetInfo; + m_callbacks->ListItem_SetProperty = CAddonHelpers_GUI::ListItem_SetProperty; + m_callbacks->ListItem_GetProperty = CAddonHelpers_GUI::ListItem_GetProperty; + m_callbacks->ListItem_SetPath = CAddonHelpers_GUI::ListItem_SetPath; +} + +CAddonHelpers_GUI::~CAddonHelpers_GUI() +{ + delete m_callbacks; +} + +void CAddonHelpers_GUI::Lock() +{ + if (iXBMCGUILockRef == 0) g_graphicsContext.Lock(); + iXBMCGUILockRef++; +} + +void CAddonHelpers_GUI::Unlock() +{ + if (iXBMCGUILockRef > 0) + { + iXBMCGUILockRef--; + if (iXBMCGUILockRef == 0) g_graphicsContext.Unlock(); + } +} + +int CAddonHelpers_GUI::GetScreenHeight() +{ + return g_graphicsContext.GetHeight(); +} + +int CAddonHelpers_GUI::GetScreenWidth() +{ + return g_graphicsContext.GetWidth(); +} + +int CAddonHelpers_GUI::GetVideoResolution() +{ + return (int)g_graphicsContext.GetVideoResolution(); +} + +GUIHANDLE CAddonHelpers_GUI::Window_New(void *addonData, const char *xmlFilename, const char *defaultSkin, bool forceFallback, bool asDialog) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return NULL; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + RESOLUTION res; + CStdString strSkinPath; + if (!forceFallback) + { + /* Check to see if the XML file exists in current skin. If not use + fallback path to find a skin for the addon */ + strSkinPath = g_SkinInfo->GetSkinPath(xmlFilename, &res); + + if (!XFILE::CFile::Exists(strSkinPath)) + { + /* Check for the matching folder for the skin in the fallback skins folder */ + CStdString basePath; + CUtil::AddFileToFolder(guiHelper->m_addon->Path(), "resources", basePath); + CUtil::AddFileToFolder(basePath, "skins", basePath); + CUtil::AddFileToFolder(basePath, CUtil::GetFileName(g_SkinInfo->Path()), basePath); + strSkinPath = g_SkinInfo->GetSkinPath(xmlFilename, &res, basePath); + if (!XFILE::CFile::Exists(strSkinPath)) + { + /* Finally fallback to the DefaultSkin as it didn't exist in either the + XBMC Skin folder or the fallback skin folder */ + forceFallback = true; + } + } + } + + if (forceFallback) + { + //FIXME make this static method of current skin? + CStdString str("none"); + AddonProps props(str, ADDON_SKIN, str); + CSkinInfo skinInfo(props); + CStdString basePath; + CUtil::AddFileToFolder(guiHelper->m_addon->Path(), "resources", basePath); + CUtil::AddFileToFolder(basePath, "skins", basePath); + CUtil::AddFileToFolder(basePath, defaultSkin, basePath); + + skinInfo.Start(basePath); + strSkinPath = skinInfo.GetSkinPath(xmlFilename, &res, basePath); + + if (!XFILE::CFile::Exists(strSkinPath)) + { + CLog::Log(LOGERROR, "Window_New: %s/%s - XML File '%s' for Window is missing, contact Developer '%s' of this AddOn", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str(), strSkinPath.c_str(), guiHelper->m_addon->Author().c_str()); + return NULL; + } + } + // window id's 14000 - 14100 are reserved for addons + // get first window id that is not in use + int id = WINDOW_ADDON_START; + // if window 14099 is in use it means addon can't create more windows + Lock(); + if (g_windowManager.GetWindow(WINDOW_ADDON_END)) + { + Unlock(); + CLog::Log(LOGERROR, "Window_New: %s/%s - maximum number of windows reached", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return NULL; + } + while(id < WINDOW_ADDON_END && g_windowManager.GetWindow(id) != NULL) id++; + Unlock(); + + CGUIWindow *window; + if (!asDialog) + window = new CGUIAddonWindow(id, strSkinPath, guiHelper->m_addon); + else + window = new CGUIAddonWindowDialog(id, strSkinPath, guiHelper->m_addon); + + Lock(); + g_windowManager.Add(window); + Unlock(); + + window->SetCoordsRes(res); + + return window; +} + +void CAddonHelpers_GUI::Window_Delete(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_Show: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return; + + Lock(); + // first change to an existing window + if (g_windowManager.GetActiveWindow() == pAddonWindow->m_iWindowId && !g_application.m_bStop) + { + if(g_windowManager.GetWindow(pAddonWindow->m_iOldWindowId)) + g_windowManager.ActivateWindow(pAddonWindow->m_iOldWindowId); + else // old window does not exist anymore, switch to home + g_windowManager.ActivateWindow(WINDOW_HOME); + } + // Free any window properties + pAddonWindow->ClearProperties(); + // free the window's resources and unload it (free all guicontrols) + pAddonWindow->FreeResources(true); + + g_windowManager.Remove(pAddonWindow->GetID()); + delete pAddonWindow; + Unlock(); +} + +void CAddonHelpers_GUI::Window_SetCallbacks(void *addonData, GUIHANDLE handle, GUIHANDLE clienthandle, bool (*initCB)(GUIHANDLE), bool (*clickCB)(GUIHANDLE, int), bool (*focusCB)(GUIHANDLE, int), bool (*onActionCB)(GUIHANDLE handle, int)) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + + Lock(); + pAddonWindow->m_clientHandle = clienthandle; + pAddonWindow->CBOnInit = initCB; + pAddonWindow->CBOnClick = clickCB; + pAddonWindow->CBOnFocus = focusCB; + pAddonWindow->CBOnAction = onActionCB; + Unlock(); +} + +bool CAddonHelpers_GUI::Window_Show(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return false; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_Show: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return false; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return false; + + if (pAddonWindow->m_iOldWindowId != pAddonWindow->m_iWindowId && pAddonWindow->m_iWindowId != g_windowManager.GetActiveWindow()) + pAddonWindow->m_iOldWindowId = g_windowManager.GetActiveWindow(); + + Lock(); + if (pAddonWindow->IsDialog()) + ((CGUIAddonWindowDialog*)pAddonWindow)->Show(); + else + g_windowManager.ActivateWindow(pAddonWindow->m_iWindowId); + Unlock(); + + return true; +} + +bool CAddonHelpers_GUI::Window_Close(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return false; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_Close: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return false; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return false; + + pAddonWindow->m_bModal = false; + if (pAddonWindow->IsDialog()) + ((CGUIAddonWindowDialog*)pAddonWindow)->PulseActionEvent(); + else + ((CGUIAddonWindow*)pAddonWindow)->PulseActionEvent(); + + Lock(); + // if it's a dialog, we have to close it a bit different + if (pAddonWindow->IsDialog()) + ((CGUIAddonWindowDialog*)pAddonWindow)->Show(false); + else + g_windowManager.ActivateWindow(pAddonWindow->m_iOldWindowId); + pAddonWindow->m_iOldWindowId = 0; + + Unlock(); + + return true; +} + +bool CAddonHelpers_GUI::Window_DoModal(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return false; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_DoModal: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return false; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return false; + + pAddonWindow->m_bModal = true; + + if (pAddonWindow->m_iWindowId != g_windowManager.GetActiveWindow()) + Window_Show(addonData, handle); + + return true; +} + +bool CAddonHelpers_GUI::Window_SetFocusId(void *addonData, GUIHANDLE handle, int iControlId) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return false; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_SetFocusId: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return false; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return false; + + if(!pWindow->GetControl(iControlId)) + { + CLog::Log(LOGERROR, "Window_SetFocusId: %s/%s - Control does not exist in window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return false; + } + + Lock(); + CGUIMessage msg = CGUIMessage(GUI_MSG_SETFOCUS, pAddonWindow->m_iWindowId, iControlId); + pWindow->OnMessage(msg); + Unlock(); + + return true; +} + +int CAddonHelpers_GUI::Window_GetFocusId(void *addonData, GUIHANDLE handle) +{ + int iControlId = -1; + + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return iControlId; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_GetFocusId: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return iControlId; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return iControlId; + + Lock(); + iControlId = pWindow->GetFocusedControlID(); + Unlock(); + + if (iControlId == -1) + { + CLog::Log(LOGERROR, "Window_GetFocusId: %s/%s - No control in this window has focus", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return iControlId; + } + + return iControlId; +} + +bool CAddonHelpers_GUI::Window_SetCoordinateResolution(void *addonData, GUIHANDLE handle, int res) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return false; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "SetCoordinateResolution: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return false; + } + + if (res < RES_HDTV_1080i || res > RES_AUTORES) + { + CLog::Log(LOGERROR, "SetCoordinateResolution: %s/%s - Invalid resolution", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return false; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return false; + + pWindow->SetCoordsRes((RESOLUTION)res); + + return true; +} + +void CAddonHelpers_GUI::Window_SetProperty(void *addonData, GUIHANDLE handle, const char *key, const char *value) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_SetProperty: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return; + + CStdString lowerKey = key; + + Lock(); + pWindow->SetProperty(lowerKey.ToLower(), value); + Unlock(); +} + +void CAddonHelpers_GUI::Window_SetPropertyInt(void *addonData, GUIHANDLE handle, const char *key, int value) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_SetPropertyInt: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return; + + CStdString lowerKey = key; + + Lock(); + pWindow->SetProperty(lowerKey.ToLower(), value); + Unlock(); +} + +void CAddonHelpers_GUI::Window_SetPropertyBool(void *addonData, GUIHANDLE handle, const char *key, bool value) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_SetPropertyBool: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return; + + CStdString lowerKey = key; + + Lock(); + pWindow->SetProperty(lowerKey.ToLower(), value); + Unlock(); +} + +void CAddonHelpers_GUI::Window_SetPropertyDouble(void *addonData, GUIHANDLE handle, const char *key, double value) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_SetPropertyDouble: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return; + + CStdString lowerKey = key; + + Lock(); + pWindow->SetProperty(lowerKey.ToLower(), value); + Unlock(); +} + +const char* CAddonHelpers_GUI::Window_GetProperty(void *addonData, GUIHANDLE handle, const char *key) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return NULL; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_GetProperty: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return NULL; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return NULL; + + Lock(); + CStdString lowerKey = key; + string value = pWindow->GetProperty(lowerKey.ToLower()); + Unlock(); + + return value.c_str(); +} + +int CAddonHelpers_GUI::Window_GetPropertyInt(void *addonData, GUIHANDLE handle, const char *key) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return -1; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_GetPropertyInt: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return -1; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return -1; + + Lock(); + CStdString lowerKey = key; + int value = pWindow->GetPropertyInt(lowerKey.ToLower()); + Unlock(); + + return value; +} + +bool CAddonHelpers_GUI::Window_GetPropertyBool(void *addonData, GUIHANDLE handle, const char *key) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return false; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_GetPropertyBool: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return false; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return false; + + Lock(); + CStdString lowerKey = key; + bool value = pWindow->GetPropertyBool(lowerKey.ToLower()); + Unlock(); + + return value; +} + +double CAddonHelpers_GUI::Window_GetPropertyDouble(void *addonData, GUIHANDLE handle, const char *key) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return 0.0; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_GetPropertyDouble: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return 0.0; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return 0.0; + + Lock(); + CStdString lowerKey = key; + double value = pWindow->GetPropertyDouble(lowerKey.ToLower()); + Unlock(); + + return value; +} + +void CAddonHelpers_GUI::Window_ClearProperties(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + + if (!handle) + { + CLog::Log(LOGERROR, "Window_ClearProperties: %s/%s - No Window", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return; + } + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIWindow *pWindow = (CGUIWindow*)g_windowManager.GetWindow(pAddonWindow->m_iWindowId); + if (!pWindow) + return; + + Lock(); + pWindow->ClearProperties(); + Unlock(); +} + +int CAddonHelpers_GUI::Window_GetListSize(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return -1; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + + Lock(); + int listSize = pAddonWindow->GetListSize(); + Unlock(); + + return listSize; +} + +void CAddonHelpers_GUI::Window_ClearList(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + + Lock(); + pAddonWindow->ClearList(); + Unlock(); + + return; +} + +GUIHANDLE CAddonHelpers_GUI::Window_AddItem(void *addonData, GUIHANDLE handle, GUIHANDLE item, int itemPosition) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle || !item) + return NULL; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CFileItemPtr pItem((CFileItem*)item); + Lock(); + pAddonWindow->AddItem(pItem, itemPosition); + Unlock(); + + return item; +} + +GUIHANDLE CAddonHelpers_GUI::Window_AddStringItem(void *addonData, GUIHANDLE handle, const char *itemName, int itemPosition) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle || !itemName) + return NULL; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CFileItemPtr item(new CFileItem(itemName)); + Lock(); + pAddonWindow->AddItem(item, itemPosition); + Unlock(); + + return item.get(); +} + +void CAddonHelpers_GUI::Window_RemoveItem(void *addonData, GUIHANDLE handle, int itemPosition) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + + Lock(); + pAddonWindow->RemoveItem(itemPosition); + Unlock(); + + return; +} + +GUIHANDLE CAddonHelpers_GUI::Window_GetListItem(void *addonData, GUIHANDLE handle, int listPos) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return NULL; + + CAddonHelpers_GUI* guiHelper = helper->GetHelperGUI(); + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + + Lock(); + CFileItemPtr fi = pAddonWindow->GetListItem(listPos); + if (fi == NULL) + { + Unlock(); + CLog::Log(LOGERROR, "Window_GetListItem: %s/%s - Index out of range", TranslateType(guiHelper->m_addon->Type()).c_str(), guiHelper->m_addon->Name().c_str()); + return NULL; + } + Unlock(); + + return fi.get(); +} + +void CAddonHelpers_GUI::Window_SetCurrentListPosition(void *addonData, GUIHANDLE handle, int listPos) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + + Lock(); + pAddonWindow->SetCurrentListPosition(listPos); + Unlock(); + + return; +} + +int CAddonHelpers_GUI::Window_GetCurrentListPosition(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return -1; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + + Lock(); + int listPos = pAddonWindow->GetCurrentListPosition(); + Unlock(); + + return listPos; +} + +GUIHANDLE CAddonHelpers_GUI::Window_GetControl_Spin(void *addonData, GUIHANDLE handle, int controlId) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return NULL; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIControl* pGUIControl = (CGUIControl*)pAddonWindow->GetControl(controlId); + if (pGUIControl && pGUIControl->GetControlType() != CGUIControl::GUICONTROL_SPINEX) + return NULL; + + return pGUIControl; +} + +GUIHANDLE CAddonHelpers_GUI::Window_GetControl_Button(void *addonData, GUIHANDLE handle, int controlId) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return NULL; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIControl* pGUIControl = (CGUIControl*)pAddonWindow->GetControl(controlId); + if (pGUIControl && pGUIControl->GetControlType() != CGUIControl::GUICONTROL_BUTTON) + return NULL; + + return pGUIControl; +} + +GUIHANDLE CAddonHelpers_GUI::Window_GetControl_RadioButton(void *addonData, GUIHANDLE handle, int controlId) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return NULL; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIControl* pGUIControl = (CGUIControl*)pAddonWindow->GetControl(controlId); + if (pGUIControl && pGUIControl->GetControlType() != CGUIControl::GUICONTROL_RADIO) + return NULL; + + return pGUIControl; +} + +GUIHANDLE CAddonHelpers_GUI::Window_GetControl_Edit(void *addonData, GUIHANDLE handle, int controlId) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return NULL; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIControl* pGUIControl = (CGUIControl*)pAddonWindow->GetControl(controlId); + if (pGUIControl && pGUIControl->GetControlType() != CGUIControl::GUICONTROL_EDIT) + return NULL; + + return pGUIControl; +} + +GUIHANDLE CAddonHelpers_GUI::Window_GetControl_Progress(void *addonData, GUIHANDLE handle, int controlId) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return NULL; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + CGUIControl* pGUIControl = (CGUIControl*)pAddonWindow->GetControl(controlId); + if (pGUIControl && pGUIControl->GetControlType() != CGUIControl::GUICONTROL_PROGRESS) + return NULL; + + return pGUIControl; +} + +void CAddonHelpers_GUI::Window_SetControlLabel(void *addonData, GUIHANDLE handle, int controlId, const char *label) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + CGUIAddonWindow *pAddonWindow = (CGUIAddonWindow*)handle; + + CGUIMessage msg(GUI_MSG_LABEL_SET, pAddonWindow->m_iWindowId, controlId); + msg.SetLabel(label); + pAddonWindow->OnMessage(msg); +} + +void CAddonHelpers_GUI::Control_Spin_SetVisible(void *addonData, GUIHANDLE spinhandle, bool yesNo) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !spinhandle) + return; + + CGUISpinControlEx *pSpin = (CGUISpinControlEx*)spinhandle; + pSpin->SetVisible(yesNo); +} + +void CAddonHelpers_GUI::Control_Spin_SetText(void *addonData, GUIHANDLE spinhandle, const char *label) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !spinhandle) + return; + + CGUISpinControlEx *pSpin = (CGUISpinControlEx*)spinhandle; + pSpin->SetText(label); +} + +void CAddonHelpers_GUI::Control_Spin_Clear(void *addonData, GUIHANDLE spinhandle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !spinhandle) + return; + + CGUISpinControlEx *pSpin = (CGUISpinControlEx*)spinhandle; + pSpin->Clear(); +} + +void CAddonHelpers_GUI::Control_Spin_AddLabel(void *addonData, GUIHANDLE spinhandle, const char *label, int iValue) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !spinhandle) + return; + + CGUISpinControlEx *pSpin = (CGUISpinControlEx*)spinhandle; + pSpin->AddLabel(label, iValue); +} + +int CAddonHelpers_GUI::Control_Spin_GetValue(void *addonData, GUIHANDLE spinhandle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !spinhandle) + return -1; + + CGUISpinControlEx *pSpin = (CGUISpinControlEx*)spinhandle; + return pSpin->GetValue(); +} + +void CAddonHelpers_GUI::Control_Spin_SetValue(void *addonData, GUIHANDLE spinhandle, int iValue) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !spinhandle) + return; + + CGUISpinControlEx *pSpin = (CGUISpinControlEx*)spinhandle; + pSpin->SetValue(iValue); +} + +void CAddonHelpers_GUI::Control_RadioButton_SetVisible(void *addonData, GUIHANDLE handle, bool yesNo) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + CGUIRadioButtonControl *pRadioButton = (CGUIRadioButtonControl*)handle; + pRadioButton->SetVisible(yesNo); +} + +void CAddonHelpers_GUI::Control_RadioButton_SetText(void *addonData, GUIHANDLE handle, const char *label) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + CGUIRadioButtonControl *pRadioButton = (CGUIRadioButtonControl*)handle; + pRadioButton->SetLabel(label); +} + +void CAddonHelpers_GUI::Control_RadioButton_SetSelected(void *addonData, GUIHANDLE handle, bool yesNo) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + CGUIRadioButtonControl *pRadioButton = (CGUIRadioButtonControl*)handle; + pRadioButton->SetSelected(yesNo); +} + +bool CAddonHelpers_GUI::Control_RadioButton_IsSelected(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return false; + + CGUIRadioButtonControl *pRadioButton = (CGUIRadioButtonControl*)handle; + return pRadioButton->IsSelected(); +} + +void CAddonHelpers_GUI::Control_Progress_SetPercentage(void *addonData, GUIHANDLE handle, float fPercent) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + CGUIProgressControl *pControl = (CGUIProgressControl*)handle; + pControl->SetPercentage(fPercent); +} + +float CAddonHelpers_GUI::Control_Progress_GetPercentage(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return 0.0; + + CGUIProgressControl *pControl = (CGUIProgressControl*)handle; + return pControl->GetPercentage(); +} + +void CAddonHelpers_GUI::Control_Progress_SetInfo(void *addonData, GUIHANDLE handle, int iInfo) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + CGUIProgressControl *pControl = (CGUIProgressControl*)handle; + pControl->SetInfo(iInfo); +} + +int CAddonHelpers_GUI::Control_Progress_GetInfo(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return -1; + + CGUIProgressControl *pControl = (CGUIProgressControl*)handle; + return pControl->GetInfo(); +} + +const char* CAddonHelpers_GUI::Control_Progress_GetDescription(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return NULL; + + CGUIProgressControl *pControl = (CGUIProgressControl*)handle; + CStdString string = pControl->GetDescription(); + + char *buffer = (char*) malloc (string.length()+1); + strcpy(buffer, string.c_str()); + return buffer; +} + +GUIHANDLE CAddonHelpers_GUI::ListItem_Create(void *addonData, const char *label, const char *label2, const char *iconImage, const char *thumbnailImage, const char *path) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper) + return NULL; + + // create CFileItem + CFileItem *pItem = new CFileItem(); + if (!pItem) + return NULL; + + if (label) + pItem->SetLabel(label); + if (label2) + pItem->SetLabel2(label2); + if (iconImage) + pItem->SetIconImage(iconImage); + if (thumbnailImage) + pItem->SetThumbnailImage(thumbnailImage); + if (path) + pItem->m_strPath = path; + + return pItem; +} + +const char* CAddonHelpers_GUI::ListItem_GetLabel(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return NULL; + + CStdString string = ((CFileItem*)handle)->GetLabel(); + char *buffer = (char*) malloc (string.length()+1); + strcpy(buffer, string.c_str()); + return buffer; +} + +void CAddonHelpers_GUI::ListItem_SetLabel(void *addonData, GUIHANDLE handle, const char *label) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + ((CFileItem*)handle)->SetLabel(label); +} + +const char* CAddonHelpers_GUI::ListItem_GetLabel2(void *addonData, GUIHANDLE handle) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return NULL; + + CStdString string = ((CFileItem*)handle)->GetLabel2(); + + char *buffer = (char*) malloc (string.length()+1); + strcpy(buffer, string.c_str()); + return buffer; +} + +void CAddonHelpers_GUI::ListItem_SetLabel2(void *addonData, GUIHANDLE handle, const char *label) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + ((CFileItem*)handle)->SetLabel2(label); +} + +void CAddonHelpers_GUI::ListItem_SetIconImage(void *addonData, GUIHANDLE handle, const char *image) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + ((CFileItem*)handle)->SetIconImage(image); +} + +void CAddonHelpers_GUI::ListItem_SetThumbnailImage(void *addonData, GUIHANDLE handle, const char *image) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + ((CFileItem*)handle)->SetThumbnailImage(image); +} + +void CAddonHelpers_GUI::ListItem_SetInfo(void *addonData, GUIHANDLE handle, const char *info) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + +} + +void CAddonHelpers_GUI::ListItem_SetProperty(void *addonData, GUIHANDLE handle, const char *key, const char *value) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + ((CFileItem*)handle)->SetProperty(key, value); +} + +const char* CAddonHelpers_GUI::ListItem_GetProperty(void *addonData, GUIHANDLE handle, const char *key) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return NULL; + + CStdString string = ((CFileItem*)handle)->GetProperty(key); + char *buffer = (char*) malloc (string.length()+1); + strcpy(buffer, string.c_str()); + return buffer; +} + +void CAddonHelpers_GUI::ListItem_SetPath(void *addonData, GUIHANDLE handle, const char *path) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (!helper || !handle) + return; + + ((CFileItem*)handle)->m_strPath = path; +} + + + + + + + +CGUIAddonWindow::CGUIAddonWindow(int id, CStdString strXML, CAddon* addon) + : CGUIMediaWindow(id, strXML) + , m_iWindowId(id) + , m_iOldWindowId(0) + , m_bModal(false) + , m_bIsDialog(false) + , m_addon(addon) +{ + m_actionEvent = CreateEvent(NULL, true, false, NULL); + m_loadOnDemand = false; + CBOnInit = NULL; + CBOnFocus = NULL; + CBOnClick = NULL; + CBOnAction = NULL; +} + +CGUIAddonWindow::~CGUIAddonWindow(void) +{ + CloseHandle(m_actionEvent); +} + +bool CGUIAddonWindow::OnAction(const CAction &action) +{ + // do the base class window first, and the call to python after this + bool ret = CGUIWindow::OnAction(action); // we don't currently want the mediawindow actions here + if (CBOnAction) + { + CBOnAction(m_clientHandle, action.GetID()); + } + return ret; +} + +bool CGUIAddonWindow::OnMessage(CGUIMessage& message) +{ + // TODO: We shouldn't be dropping down to CGUIWindow in any of this ideally. + // We have to make up our minds about what python should be doing and + // what this side of things should be doing + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_DEINIT: + { + return CGUIMediaWindow::OnMessage(message); + } + break; + + case GUI_MSG_WINDOW_INIT: + { + CGUIMediaWindow::OnMessage(message); + if (CBOnInit) + CBOnInit(m_clientHandle); + + return true; + } + break; + + case GUI_MSG_SETFOCUS: + { + if (m_viewControl.HasControl(message.GetControlId()) && m_viewControl.GetCurrentControl() != (int)message.GetControlId()) + { + m_viewControl.SetFocused(); + return true; + } + // check if our focused control is one of our category buttons + int iControl = message.GetControlId(); + if (CBOnFocus) + { + CBOnFocus(m_clientHandle, iControl); + } + } + break; + case GUI_MSG_CLICKED: + { + int iControl=message.GetSenderId(); + // Handle Sort/View internally. Scripters shouldn't use ID 2, 3 or 4. + if (iControl == CONTROL_BTNSORTASC) // sort asc + { + CLog::Log(LOGINFO, "WindowXML: Internal asc/dsc button not implemented"); + /*if (m_guiState.get()) + m_guiState->SetNextSortOrder(); + UpdateFileList();*/ + return true; + } + else if (iControl == CONTROL_BTNSORTBY) // sort by + { + CLog::Log(LOGINFO, "WindowXML: Internal sort button not implemented"); + /*if (m_guiState.get()) + m_guiState->SetNextSortMethod(); + UpdateFileList();*/ + return true; + } + + if (CBOnClick && iControl && iControl != (int)this->GetID()) + { + CGUIControl* controlClicked = (CGUIControl*)this->GetControl(iControl); + + // The old python way used to check list AND SELECITEM method or if its a button, checkmark. + // Its done this way for now to allow other controls without a python version like togglebutton to still raise a onAction event + if (controlClicked) // Will get problems if we the id is not on the window and we try to do GetControlType on it. So check to make sure it exists + { + if ((controlClicked->IsContainer() && (message.GetParam1() == ACTION_SELECT_ITEM || + message.GetParam1() == ACTION_MOUSE_LEFT_CLICK)) || + !controlClicked->IsContainer()) + { + CBOnClick(m_clientHandle, iControl); + } + else if (controlClicked->IsContainer() && message.GetParam1() == ACTION_MOUSE_RIGHT_CLICK) + { +// PyXBMCAction* inf = new PyXBMCAction; +// inf->pObject = Action_FromAction(CAction(ACTION_CONTEXT_MENU)); +// inf->pCallbackWindow = pCallbackWindow; +// +// // aquire lock? +// PyXBMC_AddPendingCall(Py_XBMC_Event_OnAction, inf); +// PulseActionEvent(); + } + return true; + } + } + } + break; + } + + return CGUIMediaWindow::OnMessage(message); +} + +void CGUIAddonWindow::AllocResources(bool forceLoad /*= FALSE */) +{ + CStdString tmpDir; + CUtil::GetDirectory(GetProperty("xmlfile"), tmpDir); + CStdString fallbackMediaPath; + CUtil::GetParentPath(tmpDir, fallbackMediaPath); + CUtil::RemoveSlashAtEnd(fallbackMediaPath); + m_mediaDir = fallbackMediaPath; + + //CLog::Log(LOGDEBUG, "CGUIPythonWindowXML::AllocResources called: %s", fallbackMediaPath.c_str()); + g_TextureManager.AddTexturePath(m_mediaDir); + CGUIMediaWindow::AllocResources(forceLoad); + g_TextureManager.RemoveTexturePath(m_mediaDir); +} + +void CGUIAddonWindow::FreeResources(bool forceUnLoad /*= FALSE */) +{ + // Unload temporary language strings + ClearAddonStrings(); + + CGUIMediaWindow::FreeResources(forceUnLoad); +} + +void CGUIAddonWindow::Render() +{ + g_TextureManager.AddTexturePath(m_mediaDir); + CGUIMediaWindow::Render(); + g_TextureManager.RemoveTexturePath(m_mediaDir); +} + +void CGUIAddonWindow::Update() +{ +} + +void CGUIAddonWindow::AddItem(CFileItemPtr fileItem, int itemPosition) +{ + if (itemPosition == -1 || itemPosition > m_vecItems->Size()) + { + m_vecItems->Add(fileItem); + } + else if (itemPosition < -1 && !(itemPosition-1 < m_vecItems->Size())) + { + m_vecItems->AddFront(fileItem,0); + } + else + { + m_vecItems->AddFront(fileItem,itemPosition); + } + m_viewControl.SetItems(*m_vecItems); + UpdateButtons(); +} + +void CGUIAddonWindow::RemoveItem(int itemPosition) +{ + m_vecItems->Remove(itemPosition); + m_viewControl.SetItems(*m_vecItems); + UpdateButtons(); +} + +int CGUIAddonWindow::GetCurrentListPosition() +{ + return m_viewControl.GetSelectedItem(); +} + +void CGUIAddonWindow::SetCurrentListPosition(int item) +{ + m_viewControl.SetSelectedItem(item); +} + +int CGUIAddonWindow::GetListSize() +{ + return m_vecItems->Size(); +} + +CFileItemPtr CGUIAddonWindow::GetListItem(int position) +{ + if (position < 0 || position >= m_vecItems->Size()) return CFileItemPtr(); + return m_vecItems->Get(position); +} + +void CGUIAddonWindow::ClearList() +{ + ClearFileItems(); + + m_viewControl.SetItems(*m_vecItems); + UpdateButtons(); +} + +void CGUIAddonWindow::GetContextButtons(int itemNumber, CContextButtons &buttons) +{ + // maybe on day we can make an easy way to do this context menu + // with out this method overriding the MediaWindow version, it will display 'Add to Favorites' +} + +void CGUIAddonWindow::WaitForActionEvent(unsigned int timeout) +{ + WaitForSingleObject(m_actionEvent, timeout); + ResetEvent(m_actionEvent); +} + +void CGUIAddonWindow::PulseActionEvent() +{ + SetEvent(m_actionEvent); +} + +void CGUIAddonWindow::ClearAddonStrings() +{ + // Unload temporary language strings + g_localizeStrings.ClearBlock(m_addon->Path()); +} + +bool CGUIAddonWindow::OnClick(int iItem) +{ + // Hook Over calling CGUIMediaWindow::OnClick(iItem) results in it trying to PLAY the file item + // which if its not media is BAD and 99 out of 100 times undesireable. + return false; +} + +// SetupShares(); +/* + CGUIMediaWindow::OnWindowLoaded() calls SetupShares() so override it +and just call UpdateButtons(); +*/ +void CGUIAddonWindow::SetupShares() +{ + UpdateButtons(); +} + + +CGUIAddonWindowDialog::CGUIAddonWindowDialog(int id, CStdString strXML, CAddon* addon) +: CGUIAddonWindow(id,strXML,addon) +{ + m_bRunning = false; + m_loadOnDemand = false; + m_bIsDialog = true; +} + +CGUIAddonWindowDialog::~CGUIAddonWindowDialog(void) +{ +} + +bool CGUIAddonWindowDialog::OnMessage(CGUIMessage &message) +{ + if (message.GetMessage() == GUI_MSG_WINDOW_DEINIT) + { + CGUIWindow *pWindow = g_windowManager.GetWindow(g_windowManager.GetActiveWindow()); + if (pWindow) + g_windowManager.ShowOverlay(pWindow->GetOverlayState()); + return CGUIWindow::OnMessage(message); + } + return CGUIAddonWindow::OnMessage(message); +} + +void CGUIAddonWindowDialog::Show(bool show /* = true */) +{ + int count = ExitCriticalSection(g_graphicsContext); + ThreadMessage tMsg = {TMSG_GUI_ADDON_DIALOG, 1, show ? 1 : 0}; + tMsg.lpVoid = this; + g_application.getApplicationMessenger().SendMessage(tMsg, true); + RestoreCriticalSection(g_graphicsContext, count); +} + +void CGUIAddonWindowDialog::Show_Internal(bool show /* = true */) +{ + if (show) + { + m_bModal = true; + m_bRunning = true; + g_windowManager.RouteToWindow(this); + + // active this window... + CGUIMessage msg(GUI_MSG_WINDOW_INIT, 0, 0, WINDOW_INVALID, m_iWindowId); + OnMessage(msg); + + while (m_bRunning && !g_application.m_bStop) + { + g_windowManager.Process(); + } + } + else // hide + { + m_bRunning = false; + + CGUIMessage msg(GUI_MSG_WINDOW_DEINIT,0,0); + OnMessage(msg); + + g_windowManager.RemoveDialog(GetID()); + } +} + +}; /* namespace ADDON */ diff --git a/xbmc/addons/AddonHelpers_GUI.h b/xbmc/addons/AddonHelpers_GUI.h new file mode 100644 index 0000000000..fe8643ed9d --- /dev/null +++ b/xbmc/addons/AddonHelpers_GUI.h @@ -0,0 +1,180 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "AddonHelpers_local.h" +#include "GUIMediaWindow.h" + +class CGUISpinControlEx; +class CGUIButtonControl; +class CGUIRadioButtonControl; +class CGUISettingsSliderControl; +class CGUIEditControl; + +namespace ADDON +{ + +class CAddonHelpers_GUI +{ +public: + CAddonHelpers_GUI(CAddon* addon); + ~CAddonHelpers_GUI(); + + /**! \name General Functions */ + CB_GUILib *GetCallbacks() { return m_callbacks; } + + static void Lock(); + static void Unlock(); + static int GetScreenHeight(); + static int GetScreenWidth(); + static int GetVideoResolution(); + + static GUIHANDLE Window_New(void *addonData, const char *xmlFilename, const char *defaultSkin, bool forceFallback, bool asDialog); + static void Window_Delete(void *addonData, GUIHANDLE handle); + static void Window_SetCallbacks(void *addonData, GUIHANDLE handle, GUIHANDLE clienthandle, bool (*initCB)(GUIHANDLE), bool (*clickCB)(GUIHANDLE, int), bool (*focusCB)(GUIHANDLE, int), bool (*onActionCB)(GUIHANDLE handle, int)); + static bool Window_Show(void *addonData, GUIHANDLE handle); + static bool Window_Close(void *addonData, GUIHANDLE handle); + static bool Window_DoModal(void *addonData, GUIHANDLE handle); + static bool Window_SetFocusId(void *addonData, GUIHANDLE handle, int iControlId); + static int Window_GetFocusId(void *addonData, GUIHANDLE handle); + static bool Window_SetCoordinateResolution(void *addonData, GUIHANDLE handle, int res); + static void Window_SetProperty(void *addonData, GUIHANDLE handle, const char *key, const char *value); + static void Window_SetPropertyInt(void *addonData, GUIHANDLE handle, const char *key, int value); + static void Window_SetPropertyBool(void *addonData, GUIHANDLE handle, const char *key, bool value); + static void Window_SetPropertyDouble(void *addonData, GUIHANDLE handle, const char *key, double value); + static const char * Window_GetProperty(void *addonData, GUIHANDLE handle, const char *key); + static int Window_GetPropertyInt(void *addonData, GUIHANDLE handle, const char *key); + static bool Window_GetPropertyBool(void *addonData, GUIHANDLE handle, const char *key); + static double Window_GetPropertyDouble(void *addonData, GUIHANDLE handle, const char *key); + static void Window_ClearProperties(void *addonData, GUIHANDLE handle); + static int Window_GetListSize(void *addonData, GUIHANDLE handle); + static void Window_ClearList(void *addonData, GUIHANDLE handle); + static GUIHANDLE Window_AddItem(void *addonData, GUIHANDLE handle, GUIHANDLE item, int itemPosition); + static GUIHANDLE Window_AddStringItem(void *addonData, GUIHANDLE handle, const char *itemName, int itemPosition); + static void Window_RemoveItem(void *addonData, GUIHANDLE handle, int itemPosition); + static GUIHANDLE Window_GetListItem(void *addonData, GUIHANDLE handle, int listPos); + static void Window_SetCurrentListPosition(void *addonData, GUIHANDLE handle, int listPos); + static int Window_GetCurrentListPosition(void *addonData, GUIHANDLE handle); + static GUIHANDLE Window_GetControl_Spin(void *addonData, GUIHANDLE handle, int controlId); + static GUIHANDLE Window_GetControl_Button(void *addonData, GUIHANDLE handle, int controlId); + static GUIHANDLE Window_GetControl_RadioButton(void *addonData, GUIHANDLE handle, int controlId); + static GUIHANDLE Window_GetControl_Edit(void *addonData, GUIHANDLE handle, int controlId); + static GUIHANDLE Window_GetControl_Progress(void *addonData, GUIHANDLE handle, int controlId); + static void Window_SetControlLabel(void *addonData, GUIHANDLE handle, int controlId, const char *label); + static void Control_Spin_SetVisible(void *addonData, GUIHANDLE spinhandle, bool yesNo); + static void Control_Spin_SetText(void *addonData, GUIHANDLE spinhandle, const char *label); + static void Control_Spin_Clear(void *addonData, GUIHANDLE spinhandle); + static void Control_Spin_AddLabel(void *addonData, GUIHANDLE spinhandle, const char *label, int iValue); + static int Control_Spin_GetValue(void *addonData, GUIHANDLE spinhandle); + static void Control_Spin_SetValue(void *addonData, GUIHANDLE spinhandle, int iValue); + static void Control_RadioButton_SetVisible(void *addonData, GUIHANDLE handle, bool yesNo); + static void Control_RadioButton_SetText(void *addonData, GUIHANDLE handle, const char *label); + static void Control_RadioButton_SetSelected(void *addonData, GUIHANDLE handle, bool yesNo); + static bool Control_RadioButton_IsSelected(void *addonData, GUIHANDLE handle); + static void Control_Progress_SetPercentage(void *addonData, GUIHANDLE handle, float fPercent); + static float Control_Progress_GetPercentage(void *addonData, GUIHANDLE handle); + static void Control_Progress_SetInfo(void *addonData, GUIHANDLE handle, int iInfo); + static int Control_Progress_GetInfo(void *addonData, GUIHANDLE handle); + static const char * Control_Progress_GetDescription(void *addonData, GUIHANDLE handle); + static GUIHANDLE ListItem_Create(void *addonData, const char *label, const char *label2, const char *iconImage, const char *thumbnailImage, const char *path); + static const char * ListItem_GetLabel(void *addonData, GUIHANDLE handle); + static void ListItem_SetLabel(void *addonData, GUIHANDLE handle, const char *label); + static const char * ListItem_GetLabel2(void *addonData, GUIHANDLE handle); + static void ListItem_SetLabel2(void *addonData, GUIHANDLE handle, const char *label); + static void ListItem_SetIconImage(void *addonData, GUIHANDLE handle, const char *image); + static void ListItem_SetThumbnailImage(void *addonData, GUIHANDLE handle, const char *image); + static void ListItem_SetInfo(void *addonData, GUIHANDLE handle, const char *info); + static void ListItem_SetProperty(void *addonData, GUIHANDLE handle, const char *key, const char *value); + static const char * ListItem_GetProperty(void *addonData, GUIHANDLE handle, const char *key); + static void ListItem_SetPath(void *addonData, GUIHANDLE handle, const char *path); + +private: + CB_GUILib *m_callbacks; + CAddon *m_addon; +}; + +class CGUIAddonWindow : public CGUIMediaWindow +{ +friend class CAddonHelpers_GUI; + +public: + CGUIAddonWindow(int id, CStdString strXML, CAddon* addon); + virtual ~CGUIAddonWindow(void); + + virtual bool OnMessage(CGUIMessage& message); + virtual bool OnAction(const CAction &action); + virtual void AllocResources(bool forceLoad = false); + virtual void FreeResources(bool forceUnLoad = false); + virtual void Render(); + void WaitForActionEvent(unsigned int timeout); + void PulseActionEvent(); + void AddItem(CFileItemPtr fileItem, int itemPosition); + void RemoveItem(int itemPosition); + void ClearList(); + CFileItemPtr GetListItem(int position); + int GetListSize(); + int GetCurrentListPosition(); + void SetCurrentListPosition(int item); + virtual bool OnClick(int iItem); + +protected: + virtual void Update(); + virtual void GetContextButtons(int itemNumber, CContextButtons &buttons); + void ClearAddonStrings(); + void SetupShares(); + + bool (*CBOnInit)(GUIHANDLE cbhdl); + bool (*CBOnFocus)(GUIHANDLE cbhdl, int controlId); + bool (*CBOnClick)(GUIHANDLE cbhdl, int controlId); + bool (*CBOnAction)(GUIHANDLE cbhdl, int); + + GUIHANDLE m_clientHandle; + const int m_iWindowId; + int m_iOldWindowId; + bool m_bModal; + bool m_bIsDialog; + +private: + HANDLE m_actionEvent; + CAddon *m_addon; + CStdString m_mediaDir; +}; + +class CGUIAddonWindowDialog : public CGUIAddonWindow +{ +public: + CGUIAddonWindowDialog(int id, CStdString strXML, CAddon* addon); + virtual ~CGUIAddonWindowDialog(void); + + void Show(bool show = true); + virtual bool OnMessage(CGUIMessage &message); + virtual bool IsDialogRunning() const { return m_bRunning; } + virtual bool IsDialog() const { return true;}; + virtual bool IsModalDialog() const { return true; }; + virtual bool IsMediaWindow() const { return false; }; + + void Show_Internal(bool show = true); + +private: + bool m_bRunning; +}; + +}; /* namespace ADDON */ diff --git a/xbmc/addons/AddonHelpers_PVR.cpp b/xbmc/addons/AddonHelpers_PVR.cpp new file mode 100644 index 0000000000..f32277a866 --- /dev/null +++ b/xbmc/addons/AddonHelpers_PVR.cpp @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "Application.h" +#include "AddonHelpers_PVR.h" +#include "PVREpg.h" +#include "PVRChannels.h" +#include "PVRTimers.h" +#include "PVRRecordings.h" +#include "PVRClient.h" +#include "log.h" +#include "PVRManager.h" + +namespace ADDON +{ + +CAddonHelpers_PVR::CAddonHelpers_PVR(CAddon* addon) +{ + m_addon = addon; + m_callbacks = new CB_PVRLib; + + /* Write XBMC PVR specific Add-on function addresses to callback table */ + m_callbacks->TransferEpgEntry = PVRTransferEpgEntry; + m_callbacks->TransferChannelEntry = PVRTransferChannelEntry; + m_callbacks->TransferTimerEntry = PVRTransferTimerEntry; + m_callbacks->TransferRecordingEntry = PVRTransferRecordingEntry; + m_callbacks->AddMenuHook = PVRAddMenuHook; + m_callbacks->Recording = PVRRecording; + m_callbacks->TriggerTimerUpdate = PVRTriggerTimerUpdate; + m_callbacks->TriggerRecordingUpdate = PVRTriggerRecordingUpdate; + m_callbacks->FreeDemuxPacket = PVRFreeDemuxPacket; + m_callbacks->AllocateDemuxPacket = PVRAllocateDemuxPacket; +}; + +CAddonHelpers_PVR::~CAddonHelpers_PVR() +{ + delete m_callbacks; +}; + +void CAddonHelpers_PVR::PVRTransferEpgEntry(void *addonData, const PVRHANDLE handle, const PVR_PROGINFO *epgentry) +{ + CAddonHelpers* addon = (CAddonHelpers*) addonData; + if (addon == NULL || handle == NULL || epgentry == NULL) + { + CLog::Log(LOGERROR, "PVR: PVRTransferEpgEntry is called with NULL-Pointer!!!"); + return; + } + + cPVREpg *xbmcEpg = (cPVREpg*) handle->DATA_ADDRESS; + PVR_PROGINFO *epgentry2 = (PVR_PROGINFO*) epgentry; + CPVRClient* client = (CPVRClient*) handle->CALLER_ADDRESS; + epgentry2->starttime += client->GetTimeCorrection(); + epgentry2->endtime += client->GetTimeCorrection(); + if (handle->DATA_IDENTIFIER == 1) + cPVREpg::AddDB(epgentry2, xbmcEpg); + else + cPVREpg::Add(epgentry2, xbmcEpg); + + return; +} + +void CAddonHelpers_PVR::PVRTransferChannelEntry(void *addonData, const PVRHANDLE handle, const PVR_CHANNEL *channel) +{ + CAddonHelpers* addon = (CAddonHelpers*) addonData; + if (addon == NULL || handle == NULL || channel == NULL) + { + CLog::Log(LOGERROR, "PVR: PVRTransferChannelEntry is called with NULL-Pointer!!!"); + return; + } + + CPVRClient* client = (CPVRClient*) handle->CALLER_ADDRESS; + cPVRChannels *xbmcChannels = (cPVRChannels*) handle->DATA_ADDRESS; + cPVRChannelInfoTag tag; + + tag.SetChannelID(-1); + tag.SetNumber(-1); + tag.SetClientNumber(channel->number); + tag.SetGroupID(0); + tag.SetClientID(client->GetClientID()); + tag.SetUniqueID(channel->uid); + tag.SetName(channel->name); + tag.SetClientName(channel->callsign); + tag.SetIcon(channel->iconpath); + tag.SetEncryptionSystem(channel->encryption); + tag.SetRadio(channel->radio); + tag.SetHidden(channel->hide); + tag.SetRecording(channel->recording); + tag.SetInputFormat(channel->input_format); + tag.SetStreamURL(channel->stream_url); + + xbmcChannels->push_back(tag); + return; +} + +void CAddonHelpers_PVR::PVRTransferRecordingEntry(void *addonData, const PVRHANDLE handle, const PVR_RECORDINGINFO *recording) +{ + CAddonHelpers* addon = (CAddonHelpers*) addonData; + if (addon == NULL || handle == NULL || recording == NULL) + { + CLog::Log(LOGERROR, "PVR: PVRTransferRecordingEntry is called with NULL-Pointer!!!"); + return; + } + + CPVRClient* client = (CPVRClient*) handle->CALLER_ADDRESS; + cPVRRecordings *xbmcRecordings = (cPVRRecordings*) handle->DATA_ADDRESS; + + cPVRRecordingInfoTag tag; + + tag.SetClientIndex(recording->index); + tag.SetClientID(client->GetClientID()); + tag.SetTitle(recording->title); + tag.SetRecordingTime(recording->recording_time); + tag.SetDuration(CDateTimeSpan(0, 0, recording->duration / 60, recording->duration % 60)); + tag.SetPriority(recording->priority); + tag.SetLifetime(recording->lifetime); + tag.SetDirectory(recording->directory); + tag.SetPlot(recording->description); + tag.SetPlotOutline(recording->subtitle); + tag.SetStreamURL(recording->stream_url); + tag.SetChannelName(recording->channel_name); + + xbmcRecordings->push_back(tag); + return; +} + +void CAddonHelpers_PVR::PVRTransferTimerEntry(void *addonData, const PVRHANDLE handle, const PVR_TIMERINFO *timer) +{ + CAddonHelpers* addon = (CAddonHelpers*) addonData; + if (addon == NULL || handle == NULL || timer == NULL) + { + CLog::Log(LOGERROR, "PVR: PVRTransferTimerEntry is called with NULL-Pointer!!!"); + return; + } + + cPVRTimers *xbmcTimers = (cPVRTimers*) handle->DATA_ADDRESS; + CPVRClient* client = (CPVRClient*) handle->CALLER_ADDRESS; + cPVRChannelInfoTag *channel = cPVRChannels::GetByClientFromAll(timer->channelNum, client->GetClientID()); + + if (channel == NULL) + { + CLog::Log(LOGERROR, "PVR: PVRTransferTimerEntry is called with not present channel"); + return; + } + + cPVRTimerInfoTag tag; + tag.SetClientID(client->GetClientID()); + tag.SetClientIndex(timer->index); + tag.SetActive(timer->active); + tag.SetTitle(timer->title); + tag.SetDir(timer->directory); + tag.SetClientNumber(timer->channelNum); + tag.SetStart((time_t) (timer->starttime+client->GetTimeCorrection())); + tag.SetStop((time_t) (timer->endtime+client->GetTimeCorrection())); + tag.SetFirstDay((time_t) (timer->firstday+client->GetTimeCorrection())); + tag.SetPriority(timer->priority); + tag.SetLifetime(timer->lifetime); + tag.SetRecording(timer->recording); + tag.SetRepeating(timer->repeat); + tag.SetWeekdays(timer->repeatflags); + tag.SetNumber(channel->Number()); + tag.SetRadio(channel->IsRadio()); + CStdString path; + path.Format("pvr://client%i/timers/%i", tag.ClientID(), tag.ClientIndex()); + tag.SetPath(path); + + CStdString summary; + if (!tag.IsRepeating()) + { + summary.Format("%s %s %s %s %s", tag.Start().GetAsLocalizedDate() + , g_localizeStrings.Get(19159) + , tag.Start().GetAsLocalizedTime("", false) + , g_localizeStrings.Get(19160) + , tag.Stop().GetAsLocalizedTime("", false)); + } + else if (tag.FirstDay() != NULL) + { + summary.Format("%s-%s-%s-%s-%s-%s-%s %s %s %s %s %s %s" + , timer->repeatflags & 0x01 ? g_localizeStrings.Get(19149) : "__" + , timer->repeatflags & 0x02 ? g_localizeStrings.Get(19150) : "__" + , timer->repeatflags & 0x04 ? g_localizeStrings.Get(19151) : "__" + , timer->repeatflags & 0x08 ? g_localizeStrings.Get(19152) : "__" + , timer->repeatflags & 0x10 ? g_localizeStrings.Get(19153) : "__" + , timer->repeatflags & 0x20 ? g_localizeStrings.Get(19154) : "__" + , timer->repeatflags & 0x40 ? g_localizeStrings.Get(19155) : "__" + , g_localizeStrings.Get(19156) + , tag.FirstDay().GetAsLocalizedDate(false) + , g_localizeStrings.Get(19159) + , tag.Start().GetAsLocalizedTime("", false) + , g_localizeStrings.Get(19160) + , tag.Stop().GetAsLocalizedTime("", false)); + } + else + { + summary.Format("%s-%s-%s-%s-%s-%s-%s %s %s %s %s" + , timer->repeatflags & 0x01 ? g_localizeStrings.Get(19149) : "__" + , timer->repeatflags & 0x02 ? g_localizeStrings.Get(19150) : "__" + , timer->repeatflags & 0x04 ? g_localizeStrings.Get(19151) : "__" + , timer->repeatflags & 0x08 ? g_localizeStrings.Get(19152) : "__" + , timer->repeatflags & 0x10 ? g_localizeStrings.Get(19153) : "__" + , timer->repeatflags & 0x20 ? g_localizeStrings.Get(19154) : "__" + , timer->repeatflags & 0x40 ? g_localizeStrings.Get(19155) : "__" + , g_localizeStrings.Get(19159) + , tag.Start().GetAsLocalizedTime("", false) + , g_localizeStrings.Get(19160) + , tag.Stop().GetAsLocalizedTime("", false)); + } + tag.SetSummary(summary); + + xbmcTimers->push_back(tag); + return; +} + +void CAddonHelpers_PVR::PVRAddMenuHook(void *addonData, PVR_MENUHOOK *hook) +{ + CAddonHelpers* addon = (CAddonHelpers*) addonData; + if (addon == NULL || hook == NULL) + { + CLog::Log(LOGERROR, "PVR: PVRAddMenuHook is called with NULL-Pointer!!!"); + return; + } + + CAddonHelpers_PVR* addonHelper = addon->GetHelperPVR(); + CPVRClient* client = (CPVRClient*) addonHelper->m_addon; + PVR_MENUHOOKS *hooks = client->GetMenuHooks(); + + PVR_MENUHOOK hookInt; + hookInt.hook_id = hook->hook_id; + hookInt.string_id = hook->string_id; + hooks->push_back(hookInt); +} + +void CAddonHelpers_PVR::PVRRecording(void *addonData, const char *Name, const char *FileName, bool On) +{ + CAddonHelpers* addon = (CAddonHelpers*) addonData; + if (addon == NULL) + { + CLog::Log(LOGERROR, "PVR: PVRRecording is called with NULL-Pointer!!!"); + return; + } + + CAddonHelpers_PVR* addonHelper = addon->GetHelperPVR(); + + CStdString line1; + CStdString line2; + if (On) + line1.Format(g_localizeStrings.Get(19197), addonHelper->m_addon->Name()); + else + line1.Format(g_localizeStrings.Get(19198), addonHelper->m_addon->Name()); + + if (Name) + line2 = Name; + else if (FileName) + line2 = FileName; + else + line2 = ""; + + g_application.m_guiDialogKaiToast.QueueNotification(CGUIDialogKaiToast::Info, line1, line2, 5000, false); + CLog::Log(LOGDEBUG, "%s: %s-%s - Recording %s : %s %s", __FUNCTION__, TranslateType(addonHelper->m_addon->Type()).c_str(), addonHelper->m_addon->Name().c_str(), On ? "started" : "finished", Name, FileName); +} + +void CAddonHelpers_PVR::PVRTriggerTimerUpdate(void *addonData) +{ + CAddonHelpers* addon = (CAddonHelpers*) addonData; + if (addon == NULL) + { + CLog::Log(LOGERROR, "PVR: PVRTriggerTimerUpdate is called with NULL-Pointer!!!"); + return; + } + + CAddonHelpers_PVR* addonHelper = addon->GetHelperPVR(); + + g_PVRManager.TriggerTimersUpdate(false); + CLog::Log(LOGDEBUG, "%s: %s-%s - Triggered Timer Update", __FUNCTION__, TranslateType(addonHelper->m_addon->Type()).c_str(), addonHelper->m_addon->Name().c_str()); +} + +void CAddonHelpers_PVR::PVRTriggerRecordingUpdate(void *addonData) +{ + CAddonHelpers* addon = (CAddonHelpers*) addonData; + if (addon == NULL) + { + CLog::Log(LOGERROR, "PVR: PVRTriggerRecordingUpdate is called with NULL-Pointer!!!"); + return; + } + + CAddonHelpers_PVR* addonHelper = addon->GetHelperPVR(); + + g_PVRManager.TriggerRecordingsUpdate(false); + CLog::Log(LOGDEBUG, "%s: %s-%s - Triggered Recording Update", __FUNCTION__, TranslateType(addonHelper->m_addon->Type()).c_str(), addonHelper->m_addon->Name().c_str()); +} + +void CAddonHelpers_PVR::PVRFreeDemuxPacket(void *addonData, DemuxPacket* pPacket) +{ + CDVDDemuxUtils::FreeDemuxPacket(pPacket); +} + +DemuxPacket* CAddonHelpers_PVR::PVRAllocateDemuxPacket(void *addonData, int iDataSize) +{ + return CDVDDemuxUtils::AllocateDemuxPacket(iDataSize); +} + +}; /* namespace ADDON */ diff --git a/xbmc/addons/AddonHelpers_PVR.h b/xbmc/addons/AddonHelpers_PVR.h new file mode 100644 index 0000000000..b5eccb6d00 --- /dev/null +++ b/xbmc/addons/AddonHelpers_PVR.h @@ -0,0 +1,55 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "AddonHelpers_local.h" +#include "include/xbmc_pvr_types.h" + +namespace ADDON +{ + +class CAddonHelpers_PVR +{ +public: + CAddonHelpers_PVR(CAddon* addon); + ~CAddonHelpers_PVR(); + + /**! \name General Functions */ + CB_PVRLib *GetCallbacks() { return m_callbacks; } + + /**! \name Callback functions */ + static void PVRTransferEpgEntry(void *addonData, const PVRHANDLE handle, const PVR_PROGINFO *epgentry); + static void PVRTransferChannelEntry(void *addonData, const PVRHANDLE handle, const PVR_CHANNEL *channel); + static void PVRTransferTimerEntry(void *addonData, const PVRHANDLE handle, const PVR_TIMERINFO *timer); + static void PVRTransferRecordingEntry(void *addonData, const PVRHANDLE handle, const PVR_RECORDINGINFO *recording); + static void PVRAddMenuHook(void *addonData, PVR_MENUHOOK *hook); + static void PVRRecording(void *addonData, const char *Name, const char *FileName, bool On); + static void PVRTriggerTimerUpdate(void *addonData); + static void PVRTriggerRecordingUpdate(void *addonData); + static void PVRFreeDemuxPacket(void *addonData, DemuxPacket* pPacket); + static DemuxPacket* PVRAllocateDemuxPacket(void *addonData, int iDataSize = 0); + +private: + CB_PVRLib *m_callbacks; + CAddon *m_addon; +}; + +}; /* namespace ADDON */ diff --git a/xbmc/addons/AddonHelpers_local.cpp b/xbmc/addons/AddonHelpers_local.cpp new file mode 100644 index 0000000000..e0f109726e --- /dev/null +++ b/xbmc/addons/AddonHelpers_local.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "Addon.h" +#include "AddonHelpers_local.h" +#include "AddonHelpers_Addon.h" +#include "AddonHelpers_GUI.h" +#include "AddonHelpers_PVR.h" +#include "FileSystem/SpecialProtocol.h" +#include "log.h" + +namespace ADDON +{ + +CAddonHelpers::CAddonHelpers(CAddon* addon) +{ + m_addon = addon; + m_callbacks = new AddonCB; + m_helperAddon = NULL; + m_helperGUI = NULL; + m_helperPVR = NULL; + + m_callbacks->libBasePath = strdup(_P("special://xbmcbin/addons")); + m_callbacks->addonData = this; + m_callbacks->AddOnLib_RegisterMe = CAddonHelpers::AddOnLib_RegisterMe; + m_callbacks->AddOnLib_UnRegisterMe = CAddonHelpers::AddOnLib_UnRegisterMe; + m_callbacks->GUILib_RegisterMe = CAddonHelpers::GUILib_RegisterMe; + m_callbacks->GUILib_UnRegisterMe = CAddonHelpers::GUILib_UnRegisterMe; + m_callbacks->PVRLib_RegisterMe = CAddonHelpers::PVRLib_RegisterMe; + m_callbacks->PVRLib_UnRegisterMe = CAddonHelpers::PVRLib_UnRegisterMe; +} + +CAddonHelpers::~CAddonHelpers() +{ + delete m_helperAddon; + m_helperAddon = NULL; + delete m_helperGUI; + m_helperGUI = NULL; + delete m_helperPVR; + m_helperPVR = NULL; + delete m_callbacks; + m_callbacks = NULL; +} + +CB_AddOnLib* CAddonHelpers::AddOnLib_RegisterMe(void *addonData) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (helper == NULL) + { + CLog::Log(LOGERROR, "Addon-Helper: AddOnLib_RegisterMe is called with NULL-Pointer!!!"); + return NULL; + } + + helper->m_helperAddon = new CAddonHelpers_Addon(helper->m_addon); + return helper->m_helperAddon->GetCallbacks(); +} + +void CAddonHelpers::AddOnLib_UnRegisterMe(void *addonData, CB_AddOnLib *cbTable) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (helper == NULL) + { + CLog::Log(LOGERROR, "Addon-Helper: AddOnLib_UnRegisterMe is called with NULL-Pointer!!!"); + return; + } + + delete helper->m_helperAddon; + helper->m_helperAddon = NULL; +} + +CB_GUILib* CAddonHelpers::GUILib_RegisterMe(void *addonData) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (helper == NULL) + { + CLog::Log(LOGERROR, "Addon-Helper: GUILib_RegisterMe is called with NULL-Pointer!!!"); + return NULL; + } + + helper->m_helperGUI = new CAddonHelpers_GUI(helper->m_addon); + return helper->m_helperGUI->GetCallbacks(); +} + +void CAddonHelpers::GUILib_UnRegisterMe(void *addonData, CB_GUILib *cbTable) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (helper == NULL) + { + CLog::Log(LOGERROR, "Addon-Helper: GUILib_UnRegisterMe is called with NULL-Pointer!!!"); + return; + } + + delete helper->m_helperGUI; + helper->m_helperGUI = NULL; +} + +CB_PVRLib* CAddonHelpers::PVRLib_RegisterMe(void *addonData) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (helper == NULL) + { + CLog::Log(LOGERROR, "Addon-Helper: PVRLib_RegisterMe is called with NULL-Pointer!!!"); + return NULL; + } + + helper->m_helperPVR = new CAddonHelpers_PVR(helper->m_addon); + return helper->m_helperPVR->GetCallbacks(); +} + +void CAddonHelpers::PVRLib_UnRegisterMe(void *addonData, CB_PVRLib *cbTable) +{ + CAddonHelpers* helper = (CAddonHelpers*) addonData; + if (helper == NULL) + { + CLog::Log(LOGERROR, "Addon-Helper: PVRLib_UnRegisterMe is called with NULL-Pointer!!!"); + return; + } + + delete helper->m_helperPVR; + helper->m_helperPVR = NULL; +} + +}; /* namespace ADDON */ diff --git a/xbmc/addons/AddonHelpers_local.h b/xbmc/addons/AddonHelpers_local.h new file mode 100644 index 0000000000..bc9f8ff4c3 --- /dev/null +++ b/xbmc/addons/AddonHelpers_local.h @@ -0,0 +1,258 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "cores/dvdplayer/DVDDemuxers/DVDDemuxUtils.h" +#include "addons/include/xbmc_pvr_types.h" +#include "../../addons/library.xbmc.addon/libXBMC_addon.h" +#include "../../addons/library.xbmc.gui/libXBMC_gui.h" + +typedef void (*AddOnLogCallback)(void *addonData, const addon_log_t loglevel, const char *msg); +typedef void (*AddOnQueueNotification)(void *addonData, const queue_msg_t type, const char *msg); +typedef bool (*AddOnGetSetting)(void *addonData, const char *settingName, void *settingValue); +typedef char* (*AddOnUnknownToUTF8)(const char *sourceDest); +typedef char* (*AddOnGetLocalizedString)(const void* addonData, long dwCode); +typedef char* (*AddOnGetDVDMenuLanguage)(const void* addonData); + +typedef struct CB_AddOn +{ + AddOnLogCallback Log; + AddOnQueueNotification QueueNotification; + AddOnGetSetting GetSetting; + AddOnUnknownToUTF8 UnknownToUTF8; + AddOnGetLocalizedString GetLocalizedString; + AddOnGetDVDMenuLanguage GetDVDMenuLanguage; +} CB_AddOnLib; + +typedef void (*GUILock)(); +typedef void (*GUIUnlock)(); +typedef int (*GUIGetScreenHeight)(); +typedef int (*GUIGetScreenWidth)(); +typedef int (*GUIGetVideoResolution)(); +typedef GUIHANDLE (*GUIWindow_New)(void *addonData, const char *xmlFilename, const char *defaultSkin, bool forceFallback, bool asDialog); +typedef void (*GUIWindow_Delete)(void *addonData, GUIHANDLE handle); +typedef void (*GUIWindow_SetCallbacks)(void *addonData, GUIHANDLE handle, GUIHANDLE clienthandle, bool (*)(GUIHANDLE handle), bool (*)(GUIHANDLE handle, int), bool (*)(GUIHANDLE handle, int), bool (*)(GUIHANDLE handle, int)); +typedef bool (*GUIWindow_Show)(void *addonData, GUIHANDLE handle); +typedef bool (*GUIWindow_Close)(void *addonData, GUIHANDLE handle); +typedef bool (*GUIWindow_DoModal)(void *addonData, GUIHANDLE handle); +typedef bool (*GUIWindow_SetFocusId)(void *addonData, GUIHANDLE handle, int iControlId); +typedef int (*GUIWindow_GetFocusId)(void *addonData, GUIHANDLE handle); +typedef bool (*GUIWindow_SetCoordinateResolution)(void *addonData, GUIHANDLE handle, int res); +typedef void (*GUIWindow_SetProperty)(void *addonData, GUIHANDLE handle, const char *key, const char *value); +typedef void (*GUIWindow_SetPropertyInt)(void *addonData, GUIHANDLE handle, const char *key, int value); +typedef void (*GUIWindow_SetPropertyBool)(void *addonData, GUIHANDLE handle, const char *key, bool value); +typedef void (*GUIWindow_SetPropertyDouble)(void *addonData, GUIHANDLE handle, const char *key, double value); +typedef const char* (*GUIWindow_GetProperty)(void *addonData, GUIHANDLE handle, const char *key); +typedef int (*GUIWindow_GetPropertyInt)(void *addonData, GUIHANDLE handle, const char *key); +typedef bool (*GUIWindow_GetPropertyBool)(void *addonData, GUIHANDLE handle, const char *key); +typedef double (*GUIWindow_GetPropertyDouble)(void *addonData, GUIHANDLE handle, const char *key); +typedef void (*GUIWindow_ClearProperties)(void *addonData, GUIHANDLE handle); +typedef int (*GUIWindow_GetListSize)(void *addonData, GUIHANDLE handle); +typedef void (*GUIWindow_ClearList)(void *addonData, GUIHANDLE handle); +typedef GUIHANDLE (*GUIWindow_AddItem)(void *addonData, GUIHANDLE handle, GUIHANDLE item, int itemPosition); +typedef GUIHANDLE (*GUIWindow_AddStringItem)(void *addonData, GUIHANDLE handle, const char *itemName, int itemPosition); +typedef void (*GUIWindow_RemoveItem)(void *addonData, GUIHANDLE handle, int itemPosition); +typedef GUIHANDLE (*GUIWindow_GetListItem)(void *addonData, GUIHANDLE handle, int listPos); +typedef void (*GUIWindow_SetCurrentListPosition)(void *addonData, GUIHANDLE handle, int listPos); +typedef int (*GUIWindow_GetCurrentListPosition)(void *addonData, GUIHANDLE handle); +typedef GUIHANDLE (*GUIWindow_GetControl_Spin)(void *addonData, GUIHANDLE handle, int controlId); +typedef GUIHANDLE (*GUIWindow_GetControl_Button)(void *addonData, GUIHANDLE handle, int controlId); +typedef GUIHANDLE (*GUIWindow_GetControl_RadioButton)(void *addonData, GUIHANDLE handle, int controlId); +typedef GUIHANDLE (*GUIWindow_GetControl_Edit)(void *addonData, GUIHANDLE handle, int controlId); +typedef GUIHANDLE (*GUIWindow_GetControl_Progress)(void *addonData, GUIHANDLE handle, int controlId); +typedef void (*GUIWindow_SetControlLabel)(void *addonData, GUIHANDLE handle, int controlId, const char *label); +typedef void (*GUIControl_Spin_SetVisible)(void *addonData, GUIHANDLE spinhandle, bool yesNo); +typedef void (*GUIControl_Spin_SetText)(void *addonData, GUIHANDLE spinhandle, const char *label); +typedef void (*GUIControl_Spin_Clear)(void *addonData, GUIHANDLE spinhandle); +typedef void (*GUIControl_Spin_AddLabel)(void *addonData, GUIHANDLE spinhandle, const char *label, int iValue); +typedef int (*GUIControl_Spin_GetValue)(void *addonData, GUIHANDLE spinhandle); +typedef void (*GUIControl_Spin_SetValue)(void *addonData, GUIHANDLE spinhandle, int iValue); +typedef void (*GUIControl_RadioButton_SetVisible)(void *addonData, GUIHANDLE handle, bool yesNo); +typedef void (*GUIControl_RadioButton_SetText)(void *addonData, GUIHANDLE handle, const char *label); +typedef void (*GUIControl_RadioButton_SetSelected)(void *addonData, GUIHANDLE handle, bool yesNo); +typedef bool (*GUIControl_RadioButton_IsSelected)(void *addonData, GUIHANDLE handle); +typedef void (*GUIControl_Progress_SetPercentage)(void *addonData, GUIHANDLE handle, float fPercent); +typedef float (*GUIControl_Progress_GetPercentage)(void *addonData, GUIHANDLE handle); +typedef void (*GUIControl_Progress_SetInfo)(void *addonData, GUIHANDLE handle, int iInfo); +typedef int (*GUIControl_Progress_GetInfo)(void *addonData, GUIHANDLE handle); +typedef const char* (*GUIControl_Progress_GetDescription)(void *addonData, GUIHANDLE handle); +typedef GUIHANDLE (*GUIListItem_Create)(void *addonData, const char *label, const char *label2, const char *iconImage, const char *thumbnailImage, const char *path); +typedef const char* (*GUIListItem_GetLabel)(void *addonData, GUIHANDLE handle); +typedef void (*GUIListItem_SetLabel)(void *addonData, GUIHANDLE handle, const char *label); +typedef const char* (*GUIListItem_GetLabel2)(void *addonData, GUIHANDLE handle); +typedef void (*GUIListItem_SetLabel2)(void *addonData, GUIHANDLE handle, const char *label); +typedef void (*GUIListItem_SetIconImage)(void *addonData, GUIHANDLE handle, const char *image); +typedef void (*GUIListItem_SetThumbnailImage)(void *addonData, GUIHANDLE handle, const char *image); +typedef void (*GUIListItem_SetInfo)(void *addonData, GUIHANDLE handle, const char *info); +typedef void (*GUIListItem_SetProperty)(void *addonData, GUIHANDLE handle, const char *key, const char *value); +typedef const char* (*GUIListItem_GetProperty)(void *addonData, GUIHANDLE handle, const char *key); +typedef void (*GUIListItem_SetPath)(void *addonData, GUIHANDLE handle, const char *path); + +typedef struct CB_GUILib +{ + GUILock Lock; + GUIUnlock Unlock; + GUIGetScreenHeight GetScreenHeight; + GUIGetScreenWidth GetScreenWidth; + GUIGetVideoResolution GetVideoResolution; + GUIWindow_New Window_New; + GUIWindow_Delete Window_Delete; + GUIWindow_SetCallbacks Window_SetCallbacks; + GUIWindow_Show Window_Show; + GUIWindow_Close Window_Close; + GUIWindow_DoModal Window_DoModal; + GUIWindow_SetFocusId Window_SetFocusId; + GUIWindow_GetFocusId Window_GetFocusId; + GUIWindow_SetCoordinateResolution Window_SetCoordinateResolution; + GUIWindow_SetProperty Window_SetProperty; + GUIWindow_SetPropertyInt Window_SetPropertyInt; + GUIWindow_SetPropertyBool Window_SetPropertyBool; + GUIWindow_SetPropertyDouble Window_SetPropertyDouble; + GUIWindow_GetProperty Window_GetProperty; + GUIWindow_GetPropertyInt Window_GetPropertyInt; + GUIWindow_GetPropertyBool Window_GetPropertyBool; + GUIWindow_GetPropertyDouble Window_GetPropertyDouble; + GUIWindow_ClearProperties Window_ClearProperties; + GUIWindow_GetListSize Window_GetListSize; + GUIWindow_ClearList Window_ClearList; + GUIWindow_AddItem Window_AddItem; + GUIWindow_AddStringItem Window_AddStringItem; + GUIWindow_RemoveItem Window_RemoveItem; + GUIWindow_GetListItem Window_GetListItem; + GUIWindow_SetCurrentListPosition Window_SetCurrentListPosition; + GUIWindow_GetCurrentListPosition Window_GetCurrentListPosition; + GUIWindow_GetControl_Spin Window_GetControl_Spin; + GUIWindow_GetControl_Button Window_GetControl_Button; + GUIWindow_GetControl_RadioButton Window_GetControl_RadioButton; + GUIWindow_GetControl_Edit Window_GetControl_Edit; + GUIWindow_GetControl_Progress Window_GetControl_Progress; + GUIWindow_SetControlLabel Window_SetControlLabel; + GUIControl_Spin_SetVisible Control_Spin_SetVisible; + GUIControl_Spin_SetText Control_Spin_SetText; + GUIControl_Spin_Clear Control_Spin_Clear; + GUIControl_Spin_AddLabel Control_Spin_AddLabel; + GUIControl_Spin_GetValue Control_Spin_GetValue; + GUIControl_Spin_SetValue Control_Spin_SetValue; + GUIControl_RadioButton_SetVisible Control_RadioButton_SetVisible; + GUIControl_RadioButton_SetText Control_RadioButton_SetText; + GUIControl_RadioButton_SetSelected Control_RadioButton_SetSelected; + GUIControl_RadioButton_IsSelected Control_RadioButton_IsSelected; + GUIControl_Progress_SetPercentage Control_Progress_SetPercentage; + GUIControl_Progress_GetPercentage Control_Progress_GetPercentage; + GUIControl_Progress_SetInfo Control_Progress_SetInfo; + GUIControl_Progress_GetInfo Control_Progress_GetInfo; + GUIControl_Progress_GetDescription Control_Progress_GetDescription; + GUIListItem_Create ListItem_Create; + GUIListItem_GetLabel ListItem_GetLabel; + GUIListItem_SetLabel ListItem_SetLabel; + GUIListItem_GetLabel2 ListItem_GetLabel2; + GUIListItem_SetLabel2 ListItem_SetLabel2; + GUIListItem_SetIconImage ListItem_SetIconImage; + GUIListItem_SetThumbnailImage ListItem_SetThumbnailImage; + GUIListItem_SetInfo ListItem_SetInfo; + GUIListItem_SetProperty ListItem_SetProperty; + GUIListItem_GetProperty ListItem_GetProperty; + GUIListItem_SetPath ListItem_SetPath; + +} CB_GUILib; + +typedef void (*PVRTransferEpgEntry)(void *userData, const PVRHANDLE handle, const PVR_PROGINFO *epgentry); +typedef void (*PVRTransferChannelEntry)(void *userData, const PVRHANDLE handle, const PVR_CHANNEL *chan); +typedef void (*PVRTransferTimerEntry)(void *userData, const PVRHANDLE handle, const PVR_TIMERINFO *timer); +typedef void (*PVRTransferRecordingEntry)(void *userData, const PVRHANDLE handle, const PVR_RECORDINGINFO *recording); +typedef void (*PVRAddMenuHook)(void *addonData, PVR_MENUHOOK *hook); +typedef void (*PVRRecording)(void *addonData, const char *Name, const char *FileName, bool On); +typedef void (*PVRTriggerTimerUpdate)(void *addonData); +typedef void (*PVRTriggerRecordingUpdate)(void *addonData); +typedef void (*PVRFreeDemuxPacket)(void *addonData, DemuxPacket* pPacket); +typedef DemuxPacket* (*PVRAllocateDemuxPacket)(void *addonData, int iDataSize); + +typedef struct CB_PVRLib +{ + PVRTransferEpgEntry TransferEpgEntry; + PVRTransferChannelEntry TransferChannelEntry; + PVRTransferTimerEntry TransferTimerEntry; + PVRTransferRecordingEntry TransferRecordingEntry; + PVRAddMenuHook AddMenuHook; + PVRRecording Recording; + PVRTriggerTimerUpdate TriggerTimerUpdate; + PVRTriggerRecordingUpdate TriggerRecordingUpdate; + PVRFreeDemuxPacket FreeDemuxPacket; + PVRAllocateDemuxPacket AllocateDemuxPacket; + +} CB_PVRLib; + + +typedef CB_AddOnLib* (*XBMCAddOnLib_RegisterMe)(void *addonData); +typedef void (*XBMCAddOnLib_UnRegisterMe)(void *addonData, CB_AddOnLib *cbTable); +typedef CB_GUILib* (*XBMCGUILib_RegisterMe)(void *addonData); +typedef void (*XBMCGUILib_UnRegisterMe)(void *addonData, CB_GUILib *cbTable); +typedef CB_PVRLib* (*XBMCPVRLib_RegisterMe)(void *addonData); +typedef void (*XBMCPVRLib_UnRegisterMe)(void *addonData, CB_PVRLib *cbTable); + +typedef struct AddonCB +{ + const char *libBasePath; ///> Never, never change this!!! + void *addonData; + XBMCAddOnLib_RegisterMe AddOnLib_RegisterMe; + XBMCAddOnLib_UnRegisterMe AddOnLib_UnRegisterMe; + XBMCGUILib_RegisterMe GUILib_RegisterMe; + XBMCGUILib_UnRegisterMe GUILib_UnRegisterMe; + XBMCPVRLib_RegisterMe PVRLib_RegisterMe; + XBMCPVRLib_UnRegisterMe PVRLib_UnRegisterMe; +} AddonCB; + + +namespace ADDON +{ + +class CAddon; +class CAddonHelpers_Addon; +class CAddonHelpers_GUI; +class CAddonHelpers_PVR; + +class CAddonHelpers +{ +public: + CAddonHelpers(CAddon* addon); + ~CAddonHelpers(); + AddonCB *GetCallbacks() { return m_callbacks; } + + static CB_AddOnLib* AddOnLib_RegisterMe(void *addonData); + static void AddOnLib_UnRegisterMe(void *addonData, CB_AddOnLib *cbTable); + static CB_GUILib* GUILib_RegisterMe(void *addonData); + static void GUILib_UnRegisterMe(void *addonData, CB_GUILib *cbTable); + static CB_PVRLib* PVRLib_RegisterMe(void *addonData); + static void PVRLib_UnRegisterMe(void *addonData, CB_PVRLib *cbTable); + + CAddonHelpers_Addon *GetHelperAddon() { return m_helperAddon; } + CAddonHelpers_GUI *GetHelperGUI() { return m_helperGUI; } + CAddonHelpers_PVR *GetHelperPVR() { return m_helperPVR; } + +private: + AddonCB *m_callbacks; + CAddon *m_addon; + CAddonHelpers_Addon *m_helperAddon; + CAddonHelpers_GUI *m_helperGUI; + CAddonHelpers_PVR *m_helperPVR; +}; + +}; /* namespace ADDON */ diff --git a/xbmc/addons/AddonManager.cpp b/xbmc/addons/AddonManager.cpp index 5475bb859b..fefbee9758 100644 --- a/xbmc/addons/AddonManager.cpp +++ b/xbmc/addons/AddonManager.cpp @@ -42,6 +42,10 @@ #include "DllScreenSaver.h" #include "ScreenSaver.h" #endif +#ifdef HAS_PVRCLIENTS +#include "DllPVRClient.h" +#include "PVRClient.h" +#endif //#ifdef HAS_SCRAPERS #include "Scraper.h" //#endif @@ -98,6 +102,7 @@ AddonPtr CAddonMgr::Factory(const cp_extension_t *props) return AddonPtr(new CScraper(props)); case ADDON_VIZ: case ADDON_SCREENSAVER: + case ADDON_PVRDLL: { // begin temporary platform handling for Dlls // ideally platforms issues will be handled by C-Pluff // this is not an attempt at a solution @@ -126,6 +131,12 @@ AddonPtr CAddonMgr::Factory(const cp_extension_t *props) { #if defined(HAS_VISUALISATION) return AddonPtr(new CVisualisation(props)); +#endif + } + else if (type == ADDON_PVRDLL) + { +#ifdef HAS_PVRCLIENTS + return AddonPtr(new CPVRClient(props)); #endif } else @@ -320,6 +331,7 @@ bool CAddonMgr::HasOutdatedAddons(bool enabled /*= true*/) bool CAddonMgr::GetAddons(const TYPE &type, VECADDONS &addons, bool enabled /* = true */) { + CStdString xbmcPath = _P("special://xbmc/addons"); CSingleLock lock(m_critSection); addons.clear(); cp_status_t status; @@ -329,6 +341,11 @@ bool CAddonMgr::GetAddons(const TYPE &type, VECADDONS &addons, bool enabled /* = for(int i=0; i Type() == ADDON_PVRDLL && addon->Path().Left(xbmcPath.size()).Equals(xbmcPath)) + { + if (m_database.IsSystemPVRAddonEnabled(addon->ID()) != enabled) + addon->Disable(); + } if (addon && m_database.IsAddonDisabled(addon->ID()) != enabled) addons.push_back(addon); } @@ -340,14 +357,24 @@ bool CAddonMgr::GetAddon(const CStdString &str, AddonPtr &addon, const TYPE &typ { CSingleLock lock(m_critSection); + CStdString xbmcPath = _P("special://xbmc/addons"); cp_status_t status; cp_plugin_info_t *cpaddon = m_cpluff->get_plugin_info(m_cp_context, str.c_str(), &status); if (status == CP_OK && cpaddon) { addon = GetAddonFromDescriptor(cpaddon); m_cpluff->release_info(m_cp_context, cpaddon); - if (addon.get() && enabledOnly && m_database.IsAddonDisabled(addon->ID())) - return false; + + if (addon && addon.get() && enabledOnly) + { + if (addon->Type() == ADDON_PVRDLL && addon->Path().Left(xbmcPath.size()).Equals(xbmcPath)) + { + if (!m_database.IsSystemPVRAddonEnabled(addon->ID())) + return false; + } + else if (m_database.IsAddonDisabled(addon->ID())) + return false; + } return NULL != addon.get(); } if (cpaddon) @@ -498,6 +525,8 @@ AddonPtr CAddonMgr::AddonFromProps(AddonProps& addonProps) return AddonPtr(new CScreenSaver(addonProps)); case ADDON_VIZ_LIBRARY: return AddonPtr(new CAddonLibrary(addonProps)); + case ADDON_PVRDLL: + return AddonPtr(new CPVRClient(addonProps)); case ADDON_REPOSITORY: return AddonPtr(new CRepository(addonProps)); default: diff --git a/xbmc/addons/AddonManager.h b/xbmc/addons/AddonManager.h index 328a3339e1..16661480d7 100644 --- a/xbmc/addons/AddonManager.h +++ b/xbmc/addons/AddonManager.h @@ -49,6 +49,7 @@ namespace ADDON const CStdString ADDON_PYTHON_EXT = "*.py"; const CStdString ADDON_SCRAPER_EXT = "*.xml"; const CStdString ADDON_SCREENSAVER_EXT = "*.xbs"; + const CStdString ADDON_PVRDLL_EXT = "*.pvr"; const CStdString ADDON_DSP_AUDIO_EXT = "*.adsp"; const CStdString ADDON_VERSION_RE = "(?\\d*)\\.?(?\\d*)?\\.?(?\\d*)?\\.?(?\\d*)?"; diff --git a/xbmc/addons/DllPVRClient.h b/xbmc/addons/DllPVRClient.h new file mode 100644 index 0000000000..26e8868706 --- /dev/null +++ b/xbmc/addons/DllPVRClient.h @@ -0,0 +1,30 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "DllAddon.h" +#include "include/xbmc_pvr_types.h" + +class DllPVRClient : public DllAddon +{ + // this is populated via Macro calls in DllAddon.h +}; + diff --git a/xbmc/addons/Makefile b/xbmc/addons/Makefile index 4c8d16c099..33dff0de31 100644 --- a/xbmc/addons/Makefile +++ b/xbmc/addons/Makefile @@ -6,9 +6,14 @@ SRCS=Addon.cpp \ PluginSource.cpp \ ScreenSaver.cpp \ Scraper.cpp \ - Skin.cpp \ Visualisation.cpp \ + PVRClient.cpp \ fft.cpp \ + Skin.cpp \ + AddonHelpers_Addon.cpp \ + AddonHelpers_GUI.cpp \ + AddonHelpers_PVR.cpp \ + AddonHelpers_local.cpp \ Repository.cpp \ LIB=addons.a diff --git a/xbmc/addons/PVRClient.cpp b/xbmc/addons/PVRClient.cpp new file mode 100644 index 0000000000..d1758a66c6 --- /dev/null +++ b/xbmc/addons/PVRClient.cpp @@ -0,0 +1,1003 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * Description: + * + * Class CPVRClient is used as a specific interface between the PVR-Client + * library and the PVRManager. Every loaded Client have his own CPVRClient + * Class, it handle default data for the Manager in the case the Client + * can't provide the data and it act as exception handler for all function + * called inside client. Further it translate the "C" compatible data + * strucures to classes that can easily used by the PVRManager. + * + * It generate also a callback table with pointers to useful helper + * functions, that can be used inside the client to access XBMC + * internals. + */ + +#include +#include "Application.h" +#include "LocalizeStrings.h" +#include "StringUtils.h" +#include "FileItem.h" +#include "PVRClient.h" +#include "PVRManager.h" +#include "URL.h" +#include "AdvancedSettings.h" +#include "../utils/log.h" +#include "../utils/SingleLock.h" + +using namespace std; +using namespace ADDON; + +CPVRClient::CPVRClient(const ADDON::AddonProps& props) : CAddonDll(props) + , m_ReadyToUse(false) + , m_hostName("unknown") + , m_iTimeCorrection(0) +{ +} + +CPVRClient::CPVRClient(const cp_extension_t *ext) : CAddonDll(ext) + , m_ReadyToUse(false) + , m_hostName("unknown") + , m_iTimeCorrection(0) +{ +} + +CPVRClient::~CPVRClient() +{ +} + +bool CPVRClient::Create(long clientID, IPVRClientCallback *pvrCB) +{ + CLog::Log(LOGDEBUG, "PVR: %s - Creating PVR-Client AddOn", Name().c_str()); + + m_manager = pvrCB; + + m_pInfo = new PVR_PROPS; + m_pInfo->clientID = clientID; + CStdString userpath = _P(Profile()); + m_pInfo->userpath = userpath.c_str(); + CStdString clientpath = _P(Path()); + m_pInfo->clientpath = clientpath.c_str(); + + /* Call Create to make connections, initializing data or whatever is + needed to become the AddOn running */ + if (CAddonDll::Create()) + { + m_ReadyToUse = true; + m_hostName = m_pStruct->GetConnectionString(); + if (!g_advancedSettings.m_bDisableEPGTimeCorrection) + { + time_t localTime; + time_t backendTime = 0; + int gmtOffset = 0; + CDateTime::GetCurrentDateTime().GetAsTime(localTime); + PVR_ERROR err = GetBackendTime(&backendTime, &gmtOffset); + if (err == PVR_ERROR_NO_ERROR && gmtOffset != 0) + { + /* Is really a big time difference between PVR Backend and XBMC or only a bad GMT Offset? */ + if (backendTime-localTime >= 30 || backendTime-localTime <= -30) + { + m_iTimeCorrection = gmtOffset; + CLog::Log(LOGDEBUG, "PVR: %s/%s - Using a timezone difference of '%i' minutes to correct EPG times", Name().c_str(), m_hostName.c_str(), m_iTimeCorrection/60); + } + else + { + m_iTimeCorrection = 0; + CLog::Log(LOGDEBUG, "PVR: %s/%s - Ignoring the timezone difference of '%i' minutes (No difference betweem XBMC and Backend Clock found)", Name().c_str(), m_hostName.c_str(), m_iTimeCorrection/60); + } + } + } + else if (g_advancedSettings.m_iUserDefinedEPGTimeCorrection > 0) + { + m_iTimeCorrection = g_advancedSettings.m_iUserDefinedEPGTimeCorrection*60; + CLog::Log(LOGDEBUG, "PVR: %s/%s - Using a userdefined timezone difference of '%i' minutes (taken from advancedsettings.xml)", Name().c_str(), m_hostName.c_str(), g_advancedSettings.m_iUserDefinedEPGTimeCorrection); + } + else + { + m_iTimeCorrection = 0; + CLog::Log(LOGDEBUG, "PVR: %s/%s - Timezone difference correction is disabled in advancedsettings.xml", Name().c_str(), m_hostName.c_str()); + } + } + + return m_ReadyToUse; +} + +void CPVRClient::Destroy() +{ + /* tell the AddOn to disconnect and prepare for destruction */ + try + { + CLog::Log(LOGDEBUG, "PVR: %s/%s - Destroying PVR-Client AddOn", Name().c_str(), m_hostName.c_str()); + m_ReadyToUse = false; + + /* Tell the client to destroy */ + CAddonDll::Destroy(); + m_menuhooks.clear(); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during destruction of AddOn occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } +} + +bool CPVRClient::ReCreate() +{ + long clientID = m_pInfo->clientID; + IPVRClientCallback *pvrCB = m_manager; + Destroy(); + return Create(clientID, pvrCB); +} + +long CPVRClient::GetID() +{ + return m_pInfo->clientID; +} + +PVR_ERROR CPVRClient::GetProperties(PVR_SERVERPROPS *props) +{ + CSingleLock lock(m_critSection); + + try + { + return m_pStruct->GetProperties(props); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetProperties occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + + /* Set all properties in a case of exception to not supported */ + props->SupportChannelLogo = false; + props->SupportTimeShift = false; + props->SupportEPG = false; + props->SupportRecordings = false; + props->SupportTimers = false; + props->SupportRadio = false; + props->SupportChannelSettings = false; + props->SupportDirector = false; + props->SupportBouquets = false; + props->SupportChannelScan = false; + } + return PVR_ERROR_UNKOWN; +} + + +/********************************************************** + * General PVR Functions + */ + +const std::string CPVRClient::GetBackendName() +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + try + { + return m_pStruct->GetBackendName(); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetBackendName occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + } + /* return string "Unavailable" as fallback */ + return g_localizeStrings.Get(161); +} + +const std::string CPVRClient::GetBackendVersion() +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + try + { + return m_pStruct->GetBackendVersion(); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetBackendVersion occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + } + /* return string "Unavailable" as fallback */ + return g_localizeStrings.Get(161); +} + +const std::string CPVRClient::GetConnectionString() +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + try + { + return m_pStruct->GetConnectionString(); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetConnectionString occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + } + /* return string "Unavailable" as fallback */ + return g_localizeStrings.Get(161); +} + +PVR_ERROR CPVRClient::GetDriveSpace(long long *total, long long *used) +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + try + { + return m_pStruct->GetDriveSpace(total, used); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetDriveSpace occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + } + *total = 0; + *used = 0; + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR CPVRClient::GetBackendTime(time_t *localTime, int *gmtOffset) +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + try + { + return m_pStruct->GetBackendTime(localTime, gmtOffset); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetBackendTime occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + } + *localTime = 0; + *gmtOffset = 0; + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR CPVRClient::StartChannelScan() +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + try + { + return m_pStruct->DialogChannelScan(); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during StartChannelScan occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + } + return PVR_ERROR_NOT_IMPLEMENTED; +} + +void CPVRClient::CallMenuHook(const PVR_MENUHOOK &hook) +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + try + { + m_pStruct->MenuHook(hook); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during CallMenuHook occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + } +} + +/********************************************************** + * EPG PVR Functions + */ + +PVR_ERROR CPVRClient::GetEPGForChannel(const cPVRChannelInfoTag &channelinfo, cPVREpg *epg, time_t start, time_t end, bool toDB/* = false*/) +{ + CSingleLock lock(m_critSection); + + PVR_ERROR ret = PVR_ERROR_UNKOWN; + + if (m_ReadyToUse) + { + try + { + if (start) + start -= m_iTimeCorrection; + if (end) + end -= m_iTimeCorrection; + PVR_CHANNEL tag; + PVRHANDLE_STRUCT handle; + handle.CALLER_ADDRESS = this; + handle.DATA_ADDRESS = (cPVREpg*) epg; + handle.DATA_IDENTIFIER = toDB ? 1 : 0; + WriteClientChannelInfo(channelinfo, tag); + ret = m_pStruct->RequestEPGForChannel(&handle, tag, start, end); + if (ret != PVR_ERROR_NO_ERROR) + throw ret; + + return PVR_ERROR_NO_ERROR; + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetEPGForChannel occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + catch (PVR_ERROR ret) + { + CLog::Log(LOGERROR, "PVR: %s/%s - Client returns bad error (%i) after GetEPGForChannel", Name().c_str(), m_hostName.c_str(), ret); + } + } + return ret; +} + + +/********************************************************** + * Channels PVR Functions + */ + +int CPVRClient::GetNumChannels() +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + try + { + return m_pStruct->GetNumChannels(); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetNumChannels occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + } + return -1; +} + +PVR_ERROR CPVRClient::GetChannelList(cPVRChannels &channels, bool radio) +{ + CSingleLock lock(m_critSection); + + PVR_ERROR ret = PVR_ERROR_UNKOWN; + + if (m_ReadyToUse) + { + try + { + PVRHANDLE_STRUCT handle; + handle.CALLER_ADDRESS = this; + handle.DATA_ADDRESS = (cPVRChannels*) &channels; + ret = m_pStruct->RequestChannelList(&handle, radio); + if (ret != PVR_ERROR_NO_ERROR) + throw ret; + + return PVR_ERROR_NO_ERROR; + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetChannelList occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + catch (PVR_ERROR ret) + { + CLog::Log(LOGERROR, "PVR: %s/%s - Client returns bad error (%i) after GetChannelList", Name().c_str(), m_hostName.c_str(), ret); + } + } + return ret; +} + +/********************************************************** + * Recordings PVR Functions + */ + +int CPVRClient::GetNumRecordings(void) +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + try + { + return m_pStruct->GetNumRecordings(); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetNumRecordings occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + } + return -1; +} + +PVR_ERROR CPVRClient::GetAllRecordings(cPVRRecordings *results) +{ + CSingleLock lock(m_critSection); + + PVR_ERROR ret = PVR_ERROR_UNKOWN; + + if (m_ReadyToUse) + { + try + { + PVRHANDLE_STRUCT handle; + handle.CALLER_ADDRESS = this; + handle.DATA_ADDRESS = (cPVRRecordings*) results; + ret = m_pStruct->RequestRecordingsList(&handle); + if (ret != PVR_ERROR_NO_ERROR) + throw ret; + + return PVR_ERROR_NO_ERROR; + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetAllRecordings occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + catch (PVR_ERROR ret) + { + CLog::Log(LOGERROR, "PVR: %s/%s - Client returns bad error (%i) after GetAllRecordings", Name().c_str(), m_hostName.c_str(), ret); + } + } + return ret; +} + +PVR_ERROR CPVRClient::DeleteRecording(const cPVRRecordingInfoTag &recinfo) +{ + CSingleLock lock(m_critSection); + + PVR_ERROR ret = PVR_ERROR_UNKOWN; + + if (m_ReadyToUse) + { + try + { + PVR_RECORDINGINFO tag; + WriteClientRecordingInfo(recinfo, tag); + + ret = m_pStruct->DeleteRecording(tag); + if (ret != PVR_ERROR_NO_ERROR) + throw ret; + + return PVR_ERROR_NO_ERROR; + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during DeleteRecording occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + catch (PVR_ERROR ret) + { + CLog::Log(LOGERROR, "PVR: %s/%s - Client returns bad error (%i) after DeleteRecording", Name().c_str(), m_hostName.c_str(), ret); + } + } + return ret; +} + +PVR_ERROR CPVRClient::RenameRecording(const cPVRRecordingInfoTag &recinfo, CStdString &newname) +{ + CSingleLock lock(m_critSection); + + PVR_ERROR ret = PVR_ERROR_UNKOWN; + + if (m_ReadyToUse) + { + try + { + PVR_RECORDINGINFO tag; + WriteClientRecordingInfo(recinfo, tag); + + ret = m_pStruct->RenameRecording(tag, newname); + if (ret != PVR_ERROR_NO_ERROR) + throw ret; + + return PVR_ERROR_NO_ERROR; + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during RenameRecording occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + catch (PVR_ERROR ret) + { + CLog::Log(LOGERROR, "PVR: %s/%s - Client returns bad error (%i) after RenameRecording", Name().c_str(), m_hostName.c_str(), ret); + } + } + return ret; +} + +void CPVRClient::WriteClientRecordingInfo(const cPVRRecordingInfoTag &recordinginfo, PVR_RECORDINGINFO &tag) +{ + time_t recTime; + recordinginfo.RecordingTime().GetAsTime(recTime); + tag.recording_time= recTime+m_iTimeCorrection; + tag.index = recordinginfo.ClientIndex(); + tag.title = recordinginfo.Title(); + tag.subtitle = recordinginfo.PlotOutline(); + tag.description = recordinginfo.Plot(); + tag.channel_name = recordinginfo.ChannelName(); + tag.duration = recordinginfo.GetDuration(); + tag.priority = recordinginfo.Priority(); + tag.lifetime = recordinginfo.Lifetime(); + tag.directory = recordinginfo.Directory(); + tag.stream_url = recordinginfo.StreamURL(); + return; +} + + +/********************************************************** + * Timers PVR Functions + */ + +int CPVRClient::GetNumTimers(void) +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + try + { + return m_pStruct->GetNumTimers(); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetNumTimers occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + } + return -1; +} + +PVR_ERROR CPVRClient::GetAllTimers(cPVRTimers *results) +{ + CSingleLock lock(m_critSection); + + PVR_ERROR ret = PVR_ERROR_UNKOWN; + + if (m_ReadyToUse) + { + try + { + PVRHANDLE_STRUCT handle; + handle.CALLER_ADDRESS = this; + handle.DATA_ADDRESS = (cPVRTimers*) results; + ret = m_pStruct->RequestTimerList(&handle); + if (ret != PVR_ERROR_NO_ERROR) + throw ret; + + return PVR_ERROR_NO_ERROR; + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetAllTimers occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + catch (PVR_ERROR ret) + { + CLog::Log(LOGERROR, "PVR: %s/%s - Client returns bad error (%i) after GetAllTimers", Name().c_str(), m_hostName.c_str(), ret); + } + } + return ret; +} + +PVR_ERROR CPVRClient::AddTimer(const cPVRTimerInfoTag &timerinfo) +{ + CSingleLock lock(m_critSection); + + PVR_ERROR ret = PVR_ERROR_UNKOWN; + + if (m_ReadyToUse) + { + try + { + PVR_TIMERINFO tag; + WriteClientTimerInfo(timerinfo, tag); + + //Workaround for string transfer to PVRclient + CStdString myTitle = timerinfo.Title(); + tag.title = myTitle.c_str(); + + ret = m_pStruct->AddTimer(tag); + if (ret != PVR_ERROR_NO_ERROR) + throw ret; + + return PVR_ERROR_NO_ERROR; + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during AddTimer occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + catch (PVR_ERROR ret) + { + CLog::Log(LOGERROR, "PVR: %s/%s - Client returns bad error (%i) after AddTimer", Name().c_str(), m_hostName.c_str(), ret); + } + } + return ret; +} + +PVR_ERROR CPVRClient::DeleteTimer(const cPVRTimerInfoTag &timerinfo, bool force) +{ + CSingleLock lock(m_critSection); + + PVR_ERROR ret = PVR_ERROR_UNKOWN; + + if (m_ReadyToUse) + { + try + { + PVR_TIMERINFO tag; + WriteClientTimerInfo(timerinfo, tag); + + //Workaround for string transfer to PVRclient + CStdString myTitle = timerinfo.Title(); + tag.title = myTitle.c_str(); + + ret = m_pStruct->DeleteTimer(tag, force); + if (ret != PVR_ERROR_NO_ERROR) + throw ret; + + return PVR_ERROR_NO_ERROR; + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during DeleteTimer occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + catch (PVR_ERROR ret) + { + CLog::Log(LOGERROR, "PVR: %s/%s - Client returns bad error (%i) after DeleteTimer", Name().c_str(), m_hostName.c_str(), ret); + } + } + return ret; +} + +PVR_ERROR CPVRClient::RenameTimer(const cPVRTimerInfoTag &timerinfo, CStdString &newname) +{ + CSingleLock lock(m_critSection); + + PVR_ERROR ret = PVR_ERROR_UNKOWN; + + if (m_ReadyToUse) + { + try + { + PVR_TIMERINFO tag; + WriteClientTimerInfo(timerinfo, tag); + + //Workaround for string transfer to PVRclient + CStdString myTitle = timerinfo.Title(); + tag.title = myTitle.c_str(); + + ret = m_pStruct->RenameTimer(tag, newname.c_str()); + if (ret != PVR_ERROR_NO_ERROR) + throw ret; + + return PVR_ERROR_NO_ERROR; + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during RenameTimer occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + catch (PVR_ERROR ret) + { + CLog::Log(LOGERROR, "PVR: %s/%s - Client returns bad error (%i) after RenameTimer", Name().c_str(), m_hostName.c_str(), ret); + } + } + return ret; +} + +PVR_ERROR CPVRClient::UpdateTimer(const cPVRTimerInfoTag &timerinfo) +{ + CSingleLock lock(m_critSection); + + PVR_ERROR ret = PVR_ERROR_UNKOWN; +// + if (m_ReadyToUse) + { + try + { + PVR_TIMERINFO tag; + WriteClientTimerInfo(timerinfo, tag); + + //Workaround for string transfer to PVRclient + CStdString myTitle = timerinfo.Title(); + tag.title = myTitle.c_str(); + + ret = m_pStruct->UpdateTimer(tag); + if (ret != PVR_ERROR_NO_ERROR) + throw ret; + + return PVR_ERROR_NO_ERROR; + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during UpdateTimer occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + catch (PVR_ERROR ret) + { + CLog::Log(LOGERROR, "PVR: %s/%s - Client returns bad error (%i) after UpdateTimer", Name().c_str(), m_hostName.c_str(), ret); + } + } + return ret; +} + +void CPVRClient::WriteClientTimerInfo(const cPVRTimerInfoTag &timerinfo, PVR_TIMERINFO &tag) +{ + tag.index = timerinfo.ClientIndex(); + tag.active = timerinfo.Active(); + tag.channelNum = timerinfo.ClientNumber(); + tag.recording = timerinfo.IsRecording(); + tag.title = timerinfo.Title(); + tag.directory = timerinfo.Dir(); + tag.priority = timerinfo.Priority(); + tag.lifetime = timerinfo.Lifetime(); + tag.repeat = timerinfo.IsRepeating(); + tag.repeatflags = timerinfo.Weekdays(); + tag.starttime = timerinfo.StartTime(); + tag.starttime -= m_iTimeCorrection; + tag.endtime = timerinfo.StopTime(); + tag.endtime -= m_iTimeCorrection; + tag.firstday = timerinfo.FirstDayTime(); + tag.firstday -= m_iTimeCorrection; + return; +} + +/********************************************************** + * Stream PVR Functions + */ + +bool CPVRClient::OpenLiveStream(const cPVRChannelInfoTag &channelinfo) +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + try + { + PVR_CHANNEL tag; + WriteClientChannelInfo(channelinfo, tag); + return m_pStruct->OpenLiveStream(tag); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during OpenLiveStream occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + } + return false; +} + +void CPVRClient::CloseLiveStream() +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + try + { + m_pStruct->CloseLiveStream(); + return; + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during CloseLiveStream occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + } + return; +} + +int CPVRClient::ReadLiveStream(void* lpBuf, int64_t uiBufSize) +{ + return m_pStruct->ReadLiveStream((unsigned char *)lpBuf, uiBufSize); +} + +int64_t CPVRClient::SeekLiveStream(int64_t iFilePosition, int iWhence/* = SEEK_SET*/) +{ + return m_pStruct->SeekLiveStream(iFilePosition, iWhence); +} + +int64_t CPVRClient::PositionLiveStream() +{ + return m_pStruct->PositionLiveStream(); +} + +int64_t CPVRClient::LengthLiveStream(void) +{ + return m_pStruct->LengthLiveStream(); +} + +int CPVRClient::GetCurrentClientChannel() +{ + CSingleLock lock(m_critSection); + + return m_pStruct->GetCurrentClientChannel(); +} + +bool CPVRClient::SwitchChannel(const cPVRChannelInfoTag &channelinfo) +{ + CSingleLock lock(m_critSection); + + PVR_CHANNEL tag; + WriteClientChannelInfo(channelinfo, tag); + return m_pStruct->SwitchChannel(tag); +} + +bool CPVRClient::SignalQuality(PVR_SIGNALQUALITY &qualityinfo) +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + PVR_ERROR ret = PVR_ERROR_UNKOWN; + try + { + ret = m_pStruct->SignalQuality(qualityinfo); + if (ret != PVR_ERROR_NO_ERROR) + throw ret; + + return true; + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during SignalQuality occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + catch (PVR_ERROR ret) + { + CLog::Log(LOGERROR, "PVR: %s/%s - Client returns bad error (%i) after SignalQuality", Name().c_str(), m_hostName.c_str(), ret); + } + } + return false; +} + +const std::string CPVRClient::GetLiveStreamURL(const cPVRChannelInfoTag &channelinfo) +{ + CSingleLock lock(m_critSection); + + if (m_ReadyToUse) + { + try + { + PVR_CHANNEL tag; + WriteClientChannelInfo(channelinfo, tag); + return m_pStruct->GetLiveStreamURL(tag); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetLiveStreamURL occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + } + } + /* return string "Unavailable" as fallback */ + return g_localizeStrings.Get(161); +} + +void CPVRClient::WriteClientChannelInfo(const cPVRChannelInfoTag &channelinfo, PVR_CHANNEL &tag) +{ + tag.uid = channelinfo.UniqueID(); + tag.number = channelinfo.ClientNumber(); + tag.name = channelinfo.Name().c_str(); + tag.callsign = channelinfo.ClientName().c_str(); + tag.iconpath = channelinfo.Icon().c_str(); + tag.encryption = channelinfo.EncryptionSystem(); + tag.radio = channelinfo.IsRadio(); + tag.hide = channelinfo.IsHidden(); + tag.recording = channelinfo.IsRecording(); + tag.bouquet = 0; + tag.multifeed = false; + tag.multifeed_master = 0; + tag.multifeed_number = 0; + tag.input_format = channelinfo.InputFormat(); + tag.stream_url = channelinfo.StreamURL(); + return; +} + +bool CPVRClient::OpenRecordedStream(const cPVRRecordingInfoTag &recinfo) +{ + CSingleLock lock(m_critSection); + + PVR_RECORDINGINFO tag; + WriteClientRecordingInfo(recinfo, tag); + return m_pStruct->OpenRecordedStream(tag); +} + +void CPVRClient::CloseRecordedStream(void) +{ + CSingleLock lock(m_critSection); + + return m_pStruct->CloseRecordedStream(); +} + +int CPVRClient::ReadRecordedStream(void* lpBuf, int64_t uiBufSize) +{ + return m_pStruct->ReadRecordedStream((unsigned char *)lpBuf, uiBufSize); +} + +int64_t CPVRClient::SeekRecordedStream(int64_t iFilePosition, int iWhence/* = SEEK_SET*/) +{ + return m_pStruct->SeekRecordedStream(iFilePosition, iWhence); +} + +int64_t CPVRClient::PositionRecordedStream() +{ + return m_pStruct->PositionRecordedStream(); +} + +int64_t CPVRClient::LengthRecordedStream(void) +{ + return m_pStruct->LengthRecordedStream(); +} + +PVR_ERROR CPVRClient::GetStreamProperties(PVR_STREAMPROPS *props) +{ + CSingleLock lock(m_critSection); + + try + { + return m_pStruct->GetStreamProperties(props); + } + catch (std::exception &e) + { + CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during GetStreamProperties occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + + /* Set all properties in a case of exception to not supported */ + } + return PVR_ERROR_UNKOWN; +} + +void CPVRClient::DemuxReset() +{ + m_pStruct->DemuxReset(); +} + +void CPVRClient::DemuxAbort() +{ + m_pStruct->DemuxAbort(); +} + +void CPVRClient::DemuxFlush() +{ + m_pStruct->DemuxFlush(); +} + +DemuxPacket* CPVRClient::DemuxRead() +{ + return m_pStruct->DemuxRead(); +} + +/********************************************************** + * Addon specific functions + * Are used for every type of AddOn + */ + +ADDON_STATUS CPVRClient::SetSetting(const char *settingName, const void *settingValue) +{ +// CSingleLock lock(m_critSection); +// +// try +// { +// return m_pDll->SetSetting(settingName, settingValue); +// } +// catch (std::exception &e) +// { +// CLog::Log(LOGERROR, "PVR: %s/%s - exception '%s' during SetSetting occurred, contact Developer '%s' of this AddOn", Name().c_str(), m_hostName.c_str(), e.what(), Author().c_str()); + return STATUS_UNKNOWN; +// } +} diff --git a/xbmc/addons/PVRClient.h b/xbmc/addons/PVRClient.h new file mode 100644 index 0000000000..de6cbf5766 --- /dev/null +++ b/xbmc/addons/PVRClient.h @@ -0,0 +1,130 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "Addon.h" +#include "AddonDll.h" +#include "DllPVRClient.h" +#include "../utils/PVREpg.h" +#include "../utils/PVRChannels.h" +#include "../utils/PVRTimers.h" +#include "../utils/PVRRecordings.h" + +typedef std::vector PVR_MENUHOOKS; + +class IPVRClientCallback +{ +public: + virtual void OnClientMessage(const long clientID, const PVR_EVENT clientEvent, const char* msg)=0; +}; + +class CPVRClient : public ADDON::CAddonDll +{ +public: + CPVRClient(const ADDON::AddonProps& props); + CPVRClient(const cp_extension_t *ext); + ~CPVRClient(); + + bool Create(long clientID, IPVRClientCallback *pvrCB); + void Destroy(); + bool ReCreate(); + + /* DLL related */ + bool ReadyToUse() { return m_ReadyToUse; } + virtual ADDON_STATUS SetSetting(const char *settingName, const void *settingValue); + + /* Server */ + long GetID(); + PVR_ERROR GetProperties(PVR_SERVERPROPS *props); + + /* General */ + const std::string GetBackendName(); + const std::string GetBackendVersion(); + const std::string GetConnectionString(); + PVR_ERROR GetDriveSpace(long long *total, long long *used); + PVR_ERROR GetBackendTime(time_t *localTime, int *gmtOffset); + PVR_ERROR StartChannelScan(); + int GetTimeCorrection() { return m_iTimeCorrection; } + int GetClientID() { return m_pInfo->clientID; } + bool HaveMenuHooks() { return m_menuhooks.size() > 0; } + PVR_MENUHOOKS *GetMenuHooks() { return &m_menuhooks; } + void CallMenuHook(const PVR_MENUHOOK &hook); + + /* TV Guide */ + PVR_ERROR GetEPGForChannel(const cPVRChannelInfoTag &channelinfo, cPVREpg *epg, time_t start, time_t end, bool toDB = false); + + /* Channels */ + int GetNumChannels(); + PVR_ERROR GetChannelList(cPVRChannels &channels, bool radio); + + /* Recordings */ + int GetNumRecordings(void); + PVR_ERROR GetAllRecordings(cPVRRecordings *results); + PVR_ERROR DeleteRecording(const cPVRRecordingInfoTag &recinfo); + PVR_ERROR RenameRecording(const cPVRRecordingInfoTag &recinfo, CStdString &newname); + + /* Timers */ + int GetNumTimers(void); + PVR_ERROR GetAllTimers(cPVRTimers *results); + PVR_ERROR AddTimer(const cPVRTimerInfoTag &timerinfo); + PVR_ERROR DeleteTimer(const cPVRTimerInfoTag &timerinfo, bool force = false); + PVR_ERROR RenameTimer(const cPVRTimerInfoTag &timerinfo, CStdString &newname); + PVR_ERROR UpdateTimer(const cPVRTimerInfoTag &timerinfo); + + bool OpenLiveStream(const cPVRChannelInfoTag &channelinfo); + void CloseLiveStream(); + int ReadLiveStream(void* lpBuf, int64_t uiBufSize); + int64_t SeekLiveStream(int64_t iFilePosition, int iWhence = SEEK_SET); + int64_t PositionLiveStream(void); + int64_t LengthLiveStream(void); + int GetCurrentClientChannel(); + bool SwitchChannel(const cPVRChannelInfoTag &channelinfo); + bool SignalQuality(PVR_SIGNALQUALITY &qualityinfo); + const std::string GetLiveStreamURL(const cPVRChannelInfoTag &channelinfo); + + bool OpenRecordedStream(const cPVRRecordingInfoTag &recinfo); + void CloseRecordedStream(void); + int ReadRecordedStream(void* lpBuf, int64_t uiBufSize); + int64_t SeekRecordedStream(int64_t iFilePosition, int iWhence = SEEK_SET); + int64_t PositionRecordedStream(void); + int64_t LengthRecordedStream(void); + + PVR_ERROR GetStreamProperties(PVR_STREAMPROPS *props); + void DemuxReset(); + void DemuxAbort(); + void DemuxFlush(); + DemuxPacket* DemuxRead(); + +protected: + bool m_ReadyToUse; + IPVRClientCallback *m_manager; + CStdString m_hostName; + CCriticalSection m_critSection; + int m_iTimeCorrection; + PVR_MENUHOOKS m_menuhooks; + +private: + void WriteClientChannelInfo(const cPVRChannelInfoTag &channelinfo, PVR_CHANNEL &tag); + void WriteClientTimerInfo(const cPVRTimerInfoTag &timerinfo, PVR_TIMERINFO &tag); + void WriteClientRecordingInfo(const cPVRRecordingInfoTag &recordinginfo, PVR_RECORDINGINFO &tag); +}; + +typedef std::vector VECCLIENTS; diff --git a/xbmc/addons/Skin.cpp b/xbmc/addons/Skin.cpp index f4c875a1d7..bfce6f4c16 100644 --- a/xbmc/addons/Skin.cpp +++ b/xbmc/addons/Skin.cpp @@ -244,6 +244,7 @@ bool CSkinInfo::LoadStartupWindows(const cp_extension_t *ext) if (!m_startupWindows.size()) { // nope - add the default ones m_startupWindows.push_back(CStartupWindow(WINDOW_HOME, "513")); + m_startupWindows.push_back(CStartupWindow(WINDOW_TV, "19180")); m_startupWindows.push_back(CStartupWindow(WINDOW_PROGRAMS, "0")); m_startupWindows.push_back(CStartupWindow(WINDOW_PICTURES, "1")); m_startupWindows.push_back(CStartupWindow(WINDOW_MUSIC, "2")); diff --git a/xbmc/addons/include/xbmc_pvr_dll.h b/xbmc/addons/include/xbmc_pvr_dll.h new file mode 100644 index 0000000000..ad76148f1f --- /dev/null +++ b/xbmc/addons/include/xbmc_pvr_dll.h @@ -0,0 +1,178 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __XBMC_PVR_H__ +#define __XBMC_PVR_H__ + +#include "xbmc_addon_dll.h" /* Dll related functions available to all AddOn's */ +#include "xbmc_pvr_types.h" + +extern "C" +{ + // Functions that your PVR client must implement, also you must implement the functions from + // xbmc_addon_dll.h + + /** PVR General Functions **/ + PVR_ERROR GetProperties(PVR_SERVERPROPS* pProps); + PVR_ERROR GetStreamProperties(PVR_STREAMPROPS* pProps); + const char* GetBackendName(); + const char* GetBackendVersion(); + const char* GetConnectionString(); + PVR_ERROR GetDriveSpace(long long *total, long long *used); + PVR_ERROR GetBackendTime(time_t *localTime, int *gmtOffset); + PVR_ERROR DialogChannelScan(); + PVR_ERROR MenuHook(const PVR_MENUHOOK &menuhook); + + /** PVR EPG Functions **/ + PVR_ERROR RequestEPGForChannel(PVRHANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end); + + /** PVR Bouquets Functions **/ + int GetNumBouquets(); + PVR_ERROR RequestBouquetsList(PVRHANDLE handle, int radio); + + /** PVR Channel Functions **/ + int GetNumChannels(); + PVR_ERROR RequestChannelList(PVRHANDLE handle, int radio); + PVR_ERROR DeleteChannel(unsigned int number); + PVR_ERROR RenameChannel(unsigned int number, const char *newname); + PVR_ERROR MoveChannel(unsigned int number, unsigned int newnumber); + PVR_ERROR DialogChannelSettings(const PVR_CHANNEL &channelinfo); + PVR_ERROR DialogAddChannel(const PVR_CHANNEL &channelinfo); + + /** PVR Recording Functions **/ + int GetNumRecordings(); + PVR_ERROR RequestRecordingsList(PVRHANDLE handle); + PVR_ERROR DeleteRecording(const PVR_RECORDINGINFO &recinfo); + PVR_ERROR RenameRecording(const PVR_RECORDINGINFO &recinfo, const char *newname); + + /** PVR Recording cut marks Functions **/ + bool HaveCutmarks(); + PVR_ERROR RequestCutMarksList(PVRHANDLE handle); + PVR_ERROR AddCutMark(const PVR_CUT_MARK &cutmark); + PVR_ERROR DeleteCutMark(const PVR_CUT_MARK &cutmark); + PVR_ERROR StartCut(); + + /** PVR Timer Functions **/ + int GetNumTimers(); + PVR_ERROR RequestTimerList(PVRHANDLE handle); + PVR_ERROR AddTimer(const PVR_TIMERINFO &timerinfo); + PVR_ERROR DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force); + PVR_ERROR RenameTimer(const PVR_TIMERINFO &timerinfo, const char *newname); + PVR_ERROR UpdateTimer(const PVR_TIMERINFO &timerinfo); + + /** PVR Live Stream Functions **/ + bool OpenLiveStream(const PVR_CHANNEL &channelinfo); + void CloseLiveStream(); + int ReadLiveStream(unsigned char* buf, int buf_size); + long long SeekLiveStream(long long pos, int whence=SEEK_SET); + long long PositionLiveStream(void); + long long LengthLiveStream(void); + int GetCurrentClientChannel(); + bool SwitchChannel(const PVR_CHANNEL &channelinfo); + PVR_ERROR SignalQuality(PVR_SIGNALQUALITY &qualityinfo); + + /** PVR Secondary Stream Functions **/ + bool SwapLiveTVSecondaryStream(); + bool OpenSecondaryStream(const PVR_CHANNEL &channelinfo); + void CloseSecondaryStream(); + int ReadSecondaryStream(unsigned char* buf, int buf_size); + + /** PVR Recording Stream Functions **/ + bool OpenRecordedStream(const PVR_RECORDINGINFO &recinfo); + void CloseRecordedStream(void); + int ReadRecordedStream(unsigned char* buf, int buf_size); + long long SeekRecordedStream(long long pos, int whence=SEEK_SET); + long long PositionRecordedStream(void); + long long LengthRecordedStream(void); + const char* GetLiveStreamURL(const PVR_CHANNEL &channelinfo); + + /** \name Demuxer Interface */ + void DemuxReset(); + void DemuxAbort(); + void DemuxFlush(); + DemuxPacket* DemuxRead(); + + // function to export the above structure to XBMC + void __declspec(dllexport) get_addon(struct PVRClient* pClient) + { + pClient->GetProperties = GetProperties; + pClient->GetStreamProperties = GetStreamProperties; + pClient->GetConnectionString = GetConnectionString; + pClient->GetBackendName = GetBackendName; + pClient->GetBackendVersion = GetBackendVersion; + pClient->GetDriveSpace = GetDriveSpace; + pClient->GetBackendTime = GetBackendTime; + pClient->DialogChannelScan = DialogChannelScan; + pClient->MenuHook = MenuHook; + pClient->GetNumBouquets = GetNumBouquets; + pClient->RequestBouquetsList = RequestBouquetsList; + pClient->GetNumChannels = GetNumChannels; + pClient->GetNumRecordings = GetNumRecordings; + pClient->GetNumTimers = GetNumTimers; + pClient->RequestEPGForChannel = RequestEPGForChannel; + pClient->RequestChannelList = RequestChannelList; + pClient->DeleteChannel = DeleteChannel; + pClient->RenameChannel = RenameChannel; + pClient->MoveChannel = MoveChannel; + pClient->DialogChannelSettings = DialogChannelSettings; + pClient->DialogAddChannel = DialogAddChannel; + pClient->RequestRecordingsList = RequestRecordingsList; + pClient->DeleteRecording = DeleteRecording; + pClient->RenameRecording = RenameRecording; + pClient->HaveCutmarks = HaveCutmarks; + pClient->RequestCutMarksList = RequestCutMarksList; + pClient->AddCutMark = AddCutMark; + pClient->DeleteCutMark = DeleteCutMark; + pClient->StartCut = StartCut; + pClient->RequestTimerList = RequestTimerList; + pClient->AddTimer = AddTimer; + pClient->DeleteTimer = DeleteTimer; + pClient->RenameTimer = RenameTimer; + pClient->UpdateTimer = UpdateTimer; + pClient->OpenLiveStream = OpenLiveStream; + pClient->CloseLiveStream = CloseLiveStream; + pClient->ReadLiveStream = ReadLiveStream; + pClient->SeekLiveStream = SeekLiveStream; + pClient->PositionLiveStream = PositionLiveStream; + pClient->LengthLiveStream = LengthLiveStream; + pClient->GetCurrentClientChannel= GetCurrentClientChannel; + pClient->SwitchChannel = SwitchChannel; + pClient->SignalQuality = SignalQuality; + pClient->SwapLiveTVSecondaryStream = SwapLiveTVSecondaryStream; + pClient->OpenSecondaryStream = OpenSecondaryStream; + pClient->CloseSecondaryStream = CloseSecondaryStream; + pClient->ReadSecondaryStream = ReadSecondaryStream; + pClient->OpenRecordedStream = OpenRecordedStream; + pClient->CloseRecordedStream = CloseRecordedStream; + pClient->ReadRecordedStream = ReadRecordedStream; + pClient->SeekRecordedStream = SeekRecordedStream; + pClient->PositionRecordedStream = PositionRecordedStream; + pClient->LengthRecordedStream = LengthRecordedStream; + pClient->GetLiveStreamURL = GetLiveStreamURL; + pClient->DemuxReset = DemuxReset; + pClient->DemuxAbort = DemuxAbort; + pClient->DemuxFlush = DemuxFlush; + pClient->DemuxRead = DemuxRead; + }; +}; + +#endif diff --git a/xbmc/addons/include/xbmc_pvr_types.h b/xbmc/addons/include/xbmc_pvr_types.h new file mode 100644 index 0000000000..7078c4dd68 --- /dev/null +++ b/xbmc/addons/include/xbmc_pvr_types.h @@ -0,0 +1,426 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/** + * \file xbmc_pvr_types.h + * \brief Implementation of a PVR Client API Interface. + * It at common data structures which a shared between XBMC and PVR clients + * + * \author Team XBMC + */ + +#ifndef __PVRCLIENT_TYPES_H__ +#define __PVRCLIENT_TYPES_H__ + +#ifdef _WIN32 +#include +#else +#ifndef __cdecl +#define __cdecl +#endif +#ifndef __declspec +#define __declspec(X) +#endif +#endif +#include + +/*! \note Define "USE_DEMUX" on compile time if demuxing inside pvr + * addon is used. Also XBMC's "DVDDemuxPacket.h" file must be inside + * the include path of the pvr addon. + */ +#ifdef USE_DEMUX +#include "DVDDemuxPacket.h" +#else +struct DemuxPacket; +#endif + +#undef ATTRIBUTE_PACKED +#undef PRAGMA_PACK_BEGIN +#undef PRAGMA_PACK_END + +#if defined(__GNUC__) +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) +#define ATTRIBUTE_PACKED __attribute__ ((packed)) +#define PRAGMA_PACK 0 +#endif +#endif + +#if !defined(ATTRIBUTE_PACKED) +#define ATTRIBUTE_PACKED +#define PRAGMA_PACK 1 +#endif + + /*! \brief PVR Event contents, used to identify the genre of a epg entry + */ +#define EVCONTENTMASK_MOVIEDRAMA 0x10 +#define EVCONTENTMASK_NEWSCURRENTAFFAIRS 0x20 +#define EVCONTENTMASK_SHOW 0x30 +#define EVCONTENTMASK_SPORTS 0x40 +#define EVCONTENTMASK_CHILDRENYOUTH 0x50 +#define EVCONTENTMASK_MUSICBALLETDANCE 0x60 +#define EVCONTENTMASK_ARTSCULTURE 0x70 +#define EVCONTENTMASK_SOCIALPOLITICALECONOMICS 0x80 +#define EVCONTENTMASK_EDUCATIONALSCIENCE 0x90 +#define EVCONTENTMASK_LEISUREHOBBIES 0xA0 +#define EVCONTENTMASK_SPECIAL 0xB0 +#define EVCONTENTMASK_USERDEFINED 0xF0 + +#ifdef __cplusplus +extern "C" { +#endif + + /*! \brief PVR Client Return Data handle + */ + struct PVRHANDLE_STRUCT + { + void* CALLER_ADDRESS; + void* DATA_ADDRESS; + int DATA_IDENTIFIER; + }; + typedef PVRHANDLE_STRUCT* PVRHANDLE; + + /*! \brief PVR Client startup properties + * Passed to the Create function + */ + struct PVR_PROPS + { + int clientID; + const char *userpath; + const char *clientpath; + }; + + /*! \brief PVR Client Error Codes + */ + typedef enum { + PVR_ERROR_NO_ERROR = 0, + PVR_ERROR_UNKOWN = -1, + PVR_ERROR_NOT_IMPLEMENTED = -2, + PVR_ERROR_SERVER_ERROR = -3, + PVR_ERROR_SERVER_TIMEOUT = -4, + PVR_ERROR_NOT_SYNC = -5, + PVR_ERROR_NOT_DELETED = -6, + PVR_ERROR_NOT_SAVED = -7, + PVR_ERROR_RECORDING_RUNNING = -8, + PVR_ERROR_ALREADY_PRESENT = -9, + PVR_ERROR_NOT_POSSIBLE = -10, + } PVR_ERROR; + + /*! \brief PVR Client Event Codes + * Sent via PVRManager callback + */ + typedef enum { + PVR_EVENT_UNKNOWN = 0, + PVR_EVENT_CLOSE = 1, + PVR_EVENT_RECORDINGS_CHANGE = 2, + PVR_EVENT_CHANNELS_CHANGE = 3, + PVR_EVENT_TIMERS_CHANGE = 4, + } PVR_EVENT; + +#if PRAGMA_PACK +#pragma pack(1) +#endif + + /*! \brief PVR Client Properties + * Returned on client initialization + */ + typedef struct PVR_SERVERPROPS { + bool SupportChannelLogo; /**< \brief Client support transfer of channel logos */ + bool SupportChannelSettings; /**< \brief Client support changing channels on backend */ + bool SupportTimeShift; /**< \brief Client handle Live TV Timeshift, otherwise it is handled by XBMC */ + bool SupportEPG; /**< \brief Client provide EPG information */ + bool SupportTV; /**< \brief Client provide TV Channels, is false for Radio only clients */ + bool SupportRadio; /**< \brief Client provide also Radio Channels */ + bool SupportRecordings; /**< \brief Client support playback of recordings stored on the backend */ + bool SupportTimers; /**< \brief Client support creation and editing of timers */ + bool SupportDirector; /**< \brief Client provide information about multifeed channels, like Sky Select */ + bool SupportBouquets; /**< \brief Client support Bouqets */ + bool SupportChannelScan; /**< \brief Client support Channelscan */ + bool HandleInputStream; /**< \brief Input stream is handled by the client if set, can be false for http */ + bool HandleDemuxing; /**< \brief Demux of stream is handled by the client, as example TVFrontend (htsp protocol) */ + } ATTRIBUTE_PACKED PVR_SERVERPROPS; + + /*! \brief PVR Stream Properties + * Returned on request + */ + typedef struct PVR_STREAMPROPS { +#define PVR_STREAM_MAX_STREAMS 16 + int nstreams; + struct PVR_STREAM { + int id; + int physid; + unsigned int codec_type; + unsigned int codec_id; + char language[4]; + int identifier; + + int fpsscale; // scale of 1000 and a rate of 29970 will result in 29.97 fps + int fpsrate; + int height; // height of the stream reported by the demuxer + int width; // width of the stream reported by the demuxer + float aspect; // display aspect of stream + + int channels; + int samplerate; + int blockalign; + int bitrate; + int bits_per_sample; + } stream[PVR_STREAM_MAX_STREAMS]; + } ATTRIBUTE_PACKED PVR_STREAMPROPS; + + + /*! \brief PVR channel defination + * + * Is used by the TransferChannelEntry function to inform XBMC that this + * channel is present, also if a channel is opened this structure is passed in. + */ + typedef struct PVR_CHANNEL { + int uid; /**< \brief Unique identifier for this channel */ + int number; /**< \brief The backend channel number */ + + const char *name; /**< \brief Channel name provided by the Broadcast */ + const char *callsign; /**< \brief Channel name provided by the user (if present) */ + const char *iconpath; /**< \brief Path to the channel icon (if present) */ + + int encryption; /**< \brief This is a encrypted channel and have a CA Id */ + bool radio; /**< \brief This is a radio channel */ + bool hide; /**< \brief This channel is hidden by the user */ + bool recording; /**< \brief This channel is currently recording */ + + int bouquet; /**< \brief Bouquet ID this channel have (if supported) */ + + bool multifeed; /**< \brief This is a multifeed channel */ + int multifeed_master; /**< \brief The Master multifeed channel, multifeed_master==number for master itself */ + int multifeed_number; /**< \brief The own number inside multifeed channel list */ + + const char *input_format; /**< \brief Input format type based upon ffmpeg/libavformat/allformats.c + if it is unknown leave it empty */ + + const char *stream_url; /**< \brief The Stream URL to access this channel, it can be all types of protocol + and types are supported by XBMC or in case the client read the stream leave + it empty as URL. */ + } ATTRIBUTE_PACKED PVR_CHANNEL; + + /*! \brief EPG Bouquet Definition + */ + typedef struct PVR_BOUQUET { + char* Name; + char* Category; + int Number; + } ATTRIBUTE_PACKED PVR_BOUQUET; + + /*! \brief EPG Programme Definition + * + * Used to signify an individual broadcast, whether it is also a recording, timer etc. + */ + typedef struct PVR_PROGINFO { + unsigned int uid; + int channum; + const char *title; + const char *subtitle; + const char *description; + time_t starttime; + time_t endtime; + int genre_type; + int genre_sub_type; + int parental_rating; + } ATTRIBUTE_PACKED PVR_PROGINFO; + + /*! \brief TV Timer Definition + */ + typedef struct PVR_TIMERINFO { + int index; + int active; + const char *title; + const char *directory; + int channelNum; + time_t starttime; + time_t endtime; + time_t firstday; + int recording; + int priority; + int lifetime; + int repeat; + int repeatflags; + } ATTRIBUTE_PACKED PVR_TIMERINFO; + + /*! \brief PVR recording defination + * + * Is used by the TransferRecordinglEntry function to inform XBMC about this + * recording, also if a recording is opened this structure is passed in. + */ + typedef struct PVR_RECORDINGINFO { + int index; /**< \brief The index number of this recording, it must always set and + is used to identify this recording later */ + const char *directory; /**< \brief The directory of this recording (used to create a organized structure). + It is not required, if it is not supported on the backend + leave it open */ + const char *title; /**< \brief The name of this recording */ + const char *subtitle; /**< \brief Optional subtitle */ + const char *description; /**< \brief Optional description of the recording content */ + const char *channel_name; /**< \brief Optional channel name */ + time_t recording_time; /**< \brief Optional time where this recording was taken */ + int duration; /**< \brief The duration in seconds of this recording */ + int priority; /**< \brief Optional priority of this recording (from 0 - 100) */ + int lifetime; /**< \brief Optional life time in days of this recording */ + const char *stream_url; /**< \brief The Stream URL to access this recording, it can be all types of protocol + and types are supported by XBMC or in case the client read the stream leave + it empty as URL. You can also define to play all files inside a folder if you + use a asterix, as example: "/media/disk/recordings/Alien/ *.ts", in this example + all files in the directory "/media/disk/recordings/Alien" with the "ts" extensions + are played by XBMC. */ + } ATTRIBUTE_PACKED PVR_RECORDINGINFO; + + /*! \brief TV Stream Signal Quality Information + */ + typedef struct PVR_SIGNALQUALITY { + char frontend_name[1024]; + char frontend_status[1024]; + int snr; + int signal; + long ber; + long unc; + double video_bitrate; + double audio_bitrate; + double dolby_bitrate; + } ATTRIBUTE_PACKED PVR_SIGNALQUALITY; + + /*! \brief PVR Addon menu hook element + * + * Are available in the context menus of the TV window, to perfrom a addon related action. + */ + typedef struct PVR_MENUHOOK { + int hook_id; /**< \brief An identifier to know what hook is called back to the addon */ + int string_id; /**< \brief The id to a name for this item inside the language files */ + } ATTRIBUTE_PACKED PVR_MENUHOOK; + + /*! \brief PVR Recordings cut mark action types. + */ + typedef enum { + CUT = 0, + MUTE = 1, + SCENE = 2, + COMM_BREAK = 3 + } CUT_MARK_ACTION; + + /*! \brief PVR Recordings cut mark element. + */ + typedef struct PVR_CUT_MARK { + long long start; /**< \brief Start position in milliseconds */ + long long stop; /**< \brief Stop position in milliseconds */ + CUT_MARK_ACTION action; /**< \brief the action to be performed */ + } ATTRIBUTE_PACKED PVR_CUT_MARK; + +#if PRAGMA_PACK +#pragma pack() +#endif + + /*! \brief Structure to transfer the PVR functions to XBMC + */ + typedef struct PVRClient + { + /** \name PVR General Functions */ + PVR_ERROR (__cdecl* GetProperties)(PVR_SERVERPROPS *props); + PVR_ERROR (__cdecl* GetStreamProperties)(PVR_STREAMPROPS *props); + const char* (__cdecl* GetBackendName)(); + const char* (__cdecl* GetBackendVersion)(); + const char* (__cdecl* GetConnectionString)(); + PVR_ERROR (__cdecl* GetDriveSpace)(long long *total, long long *used); + PVR_ERROR (__cdecl* GetBackendTime)(time_t *localTime, int *gmtOffset); + PVR_ERROR (__cdecl* DialogChannelScan)(); + PVR_ERROR (__cdecl* MenuHook)(const PVR_MENUHOOK &menuhook); + + /** \name PVR EPG Functions */ + PVR_ERROR (__cdecl* RequestEPGForChannel)(PVRHANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end); + + /** \name PVR Bouquets Functions */ + int (__cdecl* GetNumBouquets)(); + PVR_ERROR (__cdecl* RequestBouquetsList)(PVRHANDLE handle, int radio); + + /** \name PVR Channel Functions */ + int (__cdecl* GetNumChannels)(); + PVR_ERROR (__cdecl* RequestChannelList)(PVRHANDLE handle, int radio); + PVR_ERROR (__cdecl* DeleteChannel)(unsigned int number); + PVR_ERROR (__cdecl* RenameChannel)(unsigned int number, const char *newname); + PVR_ERROR (__cdecl* MoveChannel)(unsigned int number, unsigned int newnumber); + PVR_ERROR (__cdecl* DialogChannelSettings)(const PVR_CHANNEL &channelinfo); + PVR_ERROR (__cdecl* DialogAddChannel)(const PVR_CHANNEL &channelinfo); + + /** \name PVR Recording Functions */ + int (__cdecl* GetNumRecordings)(); + PVR_ERROR (__cdecl* RequestRecordingsList)(PVRHANDLE handle); + PVR_ERROR (__cdecl* DeleteRecording)(const PVR_RECORDINGINFO &recinfo); + PVR_ERROR (__cdecl* RenameRecording)(const PVR_RECORDINGINFO &recinfo, const char *newname); + + /** \name PVR Recording cut marks Functions */ + bool (__cdecl* HaveCutmarks)(); + PVR_ERROR (__cdecl* RequestCutMarksList)(PVRHANDLE handle); + PVR_ERROR (__cdecl* AddCutMark)(const PVR_CUT_MARK &cutmark); + PVR_ERROR (__cdecl* DeleteCutMark)(const PVR_CUT_MARK &cutmark); + PVR_ERROR (__cdecl* StartCut)(); + + /** \name PVR Timer Functions */ + int (__cdecl* GetNumTimers)(); + PVR_ERROR (__cdecl* RequestTimerList)(PVRHANDLE handle); + PVR_ERROR (__cdecl* AddTimer)(const PVR_TIMERINFO &timerinfo); + PVR_ERROR (__cdecl* DeleteTimer)(const PVR_TIMERINFO &timerinfo, bool force); + PVR_ERROR (__cdecl* RenameTimer)(const PVR_TIMERINFO &timerinfo, const char *newname); + PVR_ERROR (__cdecl* UpdateTimer)(const PVR_TIMERINFO &timerinfo); + + /** \name PVR Live Stream Functions */ + bool (__cdecl* OpenLiveStream)(const PVR_CHANNEL &channelinfo); + void (__cdecl* CloseLiveStream)(); + int (__cdecl* ReadLiveStream)(unsigned char* buf, int buf_size); + long long (__cdecl* SeekLiveStream)(long long pos, int whence); + long long (__cdecl* PositionLiveStream)(void); + long long (__cdecl* LengthLiveStream)(void); + int (__cdecl* GetCurrentClientChannel)(); + bool (__cdecl* SwitchChannel)(const PVR_CHANNEL &channelinfo); + PVR_ERROR (__cdecl* SignalQuality)(PVR_SIGNALQUALITY &qualityinfo); + const char* (__cdecl* GetLiveStreamURL)(const PVR_CHANNEL &channelinfo); + + /** \name PVR Secondary Stream Functions */ + bool (__cdecl* SwapLiveTVSecondaryStream)(); + bool (__cdecl* OpenSecondaryStream)(const PVR_CHANNEL &channelinfo); + void (__cdecl* CloseSecondaryStream)(); + int (__cdecl* ReadSecondaryStream)(unsigned char* buf, int buf_size); + + /** \name PVR Recording Stream Functions */ + bool (__cdecl* OpenRecordedStream)(const PVR_RECORDINGINFO &recinfo); + void (__cdecl* CloseRecordedStream)(void); + int (__cdecl* ReadRecordedStream)(unsigned char* buf, int buf_size); + long long (__cdecl* SeekRecordedStream)(long long pos, int whence); + long long (__cdecl* PositionRecordedStream)(void); + long long (__cdecl* LengthRecordedStream)(void); + + /** \name Demuxer Interface */ + void (__cdecl* DemuxReset)(); + void (__cdecl* DemuxAbort)(); + void (__cdecl* DemuxFlush)(); + DemuxPacket* (__cdecl* DemuxRead)(); + + } PVRClient; + +#ifdef __cplusplus +} +#endif + +#endif //__PVRCLIENT_TYPES_H__ diff --git a/xbmc/cores/DllLoader/DllLoaderContainer.cpp b/xbmc/cores/DllLoader/DllLoaderContainer.cpp index 28cf85d2ce..17cf01332d 100644 --- a/xbmc/cores/DllLoader/DllLoaderContainer.cpp +++ b/xbmc/cores/DllLoader/DllLoaderContainer.cpp @@ -224,7 +224,7 @@ LibraryLoader* DllLoaderContainer::LoadDll(const char* sName, bool bLoadSymbols) LibraryLoader* pLoader; #ifdef _LINUX if (strstr(sName, ".so") != NULL || strstr(sName, ".vis") != NULL || strstr(sName, ".xbs") != NULL - || strstr(sName, ".mvis") != NULL || strstr(sName, ".dylib") != NULL || strstr(sName, ".framework") != NULL) + || strstr(sName, ".mvis") != NULL || strstr(sName, ".dylib") != NULL || strstr(sName, ".framework") != NULL || strstr(sName, ".pvr") != NULL) pLoader = new SoLoader(sName, bLoadSymbols); else #elif defined(_WIN32) diff --git a/xbmc/cores/DllLoader/Win32DllLoader.cpp b/xbmc/cores/DllLoader/Win32DllLoader.cpp index eddca2f9ae..8b822d6b64 100644 --- a/xbmc/cores/DllLoader/Win32DllLoader.cpp +++ b/xbmc/cores/DllLoader/Win32DllLoader.cpp @@ -365,6 +365,9 @@ bool Win32DllLoader::NeedsHooking(const char *dllName) CStdStringW strdllNameW; g_charsetConverter.utf8ToW(_P(dllName), strdllNameW, false); HMODULE hModule = GetModuleHandleW(strdllNameW.c_str()); + if (hModule == NULL) + return false; + wchar_t filepathW[MAX_PATH]; GetModuleFileNameW(hModule, filepathW, MAX_PATH); CStdString dllPath; diff --git a/xbmc/cores/dvdplayer/DVDCodecs/Video/VDPAU.cpp b/xbmc/cores/dvdplayer/DVDCodecs/Video/VDPAU.cpp index 965c5e2727..4de8c99a53 100644 --- a/xbmc/cores/dvdplayer/DVDCodecs/Video/VDPAU.cpp +++ b/xbmc/cores/dvdplayer/DVDCodecs/Video/VDPAU.cpp @@ -503,7 +503,8 @@ bool CVDPAU::Supports(VdpVideoMixerFeature feature) bool CVDPAU::Supports(EINTERLACEMETHOD method) { if(method == VS_INTERLACEMETHOD_VDPAU_BOB - || method == VS_INTERLACEMETHOD_AUTO) + || method == VS_INTERLACEMETHOD_AUTO + || method == VS_INTERLACEMETHOD_AUTO_ION) return true; for(SInterlaceMapping* p = g_interlace_mapping; p->method != VS_INTERLACEMETHOD_NONE; p++) @@ -614,12 +615,22 @@ void CVDPAU::SetDeinterlacing() VdpVideoMixerFeature feature[] = { VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL, VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL, VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE }; - if (method == VS_INTERLACEMETHOD_AUTO) { VdpBool enabled[]={1,1,0}; vdp_st = vdp_video_mixer_set_feature_enables(videoMixer, ARSIZE(feature), feature, enabled); } + else if (method == VS_INTERLACEMETHOD_AUTO_ION) + { + if (vid_height <= 576){ + VdpBool enabled[]={1,1,0}; + vdp_st = vdp_video_mixer_set_feature_enables(videoMixer, ARSIZE(feature), feature, enabled); + } + else if (vid_height > 576){ + VdpBool enabled[]={1,0,0}; + vdp_st = vdp_video_mixer_set_feature_enables(videoMixer, ARSIZE(feature), feature, enabled); + } + } else if (method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL || method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL_HALF) { @@ -1155,6 +1166,7 @@ int CVDPAU::Decode(AVCodecContext *avctx, AVFrame *pFrame) current = render; if((method == VS_INTERLACEMETHOD_AUTO && pFrame->interlaced_frame) + || (method == VS_INTERLACEMETHOD_AUTO_ION && pFrame->interlaced_frame) || method == VS_INTERLACEMETHOD_VDPAU_BOB || method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL || method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL_HALF @@ -1164,6 +1176,7 @@ int CVDPAU::Decode(AVCodecContext *avctx, AVFrame *pFrame) { if(method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL_HALF || method == VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL_HALF + || (method == VS_INTERLACEMETHOD_AUTO_ION && vid_height > 576) || avctx->hurry_up) m_mixerstep = 0; else diff --git a/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemux.h b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemux.h index ce3aa3ab31..fe9fa59f42 100644 --- a/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemux.h +++ b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemux.h @@ -23,6 +23,7 @@ #include "StdString.h" #include "system.h" +#include "DVDDemuxPacket.h" class CDVDInputStream; @@ -205,19 +206,6 @@ public: virtual void GetStreamInfo(std::string& strInfo); }; -typedef struct DemuxPacket -{ - BYTE* pData; // data - int iSize; // data size - int iStreamId; // integer representing the stream index - int iGroupId; // the group this data belongs to, used to group data from different streams together - - double pts; // pts in DVD_TIME_BASE - double dts; // dts in DVD_TIME_BASE - double duration; // duration in DVD_TIME_BASE if available -} DemuxPacket; - - class CDVDDemux { public: diff --git a/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index f14508aa8e..535f5e019b 100644 --- a/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -33,6 +33,7 @@ #include "DVDInputStreams/DVDInputStream.h" #include "DVDInputStreams/DVDInputStreamNavigator.h" #include "DVDInputStreams/DVDInputStreamBluray.h" +#include "DVDInputStreams/DVDInputStreamPVRManager.h" #include "DVDDemuxUtils.h" #include "DVDClock.h" // for DVD_TIME_BASE #include "utils/Win32Exception.h" diff --git a/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxPVRClient.cpp b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxPVRClient.cpp new file mode 100644 index 0000000000..ba9174d1be --- /dev/null +++ b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxPVRClient.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "DVDInputStreams/DVDInputStream.h" +#include "DVDDemuxPVRClient.h" +#include "PVRManager.h" +#ifdef _WIN32 +#include +#endif + +void CDemuxStreamVideoPVRClient::GetStreamInfo(std::string& strInfo) +{ +} + +void CDemuxStreamAudioPVRClient::GetStreamInfo(std::string& strInfo) +{ + switch (codec) + { + case CODEC_ID_AC3: + strInfo = "AC3"; + break; + default: + break; + } +} + +void CDemuxStreamSubtitlePVRClient::GetStreamInfo(std::string& strInfo) +{ +} + +CDVDDemuxPVRClient::CDVDDemuxPVRClient() : CDVDDemux() +{ + m_pInput = NULL; + for (int i = 0; i < MAX_PVR_STREAMS; i++) m_streams[i] = NULL; +} + +CDVDDemuxPVRClient::~CDVDDemuxPVRClient() +{ + Dispose(); +} + +bool CDVDDemuxPVRClient::Open(CDVDInputStream* pInput) +{ + Abort(); + m_pInput = pInput; + RequestStreams(); + return true; +} + +void CDVDDemuxPVRClient::Dispose() +{ + for (int i = 0; i < MAX_PVR_STREAMS; i++) + { + if (m_streams[i]) + { + if (m_streams[i]->ExtraData) + delete[] (BYTE*)(m_streams[i]->ExtraData); + delete m_streams[i]; + } + m_streams[i] = NULL; + } + m_pInput = NULL; +} + +void CDVDDemuxPVRClient::Reset() +{ + if(m_pInput) + g_PVRManager.DemuxReset(); + + CDVDInputStream* pInputStream = m_pInput; + Dispose(); + Open(pInputStream); +} + +void CDVDDemuxPVRClient::Abort() +{ + if(m_pInput) + g_PVRManager.DemuxAbort(); +} + +void CDVDDemuxPVRClient::Flush() +{ + if(m_pInput) + g_PVRManager.DemuxFlush(); +} + +DemuxPacket* CDVDDemuxPVRClient::Read() +{ + DemuxPacket* pPacket = g_PVRManager.ReadDemuxStream(); + if (!pPacket) + return CDVDDemuxUtils::AllocateDemuxPacket(0); + + if (pPacket->iStreamId == DMX_SPECIALID_STREAMINFO) + { + UpdateStreams((PVR_STREAMPROPS*)pPacket->pData); + CDVDDemuxUtils::FreeDemuxPacket(pPacket); + return CDVDDemuxUtils::AllocateDemuxPacket(0); + } + else if (pPacket->iStreamId == DMX_SPECIALID_STREAMCHANGE) + { + Reset(); + CDVDDemuxUtils::FreeDemuxPacket(pPacket); + return CDVDDemuxUtils::AllocateDemuxPacket(0); + } + + return pPacket; +} + +CDemuxStream* CDVDDemuxPVRClient::GetStream(int iStreamId) +{ + if (iStreamId < 0 || iStreamId >= MAX_PVR_STREAMS) return NULL; + return m_streams[iStreamId]; +} + +void CDVDDemuxPVRClient::RequestStreams() +{ + PVR_STREAMPROPS *props = g_PVRManager.GetCurrentStreamProps(); + + for (int i = 0; i < props->nstreams; ++i) + { + if (props->stream[i].codec_type == CODEC_TYPE_AUDIO) + { + CDemuxStreamAudioPVRClient* st = new CDemuxStreamAudioPVRClient(this); + st->iChannels = props->stream[i].channels; + st->iSampleRate = props->stream[i].samplerate; + st->iBlockAlign = props->stream[i].blockalign; + st->iBitRate = props->stream[i].bitrate; + st->iBitsPerSample = props->stream[i].bits_per_sample; + m_streams[props->stream[i].id] = st; + } + else if (props->stream[i].codec_type == CODEC_TYPE_VIDEO) + { + CDemuxStreamVideoPVRClient* st = new CDemuxStreamVideoPVRClient(this); + st->iFpsScale = props->stream[i].fpsscale; + st->iFpsRate = props->stream[i].fpsrate; + st->iHeight = props->stream[i].height; + st->iWidth = props->stream[i].width; + st->fAspect = props->stream[i].aspect; + m_streams[props->stream[i].id] = st; + } + else if (props->stream[i].codec_id == CODEC_ID_DVB_TELETEXT) + { + m_streams[props->stream[i].id] = new CDemuxStreamTeletext(); + } + else if (props->stream[i].codec_type == CODEC_TYPE_SUBTITLE) + { + CDemuxStreamSubtitlePVRClient* st = new CDemuxStreamSubtitlePVRClient(this); + st->identifier = props->stream[i].identifier; + m_streams[props->stream[i].id] = st; + } + else + m_streams[props->stream[i].id] = new CDemuxStream(); + + m_streams[props->stream[i].id]->codec = (CodecID)props->stream[i].codec_id; + m_streams[props->stream[i].id]->iId = props->stream[i].id; + m_streams[props->stream[i].id]->iPhysicalId = props->stream[i].physid; + m_streams[props->stream[i].id]->language[0] = props->stream[i].language[0]; + m_streams[props->stream[i].id]->language[1] = props->stream[i].language[1]; + m_streams[props->stream[i].id]->language[2] = props->stream[i].language[2]; + m_streams[props->stream[i].id]->language[3] = props->stream[i].language[3]; + + CLog::Log(LOGDEBUG,"CDVDDemuxPVRClient::RequestStreams(): added stream %d:%d with codec_id %d", m_streams[props->stream[i].id]->iId, m_streams[props->stream[i].id]->iPhysicalId, m_streams[props->stream[i].id]->codec); + } +} + +void CDVDDemuxPVRClient::UpdateStreams(PVR_STREAMPROPS *props) +{ + for (int i = 0; i < props->nstreams; ++i) + { + if (m_streams[props->stream[i].id] == NULL || + m_streams[props->stream[i].id]->codec != (CodecID)props->stream[i].codec_id) + { + CLog::Log(LOGERROR,"Invalid stream inside UpdateStreams"); + continue; + } + + if (m_streams[props->stream[i].id]->type == STREAM_AUDIO) + { + CDemuxStreamAudioPVRClient* st = (CDemuxStreamAudioPVRClient*) m_streams[props->stream[i].id]; + st->iChannels = props->stream[i].channels; + st->iSampleRate = props->stream[i].samplerate; + st->iBlockAlign = props->stream[i].blockalign; + st->iBitRate = props->stream[i].bitrate; + st->iBitsPerSample = props->stream[i].bits_per_sample; + } + else if (m_streams[props->stream[i].id]->type == STREAM_VIDEO) + { + CDemuxStreamVideoPVRClient* st = (CDemuxStreamVideoPVRClient*) m_streams[props->stream[i].id]; + st->iFpsScale = props->stream[i].fpsscale; + st->iFpsRate = props->stream[i].fpsrate; + st->iHeight = props->stream[i].height; + st->iWidth = props->stream[i].width; + st->fAspect = props->stream[i].aspect; + } + else if (m_streams[props->stream[i].id]->type == STREAM_SUBTITLE) + { + CDemuxStreamSubtitlePVRClient* st = (CDemuxStreamSubtitlePVRClient*) m_streams[props->stream[i].id]; + st->identifier = props->stream[i].identifier; + } + + m_streams[props->stream[i].id]->language[0] = props->stream[i].language[0]; + m_streams[props->stream[i].id]->language[1] = props->stream[i].language[1]; + m_streams[props->stream[i].id]->language[2] = props->stream[i].language[2]; + m_streams[props->stream[i].id]->language[3] = props->stream[i].language[3]; + + CLog::Log(LOGDEBUG,"CDVDDemuxPVRClient::UpdateStreams(): update stream %d:%d with codec_id %d", m_streams[props->stream[i].id]->iId, m_streams[props->stream[i].id]->iPhysicalId, m_streams[props->stream[i].id]->codec); + } +} + +int CDVDDemuxPVRClient::GetNrOfStreams() +{ + int i = 0; + while (i < MAX_PVR_STREAMS && m_streams[i]) i++; + return i; +} + +std::string CDVDDemuxPVRClient::GetFileName() +{ + if(m_pInput) + return m_pInput->GetFileName(); + else + return ""; +} + +void CDVDDemuxPVRClient::GetStreamCodecName(int iStreamId, CStdString &strName) +{ + CDemuxStream *stream = GetStream(iStreamId); + if (stream) + { + if (stream->codec == CODEC_ID_AC3) + strName = "ac3"; + else if (stream->codec == CODEC_ID_MP2) + strName = "mp2"; + else if (stream->codec == CODEC_ID_AAC) + strName = "aac"; + else if (stream->codec == CODEC_ID_DTS) + strName = "dca"; + else if (stream->codec == CODEC_ID_MPEG2VIDEO) + strName = "mpeg2video"; + else if (stream->codec == CODEC_ID_H264) + strName = "h264"; + } +} diff --git a/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxPVRClient.h b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxPVRClient.h new file mode 100644 index 0000000000..f23edc303f --- /dev/null +++ b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxPVRClient.h @@ -0,0 +1,91 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "DVDDemux.h" +#include + +class CDVDDemuxPVRClient; +struct PVR_STREAMPROPS; + +class CDemuxStreamVideoPVRClient : public CDemuxStreamVideo +{ + CDVDDemuxPVRClient *m_parent; +public: + CDemuxStreamVideoPVRClient(CDVDDemuxPVRClient *parent) + : m_parent(parent) + {} + virtual void GetStreamInfo(std::string& strInfo); +}; + +class CDemuxStreamAudioPVRClient : public CDemuxStreamAudio +{ + CDVDDemuxPVRClient *m_parent; +public: + CDemuxStreamAudioPVRClient(CDVDDemuxPVRClient *parent) + : m_parent(parent) + {} + virtual void GetStreamInfo(std::string& strInfo); +}; + +class CDemuxStreamSubtitlePVRClient : public CDemuxStreamSubtitle +{ + CDVDDemuxPVRClient *m_parent; +public: + CDemuxStreamSubtitlePVRClient(CDVDDemuxPVRClient *parent) + : m_parent(parent) + {} + virtual void GetStreamInfo(std::string& strInfo); +}; + + +class CDVDDemuxPVRClient : public CDVDDemux +{ +public: + + CDVDDemuxPVRClient(); + ~CDVDDemuxPVRClient(); + + bool Open(CDVDInputStream* pInput); + void Dispose(); + void Reset(); + void Abort(); + void Flush(); + DemuxPacket* Read(); + bool SeekTime(int time, bool backwords = false, double* startpts = NULL) { return false; } + void SetSpeed(int iSpeed) {}; + int GetStreamLength() { return 0; } + CDemuxStream* GetStream(int iStreamId); + int GetNrOfStreams(); + std::string GetFileName(); + virtual void GetStreamCodecName(int iStreamId, CStdString &strName); + +protected: + CDVDInputStream* m_pInput; + + #define MAX_PVR_STREAMS 42 + CDemuxStream* m_streams[MAX_PVR_STREAMS]; // maximum number of streams that ffmpeg can handle + +private: + void RequestStreams(); + void UpdateStreams(PVR_STREAMPROPS *props); +}; + diff --git a/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxPacket.h b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxPacket.h new file mode 100644 index 0000000000..beadfcd2f8 --- /dev/null +++ b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxPacket.h @@ -0,0 +1,37 @@ +#pragma once + +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#define DMX_SPECIALID_STREAMINFO -10 +#define DMX_SPECIALID_STREAMCHANGE -11 + + typedef struct DemuxPacket +{ + unsigned char* pData; // data + int iSize; // data size + int iStreamId; // integer representing the stream index + int iGroupId; // the group this data belongs to, used to group data from different streams together + + double pts; // pts in DVD_TIME_BASE + double dts; // dts in DVD_TIME_BASE + double duration; // duration in DVD_TIME_BASE if available +} DemuxPacket; diff --git a/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxUtils.h b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxUtils.h index e7d5f416a2..5d288107ea 100644 --- a/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxUtils.h +++ b/xbmc/cores/dvdplayer/DVDDemuxers/DVDDemuxUtils.h @@ -21,7 +21,7 @@ * */ -#include "DVDDemux.h" +#include "DVDDemuxPacket.h" class CDVDDemuxUtils { diff --git a/xbmc/cores/dvdplayer/DVDDemuxers/DVDFactoryDemuxer.cpp b/xbmc/cores/dvdplayer/DVDDemuxers/DVDFactoryDemuxer.cpp index 7021661cd3..a9200b142b 100644 --- a/xbmc/cores/dvdplayer/DVDDemuxers/DVDFactoryDemuxer.cpp +++ b/xbmc/cores/dvdplayer/DVDDemuxers/DVDFactoryDemuxer.cpp @@ -24,12 +24,15 @@ #include "DVDInputStreams/DVDInputStream.h" #include "DVDInputStreams/DVDInputStreamHttp.h" +#include "DVDInputStreams/DVDInputStreamPVRManager.h" #include "DVDDemuxFFmpeg.h" #include "DVDDemuxShoutcast.h" #ifdef HAS_FILESYSTEM_HTSP #include "DVDDemuxHTSP.h" #endif +#include "DVDDemuxPVRClient.h" +#include "PVRManager.h" using namespace std; @@ -62,6 +65,24 @@ CDVDDemux* CDVDFactoryDemuxer::CreateDemuxer(CDVDInputStream* pInputStream) } #endif + if (pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER)) + { + std::string filename = pInputStream->GetFileName(); + /* Use PVR demuxer only for live streams */ + if (filename.substr(0, 14) == "pvr://channels") + { + PVR_SERVERPROPS *pProps = g_PVRManager.GetCurrentClientProps(); + if (pProps && pProps->HandleDemuxing) + { + auto_ptr demuxer(new CDVDDemuxPVRClient()); + if(demuxer->Open(pInputStream)) + return demuxer.release(); + else + return NULL; + } + } + } + auto_ptr demuxer(new CDVDDemuxFFmpeg()); if(demuxer->Open(pInputStream)) return demuxer.release(); diff --git a/xbmc/cores/dvdplayer/DVDDemuxers/Makefile.in b/xbmc/cores/dvdplayer/DVDDemuxers/Makefile.in index 8cebca352c..25bb7b7cfb 100644 --- a/xbmc/cores/dvdplayer/DVDDemuxers/Makefile.in +++ b/xbmc/cores/dvdplayer/DVDDemuxers/Makefile.in @@ -10,6 +10,7 @@ SRCS= DVDDemux.cpp \ DVDFactoryDemuxer.cpp \ DVDDemuxVobsub.cpp \ DVDDemuxHTSP.cpp \ + DVDDemuxPVRClient.cpp \ LIB= DVDDemuxers.a diff --git a/xbmc/cores/dvdplayer/DVDInputStreams/DVDFactoryInputStream.cpp b/xbmc/cores/dvdplayer/DVDInputStreams/DVDFactoryInputStream.cpp index 580e1989d1..4eff9c0521 100644 --- a/xbmc/cores/dvdplayer/DVDInputStreams/DVDFactoryInputStream.cpp +++ b/xbmc/cores/dvdplayer/DVDInputStreams/DVDFactoryInputStream.cpp @@ -26,6 +26,7 @@ #include "DVDInputStreamNavigator.h" #include "DVDInputStreamHttp.h" #include "DVDInputStreamFFmpeg.h" +#include "DVDInputStreamPVRManager.h" #include "DVDInputStreamTV.h" #include "DVDInputStreamRTMP.h" #ifdef HAVE_LIBBLURAY @@ -55,6 +56,8 @@ CDVDInputStream* CDVDFactoryInputStream::CreateInputStream(IDVDPlayer* pPlayer, { return (new CDVDInputStreamNavigator(pPlayer)); } + else if(file.substr(0, 6) == "pvr://") + return new CDVDInputStreamPVRManager(pPlayer); #ifdef HAVE_LIBBLURAY else if (item.IsType(".bdmv") || item.IsType(".mpls")) return new CDVDInputStreamBluray(); diff --git a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStream.h b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStream.h index d68a30e93a..72d8e95371 100644 --- a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStream.h +++ b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStream.h @@ -38,8 +38,9 @@ enum DVDStreamType DVDSTREAM_TYPE_RTMP = 7, DVDSTREAM_TYPE_HTSP = 8, DVDSTREAM_TYPE_MMS = 9, - DVDSTREAM_TYPE_MPLS = 10, - DVDSTREAM_TYPE_BLURAY = 11, + DVDSTREAM_TYPE_PVRMANAGER = 10, + DVDSTREAM_TYPE_MPLS = 11, + DVDSTREAM_TYPE_BLURAY = 12, }; #define DVDSTREAM_BLOCK_SIZE_FILE (2048 * 16) @@ -52,10 +53,16 @@ public: { public: virtual ~IChannel() {}; - virtual bool NextChannel() = 0; - virtual bool PrevChannel() = 0; + virtual bool NextChannel(bool preview = false) = 0; + virtual bool PrevChannel(bool preview = false) = 0; virtual bool SelectChannel(unsigned int channel) = 0; + virtual int GetSelectedChannel() = 0; + virtual int GetTotalTime() = 0; + virtual int GetStartTime() = 0; virtual bool UpdateItem(CFileItem& item) = 0; + virtual bool CanRecord() = 0; + virtual bool IsRecording() = 0; + virtual bool Record(bool bOnOff) = 0; }; class IDisplayTime diff --git a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamHTSP.cpp b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamHTSP.cpp index 5a5d8f42e3..52d7d7a42c 100644 --- a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamHTSP.cpp +++ b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamHTSP.cpp @@ -196,7 +196,7 @@ bool CDVDInputStreamHTSP::GetChannels(SChannelV &channels, SChannelV::iterator & return false; } -bool CDVDInputStreamHTSP::NextChannel() +bool CDVDInputStreamHTSP::NextChannel(bool preview/* = false*/) { SChannelV channels; SChannelV::iterator it; @@ -210,7 +210,7 @@ bool CDVDInputStreamHTSP::NextChannel() return SetChannel(circ->id); } -bool CDVDInputStreamHTSP::PrevChannel() +bool CDVDInputStreamHTSP::PrevChannel(bool preview/* = false*/) { SChannelV channels; SChannelV::iterator it; @@ -253,6 +253,33 @@ bool CDVDInputStreamHTSP::UpdateItem(CFileItem& item) || current.m_strTitle != item.m_strTitle; } +int CDVDInputStreamHTSP::GetTotalTime() +{ + if(m_event.id == 0) + return 0; + + long duration = (time_t)m_event.stop - (time_t)m_event.start; + CDateTimeSpan time = CDateTimeSpan(0, 0, duration / 60, duration % 60); + + return time.GetDays() * 1000 * 60 * 60 * 24 + + time.GetHours() * 1000 * 60 * 60 + + time.GetMinutes() * 1000 * 60 + + time.GetSeconds() * 1000; +} + +int CDVDInputStreamHTSP::GetStartTime() +{ + if(m_event.id == 0) + return 0; + + time_t time_c; + + CDateTime::GetCurrentDateTime().GetAsTime(time_c); + + return (m_event.start - time_c) * 1000; +} + +/* int CDVDInputStreamHTSP::GetTotalTime() { if(m_event.id == 0) @@ -271,6 +298,7 @@ int CDVDInputStreamHTSP::GetTime() + time.GetMinutes() * 1000 * 60 + time.GetSeconds() * 1000; } +*/ void CDVDInputStreamHTSP::Abort() { diff --git a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamHTSP.h b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamHTSP.h index 7f7781e9e1..e8c378ba48 100644 --- a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamHTSP.h +++ b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamHTSP.h @@ -26,7 +26,6 @@ class CDVDInputStreamHTSP : public CDVDInputStream , public CDVDInputStream::IChannel - , public CDVDInputStream::IDisplayTime { public: CDVDInputStreamHTSP(); @@ -43,13 +42,18 @@ public: virtual void Abort(); - bool NextChannel(); - bool PrevChannel(); + bool NextChannel(bool preview = false); + bool PrevChannel(bool preview = false); bool SelectChannel(unsigned int channel); + int GetSelectedChannel() {return -1; } bool UpdateItem(CFileItem& item); + bool CanRecord() { return false; } + bool IsRecording() { return false; } + bool Record(bool bOnOff) { return false; } + int GetTotalTime(); - int GetTime(); + int GetStartTime(); htsmsg_t* ReadStream(); diff --git a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamMMS.cpp b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamMMS.cpp index cdd03e2961..ac285793f0 100644 --- a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamMMS.cpp +++ b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamMMS.cpp @@ -101,7 +101,10 @@ void CDVDInputStreamMMS::Close() { CDVDInputStream::Close(); if (m_mms) + { mmsx_close(m_mms); + m_mms = NULL; + } } int CDVDInputStreamMMS::Read(BYTE* buf, int buf_size) diff --git a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamPVRManager.cpp b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamPVRManager.cpp new file mode 100644 index 0000000000..afee8b0922 --- /dev/null +++ b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamPVRManager.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "DVDFactoryInputStream.h" +#include "DVDInputStreamPVRManager.h" +#include "FileSystem/PVRFile.h" +#include "URL.h" +#include "PVRManager.h" + +using namespace XFILE; + +/************************************************************************ + * Description: Class constructor, initialize member variables + * public class is CDVDInputStream + */ +CDVDInputStreamPVRManager::CDVDInputStreamPVRManager(IDVDPlayer* pPlayer) : CDVDInputStream(DVDSTREAM_TYPE_PVRMANAGER) +{ + m_pPlayer = pPlayer; + m_pFile = NULL; + m_pRecordable = NULL; + m_pLiveTV = NULL; + m_pOtherStream = NULL; + m_eof = true; +} + +/************************************************************************ + * Description: Class destructor + */ +CDVDInputStreamPVRManager::~CDVDInputStreamPVRManager() +{ + Close(); +} + +bool CDVDInputStreamPVRManager::IsEOF() +{ + if (m_pOtherStream) + return m_pOtherStream->IsEOF(); + else + return !m_pFile || m_eof; +} + +bool CDVDInputStreamPVRManager::Open(const char* strFile, const std::string& content) +{ + /* Open PVR File for both cases, to have access to ILiveTVInterface and + * IRecordable + */ + m_pFile = new CPVRFile(); + m_pLiveTV = ((CPVRFile*)m_pFile)->GetLiveTV(); + m_pRecordable = ((CPVRFile*)m_pFile)->GetRecordable(); + + CURL url(strFile); + if (!CDVDInputStream::Open(strFile, content)) return false; + if (!m_pFile->Open(url)) + { + delete m_pFile; + m_pFile = NULL; + return false; + } + m_eof = false; + + /* + * Translate the "pvr://....." entry. + * The PVR Client can use http or whatever else is supported by DVDPlayer. + * to access streams. + * If after translation the file protocol is still "pvr://" use this class + * to read the stream data over the CPVRFile class and the PVR Library itself. + * Otherwise call CreateInputStream again with the translated filename and looks again + * for the right protocol stream handler and swap every call to this input stream + * handler. + */ + std::string transFile = XFILE::CPVRFile::TranslatePVRFilename(strFile); + if(transFile.substr(0, 6) != "pvr://") + { + m_pOtherStream = CDVDFactoryInputStream::CreateInputStream(m_pPlayer, transFile, content); + if (!m_pOtherStream) + { + CLog::Log(LOGERROR, "CDVDInputStreamPVRManager::Open - unable to create input stream for [%s]", transFile.c_str()); + return false; + } + else + m_pOtherStream->SetFileItem(m_item); + + if (!m_pOtherStream->Open(transFile.c_str(), content)) + { + CLog::Log(LOGERROR, "CDVDInputStreamPVRManager::Open - error opening [%s]", transFile.c_str()); + delete m_pFile; + m_pFile = NULL; + delete m_pOtherStream; + m_pOtherStream = NULL; + return false; + } + } + + return true; +} + +// close file and reset everyting +void CDVDInputStreamPVRManager::Close() +{ + if (m_pOtherStream) + { + m_pOtherStream->Close(); + delete m_pOtherStream; + } + + if (m_pFile) + { + m_pFile->Close(); + delete m_pFile; + } + + CDVDInputStream::Close(); + + m_pPlayer = NULL; + m_pFile = NULL; + m_pLiveTV = NULL; + m_pRecordable = NULL; + m_pOtherStream = NULL; + m_eof = true; +} + +int CDVDInputStreamPVRManager::Read(BYTE* buf, int buf_size) +{ + if(!m_pFile) return -1; + + if (m_pOtherStream) + { + return m_pOtherStream->Read(buf, buf_size); + } + else + { + unsigned int ret = m_pFile->Read(buf, buf_size); + + /* we currently don't support non completing reads */ + if( ret <= 0 ) m_eof = true; + + return (int)(ret & 0xFFFFFFFF); + } +} + +__int64 CDVDInputStreamPVRManager::Seek(__int64 offset, int whence) +{ + if(!m_pFile) return -1; + + if (m_pOtherStream) + { + return m_pOtherStream->Seek(offset, whence); + } + else + { + __int64 ret = m_pFile->Seek(offset, whence); + + /* if we succeed, we are not eof anymore */ + if( ret >= 0 ) m_eof = false; + + return ret; + } +} + +__int64 CDVDInputStreamPVRManager::GetLength() +{ + if(!m_pFile) return -1; + + if (m_pOtherStream) + return m_pOtherStream->GetLength(); + else + return m_pFile->GetLength(); +} + +int CDVDInputStreamPVRManager::GetTotalTime() +{ + if (m_pLiveTV) + return m_pLiveTV->GetTotalTime(); + return 0; +} + +int CDVDInputStreamPVRManager::GetStartTime() +{ + if (m_pLiveTV) + return m_pLiveTV->GetStartTime(); + return 0; +} + +bool CDVDInputStreamPVRManager::NextChannel(bool preview/* = false*/) +{ + if (m_pLiveTV) + return m_pLiveTV->NextChannel(preview); + return false; +} + +bool CDVDInputStreamPVRManager::PrevChannel(bool preview/* = false*/) +{ + if (m_pLiveTV) + return m_pLiveTV->PrevChannel(preview); + return false; +} + +bool CDVDInputStreamPVRManager::SelectChannel(unsigned int channel) +{ + if (m_pLiveTV) + return m_pLiveTV->SelectChannel(channel); + return false; +} + +int CDVDInputStreamPVRManager::GetSelectedChannel() +{ + int number = -1; + bool radio = false; + g_PVRManager.GetCurrentChannel(&number, &radio); + return number; +} + +bool CDVDInputStreamPVRManager::UpdateItem(CFileItem& item) +{ + if (m_pLiveTV) + return m_pLiveTV->UpdateItem(item); + return false; +} + +bool CDVDInputStreamPVRManager::NextStream() +{ + if(!m_pFile) return false; + + if (m_pOtherStream) + return m_pOtherStream->NextStream(); + else + { + if(m_pFile->SkipNext()) + { + m_eof = false; + return true; + } + } + return false; +} + +bool CDVDInputStreamPVRManager::CanRecord() +{ + if (m_pRecordable) + return m_pRecordable->CanRecord(); + return false; +} + +bool CDVDInputStreamPVRManager::IsRecording() +{ + if (m_pRecordable) + return m_pRecordable->IsRecording(); + return false; +} + +bool CDVDInputStreamPVRManager::Record(bool bOnOff) +{ + if (m_pRecordable) + return m_pRecordable->Record(bOnOff); + return false; +} + +CStdString CDVDInputStreamPVRManager::GetInputFormat() +{ + if (m_pOtherStream) + return ""; + else + return g_PVRManager.GetCurrentInputFormat(); +} diff --git a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamPVRManager.h b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamPVRManager.h new file mode 100644 index 0000000000..af7d389f82 --- /dev/null +++ b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamPVRManager.h @@ -0,0 +1,106 @@ +#pragma once + +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* +* for DESCRIPTION see 'DVDInputStreamPVRManager.cpp' +*/ + +#include "DVDInputStream.h" +#include "FileItem.h" + +namespace XFILE { +class IFile; +class ILiveTVInterface; +class IRecordable; +} + +class IDVDPlayer; + +class CDVDInputStreamPVRManager + : public CDVDInputStream + , public CDVDInputStream::IChannel +{ +public: + CDVDInputStreamPVRManager(IDVDPlayer* pPlayer); + virtual ~CDVDInputStreamPVRManager(); + virtual bool Open(const char* strFile, const std::string &content); + virtual void Close(); + virtual int Read(BYTE* buf, int buf_size); + virtual __int64 Seek(__int64 offset, int whence); + virtual bool Pause(double dTime) { return false; } + virtual bool IsEOF(); + virtual __int64 GetLength(); + + virtual bool NextStream(); + + bool SelectChannel(unsigned int iChannel); + bool NextChannel(bool preview = false); + bool PrevChannel(bool preview = false); + int GetSelectedChannel(); + + int GetTotalTime(); + int GetStartTime(); + + bool CanRecord(); + bool IsRecording(); + bool Record(bool bOnOff); + + bool UpdateItem(CFileItem& item); + + /* overloaded is streamtype to support m_pOtherStream */ + bool IsStreamType(DVDStreamType type) const; + + /*! \brief Get the input format from the Backend + If it is empty ffmpeg scanning the stream to find the right input format. + See "xbmc/cores/dvdplayer/Codecs/ffmpeg/libavformat/allformats.c" for a + list of the input formats. + \return The name of the input format + */ + CStdString GetInputFormat(); + + /* returns m_pOtherStream */ + CDVDInputStream* GetOtherStream(); + +protected: + IDVDPlayer* m_pPlayer; + CDVDInputStream* m_pOtherStream; + XFILE::IFile* m_pFile; + XFILE::ILiveTVInterface* m_pLiveTV; + XFILE::IRecordable* m_pRecordable; + bool m_eof; +}; + + +inline bool CDVDInputStreamPVRManager::IsStreamType(DVDStreamType type) const +{ + if (m_pOtherStream) + return m_pOtherStream->IsStreamType(type); + + return m_streamType == type; +} + +inline CDVDInputStream* CDVDInputStreamPVRManager::GetOtherStream() +{ + return m_pOtherStream; +}; + diff --git a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamTV.cpp b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamTV.cpp index fdeb336482..70184c7bd2 100644 --- a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamTV.cpp +++ b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamTV.cpp @@ -130,13 +130,13 @@ int CDVDInputStreamTV::GetStartTime() return m_pLiveTV->GetStartTime(); } -bool CDVDInputStreamTV::NextChannel() +bool CDVDInputStreamTV::NextChannel(bool preview/* = false*/) { if(!m_pLiveTV) return false; return m_pLiveTV->NextChannel(); } -bool CDVDInputStreamTV::PrevChannel() +bool CDVDInputStreamTV::PrevChannel(bool preview/* = false*/) { if(!m_pLiveTV) return false; return m_pLiveTV->PrevChannel(); diff --git a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamTV.h b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamTV.h index f19244a227..b1d14719d0 100644 --- a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamTV.h +++ b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamTV.h @@ -48,9 +48,10 @@ public: virtual int GetBlockSize(); - bool NextChannel(); - bool PrevChannel(); + bool NextChannel(bool preview = false); + bool PrevChannel(bool preview = false); bool SelectChannel(unsigned int channel); + int GetSelectedChannel() {return -1; } int GetTotalTime(); int GetStartTime(); diff --git a/xbmc/cores/dvdplayer/DVDInputStreams/Makefile b/xbmc/cores/dvdplayer/DVDInputStreams/Makefile index ce613e73cf..5b4a8669b8 100644 --- a/xbmc/cores/dvdplayer/DVDInputStreams/Makefile +++ b/xbmc/cores/dvdplayer/DVDInputStreams/Makefile @@ -1,4 +1,4 @@ -INCLUDES=-I. -I.. -I../.. -I../../../ -I../../../linux -I../../../../guilib -I../../../lib/libRTMP -I../../../lib +INCLUDES=-I. -I.. -I../.. -I../../../ -I../../../linux -I../../../../guilib -I../../../lib/libRTMP -I../../../lib -I../../../utils CXXFLAGS += -D__STDC_FORMAT_MACROS \ -DENABLE_DVDINPUTSTREAM_STACK \ @@ -12,6 +12,7 @@ SRCS= DVDFactoryInputStream.cpp \ DVDInputStreamFFmpeg.cpp \ DVDInputStreamTV.cpp \ DVDInputStreamRTMP.cpp \ + DVDInputStreamPVRManager.cpp \ DVDInputStreamStack.cpp \ DVDInputStreamHTSP.cpp \ DVDInputStreamMMS.cpp \ diff --git a/xbmc/cores/dvdplayer/DVDPlayer.cpp b/xbmc/cores/dvdplayer/DVDPlayer.cpp index 9d679e6543..00a3e06415 100644 --- a/xbmc/cores/dvdplayer/DVDPlayer.cpp +++ b/xbmc/cores/dvdplayer/DVDPlayer.cpp @@ -26,6 +26,7 @@ #include "DVDInputStreams/DVDFactoryInputStream.h" #include "DVDInputStreams/DVDInputStreamNavigator.h" #include "DVDInputStreams/DVDInputStreamTV.h" +#include "DVDInputStreams/DVDInputStreamPVRManager.h" #include "DVDDemuxers/DVDDemux.h" #include "DVDDemuxers/DVDDemuxUtils.h" @@ -41,6 +42,7 @@ #include "Util.h" #include "utils/GUIInfoManager.h" #include "GUIWindowManager.h" +#include "GUIDialogFullScreenInfo.h" #include "Application.h" #include "DVDPerformanceCounter.h" #include "FileSystem/File.h" @@ -65,6 +67,8 @@ #include "utils/TimeUtils.h" #include "utils/StreamDetails.h" #include "MediaManager.h" +#include "PVRManager.h" +#include "FileSystem/PVRFile.h" #include "GUIDialogBusy.h" using namespace std; @@ -384,6 +388,17 @@ bool CDVDPlayer::CloseFile() // we are done after the StopThread call StopThread(); + // Added to stop live TV streams from the PVR addons at the backend side + // Used for the MediaPortal PVR addon to stop the rtsp timeshift stream + if (m_item.HasPVRChannelInfoTag()) + { + if (m_item.GetPVRChannelInfoTag()->StreamURL().compare(0,13, "pvr://stream/") == 0) + { + m_filename = m_item.m_strPath; //Restore the original pvr path + g_PVRManager.CloseStream(); + } + } + m_Edl.Clear(); m_EdlAutoSkipMarkers.Clear(); @@ -445,6 +460,7 @@ bool CDVDPlayer::OpenInputStream() // find any available external subtitles for non dvd files if (!m_pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) + && !m_pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER) && !m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV) && !m_pInputStream->IsStreamType(DVDSTREAM_TYPE_HTSP)) { @@ -468,6 +484,7 @@ bool CDVDPlayer::OpenInputStream() m_clock.Discontinuity(CLOCK_DISC_FULL); m_dvd.Clear(); m_errorCount = 0; + m_ChannelEntryTimeOut = 0; return true; } @@ -485,7 +502,12 @@ bool CDVDPlayer::OpenDemuxStream() while(!m_bStop && attempts-- > 0) { m_pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(m_pInputStream); - if(!m_pDemuxer && m_pInputStream->NextStream()) + if(!m_pDemuxer && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER)) + { + Sleep(200); + continue; + } + else if(!m_pDemuxer && m_pInputStream->NextStream()) { CLog::Log(LOGDEBUG, "%s - New stream available from input, retry open", __FUNCTION__); continue; @@ -629,7 +651,6 @@ void CDVDPlayer::OpenDefaultStreams() m_dvdPlayerVideo.EnableSubtitle(true); else m_dvdPlayerVideo.EnableSubtitle(false); - // open teletext data stream count = m_SelectionStreams.Count(STREAM_TELETEXT); valid = false; @@ -945,6 +966,26 @@ void CDVDPlayer::Process() // update application with our state UpdateApplication(1000); + if (m_ChannelEntryTimeOut > 0 && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER)) + { + if (CTimeUtils::GetTimeMS() - m_ChannelEntryTimeOut > g_guiSettings.GetInt("pvrplayback.channelentrytimeout")) + { + m_ChannelEntryTimeOut = 0; + CDVDInputStreamPVRManager* pStream = static_cast(m_pInputStream); + int channel = pStream->GetSelectedChannel(); + if(channel > 0 && pStream->SelectChannel(channel)) + { + FlushBuffers(false); + SAFE_DELETE(m_pDemuxer); + continue; + } + else + { + break; + } + } + } + // if the queues are full, no need to read more if ((!m_dvdPlayerAudio.AcceptsData() && m_CurrentAudio.id >= 0) || (!m_dvdPlayerVideo.AcceptsData() && m_CurrentVideo.id >= 0)) @@ -1026,6 +1067,16 @@ void CDVDPlayer::Process() Sleep(100); continue; } + else if (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER)) + { + CDVDInputStreamPVRManager* pStream = static_cast(m_pInputStream); + + if (pStream->IsEOF()) + break; + + Sleep(100); + continue; + } // make sure we tell all players to finish it's data if(m_CurrentAudio.inited) @@ -1314,7 +1365,8 @@ bool CDVDPlayer::CheckStartCaching(CCurrentStream& current) return false; if(m_pInputStream->IsStreamType(DVDSTREAM_TYPE_HTSP) - || m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV)) + || m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV) + || m_pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER)) SetCaching(CACHESTATE_INIT); else { @@ -1419,7 +1471,7 @@ void CDVDPlayer::CheckContinuity(CCurrentStream& current, DemuxPacket* pPacket) #if 0 // these checks seem to cause more harm, than good // looping stillframes are not common in normal files - // and a better fix for this behaviour would be to + // and a better fix for this behaviour would be to // correct the timestamps with some offset if (current.type == STREAM_VIDEO @@ -1495,6 +1547,7 @@ void CDVDPlayer::CheckContinuity(CCurrentStream& current, DemuxPacket* pPacket) /* normally don't need to sync players since video player will keep playing at normal fps */ /* after a discontinuity */ //SynchronizePlayers(dts, pts, MSGWAIT_ALL); + m_CurrentAudio.inited = false; m_CurrentVideo.inited = false; m_CurrentSubtitle.inited = false; @@ -1897,8 +1950,9 @@ void CDVDPlayer::HandleMessages() } else if (pMsg->IsType(CDVDMsg::PLAYER_SET_RECORD)) { - if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV)) - static_cast(m_pInputStream)->Record(*(CDVDMsgBool*)pMsg); + CDVDInputStream::IChannel* input = dynamic_cast(m_pInputStream); + if(input) + input->Record(*(CDVDMsgBool*)pMsg); } else if (pMsg->IsType(CDVDMsg::GENERAL_FLUSH)) { @@ -1945,30 +1999,48 @@ void CDVDPlayer::HandleMessages() if(m_pDemuxer) m_pDemuxer->SetSpeed(speed); } - else if (pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_NEXT) || - pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_PREV) || - (pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_SELECT) && m_messenger.GetPacketCount(CDVDMsg::PLAYER_CHANNEL_SELECT) == 0)) + else if (pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_SELECT) && m_messenger.GetPacketCount(CDVDMsg::PLAYER_CHANNEL_SELECT) == 0) { CDVDInputStream::IChannel* input = dynamic_cast(m_pInputStream); - if(input) + if(input && input->SelectChannel(static_cast(pMsg)->m_value)) { - g_infoManager.SetDisplayAfterSeek(100000); + FlushBuffers(false); + SAFE_DELETE(m_pDemuxer); + } + } + else if (pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_NEXT) || pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_PREV)) + { + CDVDInputStream::IChannel* input = dynamic_cast(m_pInputStream); + if(input) + { bool result; - if (pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_SELECT)) - result = input->SelectChannel(static_cast(pMsg)->m_value); - else if(pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_NEXT)) - result = input->NextChannel(); + bool fastSwitch = g_guiSettings.GetInt("pvrplayback.channelentrytimeout") > 0; + + if(pMsg->IsType(CDVDMsg::PLAYER_CHANNEL_NEXT)) + result = input->NextChannel(fastSwitch); else - result = input->PrevChannel(); + result = input->PrevChannel(fastSwitch); if(result) { - FlushBuffers(false); - SAFE_DELETE(m_pDemuxer); + if (fastSwitch) + { + CFileItem item(g_application.CurrentFileItem()); + if(input->UpdateItem(item)) + { + g_application.CurrentFileItem() = item; + g_infoManager.SetCurrentItem(item); + } + m_ChannelEntryTimeOut = CTimeUtils::GetTimeMS(); + } + else + { + m_ChannelEntryTimeOut = 0; + FlushBuffers(false); + SAFE_DELETE(m_pDemuxer); + } } - - g_infoManager.SetDisplayAfterSeek(); } } else if (pMsg->IsType(CDVDMsg::GENERAL_GUI_ACTION)) @@ -3165,17 +3237,45 @@ bool CDVDPlayer::OnAction(const CAction &action) { switch (action.GetID()) { + case ACTION_MOVE_UP: case ACTION_NEXT_ITEM: case ACTION_PAGE_UP: m_messenger.Put(new CDVDMsg(CDVDMsg::PLAYER_CHANNEL_NEXT)); g_infoManager.SetDisplayAfterSeek(); + if (g_guiSettings.GetBool("pvrmenu.infoswitch")) + { + CGUIDialogFullScreenInfo* pDialog = (CGUIDialogFullScreenInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_FULLSCREEN_INFO); + if (pDialog) + { + if (g_guiSettings.GetBool("pvrmenu.infotimeout")) + { + pDialog->SetAutoClose(g_guiSettings.GetInt("pvrmenu.infotime")*1000); + } + if (m_ChannelEntryTimeOut == 0) + pDialog->DoModal(); + } + } return true; break; + case ACTION_MOVE_DOWN: case ACTION_PREV_ITEM: case ACTION_PAGE_DOWN: m_messenger.Put(new CDVDMsg(CDVDMsg::PLAYER_CHANNEL_PREV)); g_infoManager.SetDisplayAfterSeek(); + if (g_guiSettings.GetBool("pvrmenu.infoswitch")) + { + CGUIDialogFullScreenInfo* pDialog = (CGUIDialogFullScreenInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_FULLSCREEN_INFO); + if (pDialog) + { + if (g_guiSettings.GetBool("pvrmenu.infotimeout")) + { + pDialog->SetAutoClose(g_guiSettings.GetInt("pvrmenu.infotime")*1000); + } + if (m_ChannelEntryTimeOut == 0) + pDialog->DoModal(); + } + } return true; break; @@ -3185,6 +3285,18 @@ bool CDVDPlayer::OnAction(const CAction &action) int channel = action.GetAmount(); m_messenger.Put(new CDVDMsgInt(CDVDMsg::PLAYER_CHANNEL_SELECT, channel)); g_infoManager.SetDisplayAfterSeek(); + if (g_guiSettings.GetBool("pvrmenu.infoswitch")) + { + CGUIDialogFullScreenInfo* pDialog = (CGUIDialogFullScreenInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_FULLSCREEN_INFO); + if (pDialog) + { + if (g_guiSettings.GetBool("pvrmenu.infotimeout")) + { + pDialog->SetAutoClose(g_guiSettings.GetInt("pvrmenu.infotime")*1000); + } + pDialog->DoModal(); + } + } return true; } break; @@ -3400,12 +3512,6 @@ void CDVDPlayer::UpdatePlayState(double timeout) { // override from input stream if needed - if (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV)) - { - m_State.canrecord = static_cast(m_pInputStream)->CanRecord(); - m_State.recording = static_cast(m_pInputStream)->IsRecording(); - } - CDVDInputStream::IDisplayTime* pDisplayTime = dynamic_cast(m_pInputStream); if (pDisplayTime) { @@ -3427,13 +3533,16 @@ void CDVDPlayer::UpdatePlayState(double timeout) else m_State.player_state = ""; - - if (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV)) + CDVDInputStream::IChannel* input = dynamic_cast(m_pInputStream); + if(input) { - if(((CDVDInputStreamTV*)m_pInputStream)->GetTotalTime() > 0) + m_State.canrecord = input->CanRecord(); + m_State.recording = input->IsRecording(); + + if(input->GetTotalTime() > 0) { - m_State.time -= ((CDVDInputStreamTV*)m_pInputStream)->GetStartTime(); - m_State.time_total = ((CDVDInputStreamTV*)m_pInputStream)->GetTotalTime(); + m_State.time = input->GetStartTime(); + m_State.time_total = input->GetTotalTime(); } } } @@ -3503,7 +3612,8 @@ bool CDVDPlayer::IsRecording() bool CDVDPlayer::Record(bool bOnOff) { - if (m_pInputStream && m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV)) + if (m_pInputStream && (m_pInputStream->IsStreamType(DVDSTREAM_TYPE_TV) || + m_pInputStream->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER)) ) { m_messenger.Put(new CDVDMsgBool(CDVDMsg::PLAYER_SET_RECORD, bOnOff)); return true; diff --git a/xbmc/cores/dvdplayer/DVDPlayer.h b/xbmc/cores/dvdplayer/DVDPlayer.h index b2d6196ce2..8579b94837 100644 --- a/xbmc/cores/dvdplayer/DVDPlayer.h +++ b/xbmc/cores/dvdplayer/DVDPlayer.h @@ -292,6 +292,7 @@ protected: std::string m_mimetype; // hold a hint to what content file contains (mime type) ECacheState m_caching; CFileItem m_item; + long m_ChannelEntryTimeOut; CCurrentStream m_CurrentAudio; CCurrentStream m_CurrentVideo; diff --git a/xbmc/cores/paplayer/CodecFactory.cpp b/xbmc/cores/paplayer/CodecFactory.cpp index 70d1c4107d..b2342db015 100644 --- a/xbmc/cores/paplayer/CodecFactory.cpp +++ b/xbmc/cores/paplayer/CodecFactory.cpp @@ -78,6 +78,8 @@ ICodec* CodecFactory::CreateCodec(const CStdString& strFileType) #else return new DVDPlayerCodec(); #endif + else if (strFileType.Equals("pvr")) + return new DVDPlayerCodec(); else if (strFileType.Equals("m4a") || strFileType.Equals("aac")) return new DVDPlayerCodec(); else if (strFileType.Equals("wv")) diff --git a/xbmc/lib/libhts/Win32/include/stdint.h b/xbmc/lib/libhts/Win32/include/stdint.h index 81ecedc191..95dd2e6b74 100644 --- a/xbmc/lib/libhts/Win32/include/stdint.h +++ b/xbmc/lib/libhts/Win32/include/stdint.h @@ -210,7 +210,7 @@ typedef uint64_t uintmax_t; #define UINT8_C(val) val##ui8 #define UINT16_C(val) val##ui16 #define UINT32_C(val) val##ui32 -//#define UINT64_C(val) val##ui64 +#define UINT64_C(val) val##ui64 // 7.18.4.2 Macros for greatest-width integer constants #define INTMAX_C INT64_C diff --git a/xbmc/pvrclients/MediaPortal/Makefile b/xbmc/pvrclients/MediaPortal/Makefile new file mode 100644 index 0000000000..090108473c --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/Makefile @@ -0,0 +1,67 @@ +# +# Makefile for the XBMC MediaPortal PVR AddOn +# +# See the README for copyright information and +# how to reach the author. +# + +.DELETE_ON_ERROR: + +DESTDIR ?= +PREFIX ?= /usr/local +ADDONDIR = $(PREFIX)/share/xbmc/addons +LIBS = -ldl +INCLUDES = -I. -I../../linux -I../../ -I../../../xbmc/addons/include -I../../../addons/library.xbmc.pvr -I../../../addons/library.xbmc.addon +DEFINES += -D_LINUX -fPIC +LIBDIR = ../../../addons/pvr.team-mediaportal.tvserver +LIB = ../../../addons/pvr.team-mediaportal.tvserver/XBMC_MPTV.pvr + +CC ?= gcc +CFLAGS ?= -g -O2 -Wall + +CXX ?= g++ +ifeq ($(findstring Darwin,$(shell uname -a)), Darwin) +CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses -Wl,-no_compact_linkedit -dynamiclib -single_module -undefined dynamic_lookup -no_compact_linkedit +DEFINES += -isysroot /Developer/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4 -fno-common +else +CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses +endif + +-include Make.config + +OBJS = channels.o client.o epg.o pvrclient-mediaportal.o recordings.o timers.o utils.o Socket.o + +all: $(LIB) + +# Implicit rules: + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< + +# Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.cpp) > $@ + +-include $(DEPFILE) + +# The main library: + +$(LIB): $(OBJS) $(SILIB) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -g $(OBJS) $(LIBS) $(LIBDIRS) $(SILIB) -o $(LIB) + +# Install the files: + +install: install-lib + +# PVR library: + +install-lib: $(LIB) + @mkdir -p $(DESTDIR)$(ADDONDIR) + @cp --remove-destination -r $(LIBDIR) $(DESTDIR)$(ADDONDIR) + +clean: + -rm -f $(OBJS) $(DEPFILE) $(LIB) *~ +CLEAN: clean diff --git a/xbmc/pvrclients/MediaPortal/README b/xbmc/pvrclients/MediaPortal/README new file mode 100644 index 0000000000..a4f321b4ba --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/README @@ -0,0 +1,150 @@ +XBMC MediaPortal TV-client ('MPTV') PVR Add-on +---------------------------------------------- +Supported platforms (pvrclient): Windows +(Linux, OSX: work in progress) +Dependencies: +- MediaPortal TVServer 1.1.0 beta1 or higher +- TVServerXBMC v1.0.7.0 or higher + +THIS IS A PRELIMINARY README AND IS SUBJECT TO CHANGE!!! + +Written by: Marcel Groothuis + +Project's homepage: http://www.scintilla.utwente.nl/~marcelg/xbmc/ + +Latest version available at: XBMC's pvr-testing2 branch + +The MediaPortal TV-Server plugin "TVServerXMBC", status updates, screenshots, +last-minute patches can be found at: + +http://www.scintilla.utwente.nl/~marcelg/xbmc/ + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +any later version. +See the file LICENSE.GPL for more information. + +---------------------------------------------- +General description: + +This is a PVR Add-on for XBMC to access/control the MediaPortal TV-Server +backend from XBMC. It consists of two plugins: one for XBMC-PVR (1) and one for +the MediaPortal TV Server (2). + +1. PVR client "XBMC_MPTV_win32.pvr" (for Windows): + This is the addon in this directory. After building the XBMC solution (and + the "pvrclient_mptv") this plugin file should be in addons\pvr\MediaPortal\ + +The MediaPortal TV Server is written in C#, making it difficult for XBMC to +control it directly. The TV Server connection of this AddOn depends on a +special plugin at the TV Server side, called TVServerXBMC. + + +2. TV Server plugin TVServerXBMC.dll/.exe (Windows only): + The TVServerXBMC plugin for the TV Server provides a socket interface to + XBMC to control the TV Server. + You can download it separately from: + http://www.scintilla.utwente.nl/~marcelg/xbmc + (It is not included by default in the XBMC pvr-testing2 branch, because it + will introduce a dependency on Visual C#.) + Please read the "readme.txt" file included in the TVServerXBMC zip files + for more information. + +---------------------------------------------- +Detailed instructions: + +(Preliminary...) + +1. Install MediaPortal & MediaPortal TV Server (1.1.0 beta1 or daily build 01-08-2010) + (for older versions, you will need to recompile the TVServerXBMC plugin from source) +2. Use MediaPortal to make sure that the TV Server is working fine +3. Download the TVServerXBMC plugin for the TV Server (see my website) +4. Run the TVServerXBMC.exe (standalone version of the TV Server plugin) and + test it (see the readme.txt file included in the TVServerXBMC zip file). + Test for example the commands "ListTVChannels", "TimeshiftChannel" + (id is the first field returned by "ListTVChannels"). The "TimeshiftChannel" + command should return a URL like "rtsp://xxx.xxx.xxx.xx/stream2.0" on a + successful timeshift start. You can test this URL in VLC player. This should + work before proceeding to the next steps. +5. Test the rtsp stream in XBMC (you can just use the standard 9.11) + Create a playlist file "rtsp-stream.m3u" with the rtsp:// URL as content and + start it from inside XBMC. + Example contents rtsp-stream.m3u: +----- +rtsp://192.168.2.5/steam2.0 +----- +When this is all working fine, you can finally build XBMC-pvr-testing2: + +6. Build the XBMC solution in VC Express (pvr-testing2) + (check if XBMC_MPTV_win32.pvr was created succesfully in addons\pvr\MediaPortal) + +---------------------------------------------- +MediaPortal PVR-addon settings: + +Names are taken from addons/pvr/MediaPortal/settings.xml + +host: "Mediaportal Hostname" + IP-address of the machine that runs the TVServerXBMC tool + Default: 127.0.0.1 (localhost) +port: "Mediaportal XBMC plugin Port" + Port number for the TVServerXBMC. + Default: 9596 +ftaonly: "Free-to-air only" + Fetch/show only Free-to-air channels from MediaPortal TV + Default: false +useradio: "Include Radio" + Fetch also radio channels + Default: true +convertchar: "Character Set Conversion" + Enable character conversion to UTF-8. + Does nothing. Not yet implemented. + Default: false +timeout: "Connect timeout (s)" + Timeout on XBMC<->TVServerXBMC communication. After the selected timeout, + XBMC won't wait any longer for an answer from TVServerXBMC and abort the + selected action. Bottleneck is the timeshift start for TV channels. This + can take a long time, so don't make this + value too small. + Default: 6 +tvgroup: "Import only TV Channels from group" + Allows you to fetch only the TV channels in a specific MediaPortal TVServer + group. E.g. you can create a "XBMC" group at the TVServer side that contains + only the TV channels that you want to appear at the XBMC side. + Default: +radiogroup: "Import only Radio Channels from group" + Allows you to fetch only the radio channels in a specific MediaPortal TVServer + group. E.g. you can create a "XBMC" group at the TVServer side that contains + only the radio channels that you want to appear at the XBMC side. + Default: +resolvertsphostname: "Convert hostname to IP-adress" + Resolve the TVServer hostname in the rtsp:// streaming URLs to an ip-address + at the TVServerXBMC side. May help you with connection problems. + Default: false +readgenre: "EPG: Read genre strings (slow)" + Try to translate the EPG genre strings from MediaPortal into XBMC compatible + genre id's. However, depending on your EPG source, MediaPortal may return + strings in your local language. In this case, you can skip the genre + translation via readgenre=false. + The current implementation translates only English strings as workaround for + the mismatch between XBMC's genre ids and MediaPortals genre strings. + Default: false (= don't read and translate the genre strings) +sleeponurl: "Wait after tuning a channel (ms)" + Adds an additional waiting time between the request to start a timeshift for + the selected channel and opening the rtsp:// stream in XBMC. You may need this + in case XBMC tries to open the returned rtsp:// stream before it is really + available. Typical symptom: the channel doesn't play the first time, but it + does play the second time. + Default: 0 (milliseconds) + +---------------------------------------------- +Troubleshooting: +TODO... + + +---------------------------------------------- +Links: + +MediaPortal: http://www.team-mediaportal.com +TVServer plugin: http://www.scintilla.utwente.nl/~marcelg/xbmc + diff --git a/xbmc/pvrclients/MediaPortal/Socket.cpp b/xbmc/pvrclients/MediaPortal/Socket.cpp new file mode 100644 index 0000000000..e95b69b638 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/Socket.cpp @@ -0,0 +1,638 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include "xbmc_pvr_types.h" +#include "libXBMC_addon.h" +#include "pvrclient-mediaportal_os.h" +#include "utils.h" +#include "client.h" +#include "Socket.h" + +using namespace std; + +/* Master defines for client control */ +#define RECEIVE_TIMEOUT 6 //sec + +Socket::Socket(const enum SocketFamily family, const enum SocketDomain domain, const enum SocketType type, const enum SocketProtocol protocol) +{ + _sd = INVALID_SOCKET; + _family = family; + _domain = domain; + _type = type; + _protocol = protocol; + memset (&_sockaddr, 0, sizeof( _sockaddr ) ); +} + + +Socket::Socket() +{ + // Default constructor, default settings + _sd = INVALID_SOCKET; + _family = af_inet; + _domain = pf_inet; + _type = sock_stream; + _protocol = tcp; + memset (&_sockaddr, 0, sizeof( _sockaddr ) ); +} + + +Socket::~Socket() +{ + close(); +} + +bool Socket::setHostname ( const std::string host ) +{ + if (isalpha(host.c_str()[0])) + { + // host address is a name + struct hostent *he = NULL; + if ((he = gethostbyname( host.c_str() )) == 0) + { + //errormessage( WSAGetLastError(), "Socket::setHostname"); + + return false; + } + + _sockaddr.sin_addr = *((in_addr *) he->h_addr); + } + else + { + _sockaddr.sin_addr.s_addr = inet_addr(host.c_str()); + } + return true; +} + + +bool Socket::close() +{ + if (is_valid()) + { + closesocket(_sd); + _sd = INVALID_SOCKET; +#if defined(_WIN32) || defined(_WIN64) + WSACleanup(); +#endif //WINDOWS + return true; + } + return false; +} + +bool Socket::create() +{ + if( is_valid() ) + { + close(); + } + +#if defined(_WIN32) || defined(_WIN64) + // initialize winsock: + if (WSAStartup(MAKEWORD(2,2),&_wsaData) != 0) + { + return false; + } + + WORD wVersionRequested = MAKEWORD(2,2); + + // check version + if (_wsaData.wVersion != wVersionRequested) + { + return false; + } +#endif //WINDOWS + + _sd = socket(_family, _type, _protocol ); + //0 indicates that the default protocol for the type selected is to be used. + //For example, IPPROTO_TCP is chosen for the protocol if the type was set to + //SOCK_STREAM and the address family is AF_INET. + + if (_sd == INVALID_SOCKET) + { + //errormessage( WSAGetLastError(), "Socket::create" ); + return false; + } + + return true; +} + + +bool Socket::bind ( const unsigned short port ) +{ + + if (!is_valid()) + { + return false; + } + + _sockaddr.sin_family = _family; + _sockaddr.sin_addr.s_addr = INADDR_ANY; //listen to all + _sockaddr.sin_port = htons( port ); + + int bind_return = ::bind(_sd, (sockaddr*)(&_sockaddr), sizeof(_sockaddr)); + + if ( bind_return == -1 ) + { + //errormessage( WSAGetLastError(), "Socket::bind" ); + return false; + } + + return true; +} + + +bool Socket::listen() const +{ + + if (!is_valid()) + { + return false; + } + + int listen_return = ::listen (_sd, SOMAXCONN); + //This is defined as 5 in winsock.h, and 0x7FFFFFFF in winsock2.h. + //linux 128//MAXCONNECTIONS =1 + + if (listen_return == -1) + { + //errormessage( WSAGetLastError(), "Socket::listen" ); + return false; + } + + return true; +} + + +bool Socket::accept ( Socket& new_socket ) const +{ + if (!is_valid()) + { + return false; + } + + socklen_t addr_length = sizeof( _sockaddr ); + new_socket._sd = ::accept(_sd, const_cast( (const sockaddr*) &_sockaddr), &addr_length ); + + if (new_socket._sd <= 0) + { + //errormessage( WSAGetLastError(), "Socket::accept" ); + return false; + } + + return true; +} + + +int Socket::send ( const std::string data ) +{ + if (!is_valid()) + { + return 0; + } + + int status = Socket::send( (const char*) data.c_str(), (const unsigned int) data.size()); + + return status; +} + + +int Socket::send ( const char* data, const unsigned int len ) +{ + fd_set set_w, set_e; + struct timeval tv; + int result; + + if (!is_valid()) + { + return 0; + } + + // fill with new data + tv.tv_sec = 0; + tv.tv_usec = 0; + + FD_ZERO(&set_w); + FD_ZERO(&set_e); + FD_SET(_sd, &set_w); + FD_SET(_sd, &set_e); + + result = select(FD_SETSIZE, &set_w, NULL, &set_e, &tv); + + if (result < 0) + { + XBMC->Log(LOG_ERROR, "Socket::send - select failed"); + _sd = INVALID_SOCKET; + return 0; + } + if (FD_ISSET(_sd, &set_w)) + { + XBMC->Log(LOG_ERROR, "Socket::send - failed to send data"); + _sd = INVALID_SOCKET; + return 0; + } + + int status = ::send(_sd, data, len, 0 ); + + if (status == -1) + { + //errormessage( WSAGetLastError(), "Socket::send"); + XBMC->Log(LOG_ERROR, "Socket::send - failed to send data"); + _sd = INVALID_SOCKET; + } + return status; +} + + +int Socket::sendto ( const char* data, unsigned int size, bool sendcompletebuffer) const +{ + int sentbytes = 0; + int i; + + do + { + i = ::sendto(_sd, data, size, 0, (const struct sockaddr*) &_sockaddr, sizeof( _sockaddr ) ); + + if (i <= 0) + { +#if defined(_WIN32) || defined(_WIN64) + errormessage( WSAGetLastError(), "Socket::sendto"); + WSACleanup(); +#endif //WINDOWS + return i; + } + sentbytes += i; + } while ( (sentbytes < (int) size) && (sendcompletebuffer == true)); + + return i; +} + + +int Socket::receive ( std::string& data, unsigned int minpacketsize ) const +{ + char * buf = NULL; + int status = 0; + + if (!is_valid()) + { + return 0; + } + + buf = new char [ minpacketsize + 1 ]; + memset ( buf, 0, minpacketsize + 1 ); + + status = receive( buf, minpacketsize, minpacketsize ); + + data = buf; + + delete[] buf; + return status; +} + +//Receive until error or \n +bool Socket::ReadResponse (int &code, vector &lines) +{ + fd_set set_r, set_e; + timeval timeout; + int result; + int retries = 6; + char buffer[2048]; + char cont = 0; + string line; + size_t pos1 = 0, pos2 = 0, pos3 = 0; + + code = 0; + + while (true) + { + while ((pos1 = line.find("\r\n", pos3)) != std::string::npos) + { + pos2 = line.find(cont); + + lines.push_back(line.substr(pos2+1, pos1-pos2-1)); + + line.erase(0, pos1 + 2); + pos3 = 0; + return true; + } + + // we only need to recheck 1 byte + if (line.size() > 0) + { + pos3 = line.size() - 1; + } + else + { + pos3 = 0; + } + + if (cont == ' ') + { + break; + } + + timeout.tv_sec = RECEIVE_TIMEOUT; + timeout.tv_usec = 0; + + // fill with new data + FD_ZERO(&set_r); + FD_ZERO(&set_e); + FD_SET(_sd, &set_r); + FD_SET(_sd, &set_e); + result = select(FD_SETSIZE, &set_r, NULL, &set_e, &timeout); + + if (result < 0) + { + XBMC->Log(LOG_DEBUG, "CVTPTransceiver::ReadResponse - select failed"); + lines.push_back("ERROR: select failed"); + code = 1; //error + _sd = INVALID_SOCKET; + return false; + } + + if (result == 0) + { + if (retries != 0) + { + XBMC->Log(LOG_DEBUG, "CVTPTransceiver::ReadResponse - timeout waiting for response, retrying... (%i)", retries); + retries--; + continue; + } else { + XBMC->Log(LOG_DEBUG, "CVTPTransceiver::ReadResponse - timeout waiting for response. Failed after 10 retries."); + lines.push_back("ERROR: failed after 10 retries"); + code = 1; //error + _sd = INVALID_SOCKET; + return false; + } + } + + result = recv(_sd, buffer, sizeof(buffer) - 1, 0); + if (result < 0) + { + XBMC->Log(LOG_DEBUG, "CVTPTransceiver::ReadResponse - recv failed"); + lines.push_back("ERROR: recv failed"); + code = 1; //error + _sd = INVALID_SOCKET; + return false; + } + buffer[result] = 0; + + line.append(buffer); + } + + return true; +} + +int Socket::receive ( std::string& data) const +{ + char buf[MAXRECV + 1]; + int status = 0; + + if ( !is_valid() ) + { + return 0; + } + + memset ( buf, 0, MAXRECV + 1 ); + status = receive( buf, MAXRECV, 0 ); + data = buf; + + return status; +} + +int Socket::receive ( char* data, const unsigned int buffersize, const unsigned int minpacketsize ) const +{ + + unsigned int receivedsize = 0; + int status = 0; + + if ( !is_valid() ) + { + return 0; + } + + while ( (receivedsize <= minpacketsize) && (receivedsize < buffersize) ) + { + status = ::recv(_sd, data+receivedsize, (buffersize - receivedsize), 0 ); + + if ( status == SOCKET_ERROR ) + { + //errormessage( WSAGetLastError(), "Socket::receive" ); + return status; + } + + receivedsize += status; + } + + return receivedsize; +} + + +int Socket::recvfrom ( char* data, const int buffersize, const int minpacketsize, struct sockaddr* from, socklen_t* fromlen) const +{ + int status = ::recvfrom(_sd, data, buffersize, 0, from, fromlen); + + return status; +} + + +bool Socket::connect ( const std::string host, const unsigned short port ) +{ + if ( !is_valid() ) + { + return false; + } + + _sockaddr.sin_family = _family; + _sockaddr.sin_port = htons ( port ); + + if ( !setHostname( host ) ) + { + return false; + } + + int status = ::connect ( _sd, reinterpret_cast(&_sockaddr), sizeof ( _sockaddr ) ); + + if ( status == SOCKET_ERROR ) + { + //errormessage( WSAGetLastError(), "Socket::connect" ); + return false; + } + + return true; +} + +bool Socket::reconnect() +{ + if ( _sd != INVALID_SOCKET ) + { + return true; + } + + if( !create() ) + return false; + + int status = ::connect ( _sd, reinterpret_cast(&_sockaddr), sizeof ( _sockaddr ) ); + + if ( status == SOCKET_ERROR ) + { + //errormessage( WSAGetLastError(), "Socket::connect" ); + return false; + } + + return true; +} + +#if defined(_WIN32) || defined(_WIN64) +bool Socket::set_non_blocking ( const bool b ) +{ + u_long iMode; + + if ( b ) + iMode = 1; // enable non_blocking + else + iMode = 0; // disable non_blocking + + if (ioctlsocket(_sd, FIONBIO, &iMode) == -1) + { + XBMC->Log(LOG_ERROR, "Socket::set_non_blocking - Can't set socket condition to: %i", iMode); + return false; + } + + return true; +} +#elif defined(_LINUX) +bool Socket::set_non_blocking ( const bool b ) +{ + int opts; + + opts = fcntl(_sd, F_GETFL); + + if ( opts < 0 ) + { + return false; + } + + if ( b ) + opts = ( opts | O_NONBLOCK ); + else + opts = ( opts & ~O_NONBLOCK ); + + if(fcntl (_sd , F_SETFL, opts) == -1) + { + XBMC->Log(LOG_ERROR, "Socket::set_non_blocking - Can't set socket flags to: %i", opts); + return false; + } + return true; +} +#endif + +bool Socket::is_valid() const +{ + return (_sd != INVALID_SOCKET); +} + +#if defined(_WIN32) || defined(_WIN64) +void Socket::errormessage( int errnum, const char* functionname) const +{ + const char* errmsg = NULL; + + switch (errnum) + { + case WSANOTINITIALISED: + errmsg = "A successful WSAStartup call must occur before using this function."; + break; + case WSAENETDOWN: + errmsg = "The network subsystem or the associated service provider has failed"; + break; + case WSA_NOT_ENOUGH_MEMORY: + errmsg = "Insufficient memory available"; + break; + case WSA_INVALID_PARAMETER: + errmsg = "One or more parameters are invalid"; + break; + case WSA_OPERATION_ABORTED: + errmsg = "Overlapped operation aborted"; + break; + case WSAEINTR: + errmsg = "Interrupted function call"; + break; + case WSAEBADF: + errmsg = "File handle is not valid"; + break; + case WSAEACCES: + errmsg = "Permission denied"; + break; + case WSAEFAULT: + errmsg = "Bad address"; + break; + case WSAEINVAL: + errmsg = "Invalid argument"; + break; + case WSAENOTSOCK: + errmsg = "Socket operation on nonsocket"; + break; + case WSAEDESTADDRREQ: + errmsg = "Destination address required"; + break; + case WSAEMSGSIZE: + errmsg = "Message too long"; + break; + case WSAEPROTOTYPE: + errmsg = "Protocol wrong type for socket"; + break; + case WSAENOPROTOOPT: + errmsg = "Bad protocol option"; + break; + case WSAEPFNOSUPPORT: + errmsg = "Protocol family not supported"; + break; + case WSAEAFNOSUPPORT: + errmsg = "Address family not supported by protocol family"; + break; + case WSAEADDRINUSE: + errmsg = "Address already in use"; + break; + case WSAECONNRESET: + errmsg = "Connection reset by peer"; + break; + case WSAHOST_NOT_FOUND: + errmsg = "Authoritative answer host not found"; + break; + case WSATRY_AGAIN: + errmsg = "Nonauthoritative host not found, or server failure"; + break; + case WSAEISCONN: + errmsg = "Socket is already connected"; + break; + case WSAETIMEDOUT: + errmsg = "Connection timed out"; + break; + case WSAECONNREFUSED: + errmsg = "Connection refused"; + break; + default: + errmsg = "WSA Error"; + } + XBMC->Log(LOG_ERROR, "%s: (errno=%i) %s\n", functionname, errnum, errmsg); +} +#elif defined _LINUX +//TODO +#endif //_WINDOWS || _LINUX diff --git a/xbmc/pvrclients/MediaPortal/Socket.h b/xbmc/pvrclients/MediaPortal/Socket.h new file mode 100644 index 0000000000..a4e93ed3ff --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/Socket.h @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ +#pragma once + +//Include platform specific datatypes, header files, defines and constants: +#if defined __WINDOWS__ || defined WIN32 || defined _WINDOWS + #ifdef _WINSOCKAPI_ + #undef _WINSOCKAPI_ + #endif + #include + #include + + #ifndef NI_MAXHOST + #define NI_MAXHOST 1025 + #endif + + #ifndef socklen_t + typedef int socklen_t; + #endif + #ifndef ipaddr_t + typedef unsigned long ipaddr_t; + #endif + #ifndef port_t + typedef unsigned short port_t; + #endif +#elif defined _LINUX + #include /* for socket,connect */ + #include /* for socket,connect */ + #include /* for Unix socket */ + #include /* for inet_pton */ + #include /* for gethostbyname */ + #include /* for htons */ + #include /* for read, write, close */ + #include + #include + + typedef int SOCKET; + typedef sockaddr SOCKADDR; + typedef sockaddr_in SOCKADDR_IN; + + #define INVALID_SOCKET -1 + #define SOCKET_ERROR -1 + + #define closesocket ::close +#else + #error Platform specific socket support is not yet available on this platform! +#endif + +using namespace std; + +#include + +#define MAXCONNECTIONS 1 ///< Maximum number of pending connections before "Connection refused" +#define MAXRECV 1500 ///< Maximum packet size + +enum SocketFamily +{ + #ifdef CONFIG_SOCKET_IPV6 + af_inet6 = AF_INET6, + af_unspec = AF_UNSPEC, ///< Either INET or INET6 + #endif + af_inet = AF_INET +}; + +enum SocketDomain +{ + #if defined _LINUX + pf_unix = PF_UNIX, + pf_local = PF_LOCAL, + #endif + #ifdef CONFIG_SOCKET_IPV6 + pf_inet6 = PF_INET6, + pf_unspec = PF_UNSPEC, //< Either INET or INET6 + #endif + pf_inet = PF_INET +}; + +enum SocketType +{ + sock_stream = SOCK_STREAM, + sock_dgram = SOCK_DGRAM +}; + +enum SocketProtocol +{ + tcp = IPPROTO_TCP, + udp = IPPROTO_UDP + #ifdef CONFIG_SOCKET_IPV6 + , ipv6 = IPPROTO_IPV6 + #endif +}; + +class Socket +{ + public: + + /*! + * An unconnected socket may be created directly on the local + * machine. The socket type (SOCK_STREAM, SOCK_DGRAM) and + * protocol may also be specified. + * If the socket cannot be created, an exception is thrown. + * + * \param family Socket family (IPv4 or IPv6) + * \param domain The domain parameter specifies a communications domain within which communication will take place; + * this selects the protocol family which should be used. + * \param type base type and protocol family of the socket. + * \param protocol specific protocol to apply. + */ + Socket(const enum SocketFamily family, const enum SocketDomain domain, const enum SocketType type, const enum SocketProtocol protocol = tcp); + Socket(void); + virtual ~Socket(); + + //Socket settings + + /*! + * Socket setFamily + * \param family Can be af_inet or af_inet6. Default: af_inet + */ + void setFamily(const enum SocketFamily family) + { + _family = family; + }; + + /*! + * Socket setDomain + * \param domain Can be pf_unix, pf_local, pf_inet or pf_inet6. Default: pf_inet + */ + void setDomain(const enum SocketDomain domain) + { + _domain = domain; + }; + + /*! + * Socket setType + * \param type Can be sock_stream or sock_dgram. Default: sock_stream. + */ + void setType(const enum SocketType type) + { + _type = type; + }; + + /*! + * Socket setProtocol + * \param protocol Can be tcp or udp. Default: tcp. + */ + void setProtocol(const enum SocketProtocol protocol) + { + _protocol = protocol; + }; + + /*! + * Socket setPort + * \param port port number for socket communication + */ + void setPort (const unsigned short port) + { + _sockaddr.sin_port = htons ( port ); + }; + + bool setHostname ( const std::string host ); + + // Server initialization + + /*! + * Socket create + * Create a new socket + * \return True if succesful + */ + bool create(); + + /*! + * Socket close + * Close the socket + * \return True if succesful + */ + bool close(); + + /*! + * Socket bind + */ + bool bind ( const unsigned short port ); + bool listen() const; + bool accept ( Socket& socket ) const; + + // Client initialization + bool connect ( const std::string host, const unsigned short port ); + + bool reconnect(); + + // Data Transmission + + /*! + * Socket send function + * + * \param data Reference to a std::string with the data to transmit + * \return Number of bytes send or -1 in case of an error + */ + int send ( const std::string data ); + + /*! + * Socket send function + * + * \param data Pointer to a character array of size 'size' with the data to transmit + * \param size Length of the data to transmit + * \return Number of bytes send or -1 in case of an error + */ + int send ( const char* data, const unsigned int size ); + + /*! + * Socket sendto function + * + * \param data Reference to a std::string with the data to transmit + * \param size Length of the data to transmit + * \param sendcompletebuffer If 'true': do not return until the complete buffer is transmitted + * \return Number of bytes send or -1 in case of an error + */ + int sendto ( const char* data, unsigned int size, bool sendcompletebuffer = false) const; + // Data Receive + + /*! + * Socket receive function + * + * \param data Reference to a std::string for storage of the received data. + * \param minpacketsize The minimum number of bytes that should be received before returning from this function + * \return Number of bytes received or SOCKET_ERROR + */ + int receive ( std::string& data, unsigned int minpacketsize ) const; + + /*! + * Socket receive function + * + * \param data Reference to a std::string for storage of the received data. + * \return Number of bytes received or SOCKET_ERROR + */ + int receive ( std::string& data ) const; + + /*! + * Socket receive function + * + * \param data Pointer to a character array of size buffersize. Used to store the received data. + * \param buffersize Size of the 'data' buffer + * \param minpacketsize Specifies the minimum number of bytes that need to be received before returning + * \return Number of bytes received or SOCKET_ERROR + */ + int receive ( char* data, const unsigned int buffersize, const unsigned int minpacketsize ) const; + + /*! + * Socket recvfrom function + * + * \param data Pointer to a character array of size buffersize. Used to store the received data. + * \param buffersize Size of the 'data' buffer + * \param minpacketsize Do not return before at least 'minpacketsize' bytes are in the buffer. + * \param from Optional: pointer to a sockaddr struct that will get the address from which the data is received + * \param fromlen Optional, only required if 'from' is given: length of from struct + * \return Number of bytes received or SOCKET_ERROR + */ + int recvfrom ( char* data, const int buffersize, const int minpacketsize, struct sockaddr* from = NULL, socklen_t* fromlen = NULL) const; + + bool set_non_blocking ( const bool ); + + bool ReadResponse (int &code, vector &lines); + + bool is_valid() const; + + private: + + SOCKET _sd; ///< Socket Descriptor + SOCKADDR_IN _sockaddr; ///< Socket Address + + enum SocketFamily _family; ///< Socket Address Family + enum SocketProtocol _protocol; ///< Socket Protocol + enum SocketType _type; ///< Socket Type + enum SocketDomain _domain; ///< Socket domain + + #ifdef _WINDOWS + WSADATA _wsaData; ///< Windows Socket data + #endif + + void errormessage( int errornum, const char* functionname = NULL) const; +}; + diff --git a/xbmc/pvrclients/MediaPortal/StdString.h b/xbmc/pvrclients/MediaPortal/StdString.h new file mode 100644 index 0000000000..6b3755fa0e --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/StdString.h @@ -0,0 +1,4329 @@ +#pragma once +#include +#include "pvrclient-mediaportal_os.h" + +// ============================================================================= +// FILE: StdString.h +// AUTHOR: Joe O'Leary (with outside help noted in comments) +// +// If you find any bugs in this code, please let me know: +// +// jmoleary@earthlink.net +// http://www.joeo.net/stdstring.htm (a bit outdated) +// +// The latest version of this code should always be available at the +// following link: +// +// http://www.joeo.net/code/StdString.zip (Dec 6, 2003) +// +// +// REMARKS: +// This header file declares the CStdStr template. This template derives +// the Standard C++ Library basic_string<> template and add to it the +// the following conveniences: +// - The full MFC CString set of functions (including implicit cast) +// - writing to/reading from COM IStream interfaces +// - Functional objects for use in STL algorithms +// +// From this template, we intstantiate two classes: CStdStringA and +// CStdStringW. The name "CStdString" is just a #define of one of these, +// based upone the UNICODE macro setting +// +// This header also declares our own version of the MFC/ATL UNICODE-MBCS +// conversion macros. Our version looks exactly like the Microsoft's to +// facilitate portability. +// +// NOTE: +// If you you use this in an MFC or ATL build, you should include either +// afx.h or atlbase.h first, as appropriate. +// +// PEOPLE WHO HAVE CONTRIBUTED TO THIS CLASS: +// +// Several people have helped me iron out problems and othewise improve +// this class. OK, this is a long list but in my own defense, this code +// has undergone two major rewrites. Many of the improvements became +// necessary after I rewrote the code as a template. Others helped me +// improve the CString facade. +// +// Anyway, these people are (in chronological order): +// +// - Pete the Plumber (???) +// - Julian Selman +// - Chris (of Melbsys) +// - Dave Plummer +// - John C Sipos +// - Chris Sells +// - Nigel Nunn +// - Fan Xia +// - Matthew Williams +// - Carl Engman +// - Mark Zeren +// - Craig Watson +// - Rich Zuris +// - Karim Ratib +// - Chris Conti +// - Baptiste Lepilleur +// - Greg Pickles +// - Jim Cline +// - Jeff Kohn +// - Todd Heckel +// - Ullrich Poll�hne +// - Joe Vitaterna +// - Joe Woodbury +// - Aaron (no last name) +// - Joldakowski (???) +// - Scott Hathaway +// - Eric Nitzche +// - Pablo Presedo +// - Farrokh Nejadlotfi +// - Jason Mills +// - Igor Kholodov +// - Mike Crusader +// - John James +// - Wang Haifeng +// - Tim Dowty +// - Arnt Witteveen +// - Glen Maynard +// - Paul DeMarco +// - Bagira (full name?) +// - Ronny Schulz +// - Jakko Van Hunen +// - Charles Godwin +// - Henk Demper +// - Greg Marr +// - Bill Carducci +// - Brian Groose +// - MKingman +// - Don Beusee +// +// REVISION HISTORY +// +// 2005-JAN-10 - Thanks to Don Beusee for pointing out the danger in mapping +// length-checked formatting functions to non-length-checked +// CRT equivalents. Also thanks to him for motivating me to +// optimize my implementation of Replace() +// +// 2004-APR-22 - A big, big thank you to "MKingman" (whoever you are) for +// finally spotting a silly little error in StdCodeCvt that +// has been causing me (and users of CStdString) problems for +// years in some relatively rare conversions. I had reversed +// two length arguments. +// +// 2003-NOV-24 - Thanks to a bunch of people for helping me clean up many +// compiler warnings (and yes, even a couple of actual compiler +// errors). These include Henk Demper for figuring out how +// to make the Intellisense work on with CStdString on VC6, +// something I was never able to do. Greg Marr pointed out +// a compiler warning about an unreferenced symbol and a +// problem with my version of Load in MFC builds. Bill +// Carducci took a lot of time with me to help me figure out +// why some implementations of the Standard C++ Library were +// returning error codes for apparently successful conversions +// between ASCII and UNICODE. Finally thanks to Brian Groose +// for helping me fix compiler signed unsigned warnings in +// several functions. +// +// 2003-JUL-10 - Thanks to Charles Godwin for making me realize my 'FmtArg' +// fixes had inadvertently broken the DLL-export code (which is +// normally commented out. I had to move it up higher. Also +// this helped me catch a bug in ssicoll that would prevent +// compilation, otherwise. +// +// 2003-MAR-14 - Thanks to Jakko Van Hunen for pointing out a copy-and-paste +// bug in one of the overloads of FmtArg. +// +// 2003-MAR-10 - Thanks to Ronny Schulz for (twice!) sending me some changes +// to help CStdString build on SGI and for pointing out an +// error in placement of my preprocessor macros for ssfmtmsg. +// +// 2002-NOV-26 - Thanks to Bagira for pointing out that my implementation of +// SpanExcluding was not properly handling the case in which +// the string did NOT contain any of the given characters +// +// 2002-OCT-21 - Many thanks to Paul DeMarco who was invaluable in helping me +// get this code working with Borland's free compiler as well +// as the Dev-C++ compiler (available free at SourceForge). +// +// 2002-SEP-13 - Thanks to Glen Maynard who helped me get rid of some loud +// but harmless warnings that were showing up on g++. Glen +// also pointed out that some pre-declarations of FmtArg<> +// specializations were unnecessary (and no good on G++) +// +// 2002-JUN-26 - Thanks to Arnt Witteveen for pointing out that I was using +// static_cast<> in a place in which I should have been using +// reinterpret_cast<> (the ctor for unsigned char strings). +// That's what happens when I don't unit-test properly! +// Arnt also noticed that CString was silently correcting the +// 'nCount' argument to Left() and Right() where CStdString was +// not (and crashing if it was bad). That is also now fixed! +// +// 2002-FEB-25 - Thanks to Tim Dowty for pointing out (and giving me the fix +// for) a conversion problem with non-ASCII MBCS characters. +// CStdString is now used in my favorite commercial MP3 player! +// +// 2001-DEC-06 - Thanks to Wang Haifeng for spotting a problem in one of the +// assignment operators (for _bstr_t) that would cause compiler +// errors when refcounting protection was turned off. +// +// 2001-NOV-27 - Remove calls to operator!= which involve reverse_iterators +// due to a conflict with the rel_ops operator!=. Thanks to +// John James for pointing this out. +// +// 2001-OCT-29 - Added a minor range checking fix for the Mid function to +// make it as forgiving as CString's version is. Thanks to +// Igor Kholodov for noticing this. +// - Added a specialization of std::swap for CStdString. Thanks +// to Mike Crusader for suggesting this! It's commented out +// because you're not supposed to inject your own code into the +// 'std' namespace. But if you don't care about that, it's +// there if you want it +// - Thanks to Jason Mills for catching a case where CString was +// more forgiving in the Delete() function than I was. +// +// 2001-JUN-06 - I was violating the Standard name lookup rules stated +// in [14.6.2(3)]. None of the compilers I've tried so +// far apparently caught this but HP-UX aCC 3.30 did. The +// fix was to add 'this->' prefixes in many places. +// Thanks to Farrokh Nejadlotfi for this! +// +// 2001-APR-27 - StreamLoad was calculating the number of BYTES in one +// case, not characters. Thanks to Pablo Presedo for this. +// +// 2001-FEB-23 - Replace() had a bug which caused infinite loops if the +// source string was empty. Fixed thanks to Eric Nitzsche. +// +// 2001-FEB-23 - Scott Hathaway was a huge help in providing me with the +// ability to build CStdString on Sun Unix systems. He +// sent me detailed build reports about what works and what +// does not. If CStdString compiles on your Unix box, you +// can thank Scott for it. +// +// 2000-DEC-29 - Joldakowski noticed one overload of Insert failed to do a +// range check as CString's does. Now fixed -- thanks! +// +// 2000-NOV-07 - Aaron pointed out that I was calling static member +// functions of char_traits via a temporary. This was not +// technically wrong, but it was unnecessary and caused +// problems for poor old buggy VC5. Thanks Aaron! +// +// 2000-JUL-11 - Joe Woodbury noted that the CString::Find docs don't match +// what the CString::Find code really ends up doing. I was +// trying to match the docs. Now I match the CString code +// - Joe also caught me truncating strings for GetBuffer() calls +// when the supplied length was less than the current length. +// +// 2000-MAY-25 - Better support for STLPORT's Standard library distribution +// - Got rid of the NSP macro - it interfered with Koenig lookup +// - Thanks to Joe Woodbury for catching a TrimLeft() bug that +// I introduced in January. Empty strings were not getting +// trimmed +// +// 2000-APR-17 - Thanks to Joe Vitaterna for pointing out that ReverseFind +// is supposed to be a const function. +// +// 2000-MAR-07 - Thanks to Ullrich Poll�hne for catching a range bug in one +// of the overloads of assign. +// +// 2000-FEB-01 - You can now use CStdString on the Mac with CodeWarrior! +// Thanks to Todd Heckel for helping out with this. +// +// 2000-JAN-23 - Thanks to Jim Cline for pointing out how I could make the +// Trim() function more efficient. +// - Thanks to Jeff Kohn for prompting me to find and fix a typo +// in one of the addition operators that takes _bstr_t. +// - Got rid of the .CPP file - you only need StdString.h now! +// +// 1999-DEC-22 - Thanks to Greg Pickles for helping me identify a problem +// with my implementation of CStdString::FormatV in which +// resulting string might not be properly NULL terminated. +// +// 1999-DEC-06 - Chris Conti pointed yet another basic_string<> assignment +// bug that MS has not fixed. CStdString did nothing to fix +// it either but it does now! The bug was: create a string +// longer than 31 characters, get a pointer to it (via c_str()) +// and then assign that pointer to the original string object. +// The resulting string would be empty. Not with CStdString! +// +// 1999-OCT-06 - BufferSet was erasing the string even when it was merely +// supposed to shrink it. Fixed. Thanks to Chris Conti. +// - Some of the Q172398 fixes were not checking for assignment- +// to-self. Fixed. Thanks to Baptiste Lepilleur. +// +// 1999-AUG-20 - Improved Load() function to be more efficient by using +// SizeOfResource(). Thanks to Rich Zuris for this. +// - Corrected resource ID constructor, again thanks to Rich. +// - Fixed a bug that occurred with UNICODE characters above +// the first 255 ANSI ones. Thanks to Craig Watson. +// - Added missing overloads of TrimLeft() and TrimRight(). +// Thanks to Karim Ratib for pointing them out +// +// 1999-JUL-21 - Made all calls to GetBuf() with no args check length first. +// +// 1999-JUL-10 - Improved MFC/ATL independence of conversion macros +// - Added SS_NO_REFCOUNT macro to allow you to disable any +// reference-counting your basic_string<> impl. may do. +// - Improved ReleaseBuffer() to be as forgiving as CString. +// Thanks for Fan Xia for helping me find this and to +// Matthew Williams for pointing it out directly. +// +// 1999-JUL-06 - Thanks to Nigel Nunn for catching a very sneaky bug in +// ToLower/ToUpper. They should call GetBuf() instead of +// data() in order to ensure the changed string buffer is not +// reference-counted (in those implementations that refcount). +// +// 1999-JUL-01 - Added a true CString facade. Now you can use CStdString as +// a drop-in replacement for CString. If you find this useful, +// you can thank Chris Sells for finally convincing me to give +// in and implement it. +// - Changed operators << and >> (for MFC CArchive) to serialize +// EXACTLY as CString's do. So now you can send a CString out +// to a CArchive and later read it in as a CStdString. I have +// no idea why you would want to do this but you can. +// +// 1999-JUN-21 - Changed the CStdString class into the CStdStr template. +// - Fixed FormatV() to correctly decrement the loop counter. +// This was harmless bug but a bug nevertheless. Thanks to +// Chris (of Melbsys) for pointing it out +// - Changed Format() to try a normal stack-based array before +// using to _alloca(). +// - Updated the text conversion macros to properly use code +// pages and to fit in better in MFC/ATL builds. In other +// words, I copied Microsoft's conversion stuff again. +// - Added equivalents of CString::GetBuffer, GetBufferSetLength +// - new sscpy() replacement of CStdString::CopyString() +// - a Trim() function that combines TrimRight() and TrimLeft(). +// +// 1999-MAR-13 - Corrected the "NotSpace" functional object to use _istpace() +// instead of _isspace() Thanks to Dave Plummer for this. +// +// 1999-FEB-26 - Removed errant line (left over from testing) that #defined +// _MFC_VER. Thanks to John C Sipos for noticing this. +// +// 1999-FEB-03 - Fixed a bug in a rarely-used overload of operator+() that +// caused infinite recursion and stack overflow +// - Added member functions to simplify the process of +// persisting CStdStrings to/from DCOM IStream interfaces +// - Added functional objects (e.g. StdStringLessNoCase) that +// allow CStdStrings to be used as keys STL map objects with +// case-insensitive comparison +// - Added array indexing operators (i.e. operator[]). I +// originally assumed that these were unnecessary and would be +// inherited from basic_string. However, without them, Visual +// C++ complains about ambiguous overloads when you try to use +// them. Thanks to Julian Selman to pointing this out. +// +// 1998-FEB-?? - Added overloads of assign() function to completely account +// for Q172398 bug. Thanks to "Pete the Plumber" for this +// +// 1998-FEB-?? - Initial submission +// +// COPYRIGHT: +// 2002 Joseph M. O'Leary. This code is 100% free. Use it anywhere you +// want. Rewrite it, restructure it, whatever. If you can write software +// that makes money off of it, good for you. I kinda like capitalism. +// Please don't blame me if it causes your $30 billion dollar satellite +// explode in orbit. If you redistribute it in any form, I'd appreciate it +// if you would leave this notice here. +// ============================================================================= + +// Avoid multiple inclusion + +#ifndef STDSTRING_H +#define STDSTRING_H + +// When using VC, turn off browser references +// Turn off unavoidable compiler warnings + +#if defined(_MSC_VER) && (_MSC_VER > 1100) + #pragma component(browser, off, references, "CStdString") + #pragma warning (disable : 4290) // C++ Exception Specification ignored + #pragma warning (disable : 4127) // Conditional expression is constant + #pragma warning (disable : 4097) // typedef name used as synonym for class name +#endif + +// Borland warnings to turn off + +#ifdef __BORLANDC__ + #pragma option push -w-inl +// #pragma warn -inl // Turn off inline function warnings +#endif + +// SS_IS_INTRESOURCE +// ----------------- +// A copy of IS_INTRESOURCE from VC7. Because old VC6 version of winuser.h +// doesn't have this. + +#define SS_IS_INTRESOURCE(_r) (false) + +#if !defined (SS_ANSI) && defined(_MSC_VER) + #undef SS_IS_INTRESOURCE + #if defined(_WIN64) + #define SS_IS_INTRESOURCE(_r) (((unsigned __int64)(_r) >> 16) == 0) + #else + #define SS_IS_INTRESOURCE(_r) (((unsigned long)(_r) >> 16) == 0) + #endif +#endif + + +// MACRO: SS_UNSIGNED +// ------------------ +// This macro causes the addition of a constructor and assignment operator +// which take unsigned characters. CString has such functions and in order +// to provide maximum CString-compatability, this code needs them as well. +// In practice you will likely never need these functions... + +//#define SS_UNSIGNED + +#ifdef SS_ALLOW_UNSIGNED_CHARS + #define SS_UNSIGNED +#endif + +// MACRO: SS_SAFE_FORMAT +// --------------------- +// This macro provides limited compatability with a questionable CString +// "feature". You can define it in order to avoid a common problem that +// people encounter when switching from CString to CStdString. +// +// To illustrate the problem -- With CString, you can do this: +// +// CString sName("Joe"); +// CString sTmp; +// sTmp.Format("My name is %s", sName); // WORKS! +// +// However if you were to try this with CStdString, your program would +// crash. +// +// CStdString sName("Joe"); +// CStdString sTmp; +// sTmp.Format("My name is %s", sName); // CRASHES! +// +// You must explicitly call c_str() or cast the object to the proper type +// +// sTmp.Format("My name is %s", sName.c_str()); // WORKS! +// sTmp.Format("My name is %s", static_cast(sName));// WORKS! +// sTmp.Format("My name is %s", (PCSTR)sName); // WORKS! +// +// This is because it is illegal to pass anything but a POD type as a +// variadic argument to a variadic function (i.e. as one of the "..." +// arguments). The type const char* is a POD type. The type CStdString +// is not. Of course, neither is the type CString, but CString lets you do +// it anyway due to the way they laid out the class in binary. I have no +// control over this in CStdString since I derive from whatever +// implementation of basic_string is available. +// +// However if you have legacy code (which does this) that you want to take +// out of the MFC world and you don't want to rewrite all your calls to +// Format(), then you can define this flag and it will no longer crash. +// +// Note however that this ONLY works for Format(), not sprintf, fprintf, +// etc. If you pass a CStdString object to one of those functions, your +// program will crash. Not much I can do to get around this, short of +// writing substitutes for those functions as well. + +#define SS_SAFE_FORMAT // use new template style Format() function + + +// MACRO: SS_NO_IMPLICIT_CAST +// -------------------------- +// Some people don't like the implicit cast to const char* (or rather to +// const CT*) that CStdString (and MFC's CString) provide. That was the +// whole reason I created this class in the first place, but hey, whatever +// bakes your cake. Just #define this macro to get rid of the the implicit +// cast. + +//#define SS_NO_IMPLICIT_CAST // gets rid of operator const CT*() + + +// MACRO: SS_NO_REFCOUNT +// --------------------- +// turns off reference counting at the assignment level. Only needed +// for the version of basic_string<> that comes with Visual C++ versions +// 6.0 or earlier, and only then in some heavily multithreaded scenarios. +// Uncomment it if you feel you need it. + +//#define SS_NO_REFCOUNT + +// MACRO: SS_WIN32 +// --------------- +// When this flag is set, we are building code for the Win32 platform and +// may use Win32 specific functions (such as LoadString). This gives us +// a couple of nice extras for the code. +// +// Obviously, Microsoft's is not the only compiler available for Win32 out +// there. So I can't just check to see if _MSC_VER is defined to detect +// if I'm building on Win32. So for now, if you use MS Visual C++ or +// Borland's compiler, I turn this on. Otherwise you may turn it on +// yourself, if you prefer + +#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(_WIN32) + #define SS_WIN32 +#endif + +// MACRO: SS_ANSI +// -------------- +// When this macro is defined, the code attempts only to use ANSI/ISO +// standard library functions to do it's work. It will NOT attempt to use +// any Win32 of Visual C++ specific functions -- even if they are +// available. You may define this flag yourself to prevent any Win32 +// of VC++ specific functions from being called. + +// If we're not on Win32, we MUST use an ANSI build + +#ifndef SS_WIN32 + #if !defined(SS_NO_ANSI) + #define SS_ANSI + #endif +#endif + +// MACRO: SS_ALLOCA +// ---------------- +// Some implementations of the Standard C Library have a non-standard +// function known as alloca(). This functions allows one to allocate a +// variable amount of memory on the stack. It is needed to implement +// the ASCII/MBCS conversion macros. +// +// I wanted to find some way to determine automatically if alloca() is +// available on this platform via compiler flags but that is asking for +// trouble. The crude test presented here will likely need fixing on +// other platforms. Therefore I'll leave it up to you to fiddle with +// this test to determine if it exists. Just make sure SS_ALLOCA is or +// is not defined as appropriate and you control this feature. + +#if defined(_MSC_VER) && !defined(SS_ANSI) + #define SS_ALLOCA +#endif + + +// MACRO: SS_MBCS +// -------------- +// Setting this macro means you are using MBCS characters. In MSVC builds, +// this macro gets set automatically by detection of the preprocessor flag +// _MBCS. For other platforms you may set it manually if you wish. The +// only effect it currently has is to cause the allocation of more space +// for wchar_t --> char conversions. +// Note that MBCS does not mean UNICODE. +// +// #define SS_MBCS +// + +#ifdef _MBCS + #define SS_MBCS +#endif + + +// MACRO SS_NO_LOCALE +// ------------------ +// If your implementation of the Standard C++ Library lacks the header, +// you can #define this macro to make your code build properly. Note that this +// is some of my newest code and frankly I'm not very sure of it, though it does +// pass my unit tests. + +// #define SS_NO_LOCALE + + +// Compiler Error regarding _UNICODE and UNICODE +// ----------------------------------------------- +// Microsoft header files are screwy. Sometimes they depend on a preprocessor +// flag named "_UNICODE". Other times they check "UNICODE" (note the lack of +// leading underscore in the second version". In several places, they silently +// "synchronize" these two flags this by defining one of the other was defined. +// In older version of this header, I used to try to do the same thing. +// +// However experience has taught me that this is a bad idea. You get weird +// compiler errors that seem to indicate things like LPWSTR and LPTSTR not being +// equivalent in UNICODE builds, stuff like that (when they MUST be in a proper +// UNICODE build). You end up scratching your head and saying, "But that HAS +// to compile!". +// +// So what should you do if you get this error? +// +// Make sure that both macros (_UNICODE and UNICODE) are defined before this +// file is included. You can do that by either +// +// a) defining both yourself before any files get included +// b) including the proper MS headers in the proper order +// c) including this file before any other file, uncommenting +// the #defines below, and commenting out the #errors +// +// Personally I recommend solution a) but it's your call. + +#ifdef _MSC_VER + #if defined (_UNICODE) && !defined (UNICODE) + #error UNICODE defined but not UNICODE + // #define UNICODE // no longer silently fix this + #endif + #if defined (UNICODE) && !defined (_UNICODE) + #error Warning, UNICODE defined but not _UNICODE + // #define _UNICODE // no longer silently fix this + #endif +#endif + + +// ----------------------------------------------------------------------------- +// MIN and MAX. The Standard C++ template versions go by so many names (at +// at least in the MS implementation) that you never know what's available +// ----------------------------------------------------------------------------- +template +inline const Type& SSMIN(const Type& arg1, const Type& arg2) +{ + return arg2 < arg1 ? arg2 : arg1; +} +template +inline const Type& SSMAX(const Type& arg1, const Type& arg2) +{ + return arg2 > arg1 ? arg2 : arg1; +} + +// If they have not #included W32Base.h (part of my W32 utility library) then +// we need to define some stuff. Otherwise, this is all defined there. + +#if !defined(W32BASE_H) + + // If they want us to use only standard C++ stuff (no Win32 stuff) + + #ifdef SS_ANSI + + // On Win32 we have TCHAR.H so just include it. This is NOT violating + // the spirit of SS_ANSI as we are not calling any Win32 functions here. + + #ifdef SS_WIN32 + + #include + #include + #ifndef STRICT + #define STRICT + #endif + + // ... but on non-Win32 platforms, we must #define the types we need. + + #else + + typedef const char* PCSTR; + typedef char* PSTR; + typedef const wchar_t* PCWSTR; + typedef wchar_t* PWSTR; + #ifdef UNICODE + typedef wchar_t TCHAR; + #else + typedef char TCHAR; + #endif + typedef wchar_t OLECHAR; + + #endif // #ifndef _WIN32 + + + // Make sure ASSERT and verify are defined using only ANSI stuff + + #ifndef ASSERT + #include + #define ASSERT(f) assert((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #else // ...else SS_ANSI is NOT defined + + #include + #include + #ifndef STRICT + #define STRICT + #endif + + // Make sure ASSERT and verify are defined + + #ifndef ASSERT + #include + #define ASSERT(f) _ASSERTE((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #endif // #ifdef SS_ANSI + + #ifndef UNUSED + #define UNUSED(x) x + #endif + +#endif // #ifndef W32BASE_H + +// Standard headers needed + +#include // basic_string +#include // for_each, etc. +#include // for StdStringLessNoCase, et al +#ifndef SS_NO_LOCALE + #include // for various facets +#endif + +// If this is a recent enough version of VC include comdef.h, so we can write +// member functions to deal with COM types & compiler support classes e.g. +// _bstr_t + +#if defined (_MSC_VER) && (_MSC_VER >= 1100) + #include + #define SS_INC_COMDEF // signal that we #included MS comdef.h file + #define STDSTRING_INC_COMDEF + #define SS_NOTHROW __declspec(nothrow) +#else + #define SS_NOTHROW +#endif + +#ifndef TRACE + #define TRACE_DEFINED_HERE + #define TRACE +#endif + +// Microsoft defines PCSTR, PCWSTR, etc, but no PCTSTR. I hate to use the +// versions with the "L" in front of them because that's a leftover from Win 16 +// days, even though it evaluates to the same thing. Therefore, Define a PCSTR +// as an LPCTSTR. + +#if !defined(PCTSTR) && !defined(PCTSTR_DEFINED) + typedef const TCHAR* PCTSTR; + #define PCTSTR_DEFINED +#endif + +#if !defined(PCOLESTR) && !defined(PCOLESTR_DEFINED) + typedef const OLECHAR* PCOLESTR; + #define PCOLESTR_DEFINED +#endif + +#if !defined(POLESTR) && !defined(POLESTR_DEFINED) + typedef OLECHAR* POLESTR; + #define POLESTR_DEFINED +#endif + +#if !defined(PCUSTR) && !defined(PCUSTR_DEFINED) + typedef const unsigned char* PCUSTR; + typedef unsigned char* PUSTR; + #define PCUSTR_DEFINED +#endif + + +// SGI compiler 7.3 doesnt know these types - oh and btw, remember to use +// -LANG:std in the CXX Flags +#if defined(__sgi) + typedef unsigned long DWORD; + typedef void * LPCVOID; +#endif + + +// SS_USE_FACET macro and why we need it: +// +// Since I'm a good little Standard C++ programmer, I use locales. Thus, I +// need to make use of the use_facet<> template function here. Unfortunately, +// this need is complicated by the fact the MS' implementation of the Standard +// C++ Library has a non-standard version of use_facet that takes more +// arguments than the standard dictates. Since I'm trying to write CStdString +// to work with any version of the Standard library, this presents a problem. +// +// The upshot of this is that I can't do 'use_facet' directly. The MS' docs +// tell me that I have to use a macro, _USE() instead. Since _USE obviously +// won't be available in other implementations, this means that I have to write +// my OWN macro -- SS_USE_FACET -- that evaluates either to _USE or to the +// standard, use_facet. +// +// If you are having trouble with the SS_USE_FACET macro, in your implementation +// of the Standard C++ Library, you can define your own version of SS_USE_FACET. + +#ifndef schMSG + #define schSTR(x) #x + #define schSTR2(x) schSTR(x) + #define schMSG(desc) message(__FILE__ "(" schSTR2(__LINE__) "):" #desc) +#endif + +#ifndef SS_USE_FACET + + // STLPort #defines a macro (__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) for + // all MSVC builds, erroneously in my opinion. It causes problems for + // my SS_ANSI builds. In my code, I always comment out that line. You'll + // find it in \stlport\config\stl_msvc.h + + #if defined(__SGI_STL_PORT) && (__SGI_STL_PORT >= 0x400 ) + + #if defined(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) && defined(_MSC_VER) + #ifdef SS_ANSI + #pragma schMSG(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS defined!!) + #endif + #endif + #define SS_USE_FACET(loc, fac) std::use_facet(loc) + + #elif defined(_MSC_VER ) + + #define SS_USE_FACET(loc, fac) std::_USE(loc, fac) + + // ...and + #elif defined(_RWSTD_NO_TEMPLATE_ON_RETURN_TYPE) + + #define SS_USE_FACET(loc, fac) std::use_facet(loc, (fac*)0) + + #else + + #define SS_USE_FACET(loc, fac) std::use_facet(loc) + + #endif + +#endif + +// ============================================================================= +// UNICODE/MBCS conversion macros. Made to work just like the MFC/ATL ones. +// ============================================================================= + +#include // Added to Std Library with Amendment #1. + +// First define the conversion helper functions. We define these regardless of +// any preprocessor macro settings since their names won't collide. + +// Not sure if we need all these headers. I believe ANSI says we do. + +#include +#include +#include +#include +#include +#ifndef va_start + #include +#endif + + +#ifdef SS_NO_LOCALE + + #if defined(_WIN32) || defined (_WIN32_WCE) + + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCSTR pSrcA, int nSrc, + UINT acp=CP_ACP) + { + ASSERT(0 != pSrcA); + ASSERT(0 != pDstW); + pDstW[0] = '\0'; + MultiByteToWideChar(acp, 0, pSrcA, nSrc, pDstW, nDst); + return pDstW; + } + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCUSTR pSrcA, int nSrc, + UINT acp=CP_ACP) + { + return StdCodeCvt(pDstW, nDst, (PCSTR)pSrcA, nSrc, acp); + } + + inline PSTR StdCodeCvt(PSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + UINT acp=CP_ACP) + { + ASSERT(0 != pDstA); + ASSERT(0 != pSrcW); + pDstA[0] = '\0'; + WideCharToMultiByte(acp, 0, pSrcW, nSrc, pDstA, nDst, 0, 0); + return pDstA; + } + inline PUSTR StdCodeCvt(PUSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + UINT acp=CP_ACP) + { + return (PUSTR)StdCodeCvt((PSTR)pDstA, nDst, pSrcW, nSrc, acp); + } + #else + #endif + +#else + + // StdCodeCvt - made to look like Win32 functions WideCharToMultiByte + // and MultiByteToWideChar but uses locales in SS_ANSI + // builds. There are a number of overloads. + // First argument is the destination buffer. + // Second argument is the source buffer + //#if defined (SS_ANSI) || !defined (SS_WIN32) + + // 'SSCodeCvt' - shorthand name for the codecvt facet we use + + typedef std::codecvt SSCodeCvt; + + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCSTR pSrcA, int nSrc, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pSrcA); + ASSERT(0 != pDstW); + + pDstW[0] = '\0'; + + if ( nSrc > 0 ) + { + PCSTR pNextSrcA = pSrcA; + PWSTR pNextDstW = pDstW; + SSCodeCvt::result res = SSCodeCvt::ok; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= { 0 }; + res = conv.in(st, + pSrcA, pSrcA + nSrc, pNextSrcA, + pDstW, pDstW + nDst, pNextDstW); +#ifdef _LINUX +#define ASSERT2(a) if (!(a)) {fprintf(stderr, "StdString: Assertion Failed on line %d\n", __LINE__);} +#else +#define ASSERT2 ASSERT +#endif + ASSERT2(SSCodeCvt::ok == res); + ASSERT2(SSCodeCvt::error != res); + ASSERT2(pNextDstW >= pDstW); + ASSERT2(pNextSrcA >= pSrcA); +#undef ASSERT2 + // Null terminate the converted string + + if ( pNextDstW - pDstW > nDst ) + *(pDstW + nDst) = '\0'; + else + *pNextDstW = '\0'; + } + return pDstW; + } + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCUSTR pSrcA, int nSrc, + const std::locale& loc=std::locale()) + { + return StdCodeCvt(pDstW, nDst, (PCSTR)pSrcA, nSrc, loc); + } + + inline PSTR StdCodeCvt(PSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pDstA); + ASSERT(0 != pSrcW); + + pDstA[0] = '\0'; + + if ( nSrc > 0 ) + { + PSTR pNextDstA = pDstA; + PCWSTR pNextSrcW = pSrcW; + SSCodeCvt::result res = SSCodeCvt::ok; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= { 0 }; + res = conv.out(st, + pSrcW, pSrcW + nSrc, pNextSrcW, + pDstA, pDstA + nDst, pNextDstA); +#ifdef _LINUX +#define ASSERT2(a) if (!(a)) {fprintf(stderr, "StdString: Assertion Failed on line %d\n", __LINE__);} +#else +#define ASSERT2 ASSERT +#endif + ASSERT2(SSCodeCvt::error != res); + ASSERT2(SSCodeCvt::ok == res); // strict, comment out for sanity + ASSERT2(pNextDstA >= pDstA); + ASSERT2(pNextSrcW >= pSrcW); +#undef ASSERT2 + + // Null terminate the converted string + + if ( pNextDstA - pDstA > nDst ) + *(pDstA + nDst) = '\0'; + else + *pNextDstA = '\0'; + } + return pDstA; + } + + inline PUSTR StdCodeCvt(PUSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + const std::locale& loc=std::locale()) + { + return (PUSTR)StdCodeCvt((PSTR)pDstA, nDst, pSrcW, nSrc, loc); + } + +#endif + + + +// Unicode/MBCS conversion macros are only available on implementations of +// the "C" library that have the non-standard _alloca function. As far as I +// know that's only Microsoft's though I've heard that the function exists +// elsewhere. + +#if defined(SS_ALLOCA) && !defined SS_NO_CONVERSION + + #include // needed for _alloca + + // Define our conversion macros to look exactly like Microsoft's to + // facilitate using this stuff both with and without MFC/ATL + + #ifdef _CONVERSION_USES_THREAD_LOCALE + + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=GetACP(); \ + _acp; PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=GetACP();\ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)),\ + StdCodeCvt((PWSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pa, _cvt, _acp))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = sslen(_pw), \ + StdCodeCvt((LPSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pw, _cvt, _acp))) + #else + + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=CP_ACP; _acp;\ + PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=CP_ACP; \ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)),\ + StdCodeCvt((PWSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pa, _cvt))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = (sslen(_pw)),\ + StdCodeCvt((LPSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pw, _cvt))) + #endif + + #define SSA2CW(pa) ((PCWSTR)SSA2W((pa))) + #define SSW2CA(pw) ((PCSTR)SSW2A((pw))) + + #ifdef UNICODE + #define SST2A SSW2A + #define SSA2T SSA2W + #define SST2CA SSW2CA + #define SSA2CT SSA2CW + // (Did you get a compiler error here about not being able to convert + // PTSTR into PWSTR? Then your _UNICODE and UNICODE flags are messed + // up. Best bet: #define BOTH macros before including any MS headers.) + inline PWSTR SST2W(PTSTR p) { return p; } + inline PTSTR SSW2T(PWSTR p) { return p; } + inline PCWSTR SST2CW(PCTSTR p) { return p; } + inline PCTSTR SSW2CT(PCWSTR p) { return p; } + #else + #define SST2W SSA2W + #define SSW2T SSW2A + #define SST2CW SSA2CW + #define SSW2CT SSW2CA + inline PSTR SST2A(PTSTR p) { return p; } + inline PTSTR SSA2T(PSTR p) { return p; } + inline PCSTR SST2CA(PCTSTR p) { return p; } + inline PCTSTR SSA2CT(PCSTR p) { return p; } + #endif // #ifdef UNICODE + + #if defined(UNICODE) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #elif defined(OLE2ANSI) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #else + //CharNextW doesn't work on Win95 so we use this + #define SST2COLE(pa) SSA2CW((pa)) + #define SST2OLE(pa) SSA2W((pa)) + #define SSOLE2CT(po) SSW2CA((po)) + #define SSOLE2T(po) SSW2A((po)) + #endif + + #ifdef OLE2ANSI + #define SSW2OLE SSW2A + #define SSOLE2W SSA2W + #define SSW2COLE SSW2CA + #define SSOLE2CW SSA2CW + inline POLESTR SSA2OLE(PSTR p) { return p; } + inline PSTR SSOLE2A(POLESTR p) { return p; } + inline PCOLESTR SSA2COLE(PCSTR p) { return p; } + inline PCSTR SSOLE2CA(PCOLESTR p){ return p; } + #else + #define SSA2OLE SSA2W + #define SSOLE2A SSW2A + #define SSA2COLE SSA2CW + #define SSOLE2CA SSW2CA + inline POLESTR SSW2OLE(PWSTR p) { return p; } + inline PWSTR SSOLE2W(POLESTR p) { return p; } + inline PCOLESTR SSW2COLE(PCWSTR p) { return p; } + inline PCWSTR SSOLE2CW(PCOLESTR p){ return p; } + #endif + + // Above we've defined macros that look like MS' but all have + // an 'SS' prefix. Now we need the real macros. We'll either + // get them from the macros above or from MFC/ATL. + + #if defined (USES_CONVERSION) + + #define _NO_STDCONVERSION // just to be consistent + + #else + + #ifdef _MFC_VER + + #include + #define _NO_STDCONVERSION // just to be consistent + + #else + + #define USES_CONVERSION SSCVT + #define A2CW SSA2CW + #define W2CA SSW2CA + #define T2A SST2A + #define A2T SSA2T + #define T2W SST2W + #define W2T SSW2T + #define T2CA SST2CA + #define A2CT SSA2CT + #define T2CW SST2CW + #define W2CT SSW2CT + #define ocslen sslen + #define ocscpy sscpy + #define T2COLE SST2COLE + #define OLE2CT SSOLE2CT + #define T2OLE SST2COLE + #define OLE2T SSOLE2CT + #define A2OLE SSA2OLE + #define OLE2A SSOLE2A + #define W2OLE SSW2OLE + #define OLE2W SSOLE2W + #define A2COLE SSA2COLE + #define OLE2CA SSOLE2CA + #define W2COLE SSW2COLE + #define OLE2CW SSOLE2CW + + #endif // #ifdef _MFC_VER + #endif // #ifndef USES_CONVERSION +#endif // #ifndef SS_NO_CONVERSION + +// Define ostring - generic name for std::basic_string + +#if !defined(ostring) && !defined(OSTRING_DEFINED) + typedef std::basic_string ostring; + #define OSTRING_DEFINED +#endif + +// StdCodeCvt when there's no conversion to be done +template +inline T* StdCodeCvt(T* pDst, int nDst, const T* pSrc, int nSrc) +{ + int nChars = SSMIN(nSrc, nDst); + + if ( nChars > 0 ) + { + pDst[0] = '\0'; + std::basic_string::traits_type::copy(pDst, pSrc, nChars); +// std::char_traits::copy(pDst, pSrc, nChars); + pDst[nChars] = '\0'; + } + + return pDst; +} +inline PSTR StdCodeCvt(PSTR pDst, int nDst, PCUSTR pSrc, int nSrc) +{ + return StdCodeCvt(pDst, nDst, (PCSTR)pSrc, nSrc); +} +inline PUSTR StdCodeCvt(PUSTR pDst, int nDst, PCSTR pSrc, int nSrc) +{ + return (PUSTR)StdCodeCvt((PSTR)pDst, nDst, pSrc, nSrc); +} + +// Define tstring -- generic name for std::basic_string + +#if !defined(tstring) && !defined(TSTRING_DEFINED) + typedef std::basic_string tstring; + #define TSTRING_DEFINED +#endif + +// a very shorthand way of applying the fix for KB problem Q172398 +// (basic_string assignment bug) + +#if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + #define Q172398(x) (x).erase() +#else + #define Q172398(x) +#endif + +// ============================================================================= +// INLINE FUNCTIONS ON WHICH CSTDSTRING RELIES +// +// Usually for generic text mapping, we rely on preprocessor macro definitions +// to map to string functions. However the CStdStr<> template cannot use +// macro-based generic text mappings because its character types do not get +// resolved until template processing which comes AFTER macro processing. In +// other words, the preprocessor macro UNICODE is of little help to us in the +// CStdStr template +// +// Therefore, to keep the CStdStr declaration simple, we have these inline +// functions. The template calls them often. Since they are inline (and NOT +// exported when this is built as a DLL), they will probably be resolved away +// to nothing. +// +// Without these functions, the CStdStr<> template would probably have to broken +// out into two, almost identical classes. Either that or it would be a huge, +// convoluted mess, with tons of "if" statements all over the place checking the +// size of template parameter CT. +// ============================================================================= + +#ifdef SS_NO_LOCALE + + // -------------------------------------------------------------------------- + // Win32 GetStringTypeEx wrappers + // -------------------------------------------------------------------------- + inline bool wsGetStringType(LCID lc, DWORD dwT, PCSTR pS, int nSize, + WORD* pWd) + { + return FALSE != GetStringTypeExA(lc, dwT, pS, nSize, pWd); + } + inline bool wsGetStringType(LCID lc, DWORD dwT, PCWSTR pS, int nSize, + WORD* pWd) + { + return FALSE != GetStringTypeExW(lc, dwT, pS, nSize, pWd); + } + + + template + inline bool ssisspace (CT t) + { + WORD toYourMother; + return wsGetStringType(GetThreadLocale(), CT_CTYPE1, &t, 1, &toYourMother) + && 0 != (C1_BLANK & toYourMother); + } + +#endif + +// If they defined SS_NO_REFCOUNT, then we must convert all assignments + +#if defined (_MSC_VER) && (_MSC_VER < 1300) + #ifdef SS_NO_REFCOUNT + #define SSREF(x) (x).c_str() + #else + #define SSREF(x) (x) + #endif +#else + #define SSREF(x) (x) +#endif + +// ----------------------------------------------------------------------------- +// sslen: strlen/wcslen wrappers +// ----------------------------------------------------------------------------- +template inline int sslen(const CT* pT) +{ + return 0 == pT ? 0 : (int)std::basic_string::traits_type::length(pT); +// return 0 == pT ? 0 : std::char_traits::length(pT); +} +inline SS_NOTHROW int sslen(const std::string& s) +{ + return static_cast(s.length()); +} +inline SS_NOTHROW int sslen(const std::wstring& s) +{ + return static_cast(s.length()); +} + +// ----------------------------------------------------------------------------- +// sstolower/sstoupper -- convert characters to upper/lower case +// ----------------------------------------------------------------------------- + +#ifdef SS_NO_LOCALE + inline char sstoupper(char ch) { return (char)::toupper(ch); } + inline wchar_t sstoupper(wchar_t ch){ return (wchar_t)::towupper(ch); } + inline char sstolower(char ch) { return (char)::tolower(ch); } + inline wchar_t sstolower(wchar_t ch){ return (wchar_t)::tolower(ch); } +#else + template + inline CT sstolower(const CT& t, const std::locale& loc = std::locale()) + { + return std::tolower(t, loc); + } + template + inline CT sstoupper(const CT& t, const std::locale& loc = std::locale()) + { + return std::toupper(t, loc); + } +#endif + +// ----------------------------------------------------------------------------- +// ssasn: assignment functions -- assign "sSrc" to "sDst" +// ----------------------------------------------------------------------------- +typedef std::string::size_type SS_SIZETYPE; // just for shorthand, really +typedef std::string::pointer SS_PTRTYPE; +typedef std::wstring::size_type SW_SIZETYPE; +typedef std::wstring::pointer SW_PTRTYPE; + + +template +inline void ssasn(std::basic_string& sDst, const std::basic_string& sSrc) +{ + if ( sDst.c_str() != sSrc.c_str() ) + { + sDst.erase(); + sDst.assign(SSREF(sSrc)); + } +} +template +inline void ssasn(std::basic_string& sDst, const T *pA) +{ + // Watch out for NULLs, as always. + + if ( 0 == pA ) + { + sDst.erase(); + } + + // If pA actually points to part of sDst, we must NOT erase(), but + // rather take a substring + + else if ( pA >= sDst.c_str() && pA <= sDst.c_str() + sDst.size() ) + { + sDst =sDst.substr(static_cast::size_type>(pA-sDst.c_str())); + } + + // Otherwise (most cases) apply the assignment bug fix, if applicable + // and do the assignment + + else + { + Q172398(sDst); + sDst.assign(pA); + } +} +inline void ssasn(std::string& sDst, const std::wstring& sSrc) +{ + if ( sSrc.empty() ) + { + sDst.erase(); + } + else + { + int nDst = static_cast(sSrc.size()); + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + // In MBCS builds, we don't know how long the destination string will be. + nDst = static_cast(static_cast(nDst) * 1.3); + sDst.resize(nDst+1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), static_cast(sSrc.size())); + sDst.resize(sslen(szCvt)); +#else + sDst.resize(nDst+1); + StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), static_cast(sSrc.size())); + sDst.resize(sSrc.size()); +#endif + } +} +inline void ssasn(std::string& sDst, PCWSTR pW) +{ + int nSrc = sslen(pW); + if ( nSrc > 0 ) + { + int nSrc = sslen(pW); + int nDst = nSrc; + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + nDst = static_cast(static_cast(nDst) * 1.3); + // In MBCS builds, we don't know how long the destination string will be. + sDst.resize(nDst + 1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + pW, nSrc); + sDst.resize(sslen(szCvt)); +#else + sDst.resize(nDst + 1); + StdCodeCvt(const_cast(sDst.data()), nDst, pW, nSrc); + sDst.resize(nDst); +#endif + } + else + { + sDst.erase(); + } +} +inline void ssasn(std::string& sDst, const int nNull) +{ + //UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(""); +} +#undef StrSizeType +inline void ssasn(std::wstring& sDst, const std::string& sSrc) +{ + if ( sSrc.empty() ) + { + sDst.erase(); + } + else + { + int nSrc = static_cast(sSrc.size()); + int nDst = nSrc; + + sDst.resize(nSrc+1); + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), nSrc); + + sDst.resize(sslen(szCvt)); + } +} +inline void ssasn(std::wstring& sDst, PCSTR pA) +{ + int nSrc = sslen(pA); + + if ( 0 == nSrc ) + { + sDst.erase(); + } + else + { + int nDst = nSrc; + sDst.resize(nDst+1); + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, pA, + nSrc); + + sDst.resize(sslen(szCvt)); + } +} +inline void ssasn(std::wstring& sDst, const int nNull) +{ + //UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(L""); +} + +// ----------------------------------------------------------------------------- +// ssadd: string object concatenation -- add second argument to first +// ----------------------------------------------------------------------------- +inline void ssadd(std::string& sDst, const std::wstring& sSrc) +{ + int nSrc = static_cast(sSrc.size()); + + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + int nAdd = nSrc; + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + nAdd = static_cast(static_cast(nAdd) * 1.3); + sDst.resize(nDst+nAdd+1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nAdd, sSrc.c_str(), nSrc); + sDst.resize(nDst + sslen(szCvt)); +#else + sDst.resize(nDst+nAdd+1); + StdCodeCvt(const_cast(sDst.data()+nDst), nAdd, sSrc.c_str(), nSrc); + sDst.resize(nDst + nAdd); +#endif + } +} +template +inline void ssadd(typename std::basic_string& sDst, const typename std::basic_string& sSrc) +{ + sDst += sSrc; +} +inline void ssadd(std::string& sDst, PCWSTR pW) +{ + int nSrc = sslen(pW); + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + int nAdd = nSrc; + +#ifdef SS_MBCS + nAdd = static_cast(static_cast(nAdd) * 1.3); + sDst.resize(nDst + nAdd + 1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nAdd, pW, nSrc); + sDst.resize(nDst + sslen(szCvt)); +#else + sDst.resize(nDst + nAdd + 1); + StdCodeCvt(const_cast(sDst.data()+nDst), nAdd, pW, nSrc); + sDst.resize(nDst + nSrc); +#endif + } +} +template +inline void ssadd(typename std::basic_string& sDst, const T *pA) +{ + if ( pA ) + { + // If the string being added is our internal string or a part of our + // internal string, then we must NOT do any reallocation without + // first copying that string to another object (since we're using a + // direct pointer) + + if ( pA >= sDst.c_str() && pA <= sDst.c_str()+sDst.length()) + { + if ( sDst.capacity() <= sDst.size()+sslen(pA) ) + sDst.append(std::basic_string(pA)); + else + sDst.append(pA); + } + else + { + sDst.append(pA); + } + } +} +inline void ssadd(std::wstring& sDst, const std::string& sSrc) +{ + if ( !sSrc.empty() ) + { + int nSrc = static_cast(sSrc.size()); + int nDst = static_cast(sDst.size()); + + sDst.resize(nDst + nSrc + 1); +#ifdef SS_MBCS + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nSrc, sSrc.c_str(), nSrc+1); + sDst.resize(nDst + sslen(szCvt)); +#else + StdCodeCvt(const_cast(sDst.data()+nDst), nSrc, sSrc.c_str(), nSrc+1); + sDst.resize(nDst + nSrc); +#endif + } +} +inline void ssadd(std::wstring& sDst, PCSTR pA) +{ + int nSrc = sslen(pA); + + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + + sDst.resize(nDst + nSrc + 1); +#ifdef SS_MBCS + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nSrc, pA, nSrc+1); + sDst.resize(nDst + sslen(szCvt)); +#else + StdCodeCvt(const_cast(sDst.data()+nDst), nSrc, pA, nSrc+1); + sDst.resize(nDst + nSrc); +#endif + } +} + +// ----------------------------------------------------------------------------- +// sscmp: comparison (case sensitive, not affected by locale) +// ----------------------------------------------------------------------------- +template +inline int sscmp(const CT* pA1, const CT* pA2) +{ + CT f; + CT l; + + do + { + f = *(pA1++); + l = *(pA2++); + } while ( (f) && (f == l) ); + + return (int)(f - l); +} + +// ----------------------------------------------------------------------------- +// ssicmp: comparison (case INsensitive, not affected by locale) +// ----------------------------------------------------------------------------- +template +inline int ssicmp(const CT* pA1, const CT* pA2) +{ + // Using the "C" locale = "not affected by locale" + + std::locale loc = std::locale::classic(); + const std::ctype& ct = SS_USE_FACET(loc, std::ctype); + CT f; + CT l; + + do + { + f = ct.tolower(*(pA1++)); + l = ct.tolower(*(pA2++)); + } while ( (f) && (f == l) ); + + return (int)(f - l); +} + +// ----------------------------------------------------------------------------- +// ssupr/sslwr: Uppercase/Lowercase conversion functions +// ----------------------------------------------------------------------------- + +template +inline void sslwr(CT* pT, size_t nLen, const std::locale& loc=std::locale()) +{ + SS_USE_FACET(loc, std::ctype).tolower(pT, pT+nLen); +} +template +inline void ssupr(CT* pT, size_t nLen, const std::locale& loc=std::locale()) +{ + SS_USE_FACET(loc, std::ctype).toupper(pT, pT+nLen); +} + +// ----------------------------------------------------------------------------- +// vsprintf/vswprintf or _vsnprintf/_vsnwprintf equivalents. In standard +// builds we can't use _vsnprintf/_vsnwsprintf because they're MS extensions. +// +// ----------------------------------------------------------------------------- +// Borland's headers put some ANSI "C" functions in the 'std' namespace. +// Promote them to the global namespace so we can use them here. + +#if defined(__BORLANDC__) + using std::vsprintf; + using std::vswprintf; +#endif + + // GNU is supposed to have vsnprintf and vsnwprintf. But only the newer + // distributions do. + +#if defined(__GNUC__) + + inline int ssvsprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + return vsnprintf(pA, nCount, pFmtA, vl); + } + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + return vswprintf(pW, nCount, pFmtW, vl); + } + + // Microsofties can use +#elif defined(_MSC_VER) && !defined(SS_ANSI) + + inline int ssvsprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + return _vsnprintf(pA, nCount, pFmtA, vl); + } + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + return _vsnwprintf(pW, nCount, pFmtW, vl); + } + +#elif defined (SS_DANGEROUS_FORMAT) // ignore buffer size parameter if needed? + + inline int ssvsprintf(PSTR pA, size_t /*nCount*/, PCSTR pFmtA, va_list vl) + { + return vsprintf(pA, pFmtA, vl); + } + + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + // JMO: Some distributions of the "C" have a version of vswprintf that + // takes 3 arguments (e.g. Microsoft, Borland, GNU). Others have a + // version which takes 4 arguments (an extra "count" argument in the + // second position. The best stab I can take at this so far is that if + // you are NOT running with MS, Borland, or GNU, then I'll assume you + // have the version that takes 4 arguments. + // + // I'm sure that these checks don't catch every platform correctly so if + // you get compiler errors on one of the lines immediately below, it's + // probably because your implemntation takes a different number of + // arguments. You can comment out the offending line (and use the + // alternate version) or you can figure out what compiler flag to check + // and add that preprocessor check in. Regardless, if you get an error + // on these lines, I'd sure like to hear from you about it. + // + // Thanks to Ronny Schulz for the SGI-specific checks here. + +// #if !defined(__MWERKS__) && !defined(__SUNPRO_CC_COMPAT) && !defined(__SUNPRO_CC) + #if !defined(_MSC_VER) \ + && !defined (__BORLANDC__) \ + && !defined(__GNUC__) \ + && !defined(__sgi) + + return vswprintf(pW, nCount, pFmtW, vl); + + // suddenly with the current SGI 7.3 compiler there is no such function as + // vswprintf and the substitute needs explicit casts to compile + + #elif defined(__sgi) + + nCount; + return vsprintf( (char *)pW, (char *)pFmtW, vl); + + #else + + nCount; + return vswprintf(pW, pFmtW, vl); + + #endif + + } + +#endif + + // GOT COMPILER PROBLEMS HERE? + // --------------------------- + // Does your compiler choke on one or more of the following 2 functions? It + // probably means that you don't have have either vsnprintf or vsnwprintf in + // your version of the CRT. This is understandable since neither is an ANSI + // "C" function. However it still leaves you in a dilemma. In order to make + // this code build, you're going to have to to use some non-length-checked + // formatting functions that every CRT has: vsprintf and vswprintf. + // + // This is very dangerous. With the proper erroneous (or malicious) code, it + // can lead to buffer overlows and crashing your PC. Use at your own risk + // In order to use them, just #define SS_DANGEROUS_FORMAT at the top of + // this file. + // + // Even THEN you might not be all the way home due to some non-conforming + // distributions. More on this in the comments below. + + inline int ssnprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + #ifdef _MSC_VER + return _vsnprintf(pA, nCount, pFmtA, vl); + #else + return vsnprintf(pA, nCount, pFmtA, vl); + #endif + } + inline int ssnprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + #ifdef _MSC_VER + return _vsnwprintf(pW, nCount, pFmtW, vl); + #else + return vswprintf(pW, nCount, pFmtW, vl); + #endif + } + + + + +// ----------------------------------------------------------------------------- +// ssload: Type safe, overloaded ::LoadString wrappers +// There is no equivalent of these in non-Win32-specific builds. However, I'm +// thinking that with the message facet, there might eventually be one +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline int ssload(HMODULE hInst, UINT uId, PSTR pBuf, int nMax) + { + return ::LoadStringA(hInst, uId, pBuf, nMax); + } + inline int ssload(HMODULE hInst, UINT uId, PWSTR pBuf, int nMax) + { + return ::LoadStringW(hInst, uId, pBuf, nMax); + } +#if defined ( _MSC_VER ) && ( _MSC_VER >= 1500 ) + inline int ssload(HMODULE hInst, UINT uId, uint16_t *pBuf, int nMax) + { + return 0; + } + inline int ssload(HMODULE hInst, UINT uId, uint32_t *pBuf, int nMax) + { + return 0; + } +#endif +#endif + + +// ----------------------------------------------------------------------------- +// sscoll/ssicoll: Collation wrappers +// Note -- with MSVC I have reversed the arguments order here because the +// functions appear to return the opposite of what they should +// ----------------------------------------------------------------------------- +#ifndef SS_NO_LOCALE +template +inline int sscoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::collate& coll = + SS_USE_FACET(std::locale(), std::collate); + + return coll.compare(sz2, sz2+nLen2, sz1, sz1+nLen1); +} +template +inline int ssicoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::locale loc; + const std::collate& coll = SS_USE_FACET(loc, std::collate); + + // Some implementations seem to have trouble using the collate<> + // facet typedefs so we'll just default to basic_string and hope + // that's what the collate facet uses (which it generally should) + +// std::collate::string_type s1(sz1); +// std::collate::string_type s2(sz2); + const std::basic_string sEmpty; + std::basic_string s1(sz1 ? sz1 : sEmpty.c_str()); + std::basic_string s2(sz2 ? sz2 : sEmpty.c_str()); + + sslwr(const_cast(s1.c_str()), nLen1, loc); + sslwr(const_cast(s2.c_str()), nLen2, loc); + return coll.compare(s2.c_str(), s2.c_str()+nLen2, + s1.c_str(), s1.c_str()+nLen1); +} +#endif + + +// ----------------------------------------------------------------------------- +// ssfmtmsg: FormatMessage equivalents. Needed because I added a CString facade +// Again -- no equivalent of these on non-Win32 builds but their might one day +// be one if the message facet gets implemented +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageA(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PWSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageW(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } +#else +#endif + + + +// FUNCTION: sscpy. Copies up to 'nMax' characters from pSrc to pDst. +// ----------------------------------------------------------------------------- +// FUNCTION: sscpy +// inline int sscpy(PSTR pDst, PCSTR pSrc, int nMax=-1); +// inline int sscpy(PUSTR pDst, PCSTR pSrc, int nMax=-1) +// inline int sscpy(PSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCSTR pSrc, int nMax=-1); +// +// DESCRIPTION: +// This function is very much (but not exactly) like strcpy. These +// overloads simplify copying one C-style string into another by allowing +// the caller to specify two different types of strings if necessary. +// +// The strings must NOT overlap +// +// "Character" is expressed in terms of the destination string, not +// the source. If no 'nMax' argument is supplied, then the number of +// characters copied will be sslen(pSrc). A NULL terminator will +// also be added so pDst must actually be big enough to hold nMax+1 +// characters. The return value is the number of characters copied, +// not including the NULL terminator. +// +// PARAMETERS: +// pSrc - the string to be copied FROM. May be a char based string, an +// MBCS string (in Win32 builds) or a wide string (wchar_t). +// pSrc - the string to be copied TO. Also may be either MBCS or wide +// nMax - the maximum number of characters to be copied into szDest. Note +// that this is expressed in whatever a "character" means to pDst. +// If pDst is a wchar_t type string than this will be the maximum +// number of wchar_ts that my be copied. The pDst string must be +// large enough to hold least nMaxChars+1 characters. +// If the caller supplies no argument for nMax this is a signal to +// the routine to copy all the characters in pSrc, regardless of +// how long it is. +// +// RETURN VALUE: none +// ----------------------------------------------------------------------------- + +template +inline int sscpycvt(CT1* pDst, const CT2* pSrc, int nMax) +{ + // Note -- we assume pDst is big enough to hold pSrc. If not, we're in + // big trouble. No bounds checking. Caveat emptor. + + int nSrc = sslen(pSrc); + + const CT1* szCvt = StdCodeCvt(pDst, nMax, pSrc, nSrc); + + // If we're copying the same size characters, then all the "code convert" + // just did was basically memcpy so the #of characters copied is the same + // as the number requested. I should probably specialize this function + // template to achieve this purpose as it is silly to do a runtime check + // of a fact known at compile time. I'll get around to it. + + return sslen(szCvt); +} + +template +inline int sscpycvt(T* pDst, const T* pSrc, int nMax) +{ + int nCount = nMax; + for (; nCount > 0 && *pSrc; ++pSrc, ++pDst, --nCount) + std::basic_string::traits_type::assign(*pDst, *pSrc); + + *pDst = 0; + return nMax - nCount; +} + +inline int sscpycvt(PWSTR pDst, PCSTR pSrc, int nMax) +{ + // Note -- we assume pDst is big enough to hold pSrc. If not, we're in + // big trouble. No bounds checking. Caveat emptor. + + const PWSTR szCvt = StdCodeCvt(pDst, nMax, pSrc, nMax); + return sslen(szCvt); +} + +template +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax, int nLen) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, nLen)); +} +template +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, sslen(pSrc))); +} +template +inline int sscpy(CT1* pDst, const CT2* pSrc) +{ + return sscpycvt(pDst, pSrc, sslen(pSrc)); +} +template +inline int sscpy(CT1* pDst, const std::basic_string& sSrc, int nMax) +{ + return sscpycvt(pDst, sSrc.c_str(), SSMIN(nMax, (int)sSrc.length())); +} +template +inline int sscpy(CT1* pDst, const std::basic_string& sSrc) +{ + return sscpycvt(pDst, sSrc.c_str(), (int)sSrc.length()); +} + +#ifdef SS_INC_COMDEF + template + inline int sscpy(CT1* pDst, const _bstr_t& bs, int nMax) + { + return sscpycvt(pDst, static_cast(bs), + SSMIN(nMax, static_cast(bs.length()))); + } + template + inline int sscpy(CT1* pDst, const _bstr_t& bs) + { + return sscpy(pDst, bs, static_cast(bs.length())); + } +#endif + + +// ----------------------------------------------------------------------------- +// Functional objects for changing case. They also let you pass locales +// ----------------------------------------------------------------------------- + +#ifdef SS_NO_LOCALE + template + struct SSToUpper : public std::unary_function + { + inline CT operator()(const CT& t) const + { + return sstoupper(t); + } + }; + template + struct SSToLower : public std::unary_function + { + inline CT operator()(const CT& t) const + { + return sstolower(t); + } + }; +#else + template + struct SSToUpper : public std::binary_function + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstoupper(t, loc); + } + }; + template + struct SSToLower : public std::binary_function + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstolower(t, loc); + } + }; +#endif + +// This struct is used for TrimRight() and TrimLeft() function implementations. +//template +//struct NotSpace : public std::unary_function +//{ +// const std::locale& loc; +// inline NotSpace(const std::locale& locArg) : loc(locArg) {} +// inline bool operator() (CT t) { return !std::isspace(t, loc); } +//}; +template +struct NotSpace : public std::unary_function +{ + // DINKUMWARE BUG: + // Note -- using std::isspace in a COM DLL gives us access violations + // because it causes the dynamic addition of a function to be called + // when the library shuts down. Unfortunately the list is maintained + // in DLL memory but the function is in static memory. So the COM DLL + // goes away along with the function that was supposed to be called, + // and then later when the DLL CRT shuts down it unloads the list and + // tries to call the long-gone function. + // This is DinkumWare's implementation problem. If you encounter this + // problem, you may replace the calls here with good old isspace() and + // iswspace() from the CRT unless they specify SS_ANSI + +#ifdef SS_NO_LOCALE + + bool operator() (CT t) const { return !ssisspace(t); } + +#else + const std::locale loc; + NotSpace(const std::locale& locArg=std::locale()) : loc(locArg) {} + bool operator() (CT t) const { return !std::isspace(t, loc); } +#endif +}; + + + + +// Now we can define the template (finally!) +// ============================================================================= +// TEMPLATE: CStdStr +// template class CStdStr : public std::basic_string +// +// REMARKS: +// This template derives from basic_string and adds some MFC CString- +// like functionality +// +// Basically, this is my attempt to make Standard C++ library strings as +// easy to use as the MFC CString class. +// +// Note that although this is a template, it makes the assumption that the +// template argument (CT, the character type) is either char or wchar_t. +// ============================================================================= + +//#define CStdStr _SS // avoid compiler warning 4786 + +// template ARG& FmtArg(ARG& arg) { return arg; } +// PCSTR FmtArg(const std::string& arg) { return arg.c_str(); } +// PCWSTR FmtArg(const std::wstring& arg) { return arg.c_str(); } + +template +struct FmtArg +{ + explicit FmtArg(const ARG& arg) : a_(arg) {} + const ARG& operator()() const { return a_; } + const ARG& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; + +template +class CStdStr : public std::basic_string +{ + // Typedefs for shorter names. Using these names also appears to help + // us avoid some ambiguities that otherwise arise on some platforms + + #define MYBASE std::basic_string // my base class + //typedef typename std::basic_string MYBASE; // my base class + typedef CStdStr MYTYPE; // myself + typedef typename MYBASE::const_pointer PCMYSTR; // PCSTR or PCWSTR + typedef typename MYBASE::pointer PMYSTR; // PSTR or PWSTR + typedef typename MYBASE::iterator MYITER; // my iterator type + typedef typename MYBASE::const_iterator MYCITER; // you get the idea... + typedef typename MYBASE::reverse_iterator MYRITER; + typedef typename MYBASE::size_type MYSIZE; + typedef typename MYBASE::value_type MYVAL; + typedef typename MYBASE::allocator_type MYALLOC; + +public: + // shorthand conversion from PCTSTR to string resource ID + #define SSRES(pctstr) LOWORD(reinterpret_cast(pctstr)) + + bool TryLoad(const void* pT) + { + bool bLoaded = false; + +#if defined(SS_WIN32) && !defined(SS_ANSI) + if ( ( pT != NULL ) && SS_IS_INTRESOURCE(pT) ) + { + UINT nId = LOWORD(reinterpret_cast(pT)); + if ( !LoadString(nId) ) + { + TRACE(_T("Can't load string %u\n"), SSRES(pT)); + } + bLoaded = true; + } +#endif + + return bLoaded; + } + + + // CStdStr inline constructors + CStdStr() + { + } + + CStdStr(const MYTYPE& str) : MYBASE(SSREF(str)) + { + } + + CStdStr(const std::string& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(const std::wstring& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(PCMYSTR pT, MYSIZE n) : MYBASE(pT, n) + { + } + +#ifdef SS_UNSIGNED + CStdStr(PCUSTR pU) + { + *this = reinterpret_cast(pU); + } +#endif + + CStdStr(PCSTR pA) + { + #ifdef SS_ANSI + *this = pA; + #else + if ( !TryLoad(pA) ) + *this = pA; + #endif + } + + CStdStr(PCWSTR pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(uint16_t* pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(uint32_t* pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(MYCITER first, MYCITER last) + : MYBASE(first, last) + { + } + + CStdStr(MYSIZE nSize, MYVAL ch, const MYALLOC& al=MYALLOC()) + : MYBASE(nSize, ch, al) + { + } + + #ifdef SS_INC_COMDEF + CStdStr(const _bstr_t& bstr) + { + if ( bstr.length() > 0 ) + this->append(static_cast(bstr), bstr.length()); + } + #endif + + // CStdStr inline assignment operators -- the ssasn function now takes care + // of fixing the MSVC assignment bug (see knowledge base article Q172398). + MYTYPE& operator=(const MYTYPE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::string& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::wstring& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(PCSTR pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(PCWSTR pW) + { + ssasn(*this, pW); + return *this; + } + +#ifdef SS_UNSIGNED + MYTYPE& operator=(PCUSTR pU) + { + ssasn(*this, reinterpret_cast(pU)); + return *this; + } +#endif + + MYTYPE& operator=(uint16_t* pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(uint32_t* pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(CT t) + { + Q172398(*this); + this->assign(1, t); + return *this; + } + + #ifdef SS_INC_COMDEF + MYTYPE& operator=(const _bstr_t& bstr) + { + if ( bstr.length() > 0 ) + { + this->assign(static_cast(bstr), bstr.length()); + return *this; + } + else + { + this->erase(); + return *this; + } + } + #endif + + + // Overloads also needed to fix the MSVC assignment bug (KB: Q172398) + // *** Thanks to Pete The Plumber for catching this one *** + // They also are compiled if you have explicitly turned off refcounting + #if ( defined(_MSC_VER) && ( _MSC_VER < 1200 ) ) || defined(SS_NO_REFCOUNT) + + MYTYPE& assign(const MYTYPE& str) + { + Q172398(*this); + sscpy(GetBuffer(str.size()+1), SSREF(str)); + this->ReleaseBuffer(str.size()); + return *this; + } + + MYTYPE& assign(const MYTYPE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Poll�hne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + MYTYPE strTemp(str.c_str()+nStart, nChars); + Q172398(*this); + this->assign(strTemp); + return *this; + } + + MYTYPE& assign(const MYBASE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& assign(const MYBASE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Poll�hne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + + // Watch out for assignment to self + + if ( this == &str ) + { + MYTYPE strTemp(str.c_str() + nStart, nChars); + static_cast(this)->assign(strTemp); + } + else + { + Q172398(*this); + static_cast(this)->assign(str.c_str()+nStart, nChars); + } + return *this; + } + + MYTYPE& assign(const CT* pC, MYSIZE nChars) + { + // Q172398 only fix -- erase before assigning, but not if we're + // assigning from our own buffer + + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + if ( !this->empty() && + ( pC < this->data() || pC > this->data() + this->capacity() ) ) + { + this->erase(); + } + #endif + Q172398(*this); + static_cast(this)->assign(pC, nChars); + return *this; + } + + MYTYPE& assign(MYSIZE nChars, MYVAL val) + { + Q172398(*this); + static_cast(this)->assign(nChars, val); + return *this; + } + + MYTYPE& assign(const CT* pT) + { + return this->assign(pT, MYBASE::traits_type::length(pT)); + } + + MYTYPE& assign(MYCITER iterFirst, MYCITER iterLast) + { + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + // Q172398 fix. don't call erase() if we're assigning from ourself + if ( iterFirst < this->begin() || + iterFirst > this->begin() + this->size() ) + { + this->erase() + } + #endif + this->replace(this->begin(), this->end(), iterFirst, iterLast); + return *this; + } + #endif + + + // ------------------------------------------------------------------------- + // CStdStr inline concatenation. + // ------------------------------------------------------------------------- + MYTYPE& operator+=(const MYTYPE& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::string& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::wstring& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(PCSTR pA) + { + ssadd(*this, pA); + return *this; + } + + MYTYPE& operator+=(PCWSTR pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(uint16_t* pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(uint32_t* pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(CT t) + { + this->append(1, t); + return *this; + } + #ifdef SS_INC_COMDEF // if we have _bstr_t, define a += for it too. + MYTYPE& operator+=(const _bstr_t& bstr) + { + return this->operator+=(static_cast(bstr)); + } + #endif + + + // ------------------------------------------------------------------------- + // Case changing functions + // ------------------------------------------------------------------------- + + MYTYPE& ToUpper(const std::locale& loc=std::locale()) + { + // Note -- if there are any MBCS character sets in which the lowercase + // form a character takes up a different number of bytes than the + // uppercase form, this would probably not work... + + std::transform(this->begin(), + this->end(), + this->begin(), +#ifdef SS_NO_LOCALE + SSToUpper()); +#else + std::bind2nd(SSToUpper(), loc)); +#endif + + // ...but if it were, this would probably work better. Also, this way + // seems to be a bit faster when anything other then the "C" locale is + // used... + +// if ( !empty() ) +// { +// ssupr(this->GetBuf(), this->size(), loc); +// this->RelBuf(); +// } + + return *this; + } + + MYTYPE& ToLower(const std::locale& loc=std::locale()) + { + // Note -- if there are any MBCS character sets in which the lowercase + // form a character takes up a different number of bytes than the + // uppercase form, this would probably not work... + + std::transform(this->begin(), + this->end(), + this->begin(), +#ifdef SS_NO_LOCALE + SSToLower()); +#else + std::bind2nd(SSToLower(), loc)); +#endif + + // ...but if it were, this would probably work better. Also, this way + // seems to be a bit faster when anything other then the "C" locale is + // used... + +// if ( !empty() ) +// { +// sslwr(this->GetBuf(), this->size(), loc); +// this->RelBuf(); +// } + return *this; + } + + + MYTYPE& Normalize() + { + return Trim().ToLower(); + } + + + // ------------------------------------------------------------------------- + // CStdStr -- Direct access to character buffer. In the MS' implementation, + // the at() function that we use here also calls _Freeze() providing us some + // protection from multithreading problems associated with ref-counting. + // In VC 7 and later, of course, the ref-counting stuff is gone. + // ------------------------------------------------------------------------- + + CT* GetBuf(int nMinLen=-1) + { + if ( static_cast(this->size()) < nMinLen ) + this->resize(static_cast(nMinLen)); + + return this->empty() ? const_cast(this->data()) : &(this->at(0)); + } + + CT* SetBuf(int nLen) + { + nLen = ( nLen > 0 ? nLen : 0 ); + if ( this->capacity() < 1 && nLen == 0 ) + this->resize(1); + + this->resize(static_cast(nLen)); + return const_cast(this->data()); + } + void RelBuf(int nNewLen=-1) + { + this->resize(static_cast(nNewLen > -1 ? nNewLen : + sslen(this->c_str()))); + } + + void BufferRel() { RelBuf(); } // backwards compatability + CT* Buffer() { return GetBuf(); } // backwards compatability + CT* BufferSet(int nLen) { return SetBuf(nLen);}// backwards compatability + + bool Equals(const CT* pT, bool bUseCase=false) const + { + return 0 == (bUseCase ? this->compare(pT) : ssicmp(this->c_str(), pT)); + } + + // ------------------------------------------------------------------------- + // FUNCTION: CStdStr::Load + // REMARKS: + // Loads string from resource specified by nID + // + // PARAMETERS: + // nID - resource Identifier. Purely a Win32 thing in this case + // + // RETURN VALUE: + // true if successful, false otherwise + // ------------------------------------------------------------------------- + +#ifndef SS_ANSI + + bool Load(UINT nId, HMODULE hModule=NULL) + { + bool bLoaded = false; // set to true of we succeed. + + #ifdef _MFC_VER // When in Rome (or MFC land)... + + // If they gave a resource handle, use it. Note - this is archaic + // and not really what I would recommend. But then again, in MFC + // land, you ought to be using CString for resources anyway since + // it walks the resource chain for you. + + HMODULE hModuleOld = NULL; + + if ( NULL != hModule ) + { + hModuleOld = AfxGetResourceHandle(); + AfxSetResourceHandle(hModule); + } + + // ...load the string + + CString strRes; + bLoaded = FALSE != strRes.LoadString(nId); + + // ...and if we set the resource handle, restore it. + + if ( NULL != hModuleOld ) + AfxSetResourceHandle(hModule); + + if ( bLoaded ) + *this = strRes; + + #else // otherwise make our own hackneyed version of CString's Load + + // Get the resource name and module handle + + if ( NULL == hModule ) + hModule = GetResourceHandle(); + + PCTSTR szName = MAKEINTRESOURCE((nId>>4)+1); // lifted + DWORD dwSize = 0; + + // No sense continuing if we can't find the resource + + HRSRC hrsrc = ::FindResource(hModule, szName, RT_STRING); + + if ( NULL == hrsrc ) + { + TRACE(_T("Cannot find resource %d: 0x%X"), nId, ::GetLastError()); + } + else if ( 0 == (dwSize = ::SizeofResource(hModule, hrsrc) / sizeof(CT))) + { + TRACE(_T("Cant get size of resource %d 0x%X\n"),nId,GetLastError()); + } + else + { + bLoaded = 0 != ssload(hModule, nId, GetBuf(dwSize), dwSize); + ReleaseBuffer(); + } + + #endif // #ifdef _MFC_VER + + if ( !bLoaded ) + TRACE(_T("String not loaded 0x%X\n"), ::GetLastError()); + + return bLoaded; + } + +#endif // #ifdef SS_ANSI + + // ------------------------------------------------------------------------- + // FUNCTION: CStdStr::Format + // void _cdecl Formst(CStdStringA& PCSTR szFormat, ...) + // void _cdecl Format(PCSTR szFormat); + // + // DESCRIPTION: + // This function does sprintf/wsprintf style formatting on CStdStringA + // objects. It looks a lot like MFC's CString::Format. Some people + // might even call this identical. Fortunately, these people are now + // dead... heh heh. + // + // PARAMETERS: + // nId - ID of string resource holding the format string + // szFormat - a PCSTR holding the format specifiers + // argList - a va_list holding the arguments for the format specifiers. + // + // RETURN VALUE: None. + // ------------------------------------------------------------------------- + // formatting (using wsprintf style formatting) + + // If they want a Format() function that safely handles string objects + // without casting + +#ifdef SS_SAFE_FORMAT + + // Question: Joe, you wacky coder you, why do you have so many overloads + // of the Format() function + // Answer: One reason only - CString compatability. In short, by making + // the Format() function a template this way, I can do strong typing + // and allow people to pass CStdString arguments as fillers for + // "%s" format specifiers without crashing their program! The downside + // is that I need to overload on the number of arguments. If you are + // passing more arguments than I have listed below in any of my + // overloads, just add another one. + // + // Yes, yes, this is really ugly. In essence what I am doing here is + // protecting people from a bad (and incorrect) programming practice + // that they should not be doing anyway. I am protecting them from + // themselves. Why am I doing this? Well, if you had any idea the + // number of times I've been emailed by people about this + // "incompatability" in my code, you wouldn't ask. + + void Fmt(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + FormatV(szFmt, argList); + va_end(argList); + } + +#ifndef SS_ANSI + + void Format(UINT nId) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + this->swap(strFmt); + } + template + void Format(UINT nId, const A1& v) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + Fmt(strFmt, FmtArg(v)()); + } + template + void Format(UINT nId, const A1& v1, const A2& v2) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)()); + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(),FmtArg(v5)(), + FmtArg(v6)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(),FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(),FmtArg(v10)(),FmtArg(v11)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)(),FmtArg(v14)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(), FmtArg(v16)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16, const A17& v17) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(),FmtArg(v16)(),FmtArg(v17)()); + } + } + +#endif // #ifndef SS_ANSI + + // ...now the other overload of Format: the one that takes a string literal + + void Format(const CT* szFmt) + { + *this = szFmt; + } + template + void Format(const CT* szFmt, const A1& v) + { + Fmt(szFmt, FmtArg(v)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(),FmtArg(v10)(),FmtArg(v11)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)(),FmtArg(v14)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(), FmtArg(v16)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16, const A17& v17) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(),FmtArg(v16)(),FmtArg(v17)()); + } + +#else // #ifdef SS_SAFE_FORMAT + + +#ifndef SS_ANSI + + void Format(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + FormatV(strFmt, argList); + + va_end(argList); + } + +#endif // #ifdef SS_ANSI + + void Format(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + FormatV(szFmt, argList); + va_end(argList); + } + +#endif // #ifdef SS_SAFE_FORMAT + + void AppendFormat(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + AppendFormatV(szFmt, argList); + va_end(argList); + } + + #define MAX_FMT_TRIES 5 // #of times we try + #define FMT_BLOCK_SIZE 2048 // # of bytes to increment per try + #define BUFSIZE_1ST 256 + #define BUFSIZE_2ND 512 + #define STD_BUF_SIZE 1024 + + // an efficient way to add formatted characters to the string. You may only + // add up to STD_BUF_SIZE characters at a time, though + void AppendFormatV(const CT* szFmt, va_list argList) + { + CT szBuf[STD_BUF_SIZE]; + int nLen = ssnprintf(szBuf, STD_BUF_SIZE-1, szFmt, argList); + + if ( 0 < nLen ) + this->append(szBuf, nLen); + } + + // ------------------------------------------------------------------------- + // FUNCTION: FormatV + // void FormatV(PCSTR szFormat, va_list, argList); + // + // DESCRIPTION: + // This function formats the string with sprintf style format-specs. + // It makes a general guess at required buffer size and then tries + // successively larger buffers until it finds one big enough or a + // threshold (MAX_FMT_TRIES) is exceeded. + // + // PARAMETERS: + // szFormat - a PCSTR holding the format of the output + // argList - a Microsoft specific va_list for variable argument lists + // + // RETURN VALUE: + // ------------------------------------------------------------------------- + + // NOTE: Changed by JM to actually function under non-win32, + // and to remove the upper limit on size. + void FormatV(const CT* szFormat, va_list argList) + { + // try and grab a sufficient buffersize + int nChars = FMT_BLOCK_SIZE; + va_list argCopy; + + CT *p = reinterpret_cast(malloc(sizeof(CT)*nChars)); + if (!p) return; + + while (1) + { + va_copy(argCopy, argList); + + int nActual = ssvsprintf(p, nChars, szFormat, argCopy); + /* If that worked, return the string. */ + if (nActual > -1 && nActual < nChars) + { /* make sure it's NULL terminated */ + p[nActual] = '\0'; + this->assign(p, nActual); + free(p); + va_end(argCopy); + return; + } + /* Else try again with more space. */ + if (nActual > -1) /* glibc 2.1 */ + nChars = nActual + 1; /* precisely what is needed */ + else /* glibc 2.0 */ + nChars *= 2; /* twice the old size */ + + CT *np = reinterpret_cast(realloc(p, sizeof(CT)*nChars)); + if (np == NULL) + { + free(p); + va_end(argCopy); + return; // failed :( + } + p = np; + va_end(argCopy); + } + } + + // ------------------------------------------------------------------------- + // CString Facade Functions: + // + // The following methods are intended to allow you to use this class as a + // near drop-in replacement for CString. + // ------------------------------------------------------------------------- + #ifdef SS_WIN32 + BSTR AllocSysString() const + { + ostring os; + ssasn(os, *this); + return ::SysAllocString(os.c_str()); + } + #endif + +#ifndef SS_NO_LOCALE + int Collate(PCMYSTR szThat) const + { + return sscoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } + + int CollateNoCase(PCMYSTR szThat) const + { + return ssicoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } +#endif + int Compare(PCMYSTR szThat) const + { + return this->compare(szThat); + } + + int CompareNoCase(PCMYSTR szThat) const + { + return ssicmp(this->c_str(), szThat); + } + + int Delete(int nIdx, int nCount=1) + { + if ( nIdx < 0 ) + nIdx = 0; + + if ( nIdx < this->GetLength() ) + this->erase(static_cast(nIdx), static_cast(nCount)); + + return GetLength(); + } + + void Empty() + { + this->erase(); + } + + int Find(CT ch) const + { + MYSIZE nIdx = this->find_first_of(ch); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(PCMYSTR szSub) const + { + MYSIZE nIdx = this->find(szSub); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(CT ch, int nStart) const + { + // CString::Find docs say add 1 to nStart when it's not zero + // CString::Find code doesn't do that however. We'll stick + // with what the code does + + MYSIZE nIdx = this->find_first_of(ch, static_cast(nStart)); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(PCMYSTR szSub, int nStart) const + { + // CString::Find docs say add 1 to nStart when it's not zero + // CString::Find code doesn't do that however. We'll stick + // with what the code does + + MYSIZE nIdx = this->find(szSub, static_cast(nStart)); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int FindOneOf(PCMYSTR szCharSet) const + { + MYSIZE nIdx = this->find_first_of(szCharSet); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + +#ifndef SS_ANSI + void FormatMessage(PCMYSTR szFormat, ...) throw(std::exception) + { + va_list argList; + va_start(argList, szFormat); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + szFormat, 0, 0, + reinterpret_cast(&szTemp), 0, &argList) == 0 || + szTemp == 0 ) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } + + void FormatMessage(UINT nFormatId, ...) throw(std::exception) + { + MYTYPE sFormat; + VERIFY(sFormat.LoadString(nFormatId)); + va_list argList; + va_start(argList, nFormatId); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + sFormat, 0, 0, + reinterpret_cast(&szTemp), 0, &argList) == 0 || + szTemp == 0) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } +#endif + + // GetAllocLength -- an MSVC7 function but it costs us nothing to add it. + + int GetAllocLength() + { + return static_cast(this->capacity()); + } + + // ------------------------------------------------------------------------- + // GetXXXX -- Direct access to character buffer + // ------------------------------------------------------------------------- + CT GetAt(int nIdx) const + { + return this->at(static_cast(nIdx)); + } + + CT* GetBuffer(int nMinLen=-1) + { + return GetBuf(nMinLen); + } + + CT* GetBufferSetLength(int nLen) + { + return BufferSet(nLen); + } + + // GetLength() -- MFC docs say this is the # of BYTES but + // in truth it is the number of CHARACTERs (chars or wchar_ts) + int GetLength() const + { + return static_cast(this->length()); + } + + int Insert(int nIdx, CT ch) + { + if ( static_cast(nIdx) > this->size()-1 ) + this->append(1, ch); + else + this->insert(static_cast(nIdx), 1, ch); + + return GetLength(); + } + int Insert(int nIdx, PCMYSTR sz) + { + if ( static_cast(nIdx) >= this->size() ) + this->append(sz, static_cast(sslen(sz))); + else + this->insert(static_cast(nIdx), sz); + + return GetLength(); + } + + bool IsEmpty() const + { + return this->empty(); + } + + MYTYPE Left(int nCount) const + { + // Range check the count. + + nCount = SSMAX(0, SSMIN(nCount, static_cast(this->size()))); + return this->substr(0, static_cast(nCount)); + } + +#ifndef SS_ANSI + bool LoadString(UINT nId) + { + return this->Load(nId); + } +#endif + + void MakeLower() + { + ToLower(); + } + + void MakeReverse() + { + std::reverse(this->begin(), this->end()); + } + + void MakeUpper() + { + ToUpper(); + } + + MYTYPE Mid(int nFirst) const + { + return Mid(nFirst, this->GetLength()-nFirst); + } + + MYTYPE Mid(int nFirst, int nCount) const + { + // CString does range checking here. Since we're trying to emulate it, + // we must check too. + + if ( nFirst < 0 ) + nFirst = 0; + if ( nCount < 0 ) + nCount = 0; + + int nSize = static_cast(this->size()); + + if ( nFirst + nCount > nSize ) + nCount = nSize - nFirst; + + if ( nFirst > nSize ) + return MYTYPE(); + + ASSERT(nFirst >= 0); + ASSERT(nFirst + nCount <= nSize); + + return this->substr(static_cast(nFirst), + static_cast(nCount)); + } + + void ReleaseBuffer(int nNewLen=-1) + { + RelBuf(nNewLen); + } + + int Remove(CT ch) + { + MYSIZE nIdx = 0; + int nRemoved = 0; + while ( (nIdx=this->find_first_of(ch)) != MYBASE::npos ) + { + this->erase(nIdx, 1); + nRemoved++; + } + return nRemoved; + } + + int Replace(CT chOld, CT chNew) + { + int nReplaced = 0; + + for ( MYITER iter=this->begin(); iter != this->end(); iter++ ) + { + if ( *iter == chOld ) + { + *iter = chNew; + nReplaced++; + } + } + + return nReplaced; + } + + int Replace(PCMYSTR szOld, PCMYSTR szNew) + { + int nReplaced = 0; + MYSIZE nIdx = 0; + MYSIZE nOldLen = sslen(szOld); + + if ( 0 != nOldLen ) + { + // If the replacement string is longer than the one it replaces, this + // string is going to have to grow in size, Figure out how much + // and grow it all the way now, rather than incrementally + + MYSIZE nNewLen = sslen(szNew); + if ( nNewLen > nOldLen ) + { + int nFound = 0; + while ( nIdx < this->length() && + (nIdx=this->find(szOld, nIdx)) != MYBASE::npos ) + { + nFound++; + nIdx += nOldLen; + } + this->reserve(this->size() + nFound * (nNewLen - nOldLen)); + } + + + static const CT ch = CT(0); + PCMYSTR szRealNew = szNew == 0 ? &ch : szNew; + nIdx = 0; + + while ( nIdx < this->length() && + (nIdx=this->find(szOld, nIdx)) != MYBASE::npos ) + { + this->replace(this->begin()+nIdx, this->begin()+nIdx+nOldLen, + szRealNew); + + nReplaced++; + nIdx += nNewLen; + } + } + + return nReplaced; + } + + int ReverseFind(CT ch) const + { + MYSIZE nIdx = this->find_last_of(ch); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + // ReverseFind overload that's not in CString but might be useful + int ReverseFind(PCMYSTR szFind, MYSIZE pos=MYBASE::npos) const + { + //yuvalt - this does not compile with g++ since MYTTYPE() is different type + //MYSIZE nIdx = this->rfind(0 == szFind ? MYTYPE() : szFind, pos); + MYSIZE nIdx = this->rfind(0 == szFind ? "" : szFind, pos); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + MYTYPE Right(int nCount) const + { + // Range check the count. + + nCount = SSMAX(0, SSMIN(nCount, static_cast(this->size()))); + return this->substr(this->size()-static_cast(nCount)); + } + + void SetAt(int nIndex, CT ch) + { + ASSERT(this->size() > static_cast(nIndex)); + this->at(static_cast(nIndex)) = ch; + } + +#ifndef SS_ANSI + BSTR SetSysString(BSTR* pbstr) const + { + ostring os; + ssasn(os, *this); + if ( !::SysReAllocStringLen(pbstr, os.c_str(), os.length()) ) + throw std::runtime_error("out of memory"); + + ASSERT(*pbstr != 0); + return *pbstr; + } +#endif + + MYTYPE SpanExcluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + + MYTYPE SpanIncluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_not_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + +#if defined SS_WIN32 && !defined(UNICODE) && !defined(SS_ANSI) + + // CString's OemToAnsi and AnsiToOem functions are available only in + // Unicode builds. However since we're a template we also need a + // runtime check of CT and a reinterpret_cast to account for the fact + // that CStdStringW gets instantiated even in non-Unicode builds. + + void AnsiToOem() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::CharToOem(reinterpret_cast(this->c_str()), + reinterpret_cast(GetBuf())); + } + else + { + ASSERT(false); + } + } + + void OemToAnsi() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::OemToChar(reinterpret_cast(this->c_str()), + reinterpret_cast(GetBuf())); + } + else + { + ASSERT(false); + } + } + +#endif + + + // ------------------------------------------------------------------------- + // Trim and its variants + // ------------------------------------------------------------------------- + MYTYPE& Trim() + { + return TrimLeft().TrimRight(); + } + + MYTYPE& TrimLeft() + { + this->erase(this->begin(), + std::find_if(this->begin(), this->end(), NotSpace())); + + return *this; + } + + MYTYPE& TrimLeft(CT tTrim) + { + this->erase(0, this->find_first_not_of(tTrim)); + return *this; + } + + MYTYPE& TrimLeft(PCMYSTR szTrimChars) + { + this->erase(0, this->find_first_not_of(szTrimChars)); + return *this; + } + + MYTYPE& TrimRight() + { + // NOTE: When comparing reverse_iterators here (MYRITER), I avoid using + // operator!=. This is because namespace rel_ops also has a template + // operator!= which conflicts with the global operator!= already defined + // for reverse_iterator in the header . + // Thanks to John James for alerting me to this. + + MYRITER it = std::find_if(this->rbegin(), this->rend(), NotSpace()); + if ( !(this->rend() == it) ) + this->erase(this->rend() - it); + + this->erase(!(it == this->rend()) ? this->find_last_of(*it) + 1 : 0); + return *this; + } + + MYTYPE& TrimRight(CT tTrim) + { + MYSIZE nIdx = this->find_last_not_of(tTrim); + this->erase(MYBASE::npos == nIdx ? 0 : ++nIdx); + return *this; + } + + MYTYPE& TrimRight(PCMYSTR szTrimChars) + { + MYSIZE nIdx = this->find_last_not_of(szTrimChars); + this->erase(MYBASE::npos == nIdx ? 0 : ++nIdx); + return *this; + } + + void FreeExtra() + { + MYTYPE mt; + this->swap(mt); + if ( !mt.empty() ) + this->assign(mt.c_str(), mt.size()); + } + + // I have intentionally not implemented the following CString + // functions. You cannot make them work without taking advantage + // of implementation specific behavior. However if you absolutely + // MUST have them, uncomment out these lines for "sort-of-like" + // their behavior. You're on your own. + +// CT* LockBuffer() { return GetBuf(); }// won't really lock +// void UnlockBuffer(); { } // why have UnlockBuffer w/o LockBuffer? + + // Array-indexing operators. Required because we defined an implicit cast + // to operator const CT* (Thanks to Julian Selman for pointing this out) + + CT& operator[](int nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](int nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + CT& operator[](unsigned int nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](unsigned int nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + CT& operator[](unsigned long nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](unsigned long nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + +#ifndef SS_NO_IMPLICIT_CAST + operator const CT*() const + { + return this->c_str(); + } +#endif + + // IStream related functions. Useful in IPersistStream implementations + +#ifdef SS_INC_COMDEF + + // struct SSSHDR - useful for non Std C++ persistence schemes. + typedef struct SSSHDR + { + BYTE byCtrl; + ULONG nChars; + } SSSHDR; // as in "Standard String Stream Header" + + #define SSSO_UNICODE 0x01 // the string is a wide string + #define SSSO_COMPRESS 0x02 // the string is compressed + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSize + // REMARKS: + // Returns how many bytes it will take to StreamSave() this CStdString + // object to an IStream. + // ------------------------------------------------------------------------- + ULONG StreamSize() const + { + // Control header plus string + ASSERT(this->size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + return (this->size() * sizeof(CT)) + sizeof(SSSHDR); + } + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSave + // REMARKS: + // Saves this CStdString object to a COM IStream. + // ------------------------------------------------------------------------- + HRESULT StreamSave(IStream* pStream) const + { + ASSERT(this->size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + HRESULT hr = E_FAIL; + ASSERT(pStream != 0); + SSSHDR hdr; + hdr.byCtrl = sizeof(CT) == 2 ? SSSO_UNICODE : 0; + hdr.nChars = this->size(); + + + if ( FAILED(hr=pStream->Write(&hdr, sizeof(SSSHDR), 0)) ) + { + TRACE(_T("StreamSave: Cannot write control header, ERR=0x%X\n"),hr); + } + else if ( empty() ) + { + ; // nothing to write + } + else if ( FAILED(hr=pStream->Write(this->c_str(), + this->size()*sizeof(CT), 0)) ) + { + TRACE(_T("StreamSave: Cannot write string to stream 0x%X\n"), hr); + } + + return hr; + } + + + // ------------------------------------------------------------------------- + // FUNCTION: StreamLoad + // REMARKS: + // This method loads the object from an IStream. + // ------------------------------------------------------------------------- + HRESULT StreamLoad(IStream* pStream) + { + ASSERT(pStream != 0); + SSSHDR hdr; + HRESULT hr = E_FAIL; + + if ( FAILED(hr=pStream->Read(&hdr, sizeof(SSSHDR), 0)) ) + { + TRACE(_T("StreamLoad: Cant read control header, ERR=0x%X\n"), hr); + } + else if ( hdr.nChars > 0 ) + { + ULONG nRead = 0; + PMYSTR pMyBuf = BufferSet(hdr.nChars); + + // If our character size matches the character size of the string + // we're trying to read, then we can read it directly into our + // buffer. Otherwise, we have to read into an intermediate buffer + // and convert. + + if ( (hdr.byCtrl & SSSO_UNICODE) != 0 ) + { + ULONG nBytes = hdr.nChars * sizeof(wchar_t); + if ( sizeof(CT) == sizeof(wchar_t) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PWSTR pBufW = reinterpret_cast(_alloca((nBytes)+1)); + if ( FAILED(hr=pStream->Read(pBufW, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufW, hdr.nChars); + } + } + else + { + ULONG nBytes = hdr.nChars * sizeof(char); + if ( sizeof(CT) == sizeof(char) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PSTR pBufA = reinterpret_cast(_alloca(nBytes)); + if ( FAILED(hr=pStream->Read(pBufA, hdr.nChars, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufA, hdr.nChars); + } + } + } + else + { + this->erase(); + } + return hr; + } +#endif // #ifdef SS_INC_COMDEF + +#ifndef SS_ANSI + + // SetResourceHandle/GetResourceHandle. In MFC builds, these map directly + // to AfxSetResourceHandle and AfxGetResourceHandle. In non-MFC builds they + // point to a single static HINST so that those who call the member + // functions that take resource IDs can provide an alternate HINST of a DLL + // to search. This is not exactly the list of HMODULES that MFC provides + // but it's better than nothing. + + #ifdef _MFC_VER + static void SetResourceHandle(HMODULE hNew) + { + AfxSetResourceHandle(hNew); + } + static HMODULE GetResourceHandle() + { + return AfxGetResourceHandle(); + } + #else + static void SetResourceHandle(HMODULE hNew) + { + SSResourceHandle() = hNew; + } + static HMODULE GetResourceHandle() + { + return SSResourceHandle(); + } + #endif + +#endif +}; + +// ----------------------------------------------------------------------------- +// MSVC USERS: HOW TO EXPORT CSTDSTRING FROM A DLL +// +// If you are using MS Visual C++ and you want to export CStdStringA and +// CStdStringW from a DLL, then all you need to +// +// 1. make sure that all components link to the same DLL version +// of the CRT (not the static one). +// 2. Uncomment the 3 lines of code below +// 3. #define 2 macros per the instructions in MS KnowledgeBase +// article Q168958. The macros are: +// +// MACRO DEFINTION WHEN EXPORTING DEFINITION WHEN IMPORTING +// ----- ------------------------ ------------------------- +// SSDLLEXP (nothing, just #define it) extern +// SSDLLSPEC __declspec(dllexport) __declspec(dllimport) +// +// Note that these macros must be available to ALL clients who want to +// link to the DLL and use the class. If they +// +// A word of advice: Don't bother. +// +// Really, it is not necessary to export CStdString functions from a DLL. I +// never do. In my projects, I do generally link to the DLL version of the +// Standard C++ Library, but I do NOT attempt to export CStdString functions. +// I simply include the header where it is needed and allow for the code +// redundancy. +// +// That redundancy is a lot less than you think. This class does most of its +// work via the Standard C++ Library, particularly the base_class basic_string<> +// member functions. Most of the functions here are small enough to be inlined +// anyway. Besides, you'll find that in actual practice you use less than 1/2 +// of the code here, even in big projects and different modules will use as +// little as 10% of it. That means a lot less functions actually get linked +// your binaries. If you export this code from a DLL, it ALL gets linked in. +// +// I've compared the size of the binaries from exporting vs NOT exporting. Take +// my word for it -- exporting this code is not worth the hassle. +// +// ----------------------------------------------------------------------------- +//#pragma warning(disable:4231) // non-standard extension ("extern template") +// SSDLLEXP template class SSDLLSPEC CStdStr; +// SSDLLEXP template class SSDLLSPEC CStdStr; + + +// ============================================================================= +// END OF CStdStr INLINE FUNCTION DEFINITIONS +// ============================================================================= + +// Now typedef our class names based upon this humongous template + +typedef CStdStr CStdStringA; // a better std::string +typedef CStdStr CStdStringW; // a better std::wstring +typedef CStdStr CStdString16; // a 16bit char string +typedef CStdStr CStdString32; // a 32bit char string +typedef CStdStr CStdStringO; // almost always CStdStringW + +// ----------------------------------------------------------------------------- +// CStdStr addition functions defined as inline +// ----------------------------------------------------------------------------- + + +inline CStdStringA operator+(const CStdStringA& s1, const CStdStringA& s2) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(s2); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, CStdStringA::value_type t) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(1, t); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, PCSTR pA) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(pA); + return sRet; +} +inline CStdStringA operator+(PCSTR pA, const CStdStringA& sA) +{ + CStdStringA sRet; + CStdStringA::size_type nObjSize = sA.size(); + CStdStringA::size_type nLitSize = + static_cast(sslen(pA)); + + sRet.reserve(nLitSize + nObjSize); + sRet.assign(pA); + sRet.append(sA); + return sRet; +} + + +inline CStdStringA operator+(const CStdStringA& s1, const CStdStringW& s2) +{ + return s1 + CStdStringA(s2); +} +inline CStdStringW operator+(const CStdStringW& s1, const CStdStringW& s2) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(s2); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, PCWSTR pW) +{ + return s1 + CStdStringA(pW); +} + +#ifdef UNICODE + inline CStdStringW operator+(PCWSTR pW, const CStdStringA& sA) + { + return CStdStringW(pW) + CStdStringW(SSREF(sA)); + } + inline CStdStringW operator+(PCSTR pA, const CStdStringW& sW) + { + return CStdStringW(pA) + sW; + } +#else + inline CStdStringA operator+(PCWSTR pW, const CStdStringA& sA) + { + return CStdStringA(pW) + sA; + } + inline CStdStringA operator+(PCSTR pA, const CStdStringW& sW) + { + return pA + CStdStringA(sW); + } +#endif + +// ...Now the wide string versions. +inline CStdStringW operator+(const CStdStringW& s1, CStdStringW::value_type t) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(1, t); + return sRet; +} +inline CStdStringW operator+(const CStdStringW& s1, PCWSTR pW) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(pW); + return sRet; +} +inline CStdStringW operator+(PCWSTR pW, const CStdStringW& sW) +{ + CStdStringW sRet; + CStdStringW::size_type nObjSize = sW.size(); + CStdStringA::size_type nLitSize = + static_cast(sslen(pW)); + + sRet.reserve(nLitSize + nObjSize); + sRet.assign(pW); + sRet.append(sW); + return sRet; +} + +inline CStdStringW operator+(const CStdStringW& s1, const CStdStringA& s2) +{ + return s1 + CStdStringW(s2); +} +inline CStdStringW operator+(const CStdStringW& s1, PCSTR pA) +{ + return s1 + CStdStringW(pA); +} + + +// New-style format function is a template + +#ifdef SS_SAFE_FORMAT + +template<> +struct FmtArg +{ + explicit FmtArg(const CStdStringA& arg) : a_(arg) {} + PCSTR operator()() const { return a_.c_str(); } + const CStdStringA& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; +template<> +struct FmtArg +{ + explicit FmtArg(const CStdStringW& arg) : a_(arg) {} + PCWSTR operator()() const { return a_.c_str(); } + const CStdStringW& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; + +template<> +struct FmtArg +{ + explicit FmtArg(const std::string& arg) : a_(arg) {} + PCSTR operator()() const { return a_.c_str(); } + const std::string& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; +template<> +struct FmtArg +{ + explicit FmtArg(const std::wstring& arg) : a_(arg) {} + PCWSTR operator()() const { return a_.c_str(); } + const std::wstring& a_; +private: + FmtArg& operator=(const FmtArg&) {return *this;} +}; +#endif // #ifdef SS_SAFEFORMAT + +#ifndef SS_ANSI + // SSResourceHandle: our MFC-like resource handle + inline HMODULE& SSResourceHandle() + { + static HMODULE hModuleSS = GetModuleHandle(0); + return hModuleSS; + } +#endif + + +// In MFC builds, define some global serialization operators +// Special operators that allow us to serialize CStdStrings to CArchives. +// Note that we use an intermediate CString object in order to ensure that +// we use the exact same format. + +#ifdef _MFC_VER + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringA& strA) + { + CString strTemp = strA; + return ar << strTemp; + } + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringW& strW) + { + CString strTemp = strW; + return ar << strTemp; + } + + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringA& strA) + { + CString strTemp; + ar >> strTemp; + strA = strTemp; + return ar; + } + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringW& strW) + { + CString strTemp; + ar >> strTemp; + strW = strTemp; + return ar; + } +#endif // #ifdef _MFC_VER -- (i.e. is this MFC?) + + + +// ----------------------------------------------------------------------------- +// GLOBAL FUNCTION: WUFormat +// CStdStringA WUFormat(UINT nId, ...); +// CStdStringA WUFormat(PCSTR szFormat, ...); +// +// REMARKS: +// This function allows the caller for format and return a CStdStringA +// object with a single line of code. +// ----------------------------------------------------------------------------- +#ifdef SS_ANSI +#else + inline CStdStringA WUFormatA(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + CStdStringA strFmt; + CStdStringA strOut; + if ( strFmt.Load(nId) ) + strOut.FormatV(strFmt, argList); + + va_end(argList); + return strOut; + } + inline CStdStringA WUFormatA(PCSTR szFormat, ...) + { + va_list argList; + va_start(argList, szFormat); + CStdStringA strOut; + strOut.FormatV(szFormat, argList); + va_end(argList); + return strOut; + } + inline CStdStringW WUFormatW(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + CStdStringW strFmt; + CStdStringW strOut; + if ( strFmt.Load(nId) ) + strOut.FormatV(strFmt, argList); + + va_end(argList); + return strOut; + } + inline CStdStringW WUFormatW(PCWSTR szwFormat, ...) + { + va_list argList; + va_start(argList, szwFormat); + CStdStringW strOut; + strOut.FormatV(szwFormat, argList); + va_end(argList); + return strOut; + } +#endif // #ifdef SS_ANSI + + + +#if defined(SS_WIN32) && !defined (SS_ANSI) + // ------------------------------------------------------------------------- + // FUNCTION: WUSysMessage + // CStdStringA WUSysMessageA(DWORD dwError, DWORD dwLangId=SS_DEFLANGID); + // CStdStringW WUSysMessageW(DWORD dwError, DWORD dwLangId=SS_DEFLANGID); + // + // DESCRIPTION: + // This function simplifies the process of obtaining a string equivalent + // of a system error code returned from GetLastError(). You simply + // supply the value returned by GetLastError() to this function and the + // corresponding system string is returned in the form of a CStdStringA. + // + // PARAMETERS: + // dwError - a DWORD value representing the error code to be translated + // dwLangId - the language id to use. defaults to english. + // + // RETURN VALUE: + // a CStdStringA equivalent of the error code. Currently, this function + // only returns either English of the system default language strings. + // ------------------------------------------------------------------------- + #define SS_DEFLANGID MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT) + inline CStdStringA WUSysMessageA(DWORD dwError, DWORD dwLangId=SS_DEFLANGID) + { + CHAR szBuf[512]; + + if ( 0 != ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, + dwLangId, szBuf, 511, NULL) ) + return WUFormatA("%s (0x%X)", szBuf, dwError); + else + return WUFormatA("Unknown error (0x%X)", dwError); + } + inline CStdStringW WUSysMessageW(DWORD dwError, DWORD dwLangId=SS_DEFLANGID) + { + WCHAR szBuf[512]; + + if ( 0 != ::FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, + dwLangId, szBuf, 511, NULL) ) + return WUFormatW(L"%s (0x%X)", szBuf, dwError); + else + return WUFormatW(L"Unknown error (0x%X)", dwError); + } +#endif + +// Define TCHAR based friendly names for some of these functions + +#ifdef UNICODE + //#define CStdString CStdStringW + typedef CStdStringW CStdString; + #define WUSysMessage WUSysMessageW + #define WUFormat WUFormatW +#else + //#define CStdString CStdStringA + typedef CStdStringA CStdString; + #define WUSysMessage WUSysMessageA + #define WUFormat WUFormatA +#endif + +// ...and some shorter names for the space-efficient + +#define WUSysMsg WUSysMessage +#define WUSysMsgA WUSysMessageA +#define WUSysMsgW WUSysMessageW +#define WUFmtA WUFormatA +#define WUFmtW WUFormatW +#define WUFmt WUFormat +#define WULastErrMsg() WUSysMessage(::GetLastError()) +#define WULastErrMsgA() WUSysMessageA(::GetLastError()) +#define WULastErrMsgW() WUSysMessageW(::GetLastError()) + + +// ----------------------------------------------------------------------------- +// FUNCTIONAL COMPARATORS: +// REMARKS: +// These structs are derived from the std::binary_function template. They +// give us functional classes (which may be used in Standard C++ Library +// collections and algorithms) that perform case-insensitive comparisons of +// CStdString objects. This is useful for maps in which the key may be the +// proper string but in the wrong case. +// ----------------------------------------------------------------------------- +#define StdStringLessNoCaseW SSLNCW // avoid VC compiler warning 4786 +#define StdStringEqualsNoCaseW SSENCW +#define StdStringLessNoCaseA SSLNCA +#define StdStringEqualsNoCaseA SSENCA + +#ifdef UNICODE + #define StdStringLessNoCase SSLNCW + #define StdStringEqualsNoCase SSENCW +#else + #define StdStringLessNoCase SSLNCA + #define StdStringEqualsNoCase SSENCA +#endif + +struct StdStringLessNoCaseW + : std::binary_function +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseW + : std::binary_function +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; +struct StdStringLessNoCaseA + : std::binary_function +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseA + : std::binary_function +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; + +// If we had to define our own version of TRACE above, get rid of it now + +#ifdef TRACE_DEFINED_HERE + #undef TRACE + #undef TRACE_DEFINED_HERE +#endif + + +// These std::swap specializations come courtesy of Mike Crusader. + +//namespace std +//{ +// inline void swap(CStdStringA& s1, CStdStringA& s2) throw() +// { +// s1.swap(s2); +// } +// template<> +// inline void swap(CStdStringW& s1, CStdStringW& s2) throw() +// { +// s1.swap(s2); +// } +//} + +// Turn back on any Borland warnings we turned off. + +#ifdef __BORLANDC__ + #pragma option pop // Turn back on inline function warnings +// #pragma warn +inl // Turn back on inline function warnings +#endif + +#endif // #ifndef STDSTRING_H diff --git a/xbmc/pvrclients/MediaPortal/channels.cpp b/xbmc/pvrclients/MediaPortal/channels.cpp new file mode 100644 index 0000000000..58e2df381f --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/channels.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include "channels.h" +#include "utils.h" +#include +#include + +cChannel::cChannel(const PVR_CHANNEL *Channel) +{ + +} + +cChannel::cChannel() +{ + name = ""; + uid = 0; + external_id = 0; +} + +cChannel::~cChannel() +{ +} + +bool cChannel::Parse(const std::string& data) +{ + vector fields; + + Tokenize(data, fields, "|"); + + if (fields.size() >= 4) + { + //Expected format: + //ListTVChannels, ListRadioChannels + //0 = channel uid + //1 = channel external id/number + //2 = channel name + //3 = isencrypted ("0"/"1") + + uid = atoi(fields[0].c_str()); + external_id = atoi(fields[1].c_str()); + name = fields[2]; + encrypted = (strncmp(fields[3].c_str(), "1", 1) == 0); + return true; + } else { + return false; + } +} diff --git a/xbmc/pvrclients/MediaPortal/channels.h b/xbmc/pvrclients/MediaPortal/channels.h new file mode 100644 index 0000000000..812b4caf4a --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/channels.h @@ -0,0 +1,49 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __CHANNELS_H +#define __CHANNELS_H + +#include "libXBMC_pvr.h" +#include + +class cChannel +{ +private: + std::string name; + int uid; + int external_id; + bool encrypted; + +public: + cChannel(const PVR_CHANNEL *Channel); + cChannel(); + virtual ~cChannel(); + + bool Parse(const std::string& data); + const char *Name(void) const { return name.c_str(); } + int UID(void) const { return uid; } + int ExternalID(void) const { return external_id; } + bool Encrypted(void) const { return encrypted; } +}; + +#endif //__TIMERS_H diff --git a/xbmc/pvrclients/MediaPortal/client.cpp b/xbmc/pvrclients/MediaPortal/client.cpp new file mode 100644 index 0000000000..3799c9ea63 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/client.cpp @@ -0,0 +1,672 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "client.h" +#include "xbmc_pvr_dll.h" +#include "pvrclient-mediaportal.h" +#include "utils.h" + +using namespace std; + +cPVRClientMediaPortal *g_client = NULL; +bool m_bCreated = false; +ADDON_STATUS curStatus = STATUS_UNKNOWN; +int g_clientID = -1; + +/* User adjustable settings are saved here. + * Default values are defined inside client.h + * and exported to the other source files. + */ +std::string m_sHostname = DEFAULT_HOST; +int m_iPort = DEFAULT_PORT; +bool m_bOnlyFTA = DEFAULT_FTA_ONLY; +bool m_bRadioEnabled = DEFAULT_RADIO; +bool m_bCharsetConv = DEFAULT_CHARCONV; +int m_iConnectTimeout = DEFAULT_TIMEOUT; +bool m_bNoBadChannels = DEFAULT_BADCHANNELS; +bool m_bHandleMessages = DEFAULT_HANDLE_MSG; +std::string g_szUserPath = ""; +std::string g_szClientPath = ""; +std::string g_sTVGroup = ""; +std::string g_sRadioGroup = ""; +bool m_bResolveRTSPHostname = DEFAULT_RESOLVE_RTSP_HOSTNAME; +bool m_bReadGenre = DEFAULT_READ_GENRE; +int m_iSleepOnRTSPurl = DEFAULT_SLEEP_RTSP_URL; + +cHelper_libXBMC_addon *XBMC = NULL; +cHelper_libXBMC_pvr *PVR = NULL; + +extern "C" { + +/*********************************************************** + * Standard AddOn related public library functions + ***********************************************************/ + +//-- Create ------------------------------------------------------------------- +// Called after loading of the dll, all steps to become Client functional +// must be performed here. +//----------------------------------------------------------------------------- +ADDON_STATUS Create(void* hdl, void* props) +{ + if (!hdl || !props) + return STATUS_UNKNOWN; + + PVR_PROPS* pvrprops = (PVR_PROPS*)props; + + XBMC = new cHelper_libXBMC_addon; + if (!XBMC->RegisterMe(hdl)) + return STATUS_UNKNOWN; + + PVR = new cHelper_libXBMC_pvr; + if (!PVR->RegisterMe(hdl)) + return STATUS_UNKNOWN; + + XBMC->Log(LOG_DEBUG, "Creating MediaPortal PVR-Client"); + + curStatus = STATUS_UNKNOWN; + g_client = new cPVRClientMediaPortal(); + g_clientID = pvrprops->clientID; + g_szUserPath = pvrprops->userpath; + g_szClientPath = pvrprops->clientpath; + + /* Read setting "host" from settings.xml */ + char buffer[1024]; + + if (XBMC->GetSetting("host", &buffer)) + m_sHostname = buffer; + else + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'host' setting, falling back to '127.0.0.1' as default"); + m_sHostname = DEFAULT_HOST; + } + + /* Read setting "port" from settings.xml */ + if (!XBMC->GetSetting("port", &m_iPort)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'port' setting, falling back to '9596' as default"); + m_iPort = DEFAULT_PORT; + } + + /* Read setting "ftaonly" from settings.xml */ + if (!XBMC->GetSetting("ftaonly", &m_bOnlyFTA)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'ftaonly' setting, falling back to 'false' as default"); + m_bOnlyFTA = DEFAULT_FTA_ONLY; + } + + /* Read setting "useradio" from settings.xml */ + if (!XBMC->GetSetting("useradio", &m_bRadioEnabled)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'useradio' setting, falling back to 'true' as default"); + m_bRadioEnabled = DEFAULT_RADIO; + } + + /* Read setting "convertchar" from settings.xml */ + if (!XBMC->GetSetting("convertchar", &m_bCharsetConv)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'convertchar' setting, falling back to 'false' as default"); + m_bCharsetConv = DEFAULT_CHARCONV; + } + + /* Read setting "timeout" from settings.xml */ + if (!XBMC->GetSetting("timeout", &m_iConnectTimeout)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'timeout' setting, falling back to %i seconds as default", DEFAULT_TIMEOUT); + m_iConnectTimeout = DEFAULT_TIMEOUT; + } + + if (!XBMC->GetSetting("tvgroup", &buffer)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'tvgroup' setting, falling back to '' as default"); + } else { + g_sTVGroup = buffer; + } + + if (!XBMC->GetSetting("radiogroup", &buffer)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'tvgroup' setting, falling back to '' as default"); + } else { + g_sRadioGroup = buffer; + } + + /* Read setting "resolvertsphostname" from settings.xml */ + if (!XBMC->GetSetting("resolvertsphostname", &m_bResolveRTSPHostname)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'resolvertsphostname' setting, falling back to 'true' as default"); + m_bRadioEnabled = DEFAULT_RESOLVE_RTSP_HOSTNAME; + } + + /* Read setting "readgenre" from settings.xml */ + if (!XBMC->GetSetting("readgenre", &m_bReadGenre)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'resolvertsphostname' setting, falling back to 'true' as default"); + m_bReadGenre = DEFAULT_READ_GENRE; + } + + /* Read setting "sleeponrtspurl" from settings.xml */ + if (!XBMC->GetSetting("sleeponrtspurl", &m_iSleepOnRTSPurl)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'sleeponrtspurl' setting, falling back to %i seconds as default", DEFAULT_SLEEP_RTSP_URL); + m_iSleepOnRTSPurl = DEFAULT_SLEEP_RTSP_URL; + } + + /* Create connection to MediaPortal XBMC TV client */ + if (!g_client->Connect()) + { + curStatus = STATUS_LOST_CONNECTION; + } + else + { + curStatus = STATUS_OK; + } + + m_bCreated = true; + + return curStatus; +} + +//-- Destroy ------------------------------------------------------------------ +// Used during destruction of the client, all steps to do clean and safe Create +// again must be done. +//----------------------------------------------------------------------------- +void Destroy() +{ + if (m_bCreated) + { + g_client->Disconnect(); + delete_null(g_client); + + m_bCreated = false; + } + + if (PVR) + { + delete_null(PVR); + } + if (XBMC) + { + delete_null(XBMC); + } + + + curStatus = STATUS_UNKNOWN; +} + +//-- GetStatus ---------------------------------------------------------------- +// Report the current Add-On Status to XBMC +//----------------------------------------------------------------------------- +ADDON_STATUS GetStatus() +{ + return curStatus; +} + +//-- HasSettings -------------------------------------------------------------- +// Report "true", yes this AddOn have settings +//----------------------------------------------------------------------------- +bool HasSettings() +{ + return true; +} + +unsigned int GetSettings(StructSetting ***sSet) +{ + return 0; +} + +//-- SetSetting --------------------------------------------------------------- +// Called everytime a setting is changed by the user and to inform AddOn about +// new setting and to do required stuff to apply it. +//----------------------------------------------------------------------------- +ADDON_STATUS SetSetting(const char *settingName, const void *settingValue) +{ + string str = settingName; + if (str == "host") + { + string tmp_sHostname; + XBMC->Log(LOG_INFO, "Changed Setting 'host' from %s to %s", m_sHostname.c_str(), (const char*) settingValue); + tmp_sHostname = m_sHostname; + m_sHostname = (const char*) settingValue; + if (tmp_sHostname != m_sHostname) + return STATUS_NEED_RESTART; + } + else if (str == "port") + { + XBMC->Log(LOG_INFO, "Changed Setting 'port' from %u to %u", m_iPort, *(int*) settingValue); + if (m_iPort != *(int*) settingValue) + { + m_iPort = *(int*) settingValue; + return STATUS_NEED_RESTART; + } + } + else if (str == "ftaonly") + { + XBMC->Log(LOG_INFO, "Changed setting 'ftaonly' from %u to %u", m_bOnlyFTA, *(bool*) settingValue); + m_bOnlyFTA = *(bool*) settingValue; + } + else if (str == "useradio") + { + XBMC->Log(LOG_INFO, "Changed setting 'useradio' from %u to %u", m_bRadioEnabled, *(bool*) settingValue); + m_bRadioEnabled = *(bool*) settingValue; + } + else if (str == "convertchar") + { + XBMC->Log(LOG_INFO, "Changed setting 'convertchar' from %u to %u", m_bCharsetConv, *(bool*) settingValue); + m_bCharsetConv = *(bool*) settingValue; + } + else if (str == "timeout") + { + XBMC->Log(LOG_INFO, "Changed setting 'timeout' from %u to %u", m_iConnectTimeout, *(int*) settingValue); + m_iConnectTimeout = *(int*) settingValue; + } + else if (str == "tvgroup") + { + XBMC->Log(LOG_INFO, "Changed setting 'tvgroup' from %s to %s", g_sTVGroup.c_str(), (const char*) settingValue); + g_sTVGroup = (const char*) settingValue; + } + else if (str == "radiogroup") + { + XBMC->Log(LOG_INFO, "Changed setting 'radiogroup' from %s to %s", g_sTVGroup.c_str(), (const char*) settingValue); + g_sTVGroup = (const char*) settingValue; + } + else if (str == "resolvertsphostname") + { + XBMC->Log(LOG_INFO, "Changed setting 'resolvertsphostname' from %u to %u", m_bResolveRTSPHostname, *(bool*) settingValue); + m_bResolveRTSPHostname = *(bool*) settingValue; + } + else if (str == "readgenre") + { + XBMC->Log(LOG_INFO, "Changed setting 'readgenre' from %u to %u",m_bReadGenre, *(bool*) settingValue); + m_bReadGenre = *(bool*) settingValue; + } + else if (str == "sleeponrtspurl") + { + XBMC->Log(LOG_INFO, "Changed setting 'sleeponrtspurl' from %u to %u", m_iSleepOnRTSPurl, *(int*) settingValue); + m_iSleepOnRTSPurl = *(int*) settingValue; + } + + return STATUS_OK; +} + +//-- Remove ------------------------------------------------------------------ +// Used during destruction of the client, all steps to do clean and safe Create +// again must be done. +//----------------------------------------------------------------------------- +void Remove() +{ + if (m_bCreated) + { + g_client->Disconnect(); + + delete g_client; + g_client = NULL; + + m_bCreated = false; + } + curStatus = STATUS_UNKNOWN; +} + +void Stop() +{ +} + +void FreeSettings() +{ + +} + +/*********************************************************** + * PVR Client AddOn specific public library functions + ***********************************************************/ + +//-- GetProperties ------------------------------------------------------------ +// Tell XBMC our requirements +//----------------------------------------------------------------------------- +PVR_ERROR GetProperties(PVR_SERVERPROPS* props) +{ + XBMC->Log(LOG_DEBUG, "->GetProperties()"); + + props->SupportChannelLogo = false; + props->SupportTimeShift = false; + props->SupportEPG = true; + props->SupportRecordings = true; + props->SupportTimers = true; + props->SupportTV = true; + props->SupportRadio = m_bRadioEnabled; + props->SupportChannelSettings = true; + props->SupportDirector = false; + props->SupportBouquets = false; + props->HandleInputStream = true; + props->HandleDemuxing = false; + props->SupportChannelScan = false; + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR GetStreamProperties(PVR_STREAMPROPS* props) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +//-- GetBackendName ----------------------------------------------------------- +// Return the Name of the Backend +//----------------------------------------------------------------------------- +const char * GetBackendName() +{ + return g_client->GetBackendName(); +} + +//-- GetBackendVersion -------------------------------------------------------- +// Return the Version of the Backend as String +//----------------------------------------------------------------------------- +const char * GetBackendVersion() +{ + return g_client->GetBackendVersion(); +} + +//-- GetConnectionString ------------------------------------------------------ +// Return a String with connection info, if available +//----------------------------------------------------------------------------- +const char * GetConnectionString() +{ + return g_client->GetConnectionString(); +} + +//-- GetDriveSpace ------------------------------------------------------------ +// Return the Total and Free Drive space on the PVR Backend +//----------------------------------------------------------------------------- +PVR_ERROR GetDriveSpace(long long *total, long long *used) +{ + return g_client->GetDriveSpace(total, used); +} + +PVR_ERROR GetBackendTime(time_t *localTime, int *gmtOffset) +{ + return g_client->GetMPTVTime(localTime, gmtOffset); +} + +PVR_ERROR DialogChannelScan() +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR MenuHook(const PVR_MENUHOOK &menuhook) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + + +/*******************************************/ +/** PVR EPG Functions **/ + +PVR_ERROR RequestEPGForChannel(PVRHANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end) +{ + return g_client->RequestEPGForChannel(channel, handle, start, end); +} + + +/*******************************************/ +/** PVR Bouquets Functions **/ + +int GetNumBouquets() +{ + return 0; +} + +PVR_ERROR RequestBouquetsList(PVRHANDLE handle, int radio) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + + +/*******************************************/ +/** PVR Channel Functions **/ + +int GetNumChannels() +{ + return g_client->GetNumChannels(); +} + +PVR_ERROR RequestChannelList(PVRHANDLE handle, int radio) +{ + return g_client->RequestChannelList(handle, radio); +} + +PVR_ERROR DeleteChannel(unsigned int number) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR RenameChannel(unsigned int number, const char *newname) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR MoveChannel(unsigned int number, unsigned int newnumber) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR DialogChannelSettings(const PVR_CHANNEL &channelinfo) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR DialogAddChannel(const PVR_CHANNEL &channelinfo) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + + +/*******************************************/ +/** PVR Recording Functions **/ + +int GetNumRecordings(void) +{ + return g_client->GetNumRecordings(); +} + +PVR_ERROR RequestRecordingsList(PVRHANDLE handle) +{ + return g_client->RequestRecordingsList(handle); +} + +PVR_ERROR DeleteRecording(const PVR_RECORDINGINFO &recinfo) +{ + return g_client->DeleteRecording(recinfo); +} + +PVR_ERROR RenameRecording(const PVR_RECORDINGINFO &recinfo, const char *newname) +{ + return g_client->RenameRecording(recinfo, newname); +} + + +/*******************************************/ +/** PVR Recording cut marks Functions **/ + +bool HaveCutmarks() +{ + return false; +} + +PVR_ERROR RequestCutMarksList(PVRHANDLE handle) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR AddCutMark(const PVR_CUT_MARK &cutmark) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR DeleteCutMark(const PVR_CUT_MARK &cutmark) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR StartCut() +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +/*******************************************/ +/** PVR Timer Functions **/ + +int GetNumTimers(void) +{ + return g_client->GetNumTimers(); +} + +PVR_ERROR RequestTimerList(PVRHANDLE handle) +{ + return g_client->RequestTimerList(handle); +} + +PVR_ERROR AddTimer(const PVR_TIMERINFO &timerinfo) +{ + return g_client->AddTimer(timerinfo); +} + +PVR_ERROR DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force) +{ + return g_client->DeleteTimer(timerinfo, force); +} + +PVR_ERROR RenameTimer(const PVR_TIMERINFO &timerinfo, const char *newname) +{ + return g_client->RenameTimer(timerinfo, newname); +} + +PVR_ERROR UpdateTimer(const PVR_TIMERINFO &timerinfo) +{ + return g_client->UpdateTimer(timerinfo); +} + + +/*******************************************/ +/** PVR Live Stream Functions **/ + +bool OpenLiveStream(const PVR_CHANNEL &channelinfo) +{ + return g_client->OpenLiveStream(channelinfo); +} + +void CloseLiveStream() +{ + return g_client->CloseLiveStream(); +} + +int ReadLiveStream(unsigned char* buf, int buf_size) +{ + return g_client->ReadLiveStream(buf, buf_size); +} + +long long SeekLiveStream(long long pos, int whence) +{ + return -1; +} + +long long PositionLiveStream(void) +{ + return -1; +} + +long long LengthLiveStream(void) +{ + return -1; +} + +int GetCurrentClientChannel() +{ + return g_client->GetCurrentClientChannel(); +} + +bool SwitchChannel(const PVR_CHANNEL &channelinfo) +{ + return g_client->SwitchChannel(channelinfo); +} + +PVR_ERROR SignalQuality(PVR_SIGNALQUALITY &qualityinfo) +{ + return g_client->SignalQuality(qualityinfo); +} + +/*******************************************/ +/** PVR Recording Stream Functions **/ + +bool OpenRecordedStream(const PVR_RECORDINGINFO &recinfo) +{ + return g_client->OpenRecordedStream(recinfo); +} + +void CloseRecordedStream(void) +{ + return g_client->CloseRecordedStream(); +} + +int ReadRecordedStream(unsigned char* buf, int buf_size) +{ + return g_client->ReadRecordedStream(buf, buf_size); +} + +long long SeekRecordedStream(long long pos, int whence) +{ + return g_client->SeekRecordedStream(pos, whence); +} + +long long PositionRecordedStream(void) +{ + return -1; +} + +long long LengthRecordedStream(void) +{ + return g_client->LengthRecordedStream(); +} + +// MG: added for Mediaportal +const char * GetLiveStreamURL(const PVR_CHANNEL &channelinfo) +{ + return g_client->GetLiveStreamURL(channelinfo); +} + +/** UNUSED API FUNCTIONS */ +DemuxPacket* DemuxRead(){return NULL;} +void DemuxAbort(){} +void DemuxReset(){} +void DemuxFlush(){} +bool SwapLiveTVSecondaryStream() { return false; } +bool OpenSecondaryStream(const PVR_CHANNEL &channelinfo) { return false; } +void CloseSecondaryStream() {} +int ReadSecondaryStream(unsigned char* buf, int buf_size) { return 0; } + +} //extern "C" diff --git a/xbmc/pvrclients/MediaPortal/client.h b/xbmc/pvrclients/MediaPortal/client.h new file mode 100644 index 0000000000..49d3b4cdf5 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/client.h @@ -0,0 +1,65 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef CLIENT_H +#define CLIENT_H + +//#include +#include "StdString.h" +#include "pvrclient-mediaportal.h" +#include "libXBMC_addon.h" +#include "libXBMC_pvr.h" + +#define DEFAULT_HOST "127.0.0.1" +#define DEFAULT_PORT 9596 +#define DEFAULT_FTA_ONLY false +#define DEFAULT_RADIO true +#define DEFAULT_CHARCONV false +#define DEFAULT_TIMEOUT 3 +#define DEFAULT_BADCHANNELS true +#define DEFAULT_HANDLE_MSG false +#define DEFAULT_RESOLVE_RTSP_HOSTNAME true +#define DEFAULT_READ_GENRE false +#define DEFAULT_SLEEP_RTSP_URL 0 + +extern bool m_bCreated; +extern std::string m_sHostname; +extern int m_iPort; +extern bool m_bOnlyFTA; +extern bool m_bRadioEnabled; +extern bool m_bCharsetConv; +extern int m_iConnectTimeout; +extern bool m_bNoBadChannels; +extern bool m_bHandleMessages; +extern int g_clientID; +extern std::string g_szUserPath; +extern std::string g_szClientPath; +extern std::string g_sTVGroup; +extern std::string g_sRadioGroup; +extern bool m_bResolveRTSPHostname; +extern bool m_bReadGenre; +extern int m_iSleepOnRTSPurl; + +extern cHelper_libXBMC_addon *XBMC; +extern cHelper_libXBMC_pvr *PVR; + +#endif /* CLIENT_H */ diff --git a/xbmc/pvrclients/MediaPortal/epg.cpp b/xbmc/pvrclients/MediaPortal/epg.cpp new file mode 100644 index 0000000000..f4dde7b344 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/epg.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include + +using namespace std; + +#include "epg.h" +#include "utils.h" +#include "client.h" + +//Copied from PVREpg.h: +//subtypes derived from English strings.xml and cPVREPGInfoTag::ConvertGenreIdToString +//TODO: Finish me... This list is not yet complete +#define EVCONTENTMASK_MOVIEDRAMA 0x10 +//Subtypes MOVIE/DRAMA +#define DETECTIVE_THRILLER 0x01 +#define ADVENTURE_WESTERN_WAR 0x02 +#define SF_FANTASY_HORROR 0x03 +#define COMEDY 0x04 +#define SOAP_MELODRAMA_FOLKLORIC 0x05 +#define ROMANCE 0x06 +#define SERIOUS_CLASSICAL_RELIGIOUS_HISTORICAL_DRAMA 0x07 +#define ADULTMOVIE_DRAMA 0x08 + +#define EVCONTENTMASK_NEWSCURRENTAFFAIRS 0x20 +//subtypes: +#define NEWS_WEATHER_REPORT 0x01 +#define NEWS_MAGAZINE 0x02 +#define DOCUMENTARY 0x03 +#define DISCUSSION_INTERVIEW_DEBATE 0x04 + +#define EVCONTENTMASK_SHOW 0x30 +//subtypes: +#define GAMESHOW_QUIZ_CONTEST 0x01 +#define VARIETY_SHOW 0x02 +#define TALK_SHOW 0x03 + +#define EVCONTENTMASK_SPORTS 0x40 + +#define EVCONTENTMASK_CHILDRENYOUTH 0x50 +//subtypes +#define PRESCHOOL_CHILD_PROGRAM 0x01 +#define ENTERTAINMENT_6TO14 0x02 +#define ENTERTAINMENT_10TO16 0x03 +#define INFO_EDUC_SCHOOL_PROGRAM 0x04 +#define CARTOONS_PUPPETS 0x05 + +#define EVCONTENTMASK_MUSICBALLETDANCE 0x60 +#define EVCONTENTMASK_ARTSCULTURE 0x70 +//subtypes +#define PERFORMING_ARTS 0x01 +#define FINE_ARTS 0x02 +#define RELIGION 0x03 +#define POP_CULTURE_TRAD_ARTS 0x04 +#define LITERATURE 0x05 +#define FILM_CINEMA 0x06 +#define EXP_FILM_VIDEO 0x07 +#define BROADCASTING_PRESS 0x08 +#define NEW_MEDIA 0x09 +#define ARTS_CULTURE_MAGAZINES 0x10 +#define FASHION 0x11 + +#define EVCONTENTMASK_SOCIALPOLITICALECONOMICS 0x80 +//subtype +#define MAGAZINES_REPORTS_DOCUMENTARY 0x01 +#define ECONOMICS_SOCIAL_ADVISORY 0x02 +#define REMARKABLE_PEOPLE 0x03 + +#define EVCONTENTMASK_EDUCATIONALSCIENCE 0x90 +//subtypes +#define NATURE_ANIMALS_ENVIRONMENT 0x01 +#define TECHNOLOGY_NATURAL_SCIENCES 0x02 +#define MEDICINE_PHYSIOLOGY_PSYCHOLOGY 0x03 +#define FOREIGN_COUNTRIES_EXPEDITIONS 0x04 +#define SOCIAL_SPIRITUAL_SCIENCES 0x05 +#define FURTHER_EDUCATION 0x06 +#define LANGUAGES 0x07 + +#define EVCONTENTMASK_LEISUREHOBBIES 0xA0 +#define EVCONTENTMASK_SPECIAL 0xB0 +#define EVCONTENTMASK_USERDEFINED 0xF0 + +cEpg::cEpg() +{ + m_uid = 0; + m_StartTime = 0; + m_EndTime = 0; + m_Duration = 0; + m_genre_type = 0; + m_genre_subtype = 0; + m_UTCdiff = GetUTCdifftime(); +} + +cEpg::~cEpg() +{ +} + +void cEpg::Reset() +{ + m_genre.clear(); + m_title.clear(); + m_shortText.clear(); + m_description.clear(); + + m_StartTime = 0; + m_EndTime = 0; + m_Duration = 0; + m_genre_type = 0; + m_genre_subtype = 0; + m_uid = 0; +} + +bool cEpg::ParseLine(string& data) +{ + struct tm timeinfo; + int year, month ,day; + int hour, minute, second; + int count; + + try + { + vector epgfields; + + Tokenize(data, epgfields, "|"); + + if( epgfields.size() == 5 ) + { + XBMC->Log(LOG_DEBUG, "%s: %s", epgfields[0].c_str(), epgfields[2].c_str()); + // field 0 = start date + time + // field 1 = end date + time + // field 2 = title + // field 3 = description + // field 4 = genre string + + count = sscanf(epgfields[0].c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second); + + if( count != 6) + return false; + + timeinfo.tm_hour = hour; + timeinfo.tm_min = minute; + timeinfo.tm_sec = second; + timeinfo.tm_year = year - 1900; + timeinfo.tm_mon = month - 1; + timeinfo.tm_mday = day; + // Make the other fields empty: + timeinfo.tm_isdst = 0; + timeinfo.tm_wday = 0; + timeinfo.tm_yday = 0; + + m_StartTime = mktime (&timeinfo);// + m_UTCdiff; //m_StartTime should be localtime, MP TV returns UTC + + if(m_StartTime < 0) + { + XBMC->Log(LOG_ERROR, "cEpg::ParseLine: Unable to convert start time '%s' into date+time", epgfields[0].c_str()); + return false; + } + + count = sscanf(epgfields[1].c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second); + + if(count != 6) + return false; + + timeinfo.tm_hour = hour; + timeinfo.tm_min = minute; + timeinfo.tm_sec = second; + timeinfo.tm_year = year - 1900; + timeinfo.tm_mon = month - 1; + timeinfo.tm_mday = day; + // Make the other fields empty: + timeinfo.tm_isdst = 0; + timeinfo.tm_wday = 0; + timeinfo.tm_yday = 0; + + m_EndTime = mktime (&timeinfo);// + m_UTCdiff; //m_EndTime should be localtime, MP TV returns UTC + + if( m_EndTime < 0) + { + XBMC->Log(LOG_ERROR, "cEpg::ParseLine: Unable to convert end time '%s' into date+time", epgfields[0].c_str()); + return false; + } + + m_Duration = m_EndTime - m_StartTime; + m_uid = 0; + + m_title = epgfields[2]; + m_description = epgfields[3]; + m_shortText = epgfields[2]; + SetGenre(epgfields[4], 0, 0); + + return true; + } + } + catch(std::exception &e) + { + XBMC->Log(LOG_ERROR, "Exception '%s' during parse EPG data string.", e.what()); + } + + return false; +} + +void cEpg::SetGenre(string& Genre, int genreType, int genreSubType) +{ + //TODO: The xmltv plugin may return genre strings in local language + // The only way to solve this at the XMBC side is to transfer the + // genre string to XBMC or to let this plugin (or the TVServerXBMC + // plugin) translate it into XBMC compatible (numbered) genre types + m_genre = Genre; + m_genre_subtype = 0; + + if(m_bReadGenre && m_genre.length() > 0) { + + if(m_genre.compare("news/current affairs (general)") == 0) { + m_genre_type = EVCONTENTMASK_NEWSCURRENTAFFAIRS; + } else if (m_genre.compare("magazines/reports/documentary") == 0) { + m_genre_type = EVCONTENTMASK_SOCIALPOLITICALECONOMICS; + m_genre_subtype = MAGAZINES_REPORTS_DOCUMENTARY; + } else if (m_genre.compare("sports (general)") == 0) { + m_genre_type = EVCONTENTMASK_SPORTS; + } else if (m_genre.compare("arts/culture (without music, general)") == 0) { + m_genre_type = EVCONTENTMASK_ARTSCULTURE; + } else if (m_genre.compare("childrens's/youth program (general)") == 0) { + m_genre_type = EVCONTENTMASK_CHILDRENYOUTH; + } else if (m_genre.compare("show/game show (general)") == 0) { + m_genre_type = EVCONTENTMASK_SHOW; + } else if (m_genre.compare("detective/thriller") == 0) { + m_genre_type = EVCONTENTMASK_MOVIEDRAMA; + m_genre_subtype = DETECTIVE_THRILLER; + } else if (m_genre.compare("religion") == 0) { + m_genre_type = EVCONTENTMASK_ARTSCULTURE; + m_genre_subtype = RELIGION; + } else if (m_genre.compare("documentary") == 0) { + m_genre_type = EVCONTENTMASK_NEWSCURRENTAFFAIRS; + m_genre_subtype = DOCUMENTARY; + } else if (m_genre.compare("education/science/factual topics (general)") == 0) { + m_genre_type = EVCONTENTMASK_EDUCATIONALSCIENCE; + } else if (m_genre.compare("comedy") == 0) { + m_genre_type = EVCONTENTMASK_MOVIEDRAMA; + m_genre_subtype = COMEDY; + } else if (m_genre.compare("soap/melodram/folkloric") == 0) { + m_genre_type = EVCONTENTMASK_MOVIEDRAMA; + m_genre_subtype = SOAP_MELODRAMA_FOLKLORIC; + } else if (m_genre.compare("cartoon/puppets") == 0) { + m_genre_type = EVCONTENTMASK_CHILDRENYOUTH; + m_genre_subtype = CARTOONS_PUPPETS; + } else if (m_genre.compare("movie/drama (general)") == 0) { + m_genre_type = EVCONTENTMASK_MOVIEDRAMA; + } else if (m_genre.compare("nature/animals/environment") == 0) { + m_genre_type = EVCONTENTMASK_EDUCATIONALSCIENCE; + m_genre_subtype = NATURE_ANIMALS_ENVIRONMENT; + } else if (m_genre.compare("adult movie/drama") == 0) { + m_genre_type = EVCONTENTMASK_MOVIEDRAMA; + m_genre_subtype = ADULTMOVIE_DRAMA; + } else if (m_genre.compare("music/ballet/dance (general)") == 0) { + m_genre_type = EVCONTENTMASK_MUSICBALLETDANCE; + } else { + //XBMC->Log(LOG_DEBUG, "epg::setgenre: TODO mapping of MPTV's '%s' genre.", Genre.c_str()); + m_genre_type = genreType; + m_genre_subtype = genreSubType; + } + } else { + m_genre_type = 0; + } +} diff --git a/xbmc/pvrclients/MediaPortal/epg.h b/xbmc/pvrclients/MediaPortal/epg.h new file mode 100644 index 0000000000..7ce9afe813 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/epg.h @@ -0,0 +1,75 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __EPG_H +#define __EPG_H + +#include +#include "libXBMC_addon.h" +#include "libXBMC_pvr.h" + +class cEpg +{ +private: + unsigned int m_uid; + string m_title; + string m_shortText; + string m_description; + //string m_aux; + time_t m_StartTime; + time_t m_EndTime; + int m_Duration; + string m_genre; + int m_genre_type; + int m_genre_subtype; + //time_t m_vps; // Video Programming Service timestamp (VPS, aka "Programme Identification Label", PIL) + time_t m_UTCdiff; + +public: + cEpg(); + virtual ~cEpg(); + void Reset(); + + bool ParseLine(string& data); + //bool ParseEntryLine(const char *s); + //const char *Aux(void) const { return m_aux; } + int UniqueId(void) const { return m_uid; } + time_t StartTime(void) const { return m_StartTime; } + time_t EndTime(void) const { return m_EndTime; } + time_t Duration(void) const { return m_Duration; } + //time_t Vps(void) const { return m_vps; } + //void SetVps(time_t Vps); + const char *Title(void) const { return m_title.c_str(); } + const char *ShortText(void) const { return m_shortText.c_str(); } + const char *Description(void) const { return m_description.c_str(); } + const char *Genre(void) const { return m_genre.c_str(); } + int GenreType(void) const { return m_genre_type; } + int GenreSubType(void) const { return m_genre_subtype; } + //void SetTitle(const char *Title); + //void SetShortText(const char *ShortText); + //void SetDescription(const char *Description); + void SetGenre(string& Genre, int genreType, int genreSubType); + + +}; + +#endif //__EPG_H diff --git a/xbmc/pvrclients/MediaPortal/linux/pvrclient-mediaportal_os_posix.h b/xbmc/pvrclients/MediaPortal/linux/pvrclient-mediaportal_os_posix.h new file mode 100644 index 0000000000..61b8d63c28 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/linux/pvrclient-mediaportal_os_posix.h @@ -0,0 +1,81 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef PVRCLIENT_MEDIAPORTAL_OS_POSIX_H +#define PVRCLIENT_MEDIAPORTAL_OS_POSIX_H + +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef int bool_t; +typedef int SOCKET; + +//#define closesocket(a) close(a) +//#define SOCKET_ERROR (-1) +//#define INVALID_SOCKET (-1) + +//#define __cdecl +//#define __declspec(x) + +#define SD_BOTH SHUT_RDWR + +#define LIBTYPE +#define sock_getlasterror errno +#define sock_getlasterror_socktimeout (errno == EAGAIN) +#define console_vprintf vprintf +#define console_printf printf +#define THREAD_FUNC_PREFIX void * + +static inline uint64_t getcurrenttime(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + return ((uint64_t)t.tv_sec * 1000) + (t.tv_usec / 1000); +} + +static inline int setsocktimeout(int s, int level, int optname, uint64_t timeout) +{ + struct timeval t; + t.tv_sec = timeout / 1000; + t.tv_usec = (timeout % 1000) * 1000; + return setsockopt(s, level, optname, (char *)&t, sizeof(t)); +} + +#endif diff --git a/xbmc/pvrclients/MediaPortal/project/VS2008Express/XBMC_MPTV.sln b/xbmc/pvrclients/MediaPortal/project/VS2008Express/XBMC_MPTV.sln new file mode 100644 index 0000000000..76608760f4 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/project/VS2008Express/XBMC_MPTV.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pvrclient_mptv", "XBMC_MPTV.vcproj", "{74C9642E-1988-48DC-8404-11717C355378}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {74C9642E-1988-48DC-8404-11717C355378}.Debug|Win32.ActiveCfg = Debug|Win32 + {74C9642E-1988-48DC-8404-11717C355378}.Debug|Win32.Build.0 = Debug|Win32 + {74C9642E-1988-48DC-8404-11717C355378}.Release|Win32.ActiveCfg = Release|Win32 + {74C9642E-1988-48DC-8404-11717C355378}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/xbmc/pvrclients/MediaPortal/project/VS2008Express/XBMC_MPTV.vcproj b/xbmc/pvrclients/MediaPortal/project/VS2008Express/XBMC_MPTV.vcproj new file mode 100644 index 0000000000..a9fc74b4e3 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/project/VS2008Express/XBMC_MPTV.vcproj @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xbmc/pvrclients/MediaPortal/pvrclient-mediaportal.cpp b/xbmc/pvrclients/MediaPortal/pvrclient-mediaportal.cpp new file mode 100644 index 0000000000..1b58cf7667 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/pvrclient-mediaportal.cpp @@ -0,0 +1,1014 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "client.h" +#include "timers.h" +#include "channels.h" +#include "recordings.h" +#include "epg.h" +#include "utils.h" +#include "pvrclient-mediaportal.h" +#include + +#define SEEK_POSSIBLE 0x10 // flag used to check if protocol allows seeks + +using namespace std; + +/************************************************************/ +/** Class interface */ + +cPVRClientMediaPortal::cPVRClientMediaPortal() +{ + m_iCurrentChannel = 1; + m_tcpclient = new Socket(af_inet, pf_inet, sock_stream, tcp); + m_bConnected = false; + m_bStop = true; + m_bTimeShiftStarted = false; + m_BackendUTCoffset = 0; + m_BackendTime = 0; + m_bStop = true; + m_bConnected = false; +} + +cPVRClientMediaPortal::~cPVRClientMediaPortal() +{ + XBMC->Log(LOG_DEBUG, "->~cPVRClientMediaPortal()"); + Disconnect(); + delete m_tcpclient; +} + +string cPVRClientMediaPortal::SendCommand(string command) +{ + int code; + vector lines; + + if ( !m_tcpclient->send(command) ) + { + if ( !m_tcpclient->is_valid() ) { + // Connection lost, try to reconnect + if ( Connect() ) { + // Resend the command + if (!m_tcpclient->send(command)) + { + XBMC->Log(LOG_ERROR, "SendCommand('%s') failed.", command.c_str()); + return ""; + } + } + } + } + + string response; + if ( !m_tcpclient->ReadResponse(code, lines) ) + { + XBMC->Log(LOG_ERROR, "SendCommand - Failed with code: %d (%s)", code, lines[lines.size()-1].c_str()); + } //else + //{ + // XBMC->Log(LOG_DEBUG, "cPVRClientMediaPortal::SendCommand('%s') response: %s", command.c_str(), lines[lines.size()-1].c_str()); + //} + return lines[lines.size()-1].c_str(); +} + +bool cPVRClientMediaPortal::SendCommand2(string command, int& code, vector& lines) +{ + if ( !m_tcpclient->send(command) ) + { + if ( !m_tcpclient->is_valid() ) + { + // Connection lost, try to reconnect + if ( Connect() ) + { + // Resend the command + if (!m_tcpclient->send(command)) + { + XBMC->Log(LOG_ERROR, "SendCommand2('%s') failed.", command.c_str()); + return false; + } + } + } + } + + if (!m_tcpclient->ReadResponse(code, lines)) + { + XBMC->Log(LOG_ERROR, "SendCommand - Failed with code: %d (%s)", code, lines[lines.size()-1].c_str()); + return false; + } else { + string result = lines[lines.size()-1]; + lines.clear(); + //XBMC->Log(LOG_DEBUG, "cPVRClientMediaPortal::SendCommand('%s') response: %s", command.c_str(), result.c_str()); + + Tokenize(result, lines, ","); + + return true; + } +} + +bool cPVRClientMediaPortal::Connect() +{ + string result; + + /* Open Connection to MediaPortal Backend TV Server via the XBMC TV Server plugin*/ + XBMC->Log(LOG_DEBUG, "Connect() - Connecting to %s:%i", m_sHostname.c_str(), m_iPort); + + if (!m_tcpclient->create()) + { + XBMC->Log(LOG_ERROR, "Connect() - Could not connect create socket"); + return false; + } + + if (!m_tcpclient->connect(m_sHostname, m_iPort)) + { + XBMC->Log(LOG_ERROR, "Connect() - Could not connect to MPTV backend"); + return false; + } + + m_tcpclient->set_non_blocking(1); + + result = SendCommand("PVRclientXBMC:0-1\n"); + + if(result.find("Unexpected protocol") != std::string::npos) + { + XBMC->Log(LOG_DEBUG, "TVServer does not accept protocol: PVRclientXBMC:0-1"); + return false; + } else { + vector fields; + int major = 0, minor = 0, revision = 0 , build = 0; + int count = 0; + + // Check the version of the TVServerXBMC plugin: + Tokenize(result, fields, "|"); + if(fields.size() == 2) + { + // Ok, this TVServerXBMC version answers with a version string + count = sscanf(fields[1].c_str(), "%d.%d.%d.%d", &major, &minor, &revision, &build); + if( count < 4 ) + { + XBMC->Log(LOG_ERROR, "Connect() - Could not parse the TVServerXBMC version string '%s'", fields[1].c_str()); + } + // Check for the minimal requirement: 1.0.7.x + if( major < 1 || minor < 0 || revision < 7 ) + { + XBMC->Log(LOG_ERROR, "Warning: Your TVServerXBMC version '%s' is too old. Please upgrade to 1.0.7.0 or higher!", fields[1].c_str()); + } + } else { + XBMC->Log(LOG_ERROR, "Warning: Your TVServerXBMC version is too old. Please upgrade."); + } + } + + char buffer[512]; + snprintf(buffer, 512, "%s:%i", m_sHostname.c_str(), m_iPort); + m_ConnectionString = buffer; + + m_bConnected = true; + return true; +} + +void cPVRClientMediaPortal::Disconnect() +{ + string result; + + XBMC->Log(LOG_DEBUG, "->Disconnect()"); + + if (m_tcpclient->is_valid() && m_bTimeShiftStarted) + { + result = SendCommand("IsTimeshifting:\n"); + + if (result.find("True") != std::string::npos ) + { + result = SendCommand("StopTimeshift:\n"); + } + } + + result = SendCommand("CloseConnection:\n"); + + m_bStop = true; + + m_tcpclient->close(); + + m_bConnected = false; +} + +/* IsUp() + * \brief Check whether we still have a connection with the TVServer. If not, try + * to reconnect + * \return True when a connection is available, False when even a reconnect failed + */ +bool cPVRClientMediaPortal::IsUp() +{ + if(!m_tcpclient->is_valid()) + { + if(!Connect()) { + return false; + } + } + return true; +} + +void* cPVRClientMediaPortal::Process(void*) +{ + XBMC->Log(LOG_DEBUG, "->Process() Not yet implemented"); + return NULL; +} + + +/************************************************************/ +/** General handling */ + +const char* cPVRClientMediaPortal::GetBackendName() +{ + if (!m_tcpclient->is_valid()) + return m_sHostname.c_str(); + + XBMC->Log(LOG_DEBUG, "->GetBackendName()"); + + if(m_BackendName.length() == 0) + { + m_BackendName = SendCommand("GetBackendName:\n"); + } + + return m_BackendName.c_str(); +} + +const char* cPVRClientMediaPortal::GetBackendVersion() +{ + if (!IsUp()) + return "0.0"; + + XBMC->Log(LOG_DEBUG, "->GetBackendVersion()"); + + if(m_BackendVersion.length() == 0) + { + m_BackendVersion = SendCommand("GetVersion:\n"); + } + + return m_BackendVersion.c_str(); +} + +const char* cPVRClientMediaPortal::GetConnectionString() +{ + XBMC->Log(LOG_DEBUG, "->GetConnectionString()"); + + return m_ConnectionString.c_str(); +} + +PVR_ERROR cPVRClientMediaPortal::GetDriveSpace(long long *total, long long *used) +{ + XBMC->Log(LOG_DEBUG, "->GetDriveSpace(): Todo implement me..."); + + *total = 0; + *used = 0; + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR cPVRClientMediaPortal::GetMPTVTime(time_t *localTime, int *gmtOffset) +{ + string result; + vector fields; + int year = 0, month = 0, day = 0; + int hour = 0, minute = 0, second = 0; + int count = 0; + struct tm timeinfo; + + //XBMC->Log(LOG_DEBUG, "->GetMPTVTime"); + + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + result = SendCommand("GetTime:\n"); + + Tokenize(result, fields, "|"); + + if(fields.size() == 3) + { + //From cPVREpg::cPVREpg(): Expected PVREpg GMT offset is in seconds + m_BackendUTCoffset = ((atoi(fields[1].c_str()) * 60) + atoi(fields[2].c_str())) * 60; + + count = sscanf(fields[0].c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second); + + if(count == 6) + { + //timeinfo = *localtime ( &rawtime ); + timeinfo.tm_hour = hour; + timeinfo.tm_min = minute; + timeinfo.tm_sec = second; + timeinfo.tm_year = year - 1900; + timeinfo.tm_mon = month - 1; + timeinfo.tm_mday = day; + // Make the other fields empty: + timeinfo.tm_isdst = 0; + timeinfo.tm_wday = 0; + timeinfo.tm_yday = 0; + + m_BackendTime = mktime(&timeinfo); + + if(m_BackendTime < 0) + { + XBMC->Log(LOG_DEBUG, "GetMPTVTime: Unable to convert string '%s' into date+time", fields[0].c_str()); + return PVR_ERROR_SERVER_ERROR; + } + + XBMC->Log(LOG_DEBUG, "GetMPTVTime: %s, offset: %i seconds", asctime(gmtime(&m_BackendTime)), m_BackendUTCoffset ); + + *localTime = m_BackendTime; + *gmtOffset = m_BackendUTCoffset; + return PVR_ERROR_NO_ERROR; + } + else + { + return PVR_ERROR_SERVER_ERROR; + } + } else + return PVR_ERROR_SERVER_ERROR; +} + +/************************************************************/ +/** EPG handling */ + +PVR_ERROR cPVRClientMediaPortal::RequestEPGForChannel(const PVR_CHANNEL &channel, PVRHANDLE handle, time_t start, time_t end) +{ + vector lines; + char command[256]; + string result; + cEpg epg; + PVR_PROGINFO broadcast; + + XBMC->Log(LOG_DEBUG, "->RequestEPGForChannel(%i)", channel.number); + + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + snprintf(command, 256, "GetEPG:%i\n", channel.number); + + result = SendCommand(command); + + if(result.compare(0,5, "ERROR") != 0) + { + Tokenize(result, lines, ","); + + for (vector::iterator it = lines.begin(); it < lines.end(); it++) + { + string& data(*it); + + //CStdString str_result = data; + // + //if (m_bCharsetConv) + // XBMC_unknown_to_utf8(str_result); + + if( data.length() > 0) { + uri::decode(data); + + bool isEnd = epg.ParseLine(data); + + if (isEnd && epg.StartTime() != 0) + { + broadcast.channum = channel.number; + broadcast.uid = epg.UniqueId(); + broadcast.title = epg.Title(); + broadcast.subtitle = epg.ShortText(); + broadcast.description = epg.Description(); + broadcast.starttime = epg.StartTime(); + broadcast.endtime = epg.EndTime(); + broadcast.genre_type = epg.GenreType(); + broadcast.genre_sub_type = epg.GenreSubType(); + broadcast.parental_rating = 0; + PVR->TransferEpgEntry(handle, &broadcast); + } + epg.Reset(); + } + } + } else { + XBMC->Log(LOG_DEBUG, "RequestEPGForChannel(%i) %s", channel.number, result.c_str()); + } + + return PVR_ERROR_NO_ERROR; +} + + +/************************************************************/ +/** Channel handling */ + +int cPVRClientMediaPortal::GetNumChannels() +{ + string result; + //CStdString command; + + if (!IsUp()) + return -1; + + //command.Format("GetChannelCount:%s\n", g_sTVGroup.c_str()); + // Get the total channel count (radio+tv) + // It is only used to check whether XBMC should request the channel list + result = SendCommand("GetChannelCount:\n"); + + return atol(result.c_str()); +} + +PVR_ERROR cPVRClientMediaPortal::RequestChannelList(PVRHANDLE handle, int radio) +{ + vector lines; + CStdString command; + int code; + + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + if(radio) + { + XBMC->Log(LOG_DEBUG, "RequestChannelList for Radio group:%s", g_sRadioGroup.c_str()); + command.Format("ListRadioChannels:%s\n", g_sRadioGroup.c_str()); + } else { + XBMC->Log(LOG_DEBUG, "RequestChannelList for TV group:%s", g_sTVGroup.c_str()); + command.Format("ListTVChannels:%s\n", g_sTVGroup.c_str()); + } + SendCommand2(command.c_str(), code, lines); + + for (vector::iterator it = lines.begin(); it < lines.end(); it++) + { + string& data(*it); + + if (data.length() == 0) { + break; + } + + uri::decode(data); + //if(radio) { + // XBMC->Log(LOG_DEBUG, "Radio channel: %s", data.c_str() ); + //} else { + // XBMC->Log(LOG_DEBUG, "TV channel: %s", data.c_str() ); + //} + + cChannel channel; + if( channel.Parse(data) ) + { + PVR_CHANNEL tag; + tag.uid = channel.UID(); + tag.number = channel.UID(); //channel.ExternalID(); + tag.name = channel.Name(); + tag.callsign = ""; + tag.iconpath = ""; + tag.encryption = channel.Encrypted(); + tag.radio = (radio > 0 ? true : false) ; //TODO:(channel.Vpid() == 0) && (channel.Apid(0) != 0) ? true : false; + tag.hide = false; + tag.recording = false; + tag.bouquet = 0; + tag.multifeed = false; + tag.input_format = ""; + + if(radio) + tag.stream_url = "pvr://stream/radio/%i.ts"; //stream.c_str(); + else + tag.stream_url = "pvr://stream/tv/%i.ts"; //stream.c_str(); + + PVR->TransferChannelEntry(handle, &tag); + } + } + + //pthread_mutex_unlock(&m_critSection); + return PVR_ERROR_NO_ERROR; +} + +/************************************************************/ +/** Record handling **/ + +int cPVRClientMediaPortal::GetNumRecordings(void) +{ + string result; + + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + result = SendCommand("GetRecordingCount:\n"); + + return atol(result.c_str()); +} + +PVR_ERROR cPVRClientMediaPortal::RequestRecordingsList(PVRHANDLE handle) +{ + vector lines; + string result; + + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + result = SendCommand("ListRecordings:\n"); + + Tokenize(result, lines, ","); + + for (vector::iterator it = lines.begin(); it != lines.end(); it++) + { + string& data(*it); + uri::decode(data); + + XBMC->Log(LOG_DEBUG, "RECORDING: %s", data.c_str() ); + + ///* Convert to UTF8 string format */ + //if (m_bCharsetConv) + // XBMC_unknown_to_utf8(str_result); + + cRecording recording; + if (recording.ParseLine(data)) + { + PVR_RECORDINGINFO tag; + tag.index = recording.Index(); + tag.channel_name = recording.ChannelName(); + tag.lifetime = MAXLIFETIME; //TODO: recording.Lifetime(); + tag.priority = 0; //TODO? recording.Priority(); + tag.recording_time = recording.StartTime(); + tag.duration = (int) recording.Duration(); + tag.description = recording.Description(); + tag.stream_url = recording.Stream(); + tag.title = recording.Title(); + tag.subtitle = tag.title; + tag.directory = ""; + + PVR->TransferRecordingEntry(handle, &tag); + } + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR cPVRClientMediaPortal::DeleteRecording(const PVR_RECORDINGINFO &recinfo) +{ + char command[256]; + string result; + + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + snprintf(command, 256, "DeleteRecordedTV:%i\n", recinfo.index); + + result = SendCommand(command); + + if(result.find("True") == string::npos) + { + return PVR_ERROR_NOT_DELETED; + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR cPVRClientMediaPortal::RenameRecording(const PVR_RECORDINGINFO &recinfo, const char *newname) +{ + char command[512]; + string result; + + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + snprintf(command, 512, "UpdateRecording:%i,%s\n", + recinfo.index, + newname); + + result = SendCommand(command); + + if(result.find("True") == string::npos) + { + XBMC->Log(LOG_DEBUG, "RenameRecording(%i) to %s [failed]", recinfo.index, newname); + return PVR_ERROR_NOT_DELETED; + } + XBMC->Log(LOG_DEBUG, "RenameRecording(%i) to %s [done]", recinfo.index, newname); + + return PVR_ERROR_NO_ERROR; +} + + +/************************************************************/ +/** Timer handling */ + +int cPVRClientMediaPortal::GetNumTimers(void) +{ + string result; + + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + result = SendCommand("GetScheduleCount:\n"); + + return atol(result.c_str()); +} + +PVR_ERROR cPVRClientMediaPortal::RequestTimerList(PVRHANDLE handle) +{ + vector lines; + string result; + + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + result = SendCommand("ListSchedules:\n"); + + Tokenize(result, lines, ","); + + for (vector::iterator it = lines.begin(); it != lines.end(); it++) + { + string& data(*it); + uri::decode(data); + + XBMC->Log(LOG_DEBUG, "SCHEDULED: %s", data.c_str() ); + + cTimer timer; + timer.ParseLine(data.c_str()); + + //TODO: finish me... + PVR_TIMERINFO tag; + tag.index = timer.Index(); + tag.active = true; //false; //timer.HasFlags(tfActive); + tag.channelNum = timer.Channel(); + tag.firstday = 0; //timer.FirstDay(); + tag.starttime = timer.StartTime(); + tag.endtime = timer.StopTime(); + tag.recording = 0; //timer.HasFlags(tfRecording) || timer.HasFlags(tfInstant); + tag.title = timer.Title(); + tag.directory = timer.Dir(); + tag.priority = timer.Priority(); + tag.lifetime = 0; //timer.Lifetime(); + tag.repeat = false; //timer.WeekDays() == 0 ? false : true; + tag.repeatflags = 0;//timer.WeekDays(); + + PVR->TransferTimerEntry(handle, &tag); + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR cPVRClientMediaPortal::GetTimerInfo(unsigned int timernumber, PVR_TIMERINFO &tag) +{ + string result; + char command[256]; + + XBMC->Log(LOG_DEBUG, "->GetTimerInfo(%i)", timernumber); + + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + snprintf(command,256, "GetTimerInfo:%i\n", timernumber); + + result = SendCommand(command); + + cTimer timer; + timer.ParseLine(result.c_str()); + //TODO: finish me... + tag.index = timer.Index(); + tag.active = true; //false; //timer.HasFlags(tfActive); + tag.channelNum = timer.Channel(); + tag.firstday = 0; //timer.FirstDay(); + tag.starttime = timer.StartTime(); + tag.endtime = timer.StopTime(); + tag.recording = 0; //timer.HasFlags(tfRecording) || timer.HasFlags(tfInstant); + tag.title = timer.Title(); + tag.priority = timer.Priority(); + tag.lifetime = 0; //timer.Lifetime(); + tag.repeat = false; //timer.WeekDays() == 0 ? false : true; + tag.repeatflags = 0;//timer.WeekDays(); + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR cPVRClientMediaPortal::AddTimer(const PVR_TIMERINFO &timerinfo) +{ + char command[1024]; + string result; + +#ifdef _TIME32_T_DEFINED + XBMC->Log(LOG_DEBUG, "->AddTimer Channel: %i, starttime: %i endtime: %i program: %s", timerinfo.channelNum, timerinfo.starttime, timerinfo.endtime, timerinfo.title); +#else + XBMC->Log(LOG_DEBUG, "->AddTimer Channel: %i, 64 bit times not yet supported!", timerinfo.channelNum); +#endif + + struct tm starttime; + struct tm endtime; + + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + starttime = *gmtime( &timerinfo.starttime ); + XBMC->Log(LOG_DEBUG, "Start time %s", asctime(&starttime)); + + endtime = *gmtime( &timerinfo.endtime ); + XBMC->Log(LOG_DEBUG, "End time %s", asctime(&endtime)); + + + CStdString title = timerinfo.title; + title.Replace("|",""); //Remove commas from title field + + snprintf(command, 1024, "AddSchedule:%i|%s|%i|%i|%i|%i|%i|%i|%i|%i|%i|%i|%i|%i\n", + timerinfo.channelNum, //Channel number + title.c_str(), //Program title + starttime.tm_year + 1900, starttime.tm_mon + 1, starttime.tm_mday, //Start date + starttime.tm_hour, starttime.tm_min, starttime.tm_sec, //Start time + endtime.tm_year + 1900, endtime.tm_mon + 1, endtime.tm_mday, //End date + endtime.tm_hour, endtime.tm_min, endtime.tm_sec); //End time + + if (timerinfo.index == -1) + { + result = SendCommand(command); + + if(result.find("True") == string::npos) + { + XBMC->Log(LOG_DEBUG, "AddTimer for channel: %i [failed]", timerinfo.channelNum); + return PVR_ERROR_NOT_SAVED; + } + XBMC->Log(LOG_DEBUG, "AddTimer for channel: %i [done]", timerinfo.channelNum); + } + else + { + // Modified timer + XBMC->Log(LOG_DEBUG, "AddTimer Modify timer for channel: %i; Not yet supported!"); + return PVR_ERROR_NOT_SAVED; + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR cPVRClientMediaPortal::DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force) +{ + char command[256]; + string result; + + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + snprintf(command, 256, "DeleteSchedule:%i\n",timerinfo.index); + + if (timerinfo.index == -1) + { + XBMC->Log(LOG_DEBUG, "DeleteTimer: schedule index = -1", timerinfo.index); + return PVR_ERROR_NOT_DELETED; + } else { + XBMC->Log(LOG_DEBUG, "DeleteTimer: About to delete MediaPortal schedule index=%i", timerinfo.index); + result = SendCommand(command); + + if(result.find("True") == string::npos) + { + XBMC->Log(LOG_DEBUG, "DeleteTimer %i [failed]", timerinfo.index); + return PVR_ERROR_NOT_DELETED; + } + XBMC->Log(LOG_DEBUG, "DeleteTimer %i [done]", timerinfo.index); + + } + + // return PVR_ERROR_SERVER_ERROR; + // return PVR_ERROR_NOT_SYNC; + // return PVR_ERROR_RECORDING_RUNNING; + // return PVR_ERROR_NOT_DELETED; + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR cPVRClientMediaPortal::RenameTimer(const PVR_TIMERINFO &timerinfo, const char *newname) +{ + XBMC->Log(LOG_DEBUG, "RenameTimer %i for channel: %i; Not yet supported!", timerinfo.index, timerinfo.channelNum); + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + PVR_TIMERINFO timerinfo1; + PVR_ERROR ret = GetTimerInfo(timerinfo.index, timerinfo1); + if (ret != PVR_ERROR_NO_ERROR) + return ret; + + timerinfo1.title = newname; + return UpdateTimer(timerinfo1); +} + +PVR_ERROR cPVRClientMediaPortal::UpdateTimer(const PVR_TIMERINFO &timerinfo) +{ + char command[1024]; + string result; + + struct tm starttime; + struct tm endtime; + + //TODO: timerinfo.file bevat troep. Nakijken. => gefixed + //TODO: bij opname Journaal 18:00-18:15 => 17:58-19:25 ??? Hmmz, dit klopt niet toch? => gefixed + +#ifdef _TIME32_T_DEFINED + XBMC->Log(LOG_DEBUG, "->Updateimer Index: %i Channel: %i, starttime: %i endtime: %i program: %s", timerinfo.index, timerinfo.channelNum, timerinfo.starttime, timerinfo.endtime, timerinfo.title); +#else + XBMC->Log(LOG_DEBUG, "->UpdateTimer Channel: %i, 64 bit times not yet supported!", timerinfo.channelNum); +#endif + + if (!IsUp()) + return PVR_ERROR_SERVER_ERROR; + + starttime = *gmtime( &timerinfo.starttime ); + XBMC->Log(LOG_DEBUG, "Start time %s", asctime(&starttime)); + + endtime = *gmtime( &timerinfo.endtime ); + XBMC->Log(LOG_DEBUG, "End time %s", asctime(&endtime)); + + CStdString title = timerinfo.title; + title.Replace(",",""); //Remove commas from title field + + snprintf(command, 1024, "UpdateSchedule:%i|%i|%i|%s|%i|%i|%i|%i|%i|%i|%i|%i|%i|%i|%i|%i\n", + timerinfo.index, //Schedule index + timerinfo.active, //Active + timerinfo.channelNum, //Channel number + title.c_str(), //Program title + starttime.tm_year + 1900, starttime.tm_mon + 1, starttime.tm_mday, //Start date + starttime.tm_hour, starttime.tm_min, starttime.tm_sec, //Start time + endtime.tm_year + 1900, endtime.tm_mon + 1, endtime.tm_mday, //End date + endtime.tm_hour, endtime.tm_min, endtime.tm_sec); //End time + + result = SendCommand(command); + + if(result.find("True") == string::npos) + { + XBMC->Log(LOG_DEBUG, "AddTimer for channel: %i [failed]", timerinfo.channelNum); + return PVR_ERROR_NOT_SAVED; + } + XBMC->Log(LOG_DEBUG, "AddTimer for channel: %i [done]", timerinfo.channelNum); + + return PVR_ERROR_NO_ERROR; +} + + +/************************************************************/ +/** Live stream handling */ + +// The MediaPortal TV Server uses rtsp streams which XBMC can handle directly +// so we don't need to open the streams in this pvr addon. +// However, we still need to request the stream URL for the channel we want +// to watch as it is not known on beforehand. +// Most of the times it is the same URL for each selected channel. Only the +// stream itself changes. Example URL: rtsp://tvserverhost/stream2.0 +// The number 2.0 may change when the tvserver is streaming multiple tv channels +// at the same time. +bool cPVRClientMediaPortal::OpenLiveStream(const PVR_CHANNEL &channelinfo) +{ + unsigned int channel = channelinfo.number; + + XBMC->Log(LOG_DEBUG, "->OpenLiveStream(%i): Not supported for this PVR addon.", channel); + + return false; +} + +void cPVRClientMediaPortal::CloseLiveStream() +{ + string result; + + if (!IsUp()) + return; + + if (m_bTimeShiftStarted) + { + result = SendCommand("StopTimeshift:\n"); + XBMC->Log(LOG_INFO, "CloseLiveStream: %s", result.c_str()); + m_bTimeShiftStarted = false; + } else { + XBMC->Log(LOG_DEBUG, "CloseLiveStream: Nothing to do."); + + } +} + +int cPVRClientMediaPortal::ReadLiveStream(unsigned char* buf, int buf_size) +{ + return 0; +} + +int cPVRClientMediaPortal::GetCurrentClientChannel() +{ + XBMC->Log(LOG_DEBUG, "->GetCurrentClientChannel"); + return m_iCurrentChannel; +} + +bool cPVRClientMediaPortal::SwitchChannel(const PVR_CHANNEL &channelinfo) +{ + + XBMC->Log(LOG_DEBUG, "->SwitchChannel(%i)", channelinfo.number); + string rtsp_url = GetLiveStreamURL(channelinfo); + + if(rtsp_url.length() > 0) + { + m_bTimeShiftStarted = false; //debug test + return true; + } + else + return false; +} + +PVR_ERROR cPVRClientMediaPortal::SignalQuality(PVR_SIGNALQUALITY &qualityinfo) +{ + //XBMC->Log(LOG_DEBUG, "->SignalQuality(): Not yet supported."); + + return PVR_ERROR_NO_ERROR; +} + + +/************************************************************/ +/** Record stream handling */ +// MediaPortal recordings are also rtsp streams. Main difference here with +// respect to the live tv streams is that the URLs for the recordings +// can be requested on beforehand (done in the TVserverXBMC plugin). +// These URLs are stored in the field PVR_RECORDINGINFO.stream_url +bool cPVRClientMediaPortal::OpenRecordedStream(const PVR_RECORDINGINFO &recinfo) +{ + XBMC->Log(LOG_DEBUG, "->OpenRecordedStream(index=%i)", recinfo.index); + if (!IsUp()) + return false; + + return true; +} + +void cPVRClientMediaPortal::CloseRecordedStream(void) +{ + return; +} + +int cPVRClientMediaPortal::ReadRecordedStream(unsigned char* buf, int buf_size) +{ + return 0; +} + +long long cPVRClientMediaPortal::SeekRecordedStream(long long pos, int whence) +{ + return 0; +} + +long long cPVRClientMediaPortal::LengthRecordedStream(void) +{ + return 0; +} + +/* + * \brief Request the stream URL for live tv/live radio. + * The MediaPortal TV Server will try to open the requested channel for + * time-shifting and when successful it will start an rtsp:// stream for this + * channel and return the URL for this stream. + */ +const char* cPVRClientMediaPortal::GetLiveStreamURL(const PVR_CHANNEL &channelinfo) +{ + unsigned int channel = channelinfo.number; + + string result; + char command[256] = ""; + + XBMC->Log(LOG_DEBUG, "->GetLiveStreamURL(%i)", channel); + if (!IsUp()) + { + return false; + } + + // Closing existing timeshift streams will be done in the MediaPortal TV + // Server plugin, so we can request the new channel stream directly without + // stopping the existing stream + + if(m_bResolveRTSPHostname == false) + { + // RTSP URL may contain a hostname, XBMC will do the IP resolve + snprintf(command, 256, "TimeshiftChannel:%i|False\n", channel); + } + else + { + // RTSP URL will always contain an IP address, TVServerXBMC will + // do the IP resolve + snprintf(command, 256, "TimeshiftChannel:%i\n", channel); + } + result = SendCommand(command); + + if (result.find("ERROR") != std::string::npos || result.length() == 0) + { + XBMC->Log(LOG_ERROR, "Could not stream channel %i. %s", channel, result.c_str()); + return ""; + } + else + { + if (m_iSleepOnRTSPurl > 0) + { + XBMC->Log(LOG_INFO, "Sleeping %i ms before opening stream: %s", m_iSleepOnRTSPurl, result.c_str()); + usleep(m_iSleepOnRTSPurl * 1000); + } + + XBMC->Log(LOG_INFO, "Channel stream URL: %s", result.c_str()); + m_iCurrentChannel = channel; + m_ConnectionString = result; + + // Check the returned stream URL. When the URL is an rtsp stream, we need + // to close it again after watching to stop the timeshift. + // A radio web stream (added to the TV Server) will return the web stream + // URL without starting a timeshift. + if(result.compare(0,4, "rtsp") == 0) + { + m_bTimeShiftStarted = true; + } + return m_ConnectionString.c_str(); + } +} diff --git a/xbmc/pvrclients/MediaPortal/pvrclient-mediaportal.h b/xbmc/pvrclients/MediaPortal/pvrclient-mediaportal.h new file mode 100644 index 0000000000..395f1b98ca --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/pvrclient-mediaportal.h @@ -0,0 +1,139 @@ +#pragma once + +#ifndef __PVRCLIENT_MEDIAPORTAL_H__ +#define __PVRCLIENT_MEDIAPORTAL_H__ + +/* + * Copyright (C) 2005-2010 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* +* for DESCRIPTION see 'PVRClient-MediaPortal.cpp' +*/ + +#include "pvrclient-mediaportal_os.h" + +#include + +/* Master defines for client control */ +#ifndef _WINSOCKAPI_ +#define _WINSOCKAPI_ //Needed here to prevent inclusion of via the header below +#endif +#include "../../addons/include/xbmc_pvr_types.h" + +/* Local includes */ +#include "Socket.h" + +class cPVRClientMediaPortal +{ +public: + /* Class interface */ + cPVRClientMediaPortal(); + ~cPVRClientMediaPortal(); + + /* VTP Listening Thread */ + static void* Process(void*); + + /* Server handling */ + bool Connect(); + void Disconnect(); + bool IsUp(); + + /* General handling */ + const char* GetBackendName(); + const char* GetBackendVersion(); + const char* GetConnectionString(); + PVR_ERROR GetDriveSpace(long long *total, long long *used); + PVR_ERROR GetMPTVTime(time_t *localTime, int *gmtOffset); + + /* EPG handling */ + PVR_ERROR RequestEPGForChannel(const PVR_CHANNEL &channel, PVRHANDLE handle, time_t start = NULL, time_t end = NULL); + + /* Channel handling */ + int GetNumChannels(void); + PVR_ERROR RequestChannelList(PVRHANDLE handle, int radio = 0); + + /* Record handling **/ + int GetNumRecordings(void); + PVR_ERROR RequestRecordingsList(PVRHANDLE handle); + PVR_ERROR DeleteRecording(const PVR_RECORDINGINFO &recinfo); + PVR_ERROR RenameRecording(const PVR_RECORDINGINFO &recinfo, const char *newname); + + /* Timer handling */ + int GetNumTimers(void); + PVR_ERROR RequestTimerList(PVRHANDLE handle); + PVR_ERROR GetTimerInfo(unsigned int timernumber, PVR_TIMERINFO &tag); + PVR_ERROR AddTimer(const PVR_TIMERINFO &timerinfo); + PVR_ERROR DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force = false); + PVR_ERROR RenameTimer(const PVR_TIMERINFO &timerinfo, const char *newname); + PVR_ERROR UpdateTimer(const PVR_TIMERINFO &timerinfo); + + /* Live stream handling */ + bool OpenLiveStream(const PVR_CHANNEL &channelinfo); + void CloseLiveStream(); + int ReadLiveStream(unsigned char* buf, int buf_size); + int GetCurrentClientChannel(); + bool SwitchChannel(const PVR_CHANNEL &channelinfo); + PVR_ERROR SignalQuality(PVR_SIGNALQUALITY &qualityinfo); + + /* Record stream handling */ + bool OpenRecordedStream(const PVR_RECORDINGINFO &recinfo); + void CloseRecordedStream(void); + int ReadRecordedStream(unsigned char* buf, int buf_size); + long long SeekRecordedStream(long long pos, int whence=SEEK_SET); + long long LengthRecordedStream(void); + + //MG: Added for MediaPortal streaming + const char* GetLiveStreamURL(const PVR_CHANNEL &channelinfo); + +protected: + Socket *m_tcpclient; + +private: + bool GetChannel(unsigned int number, PVR_CHANNEL &channeldata); + /* MediaPortal to XBMC Callback functions; Not yet supported */ + //static void* CallbackRcvThread(void* arg); + //bool VDRToXBMCCommand(char *Cmd); + //bool CallBackMODT(const char *Option); + //bool CallBackDELT(const char *Option); + //bool CallBackADDT(const char *Option); + //bool CallBackSMSG(const char *Option); + //bool CallBackIMSG(const char *Option); + //bool CallBackWMSG(const char *Option); + //bool CallBackEMSG(const char *Option); + + int m_iCurrentChannel; + bool m_bConnected; + bool m_bStop; + bool m_bTimeShiftStarted; + std::string m_ConnectionString; + std::string m_BackendName; + std::string m_BackendVersion; + time_t m_BackendUTCoffset; + time_t m_BackendTime; + + void Close(); + + //MG: Added for TVServer communication: + std::string SendCommand(std::string command); + bool SendCommand2(std::string command, int& code, std::vector& lines); +}; + +#endif // __PVRCLIENT_MEDIAPORTAL_H__ diff --git a/xbmc/pvrclients/MediaPortal/pvrclient-mediaportal_os.h b/xbmc/pvrclients/MediaPortal/pvrclient-mediaportal_os.h new file mode 100644 index 0000000000..2f41472fdc --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/pvrclient-mediaportal_os.h @@ -0,0 +1,47 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef PVRCLIENT_MEDIAPORTAL_OS_H +#define PVRCLIENT_MEDIAPORTAL_OS_H + +#if defined(_WIN32) || defined(_WIN64) +#define __WINDOWS__ +#endif + +#if defined(__WINDOWS__) +#ifndef _WINSOCKAPI_ +#define _WINSOCKAPI_ +#endif +#include "windows/pvrclient-mediaportal_os_windows.h" +#else +#include "linux/pvrclient-mediaportal_os_posix.h" +#endif + +#if !defined(TRUE) +#define TRUE 1 +#endif + +#if !defined(FALSE) +#define FALSE 0 +#endif + +#endif diff --git a/xbmc/pvrclients/MediaPortal/recordings.cpp b/xbmc/pvrclients/MediaPortal/recordings.cpp new file mode 100644 index 0000000000..b87ae7ad97 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/recordings.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include + +using namespace std; + +#include "recordings.h" +#include "utils.h" + +cRecording::cRecording() +{ + m_StartTime = 0; + m_Duration = 0; + m_Index = -1; + m_UTCdiff = GetUTCdifftime(); +} + +cRecording::cRecording(const PVR_RECORDINGINFO *Recording) +{ + +} + +cRecording::~cRecording() +{ +} + +bool cRecording::ParseLine(const std::string& data) +{ + time_t endtime; + struct tm timeinfo; + int year, month ,day; + int hour, minute, second; + int count; + + vector fields; + + Tokenize(data, fields, "|"); + + if( fields.size() == 9 ) + { + //[0] index / mediaportal recording id + //[1] start time + //[2] end time + //[3] channel name + //[4] title + //[5] description + //[6] stream_url + //[7] filename (we can bypass rtsp streaming when XBMC and the TV server are on the same machine) + //[8] lifetime (mediaportal keep until?) + + m_Index = atoi(fields[0].c_str()); + + count = sscanf(fields[1].c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second); + + if (count != 6) + return false; + + timeinfo.tm_hour = hour; + timeinfo.tm_min = minute; + timeinfo.tm_sec = second; + timeinfo.tm_year = year - 1900; + timeinfo.tm_mon = month - 1; + timeinfo.tm_mday = day; + // Make the other fields empty: + timeinfo.tm_isdst = 0; + timeinfo.tm_wday = 0; + timeinfo.tm_yday = 0; + + m_StartTime = mktime (&timeinfo) + m_UTCdiff; //Start time in localtime + + if (m_StartTime < 0) + return false; + + count = sscanf(fields[2].c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second); + + if (count != 6) + return false; + + timeinfo.tm_hour = hour; + timeinfo.tm_min = minute; + timeinfo.tm_sec = second; + timeinfo.tm_year = year - 1900; + timeinfo.tm_mon = month - 1; + timeinfo.tm_mday = day; + // Make the other fields empty: + timeinfo.tm_isdst = 0; + timeinfo.tm_wday = 0; + timeinfo.tm_yday = 0; + + endtime = mktime (&timeinfo) + m_UTCdiff; //Start time in localtime + + if (endtime < 0) + return false; + + m_Duration = endtime - m_StartTime; + + m_channelName = fields[3]; + m_title = fields[4]; + m_description = fields[5]; + m_stream = fields[6]; + m_fileName = fields[7]; + m_lifetime = fields[8]; + + return true; + } + else + { + return false; + } +} diff --git a/xbmc/pvrclients/MediaPortal/recordings.h b/xbmc/pvrclients/MediaPortal/recordings.h new file mode 100644 index 0000000000..8c76fbb2b7 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/recordings.h @@ -0,0 +1,68 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __RECORDINGS_H +#define __RECORDINGS_H + +#include +#include "libXBMC_addon.h" +#include "libXBMC_pvr.h" + +using namespace std; + +#define DEFAULTFRAMESPERSECOND 25.0 +#define MAXPRIORITY 99 +#define MAXLIFETIME 99 + +class cRecording +{ +private: + int m_Index; + string m_channelName; + string m_fileName; + string m_stream; + string m_lifetime; + time_t m_StartTime; + int m_Duration; + string m_title; // Title of this event + string m_shortText; // Short description of this event (typically the episode name in case of a series) + string m_description; // Description of this event + //time_t m_vps; // Video Programming Service timestamp (VPS, aka "Programme Identification Label", PIL) + time_t m_UTCdiff; + +public: + cRecording(const PVR_RECORDINGINFO *Recording); + cRecording(); + virtual ~cRecording(); + + bool ParseLine(const std::string& data); + const char *ChannelName(void) const { return m_channelName.c_str(); } + int Index(void) const { return m_Index; } + time_t StartTime(void) const { return m_StartTime; } + time_t Duration(void) const { return m_Duration; } + const char *Title(void) const { return m_title.c_str(); } + const char *Description(void) const { return m_description.c_str(); } + const char *FileName(void) const { return m_fileName.c_str(); } + const char *Stream(void) const { return m_stream.c_str(); } +}; + +#endif //__RECORDINGS_H diff --git a/xbmc/pvrclients/MediaPortal/timers.cpp b/xbmc/pvrclients/MediaPortal/timers.cpp new file mode 100644 index 0000000000..0ee8691996 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/timers.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include + +using namespace std; + +#include "timers.h" +#include "utils.h" +/* +#define SECSINDAY 86400 +*/ + +cTimer::cTimer() +{ + m_starttime = 0; + m_stoptime = 0; + m_index = 0; + m_UTCdiff = GetUTCdifftime(); +} + +cTimer::~cTimer() +{ +} + +time_t cTimer::StartTime(void) const +{ + return m_starttime; +} + +time_t cTimer::StopTime(void) const +{ + return m_stoptime; +} + +bool cTimer::ParseLine(const char *s) +{ + struct tm timeinfo; + int year, month ,day; + int hour, minute, second; + int count; + + vector schedulefields; + string data = s; + uri::decode(data); + + Tokenize(data, schedulefields, "|"); + + if(schedulefields.size() >= 7) + { + // field 0 = index + // field 1 = start date + time + // field 2 = end date + time + // field 3 = channel nr + // field 4 = channel name + // field 5 = program name + // field 6 = repeat info + // field 7 = priority + + m_index = atoi(schedulefields[0].c_str()); + + count = sscanf(schedulefields[1].c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second); + + if(count != 6) + return false; + + //timeinfo = *localtime ( &rawtime ); + timeinfo.tm_hour = hour; + timeinfo.tm_min = minute; + timeinfo.tm_sec = second; + timeinfo.tm_year = year - 1900; + timeinfo.tm_mon = month - 1; + timeinfo.tm_mday = day; + // Make the other fields empty: + timeinfo.tm_isdst = 0; + timeinfo.tm_wday = 0; + timeinfo.tm_yday = 0; + + m_starttime = mktime (&timeinfo) + m_UTCdiff; //m_StartTime should be localtime, MP TV returns UTC + + if( m_starttime < 0) + return false; + + count = sscanf(schedulefields[2].c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second); + + if( count != 6) + return false; + + //timeinfo2 = *localtime ( &rawtime ); + timeinfo.tm_hour = hour; + timeinfo.tm_min = minute; + timeinfo.tm_sec = second; + timeinfo.tm_year = year - 1900; + timeinfo.tm_mon = month - 1; + timeinfo.tm_mday = day; + // Make the other fields empty: + timeinfo.tm_isdst = 0; + timeinfo.tm_wday = 0; + timeinfo.tm_yday = 0; + + m_stoptime = mktime (&timeinfo) + m_UTCdiff; //m_EndTime should be localtime, MP TV returns UTC + + if( m_stoptime < 0) + return false; + + m_channel = atoi(schedulefields[3].c_str()); + m_title = schedulefields[5]; + m_priority = atoi(schedulefields[7].c_str()); + + return true; + } + return false; +} diff --git a/xbmc/pvrclients/MediaPortal/timers.h b/xbmc/pvrclients/MediaPortal/timers.h new file mode 100644 index 0000000000..22ce65f091 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/timers.h @@ -0,0 +1,64 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __TIMERS_H +#define __TIMERS_H + +#include "libXBMC_pvr.h" +#include +#include + +/* +enum eTimerFlags { tfNone = 0x0000, + tfActive = 0x0001, + tfInstant = 0x0002, + tfVps = 0x0004, + tfRecording = 0x0008, + tfAll = 0xFFFF, + }; +*/ +class cTimer +{ +private: + time_t m_starttime, m_stoptime; + int m_priority; + int m_channel; + string m_title; + string m_directory; + int m_index; + time_t m_UTCdiff; + +public: + cTimer(); + virtual ~cTimer(); + + int Index(void) const { return m_index; } + unsigned int Channel(void) const { return m_channel; } + int Priority(void) const { return m_priority; } + const char* Title(void) const { return m_title.c_str(); } + const char* Dir(void) const { return m_directory.c_str(); } + time_t StartTime(void) const; + time_t StopTime(void) const; + bool ParseLine(const char *s); +}; + +#endif //__TIMERS_H diff --git a/xbmc/pvrclients/MediaPortal/utils.cpp b/xbmc/pvrclients/MediaPortal/utils.cpp new file mode 100644 index 0000000000..b65eaa49eb --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/utils.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "utils.h" + +using namespace std; + +namespace uri { + const char ENCODE_BEGIN_CHAR = '%'; + const traits SCHEME_TRAITS = { + 0, 0, ':', + { + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CVA2,CINV,CVA2,CVA2,CINV, + CVA2,CVA2,CVA2,CVA2,CVA2,CVA2,CVA2,CVA2, CVA2,CVA2,CEND,CINV,CINV,CINV,CINV,CINV, + CINV,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CINV,CINV,CINV,CINV,CINV, + CINV,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CINV,CINV,CINV,CINV,CINV, // 127 7F + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + } + }; + const traits AUTHORITY_TRAITS = { + "//", 0, 0, + { + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CEND,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, // 127 7F + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + } + }; + const traits PATH_TRAITS = { + 0, 0, 0, + { // '/' is invalid + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CVAL,CINV,CINV,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CINV, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CVAL,CINV,CVAL,CINV,CINV, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CINV,CINV,CINV,CINV,CVAL, + CINV,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CINV,CINV,CINV,CVAL,CINV, // 127 7F + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + } + }; + const traits QUERY_TRAITS = { + 0, '?', 0, + { // '=' and '&' are invalid + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CVAL,CINV,CINV,CVAL,CVAL,CINV,CVAL, CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CVAL,CINV,CINV,CINV,CVAL, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CINV,CINV,CINV,CINV,CVAL, + CINV,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CINV,CINV,CINV,CVAL,CINV, // 127 7F + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + } + }; + const traits FRAGMENT_TRAITS = { + 0, '#', 0, + { + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CVAL,CINV,CINV,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CVAL,CINV,CVAL,CINV,CVAL, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CINV,CINV,CINV,CINV,CVAL, + CINV,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, + CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL,CVAL, CVAL,CVAL,CVAL,CINV,CINV,CINV,CVAL,CINV, // 127 7F + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, CINV,CINV,CINV,CINV,CINV,CINV,CINV,CINV, + } + }; + + bool parse_hex(const std::string& s, size_t pos, char& chr) { + if (s.size() < pos + 2) + return false; + unsigned int v; + unsigned int c = (unsigned int)s[pos]; + if ('0' <= c && c <= '9') + v = (c - '0') << 4; + else if ('A' <= c && c <= 'F') + v = (10 + (c - 'A')) << 4; + else if ('a' <= c && c <= 'f') + v = (10 + (c - 'a')) << 4; + else + return false; + c = (unsigned int)s[pos + 1]; + if ('0' <= c && c <= '9') + v += c - '0'; + else if ('A' <= c && c <= 'F') + v += 10 + (c - 'A'); + else if ('a' <= c && c <= 'f') + v += 10 + (c - 'a'); + else + return false; + chr = (char)v; // Set output. + return true; + } + + void append_hex(char v, std::string& s) { + unsigned int c = (unsigned char)v & 0xF0; + c >>= 4; + s.insert(s.end(), (char)((9 < c) ? (c - 10) + 'A' : c + '0')); + c = v & 0x0F; + s.insert(s.end(), (char)((9 < c) ? (c - 10) + 'A' : c + '0')); + } + + std::string encode(const traits& ts, const std::string& comp) { + std::string::const_iterator f = comp.begin(); + std::string::const_iterator anchor = f; + std::string s; + for (; f != comp.end();) { + char c = *f; + if (ts.char_class[(unsigned char)c] < CVAL || c == ENCODE_BEGIN_CHAR) { // Must encode. + s.append(anchor, f); // Catch up to this char. + s.append(1, ENCODE_BEGIN_CHAR); + append_hex(c, s); // Convert. + anchor = ++f; + } + else + ++f; + } + return (anchor == comp.begin()) ? comp : s.append(f, comp.end()); + } + + bool decode(std::string& s) { + size_t pos = s.find(ENCODE_BEGIN_CHAR); + if (pos == std::string::npos) // Handle the "99%" case fast. + return true; + std::string v; + for (size_t i = 0;;) { + if (pos == std::string::npos) { + v.append(s, i, s.size() - i); // Append up to end. + break; + } + v.append(s, i, pos - i); // Append up to char. + i = pos + 3; // Skip all 3 chars. + char c; + if (!parse_hex(s, pos + 1, c)) // Convert hex. + return false; + v.insert(v.end(), c); // Append converted hex. + pos = s.find(ENCODE_BEGIN_CHAR, i); // Find next + } + s = v; + return true; + } +} + +void Tokenize(const string& str, vector& tokens, const string& delimiters = " ") +{ + // Skip delimiters at beginning. + //string::size_type lastPos = str.find_first_not_of(delimiters, 0); + // Don't skip delimiters at beginning. + string::size_type start_pos = 0; + // Find first "non-delimiter". + string::size_type delim_pos = 0; + + while (string::npos != delim_pos) + { + delim_pos = str.find_first_of(delimiters, start_pos); + // Found a token, add it to the vector. + tokens.push_back(str.substr(start_pos, delim_pos - start_pos)); + start_pos = delim_pos + 1; + + // Find next "non-delimiter" + } +} + +time_t GetUTCdifftime(void) +{ + // Determine time difference between UTC and localtime + time_t rawtime; + struct tm* timeinfo; + time_t local; + time_t gm; + + time( &rawtime ); // this is already localtime??? + timeinfo = localtime ( &rawtime ); + local = mktime(timeinfo); + timeinfo = gmtime ( &rawtime ); + gm = mktime(timeinfo); + + return(local - gm); +} diff --git a/xbmc/pvrclients/MediaPortal/utils.h b/xbmc/pvrclients/MediaPortal/utils.h new file mode 100644 index 0000000000..e563f68604 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/utils.h @@ -0,0 +1,65 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include +#include +#include + +using namespace std; + +#define delete_null(ptr) (delete (ptr), ptr = NULL) + +/** + * String tokenize + * Split string using the given delimiter into a vector of substrings + */ +void Tokenize(const string& str, vector& tokens, const string& delimiters); + +/** + * GetUTCdifftime + * \return time_t value with the difference between local time and UTC in seconds + */ +time_t GetUTCdifftime(void); + +namespace uri { + /// Char class. + enum char_class_e { + CINV = -2, ///< invalid + CEND = -1, ///< end delimitor + CVAL = 0, ///< valid any position + CVA2 = 1, ///< valid anywhere but 1st position + }; + /// Traits used for parsing and encoding components. + struct traits { + const char* begin_cstring; ///< begin cstring (or 0 if none) + const char begin_char; ///< begin char (or 0 if none) + const char end_char; ///< end char (or 0 if none) + char char_class[256]; ///< map of char to class + }; + + /// Encode the URI (sub) component. Note that this should be used on the + /// subcomponents before appending to subdelimiter chars, if any. + /// + /// From the RFC: URI producing applications should percent-encode data octets + /// that correspond to characters in the reserved set unless these characters + /// are specifically allowed by the URI scheme to represent data in that + /// component. If a reserved character is found in a URI component and + /// no delimiting role is known for that character, then it must be + /// interpreted as representing the data octet corresponding to that + /// character's encoding in US-ASCII. + /// @see http://tools.ietf.org/html/rfc3986 + /// @see decode + std::string encode(const traits& ts, const std::string& comp); + /// Decode the pct-encoded (hex) sequences, if any, return success. + /// Does not change string on error. + /// @see http://tools.ietf.org/html/rfc3986#section-2.1 + /// @see encode + bool decode(std::string& s); + extern const char ENCODE_BEGIN_CHAR; ///< encode begin char ('\%') + extern const traits SCHEME_TRAITS; ///< scheme traits + extern const traits AUTHORITY_TRAITS; ///< authority traits + extern const traits PATH_TRAITS; ///< path traits + extern const traits QUERY_TRAITS; ///< query traits + extern const traits FRAGMENT_TRAITS; ///< fragment traits +} +#endif //__UTILS_H__ diff --git a/xbmc/pvrclients/MediaPortal/windows/pvrclient-mediaportal_os_windows.h b/xbmc/pvrclients/MediaPortal/windows/pvrclient-mediaportal_os_windows.h new file mode 100644 index 0000000000..b7899ee049 --- /dev/null +++ b/xbmc/pvrclients/MediaPortal/windows/pvrclient-mediaportal_os_windows.h @@ -0,0 +1,87 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + + +#ifndef PVRCLIENT_MEDIAPORTAL_OS_WIN_H +#define PVRCLIENT_MEDIAPORTAL_OS_WIN_H + +#ifndef _WINSOCKAPI_ +#define _WINSOCKAPI_ +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(DLL_IMPORT) +#define LIBTYPE __declspec( dllexport ) +#elif defined(DLL_EXPORT) +#define LIBTYPE __declspec( dllimport ) +#else +#define LIBTYPE +#endif + +//#define SD_BOTH 2 // See winsock2.h + +typedef int bool_t; +typedef signed __int8 int8_t; +typedef signed __int16 int16_t; +typedef signed __int32 int32_t; +typedef signed __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +typedef HANDLE pthread_t; +typedef HANDLE pthread_mutex_t; +typedef unsigned __int32 uint; + +#ifndef va_copy +#define va_copy(x, y) x = y +#endif + +#define snprintf _snprintf + +static inline void pthread_mutex_init(pthread_mutex_t *mutex, void *attr) +{ + *mutex = CreateMutex(NULL, FALSE, NULL); +} + +static inline void pthread_mutex_lock(pthread_mutex_t *mutex) +{ + WaitForSingleObject(*mutex, INFINITE); +} + +static inline void pthread_mutex_unlock(pthread_mutex_t *mutex) +{ + ReleaseMutex(*mutex); +} + +static inline void usleep(unsigned long usec) +{ + Sleep(usec/1000); +} +#endif diff --git a/xbmc/pvrclients/mythtv/Makefile.in b/xbmc/pvrclients/mythtv/Makefile.in new file mode 100644 index 0000000000..205cf83b88 --- /dev/null +++ b/xbmc/pvrclients/mythtv/Makefile.in @@ -0,0 +1,70 @@ +# +# Makefile for the XBMC MythTV PVR AddOn +# +# See the README for copyright information and +# how to reach the author. +# + +.DELETE_ON_ERROR: + +ARCH = @ARCH@ +DESTDIR ?= +PREFIX ?= /usr/local +ADDONDIR = $(PREFIX)/share/xbmc/addons +LIBS = libmythxml/libmythxml.a -ldl +INCLUDES = -I. -I../../linux -I../../ -I ../../../xbmc/addons/include -I../../../guilib +DEFINES += -D_LINUX -fPIC +LIBDIR = ../../../addons/pvr.mythtv +LIB = ../../../addons/pvr.mythtv/XBMC_MythTV.pvr + +CC ?= gcc +CFLAGS ?= -g -O2 -Wall + +CXX ?= g++ +ifeq ($(findstring Darwin,$(shell uname -a)), Darwin) +CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses -dynamiclib -single_module -undefined dynamic_lookup +DEFINES += -D__APPLE__ +else +CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses +endif + +-include Make.config + +OBJS = client.o MythXml.o + +all: $(LIB) + +# Implicit rules: + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< + +# Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.cpp) > $@ + +-include $(DEPFILE) + +# The main library: + +$(LIB): $(OBJS) $(SILIB) + $(MAKE) -C libmythxml + $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -g $(OBJS) $(LIBS) $(LIBDIRS) $(SILIB) -o $(LIB) + +# Install the files: + +install: install-lib + +# PVR library: + +install-lib: $(LIB) + @mkdir -p $(DESTDIR)$(ADDONDIR) + @cp --remove-destination -r $(LIBDIR) $(DESTDIR)$(ADDONDIR) + +clean: + -rm -f $(OBJS) $(DEPFILE) $(LIB) *~ + $(MAKE) -C libmythxml clean +CLEAN: clean diff --git a/xbmc/pvrclients/mythtv/MythXml.cpp b/xbmc/pvrclients/mythtv/MythXml.cpp new file mode 100644 index 0000000000..4642764b0c --- /dev/null +++ b/xbmc/pvrclients/mythtv/MythXml.cpp @@ -0,0 +1,144 @@ +/* + * MythXml.cpp + * + * Created on: Oct 7, 2010 + * Author: tafypz + */ + +#include "MythXml.h" + +#include "FileSystem/FileCurl.h" +#include "utils/log.h" + +#include "libmythxml/GetNumChannelsParameters.h" +#include "libmythxml/GetNumChannelsResult.h" +#include "libmythxml/GetNumChannelsCommand.h" +#include "libmythxml/GetChannelListCommand.h" +#include "libmythxml/GetChannelListParameters.h" +#include "libmythxml/GetChannelListResult.h" +#include "libmythxml/GetProgramGuideParameters.h" +#include "libmythxml/GetProgramGuideResult.h" +#include "libmythxml/GetProgramGuideCommand.h" + + +using namespace XFILE; + +MythXml::MythXml() { + hostname_ = ""; + port_ = -1; + pin_ = -1; + timeout_ = -1; +} + +MythXml::~MythXml() { +} + +void MythXml::init(){ +} + +void MythXml::cleanup(){ +} + +bool MythXml::open(CStdString hostname, int port, CStdString user, CStdString pass, int pin, long timeout){ + hostname_ = hostname; + port_ = port; + timeout_ = timeout; + pin_ = pin; + CStdString strUrl; + strUrl.Format("http://%s:%i/Myth/GetConnectionInfo?Pin=%i", hostname.c_str(), port, pin); + CStdString strXML; + + CFileCurl http; + + http.SetTimeout(timeout); + if(!http.Get(strUrl, strXML)){ + CLog::Log(LOGDEBUG, "MythXml - Could not open connection to mythtv backend."); + http.Cancel(); + return false; + } + http.Cancel(); + return true; +} + +int MythXml::getNumChannels(){ + if(!checkConnection()) + return 0; + GetNumChannelsCommand cmd; + GetNumChannelsParameters params; + GetNumChannelsResult result; + cmd.execute(hostname_, port_, params, result, timeout_); + return result.getNumberOfChannels(); +} + +PVR_ERROR MythXml::requestChannelList(PVRHANDLE handle, int radio){ + if(!checkConnection()) + return PVR_ERROR_SERVER_ERROR; + GetChannelListCommand cmd; + GetChannelListParameters params; + GetChannelListResult result; + cmd.execute(hostname_, port_, params, result, timeout_); + + if(!result.isSuccess()) + return PVR_ERROR_UNKOWN; + + const vector& channellist = result.getChannels(); + vector::const_iterator it; + PVR_CHANNEL tag; + for( it = channellist.begin(); it != channellist.end(); ++it){ + const SChannel& channel = *it; + memset(&tag, 0 , sizeof(tag)); + tag.uid = channel.id; + tag.number = channel.id; + tag.name = channel.name.c_str(); + tag.callsign = channel.callsign.c_str();; + tag.radio = false; + tag.input_format = ""; + tag.stream_url = ""; + tag.bouquet = 0; + + PVR->TransferChannelEntry(handle, &tag); + } + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR MythXml::requestEPGForChannel(PVRHANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end){ + if(!checkConnection()) + return PVR_ERROR_SERVER_ERROR; + GetProgramGuideCommand cmd; + GetProgramGuideParameters params(channel.uid, CDateTime(start), CDateTime(end), true); + GetProgramGuideResult result; + + cmd.execute(hostname_, port_, params, result, timeout_); + + if(!result.isSuccess()) + return PVR_ERROR_UNKOWN; + + PVR_PROGINFO guideItem; + const vector& epgInfo = result.getEpg(); + vector::const_iterator it; + for( it = epgInfo.begin(); it != epgInfo.end(); ++it) + { + const SEpg& epg = *it; + time_t itemStart; + time_t itemEnd; + epg.start_time.GetAsTime(itemStart); + epg.end_time.GetAsTime(itemEnd); + + guideItem.channum = epg.chan_num; + guideItem.uid = epg.id; + guideItem.title = epg.title; + guideItem.subtitle = epg.subtitle; + guideItem.description = epg.description; + guideItem.genre_type = epg.genre_type; + guideItem.genre_sub_type = epg.genre_subtype; + guideItem.parental_rating = epg.parental_rating; + guideItem.starttime = itemStart; + guideItem.endtime = itemEnd; + PVR->TransferEpgEntry(handle, &guideItem); + } + return PVR_ERROR_NO_ERROR; +} + +bool MythXml::checkConnection(){ + return true; +} diff --git a/xbmc/pvrclients/mythtv/MythXml.h b/xbmc/pvrclients/mythtv/MythXml.h new file mode 100644 index 0000000000..f41d6e573d --- /dev/null +++ b/xbmc/pvrclients/mythtv/MythXml.h @@ -0,0 +1,34 @@ +/* + * MythXml.h + * + * Created on: Oct 7, 2010 + * Author: tafypz + */ + +#ifndef XBMC_PVRCLIENTS_MYTHTV_MYTHXML_H_ +#define XBMC_PVRCLIENTS_MYTHTV_MYTHXML_H_ + +#include "client.h" + +/*! \class MythXml + \brief Acts as the glue between the PVR Addon world and the mythXML world. + */ +class MythXml { +public: + MythXml(); + virtual ~MythXml(); + void init(); + void cleanup(); + bool open(CStdString hostname, int port, CStdString user, CStdString pass, int pin, long timeout); + int getNumChannels(); + PVR_ERROR requestChannelList(PVRHANDLE handle, int radio); + PVR_ERROR requestEPGForChannel(PVRHANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end); +private: + bool checkConnection(); + CStdString hostname_; + int port_; + int timeout_; + int pin_; +}; + +#endif /* XBMC_PVRCLIENTS_MYTHTV_MYTHXML_H_ */ diff --git a/xbmc/pvrclients/mythtv/StdString.h b/xbmc/pvrclients/mythtv/StdString.h new file mode 100644 index 0000000000..b6f8203ecc --- /dev/null +++ b/xbmc/pvrclients/mythtv/StdString.h @@ -0,0 +1,4333 @@ +#pragma once +#include +#include +#if !defined(_LINUX) +#include +#include "pvrclient-mythtv_os.h" +#endif + +// ============================================================================= +// FILE: StdString.h +// AUTHOR: Joe O'Leary (with outside help noted in comments) +// +// If you find any bugs in this code, please let me know: +// +// jmoleary@earthlink.net +// http://www.joeo.net/stdstring.htm (a bit outdated) +// +// The latest version of this code should always be available at the +// following link: +// +// http://www.joeo.net/code/StdString.zip (Dec 6, 2003) +// +// +// REMARKS: +// This header file declares the CStdStr template. This template derives +// the Standard C++ Library basic_string<> template and add to it the +// the following conveniences: +// - The full MFC CString set of functions (including implicit cast) +// - writing to/reading from COM IStream interfaces +// - Functional objects for use in STL algorithms +// +// From this template, we intstantiate two classes: CStdStringA and +// CStdStringW. The name "CStdString" is just a #define of one of these, +// based upone the UNICODE macro setting +// +// This header also declares our own version of the MFC/ATL UNICODE-MBCS +// conversion macros. Our version looks exactly like the Microsoft's to +// facilitate portability. +// +// NOTE: +// If you you use this in an MFC or ATL build, you should include either +// afx.h or atlbase.h first, as appropriate. +// +// PEOPLE WHO HAVE CONTRIBUTED TO THIS CLASS: +// +// Several people have helped me iron out problems and othewise improve +// this class. OK, this is a long list but in my own defense, this code +// has undergone two major rewrites. Many of the improvements became +// necessary after I rewrote the code as a template. Others helped me +// improve the CString facade. +// +// Anyway, these people are (in chronological order): +// +// - Pete the Plumber (???) +// - Julian Selman +// - Chris (of Melbsys) +// - Dave Plummer +// - John C Sipos +// - Chris Sells +// - Nigel Nunn +// - Fan Xia +// - Matthew Williams +// - Carl Engman +// - Mark Zeren +// - Craig Watson +// - Rich Zuris +// - Karim Ratib +// - Chris Conti +// - Baptiste Lepilleur +// - Greg Pickles +// - Jim Cline +// - Jeff Kohn +// - Todd Heckel +// - Ullrich Poll�hne +// - Joe Vitaterna +// - Joe Woodbury +// - Aaron (no last name) +// - Joldakowski (???) +// - Scott Hathaway +// - Eric Nitzche +// - Pablo Presedo +// - Farrokh Nejadlotfi +// - Jason Mills +// - Igor Kholodov +// - Mike Crusader +// - John James +// - Wang Haifeng +// - Tim Dowty +// - Arnt Witteveen +// - Glen Maynard +// - Paul DeMarco +// - Bagira (full name?) +// - Ronny Schulz +// - Jakko Van Hunen +// - Charles Godwin +// - Henk Demper +// - Greg Marr +// - Bill Carducci +// - Brian Groose +// - MKingman +// - Don Beusee +// +// REVISION HISTORY +// +// 2005-JAN-10 - Thanks to Don Beusee for pointing out the danger in mapping +// length-checked formatting functions to non-length-checked +// CRT equivalents. Also thanks to him for motivating me to +// optimize my implementation of Replace() +// +// 2004-APR-22 - A big, big thank you to "MKingman" (whoever you are) for +// finally spotting a silly little error in StdCodeCvt that +// has been causing me (and users of CStdString) problems for +// years in some relatively rare conversions. I had reversed +// two length arguments. +// +// 2003-NOV-24 - Thanks to a bunch of people for helping me clean up many +// compiler warnings (and yes, even a couple of actual compiler +// errors). These include Henk Demper for figuring out how +// to make the Intellisense work on with CStdString on VC6, +// something I was never able to do. Greg Marr pointed out +// a compiler warning about an unreferenced symbol and a +// problem with my version of Load in MFC builds. Bill +// Carducci took a lot of time with me to help me figure out +// why some implementations of the Standard C++ Library were +// returning error codes for apparently successful conversions +// between ASCII and UNICODE. Finally thanks to Brian Groose +// for helping me fix compiler signed unsigned warnings in +// several functions. +// +// 2003-JUL-10 - Thanks to Charles Godwin for making me realize my 'FmtArg' +// fixes had inadvertently broken the DLL-export code (which is +// normally commented out. I had to move it up higher. Also +// this helped me catch a bug in ssicoll that would prevent +// compilation, otherwise. +// +// 2003-MAR-14 - Thanks to Jakko Van Hunen for pointing out a copy-and-paste +// bug in one of the overloads of FmtArg. +// +// 2003-MAR-10 - Thanks to Ronny Schulz for (twice!) sending me some changes +// to help CStdString build on SGI and for pointing out an +// error in placement of my preprocessor macros for ssfmtmsg. +// +// 2002-NOV-26 - Thanks to Bagira for pointing out that my implementation of +// SpanExcluding was not properly handling the case in which +// the string did NOT contain any of the given characters +// +// 2002-OCT-21 - Many thanks to Paul DeMarco who was invaluable in helping me +// get this code working with Borland's free compiler as well +// as the Dev-C++ compiler (available free at SourceForge). +// +// 2002-SEP-13 - Thanks to Glen Maynard who helped me get rid of some loud +// but harmless warnings that were showing up on g++. Glen +// also pointed out that some pre-declarations of FmtArg<> +// specializations were unnecessary (and no good on G++) +// +// 2002-JUN-26 - Thanks to Arnt Witteveen for pointing out that I was using +// static_cast<> in a place in which I should have been using +// reinterpret_cast<> (the ctor for unsigned char strings). +// That's what happens when I don't unit-test properly! +// Arnt also noticed that CString was silently correcting the +// 'nCount' argument to Left() and Right() where CStdString was +// not (and crashing if it was bad). That is also now fixed! +// +// 2002-FEB-25 - Thanks to Tim Dowty for pointing out (and giving me the fix +// for) a conversion problem with non-ASCII MBCS characters. +// CStdString is now used in my favorite commercial MP3 player! +// +// 2001-DEC-06 - Thanks to Wang Haifeng for spotting a problem in one of the +// assignment operators (for _bstr_t) that would cause compiler +// errors when refcounting protection was turned off. +// +// 2001-NOV-27 - Remove calls to operator!= which involve reverse_iterators +// due to a conflict with the rel_ops operator!=. Thanks to +// John James for pointing this out. +// +// 2001-OCT-29 - Added a minor range checking fix for the Mid function to +// make it as forgiving as CString's version is. Thanks to +// Igor Kholodov for noticing this. +// - Added a specialization of std::swap for CStdString. Thanks +// to Mike Crusader for suggesting this! It's commented out +// because you're not supposed to inject your own code into the +// 'std' namespace. But if you don't care about that, it's +// there if you want it +// - Thanks to Jason Mills for catching a case where CString was +// more forgiving in the Delete() function than I was. +// +// 2001-JUN-06 - I was violating the Standard name lookup rules stated +// in [14.6.2(3)]. None of the compilers I've tried so +// far apparently caught this but HP-UX aCC 3.30 did. The +// fix was to add 'this->' prefixes in many places. +// Thanks to Farrokh Nejadlotfi for this! +// +// 2001-APR-27 - StreamLoad was calculating the number of BYTES in one +// case, not characters. Thanks to Pablo Presedo for this. +// +// 2001-FEB-23 - Replace() had a bug which caused infinite loops if the +// source string was empty. Fixed thanks to Eric Nitzsche. +// +// 2001-FEB-23 - Scott Hathaway was a huge help in providing me with the +// ability to build CStdString on Sun Unix systems. He +// sent me detailed build reports about what works and what +// does not. If CStdString compiles on your Unix box, you +// can thank Scott for it. +// +// 2000-DEC-29 - Joldakowski noticed one overload of Insert failed to do a +// range check as CString's does. Now fixed -- thanks! +// +// 2000-NOV-07 - Aaron pointed out that I was calling static member +// functions of char_traits via a temporary. This was not +// technically wrong, but it was unnecessary and caused +// problems for poor old buggy VC5. Thanks Aaron! +// +// 2000-JUL-11 - Joe Woodbury noted that the CString::Find docs don't match +// what the CString::Find code really ends up doing. I was +// trying to match the docs. Now I match the CString code +// - Joe also caught me truncating strings for GetBuffer() calls +// when the supplied length was less than the current length. +// +// 2000-MAY-25 - Better support for STLPORT's Standard library distribution +// - Got rid of the NSP macro - it interfered with Koenig lookup +// - Thanks to Joe Woodbury for catching a TrimLeft() bug that +// I introduced in January. Empty strings were not getting +// trimmed +// +// 2000-APR-17 - Thanks to Joe Vitaterna for pointing out that ReverseFind +// is supposed to be a const function. +// +// 2000-MAR-07 - Thanks to Ullrich Poll�hne for catching a range bug in one +// of the overloads of assign. +// +// 2000-FEB-01 - You can now use CStdString on the Mac with CodeWarrior! +// Thanks to Todd Heckel for helping out with this. +// +// 2000-JAN-23 - Thanks to Jim Cline for pointing out how I could make the +// Trim() function more efficient. +// - Thanks to Jeff Kohn for prompting me to find and fix a typo +// in one of the addition operators that takes _bstr_t. +// - Got rid of the .CPP file - you only need StdString.h now! +// +// 1999-DEC-22 - Thanks to Greg Pickles for helping me identify a problem +// with my implementation of CStdString::FormatV in which +// resulting string might not be properly NULL terminated. +// +// 1999-DEC-06 - Chris Conti pointed yet another basic_string<> assignment +// bug that MS has not fixed. CStdString did nothing to fix +// it either but it does now! The bug was: create a string +// longer than 31 characters, get a pointer to it (via c_str()) +// and then assign that pointer to the original string object. +// The resulting string would be empty. Not with CStdString! +// +// 1999-OCT-06 - BufferSet was erasing the string even when it was merely +// supposed to shrink it. Fixed. Thanks to Chris Conti. +// - Some of the Q172398 fixes were not checking for assignment- +// to-self. Fixed. Thanks to Baptiste Lepilleur. +// +// 1999-AUG-20 - Improved Load() function to be more efficient by using +// SizeOfResource(). Thanks to Rich Zuris for this. +// - Corrected resource ID constructor, again thanks to Rich. +// - Fixed a bug that occurred with UNICODE characters above +// the first 255 ANSI ones. Thanks to Craig Watson. +// - Added missing overloads of TrimLeft() and TrimRight(). +// Thanks to Karim Ratib for pointing them out +// +// 1999-JUL-21 - Made all calls to GetBuf() with no args check length first. +// +// 1999-JUL-10 - Improved MFC/ATL independence of conversion macros +// - Added SS_NO_REFCOUNT macro to allow you to disable any +// reference-counting your basic_string<> impl. may do. +// - Improved ReleaseBuffer() to be as forgiving as CString. +// Thanks for Fan Xia for helping me find this and to +// Matthew Williams for pointing it out directly. +// +// 1999-JUL-06 - Thanks to Nigel Nunn for catching a very sneaky bug in +// ToLower/ToUpper. They should call GetBuf() instead of +// data() in order to ensure the changed string buffer is not +// reference-counted (in those implementations that refcount). +// +// 1999-JUL-01 - Added a true CString facade. Now you can use CStdString as +// a drop-in replacement for CString. If you find this useful, +// you can thank Chris Sells for finally convincing me to give +// in and implement it. +// - Changed operators << and >> (for MFC CArchive) to serialize +// EXACTLY as CString's do. So now you can send a CString out +// to a CArchive and later read it in as a CStdString. I have +// no idea why you would want to do this but you can. +// +// 1999-JUN-21 - Changed the CStdString class into the CStdStr template. +// - Fixed FormatV() to correctly decrement the loop counter. +// This was harmless bug but a bug nevertheless. Thanks to +// Chris (of Melbsys) for pointing it out +// - Changed Format() to try a normal stack-based array before +// using to _alloca(). +// - Updated the text conversion macros to properly use code +// pages and to fit in better in MFC/ATL builds. In other +// words, I copied Microsoft's conversion stuff again. +// - Added equivalents of CString::GetBuffer, GetBufferSetLength +// - new sscpy() replacement of CStdString::CopyString() +// - a Trim() function that combines TrimRight() and TrimLeft(). +// +// 1999-MAR-13 - Corrected the "NotSpace" functional object to use _istpace() +// instead of _isspace() Thanks to Dave Plummer for this. +// +// 1999-FEB-26 - Removed errant line (left over from testing) that #defined +// _MFC_VER. Thanks to John C Sipos for noticing this. +// +// 1999-FEB-03 - Fixed a bug in a rarely-used overload of operator+() that +// caused infinite recursion and stack overflow +// - Added member functions to simplify the process of +// persisting CStdStrings to/from DCOM IStream interfaces +// - Added functional objects (e.g. StdStringLessNoCase) that +// allow CStdStrings to be used as keys STL map objects with +// case-insensitive comparison +// - Added array indexing operators (i.e. operator[]). I +// originally assumed that these were unnecessary and would be +// inherited from basic_string. However, without them, Visual +// C++ complains about ambiguous overloads when you try to use +// them. Thanks to Julian Selman to pointing this out. +// +// 1998-FEB-?? - Added overloads of assign() function to completely account +// for Q172398 bug. Thanks to "Pete the Plumber" for this +// +// 1998-FEB-?? - Initial submission +// +// COPYRIGHT: +// 2002 Joseph M. O'Leary. This code is 100% free. Use it anywhere you +// want. Rewrite it, restructure it, whatever. If you can write software +// that makes money off of it, good for you. I kinda like capitalism. +// Please don't blame me if it causes your $30 billion dollar satellite +// explode in orbit. If you redistribute it in any form, I'd appreciate it +// if you would leave this notice here. +// ============================================================================= + +// Avoid multiple inclusion + +#ifndef STDSTRING_H +#define STDSTRING_H + +// When using VC, turn off browser references +// Turn off unavoidable compiler warnings + +#if defined(_MSC_VER) && (_MSC_VER > 1100) + #pragma component(browser, off, references, "CStdString") + #pragma warning (disable : 4290) // C++ Exception Specification ignored + #pragma warning (disable : 4127) // Conditional expression is constant + #pragma warning (disable : 4097) // typedef name used as synonym for class name +#endif + +// Borland warnings to turn off + +#ifdef __BORLANDC__ + #pragma option push -w-inl +// #pragma warn -inl // Turn off inline function warnings +#endif + +// SS_IS_INTRESOURCE +// ----------------- +// A copy of IS_INTRESOURCE from VC7. Because old VC6 version of winuser.h +// doesn't have this. + +#define SS_IS_INTRESOURCE(_r) (false) + +#if !defined (SS_ANSI) && defined(_MSC_VER) + #undef SS_IS_INTRESOURCE + #if defined(_WIN64) + #define SS_IS_INTRESOURCE(_r) (((unsigned __int64)(_r) >> 16) == 0) + #else + #define SS_IS_INTRESOURCE(_r) (((unsigned long)(_r) >> 16) == 0) + #endif +#endif + + +// MACRO: SS_UNSIGNED +// ------------------ +// This macro causes the addition of a constructor and assignment operator +// which take unsigned characters. CString has such functions and in order +// to provide maximum CString-compatability, this code needs them as well. +// In practice you will likely never need these functions... + +//#define SS_UNSIGNED + +#ifdef SS_ALLOW_UNSIGNED_CHARS + #define SS_UNSIGNED +#endif + +// MACRO: SS_SAFE_FORMAT +// --------------------- +// This macro provides limited compatability with a questionable CString +// "feature". You can define it in order to avoid a common problem that +// people encounter when switching from CString to CStdString. +// +// To illustrate the problem -- With CString, you can do this: +// +// CString sName("Joe"); +// CString sTmp; +// sTmp.Format("My name is %s", sName); // WORKS! +// +// However if you were to try this with CStdString, your program would +// crash. +// +// CStdString sName("Joe"); +// CStdString sTmp; +// sTmp.Format("My name is %s", sName); // CRASHES! +// +// You must explicitly call c_str() or cast the object to the proper type +// +// sTmp.Format("My name is %s", sName.c_str()); // WORKS! +// sTmp.Format("My name is %s", static_cast(sName));// WORKS! +// sTmp.Format("My name is %s", (PCSTR)sName); // WORKS! +// +// This is because it is illegal to pass anything but a POD type as a +// variadic argument to a variadic function (i.e. as one of the "..." +// arguments). The type const char* is a POD type. The type CStdString +// is not. Of course, neither is the type CString, but CString lets you do +// it anyway due to the way they laid out the class in binary. I have no +// control over this in CStdString since I derive from whatever +// implementation of basic_string is available. +// +// However if you have legacy code (which does this) that you want to take +// out of the MFC world and you don't want to rewrite all your calls to +// Format(), then you can define this flag and it will no longer crash. +// +// Note however that this ONLY works for Format(), not sprintf, fprintf, +// etc. If you pass a CStdString object to one of those functions, your +// program will crash. Not much I can do to get around this, short of +// writing substitutes for those functions as well. + +#define SS_SAFE_FORMAT // use new template style Format() function + + +// MACRO: SS_NO_IMPLICIT_CAST +// -------------------------- +// Some people don't like the implicit cast to const char* (or rather to +// const CT*) that CStdString (and MFC's CString) provide. That was the +// whole reason I created this class in the first place, but hey, whatever +// bakes your cake. Just #define this macro to get rid of the the implicit +// cast. + +//#define SS_NO_IMPLICIT_CAST // gets rid of operator const CT*() + + +// MACRO: SS_NO_REFCOUNT +// --------------------- +// turns off reference counting at the assignment level. Only needed +// for the version of basic_string<> that comes with Visual C++ versions +// 6.0 or earlier, and only then in some heavily multithreaded scenarios. +// Uncomment it if you feel you need it. + +//#define SS_NO_REFCOUNT + +// MACRO: SS_WIN32 +// --------------- +// When this flag is set, we are building code for the Win32 platform and +// may use Win32 specific functions (such as LoadString). This gives us +// a couple of nice extras for the code. +// +// Obviously, Microsoft's is not the only compiler available for Win32 out +// there. So I can't just check to see if _MSC_VER is defined to detect +// if I'm building on Win32. So for now, if you use MS Visual C++ or +// Borland's compiler, I turn this on. Otherwise you may turn it on +// yourself, if you prefer + +#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(_WIN32) + #define SS_WIN32 +#endif + +// MACRO: SS_ANSI +// -------------- +// When this macro is defined, the code attempts only to use ANSI/ISO +// standard library functions to do it's work. It will NOT attempt to use +// any Win32 of Visual C++ specific functions -- even if they are +// available. You may define this flag yourself to prevent any Win32 +// of VC++ specific functions from being called. + +// If we're not on Win32, we MUST use an ANSI build + +#ifndef SS_WIN32 + #if !defined(SS_NO_ANSI) + #define SS_ANSI + #endif +#endif + +// MACRO: SS_ALLOCA +// ---------------- +// Some implementations of the Standard C Library have a non-standard +// function known as alloca(). This functions allows one to allocate a +// variable amount of memory on the stack. It is needed to implement +// the ASCII/MBCS conversion macros. +// +// I wanted to find some way to determine automatically if alloca() is +// available on this platform via compiler flags but that is asking for +// trouble. The crude test presented here will likely need fixing on +// other platforms. Therefore I'll leave it up to you to fiddle with +// this test to determine if it exists. Just make sure SS_ALLOCA is or +// is not defined as appropriate and you control this feature. + +#if defined(_MSC_VER) && !defined(SS_ANSI) + #define SS_ALLOCA +#endif + + +// MACRO: SS_MBCS +// -------------- +// Setting this macro means you are using MBCS characters. In MSVC builds, +// this macro gets set automatically by detection of the preprocessor flag +// _MBCS. For other platforms you may set it manually if you wish. The +// only effect it currently has is to cause the allocation of more space +// for wchar_t --> char conversions. +// Note that MBCS does not mean UNICODE. +// +// #define SS_MBCS +// + +#ifdef _MBCS + #define SS_MBCS +#endif + + +// MACRO SS_NO_LOCALE +// ------------------ +// If your implementation of the Standard C++ Library lacks the header, +// you can #define this macro to make your code build properly. Note that this +// is some of my newest code and frankly I'm not very sure of it, though it does +// pass my unit tests. + +// #define SS_NO_LOCALE + + +// Compiler Error regarding _UNICODE and UNICODE +// ----------------------------------------------- +// Microsoft header files are screwy. Sometimes they depend on a preprocessor +// flag named "_UNICODE". Other times they check "UNICODE" (note the lack of +// leading underscore in the second version". In several places, they silently +// "synchronize" these two flags this by defining one of the other was defined. +// In older version of this header, I used to try to do the same thing. +// +// However experience has taught me that this is a bad idea. You get weird +// compiler errors that seem to indicate things like LPWSTR and LPTSTR not being +// equivalent in UNICODE builds, stuff like that (when they MUST be in a proper +// UNICODE build). You end up scratching your head and saying, "But that HAS +// to compile!". +// +// So what should you do if you get this error? +// +// Make sure that both macros (_UNICODE and UNICODE) are defined before this +// file is included. You can do that by either +// +// a) defining both yourself before any files get included +// b) including the proper MS headers in the proper order +// c) including this file before any other file, uncommenting +// the #defines below, and commenting out the #errors +// +// Personally I recommend solution a) but it's your call. + +#ifdef _MSC_VER + #if defined (_UNICODE) && !defined (UNICODE) + #error UNICODE defined but not UNICODE + // #define UNICODE // no longer silently fix this + #endif + #if defined (UNICODE) && !defined (_UNICODE) + #error Warning, UNICODE defined but not _UNICODE + // #define _UNICODE // no longer silently fix this + #endif +#endif + + +// ----------------------------------------------------------------------------- +// MIN and MAX. The Standard C++ template versions go by so many names (at +// at least in the MS implementation) that you never know what's available +// ----------------------------------------------------------------------------- +template +inline const Type& SSMIN(const Type& arg1, const Type& arg2) +{ + return arg2 < arg1 ? arg2 : arg1; +} +template +inline const Type& SSMAX(const Type& arg1, const Type& arg2) +{ + return arg2 > arg1 ? arg2 : arg1; +} + +// If they have not #included W32Base.h (part of my W32 utility library) then +// we need to define some stuff. Otherwise, this is all defined there. + +#if !defined(W32BASE_H) + + // If they want us to use only standard C++ stuff (no Win32 stuff) + + #ifdef SS_ANSI + + // On Win32 we have TCHAR.H so just include it. This is NOT violating + // the spirit of SS_ANSI as we are not calling any Win32 functions here. + + #ifdef SS_WIN32 + + #include + #include + #ifndef STRICT + #define STRICT + #endif + + // ... but on non-Win32 platforms, we must #define the types we need. + + #else + + typedef const char* PCSTR; + typedef char* PSTR; + typedef const wchar_t* PCWSTR; + typedef wchar_t* PWSTR; + #ifdef UNICODE + typedef wchar_t TCHAR; + #else + typedef char TCHAR; + #endif + typedef wchar_t OLECHAR; + + #endif // #ifndef _WIN32 + + + // Make sure ASSERT and verify are defined using only ANSI stuff + + #ifndef ASSERT + #include + #define ASSERT(f) assert((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #else // ...else SS_ANSI is NOT defined + + #include + #include + #ifndef STRICT + #define STRICT + #endif + + // Make sure ASSERT and verify are defined + + #ifndef ASSERT + #include + #define ASSERT(f) _ASSERTE((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #endif // #ifdef SS_ANSI + + #ifndef UNUSED + #define UNUSED(x) x + #endif + +#endif // #ifndef W32BASE_H + +// Standard headers needed + +#include // basic_string +#include // for_each, etc. +#include // for StdStringLessNoCase, et al +#ifndef SS_NO_LOCALE + #include // for various facets +#endif + +// If this is a recent enough version of VC include comdef.h, so we can write +// member functions to deal with COM types & compiler support classes e.g. +// _bstr_t + +#if defined (_MSC_VER) && (_MSC_VER >= 1100) + #include + #define SS_INC_COMDEF // signal that we #included MS comdef.h file + #define STDSTRING_INC_COMDEF + #define SS_NOTHROW __declspec(nothrow) +#else + #define SS_NOTHROW +#endif + +#ifndef TRACE + #define TRACE_DEFINED_HERE + #define TRACE +#endif + +// Microsoft defines PCSTR, PCWSTR, etc, but no PCTSTR. I hate to use the +// versions with the "L" in front of them because that's a leftover from Win 16 +// days, even though it evaluates to the same thing. Therefore, Define a PCSTR +// as an LPCTSTR. + +#if !defined(PCTSTR) && !defined(PCTSTR_DEFINED) + typedef const TCHAR* PCTSTR; + #define PCTSTR_DEFINED +#endif + +#if !defined(PCOLESTR) && !defined(PCOLESTR_DEFINED) + typedef const OLECHAR* PCOLESTR; + #define PCOLESTR_DEFINED +#endif + +#if !defined(POLESTR) && !defined(POLESTR_DEFINED) + typedef OLECHAR* POLESTR; + #define POLESTR_DEFINED +#endif + +#if !defined(PCUSTR) && !defined(PCUSTR_DEFINED) + typedef const unsigned char* PCUSTR; + typedef unsigned char* PUSTR; + #define PCUSTR_DEFINED +#endif + + +// SGI compiler 7.3 doesnt know these types - oh and btw, remember to use +// -LANG:std in the CXX Flags +#if defined(__sgi) + typedef unsigned long DWORD; + typedef void * LPCVOID; +#endif + + +// SS_USE_FACET macro and why we need it: +// +// Since I'm a good little Standard C++ programmer, I use locales. Thus, I +// need to make use of the use_facet<> template function here. Unfortunately, +// this need is complicated by the fact the MS' implementation of the Standard +// C++ Library has a non-standard version of use_facet that takes more +// arguments than the standard dictates. Since I'm trying to write CStdString +// to work with any version of the Standard library, this presents a problem. +// +// The upshot of this is that I can't do 'use_facet' directly. The MS' docs +// tell me that I have to use a macro, _USE() instead. Since _USE obviously +// won't be available in other implementations, this means that I have to write +// my OWN macro -- SS_USE_FACET -- that evaluates either to _USE or to the +// standard, use_facet. +// +// If you are having trouble with the SS_USE_FACET macro, in your implementation +// of the Standard C++ Library, you can define your own version of SS_USE_FACET. + +#ifndef schMSG + #define schSTR(x) #x + #define schSTR2(x) schSTR(x) + #define schMSG(desc) message(__FILE__ "(" schSTR2(__LINE__) "):" #desc) +#endif + +#ifndef SS_USE_FACET + + // STLPort #defines a macro (__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) for + // all MSVC builds, erroneously in my opinion. It causes problems for + // my SS_ANSI builds. In my code, I always comment out that line. You'll + // find it in \stlport\config\stl_msvc.h + + #if defined(__SGI_STL_PORT) && (__SGI_STL_PORT >= 0x400 ) + + #if defined(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) && defined(_MSC_VER) + #ifdef SS_ANSI + #pragma schMSG(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS defined!!) + #endif + #endif + #define SS_USE_FACET(loc, fac) std::use_facet(loc) + + #elif defined(_MSC_VER ) + + #define SS_USE_FACET(loc, fac) std::_USE(loc, fac) + + // ...and + #elif defined(_RWSTD_NO_TEMPLATE_ON_RETURN_TYPE) + + #define SS_USE_FACET(loc, fac) std::use_facet(loc, (fac*)0) + + #else + + #define SS_USE_FACET(loc, fac) std::use_facet(loc) + + #endif + +#endif + +// ============================================================================= +// UNICODE/MBCS conversion macros. Made to work just like the MFC/ATL ones. +// ============================================================================= + +#include // Added to Std Library with Amendment #1. + +// First define the conversion helper functions. We define these regardless of +// any preprocessor macro settings since their names won't collide. + +// Not sure if we need all these headers. I believe ANSI says we do. + +#include +#include +#include +#include +#include +#ifndef va_start + #include +#endif + + +#ifdef SS_NO_LOCALE + + #if defined(_WIN32) || defined (_WIN32_WCE) + + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCSTR pSrcA, int nSrc, + UINT acp=CP_ACP) + { + ASSERT(0 != pSrcA); + ASSERT(0 != pDstW); + pDstW[0] = '\0'; + MultiByteToWideChar(acp, 0, pSrcA, nSrc, pDstW, nDst); + return pDstW; + } + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCUSTR pSrcA, int nSrc, + UINT acp=CP_ACP) + { + return StdCodeCvt(pDstW, nDst, (PCSTR)pSrcA, nSrc, acp); + } + + inline PSTR StdCodeCvt(PSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + UINT acp=CP_ACP) + { + ASSERT(0 != pDstA); + ASSERT(0 != pSrcW); + pDstA[0] = '\0'; + WideCharToMultiByte(acp, 0, pSrcW, nSrc, pDstA, nDst, 0, 0); + return pDstA; + } + inline PUSTR StdCodeCvt(PUSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + UINT acp=CP_ACP) + { + return (PUSTR)StdCodeCvt((PSTR)pDstA, nDst, pSrcW, nSrc, acp); + } + #else + #endif + +#else + + // StdCodeCvt - made to look like Win32 functions WideCharToMultiByte + // and MultiByteToWideChar but uses locales in SS_ANSI + // builds. There are a number of overloads. + // First argument is the destination buffer. + // Second argument is the source buffer + //#if defined (SS_ANSI) || !defined (SS_WIN32) + + // 'SSCodeCvt' - shorthand name for the codecvt facet we use + + typedef std::codecvt SSCodeCvt; + + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCSTR pSrcA, int nSrc, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pSrcA); + ASSERT(0 != pDstW); + + pDstW[0] = '\0'; + + if ( nSrc > 0 ) + { + PCSTR pNextSrcA = pSrcA; + PWSTR pNextDstW = pDstW; + SSCodeCvt::result res = SSCodeCvt::ok; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= { 0 }; + res = conv.in(st, + pSrcA, pSrcA + nSrc, pNextSrcA, + pDstW, pDstW + nDst, pNextDstW); +#ifdef _LINUX +#define ASSERT2(a) if (!(a)) {fprintf(stderr, "StdString: Assertion Failed on line %d\n", __LINE__);} +#else +#define ASSERT2 ASSERT +#endif + ASSERT2(SSCodeCvt::ok == res); + ASSERT2(SSCodeCvt::error != res); + ASSERT2(pNextDstW >= pDstW); + ASSERT2(pNextSrcA >= pSrcA); +#undef ASSERT2 + // Null terminate the converted string + + if ( pNextDstW - pDstW > nDst ) + *(pDstW + nDst) = '\0'; + else + *pNextDstW = '\0'; + } + return pDstW; + } + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCUSTR pSrcA, int nSrc, + const std::locale& loc=std::locale()) + { + return StdCodeCvt(pDstW, nDst, (PCSTR)pSrcA, nSrc, loc); + } + + inline PSTR StdCodeCvt(PSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pDstA); + ASSERT(0 != pSrcW); + + pDstA[0] = '\0'; + + if ( nSrc > 0 ) + { + PSTR pNextDstA = pDstA; + PCWSTR pNextSrcW = pSrcW; + SSCodeCvt::result res = SSCodeCvt::ok; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= { 0 }; + res = conv.out(st, + pSrcW, pSrcW + nSrc, pNextSrcW, + pDstA, pDstA + nDst, pNextDstA); +#ifdef _LINUX +#define ASSERT2(a) if (!(a)) {fprintf(stderr, "StdString: Assertion Failed on line %d\n", __LINE__);} +#else +#define ASSERT2 ASSERT +#endif + ASSERT2(SSCodeCvt::error != res); + ASSERT2(SSCodeCvt::ok == res); // strict, comment out for sanity + ASSERT2(pNextDstA >= pDstA); + ASSERT2(pNextSrcW >= pSrcW); +#undef ASSERT2 + + // Null terminate the converted string + + if ( pNextDstA - pDstA > nDst ) + *(pDstA + nDst) = '\0'; + else + *pNextDstA = '\0'; + } + return pDstA; + } + + inline PUSTR StdCodeCvt(PUSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + const std::locale& loc=std::locale()) + { + return (PUSTR)StdCodeCvt((PSTR)pDstA, nDst, pSrcW, nSrc, loc); + } + +#endif + + + +// Unicode/MBCS conversion macros are only available on implementations of +// the "C" library that have the non-standard _alloca function. As far as I +// know that's only Microsoft's though I've heard that the function exists +// elsewhere. + +#if defined(SS_ALLOCA) && !defined SS_NO_CONVERSION + + #include // needed for _alloca + + // Define our conversion macros to look exactly like Microsoft's to + // facilitate using this stuff both with and without MFC/ATL + + #ifdef _CONVERSION_USES_THREAD_LOCALE + + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=GetACP(); \ + _acp; PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=GetACP();\ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)),\ + StdCodeCvt((PWSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pa, _cvt, _acp))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = sslen(_pw), \ + StdCodeCvt((LPSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pw, _cvt, _acp))) + #else + + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=CP_ACP; _acp;\ + PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=CP_ACP; \ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)),\ + StdCodeCvt((PWSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pa, _cvt))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = (sslen(_pw)),\ + StdCodeCvt((LPSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pw, _cvt))) + #endif + + #define SSA2CW(pa) ((PCWSTR)SSA2W((pa))) + #define SSW2CA(pw) ((PCSTR)SSW2A((pw))) + + #ifdef UNICODE + #define SST2A SSW2A + #define SSA2T SSA2W + #define SST2CA SSW2CA + #define SSA2CT SSA2CW + // (Did you get a compiler error here about not being able to convert + // PTSTR into PWSTR? Then your _UNICODE and UNICODE flags are messed + // up. Best bet: #define BOTH macros before including any MS headers.) + inline PWSTR SST2W(PTSTR p) { return p; } + inline PTSTR SSW2T(PWSTR p) { return p; } + inline PCWSTR SST2CW(PCTSTR p) { return p; } + inline PCTSTR SSW2CT(PCWSTR p) { return p; } + #else + #define SST2W SSA2W + #define SSW2T SSW2A + #define SST2CW SSA2CW + #define SSW2CT SSW2CA + inline PSTR SST2A(PTSTR p) { return p; } + inline PTSTR SSA2T(PSTR p) { return p; } + inline PCSTR SST2CA(PCTSTR p) { return p; } + inline PCTSTR SSA2CT(PCSTR p) { return p; } + #endif // #ifdef UNICODE + + #if defined(UNICODE) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #elif defined(OLE2ANSI) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #else + //CharNextW doesn't work on Win95 so we use this + #define SST2COLE(pa) SSA2CW((pa)) + #define SST2OLE(pa) SSA2W((pa)) + #define SSOLE2CT(po) SSW2CA((po)) + #define SSOLE2T(po) SSW2A((po)) + #endif + + #ifdef OLE2ANSI + #define SSW2OLE SSW2A + #define SSOLE2W SSA2W + #define SSW2COLE SSW2CA + #define SSOLE2CW SSA2CW + inline POLESTR SSA2OLE(PSTR p) { return p; } + inline PSTR SSOLE2A(POLESTR p) { return p; } + inline PCOLESTR SSA2COLE(PCSTR p) { return p; } + inline PCSTR SSOLE2CA(PCOLESTR p){ return p; } + #else + #define SSA2OLE SSA2W + #define SSOLE2A SSW2A + #define SSA2COLE SSA2CW + #define SSOLE2CA SSW2CA + inline POLESTR SSW2OLE(PWSTR p) { return p; } + inline PWSTR SSOLE2W(POLESTR p) { return p; } + inline PCOLESTR SSW2COLE(PCWSTR p) { return p; } + inline PCWSTR SSOLE2CW(PCOLESTR p){ return p; } + #endif + + // Above we've defined macros that look like MS' but all have + // an 'SS' prefix. Now we need the real macros. We'll either + // get them from the macros above or from MFC/ATL. + + #if defined (USES_CONVERSION) + + #define _NO_STDCONVERSION // just to be consistent + + #else + + #ifdef _MFC_VER + + #include + #define _NO_STDCONVERSION // just to be consistent + + #else + + #define USES_CONVERSION SSCVT + #define A2CW SSA2CW + #define W2CA SSW2CA + #define T2A SST2A + #define A2T SSA2T + #define T2W SST2W + #define W2T SSW2T + #define T2CA SST2CA + #define A2CT SSA2CT + #define T2CW SST2CW + #define W2CT SSW2CT + #define ocslen sslen + #define ocscpy sscpy + #define T2COLE SST2COLE + #define OLE2CT SSOLE2CT + #define T2OLE SST2COLE + #define OLE2T SSOLE2CT + #define A2OLE SSA2OLE + #define OLE2A SSOLE2A + #define W2OLE SSW2OLE + #define OLE2W SSOLE2W + #define A2COLE SSA2COLE + #define OLE2CA SSOLE2CA + #define W2COLE SSW2COLE + #define OLE2CW SSOLE2CW + + #endif // #ifdef _MFC_VER + #endif // #ifndef USES_CONVERSION +#endif // #ifndef SS_NO_CONVERSION + +// Define ostring - generic name for std::basic_string + +#if !defined(ostring) && !defined(OSTRING_DEFINED) + typedef std::basic_string ostring; + #define OSTRING_DEFINED +#endif + +// StdCodeCvt when there's no conversion to be done +template +inline T* StdCodeCvt(T* pDst, int nDst, const T* pSrc, int nSrc) +{ + int nChars = SSMIN(nSrc, nDst); + + if ( nChars > 0 ) + { + pDst[0] = '\0'; + std::basic_string::traits_type::copy(pDst, pSrc, nChars); +// std::char_traits::copy(pDst, pSrc, nChars); + pDst[nChars] = '\0'; + } + + return pDst; +} +inline PSTR StdCodeCvt(PSTR pDst, int nDst, PCUSTR pSrc, int nSrc) +{ + return StdCodeCvt(pDst, nDst, (PCSTR)pSrc, nSrc); +} +inline PUSTR StdCodeCvt(PUSTR pDst, int nDst, PCSTR pSrc, int nSrc) +{ + return (PUSTR)StdCodeCvt((PSTR)pDst, nDst, pSrc, nSrc); +} + +// Define tstring -- generic name for std::basic_string + +#if !defined(tstring) && !defined(TSTRING_DEFINED) + typedef std::basic_string tstring; + #define TSTRING_DEFINED +#endif + +// a very shorthand way of applying the fix for KB problem Q172398 +// (basic_string assignment bug) + +#if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + #define Q172398(x) (x).erase() +#else + #define Q172398(x) +#endif + +// ============================================================================= +// INLINE FUNCTIONS ON WHICH CSTDSTRING RELIES +// +// Usually for generic text mapping, we rely on preprocessor macro definitions +// to map to string functions. However the CStdStr<> template cannot use +// macro-based generic text mappings because its character types do not get +// resolved until template processing which comes AFTER macro processing. In +// other words, the preprocessor macro UNICODE is of little help to us in the +// CStdStr template +// +// Therefore, to keep the CStdStr declaration simple, we have these inline +// functions. The template calls them often. Since they are inline (and NOT +// exported when this is built as a DLL), they will probably be resolved away +// to nothing. +// +// Without these functions, the CStdStr<> template would probably have to broken +// out into two, almost identical classes. Either that or it would be a huge, +// convoluted mess, with tons of "if" statements all over the place checking the +// size of template parameter CT. +// ============================================================================= + +#ifdef SS_NO_LOCALE + + // -------------------------------------------------------------------------- + // Win32 GetStringTypeEx wrappers + // -------------------------------------------------------------------------- + inline bool wsGetStringType(LCID lc, DWORD dwT, PCSTR pS, int nSize, + WORD* pWd) + { + return FALSE != GetStringTypeExA(lc, dwT, pS, nSize, pWd); + } + inline bool wsGetStringType(LCID lc, DWORD dwT, PCWSTR pS, int nSize, + WORD* pWd) + { + return FALSE != GetStringTypeExW(lc, dwT, pS, nSize, pWd); + } + + + template + inline bool ssisspace (CT t) + { + WORD toYourMother; + return wsGetStringType(GetThreadLocale(), CT_CTYPE1, &t, 1, &toYourMother) + && 0 != (C1_BLANK & toYourMother); + } + +#endif + +// If they defined SS_NO_REFCOUNT, then we must convert all assignments + +#if defined (_MSC_VER) && (_MSC_VER < 1300) + #ifdef SS_NO_REFCOUNT + #define SSREF(x) (x).c_str() + #else + #define SSREF(x) (x) + #endif +#else + #define SSREF(x) (x) +#endif + +// ----------------------------------------------------------------------------- +// sslen: strlen/wcslen wrappers +// ----------------------------------------------------------------------------- +template inline int sslen(const CT* pT) +{ + return 0 == pT ? 0 : (int)std::basic_string::traits_type::length(pT); +// return 0 == pT ? 0 : std::char_traits::length(pT); +} +inline SS_NOTHROW int sslen(const std::string& s) +{ + return static_cast(s.length()); +} +inline SS_NOTHROW int sslen(const std::wstring& s) +{ + return static_cast(s.length()); +} + +// ----------------------------------------------------------------------------- +// sstolower/sstoupper -- convert characters to upper/lower case +// ----------------------------------------------------------------------------- + +#ifdef SS_NO_LOCALE + inline char sstoupper(char ch) { return (char)::toupper(ch); } + inline wchar_t sstoupper(wchar_t ch){ return (wchar_t)::towupper(ch); } + inline char sstolower(char ch) { return (char)::tolower(ch); } + inline wchar_t sstolower(wchar_t ch){ return (wchar_t)::tolower(ch); } +#else + template + inline CT sstolower(const CT& t, const std::locale& loc = std::locale()) + { + return std::tolower(t, loc); + } + template + inline CT sstoupper(const CT& t, const std::locale& loc = std::locale()) + { + return std::toupper(t, loc); + } +#endif + +// ----------------------------------------------------------------------------- +// ssasn: assignment functions -- assign "sSrc" to "sDst" +// ----------------------------------------------------------------------------- +typedef std::string::size_type SS_SIZETYPE; // just for shorthand, really +typedef std::string::pointer SS_PTRTYPE; +typedef std::wstring::size_type SW_SIZETYPE; +typedef std::wstring::pointer SW_PTRTYPE; + + +template +inline void ssasn(std::basic_string& sDst, const std::basic_string& sSrc) +{ + if ( sDst.c_str() != sSrc.c_str() ) + { + sDst.erase(); + sDst.assign(SSREF(sSrc)); + } +} +template +inline void ssasn(std::basic_string& sDst, const T *pA) +{ + // Watch out for NULLs, as always. + + if ( 0 == pA ) + { + sDst.erase(); + } + + // If pA actually points to part of sDst, we must NOT erase(), but + // rather take a substring + + else if ( pA >= sDst.c_str() && pA <= sDst.c_str() + sDst.size() ) + { + sDst =sDst.substr(static_cast::size_type>(pA-sDst.c_str())); + } + + // Otherwise (most cases) apply the assignment bug fix, if applicable + // and do the assignment + + else + { + Q172398(sDst); + sDst.assign(pA); + } +} +inline void ssasn(std::string& sDst, const std::wstring& sSrc) +{ + if ( sSrc.empty() ) + { + sDst.erase(); + } + else + { + int nDst = static_cast(sSrc.size()); + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + // In MBCS builds, we don't know how long the destination string will be. + nDst = static_cast(static_cast(nDst) * 1.3); + sDst.resize(nDst+1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), static_cast(sSrc.size())); + sDst.resize(sslen(szCvt)); +#else + sDst.resize(nDst+1); + StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), static_cast(sSrc.size())); + sDst.resize(sSrc.size()); +#endif + } +} +inline void ssasn(std::string& sDst, PCWSTR pW) +{ + int nSrc = sslen(pW); + if ( nSrc > 0 ) + { + int nSrc = sslen(pW); + int nDst = nSrc; + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + nDst = static_cast(static_cast(nDst) * 1.3); + // In MBCS builds, we don't know how long the destination string will be. + sDst.resize(nDst + 1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + pW, nSrc); + sDst.resize(sslen(szCvt)); +#else + sDst.resize(nDst + 1); + StdCodeCvt(const_cast(sDst.data()), nDst, pW, nSrc); + sDst.resize(nDst); +#endif + } + else + { + sDst.erase(); + } +} +inline void ssasn(std::string& sDst, const int nNull) +{ + //UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(""); +} +#undef StrSizeType +inline void ssasn(std::wstring& sDst, const std::string& sSrc) +{ + if ( sSrc.empty() ) + { + sDst.erase(); + } + else + { + int nSrc = static_cast(sSrc.size()); + int nDst = nSrc; + + sDst.resize(nSrc+1); + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), nSrc); + + sDst.resize(sslen(szCvt)); + } +} +inline void ssasn(std::wstring& sDst, PCSTR pA) +{ + int nSrc = sslen(pA); + + if ( 0 == nSrc ) + { + sDst.erase(); + } + else + { + int nDst = nSrc; + sDst.resize(nDst+1); + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, pA, + nSrc); + + sDst.resize(sslen(szCvt)); + } +} +inline void ssasn(std::wstring& sDst, const int nNull) +{ + //UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(L""); +} + +// ----------------------------------------------------------------------------- +// ssadd: string object concatenation -- add second argument to first +// ----------------------------------------------------------------------------- +inline void ssadd(std::string& sDst, const std::wstring& sSrc) +{ + int nSrc = static_cast(sSrc.size()); + + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + int nAdd = nSrc; + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + nAdd = static_cast(static_cast(nAdd) * 1.3); + sDst.resize(nDst+nAdd+1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nAdd, sSrc.c_str(), nSrc); + sDst.resize(nDst + sslen(szCvt)); +#else + sDst.resize(nDst+nAdd+1); + StdCodeCvt(const_cast(sDst.data()+nDst), nAdd, sSrc.c_str(), nSrc); + sDst.resize(nDst + nAdd); +#endif + } +} +template +inline void ssadd(typename std::basic_string& sDst, const typename std::basic_string& sSrc) +{ + sDst += sSrc; +} +inline void ssadd(std::string& sDst, PCWSTR pW) +{ + int nSrc = sslen(pW); + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + int nAdd = nSrc; + +#ifdef SS_MBCS + nAdd = static_cast(static_cast(nAdd) * 1.3); + sDst.resize(nDst + nAdd + 1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nAdd, pW, nSrc); + sDst.resize(nDst + sslen(szCvt)); +#else + sDst.resize(nDst + nAdd + 1); + StdCodeCvt(const_cast(sDst.data()+nDst), nAdd, pW, nSrc); + sDst.resize(nDst + nSrc); +#endif + } +} +template +inline void ssadd(typename std::basic_string& sDst, const T *pA) +{ + if ( pA ) + { + // If the string being added is our internal string or a part of our + // internal string, then we must NOT do any reallocation without + // first copying that string to another object (since we're using a + // direct pointer) + + if ( pA >= sDst.c_str() && pA <= sDst.c_str()+sDst.length()) + { + if ( sDst.capacity() <= sDst.size()+sslen(pA) ) + sDst.append(std::basic_string(pA)); + else + sDst.append(pA); + } + else + { + sDst.append(pA); + } + } +} +inline void ssadd(std::wstring& sDst, const std::string& sSrc) +{ + if ( !sSrc.empty() ) + { + int nSrc = static_cast(sSrc.size()); + int nDst = static_cast(sDst.size()); + + sDst.resize(nDst + nSrc + 1); +#ifdef SS_MBCS + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nSrc, sSrc.c_str(), nSrc+1); + sDst.resize(nDst + sslen(szCvt)); +#else + StdCodeCvt(const_cast(sDst.data()+nDst), nSrc, sSrc.c_str(), nSrc+1); + sDst.resize(nDst + nSrc); +#endif + } +} +inline void ssadd(std::wstring& sDst, PCSTR pA) +{ + int nSrc = sslen(pA); + + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + + sDst.resize(nDst + nSrc + 1); +#ifdef SS_MBCS + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nSrc, pA, nSrc+1); + sDst.resize(nDst + sslen(szCvt)); +#else + StdCodeCvt(const_cast(sDst.data()+nDst), nSrc, pA, nSrc+1); + sDst.resize(nDst + nSrc); +#endif + } +} + +// ----------------------------------------------------------------------------- +// sscmp: comparison (case sensitive, not affected by locale) +// ----------------------------------------------------------------------------- +template +inline int sscmp(const CT* pA1, const CT* pA2) +{ + CT f; + CT l; + + do + { + f = *(pA1++); + l = *(pA2++); + } while ( (f) && (f == l) ); + + return (int)(f - l); +} + +// ----------------------------------------------------------------------------- +// ssicmp: comparison (case INsensitive, not affected by locale) +// ----------------------------------------------------------------------------- +template +inline int ssicmp(const CT* pA1, const CT* pA2) +{ + // Using the "C" locale = "not affected by locale" + + std::locale loc = std::locale::classic(); + const std::ctype& ct = SS_USE_FACET(loc, std::ctype); + CT f; + CT l; + + do + { + f = ct.tolower(*(pA1++)); + l = ct.tolower(*(pA2++)); + } while ( (f) && (f == l) ); + + return (int)(f - l); +} + +// ----------------------------------------------------------------------------- +// ssupr/sslwr: Uppercase/Lowercase conversion functions +// ----------------------------------------------------------------------------- + +template +inline void sslwr(CT* pT, size_t nLen, const std::locale& loc=std::locale()) +{ + SS_USE_FACET(loc, std::ctype).tolower(pT, pT+nLen); +} +template +inline void ssupr(CT* pT, size_t nLen, const std::locale& loc=std::locale()) +{ + SS_USE_FACET(loc, std::ctype).toupper(pT, pT+nLen); +} + +// ----------------------------------------------------------------------------- +// vsprintf/vswprintf or _vsnprintf/_vsnwprintf equivalents. In standard +// builds we can't use _vsnprintf/_vsnwsprintf because they're MS extensions. +// +// ----------------------------------------------------------------------------- +// Borland's headers put some ANSI "C" functions in the 'std' namespace. +// Promote them to the global namespace so we can use them here. + +#if defined(__BORLANDC__) + using std::vsprintf; + using std::vswprintf; +#endif + + // GNU is supposed to have vsnprintf and vsnwprintf. But only the newer + // distributions do. + +#if defined(__GNUC__) + + inline int ssvsprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + return vsnprintf(pA, nCount, pFmtA, vl); + } + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + return vswprintf(pW, nCount, pFmtW, vl); + } + + // Microsofties can use +#elif defined(_MSC_VER) && !defined(SS_ANSI) + + inline int ssvsprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + return _vsnprintf(pA, nCount, pFmtA, vl); + } + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + return _vsnwprintf(pW, nCount, pFmtW, vl); + } + +#elif defined (SS_DANGEROUS_FORMAT) // ignore buffer size parameter if needed? + + inline int ssvsprintf(PSTR pA, size_t /*nCount*/, PCSTR pFmtA, va_list vl) + { + return vsprintf(pA, pFmtA, vl); + } + + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + // JMO: Some distributions of the "C" have a version of vswprintf that + // takes 3 arguments (e.g. Microsoft, Borland, GNU). Others have a + // version which takes 4 arguments (an extra "count" argument in the + // second position. The best stab I can take at this so far is that if + // you are NOT running with MS, Borland, or GNU, then I'll assume you + // have the version that takes 4 arguments. + // + // I'm sure that these checks don't catch every platform correctly so if + // you get compiler errors on one of the lines immediately below, it's + // probably because your implemntation takes a different number of + // arguments. You can comment out the offending line (and use the + // alternate version) or you can figure out what compiler flag to check + // and add that preprocessor check in. Regardless, if you get an error + // on these lines, I'd sure like to hear from you about it. + // + // Thanks to Ronny Schulz for the SGI-specific checks here. + +// #if !defined(__MWERKS__) && !defined(__SUNPRO_CC_COMPAT) && !defined(__SUNPRO_CC) + #if !defined(_MSC_VER) \ + && !defined (__BORLANDC__) \ + && !defined(__GNUC__) \ + && !defined(__sgi) + + return vswprintf(pW, nCount, pFmtW, vl); + + // suddenly with the current SGI 7.3 compiler there is no such function as + // vswprintf and the substitute needs explicit casts to compile + + #elif defined(__sgi) + + nCount; + return vsprintf( (char *)pW, (char *)pFmtW, vl); + + #else + + nCount; + return vswprintf(pW, pFmtW, vl); + + #endif + + } + +#endif + + // GOT COMPILER PROBLEMS HERE? + // --------------------------- + // Does your compiler choke on one or more of the following 2 functions? It + // probably means that you don't have have either vsnprintf or vsnwprintf in + // your version of the CRT. This is understandable since neither is an ANSI + // "C" function. However it still leaves you in a dilemma. In order to make + // this code build, you're going to have to to use some non-length-checked + // formatting functions that every CRT has: vsprintf and vswprintf. + // + // This is very dangerous. With the proper erroneous (or malicious) code, it + // can lead to buffer overlows and crashing your PC. Use at your own risk + // In order to use them, just #define SS_DANGEROUS_FORMAT at the top of + // this file. + // + // Even THEN you might not be all the way home due to some non-conforming + // distributions. More on this in the comments below. + + inline int ssnprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + #ifdef _MSC_VER + return _vsnprintf(pA, nCount, pFmtA, vl); + #else + return vsnprintf(pA, nCount, pFmtA, vl); + #endif + } + inline int ssnprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + #ifdef _MSC_VER + return _vsnwprintf(pW, nCount, pFmtW, vl); + #else + return vswprintf(pW, nCount, pFmtW, vl); + #endif + } + + + + +// ----------------------------------------------------------------------------- +// ssload: Type safe, overloaded ::LoadString wrappers +// There is no equivalent of these in non-Win32-specific builds. However, I'm +// thinking that with the message facet, there might eventually be one +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline int ssload(HMODULE hInst, UINT uId, PSTR pBuf, int nMax) + { + return ::LoadStringA(hInst, uId, pBuf, nMax); + } + inline int ssload(HMODULE hInst, UINT uId, PWSTR pBuf, int nMax) + { + return ::LoadStringW(hInst, uId, pBuf, nMax); + } +#if defined ( _MSC_VER ) && ( _MSC_VER >= 1500 ) + inline int ssload(HMODULE hInst, UINT uId, uint16_t *pBuf, int nMax) + { + return 0; + } + inline int ssload(HMODULE hInst, UINT uId, uint32_t *pBuf, int nMax) + { + return 0; + } +#endif +#endif + + +// ----------------------------------------------------------------------------- +// sscoll/ssicoll: Collation wrappers +// Note -- with MSVC I have reversed the arguments order here because the +// functions appear to return the opposite of what they should +// ----------------------------------------------------------------------------- +#ifndef SS_NO_LOCALE +template +inline int sscoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::collate& coll = + SS_USE_FACET(std::locale(), std::collate); + + return coll.compare(sz2, sz2+nLen2, sz1, sz1+nLen1); +} +template +inline int ssicoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::locale loc; + const std::collate& coll = SS_USE_FACET(loc, std::collate); + + // Some implementations seem to have trouble using the collate<> + // facet typedefs so we'll just default to basic_string and hope + // that's what the collate facet uses (which it generally should) + +// std::collate::string_type s1(sz1); +// std::collate::string_type s2(sz2); + const std::basic_string sEmpty; + std::basic_string s1(sz1 ? sz1 : sEmpty.c_str()); + std::basic_string s2(sz2 ? sz2 : sEmpty.c_str()); + + sslwr(const_cast(s1.c_str()), nLen1, loc); + sslwr(const_cast(s2.c_str()), nLen2, loc); + return coll.compare(s2.c_str(), s2.c_str()+nLen2, + s1.c_str(), s1.c_str()+nLen1); +} +#endif + + +// ----------------------------------------------------------------------------- +// ssfmtmsg: FormatMessage equivalents. Needed because I added a CString facade +// Again -- no equivalent of these on non-Win32 builds but their might one day +// be one if the message facet gets implemented +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageA(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PWSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageW(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } +#else +#endif + + + +// FUNCTION: sscpy. Copies up to 'nMax' characters from pSrc to pDst. +// ----------------------------------------------------------------------------- +// FUNCTION: sscpy +// inline int sscpy(PSTR pDst, PCSTR pSrc, int nMax=-1); +// inline int sscpy(PUSTR pDst, PCSTR pSrc, int nMax=-1) +// inline int sscpy(PSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCSTR pSrc, int nMax=-1); +// +// DESCRIPTION: +// This function is very much (but not exactly) like strcpy. These +// overloads simplify copying one C-style string into another by allowing +// the caller to specify two different types of strings if necessary. +// +// The strings must NOT overlap +// +// "Character" is expressed in terms of the destination string, not +// the source. If no 'nMax' argument is supplied, then the number of +// characters copied will be sslen(pSrc). A NULL terminator will +// also be added so pDst must actually be big enough to hold nMax+1 +// characters. The return value is the number of characters copied, +// not including the NULL terminator. +// +// PARAMETERS: +// pSrc - the string to be copied FROM. May be a char based string, an +// MBCS string (in Win32 builds) or a wide string (wchar_t). +// pSrc - the string to be copied TO. Also may be either MBCS or wide +// nMax - the maximum number of characters to be copied into szDest. Note +// that this is expressed in whatever a "character" means to pDst. +// If pDst is a wchar_t type string than this will be the maximum +// number of wchar_ts that my be copied. The pDst string must be +// large enough to hold least nMaxChars+1 characters. +// If the caller supplies no argument for nMax this is a signal to +// the routine to copy all the characters in pSrc, regardless of +// how long it is. +// +// RETURN VALUE: none +// ----------------------------------------------------------------------------- + +template +inline int sscpycvt(CT1* pDst, const CT2* pSrc, int nMax) +{ + // Note -- we assume pDst is big enough to hold pSrc. If not, we're in + // big trouble. No bounds checking. Caveat emptor. + + int nSrc = sslen(pSrc); + + const CT1* szCvt = StdCodeCvt(pDst, nMax, pSrc, nSrc); + + // If we're copying the same size characters, then all the "code convert" + // just did was basically memcpy so the #of characters copied is the same + // as the number requested. I should probably specialize this function + // template to achieve this purpose as it is silly to do a runtime check + // of a fact known at compile time. I'll get around to it. + + return sslen(szCvt); +} + +template +inline int sscpycvt(T* pDst, const T* pSrc, int nMax) +{ + int nCount = nMax; + for (; nCount > 0 && *pSrc; ++pSrc, ++pDst, --nCount) + std::basic_string::traits_type::assign(*pDst, *pSrc); + + *pDst = 0; + return nMax - nCount; +} + +inline int sscpycvt(PWSTR pDst, PCSTR pSrc, int nMax) +{ + // Note -- we assume pDst is big enough to hold pSrc. If not, we're in + // big trouble. No bounds checking. Caveat emptor. + + const PWSTR szCvt = StdCodeCvt(pDst, nMax, pSrc, nMax); + return sslen(szCvt); +} + +template +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax, int nLen) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, nLen)); +} +template +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, sslen(pSrc))); +} +template +inline int sscpy(CT1* pDst, const CT2* pSrc) +{ + return sscpycvt(pDst, pSrc, sslen(pSrc)); +} +template +inline int sscpy(CT1* pDst, const std::basic_string& sSrc, int nMax) +{ + return sscpycvt(pDst, sSrc.c_str(), SSMIN(nMax, (int)sSrc.length())); +} +template +inline int sscpy(CT1* pDst, const std::basic_string& sSrc) +{ + return sscpycvt(pDst, sSrc.c_str(), (int)sSrc.length()); +} + +#ifdef SS_INC_COMDEF + template + inline int sscpy(CT1* pDst, const _bstr_t& bs, int nMax) + { + return sscpycvt(pDst, static_cast(bs), + SSMIN(nMax, static_cast(bs.length()))); + } + template + inline int sscpy(CT1* pDst, const _bstr_t& bs) + { + return sscpy(pDst, bs, static_cast(bs.length())); + } +#endif + + +// ----------------------------------------------------------------------------- +// Functional objects for changing case. They also let you pass locales +// ----------------------------------------------------------------------------- + +#ifdef SS_NO_LOCALE + template + struct SSToUpper : public std::unary_function + { + inline CT operator()(const CT& t) const + { + return sstoupper(t); + } + }; + template + struct SSToLower : public std::unary_function + { + inline CT operator()(const CT& t) const + { + return sstolower(t); + } + }; +#else + template + struct SSToUpper : public std::binary_function + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstoupper(t, loc); + } + }; + template + struct SSToLower : public std::binary_function + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstolower(t, loc); + } + }; +#endif + +// This struct is used for TrimRight() and TrimLeft() function implementations. +//template +//struct NotSpace : public std::unary_function +//{ +// const std::locale& loc; +// inline NotSpace(const std::locale& locArg) : loc(locArg) {} +// inline bool operator() (CT t) { return !std::isspace(t, loc); } +//}; +template +struct NotSpace : public std::unary_function +{ + // DINKUMWARE BUG: + // Note -- using std::isspace in a COM DLL gives us access violations + // because it causes the dynamic addition of a function to be called + // when the library shuts down. Unfortunately the list is maintained + // in DLL memory but the function is in static memory. So the COM DLL + // goes away along with the function that was supposed to be called, + // and then later when the DLL CRT shuts down it unloads the list and + // tries to call the long-gone function. + // This is DinkumWare's implementation problem. If you encounter this + // problem, you may replace the calls here with good old isspace() and + // iswspace() from the CRT unless they specify SS_ANSI + +#ifdef SS_NO_LOCALE + + bool operator() (CT t) const { return !ssisspace(t); } + +#else + const std::locale loc; + NotSpace(const std::locale& locArg=std::locale()) : loc(locArg) {} + bool operator() (CT t) const { return !std::isspace(t, loc); } +#endif +}; + + + + +// Now we can define the template (finally!) +// ============================================================================= +// TEMPLATE: CStdStr +// template class CStdStr : public std::basic_string +// +// REMARKS: +// This template derives from basic_string and adds some MFC CString- +// like functionality +// +// Basically, this is my attempt to make Standard C++ library strings as +// easy to use as the MFC CString class. +// +// Note that although this is a template, it makes the assumption that the +// template argument (CT, the character type) is either char or wchar_t. +// ============================================================================= + +//#define CStdStr _SS // avoid compiler warning 4786 + +// template ARG& FmtArg(ARG& arg) { return arg; } +// PCSTR FmtArg(const std::string& arg) { return arg.c_str(); } +// PCWSTR FmtArg(const std::wstring& arg) { return arg.c_str(); } + +template +struct FmtArg +{ + explicit FmtArg(const ARG& arg) : a_(arg) {} + const ARG& operator()() const { return a_; } + const ARG& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; + +template +class CStdStr : public std::basic_string +{ + // Typedefs for shorter names. Using these names also appears to help + // us avoid some ambiguities that otherwise arise on some platforms + + #define MYBASE std::basic_string // my base class + //typedef typename std::basic_string MYBASE; // my base class + typedef CStdStr MYTYPE; // myself + typedef typename MYBASE::const_pointer PCMYSTR; // PCSTR or PCWSTR + typedef typename MYBASE::pointer PMYSTR; // PSTR or PWSTR + typedef typename MYBASE::iterator MYITER; // my iterator type + typedef typename MYBASE::const_iterator MYCITER; // you get the idea... + typedef typename MYBASE::reverse_iterator MYRITER; + typedef typename MYBASE::size_type MYSIZE; + typedef typename MYBASE::value_type MYVAL; + typedef typename MYBASE::allocator_type MYALLOC; + +public: + // shorthand conversion from PCTSTR to string resource ID + #define SSRES(pctstr) LOWORD(reinterpret_cast(pctstr)) + + bool TryLoad(const void* pT) + { + bool bLoaded = false; + +#if defined(SS_WIN32) && !defined(SS_ANSI) + if ( ( pT != NULL ) && SS_IS_INTRESOURCE(pT) ) + { + UINT nId = LOWORD(reinterpret_cast(pT)); + if ( !LoadString(nId) ) + { + TRACE(_T("Can't load string %u\n"), SSRES(pT)); + } + bLoaded = true; + } +#endif + + return bLoaded; + } + + + // CStdStr inline constructors + CStdStr() + { + } + + CStdStr(const MYTYPE& str) : MYBASE(SSREF(str)) + { + } + + CStdStr(const std::string& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(const std::wstring& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(PCMYSTR pT, MYSIZE n) : MYBASE(pT, n) + { + } + +#ifdef SS_UNSIGNED + CStdStr(PCUSTR pU) + { + *this = reinterpret_cast(pU); + } +#endif + + CStdStr(PCSTR pA) + { + #ifdef SS_ANSI + *this = pA; + #else + if ( !TryLoad(pA) ) + *this = pA; + #endif + } + + CStdStr(PCWSTR pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(uint16_t* pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(uint32_t* pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(MYCITER first, MYCITER last) + : MYBASE(first, last) + { + } + + CStdStr(MYSIZE nSize, MYVAL ch, const MYALLOC& al=MYALLOC()) + : MYBASE(nSize, ch, al) + { + } + + #ifdef SS_INC_COMDEF + CStdStr(const _bstr_t& bstr) + { + if ( bstr.length() > 0 ) + this->append(static_cast(bstr), bstr.length()); + } + #endif + + // CStdStr inline assignment operators -- the ssasn function now takes care + // of fixing the MSVC assignment bug (see knowledge base article Q172398). + MYTYPE& operator=(const MYTYPE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::string& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::wstring& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(PCSTR pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(PCWSTR pW) + { + ssasn(*this, pW); + return *this; + } + +#ifdef SS_UNSIGNED + MYTYPE& operator=(PCUSTR pU) + { + ssasn(*this, reinterpret_cast(pU)); + return *this; + } +#endif + + MYTYPE& operator=(uint16_t* pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(uint32_t* pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(CT t) + { + Q172398(*this); + this->assign(1, t); + return *this; + } + + #ifdef SS_INC_COMDEF + MYTYPE& operator=(const _bstr_t& bstr) + { + if ( bstr.length() > 0 ) + { + this->assign(static_cast(bstr), bstr.length()); + return *this; + } + else + { + this->erase(); + return *this; + } + } + #endif + + + // Overloads also needed to fix the MSVC assignment bug (KB: Q172398) + // *** Thanks to Pete The Plumber for catching this one *** + // They also are compiled if you have explicitly turned off refcounting + #if ( defined(_MSC_VER) && ( _MSC_VER < 1200 ) ) || defined(SS_NO_REFCOUNT) + + MYTYPE& assign(const MYTYPE& str) + { + Q172398(*this); + sscpy(GetBuffer(str.size()+1), SSREF(str)); + this->ReleaseBuffer(str.size()); + return *this; + } + + MYTYPE& assign(const MYTYPE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Poll�hne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + MYTYPE strTemp(str.c_str()+nStart, nChars); + Q172398(*this); + this->assign(strTemp); + return *this; + } + + MYTYPE& assign(const MYBASE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& assign(const MYBASE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Poll�hne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + + // Watch out for assignment to self + + if ( this == &str ) + { + MYTYPE strTemp(str.c_str() + nStart, nChars); + static_cast(this)->assign(strTemp); + } + else + { + Q172398(*this); + static_cast(this)->assign(str.c_str()+nStart, nChars); + } + return *this; + } + + MYTYPE& assign(const CT* pC, MYSIZE nChars) + { + // Q172398 only fix -- erase before assigning, but not if we're + // assigning from our own buffer + + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + if ( !this->empty() && + ( pC < this->data() || pC > this->data() + this->capacity() ) ) + { + this->erase(); + } + #endif + Q172398(*this); + static_cast(this)->assign(pC, nChars); + return *this; + } + + MYTYPE& assign(MYSIZE nChars, MYVAL val) + { + Q172398(*this); + static_cast(this)->assign(nChars, val); + return *this; + } + + MYTYPE& assign(const CT* pT) + { + return this->assign(pT, MYBASE::traits_type::length(pT)); + } + + MYTYPE& assign(MYCITER iterFirst, MYCITER iterLast) + { + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + // Q172398 fix. don't call erase() if we're assigning from ourself + if ( iterFirst < this->begin() || + iterFirst > this->begin() + this->size() ) + { + this->erase() + } + #endif + this->replace(this->begin(), this->end(), iterFirst, iterLast); + return *this; + } + #endif + + + // ------------------------------------------------------------------------- + // CStdStr inline concatenation. + // ------------------------------------------------------------------------- + MYTYPE& operator+=(const MYTYPE& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::string& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::wstring& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(PCSTR pA) + { + ssadd(*this, pA); + return *this; + } + + MYTYPE& operator+=(PCWSTR pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(uint16_t* pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(uint32_t* pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(CT t) + { + this->append(1, t); + return *this; + } + #ifdef SS_INC_COMDEF // if we have _bstr_t, define a += for it too. + MYTYPE& operator+=(const _bstr_t& bstr) + { + return this->operator+=(static_cast(bstr)); + } + #endif + + + // ------------------------------------------------------------------------- + // Case changing functions + // ------------------------------------------------------------------------- + + MYTYPE& ToUpper(const std::locale& loc=std::locale()) + { + // Note -- if there are any MBCS character sets in which the lowercase + // form a character takes up a different number of bytes than the + // uppercase form, this would probably not work... + + std::transform(this->begin(), + this->end(), + this->begin(), +#ifdef SS_NO_LOCALE + SSToUpper()); +#else + std::bind2nd(SSToUpper(), loc)); +#endif + + // ...but if it were, this would probably work better. Also, this way + // seems to be a bit faster when anything other then the "C" locale is + // used... + +// if ( !empty() ) +// { +// ssupr(this->GetBuf(), this->size(), loc); +// this->RelBuf(); +// } + + return *this; + } + + MYTYPE& ToLower(const std::locale& loc=std::locale()) + { + // Note -- if there are any MBCS character sets in which the lowercase + // form a character takes up a different number of bytes than the + // uppercase form, this would probably not work... + + std::transform(this->begin(), + this->end(), + this->begin(), +#ifdef SS_NO_LOCALE + SSToLower()); +#else + std::bind2nd(SSToLower(), loc)); +#endif + + // ...but if it were, this would probably work better. Also, this way + // seems to be a bit faster when anything other then the "C" locale is + // used... + +// if ( !empty() ) +// { +// sslwr(this->GetBuf(), this->size(), loc); +// this->RelBuf(); +// } + return *this; + } + + + MYTYPE& Normalize() + { + return Trim().ToLower(); + } + + + // ------------------------------------------------------------------------- + // CStdStr -- Direct access to character buffer. In the MS' implementation, + // the at() function that we use here also calls _Freeze() providing us some + // protection from multithreading problems associated with ref-counting. + // In VC 7 and later, of course, the ref-counting stuff is gone. + // ------------------------------------------------------------------------- + + CT* GetBuf(int nMinLen=-1) + { + if ( static_cast(this->size()) < nMinLen ) + this->resize(static_cast(nMinLen)); + + return this->empty() ? const_cast(this->data()) : &(this->at(0)); + } + + CT* SetBuf(int nLen) + { + nLen = ( nLen > 0 ? nLen : 0 ); + if ( this->capacity() < 1 && nLen == 0 ) + this->resize(1); + + this->resize(static_cast(nLen)); + return const_cast(this->data()); + } + void RelBuf(int nNewLen=-1) + { + this->resize(static_cast(nNewLen > -1 ? nNewLen : + sslen(this->c_str()))); + } + + void BufferRel() { RelBuf(); } // backwards compatability + CT* Buffer() { return GetBuf(); } // backwards compatability + CT* BufferSet(int nLen) { return SetBuf(nLen);}// backwards compatability + + bool Equals(const CT* pT, bool bUseCase=false) const + { + return 0 == (bUseCase ? this->compare(pT) : ssicmp(this->c_str(), pT)); + } + + // ------------------------------------------------------------------------- + // FUNCTION: CStdStr::Load + // REMARKS: + // Loads string from resource specified by nID + // + // PARAMETERS: + // nID - resource Identifier. Purely a Win32 thing in this case + // + // RETURN VALUE: + // true if successful, false otherwise + // ------------------------------------------------------------------------- + +#ifndef SS_ANSI + + bool Load(UINT nId, HMODULE hModule=NULL) + { + bool bLoaded = false; // set to true of we succeed. + + #ifdef _MFC_VER // When in Rome (or MFC land)... + + // If they gave a resource handle, use it. Note - this is archaic + // and not really what I would recommend. But then again, in MFC + // land, you ought to be using CString for resources anyway since + // it walks the resource chain for you. + + HMODULE hModuleOld = NULL; + + if ( NULL != hModule ) + { + hModuleOld = AfxGetResourceHandle(); + AfxSetResourceHandle(hModule); + } + + // ...load the string + + CString strRes; + bLoaded = FALSE != strRes.LoadString(nId); + + // ...and if we set the resource handle, restore it. + + if ( NULL != hModuleOld ) + AfxSetResourceHandle(hModule); + + if ( bLoaded ) + *this = strRes; + + #else // otherwise make our own hackneyed version of CString's Load + + // Get the resource name and module handle + + if ( NULL == hModule ) + hModule = GetResourceHandle(); + + PCTSTR szName = MAKEINTRESOURCE((nId>>4)+1); // lifted + DWORD dwSize = 0; + + // No sense continuing if we can't find the resource + + HRSRC hrsrc = ::FindResource(hModule, szName, RT_STRING); + + if ( NULL == hrsrc ) + { + TRACE(_T("Cannot find resource %d: 0x%X"), nId, ::GetLastError()); + } + else if ( 0 == (dwSize = ::SizeofResource(hModule, hrsrc) / sizeof(CT))) + { + TRACE(_T("Cant get size of resource %d 0x%X\n"),nId,GetLastError()); + } + else + { + bLoaded = 0 != ssload(hModule, nId, GetBuf(dwSize), dwSize); + ReleaseBuffer(); + } + + #endif // #ifdef _MFC_VER + + if ( !bLoaded ) + TRACE(_T("String not loaded 0x%X\n"), ::GetLastError()); + + return bLoaded; + } + +#endif // #ifdef SS_ANSI + + // ------------------------------------------------------------------------- + // FUNCTION: CStdStr::Format + // void _cdecl Formst(CStdStringA& PCSTR szFormat, ...) + // void _cdecl Format(PCSTR szFormat); + // + // DESCRIPTION: + // This function does sprintf/wsprintf style formatting on CStdStringA + // objects. It looks a lot like MFC's CString::Format. Some people + // might even call this identical. Fortunately, these people are now + // dead... heh heh. + // + // PARAMETERS: + // nId - ID of string resource holding the format string + // szFormat - a PCSTR holding the format specifiers + // argList - a va_list holding the arguments for the format specifiers. + // + // RETURN VALUE: None. + // ------------------------------------------------------------------------- + // formatting (using wsprintf style formatting) + + // If they want a Format() function that safely handles string objects + // without casting + +#ifdef SS_SAFE_FORMAT + + // Question: Joe, you wacky coder you, why do you have so many overloads + // of the Format() function + // Answer: One reason only - CString compatability. In short, by making + // the Format() function a template this way, I can do strong typing + // and allow people to pass CStdString arguments as fillers for + // "%s" format specifiers without crashing their program! The downside + // is that I need to overload on the number of arguments. If you are + // passing more arguments than I have listed below in any of my + // overloads, just add another one. + // + // Yes, yes, this is really ugly. In essence what I am doing here is + // protecting people from a bad (and incorrect) programming practice + // that they should not be doing anyway. I am protecting them from + // themselves. Why am I doing this? Well, if you had any idea the + // number of times I've been emailed by people about this + // "incompatability" in my code, you wouldn't ask. + + void Fmt(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + FormatV(szFmt, argList); + va_end(argList); + } + +#ifndef SS_ANSI + + void Format(UINT nId) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + this->swap(strFmt); + } + template + void Format(UINT nId, const A1& v) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + Fmt(strFmt, FmtArg(v)()); + } + template + void Format(UINT nId, const A1& v1, const A2& v2) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)()); + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(),FmtArg(v5)(), + FmtArg(v6)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(),FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(),FmtArg(v10)(),FmtArg(v11)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)(),FmtArg(v14)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(), FmtArg(v16)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16, const A17& v17) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(),FmtArg(v16)(),FmtArg(v17)()); + } + } + +#endif // #ifndef SS_ANSI + + // ...now the other overload of Format: the one that takes a string literal + + void Format(const CT* szFmt) + { + *this = szFmt; + } + template + void Format(const CT* szFmt, const A1& v) + { + Fmt(szFmt, FmtArg(v)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(),FmtArg(v10)(),FmtArg(v11)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)(),FmtArg(v14)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(), FmtArg(v16)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16, const A17& v17) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(),FmtArg(v16)(),FmtArg(v17)()); + } + +#else // #ifdef SS_SAFE_FORMAT + + +#ifndef SS_ANSI + + void Format(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + FormatV(strFmt, argList); + + va_end(argList); + } + +#endif // #ifdef SS_ANSI + + void Format(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + FormatV(szFmt, argList); + va_end(argList); + } + +#endif // #ifdef SS_SAFE_FORMAT + + void AppendFormat(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + AppendFormatV(szFmt, argList); + va_end(argList); + } + + #define MAX_FMT_TRIES 5 // #of times we try + #define FMT_BLOCK_SIZE 2048 // # of bytes to increment per try + #define BUFSIZE_1ST 256 + #define BUFSIZE_2ND 512 + #define STD_BUF_SIZE 1024 + + // an efficient way to add formatted characters to the string. You may only + // add up to STD_BUF_SIZE characters at a time, though + void AppendFormatV(const CT* szFmt, va_list argList) + { + CT szBuf[STD_BUF_SIZE]; + int nLen = ssnprintf(szBuf, STD_BUF_SIZE-1, szFmt, argList); + + if ( 0 < nLen ) + this->append(szBuf, nLen); + } + + // ------------------------------------------------------------------------- + // FUNCTION: FormatV + // void FormatV(PCSTR szFormat, va_list, argList); + // + // DESCRIPTION: + // This function formats the string with sprintf style format-specs. + // It makes a general guess at required buffer size and then tries + // successively larger buffers until it finds one big enough or a + // threshold (MAX_FMT_TRIES) is exceeded. + // + // PARAMETERS: + // szFormat - a PCSTR holding the format of the output + // argList - a Microsoft specific va_list for variable argument lists + // + // RETURN VALUE: + // ------------------------------------------------------------------------- + + // NOTE: Changed by JM to actually function under non-win32, + // and to remove the upper limit on size. + void FormatV(const CT* szFormat, va_list argList) + { + // try and grab a sufficient buffersize + int nChars = FMT_BLOCK_SIZE; + va_list argCopy; + + CT *p = reinterpret_cast(malloc(sizeof(CT)*nChars)); + if (!p) return; + + while (1) + { + va_copy(argCopy, argList); + + int nActual = ssvsprintf(p, nChars, szFormat, argCopy); + /* If that worked, return the string. */ + if (nActual > -1 && nActual < nChars) + { /* make sure it's NULL terminated */ + p[nActual] = '\0'; + this->assign(p, nActual); + free(p); + va_end(argCopy); + return; + } + /* Else try again with more space. */ + if (nActual > -1) /* glibc 2.1 */ + nChars = nActual + 1; /* precisely what is needed */ + else /* glibc 2.0 */ + nChars *= 2; /* twice the old size */ + + CT *np = reinterpret_cast(realloc(p, sizeof(CT)*nChars)); + if (np == NULL) + { + free(p); + va_end(argCopy); + return; // failed :( + } + p = np; + va_end(argCopy); + } + } + + // ------------------------------------------------------------------------- + // CString Facade Functions: + // + // The following methods are intended to allow you to use this class as a + // near drop-in replacement for CString. + // ------------------------------------------------------------------------- + #ifdef SS_WIN32 + BSTR AllocSysString() const + { + ostring os; + ssasn(os, *this); + return ::SysAllocString(os.c_str()); + } + #endif + +#ifndef SS_NO_LOCALE + int Collate(PCMYSTR szThat) const + { + return sscoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } + + int CollateNoCase(PCMYSTR szThat) const + { + return ssicoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } +#endif + int Compare(PCMYSTR szThat) const + { + return this->compare(szThat); + } + + int CompareNoCase(PCMYSTR szThat) const + { + return ssicmp(this->c_str(), szThat); + } + + int Delete(int nIdx, int nCount=1) + { + if ( nIdx < 0 ) + nIdx = 0; + + if ( nIdx < this->GetLength() ) + this->erase(static_cast(nIdx), static_cast(nCount)); + + return GetLength(); + } + + void Empty() + { + this->erase(); + } + + int Find(CT ch) const + { + MYSIZE nIdx = this->find_first_of(ch); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(PCMYSTR szSub) const + { + MYSIZE nIdx = this->find(szSub); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(CT ch, int nStart) const + { + // CString::Find docs say add 1 to nStart when it's not zero + // CString::Find code doesn't do that however. We'll stick + // with what the code does + + MYSIZE nIdx = this->find_first_of(ch, static_cast(nStart)); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(PCMYSTR szSub, int nStart) const + { + // CString::Find docs say add 1 to nStart when it's not zero + // CString::Find code doesn't do that however. We'll stick + // with what the code does + + MYSIZE nIdx = this->find(szSub, static_cast(nStart)); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int FindOneOf(PCMYSTR szCharSet) const + { + MYSIZE nIdx = this->find_first_of(szCharSet); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + +#ifndef SS_ANSI + void FormatMessage(PCMYSTR szFormat, ...) throw(std::exception) + { + va_list argList; + va_start(argList, szFormat); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + szFormat, 0, 0, + reinterpret_cast(&szTemp), 0, &argList) == 0 || + szTemp == 0 ) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } + + void FormatMessage(UINT nFormatId, ...) throw(std::exception) + { + MYTYPE sFormat; + VERIFY(sFormat.LoadString(nFormatId)); + va_list argList; + va_start(argList, nFormatId); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + sFormat, 0, 0, + reinterpret_cast(&szTemp), 0, &argList) == 0 || + szTemp == 0) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } +#endif + + // GetAllocLength -- an MSVC7 function but it costs us nothing to add it. + + int GetAllocLength() + { + return static_cast(this->capacity()); + } + + // ------------------------------------------------------------------------- + // GetXXXX -- Direct access to character buffer + // ------------------------------------------------------------------------- + CT GetAt(int nIdx) const + { + return this->at(static_cast(nIdx)); + } + + CT* GetBuffer(int nMinLen=-1) + { + return GetBuf(nMinLen); + } + + CT* GetBufferSetLength(int nLen) + { + return BufferSet(nLen); + } + + // GetLength() -- MFC docs say this is the # of BYTES but + // in truth it is the number of CHARACTERs (chars or wchar_ts) + int GetLength() const + { + return static_cast(this->length()); + } + + int Insert(int nIdx, CT ch) + { + if ( static_cast(nIdx) > this->size()-1 ) + this->append(1, ch); + else + this->insert(static_cast(nIdx), 1, ch); + + return GetLength(); + } + int Insert(int nIdx, PCMYSTR sz) + { + if ( static_cast(nIdx) >= this->size() ) + this->append(sz, static_cast(sslen(sz))); + else + this->insert(static_cast(nIdx), sz); + + return GetLength(); + } + + bool IsEmpty() const + { + return this->empty(); + } + + MYTYPE Left(int nCount) const + { + // Range check the count. + + nCount = SSMAX(0, SSMIN(nCount, static_cast(this->size()))); + return this->substr(0, static_cast(nCount)); + } + +#ifndef SS_ANSI + bool LoadString(UINT nId) + { + return this->Load(nId); + } +#endif + + void MakeLower() + { + ToLower(); + } + + void MakeReverse() + { + std::reverse(this->begin(), this->end()); + } + + void MakeUpper() + { + ToUpper(); + } + + MYTYPE Mid(int nFirst) const + { + return Mid(nFirst, this->GetLength()-nFirst); + } + + MYTYPE Mid(int nFirst, int nCount) const + { + // CString does range checking here. Since we're trying to emulate it, + // we must check too. + + if ( nFirst < 0 ) + nFirst = 0; + if ( nCount < 0 ) + nCount = 0; + + int nSize = static_cast(this->size()); + + if ( nFirst + nCount > nSize ) + nCount = nSize - nFirst; + + if ( nFirst > nSize ) + return MYTYPE(); + + ASSERT(nFirst >= 0); + ASSERT(nFirst + nCount <= nSize); + + return this->substr(static_cast(nFirst), + static_cast(nCount)); + } + + void ReleaseBuffer(int nNewLen=-1) + { + RelBuf(nNewLen); + } + + int Remove(CT ch) + { + MYSIZE nIdx = 0; + int nRemoved = 0; + while ( (nIdx=this->find_first_of(ch)) != MYBASE::npos ) + { + this->erase(nIdx, 1); + nRemoved++; + } + return nRemoved; + } + + int Replace(CT chOld, CT chNew) + { + int nReplaced = 0; + + for ( MYITER iter=this->begin(); iter != this->end(); iter++ ) + { + if ( *iter == chOld ) + { + *iter = chNew; + nReplaced++; + } + } + + return nReplaced; + } + + int Replace(PCMYSTR szOld, PCMYSTR szNew) + { + int nReplaced = 0; + MYSIZE nIdx = 0; + MYSIZE nOldLen = sslen(szOld); + + if ( 0 != nOldLen ) + { + // If the replacement string is longer than the one it replaces, this + // string is going to have to grow in size, Figure out how much + // and grow it all the way now, rather than incrementally + + MYSIZE nNewLen = sslen(szNew); + if ( nNewLen > nOldLen ) + { + int nFound = 0; + while ( nIdx < this->length() && + (nIdx=this->find(szOld, nIdx)) != MYBASE::npos ) + { + nFound++; + nIdx += nOldLen; + } + this->reserve(this->size() + nFound * (nNewLen - nOldLen)); + } + + + static const CT ch = CT(0); + PCMYSTR szRealNew = szNew == 0 ? &ch : szNew; + nIdx = 0; + + while ( nIdx < this->length() && + (nIdx=this->find(szOld, nIdx)) != MYBASE::npos ) + { + this->replace(this->begin()+nIdx, this->begin()+nIdx+nOldLen, + szRealNew); + + nReplaced++; + nIdx += nNewLen; + } + } + + return nReplaced; + } + + int ReverseFind(CT ch) const + { + MYSIZE nIdx = this->find_last_of(ch); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + // ReverseFind overload that's not in CString but might be useful + int ReverseFind(PCMYSTR szFind, MYSIZE pos=MYBASE::npos) const + { + //yuvalt - this does not compile with g++ since MYTTYPE() is different type + //MYSIZE nIdx = this->rfind(0 == szFind ? MYTYPE() : szFind, pos); + MYSIZE nIdx = this->rfind(0 == szFind ? "" : szFind, pos); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + MYTYPE Right(int nCount) const + { + // Range check the count. + + nCount = SSMAX(0, SSMIN(nCount, static_cast(this->size()))); + return this->substr(this->size()-static_cast(nCount)); + } + + void SetAt(int nIndex, CT ch) + { + ASSERT(this->size() > static_cast(nIndex)); + this->at(static_cast(nIndex)) = ch; + } + +#ifndef SS_ANSI + BSTR SetSysString(BSTR* pbstr) const + { + ostring os; + ssasn(os, *this); + if ( !::SysReAllocStringLen(pbstr, os.c_str(), os.length()) ) + throw std::runtime_error("out of memory"); + + ASSERT(*pbstr != 0); + return *pbstr; + } +#endif + + MYTYPE SpanExcluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + + MYTYPE SpanIncluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_not_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + +#if defined SS_WIN32 && !defined(UNICODE) && !defined(SS_ANSI) + + // CString's OemToAnsi and AnsiToOem functions are available only in + // Unicode builds. However since we're a template we also need a + // runtime check of CT and a reinterpret_cast to account for the fact + // that CStdStringW gets instantiated even in non-Unicode builds. + + void AnsiToOem() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::CharToOem(reinterpret_cast(this->c_str()), + reinterpret_cast(GetBuf())); + } + else + { + ASSERT(false); + } + } + + void OemToAnsi() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::OemToChar(reinterpret_cast(this->c_str()), + reinterpret_cast(GetBuf())); + } + else + { + ASSERT(false); + } + } + +#endif + + + // ------------------------------------------------------------------------- + // Trim and its variants + // ------------------------------------------------------------------------- + MYTYPE& Trim() + { + return TrimLeft().TrimRight(); + } + + MYTYPE& TrimLeft() + { + this->erase(this->begin(), + std::find_if(this->begin(), this->end(), NotSpace())); + + return *this; + } + + MYTYPE& TrimLeft(CT tTrim) + { + this->erase(0, this->find_first_not_of(tTrim)); + return *this; + } + + MYTYPE& TrimLeft(PCMYSTR szTrimChars) + { + this->erase(0, this->find_first_not_of(szTrimChars)); + return *this; + } + + MYTYPE& TrimRight() + { + // NOTE: When comparing reverse_iterators here (MYRITER), I avoid using + // operator!=. This is because namespace rel_ops also has a template + // operator!= which conflicts with the global operator!= already defined + // for reverse_iterator in the header . + // Thanks to John James for alerting me to this. + + MYRITER it = std::find_if(this->rbegin(), this->rend(), NotSpace()); + if ( !(this->rend() == it) ) + this->erase(this->rend() - it); + + this->erase(!(it == this->rend()) ? this->find_last_of(*it) + 1 : 0); + return *this; + } + + MYTYPE& TrimRight(CT tTrim) + { + MYSIZE nIdx = this->find_last_not_of(tTrim); + this->erase(MYBASE::npos == nIdx ? 0 : ++nIdx); + return *this; + } + + MYTYPE& TrimRight(PCMYSTR szTrimChars) + { + MYSIZE nIdx = this->find_last_not_of(szTrimChars); + this->erase(MYBASE::npos == nIdx ? 0 : ++nIdx); + return *this; + } + + void FreeExtra() + { + MYTYPE mt; + this->swap(mt); + if ( !mt.empty() ) + this->assign(mt.c_str(), mt.size()); + } + + // I have intentionally not implemented the following CString + // functions. You cannot make them work without taking advantage + // of implementation specific behavior. However if you absolutely + // MUST have them, uncomment out these lines for "sort-of-like" + // their behavior. You're on your own. + +// CT* LockBuffer() { return GetBuf(); }// won't really lock +// void UnlockBuffer(); { } // why have UnlockBuffer w/o LockBuffer? + + // Array-indexing operators. Required because we defined an implicit cast + // to operator const CT* (Thanks to Julian Selman for pointing this out) + + CT& operator[](int nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](int nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + CT& operator[](unsigned int nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](unsigned int nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + CT& operator[](unsigned long nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](unsigned long nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + +#ifndef SS_NO_IMPLICIT_CAST + operator const CT*() const + { + return this->c_str(); + } +#endif + + // IStream related functions. Useful in IPersistStream implementations + +#ifdef SS_INC_COMDEF + + // struct SSSHDR - useful for non Std C++ persistence schemes. + typedef struct SSSHDR + { + BYTE byCtrl; + ULONG nChars; + } SSSHDR; // as in "Standard String Stream Header" + + #define SSSO_UNICODE 0x01 // the string is a wide string + #define SSSO_COMPRESS 0x02 // the string is compressed + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSize + // REMARKS: + // Returns how many bytes it will take to StreamSave() this CStdString + // object to an IStream. + // ------------------------------------------------------------------------- + ULONG StreamSize() const + { + // Control header plus string + ASSERT(this->size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + return (this->size() * sizeof(CT)) + sizeof(SSSHDR); + } + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSave + // REMARKS: + // Saves this CStdString object to a COM IStream. + // ------------------------------------------------------------------------- + HRESULT StreamSave(IStream* pStream) const + { + ASSERT(this->size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + HRESULT hr = E_FAIL; + ASSERT(pStream != 0); + SSSHDR hdr; + hdr.byCtrl = sizeof(CT) == 2 ? SSSO_UNICODE : 0; + hdr.nChars = this->size(); + + + if ( FAILED(hr=pStream->Write(&hdr, sizeof(SSSHDR), 0)) ) + { + TRACE(_T("StreamSave: Cannot write control header, ERR=0x%X\n"),hr); + } + else if ( empty() ) + { + ; // nothing to write + } + else if ( FAILED(hr=pStream->Write(this->c_str(), + this->size()*sizeof(CT), 0)) ) + { + TRACE(_T("StreamSave: Cannot write string to stream 0x%X\n"), hr); + } + + return hr; + } + + + // ------------------------------------------------------------------------- + // FUNCTION: StreamLoad + // REMARKS: + // This method loads the object from an IStream. + // ------------------------------------------------------------------------- + HRESULT StreamLoad(IStream* pStream) + { + ASSERT(pStream != 0); + SSSHDR hdr; + HRESULT hr = E_FAIL; + + if ( FAILED(hr=pStream->Read(&hdr, sizeof(SSSHDR), 0)) ) + { + TRACE(_T("StreamLoad: Cant read control header, ERR=0x%X\n"), hr); + } + else if ( hdr.nChars > 0 ) + { + ULONG nRead = 0; + PMYSTR pMyBuf = BufferSet(hdr.nChars); + + // If our character size matches the character size of the string + // we're trying to read, then we can read it directly into our + // buffer. Otherwise, we have to read into an intermediate buffer + // and convert. + + if ( (hdr.byCtrl & SSSO_UNICODE) != 0 ) + { + ULONG nBytes = hdr.nChars * sizeof(wchar_t); + if ( sizeof(CT) == sizeof(wchar_t) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PWSTR pBufW = reinterpret_cast(_alloca((nBytes)+1)); + if ( FAILED(hr=pStream->Read(pBufW, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufW, hdr.nChars); + } + } + else + { + ULONG nBytes = hdr.nChars * sizeof(char); + if ( sizeof(CT) == sizeof(char) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PSTR pBufA = reinterpret_cast(_alloca(nBytes)); + if ( FAILED(hr=pStream->Read(pBufA, hdr.nChars, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufA, hdr.nChars); + } + } + } + else + { + this->erase(); + } + return hr; + } +#endif // #ifdef SS_INC_COMDEF + +#ifndef SS_ANSI + + // SetResourceHandle/GetResourceHandle. In MFC builds, these map directly + // to AfxSetResourceHandle and AfxGetResourceHandle. In non-MFC builds they + // point to a single static HINST so that those who call the member + // functions that take resource IDs can provide an alternate HINST of a DLL + // to search. This is not exactly the list of HMODULES that MFC provides + // but it's better than nothing. + + #ifdef _MFC_VER + static void SetResourceHandle(HMODULE hNew) + { + AfxSetResourceHandle(hNew); + } + static HMODULE GetResourceHandle() + { + return AfxGetResourceHandle(); + } + #else + static void SetResourceHandle(HMODULE hNew) + { + SSResourceHandle() = hNew; + } + static HMODULE GetResourceHandle() + { + return SSResourceHandle(); + } + #endif + +#endif +}; + +// ----------------------------------------------------------------------------- +// MSVC USERS: HOW TO EXPORT CSTDSTRING FROM A DLL +// +// If you are using MS Visual C++ and you want to export CStdStringA and +// CStdStringW from a DLL, then all you need to +// +// 1. make sure that all components link to the same DLL version +// of the CRT (not the static one). +// 2. Uncomment the 3 lines of code below +// 3. #define 2 macros per the instructions in MS KnowledgeBase +// article Q168958. The macros are: +// +// MACRO DEFINTION WHEN EXPORTING DEFINITION WHEN IMPORTING +// ----- ------------------------ ------------------------- +// SSDLLEXP (nothing, just #define it) extern +// SSDLLSPEC __declspec(dllexport) __declspec(dllimport) +// +// Note that these macros must be available to ALL clients who want to +// link to the DLL and use the class. If they +// +// A word of advice: Don't bother. +// +// Really, it is not necessary to export CStdString functions from a DLL. I +// never do. In my projects, I do generally link to the DLL version of the +// Standard C++ Library, but I do NOT attempt to export CStdString functions. +// I simply include the header where it is needed and allow for the code +// redundancy. +// +// That redundancy is a lot less than you think. This class does most of its +// work via the Standard C++ Library, particularly the base_class basic_string<> +// member functions. Most of the functions here are small enough to be inlined +// anyway. Besides, you'll find that in actual practice you use less than 1/2 +// of the code here, even in big projects and different modules will use as +// little as 10% of it. That means a lot less functions actually get linked +// your binaries. If you export this code from a DLL, it ALL gets linked in. +// +// I've compared the size of the binaries from exporting vs NOT exporting. Take +// my word for it -- exporting this code is not worth the hassle. +// +// ----------------------------------------------------------------------------- +//#pragma warning(disable:4231) // non-standard extension ("extern template") +// SSDLLEXP template class SSDLLSPEC CStdStr; +// SSDLLEXP template class SSDLLSPEC CStdStr; + + +// ============================================================================= +// END OF CStdStr INLINE FUNCTION DEFINITIONS +// ============================================================================= + +// Now typedef our class names based upon this humongous template + +typedef CStdStr CStdStringA; // a better std::string +typedef CStdStr CStdStringW; // a better std::wstring +typedef CStdStr CStdString16; // a 16bit char string +typedef CStdStr CStdString32; // a 32bit char string +typedef CStdStr CStdStringO; // almost always CStdStringW + +// ----------------------------------------------------------------------------- +// CStdStr addition functions defined as inline +// ----------------------------------------------------------------------------- + + +inline CStdStringA operator+(const CStdStringA& s1, const CStdStringA& s2) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(s2); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, CStdStringA::value_type t) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(1, t); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, PCSTR pA) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(pA); + return sRet; +} +inline CStdStringA operator+(PCSTR pA, const CStdStringA& sA) +{ + CStdStringA sRet; + CStdStringA::size_type nObjSize = sA.size(); + CStdStringA::size_type nLitSize = + static_cast(sslen(pA)); + + sRet.reserve(nLitSize + nObjSize); + sRet.assign(pA); + sRet.append(sA); + return sRet; +} + + +inline CStdStringA operator+(const CStdStringA& s1, const CStdStringW& s2) +{ + return s1 + CStdStringA(s2); +} +inline CStdStringW operator+(const CStdStringW& s1, const CStdStringW& s2) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(s2); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, PCWSTR pW) +{ + return s1 + CStdStringA(pW); +} + +#ifdef UNICODE + inline CStdStringW operator+(PCWSTR pW, const CStdStringA& sA) + { + return CStdStringW(pW) + CStdStringW(SSREF(sA)); + } + inline CStdStringW operator+(PCSTR pA, const CStdStringW& sW) + { + return CStdStringW(pA) + sW; + } +#else + inline CStdStringA operator+(PCWSTR pW, const CStdStringA& sA) + { + return CStdStringA(pW) + sA; + } + inline CStdStringA operator+(PCSTR pA, const CStdStringW& sW) + { + return pA + CStdStringA(sW); + } +#endif + +// ...Now the wide string versions. +inline CStdStringW operator+(const CStdStringW& s1, CStdStringW::value_type t) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(1, t); + return sRet; +} +inline CStdStringW operator+(const CStdStringW& s1, PCWSTR pW) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(pW); + return sRet; +} +inline CStdStringW operator+(PCWSTR pW, const CStdStringW& sW) +{ + CStdStringW sRet; + CStdStringW::size_type nObjSize = sW.size(); + CStdStringA::size_type nLitSize = + static_cast(sslen(pW)); + + sRet.reserve(nLitSize + nObjSize); + sRet.assign(pW); + sRet.append(sW); + return sRet; +} + +inline CStdStringW operator+(const CStdStringW& s1, const CStdStringA& s2) +{ + return s1 + CStdStringW(s2); +} +inline CStdStringW operator+(const CStdStringW& s1, PCSTR pA) +{ + return s1 + CStdStringW(pA); +} + + +// New-style format function is a template + +#ifdef SS_SAFE_FORMAT + +template<> +struct FmtArg +{ + explicit FmtArg(const CStdStringA& arg) : a_(arg) {} + PCSTR operator()() const { return a_.c_str(); } + const CStdStringA& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; +template<> +struct FmtArg +{ + explicit FmtArg(const CStdStringW& arg) : a_(arg) {} + PCWSTR operator()() const { return a_.c_str(); } + const CStdStringW& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; + +template<> +struct FmtArg +{ + explicit FmtArg(const std::string& arg) : a_(arg) {} + PCSTR operator()() const { return a_.c_str(); } + const std::string& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; +template<> +struct FmtArg +{ + explicit FmtArg(const std::wstring& arg) : a_(arg) {} + PCWSTR operator()() const { return a_.c_str(); } + const std::wstring& a_; +private: + FmtArg& operator=(const FmtArg&) {return *this;} +}; +#endif // #ifdef SS_SAFEFORMAT + +#ifndef SS_ANSI + // SSResourceHandle: our MFC-like resource handle + inline HMODULE& SSResourceHandle() + { + static HMODULE hModuleSS = GetModuleHandle(0); + return hModuleSS; + } +#endif + + +// In MFC builds, define some global serialization operators +// Special operators that allow us to serialize CStdStrings to CArchives. +// Note that we use an intermediate CString object in order to ensure that +// we use the exact same format. + +#ifdef _MFC_VER + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringA& strA) + { + CString strTemp = strA; + return ar << strTemp; + } + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringW& strW) + { + CString strTemp = strW; + return ar << strTemp; + } + + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringA& strA) + { + CString strTemp; + ar >> strTemp; + strA = strTemp; + return ar; + } + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringW& strW) + { + CString strTemp; + ar >> strTemp; + strW = strTemp; + return ar; + } +#endif // #ifdef _MFC_VER -- (i.e. is this MFC?) + + + +// ----------------------------------------------------------------------------- +// GLOBAL FUNCTION: WUFormat +// CStdStringA WUFormat(UINT nId, ...); +// CStdStringA WUFormat(PCSTR szFormat, ...); +// +// REMARKS: +// This function allows the caller for format and return a CStdStringA +// object with a single line of code. +// ----------------------------------------------------------------------------- +#ifdef SS_ANSI +#else + inline CStdStringA WUFormatA(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + CStdStringA strFmt; + CStdStringA strOut; + if ( strFmt.Load(nId) ) + strOut.FormatV(strFmt, argList); + + va_end(argList); + return strOut; + } + inline CStdStringA WUFormatA(PCSTR szFormat, ...) + { + va_list argList; + va_start(argList, szFormat); + CStdStringA strOut; + strOut.FormatV(szFormat, argList); + va_end(argList); + return strOut; + } + inline CStdStringW WUFormatW(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + CStdStringW strFmt; + CStdStringW strOut; + if ( strFmt.Load(nId) ) + strOut.FormatV(strFmt, argList); + + va_end(argList); + return strOut; + } + inline CStdStringW WUFormatW(PCWSTR szwFormat, ...) + { + va_list argList; + va_start(argList, szwFormat); + CStdStringW strOut; + strOut.FormatV(szwFormat, argList); + va_end(argList); + return strOut; + } +#endif // #ifdef SS_ANSI + + + +#if defined(SS_WIN32) && !defined (SS_ANSI) + // ------------------------------------------------------------------------- + // FUNCTION: WUSysMessage + // CStdStringA WUSysMessageA(DWORD dwError, DWORD dwLangId=SS_DEFLANGID); + // CStdStringW WUSysMessageW(DWORD dwError, DWORD dwLangId=SS_DEFLANGID); + // + // DESCRIPTION: + // This function simplifies the process of obtaining a string equivalent + // of a system error code returned from GetLastError(). You simply + // supply the value returned by GetLastError() to this function and the + // corresponding system string is returned in the form of a CStdStringA. + // + // PARAMETERS: + // dwError - a DWORD value representing the error code to be translated + // dwLangId - the language id to use. defaults to english. + // + // RETURN VALUE: + // a CStdStringA equivalent of the error code. Currently, this function + // only returns either English of the system default language strings. + // ------------------------------------------------------------------------- + #define SS_DEFLANGID MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT) + inline CStdStringA WUSysMessageA(DWORD dwError, DWORD dwLangId=SS_DEFLANGID) + { + CHAR szBuf[512]; + + if ( 0 != ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, + dwLangId, szBuf, 511, NULL) ) + return WUFormatA("%s (0x%X)", szBuf, dwError); + else + return WUFormatA("Unknown error (0x%X)", dwError); + } + inline CStdStringW WUSysMessageW(DWORD dwError, DWORD dwLangId=SS_DEFLANGID) + { + WCHAR szBuf[512]; + + if ( 0 != ::FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, + dwLangId, szBuf, 511, NULL) ) + return WUFormatW(L"%s (0x%X)", szBuf, dwError); + else + return WUFormatW(L"Unknown error (0x%X)", dwError); + } +#endif + +// Define TCHAR based friendly names for some of these functions + +#ifdef UNICODE + //#define CStdString CStdStringW + typedef CStdStringW CStdString; + #define WUSysMessage WUSysMessageW + #define WUFormat WUFormatW +#else + //#define CStdString CStdStringA + typedef CStdStringA CStdString; + #define WUSysMessage WUSysMessageA + #define WUFormat WUFormatA +#endif + +// ...and some shorter names for the space-efficient + +#define WUSysMsg WUSysMessage +#define WUSysMsgA WUSysMessageA +#define WUSysMsgW WUSysMessageW +#define WUFmtA WUFormatA +#define WUFmtW WUFormatW +#define WUFmt WUFormat +#define WULastErrMsg() WUSysMessage(::GetLastError()) +#define WULastErrMsgA() WUSysMessageA(::GetLastError()) +#define WULastErrMsgW() WUSysMessageW(::GetLastError()) + + +// ----------------------------------------------------------------------------- +// FUNCTIONAL COMPARATORS: +// REMARKS: +// These structs are derived from the std::binary_function template. They +// give us functional classes (which may be used in Standard C++ Library +// collections and algorithms) that perform case-insensitive comparisons of +// CStdString objects. This is useful for maps in which the key may be the +// proper string but in the wrong case. +// ----------------------------------------------------------------------------- +#define StdStringLessNoCaseW SSLNCW // avoid VC compiler warning 4786 +#define StdStringEqualsNoCaseW SSENCW +#define StdStringLessNoCaseA SSLNCA +#define StdStringEqualsNoCaseA SSENCA + +#ifdef UNICODE + #define StdStringLessNoCase SSLNCW + #define StdStringEqualsNoCase SSENCW +#else + #define StdStringLessNoCase SSLNCA + #define StdStringEqualsNoCase SSENCA +#endif + +struct StdStringLessNoCaseW + : std::binary_function +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseW + : std::binary_function +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; +struct StdStringLessNoCaseA + : std::binary_function +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseA + : std::binary_function +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; + +// If we had to define our own version of TRACE above, get rid of it now + +#ifdef TRACE_DEFINED_HERE + #undef TRACE + #undef TRACE_DEFINED_HERE +#endif + + +// These std::swap specializations come courtesy of Mike Crusader. + +//namespace std +//{ +// inline void swap(CStdStringA& s1, CStdStringA& s2) throw() +// { +// s1.swap(s2); +// } +// template<> +// inline void swap(CStdStringW& s1, CStdStringW& s2) throw() +// { +// s1.swap(s2); +// } +//} + +// Turn back on any Borland warnings we turned off. + +#ifdef __BORLANDC__ + #pragma option pop // Turn back on inline function warnings +// #pragma warn +inl // Turn back on inline function warnings +#endif + +#endif // #ifndef STDSTRING_H diff --git a/xbmc/pvrclients/mythtv/client.cpp b/xbmc/pvrclients/mythtv/client.cpp new file mode 100644 index 0000000000..d8b249f32a --- /dev/null +++ b/xbmc/pvrclients/mythtv/client.cpp @@ -0,0 +1,542 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "client.h" +#include "xbmc_pvr_dll.h" +#include "MythXml.h" + +using namespace std; + +#define SEEK_POSSIBLE 0x10 // flag used to check if protocol allows seeks + +/* User adjustable settings are saved here. + * Default values are defined inside client.h + * and exported to the other source files. + */ +CStdString g_szHostname = DEFAULT_HOST; ///< The Host name or IP of MythTV +int g_iMythXmlPort = DEFAULT_MYTHXML_PORT; ///< The MyhtXML Port of MythTV (default is 6544) +int g_iPin = DEFAULT_PIN; ///< The Mythtv server PIN (default is 0000) +int g_iMythXmlConnectTimeout = DEFAULT_MYTHXML_CONNECTION_TIMEOUT; ///< The MYTHXML Connection Timeout value (default is 30 seconds) +///* Client member variables */ + +bool m_recordingFirstRead; +char m_noSignalStreamData[ 6 + 0xffff ]; +long m_noSignalStreamSize = 0; +long m_noSignalStreamReadPos = 0; +bool m_bPlayingNoSignal = false; +int m_iCurrentChannel = 1; +ADDON_STATUS m_CurStatus = STATUS_UNKNOWN; +bool g_bCreated = false; +int g_iClientID = -1; +CStdString g_szUserPath = ""; +CStdString g_szClientPath = ""; +MythXml *MythXmlApi = NULL; +cHelper_libXBMC_addon *XBMC = NULL; +cHelper_libXBMC_pvr *PVR = NULL; + + +extern "C" { + +/*********************************************************** + * Standard AddOn related public library functions + ***********************************************************/ + +ADDON_STATUS Create(void* hdl, void* props) +{ + if (!props) + return STATUS_UNKNOWN; + + PVR_PROPS* pvrprops = (PVR_PROPS*)props; + + XBMC = new cHelper_libXBMC_addon; + if (!XBMC->RegisterMe(hdl)) + return STATUS_UNKNOWN; + + PVR = new cHelper_libXBMC_pvr; + if (!PVR->RegisterMe(hdl)) + return STATUS_UNKNOWN; + + XBMC->Log(LOG_DEBUG, "Creating MythTV PVR-Client"); + + m_CurStatus = STATUS_UNKNOWN; + g_iClientID = pvrprops->clientID; + g_szUserPath = pvrprops->userpath; + g_szClientPath = pvrprops->clientpath; + + /* Read setting "host" from settings.xml */ + char * buffer; + buffer = (char*) malloc (1024); + buffer[0] = 0; /* Set the end of string */ + + if (XBMC->GetSetting("host", buffer)) + g_szHostname = buffer; + else + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'host' setting, falling back to '%s' as default", DEFAULT_HOST); + g_szHostname = DEFAULT_HOST; + } + free (buffer); + + /* Read setting "port" from settings.xml */ + if (!XBMC->GetSetting("mythXMLPort", &g_iMythXmlPort)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'mythXMLPort' setting, falling back to '%i' as default", DEFAULT_MYTHXML_PORT); + g_iMythXmlPort = DEFAULT_MYTHXML_PORT; + } + + /* Read setting "pin" from settings.xml */ + if (!XBMC->GetSetting("mythXMLTimeout", &g_iMythXmlConnectTimeout)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'mythXMLTimeout' setting, falling back to '%i' as default", DEFAULT_MYTHXML_CONNECTION_TIMEOUT); + g_iMythXmlConnectTimeout = DEFAULT_MYTHXML_CONNECTION_TIMEOUT; + } else { + // we need to multiply by 1000 the value as the settings file is in seconds + g_iMythXmlConnectTimeout *= 1000; + } + + /* Read setting "pin" from settings.xml */ + if (!XBMC->GetSetting("pin", &g_iPin)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'pin' setting, falling back to '%i' as default", DEFAULT_PIN); + g_iPin = DEFAULT_PIN; + } + + MythXmlApi = new MythXml(); + if (!MythXmlApi->open(g_szHostname, g_iMythXmlPort, "", "", g_iPin, g_iMythXmlConnectTimeout)) + { + m_CurStatus = STATUS_LOST_CONNECTION; + return m_CurStatus; + } + + m_CurStatus = STATUS_OK; + + g_bCreated = true; + return m_CurStatus; +} + +void Destroy() +{ + if (g_bCreated) + { + delete MythXmlApi; + MythXmlApi = NULL; + g_bCreated = false; + } + m_CurStatus = STATUS_UNKNOWN; +} + +ADDON_STATUS GetStatus() +{ + return m_CurStatus; +} + +bool HasSettings() +{ + return true; +} + +unsigned int GetSettings(StructSetting ***sSet) +{ + return 0; +} + +ADDON_STATUS SetSetting(const char *settingName, const void *settingValue) +{ + string str = settingName; + if (str == "host") + { + string tmp_sHostname; + XBMC->Log(LOG_INFO, "Changed Setting 'host' from %s to %s", g_szHostname.c_str(), (const char*) settingValue); + tmp_sHostname = g_szHostname; + g_szHostname = (const char*) settingValue; + if (tmp_sHostname != g_szHostname) + return STATUS_NEED_RESTART; + } + else if (str == "mythXMLPort") + { + XBMC->Log(LOG_INFO, "Changed Setting 'port' from %u to %u", g_iMythXmlPort, *(int*) settingValue); + if (g_iMythXmlPort != *(int*) settingValue) + { + g_iMythXmlPort = *(int*) settingValue; + return STATUS_NEED_RESTART; + } + } + else if (str == "mythXMLTimeout") + { + XBMC->Log(LOG_INFO, "Changed Setting 'mythXMLTimeout' from %u to %u", g_iMythXmlConnectTimeout, *(int*) settingValue); + if (g_iMythXmlConnectTimeout / 1000 != *(int*) settingValue) + { + g_iMythXmlConnectTimeout = *(int*) settingValue; + g_iMythXmlConnectTimeout *= 1000; + return STATUS_NEED_RESTART; + } + } + else if (str == "pin") + { + XBMC->Log(LOG_INFO, "Changed Setting 'pin' from %u to %u", g_iPin, *(int*) settingValue); + if (g_iPin != *(int*) settingValue) + { + g_iPin = *(int*) settingValue; + return STATUS_NEED_RESTART; + } + } + return STATUS_OK; +} + +void Stop() +{ + return; +} + +void FreeSettings() +{ + return; +} + + +/*********************************************************** + * PVR Client AddOn specific public library functions + ***********************************************************/ + +PVR_ERROR GetProperties(PVR_SERVERPROPS* props) +{ + props->SupportChannelLogo = false; + props->SupportTimeShift = false; + props->SupportEPG = true; + props->SupportRecordings = false; + props->SupportTimers = false; + props->SupportTV = false; + props->SupportRadio = false; + props->SupportChannelSettings = false; + props->SupportDirector = false; + props->SupportBouquets = false; + props->HandleInputStream = false; + props->HandleDemuxing = false; + props->SupportChannelScan = false; + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR GetStreamProperties(PVR_STREAMPROPS* props) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +const char * GetBackendName() +{ + return ""; +} + +const char * GetBackendVersion() +{ + return ""; +} + +const char * GetConnectionString() +{ + return ""; +} + +PVR_ERROR GetDriveSpace(long long *total, long long *used) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR GetBackendTime(time_t *localTime, int *gmtOffset) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR DialogChannelScan() +{ + return PVR_ERROR_NOT_POSSIBLE; +} + +PVR_ERROR MenuHook(const PVR_MENUHOOK &menuhook) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +/*******************************************/ +/** PVR EPG Functions **/ + +PVR_ERROR RequestEPGForChannel(PVRHANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end) +{ + if (MythXmlApi == NULL) + return PVR_ERROR_SERVER_ERROR; + + return MythXmlApi->requestEPGForChannel(handle, channel, start, end); +} + + +/*******************************************/ +/** PVR Bouquets Functions **/ + +int GetNumBouquets() +{ + return 0; +} + +PVR_ERROR RequestBouquetsList(PVRHANDLE handle, int radio) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + + +/*******************************************/ +/** PVR Channel Functions **/ + +int GetNumChannels() +{ + if (MythXmlApi == NULL) + return PVR_ERROR_SERVER_ERROR; + + return MythXmlApi->getNumChannels(); +} + +PVR_ERROR RequestChannelList(PVRHANDLE handle, int radio) +{ + if (MythXmlApi == NULL) + return PVR_ERROR_SERVER_ERROR; + + return MythXmlApi->requestChannelList(handle, radio); +} + +PVR_ERROR DeleteChannel(unsigned int number) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR RenameChannel(unsigned int number, const char *newname) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR MoveChannel(unsigned int number, unsigned int newnumber) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR DialogChannelSettings(const PVR_CHANNEL &channelinfo) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR DialogAddChannel(const PVR_CHANNEL &channelinfo) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + + +/*******************************************/ +/** PVR Recording Functions **/ + +int GetNumRecordings(void) +{ + return 0; +} + +PVR_ERROR RequestRecordingsList(PVRHANDLE handle) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR DeleteRecording(const PVR_RECORDINGINFO &recinfo) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR RenameRecording(const PVR_RECORDINGINFO &recinfo, const char *newname) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + + +/*******************************************/ +/** PVR Recording cut marks Functions **/ + +bool HaveCutmarks() +{ + return false; +} + +PVR_ERROR RequestCutMarksList(PVRHANDLE handle) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR AddCutMark(const PVR_CUT_MARK &cutmark) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR DeleteCutMark(const PVR_CUT_MARK &cutmark) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR StartCut() +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + + +/*******************************************/ +/** PVR Timer Functions **/ + +int GetNumTimers(void) +{ + return 0; +} + +PVR_ERROR RequestTimerList(PVRHANDLE handle) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR AddTimer(const PVR_TIMERINFO &timerinfo) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR RenameTimer(const PVR_TIMERINFO &timerinfo, const char *newname) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR UpdateTimer(const PVR_TIMERINFO &timerinfo) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + + +/*******************************************/ +/** PVR Live Stream Functions **/ + +bool OpenLiveStream(const PVR_CHANNEL &channelinfo) +{ + return false; +} + +void CloseLiveStream() +{ + return; +} + +int ReadLiveStream(unsigned char* buf, int buf_size) +{ + return -1; +} + +int GetCurrentClientChannel() +{ + return m_iCurrentChannel; +} + +bool SwitchChannel(const PVR_CHANNEL &channelinfo) +{ + return false; +} + +PVR_ERROR SignalQuality(PVR_SIGNALQUALITY &qualityinfo) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + + +/*******************************************/ +/** PVR Secondary Stream Functions **/ + +bool SwapLiveTVSecondaryStream() +{ + return false; +} + +bool OpenSecondaryStream(const PVR_CHANNEL &channelinfo) +{ + return false; +} + +void CloseSecondaryStream() +{ + return; +} + +int ReadSecondaryStream(unsigned char* buf, int buf_size) +{ + return 0; +} + + +/*******************************************/ +/** PVR Recording Stream Functions **/ + +bool OpenRecordedStream(const PVR_RECORDINGINFO &recinfo) +{ + return false; +} + +void CloseRecordedStream(void) +{ + return; +} + +int ReadRecordedStream(unsigned char* buf, int buf_size) +{ + return 0; +} + +long long SeekRecordedStream(long long pos, int whence) +{ + return -1; +} + +long long PositionRecordedStream(void) +{ + return -1; +} + +long long LengthRecordedStream(void) +{ + return 0; +} + + +/** UNUSED API FUNCTIONS */ +DemuxPacket* DemuxRead() { return NULL; } +void DemuxAbort() {} +void DemuxReset() {} +void DemuxFlush() {} +long long SeekLiveStream(long long pos, int whence) { return -1; } +long long PositionLiveStream(void) { return -1; } +long long LengthLiveStream(void) { return -1; } +const char * GetLiveStreamURL(const PVR_CHANNEL &channelinfo) { return ""; } + +} //end extern "C" diff --git a/xbmc/pvrclients/mythtv/client.h b/xbmc/pvrclients/mythtv/client.h new file mode 100644 index 0000000000..dd2f610694 --- /dev/null +++ b/xbmc/pvrclients/mythtv/client.h @@ -0,0 +1,48 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef CLIENT_H +#define CLIENT_H + +#include "StdString.h" +#include "../../../addons/library.xbmc.addon/libXBMC_addon.h" +#include "../../../addons/library.xbmc.pvr/libXBMC_pvr.h" + +#define DEFAULT_HOST "127.0.0.1" +#define DEFAULT_MYTHXML_PORT 6544 +#define DEFAULT_PIN 0000 +#define DEFAULT_MYTHXML_CONNECTION_TIMEOUT 30000 + +extern bool g_bCreated; ///< Shows that the Create function was successfully called +extern int g_iClientID; ///< The PVR client ID used by XBMC for this driver +extern CStdString g_szUserPath; ///< The Path to the user directory inside user profile +extern CStdString g_szClientPath; ///< The Path where this driver is located + +/* Client Settings */ +extern CStdString g_szHostname; ///< The Host name or IP of the mythtv server +extern int g_iMythXmlPort; ///< The MYTHXML Port (default is 6544) +extern int g_iPin; ///< The Mythtv server PIN (default is 0000) + +extern cHelper_libXBMC_addon *XBMC; +extern cHelper_libXBMC_pvr *PVR; + +#endif /* CLIENT_H */ diff --git a/xbmc/pvrclients/mythtv/libmythxml/GetChannelListCommand.h b/xbmc/pvrclients/mythtv/libmythxml/GetChannelListCommand.h new file mode 100644 index 0000000000..541b26a5ca --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/GetChannelListCommand.h @@ -0,0 +1,25 @@ +/* + * GetChannelListCommand.h + * + * Created on: Oct 7, 2010 + * Author: tafypz + */ + +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETCHANNELLISTCOMMAND_H_ +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETCHANNELLISTCOMMAND_H_ + +#include "MythXmlCommand.h" + +class GetChannelListCommand: public MythXmlCommand { + +public: + GetChannelListCommand() {}; + virtual ~GetChannelListCommand() {}; +protected: + virtual const CStdString& getCommand() const { + static CStdString result = "GetProgramGuide"; + return result; + }; +}; + +#endif /* XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETCHANNELLISTCOMMAND_H_ */ diff --git a/xbmc/pvrclients/mythtv/libmythxml/GetChannelListParameters.h b/xbmc/pvrclients/mythtv/libmythxml/GetChannelListParameters.h new file mode 100644 index 0000000000..6627ed0b6c --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/GetChannelListParameters.h @@ -0,0 +1,19 @@ +/* + * GetChannelListParameters.h + * + * Created on: Oct 8, 2010 + * Author: tafypz + */ + +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETCHANNELLISTPARAMETERS_H_ +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETCHANNELLISTPARAMETERS_H_ + +#include "GetProgramGuideParameters.h" + +class GetChannelListParameters: public GetProgramGuideParameters { +public: + GetChannelListParameters() : GetProgramGuideParameters(false) {}; + virtual ~GetChannelListParameters(){}; +}; + +#endif /* XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETCHANNELLISTPARAMETERS_H_ */ diff --git a/xbmc/pvrclients/mythtv/libmythxml/GetChannelListResult.cpp b/xbmc/pvrclients/mythtv/libmythxml/GetChannelListResult.cpp new file mode 100644 index 0000000000..9504c0006a --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/GetChannelListResult.cpp @@ -0,0 +1,53 @@ +#include "GetChannelListResult.h" +#include + +#include "tinyXML/tinyxml.h" +#include "utils/log.h" + +GetChannelListResult::GetChannelListResult() { +} + +GetChannelListResult::~GetChannelListResult() { +} + +void GetChannelListResult::parseData(const CStdString& xmlData) { + TiXmlDocument xml; + xml.Parse(xmlData.c_str(), 0, TIXML_ENCODING_LEGACY); + + TiXmlElement* rootXmlNode = xml.RootElement(); + + if (!rootXmlNode) { + errors_.push_back(" No root node parsed"); + CLog::Log(LOGDEBUG, "MythXML GetChannelListResult - No root node parsed"); + return; + } + + TiXmlElement* programGuideResponseNode = NULL; + CStdString strValue = rootXmlNode->Value(); + if (strValue.Find("GetProgramGuideResponse") >= 0 ) { + programGuideResponseNode = rootXmlNode; + } + else if (strValue.Find("detail") >= 0 ) { + // process the error. + TiXmlElement* errorCodeXmlNode = rootXmlNode->FirstChildElement("errorCode"); + TiXmlElement* errorDescXmlNode = rootXmlNode->FirstChildElement("errorDescription"); + CStdString error; + error.Format("ErrorCode [%i] - %s", errorCodeXmlNode->GetText(), errorDescXmlNode->GetText()); + errors_.push_back(error); + return; + } + else + return; + + TiXmlElement* programGuideNode = programGuideResponseNode->FirstChildElement("ProgramGuide"); + TiXmlElement* channelsNode = programGuideNode->FirstChildElement("Channels"); + TiXmlElement* child = NULL; + for( child = channelsNode->FirstChildElement("Channel"); child; child = child->NextSiblingElement("Channel")){ + SChannel channel; + channel.id = atoi(child->Attribute("chanId")); + channel.name = child->Attribute("channelName"); + channel.callsign = child->Attribute("callSign"); + channel.number = child->Attribute("chanNum"); + channels_.push_back(channel); + } +} diff --git a/xbmc/pvrclients/mythtv/libmythxml/GetChannelListResult.h b/xbmc/pvrclients/mythtv/libmythxml/GetChannelListResult.h new file mode 100644 index 0000000000..daf298846a --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/GetChannelListResult.h @@ -0,0 +1,17 @@ +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETCHANNELLISTRESULT_H +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETCHANNELLISTRESULT_H + +#include "MythXmlCommandResult.h" +#include "SChannel.h" + +class GetChannelListResult: public MythXmlCommandResult { +public: + GetChannelListResult(); + virtual ~GetChannelListResult(); + virtual void parseData(const CStdString& xmlData); + inline const vector& getChannels() {return channels_;}; +private: + vector channels_; +}; + +#endif // XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETCHANNELLISTRESULT_H diff --git a/xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsCommand.h b/xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsCommand.h new file mode 100644 index 0000000000..cede160967 --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsCommand.h @@ -0,0 +1,24 @@ +/* + * GetNumChannelsCommand.h + * + * Created on: Oct 7, 2010 + * Author: tafypz + */ + +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETNUMCHANNELSCOMMAND_H_ +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETNUMCHANNELSCOMMAND_H_ + +#include "MythXmlCommand.h" + +class GetNumChannelsCommand: public MythXmlCommand { +public: + GetNumChannelsCommand(){}; + virtual ~GetNumChannelsCommand(){}; +protected: + virtual const CStdString& getCommand() const{ + static CStdString result = "GetProgramGuide"; + return result; + } +}; + +#endif /* XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETNUMCHANNELSCOMMAND_H_ */ diff --git a/xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsParameters.h b/xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsParameters.h new file mode 100644 index 0000000000..fad18da8a1 --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsParameters.h @@ -0,0 +1,19 @@ +/* + * GetNumChannelsParameters.h + * + * Created on: Oct 8, 2010 + * Author: tafypz + */ + +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETNUMCHANNELSPARAMETERS_H_ +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETNUMCHANNELSPARAMETERS_H_ + +#include "GetProgramGuideParameters.h" + +class GetNumChannelsParameters: public GetProgramGuideParameters { +public: + GetNumChannelsParameters(): GetProgramGuideParameters(false) {}; + virtual ~GetNumChannelsParameters() {}; +}; + +#endif /* XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETNUMCHANNELSPARAMETERS_H_ */ diff --git a/xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsResult.cpp b/xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsResult.cpp new file mode 100644 index 0000000000..79efc8b2ea --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsResult.cpp @@ -0,0 +1,54 @@ +/* + * GetNumChannelsResult.cpp + * + * Created on: Oct 7, 2010 + * Author: tafypz + */ + +#include "GetNumChannelsResult.h" +#include + +#include "tinyXML/tinyxml.h" +#include "utils/log.h" + +GetNumChannelsResult::GetNumChannelsResult() { + numberOfChannels_ = 0; +} + +GetNumChannelsResult::~GetNumChannelsResult() { +} + +void GetNumChannelsResult::parseData(const CStdString& xmlData) { + TiXmlDocument xml; + xml.Parse(xmlData.c_str(), 0, TIXML_ENCODING_LEGACY); + + TiXmlElement* rootXmlNode = xml.RootElement(); + + if (!rootXmlNode) { + errors_.push_back(" No root node parsed"); + CLog::Log(LOGDEBUG, "MythXML GetNumChannelsResult - No root node parsed"); + return; + } + + TiXmlElement* programGuideResponseNode = NULL; + CStdString strValue = rootXmlNode->Value(); + if (strValue.Find("GetProgramGuideResponse") >= 0 ) { + programGuideResponseNode = rootXmlNode; + } + else if (strValue.Find("detail") >= 0 ) { + // process the error. + TiXmlElement* errorCodeXmlNode = rootXmlNode->FirstChildElement("errorCode"); + TiXmlElement* errorDescXmlNode = rootXmlNode->FirstChildElement("errorDescription"); + CStdString error; + error.Format("ErrorCode [%i] - %s", errorCodeXmlNode->GetText(), errorDescXmlNode->GetText()); + errors_.push_back(error); + return; + } + else + return; + + TiXmlElement* numOfChannelsXmlNode = programGuideResponseNode->FirstChildElement("NumOfChannels"); + CStdString val = numOfChannelsXmlNode->GetText(); + int numberOfChannels = atoi(val.c_str()); + numberOfChannels_ = numberOfChannels; +} diff --git a/xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsResult.h b/xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsResult.h new file mode 100644 index 0000000000..b39919b6ec --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/GetNumChannelsResult.h @@ -0,0 +1,23 @@ +/* + * GetNumChannelsResult.h + * + * Created on: Oct 7, 2010 + * Author: tafypz + */ + +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETNUMCHANNELSRESULT_H_ +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETNUMCHANNELSRESULT_H_ + +#include "MythXmlCommandResult.h" + +class GetNumChannelsResult: public MythXmlCommandResult { +public: + GetNumChannelsResult(); + virtual ~GetNumChannelsResult(); + virtual void parseData(const CStdString& xmlData); + inline int getNumberOfChannels() const {return numberOfChannels_;}; +private: + int numberOfChannels_; +}; + +#endif /* XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETNUMCHANNELSRESULT_H_ */ diff --git a/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideCommand.h b/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideCommand.h new file mode 100644 index 0000000000..4daf3bd58d --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideCommand.h @@ -0,0 +1,18 @@ +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETPROGRAMGUIDECOMMAND_H +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETPROGRAMGUIDECOMMAND_H +// +#include "MythXmlCommand.h" + +class GetProgramGuideCommand: public MythXmlCommand { +public: + GetProgramGuideCommand() {}; + virtual ~GetProgramGuideCommand() {}; +protected: + virtual const CStdString& getCommand() const { + static CStdString result = "GetProgramGuide"; + return result; + }; +}; + + +#endif // XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETPROGRAMGUIDECOMMAND_H diff --git a/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideParameters.cpp b/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideParameters.cpp new file mode 100644 index 0000000000..4cab67fb67 --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideParameters.cpp @@ -0,0 +1,43 @@ +/* + * GetProgramGuideParameters.cpp + * + * Created on: Oct 8, 2010 + * Author: tafypz + */ + +#include "GetProgramGuideParameters.h" +#include "utils/TimeUtils.h" + +GetProgramGuideParameters::GetProgramGuideParameters(bool retrieveDetails) : MythXmlCommandParameters(true){ + CDateTime now = CTimeUtils::GetLocalTime(time(NULL)); + channelid_ = -1; + starttime_ = now; + endtime_ = now; + retrieveDetails_ = retrieveDetails_; +} + +GetProgramGuideParameters::GetProgramGuideParameters(int channelid, CDateTime starttime, CDateTime endtime, bool retrieveDetails) : MythXmlCommandParameters(true){ + channelid_ = channelid; + retrieveDetails_ = retrieveDetails; + starttime_ = starttime; + endtime_ = endtime; +} + +GetProgramGuideParameters::~GetProgramGuideParameters() { +} + +CStdString GetProgramGuideParameters::createParameterString() const{ + CStdString result = "?StartTime=%s&EndTime=%s&NumOfChannels=%i"; + CStdString start = MythXmlCommandParameters::convertTimeToString(starttime_); + CStdString end = MythXmlCommandParameters::convertTimeToString(endtime_); + int numChannels = 1; + if(channelid_ == -1) + numChannels = -1; + result.Format(result.c_str(), start.c_str(), end.c_str(),numChannels); + if(numChannels == 1){ + CStdString chanid = "&StartChanId=%i"; + chanid.Format(chanid.c_str(), channelid_); + result += chanid; + } + return result; +}; diff --git a/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideParameters.h b/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideParameters.h new file mode 100644 index 0000000000..cb8cdfc1a5 --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideParameters.h @@ -0,0 +1,38 @@ +/* + * GetProgramGuideParameters.h + * + * Created on: Oct 8, 2010 + * Author: tafypz + */ + +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETPROGRAMGUIDEPARAMETERS_H_ +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETPROGRAMGUIDEPARAMETERS_H_ + +#include "DateTime.h" + +#include "MythXmlCommandParameters.h" + +class GetProgramGuideParameters: public MythXmlCommandParameters { +public: + GetProgramGuideParameters(int channelid, CDateTime starttime, CDateTime endtime, bool retrieveDetails); + virtual ~GetProgramGuideParameters(); + + virtual CStdString createParameterString() const; + inline const CDateTime& get_starttime() const {return starttime_;}; + inline const CDateTime& get_endtime() const {return endtime_;}; + inline void set_channelid(int channelid) {channelid_ = channelid;}; + inline int get_channelid() const { return channelid_;}; + inline void set_retrievedetailsflag(bool retrievedetails) {retrieveDetails_ = retrievedetails;}; + inline bool get_retrievedetailsflag() const {return retrieveDetails_;}; + +protected: + GetProgramGuideParameters(bool retrieveDetails); + +private: + CDateTime starttime_; + CDateTime endtime_; + int channelid_; + bool retrieveDetails_; +}; + +#endif /* XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETPROGRAMGUIDEPARAMETERS_H_ */ diff --git a/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideResult.cpp b/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideResult.cpp new file mode 100644 index 0000000000..d7545eb354 --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideResult.cpp @@ -0,0 +1,331 @@ +#include "GetProgramGuideResult.h" +#include + +#include "tinyXML/tinyxml.h" +#include "utils/log.h" +/* +static const SContentType g_content_group[] = +{ { 0x10, "Movie/Drama" } +, { 0x20, "News/Current Affairs" } +, { 0x30, "Show/Game show" } +, { 0x40, "Sports" } +, { 0x50, "Children's/Youth" } +, { 0x60, "Music/Ballet/Dance" } +, { 0x70, "Arts/Culture (without music)" } +, { 0x80, "Social/Political issues/Economics" } +, { 0x90, "Childrens/Youth Education/Science/Factual" } +, { 0xa0, "Leisure hobbies" } +, { 0xb0, "Misc" } +, { 0xf0, "Unknown" } +}; + +static const SContentType g_content_type[] = +{ +// movie/drama + { 0x11, "Detective/Thriller" } +, { 0x12, "Adventure/Western/War" } +, { 0x13, "Science Fiction/Fantasy/Horror" } +, { 0x14, "Comedy" } +, { 0x15, "Soap/Melodrama/Folkloric" } +, { 0x16, "Romance" } +, { 0x17, "Serious/ClassicalReligion/Historical" } +, { 0x18, "Adult Movie/Drama" } + +// news/current affairs +, { 0x21, "News/Weather Report" } +, { 0x22, "Magazine" } +, { 0x23, "Documentary" } +, { 0x24, "Discussion/Interview/Debate" } + +// show/game show +, { 0x31, "Game show/Quiz/Contest" } +, { 0x32, "Variety" } +, { 0x33, "Talk" } + +// sports +, { 0x41, "Special Event (Olympics/World cup/...)" } +, { 0x42, "Magazine" } +, { 0x43, "Football/Soccer" } +, { 0x44, "Tennis/Squash" } +, { 0x45, "Team sports (excluding football)" } +, { 0x46, "Athletics" } +, { 0x47, "Motor Sport" } +, { 0x48, "Water Sport" } +, { 0x49, "Winter Sports" } +, { 0x4a, "Equestrian" } +, { 0x4b, "Martial sports" } + +// childrens/youth +, { 0x51, "Pre-school" } +, { 0x52, "Entertainment (6 to 14 year-olds)" } +, { 0x53, "Entertainment (10 to 16 year-olds)" } +, { 0x54, "Informational/Educational/Schools" } +, { 0x55, "Cartoons/Puppets" } + +// music/ballet/dance +, { 0x61, "Rock/Pop" } +, { 0x62, "Serious music/Classical Music" } +, { 0x63, "Folk/Traditional music" } +, { 0x64, "Jazz" } +, { 0x65, "Musical/Opera" } +, { 0x66, "Ballet" } + +// arts/culture +, { 0x71, "Performing Arts" } +, { 0x72, "Fine Arts" } +, { 0x73, "Religion" } +, { 0x74, "Popular Culture/Tradital Arts" } +, { 0x75, "Literature" } +, { 0x76, "Film/Cinema" } +, { 0x77, "Experimental Film/Video" } +, { 0x78, "Broadcasting/Press" } +, { 0x79, "New Media" } +, { 0x7a, "Magazine" } +, { 0x7b, "Fashion" } + +// social/political/economic +, { 0x81, "Magazine/Report/Documentary" } +, { 0x82, "Economics/Social Advisory" } +, { 0x83, "Remarkable People" } + +// children's youth: educational/science/factual +, { 0x91, "Nature/Animals/Environment" } +, { 0x92, "Technology/Natural sciences" } +, { 0x93, "Medicine/Physiology/Psychology" } +, { 0x94, "Foreign Countries/Expeditions" } +, { 0x95, "Social/Spiritual Sciences" } +, { 0x96, "Further Education" } +, { 0x97, "Languages" } + +// leisure hobbies +, { 0xa1, "Tourism/Travel" } +, { 0xa2, "Handicraft" } +, { 0xa3, "Motoring" } +, { 0xa4, "Fitness & Health" } +, { 0xa5, "Cooking" } +, { 0xa6, "Advertisement/Shopping" } +, { 0xa7, "Gardening" } + +// misc +, { 0xb0, "Original Language" } +, { 0xb1, "Black and White" } +, { 0xb2, "Unpublished" } +, { 0xb3, "Live Broadcast" } +}; +*/ + +struct GenrePair +{ + GenrePair() + { + genretype_ = 0xf; + genresubtype_ = 0xb2; + }; + + GenrePair(int type, int subtype) + { + genretype_ = type; + genresubtype_ = subtype; + }; + + int genretype_; + int genresubtype_; +}; + + + +class GenreIdMapper +{ +public: + GenreIdMapper() + { + genreTypeIdMap_["Auction"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Awards"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Biography"] = GenrePair (0x70, 0x74); + genreTypeIdMap_["Educational"] = GenrePair (0x90, 0x96); + genreTypeIdMap_["Entertainment"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Holiday"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Holiday special"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Home improvement"] = GenrePair (0xA0, 0xa7); + genreTypeIdMap_["How-to"] = GenrePair (0x90, 0x96); + genreTypeIdMap_["Music"] = GenrePair (0x60, 0x61); + genreTypeIdMap_["Music special"] = GenrePair (0x60, 0x61); + genreTypeIdMap_["Music talk"] = GenrePair (0x60, 0x61); + genreTypeIdMap_["Shopping"] = GenrePair (0xA0, 0xa6); + genreTypeIdMap_["Sitcom"] = GenrePair (0x10, 0x14); + genreTypeIdMap_["Soap"] = GenrePair (0x10, 0x15); + genreTypeIdMap_["Soap talk"] = GenrePair (0x10, 0x15); + genreTypeIdMap_["Golf"] = GenrePair (0x40, 0x41); + genreTypeIdMap_["Lacrosse"] = GenrePair (0x40, 0x45); + genreTypeIdMap_["Law"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Card games"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Collectibles"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Community"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Computers"] = GenrePair (0x90, 0x92); + genreTypeIdMap_["Consumer"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Fundraiser"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Gaming"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Gay/lesbian"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Military"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Miniseries"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Opera"] = GenrePair (0x60, 0x65); + genreTypeIdMap_["Parade"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Paranormal"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Parenting"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Poker"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Reality"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Self improvement"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Special"] = GenrePair (0xB0, 0xb2); + genreTypeIdMap_["Standup"] = GenrePair (0x10, 0x14); + genreTypeIdMap_["Politics"] = GenrePair (0x80, 0x82); + genreTypeIdMap_["Public affairs"] = GenrePair (0x80, 0x82); + genreTypeIdMap_["Historical drama"] = GenrePair (0x10, 0x17); + genreTypeIdMap_["History"] = GenrePair (0x10, 0x17); + genreTypeIdMap_["Boat"] = GenrePair (0xA0, 0xa3); + genreTypeIdMap_["Bus./financial"] = GenrePair (0x80, 0x82); + genreTypeIdMap_["Auto"] = GenrePair (0xA0, 0xa3); + genreTypeIdMap_["Aviation"] = GenrePair (0xA0, 0xa3); + genreTypeIdMap_["Nature"] = GenrePair (0x90, 0x91); + genreTypeIdMap_["Agriculture"] = GenrePair (0x90, 0x91); + genreTypeIdMap_["Animals"] = GenrePair (0x90, 0x91); + genreTypeIdMap_["Environment"] = GenrePair (0x90, 0x91); + genreTypeIdMap_["French"] = GenrePair (0x90, 0x97); + genreTypeIdMap_["Horse"] = GenrePair (0x90, 0x91); + genreTypeIdMap_["Outdoors"] = GenrePair (0x90, 0x91); + genreTypeIdMap_["Science"] = GenrePair (0x90, 0x92); + genreTypeIdMap_["Technology"] = GenrePair (0x90, 0x92); + genreTypeIdMap_["Medical"] = GenrePair (0x90, 0x93); + genreTypeIdMap_["Hunting"] = GenrePair (0x90, 0x91); + genreTypeIdMap_["Fishing"] = GenrePair (0x90, 0x91); + genreTypeIdMap_["Health"] = GenrePair (0xA0, 0xa4); + genreTypeIdMap_["Cooking"] = GenrePair (0xA0, 0xa5); + genreTypeIdMap_["House/garden"] = GenrePair (0xA0, 0xa7); + genreTypeIdMap_["Motorcycle"] = GenrePair (0xA0, 0xa3); + genreTypeIdMap_["Travel"] = GenrePair (0xA0, 0xa1); + genreTypeIdMap_["Aerobics"] = GenrePair (0xA0, 0xa4); + genreTypeIdMap_["Exercise"] = GenrePair (0xA0, 0xa4); + genreTypeIdMap_["Anthology"] = GenrePair (0x70, 0x74); + genreTypeIdMap_["Art"] = GenrePair (0x70, 0x72); + genreTypeIdMap_["Arts/crafts"] = GenrePair (0x70, 0x74); + genreTypeIdMap_["Fashion"] = GenrePair (0x70, 0x7b); + genreTypeIdMap_["Performing arts"] = GenrePair (0x70, 0x71); + genreTypeIdMap_["Spanish"] = GenrePair (0x90, 0x97); + genreTypeIdMap_["Religious"] = GenrePair (0x70, 0x73); + genreTypeIdMap_["Dance"] = GenrePair (0x60, 0x66); + genreTypeIdMap_["Animated"] = GenrePair (0x50, 0x55); + genreTypeIdMap_["Anime"] = GenrePair (0x50, 0x55); + genreTypeIdMap_["Children"] = GenrePair (0x50, 0x52); + genreTypeIdMap_["Children-music"] = GenrePair (0x50, 0x52); + genreTypeIdMap_["Children-special"] = GenrePair (0x50, 0x53); + genreTypeIdMap_["Holiday-children"] = GenrePair (0x50, 0x52); + genreTypeIdMap_["Holiday-children special"] = GenrePair (0x50, 0x52); + genreTypeIdMap_["Game show"] = GenrePair (0x30, 0x31); + genreTypeIdMap_["Talk"] = GenrePair (0x30, 0x33); + genreTypeIdMap_["Variety"] = GenrePair (0x30, 0x32); + genreTypeIdMap_["Debate"] = GenrePair (0x20, 0x24); + genreTypeIdMap_["Docudrama"] = GenrePair (0x20, 0x23); + genreTypeIdMap_["Documentary"] = GenrePair (0x20, 0x23); + genreTypeIdMap_["Interview"] = GenrePair (0x20, 0x24); + genreTypeIdMap_["News"] = GenrePair (0x20, 0x21); + genreTypeIdMap_["Newsmagazine"] = GenrePair (0x20, 0x21); + genreTypeIdMap_["Weather"] = GenrePair (0x20, 0x21); + genreTypeIdMap_["Action"] = GenrePair (0x10, 0x12); + genreTypeIdMap_["Adults only"] = GenrePair (0x10, 0x18); + genreTypeIdMap_["Adventure"] = GenrePair (0x10, 0x12); + genreTypeIdMap_["Comedy"] = GenrePair (0x10, 0x14); + genreTypeIdMap_["Comedy-drama"] = GenrePair (0x10, 0x14); + genreTypeIdMap_["Crime"] = GenrePair (0x10, 0x11); + genreTypeIdMap_["Crime drama"] = GenrePair (0x10, 0x11); + genreTypeIdMap_["Drama"] = GenrePair (0x10, 0x18); + genreTypeIdMap_["Fantasy"] = GenrePair (0x10, 0x13); + genreTypeIdMap_["Horror"] = GenrePair (0x10, 0x13); + genreTypeIdMap_["Musical"] = GenrePair (0x60, 0x65); + genreTypeIdMap_["Musical comedy"] = GenrePair (0x60, 0x65); + genreTypeIdMap_["Mystery"] = GenrePair (0x10, 0x12); + genreTypeIdMap_["Romance"] = GenrePair (0x10, 0x16); + genreTypeIdMap_["Romance-comedy"] = GenrePair (0x10, 0x16); + genreTypeIdMap_["Science fiction"] = GenrePair (0x10, 0x13); + genreTypeIdMap_["Suspense"] = GenrePair (0x10, 0x11); + genreTypeIdMap_["War"] = GenrePair (0x10, 0x12); + genreTypeIdMap_["Western"] = GenrePair (0x10, 0x12); + genreTypeIdMap_["Action sports"] = GenrePair (0x40, 0x4b); + }; + ~GenreIdMapper() + { + }; + + const GenrePair& getGenreTypeId(CStdString& genre) + { + std::map::iterator it; + it = genreTypeIdMap_.find(genre); + if(it != genreTypeIdMap_.end()) + return it->second; + return c_unknown_; + }; + +private: + std::map genreTypeIdMap_; + const GenrePair c_unknown_; +}; + +GenreIdMapper GetProgramGuideResult::s_mapper_; + +GetProgramGuideResult::GetProgramGuideResult() { +} + +GetProgramGuideResult::~GetProgramGuideResult() { +} + +void GetProgramGuideResult::parseData(const CStdString& xmlData) { + TiXmlDocument xml; + xml.Parse(xmlData.c_str(), 0, TIXML_ENCODING_LEGACY); + + TiXmlElement* rootXmlNode = xml.RootElement(); + + if (!rootXmlNode) { + errors_.push_back(" No root node parsed"); + CLog::Log(LOGDEBUG, "MythXML GetProgramGuideResult - No root node parsed"); + return; + } + + TiXmlElement* programGuideResponseNode = NULL; + CStdString strValue = rootXmlNode->Value(); + if (strValue.Find("GetProgramGuideResponse") >= 0 ) { + programGuideResponseNode = rootXmlNode; + } + else if (strValue.Find("detail") >= 0 ) { + // process the error. + TiXmlElement* errorCodeXmlNode = rootXmlNode->FirstChildElement("errorCode"); + TiXmlElement* errorDescXmlNode = rootXmlNode->FirstChildElement("errorDescription"); + CStdString error; + error.Format("ErrorCode [%i] - %s", errorCodeXmlNode->GetText(), errorDescXmlNode->GetText()); + errors_.push_back(error); + return; + } + else + return; + + TiXmlElement* programGuideNode = programGuideResponseNode->FirstChildElement("ProgramGuide"); + TiXmlElement* channelsNode = programGuideNode->FirstChildElement("Channels"); + TiXmlElement* channelNode = NULL; + TiXmlElement* programNode = NULL; + for( channelNode = channelsNode->FirstChildElement("Channel"); channelNode; channelNode = channelNode->NextSiblingElement("Channel")){ + int chanId = atoi(channelNode->Attribute("chanId")); + for( programNode = channelNode->FirstChildElement("Program"); programNode; programNode = programNode->NextSiblingElement("Program")){ + CStdString category = programNode->Attribute("category"); + CStdString itemStart = programNode->Attribute("startTime"); + CStdString itemEnd = programNode->Attribute("endTime"); + const GenrePair& genres = s_mapper_.getGenreTypeId(category); + SEpg epg; + epg.chan_num = chanId; + epg.description = programNode->GetText(); + epg.title = programNode->Attribute("title"); + epg.subtitle = programNode->Attribute("subTitle"); + epg.genre_type = genres.genretype_; + epg.genre_subtype = genres.genresubtype_; + epg.start_time = MythXmlCommandResult::convertTimeStringToObject(itemStart); + epg.end_time = MythXmlCommandResult::convertTimeStringToObject(itemEnd); + epg_.push_back(epg); + } + } +} diff --git a/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideResult.h b/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideResult.h new file mode 100644 index 0000000000..b54e0ef6b7 --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/GetProgramGuideResult.h @@ -0,0 +1,25 @@ +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETPROGRAMGUIDERESULT_H +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETPROGRAMGUIDERESULT_H + +#include + +#include "MythXmlCommandResult.h" +#include "SEpg.h" + +class GenreIdMapper; + +class GetProgramGuideResult:public MythXmlCommandResult +{ +public: + GetProgramGuideResult(); + virtual ~GetProgramGuideResult(); + virtual void parseData(const CStdString& xmlData); + inline const vector& getEpg() {return epg_;}; + +private: + void initialize(); + vector epg_; + static GenreIdMapper s_mapper_; +}; + +#endif // XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_GETPROGRAMGUIDERESULT_H diff --git a/xbmc/pvrclients/mythtv/libmythxml/Makefile b/xbmc/pvrclients/mythtv/libmythxml/Makefile new file mode 100644 index 0000000000..27e24d286e --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/Makefile @@ -0,0 +1,16 @@ +INCLUDES += -I. -I../../ -I../../../linux -I../../../ -I../../../../xbmc/addons/include -I../../../../guilib +DEFINES += -D_LINUX -fPIC + +OBJS = GetProgramGuideResult.o \ + GetChannelListResult.o \ + GetNumChannelsResult.o \ + GetProgramGuideParameters.o \ + MythXmlCommand.o \ + MythXmlCommandParameters.o + +LIB = libmythxml.a + +# all is the default rule +all: $(LIB) + +include ../../../../Makefile.include diff --git a/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommand.cpp b/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommand.cpp new file mode 100644 index 0000000000..049fd3e59f --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommand.cpp @@ -0,0 +1,42 @@ +/* + * MythXmlCommand.cpp + * + * Created on: Oct 7, 2010 + * Author: mythtv + */ + +#include "MythXmlCommand.h" + +#include "FileSystem/FileCurl.h" + +#include "utils/log.h" + +using namespace XFILE; + +MythXmlCommand::MythXmlCommand() { +} + +MythXmlCommand::~MythXmlCommand() { +} + +void MythXmlCommand::execute(const CStdString& hostname, int port, const MythXmlCommandParameters& params, MythXmlCommandResult& result, int timeout){ + CStdString strUrl = createRequestUrl(hostname, port, params); + CStdString strXML; + CFileCurl http; + http.SetTimeout(timeout); + if (http.Get(strUrl, strXML)) + { + CLog::Log(LOGDEBUG, "Got response from mythtv backend: %s", strUrl.c_str()); + } + http.Cancel(); + result.parseData(strXML); +} + +CStdString MythXmlCommand::createRequestUrl(const CStdString& hostname, int port, const MythXmlCommandParameters& params){ + CStdString requestURL; + requestURL.Format("http://%s:%i/Myth/%s", hostname.c_str(), port, getCommand().c_str()); + if(params.hasParameters()){ + requestURL += params.createParameterString(); + } + return requestURL; +} diff --git a/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommand.h b/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommand.h new file mode 100644 index 0000000000..9bc114c2fe --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommand.h @@ -0,0 +1,49 @@ +/* + * MythXmlCommand.h + * + * Created on: Oct 7, 2010 + * Author: tafypz + */ + +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_MYTHXMLCOMMAND_H_ +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_MYTHXMLCOMMAND_H_ + + +#include "MythXmlCommandResult.h" +#include "MythXmlCommandParameters.h" + +#include "../StdString.h" + +/*! \class MythXmlCommand + \brief The base class for all MythXML Commands. + This class will be subclassed by all MythXML commands; it provides a few basic services: + - creation of the request url (complete with parameters). + - execution of the created request. + */ +class MythXmlCommand { +public: + MythXmlCommand(); + virtual ~MythXmlCommand(); + /*! \brief Execute the command with the given request parameters. + \param hostname the mythtv backend hostname to connect to. + \param port the mythtv backend port to connect to. + \param params the parameters specific to the command. + \param result the result instance to use to handle the data returned by the request. + \param timeout the timeout value for this request. + */ + void execute(const CStdString& hostname, int port, const MythXmlCommandParameters& params, MythXmlCommandResult& result, int timeout); +protected: + /*! \brief The MythXML Command to use. + \return The MythXML Command to use. + */ + virtual const CStdString& getCommand() const = 0; +private: + /*! \brief creates the url to use to issue the request. + \param hostname the mythtv backend hostname to connect to. + \param port the mythtv backend port to connect to. + \param params the parameters specific to the command. + */ + CStdString createRequestUrl(const CStdString& hostname, int port, const MythXmlCommandParameters& params); +}; + +#endif /* XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_MYTHXMLCOMMAND_H_ */ diff --git a/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommandParameters.cpp b/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommandParameters.cpp new file mode 100644 index 0000000000..076bc0b775 --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommandParameters.cpp @@ -0,0 +1,33 @@ +/* + * MythXmlCommandParameters.cpp + * + * Created on: Oct 8, 2010 + * Author: tafypz + */ + +#include "MythXmlCommandParameters.h" +#include "DateTime.h" + +MythXmlCommandParameters::MythXmlCommandParameters(bool hasParameters) { + hasParameters_ = hasParameters; +} + +MythXmlCommandParameters::~MythXmlCommandParameters() { +} + +CStdString MythXmlCommandParameters::convertTimeToString(const CDateTime& time){ + CStdString result = "%i-%02.2i-%02.2iT%02.2i:%02.2i"; + result.Format(result.c_str(), time.GetYear(), time.GetMonth(), time.GetDay(), time.GetHour(), time.GetMinute()); + return result; +} + +MythXmlEmptyCommandParameters::MythXmlEmptyCommandParameters() : MythXmlCommandParameters(false){ +} + +MythXmlEmptyCommandParameters::~MythXmlEmptyCommandParameters(){ +} + +CStdString MythXmlEmptyCommandParameters::createParameterString() const{ + static CStdString result = ""; + return result; +}; diff --git a/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommandParameters.h b/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommandParameters.h new file mode 100644 index 0000000000..d163d75cd8 --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommandParameters.h @@ -0,0 +1,43 @@ +/* + * MythXmlCommandParameters.h + * + * Created on: Oct 8, 2010 + * Author: tafypz + */ + +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_MYTHXMLCOMMANDPARAMETERS_H_ +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_MYTHXMLCOMMANDPARAMETERS_H_ + +#include "DateTime.h" + +#include "../StdString.h" + +/*! \class MythXmlCommandParameters + \brief This is the base abstract class for all MythXML command parameters. + This class should be subclassed in order to provide parameters to the mythxml commands. + It has the responsibility to create a list of HTTP parameters to use for the request. + A subclass called \ref MythXmlEmptyCommandParameters MythXmlEmptyCommandParameters has been provided for convenience + in case the mythXML command doesn't need parameters. + */ +class MythXmlCommandParameters { +public: + static CStdString convertTimeToString(const CDateTime& time); + + MythXmlCommandParameters(bool hasParameters); + virtual ~MythXmlCommandParameters(); + virtual CStdString createParameterString() const = 0; + inline bool hasParameters() const {return hasParameters_;}; +private: + bool hasParameters_; +}; + +/*! \class MythXmlEmptyCommandParameters + \brief This class is to be used with commands that do not take parameters. + */ +class MythXmlEmptyCommandParameters : public MythXmlCommandParameters { + MythXmlEmptyCommandParameters(); + virtual ~MythXmlEmptyCommandParameters(); + virtual CStdString createParameterString() const; +}; + +#endif /* MYTHXMLCOMMANDPARAMETERS_H_ */ diff --git a/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommandResult.h b/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommandResult.h new file mode 100644 index 0000000000..fcf450a0e6 --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/MythXmlCommandResult.h @@ -0,0 +1,35 @@ +/* + * MythXmlCommandResult.h + * + * Created on: Oct 7, 2010 + * Author: tafypz + */ + +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_MYTHXMLCOMMANDRESULT_H_ +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_MYTHXMLCOMMANDRESULT_H_ + +#include +#include "DateTime.h" +#include "../StdString.h" + +using std::vector; +class MythXmlCommandResult { +public: + static CDateTime convertTimeStringToObject(const CStdString& time) + { + int year = 0, month = 0, day = 0; + int hour = 0, minute = 0, second = 0; + sscanf(time.c_str(), "%d-%d-%dT%d:%d:%d", &year, &month, &day, &hour, &minute, &second); + return CDateTime(year, month, day,hour,minute, second); + } + + MythXmlCommandResult(){}; + virtual ~MythXmlCommandResult(){}; + inline bool isSuccess() const { return errors_.empty(); }; + inline vector getErrors() const {return errors_;}; + virtual void parseData(const CStdString& xmlData) = 0; +protected: + vector errors_; +}; + +#endif /* XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_MYTHXMLCOMMANDRESULT_H_ */ diff --git a/xbmc/pvrclients/mythtv/libmythxml/SChannel.h b/xbmc/pvrclients/mythtv/libmythxml/SChannel.h new file mode 100644 index 0000000000..a4df434997 --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/SChannel.h @@ -0,0 +1,18 @@ +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_SCHANNEL_H_ +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_SCHANNEL_H_ + +#include "../StdString.h" + +struct SChannel +{ + int id; + CStdString name; + CStdString callsign; + CStdString number; + + SChannel() { + id = 0; + } +}; + +#endif /* XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_SCHANNEL_H_ */ diff --git a/xbmc/pvrclients/mythtv/libmythxml/SEpg.h b/xbmc/pvrclients/mythtv/libmythxml/SEpg.h new file mode 100644 index 0000000000..86bea1d2ff --- /dev/null +++ b/xbmc/pvrclients/mythtv/libmythxml/SEpg.h @@ -0,0 +1,29 @@ +#ifndef XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_SEPG_H_ +#define XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_SEPG_H_ + +#include "../StdString.h" +#include "DateTime.h" + +struct SEpg +{ + int id; + int chan_num; + int genre_type; + int genre_subtype; + int parental_rating; + CStdString title; + CStdString subtitle; + CStdString description; + CDateTime start_time; + CDateTime end_time; + + SEpg() { + id = 0; + chan_num = 0; + genre_type = 0; + genre_subtype = 0; + parental_rating = 0; + } +}; + +#endif /* XBMC_PVRCLIENTS_MYTHTV_LIBMYTHXML_SEPG_H_ */ diff --git a/xbmc/pvrclients/mythtv/linux/pvrclient-mythtv_os_posix.h b/xbmc/pvrclients/mythtv/linux/pvrclient-mythtv_os_posix.h new file mode 100644 index 0000000000..02e1450bc5 --- /dev/null +++ b/xbmc/pvrclients/mythtv/linux/pvrclient-mythtv_os_posix.h @@ -0,0 +1,108 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef PVRCLIENT_MYTHTV_OS_POSIX_H +#define PVRCLIENT_MYTHTV_OS_POSIX_H + +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef __APPLE__ +#include +#endif +#include +#include +#include +#include +#include + +typedef int bool_t; +typedef int SOCKET; + +#define __close(a) close(a) +#define __select select +#define __recv recv +#define __shutdown shutdown +#define __socket socket +#define __bind bind +#define __getsockname getsockname +#define __connect connect +#define __getpeername getpeername +#define __send send +#define __getsockopt getsockopt +#define __listen listen +#define __accept accept +#define __setsockopt setsockopt +#define __fcntl fcntl +#define __gethostbyname gethostbyname +#define __read read +#define __write write +#define __poll poll +#define SOCKET_ERROR (-1) +#define INVALID_SOCKET (-1) +#define SD_BOTH SHUT_RDWR + +#define LIBTYPE +#define sock_getlasterror errno +#define sock_getlasterror_socktimeout (errno == EAGAIN) +#define console_vprintf vprintf +#define console_printf printf +#define THREAD_FUNC_PREFIX void * + +#ifndef __STL_CONFIG_H +template inline T min(T a, T b) { return a <= b ? a : b; } +template inline T max(T a, T b) { return a >= b ? a : b; } +template inline int sgn(T a) { return a < 0 ? -1 : a > 0 ? 1 : 0; } +template inline void swap(T &a, T &b) { T t = a; a = b; b = t; } +#endif + +#define Sleep(t) usleep(t*1000) + +static inline uint64_t getcurrenttime(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + return ((uint64_t)t.tv_sec * 1000) + (t.tv_usec / 1000); +} + +static inline int setsocktimeout(int s, int level, int optname, uint64_t timeout) +{ + struct timeval t; + t.tv_sec = timeout / 1000; + t.tv_usec = (timeout % 1000) * 1000; + return setsockopt(s, level, optname, (char *)&t, sizeof(t)); +} + +#endif diff --git a/xbmc/pvrclients/mythtv/pvrclient-mythtv_os.h b/xbmc/pvrclients/mythtv/pvrclient-mythtv_os.h new file mode 100644 index 0000000000..032af6cfaa --- /dev/null +++ b/xbmc/pvrclients/mythtv/pvrclient-mythtv_os.h @@ -0,0 +1,44 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef PVRCLIENT_MYTHTV_OS_H +#define PVRCLIENT_MYTHTV_OS_H + +#if defined(_WIN32) || defined(_WIN64) +#define __WINDOWS__ +#endif + +#if defined(__WINDOWS__) +/* windows code not in yet */ +#else +#include "linux/pvrclient-mythtv_os_posix.h" +#endif + +#if !defined(TRUE) +#define TRUE 1 +#endif + +#if !defined(FALSE) +#define FALSE 0 +#endif + +#endif diff --git a/xbmc/pvrclients/tvheadend/HTSPData.cpp b/xbmc/pvrclients/tvheadend/HTSPData.cpp new file mode 100644 index 0000000000..850b0655f3 --- /dev/null +++ b/xbmc/pvrclients/tvheadend/HTSPData.cpp @@ -0,0 +1,567 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "HTSPData.h" + +extern "C" { +#include "libhts/htsmsg.h" +#include "libhts/htsmsg_binary.h" +} + +#define CMD_LOCK cMutexLock CmdLock((cMutex*)&m_Mutex) + +cHTSPData::cHTSPData() +{ +} + +cHTSPData::~cHTSPData() +{ + Close(); +} + +bool cHTSPData::Open(CStdString hostname, int port, CStdString user, CStdString pass, long timeout) +{ + if(!m_session.Connect(hostname, port)) + return false; + + if(m_session.GetProtocol() < 2) + { + XBMC->Log(LOG_ERROR, "Incompatible protocol version %d", m_session.GetProtocol()); + return false; + } + + if(!user.IsEmpty()) + m_session.Auth(user, pass); + + if(!m_session.SendEnableAsync()) + return false; + + SetDescription("HTSP Data Listener"); + Start(); + + m_started.Wait(timeout); + return Running(); +} + +void cHTSPData::Close() +{ + Cancel(3); + m_session.Abort(); + m_session.Close(); +} + +bool cHTSPData::CheckConnection() +{ + return true; +} + +htsmsg_t* cHTSPData::ReadResult(htsmsg_t* m) +{ + m_Mutex.Lock(); + unsigned seq (m_session.AddSequence()); + + SMessage &message(m_queue[seq]); + message.event = new cCondWait(); + message.msg = NULL; + + m_Mutex.Unlock(); + htsmsg_add_u32(m, "seq", seq); + if(!m_session.SendMessage(m)) + { + m_queue.erase(seq); + return NULL; + } + + if(!message.event->Wait(2000)) + XBMC->Log(LOG_ERROR, "cHTSPData::ReadResult - Timeout waiting for response"); + m_Mutex.Lock(); + + m = message.msg; + delete message.event; + + m_queue.erase(seq); + + m_Mutex.Unlock(); + return m; +} + +bool cHTSPData::GetDriveSpace(long long *total, long long *used) +{ + htsmsg_t *msg = htsmsg_create_map(); + htsmsg_add_str(msg, "method", "getDiskSpace"); + if ((msg = ReadResult(msg)) == NULL) + { + XBMC->Log(LOG_DEBUG, "cHTSPData::GetDriveSpace - failed to get getDiskSpace"); + return false; + } + + int64_t freespace; + if (htsmsg_get_s64(msg, "freediskspace", &freespace) != 0) + return false; + + int64_t totalspace; + if (htsmsg_get_s64(msg, "totaldiskspace", &totalspace) != 0) + return false; + + *total = totalspace / 1024; + *used = (totalspace - freespace) / 1024; + return true; +} + +bool cHTSPData::GetTime(time_t *localTime, int *gmtOffset) +{ + XBMC->Log(LOG_DEBUG, "cHTSPData::GetTime()"); + + htsmsg_t *msg = htsmsg_create_map(); + htsmsg_add_str(msg, "method", "getSysTime"); + if ((msg = ReadResult(msg)) == NULL) + { + XBMC->Log(LOG_ERROR, "cHTSPData::GetTime - failed to get sysTime"); + return false; + } + + unsigned int secs; + if (htsmsg_get_u32(msg, "time", &secs) != 0) + return false; + + int offset; + if (htsmsg_get_s32(msg, "timezone", &offset) != 0) + return false; + + XBMC->Log(LOG_DEBUG, "cHTSPData::GetTime - tvheadend reported time=%u, timezone=%d, correction=%d", + secs, offset, g_iEpgOffsetCorrection * 60); + + /* XBMC needs the timezone difference in seconds from GMT */ + offset = (offset - (g_iEpgOffsetCorrection * 60)) * 60; + + *localTime = secs + offset; + *gmtOffset = -offset; + return true; +} + +int cHTSPData::GetNumChannels() +{ + return GetChannels().size(); +} + +PVR_ERROR cHTSPData::RequestChannelList(PVRHANDLE handle, int radio) +{ + if (!CheckConnection()) + return PVR_ERROR_SERVER_ERROR; + + SChannels channels = GetChannels(); + for(SChannels::iterator it = channels.begin(); it != channels.end(); ++it) + { + SChannel& channel = it->second; + + PVR_CHANNEL tag; + memset(&tag, 0 , sizeof(tag)); + tag.uid = channel.id; + tag.number = channel.id; + tag.name = channel.name.c_str(); + tag.callsign = channel.name.c_str(); + tag.radio = channel.radio; + tag.encryption = channel.caid; + tag.input_format = ""; + tag.stream_url = ""; + tag.bouquet = 0; + + if(((bool)radio) == tag.radio) + { + PVR->TransferChannelEntry(handle, &tag); + } + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR cHTSPData::RequestEPGForChannel(PVRHANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end) +{ + if (!CheckConnection()) + return PVR_ERROR_SERVER_ERROR; + + SChannels channels = GetChannels(); + + if (channels.find(channel.uid) != channels.end()) + { + time_t stop; + + SEvent event; + event.id = channels[channel.uid].event; + if (event.id == 0) + return PVR_ERROR_NO_ERROR; + + do + { + bool success = GetEvent(event, event.id); + if (success) + { + PVR_PROGINFO broadcast; + memset(&broadcast, 0, sizeof(PVR_PROGINFO)); + + broadcast.channum = event.chan_id >= 0 ? event.chan_id : channel.number; + broadcast.uid = event.id; + broadcast.title = event.title.c_str(); + broadcast.subtitle = event.title.c_str(); + broadcast.description = event.descs.c_str(); + broadcast.starttime = event.start; + broadcast.endtime = event.stop; + broadcast.genre_type = (event.content & 0x0F) << 4; + broadcast.genre_sub_type = event.content & 0xF0; + PVR->TransferEpgEntry(handle, &broadcast); + + event.id = event.next; + stop = event.stop; + } + else + break; + + } while(end > stop); + + return PVR_ERROR_NO_ERROR; + } + + return PVR_ERROR_NO_ERROR; +} + +SRecordings cHTSPData::GetDVREntries(bool recorded, bool scheduled) +{ + time_t localTime; + int gmtOffset; + GetTime(&localTime, &gmtOffset); + + CMD_LOCK; + SRecordings recordings; + + for(SRecordings::const_iterator it = m_recordings.begin(); it != m_recordings.end(); ++it) + { + SRecording recording = it->second; + + bool recordedOrRecording = (unsigned int)localTime >= recording.start; + bool scheduledOrRecording = (unsigned int)localTime <= recording.stop; + + if ((scheduledOrRecording && scheduled) || (recordedOrRecording && recorded)) + recordings[recording.id] = recording; + } + + return recordings; +} + +int cHTSPData::GetNumRecordings() +{ + SRecordings recordings = GetDVREntries(true, false); + return recordings.size(); +} + +PVR_ERROR cHTSPData::RequestRecordingsList(PVRHANDLE handle) +{ + time_t localTime; + int gmtOffset; + GetTime(&localTime, &gmtOffset); + + SRecordings recordings = GetDVREntries(true, false); + + for(SRecordings::const_iterator it = recordings.begin(); it != recordings.end(); ++it) + { + SRecording recording = it->second; + + PVR_RECORDINGINFO tag; + tag.index = recording.id; + tag.directory = "/"; + tag.subtitle = ""; + tag.channel_name = ""; + tag.recording_time = recording.start + gmtOffset; + tag.duration = recording.stop - recording.start; + tag.description = recording.description.c_str(); + tag.title = recording.title.c_str(); + + CStdString streamURL = "http://"; + { + CMD_LOCK; + SChannels::const_iterator itr = m_channels.find(recording.channel); + if (itr != m_channels.end()) + tag.channel_name = itr->second.name.c_str(); + else + tag.channel_name = ""; + + if (g_szUsername != "") + { + streamURL += g_szUsername; + if (g_szPassword != "") + { + streamURL += ":"; + streamURL += g_szPassword; + } + streamURL += "@"; + } + streamURL.Format("%s%s:%i/dvrfile/%i", streamURL.c_str(), g_szHostname.c_str(), g_iPortHTTP, recording.id); + } + tag.stream_url = streamURL.c_str(); + + PVR->TransferRecordingEntry(handle, &tag); + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR cHTSPData::DeleteRecording(const PVR_RECORDINGINFO &recinfo) +{ + XBMC->Log(LOG_DEBUG, "cHTSPData::DeleteRecording()"); + + htsmsg_t *msg = htsmsg_create_map(); + htsmsg_add_str(msg, "method", "deleteDvrEntry"); + htsmsg_add_u32(msg, "id", recinfo.index); + if ((msg = ReadResult(msg)) == NULL) + { + XBMC->Log(LOG_DEBUG, "cHTSPData::DeleteRecording - Failed to get deleteDvrEntry"); + return PVR_ERROR_SERVER_ERROR; + } + + unsigned int success; + if (htsmsg_get_u32(msg, "success", &success) != 0) + { + XBMC->Log(LOG_DEBUG, "cHTSPData::DeleteRecording - Failed to parse param"); + return PVR_ERROR_SERVER_ERROR; + } + + return success > 0 ? PVR_ERROR_NO_ERROR : PVR_ERROR_NOT_DELETED; +} + +int cHTSPData::GetNumTimers() +{ + SRecordings recordings = GetDVREntries(false, true); + return recordings.size(); +} + +PVR_ERROR cHTSPData::RequestTimerList(PVRHANDLE handle) +{ + time_t localTime; + int gmtOffset; + GetTime(&localTime, &gmtOffset); + + SRecordings recordings = GetDVREntries(false, true); + + for(SRecordings::const_iterator it = recordings.begin(); it != recordings.end(); ++it) + { + SRecording recording = it->second; + + PVR_TIMERINFO tag; + tag.index = recording.id; + tag.channelNum = recording.channel; + tag.starttime = recording.start; + tag.endtime = recording.stop; + tag.active = true; + tag.recording = (localTime > tag.starttime) && (localTime < tag.endtime); + tag.title = recording.title.c_str(); + tag.directory = "/"; + tag.priority = 0; + tag.lifetime = tag.endtime - tag.starttime; + tag.repeat = false; + tag.repeatflags = 0; + + PVR->TransferTimerEntry(handle, &tag); + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR cHTSPData::DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force) +{ + XBMC->Log(LOG_DEBUG, "cHTSPData::DeleteRecording()"); + + htsmsg_t *msg = htsmsg_create_map(); + htsmsg_add_str(msg, "method", "deleteDvrEntry"); + htsmsg_add_u32(msg, "id", timerinfo.index); + if ((msg = ReadResult(msg)) == NULL) + { + XBMC->Log(LOG_DEBUG, "cHTSPData::DeleteRecording - Failed to get deleteDvrEntry"); + return PVR_ERROR_SERVER_ERROR; + } + + unsigned int success; + if (htsmsg_get_u32(msg, "success", &success) != 0) + { + XBMC->Log(LOG_DEBUG, "cHTSPData::DeleteRecording - Failed to parse param"); + return PVR_ERROR_SERVER_ERROR; + } + + return success > 0 ? PVR_ERROR_NO_ERROR : PVR_ERROR_NOT_DELETED; +} + +PVR_ERROR cHTSPData::AddTimer(const PVR_TIMERINFO &timerinfo) +{ + XBMC->Log(LOG_DEBUG, "cHTSPData::AddTimer()"); + + htsmsg_t *msg = htsmsg_create_map(); + htsmsg_add_str(msg, "method", "addDvrEntry"); + htsmsg_add_u32(msg, "eventId", timerinfo.index); + if ((msg = ReadResult(msg)) == NULL) + { + XBMC->Log(LOG_DEBUG, "cHTSPData::AddTimer - Failed to get addDvrEntry"); + return PVR_ERROR_SERVER_ERROR; + } + + unsigned int success; + if (htsmsg_get_u32(msg, "success", &success) != 0) + { + XBMC->Log(LOG_DEBUG, "cHTSPData::AddTimer - Failed to parse param"); + return PVR_ERROR_SERVER_ERROR; + } + + return success > 0 ? PVR_ERROR_NO_ERROR : PVR_ERROR_NOT_DELETED; +} + +void cHTSPData::Action() +{ + XBMC->Log(LOG_DEBUG, "cHTSPData::Action() - Starting"); + + htsmsg_t* msg; + + while (Running()) + { + if((msg = m_session.ReadMessage()) == NULL) + break; + + uint32_t seq; + if(htsmsg_get_u32(msg, "seq", &seq) == 0) + { + CMD_LOCK; + SMessages::iterator it = m_queue.find(seq); + if(it != m_queue.end()) + { + it->second.msg = msg; + it->second.event->Signal(); + continue; + } + } + + const char* method; + if((method = htsmsg_get_str(msg, "method")) == NULL) + { + htsmsg_destroy(msg); + continue; + } + + CMD_LOCK; + if (strstr(method, "channelAdd")) + cHTSPSession::ParseChannelUpdate(msg, m_channels); + else if(strstr(method, "channelUpdate")) + cHTSPSession::ParseChannelUpdate(msg, m_channels); + else if(strstr(method, "channelDelete")) + cHTSPSession::ParseChannelRemove(msg, m_channels); + else if(strstr(method, "tagAdd")) + cHTSPSession::ParseTagUpdate(msg, m_tags); + else if(strstr(method, "tagUpdate")) + cHTSPSession::ParseTagUpdate(msg, m_tags); + else if(strstr(method, "tagDelete")) + cHTSPSession::ParseTagRemove(msg, m_tags); + else if(strstr(method, "initialSyncCompleted")) + m_started.Signal(); + else if(strstr(method, "dvrEntryAdd")) + cHTSPSession::ParseDVREntryUpdate(msg, m_recordings); + else if(strstr(method, "dvrEntryUpdate")) + cHTSPSession::ParseDVREntryUpdate(msg, m_recordings); + else if(strstr(method, "dvrEntryDelete")) + cHTSPSession::ParseDVREntryDelete(msg, m_recordings); + else + XBMC->Log(LOG_DEBUG, "Unmapped action recieved '%s'", method); + + htsmsg_destroy(msg); + } + + m_started.Signal(); + XBMC->Log(LOG_DEBUG, "cHTSPData::Action() - Exiting"); +} + +SChannels cHTSPData::GetChannels() +{ + return GetChannels(0); +} + +SChannels cHTSPData::GetChannels(int tag) +{ + CMD_LOCK; + if(tag == 0) + return m_channels; + + STags::iterator it = m_tags.find(tag); + if(it == m_tags.end()) + { + SChannels channels; + return channels; + } + return GetChannels(it->second); +} + +SChannels cHTSPData::GetChannels(STag& tag) +{ + CMD_LOCK; + SChannels channels; + + std::vector::iterator it; + for(it = tag.channels.begin(); it != tag.channels.end(); it++) + { + SChannels::iterator it2 = m_channels.find(*it); + if(it2 == m_channels.end()) + { + XBMC->Log(LOG_ERROR, "cHTSPData::GetChannels - tag points to unknown channel %d", *it); + continue; + } + channels[*it] = it2->second; + } + return channels; +} + +STags cHTSPData::GetTags() +{ + CMD_LOCK; + return m_tags; +} + +bool cHTSPData::GetEvent(SEvent& event, uint32_t id) +{ + if(id == 0) + { + event.Clear(); + return false; + } + + SEvents::iterator it = m_events.find(id); + if(it != m_events.end()) + { + event = it->second; + return true; + } + + htsmsg_t *msg = htsmsg_create_map(); + htsmsg_add_str(msg, "method", "getEvent"); + htsmsg_add_u32(msg, "eventId", id); + if((msg = ReadResult(msg)) == NULL) + { + XBMC->Log(LOG_DEBUG, "cHTSPData::GetEvent - failed to get event %u", id); + return false; + } + if(!cHTSPSession::ParseEvent(msg, id, event)) + return false; + + m_events[id] = event; + return true; +} diff --git a/xbmc/pvrclients/tvheadend/HTSPData.h b/xbmc/pvrclients/tvheadend/HTSPData.h new file mode 100644 index 0000000000..9351eedbaf --- /dev/null +++ b/xbmc/pvrclients/tvheadend/HTSPData.h @@ -0,0 +1,82 @@ +#pragma once + +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "client.h" +#include "thread.h" +#include "HTSPSession.h" + +class cHTSPData : public cThread +{ +public: + cHTSPData(); + ~cHTSPData(); + + bool Open(CStdString hostname, int port, CStdString user, CStdString pass, long timeout); + void Close(); + bool CheckConnection(); + + htsmsg_t* ReadResult(htsmsg_t* m); + int GetProtocol() { return m_session.GetProtocol(); } + CStdString GetServerName() { return m_session.GetServerName(); } + CStdString GetVersion() { return m_session.GetVersion(); } + bool GetDriveSpace(long long *total, long long *used); + bool GetTime(time_t *localTime, int *gmtOffset); + int GetNumChannels(); + int GetNumRecordings(); + PVR_ERROR RequestChannelList(PVRHANDLE handle, int radio); + PVR_ERROR RequestEPGForChannel(PVRHANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end); + PVR_ERROR RequestRecordingsList(PVRHANDLE handle); + PVR_ERROR DeleteRecording(const PVR_RECORDINGINFO &recinfo); + PVR_ERROR AddTimer(const PVR_TIMERINFO &timerinfo); + + int GetNumTimers(); + PVR_ERROR RequestTimerList(PVRHANDLE handle); + PVR_ERROR DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force); + +protected: + virtual void Action(void); + +private: + struct SMessage + { + cCondWait * event; + htsmsg_t * msg; + }; + typedef std::map SMessages; + + SChannels GetChannels(); + SChannels GetChannels(int tag); + SChannels GetChannels(STag &tag); + STags GetTags(); + bool GetEvent(SEvent& event, uint32_t id); + SRecordings GetDVREntries(bool recorded, bool scheduled); + + cHTSPSession m_session; + cCondWait m_started; + cMutex m_Mutex; + SChannels m_channels; + STags m_tags; + SEvents m_events; + SMessages m_queue; + SRecordings m_recordings; +}; diff --git a/xbmc/pvrclients/tvheadend/HTSPDemux.cpp b/xbmc/pvrclients/tvheadend/HTSPDemux.cpp new file mode 100644 index 0000000000..25ababb1e1 --- /dev/null +++ b/xbmc/pvrclients/tvheadend/HTSPDemux.cpp @@ -0,0 +1,578 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#define __STDC_CONSTANT_MACROS +#include +#include +#include // For codec id's +#include "HTSPDemux.h" + +extern "C" { +#include "libhts/net.h" +#include "libhts/htsmsg.h" +#include "libhts/htsmsg_binary.h" +#include "libhts/sha1.h" +} + +cHTSPDemux::cHTSPDemux() + : m_subs(0) + , m_channel(0) + , m_tag(0) + , m_StatusCount(0) + , m_SkipIFrame(0) +{ + m_Streams.nstreams = 0; +} + +cHTSPDemux::~cHTSPDemux() +{ + Close(); +} + +bool cHTSPDemux::Open(const PVR_CHANNEL &channelinfo) +{ + m_channel = channelinfo.number; + m_tag = channelinfo.bouquet; + + if(!m_session.Connect(g_szHostname, g_iPortHTSP)) + return false; + + if(!g_szUsername.IsEmpty()) + m_session.Auth(g_szUsername, g_szPassword); + + m_session.SendEnableAsync(); + + if(!m_session.SendSubscribe(m_subs, m_channel)) + return false; + + m_StatusCount = 0; + m_SkipIFrame = 0; + + while(m_Streams.nstreams == 0 && m_StatusCount == 0 ) + { + DemuxPacket* pkg = Read(); + if(!pkg) + { + Close(); + return false; + } + PVR->FreeDemuxPacket(pkg); + } + + return true; +} + +void cHTSPDemux::Close() +{ + m_session.Close(); +} + +bool cHTSPDemux::GetStreamProperties(PVR_STREAMPROPS* props) +{ + props->nstreams = m_Streams.nstreams; + for (int i = 0; i < m_Streams.nstreams; i++) + { + props->stream[i].id = m_Streams.stream[i].id; + props->stream[i].physid = m_Streams.stream[i].physid; + props->stream[i].codec_type = m_Streams.stream[i].codec_type; + props->stream[i].codec_id = m_Streams.stream[i].codec_id; + props->stream[i].height = m_Streams.stream[i].height; + props->stream[i].width = m_Streams.stream[i].width; + props->stream[i].language[0] = m_Streams.stream[i].language[0]; + props->stream[i].language[1] = m_Streams.stream[i].language[1]; + props->stream[i].language[2] = m_Streams.stream[i].language[2]; + props->stream[i].language[3] = m_Streams.stream[i].language[3]; + props->stream[i].identifier = m_Streams.stream[i].identifier; + } + return (props->nstreams > 0); +} + +void cHTSPDemux::Abort() +{ + m_Streams.nstreams = 0; + m_session.Abort(); +} + +DemuxPacket* cHTSPDemux::Read() +{ + htsmsg_t * msg; + const char* method; + while((msg = ReadStream())) + { + method = htsmsg_get_str(msg, "method"); + if(method == NULL) + break; + + if (strcmp("subscriptionStart", method) == 0) + { + SubscriptionStart(msg); + m_SkipIFrame = 0; + DemuxPacket* pkt = PVR->AllocateDemuxPacket(0); + pkt->iStreamId = DMX_SPECIALID_STREAMCHANGE; + htsmsg_destroy(msg); + return pkt; + } + else if(strcmp("subscriptionStop", method) == 0) + SubscriptionStop (msg); + else if(strcmp("subscriptionStatus", method) == 0) + SubscriptionStatus(msg); + else if(strcmp("queueStatus" , method) == 0) + cHTSPSession::ParseQueueStatus(msg, m_QueueStatus); + else if(strcmp("signalStatus" , method) == 0) + cHTSPSession::ParseSignalStatus(msg, m_Quality); + else if(strcmp("muxpkt" , method) == 0) + { + uint32_t index, duration, frametype; + const void* bin; + size_t binlen; + int64_t ts; + char frametypechar[1]; + + htsmsg_get_u32(msg, "frametype", &frametype); + frametypechar[0] = static_cast( frametype ); + XBMC->Log(LOG_DEBUG, "cHTSPDemux::Read - Frame type %c", frametypechar[0]); + + // Not the best solution - need to find how to pause video player for longer time. + // This way video player could get enough audio packets in buffer to play without audio discontinuity + + // Jumpy video error on channel change is fixed if first I-frame is not send to demuxer on MPEG2 stream + // Problem exists on some HD H264 stream, but it can help if 2 I-frames are skipped :D + // SD H264 stream not tested + if (g_bSkipIFrame && m_SkipIFrame == 0 && frametypechar[0]=='I') + { + m_SkipIFrame++; + XBMC->Log(LOG_DEBUG, "cHTSPDemux::Skipped first I-frame"); + htsmsg_destroy(msg); + DemuxPacket* pkt = PVR->AllocateDemuxPacket(0); + return pkt; + } + + if(htsmsg_get_u32(msg, "stream" , &index) || + htsmsg_get_bin(msg, "payload", &bin, &binlen)) + break; + + DemuxPacket* pkt = PVR->AllocateDemuxPacket(binlen); + memcpy(pkt->pData, bin, binlen); + + pkt->iSize = binlen; + + if(!htsmsg_get_u32(msg, "duration", &duration)) + pkt->duration = (double)duration * DVD_TIME_BASE / 1000000; + + if(!htsmsg_get_s64(msg, "dts", &ts)) + pkt->dts = (double)ts * DVD_TIME_BASE / 1000000; + else + pkt->dts = DVD_NOPTS_VALUE; + + if(!htsmsg_get_s64(msg, "pts", &ts)) + pkt->pts = (double)ts * DVD_TIME_BASE / 1000000; + else + pkt->pts = DVD_NOPTS_VALUE; + + pkt->iStreamId = -1; + for(int i = 0; i < m_Streams.nstreams; i++) + { + if(m_Streams.stream[i].physid == (int)index) + { + pkt->iStreamId = i; + break; + } + } + + htsmsg_destroy(msg); + return pkt; + } + + break; + } + + if(msg) + { + htsmsg_destroy(msg); + DemuxPacket* pkt = PVR->AllocateDemuxPacket(0); + return pkt; + } + return NULL; +} + +bool cHTSPDemux::SwitchChannel(const PVR_CHANNEL &channelinfo) +{ + XBMC->Log(LOG_DEBUG, "cHTSPDemux::SetChannel - changing to channel %d", channelinfo.number); + + if (!m_session.SendUnsubscribe(m_subs)) + XBMC->Log(LOG_ERROR, "cHTSPDemux::SetChannel - failed to unsubscribe from previous channel"); + + if (!m_session.SendSubscribe(m_subs+1, channelinfo.number)) + XBMC->Log(LOG_ERROR, "cHTSPDemux::SetChannel - failed to set channel"); + else + { + m_channel = channelinfo.number; + m_subs = m_subs+1; + m_Streams.nstreams = 0; + m_StatusCount = 0; + while (m_Streams.nstreams == 0 && m_StatusCount == 0) + { + DemuxPacket* pkg = Read(); + if (!pkg) + { + return false; + } + PVR->FreeDemuxPacket(pkg); + } + return true; + } + return false; +} + +bool cHTSPDemux::GetSignalStatus(PVR_SIGNALQUALITY &qualityinfo) +{ + if (m_SourceInfo.si_adapter.IsEmpty() || m_Quality.fe_status.IsEmpty()) + return false; + + strncpy(qualityinfo.frontend_name, m_SourceInfo.si_adapter.c_str(), sizeof(qualityinfo.frontend_name)); + strncpy(qualityinfo.frontend_status, m_Quality.fe_status.c_str(), sizeof(qualityinfo.frontend_status)); + qualityinfo.signal = (uint16_t)m_Quality.fe_signal; + qualityinfo.snr = (uint16_t)m_Quality.fe_snr; + qualityinfo.ber = (uint32_t)m_Quality.fe_ber; + qualityinfo.unc = (uint32_t)m_Quality.fe_unc; + qualityinfo.video_bitrate = 0; + qualityinfo.audio_bitrate = 0; + qualityinfo.dolby_bitrate = 0; + + return true; +} + +void cHTSPDemux::SubscriptionStart (htsmsg_t *m) +{ + htsmsg_t *streams; + htsmsg_field_t *f; + if((streams = htsmsg_get_list(m, "streams")) == NULL) + { + XBMC->Log(LOG_ERROR, "cHTSPDemux::SubscriptionStart - malformed message"); + return; + } + + m_Streams.nstreams = 0; + + HTSMSG_FOREACH(f, streams) + { + uint32_t index; + const char* type; + htsmsg_t* sub; + + if (f->hmf_type != HMF_MAP) + continue; + + sub = &f->hmf_msg; + + if ((type = htsmsg_get_str(sub, "type")) == NULL) + continue; + + if (htsmsg_get_u32(sub, "index", &index)) + continue; + + const char *language = htsmsg_get_str(sub, "language"); + XBMC->Log(LOG_DEBUG, "cHTSPDemux::SubscriptionStart - id: %d, type: %s, language: %s", index, type, language); + + m_Streams.stream[m_Streams.nstreams].fpsscale = 0; + m_Streams.stream[m_Streams.nstreams].fpsrate = 0; + m_Streams.stream[m_Streams.nstreams].height = 0; + m_Streams.stream[m_Streams.nstreams].width = 0; + m_Streams.stream[m_Streams.nstreams].aspect = 0.0; + + m_Streams.stream[m_Streams.nstreams].channels = 0; + m_Streams.stream[m_Streams.nstreams].samplerate = 0; + m_Streams.stream[m_Streams.nstreams].blockalign = 0; + m_Streams.stream[m_Streams.nstreams].bitrate = 0; + m_Streams.stream[m_Streams.nstreams].bits_per_sample = 0; + + if(!strcmp(type, "AC3")) + { + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_AUDIO; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_AC3; + if (language == NULL) + { + m_Streams.stream[m_Streams.nstreams].language[0]= 0; + m_Streams.stream[m_Streams.nstreams].language[1]= 0; + m_Streams.stream[m_Streams.nstreams].language[2]= 0; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + } + else + { + m_Streams.stream[m_Streams.nstreams].language[0]= language[0]; + m_Streams.stream[m_Streams.nstreams].language[1]= language[1]; + m_Streams.stream[m_Streams.nstreams].language[2]= language[2]; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + } + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "EAC3")) + { + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_AUDIO; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_EAC3; + if (language == NULL) + { + m_Streams.stream[m_Streams.nstreams].language[0]= 0; + m_Streams.stream[m_Streams.nstreams].language[1]= 0; + m_Streams.stream[m_Streams.nstreams].language[2]= 0; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + } + else + { + m_Streams.stream[m_Streams.nstreams].language[0]= language[0]; + m_Streams.stream[m_Streams.nstreams].language[1]= language[1]; + m_Streams.stream[m_Streams.nstreams].language[2]= language[2]; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + } + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "MPEG2AUDIO")) + { + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_AUDIO; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_MP2; + if (language == NULL) + { + m_Streams.stream[m_Streams.nstreams].language[0]= 0; + m_Streams.stream[m_Streams.nstreams].language[1]= 0; + m_Streams.stream[m_Streams.nstreams].language[2]= 0; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + } + else + { + m_Streams.stream[m_Streams.nstreams].language[0]= language[0]; + m_Streams.stream[m_Streams.nstreams].language[1]= language[1]; + m_Streams.stream[m_Streams.nstreams].language[2]= language[2]; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + } + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "AAC")) + { + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_AUDIO; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_AAC; + if (language == NULL) + { + m_Streams.stream[m_Streams.nstreams].language[0]= 0; + m_Streams.stream[m_Streams.nstreams].language[1]= 0; + m_Streams.stream[m_Streams.nstreams].language[2]= 0; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + } + else + { + m_Streams.stream[m_Streams.nstreams].language[0]= language[0]; + m_Streams.stream[m_Streams.nstreams].language[1]= language[1]; + m_Streams.stream[m_Streams.nstreams].language[2]= language[2]; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + } + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "MPEG2VIDEO")) + { + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_VIDEO; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_MPEG2VIDEO; + m_Streams.stream[m_Streams.nstreams].width = htsmsg_get_u32_or_default(sub, "width" , 0); + m_Streams.stream[m_Streams.nstreams].height = htsmsg_get_u32_or_default(sub, "height" , 0); + m_Streams.stream[m_Streams.nstreams].language[0]= 0; + m_Streams.stream[m_Streams.nstreams].language[1]= 0; + m_Streams.stream[m_Streams.nstreams].language[2]= 0; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "H264")) + { + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_VIDEO; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_H264; + m_Streams.stream[m_Streams.nstreams].width = htsmsg_get_u32_or_default(sub, "width" , 0); + m_Streams.stream[m_Streams.nstreams].height = htsmsg_get_u32_or_default(sub, "height" , 0); + m_Streams.stream[m_Streams.nstreams].language[0]= 0; + m_Streams.stream[m_Streams.nstreams].language[1]= 0; + m_Streams.stream[m_Streams.nstreams].language[2]= 0; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "DVBSUB")) + { + uint32_t composition_id = 0, ancillary_id = 0; + htsmsg_get_u32(sub, "composition_id", &composition_id); + htsmsg_get_u32(sub, "ancillary_id" , &ancillary_id); + + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_SUBTITLE; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_DVB_SUBTITLE; + if (language == NULL) + { + m_Streams.stream[m_Streams.nstreams].language[0]= 0; + m_Streams.stream[m_Streams.nstreams].language[1]= 0; + m_Streams.stream[m_Streams.nstreams].language[2]= 0; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + } + else + { + m_Streams.stream[m_Streams.nstreams].language[0]= language[0]; + m_Streams.stream[m_Streams.nstreams].language[1]= language[1]; + m_Streams.stream[m_Streams.nstreams].language[2]= language[2]; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + } + m_Streams.stream[m_Streams.nstreams].identifier = (composition_id & 0xffff) | ((ancillary_id & 0xffff) << 16); + m_Streams.nstreams++; + } + else if(!strcmp(type, "TEXTSUB")) + { + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_SUBTITLE; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_TEXT; + if (language == NULL) + { + m_Streams.stream[m_Streams.nstreams].language[0]= 0; + m_Streams.stream[m_Streams.nstreams].language[1]= 0; + m_Streams.stream[m_Streams.nstreams].language[2]= 0; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + } + else + { + m_Streams.stream[m_Streams.nstreams].language[0]= language[0]; + m_Streams.stream[m_Streams.nstreams].language[1]= language[1]; + m_Streams.stream[m_Streams.nstreams].language[2]= language[2]; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + } + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "TELETEXT")) + { + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_SUBTITLE; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_DVB_TELETEXT; + m_Streams.stream[m_Streams.nstreams].language[0]= 0; + m_Streams.stream[m_Streams.nstreams].language[1]= 0; + m_Streams.stream[m_Streams.nstreams].language[2]= 0; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + + if (m_Streams.nstreams >= PVR_STREAM_MAX_STREAMS) + { + XBMC->Log(LOG_ERROR, "cHTSPDemux::SubscriptionStart - max amount of streams reached"); + break; + } + } + + if (cHTSPSession::ParseSourceInfo(m, m_SourceInfo)) + { + XBMC->Log(LOG_DEBUG, "cHTSPDemux::SubscriptionStart - subscription started on adapter %s, mux %s, network %s, provider %s, service %s", + m_SourceInfo.si_adapter.c_str(), m_SourceInfo.si_mux.c_str(), + m_SourceInfo.si_network.c_str(), m_SourceInfo.si_provider.c_str(), + m_SourceInfo.si_service.c_str()); + } + else + { + XBMC->Log(LOG_DEBUG, "cHTSPDemux::SubscriptionStart - subscription started on an unknown device"); + } +} + +void cHTSPDemux::SubscriptionStop (htsmsg_t *m) +{ + XBMC->Log(LOG_DEBUG, "cHTSPDemux::SubscriptionStop - subscription ended on adapter %s", m_SourceInfo.si_adapter.c_str()); + m_Streams.nstreams = 0; + + /* reset the signal status */ + m_Quality.fe_status = ""; + m_Quality.fe_ber = -2; + m_Quality.fe_signal = -2; + m_Quality.fe_snr = -2; + m_Quality.fe_unc = -2; + + /* reset the source info */ + m_SourceInfo.si_adapter = ""; + m_SourceInfo.si_mux = ""; + m_SourceInfo.si_network = ""; + m_SourceInfo.si_provider = ""; + m_SourceInfo.si_service = ""; +} + +void cHTSPDemux::SubscriptionStatus(htsmsg_t *m) +{ + const char* status; + status = htsmsg_get_str(m, "status"); + if(status == NULL) + m_Status = ""; + else + { + m_StatusCount++; + m_Status = status; + XBMC->Log(LOG_DEBUG, "cHTSPDemux::SubscriptionStatus - %s", status); + XBMC->QueueNotification(QUEUE_INFO, status); + } +} + +htsmsg_t* cHTSPDemux::ReadStream() +{ + htsmsg_t* msg; + + /* after anything has started reading, * + * we can guarantee a new stream */ + + while((msg = m_session.ReadMessage(1000))) + { + const char* method; + if((method = htsmsg_get_str(msg, "method")) == NULL) + return msg; + + if (strstr(method, "channelAdd")) + cHTSPSession::ParseChannelUpdate(msg, m_channels); + else if(strstr(method, "channelUpdate")) + cHTSPSession::ParseChannelUpdate(msg, m_channels); + else if(strstr(method, "channelDelete")) + cHTSPSession::ParseChannelRemove(msg, m_channels); + + uint32_t subs; + if(htsmsg_get_u32(msg, "subscriptionId", &subs) || subs != m_subs) + { + htsmsg_destroy(msg); + continue; + } + + return msg; + } + return NULL; +} diff --git a/xbmc/pvrclients/tvheadend/HTSPDemux.h b/xbmc/pvrclients/tvheadend/HTSPDemux.h new file mode 100644 index 0000000000..e394f5bd7e --- /dev/null +++ b/xbmc/pvrclients/tvheadend/HTSPDemux.h @@ -0,0 +1,62 @@ +#pragma once + +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "client.h" +#include "HTSPSession.h" + +class cHTSPDemux +{ +public: + cHTSPDemux(); + ~cHTSPDemux(); + + bool Open(const PVR_CHANNEL &channelinfo); + void Close(); + bool GetStreamProperties(PVR_STREAMPROPS* props); + void Abort(); + DemuxPacket* Read(); + bool SwitchChannel(const PVR_CHANNEL &channelinfo); + int CurrentChannel() { return m_channel; } + bool GetSignalStatus(PVR_SIGNALQUALITY &qualityinfo); + +protected: + void SubscriptionStart (htsmsg_t *m); + void SubscriptionStop (htsmsg_t *m); + void SubscriptionStatus(htsmsg_t *m); + + htsmsg_t* ReadStream(); + +private: + unsigned m_subs; + cHTSPSession m_session; + int m_channel; + int m_tag; + int m_StatusCount; + int m_SkipIFrame; + CStdString m_Status; + PVR_STREAMPROPS m_Streams; + SChannels m_channels; + SQueueStatus m_QueueStatus; + SQuality m_Quality; + SSourceInfo m_SourceInfo; +}; diff --git a/xbmc/pvrclients/tvheadend/HTSPSession.cpp b/xbmc/pvrclients/tvheadend/HTSPSession.cpp new file mode 100644 index 0000000000..59ef5dfc0d --- /dev/null +++ b/xbmc/pvrclients/tvheadend/HTSPSession.cpp @@ -0,0 +1,657 @@ +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "HTSPSession.h" +#include "client.h" +#ifdef _MSC_VER +#include +#define SHUT_RDWR SD_BOTH +#ifndef ETIMEDOUT +#define ETIMEDOUT WSAETIMEDOUT +#endif +#else +#include +#include +#endif + +extern "C" { +#include "libhts/net.h" +#include "libhts/htsmsg.h" +#include "libhts/htsmsg_binary.h" +#include "libhts/sha1.h" +} + +using namespace std; + +cHTSPSession::cHTSPSession() + : m_fd(INVALID_SOCKET) + , m_seq(0) + , m_challenge(NULL) + , m_challenge_len(0) + , m_protocol(0) + , m_queue_size(1000) +{ +} + +cHTSPSession::~cHTSPSession() +{ + Close(); +} + +void cHTSPSession::Abort() +{ + shutdown(m_fd, SHUT_RDWR); +} + +void cHTSPSession::Close() +{ + if(m_fd != INVALID_SOCKET) + { + closesocket(m_fd); + m_fd = INVALID_SOCKET; + } + + if(m_challenge) + { + free(m_challenge); + m_challenge = NULL; + m_challenge_len = 0; + } +} + +bool cHTSPSession::Connect(const std::string& hostname, int port) +{ + char errbuf[1024]; + int errlen = sizeof(errbuf); + htsmsg_t *m; + const char *method, *server, *version; + const void * chall = NULL; + size_t chall_len = 0; + int32_t proto = 0; + + if(port == 0) + port = 9982; + + XBMC->Log(LOG_DEBUG, "cHTSPSession::Connect - connecting to '%s', port '%d'\n", hostname.c_str(), port); + + m_fd = htsp_tcp_connect(hostname.c_str() + , port + , errbuf, errlen, 3000); + if(m_fd == INVALID_SOCKET) + { + XBMC->Log(LOG_ERROR, "cHTSPSession::Connect - failed to connect to server (%s)\n", errbuf); + return false; + } + + // send hello + m = htsmsg_create_map(); + htsmsg_add_str(m, "method", "hello"); + htsmsg_add_str(m, "clientname", "XBMC Media Center"); + htsmsg_add_u32(m, "htspversion", 1); + + // read welcome + if((m = ReadResult(m)) == NULL) + { + XBMC->Log(LOG_ERROR, "cHTSPSession::Connect - failed to read greeting from server"); + return false; + } + method = htsmsg_get_str(m, "method"); + htsmsg_get_s32(m, "htspversion", &proto); + server = htsmsg_get_str(m, "servername"); + version = htsmsg_get_str(m, "serverversion"); + htsmsg_get_bin(m, "challenge", &chall, &chall_len); + + XBMC->Log(LOG_DEBUG, "cHTSPSession::Connect - connected to server: [%s], version: [%s], proto: %d" + , server ? server : "", version ? version : "", proto); + + m_server = server; + m_version = version; + m_protocol = proto; + + if(chall && chall_len) + { + m_challenge = malloc(chall_len); + m_challenge_len = chall_len; + memcpy(m_challenge, chall, chall_len); + } + + htsmsg_destroy(m); + return true; +} + +bool cHTSPSession::Auth(const std::string& username, const std::string& password) +{ + htsmsg_t *m = htsmsg_create_map(); + htsmsg_add_str(m, "method" , "authenticate"); + htsmsg_add_str(m, "username", username.c_str()); + + if(password != "" && m_challenge) + { + struct HTSSHA1* shactx = (struct HTSSHA1*) malloc(hts_sha1_size); + uint8_t d[20]; + hts_sha1_init(shactx); + hts_sha1_update(shactx + , (const uint8_t *)password.c_str() + , password.length()); + hts_sha1_update(shactx + , (const uint8_t *)m_challenge + , m_challenge_len); + hts_sha1_final(shactx, d); + htsmsg_add_bin(m, "digest", d, 20); + free(shactx); + } + + return ReadSuccess(m, false, "get reply from authentication with server"); +} + +htsmsg_t* cHTSPSession::ReadMessage(int timeout) +{ + void* buf; + uint32_t l; + int x; + + if(m_queue.size()) + { + htsmsg_t* m = m_queue.front(); + m_queue.pop_front(); + return m; + } + + x = htsp_tcp_read_timeout(m_fd, &l, 4, timeout); + if(x == ETIMEDOUT) + return htsmsg_create_map(); + + if(x) + { + XBMC->Log(LOG_ERROR, "cHTSPSession::ReadMessage - Failed to read packet size (%d)\n", x); + return NULL; + } + + l = ntohl(l); + if(l == 0) + return htsmsg_create_map(); + + buf = malloc(l); + + x = htsp_tcp_read(m_fd, buf, l); + if(x) + { + XBMC->Log(LOG_ERROR, "cHTSPSession::ReadMessage - Failed to read packet (%d)\n", x); + free(buf); + return NULL; + } + + return htsmsg_binary_deserialize(buf, l, buf); /* consumes 'buf' */ +} + +bool cHTSPSession::SendMessage(htsmsg_t* m) +{ + void* buf; + size_t len; + + if(htsmsg_binary_serialize(m, &buf, &len, -1) < 0) + { + htsmsg_destroy(m); + return false; + } + htsmsg_destroy(m); + + if(send(m_fd, (char*)buf, len, 0) < 0) + { + free(buf); + return false; + } + free(buf); + return true; +} + +htsmsg_t* cHTSPSession::ReadResult(htsmsg_t* m, bool sequence) +{ + if(sequence) + htsmsg_add_u32(m, "seq", ++m_seq); + + if(!SendMessage(m)) + return NULL; + + std::deque queue; + m_queue.swap(queue); + + while((m = ReadMessage())) + { + uint32_t seq; + if(!sequence) + break; + if(!htsmsg_get_u32(m, "seq", &seq) && seq == m_seq) + break; + + queue.push_back(m); + if(queue.size() >= m_queue_size) + { + XBMC->Log(LOG_ERROR, "CDVDInputStreamHTSP::ReadResult - maximum queue size (%u) reached", m_queue_size); + m_queue.swap(queue); + return NULL; + } + } + + m_queue.swap(queue); + + const char* error; + if(m && (error = htsmsg_get_str(m, "error"))) + { + XBMC->Log(LOG_ERROR, "CDVDInputStreamHTSP::ReadResult - error (%s)", error); + htsmsg_destroy(m); + return NULL; + } + uint32_t noaccess; + if(m && !htsmsg_get_u32(m, "noaccess", &noaccess) && noaccess) + { + XBMC->Log(LOG_ERROR, "CDVDInputStreamHTSP::ReadResult - access denied (%d)", noaccess); + htsmsg_destroy(m); + return NULL; + } + + return m; +} + +bool cHTSPSession::ReadSuccess(htsmsg_t* m, bool sequence, std::string action) +{ + if((m = ReadResult(m, sequence)) == NULL) + { + XBMC->Log(LOG_DEBUG, "CDVDInputStreamHTSP::ReadSuccess - failed to %s", action.c_str()); + return false; + } + htsmsg_destroy(m); + return true; +} + +bool cHTSPSession::SendSubscribe(int subscription, int channel) +{ + htsmsg_t *m = htsmsg_create_map(); + htsmsg_add_str(m, "method" , "subscribe"); + htsmsg_add_s32(m, "channelId" , channel); + htsmsg_add_s32(m, "subscriptionId", subscription); + return ReadSuccess(m, true, "subscribe to channel"); +} + +bool cHTSPSession::SendUnsubscribe(int subscription) +{ + htsmsg_t *m = htsmsg_create_map(); + htsmsg_add_str(m, "method" , "unsubscribe"); + htsmsg_add_s32(m, "subscriptionId", subscription); + return ReadSuccess(m, true, "unsubscribe from channel"); +} + +bool cHTSPSession::SendEnableAsync() +{ + htsmsg_t *m = htsmsg_create_map(); + htsmsg_add_str(m, "method", "enableAsyncMetadata"); + return ReadSuccess(m, true, "enableAsyncMetadata failed"); +} + +bool cHTSPSession::GetEvent(SEvent& event, uint32_t id) +{ + if(id == 0) + { + event.Clear(); + return false; + } + + htsmsg_t *msg = htsmsg_create_map(); + htsmsg_add_str(msg, "method", "getEvent"); + htsmsg_add_u32(msg, "eventId", id); + if((msg = ReadResult(msg, true)) == NULL) + { + XBMC->Log(LOG_DEBUG, "cHTSPSession::GetEvent - failed to get event %d", id); + return false; + } + return ParseEvent(msg, id, event); +} + +bool cHTSPSession::ParseEvent(htsmsg_t* msg, uint32_t id, SEvent &event) +{ + uint32_t start, stop, next, chan_id, content; + const char *title, *desc; + if( htsmsg_get_u32(msg, "start", &start) + || htsmsg_get_u32(msg, "stop" , &stop) + || (title = htsmsg_get_str(msg, "title")) == NULL) + { + XBMC->Log(LOG_DEBUG, "cHTSPSession::ParseEvent - malformed event"); + htsmsg_print(msg); + htsmsg_destroy(msg); + return false; + } + event.Clear(); + event.id = id; + event.start = start; + event.stop = stop; + event.title = title; + + if((desc = htsmsg_get_str(msg, "description"))) + event.descs = desc; + if(htsmsg_get_u32(msg, "nextEventId", &next)) + event.next = 0; + else + event.next = next; + if(htsmsg_get_u32(msg, "channelId", &chan_id)) + event.chan_id = -1; + else + event.chan_id = chan_id; + if(htsmsg_get_u32(msg, "contentType", &content)) + event.content = -1; + else + event.content = content; + + XBMC->Log(LOG_DEBUG, "cHTSPSession::ParseEvent - id:%u, chan_id:%u, title:'%s', genre_type:%u, genre_sub_type:%u, desc:'%s', start:%u, stop:%u, next:%u" + , event.id + , event.chan_id + , event.title.c_str() + , event.content & 0x0F + , event.content & 0xF0 + , event.descs.c_str() + , event.start + , event.stop + , event.next); + + return true; +} + +void cHTSPSession::ParseChannelUpdate(htsmsg_t* msg, SChannels &channels) +{ + uint32_t id, event = 0, num = 0, caid = 0; + const char *name, *icon; + if(htsmsg_get_u32(msg, "channelId", &id)) + { + XBMC->Log(LOG_ERROR, "cHTSPSession::ParseChannelUpdate - malformed message received"); + htsmsg_print(msg); + return; + } + + SChannel &channel = channels[id]; + channel.id = id; + + if(htsmsg_get_u32(msg, "eventId", &event) == 0) + channel.event = event; + + if((name = htsmsg_get_str(msg, "channelName"))) + channel.name = name; + + if((icon = htsmsg_get_str(msg, "channelIcon"))) + channel.icon = icon; + + if(htsmsg_get_u32(msg, "channelCaid", &caid) == 0) + channel.caid = caid; + + if(htsmsg_get_u32(msg, "channelNumber", &num) == 0) + { + if(num == 0) + channel.num = id + 1000; + else + channel.num = num; + } + else + channel.num = id; // fallback older servers + + htsmsg_t *tags; + + if((tags = htsmsg_get_list(msg, "tags"))) + { + channel.tags.clear(); + + htsmsg_field_t *f; + HTSMSG_FOREACH(f, tags) + { + if(f->hmf_type != HMF_S64) + continue; + channel.tags.push_back((int)f->hmf_s64); + } + } + + htsmsg_t *services; + + if((services = htsmsg_get_list(msg, "services"))) + { + htsmsg_field_t *f; + HTSMSG_FOREACH(f, services) + { + if(f->hmf_type != HMF_MAP) + continue; + + htsmsg_t *service = &f->hmf_msg; + const char *service_type = htsmsg_get_str(service, "type"); + + if(service_type != NULL) + { + channel.radio = !strcmp(service_type, "Radio"); + } + } + } + + XBMC->Log(LOG_DEBUG, "cHTSPSession::ParseChannelUpdate - id:%u, name:'%s', icon:'%s', event:%u" + , id, name ? name : "(null)", icon ? icon : "(null)", event); +} + +void cHTSPSession::ParseChannelRemove(htsmsg_t* msg, SChannels &channels) +{ + uint32_t id; + if(htsmsg_get_u32(msg, "channelId", &id)) + { + XBMC->Log(LOG_ERROR, "CDVDInputStreamHTSP::ParseChannelRemove - malformed message received"); + htsmsg_print(msg); + return; + } + XBMC->Log(LOG_DEBUG, "cHTSPSession::ParseChannelRemove - id:%u", id); + + channels.erase(id); +} + +void cHTSPSession::ParseTagUpdate(htsmsg_t* msg, STags &tags) +{ + uint32_t id; + const char *name, *icon; + if(htsmsg_get_u32(msg, "tagId", &id)) + { + XBMC->Log(LOG_ERROR, "cHTSPSession::ParseTagUpdate - malformed message received"); + htsmsg_print(msg); + return; + } + STag &tag = tags[id]; + tag.id = id; + + if((icon = htsmsg_get_str(msg, "tagIcon"))) + tag.icon = icon; + + if((name = htsmsg_get_str(msg, "tagName"))) + tag.name = name; + + htsmsg_t *channels; + + if((channels = htsmsg_get_list(msg, "members"))) + { + tag.channels.clear(); + + htsmsg_field_t *f; + HTSMSG_FOREACH(f, channels) + { + if(f->hmf_type != HMF_S64) + continue; + tag.channels.push_back((int)f->hmf_s64); + } + } + + XBMC->Log(LOG_DEBUG, "cHTSPSession::ParseTagUpdate - id:%u, name:'%s', icon:'%s'" + , id, name ? name : "(null)", icon ? icon : "(null)"); + +} + +void cHTSPSession::ParseTagRemove(htsmsg_t* msg, STags &tags) +{ + uint32_t id; + if(htsmsg_get_u32(msg, "tagId", &id)) + { + XBMC->Log(LOG_ERROR, "cHTSPSession::ParseTagRemove - malformed message received"); + htsmsg_print(msg); + return; + } + XBMC->Log(LOG_DEBUG, "cHTSPSession::ParseTagRemove - id:%u", id); + + tags.erase(id); +} + +bool cHTSPSession::ParseSignalStatus (htsmsg_t* msg, SQuality &quality) +{ + if(htsmsg_get_u32(msg, "feSNR", &quality.fe_snr)) + quality.fe_snr = -2; + + if(htsmsg_get_u32(msg, "feSignal", &quality.fe_signal)) + quality.fe_signal = -2; + + if(htsmsg_get_u32(msg, "feBER", &quality.fe_ber)) + quality.fe_ber = -2; + + if(htsmsg_get_u32(msg, "feUNC", &quality.fe_unc)) + quality.fe_unc = -2; + + const char* status; + if((status = htsmsg_get_str(msg, "feStatus"))) + quality.fe_status = status; + else + quality.fe_status = "(unknown)"; + + XBMC->Log(LOG_DEBUG, "cHTSPSession::ParseSignalStatus - updated signal status: snr=%d, signal=%d, ber=%d, unc=%d, status=%s" + , quality.fe_snr, quality.fe_signal, quality.fe_ber + , quality.fe_unc, quality.fe_status.c_str()); + + return true; +} + +bool cHTSPSession::ParseSourceInfo (htsmsg_t* msg, SSourceInfo &si) +{ + htsmsg_t *sourceinfo; + if((sourceinfo = htsmsg_get_map(msg, "sourceinfo")) == NULL) + { + XBMC->Log(LOG_ERROR, "cHTSPSession::ParseSourceInfo - malformed message"); + return false; + } + + const char* str; + if((str = htsmsg_get_str(sourceinfo, "adapter")) == NULL) + si.si_adapter = ""; + else + si.si_adapter = str; + + if((str = htsmsg_get_str(sourceinfo, "mux")) == NULL) + si.si_mux = ""; + else + si.si_mux = str; + + if((str = htsmsg_get_str(sourceinfo, "network")) == NULL) + si.si_network = ""; + else + si.si_network = str; + + if((str = htsmsg_get_str(sourceinfo, "provider")) == NULL) + si.si_provider = ""; + else + si.si_provider = str; + + if((str = htsmsg_get_str(sourceinfo, "service")) == NULL) + si.si_service = ""; + else + si.si_service = str; + + return true; +} + +bool cHTSPSession::ParseQueueStatus (htsmsg_t* msg, SQueueStatus &queue) +{ + if(htsmsg_get_u32(msg, "packets", &queue.packets) + || htsmsg_get_u32(msg, "bytes", &queue.bytes) + || htsmsg_get_u32(msg, "Bdrops", &queue.bdrops) + || htsmsg_get_u32(msg, "Pdrops", &queue.pdrops) + || htsmsg_get_u32(msg, "Idrops", &queue.idrops)) + { + XBMC->Log(LOG_ERROR, "cHTSPSession::ParseQueueStatus - malformed message received"); + htsmsg_print(msg); + return false; + } + + /* delay isn't always transmitted */ + if(htsmsg_get_u32(msg, "delay", &queue.delay)) + queue.delay = 0; + + return true; +} + +void cHTSPSession::ParseDVREntryUpdate(htsmsg_t* msg, SRecordings &recordings) +{ + SRecording recording; + + if(htsmsg_get_u32(msg, "id", &recording.id) + || htsmsg_get_u32(msg, "channel", &recording.channel) + || htsmsg_get_u32(msg, "start", &recording.start) + || htsmsg_get_u32(msg, "stop", &recording.stop)) + { + XBMC->Log(LOG_ERROR, "cHTSPSession::ParseDVREntryUpdate - malformed message received"); + htsmsg_print(msg); + return; + } + + const char* str; + if((str = htsmsg_get_str(msg, "title")) == NULL) + recording.title = ""; + else + recording.title = str; + + if((str = htsmsg_get_str(msg, "description")) == NULL) + recording.description = ""; + else + recording.description = str; + + if((str = htsmsg_get_str(msg, "state")) == NULL) + recording.state = ""; + else + recording.state = str; + + if((str = htsmsg_get_str(msg, "error")) == NULL) + recording.error = ""; + else + recording.error = str; + + XBMC->Log(LOG_DEBUG, "cHTSPSession::ParseDVREntryUpdate - id:%u title '%s'\ndescription '%s'", recording.id, recording.title.c_str(), recording.description.c_str()); + + recordings[recording.id] = recording; +} + +void cHTSPSession::ParseDVREntryDelete(htsmsg_t* msg, SRecordings &recordings) +{ + uint32_t id; + + if(htsmsg_get_u32(msg, "id", &id)) + { + XBMC->Log(LOG_ERROR, "cHTSPSession::ParseDVREntryDelete - malformed message received"); + htsmsg_print(msg); + return; + } + + XBMC->Log(LOG_DEBUG, "cHTSPSession::ParseDVREntryDelete - Recording %i was deleted", id); + + recordings.erase(id); +} diff --git a/xbmc/pvrclients/tvheadend/HTSPSession.h b/xbmc/pvrclients/tvheadend/HTSPSession.h new file mode 100644 index 0000000000..1748936b8b --- /dev/null +++ b/xbmc/pvrclients/tvheadend/HTSPSession.h @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#pragma once +#include +#include "pvrclient-tvheadend_os.h" +#include "StdString.h" + +#include +#include +#include + +typedef struct htsmsg htsmsg_t; + +template +class const_circular_iter + : public std::iterator< typename std::iterator_traits::iterator_category + , typename std::iterator_traits::value_type + , typename std::iterator_traits::difference_type + , typename std::iterator_traits::pointer + , typename std::iterator_traits::reference> +{ + protected: + T begin; + T end; + T iter; + public: + typedef typename std::iterator_traits::value_type value_type; + typedef typename std::iterator_traits::difference_type difference_type; + typedef typename std::iterator_traits::pointer pointer; + typedef typename std::iterator_traits::reference reference; + + const_circular_iter(const const_circular_iter& src) : begin(src.begin), end(src.end), iter(src.iter) {}; + const_circular_iter(const T& b, const T& e) : begin(b), end(e), iter(b) {}; + const_circular_iter(const T& b, const T& e, const T& c) : begin(b), end(e), iter(c) {}; + const_circular_iter& operator++() + { + if(begin == end) + return(*this); + ++iter; + if (iter == end) + iter = begin; + return(*this); + } + + const_circular_iter& operator--() + { + if(begin == end) + return(*this); + if (iter == begin) + iter = end; + iter--; + return(*this); + } + + reference operator*() const { return (*iter); } + const pointer operator->() const { return &(*iter); } + bool operator==(const const_circular_iter& rhs) const { return (iter == rhs.iter); } + bool operator==(const T& rhs) const { return (iter == rhs); } + bool operator!=(const const_circular_iter& rhs) const { return ! operator==(rhs); } + bool operator!=(const T& rhs) const { return ! operator==(rhs); } +}; + +struct STag +{ + int id; + std::string name; + std::string icon; + std::vector channels; + + STag() { Clear(); } + void Clear() + { + id = 0; + name.clear(); + icon.clear(); + channels.clear(); + } + bool BelongsTo(int channel) const + { + return std::find(channels.begin(), channels.end(), channel) != channels.end(); + } + +}; + +struct SChannel +{ + int id; + std::string name; + std::string icon; + int event; + int num; + bool radio; + int caid; + std::vector tags; + + SChannel() { Clear(); } + void Clear() + { + id = 0; + event = 0; + num = 0; + radio = false; + caid = 0; + name.clear(); + icon.clear(); + tags.clear(); + } + bool MemberOf(int tag) const + { + return std::find(tags.begin(), tags.end(), tag) != tags.end(); + } + bool operator<(const SChannel &right) const + { + return num < right.num; + } +}; + +struct SEvent +{ + int id; + int next; + int chan_id; + + int content; + int start; + int stop; + std::string title; + std::string descs; + + SEvent() { Clear(); } + void Clear() + { + id = 0; + next = 0; + start = 0; + stop = 0; + title.clear(); + descs.clear(); + } +}; + +struct SRecording +{ + uint32_t id; + uint32_t channel; + uint32_t start; + uint32_t stop; + std::string title; + std::string description; + std::string state; + std::string error; + + SRecording() { Clear(); } + void Clear() + { + id = channel = start = stop = 0; + title.clear(); + description.clear(); + state.clear(); + error.clear(); + } +}; + +struct SQueueStatus +{ + uint32_t packets; // Number of data packets in queue. + uint32_t bytes; // Number of bytes in queue. + uint32_t delay; // Estimated delay of queue (in µs) + uint32_t bdrops; // Number of B-frames dropped + uint32_t pdrops; // Number of P-frames dropped + uint32_t idrops; // Number of I-frames dropped + + SQueueStatus() { Clear(); } + void Clear() + { + packets = 0; + bytes = 0; + delay = 0; + bdrops = 0; + pdrops = 0; + idrops = 0; + } +}; + +struct SQuality +{ +// CStdString fe_name; // get the adapter name from the mux instead + CStdString fe_status; + uint32_t fe_snr; + uint32_t fe_signal; + uint32_t fe_ber; + uint32_t fe_unc; +}; + +struct SSourceInfo +{ +// CStdString si_device; // currently not sent by tvheadend + CStdString si_adapter; + CStdString si_network; + CStdString si_mux; + CStdString si_provider; + CStdString si_service; +}; + +typedef std::map SChannels; +typedef std::map STags; +typedef std::map SEvents; +typedef std::map SRecordings; + + +class cHTSPSession +{ +public: + cHTSPSession(); + ~cHTSPSession(); + + bool Connect(const std::string& hostname, int port); + void Close(); + void Abort(); + bool Auth(const std::string& username, const std::string& password); + + htsmsg_t* ReadMessage(int timeout = 10000); + bool SendMessage(htsmsg_t* m); + + htsmsg_t* ReadResult (htsmsg_t* m, bool sequence = true); + bool ReadSuccess(htsmsg_t* m, bool sequence = true, std::string action = ""); + + bool SendSubscribe (int subscription, int channel); + bool SendUnsubscribe(int subscription); + bool SendEnableAsync(); + bool GetEvent(SEvent& event, uint32_t id); + + int GetProtocol() { return m_protocol; } + CStdString GetServerName() { return m_server; } + CStdString GetVersion() { return m_version; } + unsigned AddSequence() { return ++m_seq; } + + static bool ParseEvent (htsmsg_t* msg, uint32_t id, SEvent &event); + static void ParseChannelUpdate (htsmsg_t* msg, SChannels &channels); + static void ParseChannelRemove (htsmsg_t* msg, SChannels &channels); + static void ParseTagUpdate (htsmsg_t* msg, STags &tags); + static void ParseTagRemove (htsmsg_t* msg, STags &tags); + static bool ParseQueueStatus (htsmsg_t* msg, SQueueStatus &queue); + static bool ParseSignalStatus (htsmsg_t* msg, SQuality &quality); + static bool ParseSourceInfo (htsmsg_t* msg, SSourceInfo &si); + static void ParseDVREntryUpdate(htsmsg_t* msg, SRecordings &recordings); + static void ParseDVREntryDelete(htsmsg_t* msg, SRecordings &recordings); + +private: + SOCKET m_fd; + unsigned m_seq; + void* m_challenge; + int m_challenge_len; + int m_protocol; + CStdString m_server; + CStdString m_version; + + std::deque m_queue; + const unsigned int m_queue_size; + +}; diff --git a/xbmc/pvrclients/tvheadend/Makefile b/xbmc/pvrclients/tvheadend/Makefile new file mode 100644 index 0000000000..4dee7882a7 --- /dev/null +++ b/xbmc/pvrclients/tvheadend/Makefile @@ -0,0 +1,70 @@ +# +# Makefile for the XBMC Video Disk Recorder PVR AddOn +# +# See the README for copyright information and +# how to reach the author. +# + +.DELETE_ON_ERROR: + +DESTDIR ?= +PREFIX ?= /usr/local +ADDONDIR = $(PREFIX)/share/xbmc/addons +LIBS = ../../../xbmc/lib/libhts/libhts.a -ldl +INCLUDES = -I. -I../../../xbmc/cores/dvdplayer/DVDDemuxers -I../../../xbmc/addons/include -I../../../xbmc/cores/dvdplayer/Codecs/ffmpeg -I../../../xbmc/lib +ifeq ($(findstring Darwin,$(shell uname -a)), Darwin) +INCLUDES += -I/opt/local/include +endif +DEFINES += -D_LINUX -fPIC -DUSE_DEMUX +LIBDIR = ../../../addons/pvr.hts +LIB = ../../../addons/pvr.hts/XBMC_Tvheadend.pvr + +CC ?= gcc +CFLAGS ?= -g -O2 -Wall + +CXX ?= g++ +ifeq ($(findstring Darwin,$(shell uname -a)), Darwin) +CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses -dynamiclib -single_module -undefined dynamic_lookup +else +CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses +endif + +-include Make.config + +OBJS = client.o HTSPSession.o HTSPData.o HTSPDemux.o thread.o tools.o + +all: $(LIB) + +# Implicit rules: + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< + +# Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.cpp) > $@ + +-include $(DEPFILE) + +# The main library: + +$(LIB): $(OBJS) $(SILIB) + $(MAKE) -C ../../../xbmc/lib/libhts + $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -g $(OBJS) $(LIBS) $(LIBDIRS) $(SILIB) -o $(LIB) + +# Install the files: + +install: install-lib + +# PVR library: + +install-lib: $(LIB) + @mkdir -p $(DESTDIR)$(ADDONDIR) + @cp --remove-destination -r $(LIBDIR) $(DESTDIR)$(ADDONDIR) + +clean: + -rm -f $(OBJS) $(DEPFILE) $(LIB) *~ +CLEAN: clean diff --git a/xbmc/pvrclients/tvheadend/StdString.h b/xbmc/pvrclients/tvheadend/StdString.h new file mode 100644 index 0000000000..375741011a --- /dev/null +++ b/xbmc/pvrclients/tvheadend/StdString.h @@ -0,0 +1,4333 @@ +#pragma once +#include +#include +#if !defined(_LINUX) +#include +#include "pvrclient-tvheadend_os.h" +#endif + +// ============================================================================= +// FILE: StdString.h +// AUTHOR: Joe O'Leary (with outside help noted in comments) +// +// If you find any bugs in this code, please let me know: +// +// jmoleary@earthlink.net +// http://www.joeo.net/stdstring.htm (a bit outdated) +// +// The latest version of this code should always be available at the +// following link: +// +// http://www.joeo.net/code/StdString.zip (Dec 6, 2003) +// +// +// REMARKS: +// This header file declares the CStdStr template. This template derives +// the Standard C++ Library basic_string<> template and add to it the +// the following conveniences: +// - The full MFC CString set of functions (including implicit cast) +// - writing to/reading from COM IStream interfaces +// - Functional objects for use in STL algorithms +// +// From this template, we intstantiate two classes: CStdStringA and +// CStdStringW. The name "CStdString" is just a #define of one of these, +// based upone the UNICODE macro setting +// +// This header also declares our own version of the MFC/ATL UNICODE-MBCS +// conversion macros. Our version looks exactly like the Microsoft's to +// facilitate portability. +// +// NOTE: +// If you you use this in an MFC or ATL build, you should include either +// afx.h or atlbase.h first, as appropriate. +// +// PEOPLE WHO HAVE CONTRIBUTED TO THIS CLASS: +// +// Several people have helped me iron out problems and othewise improve +// this class. OK, this is a long list but in my own defense, this code +// has undergone two major rewrites. Many of the improvements became +// necessary after I rewrote the code as a template. Others helped me +// improve the CString facade. +// +// Anyway, these people are (in chronological order): +// +// - Pete the Plumber (???) +// - Julian Selman +// - Chris (of Melbsys) +// - Dave Plummer +// - John C Sipos +// - Chris Sells +// - Nigel Nunn +// - Fan Xia +// - Matthew Williams +// - Carl Engman +// - Mark Zeren +// - Craig Watson +// - Rich Zuris +// - Karim Ratib +// - Chris Conti +// - Baptiste Lepilleur +// - Greg Pickles +// - Jim Cline +// - Jeff Kohn +// - Todd Heckel +// - Ullrich Poll�hne +// - Joe Vitaterna +// - Joe Woodbury +// - Aaron (no last name) +// - Joldakowski (???) +// - Scott Hathaway +// - Eric Nitzche +// - Pablo Presedo +// - Farrokh Nejadlotfi +// - Jason Mills +// - Igor Kholodov +// - Mike Crusader +// - John James +// - Wang Haifeng +// - Tim Dowty +// - Arnt Witteveen +// - Glen Maynard +// - Paul DeMarco +// - Bagira (full name?) +// - Ronny Schulz +// - Jakko Van Hunen +// - Charles Godwin +// - Henk Demper +// - Greg Marr +// - Bill Carducci +// - Brian Groose +// - MKingman +// - Don Beusee +// +// REVISION HISTORY +// +// 2005-JAN-10 - Thanks to Don Beusee for pointing out the danger in mapping +// length-checked formatting functions to non-length-checked +// CRT equivalents. Also thanks to him for motivating me to +// optimize my implementation of Replace() +// +// 2004-APR-22 - A big, big thank you to "MKingman" (whoever you are) for +// finally spotting a silly little error in StdCodeCvt that +// has been causing me (and users of CStdString) problems for +// years in some relatively rare conversions. I had reversed +// two length arguments. +// +// 2003-NOV-24 - Thanks to a bunch of people for helping me clean up many +// compiler warnings (and yes, even a couple of actual compiler +// errors). These include Henk Demper for figuring out how +// to make the Intellisense work on with CStdString on VC6, +// something I was never able to do. Greg Marr pointed out +// a compiler warning about an unreferenced symbol and a +// problem with my version of Load in MFC builds. Bill +// Carducci took a lot of time with me to help me figure out +// why some implementations of the Standard C++ Library were +// returning error codes for apparently successful conversions +// between ASCII and UNICODE. Finally thanks to Brian Groose +// for helping me fix compiler signed unsigned warnings in +// several functions. +// +// 2003-JUL-10 - Thanks to Charles Godwin for making me realize my 'FmtArg' +// fixes had inadvertently broken the DLL-export code (which is +// normally commented out. I had to move it up higher. Also +// this helped me catch a bug in ssicoll that would prevent +// compilation, otherwise. +// +// 2003-MAR-14 - Thanks to Jakko Van Hunen for pointing out a copy-and-paste +// bug in one of the overloads of FmtArg. +// +// 2003-MAR-10 - Thanks to Ronny Schulz for (twice!) sending me some changes +// to help CStdString build on SGI and for pointing out an +// error in placement of my preprocessor macros for ssfmtmsg. +// +// 2002-NOV-26 - Thanks to Bagira for pointing out that my implementation of +// SpanExcluding was not properly handling the case in which +// the string did NOT contain any of the given characters +// +// 2002-OCT-21 - Many thanks to Paul DeMarco who was invaluable in helping me +// get this code working with Borland's free compiler as well +// as the Dev-C++ compiler (available free at SourceForge). +// +// 2002-SEP-13 - Thanks to Glen Maynard who helped me get rid of some loud +// but harmless warnings that were showing up on g++. Glen +// also pointed out that some pre-declarations of FmtArg<> +// specializations were unnecessary (and no good on G++) +// +// 2002-JUN-26 - Thanks to Arnt Witteveen for pointing out that I was using +// static_cast<> in a place in which I should have been using +// reinterpret_cast<> (the ctor for unsigned char strings). +// That's what happens when I don't unit-test properly! +// Arnt also noticed that CString was silently correcting the +// 'nCount' argument to Left() and Right() where CStdString was +// not (and crashing if it was bad). That is also now fixed! +// +// 2002-FEB-25 - Thanks to Tim Dowty for pointing out (and giving me the fix +// for) a conversion problem with non-ASCII MBCS characters. +// CStdString is now used in my favorite commercial MP3 player! +// +// 2001-DEC-06 - Thanks to Wang Haifeng for spotting a problem in one of the +// assignment operators (for _bstr_t) that would cause compiler +// errors when refcounting protection was turned off. +// +// 2001-NOV-27 - Remove calls to operator!= which involve reverse_iterators +// due to a conflict with the rel_ops operator!=. Thanks to +// John James for pointing this out. +// +// 2001-OCT-29 - Added a minor range checking fix for the Mid function to +// make it as forgiving as CString's version is. Thanks to +// Igor Kholodov for noticing this. +// - Added a specialization of std::swap for CStdString. Thanks +// to Mike Crusader for suggesting this! It's commented out +// because you're not supposed to inject your own code into the +// 'std' namespace. But if you don't care about that, it's +// there if you want it +// - Thanks to Jason Mills for catching a case where CString was +// more forgiving in the Delete() function than I was. +// +// 2001-JUN-06 - I was violating the Standard name lookup rules stated +// in [14.6.2(3)]. None of the compilers I've tried so +// far apparently caught this but HP-UX aCC 3.30 did. The +// fix was to add 'this->' prefixes in many places. +// Thanks to Farrokh Nejadlotfi for this! +// +// 2001-APR-27 - StreamLoad was calculating the number of BYTES in one +// case, not characters. Thanks to Pablo Presedo for this. +// +// 2001-FEB-23 - Replace() had a bug which caused infinite loops if the +// source string was empty. Fixed thanks to Eric Nitzsche. +// +// 2001-FEB-23 - Scott Hathaway was a huge help in providing me with the +// ability to build CStdString on Sun Unix systems. He +// sent me detailed build reports about what works and what +// does not. If CStdString compiles on your Unix box, you +// can thank Scott for it. +// +// 2000-DEC-29 - Joldakowski noticed one overload of Insert failed to do a +// range check as CString's does. Now fixed -- thanks! +// +// 2000-NOV-07 - Aaron pointed out that I was calling static member +// functions of char_traits via a temporary. This was not +// technically wrong, but it was unnecessary and caused +// problems for poor old buggy VC5. Thanks Aaron! +// +// 2000-JUL-11 - Joe Woodbury noted that the CString::Find docs don't match +// what the CString::Find code really ends up doing. I was +// trying to match the docs. Now I match the CString code +// - Joe also caught me truncating strings for GetBuffer() calls +// when the supplied length was less than the current length. +// +// 2000-MAY-25 - Better support for STLPORT's Standard library distribution +// - Got rid of the NSP macro - it interfered with Koenig lookup +// - Thanks to Joe Woodbury for catching a TrimLeft() bug that +// I introduced in January. Empty strings were not getting +// trimmed +// +// 2000-APR-17 - Thanks to Joe Vitaterna for pointing out that ReverseFind +// is supposed to be a const function. +// +// 2000-MAR-07 - Thanks to Ullrich Poll�hne for catching a range bug in one +// of the overloads of assign. +// +// 2000-FEB-01 - You can now use CStdString on the Mac with CodeWarrior! +// Thanks to Todd Heckel for helping out with this. +// +// 2000-JAN-23 - Thanks to Jim Cline for pointing out how I could make the +// Trim() function more efficient. +// - Thanks to Jeff Kohn for prompting me to find and fix a typo +// in one of the addition operators that takes _bstr_t. +// - Got rid of the .CPP file - you only need StdString.h now! +// +// 1999-DEC-22 - Thanks to Greg Pickles for helping me identify a problem +// with my implementation of CStdString::FormatV in which +// resulting string might not be properly NULL terminated. +// +// 1999-DEC-06 - Chris Conti pointed yet another basic_string<> assignment +// bug that MS has not fixed. CStdString did nothing to fix +// it either but it does now! The bug was: create a string +// longer than 31 characters, get a pointer to it (via c_str()) +// and then assign that pointer to the original string object. +// The resulting string would be empty. Not with CStdString! +// +// 1999-OCT-06 - BufferSet was erasing the string even when it was merely +// supposed to shrink it. Fixed. Thanks to Chris Conti. +// - Some of the Q172398 fixes were not checking for assignment- +// to-self. Fixed. Thanks to Baptiste Lepilleur. +// +// 1999-AUG-20 - Improved Load() function to be more efficient by using +// SizeOfResource(). Thanks to Rich Zuris for this. +// - Corrected resource ID constructor, again thanks to Rich. +// - Fixed a bug that occurred with UNICODE characters above +// the first 255 ANSI ones. Thanks to Craig Watson. +// - Added missing overloads of TrimLeft() and TrimRight(). +// Thanks to Karim Ratib for pointing them out +// +// 1999-JUL-21 - Made all calls to GetBuf() with no args check length first. +// +// 1999-JUL-10 - Improved MFC/ATL independence of conversion macros +// - Added SS_NO_REFCOUNT macro to allow you to disable any +// reference-counting your basic_string<> impl. may do. +// - Improved ReleaseBuffer() to be as forgiving as CString. +// Thanks for Fan Xia for helping me find this and to +// Matthew Williams for pointing it out directly. +// +// 1999-JUL-06 - Thanks to Nigel Nunn for catching a very sneaky bug in +// ToLower/ToUpper. They should call GetBuf() instead of +// data() in order to ensure the changed string buffer is not +// reference-counted (in those implementations that refcount). +// +// 1999-JUL-01 - Added a true CString facade. Now you can use CStdString as +// a drop-in replacement for CString. If you find this useful, +// you can thank Chris Sells for finally convincing me to give +// in and implement it. +// - Changed operators << and >> (for MFC CArchive) to serialize +// EXACTLY as CString's do. So now you can send a CString out +// to a CArchive and later read it in as a CStdString. I have +// no idea why you would want to do this but you can. +// +// 1999-JUN-21 - Changed the CStdString class into the CStdStr template. +// - Fixed FormatV() to correctly decrement the loop counter. +// This was harmless bug but a bug nevertheless. Thanks to +// Chris (of Melbsys) for pointing it out +// - Changed Format() to try a normal stack-based array before +// using to _alloca(). +// - Updated the text conversion macros to properly use code +// pages and to fit in better in MFC/ATL builds. In other +// words, I copied Microsoft's conversion stuff again. +// - Added equivalents of CString::GetBuffer, GetBufferSetLength +// - new sscpy() replacement of CStdString::CopyString() +// - a Trim() function that combines TrimRight() and TrimLeft(). +// +// 1999-MAR-13 - Corrected the "NotSpace" functional object to use _istpace() +// instead of _isspace() Thanks to Dave Plummer for this. +// +// 1999-FEB-26 - Removed errant line (left over from testing) that #defined +// _MFC_VER. Thanks to John C Sipos for noticing this. +// +// 1999-FEB-03 - Fixed a bug in a rarely-used overload of operator+() that +// caused infinite recursion and stack overflow +// - Added member functions to simplify the process of +// persisting CStdStrings to/from DCOM IStream interfaces +// - Added functional objects (e.g. StdStringLessNoCase) that +// allow CStdStrings to be used as keys STL map objects with +// case-insensitive comparison +// - Added array indexing operators (i.e. operator[]). I +// originally assumed that these were unnecessary and would be +// inherited from basic_string. However, without them, Visual +// C++ complains about ambiguous overloads when you try to use +// them. Thanks to Julian Selman to pointing this out. +// +// 1998-FEB-?? - Added overloads of assign() function to completely account +// for Q172398 bug. Thanks to "Pete the Plumber" for this +// +// 1998-FEB-?? - Initial submission +// +// COPYRIGHT: +// 2002 Joseph M. O'Leary. This code is 100% free. Use it anywhere you +// want. Rewrite it, restructure it, whatever. If you can write software +// that makes money off of it, good for you. I kinda like capitalism. +// Please don't blame me if it causes your $30 billion dollar satellite +// explode in orbit. If you redistribute it in any form, I'd appreciate it +// if you would leave this notice here. +// ============================================================================= + +// Avoid multiple inclusion + +#ifndef STDSTRING_H +#define STDSTRING_H + +// When using VC, turn off browser references +// Turn off unavoidable compiler warnings + +#if defined(_MSC_VER) && (_MSC_VER > 1100) + #pragma component(browser, off, references, "CStdString") + #pragma warning (disable : 4290) // C++ Exception Specification ignored + #pragma warning (disable : 4127) // Conditional expression is constant + #pragma warning (disable : 4097) // typedef name used as synonym for class name +#endif + +// Borland warnings to turn off + +#ifdef __BORLANDC__ + #pragma option push -w-inl +// #pragma warn -inl // Turn off inline function warnings +#endif + +// SS_IS_INTRESOURCE +// ----------------- +// A copy of IS_INTRESOURCE from VC7. Because old VC6 version of winuser.h +// doesn't have this. + +#define SS_IS_INTRESOURCE(_r) (false) + +#if !defined (SS_ANSI) && defined(_MSC_VER) + #undef SS_IS_INTRESOURCE + #if defined(_WIN64) + #define SS_IS_INTRESOURCE(_r) (((unsigned __int64)(_r) >> 16) == 0) + #else + #define SS_IS_INTRESOURCE(_r) (((unsigned long)(_r) >> 16) == 0) + #endif +#endif + + +// MACRO: SS_UNSIGNED +// ------------------ +// This macro causes the addition of a constructor and assignment operator +// which take unsigned characters. CString has such functions and in order +// to provide maximum CString-compatability, this code needs them as well. +// In practice you will likely never need these functions... + +//#define SS_UNSIGNED + +#ifdef SS_ALLOW_UNSIGNED_CHARS + #define SS_UNSIGNED +#endif + +// MACRO: SS_SAFE_FORMAT +// --------------------- +// This macro provides limited compatability with a questionable CString +// "feature". You can define it in order to avoid a common problem that +// people encounter when switching from CString to CStdString. +// +// To illustrate the problem -- With CString, you can do this: +// +// CString sName("Joe"); +// CString sTmp; +// sTmp.Format("My name is %s", sName); // WORKS! +// +// However if you were to try this with CStdString, your program would +// crash. +// +// CStdString sName("Joe"); +// CStdString sTmp; +// sTmp.Format("My name is %s", sName); // CRASHES! +// +// You must explicitly call c_str() or cast the object to the proper type +// +// sTmp.Format("My name is %s", sName.c_str()); // WORKS! +// sTmp.Format("My name is %s", static_cast(sName));// WORKS! +// sTmp.Format("My name is %s", (PCSTR)sName); // WORKS! +// +// This is because it is illegal to pass anything but a POD type as a +// variadic argument to a variadic function (i.e. as one of the "..." +// arguments). The type const char* is a POD type. The type CStdString +// is not. Of course, neither is the type CString, but CString lets you do +// it anyway due to the way they laid out the class in binary. I have no +// control over this in CStdString since I derive from whatever +// implementation of basic_string is available. +// +// However if you have legacy code (which does this) that you want to take +// out of the MFC world and you don't want to rewrite all your calls to +// Format(), then you can define this flag and it will no longer crash. +// +// Note however that this ONLY works for Format(), not sprintf, fprintf, +// etc. If you pass a CStdString object to one of those functions, your +// program will crash. Not much I can do to get around this, short of +// writing substitutes for those functions as well. + +#define SS_SAFE_FORMAT // use new template style Format() function + + +// MACRO: SS_NO_IMPLICIT_CAST +// -------------------------- +// Some people don't like the implicit cast to const char* (or rather to +// const CT*) that CStdString (and MFC's CString) provide. That was the +// whole reason I created this class in the first place, but hey, whatever +// bakes your cake. Just #define this macro to get rid of the the implicit +// cast. + +//#define SS_NO_IMPLICIT_CAST // gets rid of operator const CT*() + + +// MACRO: SS_NO_REFCOUNT +// --------------------- +// turns off reference counting at the assignment level. Only needed +// for the version of basic_string<> that comes with Visual C++ versions +// 6.0 or earlier, and only then in some heavily multithreaded scenarios. +// Uncomment it if you feel you need it. + +//#define SS_NO_REFCOUNT + +// MACRO: SS_WIN32 +// --------------- +// When this flag is set, we are building code for the Win32 platform and +// may use Win32 specific functions (such as LoadString). This gives us +// a couple of nice extras for the code. +// +// Obviously, Microsoft's is not the only compiler available for Win32 out +// there. So I can't just check to see if _MSC_VER is defined to detect +// if I'm building on Win32. So for now, if you use MS Visual C++ or +// Borland's compiler, I turn this on. Otherwise you may turn it on +// yourself, if you prefer + +#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(_WIN32) + #define SS_WIN32 +#endif + +// MACRO: SS_ANSI +// -------------- +// When this macro is defined, the code attempts only to use ANSI/ISO +// standard library functions to do it's work. It will NOT attempt to use +// any Win32 of Visual C++ specific functions -- even if they are +// available. You may define this flag yourself to prevent any Win32 +// of VC++ specific functions from being called. + +// If we're not on Win32, we MUST use an ANSI build + +#ifndef SS_WIN32 + #if !defined(SS_NO_ANSI) + #define SS_ANSI + #endif +#endif + +// MACRO: SS_ALLOCA +// ---------------- +// Some implementations of the Standard C Library have a non-standard +// function known as alloca(). This functions allows one to allocate a +// variable amount of memory on the stack. It is needed to implement +// the ASCII/MBCS conversion macros. +// +// I wanted to find some way to determine automatically if alloca() is +// available on this platform via compiler flags but that is asking for +// trouble. The crude test presented here will likely need fixing on +// other platforms. Therefore I'll leave it up to you to fiddle with +// this test to determine if it exists. Just make sure SS_ALLOCA is or +// is not defined as appropriate and you control this feature. + +#if defined(_MSC_VER) && !defined(SS_ANSI) + #define SS_ALLOCA +#endif + + +// MACRO: SS_MBCS +// -------------- +// Setting this macro means you are using MBCS characters. In MSVC builds, +// this macro gets set automatically by detection of the preprocessor flag +// _MBCS. For other platforms you may set it manually if you wish. The +// only effect it currently has is to cause the allocation of more space +// for wchar_t --> char conversions. +// Note that MBCS does not mean UNICODE. +// +// #define SS_MBCS +// + +#ifdef _MBCS + #define SS_MBCS +#endif + + +// MACRO SS_NO_LOCALE +// ------------------ +// If your implementation of the Standard C++ Library lacks the header, +// you can #define this macro to make your code build properly. Note that this +// is some of my newest code and frankly I'm not very sure of it, though it does +// pass my unit tests. + +// #define SS_NO_LOCALE + + +// Compiler Error regarding _UNICODE and UNICODE +// ----------------------------------------------- +// Microsoft header files are screwy. Sometimes they depend on a preprocessor +// flag named "_UNICODE". Other times they check "UNICODE" (note the lack of +// leading underscore in the second version". In several places, they silently +// "synchronize" these two flags this by defining one of the other was defined. +// In older version of this header, I used to try to do the same thing. +// +// However experience has taught me that this is a bad idea. You get weird +// compiler errors that seem to indicate things like LPWSTR and LPTSTR not being +// equivalent in UNICODE builds, stuff like that (when they MUST be in a proper +// UNICODE build). You end up scratching your head and saying, "But that HAS +// to compile!". +// +// So what should you do if you get this error? +// +// Make sure that both macros (_UNICODE and UNICODE) are defined before this +// file is included. You can do that by either +// +// a) defining both yourself before any files get included +// b) including the proper MS headers in the proper order +// c) including this file before any other file, uncommenting +// the #defines below, and commenting out the #errors +// +// Personally I recommend solution a) but it's your call. + +#ifdef _MSC_VER + #if defined (_UNICODE) && !defined (UNICODE) + #error UNICODE defined but not UNICODE + // #define UNICODE // no longer silently fix this + #endif + #if defined (UNICODE) && !defined (_UNICODE) + #error Warning, UNICODE defined but not _UNICODE + // #define _UNICODE // no longer silently fix this + #endif +#endif + + +// ----------------------------------------------------------------------------- +// MIN and MAX. The Standard C++ template versions go by so many names (at +// at least in the MS implementation) that you never know what's available +// ----------------------------------------------------------------------------- +template +inline const Type& SSMIN(const Type& arg1, const Type& arg2) +{ + return arg2 < arg1 ? arg2 : arg1; +} +template +inline const Type& SSMAX(const Type& arg1, const Type& arg2) +{ + return arg2 > arg1 ? arg2 : arg1; +} + +// If they have not #included W32Base.h (part of my W32 utility library) then +// we need to define some stuff. Otherwise, this is all defined there. + +#if !defined(W32BASE_H) + + // If they want us to use only standard C++ stuff (no Win32 stuff) + + #ifdef SS_ANSI + + // On Win32 we have TCHAR.H so just include it. This is NOT violating + // the spirit of SS_ANSI as we are not calling any Win32 functions here. + + #ifdef SS_WIN32 + + #include + #include + #ifndef STRICT + #define STRICT + #endif + + // ... but on non-Win32 platforms, we must #define the types we need. + + #else + + typedef const char* PCSTR; + typedef char* PSTR; + typedef const wchar_t* PCWSTR; + typedef wchar_t* PWSTR; + #ifdef UNICODE + typedef wchar_t TCHAR; + #else + typedef char TCHAR; + #endif + typedef wchar_t OLECHAR; + + #endif // #ifndef _WIN32 + + + // Make sure ASSERT and verify are defined using only ANSI stuff + + #ifndef ASSERT + #include + #define ASSERT(f) assert((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #else // ...else SS_ANSI is NOT defined + + #include + #include + #ifndef STRICT + #define STRICT + #endif + + // Make sure ASSERT and verify are defined + + #ifndef ASSERT + #include + #define ASSERT(f) _ASSERTE((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #endif // #ifdef SS_ANSI + + #ifndef UNUSED + #define UNUSED(x) x + #endif + +#endif // #ifndef W32BASE_H + +// Standard headers needed + +#include // basic_string +#include // for_each, etc. +#include // for StdStringLessNoCase, et al +#ifndef SS_NO_LOCALE + #include // for various facets +#endif + +// If this is a recent enough version of VC include comdef.h, so we can write +// member functions to deal with COM types & compiler support classes e.g. +// _bstr_t + +#if defined (_MSC_VER) && (_MSC_VER >= 1100) + #include + #define SS_INC_COMDEF // signal that we #included MS comdef.h file + #define STDSTRING_INC_COMDEF + #define SS_NOTHROW __declspec(nothrow) +#else + #define SS_NOTHROW +#endif + +#ifndef TRACE + #define TRACE_DEFINED_HERE + #define TRACE +#endif + +// Microsoft defines PCSTR, PCWSTR, etc, but no PCTSTR. I hate to use the +// versions with the "L" in front of them because that's a leftover from Win 16 +// days, even though it evaluates to the same thing. Therefore, Define a PCSTR +// as an LPCTSTR. + +#if !defined(PCTSTR) && !defined(PCTSTR_DEFINED) + typedef const TCHAR* PCTSTR; + #define PCTSTR_DEFINED +#endif + +#if !defined(PCOLESTR) && !defined(PCOLESTR_DEFINED) + typedef const OLECHAR* PCOLESTR; + #define PCOLESTR_DEFINED +#endif + +#if !defined(POLESTR) && !defined(POLESTR_DEFINED) + typedef OLECHAR* POLESTR; + #define POLESTR_DEFINED +#endif + +#if !defined(PCUSTR) && !defined(PCUSTR_DEFINED) + typedef const unsigned char* PCUSTR; + typedef unsigned char* PUSTR; + #define PCUSTR_DEFINED +#endif + + +// SGI compiler 7.3 doesnt know these types - oh and btw, remember to use +// -LANG:std in the CXX Flags +#if defined(__sgi) + typedef unsigned long DWORD; + typedef void * LPCVOID; +#endif + + +// SS_USE_FACET macro and why we need it: +// +// Since I'm a good little Standard C++ programmer, I use locales. Thus, I +// need to make use of the use_facet<> template function here. Unfortunately, +// this need is complicated by the fact the MS' implementation of the Standard +// C++ Library has a non-standard version of use_facet that takes more +// arguments than the standard dictates. Since I'm trying to write CStdString +// to work with any version of the Standard library, this presents a problem. +// +// The upshot of this is that I can't do 'use_facet' directly. The MS' docs +// tell me that I have to use a macro, _USE() instead. Since _USE obviously +// won't be available in other implementations, this means that I have to write +// my OWN macro -- SS_USE_FACET -- that evaluates either to _USE or to the +// standard, use_facet. +// +// If you are having trouble with the SS_USE_FACET macro, in your implementation +// of the Standard C++ Library, you can define your own version of SS_USE_FACET. + +#ifndef schMSG + #define schSTR(x) #x + #define schSTR2(x) schSTR(x) + #define schMSG(desc) message(__FILE__ "(" schSTR2(__LINE__) "):" #desc) +#endif + +#ifndef SS_USE_FACET + + // STLPort #defines a macro (__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) for + // all MSVC builds, erroneously in my opinion. It causes problems for + // my SS_ANSI builds. In my code, I always comment out that line. You'll + // find it in \stlport\config\stl_msvc.h + + #if defined(__SGI_STL_PORT) && (__SGI_STL_PORT >= 0x400 ) + + #if defined(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) && defined(_MSC_VER) + #ifdef SS_ANSI + #pragma schMSG(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS defined!!) + #endif + #endif + #define SS_USE_FACET(loc, fac) std::use_facet(loc) + + #elif defined(_MSC_VER ) + + #define SS_USE_FACET(loc, fac) std::_USE(loc, fac) + + // ...and + #elif defined(_RWSTD_NO_TEMPLATE_ON_RETURN_TYPE) + + #define SS_USE_FACET(loc, fac) std::use_facet(loc, (fac*)0) + + #else + + #define SS_USE_FACET(loc, fac) std::use_facet(loc) + + #endif + +#endif + +// ============================================================================= +// UNICODE/MBCS conversion macros. Made to work just like the MFC/ATL ones. +// ============================================================================= + +#include // Added to Std Library with Amendment #1. + +// First define the conversion helper functions. We define these regardless of +// any preprocessor macro settings since their names won't collide. + +// Not sure if we need all these headers. I believe ANSI says we do. + +#include +#include +#include +#include +#include +#ifndef va_start + #include +#endif + + +#ifdef SS_NO_LOCALE + + #if defined(_WIN32) || defined (_WIN32_WCE) + + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCSTR pSrcA, int nSrc, + UINT acp=CP_ACP) + { + ASSERT(0 != pSrcA); + ASSERT(0 != pDstW); + pDstW[0] = '\0'; + MultiByteToWideChar(acp, 0, pSrcA, nSrc, pDstW, nDst); + return pDstW; + } + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCUSTR pSrcA, int nSrc, + UINT acp=CP_ACP) + { + return StdCodeCvt(pDstW, nDst, (PCSTR)pSrcA, nSrc, acp); + } + + inline PSTR StdCodeCvt(PSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + UINT acp=CP_ACP) + { + ASSERT(0 != pDstA); + ASSERT(0 != pSrcW); + pDstA[0] = '\0'; + WideCharToMultiByte(acp, 0, pSrcW, nSrc, pDstA, nDst, 0, 0); + return pDstA; + } + inline PUSTR StdCodeCvt(PUSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + UINT acp=CP_ACP) + { + return (PUSTR)StdCodeCvt((PSTR)pDstA, nDst, pSrcW, nSrc, acp); + } + #else + #endif + +#else + + // StdCodeCvt - made to look like Win32 functions WideCharToMultiByte + // and MultiByteToWideChar but uses locales in SS_ANSI + // builds. There are a number of overloads. + // First argument is the destination buffer. + // Second argument is the source buffer + //#if defined (SS_ANSI) || !defined (SS_WIN32) + + // 'SSCodeCvt' - shorthand name for the codecvt facet we use + + typedef std::codecvt SSCodeCvt; + + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCSTR pSrcA, int nSrc, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pSrcA); + ASSERT(0 != pDstW); + + pDstW[0] = '\0'; + + if ( nSrc > 0 ) + { + PCSTR pNextSrcA = pSrcA; + PWSTR pNextDstW = pDstW; + SSCodeCvt::result res = SSCodeCvt::ok; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= { 0 }; + res = conv.in(st, + pSrcA, pSrcA + nSrc, pNextSrcA, + pDstW, pDstW + nDst, pNextDstW); +#ifdef _LINUX +#define ASSERT2(a) if (!(a)) {fprintf(stderr, "StdString: Assertion Failed on line %d\n", __LINE__);} +#else +#define ASSERT2 ASSERT +#endif + ASSERT2(SSCodeCvt::ok == res); + ASSERT2(SSCodeCvt::error != res); + ASSERT2(pNextDstW >= pDstW); + ASSERT2(pNextSrcA >= pSrcA); +#undef ASSERT2 + // Null terminate the converted string + + if ( pNextDstW - pDstW > nDst ) + *(pDstW + nDst) = '\0'; + else + *pNextDstW = '\0'; + } + return pDstW; + } + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCUSTR pSrcA, int nSrc, + const std::locale& loc=std::locale()) + { + return StdCodeCvt(pDstW, nDst, (PCSTR)pSrcA, nSrc, loc); + } + + inline PSTR StdCodeCvt(PSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pDstA); + ASSERT(0 != pSrcW); + + pDstA[0] = '\0'; + + if ( nSrc > 0 ) + { + PSTR pNextDstA = pDstA; + PCWSTR pNextSrcW = pSrcW; + SSCodeCvt::result res = SSCodeCvt::ok; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= { 0 }; + res = conv.out(st, + pSrcW, pSrcW + nSrc, pNextSrcW, + pDstA, pDstA + nDst, pNextDstA); +#ifdef _LINUX +#define ASSERT2(a) if (!(a)) {fprintf(stderr, "StdString: Assertion Failed on line %d\n", __LINE__);} +#else +#define ASSERT2 ASSERT +#endif + ASSERT2(SSCodeCvt::error != res); + ASSERT2(SSCodeCvt::ok == res); // strict, comment out for sanity + ASSERT2(pNextDstA >= pDstA); + ASSERT2(pNextSrcW >= pSrcW); +#undef ASSERT2 + + // Null terminate the converted string + + if ( pNextDstA - pDstA > nDst ) + *(pDstA + nDst) = '\0'; + else + *pNextDstA = '\0'; + } + return pDstA; + } + + inline PUSTR StdCodeCvt(PUSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + const std::locale& loc=std::locale()) + { + return (PUSTR)StdCodeCvt((PSTR)pDstA, nDst, pSrcW, nSrc, loc); + } + +#endif + + + +// Unicode/MBCS conversion macros are only available on implementations of +// the "C" library that have the non-standard _alloca function. As far as I +// know that's only Microsoft's though I've heard that the function exists +// elsewhere. + +#if defined(SS_ALLOCA) && !defined SS_NO_CONVERSION + + #include // needed for _alloca + + // Define our conversion macros to look exactly like Microsoft's to + // facilitate using this stuff both with and without MFC/ATL + + #ifdef _CONVERSION_USES_THREAD_LOCALE + + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=GetACP(); \ + _acp; PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=GetACP();\ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)),\ + StdCodeCvt((PWSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pa, _cvt, _acp))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = sslen(_pw), \ + StdCodeCvt((LPSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pw, _cvt, _acp))) + #else + + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=CP_ACP; _acp;\ + PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=CP_ACP; \ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)),\ + StdCodeCvt((PWSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pa, _cvt))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = (sslen(_pw)),\ + StdCodeCvt((LPSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pw, _cvt))) + #endif + + #define SSA2CW(pa) ((PCWSTR)SSA2W((pa))) + #define SSW2CA(pw) ((PCSTR)SSW2A((pw))) + + #ifdef UNICODE + #define SST2A SSW2A + #define SSA2T SSA2W + #define SST2CA SSW2CA + #define SSA2CT SSA2CW + // (Did you get a compiler error here about not being able to convert + // PTSTR into PWSTR? Then your _UNICODE and UNICODE flags are messed + // up. Best bet: #define BOTH macros before including any MS headers.) + inline PWSTR SST2W(PTSTR p) { return p; } + inline PTSTR SSW2T(PWSTR p) { return p; } + inline PCWSTR SST2CW(PCTSTR p) { return p; } + inline PCTSTR SSW2CT(PCWSTR p) { return p; } + #else + #define SST2W SSA2W + #define SSW2T SSW2A + #define SST2CW SSA2CW + #define SSW2CT SSW2CA + inline PSTR SST2A(PTSTR p) { return p; } + inline PTSTR SSA2T(PSTR p) { return p; } + inline PCSTR SST2CA(PCTSTR p) { return p; } + inline PCTSTR SSA2CT(PCSTR p) { return p; } + #endif // #ifdef UNICODE + + #if defined(UNICODE) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #elif defined(OLE2ANSI) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #else + //CharNextW doesn't work on Win95 so we use this + #define SST2COLE(pa) SSA2CW((pa)) + #define SST2OLE(pa) SSA2W((pa)) + #define SSOLE2CT(po) SSW2CA((po)) + #define SSOLE2T(po) SSW2A((po)) + #endif + + #ifdef OLE2ANSI + #define SSW2OLE SSW2A + #define SSOLE2W SSA2W + #define SSW2COLE SSW2CA + #define SSOLE2CW SSA2CW + inline POLESTR SSA2OLE(PSTR p) { return p; } + inline PSTR SSOLE2A(POLESTR p) { return p; } + inline PCOLESTR SSA2COLE(PCSTR p) { return p; } + inline PCSTR SSOLE2CA(PCOLESTR p){ return p; } + #else + #define SSA2OLE SSA2W + #define SSOLE2A SSW2A + #define SSA2COLE SSA2CW + #define SSOLE2CA SSW2CA + inline POLESTR SSW2OLE(PWSTR p) { return p; } + inline PWSTR SSOLE2W(POLESTR p) { return p; } + inline PCOLESTR SSW2COLE(PCWSTR p) { return p; } + inline PCWSTR SSOLE2CW(PCOLESTR p){ return p; } + #endif + + // Above we've defined macros that look like MS' but all have + // an 'SS' prefix. Now we need the real macros. We'll either + // get them from the macros above or from MFC/ATL. + + #if defined (USES_CONVERSION) + + #define _NO_STDCONVERSION // just to be consistent + + #else + + #ifdef _MFC_VER + + #include + #define _NO_STDCONVERSION // just to be consistent + + #else + + #define USES_CONVERSION SSCVT + #define A2CW SSA2CW + #define W2CA SSW2CA + #define T2A SST2A + #define A2T SSA2T + #define T2W SST2W + #define W2T SSW2T + #define T2CA SST2CA + #define A2CT SSA2CT + #define T2CW SST2CW + #define W2CT SSW2CT + #define ocslen sslen + #define ocscpy sscpy + #define T2COLE SST2COLE + #define OLE2CT SSOLE2CT + #define T2OLE SST2COLE + #define OLE2T SSOLE2CT + #define A2OLE SSA2OLE + #define OLE2A SSOLE2A + #define W2OLE SSW2OLE + #define OLE2W SSOLE2W + #define A2COLE SSA2COLE + #define OLE2CA SSOLE2CA + #define W2COLE SSW2COLE + #define OLE2CW SSOLE2CW + + #endif // #ifdef _MFC_VER + #endif // #ifndef USES_CONVERSION +#endif // #ifndef SS_NO_CONVERSION + +// Define ostring - generic name for std::basic_string + +#if !defined(ostring) && !defined(OSTRING_DEFINED) + typedef std::basic_string ostring; + #define OSTRING_DEFINED +#endif + +// StdCodeCvt when there's no conversion to be done +template +inline T* StdCodeCvt(T* pDst, int nDst, const T* pSrc, int nSrc) +{ + int nChars = SSMIN(nSrc, nDst); + + if ( nChars > 0 ) + { + pDst[0] = '\0'; + std::basic_string::traits_type::copy(pDst, pSrc, nChars); +// std::char_traits::copy(pDst, pSrc, nChars); + pDst[nChars] = '\0'; + } + + return pDst; +} +inline PSTR StdCodeCvt(PSTR pDst, int nDst, PCUSTR pSrc, int nSrc) +{ + return StdCodeCvt(pDst, nDst, (PCSTR)pSrc, nSrc); +} +inline PUSTR StdCodeCvt(PUSTR pDst, int nDst, PCSTR pSrc, int nSrc) +{ + return (PUSTR)StdCodeCvt((PSTR)pDst, nDst, pSrc, nSrc); +} + +// Define tstring -- generic name for std::basic_string + +#if !defined(tstring) && !defined(TSTRING_DEFINED) + typedef std::basic_string tstring; + #define TSTRING_DEFINED +#endif + +// a very shorthand way of applying the fix for KB problem Q172398 +// (basic_string assignment bug) + +#if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + #define Q172398(x) (x).erase() +#else + #define Q172398(x) +#endif + +// ============================================================================= +// INLINE FUNCTIONS ON WHICH CSTDSTRING RELIES +// +// Usually for generic text mapping, we rely on preprocessor macro definitions +// to map to string functions. However the CStdStr<> template cannot use +// macro-based generic text mappings because its character types do not get +// resolved until template processing which comes AFTER macro processing. In +// other words, the preprocessor macro UNICODE is of little help to us in the +// CStdStr template +// +// Therefore, to keep the CStdStr declaration simple, we have these inline +// functions. The template calls them often. Since they are inline (and NOT +// exported when this is built as a DLL), they will probably be resolved away +// to nothing. +// +// Without these functions, the CStdStr<> template would probably have to broken +// out into two, almost identical classes. Either that or it would be a huge, +// convoluted mess, with tons of "if" statements all over the place checking the +// size of template parameter CT. +// ============================================================================= + +#ifdef SS_NO_LOCALE + + // -------------------------------------------------------------------------- + // Win32 GetStringTypeEx wrappers + // -------------------------------------------------------------------------- + inline bool wsGetStringType(LCID lc, DWORD dwT, PCSTR pS, int nSize, + WORD* pWd) + { + return FALSE != GetStringTypeExA(lc, dwT, pS, nSize, pWd); + } + inline bool wsGetStringType(LCID lc, DWORD dwT, PCWSTR pS, int nSize, + WORD* pWd) + { + return FALSE != GetStringTypeExW(lc, dwT, pS, nSize, pWd); + } + + + template + inline bool ssisspace (CT t) + { + WORD toYourMother; + return wsGetStringType(GetThreadLocale(), CT_CTYPE1, &t, 1, &toYourMother) + && 0 != (C1_BLANK & toYourMother); + } + +#endif + +// If they defined SS_NO_REFCOUNT, then we must convert all assignments + +#if defined (_MSC_VER) && (_MSC_VER < 1300) + #ifdef SS_NO_REFCOUNT + #define SSREF(x) (x).c_str() + #else + #define SSREF(x) (x) + #endif +#else + #define SSREF(x) (x) +#endif + +// ----------------------------------------------------------------------------- +// sslen: strlen/wcslen wrappers +// ----------------------------------------------------------------------------- +template inline int sslen(const CT* pT) +{ + return 0 == pT ? 0 : (int)std::basic_string::traits_type::length(pT); +// return 0 == pT ? 0 : std::char_traits::length(pT); +} +inline SS_NOTHROW int sslen(const std::string& s) +{ + return static_cast(s.length()); +} +inline SS_NOTHROW int sslen(const std::wstring& s) +{ + return static_cast(s.length()); +} + +// ----------------------------------------------------------------------------- +// sstolower/sstoupper -- convert characters to upper/lower case +// ----------------------------------------------------------------------------- + +#ifdef SS_NO_LOCALE + inline char sstoupper(char ch) { return (char)::toupper(ch); } + inline wchar_t sstoupper(wchar_t ch){ return (wchar_t)::towupper(ch); } + inline char sstolower(char ch) { return (char)::tolower(ch); } + inline wchar_t sstolower(wchar_t ch){ return (wchar_t)::tolower(ch); } +#else + template + inline CT sstolower(const CT& t, const std::locale& loc = std::locale()) + { + return std::tolower(t, loc); + } + template + inline CT sstoupper(const CT& t, const std::locale& loc = std::locale()) + { + return std::toupper(t, loc); + } +#endif + +// ----------------------------------------------------------------------------- +// ssasn: assignment functions -- assign "sSrc" to "sDst" +// ----------------------------------------------------------------------------- +typedef std::string::size_type SS_SIZETYPE; // just for shorthand, really +typedef std::string::pointer SS_PTRTYPE; +typedef std::wstring::size_type SW_SIZETYPE; +typedef std::wstring::pointer SW_PTRTYPE; + + +template +inline void ssasn(std::basic_string& sDst, const std::basic_string& sSrc) +{ + if ( sDst.c_str() != sSrc.c_str() ) + { + sDst.erase(); + sDst.assign(SSREF(sSrc)); + } +} +template +inline void ssasn(std::basic_string& sDst, const T *pA) +{ + // Watch out for NULLs, as always. + + if ( 0 == pA ) + { + sDst.erase(); + } + + // If pA actually points to part of sDst, we must NOT erase(), but + // rather take a substring + + else if ( pA >= sDst.c_str() && pA <= sDst.c_str() + sDst.size() ) + { + sDst =sDst.substr(static_cast::size_type>(pA-sDst.c_str())); + } + + // Otherwise (most cases) apply the assignment bug fix, if applicable + // and do the assignment + + else + { + Q172398(sDst); + sDst.assign(pA); + } +} +inline void ssasn(std::string& sDst, const std::wstring& sSrc) +{ + if ( sSrc.empty() ) + { + sDst.erase(); + } + else + { + int nDst = static_cast(sSrc.size()); + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + // In MBCS builds, we don't know how long the destination string will be. + nDst = static_cast(static_cast(nDst) * 1.3); + sDst.resize(nDst+1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), static_cast(sSrc.size())); + sDst.resize(sslen(szCvt)); +#else + sDst.resize(nDst+1); + StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), static_cast(sSrc.size())); + sDst.resize(sSrc.size()); +#endif + } +} +inline void ssasn(std::string& sDst, PCWSTR pW) +{ + int nSrc = sslen(pW); + if ( nSrc > 0 ) + { + int nSrc = sslen(pW); + int nDst = nSrc; + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + nDst = static_cast(static_cast(nDst) * 1.3); + // In MBCS builds, we don't know how long the destination string will be. + sDst.resize(nDst + 1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + pW, nSrc); + sDst.resize(sslen(szCvt)); +#else + sDst.resize(nDst + 1); + StdCodeCvt(const_cast(sDst.data()), nDst, pW, nSrc); + sDst.resize(nDst); +#endif + } + else + { + sDst.erase(); + } +} +inline void ssasn(std::string& sDst, const int nNull) +{ + //UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(""); +} +#undef StrSizeType +inline void ssasn(std::wstring& sDst, const std::string& sSrc) +{ + if ( sSrc.empty() ) + { + sDst.erase(); + } + else + { + int nSrc = static_cast(sSrc.size()); + int nDst = nSrc; + + sDst.resize(nSrc+1); + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), nSrc); + + sDst.resize(sslen(szCvt)); + } +} +inline void ssasn(std::wstring& sDst, PCSTR pA) +{ + int nSrc = sslen(pA); + + if ( 0 == nSrc ) + { + sDst.erase(); + } + else + { + int nDst = nSrc; + sDst.resize(nDst+1); + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, pA, + nSrc); + + sDst.resize(sslen(szCvt)); + } +} +inline void ssasn(std::wstring& sDst, const int nNull) +{ + //UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(L""); +} + +// ----------------------------------------------------------------------------- +// ssadd: string object concatenation -- add second argument to first +// ----------------------------------------------------------------------------- +inline void ssadd(std::string& sDst, const std::wstring& sSrc) +{ + int nSrc = static_cast(sSrc.size()); + + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + int nAdd = nSrc; + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + nAdd = static_cast(static_cast(nAdd) * 1.3); + sDst.resize(nDst+nAdd+1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nAdd, sSrc.c_str(), nSrc); + sDst.resize(nDst + sslen(szCvt)); +#else + sDst.resize(nDst+nAdd+1); + StdCodeCvt(const_cast(sDst.data()+nDst), nAdd, sSrc.c_str(), nSrc); + sDst.resize(nDst + nAdd); +#endif + } +} +template +inline void ssadd(typename std::basic_string& sDst, const typename std::basic_string& sSrc) +{ + sDst += sSrc; +} +inline void ssadd(std::string& sDst, PCWSTR pW) +{ + int nSrc = sslen(pW); + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + int nAdd = nSrc; + +#ifdef SS_MBCS + nAdd = static_cast(static_cast(nAdd) * 1.3); + sDst.resize(nDst + nAdd + 1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nAdd, pW, nSrc); + sDst.resize(nDst + sslen(szCvt)); +#else + sDst.resize(nDst + nAdd + 1); + StdCodeCvt(const_cast(sDst.data()+nDst), nAdd, pW, nSrc); + sDst.resize(nDst + nSrc); +#endif + } +} +template +inline void ssadd(typename std::basic_string& sDst, const T *pA) +{ + if ( pA ) + { + // If the string being added is our internal string or a part of our + // internal string, then we must NOT do any reallocation without + // first copying that string to another object (since we're using a + // direct pointer) + + if ( pA >= sDst.c_str() && pA <= sDst.c_str()+sDst.length()) + { + if ( sDst.capacity() <= sDst.size()+sslen(pA) ) + sDst.append(std::basic_string(pA)); + else + sDst.append(pA); + } + else + { + sDst.append(pA); + } + } +} +inline void ssadd(std::wstring& sDst, const std::string& sSrc) +{ + if ( !sSrc.empty() ) + { + int nSrc = static_cast(sSrc.size()); + int nDst = static_cast(sDst.size()); + + sDst.resize(nDst + nSrc + 1); +#ifdef SS_MBCS + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nSrc, sSrc.c_str(), nSrc+1); + sDst.resize(nDst + sslen(szCvt)); +#else + StdCodeCvt(const_cast(sDst.data()+nDst), nSrc, sSrc.c_str(), nSrc+1); + sDst.resize(nDst + nSrc); +#endif + } +} +inline void ssadd(std::wstring& sDst, PCSTR pA) +{ + int nSrc = sslen(pA); + + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + + sDst.resize(nDst + nSrc + 1); +#ifdef SS_MBCS + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nSrc, pA, nSrc+1); + sDst.resize(nDst + sslen(szCvt)); +#else + StdCodeCvt(const_cast(sDst.data()+nDst), nSrc, pA, nSrc+1); + sDst.resize(nDst + nSrc); +#endif + } +} + +// ----------------------------------------------------------------------------- +// sscmp: comparison (case sensitive, not affected by locale) +// ----------------------------------------------------------------------------- +template +inline int sscmp(const CT* pA1, const CT* pA2) +{ + CT f; + CT l; + + do + { + f = *(pA1++); + l = *(pA2++); + } while ( (f) && (f == l) ); + + return (int)(f - l); +} + +// ----------------------------------------------------------------------------- +// ssicmp: comparison (case INsensitive, not affected by locale) +// ----------------------------------------------------------------------------- +template +inline int ssicmp(const CT* pA1, const CT* pA2) +{ + // Using the "C" locale = "not affected by locale" + + std::locale loc = std::locale::classic(); + const std::ctype& ct = SS_USE_FACET(loc, std::ctype); + CT f; + CT l; + + do + { + f = ct.tolower(*(pA1++)); + l = ct.tolower(*(pA2++)); + } while ( (f) && (f == l) ); + + return (int)(f - l); +} + +// ----------------------------------------------------------------------------- +// ssupr/sslwr: Uppercase/Lowercase conversion functions +// ----------------------------------------------------------------------------- + +template +inline void sslwr(CT* pT, size_t nLen, const std::locale& loc=std::locale()) +{ + SS_USE_FACET(loc, std::ctype).tolower(pT, pT+nLen); +} +template +inline void ssupr(CT* pT, size_t nLen, const std::locale& loc=std::locale()) +{ + SS_USE_FACET(loc, std::ctype).toupper(pT, pT+nLen); +} + +// ----------------------------------------------------------------------------- +// vsprintf/vswprintf or _vsnprintf/_vsnwprintf equivalents. In standard +// builds we can't use _vsnprintf/_vsnwsprintf because they're MS extensions. +// +// ----------------------------------------------------------------------------- +// Borland's headers put some ANSI "C" functions in the 'std' namespace. +// Promote them to the global namespace so we can use them here. + +#if defined(__BORLANDC__) + using std::vsprintf; + using std::vswprintf; +#endif + + // GNU is supposed to have vsnprintf and vsnwprintf. But only the newer + // distributions do. + +#if defined(__GNUC__) + + inline int ssvsprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + return vsnprintf(pA, nCount, pFmtA, vl); + } + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + return vswprintf(pW, nCount, pFmtW, vl); + } + + // Microsofties can use +#elif defined(_MSC_VER) && !defined(SS_ANSI) + + inline int ssvsprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + return _vsnprintf(pA, nCount, pFmtA, vl); + } + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + return _vsnwprintf(pW, nCount, pFmtW, vl); + } + +#elif defined (SS_DANGEROUS_FORMAT) // ignore buffer size parameter if needed? + + inline int ssvsprintf(PSTR pA, size_t /*nCount*/, PCSTR pFmtA, va_list vl) + { + return vsprintf(pA, pFmtA, vl); + } + + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + // JMO: Some distributions of the "C" have a version of vswprintf that + // takes 3 arguments (e.g. Microsoft, Borland, GNU). Others have a + // version which takes 4 arguments (an extra "count" argument in the + // second position. The best stab I can take at this so far is that if + // you are NOT running with MS, Borland, or GNU, then I'll assume you + // have the version that takes 4 arguments. + // + // I'm sure that these checks don't catch every platform correctly so if + // you get compiler errors on one of the lines immediately below, it's + // probably because your implemntation takes a different number of + // arguments. You can comment out the offending line (and use the + // alternate version) or you can figure out what compiler flag to check + // and add that preprocessor check in. Regardless, if you get an error + // on these lines, I'd sure like to hear from you about it. + // + // Thanks to Ronny Schulz for the SGI-specific checks here. + +// #if !defined(__MWERKS__) && !defined(__SUNPRO_CC_COMPAT) && !defined(__SUNPRO_CC) + #if !defined(_MSC_VER) \ + && !defined (__BORLANDC__) \ + && !defined(__GNUC__) \ + && !defined(__sgi) + + return vswprintf(pW, nCount, pFmtW, vl); + + // suddenly with the current SGI 7.3 compiler there is no such function as + // vswprintf and the substitute needs explicit casts to compile + + #elif defined(__sgi) + + nCount; + return vsprintf( (char *)pW, (char *)pFmtW, vl); + + #else + + nCount; + return vswprintf(pW, pFmtW, vl); + + #endif + + } + +#endif + + // GOT COMPILER PROBLEMS HERE? + // --------------------------- + // Does your compiler choke on one or more of the following 2 functions? It + // probably means that you don't have have either vsnprintf or vsnwprintf in + // your version of the CRT. This is understandable since neither is an ANSI + // "C" function. However it still leaves you in a dilemma. In order to make + // this code build, you're going to have to to use some non-length-checked + // formatting functions that every CRT has: vsprintf and vswprintf. + // + // This is very dangerous. With the proper erroneous (or malicious) code, it + // can lead to buffer overlows and crashing your PC. Use at your own risk + // In order to use them, just #define SS_DANGEROUS_FORMAT at the top of + // this file. + // + // Even THEN you might not be all the way home due to some non-conforming + // distributions. More on this in the comments below. + + inline int ssnprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + #ifdef _MSC_VER + return _vsnprintf(pA, nCount, pFmtA, vl); + #else + return vsnprintf(pA, nCount, pFmtA, vl); + #endif + } + inline int ssnprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + #ifdef _MSC_VER + return _vsnwprintf(pW, nCount, pFmtW, vl); + #else + return vswprintf(pW, nCount, pFmtW, vl); + #endif + } + + + + +// ----------------------------------------------------------------------------- +// ssload: Type safe, overloaded ::LoadString wrappers +// There is no equivalent of these in non-Win32-specific builds. However, I'm +// thinking that with the message facet, there might eventually be one +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline int ssload(HMODULE hInst, UINT uId, PSTR pBuf, int nMax) + { + return ::LoadStringA(hInst, uId, pBuf, nMax); + } + inline int ssload(HMODULE hInst, UINT uId, PWSTR pBuf, int nMax) + { + return ::LoadStringW(hInst, uId, pBuf, nMax); + } +#if defined ( _MSC_VER ) && ( _MSC_VER >= 1500 ) + inline int ssload(HMODULE hInst, UINT uId, uint16_t *pBuf, int nMax) + { + return 0; + } + inline int ssload(HMODULE hInst, UINT uId, uint32_t *pBuf, int nMax) + { + return 0; + } +#endif +#endif + + +// ----------------------------------------------------------------------------- +// sscoll/ssicoll: Collation wrappers +// Note -- with MSVC I have reversed the arguments order here because the +// functions appear to return the opposite of what they should +// ----------------------------------------------------------------------------- +#ifndef SS_NO_LOCALE +template +inline int sscoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::collate& coll = + SS_USE_FACET(std::locale(), std::collate); + + return coll.compare(sz2, sz2+nLen2, sz1, sz1+nLen1); +} +template +inline int ssicoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::locale loc; + const std::collate& coll = SS_USE_FACET(loc, std::collate); + + // Some implementations seem to have trouble using the collate<> + // facet typedefs so we'll just default to basic_string and hope + // that's what the collate facet uses (which it generally should) + +// std::collate::string_type s1(sz1); +// std::collate::string_type s2(sz2); + const std::basic_string sEmpty; + std::basic_string s1(sz1 ? sz1 : sEmpty.c_str()); + std::basic_string s2(sz2 ? sz2 : sEmpty.c_str()); + + sslwr(const_cast(s1.c_str()), nLen1, loc); + sslwr(const_cast(s2.c_str()), nLen2, loc); + return coll.compare(s2.c_str(), s2.c_str()+nLen2, + s1.c_str(), s1.c_str()+nLen1); +} +#endif + + +// ----------------------------------------------------------------------------- +// ssfmtmsg: FormatMessage equivalents. Needed because I added a CString facade +// Again -- no equivalent of these on non-Win32 builds but their might one day +// be one if the message facet gets implemented +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageA(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PWSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageW(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } +#else +#endif + + + +// FUNCTION: sscpy. Copies up to 'nMax' characters from pSrc to pDst. +// ----------------------------------------------------------------------------- +// FUNCTION: sscpy +// inline int sscpy(PSTR pDst, PCSTR pSrc, int nMax=-1); +// inline int sscpy(PUSTR pDst, PCSTR pSrc, int nMax=-1) +// inline int sscpy(PSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCSTR pSrc, int nMax=-1); +// +// DESCRIPTION: +// This function is very much (but not exactly) like strcpy. These +// overloads simplify copying one C-style string into another by allowing +// the caller to specify two different types of strings if necessary. +// +// The strings must NOT overlap +// +// "Character" is expressed in terms of the destination string, not +// the source. If no 'nMax' argument is supplied, then the number of +// characters copied will be sslen(pSrc). A NULL terminator will +// also be added so pDst must actually be big enough to hold nMax+1 +// characters. The return value is the number of characters copied, +// not including the NULL terminator. +// +// PARAMETERS: +// pSrc - the string to be copied FROM. May be a char based string, an +// MBCS string (in Win32 builds) or a wide string (wchar_t). +// pSrc - the string to be copied TO. Also may be either MBCS or wide +// nMax - the maximum number of characters to be copied into szDest. Note +// that this is expressed in whatever a "character" means to pDst. +// If pDst is a wchar_t type string than this will be the maximum +// number of wchar_ts that my be copied. The pDst string must be +// large enough to hold least nMaxChars+1 characters. +// If the caller supplies no argument for nMax this is a signal to +// the routine to copy all the characters in pSrc, regardless of +// how long it is. +// +// RETURN VALUE: none +// ----------------------------------------------------------------------------- + +template +inline int sscpycvt(CT1* pDst, const CT2* pSrc, int nMax) +{ + // Note -- we assume pDst is big enough to hold pSrc. If not, we're in + // big trouble. No bounds checking. Caveat emptor. + + int nSrc = sslen(pSrc); + + const CT1* szCvt = StdCodeCvt(pDst, nMax, pSrc, nSrc); + + // If we're copying the same size characters, then all the "code convert" + // just did was basically memcpy so the #of characters copied is the same + // as the number requested. I should probably specialize this function + // template to achieve this purpose as it is silly to do a runtime check + // of a fact known at compile time. I'll get around to it. + + return sslen(szCvt); +} + +template +inline int sscpycvt(T* pDst, const T* pSrc, int nMax) +{ + int nCount = nMax; + for (; nCount > 0 && *pSrc; ++pSrc, ++pDst, --nCount) + std::basic_string::traits_type::assign(*pDst, *pSrc); + + *pDst = 0; + return nMax - nCount; +} + +inline int sscpycvt(PWSTR pDst, PCSTR pSrc, int nMax) +{ + // Note -- we assume pDst is big enough to hold pSrc. If not, we're in + // big trouble. No bounds checking. Caveat emptor. + + const PWSTR szCvt = StdCodeCvt(pDst, nMax, pSrc, nMax); + return sslen(szCvt); +} + +template +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax, int nLen) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, nLen)); +} +template +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, sslen(pSrc))); +} +template +inline int sscpy(CT1* pDst, const CT2* pSrc) +{ + return sscpycvt(pDst, pSrc, sslen(pSrc)); +} +template +inline int sscpy(CT1* pDst, const std::basic_string& sSrc, int nMax) +{ + return sscpycvt(pDst, sSrc.c_str(), SSMIN(nMax, (int)sSrc.length())); +} +template +inline int sscpy(CT1* pDst, const std::basic_string& sSrc) +{ + return sscpycvt(pDst, sSrc.c_str(), (int)sSrc.length()); +} + +#ifdef SS_INC_COMDEF + template + inline int sscpy(CT1* pDst, const _bstr_t& bs, int nMax) + { + return sscpycvt(pDst, static_cast(bs), + SSMIN(nMax, static_cast(bs.length()))); + } + template + inline int sscpy(CT1* pDst, const _bstr_t& bs) + { + return sscpy(pDst, bs, static_cast(bs.length())); + } +#endif + + +// ----------------------------------------------------------------------------- +// Functional objects for changing case. They also let you pass locales +// ----------------------------------------------------------------------------- + +#ifdef SS_NO_LOCALE + template + struct SSToUpper : public std::unary_function + { + inline CT operator()(const CT& t) const + { + return sstoupper(t); + } + }; + template + struct SSToLower : public std::unary_function + { + inline CT operator()(const CT& t) const + { + return sstolower(t); + } + }; +#else + template + struct SSToUpper : public std::binary_function + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstoupper(t, loc); + } + }; + template + struct SSToLower : public std::binary_function + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstolower(t, loc); + } + }; +#endif + +// This struct is used for TrimRight() and TrimLeft() function implementations. +//template +//struct NotSpace : public std::unary_function +//{ +// const std::locale& loc; +// inline NotSpace(const std::locale& locArg) : loc(locArg) {} +// inline bool operator() (CT t) { return !std::isspace(t, loc); } +//}; +template +struct NotSpace : public std::unary_function +{ + // DINKUMWARE BUG: + // Note -- using std::isspace in a COM DLL gives us access violations + // because it causes the dynamic addition of a function to be called + // when the library shuts down. Unfortunately the list is maintained + // in DLL memory but the function is in static memory. So the COM DLL + // goes away along with the function that was supposed to be called, + // and then later when the DLL CRT shuts down it unloads the list and + // tries to call the long-gone function. + // This is DinkumWare's implementation problem. If you encounter this + // problem, you may replace the calls here with good old isspace() and + // iswspace() from the CRT unless they specify SS_ANSI + +#ifdef SS_NO_LOCALE + + bool operator() (CT t) const { return !ssisspace(t); } + +#else + const std::locale loc; + NotSpace(const std::locale& locArg=std::locale()) : loc(locArg) {} + bool operator() (CT t) const { return !std::isspace(t, loc); } +#endif +}; + + + + +// Now we can define the template (finally!) +// ============================================================================= +// TEMPLATE: CStdStr +// template class CStdStr : public std::basic_string +// +// REMARKS: +// This template derives from basic_string and adds some MFC CString- +// like functionality +// +// Basically, this is my attempt to make Standard C++ library strings as +// easy to use as the MFC CString class. +// +// Note that although this is a template, it makes the assumption that the +// template argument (CT, the character type) is either char or wchar_t. +// ============================================================================= + +//#define CStdStr _SS // avoid compiler warning 4786 + +// template ARG& FmtArg(ARG& arg) { return arg; } +// PCSTR FmtArg(const std::string& arg) { return arg.c_str(); } +// PCWSTR FmtArg(const std::wstring& arg) { return arg.c_str(); } + +template +struct FmtArg +{ + explicit FmtArg(const ARG& arg) : a_(arg) {} + const ARG& operator()() const { return a_; } + const ARG& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; + +template +class CStdStr : public std::basic_string +{ + // Typedefs for shorter names. Using these names also appears to help + // us avoid some ambiguities that otherwise arise on some platforms + + #define MYBASE std::basic_string // my base class + //typedef typename std::basic_string MYBASE; // my base class + typedef CStdStr MYTYPE; // myself + typedef typename MYBASE::const_pointer PCMYSTR; // PCSTR or PCWSTR + typedef typename MYBASE::pointer PMYSTR; // PSTR or PWSTR + typedef typename MYBASE::iterator MYITER; // my iterator type + typedef typename MYBASE::const_iterator MYCITER; // you get the idea... + typedef typename MYBASE::reverse_iterator MYRITER; + typedef typename MYBASE::size_type MYSIZE; + typedef typename MYBASE::value_type MYVAL; + typedef typename MYBASE::allocator_type MYALLOC; + +public: + // shorthand conversion from PCTSTR to string resource ID + #define SSRES(pctstr) LOWORD(reinterpret_cast(pctstr)) + + bool TryLoad(const void* pT) + { + bool bLoaded = false; + +#if defined(SS_WIN32) && !defined(SS_ANSI) + if ( ( pT != NULL ) && SS_IS_INTRESOURCE(pT) ) + { + UINT nId = LOWORD(reinterpret_cast(pT)); + if ( !LoadString(nId) ) + { + TRACE(_T("Can't load string %u\n"), SSRES(pT)); + } + bLoaded = true; + } +#endif + + return bLoaded; + } + + + // CStdStr inline constructors + CStdStr() + { + } + + CStdStr(const MYTYPE& str) : MYBASE(SSREF(str)) + { + } + + CStdStr(const std::string& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(const std::wstring& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(PCMYSTR pT, MYSIZE n) : MYBASE(pT, n) + { + } + +#ifdef SS_UNSIGNED + CStdStr(PCUSTR pU) + { + *this = reinterpret_cast(pU); + } +#endif + + CStdStr(PCSTR pA) + { + #ifdef SS_ANSI + *this = pA; + #else + if ( !TryLoad(pA) ) + *this = pA; + #endif + } + + CStdStr(PCWSTR pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(uint16_t* pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(uint32_t* pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(MYCITER first, MYCITER last) + : MYBASE(first, last) + { + } + + CStdStr(MYSIZE nSize, MYVAL ch, const MYALLOC& al=MYALLOC()) + : MYBASE(nSize, ch, al) + { + } + + #ifdef SS_INC_COMDEF + CStdStr(const _bstr_t& bstr) + { + if ( bstr.length() > 0 ) + this->append(static_cast(bstr), bstr.length()); + } + #endif + + // CStdStr inline assignment operators -- the ssasn function now takes care + // of fixing the MSVC assignment bug (see knowledge base article Q172398). + MYTYPE& operator=(const MYTYPE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::string& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::wstring& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(PCSTR pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(PCWSTR pW) + { + ssasn(*this, pW); + return *this; + } + +#ifdef SS_UNSIGNED + MYTYPE& operator=(PCUSTR pU) + { + ssasn(*this, reinterpret_cast(pU)); + return *this; + } +#endif + + MYTYPE& operator=(uint16_t* pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(uint32_t* pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(CT t) + { + Q172398(*this); + this->assign(1, t); + return *this; + } + + #ifdef SS_INC_COMDEF + MYTYPE& operator=(const _bstr_t& bstr) + { + if ( bstr.length() > 0 ) + { + this->assign(static_cast(bstr), bstr.length()); + return *this; + } + else + { + this->erase(); + return *this; + } + } + #endif + + + // Overloads also needed to fix the MSVC assignment bug (KB: Q172398) + // *** Thanks to Pete The Plumber for catching this one *** + // They also are compiled if you have explicitly turned off refcounting + #if ( defined(_MSC_VER) && ( _MSC_VER < 1200 ) ) || defined(SS_NO_REFCOUNT) + + MYTYPE& assign(const MYTYPE& str) + { + Q172398(*this); + sscpy(GetBuffer(str.size()+1), SSREF(str)); + this->ReleaseBuffer(str.size()); + return *this; + } + + MYTYPE& assign(const MYTYPE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Poll�hne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + MYTYPE strTemp(str.c_str()+nStart, nChars); + Q172398(*this); + this->assign(strTemp); + return *this; + } + + MYTYPE& assign(const MYBASE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& assign(const MYBASE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Poll�hne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + + // Watch out for assignment to self + + if ( this == &str ) + { + MYTYPE strTemp(str.c_str() + nStart, nChars); + static_cast(this)->assign(strTemp); + } + else + { + Q172398(*this); + static_cast(this)->assign(str.c_str()+nStart, nChars); + } + return *this; + } + + MYTYPE& assign(const CT* pC, MYSIZE nChars) + { + // Q172398 only fix -- erase before assigning, but not if we're + // assigning from our own buffer + + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + if ( !this->empty() && + ( pC < this->data() || pC > this->data() + this->capacity() ) ) + { + this->erase(); + } + #endif + Q172398(*this); + static_cast(this)->assign(pC, nChars); + return *this; + } + + MYTYPE& assign(MYSIZE nChars, MYVAL val) + { + Q172398(*this); + static_cast(this)->assign(nChars, val); + return *this; + } + + MYTYPE& assign(const CT* pT) + { + return this->assign(pT, MYBASE::traits_type::length(pT)); + } + + MYTYPE& assign(MYCITER iterFirst, MYCITER iterLast) + { + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + // Q172398 fix. don't call erase() if we're assigning from ourself + if ( iterFirst < this->begin() || + iterFirst > this->begin() + this->size() ) + { + this->erase() + } + #endif + this->replace(this->begin(), this->end(), iterFirst, iterLast); + return *this; + } + #endif + + + // ------------------------------------------------------------------------- + // CStdStr inline concatenation. + // ------------------------------------------------------------------------- + MYTYPE& operator+=(const MYTYPE& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::string& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::wstring& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(PCSTR pA) + { + ssadd(*this, pA); + return *this; + } + + MYTYPE& operator+=(PCWSTR pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(uint16_t* pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(uint32_t* pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(CT t) + { + this->append(1, t); + return *this; + } + #ifdef SS_INC_COMDEF // if we have _bstr_t, define a += for it too. + MYTYPE& operator+=(const _bstr_t& bstr) + { + return this->operator+=(static_cast(bstr)); + } + #endif + + + // ------------------------------------------------------------------------- + // Case changing functions + // ------------------------------------------------------------------------- + + MYTYPE& ToUpper(const std::locale& loc=std::locale()) + { + // Note -- if there are any MBCS character sets in which the lowercase + // form a character takes up a different number of bytes than the + // uppercase form, this would probably not work... + + std::transform(this->begin(), + this->end(), + this->begin(), +#ifdef SS_NO_LOCALE + SSToUpper()); +#else + std::bind2nd(SSToUpper(), loc)); +#endif + + // ...but if it were, this would probably work better. Also, this way + // seems to be a bit faster when anything other then the "C" locale is + // used... + +// if ( !empty() ) +// { +// ssupr(this->GetBuf(), this->size(), loc); +// this->RelBuf(); +// } + + return *this; + } + + MYTYPE& ToLower(const std::locale& loc=std::locale()) + { + // Note -- if there are any MBCS character sets in which the lowercase + // form a character takes up a different number of bytes than the + // uppercase form, this would probably not work... + + std::transform(this->begin(), + this->end(), + this->begin(), +#ifdef SS_NO_LOCALE + SSToLower()); +#else + std::bind2nd(SSToLower(), loc)); +#endif + + // ...but if it were, this would probably work better. Also, this way + // seems to be a bit faster when anything other then the "C" locale is + // used... + +// if ( !empty() ) +// { +// sslwr(this->GetBuf(), this->size(), loc); +// this->RelBuf(); +// } + return *this; + } + + + MYTYPE& Normalize() + { + return Trim().ToLower(); + } + + + // ------------------------------------------------------------------------- + // CStdStr -- Direct access to character buffer. In the MS' implementation, + // the at() function that we use here also calls _Freeze() providing us some + // protection from multithreading problems associated with ref-counting. + // In VC 7 and later, of course, the ref-counting stuff is gone. + // ------------------------------------------------------------------------- + + CT* GetBuf(int nMinLen=-1) + { + if ( static_cast(this->size()) < nMinLen ) + this->resize(static_cast(nMinLen)); + + return this->empty() ? const_cast(this->data()) : &(this->at(0)); + } + + CT* SetBuf(int nLen) + { + nLen = ( nLen > 0 ? nLen : 0 ); + if ( this->capacity() < 1 && nLen == 0 ) + this->resize(1); + + this->resize(static_cast(nLen)); + return const_cast(this->data()); + } + void RelBuf(int nNewLen=-1) + { + this->resize(static_cast(nNewLen > -1 ? nNewLen : + sslen(this->c_str()))); + } + + void BufferRel() { RelBuf(); } // backwards compatability + CT* Buffer() { return GetBuf(); } // backwards compatability + CT* BufferSet(int nLen) { return SetBuf(nLen);}// backwards compatability + + bool Equals(const CT* pT, bool bUseCase=false) const + { + return 0 == (bUseCase ? this->compare(pT) : ssicmp(this->c_str(), pT)); + } + + // ------------------------------------------------------------------------- + // FUNCTION: CStdStr::Load + // REMARKS: + // Loads string from resource specified by nID + // + // PARAMETERS: + // nID - resource Identifier. Purely a Win32 thing in this case + // + // RETURN VALUE: + // true if successful, false otherwise + // ------------------------------------------------------------------------- + +#ifndef SS_ANSI + + bool Load(UINT nId, HMODULE hModule=NULL) + { + bool bLoaded = false; // set to true of we succeed. + + #ifdef _MFC_VER // When in Rome (or MFC land)... + + // If they gave a resource handle, use it. Note - this is archaic + // and not really what I would recommend. But then again, in MFC + // land, you ought to be using CString for resources anyway since + // it walks the resource chain for you. + + HMODULE hModuleOld = NULL; + + if ( NULL != hModule ) + { + hModuleOld = AfxGetResourceHandle(); + AfxSetResourceHandle(hModule); + } + + // ...load the string + + CString strRes; + bLoaded = FALSE != strRes.LoadString(nId); + + // ...and if we set the resource handle, restore it. + + if ( NULL != hModuleOld ) + AfxSetResourceHandle(hModule); + + if ( bLoaded ) + *this = strRes; + + #else // otherwise make our own hackneyed version of CString's Load + + // Get the resource name and module handle + + if ( NULL == hModule ) + hModule = GetResourceHandle(); + + PCTSTR szName = MAKEINTRESOURCE((nId>>4)+1); // lifted + DWORD dwSize = 0; + + // No sense continuing if we can't find the resource + + HRSRC hrsrc = ::FindResource(hModule, szName, RT_STRING); + + if ( NULL == hrsrc ) + { + TRACE(_T("Cannot find resource %d: 0x%X"), nId, ::GetLastError()); + } + else if ( 0 == (dwSize = ::SizeofResource(hModule, hrsrc) / sizeof(CT))) + { + TRACE(_T("Cant get size of resource %d 0x%X\n"),nId,GetLastError()); + } + else + { + bLoaded = 0 != ssload(hModule, nId, GetBuf(dwSize), dwSize); + ReleaseBuffer(); + } + + #endif // #ifdef _MFC_VER + + if ( !bLoaded ) + TRACE(_T("String not loaded 0x%X\n"), ::GetLastError()); + + return bLoaded; + } + +#endif // #ifdef SS_ANSI + + // ------------------------------------------------------------------------- + // FUNCTION: CStdStr::Format + // void _cdecl Formst(CStdStringA& PCSTR szFormat, ...) + // void _cdecl Format(PCSTR szFormat); + // + // DESCRIPTION: + // This function does sprintf/wsprintf style formatting on CStdStringA + // objects. It looks a lot like MFC's CString::Format. Some people + // might even call this identical. Fortunately, these people are now + // dead... heh heh. + // + // PARAMETERS: + // nId - ID of string resource holding the format string + // szFormat - a PCSTR holding the format specifiers + // argList - a va_list holding the arguments for the format specifiers. + // + // RETURN VALUE: None. + // ------------------------------------------------------------------------- + // formatting (using wsprintf style formatting) + + // If they want a Format() function that safely handles string objects + // without casting + +#ifdef SS_SAFE_FORMAT + + // Question: Joe, you wacky coder you, why do you have so many overloads + // of the Format() function + // Answer: One reason only - CString compatability. In short, by making + // the Format() function a template this way, I can do strong typing + // and allow people to pass CStdString arguments as fillers for + // "%s" format specifiers without crashing their program! The downside + // is that I need to overload on the number of arguments. If you are + // passing more arguments than I have listed below in any of my + // overloads, just add another one. + // + // Yes, yes, this is really ugly. In essence what I am doing here is + // protecting people from a bad (and incorrect) programming practice + // that they should not be doing anyway. I am protecting them from + // themselves. Why am I doing this? Well, if you had any idea the + // number of times I've been emailed by people about this + // "incompatability" in my code, you wouldn't ask. + + void Fmt(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + FormatV(szFmt, argList); + va_end(argList); + } + +#ifndef SS_ANSI + + void Format(UINT nId) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + this->swap(strFmt); + } + template + void Format(UINT nId, const A1& v) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + Fmt(strFmt, FmtArg(v)()); + } + template + void Format(UINT nId, const A1& v1, const A2& v2) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)()); + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(),FmtArg(v5)(), + FmtArg(v6)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(),FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(),FmtArg(v10)(),FmtArg(v11)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)(),FmtArg(v14)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(), FmtArg(v16)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16, const A17& v17) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(),FmtArg(v16)(),FmtArg(v17)()); + } + } + +#endif // #ifndef SS_ANSI + + // ...now the other overload of Format: the one that takes a string literal + + void Format(const CT* szFmt) + { + *this = szFmt; + } + template + void Format(const CT* szFmt, const A1& v) + { + Fmt(szFmt, FmtArg(v)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(),FmtArg(v10)(),FmtArg(v11)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)(),FmtArg(v14)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(), FmtArg(v16)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16, const A17& v17) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(),FmtArg(v16)(),FmtArg(v17)()); + } + +#else // #ifdef SS_SAFE_FORMAT + + +#ifndef SS_ANSI + + void Format(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + FormatV(strFmt, argList); + + va_end(argList); + } + +#endif // #ifdef SS_ANSI + + void Format(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + FormatV(szFmt, argList); + va_end(argList); + } + +#endif // #ifdef SS_SAFE_FORMAT + + void AppendFormat(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + AppendFormatV(szFmt, argList); + va_end(argList); + } + + #define MAX_FMT_TRIES 5 // #of times we try + #define FMT_BLOCK_SIZE 2048 // # of bytes to increment per try + #define BUFSIZE_1ST 256 + #define BUFSIZE_2ND 512 + #define STD_BUF_SIZE 1024 + + // an efficient way to add formatted characters to the string. You may only + // add up to STD_BUF_SIZE characters at a time, though + void AppendFormatV(const CT* szFmt, va_list argList) + { + CT szBuf[STD_BUF_SIZE]; + int nLen = ssnprintf(szBuf, STD_BUF_SIZE-1, szFmt, argList); + + if ( 0 < nLen ) + this->append(szBuf, nLen); + } + + // ------------------------------------------------------------------------- + // FUNCTION: FormatV + // void FormatV(PCSTR szFormat, va_list, argList); + // + // DESCRIPTION: + // This function formats the string with sprintf style format-specs. + // It makes a general guess at required buffer size and then tries + // successively larger buffers until it finds one big enough or a + // threshold (MAX_FMT_TRIES) is exceeded. + // + // PARAMETERS: + // szFormat - a PCSTR holding the format of the output + // argList - a Microsoft specific va_list for variable argument lists + // + // RETURN VALUE: + // ------------------------------------------------------------------------- + + // NOTE: Changed by JM to actually function under non-win32, + // and to remove the upper limit on size. + void FormatV(const CT* szFormat, va_list argList) + { + // try and grab a sufficient buffersize + int nChars = FMT_BLOCK_SIZE; + va_list argCopy; + + CT *p = reinterpret_cast(malloc(sizeof(CT)*nChars)); + if (!p) return; + + while (1) + { + va_copy(argCopy, argList); + + int nActual = ssvsprintf(p, nChars, szFormat, argCopy); + /* If that worked, return the string. */ + if (nActual > -1 && nActual < nChars) + { /* make sure it's NULL terminated */ + p[nActual] = '\0'; + this->assign(p, nActual); + free(p); + va_end(argCopy); + return; + } + /* Else try again with more space. */ + if (nActual > -1) /* glibc 2.1 */ + nChars = nActual + 1; /* precisely what is needed */ + else /* glibc 2.0 */ + nChars *= 2; /* twice the old size */ + + CT *np = reinterpret_cast(realloc(p, sizeof(CT)*nChars)); + if (np == NULL) + { + free(p); + va_end(argCopy); + return; // failed :( + } + p = np; + va_end(argCopy); + } + } + + // ------------------------------------------------------------------------- + // CString Facade Functions: + // + // The following methods are intended to allow you to use this class as a + // near drop-in replacement for CString. + // ------------------------------------------------------------------------- + #ifdef SS_WIN32 + BSTR AllocSysString() const + { + ostring os; + ssasn(os, *this); + return ::SysAllocString(os.c_str()); + } + #endif + +#ifndef SS_NO_LOCALE + int Collate(PCMYSTR szThat) const + { + return sscoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } + + int CollateNoCase(PCMYSTR szThat) const + { + return ssicoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } +#endif + int Compare(PCMYSTR szThat) const + { + return this->compare(szThat); + } + + int CompareNoCase(PCMYSTR szThat) const + { + return ssicmp(this->c_str(), szThat); + } + + int Delete(int nIdx, int nCount=1) + { + if ( nIdx < 0 ) + nIdx = 0; + + if ( nIdx < this->GetLength() ) + this->erase(static_cast(nIdx), static_cast(nCount)); + + return GetLength(); + } + + void Empty() + { + this->erase(); + } + + int Find(CT ch) const + { + MYSIZE nIdx = this->find_first_of(ch); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(PCMYSTR szSub) const + { + MYSIZE nIdx = this->find(szSub); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(CT ch, int nStart) const + { + // CString::Find docs say add 1 to nStart when it's not zero + // CString::Find code doesn't do that however. We'll stick + // with what the code does + + MYSIZE nIdx = this->find_first_of(ch, static_cast(nStart)); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(PCMYSTR szSub, int nStart) const + { + // CString::Find docs say add 1 to nStart when it's not zero + // CString::Find code doesn't do that however. We'll stick + // with what the code does + + MYSIZE nIdx = this->find(szSub, static_cast(nStart)); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int FindOneOf(PCMYSTR szCharSet) const + { + MYSIZE nIdx = this->find_first_of(szCharSet); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + +#ifndef SS_ANSI + void FormatMessage(PCMYSTR szFormat, ...) throw(std::exception) + { + va_list argList; + va_start(argList, szFormat); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + szFormat, 0, 0, + reinterpret_cast(&szTemp), 0, &argList) == 0 || + szTemp == 0 ) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } + + void FormatMessage(UINT nFormatId, ...) throw(std::exception) + { + MYTYPE sFormat; + VERIFY(sFormat.LoadString(nFormatId)); + va_list argList; + va_start(argList, nFormatId); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + sFormat, 0, 0, + reinterpret_cast(&szTemp), 0, &argList) == 0 || + szTemp == 0) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } +#endif + + // GetAllocLength -- an MSVC7 function but it costs us nothing to add it. + + int GetAllocLength() + { + return static_cast(this->capacity()); + } + + // ------------------------------------------------------------------------- + // GetXXXX -- Direct access to character buffer + // ------------------------------------------------------------------------- + CT GetAt(int nIdx) const + { + return this->at(static_cast(nIdx)); + } + + CT* GetBuffer(int nMinLen=-1) + { + return GetBuf(nMinLen); + } + + CT* GetBufferSetLength(int nLen) + { + return BufferSet(nLen); + } + + // GetLength() -- MFC docs say this is the # of BYTES but + // in truth it is the number of CHARACTERs (chars or wchar_ts) + int GetLength() const + { + return static_cast(this->length()); + } + + int Insert(int nIdx, CT ch) + { + if ( static_cast(nIdx) > this->size()-1 ) + this->append(1, ch); + else + this->insert(static_cast(nIdx), 1, ch); + + return GetLength(); + } + int Insert(int nIdx, PCMYSTR sz) + { + if ( static_cast(nIdx) >= this->size() ) + this->append(sz, static_cast(sslen(sz))); + else + this->insert(static_cast(nIdx), sz); + + return GetLength(); + } + + bool IsEmpty() const + { + return this->empty(); + } + + MYTYPE Left(int nCount) const + { + // Range check the count. + + nCount = SSMAX(0, SSMIN(nCount, static_cast(this->size()))); + return this->substr(0, static_cast(nCount)); + } + +#ifndef SS_ANSI + bool LoadString(UINT nId) + { + return this->Load(nId); + } +#endif + + void MakeLower() + { + ToLower(); + } + + void MakeReverse() + { + std::reverse(this->begin(), this->end()); + } + + void MakeUpper() + { + ToUpper(); + } + + MYTYPE Mid(int nFirst) const + { + return Mid(nFirst, this->GetLength()-nFirst); + } + + MYTYPE Mid(int nFirst, int nCount) const + { + // CString does range checking here. Since we're trying to emulate it, + // we must check too. + + if ( nFirst < 0 ) + nFirst = 0; + if ( nCount < 0 ) + nCount = 0; + + int nSize = static_cast(this->size()); + + if ( nFirst + nCount > nSize ) + nCount = nSize - nFirst; + + if ( nFirst > nSize ) + return MYTYPE(); + + ASSERT(nFirst >= 0); + ASSERT(nFirst + nCount <= nSize); + + return this->substr(static_cast(nFirst), + static_cast(nCount)); + } + + void ReleaseBuffer(int nNewLen=-1) + { + RelBuf(nNewLen); + } + + int Remove(CT ch) + { + MYSIZE nIdx = 0; + int nRemoved = 0; + while ( (nIdx=this->find_first_of(ch)) != MYBASE::npos ) + { + this->erase(nIdx, 1); + nRemoved++; + } + return nRemoved; + } + + int Replace(CT chOld, CT chNew) + { + int nReplaced = 0; + + for ( MYITER iter=this->begin(); iter != this->end(); iter++ ) + { + if ( *iter == chOld ) + { + *iter = chNew; + nReplaced++; + } + } + + return nReplaced; + } + + int Replace(PCMYSTR szOld, PCMYSTR szNew) + { + int nReplaced = 0; + MYSIZE nIdx = 0; + MYSIZE nOldLen = sslen(szOld); + + if ( 0 != nOldLen ) + { + // If the replacement string is longer than the one it replaces, this + // string is going to have to grow in size, Figure out how much + // and grow it all the way now, rather than incrementally + + MYSIZE nNewLen = sslen(szNew); + if ( nNewLen > nOldLen ) + { + int nFound = 0; + while ( nIdx < this->length() && + (nIdx=this->find(szOld, nIdx)) != MYBASE::npos ) + { + nFound++; + nIdx += nOldLen; + } + this->reserve(this->size() + nFound * (nNewLen - nOldLen)); + } + + + static const CT ch = CT(0); + PCMYSTR szRealNew = szNew == 0 ? &ch : szNew; + nIdx = 0; + + while ( nIdx < this->length() && + (nIdx=this->find(szOld, nIdx)) != MYBASE::npos ) + { + this->replace(this->begin()+nIdx, this->begin()+nIdx+nOldLen, + szRealNew); + + nReplaced++; + nIdx += nNewLen; + } + } + + return nReplaced; + } + + int ReverseFind(CT ch) const + { + MYSIZE nIdx = this->find_last_of(ch); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + // ReverseFind overload that's not in CString but might be useful + int ReverseFind(PCMYSTR szFind, MYSIZE pos=MYBASE::npos) const + { + //yuvalt - this does not compile with g++ since MYTTYPE() is different type + //MYSIZE nIdx = this->rfind(0 == szFind ? MYTYPE() : szFind, pos); + MYSIZE nIdx = this->rfind(0 == szFind ? "" : szFind, pos); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + MYTYPE Right(int nCount) const + { + // Range check the count. + + nCount = SSMAX(0, SSMIN(nCount, static_cast(this->size()))); + return this->substr(this->size()-static_cast(nCount)); + } + + void SetAt(int nIndex, CT ch) + { + ASSERT(this->size() > static_cast(nIndex)); + this->at(static_cast(nIndex)) = ch; + } + +#ifndef SS_ANSI + BSTR SetSysString(BSTR* pbstr) const + { + ostring os; + ssasn(os, *this); + if ( !::SysReAllocStringLen(pbstr, os.c_str(), os.length()) ) + throw std::runtime_error("out of memory"); + + ASSERT(*pbstr != 0); + return *pbstr; + } +#endif + + MYTYPE SpanExcluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + + MYTYPE SpanIncluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_not_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + +#if defined SS_WIN32 && !defined(UNICODE) && !defined(SS_ANSI) + + // CString's OemToAnsi and AnsiToOem functions are available only in + // Unicode builds. However since we're a template we also need a + // runtime check of CT and a reinterpret_cast to account for the fact + // that CStdStringW gets instantiated even in non-Unicode builds. + + void AnsiToOem() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::CharToOem(reinterpret_cast(this->c_str()), + reinterpret_cast(GetBuf())); + } + else + { + ASSERT(false); + } + } + + void OemToAnsi() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::OemToChar(reinterpret_cast(this->c_str()), + reinterpret_cast(GetBuf())); + } + else + { + ASSERT(false); + } + } + +#endif + + + // ------------------------------------------------------------------------- + // Trim and its variants + // ------------------------------------------------------------------------- + MYTYPE& Trim() + { + return TrimLeft().TrimRight(); + } + + MYTYPE& TrimLeft() + { + this->erase(this->begin(), + std::find_if(this->begin(), this->end(), NotSpace())); + + return *this; + } + + MYTYPE& TrimLeft(CT tTrim) + { + this->erase(0, this->find_first_not_of(tTrim)); + return *this; + } + + MYTYPE& TrimLeft(PCMYSTR szTrimChars) + { + this->erase(0, this->find_first_not_of(szTrimChars)); + return *this; + } + + MYTYPE& TrimRight() + { + // NOTE: When comparing reverse_iterators here (MYRITER), I avoid using + // operator!=. This is because namespace rel_ops also has a template + // operator!= which conflicts with the global operator!= already defined + // for reverse_iterator in the header . + // Thanks to John James for alerting me to this. + + MYRITER it = std::find_if(this->rbegin(), this->rend(), NotSpace()); + if ( !(this->rend() == it) ) + this->erase(this->rend() - it); + + this->erase(!(it == this->rend()) ? this->find_last_of(*it) + 1 : 0); + return *this; + } + + MYTYPE& TrimRight(CT tTrim) + { + MYSIZE nIdx = this->find_last_not_of(tTrim); + this->erase(MYBASE::npos == nIdx ? 0 : ++nIdx); + return *this; + } + + MYTYPE& TrimRight(PCMYSTR szTrimChars) + { + MYSIZE nIdx = this->find_last_not_of(szTrimChars); + this->erase(MYBASE::npos == nIdx ? 0 : ++nIdx); + return *this; + } + + void FreeExtra() + { + MYTYPE mt; + this->swap(mt); + if ( !mt.empty() ) + this->assign(mt.c_str(), mt.size()); + } + + // I have intentionally not implemented the following CString + // functions. You cannot make them work without taking advantage + // of implementation specific behavior. However if you absolutely + // MUST have them, uncomment out these lines for "sort-of-like" + // their behavior. You're on your own. + +// CT* LockBuffer() { return GetBuf(); }// won't really lock +// void UnlockBuffer(); { } // why have UnlockBuffer w/o LockBuffer? + + // Array-indexing operators. Required because we defined an implicit cast + // to operator const CT* (Thanks to Julian Selman for pointing this out) + + CT& operator[](int nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](int nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + CT& operator[](unsigned int nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](unsigned int nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + CT& operator[](unsigned long nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](unsigned long nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + +#ifndef SS_NO_IMPLICIT_CAST + operator const CT*() const + { + return this->c_str(); + } +#endif + + // IStream related functions. Useful in IPersistStream implementations + +#ifdef SS_INC_COMDEF + + // struct SSSHDR - useful for non Std C++ persistence schemes. + typedef struct SSSHDR + { + BYTE byCtrl; + ULONG nChars; + } SSSHDR; // as in "Standard String Stream Header" + + #define SSSO_UNICODE 0x01 // the string is a wide string + #define SSSO_COMPRESS 0x02 // the string is compressed + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSize + // REMARKS: + // Returns how many bytes it will take to StreamSave() this CStdString + // object to an IStream. + // ------------------------------------------------------------------------- + ULONG StreamSize() const + { + // Control header plus string + ASSERT(this->size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + return (this->size() * sizeof(CT)) + sizeof(SSSHDR); + } + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSave + // REMARKS: + // Saves this CStdString object to a COM IStream. + // ------------------------------------------------------------------------- + HRESULT StreamSave(IStream* pStream) const + { + ASSERT(this->size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + HRESULT hr = E_FAIL; + ASSERT(pStream != 0); + SSSHDR hdr; + hdr.byCtrl = sizeof(CT) == 2 ? SSSO_UNICODE : 0; + hdr.nChars = this->size(); + + + if ( FAILED(hr=pStream->Write(&hdr, sizeof(SSSHDR), 0)) ) + { + TRACE(_T("StreamSave: Cannot write control header, ERR=0x%X\n"),hr); + } + else if ( empty() ) + { + ; // nothing to write + } + else if ( FAILED(hr=pStream->Write(this->c_str(), + this->size()*sizeof(CT), 0)) ) + { + TRACE(_T("StreamSave: Cannot write string to stream 0x%X\n"), hr); + } + + return hr; + } + + + // ------------------------------------------------------------------------- + // FUNCTION: StreamLoad + // REMARKS: + // This method loads the object from an IStream. + // ------------------------------------------------------------------------- + HRESULT StreamLoad(IStream* pStream) + { + ASSERT(pStream != 0); + SSSHDR hdr; + HRESULT hr = E_FAIL; + + if ( FAILED(hr=pStream->Read(&hdr, sizeof(SSSHDR), 0)) ) + { + TRACE(_T("StreamLoad: Cant read control header, ERR=0x%X\n"), hr); + } + else if ( hdr.nChars > 0 ) + { + ULONG nRead = 0; + PMYSTR pMyBuf = BufferSet(hdr.nChars); + + // If our character size matches the character size of the string + // we're trying to read, then we can read it directly into our + // buffer. Otherwise, we have to read into an intermediate buffer + // and convert. + + if ( (hdr.byCtrl & SSSO_UNICODE) != 0 ) + { + ULONG nBytes = hdr.nChars * sizeof(wchar_t); + if ( sizeof(CT) == sizeof(wchar_t) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PWSTR pBufW = reinterpret_cast(_alloca((nBytes)+1)); + if ( FAILED(hr=pStream->Read(pBufW, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufW, hdr.nChars); + } + } + else + { + ULONG nBytes = hdr.nChars * sizeof(char); + if ( sizeof(CT) == sizeof(char) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PSTR pBufA = reinterpret_cast(_alloca(nBytes)); + if ( FAILED(hr=pStream->Read(pBufA, hdr.nChars, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufA, hdr.nChars); + } + } + } + else + { + this->erase(); + } + return hr; + } +#endif // #ifdef SS_INC_COMDEF + +#ifndef SS_ANSI + + // SetResourceHandle/GetResourceHandle. In MFC builds, these map directly + // to AfxSetResourceHandle and AfxGetResourceHandle. In non-MFC builds they + // point to a single static HINST so that those who call the member + // functions that take resource IDs can provide an alternate HINST of a DLL + // to search. This is not exactly the list of HMODULES that MFC provides + // but it's better than nothing. + + #ifdef _MFC_VER + static void SetResourceHandle(HMODULE hNew) + { + AfxSetResourceHandle(hNew); + } + static HMODULE GetResourceHandle() + { + return AfxGetResourceHandle(); + } + #else + static void SetResourceHandle(HMODULE hNew) + { + SSResourceHandle() = hNew; + } + static HMODULE GetResourceHandle() + { + return SSResourceHandle(); + } + #endif + +#endif +}; + +// ----------------------------------------------------------------------------- +// MSVC USERS: HOW TO EXPORT CSTDSTRING FROM A DLL +// +// If you are using MS Visual C++ and you want to export CStdStringA and +// CStdStringW from a DLL, then all you need to +// +// 1. make sure that all components link to the same DLL version +// of the CRT (not the static one). +// 2. Uncomment the 3 lines of code below +// 3. #define 2 macros per the instructions in MS KnowledgeBase +// article Q168958. The macros are: +// +// MACRO DEFINTION WHEN EXPORTING DEFINITION WHEN IMPORTING +// ----- ------------------------ ------------------------- +// SSDLLEXP (nothing, just #define it) extern +// SSDLLSPEC __declspec(dllexport) __declspec(dllimport) +// +// Note that these macros must be available to ALL clients who want to +// link to the DLL and use the class. If they +// +// A word of advice: Don't bother. +// +// Really, it is not necessary to export CStdString functions from a DLL. I +// never do. In my projects, I do generally link to the DLL version of the +// Standard C++ Library, but I do NOT attempt to export CStdString functions. +// I simply include the header where it is needed and allow for the code +// redundancy. +// +// That redundancy is a lot less than you think. This class does most of its +// work via the Standard C++ Library, particularly the base_class basic_string<> +// member functions. Most of the functions here are small enough to be inlined +// anyway. Besides, you'll find that in actual practice you use less than 1/2 +// of the code here, even in big projects and different modules will use as +// little as 10% of it. That means a lot less functions actually get linked +// your binaries. If you export this code from a DLL, it ALL gets linked in. +// +// I've compared the size of the binaries from exporting vs NOT exporting. Take +// my word for it -- exporting this code is not worth the hassle. +// +// ----------------------------------------------------------------------------- +//#pragma warning(disable:4231) // non-standard extension ("extern template") +// SSDLLEXP template class SSDLLSPEC CStdStr; +// SSDLLEXP template class SSDLLSPEC CStdStr; + + +// ============================================================================= +// END OF CStdStr INLINE FUNCTION DEFINITIONS +// ============================================================================= + +// Now typedef our class names based upon this humongous template + +typedef CStdStr CStdStringA; // a better std::string +typedef CStdStr CStdStringW; // a better std::wstring +typedef CStdStr CStdString16; // a 16bit char string +typedef CStdStr CStdString32; // a 32bit char string +typedef CStdStr CStdStringO; // almost always CStdStringW + +// ----------------------------------------------------------------------------- +// CStdStr addition functions defined as inline +// ----------------------------------------------------------------------------- + + +inline CStdStringA operator+(const CStdStringA& s1, const CStdStringA& s2) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(s2); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, CStdStringA::value_type t) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(1, t); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, PCSTR pA) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(pA); + return sRet; +} +inline CStdStringA operator+(PCSTR pA, const CStdStringA& sA) +{ + CStdStringA sRet; + CStdStringA::size_type nObjSize = sA.size(); + CStdStringA::size_type nLitSize = + static_cast(sslen(pA)); + + sRet.reserve(nLitSize + nObjSize); + sRet.assign(pA); + sRet.append(sA); + return sRet; +} + + +inline CStdStringA operator+(const CStdStringA& s1, const CStdStringW& s2) +{ + return s1 + CStdStringA(s2); +} +inline CStdStringW operator+(const CStdStringW& s1, const CStdStringW& s2) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(s2); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, PCWSTR pW) +{ + return s1 + CStdStringA(pW); +} + +#ifdef UNICODE + inline CStdStringW operator+(PCWSTR pW, const CStdStringA& sA) + { + return CStdStringW(pW) + CStdStringW(SSREF(sA)); + } + inline CStdStringW operator+(PCSTR pA, const CStdStringW& sW) + { + return CStdStringW(pA) + sW; + } +#else + inline CStdStringA operator+(PCWSTR pW, const CStdStringA& sA) + { + return CStdStringA(pW) + sA; + } + inline CStdStringA operator+(PCSTR pA, const CStdStringW& sW) + { + return pA + CStdStringA(sW); + } +#endif + +// ...Now the wide string versions. +inline CStdStringW operator+(const CStdStringW& s1, CStdStringW::value_type t) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(1, t); + return sRet; +} +inline CStdStringW operator+(const CStdStringW& s1, PCWSTR pW) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(pW); + return sRet; +} +inline CStdStringW operator+(PCWSTR pW, const CStdStringW& sW) +{ + CStdStringW sRet; + CStdStringW::size_type nObjSize = sW.size(); + CStdStringA::size_type nLitSize = + static_cast(sslen(pW)); + + sRet.reserve(nLitSize + nObjSize); + sRet.assign(pW); + sRet.append(sW); + return sRet; +} + +inline CStdStringW operator+(const CStdStringW& s1, const CStdStringA& s2) +{ + return s1 + CStdStringW(s2); +} +inline CStdStringW operator+(const CStdStringW& s1, PCSTR pA) +{ + return s1 + CStdStringW(pA); +} + + +// New-style format function is a template + +#ifdef SS_SAFE_FORMAT + +template<> +struct FmtArg +{ + explicit FmtArg(const CStdStringA& arg) : a_(arg) {} + PCSTR operator()() const { return a_.c_str(); } + const CStdStringA& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; +template<> +struct FmtArg +{ + explicit FmtArg(const CStdStringW& arg) : a_(arg) {} + PCWSTR operator()() const { return a_.c_str(); } + const CStdStringW& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; + +template<> +struct FmtArg +{ + explicit FmtArg(const std::string& arg) : a_(arg) {} + PCSTR operator()() const { return a_.c_str(); } + const std::string& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; +template<> +struct FmtArg +{ + explicit FmtArg(const std::wstring& arg) : a_(arg) {} + PCWSTR operator()() const { return a_.c_str(); } + const std::wstring& a_; +private: + FmtArg& operator=(const FmtArg&) {return *this;} +}; +#endif // #ifdef SS_SAFEFORMAT + +#ifndef SS_ANSI + // SSResourceHandle: our MFC-like resource handle + inline HMODULE& SSResourceHandle() + { + static HMODULE hModuleSS = GetModuleHandle(0); + return hModuleSS; + } +#endif + + +// In MFC builds, define some global serialization operators +// Special operators that allow us to serialize CStdStrings to CArchives. +// Note that we use an intermediate CString object in order to ensure that +// we use the exact same format. + +#ifdef _MFC_VER + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringA& strA) + { + CString strTemp = strA; + return ar << strTemp; + } + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringW& strW) + { + CString strTemp = strW; + return ar << strTemp; + } + + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringA& strA) + { + CString strTemp; + ar >> strTemp; + strA = strTemp; + return ar; + } + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringW& strW) + { + CString strTemp; + ar >> strTemp; + strW = strTemp; + return ar; + } +#endif // #ifdef _MFC_VER -- (i.e. is this MFC?) + + + +// ----------------------------------------------------------------------------- +// GLOBAL FUNCTION: WUFormat +// CStdStringA WUFormat(UINT nId, ...); +// CStdStringA WUFormat(PCSTR szFormat, ...); +// +// REMARKS: +// This function allows the caller for format and return a CStdStringA +// object with a single line of code. +// ----------------------------------------------------------------------------- +#ifdef SS_ANSI +#else + inline CStdStringA WUFormatA(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + CStdStringA strFmt; + CStdStringA strOut; + if ( strFmt.Load(nId) ) + strOut.FormatV(strFmt, argList); + + va_end(argList); + return strOut; + } + inline CStdStringA WUFormatA(PCSTR szFormat, ...) + { + va_list argList; + va_start(argList, szFormat); + CStdStringA strOut; + strOut.FormatV(szFormat, argList); + va_end(argList); + return strOut; + } + inline CStdStringW WUFormatW(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + CStdStringW strFmt; + CStdStringW strOut; + if ( strFmt.Load(nId) ) + strOut.FormatV(strFmt, argList); + + va_end(argList); + return strOut; + } + inline CStdStringW WUFormatW(PCWSTR szwFormat, ...) + { + va_list argList; + va_start(argList, szwFormat); + CStdStringW strOut; + strOut.FormatV(szwFormat, argList); + va_end(argList); + return strOut; + } +#endif // #ifdef SS_ANSI + + + +#if defined(SS_WIN32) && !defined (SS_ANSI) + // ------------------------------------------------------------------------- + // FUNCTION: WUSysMessage + // CStdStringA WUSysMessageA(DWORD dwError, DWORD dwLangId=SS_DEFLANGID); + // CStdStringW WUSysMessageW(DWORD dwError, DWORD dwLangId=SS_DEFLANGID); + // + // DESCRIPTION: + // This function simplifies the process of obtaining a string equivalent + // of a system error code returned from GetLastError(). You simply + // supply the value returned by GetLastError() to this function and the + // corresponding system string is returned in the form of a CStdStringA. + // + // PARAMETERS: + // dwError - a DWORD value representing the error code to be translated + // dwLangId - the language id to use. defaults to english. + // + // RETURN VALUE: + // a CStdStringA equivalent of the error code. Currently, this function + // only returns either English of the system default language strings. + // ------------------------------------------------------------------------- + #define SS_DEFLANGID MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT) + inline CStdStringA WUSysMessageA(DWORD dwError, DWORD dwLangId=SS_DEFLANGID) + { + CHAR szBuf[512]; + + if ( 0 != ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, + dwLangId, szBuf, 511, NULL) ) + return WUFormatA("%s (0x%X)", szBuf, dwError); + else + return WUFormatA("Unknown error (0x%X)", dwError); + } + inline CStdStringW WUSysMessageW(DWORD dwError, DWORD dwLangId=SS_DEFLANGID) + { + WCHAR szBuf[512]; + + if ( 0 != ::FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, + dwLangId, szBuf, 511, NULL) ) + return WUFormatW(L"%s (0x%X)", szBuf, dwError); + else + return WUFormatW(L"Unknown error (0x%X)", dwError); + } +#endif + +// Define TCHAR based friendly names for some of these functions + +#ifdef UNICODE + //#define CStdString CStdStringW + typedef CStdStringW CStdString; + #define WUSysMessage WUSysMessageW + #define WUFormat WUFormatW +#else + //#define CStdString CStdStringA + typedef CStdStringA CStdString; + #define WUSysMessage WUSysMessageA + #define WUFormat WUFormatA +#endif + +// ...and some shorter names for the space-efficient + +#define WUSysMsg WUSysMessage +#define WUSysMsgA WUSysMessageA +#define WUSysMsgW WUSysMessageW +#define WUFmtA WUFormatA +#define WUFmtW WUFormatW +#define WUFmt WUFormat +#define WULastErrMsg() WUSysMessage(::GetLastError()) +#define WULastErrMsgA() WUSysMessageA(::GetLastError()) +#define WULastErrMsgW() WUSysMessageW(::GetLastError()) + + +// ----------------------------------------------------------------------------- +// FUNCTIONAL COMPARATORS: +// REMARKS: +// These structs are derived from the std::binary_function template. They +// give us functional classes (which may be used in Standard C++ Library +// collections and algorithms) that perform case-insensitive comparisons of +// CStdString objects. This is useful for maps in which the key may be the +// proper string but in the wrong case. +// ----------------------------------------------------------------------------- +#define StdStringLessNoCaseW SSLNCW // avoid VC compiler warning 4786 +#define StdStringEqualsNoCaseW SSENCW +#define StdStringLessNoCaseA SSLNCA +#define StdStringEqualsNoCaseA SSENCA + +#ifdef UNICODE + #define StdStringLessNoCase SSLNCW + #define StdStringEqualsNoCase SSENCW +#else + #define StdStringLessNoCase SSLNCA + #define StdStringEqualsNoCase SSENCA +#endif + +struct StdStringLessNoCaseW + : std::binary_function +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseW + : std::binary_function +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; +struct StdStringLessNoCaseA + : std::binary_function +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseA + : std::binary_function +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; + +// If we had to define our own version of TRACE above, get rid of it now + +#ifdef TRACE_DEFINED_HERE + #undef TRACE + #undef TRACE_DEFINED_HERE +#endif + + +// These std::swap specializations come courtesy of Mike Crusader. + +//namespace std +//{ +// inline void swap(CStdStringA& s1, CStdStringA& s2) throw() +// { +// s1.swap(s2); +// } +// template<> +// inline void swap(CStdStringW& s1, CStdStringW& s2) throw() +// { +// s1.swap(s2); +// } +//} + +// Turn back on any Borland warnings we turned off. + +#ifdef __BORLANDC__ + #pragma option pop // Turn back on inline function warnings +// #pragma warn +inl // Turn back on inline function warnings +#endif + +#endif // #ifndef STDSTRING_H diff --git a/xbmc/pvrclients/tvheadend/client.cpp b/xbmc/pvrclients/tvheadend/client.cpp new file mode 100644 index 0000000000..314132b7cc --- /dev/null +++ b/xbmc/pvrclients/tvheadend/client.cpp @@ -0,0 +1,521 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "client.h" +#include "xbmc_pvr_dll.h" +#include "HTSPData.h" +#include "HTSPDemux.h" + +using namespace std; + +//cPVRClientTvheadend *g_client = NULL; +bool m_bCreated = false; +ADDON_STATUS m_CurStatus = STATUS_UNKNOWN; +int g_clientID = -1; + +/* User adjustable settings are saved here. + * Default values are defined inside client.h + * and exported to the other source files. + */ +CStdString g_szHostname = DEFAULT_HOST; +int g_iPortHTSP = DEFAULT_HTSP_PORT; +int g_iPortHTTP = DEFAULT_HTTP_PORT; +int g_iConnectTimout = DEFAULT_TIMEOUT; +bool g_bSkipIFrame = DEFAULT_SKIP_I_FRAME; +int g_iEpgOffsetCorrection = DEFAULT_EPG_OFFSET_CORRECTION; +CStdString g_szUsername = ""; +CStdString g_szPassword = ""; +CStdString g_szUserPath = ""; +CStdString g_szClientPath = ""; +cHelper_libXBMC_addon *XBMC = NULL; +cHelper_libXBMC_pvr *PVR = NULL; +cHTSPDemux *HTSPDemuxer = NULL; +cHTSPData *HTSPData = NULL; + +extern "C" { + +/*********************************************************** + * Standart AddOn related public library functions + ***********************************************************/ + +ADDON_STATUS Create(void* hdl, void* props) +{ + if (!hdl || !props) + return STATUS_UNKNOWN; + + PVR_PROPS* pvrprops = (PVR_PROPS*)props; + + XBMC = new cHelper_libXBMC_addon; + if (!XBMC->RegisterMe(hdl)) + return STATUS_UNKNOWN; + + PVR = new cHelper_libXBMC_pvr; + if (!PVR->RegisterMe(hdl)) + return STATUS_UNKNOWN; + + XBMC->Log(LOG_DEBUG, "Creating Tvheadend PVR-Client"); + + m_CurStatus = STATUS_UNKNOWN; + g_clientID = pvrprops->clientID; + g_szUserPath = pvrprops->userpath; + g_szClientPath = pvrprops->clientpath; + + /* Read setting "host" from settings.xml */ + char * buffer; + buffer = (char*) malloc (1024); + buffer[0] = 0; /* Set the end of string */ + + if (XBMC->GetSetting("host", buffer)) + g_szHostname = buffer; + else + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'host' setting, falling back to '%s' as default", DEFAULT_HOST); + g_szHostname = DEFAULT_HOST; + } + buffer[0] = 0; /* Set the end of string */ + + if (XBMC->GetSetting("user", buffer)) + g_szUsername = buffer; + else + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'user' setting"); + g_szUsername = ""; + } + buffer[0] = 0; /* Set the end of string */ + + if (XBMC->GetSetting("pass", buffer)) + g_szPassword = buffer; + else + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'pass' setting"); + g_szPassword = ""; + } + free (buffer); + + /* Read setting "port" from settings.xml */ + if (!XBMC->GetSetting("htsp_port", &g_iPortHTSP)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'htsp_port' setting, falling back to '%i' as default", DEFAULT_HTSP_PORT); + g_iPortHTSP = DEFAULT_HTSP_PORT; + } + + /* Read setting "port" from settings.xml */ + if (!XBMC->GetSetting("http_port", &g_iPortHTTP)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'http_port' setting, falling back to '%i' as default", DEFAULT_HTTP_PORT); + g_iPortHTTP = DEFAULT_HTTP_PORT; + } + + /* Read setting "skip_I_frame" from settings.xml */ + if (!XBMC->GetSetting("skip_I_frame", &g_bSkipIFrame)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'skip_I_frame' setting, falling back to 'true' as default"); + g_bSkipIFrame = DEFAULT_SKIP_I_FRAME; + } + + /* Read setting "epg_offset_correction" from settings.xml */ + if (!XBMC->GetSetting("epg_offset_correction", &g_iEpgOffsetCorrection)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'epg_offset_correction' setting, falling back to '%i' as default", DEFAULT_EPG_OFFSET_CORRECTION); + g_iEpgOffsetCorrection = DEFAULT_EPG_OFFSET_CORRECTION; + }else + { + g_iEpgOffsetCorrection -= 12; + } + + HTSPData = new cHTSPData; + if (!HTSPData->Open(g_szHostname, g_iPortHTSP, g_szUsername, g_szPassword, g_iConnectTimout)) + { + m_CurStatus = STATUS_LOST_CONNECTION; + return m_CurStatus; + } + + m_CurStatus = STATUS_OK; + m_bCreated = true; + return m_CurStatus; +} + +ADDON_STATUS GetStatus() +{ + return m_CurStatus; +} + +void Destroy() +{ + if (m_bCreated) + { + delete HTSPData; + HTSPData = NULL; + } + m_CurStatus = STATUS_UNKNOWN; +} + +bool HasSettings() +{ + return true; +} + +unsigned int GetSettings(StructSetting ***sSet) +{ + return 0; +} + +ADDON_STATUS SetSetting(const char *settingName, const void *settingValue) +{ + string str = settingName; + if (str == "host") + { + string tmp_sHostname; + XBMC->Log(LOG_INFO, "Changed Setting 'host' from %s to %s", g_szHostname.c_str(), (const char*) settingValue); + tmp_sHostname = g_szHostname; + g_szHostname = (const char*) settingValue; + if (tmp_sHostname != g_szHostname) + return STATUS_NEED_RESTART; + } + else if (str == "user") + { + XBMC->Log(LOG_INFO, "Changed Setting 'user'"); + string tmp_sUsername = g_szUsername; + g_szUsername = (const char*) settingValue; + if (tmp_sUsername != g_szUsername) + return STATUS_NEED_RESTART; + } + else if (str == "pass") + { + XBMC->Log(LOG_INFO, "Changed Setting 'pass'"); + string tmp_sPassword = g_szPassword; + g_szPassword = (const char*) settingValue; + if (tmp_sPassword != g_szPassword) + return STATUS_NEED_RESTART; + } + else if (str == "htsp_port") + { + XBMC->Log(LOG_INFO, "Changed Setting 'port' from %u to %u", g_iPortHTSP, *(int*) settingValue); + if (g_iPortHTSP != *(int*) settingValue) + { + g_iPortHTSP = *(int*) settingValue; + return STATUS_NEED_RESTART; + } + } + else if (str == "http_port") + { + XBMC->Log(LOG_INFO, "Changed Setting 'port' from %u to %u", g_iPortHTTP, *(int*) settingValue); + if (g_iPortHTTP != *(int*) settingValue) + { + g_iPortHTTP = *(int*) settingValue; + return STATUS_NEED_RESTART; + } + } + else if (str == "skip_I_frame") + { + XBMC->Log(LOG_INFO, "Changed Setting 'skip_I_frame' from %u to %u", g_bSkipIFrame, *(bool*) settingValue); + if (g_bSkipIFrame != *(bool*) settingValue) + { + g_bSkipIFrame = *(bool*) settingValue; + return STATUS_OK; + } + } + else if (str == "epg_offset_correction") + { + XBMC->Log(LOG_INFO, "Changed Setting 'epg_offset_correction' from %d to %d", g_iEpgOffsetCorrection, *(int*) settingValue); + if (g_iEpgOffsetCorrection != *(int*) settingValue - 12) + { + g_iEpgOffsetCorrection = *(int*) settingValue - 12; + return STATUS_NEED_RESTART; + } + } + + return STATUS_OK; +} + +void Stop() +{ +} + +void FreeSettings() +{ + +} + +/*********************************************************** + * PVR Client AddOn specific public library functions + ***********************************************************/ + +PVR_ERROR GetProperties(PVR_SERVERPROPS* props) +{ + props->SupportChannelLogo = false; + props->SupportTimeShift = false; + props->SupportEPG = true; + props->SupportRecordings = true; + props->SupportTimers = true; + props->SupportTV = true; + props->SupportRadio = true; + props->SupportChannelSettings = false; + props->SupportDirector = false; + props->SupportBouquets = false; + props->HandleInputStream = true; + props->HandleDemuxing = true; + props->SupportChannelScan = false; + + return PVR_ERROR_NO_ERROR; +} + +const char * GetBackendName() +{ + static CStdString BackendName = HTSPData ? HTSPData->GetServerName() : "unknown"; + return BackendName.c_str(); +} + +const char * GetBackendVersion() +{ + static CStdString BackendVersion; + if (HTSPData) + BackendVersion.Format("%s (Protocol: %i)", HTSPData->GetVersion(), HTSPData->GetProtocol()); + return BackendVersion.c_str(); +} + +const char * GetConnectionString() +{ + static CStdString ConnectionString; + if (HTSPData) + ConnectionString.Format("%s:%i%s", g_szHostname.c_str(), g_iPortHTSP, HTSPData->CheckConnection() ? "" : " (Not connected!)"); + else + ConnectionString.Format("%s:%i (addon error!)", g_szHostname.c_str(), g_iPortHTSP); + return ConnectionString.c_str(); +} + +PVR_ERROR GetDriveSpace(long long *total, long long *used) +{ + if (HTSPData && HTSPData->GetDriveSpace(total, used)) + return PVR_ERROR_NO_ERROR; + + return PVR_ERROR_SERVER_ERROR; +} + +PVR_ERROR GetBackendTime(time_t *localTime, int *gmtOffset) +{ + if (HTSPData && HTSPData->GetTime(localTime, gmtOffset)) + return PVR_ERROR_NO_ERROR; + + return PVR_ERROR_SERVER_ERROR; +} + + +/*******************************************/ +/** PVR EPG Functions **/ + +PVR_ERROR RequestEPGForChannel(PVRHANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end) +{ + if (!HTSPData) + return PVR_ERROR_SERVER_ERROR; + + return HTSPData->RequestEPGForChannel(handle, channel, start, end); +} + + +/*******************************************/ +/** PVR Channel Functions **/ + +int GetNumChannels() +{ + if (!HTSPData) + return 0; + + return HTSPData->GetNumChannels(); +} + +PVR_ERROR RequestChannelList(PVRHANDLE handle, int radio) +{ + if (!HTSPData) + return PVR_ERROR_SERVER_ERROR; + + return HTSPData->RequestChannelList(handle, radio); +} + +/*******************************************/ +/** PVR Live Stream Functions **/ + +bool OpenLiveStream(const PVR_CHANNEL &channelinfo) +{ + CloseLiveStream(); + + HTSPDemuxer = new cHTSPDemux; + return HTSPDemuxer->Open(channelinfo); +} + +void CloseLiveStream() +{ + if (HTSPDemuxer) + { + HTSPDemuxer->Close(); + delete HTSPDemuxer; + HTSPDemuxer = NULL; + } +} + +PVR_ERROR GetStreamProperties(PVR_STREAMPROPS* props) +{ + if (HTSPDemuxer && HTSPDemuxer->GetStreamProperties(props)) + return PVR_ERROR_NO_ERROR; + + return PVR_ERROR_SERVER_ERROR; +} + +void DemuxAbort() +{ + if (HTSPDemuxer) HTSPDemuxer->Abort(); +} + +DemuxPacket* DemuxRead() +{ + return HTSPDemuxer->Read(); +} + +int GetCurrentClientChannel() +{ + if (HTSPDemuxer) + return HTSPDemuxer->CurrentChannel(); + + return -1; +} + +bool SwitchChannel(const PVR_CHANNEL &channelinfo) +{ + if (HTSPDemuxer) + return HTSPDemuxer->SwitchChannel(channelinfo); + + return false; +} + +PVR_ERROR SignalQuality(PVR_SIGNALQUALITY &qualityinfo) +{ + if (HTSPDemuxer && HTSPDemuxer->GetSignalStatus(qualityinfo)) + return PVR_ERROR_NO_ERROR; + + return PVR_ERROR_SERVER_ERROR; +} + +/*******************************************/ +/** PVR Recording Functions **/ + +PVR_ERROR RequestRecordingsList(PVRHANDLE handle) +{ + if (!HTSPData) + return PVR_ERROR_SERVER_ERROR; + + return HTSPData->RequestRecordingsList(handle); +} + +int GetNumRecordings(void) +{ + if (!HTSPData) + return PVR_ERROR_SERVER_ERROR; + + return HTSPData->GetNumRecordings(); +} + +PVR_ERROR DeleteRecording(const PVR_RECORDINGINFO &recinfo) +{ + if (!HTSPData) + return PVR_ERROR_SERVER_ERROR; + + return HTSPData->DeleteRecording(recinfo); +} + +/*******************************************/ +/** PVR Timer Functions **/ + +int GetNumTimers(void) +{ + if (!HTSPData) + return PVR_ERROR_SERVER_ERROR; + + return HTSPData->GetNumTimers(); +} + +PVR_ERROR RequestTimerList(PVRHANDLE handle) +{ + if (!HTSPData) + return PVR_ERROR_SERVER_ERROR; + + return HTSPData->RequestTimerList(handle); +} + +PVR_ERROR DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force) +{ + if (!HTSPData) + return PVR_ERROR_SERVER_ERROR; + + return HTSPData->DeleteTimer(timerinfo, force); +} + +PVR_ERROR AddTimer(const PVR_TIMERINFO &timerinfo) +{ + if (!HTSPData) + return PVR_ERROR_SERVER_ERROR; + + return HTSPData->AddTimer(timerinfo); +} + +/** UNUSED API FUNCTIONS */ +PVR_ERROR DialogChannelScan() { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR MenuHook(const PVR_MENUHOOK &menuhook) { return PVR_ERROR_NOT_IMPLEMENTED; } +int GetNumBouquets() { return 0; } +PVR_ERROR RequestBouquetsList(PVRHANDLE handle, int radio) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR DeleteChannel(unsigned int number) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR RenameChannel(unsigned int number, const char *newname) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR MoveChannel(unsigned int number, unsigned int newnumber) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR DialogChannelSettings(const PVR_CHANNEL &channelinfo) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR DialogAddChannel(const PVR_CHANNEL &channelinfo) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR RenameRecording(const PVR_RECORDINGINFO &recinfo, const char *newname) { return PVR_ERROR_NOT_IMPLEMENTED; } +bool HaveCutmarks() { return false; } +PVR_ERROR RequestCutMarksList(PVRHANDLE handle) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR AddCutMark(const PVR_CUT_MARK &cutmark) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR DeleteCutMark(const PVR_CUT_MARK &cutmark) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR StartCut() { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR RenameTimer(const PVR_TIMERINFO &timerinfo, const char *newname) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR UpdateTimer(const PVR_TIMERINFO &timerinfo) { return PVR_ERROR_NOT_IMPLEMENTED; } +bool SwapLiveTVSecondaryStream() { return false; } +bool OpenSecondaryStream(const PVR_CHANNEL &channelinfo) { return false; } +void CloseSecondaryStream() {} +int ReadSecondaryStream(unsigned char* buf, int buf_size) { return 0; } +bool OpenRecordedStream(const PVR_RECORDINGINFO &recinfo) { return false; } +void CloseRecordedStream(void) {} +int ReadRecordedStream(unsigned char* buf, int buf_size) { return 0; } +long long SeekRecordedStream(long long pos, int whence) { return 0; } +long long PositionRecordedStream(void) { return -1; } +long long LengthRecordedStream(void) { return 0; } +void DemuxReset(){} +void DemuxFlush(){} +int ReadLiveStream(unsigned char* buf, int buf_size) { return 0; } +long long SeekLiveStream(long long pos, int whence) { return -1; } +long long PositionLiveStream(void) { return -1; } +long long LengthLiveStream(void) { return -1; } +const char * GetLiveStreamURL(const PVR_CHANNEL &channelinfo) { return ""; } + +} diff --git a/xbmc/pvrclients/tvheadend/client.h b/xbmc/pvrclients/tvheadend/client.h new file mode 100644 index 0000000000..c8d14d691b --- /dev/null +++ b/xbmc/pvrclients/tvheadend/client.h @@ -0,0 +1,52 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef CLIENT_H +#define CLIENT_H + +#include "StdString.h" +#include "../../../addons/library.xbmc.addon/libXBMC_addon.h" +#include "../../../addons/library.xbmc.pvr/libXBMC_pvr.h" + +#define DEFAULT_HOST "127.0.0.1" +#define DEFAULT_HTTP_PORT 9981 +#define DEFAULT_HTSP_PORT 9982 +#define DEFAULT_TIMEOUT 30000 +#define DEFAULT_SKIP_I_FRAME true +#define DEFAULT_EPG_OFFSET_CORRECTION 0 + +extern bool m_bCreated; +extern CStdString g_szHostname; +extern int g_iPortHTSP; +extern int g_iPortHTTP; +extern CStdString g_szUsername; +extern CStdString g_szPassword; +extern int g_iConnectTimout; +extern bool g_bSkipIFrame; +extern int g_iEpgOffsetCorrection; +extern int g_clientID; +extern CStdString g_szUserPath; +extern CStdString g_szClientPath; +extern cHelper_libXBMC_addon *XBMC; +extern cHelper_libXBMC_pvr *PVR; + +#endif /* CLIENT_H */ diff --git a/xbmc/pvrclients/tvheadend/linux/os_posix.h b/xbmc/pvrclients/tvheadend/linux/os_posix.h new file mode 100644 index 0000000000..0adf70d2dc --- /dev/null +++ b/xbmc/pvrclients/tvheadend/linux/os_posix.h @@ -0,0 +1,93 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef PVRCLIENT_VDR_OS_POSIX_H +#define PVRCLIENT_VDR_OS_POSIX_H + +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef __APPLE__ +#include +#endif +#include +#include +#include +#include +#include + +typedef int bool_t; +typedef int SOCKET; + +#ifndef closesocket +#define closesocket(a) close(a) +#endif + +#define SOCKET_ERROR (-1) +#define INVALID_SOCKET (-1) +#define SD_BOTH SHUT_RDWR + +#define LIBTYPE +#define sock_getlasterror errno +#define sock_getlasterror_socktimeout (errno == EAGAIN) +#define console_vprintf vprintf +#define console_printf printf +#define THREAD_FUNC_PREFIX void * + +#ifndef __STL_CONFIG_H +template inline T min(T a, T b) { return a <= b ? a : b; } +template inline T max(T a, T b) { return a >= b ? a : b; } +template inline int sgn(T a) { return a < 0 ? -1 : a > 0 ? 1 : 0; } +template inline void swap(T &a, T &b) { T t = a; a = b; b = t; } +#endif + +#define Sleep(t) usleep(t*1000) + +static inline uint64_t getcurrenttime(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + return ((uint64_t)t.tv_sec * 1000) + (t.tv_usec / 1000); +} + +static inline int setsocktimeout(int s, int level, int optname, uint64_t timeout) +{ + struct timeval t; + t.tv_sec = timeout / 1000; + t.tv_usec = (timeout % 1000) * 1000; + return setsockopt(s, level, optname, (char *)&t, sizeof(t)); +} + +#endif diff --git a/xbmc/pvrclients/tvheadend/patches/hts-tvheadend-2.10-AddSignalQuality.diff b/xbmc/pvrclients/tvheadend/patches/hts-tvheadend-2.10-AddSignalQuality.diff new file mode 100644 index 0000000000..9afe6077d0 --- /dev/null +++ b/xbmc/pvrclients/tvheadend/patches/hts-tvheadend-2.10-AddSignalQuality.diff @@ -0,0 +1,66 @@ +diff -NaurwB hts-tvheadend-2.10/src/htsp.c hts-tvheadend-2.10_patched/src/htsp.c +--- hts-tvheadend-2.10/src/htsp.c 2010-02-03 20:19:25.000000000 +0100 ++++ hts-tvheadend-2.10_patched/src/htsp.c 2010-03-18 23:42:59.000000000 +0100 +@@ -38,12 +38,14 @@ + #include "htsp.h" + #include "streaming.h" + #include "transports.h" ++#include "dvb/dvb.h" + + #include "htsmsg_binary.h" + + #include + #include "settings.h" + #include ++#include + + + static void *htsp_server; +@@ -1399,6 +1401,47 @@ + htsmsg_add_u32(m, "Pdrops", hs->hs_dropstats[PKT_P_FRAME]); + htsmsg_add_u32(m, "Idrops", hs->hs_dropstats[PKT_I_FRAME]); + ++ if (hs->hs_s) ++ { ++ th_transport_t *t = hs->hs_s->ths_transport; ++ if (t && t->tht_type == TRANSPORT_DVB && t->tht_dvb_mux_instance->tdmi_adapter) ++ { ++ th_dvb_adapter_t *tda = t->tht_dvb_mux_instance->tdmi_adapter; ++ if (tda->tda_fe_fd > 0) ++ { ++ struct dvb_frontend_info value; ++ fe_status_t status; ++ uint16_t fe_snr; ++ uint16_t fe_signal; ++ uint32_t fe_ber; ++ uint32_t fe_unc; ++ ++ memset(&value, 0, sizeof(value)); ++ memset(&status, 0, sizeof(status)); ++ ioctl(tda->tda_fe_fd, FE_GET_INFO, &value); ++ ioctl(tda->tda_fe_fd, FE_READ_STATUS, &status); ++ ++ if (ioctl(tda->tda_fe_fd, FE_READ_SIGNAL_STRENGTH, &fe_signal) == -1) ++ fe_signal = -2; ++ if (ioctl(tda->tda_fe_fd, FE_READ_SNR, &fe_snr) == -1) ++ fe_snr = -2; ++ if (ioctl(tda->tda_fe_fd, FE_READ_BER, &fe_ber) == -1) ++ fe_ber = -2; ++ if (ioctl(tda->tda_fe_fd, FE_READ_UNCORRECTED_BLOCKS, &fe_unc) == -1) ++ fe_unc = -2; ++ ++ char buf[2048]; ++ snprintf(buf, sizeof(buf), "%s:%s:%s:%s:%s", (status & FE_HAS_LOCK) ? "LOCKED" : "-", (status & FE_HAS_SIGNAL) ? "SIGNAL" : "-", (status & FE_HAS_CARRIER) ? "CARRIER" : "-", (status & FE_HAS_VITERBI) ? "VITERBI" : "-", (status & FE_HAS_SYNC) ? "SYNC" : "-"); ++ htsmsg_add_str(m, "feStatus", buf); ++ htsmsg_add_str(m, "feName", value.name); ++ htsmsg_add_u32(m, "feSNR", fe_snr); ++ htsmsg_add_u32(m, "feSignal", fe_signal); ++ htsmsg_add_u32(m, "feBER", fe_ber); ++ htsmsg_add_u32(m, "feUNC", fe_unc); ++ } ++ } ++ } ++ + /* We use a special queue for queue status message so they're not + blocked by anything else */ + diff --git a/xbmc/pvrclients/tvheadend/patches/hts-tvheadend-2.11-01-add-CAID-to-channel-in-HTSP.patch b/xbmc/pvrclients/tvheadend/patches/hts-tvheadend-2.11-01-add-CAID-to-channel-in-HTSP.patch new file mode 100644 index 0000000000..6f12095cf3 --- /dev/null +++ b/xbmc/pvrclients/tvheadend/patches/hts-tvheadend-2.11-01-add-CAID-to-channel-in-HTSP.patch @@ -0,0 +1,108 @@ +From fee41526769ee0331615add141c78a14469d1d49 Mon Sep 17 00:00:00 2001 +From: Lars Op den Kamp +Date: Wed, 10 Nov 2010 12:28:54 +0100 +Subject: [PATCH] add CA id and name to the channel entry in htsp + +--- + src/htsp.c | 7 +++++++ + src/htsp.h | 1 + + src/transports.c | 27 +++++++++++++++++++++++++++ + src/transports.h | 2 ++ + 4 files changed, 37 insertions(+), 0 deletions(-) + +diff --git a/src/htsp.c b/src/htsp.c +index bc09946..6798c91 100644 +--- a/src/htsp.c ++++ b/src/htsp.c +@@ -296,6 +296,7 @@ htsp_build_channel(channel_t *ch, const char *method) + channel_tag_mapping_t *ctm; + channel_tag_t *ct; + th_transport_t *t; ++ uint16_t caid = 0; + + htsmsg_t *out = htsmsg_create_map(); + htsmsg_t *tags = htsmsg_create_list(); +@@ -322,11 +323,17 @@ htsp_build_channel(channel_t *ch, const char *method) + htsmsg_add_str(svcmsg, "name", transport_nicename(t)); + htsmsg_add_str(svcmsg, "type", transport_servicetype_txt(t)); + htsmsg_add_msg(services, NULL, svcmsg); ++ ++ if (caid == 0) { ++ caid = transport_get_encryption(t); ++ } + } + + htsmsg_add_msg(out, "services", services); + htsmsg_add_msg(out, "tags", tags); + htsmsg_add_str(out, "method", method); ++ htsmsg_add_u32(out, "channelCaId", caid); ++ htsmsg_add_str(out, "channelCaName", psi_caid2name(caid)); + return out; + } + +diff --git a/src/htsp.h b/src/htsp.h +index db6ca8d..367e946 100644 +--- a/src/htsp.h ++++ b/src/htsp.h +@@ -21,6 +21,7 @@ + + #include "epg.h" + #include "dvr/dvr.h" ++#include "psi.h" + + void htsp_init(void); + +diff --git a/src/transports.c b/src/transports.c +index e6449e8..2b81e7a 100644 +--- a/src/transports.c ++++ b/src/transports.c +@@ -1027,6 +1027,33 @@ transport_refresh_channel(th_transport_t *t) + } + + ++/** ++ * Get the encryption CAID from a transport ++ * only the first CA stream in a transport is returned ++ */ ++uint16_t ++transport_get_encryption(th_transport_t *t) ++{ ++ th_stream_t *st; ++ caid_t *c; ++ ++ TAILQ_FOREACH(st, &t->tht_components, st_link) { ++ switch(st->st_type) { ++ case SCT_CA: ++ while((c = LIST_FIRST(&st->st_caids)) != NULL) { ++ if (c->caid) { ++ return c->caid; ++ } ++ } ++ break; ++ default: ++ break; ++ } ++ } ++ ++ return 0; ++} ++ + + /** + * Get the signal status from a transport +diff --git a/src/transports.h b/src/transports.h +index 71fe54f..ee5767a 100644 +--- a/src/transports.h ++++ b/src/transports.h +@@ -98,6 +98,8 @@ void transport_refresh_channel(th_transport_t *t); + + int tss2errcode(int tss); + ++uint16_t transport_get_encryption(th_transport_t *t); ++ + int transport_get_signal_status(th_transport_t *t, signal_status_t *status); + + #endif /* TRANSPORTS_H */ +-- +1.7.1 + diff --git a/xbmc/pvrclients/tvheadend/project/VS2008Express/XBMC_hts.vcproj b/xbmc/pvrclients/tvheadend/project/VS2008Express/XBMC_hts.vcproj new file mode 100644 index 0000000000..bae3c884c1 --- /dev/null +++ b/xbmc/pvrclients/tvheadend/project/VS2008Express/XBMC_hts.vcproj @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xbmc/pvrclients/tvheadend/pthread_win32/pthread.h b/xbmc/pvrclients/tvheadend/pthread_win32/pthread.h new file mode 100644 index 0000000000..9f2868bccb --- /dev/null +++ b/xbmc/pvrclients/tvheadend/pthread_win32/pthread.h @@ -0,0 +1,1368 @@ +/* This is an implementation of the threads API of POSIX 1003.1-2001. + * + * -------------------------------------------------------------------------- + * + * Pthreads-win32 - POSIX Threads Library for Win32 + * Copyright(C) 1998 John E. Bossom + * Copyright(C) 1999,2005 Pthreads-win32 contributors + * + * Contact Email: rpj@callisto.canberra.edu.au + * + * The current list of contributors is contained + * in the file CONTRIBUTORS included with the source + * code distribution. The list can also be seen at the + * following World Wide Web location: + * http://sources.redhat.com/pthreads-win32/contributors.html + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library in the file COPYING.LIB; + * if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#if !defined( PTHREAD_H ) +#define PTHREAD_H + +/* + * See the README file for an explanation of the pthreads-win32 version + * numbering scheme and how the DLL is named etc. + */ +#define PTW32_VERSION 2,8,0,0 +#define PTW32_VERSION_STRING "2, 8, 0, 0\0" + +/* There are three implementations of cancel cleanup. + * Note that pthread.h is included in both application + * compilation units and also internally for the library. + * The code here and within the library aims to work + * for all reasonable combinations of environments. + * + * The three implementations are: + * + * WIN32 SEH + * C + * C++ + * + * Please note that exiting a push/pop block via + * "return", "exit", "break", or "continue" will + * lead to different behaviour amongst applications + * depending upon whether the library was built + * using SEH, C++, or C. For example, a library built + * with SEH will call the cleanup routine, while both + * C++ and C built versions will not. + */ + +/* + * Define defaults for cleanup code. + * Note: Unless the build explicitly defines one of the following, then + * we default to standard C style cleanup. This style uses setjmp/longjmp + * in the cancelation and thread exit implementations and therefore won't + * do stack unwinding if linked to applications that have it (e.g. + * C++ apps). This is currently consistent with most/all commercial Unix + * POSIX threads implementations. + */ +#if !defined( __CLEANUP_SEH ) && !defined( __CLEANUP_CXX ) && !defined( __CLEANUP_C ) +# define __CLEANUP_C +#endif + +#if defined( __CLEANUP_SEH ) && ( !defined( _MSC_VER ) && !defined(PTW32_RC_MSC)) +#error ERROR [__FILE__, line __LINE__]: SEH is not supported for this compiler. +#endif + +/* + * Stop here if we are being included by the resource compiler. + */ +#ifndef RC_INVOKED + +#undef PTW32_LEVEL + +#if defined(_POSIX_SOURCE) +#define PTW32_LEVEL 0 +/* Early POSIX */ +#endif + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309 +#undef PTW32_LEVEL +#define PTW32_LEVEL 1 +/* Include 1b, 1c and 1d */ +#endif + +#if defined(INCLUDE_NP) +#undef PTW32_LEVEL +#define PTW32_LEVEL 2 +/* Include Non-Portable extensions */ +#endif + +#define PTW32_LEVEL_MAX 3 + +#if !defined(PTW32_LEVEL) +#define PTW32_LEVEL PTW32_LEVEL_MAX +/* Include everything */ +#endif + +#ifdef _UWIN +# define HAVE_STRUCT_TIMESPEC 1 +# define HAVE_SIGNAL_H 1 +# undef HAVE_CONFIG_H +# pragma comment(lib, "pthread") +#endif + +/* + * ------------------------------------------------------------- + * + * + * Module: pthread.h + * + * Purpose: + * Provides an implementation of PThreads based upon the + * standard: + * + * POSIX 1003.1-2001 + * and + * The Single Unix Specification version 3 + * + * (these two are equivalent) + * + * in order to enhance code portability between Windows, + * various commercial Unix implementations, and Linux. + * + * See the ANNOUNCE file for a full list of conforming + * routines and defined constants, and a list of missing + * routines and constants not defined in this implementation. + * + * Authors: + * There have been many contributors to this library. + * The initial implementation was contributed by + * John Bossom, and several others have provided major + * sections or revisions of parts of the implementation. + * Often significant effort has been contributed to + * find and fix important bugs and other problems to + * improve the reliability of the library, which sometimes + * is not reflected in the amount of code which changed as + * result. + * As much as possible, the contributors are acknowledged + * in the ChangeLog file in the source code distribution + * where their changes are noted in detail. + * + * Contributors are listed in the CONTRIBUTORS file. + * + * As usual, all bouquets go to the contributors, and all + * brickbats go to the project maintainer. + * + * Maintainer: + * The code base for this project is coordinated and + * eventually pre-tested, packaged, and made available by + * + * Ross Johnson + * + * QA Testers: + * Ultimately, the library is tested in the real world by + * a host of competent and demanding scientists and + * engineers who report bugs and/or provide solutions + * which are then fixed or incorporated into subsequent + * versions of the library. Each time a bug is fixed, a + * test case is written to prove the fix and ensure + * that later changes to the code don't reintroduce the + * same error. The number of test cases is slowly growing + * and therefore so is the code reliability. + * + * Compliance: + * See the file ANNOUNCE for the list of implemented + * and not-implemented routines and defined options. + * Of course, these are all defined is this file as well. + * + * Web site: + * The source code and other information about this library + * are available from + * + * http://sources.redhat.com/pthreads-win32/ + * + * ------------------------------------------------------------- + */ + +/* Try to avoid including windows.h */ +#if defined(__MINGW32__) && defined(__cplusplus) +#define PTW32_INCLUDE_WINDOWS_H +#endif + +#ifdef PTW32_INCLUDE_WINDOWS_H +#include +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1300 || defined(__DMC__) +/* + * VC++6.0 or early compiler's header has no DWORD_PTR type. + */ +typedef unsigned long DWORD_PTR; +#endif +/* + * ----------------- + * autoconf switches + * ----------------- + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#ifndef NEED_FTIME +#include +#else /* NEED_FTIME */ +/* use native WIN32 time API */ +#endif /* NEED_FTIME */ + +#if HAVE_SIGNAL_H +#include +#endif /* HAVE_SIGNAL_H */ + +#include +#include + +/* + * Boolean values to make us independent of system includes. + */ +enum { + PTW32_FALSE = 0, + PTW32_TRUE = (! PTW32_FALSE) +}; + +/* + * This is a duplicate of what is in the autoconf config.h, + * which is only used when building the pthread-win32 libraries. + */ + +#ifndef PTW32_CONFIG_H +# if defined(WINCE) +# define NEED_ERRNO +# define NEED_SEM +# endif +# if defined(_UWIN) || defined(__MINGW32__) +# define HAVE_MODE_T +# endif +#endif + +/* + * + */ + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX +#ifdef NEED_ERRNO +#include "need_errno.h" +#else +#include +#endif +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +/* + * Several systems don't define some error numbers. + */ +#ifndef ENOTSUP +# define ENOTSUP 48 /* This is the value in Solaris. */ +#endif + +#ifndef ETIMEDOUT +# define ETIMEDOUT 10060 /* This is the value in winsock.h. */ +#endif + +#ifndef ENOSYS +# define ENOSYS 140 /* Semi-arbitrary value */ +#endif + +#ifndef EDEADLK +# ifdef EDEADLOCK +# define EDEADLK EDEADLOCK +# else +# define EDEADLK 36 /* This is the value in MSVC. */ +# endif +#endif + +#include + +/* + * To avoid including windows.h we define only those things that we + * actually need from it. + */ +#ifndef PTW32_INCLUDE_WINDOWS_H +#ifndef HANDLE +# define PTW32__HANDLE_DEF +# define HANDLE void * +#endif +#ifndef DWORD +# define PTW32__DWORD_DEF +# define DWORD unsigned long +#endif +#endif + +#ifndef HAVE_STRUCT_TIMESPEC +#define HAVE_STRUCT_TIMESPEC 1 +struct timespec { + long tv_sec; + long tv_nsec; +}; +#endif /* HAVE_STRUCT_TIMESPEC */ + +#ifndef SIG_BLOCK +#define SIG_BLOCK 0 +#endif /* SIG_BLOCK */ + +#ifndef SIG_UNBLOCK +#define SIG_UNBLOCK 1 +#endif /* SIG_UNBLOCK */ + +#ifndef SIG_SETMASK +#define SIG_SETMASK 2 +#endif /* SIG_SETMASK */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * ------------------------------------------------------------- + * + * POSIX 1003.1-2001 Options + * ========================= + * + * Options are normally set in , which is not provided + * with pthreads-win32. + * + * For conformance with the Single Unix Specification (version 3), all of the + * options below are defined, and have a value of either -1 (not supported) + * or 200112L (supported). + * + * These options can neither be left undefined nor have a value of 0, because + * either indicates that sysconf(), which is not implemented, may be used at + * runtime to check the status of the option. + * + * _POSIX_THREADS (== 200112L) + * If == 200112L, you can use threads + * + * _POSIX_THREAD_ATTR_STACKSIZE (== 200112L) + * If == 200112L, you can control the size of a thread's + * stack + * pthread_attr_getstacksize + * pthread_attr_setstacksize + * + * _POSIX_THREAD_ATTR_STACKADDR (== -1) + * If == 200112L, you can allocate and control a thread's + * stack. If not supported, the following functions + * will return ENOSYS, indicating they are not + * supported: + * pthread_attr_getstackaddr + * pthread_attr_setstackaddr + * + * _POSIX_THREAD_PRIORITY_SCHEDULING (== -1) + * If == 200112L, you can use realtime scheduling. + * This option indicates that the behaviour of some + * implemented functions conforms to the additional TPS + * requirements in the standard. E.g. rwlocks favour + * writers over readers when threads have equal priority. + * + * _POSIX_THREAD_PRIO_INHERIT (== -1) + * If == 200112L, you can create priority inheritance + * mutexes. + * pthread_mutexattr_getprotocol + + * pthread_mutexattr_setprotocol + + * + * _POSIX_THREAD_PRIO_PROTECT (== -1) + * If == 200112L, you can create priority ceiling mutexes + * Indicates the availability of: + * pthread_mutex_getprioceiling + * pthread_mutex_setprioceiling + * pthread_mutexattr_getprioceiling + * pthread_mutexattr_getprotocol + + * pthread_mutexattr_setprioceiling + * pthread_mutexattr_setprotocol + + * + * _POSIX_THREAD_PROCESS_SHARED (== -1) + * If set, you can create mutexes and condition + * variables that can be shared with another + * process.If set, indicates the availability + * of: + * pthread_mutexattr_getpshared + * pthread_mutexattr_setpshared + * pthread_condattr_getpshared + * pthread_condattr_setpshared + * + * _POSIX_THREAD_SAFE_FUNCTIONS (== 200112L) + * If == 200112L you can use the special *_r library + * functions that provide thread-safe behaviour + * + * _POSIX_READER_WRITER_LOCKS (== 200112L) + * If == 200112L, you can use read/write locks + * + * _POSIX_SPIN_LOCKS (== 200112L) + * If == 200112L, you can use spin locks + * + * _POSIX_BARRIERS (== 200112L) + * If == 200112L, you can use barriers + * + * + These functions provide both 'inherit' and/or + * 'protect' protocol, based upon these macro + * settings. + * + * ------------------------------------------------------------- + */ + +/* + * POSIX Options + */ +#undef _POSIX_THREADS +#define _POSIX_THREADS 200112L + +#undef _POSIX_READER_WRITER_LOCKS +#define _POSIX_READER_WRITER_LOCKS 200112L + +#undef _POSIX_SPIN_LOCKS +#define _POSIX_SPIN_LOCKS 200112L + +#undef _POSIX_BARRIERS +#define _POSIX_BARRIERS 200112L + +#undef _POSIX_THREAD_SAFE_FUNCTIONS +#define _POSIX_THREAD_SAFE_FUNCTIONS 200112L + +#undef _POSIX_THREAD_ATTR_STACKSIZE +#define _POSIX_THREAD_ATTR_STACKSIZE 200112L + +/* + * The following options are not supported + */ +#undef _POSIX_THREAD_ATTR_STACKADDR +#define _POSIX_THREAD_ATTR_STACKADDR -1 + +#undef _POSIX_THREAD_PRIO_INHERIT +#define _POSIX_THREAD_PRIO_INHERIT -1 + +#undef _POSIX_THREAD_PRIO_PROTECT +#define _POSIX_THREAD_PRIO_PROTECT -1 + +/* TPS is not fully supported. */ +#undef _POSIX_THREAD_PRIORITY_SCHEDULING +#define _POSIX_THREAD_PRIORITY_SCHEDULING -1 + +#undef _POSIX_THREAD_PROCESS_SHARED +#define _POSIX_THREAD_PROCESS_SHARED -1 + + +/* + * POSIX 1003.1-2001 Limits + * =========================== + * + * These limits are normally set in , which is not provided with + * pthreads-win32. + * + * PTHREAD_DESTRUCTOR_ITERATIONS + * Maximum number of attempts to destroy + * a thread's thread-specific data on + * termination (must be at least 4) + * + * PTHREAD_KEYS_MAX + * Maximum number of thread-specific data keys + * available per process (must be at least 128) + * + * PTHREAD_STACK_MIN + * Minimum supported stack size for a thread + * + * PTHREAD_THREADS_MAX + * Maximum number of threads supported per + * process (must be at least 64). + * + * SEM_NSEMS_MAX + * The maximum number of semaphores a process can have. + * (must be at least 256) + * + * SEM_VALUE_MAX + * The maximum value a semaphore can have. + * (must be at least 32767) + * + */ +#undef _POSIX_THREAD_DESTRUCTOR_ITERATIONS +#define _POSIX_THREAD_DESTRUCTOR_ITERATIONS 4 + +#undef PTHREAD_DESTRUCTOR_ITERATIONS +#define PTHREAD_DESTRUCTOR_ITERATIONS _POSIX_THREAD_DESTRUCTOR_ITERATIONS + +#undef _POSIX_THREAD_KEYS_MAX +#define _POSIX_THREAD_KEYS_MAX 128 + +#undef PTHREAD_KEYS_MAX +#define PTHREAD_KEYS_MAX _POSIX_THREAD_KEYS_MAX + +#undef PTHREAD_STACK_MIN +#define PTHREAD_STACK_MIN 0 + +#undef _POSIX_THREAD_THREADS_MAX +#define _POSIX_THREAD_THREADS_MAX 64 + + /* Arbitrary value */ +#undef PTHREAD_THREADS_MAX +#define PTHREAD_THREADS_MAX 2019 + +#undef _POSIX_SEM_NSEMS_MAX +#define _POSIX_SEM_NSEMS_MAX 256 + + /* Arbitrary value */ +#undef SEM_NSEMS_MAX +#define SEM_NSEMS_MAX 1024 + +#undef _POSIX_SEM_VALUE_MAX +#define _POSIX_SEM_VALUE_MAX 32767 + +#undef SEM_VALUE_MAX +#define SEM_VALUE_MAX INT_MAX + + +#if __GNUC__ && ! defined (__declspec) +# error Please upgrade your GNU compiler to one that supports __declspec. +#endif + +/* + * When building the DLL code, you should define PTW32_BUILD so that + * the variables/functions are exported correctly. When using the DLL, + * do NOT define PTW32_BUILD, and then the variables/functions will + * be imported correctly. + */ +#ifndef PTW32_STATIC_LIB +# ifdef PTW32_BUILD +# define PTW32_DLLPORT __declspec (dllexport) +# else +# define PTW32_DLLPORT __declspec (dllimport) +# endif +#else +# define PTW32_DLLPORT +#endif + +/* + * The Open Watcom C/C++ compiler uses a non-standard calling convention + * that passes function args in registers unless __cdecl is explicitly specified + * in exposed function prototypes. + * + * We force all calls to cdecl even though this could slow Watcom code down + * slightly. If you know that the Watcom compiler will be used to build both + * the DLL and application, then you can probably define this as a null string. + * Remember that pthread.h (this file) is used for both the DLL and application builds. + */ +#define PTW32_CDECL __cdecl + +#if defined(_UWIN) && PTW32_LEVEL >= PTW32_LEVEL_MAX +# include +#else +/* + * Generic handle type - intended to extend uniqueness beyond + * that available with a simple pointer. It should scale for either + * IA-32 or IA-64. + */ +typedef struct { + void * p; /* Pointer to actual object */ + unsigned int x; /* Extra information - reuse count etc */ +} ptw32_handle_t; + +typedef ptw32_handle_t pthread_t; +typedef struct pthread_attr_t_ * pthread_attr_t; +typedef struct pthread_once_t_ pthread_once_t; +typedef struct pthread_key_t_ * pthread_key_t; +typedef struct pthread_mutex_t_ * pthread_mutex_t; +typedef struct pthread_mutexattr_t_ * pthread_mutexattr_t; +typedef struct pthread_cond_t_ * pthread_cond_t; +typedef struct pthread_condattr_t_ * pthread_condattr_t; +#endif +typedef struct pthread_rwlock_t_ * pthread_rwlock_t; +typedef struct pthread_rwlockattr_t_ * pthread_rwlockattr_t; +typedef struct pthread_spinlock_t_ * pthread_spinlock_t; +typedef struct pthread_barrier_t_ * pthread_barrier_t; +typedef struct pthread_barrierattr_t_ * pthread_barrierattr_t; + +/* + * ==================== + * ==================== + * POSIX Threads + * ==================== + * ==================== + */ + +enum { +/* + * pthread_attr_{get,set}detachstate + */ + PTHREAD_CREATE_JOINABLE = 0, /* Default */ + PTHREAD_CREATE_DETACHED = 1, + +/* + * pthread_attr_{get,set}inheritsched + */ + PTHREAD_INHERIT_SCHED = 0, + PTHREAD_EXPLICIT_SCHED = 1, /* Default */ + +/* + * pthread_{get,set}scope + */ + PTHREAD_SCOPE_PROCESS = 0, + PTHREAD_SCOPE_SYSTEM = 1, /* Default */ + +/* + * pthread_setcancelstate paramters + */ + PTHREAD_CANCEL_ENABLE = 0, /* Default */ + PTHREAD_CANCEL_DISABLE = 1, + +/* + * pthread_setcanceltype parameters + */ + PTHREAD_CANCEL_ASYNCHRONOUS = 0, + PTHREAD_CANCEL_DEFERRED = 1, /* Default */ + +/* + * pthread_mutexattr_{get,set}pshared + * pthread_condattr_{get,set}pshared + */ + PTHREAD_PROCESS_PRIVATE = 0, + PTHREAD_PROCESS_SHARED = 1, + +/* + * pthread_barrier_wait + */ + PTHREAD_BARRIER_SERIAL_THREAD = -1 +}; + +/* + * ==================== + * ==================== + * Cancelation + * ==================== + * ==================== + */ +#define PTHREAD_CANCELED ((void *) -1) + + +/* + * ==================== + * ==================== + * Once Key + * ==================== + * ==================== + */ +#define PTHREAD_ONCE_INIT { PTW32_FALSE, 0, 0, 0} + +struct pthread_once_t_ +{ + int done; /* indicates if user function has been executed */ + void * lock; + int reserved1; + int reserved2; +}; + + +/* + * ==================== + * ==================== + * Object initialisers + * ==================== + * ==================== + */ +#define PTHREAD_MUTEX_INITIALIZER ((pthread_mutex_t) -1) +#define PTHREAD_RECURSIVE_MUTEX_INITIALIZER ((pthread_mutex_t) -2) +#define PTHREAD_ERRORCHECK_MUTEX_INITIALIZER ((pthread_mutex_t) -3) + +/* + * Compatibility with LinuxThreads + */ +#define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP PTHREAD_RECURSIVE_MUTEX_INITIALIZER +#define PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP PTHREAD_ERRORCHECK_MUTEX_INITIALIZER + +#define PTHREAD_COND_INITIALIZER ((pthread_cond_t) -1) + +#define PTHREAD_RWLOCK_INITIALIZER ((pthread_rwlock_t) -1) + +#define PTHREAD_SPINLOCK_INITIALIZER ((pthread_spinlock_t) -1) + + +/* + * Mutex types. + */ +enum +{ + /* Compatibility with LinuxThreads */ + PTHREAD_MUTEX_FAST_NP, + PTHREAD_MUTEX_RECURSIVE_NP, + PTHREAD_MUTEX_ERRORCHECK_NP, + PTHREAD_MUTEX_TIMED_NP = PTHREAD_MUTEX_FAST_NP, + PTHREAD_MUTEX_ADAPTIVE_NP = PTHREAD_MUTEX_FAST_NP, + /* For compatibility with POSIX */ + PTHREAD_MUTEX_NORMAL = PTHREAD_MUTEX_FAST_NP, + PTHREAD_MUTEX_RECURSIVE = PTHREAD_MUTEX_RECURSIVE_NP, + PTHREAD_MUTEX_ERRORCHECK = PTHREAD_MUTEX_ERRORCHECK_NP, + PTHREAD_MUTEX_DEFAULT = PTHREAD_MUTEX_NORMAL +}; + + +typedef struct ptw32_cleanup_t ptw32_cleanup_t; + +#if defined(_MSC_VER) +/* Disable MSVC 'anachronism used' warning */ +#pragma warning( disable : 4229 ) +#endif + +typedef void (* PTW32_CDECL ptw32_cleanup_callback_t)(void *); + +#if defined(_MSC_VER) +#pragma warning( default : 4229 ) +#endif + +struct ptw32_cleanup_t +{ + ptw32_cleanup_callback_t routine; + void *arg; + struct ptw32_cleanup_t *prev; +}; + +#ifdef __CLEANUP_SEH + /* + * WIN32 SEH version of cancel cleanup. + */ + +#define pthread_cleanup_push( _rout, _arg ) \ + { \ + ptw32_cleanup_t _cleanup; \ + \ + _cleanup.routine = (ptw32_cleanup_callback_t)(_rout); \ + _cleanup.arg = (_arg); \ + __try \ + { \ + +#define pthread_cleanup_pop( _execute ) \ + } \ + __finally \ + { \ + if( _execute || AbnormalTermination()) \ + { \ + (*(_cleanup.routine))( _cleanup.arg ); \ + } \ + } \ + } + +#else /* __CLEANUP_SEH */ + +#ifdef __CLEANUP_C + + /* + * C implementation of PThreads cancel cleanup + */ + +#define pthread_cleanup_push( _rout, _arg ) \ + { \ + ptw32_cleanup_t _cleanup; \ + \ + ptw32_push_cleanup( &_cleanup, (ptw32_cleanup_callback_t) (_rout), (_arg) ); \ + +#define pthread_cleanup_pop( _execute ) \ + (void) ptw32_pop_cleanup( _execute ); \ + } + +#else /* __CLEANUP_C */ + +#ifdef __CLEANUP_CXX + + /* + * C++ version of cancel cleanup. + * - John E. Bossom. + */ + + class PThreadCleanup { + /* + * PThreadCleanup + * + * Purpose + * This class is a C++ helper class that is + * used to implement pthread_cleanup_push/ + * pthread_cleanup_pop. + * The destructor of this class automatically + * pops the pushed cleanup routine regardless + * of how the code exits the scope + * (i.e. such as by an exception) + */ + ptw32_cleanup_callback_t cleanUpRout; + void * obj; + int executeIt; + + public: + PThreadCleanup() : + cleanUpRout( 0 ), + obj( 0 ), + executeIt( 0 ) + /* + * No cleanup performed + */ + { + } + + PThreadCleanup( + ptw32_cleanup_callback_t routine, + void * arg ) : + cleanUpRout( routine ), + obj( arg ), + executeIt( 1 ) + /* + * Registers a cleanup routine for 'arg' + */ + { + } + + ~PThreadCleanup() + { + if ( executeIt && ((void *) cleanUpRout != (void *) 0) ) + { + (void) (*cleanUpRout)( obj ); + } + } + + void execute( int exec ) + { + executeIt = exec; + } + }; + + /* + * C++ implementation of PThreads cancel cleanup; + * This implementation takes advantage of a helper + * class who's destructor automatically calls the + * cleanup routine if we exit our scope weirdly + */ +#define pthread_cleanup_push( _rout, _arg ) \ + { \ + PThreadCleanup cleanup((ptw32_cleanup_callback_t)(_rout), \ + (void *) (_arg) ); + +#define pthread_cleanup_pop( _execute ) \ + cleanup.execute( _execute ); \ + } + +#else + +#error ERROR [__FILE__, line __LINE__]: Cleanup type undefined. + +#endif /* __CLEANUP_CXX */ + +#endif /* __CLEANUP_C */ + +#endif /* __CLEANUP_SEH */ + +/* + * =============== + * =============== + * Methods + * =============== + * =============== + */ + +/* + * PThread Attribute Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_attr_init (pthread_attr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_destroy (pthread_attr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getdetachstate (const pthread_attr_t * attr, + int *detachstate); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getstackaddr (const pthread_attr_t * attr, + void **stackaddr); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getstacksize (const pthread_attr_t * attr, + size_t * stacksize); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setdetachstate (pthread_attr_t * attr, + int detachstate); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setstackaddr (pthread_attr_t * attr, + void *stackaddr); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setstacksize (pthread_attr_t * attr, + size_t stacksize); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getschedparam (const pthread_attr_t *attr, + struct sched_param *param); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setschedparam (pthread_attr_t *attr, + const struct sched_param *param); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setschedpolicy (pthread_attr_t *, + int); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getschedpolicy (pthread_attr_t *, + int *); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setinheritsched(pthread_attr_t * attr, + int inheritsched); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getinheritsched(pthread_attr_t * attr, + int * inheritsched); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setscope (pthread_attr_t *, + int); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getscope (const pthread_attr_t *, + int *); + +/* + * PThread Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_create (pthread_t * tid, + const pthread_attr_t * attr, + void *(*start) (void *), + void *arg); + +PTW32_DLLPORT int PTW32_CDECL pthread_detach (pthread_t tid); + +PTW32_DLLPORT int PTW32_CDECL pthread_equal (pthread_t t1, + pthread_t t2); + +PTW32_DLLPORT void PTW32_CDECL pthread_exit (void *value_ptr); + +PTW32_DLLPORT int PTW32_CDECL pthread_join (pthread_t thread, + void **value_ptr); + +PTW32_DLLPORT pthread_t PTW32_CDECL pthread_self (void); + +PTW32_DLLPORT int PTW32_CDECL pthread_cancel (pthread_t thread); + +PTW32_DLLPORT int PTW32_CDECL pthread_setcancelstate (int state, + int *oldstate); + +PTW32_DLLPORT int PTW32_CDECL pthread_setcanceltype (int type, + int *oldtype); + +PTW32_DLLPORT void PTW32_CDECL pthread_testcancel (void); + +PTW32_DLLPORT int PTW32_CDECL pthread_once (pthread_once_t * once_control, + void (*init_routine) (void)); + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX +PTW32_DLLPORT ptw32_cleanup_t * PTW32_CDECL ptw32_pop_cleanup (int execute); + +PTW32_DLLPORT void PTW32_CDECL ptw32_push_cleanup (ptw32_cleanup_t * cleanup, + void (*routine) (void *), + void *arg); +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +/* + * Thread Specific Data Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_key_create (pthread_key_t * key, + void (*destructor) (void *)); + +PTW32_DLLPORT int PTW32_CDECL pthread_key_delete (pthread_key_t key); + +PTW32_DLLPORT int PTW32_CDECL pthread_setspecific (pthread_key_t key, + const void *value); + +PTW32_DLLPORT void * PTW32_CDECL pthread_getspecific (pthread_key_t key); + + +/* + * Mutex Attribute Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_init (pthread_mutexattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_destroy (pthread_mutexattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_getpshared (const pthread_mutexattr_t + * attr, + int *pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_setpshared (pthread_mutexattr_t * attr, + int pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_settype (pthread_mutexattr_t * attr, int kind); +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_gettype (pthread_mutexattr_t * attr, int *kind); + +/* + * Barrier Attribute Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_barrierattr_init (pthread_barrierattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrierattr_destroy (pthread_barrierattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrierattr_getpshared (const pthread_barrierattr_t + * attr, + int *pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrierattr_setpshared (pthread_barrierattr_t * attr, + int pshared); + +/* + * Mutex Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_init (pthread_mutex_t * mutex, + const pthread_mutexattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_destroy (pthread_mutex_t * mutex); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_lock (pthread_mutex_t * mutex); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_timedlock(pthread_mutex_t *mutex, + const struct timespec *abstime); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_trylock (pthread_mutex_t * mutex); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_unlock (pthread_mutex_t * mutex); + +/* + * Spinlock Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_spin_init (pthread_spinlock_t * lock, int pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_spin_destroy (pthread_spinlock_t * lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_spin_lock (pthread_spinlock_t * lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_spin_trylock (pthread_spinlock_t * lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_spin_unlock (pthread_spinlock_t * lock); + +/* + * Barrier Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_barrier_init (pthread_barrier_t * barrier, + const pthread_barrierattr_t * attr, + unsigned int count); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrier_destroy (pthread_barrier_t * barrier); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrier_wait (pthread_barrier_t * barrier); + +/* + * Condition Variable Attribute Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_condattr_init (pthread_condattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_condattr_destroy (pthread_condattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_condattr_getpshared (const pthread_condattr_t * attr, + int *pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_condattr_setpshared (pthread_condattr_t * attr, + int pshared); + +/* + * Condition Variable Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_cond_init (pthread_cond_t * cond, + const pthread_condattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_destroy (pthread_cond_t * cond); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_wait (pthread_cond_t * cond, + pthread_mutex_t * mutex); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_timedwait (pthread_cond_t * cond, + pthread_mutex_t * mutex, + const struct timespec *abstime); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_signal (pthread_cond_t * cond); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_broadcast (pthread_cond_t * cond); + +/* + * Scheduling + */ +PTW32_DLLPORT int PTW32_CDECL pthread_setschedparam (pthread_t thread, + int policy, + const struct sched_param *param); + +PTW32_DLLPORT int PTW32_CDECL pthread_getschedparam (pthread_t thread, + int *policy, + struct sched_param *param); + +PTW32_DLLPORT int PTW32_CDECL pthread_setconcurrency (int); + +PTW32_DLLPORT int PTW32_CDECL pthread_getconcurrency (void); + +/* + * Read-Write Lock Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_init(pthread_rwlock_t *lock, + const pthread_rwlockattr_t *attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_destroy(pthread_rwlock_t *lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_tryrdlock(pthread_rwlock_t *); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_trywrlock(pthread_rwlock_t *); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_rdlock(pthread_rwlock_t *lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_timedrdlock(pthread_rwlock_t *lock, + const struct timespec *abstime); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_wrlock(pthread_rwlock_t *lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_timedwrlock(pthread_rwlock_t *lock, + const struct timespec *abstime); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_unlock(pthread_rwlock_t *lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlockattr_init (pthread_rwlockattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlockattr_destroy (pthread_rwlockattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlockattr_getpshared (const pthread_rwlockattr_t * attr, + int *pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlockattr_setpshared (pthread_rwlockattr_t * attr, + int pshared); + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX - 1 + +/* + * Signal Functions. Should be defined in but MSVC and MinGW32 + * already have signal.h that don't define these. + */ +PTW32_DLLPORT int PTW32_CDECL pthread_kill(pthread_t thread, int sig); + +/* + * Non-portable functions + */ + +/* + * Compatibility with Linux. + */ +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_setkind_np(pthread_mutexattr_t * attr, + int kind); +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_getkind_np(pthread_mutexattr_t * attr, + int *kind); + +/* + * Possibly supported by other POSIX threads implementations + */ +PTW32_DLLPORT int PTW32_CDECL pthread_delay_np (struct timespec * interval); +PTW32_DLLPORT int PTW32_CDECL pthread_num_processors_np(void); + +/* + * Useful if an application wants to statically link + * the lib rather than load the DLL at run-time. + */ +PTW32_DLLPORT int PTW32_CDECL pthread_win32_process_attach_np(void); +PTW32_DLLPORT int PTW32_CDECL pthread_win32_process_detach_np(void); +PTW32_DLLPORT int PTW32_CDECL pthread_win32_thread_attach_np(void); +PTW32_DLLPORT int PTW32_CDECL pthread_win32_thread_detach_np(void); + +/* + * Features that are auto-detected at load/run time. + */ +PTW32_DLLPORT int PTW32_CDECL pthread_win32_test_features_np(int); +enum ptw32_features { + PTW32_SYSTEM_INTERLOCKED_COMPARE_EXCHANGE = 0x0001, /* System provides it. */ + PTW32_ALERTABLE_ASYNC_CANCEL = 0x0002 /* Can cancel blocked threads. */ +}; + +/* + * Register a system time change with the library. + * Causes the library to perform various functions + * in response to the change. Should be called whenever + * the application's top level window receives a + * WM_TIMECHANGE message. It can be passed directly to + * pthread_create() as a new thread if desired. + */ +PTW32_DLLPORT void * PTW32_CDECL pthread_timechange_handler_np(void *); + +#endif /*PTW32_LEVEL >= PTW32_LEVEL_MAX - 1 */ + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX + +/* + * Returns the Win32 HANDLE for the POSIX thread. + */ +PTW32_DLLPORT HANDLE PTW32_CDECL pthread_getw32threadhandle_np(pthread_t thread); + + +/* + * Protected Methods + * + * This function blocks until the given WIN32 handle + * is signaled or pthread_cancel had been called. + * This function allows the caller to hook into the + * PThreads cancel mechanism. It is implemented using + * + * WaitForMultipleObjects + * + * on 'waitHandle' and a manually reset WIN32 Event + * used to implement pthread_cancel. The 'timeout' + * argument to TimedWait is simply passed to + * WaitForMultipleObjects. + */ +PTW32_DLLPORT int PTW32_CDECL pthreadCancelableWait (HANDLE waitHandle); +PTW32_DLLPORT int PTW32_CDECL pthreadCancelableTimedWait (HANDLE waitHandle, + DWORD timeout); + +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +/* + * Thread-Safe C Runtime Library Mappings. + */ +#ifndef _UWIN +# if defined(NEED_ERRNO) + PTW32_DLLPORT int * PTW32_CDECL _errno( void ); +# else +# ifndef errno +# if (defined(_MT) || defined(_DLL)) + __declspec(dllimport) extern int * __cdecl _errno(void); +# define errno (*_errno()) +# endif +# endif +# endif +#endif + +/* + * WIN32 C runtime library had been made thread-safe + * without affecting the user interface. Provide + * mappings from the UNIX thread-safe versions to + * the standard C runtime library calls. + * Only provide function mappings for functions that + * actually exist on WIN32. + */ + +#if !defined(__MINGW32__) +#define strtok_r( _s, _sep, _lasts ) \ + ( *(_lasts) = strtok( (_s), (_sep) ) ) +#endif /* !__MINGW32__ */ + +#define asctime_r( _tm, _buf ) \ + ( strcpy( (_buf), asctime( (_tm) ) ), \ + (_buf) ) + +#define ctime_r( _clock, _buf ) \ + ( strcpy( (_buf), ctime( (_clock) ) ), \ + (_buf) ) + +#define gmtime_r( _clock, _result ) \ + ( *(_result) = *gmtime( (_clock) ), \ + (_result) ) + +#define localtime_r( _clock, _result ) \ + ( *(_result) = *localtime( (_clock) ), \ + (_result) ) + +#define rand_r( _seed ) \ + ( _seed == _seed? rand() : rand() ) + + +/* + * Some compiler environments don't define some things. + */ +#if defined(__BORLANDC__) +# define _ftime ftime +# define _timeb timeb +#endif + +#ifdef __cplusplus + +/* + * Internal exceptions + */ +class ptw32_exception {}; +class ptw32_exception_cancel : public ptw32_exception {}; +class ptw32_exception_exit : public ptw32_exception {}; + +#endif + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX + +/* FIXME: This is only required if the library was built using SEH */ +/* + * Get internal SEH tag + */ +PTW32_DLLPORT DWORD PTW32_CDECL ptw32_get_exception_services_code(void); + +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +#ifndef PTW32_BUILD + +#ifdef __CLEANUP_SEH + +/* + * Redefine the SEH __except keyword to ensure that applications + * propagate our internal exceptions up to the library's internal handlers. + */ +#define __except( E ) \ + __except( ( GetExceptionCode() == ptw32_get_exception_services_code() ) \ + ? EXCEPTION_CONTINUE_SEARCH : ( E ) ) + +#endif /* __CLEANUP_SEH */ + +#ifdef __CLEANUP_CXX + +/* + * Redefine the C++ catch keyword to ensure that applications + * propagate our internal exceptions up to the library's internal handlers. + */ +#ifdef _MSC_VER + /* + * WARNING: Replace any 'catch( ... )' with 'PtW32CatchAll' + * if you want Pthread-Win32 cancelation and pthread_exit to work. + */ + +#ifndef PtW32NoCatchWarn + +#pragma message("Specify \"/DPtW32NoCatchWarn\" compiler flag to skip this message.") +#pragma message("------------------------------------------------------------------") +#pragma message("When compiling applications with MSVC++ and C++ exception handling:") +#pragma message(" Replace any 'catch( ... )' in routines called from POSIX threads") +#pragma message(" with 'PtW32CatchAll' or 'CATCHALL' if you want POSIX thread") +#pragma message(" cancelation and pthread_exit to work. For example:") +#pragma message("") +#pragma message(" #ifdef PtW32CatchAll") +#pragma message(" PtW32CatchAll") +#pragma message(" #else") +#pragma message(" catch(...)") +#pragma message(" #endif") +#pragma message(" {") +#pragma message(" /* Catchall block processing */") +#pragma message(" }") +#pragma message("------------------------------------------------------------------") + +#endif + +#define PtW32CatchAll \ + catch( ptw32_exception & ) { throw; } \ + catch( ... ) + +#else /* _MSC_VER */ + +#define catch( E ) \ + catch( ptw32_exception & ) { throw; } \ + catch( E ) + +#endif /* _MSC_VER */ + +#endif /* __CLEANUP_CXX */ + +#endif /* ! PTW32_BUILD */ + +#ifdef __cplusplus +} /* End of extern "C" */ +#endif /* __cplusplus */ + +#ifdef PTW32__HANDLE_DEF +# undef HANDLE +#endif +#ifdef PTW32__DWORD_DEF +# undef DWORD +#endif + +#undef PTW32_LEVEL +#undef PTW32_LEVEL_MAX + +#endif /* ! RC_INVOKED */ + +#endif /* PTHREAD_H */ diff --git a/xbmc/pvrclients/tvheadend/pthread_win32/pthreadVC2.lib b/xbmc/pvrclients/tvheadend/pthread_win32/pthreadVC2.lib new file mode 100644 index 0000000000000000000000000000000000000000..bc36770d52c3a3880e1fbd6924387256ca17829e GIT binary patch literal 29052 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 literal 0 HcmV?d00001 diff --git a/xbmc/pvrclients/tvheadend/pthread_win32/pthreadVC2d.lib b/xbmc/pvrclients/tvheadend/pthread_win32/pthreadVC2d.lib new file mode 100644 index 0000000000000000000000000000000000000000..0df71c7dfbf37c046823c184ed0f576b1d2b47c0 GIT binary patch literal 29172 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 199309 +#undef PTW32_LEVEL +#define PTW32_LEVEL 1 +/* Include 1b, 1c and 1d */ +#endif + +#if defined(INCLUDE_NP) +#undef PTW32_LEVEL +#define PTW32_LEVEL 2 +/* Include Non-Portable extensions */ +#endif + +#define PTW32_LEVEL_MAX 3 + +#if !defined(PTW32_LEVEL) +#define PTW32_LEVEL PTW32_LEVEL_MAX +/* Include everything */ +#endif + + +#if __GNUC__ && ! defined (__declspec) +# error Please upgrade your GNU compiler to one that supports __declspec. +#endif + +/* + * When building the DLL code, you should define PTW32_BUILD so that + * the variables/functions are exported correctly. When using the DLL, + * do NOT define PTW32_BUILD, and then the variables/functions will + * be imported correctly. + */ +#ifndef PTW32_STATIC_LIB +# ifdef PTW32_BUILD +# define PTW32_DLLPORT __declspec (dllexport) +# else +# define PTW32_DLLPORT __declspec (dllimport) +# endif +#else +# define PTW32_DLLPORT +#endif + +/* + * This is a duplicate of what is in the autoconf config.h, + * which is only used when building the pthread-win32 libraries. + */ + +#ifndef PTW32_CONFIG_H +# if defined(WINCE) +# define NEED_ERRNO +# define NEED_SEM +# endif +# if defined(_UWIN) || defined(__MINGW32__) +# define HAVE_MODE_T +# endif +#endif + +/* + * + */ + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX +#ifdef NEED_ERRNO +#include "need_errno.h" +#else +#include +#endif +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +#if defined(__MINGW32__) || defined(_UWIN) +#if PTW32_LEVEL >= PTW32_LEVEL_MAX +/* For pid_t */ +# include +/* Required by Unix 98 */ +# include +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ +#else +typedef int pid_t; +#endif + +/* Thread scheduling policies */ + +enum { + SCHED_OTHER = 0, + SCHED_FIFO, + SCHED_RR, + SCHED_MIN = SCHED_OTHER, + SCHED_MAX = SCHED_RR +}; + +struct sched_param { + int sched_priority; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PTW32_DLLPORT int __cdecl sched_yield (void); + +PTW32_DLLPORT int __cdecl sched_get_priority_min (int policy); + +PTW32_DLLPORT int __cdecl sched_get_priority_max (int policy); + +PTW32_DLLPORT int __cdecl sched_setscheduler (pid_t pid, int policy); + +PTW32_DLLPORT int __cdecl sched_getscheduler (pid_t pid); + +/* + * Note that this macro returns ENOTSUP rather than + * ENOSYS as might be expected. However, returning ENOSYS + * should mean that sched_get_priority_{min,max} are + * not implemented as well as sched_rr_get_interval. + * This is not the case, since we just don't support + * round-robin scheduling. Therefore I have chosen to + * return the same value as sched_setscheduler when + * SCHED_RR is passed to it. + */ +#define sched_rr_get_interval(_pid, _interval) \ + ( errno = ENOTSUP, (int) -1 ) + + +#ifdef __cplusplus +} /* End of extern "C" */ +#endif /* __cplusplus */ + +#undef PTW32_LEVEL +#undef PTW32_LEVEL_MAX + +#endif /* !_SCHED_H */ + diff --git a/xbmc/pvrclients/tvheadend/pthread_win32/semaphore.h b/xbmc/pvrclients/tvheadend/pthread_win32/semaphore.h new file mode 100644 index 0000000000..ea42ce3703 --- /dev/null +++ b/xbmc/pvrclients/tvheadend/pthread_win32/semaphore.h @@ -0,0 +1,166 @@ +/* + * Module: semaphore.h + * + * Purpose: + * Semaphores aren't actually part of the PThreads standard. + * They are defined by the POSIX Standard: + * + * POSIX 1003.1b-1993 (POSIX.1b) + * + * -------------------------------------------------------------------------- + * + * Pthreads-win32 - POSIX Threads Library for Win32 + * Copyright(C) 1998 John E. Bossom + * Copyright(C) 1999,2005 Pthreads-win32 contributors + * + * Contact Email: rpj@callisto.canberra.edu.au + * + * The current list of contributors is contained + * in the file CONTRIBUTORS included with the source + * code distribution. The list can also be seen at the + * following World Wide Web location: + * http://sources.redhat.com/pthreads-win32/contributors.html + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library in the file COPYING.LIB; + * if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ +#if !defined( SEMAPHORE_H ) +#define SEMAPHORE_H + +#undef PTW32_LEVEL + +#if defined(_POSIX_SOURCE) +#define PTW32_LEVEL 0 +/* Early POSIX */ +#endif + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309 +#undef PTW32_LEVEL +#define PTW32_LEVEL 1 +/* Include 1b, 1c and 1d */ +#endif + +#if defined(INCLUDE_NP) +#undef PTW32_LEVEL +#define PTW32_LEVEL 2 +/* Include Non-Portable extensions */ +#endif + +#define PTW32_LEVEL_MAX 3 + +#if !defined(PTW32_LEVEL) +#define PTW32_LEVEL PTW32_LEVEL_MAX +/* Include everything */ +#endif + +#if __GNUC__ && ! defined (__declspec) +# error Please upgrade your GNU compiler to one that supports __declspec. +#endif + +/* + * When building the DLL code, you should define PTW32_BUILD so that + * the variables/functions are exported correctly. When using the DLL, + * do NOT define PTW32_BUILD, and then the variables/functions will + * be imported correctly. + */ +#ifndef PTW32_STATIC_LIB +# ifdef PTW32_BUILD +# define PTW32_DLLPORT __declspec (dllexport) +# else +# define PTW32_DLLPORT __declspec (dllimport) +# endif +#else +# define PTW32_DLLPORT +#endif + +/* + * This is a duplicate of what is in the autoconf config.h, + * which is only used when building the pthread-win32 libraries. + */ + +#ifndef PTW32_CONFIG_H +# if defined(WINCE) +# define NEED_ERRNO +# define NEED_SEM +# endif +# if defined(_UWIN) || defined(__MINGW32__) +# define HAVE_MODE_T +# endif +#endif + +/* + * + */ + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX +#ifdef NEED_ERRNO +#include "need_errno.h" +#else +#include +#endif +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +#define _POSIX_SEMAPHORES + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#ifndef HAVE_MODE_T +typedef unsigned int mode_t; +#endif + + +typedef struct sem_t_ * sem_t; + +PTW32_DLLPORT int __cdecl sem_init (sem_t * sem, + int pshared, + unsigned int value); + +PTW32_DLLPORT int __cdecl sem_destroy (sem_t * sem); + +PTW32_DLLPORT int __cdecl sem_trywait (sem_t * sem); + +PTW32_DLLPORT int __cdecl sem_wait (sem_t * sem); + +PTW32_DLLPORT int __cdecl sem_timedwait (sem_t * sem, + const struct timespec * abstime); + +PTW32_DLLPORT int __cdecl sem_post (sem_t * sem); + +PTW32_DLLPORT int __cdecl sem_post_multiple (sem_t * sem, + int count); + +PTW32_DLLPORT int __cdecl sem_open (const char * name, + int oflag, + mode_t mode, + unsigned int value); + +PTW32_DLLPORT int __cdecl sem_close (sem_t * sem); + +PTW32_DLLPORT int __cdecl sem_unlink (const char * name); + +PTW32_DLLPORT int __cdecl sem_getvalue (sem_t * sem, + int * sval); + +#ifdef __cplusplus +} /* End of extern "C" */ +#endif /* __cplusplus */ + +#undef PTW32_LEVEL +#undef PTW32_LEVEL_MAX + +#endif /* !SEMAPHORE_H */ diff --git a/xbmc/pvrclients/tvheadend/pvrclient-tvheadend_os.h b/xbmc/pvrclients/tvheadend/pvrclient-tvheadend_os.h new file mode 100644 index 0000000000..9575847a0e --- /dev/null +++ b/xbmc/pvrclients/tvheadend/pvrclient-tvheadend_os.h @@ -0,0 +1,44 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef PVRCLIENT_TVHEADEND_OS_H +#define PVRCLIENT_TVHEADEND_OS_H + +#if defined(_WIN32) || defined(_WIN64) +#define __WINDOWS__ +#endif + +#if defined(__WINDOWS__) +#include "windows/os_windows.h" +#else +#include "linux/os_posix.h" +#endif + +#if !defined(TRUE) +#define TRUE 1 +#endif + +#if !defined(FALSE) +#define FALSE 0 +#endif + +#endif diff --git a/xbmc/pvrclients/tvheadend/thread.cpp b/xbmc/pvrclients/tvheadend/thread.cpp new file mode 100644 index 0000000000..5187006795 --- /dev/null +++ b/xbmc/pvrclients/tvheadend/thread.cpp @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * Most of this code is taken from thread.c in the Video Disk Recorder ('VDR') + */ + +#include "thread.h" +#include +#ifndef __APPLE__ +#include +#endif + +#include +#include +#include "StdString.h" + +static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow) +{ + struct timeval now; + if (gettimeofday(&now, NULL) == 0) { // get current time + now.tv_sec += MillisecondsFromNow / 1000; // add full seconds + now.tv_usec += (MillisecondsFromNow % 1000) * 1000; // add microseconds + if (now.tv_usec >= 1000000) { // take care of an overflow + now.tv_sec++; + now.tv_usec -= 1000000; + } + Abstime->tv_sec = now.tv_sec; // seconds + Abstime->tv_nsec = now.tv_usec * 1000; // nano seconds + return true; + } + return false; +} + +// --- cCondWait ------------------------------------------------------------- + +cCondWait::cCondWait(void) +{ + signaled = false; + pthread_mutex_init(&mutex, NULL); + pthread_cond_init(&cond, NULL); +} + +cCondWait::~cCondWait() +{ + pthread_cond_broadcast(&cond); // wake up any sleepers + pthread_cond_destroy(&cond); + pthread_mutex_destroy(&mutex); +} + +void cCondWait::SleepMs(int TimeoutMs) +{ + cCondWait w; + w.Wait(max(TimeoutMs, 3)); // making sure the time is >2ms to avoid a possible busy wait +} + +bool cCondWait::Wait(int TimeoutMs) +{ + pthread_mutex_lock(&mutex); + if (!signaled) { + if (TimeoutMs) { + struct timespec abstime; + if (GetAbsTime(&abstime, TimeoutMs)) { + while (!signaled) { + if (pthread_cond_timedwait(&cond, &mutex, &abstime) == ETIMEDOUT) + break; + } + } + } + else + pthread_cond_wait(&cond, &mutex); + } + bool r = signaled; + signaled = false; + pthread_mutex_unlock(&mutex); + return r; +} + +void cCondWait::Signal(void) +{ + pthread_mutex_lock(&mutex); + signaled = true; + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mutex); +} + +// --- cCondVar -------------------------------------------------------------- + +cCondVar::cCondVar(void) +{ + pthread_cond_init(&cond, 0); +} + +cCondVar::~cCondVar() +{ + pthread_cond_broadcast(&cond); // wake up any sleepers + pthread_cond_destroy(&cond); +} + +void cCondVar::Wait(cMutex &Mutex) +{ + if (Mutex.locked) { + int locked = Mutex.locked; + Mutex.locked = 0; // have to clear the locked count here, as pthread_cond_wait + // does an implicit unlock of the mutex + pthread_cond_wait(&cond, &Mutex.mutex); + Mutex.locked = locked; + } +} + +bool cCondVar::TimedWait(cMutex &Mutex, int TimeoutMs) +{ + bool r = true; // true = condition signaled, false = timeout + + if (Mutex.locked) { + struct timespec abstime; + if (GetAbsTime(&abstime, TimeoutMs)) { + int locked = Mutex.locked; + Mutex.locked = 0; // have to clear the locked count here, as pthread_cond_timedwait + // does an implicit unlock of the mutex. + if (pthread_cond_timedwait(&cond, &Mutex.mutex, &abstime) == ETIMEDOUT) + r = false; + Mutex.locked = locked; + } + } + return r; +} + +void cCondVar::Broadcast(void) +{ + pthread_cond_broadcast(&cond); +} + +// --- cMutex ---------------------------------------------------------------- + +cMutex::cMutex(void) +{ + locked = 0; + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); +#ifndef __APPLE__ + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK_NP); +#else + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); +#endif + pthread_mutex_init(&mutex, &attr); +} + +cMutex::~cMutex() +{ + pthread_mutex_destroy(&mutex); +} + +void cMutex::Lock(void) +{ + pthread_mutex_lock(&mutex); + locked++; +} + +void cMutex::Unlock(void) +{ + if (!--locked) + pthread_mutex_unlock(&mutex); +} + +// --- cThread --------------------------------------------------------------- + +tThreadId cThread::mainThreadId = 0; + +cThread::cThread(const char *Description) +{ + active = running = false; +#if !defined(__WINDOWS__) + childTid = 0; +#endif + childThreadId = 0; + description = NULL; + if (Description) + SetDescription("%s", Description); +} + +cThread::~cThread() +{ + Cancel(); // just in case the derived class didn't call it + free(description); +} + +void cThread::SetPriority(int Priority) +{ +#if !defined(__WINDOWS__) + if (setpriority(PRIO_PROCESS, 0, Priority) < 0) + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); +#endif +} + +void cThread::SetIOPriority(int Priority) +{ +#if !defined(__WINDOWS__) +#ifdef HAVE_LINUXIOPRIO + if (syscall(SYS_ioprio_set, 1, 0, (Priority & 0xff) | (2 << 13)) < 0) // best effort class + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); +#endif +#endif +} + +void cThread::SetDescription(const char *Description, ...) +{ + free(description); + description = NULL; + if (Description) + { + va_list ap; + va_start(ap, Description); + CStdString desc; + desc.FormatV(Description, ap); + description = strdup(desc.c_str()); + va_end(ap); + } +} + +void *cThread::StartThread(cThread *Thread) +{ + Thread->childThreadId = ThreadId(); + if (Thread->description) { + XBMC->Log(LOG_DEBUG, "%s thread started (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId); +#ifdef PR_SET_NAME + if (prctl(PR_SET_NAME, Thread->description, 0, 0, 0) < 0) + XBMC->Log(LOG_ERROR, "%s thread naming failed (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId); +#endif + } + Thread->Action(); + if (Thread->description) + XBMC->Log(LOG_DEBUG, "%s thread ended (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId); + Thread->running = false; + Thread->active = false; + return NULL; +} + +#define THREAD_STOP_TIMEOUT 3000 // ms to wait for a thread to stop before newly starting it +#define THREAD_STOP_SLEEP 30 // ms to sleep while waiting for a thread to stop + +bool cThread::Start(void) +{ + if (!running) { + if (active) { + // Wait until the previous incarnation of this thread has completely ended + // before starting it newly: + cTimeMs RestartTimeout; + while (!running && active && RestartTimeout.Elapsed() < THREAD_STOP_TIMEOUT) + cCondWait::SleepMs(THREAD_STOP_SLEEP); + } + if (!active) { + active = running = true; + if (pthread_create(&childTid, NULL, (void *(*) (void *))&StartThread, (void *)this) == 0) { + pthread_detach(childTid); // auto-reap + } + else { + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); + active = running = false; + return false; + } + } + } + return true; +} + +bool cThread::Active(void) +{ + if (active) { + // + // Single UNIX Spec v2 says: + // + // The pthread_kill() function is used to request + // that a signal be delivered to the specified thread. + // + // As in kill(), if sig is zero, error checking is + // performed but no signal is actually sent. + // + int err; + if ((err = pthread_kill(childTid, 0)) != 0) { + if (err != ESRCH) + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); +#if !defined(__WINDOWS__) + childTid = 0; +#endif + active = running = false; + } + else + return true; + } + return false; +} + +void cThread::Cancel(int WaitSeconds) +{ + running = false; + if (active && WaitSeconds > -1) + { + if (WaitSeconds > 0) + { + for (time_t t0 = time(NULL) + WaitSeconds; time(NULL) < t0; ) + { + if (!Active()) + return; + cCondWait::SleepMs(10); + } + XBMC->Log(LOG_ERROR, "ERROR: %s thread %d won't end (waited %d seconds) - canceling it...", description ? description : "", childThreadId, WaitSeconds); + } + pthread_cancel(childTid); +#if !defined(__WINDOWS__) + childTid = 0; +#endif + active = false; + } +} + +tThreadId cThread::ThreadId(void) +{ +#ifdef __APPLE__ + return (int)pthread_self(); +#else +#ifdef __WINDOWS__ + return GetCurrentThreadId(); +#else + return syscall(__NR_gettid); +#endif +#endif +} + +void cThread::SetMainThreadId(void) +{ + if (mainThreadId == 0) + mainThreadId = ThreadId(); + else + XBMC->Log(LOG_ERROR, "ERROR: attempt to set main thread id to %d while it already is %d", ThreadId(), mainThreadId); +} + +// --- cMutexLock ------------------------------------------------------------ + +cMutexLock::cMutexLock(cMutex *Mutex) +{ + mutex = NULL; + locked = false; + Lock(Mutex); +} + +cMutexLock::~cMutexLock() +{ + if (mutex && locked) + mutex->Unlock(); +} + +bool cMutexLock::Lock(cMutex *Mutex) +{ + if (Mutex && !mutex) + { + mutex = Mutex; + Mutex->Lock(); + locked = true; + return true; + } + return false; +} + +// --- cThreadLock ----------------------------------------------------------- + +cThreadLock::cThreadLock(cThread *Thread) +{ + thread = NULL; + locked = false; + Lock(Thread); +} + +cThreadLock::~cThreadLock() +{ + if (thread && locked) + thread->Unlock(); +} + +bool cThreadLock::Lock(cThread *Thread) +{ + if (Thread && !thread) + { + thread = Thread; + Thread->Lock(); + locked = true; + return true; + } + return false; +} diff --git a/xbmc/pvrclients/tvheadend/thread.h b/xbmc/pvrclients/tvheadend/thread.h new file mode 100644 index 0000000000..960a6e9594 --- /dev/null +++ b/xbmc/pvrclients/tvheadend/thread.h @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __THREAD_H +#define __THREAD_H + +#include +#include +#include "tools.h" + +class cCondWait { +private: + pthread_mutex_t mutex; + pthread_cond_t cond; + bool signaled; +public: + cCondWait(void); + ~cCondWait(); + static void SleepMs(int TimeoutMs); + ///< Creates a cCondWait object and uses it to sleep for TimeoutMs + ///< milliseconds, immediately giving up the calling thread's time + ///< slice and thus avoiding a "busy wait". + ///< In order to avoid a possible busy wait, TimeoutMs will be automatically + ///< limited to values >2. + bool Wait(int TimeoutMs = 0); + ///< Waits at most TimeoutMs milliseconds for a call to Signal(), or + ///< forever if TimeoutMs is 0. + ///< \return Returns true if Signal() has been called, false it the given + ///< timeout has expired. + void Signal(void); + ///< Signals a caller of Wait() that the condition it is waiting for is met. + }; + +class cMutex; + +class cCondVar { +private: + pthread_cond_t cond; +public: + cCondVar(void); + ~cCondVar(); + void Wait(cMutex &Mutex); + bool TimedWait(cMutex &Mutex, int TimeoutMs); + void Broadcast(void); + }; + +class cMutex { + friend class cCondVar; +private: + pthread_mutex_t mutex; + int locked; +public: + cMutex(void); + ~cMutex(); + void Lock(void); + void Unlock(void); + }; + +typedef pid_t tThreadId; + +class cThread { + friend class cThreadLock; +private: + bool active; + bool running; + pthread_t childTid; + tThreadId childThreadId; + cMutex mutex; + char *description; + static tThreadId mainThreadId; + static void *StartThread(cThread *Thread); +protected: + void SetPriority(int Priority); + void SetIOPriority(int Priority); + void Lock(void) { mutex.Lock(); } + void Unlock(void) { mutex.Unlock(); } + virtual void Action(void) = 0; + ///< A derived cThread class must implement the code it wants to + ///< execute as a separate thread in this function. If this is + ///< a loop, it must check Running() repeatedly to see whether + ///< it's time to stop. + bool Running(void) { return running; } + ///< Returns false if a derived cThread object shall leave its Action() + ///< function. + void Cancel(int WaitSeconds = 0); + ///< Cancels the thread by first setting 'running' to false, so that + ///< the Action() loop can finish in an orderly fashion and then waiting + ///< up to WaitSeconds seconds for the thread to actually end. If the + ///< thread doesn't end by itself, it is killed. + ///< If WaitSeconds is -1, only 'running' is set to false and Cancel() + ///< returns immediately, without killing the thread. +public: + cThread(const char *Description = NULL); + ///< Creates a new thread. + ///< If Description is present, a log file entry will be made when + ///< the thread starts and stops. The Start() function must be called + ///< to actually start the thread. + virtual ~cThread(); +#ifdef __WINDOWS__ + void SetDescription(const char *Description, ...); +#else + void SetDescription(const char *Description, ...) __attribute__ ((format (printf, 2, 3))); +#endif + bool Start(void); + ///< Actually starts the thread. + ///< If the thread is already running, nothing happens. + bool Active(void); + ///< Checks whether the thread is still alive. + static tThreadId ThreadId(void); + static tThreadId IsMainThread(void) { return ThreadId() == mainThreadId; } + static void SetMainThreadId(void); + }; + +// cMutexLock can be used to easily set a lock on mutex and make absolutely +// sure that it will be unlocked when the block will be left. Several locks can +// be stacked, so a function that makes many calls to another function which uses +// cMutexLock may itself use a cMutexLock to make one longer lock instead of many +// short ones. + +class cMutexLock { +private: + cMutex *mutex; + bool locked; +public: + cMutexLock(cMutex *Mutex = NULL); + ~cMutexLock(); + bool Lock(cMutex *Mutex); + }; + +// cThreadLock can be used to easily set a lock in a thread and make absolutely +// sure that it will be unlocked when the block will be left. Several locks can +// be stacked, so a function that makes many calls to another function which uses +// cThreadLock may itself use a cThreadLock to make one longer lock instead of many +// short ones. + +class cThreadLock { +private: + cThread *thread; + bool locked; +public: + cThreadLock(cThread *Thread = NULL); + ~cThreadLock(); + bool Lock(cThread *Thread); + }; + +#define LOCK_THREAD cThreadLock ThreadLock(this) + +#endif //__THREAD_H diff --git a/xbmc/pvrclients/tvheadend/tools.cpp b/xbmc/pvrclients/tvheadend/tools.cpp new file mode 100644 index 0000000000..4467f39f41 --- /dev/null +++ b/xbmc/pvrclients/tvheadend/tools.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * Most of this code is taken from tools.c in the Video Disk Recorder ('VDR') + */ + +#include "tools.h" + +// --- cTimeMs --------------------------------------------------------------- + +cTimeMs::cTimeMs(int Ms) +{ + Set(Ms); +} + +uint64_t cTimeMs::Now(void) +{ +#if _POSIX_TIMERS > 0 && defined(_POSIX_MONOTONIC_CLOCK) +#define MIN_RESOLUTION 5 // ms + static bool initialized = false; + static bool monotonic = false; + struct timespec tp; + if (!initialized) { + // check if monotonic timer is available and provides enough accurate resolution: + if (clock_getres(CLOCK_MONOTONIC, &tp) == 0) { + long Resolution = tp.tv_nsec; + // require a minimum resolution: + if (tp.tv_sec == 0 && tp.tv_nsec <= MIN_RESOLUTION * 1000000) { + if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) { + XBMC->Log(LOG_DEBUG, "cTimeMs: using monotonic clock (resolution is %ld ns)", Resolution); + monotonic = true; + } + else + XBMC->Log(LOG_ERROR, "cTimeMs: clock_gettime(CLOCK_MONOTONIC) failed"); + } + else + XBMC->Log(LOG_DEBUG, "cTimeMs: not using monotonic clock - resolution is too bad (%ld s %ld ns)", tp.tv_sec, tp.tv_nsec); + } + else + XBMC->Log(LOG_ERROR, "cTimeMs: clock_getres(CLOCK_MONOTONIC) failed"); + initialized = true; + } + if (monotonic) { + if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) + return (uint64_t(tp.tv_sec)) * 1000 + tp.tv_nsec / 1000000; + XBMC->Log(LOG_ERROR, "cTimeMs: clock_gettime(CLOCK_MONOTONIC) failed"); + monotonic = false; + // fall back to gettimeofday() + } +#else +#if !defined(__WINDOWS__) +# warning Posix monotonic clock not available +#endif +#endif + struct timeval t; + if (gettimeofday(&t, NULL) == 0) + return (uint64_t(t.tv_sec)) * 1000 + t.tv_usec / 1000; + return 0; +} + +void cTimeMs::Set(int Ms) +{ + begin = Now() + Ms; +} + +bool cTimeMs::TimedOut(void) +{ + return Now() >= begin; +} + +uint64_t cTimeMs::Elapsed(void) +{ + return Now() - begin; +} + diff --git a/xbmc/pvrclients/tvheadend/tools.h b/xbmc/pvrclients/tvheadend/tools.h new file mode 100644 index 0000000000..8b1e197fba --- /dev/null +++ b/xbmc/pvrclients/tvheadend/tools.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __TOOLS_H +#define __TOOLS_H + +#include "pvrclient-tvheadend_os.h" +#include "client.h" +#include "StdString.h" +#include +#include +#include +#include +#include +#include +#include + +#define ERRNUL(e) {errno=e;return 0;} +#define ERRSYS(e) {errno=e;return -1;} + +#define SECSINDAY 86400 + +#define KILOBYTE(n) ((n) * 1024) +#define MEGABYTE(n) ((n) * 1024LL * 1024LL) + +#define MALLOC(type, size) (type *)malloc(sizeof(type) * (size)) + +#define DELETENULL(p) (delete (p), p = NULL) +// +//#define CHECK(s) { if ((s) < 0) LOG_ERROR; } // used for 'ioctl()' calls +#define FATALERRNO (errno && errno != EAGAIN && errno != EINTR) + +class cTimeMs +{ +private: + uint64_t begin; +public: + cTimeMs(int Ms = 0); + ///< Creates a timer with ms resolution and an initial timeout of Ms. + static uint64_t Now(void); + void Set(int Ms = 0); + bool TimedOut(void); + uint64_t Elapsed(void); +}; + + +#endif //__TOOLS_H diff --git a/xbmc/pvrclients/tvheadend/windows/dirent.cpp b/xbmc/pvrclients/tvheadend/windows/dirent.cpp new file mode 100644 index 0000000000..90b26e7a5d --- /dev/null +++ b/xbmc/pvrclients/tvheadend/windows/dirent.cpp @@ -0,0 +1,159 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pvrclient-tvheadend_os.h" +#include + +/********************************************************************** + * Implement dirent-style opendir/readdir/rewinddir/closedir on Win32 + * + * Functions defined are opendir(), readdir(), rewinddir() and + * closedir() with the same prototypes as the normal dirent.h + * implementation. + * + * Does not implement telldir(), seekdir(), or scandir(). The dirent + * struct is compatible with Unix, except that d_ino is always 1 and + * d_off is made up as we go along. + * + * The DIR typedef is not compatible with Unix. + **********************************************************************/ + +DIR *opendir(const char *dir) +{ + DIR *dirp; + char *filespec; + long handle; + int index; + + filespec = (char *)malloc(strlen(dir) + 2 + 1); + strcpy(filespec, dir); + index = (int)strlen(filespec) - 1; + if (index >= 0 && (filespec[index] == '/' || + (filespec[index] == '\\' && !IsDBCSLeadByte(filespec[index-1])))) + filespec[index] = '\0'; + strcat(filespec, "/*"); + + dirp = (DIR *) malloc(sizeof(DIR)); + dirp->offset = 0; + dirp->finished = 0; + + if ((handle = _findfirst(filespec, &(dirp->fileinfo))) < 0) + { + if (errno == ENOENT || errno == EINVAL) + dirp->finished = 1; + else + { + free(dirp); + free(filespec); + return NULL; + } + } + dirp->dirname = strdup(dir); + dirp->handle = handle; + free(filespec); + + return dirp; +} + +int closedir(DIR *dp) +{ + int iret = -1; + if (!dp) + return iret; + iret = _findclose(dp->handle); + if (iret == 0 && dp->dirname) + free(dp->dirname); + if (iret == 0 && dp) + free(dp); + + return iret; +} + +struct dirent *readdir(DIR *dp) +{ + if (!dp || dp->finished) + return NULL; + + if (dp->offset != 0) + { + if (_findnext(dp->handle, &(dp->fileinfo)) < 0) + { + dp->finished = 1; + return NULL; + } + } + dp->offset++; + + strcpy(dp->dent.d_name, dp->fileinfo.name);/*, _MAX_FNAME+1);*/ + dp->dent.d_ino = 1; + dp->dent.d_reclen = (unsigned short)strlen(dp->dent.d_name); + dp->dent.d_off = dp->offset; + + return &(dp->dent); +} + +int readdir_r(DIR *dp, struct dirent *entry, struct dirent **result) +{ + if (!dp || dp->finished) + { + *result = NULL; + return -1; + } + + if (dp->offset != 0) + { + if (_findnext(dp->handle, &(dp->fileinfo)) < 0) + { + dp->finished = 1; + *result = NULL; + return -1; + } + } + dp->offset++; + + strcpy(dp->dent.d_name, dp->fileinfo.name);/*, _MAX_FNAME+1);*/ + dp->dent.d_ino = 1; + dp->dent.d_reclen = (unsigned short)strlen(dp->dent.d_name); + dp->dent.d_off = dp->offset; + + memcpy(entry, &dp->dent, sizeof(*entry)); + + *result = &dp->dent; + + return 0; +} + +int rewinddir(DIR *dp) +{ + char *filespec; + long handle; + int index; + + _findclose(dp->handle); + + dp->offset = 0; + dp->finished = 0; + + filespec = (char *)malloc(strlen(dp->dirname) + 2 + 1); + strcpy(filespec, dp->dirname); + index = (int)(strlen(filespec) - 1); + if (index >= 0 && (filespec[index] == '/' || filespec[index] == '\\')) + filespec[index] = '\0'; + strcat(filespec, "/*"); + + if ((handle = _findfirst(filespec, &(dp->fileinfo))) < 0) + { + if (errno == ENOENT || errno == EINVAL) + dp->finished = 1; + } + dp->handle = handle; + free(filespec); + + return 0; +} diff --git a/xbmc/pvrclients/tvheadend/windows/dirent.h b/xbmc/pvrclients/tvheadend/windows/dirent.h new file mode 100644 index 0000000000..4b127335b1 --- /dev/null +++ b/xbmc/pvrclients/tvheadend/windows/dirent.h @@ -0,0 +1,103 @@ +/***************************************************************************** + * dirent.h - dirent API for Microsoft Visual Studio + * + * Copyright (C) 2006 Toni Ronkko + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * ``Software''), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL TONI RONKKO BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Dec 15, 2009, John Cunningham + * Added rewinddir member function + * + * Jan 18, 2008, Toni Ronkko + * Using FindFirstFileA and WIN32_FIND_DATAA to avoid converting string + * between multi-byte and unicode representations. This makes the + * code simpler and also allows the code to be compiled under MingW. Thanks + * to Azriel Fasten for the suggestion. + * + * Mar 4, 2007, Toni Ronkko + * Bug fix: due to the strncpy_s() function this file only compiled in + * Visual Studio 2005. Using the new string functions only when the + * compiler version allows. + * + * Nov 2, 2006, Toni Ronkko + * Major update: removed support for Watcom C, MS-DOS and Turbo C to + * simplify the file, updated the code to compile cleanly on Visual + * Studio 2005 with both unicode and multi-byte character strings, + * removed rewinddir() as it had a bug. + * + * Aug 20, 2006, Toni Ronkko + * Removed all remarks about MSVC 1.0, which is antiqued now. Simplified + * comments by removing SGML tags. + * + * May 14 2002, Toni Ronkko + * Embedded the function definitions directly to the header so that no + * source modules need to be included in the Visual Studio project. Removed + * all the dependencies to other projects so that this very header can be + * used independently. + * + * May 28 1998, Toni Ronkko + * First version. + *****************************************************************************/ +#ifndef DIRENT_H +#define DIRENT_H + +#include +#include +#include + +/* struct dirent - same as Unix dirent.h */ +struct dirent +{ + long d_ino; /* inode number (always 1 in WIN32) */ + off_t d_off; /* offset to this dirent */ + unsigned short d_reclen; /* length of d_name */ + char d_name[_MAX_FNAME + 1]; /* filename (null terminated) */ + /*unsigned char d_type;*/ /*type of file*/ +}; + + +/* def struct DIR - different from Unix DIR */ +typedef struct +{ + long handle; /* _findfirst/_findnext handle */ + short offset; /* offset into directory */ + short finished; /* 1 if there are not more files */ + struct _finddata_t fileinfo; /* from _findfirst/_findnext */ + char *dirname; /* the dir we are reading */ + struct dirent dent; /* the dirent to return */ +} DIR; + +/* Function prototypes */ +DIR *opendir(const char *); +struct dirent *readdir(DIR *); +int readdir_r(DIR *, struct dirent *, struct dirent **); +int closedir(DIR *); +int rewinddir(DIR *); + + +/* Use the new safe string functions introduced in Visual Studio 2005 */ +#if defined(_MSC_VER) && _MSC_VER >= 1400 +# define STRNCPY(dest,src,size) strncpy_s((dest),(size),(src),_TRUNCATE) +#else +# define STRNCPY(dest,src,size) strncpy((dest),(src),(size)) +#endif + + +#endif /*DIRENT_H*/ diff --git a/xbmc/pvrclients/tvheadend/windows/os_windows.cpp b/xbmc/pvrclients/tvheadend/windows/os_windows.cpp new file mode 100644 index 0000000000..7e222a3b10 --- /dev/null +++ b/xbmc/pvrclients/tvheadend/windows/os_windows.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "os_windows.h" +#include + +THREADLOCAL int ws32_result; +THREADLOCAL int _so_err; +THREADLOCAL int _so_err_siz = sizeof(int); + +int gettimeofday(struct timeval *pcur_time, struct timezone *tz) +{ + struct _timeb current; + + _ftime(¤t); + + pcur_time->tv_sec = current.time; + pcur_time->tv_usec = current.millitm * 1000L; + if (tz) + { + tz->tz_minuteswest = current.timezone; /* minutes west of Greenwich */ + tz->tz_dsttime = current.dstflag; /* type of dst correction */ + } + return 0; +} diff --git a/xbmc/pvrclients/tvheadend/windows/os_windows.h b/xbmc/pvrclients/tvheadend/windows/os_windows.h new file mode 100644 index 0000000000..50d41687ee --- /dev/null +++ b/xbmc/pvrclients/tvheadend/windows/os_windows.h @@ -0,0 +1,365 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef PVRCLIENT_VDR_OS_WIN_H +#define PVRCLIENT_VDR_OS_WIN_H + +#if defined _FILE_OFFSET_BITS && _FILE_OFFSET_BITS == 64 +# define __USE_FILE_OFFSET64 1 +#endif + +//typedef int ssize_t; +typedef int mode_t; +typedef int bool_t; +typedef __int8 int8_t; +typedef signed __int16 int16_t; +typedef signed __int32 int32_t; +typedef signed __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; + +#if defined __USE_FILE_OFFSET64 +typedef int64_t off_t; +typedef uint64_t ino_t; +#else +typedef long off_t; +#endif + +#define NAME_MAX 255 /* # chars in a file name */ +#define MAXPATHLEN 255 +#define INT64_MAX _I64_MAX +#define INT64_MIN _I64_MIN + +#ifndef S_ISLNK +# define S_ISLNK(x) 0 +#endif + +#ifndef S_ISREG +#define S_ISREG(x) (((x) & S_IFMT) == S_IFREG) +#endif + +#ifndef S_ISDIR +#define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR) +#endif + +/* Some tricks for MS Compilers */ +#define THREADLOCAL __declspec(thread) + +#ifndef DEFFILEMODE +#define DEFFILEMODE 0 +#endif + +#define alloca _alloca +#define chdir _chdir +#define dup _dup +#define dup2 _dup2 +#define fdopen _fdopen +#define fileno _fileno +#define getcwd _getcwd +#define getpid _getpid +#define ioctl ioctlsocket +#define mkdir(p) _mkdir(p) +#define mktemp _mktemp +#define open _open +#define pclose _pclose +#define popen _popen +#define putenv _putenv +#define setmode _setmode +#define sleep(t) Sleep((t)*1000) +#define usleep(t) Sleep((t)/1000) +#define snprintf _snprintf +#define strcasecmp _stricmp +#define strdup _strdup +#define strlwr _strlwr +#define strncasecmp _strnicmp +#define tempnam _tempnam +#define umask _umask +#define unlink _unlink +#define close _close + +#define O_RDONLY _O_RDONLY +#define O_WRONLY _O_WRONLY +#define O_RDWR _O_RDWR +#define O_APPEND _O_APPEND + +#define O_CREAT _O_CREAT +#define O_TRUNC _O_TRUNC +#define O_EXCL _O_EXCL + +#define O_TEXT _O_TEXT +#define O_BINARY _O_BINARY +#define O_RAW _O_BINARY +#define O_TEMPORARY _O_TEMPORARY +#define O_NOINHERIT _O_NOINHERIT +#define O_SEQUENTIAL _O_SEQUENTIAL +#define O_RANDOM _O_RANDOM +#define O_NDELAY 0 + +#define S_IRWXO 007 +//#define S_ISDIR(m) (((m) & _S_IFDIR) == _S_IFDIR) +//#define S_ISREG(m) (((m) & _S_IFREG) == _S_IFREG) + +#ifndef SIGHUP +#define SIGHUP 1 /* hangup */ +#endif +#ifndef SIGBUS +#define SIGBUS 7 /* bus error */ +#endif +#ifndef SIGKILL +#define SIGKILL 9 /* kill (cannot be caught or ignored) */ +#endif +#ifndef SIGSEGV +#define SIGSEGV 11 /* segment violation */ +#endif +#ifndef SIGPIPE +#define SIGPIPE 13 /* write on a pipe with no one to read it */ +#endif +#ifndef SIGCHLD +#define SIGCHLD 20 /* to parent on child stop or exit */ +#endif +#ifndef SIGUSR1 +#define SIGUSR1 30 /* user defined signal 1 */ +#endif +#ifndef SIGUSR2 +#define SIGUSR2 31 /* user defined signal 2 */ +#endif + +typedef unsigned short in_port_t; +typedef unsigned short int ushort; +typedef unsigned int in_addr_t; +typedef int socklen_t; +typedef int uid_t; +typedef int gid_t; + +#if defined __USE_FILE_OFFSET64 +#define stat _stati64 +#define lseek _lseeki64 +#define fstat _fstati64 +#define tell _telli64 +#else +#define stat _stat +#define lseek _lseek +#define fstat _fstat +#define tell _tell +#endif + +#define atoll _atoi64 +#ifndef va_copy +#define va_copy(x, y) x = y +#endif + +#include +#include +#if defined(_MSC_VER) /* Microsoft C Compiler ONLY */ +/* Hack to suppress compiler warnings on FD_SET() & FD_CLR() */ +#pragma warning (push) +#pragma warning (disable:4142) +#endif +/* prevent inclusion of wingdi.h */ +#define NOGDI +#include +#include +#if defined(_MSC_VER) /* Microsoft C Compiler ONLY */ +#pragma warning (pop) +#endif +#include +#include +#include +#include "pthread_win32/pthread.h" + +typedef char * caddr_t; + +#undef FD_CLOSE +#undef FD_OPEN +#undef FD_READ +#undef FD_WRITE +#define EISCONN WSAEISCONN +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define EALREADY WSAEALREADY +#ifndef ETIMEDOUT +#define ETIMEDOUT WSAETIMEDOUT +#endif +#define ECONNABORTED WSAECONNABORTED +#define ECONNREFUSED WSAECONNREFUSED +#define ECONNRESET WSAECONNRESET +#define ERESTART WSATRY_AGAIN +#define ENOTCONN WSAENOTCONN +#define ENOBUFS WSAENOBUFS +#define EOVERFLOW 2006 + +#undef h_errno +#define h_errno errno /* we'll set it ourselves */ + +struct timezone +{ + int tz_minuteswest; /* minutes west of Greenwich */ + int tz_dsttime; /* type of dst correction */ +}; + +extern int gettimeofday(struct timeval *, struct timezone *); + +/* Unix socket emulation macros */ +#define __close closesocket + +#undef FD_CLR +#define FD_CLR(fd, set) do { \ + u_int __i; \ + SOCKET __sock = _get_osfhandle(fd); \ + for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count ; __i++) { \ + if (((fd_set FAR *)(set))->fd_array[__i] == __sock) { \ + while (__i < ((fd_set FAR *)(set))->fd_count-1) { \ + ((fd_set FAR *)(set))->fd_array[__i] = \ + ((fd_set FAR *)(set))->fd_array[__i+1]; \ + __i++; \ + } \ + ((fd_set FAR *)(set))->fd_count--; \ + break; \ + } \ + } \ +} while(0) + +#undef FD_SET +#define FD_SET(fd, set) do { \ + u_int __i; \ + SOCKET __sock = _get_osfhandle(fd); \ + for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \ + if (((fd_set FAR *)(set))->fd_array[__i] == (__sock)) { \ + break; \ + } \ + } \ + if (__i == ((fd_set FAR *)(set))->fd_count) { \ + if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \ + ((fd_set FAR *)(set))->fd_array[__i] = (__sock); \ + ((fd_set FAR *)(set))->fd_count++; \ + } \ + } \ +} while(0) + +#undef FD_ISSET +#define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(_get_osfhandle(fd)), (fd_set FAR *)(set)) + +extern THREADLOCAL int ws32_result; +#define __poll(f,n,t) \ + (SOCKET_ERROR == WSAPoll(f,n,t) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __socket(f,t,p) \ + (INVALID_SOCKET == ((SOCKET)(ws32_result = (int)socket(f,t,p))) ? \ + ((WSAEMFILE == (errno = WSAGetLastError()) ? errno = EMFILE : -1), -1) : \ + (SOCKET)_open_osfhandle(ws32_result,0)) +#define __accept(s,a,l) \ + (INVALID_SOCKET == ((SOCKET)(ws32_result = (int)accept(_get_osfhandle(s),a,l))) ? \ + ((WSAEMFILE == (errno = WSAGetLastError()) ? errno = EMFILE : -1), -1) : \ + (SOCKET)_open_osfhandle(ws32_result,0)) +#define __bind(s,n,l) \ + (SOCKET_ERROR == bind(_get_osfhandle(s),n,l) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __connect(s,n,l) \ + (SOCKET_ERROR == connect(_get_osfhandle(s),n,l) ? \ + (WSAEMFILE == (errno = WSAGetLastError()) ? errno = EMFILE : -1, -1) : 0) +#define __listen(s,b) \ + (SOCKET_ERROR == listen(_get_osfhandle(s),b) ? \ + (WSAEMFILE == (errno = WSAGetLastError()) ? errno = EMFILE : -1, -1) : 0) +#define __shutdown(s,h) \ + (SOCKET_ERROR == shutdown(_get_osfhandle(s),h) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __select(n,r,w,e,t) \ + (SOCKET_ERROR == (ws32_result = select(n,r,w,e,t)) ? \ + (errno = WSAGetLastError()), -1 : ws32_result) +#define __recv(s,b,l,f) \ + (SOCKET_ERROR == (ws32_result = recv(_get_osfhandle(s),b,l,f)) ? \ + (errno = WSAGetLastError()), -1 : ws32_result) +#define __recvfrom(s,b,l,f,fr,frl) \ + (SOCKET_ERROR == (ws32_result = recvfrom(_get_osfhandle(s),b,l,f,fr,frl)) ? \ + (errno = WSAGetLastError()), -1 : ws32_result) +#define __send(s,b,l,f) \ + (SOCKET_ERROR == (ws32_result = send(_get_osfhandle(s),b,l,f)) ? \ + (errno = WSAGetLastError()), -1 : ws32_result) +#define __sendto(s,b,l,f,t,tl) \ + (SOCKET_ERROR == (ws32_result = sendto(_get_osfhandle(s),b,l,f,t,tl)) ? \ + (errno = WSAGetLastError()), -1 : ws32_result) +#define __getsockname(s,n,l) \ + (SOCKET_ERROR == getsockname(_get_osfhandle(s),n,l) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __getpeername(s,n,l) \ + (SOCKET_ERROR == getpeername(_get_osfhandle(s),n,l) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __getsockopt(s,l,o,v,n) \ + (Sleep(1), SOCKET_ERROR == getsockopt(_get_osfhandle(s),l,o,(char*)v,n) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __setsockopt(s,l,o,v,n) \ + (SOCKET_ERROR == setsockopt(_get_osfhandle(s),l,o,v,n) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __ioctlsocket(s,c,a) \ + (SOCKET_ERROR == ioctlsocket(_get_osfhandle(s),c,a) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __gethostname(n,l) \ + (SOCKET_ERROR == gethostname(n,l) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __gethostbyname(n) \ + (NULL == ((HOSTENT FAR*)(ws32_result = (int)gethostbyname(n))) ? \ + (errno = WSAGetLastError()), NULL : (HOSTENT FAR*)ws32_result) +#define __getservbyname(n,p) \ + (NULL == ((SERVENT FAR*)(ws32_result = (int)getservbyname(n,p))) ? \ + (errno = WSAGetLastError()), NULL : (SERVENT FAR*)ws32_result) +#define __gethostbyaddr(a,l,t) \ + (NULL == ((HOSTENT FAR*)(ws32_result = (int)gethostbyaddr(a,l,t))) ? \ + (errno = WSAGetLastError()), NULL : (HOSTENT FAR*)ws32_result) +extern THREADLOCAL int _so_err; +extern THREADLOCAL int _so_err_siz; +#define __read(fd,buf,siz) \ + (_so_err_siz = sizeof(_so_err), \ + __getsockopt((fd),SOL_SOCKET,SO_ERROR,&_so_err,&_so_err_siz) \ + == 0 ? __recv((fd),(char *)(buf),(siz),0) : _read((fd),(char *)(buf),(siz))) +#define __write(fd,buf,siz) \ + (_so_err_siz = sizeof(_so_err), \ + __getsockopt((fd),SOL_SOCKET,SO_ERROR,&_so_err,&_so_err_siz) \ + == 0 ? __send((fd),(const char *)(buf),(siz),0) : _write((fd),(const char *)(buf),(siz))) + + +#if !defined(__MINGW32__) +#define strtok_r( _s, _sep, _lasts ) \ + ( *(_lasts) = strtok( (_s), (_sep) ) ) +#endif /* !__MINGW32__ */ + +#define asctime_r( _tm, _buf ) \ + ( strcpy( (_buf), asctime( (_tm) ) ), \ + (_buf) ) + +#define ctime_r( _clock, _buf ) \ + ( strcpy( (_buf), ctime( (_clock) ) ), \ + (_buf) ) + +#define gmtime_r( _clock, _result ) \ + ( *(_result) = *gmtime( (_clock) ), \ + (_result) ) + +#define localtime_r( _clock, _result ) \ + ( *(_result) = *localtime( (_clock) ), \ + (_result) ) + +#define rand_r( _seed ) \ + ( _seed == _seed? rand() : rand() ) + +#endif diff --git a/xbmc/pvrclients/vdr-streamdev/Makefile.in b/xbmc/pvrclients/vdr-streamdev/Makefile.in new file mode 100644 index 0000000000..0a53be4e3f --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/Makefile.in @@ -0,0 +1,68 @@ +# +# Makefile for the XBMC Video Disk Recorder PVR AddOn +# +# See the README for copyright information and +# how to reach the author. +# + +.DELETE_ON_ERROR: + +ARCH = @ARCH@ +DESTDIR ?= +PREFIX ?= /usr/local +ADDONDIR = $(PREFIX)/share/xbmc/addons +LIBS = -ldl +INCLUDES = -I. -I../../linux -I../../ -I ../../../xbmc/addons/include +DEFINES += -D_LINUX -fPIC +LIBDIR = ../../../addons/pvr.vdr.streamdev +LIB = ../../../addons/pvr.vdr.streamdev/XBMC_VDR.pvr + +CC ?= gcc +CFLAGS ?= -g -O2 -Wall + +CXX ?= g++ +ifeq ($(findstring Darwin,$(shell uname -a)), Darwin) +CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses -dynamiclib -single_module -undefined dynamic_lookup +DEFINES += -D__APPLE__ +else +CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses +endif + +-include Make.config + +OBJS = channelscan.o client.o vtptransceiver.o timers.o channels.o recordings.o epg.o tools.o thread.o ringbuffer.o select.o + +all: $(LIB) + +# Implicit rules: + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< + +# Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.cpp) > $@ + +-include $(DEPFILE) + +# The main library: + +$(LIB): $(OBJS) $(SILIB) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -g $(OBJS) $(LIBS) $(LIBDIRS) $(SILIB) -o $(LIB) + +# Install the files: + +install: install-lib + +# PVR library: + +install-lib: $(LIB) + @mkdir -p $(DESTDIR)$(ADDONDIR) + @cp --remove-destination -r $(LIBDIR) $(DESTDIR)$(ADDONDIR) + +clean: + -rm -f $(OBJS) $(DEPFILE) $(LIB) *~ +CLEAN: clean diff --git a/xbmc/pvrclients/vdr-streamdev/README b/xbmc/pvrclients/vdr-streamdev/README new file mode 100644 index 0000000000..52fb91b248 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/README @@ -0,0 +1,34 @@ +XBMC Video Disk Recorder ('VDR') PVR Add-on +------------------------------------------ + +THIS IS A PRELIMINARY README AND IS SUBJECT TO CHANGE!!! + +Written by: The XBMC Team + +Project's homepage: http://code.google.com/p/xbmcpvr-vdr/ + +Latest version available at: http://code.google.com/p/xbmcpvr-vdr/ + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +any later version. +See the file LICENSE.GPL for more information. + +------------------------------------------ + +This is a PVR Add-on for XBMC to add VDR (http://www.cadsoft.de/vdr) as a TV/PVR Backend to +XBMC. + +It want to support Live TV watching, replaying of Recordings, programming Timers and +EPG TV Guide to use on same computer or over the Network. + +The connection of this AddOn depend upon a installed Streamdev-Server-Plugin on the VDR. +This is the only minimum requirement to VDR for a connection to this addon, but the +functionality can be improved by some other types of VDR Plugins. + + +Links: +VDR: http://www.cadsoft.de/vdr +Streamdev-Plugin: http://streamdev.vdr-developer.org/ +Dummydevice-Plugin: http://phivdr.dyndns.org/vdr/ diff --git a/xbmc/pvrclients/vdr-streamdev/StdString.h b/xbmc/pvrclients/vdr-streamdev/StdString.h new file mode 100644 index 0000000000..8ee30603da --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/StdString.h @@ -0,0 +1,4333 @@ +#pragma once +#include +#include +#if !defined(_LINUX) +#include +#include "pvrclient-vdr_os.h" +#endif + +// ============================================================================= +// FILE: StdString.h +// AUTHOR: Joe O'Leary (with outside help noted in comments) +// +// If you find any bugs in this code, please let me know: +// +// jmoleary@earthlink.net +// http://www.joeo.net/stdstring.htm (a bit outdated) +// +// The latest version of this code should always be available at the +// following link: +// +// http://www.joeo.net/code/StdString.zip (Dec 6, 2003) +// +// +// REMARKS: +// This header file declares the CStdStr template. This template derives +// the Standard C++ Library basic_string<> template and add to it the +// the following conveniences: +// - The full MFC CString set of functions (including implicit cast) +// - writing to/reading from COM IStream interfaces +// - Functional objects for use in STL algorithms +// +// From this template, we intstantiate two classes: CStdStringA and +// CStdStringW. The name "CStdString" is just a #define of one of these, +// based upone the UNICODE macro setting +// +// This header also declares our own version of the MFC/ATL UNICODE-MBCS +// conversion macros. Our version looks exactly like the Microsoft's to +// facilitate portability. +// +// NOTE: +// If you you use this in an MFC or ATL build, you should include either +// afx.h or atlbase.h first, as appropriate. +// +// PEOPLE WHO HAVE CONTRIBUTED TO THIS CLASS: +// +// Several people have helped me iron out problems and othewise improve +// this class. OK, this is a long list but in my own defense, this code +// has undergone two major rewrites. Many of the improvements became +// necessary after I rewrote the code as a template. Others helped me +// improve the CString facade. +// +// Anyway, these people are (in chronological order): +// +// - Pete the Plumber (???) +// - Julian Selman +// - Chris (of Melbsys) +// - Dave Plummer +// - John C Sipos +// - Chris Sells +// - Nigel Nunn +// - Fan Xia +// - Matthew Williams +// - Carl Engman +// - Mark Zeren +// - Craig Watson +// - Rich Zuris +// - Karim Ratib +// - Chris Conti +// - Baptiste Lepilleur +// - Greg Pickles +// - Jim Cline +// - Jeff Kohn +// - Todd Heckel +// - Ullrich Poll�hne +// - Joe Vitaterna +// - Joe Woodbury +// - Aaron (no last name) +// - Joldakowski (???) +// - Scott Hathaway +// - Eric Nitzche +// - Pablo Presedo +// - Farrokh Nejadlotfi +// - Jason Mills +// - Igor Kholodov +// - Mike Crusader +// - John James +// - Wang Haifeng +// - Tim Dowty +// - Arnt Witteveen +// - Glen Maynard +// - Paul DeMarco +// - Bagira (full name?) +// - Ronny Schulz +// - Jakko Van Hunen +// - Charles Godwin +// - Henk Demper +// - Greg Marr +// - Bill Carducci +// - Brian Groose +// - MKingman +// - Don Beusee +// +// REVISION HISTORY +// +// 2005-JAN-10 - Thanks to Don Beusee for pointing out the danger in mapping +// length-checked formatting functions to non-length-checked +// CRT equivalents. Also thanks to him for motivating me to +// optimize my implementation of Replace() +// +// 2004-APR-22 - A big, big thank you to "MKingman" (whoever you are) for +// finally spotting a silly little error in StdCodeCvt that +// has been causing me (and users of CStdString) problems for +// years in some relatively rare conversions. I had reversed +// two length arguments. +// +// 2003-NOV-24 - Thanks to a bunch of people for helping me clean up many +// compiler warnings (and yes, even a couple of actual compiler +// errors). These include Henk Demper for figuring out how +// to make the Intellisense work on with CStdString on VC6, +// something I was never able to do. Greg Marr pointed out +// a compiler warning about an unreferenced symbol and a +// problem with my version of Load in MFC builds. Bill +// Carducci took a lot of time with me to help me figure out +// why some implementations of the Standard C++ Library were +// returning error codes for apparently successful conversions +// between ASCII and UNICODE. Finally thanks to Brian Groose +// for helping me fix compiler signed unsigned warnings in +// several functions. +// +// 2003-JUL-10 - Thanks to Charles Godwin for making me realize my 'FmtArg' +// fixes had inadvertently broken the DLL-export code (which is +// normally commented out. I had to move it up higher. Also +// this helped me catch a bug in ssicoll that would prevent +// compilation, otherwise. +// +// 2003-MAR-14 - Thanks to Jakko Van Hunen for pointing out a copy-and-paste +// bug in one of the overloads of FmtArg. +// +// 2003-MAR-10 - Thanks to Ronny Schulz for (twice!) sending me some changes +// to help CStdString build on SGI and for pointing out an +// error in placement of my preprocessor macros for ssfmtmsg. +// +// 2002-NOV-26 - Thanks to Bagira for pointing out that my implementation of +// SpanExcluding was not properly handling the case in which +// the string did NOT contain any of the given characters +// +// 2002-OCT-21 - Many thanks to Paul DeMarco who was invaluable in helping me +// get this code working with Borland's free compiler as well +// as the Dev-C++ compiler (available free at SourceForge). +// +// 2002-SEP-13 - Thanks to Glen Maynard who helped me get rid of some loud +// but harmless warnings that were showing up on g++. Glen +// also pointed out that some pre-declarations of FmtArg<> +// specializations were unnecessary (and no good on G++) +// +// 2002-JUN-26 - Thanks to Arnt Witteveen for pointing out that I was using +// static_cast<> in a place in which I should have been using +// reinterpret_cast<> (the ctor for unsigned char strings). +// That's what happens when I don't unit-test properly! +// Arnt also noticed that CString was silently correcting the +// 'nCount' argument to Left() and Right() where CStdString was +// not (and crashing if it was bad). That is also now fixed! +// +// 2002-FEB-25 - Thanks to Tim Dowty for pointing out (and giving me the fix +// for) a conversion problem with non-ASCII MBCS characters. +// CStdString is now used in my favorite commercial MP3 player! +// +// 2001-DEC-06 - Thanks to Wang Haifeng for spotting a problem in one of the +// assignment operators (for _bstr_t) that would cause compiler +// errors when refcounting protection was turned off. +// +// 2001-NOV-27 - Remove calls to operator!= which involve reverse_iterators +// due to a conflict with the rel_ops operator!=. Thanks to +// John James for pointing this out. +// +// 2001-OCT-29 - Added a minor range checking fix for the Mid function to +// make it as forgiving as CString's version is. Thanks to +// Igor Kholodov for noticing this. +// - Added a specialization of std::swap for CStdString. Thanks +// to Mike Crusader for suggesting this! It's commented out +// because you're not supposed to inject your own code into the +// 'std' namespace. But if you don't care about that, it's +// there if you want it +// - Thanks to Jason Mills for catching a case where CString was +// more forgiving in the Delete() function than I was. +// +// 2001-JUN-06 - I was violating the Standard name lookup rules stated +// in [14.6.2(3)]. None of the compilers I've tried so +// far apparently caught this but HP-UX aCC 3.30 did. The +// fix was to add 'this->' prefixes in many places. +// Thanks to Farrokh Nejadlotfi for this! +// +// 2001-APR-27 - StreamLoad was calculating the number of BYTES in one +// case, not characters. Thanks to Pablo Presedo for this. +// +// 2001-FEB-23 - Replace() had a bug which caused infinite loops if the +// source string was empty. Fixed thanks to Eric Nitzsche. +// +// 2001-FEB-23 - Scott Hathaway was a huge help in providing me with the +// ability to build CStdString on Sun Unix systems. He +// sent me detailed build reports about what works and what +// does not. If CStdString compiles on your Unix box, you +// can thank Scott for it. +// +// 2000-DEC-29 - Joldakowski noticed one overload of Insert failed to do a +// range check as CString's does. Now fixed -- thanks! +// +// 2000-NOV-07 - Aaron pointed out that I was calling static member +// functions of char_traits via a temporary. This was not +// technically wrong, but it was unnecessary and caused +// problems for poor old buggy VC5. Thanks Aaron! +// +// 2000-JUL-11 - Joe Woodbury noted that the CString::Find docs don't match +// what the CString::Find code really ends up doing. I was +// trying to match the docs. Now I match the CString code +// - Joe also caught me truncating strings for GetBuffer() calls +// when the supplied length was less than the current length. +// +// 2000-MAY-25 - Better support for STLPORT's Standard library distribution +// - Got rid of the NSP macro - it interfered with Koenig lookup +// - Thanks to Joe Woodbury for catching a TrimLeft() bug that +// I introduced in January. Empty strings were not getting +// trimmed +// +// 2000-APR-17 - Thanks to Joe Vitaterna for pointing out that ReverseFind +// is supposed to be a const function. +// +// 2000-MAR-07 - Thanks to Ullrich Poll�hne for catching a range bug in one +// of the overloads of assign. +// +// 2000-FEB-01 - You can now use CStdString on the Mac with CodeWarrior! +// Thanks to Todd Heckel for helping out with this. +// +// 2000-JAN-23 - Thanks to Jim Cline for pointing out how I could make the +// Trim() function more efficient. +// - Thanks to Jeff Kohn for prompting me to find and fix a typo +// in one of the addition operators that takes _bstr_t. +// - Got rid of the .CPP file - you only need StdString.h now! +// +// 1999-DEC-22 - Thanks to Greg Pickles for helping me identify a problem +// with my implementation of CStdString::FormatV in which +// resulting string might not be properly NULL terminated. +// +// 1999-DEC-06 - Chris Conti pointed yet another basic_string<> assignment +// bug that MS has not fixed. CStdString did nothing to fix +// it either but it does now! The bug was: create a string +// longer than 31 characters, get a pointer to it (via c_str()) +// and then assign that pointer to the original string object. +// The resulting string would be empty. Not with CStdString! +// +// 1999-OCT-06 - BufferSet was erasing the string even when it was merely +// supposed to shrink it. Fixed. Thanks to Chris Conti. +// - Some of the Q172398 fixes were not checking for assignment- +// to-self. Fixed. Thanks to Baptiste Lepilleur. +// +// 1999-AUG-20 - Improved Load() function to be more efficient by using +// SizeOfResource(). Thanks to Rich Zuris for this. +// - Corrected resource ID constructor, again thanks to Rich. +// - Fixed a bug that occurred with UNICODE characters above +// the first 255 ANSI ones. Thanks to Craig Watson. +// - Added missing overloads of TrimLeft() and TrimRight(). +// Thanks to Karim Ratib for pointing them out +// +// 1999-JUL-21 - Made all calls to GetBuf() with no args check length first. +// +// 1999-JUL-10 - Improved MFC/ATL independence of conversion macros +// - Added SS_NO_REFCOUNT macro to allow you to disable any +// reference-counting your basic_string<> impl. may do. +// - Improved ReleaseBuffer() to be as forgiving as CString. +// Thanks for Fan Xia for helping me find this and to +// Matthew Williams for pointing it out directly. +// +// 1999-JUL-06 - Thanks to Nigel Nunn for catching a very sneaky bug in +// ToLower/ToUpper. They should call GetBuf() instead of +// data() in order to ensure the changed string buffer is not +// reference-counted (in those implementations that refcount). +// +// 1999-JUL-01 - Added a true CString facade. Now you can use CStdString as +// a drop-in replacement for CString. If you find this useful, +// you can thank Chris Sells for finally convincing me to give +// in and implement it. +// - Changed operators << and >> (for MFC CArchive) to serialize +// EXACTLY as CString's do. So now you can send a CString out +// to a CArchive and later read it in as a CStdString. I have +// no idea why you would want to do this but you can. +// +// 1999-JUN-21 - Changed the CStdString class into the CStdStr template. +// - Fixed FormatV() to correctly decrement the loop counter. +// This was harmless bug but a bug nevertheless. Thanks to +// Chris (of Melbsys) for pointing it out +// - Changed Format() to try a normal stack-based array before +// using to _alloca(). +// - Updated the text conversion macros to properly use code +// pages and to fit in better in MFC/ATL builds. In other +// words, I copied Microsoft's conversion stuff again. +// - Added equivalents of CString::GetBuffer, GetBufferSetLength +// - new sscpy() replacement of CStdString::CopyString() +// - a Trim() function that combines TrimRight() and TrimLeft(). +// +// 1999-MAR-13 - Corrected the "NotSpace" functional object to use _istpace() +// instead of _isspace() Thanks to Dave Plummer for this. +// +// 1999-FEB-26 - Removed errant line (left over from testing) that #defined +// _MFC_VER. Thanks to John C Sipos for noticing this. +// +// 1999-FEB-03 - Fixed a bug in a rarely-used overload of operator+() that +// caused infinite recursion and stack overflow +// - Added member functions to simplify the process of +// persisting CStdStrings to/from DCOM IStream interfaces +// - Added functional objects (e.g. StdStringLessNoCase) that +// allow CStdStrings to be used as keys STL map objects with +// case-insensitive comparison +// - Added array indexing operators (i.e. operator[]). I +// originally assumed that these were unnecessary and would be +// inherited from basic_string. However, without them, Visual +// C++ complains about ambiguous overloads when you try to use +// them. Thanks to Julian Selman to pointing this out. +// +// 1998-FEB-?? - Added overloads of assign() function to completely account +// for Q172398 bug. Thanks to "Pete the Plumber" for this +// +// 1998-FEB-?? - Initial submission +// +// COPYRIGHT: +// 2002 Joseph M. O'Leary. This code is 100% free. Use it anywhere you +// want. Rewrite it, restructure it, whatever. If you can write software +// that makes money off of it, good for you. I kinda like capitalism. +// Please don't blame me if it causes your $30 billion dollar satellite +// explode in orbit. If you redistribute it in any form, I'd appreciate it +// if you would leave this notice here. +// ============================================================================= + +// Avoid multiple inclusion + +#ifndef STDSTRING_H +#define STDSTRING_H + +// When using VC, turn off browser references +// Turn off unavoidable compiler warnings + +#if defined(_MSC_VER) && (_MSC_VER > 1100) + #pragma component(browser, off, references, "CStdString") + #pragma warning (disable : 4290) // C++ Exception Specification ignored + #pragma warning (disable : 4127) // Conditional expression is constant + #pragma warning (disable : 4097) // typedef name used as synonym for class name +#endif + +// Borland warnings to turn off + +#ifdef __BORLANDC__ + #pragma option push -w-inl +// #pragma warn -inl // Turn off inline function warnings +#endif + +// SS_IS_INTRESOURCE +// ----------------- +// A copy of IS_INTRESOURCE from VC7. Because old VC6 version of winuser.h +// doesn't have this. + +#define SS_IS_INTRESOURCE(_r) (false) + +#if !defined (SS_ANSI) && defined(_MSC_VER) + #undef SS_IS_INTRESOURCE + #if defined(_WIN64) + #define SS_IS_INTRESOURCE(_r) (((unsigned __int64)(_r) >> 16) == 0) + #else + #define SS_IS_INTRESOURCE(_r) (((unsigned long)(_r) >> 16) == 0) + #endif +#endif + + +// MACRO: SS_UNSIGNED +// ------------------ +// This macro causes the addition of a constructor and assignment operator +// which take unsigned characters. CString has such functions and in order +// to provide maximum CString-compatability, this code needs them as well. +// In practice you will likely never need these functions... + +//#define SS_UNSIGNED + +#ifdef SS_ALLOW_UNSIGNED_CHARS + #define SS_UNSIGNED +#endif + +// MACRO: SS_SAFE_FORMAT +// --------------------- +// This macro provides limited compatability with a questionable CString +// "feature". You can define it in order to avoid a common problem that +// people encounter when switching from CString to CStdString. +// +// To illustrate the problem -- With CString, you can do this: +// +// CString sName("Joe"); +// CString sTmp; +// sTmp.Format("My name is %s", sName); // WORKS! +// +// However if you were to try this with CStdString, your program would +// crash. +// +// CStdString sName("Joe"); +// CStdString sTmp; +// sTmp.Format("My name is %s", sName); // CRASHES! +// +// You must explicitly call c_str() or cast the object to the proper type +// +// sTmp.Format("My name is %s", sName.c_str()); // WORKS! +// sTmp.Format("My name is %s", static_cast(sName));// WORKS! +// sTmp.Format("My name is %s", (PCSTR)sName); // WORKS! +// +// This is because it is illegal to pass anything but a POD type as a +// variadic argument to a variadic function (i.e. as one of the "..." +// arguments). The type const char* is a POD type. The type CStdString +// is not. Of course, neither is the type CString, but CString lets you do +// it anyway due to the way they laid out the class in binary. I have no +// control over this in CStdString since I derive from whatever +// implementation of basic_string is available. +// +// However if you have legacy code (which does this) that you want to take +// out of the MFC world and you don't want to rewrite all your calls to +// Format(), then you can define this flag and it will no longer crash. +// +// Note however that this ONLY works for Format(), not sprintf, fprintf, +// etc. If you pass a CStdString object to one of those functions, your +// program will crash. Not much I can do to get around this, short of +// writing substitutes for those functions as well. + +#define SS_SAFE_FORMAT // use new template style Format() function + + +// MACRO: SS_NO_IMPLICIT_CAST +// -------------------------- +// Some people don't like the implicit cast to const char* (or rather to +// const CT*) that CStdString (and MFC's CString) provide. That was the +// whole reason I created this class in the first place, but hey, whatever +// bakes your cake. Just #define this macro to get rid of the the implicit +// cast. + +//#define SS_NO_IMPLICIT_CAST // gets rid of operator const CT*() + + +// MACRO: SS_NO_REFCOUNT +// --------------------- +// turns off reference counting at the assignment level. Only needed +// for the version of basic_string<> that comes with Visual C++ versions +// 6.0 or earlier, and only then in some heavily multithreaded scenarios. +// Uncomment it if you feel you need it. + +//#define SS_NO_REFCOUNT + +// MACRO: SS_WIN32 +// --------------- +// When this flag is set, we are building code for the Win32 platform and +// may use Win32 specific functions (such as LoadString). This gives us +// a couple of nice extras for the code. +// +// Obviously, Microsoft's is not the only compiler available for Win32 out +// there. So I can't just check to see if _MSC_VER is defined to detect +// if I'm building on Win32. So for now, if you use MS Visual C++ or +// Borland's compiler, I turn this on. Otherwise you may turn it on +// yourself, if you prefer + +#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(_WIN32) + #define SS_WIN32 +#endif + +// MACRO: SS_ANSI +// -------------- +// When this macro is defined, the code attempts only to use ANSI/ISO +// standard library functions to do it's work. It will NOT attempt to use +// any Win32 of Visual C++ specific functions -- even if they are +// available. You may define this flag yourself to prevent any Win32 +// of VC++ specific functions from being called. + +// If we're not on Win32, we MUST use an ANSI build + +#ifndef SS_WIN32 + #if !defined(SS_NO_ANSI) + #define SS_ANSI + #endif +#endif + +// MACRO: SS_ALLOCA +// ---------------- +// Some implementations of the Standard C Library have a non-standard +// function known as alloca(). This functions allows one to allocate a +// variable amount of memory on the stack. It is needed to implement +// the ASCII/MBCS conversion macros. +// +// I wanted to find some way to determine automatically if alloca() is +// available on this platform via compiler flags but that is asking for +// trouble. The crude test presented here will likely need fixing on +// other platforms. Therefore I'll leave it up to you to fiddle with +// this test to determine if it exists. Just make sure SS_ALLOCA is or +// is not defined as appropriate and you control this feature. + +#if defined(_MSC_VER) && !defined(SS_ANSI) + #define SS_ALLOCA +#endif + + +// MACRO: SS_MBCS +// -------------- +// Setting this macro means you are using MBCS characters. In MSVC builds, +// this macro gets set automatically by detection of the preprocessor flag +// _MBCS. For other platforms you may set it manually if you wish. The +// only effect it currently has is to cause the allocation of more space +// for wchar_t --> char conversions. +// Note that MBCS does not mean UNICODE. +// +// #define SS_MBCS +// + +#ifdef _MBCS + #define SS_MBCS +#endif + + +// MACRO SS_NO_LOCALE +// ------------------ +// If your implementation of the Standard C++ Library lacks the header, +// you can #define this macro to make your code build properly. Note that this +// is some of my newest code and frankly I'm not very sure of it, though it does +// pass my unit tests. + +// #define SS_NO_LOCALE + + +// Compiler Error regarding _UNICODE and UNICODE +// ----------------------------------------------- +// Microsoft header files are screwy. Sometimes they depend on a preprocessor +// flag named "_UNICODE". Other times they check "UNICODE" (note the lack of +// leading underscore in the second version". In several places, they silently +// "synchronize" these two flags this by defining one of the other was defined. +// In older version of this header, I used to try to do the same thing. +// +// However experience has taught me that this is a bad idea. You get weird +// compiler errors that seem to indicate things like LPWSTR and LPTSTR not being +// equivalent in UNICODE builds, stuff like that (when they MUST be in a proper +// UNICODE build). You end up scratching your head and saying, "But that HAS +// to compile!". +// +// So what should you do if you get this error? +// +// Make sure that both macros (_UNICODE and UNICODE) are defined before this +// file is included. You can do that by either +// +// a) defining both yourself before any files get included +// b) including the proper MS headers in the proper order +// c) including this file before any other file, uncommenting +// the #defines below, and commenting out the #errors +// +// Personally I recommend solution a) but it's your call. + +#ifdef _MSC_VER + #if defined (_UNICODE) && !defined (UNICODE) + #error UNICODE defined but not UNICODE + // #define UNICODE // no longer silently fix this + #endif + #if defined (UNICODE) && !defined (_UNICODE) + #error Warning, UNICODE defined but not _UNICODE + // #define _UNICODE // no longer silently fix this + #endif +#endif + + +// ----------------------------------------------------------------------------- +// MIN and MAX. The Standard C++ template versions go by so many names (at +// at least in the MS implementation) that you never know what's available +// ----------------------------------------------------------------------------- +template +inline const Type& SSMIN(const Type& arg1, const Type& arg2) +{ + return arg2 < arg1 ? arg2 : arg1; +} +template +inline const Type& SSMAX(const Type& arg1, const Type& arg2) +{ + return arg2 > arg1 ? arg2 : arg1; +} + +// If they have not #included W32Base.h (part of my W32 utility library) then +// we need to define some stuff. Otherwise, this is all defined there. + +#if !defined(W32BASE_H) + + // If they want us to use only standard C++ stuff (no Win32 stuff) + + #ifdef SS_ANSI + + // On Win32 we have TCHAR.H so just include it. This is NOT violating + // the spirit of SS_ANSI as we are not calling any Win32 functions here. + + #ifdef SS_WIN32 + + #include + #include + #ifndef STRICT + #define STRICT + #endif + + // ... but on non-Win32 platforms, we must #define the types we need. + + #else + + typedef const char* PCSTR; + typedef char* PSTR; + typedef const wchar_t* PCWSTR; + typedef wchar_t* PWSTR; + #ifdef UNICODE + typedef wchar_t TCHAR; + #else + typedef char TCHAR; + #endif + typedef wchar_t OLECHAR; + + #endif // #ifndef _WIN32 + + + // Make sure ASSERT and verify are defined using only ANSI stuff + + #ifndef ASSERT + #include + #define ASSERT(f) assert((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #else // ...else SS_ANSI is NOT defined + + #include + #include + #ifndef STRICT + #define STRICT + #endif + + // Make sure ASSERT and verify are defined + + #ifndef ASSERT + #include + #define ASSERT(f) _ASSERTE((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #endif // #ifdef SS_ANSI + + #ifndef UNUSED + #define UNUSED(x) x + #endif + +#endif // #ifndef W32BASE_H + +// Standard headers needed + +#include // basic_string +#include // for_each, etc. +#include // for StdStringLessNoCase, et al +#ifndef SS_NO_LOCALE + #include // for various facets +#endif + +// If this is a recent enough version of VC include comdef.h, so we can write +// member functions to deal with COM types & compiler support classes e.g. +// _bstr_t + +#if defined (_MSC_VER) && (_MSC_VER >= 1100) + #include + #define SS_INC_COMDEF // signal that we #included MS comdef.h file + #define STDSTRING_INC_COMDEF + #define SS_NOTHROW __declspec(nothrow) +#else + #define SS_NOTHROW +#endif + +#ifndef TRACE + #define TRACE_DEFINED_HERE + #define TRACE +#endif + +// Microsoft defines PCSTR, PCWSTR, etc, but no PCTSTR. I hate to use the +// versions with the "L" in front of them because that's a leftover from Win 16 +// days, even though it evaluates to the same thing. Therefore, Define a PCSTR +// as an LPCTSTR. + +#if !defined(PCTSTR) && !defined(PCTSTR_DEFINED) + typedef const TCHAR* PCTSTR; + #define PCTSTR_DEFINED +#endif + +#if !defined(PCOLESTR) && !defined(PCOLESTR_DEFINED) + typedef const OLECHAR* PCOLESTR; + #define PCOLESTR_DEFINED +#endif + +#if !defined(POLESTR) && !defined(POLESTR_DEFINED) + typedef OLECHAR* POLESTR; + #define POLESTR_DEFINED +#endif + +#if !defined(PCUSTR) && !defined(PCUSTR_DEFINED) + typedef const unsigned char* PCUSTR; + typedef unsigned char* PUSTR; + #define PCUSTR_DEFINED +#endif + + +// SGI compiler 7.3 doesnt know these types - oh and btw, remember to use +// -LANG:std in the CXX Flags +#if defined(__sgi) + typedef unsigned long DWORD; + typedef void * LPCVOID; +#endif + + +// SS_USE_FACET macro and why we need it: +// +// Since I'm a good little Standard C++ programmer, I use locales. Thus, I +// need to make use of the use_facet<> template function here. Unfortunately, +// this need is complicated by the fact the MS' implementation of the Standard +// C++ Library has a non-standard version of use_facet that takes more +// arguments than the standard dictates. Since I'm trying to write CStdString +// to work with any version of the Standard library, this presents a problem. +// +// The upshot of this is that I can't do 'use_facet' directly. The MS' docs +// tell me that I have to use a macro, _USE() instead. Since _USE obviously +// won't be available in other implementations, this means that I have to write +// my OWN macro -- SS_USE_FACET -- that evaluates either to _USE or to the +// standard, use_facet. +// +// If you are having trouble with the SS_USE_FACET macro, in your implementation +// of the Standard C++ Library, you can define your own version of SS_USE_FACET. + +#ifndef schMSG + #define schSTR(x) #x + #define schSTR2(x) schSTR(x) + #define schMSG(desc) message(__FILE__ "(" schSTR2(__LINE__) "):" #desc) +#endif + +#ifndef SS_USE_FACET + + // STLPort #defines a macro (__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) for + // all MSVC builds, erroneously in my opinion. It causes problems for + // my SS_ANSI builds. In my code, I always comment out that line. You'll + // find it in \stlport\config\stl_msvc.h + + #if defined(__SGI_STL_PORT) && (__SGI_STL_PORT >= 0x400 ) + + #if defined(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) && defined(_MSC_VER) + #ifdef SS_ANSI + #pragma schMSG(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS defined!!) + #endif + #endif + #define SS_USE_FACET(loc, fac) std::use_facet(loc) + + #elif defined(_MSC_VER ) + + #define SS_USE_FACET(loc, fac) std::_USE(loc, fac) + + // ...and + #elif defined(_RWSTD_NO_TEMPLATE_ON_RETURN_TYPE) + + #define SS_USE_FACET(loc, fac) std::use_facet(loc, (fac*)0) + + #else + + #define SS_USE_FACET(loc, fac) std::use_facet(loc) + + #endif + +#endif + +// ============================================================================= +// UNICODE/MBCS conversion macros. Made to work just like the MFC/ATL ones. +// ============================================================================= + +#include // Added to Std Library with Amendment #1. + +// First define the conversion helper functions. We define these regardless of +// any preprocessor macro settings since their names won't collide. + +// Not sure if we need all these headers. I believe ANSI says we do. + +#include +#include +#include +#include +#include +#ifndef va_start + #include +#endif + + +#ifdef SS_NO_LOCALE + + #if defined(_WIN32) || defined (_WIN32_WCE) + + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCSTR pSrcA, int nSrc, + UINT acp=CP_ACP) + { + ASSERT(0 != pSrcA); + ASSERT(0 != pDstW); + pDstW[0] = '\0'; + MultiByteToWideChar(acp, 0, pSrcA, nSrc, pDstW, nDst); + return pDstW; + } + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCUSTR pSrcA, int nSrc, + UINT acp=CP_ACP) + { + return StdCodeCvt(pDstW, nDst, (PCSTR)pSrcA, nSrc, acp); + } + + inline PSTR StdCodeCvt(PSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + UINT acp=CP_ACP) + { + ASSERT(0 != pDstA); + ASSERT(0 != pSrcW); + pDstA[0] = '\0'; + WideCharToMultiByte(acp, 0, pSrcW, nSrc, pDstA, nDst, 0, 0); + return pDstA; + } + inline PUSTR StdCodeCvt(PUSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + UINT acp=CP_ACP) + { + return (PUSTR)StdCodeCvt((PSTR)pDstA, nDst, pSrcW, nSrc, acp); + } + #else + #endif + +#else + + // StdCodeCvt - made to look like Win32 functions WideCharToMultiByte + // and MultiByteToWideChar but uses locales in SS_ANSI + // builds. There are a number of overloads. + // First argument is the destination buffer. + // Second argument is the source buffer + //#if defined (SS_ANSI) || !defined (SS_WIN32) + + // 'SSCodeCvt' - shorthand name for the codecvt facet we use + + typedef std::codecvt SSCodeCvt; + + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCSTR pSrcA, int nSrc, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pSrcA); + ASSERT(0 != pDstW); + + pDstW[0] = '\0'; + + if ( nSrc > 0 ) + { + PCSTR pNextSrcA = pSrcA; + PWSTR pNextDstW = pDstW; + SSCodeCvt::result res = SSCodeCvt::ok; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= { 0 }; + res = conv.in(st, + pSrcA, pSrcA + nSrc, pNextSrcA, + pDstW, pDstW + nDst, pNextDstW); +#ifdef _LINUX +#define ASSERT2(a) if (!(a)) {fprintf(stderr, "StdString: Assertion Failed on line %d\n", __LINE__);} +#else +#define ASSERT2 ASSERT +#endif + ASSERT2(SSCodeCvt::ok == res); + ASSERT2(SSCodeCvt::error != res); + ASSERT2(pNextDstW >= pDstW); + ASSERT2(pNextSrcA >= pSrcA); +#undef ASSERT2 + // Null terminate the converted string + + if ( pNextDstW - pDstW > nDst ) + *(pDstW + nDst) = '\0'; + else + *pNextDstW = '\0'; + } + return pDstW; + } + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCUSTR pSrcA, int nSrc, + const std::locale& loc=std::locale()) + { + return StdCodeCvt(pDstW, nDst, (PCSTR)pSrcA, nSrc, loc); + } + + inline PSTR StdCodeCvt(PSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pDstA); + ASSERT(0 != pSrcW); + + pDstA[0] = '\0'; + + if ( nSrc > 0 ) + { + PSTR pNextDstA = pDstA; + PCWSTR pNextSrcW = pSrcW; + SSCodeCvt::result res = SSCodeCvt::ok; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= { 0 }; + res = conv.out(st, + pSrcW, pSrcW + nSrc, pNextSrcW, + pDstA, pDstA + nDst, pNextDstA); +#ifdef _LINUX +#define ASSERT2(a) if (!(a)) {fprintf(stderr, "StdString: Assertion Failed on line %d\n", __LINE__);} +#else +#define ASSERT2 ASSERT +#endif + ASSERT2(SSCodeCvt::error != res); + ASSERT2(SSCodeCvt::ok == res); // strict, comment out for sanity + ASSERT2(pNextDstA >= pDstA); + ASSERT2(pNextSrcW >= pSrcW); +#undef ASSERT2 + + // Null terminate the converted string + + if ( pNextDstA - pDstA > nDst ) + *(pDstA + nDst) = '\0'; + else + *pNextDstA = '\0'; + } + return pDstA; + } + + inline PUSTR StdCodeCvt(PUSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + const std::locale& loc=std::locale()) + { + return (PUSTR)StdCodeCvt((PSTR)pDstA, nDst, pSrcW, nSrc, loc); + } + +#endif + + + +// Unicode/MBCS conversion macros are only available on implementations of +// the "C" library that have the non-standard _alloca function. As far as I +// know that's only Microsoft's though I've heard that the function exists +// elsewhere. + +#if defined(SS_ALLOCA) && !defined SS_NO_CONVERSION + + #include // needed for _alloca + + // Define our conversion macros to look exactly like Microsoft's to + // facilitate using this stuff both with and without MFC/ATL + + #ifdef _CONVERSION_USES_THREAD_LOCALE + + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=GetACP(); \ + _acp; PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=GetACP();\ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)),\ + StdCodeCvt((PWSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pa, _cvt, _acp))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = sslen(_pw), \ + StdCodeCvt((LPSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pw, _cvt, _acp))) + #else + + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=CP_ACP; _acp;\ + PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=CP_ACP; \ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)),\ + StdCodeCvt((PWSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pa, _cvt))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = (sslen(_pw)),\ + StdCodeCvt((LPSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pw, _cvt))) + #endif + + #define SSA2CW(pa) ((PCWSTR)SSA2W((pa))) + #define SSW2CA(pw) ((PCSTR)SSW2A((pw))) + + #ifdef UNICODE + #define SST2A SSW2A + #define SSA2T SSA2W + #define SST2CA SSW2CA + #define SSA2CT SSA2CW + // (Did you get a compiler error here about not being able to convert + // PTSTR into PWSTR? Then your _UNICODE and UNICODE flags are messed + // up. Best bet: #define BOTH macros before including any MS headers.) + inline PWSTR SST2W(PTSTR p) { return p; } + inline PTSTR SSW2T(PWSTR p) { return p; } + inline PCWSTR SST2CW(PCTSTR p) { return p; } + inline PCTSTR SSW2CT(PCWSTR p) { return p; } + #else + #define SST2W SSA2W + #define SSW2T SSW2A + #define SST2CW SSA2CW + #define SSW2CT SSW2CA + inline PSTR SST2A(PTSTR p) { return p; } + inline PTSTR SSA2T(PSTR p) { return p; } + inline PCSTR SST2CA(PCTSTR p) { return p; } + inline PCTSTR SSA2CT(PCSTR p) { return p; } + #endif // #ifdef UNICODE + + #if defined(UNICODE) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #elif defined(OLE2ANSI) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #else + //CharNextW doesn't work on Win95 so we use this + #define SST2COLE(pa) SSA2CW((pa)) + #define SST2OLE(pa) SSA2W((pa)) + #define SSOLE2CT(po) SSW2CA((po)) + #define SSOLE2T(po) SSW2A((po)) + #endif + + #ifdef OLE2ANSI + #define SSW2OLE SSW2A + #define SSOLE2W SSA2W + #define SSW2COLE SSW2CA + #define SSOLE2CW SSA2CW + inline POLESTR SSA2OLE(PSTR p) { return p; } + inline PSTR SSOLE2A(POLESTR p) { return p; } + inline PCOLESTR SSA2COLE(PCSTR p) { return p; } + inline PCSTR SSOLE2CA(PCOLESTR p){ return p; } + #else + #define SSA2OLE SSA2W + #define SSOLE2A SSW2A + #define SSA2COLE SSA2CW + #define SSOLE2CA SSW2CA + inline POLESTR SSW2OLE(PWSTR p) { return p; } + inline PWSTR SSOLE2W(POLESTR p) { return p; } + inline PCOLESTR SSW2COLE(PCWSTR p) { return p; } + inline PCWSTR SSOLE2CW(PCOLESTR p){ return p; } + #endif + + // Above we've defined macros that look like MS' but all have + // an 'SS' prefix. Now we need the real macros. We'll either + // get them from the macros above or from MFC/ATL. + + #if defined (USES_CONVERSION) + + #define _NO_STDCONVERSION // just to be consistent + + #else + + #ifdef _MFC_VER + + #include + #define _NO_STDCONVERSION // just to be consistent + + #else + + #define USES_CONVERSION SSCVT + #define A2CW SSA2CW + #define W2CA SSW2CA + #define T2A SST2A + #define A2T SSA2T + #define T2W SST2W + #define W2T SSW2T + #define T2CA SST2CA + #define A2CT SSA2CT + #define T2CW SST2CW + #define W2CT SSW2CT + #define ocslen sslen + #define ocscpy sscpy + #define T2COLE SST2COLE + #define OLE2CT SSOLE2CT + #define T2OLE SST2COLE + #define OLE2T SSOLE2CT + #define A2OLE SSA2OLE + #define OLE2A SSOLE2A + #define W2OLE SSW2OLE + #define OLE2W SSOLE2W + #define A2COLE SSA2COLE + #define OLE2CA SSOLE2CA + #define W2COLE SSW2COLE + #define OLE2CW SSOLE2CW + + #endif // #ifdef _MFC_VER + #endif // #ifndef USES_CONVERSION +#endif // #ifndef SS_NO_CONVERSION + +// Define ostring - generic name for std::basic_string + +#if !defined(ostring) && !defined(OSTRING_DEFINED) + typedef std::basic_string ostring; + #define OSTRING_DEFINED +#endif + +// StdCodeCvt when there's no conversion to be done +template +inline T* StdCodeCvt(T* pDst, int nDst, const T* pSrc, int nSrc) +{ + int nChars = SSMIN(nSrc, nDst); + + if ( nChars > 0 ) + { + pDst[0] = '\0'; + std::basic_string::traits_type::copy(pDst, pSrc, nChars); +// std::char_traits::copy(pDst, pSrc, nChars); + pDst[nChars] = '\0'; + } + + return pDst; +} +inline PSTR StdCodeCvt(PSTR pDst, int nDst, PCUSTR pSrc, int nSrc) +{ + return StdCodeCvt(pDst, nDst, (PCSTR)pSrc, nSrc); +} +inline PUSTR StdCodeCvt(PUSTR pDst, int nDst, PCSTR pSrc, int nSrc) +{ + return (PUSTR)StdCodeCvt((PSTR)pDst, nDst, pSrc, nSrc); +} + +// Define tstring -- generic name for std::basic_string + +#if !defined(tstring) && !defined(TSTRING_DEFINED) + typedef std::basic_string tstring; + #define TSTRING_DEFINED +#endif + +// a very shorthand way of applying the fix for KB problem Q172398 +// (basic_string assignment bug) + +#if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + #define Q172398(x) (x).erase() +#else + #define Q172398(x) +#endif + +// ============================================================================= +// INLINE FUNCTIONS ON WHICH CSTDSTRING RELIES +// +// Usually for generic text mapping, we rely on preprocessor macro definitions +// to map to string functions. However the CStdStr<> template cannot use +// macro-based generic text mappings because its character types do not get +// resolved until template processing which comes AFTER macro processing. In +// other words, the preprocessor macro UNICODE is of little help to us in the +// CStdStr template +// +// Therefore, to keep the CStdStr declaration simple, we have these inline +// functions. The template calls them often. Since they are inline (and NOT +// exported when this is built as a DLL), they will probably be resolved away +// to nothing. +// +// Without these functions, the CStdStr<> template would probably have to broken +// out into two, almost identical classes. Either that or it would be a huge, +// convoluted mess, with tons of "if" statements all over the place checking the +// size of template parameter CT. +// ============================================================================= + +#ifdef SS_NO_LOCALE + + // -------------------------------------------------------------------------- + // Win32 GetStringTypeEx wrappers + // -------------------------------------------------------------------------- + inline bool wsGetStringType(LCID lc, DWORD dwT, PCSTR pS, int nSize, + WORD* pWd) + { + return FALSE != GetStringTypeExA(lc, dwT, pS, nSize, pWd); + } + inline bool wsGetStringType(LCID lc, DWORD dwT, PCWSTR pS, int nSize, + WORD* pWd) + { + return FALSE != GetStringTypeExW(lc, dwT, pS, nSize, pWd); + } + + + template + inline bool ssisspace (CT t) + { + WORD toYourMother; + return wsGetStringType(GetThreadLocale(), CT_CTYPE1, &t, 1, &toYourMother) + && 0 != (C1_BLANK & toYourMother); + } + +#endif + +// If they defined SS_NO_REFCOUNT, then we must convert all assignments + +#if defined (_MSC_VER) && (_MSC_VER < 1300) + #ifdef SS_NO_REFCOUNT + #define SSREF(x) (x).c_str() + #else + #define SSREF(x) (x) + #endif +#else + #define SSREF(x) (x) +#endif + +// ----------------------------------------------------------------------------- +// sslen: strlen/wcslen wrappers +// ----------------------------------------------------------------------------- +template inline int sslen(const CT* pT) +{ + return 0 == pT ? 0 : (int)std::basic_string::traits_type::length(pT); +// return 0 == pT ? 0 : std::char_traits::length(pT); +} +inline SS_NOTHROW int sslen(const std::string& s) +{ + return static_cast(s.length()); +} +inline SS_NOTHROW int sslen(const std::wstring& s) +{ + return static_cast(s.length()); +} + +// ----------------------------------------------------------------------------- +// sstolower/sstoupper -- convert characters to upper/lower case +// ----------------------------------------------------------------------------- + +#ifdef SS_NO_LOCALE + inline char sstoupper(char ch) { return (char)::toupper(ch); } + inline wchar_t sstoupper(wchar_t ch){ return (wchar_t)::towupper(ch); } + inline char sstolower(char ch) { return (char)::tolower(ch); } + inline wchar_t sstolower(wchar_t ch){ return (wchar_t)::tolower(ch); } +#else + template + inline CT sstolower(const CT& t, const std::locale& loc = std::locale()) + { + return std::tolower(t, loc); + } + template + inline CT sstoupper(const CT& t, const std::locale& loc = std::locale()) + { + return std::toupper(t, loc); + } +#endif + +// ----------------------------------------------------------------------------- +// ssasn: assignment functions -- assign "sSrc" to "sDst" +// ----------------------------------------------------------------------------- +typedef std::string::size_type SS_SIZETYPE; // just for shorthand, really +typedef std::string::pointer SS_PTRTYPE; +typedef std::wstring::size_type SW_SIZETYPE; +typedef std::wstring::pointer SW_PTRTYPE; + + +template +inline void ssasn(std::basic_string& sDst, const std::basic_string& sSrc) +{ + if ( sDst.c_str() != sSrc.c_str() ) + { + sDst.erase(); + sDst.assign(SSREF(sSrc)); + } +} +template +inline void ssasn(std::basic_string& sDst, const T *pA) +{ + // Watch out for NULLs, as always. + + if ( 0 == pA ) + { + sDst.erase(); + } + + // If pA actually points to part of sDst, we must NOT erase(), but + // rather take a substring + + else if ( pA >= sDst.c_str() && pA <= sDst.c_str() + sDst.size() ) + { + sDst =sDst.substr(static_cast::size_type>(pA-sDst.c_str())); + } + + // Otherwise (most cases) apply the assignment bug fix, if applicable + // and do the assignment + + else + { + Q172398(sDst); + sDst.assign(pA); + } +} +inline void ssasn(std::string& sDst, const std::wstring& sSrc) +{ + if ( sSrc.empty() ) + { + sDst.erase(); + } + else + { + int nDst = static_cast(sSrc.size()); + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + // In MBCS builds, we don't know how long the destination string will be. + nDst = static_cast(static_cast(nDst) * 1.3); + sDst.resize(nDst+1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), static_cast(sSrc.size())); + sDst.resize(sslen(szCvt)); +#else + sDst.resize(nDst+1); + StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), static_cast(sSrc.size())); + sDst.resize(sSrc.size()); +#endif + } +} +inline void ssasn(std::string& sDst, PCWSTR pW) +{ + int nSrc = sslen(pW); + if ( nSrc > 0 ) + { + int nSrc = sslen(pW); + int nDst = nSrc; + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + nDst = static_cast(static_cast(nDst) * 1.3); + // In MBCS builds, we don't know how long the destination string will be. + sDst.resize(nDst + 1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + pW, nSrc); + sDst.resize(sslen(szCvt)); +#else + sDst.resize(nDst + 1); + StdCodeCvt(const_cast(sDst.data()), nDst, pW, nSrc); + sDst.resize(nDst); +#endif + } + else + { + sDst.erase(); + } +} +inline void ssasn(std::string& sDst, const int nNull) +{ + //UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(""); +} +#undef StrSizeType +inline void ssasn(std::wstring& sDst, const std::string& sSrc) +{ + if ( sSrc.empty() ) + { + sDst.erase(); + } + else + { + int nSrc = static_cast(sSrc.size()); + int nDst = nSrc; + + sDst.resize(nSrc+1); + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), nSrc); + + sDst.resize(sslen(szCvt)); + } +} +inline void ssasn(std::wstring& sDst, PCSTR pA) +{ + int nSrc = sslen(pA); + + if ( 0 == nSrc ) + { + sDst.erase(); + } + else + { + int nDst = nSrc; + sDst.resize(nDst+1); + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, pA, + nSrc); + + sDst.resize(sslen(szCvt)); + } +} +inline void ssasn(std::wstring& sDst, const int nNull) +{ + //UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(L""); +} + +// ----------------------------------------------------------------------------- +// ssadd: string object concatenation -- add second argument to first +// ----------------------------------------------------------------------------- +inline void ssadd(std::string& sDst, const std::wstring& sSrc) +{ + int nSrc = static_cast(sSrc.size()); + + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + int nAdd = nSrc; + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + nAdd = static_cast(static_cast(nAdd) * 1.3); + sDst.resize(nDst+nAdd+1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nAdd, sSrc.c_str(), nSrc); + sDst.resize(nDst + sslen(szCvt)); +#else + sDst.resize(nDst+nAdd+1); + StdCodeCvt(const_cast(sDst.data()+nDst), nAdd, sSrc.c_str(), nSrc); + sDst.resize(nDst + nAdd); +#endif + } +} +template +inline void ssadd(typename std::basic_string& sDst, const typename std::basic_string& sSrc) +{ + sDst += sSrc; +} +inline void ssadd(std::string& sDst, PCWSTR pW) +{ + int nSrc = sslen(pW); + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + int nAdd = nSrc; + +#ifdef SS_MBCS + nAdd = static_cast(static_cast(nAdd) * 1.3); + sDst.resize(nDst + nAdd + 1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nAdd, pW, nSrc); + sDst.resize(nDst + sslen(szCvt)); +#else + sDst.resize(nDst + nAdd + 1); + StdCodeCvt(const_cast(sDst.data()+nDst), nAdd, pW, nSrc); + sDst.resize(nDst + nSrc); +#endif + } +} +template +inline void ssadd(typename std::basic_string& sDst, const T *pA) +{ + if ( pA ) + { + // If the string being added is our internal string or a part of our + // internal string, then we must NOT do any reallocation without + // first copying that string to another object (since we're using a + // direct pointer) + + if ( pA >= sDst.c_str() && pA <= sDst.c_str()+sDst.length()) + { + if ( sDst.capacity() <= sDst.size()+sslen(pA) ) + sDst.append(std::basic_string(pA)); + else + sDst.append(pA); + } + else + { + sDst.append(pA); + } + } +} +inline void ssadd(std::wstring& sDst, const std::string& sSrc) +{ + if ( !sSrc.empty() ) + { + int nSrc = static_cast(sSrc.size()); + int nDst = static_cast(sDst.size()); + + sDst.resize(nDst + nSrc + 1); +#ifdef SS_MBCS + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nSrc, sSrc.c_str(), nSrc+1); + sDst.resize(nDst + sslen(szCvt)); +#else + StdCodeCvt(const_cast(sDst.data()+nDst), nSrc, sSrc.c_str(), nSrc+1); + sDst.resize(nDst + nSrc); +#endif + } +} +inline void ssadd(std::wstring& sDst, PCSTR pA) +{ + int nSrc = sslen(pA); + + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + + sDst.resize(nDst + nSrc + 1); +#ifdef SS_MBCS + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nSrc, pA, nSrc+1); + sDst.resize(nDst + sslen(szCvt)); +#else + StdCodeCvt(const_cast(sDst.data()+nDst), nSrc, pA, nSrc+1); + sDst.resize(nDst + nSrc); +#endif + } +} + +// ----------------------------------------------------------------------------- +// sscmp: comparison (case sensitive, not affected by locale) +// ----------------------------------------------------------------------------- +template +inline int sscmp(const CT* pA1, const CT* pA2) +{ + CT f; + CT l; + + do + { + f = *(pA1++); + l = *(pA2++); + } while ( (f) && (f == l) ); + + return (int)(f - l); +} + +// ----------------------------------------------------------------------------- +// ssicmp: comparison (case INsensitive, not affected by locale) +// ----------------------------------------------------------------------------- +template +inline int ssicmp(const CT* pA1, const CT* pA2) +{ + // Using the "C" locale = "not affected by locale" + + std::locale loc = std::locale::classic(); + const std::ctype& ct = SS_USE_FACET(loc, std::ctype); + CT f; + CT l; + + do + { + f = ct.tolower(*(pA1++)); + l = ct.tolower(*(pA2++)); + } while ( (f) && (f == l) ); + + return (int)(f - l); +} + +// ----------------------------------------------------------------------------- +// ssupr/sslwr: Uppercase/Lowercase conversion functions +// ----------------------------------------------------------------------------- + +template +inline void sslwr(CT* pT, size_t nLen, const std::locale& loc=std::locale()) +{ + SS_USE_FACET(loc, std::ctype).tolower(pT, pT+nLen); +} +template +inline void ssupr(CT* pT, size_t nLen, const std::locale& loc=std::locale()) +{ + SS_USE_FACET(loc, std::ctype).toupper(pT, pT+nLen); +} + +// ----------------------------------------------------------------------------- +// vsprintf/vswprintf or _vsnprintf/_vsnwprintf equivalents. In standard +// builds we can't use _vsnprintf/_vsnwsprintf because they're MS extensions. +// +// ----------------------------------------------------------------------------- +// Borland's headers put some ANSI "C" functions in the 'std' namespace. +// Promote them to the global namespace so we can use them here. + +#if defined(__BORLANDC__) + using std::vsprintf; + using std::vswprintf; +#endif + + // GNU is supposed to have vsnprintf and vsnwprintf. But only the newer + // distributions do. + +#if defined(__GNUC__) + + inline int ssvsprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + return vsnprintf(pA, nCount, pFmtA, vl); + } + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + return vswprintf(pW, nCount, pFmtW, vl); + } + + // Microsofties can use +#elif defined(_MSC_VER) && !defined(SS_ANSI) + + inline int ssvsprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + return _vsnprintf(pA, nCount, pFmtA, vl); + } + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + return _vsnwprintf(pW, nCount, pFmtW, vl); + } + +#elif defined (SS_DANGEROUS_FORMAT) // ignore buffer size parameter if needed? + + inline int ssvsprintf(PSTR pA, size_t /*nCount*/, PCSTR pFmtA, va_list vl) + { + return vsprintf(pA, pFmtA, vl); + } + + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + // JMO: Some distributions of the "C" have a version of vswprintf that + // takes 3 arguments (e.g. Microsoft, Borland, GNU). Others have a + // version which takes 4 arguments (an extra "count" argument in the + // second position. The best stab I can take at this so far is that if + // you are NOT running with MS, Borland, or GNU, then I'll assume you + // have the version that takes 4 arguments. + // + // I'm sure that these checks don't catch every platform correctly so if + // you get compiler errors on one of the lines immediately below, it's + // probably because your implemntation takes a different number of + // arguments. You can comment out the offending line (and use the + // alternate version) or you can figure out what compiler flag to check + // and add that preprocessor check in. Regardless, if you get an error + // on these lines, I'd sure like to hear from you about it. + // + // Thanks to Ronny Schulz for the SGI-specific checks here. + +// #if !defined(__MWERKS__) && !defined(__SUNPRO_CC_COMPAT) && !defined(__SUNPRO_CC) + #if !defined(_MSC_VER) \ + && !defined (__BORLANDC__) \ + && !defined(__GNUC__) \ + && !defined(__sgi) + + return vswprintf(pW, nCount, pFmtW, vl); + + // suddenly with the current SGI 7.3 compiler there is no such function as + // vswprintf and the substitute needs explicit casts to compile + + #elif defined(__sgi) + + nCount; + return vsprintf( (char *)pW, (char *)pFmtW, vl); + + #else + + nCount; + return vswprintf(pW, pFmtW, vl); + + #endif + + } + +#endif + + // GOT COMPILER PROBLEMS HERE? + // --------------------------- + // Does your compiler choke on one or more of the following 2 functions? It + // probably means that you don't have have either vsnprintf or vsnwprintf in + // your version of the CRT. This is understandable since neither is an ANSI + // "C" function. However it still leaves you in a dilemma. In order to make + // this code build, you're going to have to to use some non-length-checked + // formatting functions that every CRT has: vsprintf and vswprintf. + // + // This is very dangerous. With the proper erroneous (or malicious) code, it + // can lead to buffer overlows and crashing your PC. Use at your own risk + // In order to use them, just #define SS_DANGEROUS_FORMAT at the top of + // this file. + // + // Even THEN you might not be all the way home due to some non-conforming + // distributions. More on this in the comments below. + + inline int ssnprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + #ifdef _MSC_VER + return _vsnprintf(pA, nCount, pFmtA, vl); + #else + return vsnprintf(pA, nCount, pFmtA, vl); + #endif + } + inline int ssnprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + #ifdef _MSC_VER + return _vsnwprintf(pW, nCount, pFmtW, vl); + #else + return vswprintf(pW, nCount, pFmtW, vl); + #endif + } + + + + +// ----------------------------------------------------------------------------- +// ssload: Type safe, overloaded ::LoadString wrappers +// There is no equivalent of these in non-Win32-specific builds. However, I'm +// thinking that with the message facet, there might eventually be one +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline int ssload(HMODULE hInst, UINT uId, PSTR pBuf, int nMax) + { + return ::LoadStringA(hInst, uId, pBuf, nMax); + } + inline int ssload(HMODULE hInst, UINT uId, PWSTR pBuf, int nMax) + { + return ::LoadStringW(hInst, uId, pBuf, nMax); + } +#if defined ( _MSC_VER ) && ( _MSC_VER >= 1500 ) + inline int ssload(HMODULE hInst, UINT uId, uint16_t *pBuf, int nMax) + { + return 0; + } + inline int ssload(HMODULE hInst, UINT uId, uint32_t *pBuf, int nMax) + { + return 0; + } +#endif +#endif + + +// ----------------------------------------------------------------------------- +// sscoll/ssicoll: Collation wrappers +// Note -- with MSVC I have reversed the arguments order here because the +// functions appear to return the opposite of what they should +// ----------------------------------------------------------------------------- +#ifndef SS_NO_LOCALE +template +inline int sscoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::collate& coll = + SS_USE_FACET(std::locale(), std::collate); + + return coll.compare(sz2, sz2+nLen2, sz1, sz1+nLen1); +} +template +inline int ssicoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::locale loc; + const std::collate& coll = SS_USE_FACET(loc, std::collate); + + // Some implementations seem to have trouble using the collate<> + // facet typedefs so we'll just default to basic_string and hope + // that's what the collate facet uses (which it generally should) + +// std::collate::string_type s1(sz1); +// std::collate::string_type s2(sz2); + const std::basic_string sEmpty; + std::basic_string s1(sz1 ? sz1 : sEmpty.c_str()); + std::basic_string s2(sz2 ? sz2 : sEmpty.c_str()); + + sslwr(const_cast(s1.c_str()), nLen1, loc); + sslwr(const_cast(s2.c_str()), nLen2, loc); + return coll.compare(s2.c_str(), s2.c_str()+nLen2, + s1.c_str(), s1.c_str()+nLen1); +} +#endif + + +// ----------------------------------------------------------------------------- +// ssfmtmsg: FormatMessage equivalents. Needed because I added a CString facade +// Again -- no equivalent of these on non-Win32 builds but their might one day +// be one if the message facet gets implemented +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageA(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PWSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageW(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } +#else +#endif + + + +// FUNCTION: sscpy. Copies up to 'nMax' characters from pSrc to pDst. +// ----------------------------------------------------------------------------- +// FUNCTION: sscpy +// inline int sscpy(PSTR pDst, PCSTR pSrc, int nMax=-1); +// inline int sscpy(PUSTR pDst, PCSTR pSrc, int nMax=-1) +// inline int sscpy(PSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCSTR pSrc, int nMax=-1); +// +// DESCRIPTION: +// This function is very much (but not exactly) like strcpy. These +// overloads simplify copying one C-style string into another by allowing +// the caller to specify two different types of strings if necessary. +// +// The strings must NOT overlap +// +// "Character" is expressed in terms of the destination string, not +// the source. If no 'nMax' argument is supplied, then the number of +// characters copied will be sslen(pSrc). A NULL terminator will +// also be added so pDst must actually be big enough to hold nMax+1 +// characters. The return value is the number of characters copied, +// not including the NULL terminator. +// +// PARAMETERS: +// pSrc - the string to be copied FROM. May be a char based string, an +// MBCS string (in Win32 builds) or a wide string (wchar_t). +// pSrc - the string to be copied TO. Also may be either MBCS or wide +// nMax - the maximum number of characters to be copied into szDest. Note +// that this is expressed in whatever a "character" means to pDst. +// If pDst is a wchar_t type string than this will be the maximum +// number of wchar_ts that my be copied. The pDst string must be +// large enough to hold least nMaxChars+1 characters. +// If the caller supplies no argument for nMax this is a signal to +// the routine to copy all the characters in pSrc, regardless of +// how long it is. +// +// RETURN VALUE: none +// ----------------------------------------------------------------------------- + +template +inline int sscpycvt(CT1* pDst, const CT2* pSrc, int nMax) +{ + // Note -- we assume pDst is big enough to hold pSrc. If not, we're in + // big trouble. No bounds checking. Caveat emptor. + + int nSrc = sslen(pSrc); + + const CT1* szCvt = StdCodeCvt(pDst, nMax, pSrc, nSrc); + + // If we're copying the same size characters, then all the "code convert" + // just did was basically memcpy so the #of characters copied is the same + // as the number requested. I should probably specialize this function + // template to achieve this purpose as it is silly to do a runtime check + // of a fact known at compile time. I'll get around to it. + + return sslen(szCvt); +} + +template +inline int sscpycvt(T* pDst, const T* pSrc, int nMax) +{ + int nCount = nMax; + for (; nCount > 0 && *pSrc; ++pSrc, ++pDst, --nCount) + std::basic_string::traits_type::assign(*pDst, *pSrc); + + *pDst = 0; + return nMax - nCount; +} + +inline int sscpycvt(PWSTR pDst, PCSTR pSrc, int nMax) +{ + // Note -- we assume pDst is big enough to hold pSrc. If not, we're in + // big trouble. No bounds checking. Caveat emptor. + + const PWSTR szCvt = StdCodeCvt(pDst, nMax, pSrc, nMax); + return sslen(szCvt); +} + +template +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax, int nLen) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, nLen)); +} +template +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, sslen(pSrc))); +} +template +inline int sscpy(CT1* pDst, const CT2* pSrc) +{ + return sscpycvt(pDst, pSrc, sslen(pSrc)); +} +template +inline int sscpy(CT1* pDst, const std::basic_string& sSrc, int nMax) +{ + return sscpycvt(pDst, sSrc.c_str(), SSMIN(nMax, (int)sSrc.length())); +} +template +inline int sscpy(CT1* pDst, const std::basic_string& sSrc) +{ + return sscpycvt(pDst, sSrc.c_str(), (int)sSrc.length()); +} + +#ifdef SS_INC_COMDEF + template + inline int sscpy(CT1* pDst, const _bstr_t& bs, int nMax) + { + return sscpycvt(pDst, static_cast(bs), + SSMIN(nMax, static_cast(bs.length()))); + } + template + inline int sscpy(CT1* pDst, const _bstr_t& bs) + { + return sscpy(pDst, bs, static_cast(bs.length())); + } +#endif + + +// ----------------------------------------------------------------------------- +// Functional objects for changing case. They also let you pass locales +// ----------------------------------------------------------------------------- + +#ifdef SS_NO_LOCALE + template + struct SSToUpper : public std::unary_function + { + inline CT operator()(const CT& t) const + { + return sstoupper(t); + } + }; + template + struct SSToLower : public std::unary_function + { + inline CT operator()(const CT& t) const + { + return sstolower(t); + } + }; +#else + template + struct SSToUpper : public std::binary_function + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstoupper(t, loc); + } + }; + template + struct SSToLower : public std::binary_function + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstolower(t, loc); + } + }; +#endif + +// This struct is used for TrimRight() and TrimLeft() function implementations. +//template +//struct NotSpace : public std::unary_function +//{ +// const std::locale& loc; +// inline NotSpace(const std::locale& locArg) : loc(locArg) {} +// inline bool operator() (CT t) { return !std::isspace(t, loc); } +//}; +template +struct NotSpace : public std::unary_function +{ + // DINKUMWARE BUG: + // Note -- using std::isspace in a COM DLL gives us access violations + // because it causes the dynamic addition of a function to be called + // when the library shuts down. Unfortunately the list is maintained + // in DLL memory but the function is in static memory. So the COM DLL + // goes away along with the function that was supposed to be called, + // and then later when the DLL CRT shuts down it unloads the list and + // tries to call the long-gone function. + // This is DinkumWare's implementation problem. If you encounter this + // problem, you may replace the calls here with good old isspace() and + // iswspace() from the CRT unless they specify SS_ANSI + +#ifdef SS_NO_LOCALE + + bool operator() (CT t) const { return !ssisspace(t); } + +#else + const std::locale loc; + NotSpace(const std::locale& locArg=std::locale()) : loc(locArg) {} + bool operator() (CT t) const { return !std::isspace(t, loc); } +#endif +}; + + + + +// Now we can define the template (finally!) +// ============================================================================= +// TEMPLATE: CStdStr +// template class CStdStr : public std::basic_string +// +// REMARKS: +// This template derives from basic_string and adds some MFC CString- +// like functionality +// +// Basically, this is my attempt to make Standard C++ library strings as +// easy to use as the MFC CString class. +// +// Note that although this is a template, it makes the assumption that the +// template argument (CT, the character type) is either char or wchar_t. +// ============================================================================= + +//#define CStdStr _SS // avoid compiler warning 4786 + +// template ARG& FmtArg(ARG& arg) { return arg; } +// PCSTR FmtArg(const std::string& arg) { return arg.c_str(); } +// PCWSTR FmtArg(const std::wstring& arg) { return arg.c_str(); } + +template +struct FmtArg +{ + explicit FmtArg(const ARG& arg) : a_(arg) {} + const ARG& operator()() const { return a_; } + const ARG& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; + +template +class CStdStr : public std::basic_string +{ + // Typedefs for shorter names. Using these names also appears to help + // us avoid some ambiguities that otherwise arise on some platforms + + #define MYBASE std::basic_string // my base class + //typedef typename std::basic_string MYBASE; // my base class + typedef CStdStr MYTYPE; // myself + typedef typename MYBASE::const_pointer PCMYSTR; // PCSTR or PCWSTR + typedef typename MYBASE::pointer PMYSTR; // PSTR or PWSTR + typedef typename MYBASE::iterator MYITER; // my iterator type + typedef typename MYBASE::const_iterator MYCITER; // you get the idea... + typedef typename MYBASE::reverse_iterator MYRITER; + typedef typename MYBASE::size_type MYSIZE; + typedef typename MYBASE::value_type MYVAL; + typedef typename MYBASE::allocator_type MYALLOC; + +public: + // shorthand conversion from PCTSTR to string resource ID + #define SSRES(pctstr) LOWORD(reinterpret_cast(pctstr)) + + bool TryLoad(const void* pT) + { + bool bLoaded = false; + +#if defined(SS_WIN32) && !defined(SS_ANSI) + if ( ( pT != NULL ) && SS_IS_INTRESOURCE(pT) ) + { + UINT nId = LOWORD(reinterpret_cast(pT)); + if ( !LoadString(nId) ) + { + TRACE(_T("Can't load string %u\n"), SSRES(pT)); + } + bLoaded = true; + } +#endif + + return bLoaded; + } + + + // CStdStr inline constructors + CStdStr() + { + } + + CStdStr(const MYTYPE& str) : MYBASE(SSREF(str)) + { + } + + CStdStr(const std::string& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(const std::wstring& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(PCMYSTR pT, MYSIZE n) : MYBASE(pT, n) + { + } + +#ifdef SS_UNSIGNED + CStdStr(PCUSTR pU) + { + *this = reinterpret_cast(pU); + } +#endif + + CStdStr(PCSTR pA) + { + #ifdef SS_ANSI + *this = pA; + #else + if ( !TryLoad(pA) ) + *this = pA; + #endif + } + + CStdStr(PCWSTR pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(uint16_t* pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(uint32_t* pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(MYCITER first, MYCITER last) + : MYBASE(first, last) + { + } + + CStdStr(MYSIZE nSize, MYVAL ch, const MYALLOC& al=MYALLOC()) + : MYBASE(nSize, ch, al) + { + } + + #ifdef SS_INC_COMDEF + CStdStr(const _bstr_t& bstr) + { + if ( bstr.length() > 0 ) + this->append(static_cast(bstr), bstr.length()); + } + #endif + + // CStdStr inline assignment operators -- the ssasn function now takes care + // of fixing the MSVC assignment bug (see knowledge base article Q172398). + MYTYPE& operator=(const MYTYPE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::string& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::wstring& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(PCSTR pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(PCWSTR pW) + { + ssasn(*this, pW); + return *this; + } + +#ifdef SS_UNSIGNED + MYTYPE& operator=(PCUSTR pU) + { + ssasn(*this, reinterpret_cast(pU)); + return *this; + } +#endif + + MYTYPE& operator=(uint16_t* pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(uint32_t* pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(CT t) + { + Q172398(*this); + this->assign(1, t); + return *this; + } + + #ifdef SS_INC_COMDEF + MYTYPE& operator=(const _bstr_t& bstr) + { + if ( bstr.length() > 0 ) + { + this->assign(static_cast(bstr), bstr.length()); + return *this; + } + else + { + this->erase(); + return *this; + } + } + #endif + + + // Overloads also needed to fix the MSVC assignment bug (KB: Q172398) + // *** Thanks to Pete The Plumber for catching this one *** + // They also are compiled if you have explicitly turned off refcounting + #if ( defined(_MSC_VER) && ( _MSC_VER < 1200 ) ) || defined(SS_NO_REFCOUNT) + + MYTYPE& assign(const MYTYPE& str) + { + Q172398(*this); + sscpy(GetBuffer(str.size()+1), SSREF(str)); + this->ReleaseBuffer(str.size()); + return *this; + } + + MYTYPE& assign(const MYTYPE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Poll�hne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + MYTYPE strTemp(str.c_str()+nStart, nChars); + Q172398(*this); + this->assign(strTemp); + return *this; + } + + MYTYPE& assign(const MYBASE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& assign(const MYBASE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Poll�hne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + + // Watch out for assignment to self + + if ( this == &str ) + { + MYTYPE strTemp(str.c_str() + nStart, nChars); + static_cast(this)->assign(strTemp); + } + else + { + Q172398(*this); + static_cast(this)->assign(str.c_str()+nStart, nChars); + } + return *this; + } + + MYTYPE& assign(const CT* pC, MYSIZE nChars) + { + // Q172398 only fix -- erase before assigning, but not if we're + // assigning from our own buffer + + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + if ( !this->empty() && + ( pC < this->data() || pC > this->data() + this->capacity() ) ) + { + this->erase(); + } + #endif + Q172398(*this); + static_cast(this)->assign(pC, nChars); + return *this; + } + + MYTYPE& assign(MYSIZE nChars, MYVAL val) + { + Q172398(*this); + static_cast(this)->assign(nChars, val); + return *this; + } + + MYTYPE& assign(const CT* pT) + { + return this->assign(pT, MYBASE::traits_type::length(pT)); + } + + MYTYPE& assign(MYCITER iterFirst, MYCITER iterLast) + { + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + // Q172398 fix. don't call erase() if we're assigning from ourself + if ( iterFirst < this->begin() || + iterFirst > this->begin() + this->size() ) + { + this->erase() + } + #endif + this->replace(this->begin(), this->end(), iterFirst, iterLast); + return *this; + } + #endif + + + // ------------------------------------------------------------------------- + // CStdStr inline concatenation. + // ------------------------------------------------------------------------- + MYTYPE& operator+=(const MYTYPE& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::string& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::wstring& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(PCSTR pA) + { + ssadd(*this, pA); + return *this; + } + + MYTYPE& operator+=(PCWSTR pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(uint16_t* pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(uint32_t* pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(CT t) + { + this->append(1, t); + return *this; + } + #ifdef SS_INC_COMDEF // if we have _bstr_t, define a += for it too. + MYTYPE& operator+=(const _bstr_t& bstr) + { + return this->operator+=(static_cast(bstr)); + } + #endif + + + // ------------------------------------------------------------------------- + // Case changing functions + // ------------------------------------------------------------------------- + + MYTYPE& ToUpper(const std::locale& loc=std::locale()) + { + // Note -- if there are any MBCS character sets in which the lowercase + // form a character takes up a different number of bytes than the + // uppercase form, this would probably not work... + + std::transform(this->begin(), + this->end(), + this->begin(), +#ifdef SS_NO_LOCALE + SSToUpper()); +#else + std::bind2nd(SSToUpper(), loc)); +#endif + + // ...but if it were, this would probably work better. Also, this way + // seems to be a bit faster when anything other then the "C" locale is + // used... + +// if ( !empty() ) +// { +// ssupr(this->GetBuf(), this->size(), loc); +// this->RelBuf(); +// } + + return *this; + } + + MYTYPE& ToLower(const std::locale& loc=std::locale()) + { + // Note -- if there are any MBCS character sets in which the lowercase + // form a character takes up a different number of bytes than the + // uppercase form, this would probably not work... + + std::transform(this->begin(), + this->end(), + this->begin(), +#ifdef SS_NO_LOCALE + SSToLower()); +#else + std::bind2nd(SSToLower(), loc)); +#endif + + // ...but if it were, this would probably work better. Also, this way + // seems to be a bit faster when anything other then the "C" locale is + // used... + +// if ( !empty() ) +// { +// sslwr(this->GetBuf(), this->size(), loc); +// this->RelBuf(); +// } + return *this; + } + + + MYTYPE& Normalize() + { + return Trim().ToLower(); + } + + + // ------------------------------------------------------------------------- + // CStdStr -- Direct access to character buffer. In the MS' implementation, + // the at() function that we use here also calls _Freeze() providing us some + // protection from multithreading problems associated with ref-counting. + // In VC 7 and later, of course, the ref-counting stuff is gone. + // ------------------------------------------------------------------------- + + CT* GetBuf(int nMinLen=-1) + { + if ( static_cast(this->size()) < nMinLen ) + this->resize(static_cast(nMinLen)); + + return this->empty() ? const_cast(this->data()) : &(this->at(0)); + } + + CT* SetBuf(int nLen) + { + nLen = ( nLen > 0 ? nLen : 0 ); + if ( this->capacity() < 1 && nLen == 0 ) + this->resize(1); + + this->resize(static_cast(nLen)); + return const_cast(this->data()); + } + void RelBuf(int nNewLen=-1) + { + this->resize(static_cast(nNewLen > -1 ? nNewLen : + sslen(this->c_str()))); + } + + void BufferRel() { RelBuf(); } // backwards compatability + CT* Buffer() { return GetBuf(); } // backwards compatability + CT* BufferSet(int nLen) { return SetBuf(nLen);}// backwards compatability + + bool Equals(const CT* pT, bool bUseCase=false) const + { + return 0 == (bUseCase ? this->compare(pT) : ssicmp(this->c_str(), pT)); + } + + // ------------------------------------------------------------------------- + // FUNCTION: CStdStr::Load + // REMARKS: + // Loads string from resource specified by nID + // + // PARAMETERS: + // nID - resource Identifier. Purely a Win32 thing in this case + // + // RETURN VALUE: + // true if successful, false otherwise + // ------------------------------------------------------------------------- + +#ifndef SS_ANSI + + bool Load(UINT nId, HMODULE hModule=NULL) + { + bool bLoaded = false; // set to true of we succeed. + + #ifdef _MFC_VER // When in Rome (or MFC land)... + + // If they gave a resource handle, use it. Note - this is archaic + // and not really what I would recommend. But then again, in MFC + // land, you ought to be using CString for resources anyway since + // it walks the resource chain for you. + + HMODULE hModuleOld = NULL; + + if ( NULL != hModule ) + { + hModuleOld = AfxGetResourceHandle(); + AfxSetResourceHandle(hModule); + } + + // ...load the string + + CString strRes; + bLoaded = FALSE != strRes.LoadString(nId); + + // ...and if we set the resource handle, restore it. + + if ( NULL != hModuleOld ) + AfxSetResourceHandle(hModule); + + if ( bLoaded ) + *this = strRes; + + #else // otherwise make our own hackneyed version of CString's Load + + // Get the resource name and module handle + + if ( NULL == hModule ) + hModule = GetResourceHandle(); + + PCTSTR szName = MAKEINTRESOURCE((nId>>4)+1); // lifted + DWORD dwSize = 0; + + // No sense continuing if we can't find the resource + + HRSRC hrsrc = ::FindResource(hModule, szName, RT_STRING); + + if ( NULL == hrsrc ) + { + TRACE(_T("Cannot find resource %d: 0x%X"), nId, ::GetLastError()); + } + else if ( 0 == (dwSize = ::SizeofResource(hModule, hrsrc) / sizeof(CT))) + { + TRACE(_T("Cant get size of resource %d 0x%X\n"),nId,GetLastError()); + } + else + { + bLoaded = 0 != ssload(hModule, nId, GetBuf(dwSize), dwSize); + ReleaseBuffer(); + } + + #endif // #ifdef _MFC_VER + + if ( !bLoaded ) + TRACE(_T("String not loaded 0x%X\n"), ::GetLastError()); + + return bLoaded; + } + +#endif // #ifdef SS_ANSI + + // ------------------------------------------------------------------------- + // FUNCTION: CStdStr::Format + // void _cdecl Formst(CStdStringA& PCSTR szFormat, ...) + // void _cdecl Format(PCSTR szFormat); + // + // DESCRIPTION: + // This function does sprintf/wsprintf style formatting on CStdStringA + // objects. It looks a lot like MFC's CString::Format. Some people + // might even call this identical. Fortunately, these people are now + // dead... heh heh. + // + // PARAMETERS: + // nId - ID of string resource holding the format string + // szFormat - a PCSTR holding the format specifiers + // argList - a va_list holding the arguments for the format specifiers. + // + // RETURN VALUE: None. + // ------------------------------------------------------------------------- + // formatting (using wsprintf style formatting) + + // If they want a Format() function that safely handles string objects + // without casting + +#ifdef SS_SAFE_FORMAT + + // Question: Joe, you wacky coder you, why do you have so many overloads + // of the Format() function + // Answer: One reason only - CString compatability. In short, by making + // the Format() function a template this way, I can do strong typing + // and allow people to pass CStdString arguments as fillers for + // "%s" format specifiers without crashing their program! The downside + // is that I need to overload on the number of arguments. If you are + // passing more arguments than I have listed below in any of my + // overloads, just add another one. + // + // Yes, yes, this is really ugly. In essence what I am doing here is + // protecting people from a bad (and incorrect) programming practice + // that they should not be doing anyway. I am protecting them from + // themselves. Why am I doing this? Well, if you had any idea the + // number of times I've been emailed by people about this + // "incompatability" in my code, you wouldn't ask. + + void Fmt(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + FormatV(szFmt, argList); + va_end(argList); + } + +#ifndef SS_ANSI + + void Format(UINT nId) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + this->swap(strFmt); + } + template + void Format(UINT nId, const A1& v) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + Fmt(strFmt, FmtArg(v)()); + } + template + void Format(UINT nId, const A1& v1, const A2& v2) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)()); + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(),FmtArg(v5)(), + FmtArg(v6)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(),FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(),FmtArg(v10)(),FmtArg(v11)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)(),FmtArg(v14)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(), FmtArg(v16)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16, const A17& v17) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(),FmtArg(v16)(),FmtArg(v17)()); + } + } + +#endif // #ifndef SS_ANSI + + // ...now the other overload of Format: the one that takes a string literal + + void Format(const CT* szFmt) + { + *this = szFmt; + } + template + void Format(const CT* szFmt, const A1& v) + { + Fmt(szFmt, FmtArg(v)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(),FmtArg(v10)(),FmtArg(v11)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)(),FmtArg(v14)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(), FmtArg(v16)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16, const A17& v17) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(),FmtArg(v16)(),FmtArg(v17)()); + } + +#else // #ifdef SS_SAFE_FORMAT + + +#ifndef SS_ANSI + + void Format(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + FormatV(strFmt, argList); + + va_end(argList); + } + +#endif // #ifdef SS_ANSI + + void Format(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + FormatV(szFmt, argList); + va_end(argList); + } + +#endif // #ifdef SS_SAFE_FORMAT + + void AppendFormat(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + AppendFormatV(szFmt, argList); + va_end(argList); + } + + #define MAX_FMT_TRIES 5 // #of times we try + #define FMT_BLOCK_SIZE 2048 // # of bytes to increment per try + #define BUFSIZE_1ST 256 + #define BUFSIZE_2ND 512 + #define STD_BUF_SIZE 1024 + + // an efficient way to add formatted characters to the string. You may only + // add up to STD_BUF_SIZE characters at a time, though + void AppendFormatV(const CT* szFmt, va_list argList) + { + CT szBuf[STD_BUF_SIZE]; + int nLen = ssnprintf(szBuf, STD_BUF_SIZE-1, szFmt, argList); + + if ( 0 < nLen ) + this->append(szBuf, nLen); + } + + // ------------------------------------------------------------------------- + // FUNCTION: FormatV + // void FormatV(PCSTR szFormat, va_list, argList); + // + // DESCRIPTION: + // This function formats the string with sprintf style format-specs. + // It makes a general guess at required buffer size and then tries + // successively larger buffers until it finds one big enough or a + // threshold (MAX_FMT_TRIES) is exceeded. + // + // PARAMETERS: + // szFormat - a PCSTR holding the format of the output + // argList - a Microsoft specific va_list for variable argument lists + // + // RETURN VALUE: + // ------------------------------------------------------------------------- + + // NOTE: Changed by JM to actually function under non-win32, + // and to remove the upper limit on size. + void FormatV(const CT* szFormat, va_list argList) + { + // try and grab a sufficient buffersize + int nChars = FMT_BLOCK_SIZE; + va_list argCopy; + + CT *p = reinterpret_cast(malloc(sizeof(CT)*nChars)); + if (!p) return; + + while (1) + { + va_copy(argCopy, argList); + + int nActual = ssvsprintf(p, nChars, szFormat, argCopy); + /* If that worked, return the string. */ + if (nActual > -1 && nActual < nChars) + { /* make sure it's NULL terminated */ + p[nActual] = '\0'; + this->assign(p, nActual); + free(p); + va_end(argCopy); + return; + } + /* Else try again with more space. */ + if (nActual > -1) /* glibc 2.1 */ + nChars = nActual + 1; /* precisely what is needed */ + else /* glibc 2.0 */ + nChars *= 2; /* twice the old size */ + + CT *np = reinterpret_cast(realloc(p, sizeof(CT)*nChars)); + if (np == NULL) + { + free(p); + va_end(argCopy); + return; // failed :( + } + p = np; + va_end(argCopy); + } + } + + // ------------------------------------------------------------------------- + // CString Facade Functions: + // + // The following methods are intended to allow you to use this class as a + // near drop-in replacement for CString. + // ------------------------------------------------------------------------- + #ifdef SS_WIN32 + BSTR AllocSysString() const + { + ostring os; + ssasn(os, *this); + return ::SysAllocString(os.c_str()); + } + #endif + +#ifndef SS_NO_LOCALE + int Collate(PCMYSTR szThat) const + { + return sscoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } + + int CollateNoCase(PCMYSTR szThat) const + { + return ssicoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } +#endif + int Compare(PCMYSTR szThat) const + { + return this->compare(szThat); + } + + int CompareNoCase(PCMYSTR szThat) const + { + return ssicmp(this->c_str(), szThat); + } + + int Delete(int nIdx, int nCount=1) + { + if ( nIdx < 0 ) + nIdx = 0; + + if ( nIdx < this->GetLength() ) + this->erase(static_cast(nIdx), static_cast(nCount)); + + return GetLength(); + } + + void Empty() + { + this->erase(); + } + + int Find(CT ch) const + { + MYSIZE nIdx = this->find_first_of(ch); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(PCMYSTR szSub) const + { + MYSIZE nIdx = this->find(szSub); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(CT ch, int nStart) const + { + // CString::Find docs say add 1 to nStart when it's not zero + // CString::Find code doesn't do that however. We'll stick + // with what the code does + + MYSIZE nIdx = this->find_first_of(ch, static_cast(nStart)); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(PCMYSTR szSub, int nStart) const + { + // CString::Find docs say add 1 to nStart when it's not zero + // CString::Find code doesn't do that however. We'll stick + // with what the code does + + MYSIZE nIdx = this->find(szSub, static_cast(nStart)); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int FindOneOf(PCMYSTR szCharSet) const + { + MYSIZE nIdx = this->find_first_of(szCharSet); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + +#ifndef SS_ANSI + void FormatMessage(PCMYSTR szFormat, ...) throw(std::exception) + { + va_list argList; + va_start(argList, szFormat); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + szFormat, 0, 0, + reinterpret_cast(&szTemp), 0, &argList) == 0 || + szTemp == 0 ) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } + + void FormatMessage(UINT nFormatId, ...) throw(std::exception) + { + MYTYPE sFormat; + VERIFY(sFormat.LoadString(nFormatId)); + va_list argList; + va_start(argList, nFormatId); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + sFormat, 0, 0, + reinterpret_cast(&szTemp), 0, &argList) == 0 || + szTemp == 0) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } +#endif + + // GetAllocLength -- an MSVC7 function but it costs us nothing to add it. + + int GetAllocLength() + { + return static_cast(this->capacity()); + } + + // ------------------------------------------------------------------------- + // GetXXXX -- Direct access to character buffer + // ------------------------------------------------------------------------- + CT GetAt(int nIdx) const + { + return this->at(static_cast(nIdx)); + } + + CT* GetBuffer(int nMinLen=-1) + { + return GetBuf(nMinLen); + } + + CT* GetBufferSetLength(int nLen) + { + return BufferSet(nLen); + } + + // GetLength() -- MFC docs say this is the # of BYTES but + // in truth it is the number of CHARACTERs (chars or wchar_ts) + int GetLength() const + { + return static_cast(this->length()); + } + + int Insert(int nIdx, CT ch) + { + if ( static_cast(nIdx) > this->size()-1 ) + this->append(1, ch); + else + this->insert(static_cast(nIdx), 1, ch); + + return GetLength(); + } + int Insert(int nIdx, PCMYSTR sz) + { + if ( static_cast(nIdx) >= this->size() ) + this->append(sz, static_cast(sslen(sz))); + else + this->insert(static_cast(nIdx), sz); + + return GetLength(); + } + + bool IsEmpty() const + { + return this->empty(); + } + + MYTYPE Left(int nCount) const + { + // Range check the count. + + nCount = SSMAX(0, SSMIN(nCount, static_cast(this->size()))); + return this->substr(0, static_cast(nCount)); + } + +#ifndef SS_ANSI + bool LoadString(UINT nId) + { + return this->Load(nId); + } +#endif + + void MakeLower() + { + ToLower(); + } + + void MakeReverse() + { + std::reverse(this->begin(), this->end()); + } + + void MakeUpper() + { + ToUpper(); + } + + MYTYPE Mid(int nFirst) const + { + return Mid(nFirst, this->GetLength()-nFirst); + } + + MYTYPE Mid(int nFirst, int nCount) const + { + // CString does range checking here. Since we're trying to emulate it, + // we must check too. + + if ( nFirst < 0 ) + nFirst = 0; + if ( nCount < 0 ) + nCount = 0; + + int nSize = static_cast(this->size()); + + if ( nFirst + nCount > nSize ) + nCount = nSize - nFirst; + + if ( nFirst > nSize ) + return MYTYPE(); + + ASSERT(nFirst >= 0); + ASSERT(nFirst + nCount <= nSize); + + return this->substr(static_cast(nFirst), + static_cast(nCount)); + } + + void ReleaseBuffer(int nNewLen=-1) + { + RelBuf(nNewLen); + } + + int Remove(CT ch) + { + MYSIZE nIdx = 0; + int nRemoved = 0; + while ( (nIdx=this->find_first_of(ch)) != MYBASE::npos ) + { + this->erase(nIdx, 1); + nRemoved++; + } + return nRemoved; + } + + int Replace(CT chOld, CT chNew) + { + int nReplaced = 0; + + for ( MYITER iter=this->begin(); iter != this->end(); iter++ ) + { + if ( *iter == chOld ) + { + *iter = chNew; + nReplaced++; + } + } + + return nReplaced; + } + + int Replace(PCMYSTR szOld, PCMYSTR szNew) + { + int nReplaced = 0; + MYSIZE nIdx = 0; + MYSIZE nOldLen = sslen(szOld); + + if ( 0 != nOldLen ) + { + // If the replacement string is longer than the one it replaces, this + // string is going to have to grow in size, Figure out how much + // and grow it all the way now, rather than incrementally + + MYSIZE nNewLen = sslen(szNew); + if ( nNewLen > nOldLen ) + { + int nFound = 0; + while ( nIdx < this->length() && + (nIdx=this->find(szOld, nIdx)) != MYBASE::npos ) + { + nFound++; + nIdx += nOldLen; + } + this->reserve(this->size() + nFound * (nNewLen - nOldLen)); + } + + + static const CT ch = CT(0); + PCMYSTR szRealNew = szNew == 0 ? &ch : szNew; + nIdx = 0; + + while ( nIdx < this->length() && + (nIdx=this->find(szOld, nIdx)) != MYBASE::npos ) + { + this->replace(this->begin()+nIdx, this->begin()+nIdx+nOldLen, + szRealNew); + + nReplaced++; + nIdx += nNewLen; + } + } + + return nReplaced; + } + + int ReverseFind(CT ch) const + { + MYSIZE nIdx = this->find_last_of(ch); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + // ReverseFind overload that's not in CString but might be useful + int ReverseFind(PCMYSTR szFind, MYSIZE pos=MYBASE::npos) const + { + //yuvalt - this does not compile with g++ since MYTTYPE() is different type + //MYSIZE nIdx = this->rfind(0 == szFind ? MYTYPE() : szFind, pos); + MYSIZE nIdx = this->rfind(0 == szFind ? "" : szFind, pos); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + MYTYPE Right(int nCount) const + { + // Range check the count. + + nCount = SSMAX(0, SSMIN(nCount, static_cast(this->size()))); + return this->substr(this->size()-static_cast(nCount)); + } + + void SetAt(int nIndex, CT ch) + { + ASSERT(this->size() > static_cast(nIndex)); + this->at(static_cast(nIndex)) = ch; + } + +#ifndef SS_ANSI + BSTR SetSysString(BSTR* pbstr) const + { + ostring os; + ssasn(os, *this); + if ( !::SysReAllocStringLen(pbstr, os.c_str(), os.length()) ) + throw std::runtime_error("out of memory"); + + ASSERT(*pbstr != 0); + return *pbstr; + } +#endif + + MYTYPE SpanExcluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + + MYTYPE SpanIncluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_not_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + +#if defined SS_WIN32 && !defined(UNICODE) && !defined(SS_ANSI) + + // CString's OemToAnsi and AnsiToOem functions are available only in + // Unicode builds. However since we're a template we also need a + // runtime check of CT and a reinterpret_cast to account for the fact + // that CStdStringW gets instantiated even in non-Unicode builds. + + void AnsiToOem() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::CharToOem(reinterpret_cast(this->c_str()), + reinterpret_cast(GetBuf())); + } + else + { + ASSERT(false); + } + } + + void OemToAnsi() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::OemToChar(reinterpret_cast(this->c_str()), + reinterpret_cast(GetBuf())); + } + else + { + ASSERT(false); + } + } + +#endif + + + // ------------------------------------------------------------------------- + // Trim and its variants + // ------------------------------------------------------------------------- + MYTYPE& Trim() + { + return TrimLeft().TrimRight(); + } + + MYTYPE& TrimLeft() + { + this->erase(this->begin(), + std::find_if(this->begin(), this->end(), NotSpace())); + + return *this; + } + + MYTYPE& TrimLeft(CT tTrim) + { + this->erase(0, this->find_first_not_of(tTrim)); + return *this; + } + + MYTYPE& TrimLeft(PCMYSTR szTrimChars) + { + this->erase(0, this->find_first_not_of(szTrimChars)); + return *this; + } + + MYTYPE& TrimRight() + { + // NOTE: When comparing reverse_iterators here (MYRITER), I avoid using + // operator!=. This is because namespace rel_ops also has a template + // operator!= which conflicts with the global operator!= already defined + // for reverse_iterator in the header . + // Thanks to John James for alerting me to this. + + MYRITER it = std::find_if(this->rbegin(), this->rend(), NotSpace()); + if ( !(this->rend() == it) ) + this->erase(this->rend() - it); + + this->erase(!(it == this->rend()) ? this->find_last_of(*it) + 1 : 0); + return *this; + } + + MYTYPE& TrimRight(CT tTrim) + { + MYSIZE nIdx = this->find_last_not_of(tTrim); + this->erase(MYBASE::npos == nIdx ? 0 : ++nIdx); + return *this; + } + + MYTYPE& TrimRight(PCMYSTR szTrimChars) + { + MYSIZE nIdx = this->find_last_not_of(szTrimChars); + this->erase(MYBASE::npos == nIdx ? 0 : ++nIdx); + return *this; + } + + void FreeExtra() + { + MYTYPE mt; + this->swap(mt); + if ( !mt.empty() ) + this->assign(mt.c_str(), mt.size()); + } + + // I have intentionally not implemented the following CString + // functions. You cannot make them work without taking advantage + // of implementation specific behavior. However if you absolutely + // MUST have them, uncomment out these lines for "sort-of-like" + // their behavior. You're on your own. + +// CT* LockBuffer() { return GetBuf(); }// won't really lock +// void UnlockBuffer(); { } // why have UnlockBuffer w/o LockBuffer? + + // Array-indexing operators. Required because we defined an implicit cast + // to operator const CT* (Thanks to Julian Selman for pointing this out) + + CT& operator[](int nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](int nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + CT& operator[](unsigned int nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](unsigned int nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + CT& operator[](unsigned long nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](unsigned long nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + +#ifndef SS_NO_IMPLICIT_CAST + operator const CT*() const + { + return this->c_str(); + } +#endif + + // IStream related functions. Useful in IPersistStream implementations + +#ifdef SS_INC_COMDEF + + // struct SSSHDR - useful for non Std C++ persistence schemes. + typedef struct SSSHDR + { + BYTE byCtrl; + ULONG nChars; + } SSSHDR; // as in "Standard String Stream Header" + + #define SSSO_UNICODE 0x01 // the string is a wide string + #define SSSO_COMPRESS 0x02 // the string is compressed + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSize + // REMARKS: + // Returns how many bytes it will take to StreamSave() this CStdString + // object to an IStream. + // ------------------------------------------------------------------------- + ULONG StreamSize() const + { + // Control header plus string + ASSERT(this->size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + return (this->size() * sizeof(CT)) + sizeof(SSSHDR); + } + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSave + // REMARKS: + // Saves this CStdString object to a COM IStream. + // ------------------------------------------------------------------------- + HRESULT StreamSave(IStream* pStream) const + { + ASSERT(this->size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + HRESULT hr = E_FAIL; + ASSERT(pStream != 0); + SSSHDR hdr; + hdr.byCtrl = sizeof(CT) == 2 ? SSSO_UNICODE : 0; + hdr.nChars = this->size(); + + + if ( FAILED(hr=pStream->Write(&hdr, sizeof(SSSHDR), 0)) ) + { + TRACE(_T("StreamSave: Cannot write control header, ERR=0x%X\n"),hr); + } + else if ( empty() ) + { + ; // nothing to write + } + else if ( FAILED(hr=pStream->Write(this->c_str(), + this->size()*sizeof(CT), 0)) ) + { + TRACE(_T("StreamSave: Cannot write string to stream 0x%X\n"), hr); + } + + return hr; + } + + + // ------------------------------------------------------------------------- + // FUNCTION: StreamLoad + // REMARKS: + // This method loads the object from an IStream. + // ------------------------------------------------------------------------- + HRESULT StreamLoad(IStream* pStream) + { + ASSERT(pStream != 0); + SSSHDR hdr; + HRESULT hr = E_FAIL; + + if ( FAILED(hr=pStream->Read(&hdr, sizeof(SSSHDR), 0)) ) + { + TRACE(_T("StreamLoad: Cant read control header, ERR=0x%X\n"), hr); + } + else if ( hdr.nChars > 0 ) + { + ULONG nRead = 0; + PMYSTR pMyBuf = BufferSet(hdr.nChars); + + // If our character size matches the character size of the string + // we're trying to read, then we can read it directly into our + // buffer. Otherwise, we have to read into an intermediate buffer + // and convert. + + if ( (hdr.byCtrl & SSSO_UNICODE) != 0 ) + { + ULONG nBytes = hdr.nChars * sizeof(wchar_t); + if ( sizeof(CT) == sizeof(wchar_t) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PWSTR pBufW = reinterpret_cast(_alloca((nBytes)+1)); + if ( FAILED(hr=pStream->Read(pBufW, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufW, hdr.nChars); + } + } + else + { + ULONG nBytes = hdr.nChars * sizeof(char); + if ( sizeof(CT) == sizeof(char) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PSTR pBufA = reinterpret_cast(_alloca(nBytes)); + if ( FAILED(hr=pStream->Read(pBufA, hdr.nChars, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufA, hdr.nChars); + } + } + } + else + { + this->erase(); + } + return hr; + } +#endif // #ifdef SS_INC_COMDEF + +#ifndef SS_ANSI + + // SetResourceHandle/GetResourceHandle. In MFC builds, these map directly + // to AfxSetResourceHandle and AfxGetResourceHandle. In non-MFC builds they + // point to a single static HINST so that those who call the member + // functions that take resource IDs can provide an alternate HINST of a DLL + // to search. This is not exactly the list of HMODULES that MFC provides + // but it's better than nothing. + + #ifdef _MFC_VER + static void SetResourceHandle(HMODULE hNew) + { + AfxSetResourceHandle(hNew); + } + static HMODULE GetResourceHandle() + { + return AfxGetResourceHandle(); + } + #else + static void SetResourceHandle(HMODULE hNew) + { + SSResourceHandle() = hNew; + } + static HMODULE GetResourceHandle() + { + return SSResourceHandle(); + } + #endif + +#endif +}; + +// ----------------------------------------------------------------------------- +// MSVC USERS: HOW TO EXPORT CSTDSTRING FROM A DLL +// +// If you are using MS Visual C++ and you want to export CStdStringA and +// CStdStringW from a DLL, then all you need to +// +// 1. make sure that all components link to the same DLL version +// of the CRT (not the static one). +// 2. Uncomment the 3 lines of code below +// 3. #define 2 macros per the instructions in MS KnowledgeBase +// article Q168958. The macros are: +// +// MACRO DEFINTION WHEN EXPORTING DEFINITION WHEN IMPORTING +// ----- ------------------------ ------------------------- +// SSDLLEXP (nothing, just #define it) extern +// SSDLLSPEC __declspec(dllexport) __declspec(dllimport) +// +// Note that these macros must be available to ALL clients who want to +// link to the DLL and use the class. If they +// +// A word of advice: Don't bother. +// +// Really, it is not necessary to export CStdString functions from a DLL. I +// never do. In my projects, I do generally link to the DLL version of the +// Standard C++ Library, but I do NOT attempt to export CStdString functions. +// I simply include the header where it is needed and allow for the code +// redundancy. +// +// That redundancy is a lot less than you think. This class does most of its +// work via the Standard C++ Library, particularly the base_class basic_string<> +// member functions. Most of the functions here are small enough to be inlined +// anyway. Besides, you'll find that in actual practice you use less than 1/2 +// of the code here, even in big projects and different modules will use as +// little as 10% of it. That means a lot less functions actually get linked +// your binaries. If you export this code from a DLL, it ALL gets linked in. +// +// I've compared the size of the binaries from exporting vs NOT exporting. Take +// my word for it -- exporting this code is not worth the hassle. +// +// ----------------------------------------------------------------------------- +//#pragma warning(disable:4231) // non-standard extension ("extern template") +// SSDLLEXP template class SSDLLSPEC CStdStr; +// SSDLLEXP template class SSDLLSPEC CStdStr; + + +// ============================================================================= +// END OF CStdStr INLINE FUNCTION DEFINITIONS +// ============================================================================= + +// Now typedef our class names based upon this humongous template + +typedef CStdStr CStdStringA; // a better std::string +typedef CStdStr CStdStringW; // a better std::wstring +typedef CStdStr CStdString16; // a 16bit char string +typedef CStdStr CStdString32; // a 32bit char string +typedef CStdStr CStdStringO; // almost always CStdStringW + +// ----------------------------------------------------------------------------- +// CStdStr addition functions defined as inline +// ----------------------------------------------------------------------------- + + +inline CStdStringA operator+(const CStdStringA& s1, const CStdStringA& s2) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(s2); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, CStdStringA::value_type t) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(1, t); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, PCSTR pA) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(pA); + return sRet; +} +inline CStdStringA operator+(PCSTR pA, const CStdStringA& sA) +{ + CStdStringA sRet; + CStdStringA::size_type nObjSize = sA.size(); + CStdStringA::size_type nLitSize = + static_cast(sslen(pA)); + + sRet.reserve(nLitSize + nObjSize); + sRet.assign(pA); + sRet.append(sA); + return sRet; +} + + +inline CStdStringA operator+(const CStdStringA& s1, const CStdStringW& s2) +{ + return s1 + CStdStringA(s2); +} +inline CStdStringW operator+(const CStdStringW& s1, const CStdStringW& s2) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(s2); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, PCWSTR pW) +{ + return s1 + CStdStringA(pW); +} + +#ifdef UNICODE + inline CStdStringW operator+(PCWSTR pW, const CStdStringA& sA) + { + return CStdStringW(pW) + CStdStringW(SSREF(sA)); + } + inline CStdStringW operator+(PCSTR pA, const CStdStringW& sW) + { + return CStdStringW(pA) + sW; + } +#else + inline CStdStringA operator+(PCWSTR pW, const CStdStringA& sA) + { + return CStdStringA(pW) + sA; + } + inline CStdStringA operator+(PCSTR pA, const CStdStringW& sW) + { + return pA + CStdStringA(sW); + } +#endif + +// ...Now the wide string versions. +inline CStdStringW operator+(const CStdStringW& s1, CStdStringW::value_type t) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(1, t); + return sRet; +} +inline CStdStringW operator+(const CStdStringW& s1, PCWSTR pW) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(pW); + return sRet; +} +inline CStdStringW operator+(PCWSTR pW, const CStdStringW& sW) +{ + CStdStringW sRet; + CStdStringW::size_type nObjSize = sW.size(); + CStdStringA::size_type nLitSize = + static_cast(sslen(pW)); + + sRet.reserve(nLitSize + nObjSize); + sRet.assign(pW); + sRet.append(sW); + return sRet; +} + +inline CStdStringW operator+(const CStdStringW& s1, const CStdStringA& s2) +{ + return s1 + CStdStringW(s2); +} +inline CStdStringW operator+(const CStdStringW& s1, PCSTR pA) +{ + return s1 + CStdStringW(pA); +} + + +// New-style format function is a template + +#ifdef SS_SAFE_FORMAT + +template<> +struct FmtArg +{ + explicit FmtArg(const CStdStringA& arg) : a_(arg) {} + PCSTR operator()() const { return a_.c_str(); } + const CStdStringA& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; +template<> +struct FmtArg +{ + explicit FmtArg(const CStdStringW& arg) : a_(arg) {} + PCWSTR operator()() const { return a_.c_str(); } + const CStdStringW& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; + +template<> +struct FmtArg +{ + explicit FmtArg(const std::string& arg) : a_(arg) {} + PCSTR operator()() const { return a_.c_str(); } + const std::string& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; +template<> +struct FmtArg +{ + explicit FmtArg(const std::wstring& arg) : a_(arg) {} + PCWSTR operator()() const { return a_.c_str(); } + const std::wstring& a_; +private: + FmtArg& operator=(const FmtArg&) {return *this;} +}; +#endif // #ifdef SS_SAFEFORMAT + +#ifndef SS_ANSI + // SSResourceHandle: our MFC-like resource handle + inline HMODULE& SSResourceHandle() + { + static HMODULE hModuleSS = GetModuleHandle(0); + return hModuleSS; + } +#endif + + +// In MFC builds, define some global serialization operators +// Special operators that allow us to serialize CStdStrings to CArchives. +// Note that we use an intermediate CString object in order to ensure that +// we use the exact same format. + +#ifdef _MFC_VER + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringA& strA) + { + CString strTemp = strA; + return ar << strTemp; + } + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringW& strW) + { + CString strTemp = strW; + return ar << strTemp; + } + + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringA& strA) + { + CString strTemp; + ar >> strTemp; + strA = strTemp; + return ar; + } + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringW& strW) + { + CString strTemp; + ar >> strTemp; + strW = strTemp; + return ar; + } +#endif // #ifdef _MFC_VER -- (i.e. is this MFC?) + + + +// ----------------------------------------------------------------------------- +// GLOBAL FUNCTION: WUFormat +// CStdStringA WUFormat(UINT nId, ...); +// CStdStringA WUFormat(PCSTR szFormat, ...); +// +// REMARKS: +// This function allows the caller for format and return a CStdStringA +// object with a single line of code. +// ----------------------------------------------------------------------------- +#ifdef SS_ANSI +#else + inline CStdStringA WUFormatA(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + CStdStringA strFmt; + CStdStringA strOut; + if ( strFmt.Load(nId) ) + strOut.FormatV(strFmt, argList); + + va_end(argList); + return strOut; + } + inline CStdStringA WUFormatA(PCSTR szFormat, ...) + { + va_list argList; + va_start(argList, szFormat); + CStdStringA strOut; + strOut.FormatV(szFormat, argList); + va_end(argList); + return strOut; + } + inline CStdStringW WUFormatW(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + CStdStringW strFmt; + CStdStringW strOut; + if ( strFmt.Load(nId) ) + strOut.FormatV(strFmt, argList); + + va_end(argList); + return strOut; + } + inline CStdStringW WUFormatW(PCWSTR szwFormat, ...) + { + va_list argList; + va_start(argList, szwFormat); + CStdStringW strOut; + strOut.FormatV(szwFormat, argList); + va_end(argList); + return strOut; + } +#endif // #ifdef SS_ANSI + + + +#if defined(SS_WIN32) && !defined (SS_ANSI) + // ------------------------------------------------------------------------- + // FUNCTION: WUSysMessage + // CStdStringA WUSysMessageA(DWORD dwError, DWORD dwLangId=SS_DEFLANGID); + // CStdStringW WUSysMessageW(DWORD dwError, DWORD dwLangId=SS_DEFLANGID); + // + // DESCRIPTION: + // This function simplifies the process of obtaining a string equivalent + // of a system error code returned from GetLastError(). You simply + // supply the value returned by GetLastError() to this function and the + // corresponding system string is returned in the form of a CStdStringA. + // + // PARAMETERS: + // dwError - a DWORD value representing the error code to be translated + // dwLangId - the language id to use. defaults to english. + // + // RETURN VALUE: + // a CStdStringA equivalent of the error code. Currently, this function + // only returns either English of the system default language strings. + // ------------------------------------------------------------------------- + #define SS_DEFLANGID MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT) + inline CStdStringA WUSysMessageA(DWORD dwError, DWORD dwLangId=SS_DEFLANGID) + { + CHAR szBuf[512]; + + if ( 0 != ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, + dwLangId, szBuf, 511, NULL) ) + return WUFormatA("%s (0x%X)", szBuf, dwError); + else + return WUFormatA("Unknown error (0x%X)", dwError); + } + inline CStdStringW WUSysMessageW(DWORD dwError, DWORD dwLangId=SS_DEFLANGID) + { + WCHAR szBuf[512]; + + if ( 0 != ::FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, + dwLangId, szBuf, 511, NULL) ) + return WUFormatW(L"%s (0x%X)", szBuf, dwError); + else + return WUFormatW(L"Unknown error (0x%X)", dwError); + } +#endif + +// Define TCHAR based friendly names for some of these functions + +#ifdef UNICODE + //#define CStdString CStdStringW + typedef CStdStringW CStdString; + #define WUSysMessage WUSysMessageW + #define WUFormat WUFormatW +#else + //#define CStdString CStdStringA + typedef CStdStringA CStdString; + #define WUSysMessage WUSysMessageA + #define WUFormat WUFormatA +#endif + +// ...and some shorter names for the space-efficient + +#define WUSysMsg WUSysMessage +#define WUSysMsgA WUSysMessageA +#define WUSysMsgW WUSysMessageW +#define WUFmtA WUFormatA +#define WUFmtW WUFormatW +#define WUFmt WUFormat +#define WULastErrMsg() WUSysMessage(::GetLastError()) +#define WULastErrMsgA() WUSysMessageA(::GetLastError()) +#define WULastErrMsgW() WUSysMessageW(::GetLastError()) + + +// ----------------------------------------------------------------------------- +// FUNCTIONAL COMPARATORS: +// REMARKS: +// These structs are derived from the std::binary_function template. They +// give us functional classes (which may be used in Standard C++ Library +// collections and algorithms) that perform case-insensitive comparisons of +// CStdString objects. This is useful for maps in which the key may be the +// proper string but in the wrong case. +// ----------------------------------------------------------------------------- +#define StdStringLessNoCaseW SSLNCW // avoid VC compiler warning 4786 +#define StdStringEqualsNoCaseW SSENCW +#define StdStringLessNoCaseA SSLNCA +#define StdStringEqualsNoCaseA SSENCA + +#ifdef UNICODE + #define StdStringLessNoCase SSLNCW + #define StdStringEqualsNoCase SSENCW +#else + #define StdStringLessNoCase SSLNCA + #define StdStringEqualsNoCase SSENCA +#endif + +struct StdStringLessNoCaseW + : std::binary_function +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseW + : std::binary_function +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; +struct StdStringLessNoCaseA + : std::binary_function +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseA + : std::binary_function +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; + +// If we had to define our own version of TRACE above, get rid of it now + +#ifdef TRACE_DEFINED_HERE + #undef TRACE + #undef TRACE_DEFINED_HERE +#endif + + +// These std::swap specializations come courtesy of Mike Crusader. + +//namespace std +//{ +// inline void swap(CStdStringA& s1, CStdStringA& s2) throw() +// { +// s1.swap(s2); +// } +// template<> +// inline void swap(CStdStringW& s1, CStdStringW& s2) throw() +// { +// s1.swap(s2); +// } +//} + +// Turn back on any Borland warnings we turned off. + +#ifdef __BORLANDC__ + #pragma option pop // Turn back on inline function warnings +// #pragma warn +inl // Turn back on inline function warnings +#endif + +#endif // #ifndef STDSTRING_H diff --git a/xbmc/pvrclients/vdr-streamdev/channels.cpp b/xbmc/pvrclients/vdr-streamdev/channels.cpp new file mode 100644 index 0000000000..c174bf2271 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/channels.cpp @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * This code is taken from channels.c in the Video Disk Recorder ('VDR') + */ + +#include +#include +#include "vtptransceiver.h" +#include "pvrclient-vdr_os.h" +#include "channels.h" +#include "client.h" + +using namespace std; + +const tChannelParameterMap InversionValues[] = { + { 0, INVERSION_OFF, "off" }, + { 1, INVERSION_ON, "on" }, + { 999, INVERSION_AUTO, "auto" }, + { -1 } + }; + +const tChannelParameterMap BandwidthValues[] = { + { 6, 6000000, "6 MHz" }, + { 7, 7000000, "7 MHz" }, + { 8, 8000000, "8 MHz" }, + { -1 } + }; + +const tChannelParameterMap CoderateValues[] = { + { 0, FEC_NONE, "none" }, + { 12, FEC_1_2, "1/2" }, + { 23, FEC_2_3, "2/3" }, + { 34, FEC_3_4, "3/4" }, + { 35, FEC_3_5, "3/5" }, + { 45, FEC_4_5, "4/5" }, + { 56, FEC_5_6, "5/6" }, + { 67, FEC_6_7, "6/7" }, + { 78, FEC_7_8, "7/8" }, + { 89, FEC_8_9, "8/9" }, + { 910, FEC_9_10, "9/10" }, + { 999, FEC_AUTO, "auto" }, + { -1 } + }; + +const tChannelParameterMap ModulationValues[] = { + { 16, QAM_16, "QAM16" }, + { 32, QAM_32, "QAM32" }, + { 64, QAM_64, "QAM64" }, + { 128, QAM_128, "QAM128" }, + { 256, QAM_256, "QAM256" }, + { 2, QPSK, "QPSK" }, + { 5, PSK_8, "8PSK" }, + { 6, APSK_16, "16APSK" }, + { 10, VSB_8, "VSB8" }, + { 11, VSB_16, "VSB16" }, + { 998, QAM_AUTO, "QAMAUTO" }, + { -1 } + }; + +const tChannelParameterMap SystemValues[] = { + { 0, SYS_DVBS, "DVB-S" }, + { 1, SYS_DVBS2, "DVB-S2" }, + { -1 } + }; + +const tChannelParameterMap TransmissionValues[] = { + { 2, TRANSMISSION_MODE_2K, "2K" }, + { 8, TRANSMISSION_MODE_8K, "8K" }, + { 999, TRANSMISSION_MODE_AUTO, "auto" }, + { -1 } + }; + +const tChannelParameterMap GuardValues[] = { + { 4, GUARD_INTERVAL_1_4, "1/4" }, + { 8, GUARD_INTERVAL_1_8, "1/8" }, + { 16, GUARD_INTERVAL_1_16, "1/16" }, + { 32, GUARD_INTERVAL_1_32, "1/32" }, + { 999, GUARD_INTERVAL_AUTO, "auto" }, + { -1 } + }; + +const tChannelParameterMap HierarchyValues[] = { + { 0, HIERARCHY_NONE, "none" }, + { 1, HIERARCHY_1, "1" }, + { 2, HIERARCHY_2, "2" }, + { 4, HIERARCHY_4, "4" }, + { 999, HIERARCHY_AUTO, "auto" }, + { -1 } + }; + +const tChannelParameterMap RollOffValues[] = { + { 0, ROLLOFF_AUTO, "auto" }, + { 20, ROLLOFF_20, "0.20" }, + { 25, ROLLOFF_25, "0.25" }, + { 35, ROLLOFF_35, "0.35" }, + { -1 } + }; + + +cChannel::cChannel(const PVR_CHANNEL *Channel) +{ + +} + +cChannel::cChannel() +{ + name = strdup(""); + shortName = strdup(""); + provider = strdup(""); + memset(&__BeginData__, 0, (char *)&__EndData__ - (char *)&__BeginData__); + inversion = INVERSION_AUTO; + bandwidth = 8000000; + coderateH = FEC_AUTO; + coderateL = FEC_AUTO; + modulation = QPSK; + system = SYS_DVBS; + transmission = TRANSMISSION_MODE_AUTO; + guard = GUARD_INTERVAL_AUTO; + hierarchy = HIERARCHY_AUTO; + rollOff = ROLLOFF_AUTO; + modification = CHANNELMOD_NONE; + +} + +cChannel::~cChannel() +{ + free(name); + free(shortName); + free(provider); +} + +bool cChannel::ReadFromVTP(int channel) +{ + vector lines; + int code; + char buffer[64]; + + if (!VTPTransceiver.CheckConnection()) + return false; + + sprintf(buffer, "LSTC %i", channel); + while (!VTPTransceiver.SendCommand(buffer, code, lines)) + { + if (code != 451) + return false; + + Sleep(100); + } + + vector::iterator it = lines.begin(); + string& data(*it); + CStdString str_result = data; + + if (g_bCharsetConv) + XBMC->UnknownToUTF8(str_result); + + Parse(str_result.c_str()); + return true; +} + +int UserIndex(int Value, const tChannelParameterMap *Map) +{ + const tChannelParameterMap *map = Map; + while (map && map->userValue != -1) + { + if (map->userValue == Value) + return map - Map; + map++; + } + return -1; +} + +int MapToDriver(int Value, const tChannelParameterMap *Map) +{ + int n = UserIndex(Value, Map); + if (n >= 0) + return Map[n].driverValue; + return -1; +} + +static const char *ParseParameter(const char *s, int &Value, const tChannelParameterMap *Map) +{ + if (*++s) + { + char *p = NULL; + errno = 0; + int n = strtol(s, &p, 10); + if (!errno && p != s) + { + Value = MapToDriver(n, Map); + if (Value >= 0) + return p; + } + } + XBMC->Log(LOG_ERROR, "PCRClient-vdr: invalid value for channelparameter '%c'", *(s - 1)); + return NULL; +} + +static const char *SkipDigits(const char *s) +{ + while (*++s && isdigit(*s)) + ; + return s; +} + +bool cChannel::StringToParameters(const char *s) +{ + while (s && *s) + { + switch (toupper(*s)) + { + case 'A': s = SkipDigits(s); break; // for compatibility with the "multiproto" approach - may be removed in future versions + case 'B': s = ParseParameter(s, bandwidth, BandwidthValues); break; + case 'C': s = ParseParameter(s, coderateH, CoderateValues); break; + case 'D': s = ParseParameter(s, coderateL, CoderateValues); break; + case 'G': s = ParseParameter(s, guard, GuardValues); break; + case 'H': polarization = *s++; break; + case 'I': s = ParseParameter(s, inversion, InversionValues); break; + case 'L': polarization = *s++; break; + case 'M': s = ParseParameter(s, modulation, ModulationValues); break; + case 'O': s = ParseParameter(s, rollOff, RollOffValues); break; + case 'P': s = SkipDigits(s); break; // for compatibility with the "multiproto" approach - may be removed in future versions + case 'R': polarization = *s++; break; + case 'S': s = ParseParameter(s, system, SystemValues); break; + case 'T': s = ParseParameter(s, transmission, TransmissionValues); break; + case 'V': polarization = *s++; break; + case 'Y': s = ParseParameter(s, hierarchy, HierarchyValues); break; + case 'Z': s = SkipDigits(s); break; // for compatibility with the original DVB-S2 patch - may be removed in future versions + default: XBMC->Log(LOG_ERROR, "PCRClient-vdr: unknown parameter key '%c'", *s); + return false; + } + } + return true; +} + +bool cChannel::Parse(const char *s) +{ + bool ok = true; + + char namebuf[256]; + char sourcebuf[256]; + char parambuf[256]; + char vpidbuf[128]; + char apidbuf[128]; + char caidbuf[128]; + int fields = sscanf(s, "%d %[^:]:%d:%[^:]:%[^:]:%d :%[^:]:%[^:]:%d :%[^:]:%d :%d :%d :%d ", &number, namebuf, &frequency, parambuf, sourcebuf, &srate, vpidbuf, apidbuf, &tpid, caidbuf, &sid, &nid, &tid, &rid); + if (fields >= 9) + { + if (fields == 9) + { + // allow reading of old format + sid = atoi(caidbuf); + caids[0] = tpid; + caids[1] = 0; + tpid = 0; + } + vpid = ppid = 0; + vtype = 2; // default is MPEG-2 + apids[0] = 0; + dpids[0] = 0; + + ok = StringToParameters(parambuf) >= 0; + + char *p; + if ((p = strchr(vpidbuf, '=')) != NULL) + { + *p++ = 0; + if (sscanf(p, "%d", &vtype) != 1) + return false; + } + if ((p = strchr(vpidbuf, '+')) != NULL) + { + *p++ = 0; + if (sscanf(p, "%d", &ppid) != 1) + return false; + } + if (sscanf(vpidbuf, "%d", &vpid) != 1) + return false; + if (!ppid) + ppid = vpid; + + char *dpidbuf = strchr(apidbuf, ';'); + if (dpidbuf) + *dpidbuf++ = 0; + p = apidbuf; + char *q; + int NumApids = 0; + char *strtok_next; + while ((q = strtok_r(p, ",", &strtok_next)) != NULL) + { + if (NumApids < MAXAPIDS) { + char *l = strchr(q, '='); + if (l) + { + *l++ = 0; + strn0cpy(alangs[NumApids], l, MAXLANGCODE2); + } + else + *alangs[NumApids] = 0; + apids[NumApids++] = strtol(q, NULL, 10); + } + else + XBMC->Log(LOG_ERROR, "PCRClient-vdr: too many APIDs!"); // no need to set ok to 'false' + p = NULL; + } + apids[NumApids] = 0; + + if (dpidbuf) + { + char *p = dpidbuf; + char *q; + char *strtok_next; + + int NumDpids = 0; + while ((q = strtok_r(p, ",", &strtok_next)) != NULL) + { + if (NumDpids < MAXDPIDS) + { + char *l = strchr(q, '='); + if (l) + { + *l++ = 0; + strn0cpy(dlangs[NumDpids], l, MAXLANGCODE2); + } + else + *dlangs[NumDpids] = 0; + dpids[NumDpids++] = strtol(q, NULL, 10); + } + else + XBMC->Log(LOG_ERROR, "PCRClient-vdr: too many DPIDs!"); // no need to set ok to 'false' + p = NULL; + } + dpids[NumDpids] = 0; + } + + p = caidbuf; + int NumCaIds = 0; + while ((q = strtok_r(p, ",", &strtok_next)) != NULL) + { + if (NumCaIds < MAXCAIDS) + { + caids[NumCaIds++] = strtol(q, NULL, 16) & 0xFFFF; + if (NumCaIds == 1 && caids[0] <= CA_USER_MAX) + break; + } + else + XBMC->Log(LOG_ERROR, "PCRClient-vdr: too many CA ids!"); // no need to set ok to 'false' + p = NULL; + } + caids[NumCaIds] = 0; + strreplace(namebuf, '|', ':'); + + p = strchr(namebuf, ';'); + if (p) + { + *p++ = 0; + provider = strcpyrealloc(provider, p); + } + p = strchr(namebuf, ','); + if (p) + { + *p++ = 0; + shortName = strcpyrealloc(shortName, p); + } + name = strcpyrealloc(name, namebuf); + } + else + return false; + + return ok; +} diff --git a/xbmc/pvrclients/vdr-streamdev/channels.h b/xbmc/pvrclients/vdr-streamdev/channels.h new file mode 100644 index 0000000000..13a43ff9fd --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/channels.h @@ -0,0 +1,326 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __CHANNELS_H +#define __CHANNELS_H + +#include +#include +#include +#include "tools.h" + +#define MAXAPIDS 32 // audio +#define MAXDPIDS 16 // dolby (AC3 + DTS) +#define MAXSPIDS 32 // subtitles +#define MAXCAIDS 8 // conditional access + +#define MAXLANGCODE1 4 // a 3 letter language code, zero terminated +#define MAXLANGCODE2 8 // up to two 3 letter language codes, separated by '+' and zero terminated + +#define CHANNELMOD_NONE 0x00 +#define CHANNELMOD_ALL 0xFF +#define CHANNELMOD_NAME 0x01 +#define CHANNELMOD_PIDS 0x02 +#define CHANNELMOD_ID 0x04 +#define CHANNELMOD_CA 0x10 +#define CHANNELMOD_TRANSP 0x20 +#define CHANNELMOD_LANGS 0x40 +#define CHANNELMOD_RETUNE (CHANNELMOD_PIDS | CHANNELMOD_CA | CHANNELMOD_TRANSP) + +#define CA_FTA 0x0000 +#define CA_DVB_MIN 0x0001 +#define CA_DVB_MAX 0x000F +#define CA_USER_MIN 0x0010 +#define CA_USER_MAX 0x00FF +#define CA_ENCRYPTED_MIN 0x0100 +#define CA_ENCRYPTED_MAX 0xFFFF + +typedef enum fe_type { + FE_QPSK, + FE_QAM, + FE_OFDM, + FE_ATSC +} fe_type_t; + + +typedef enum fe_caps { + FE_IS_STUPID = 0, + FE_CAN_INVERSION_AUTO = 0x1, + FE_CAN_FEC_1_2 = 0x2, + FE_CAN_FEC_2_3 = 0x4, + FE_CAN_FEC_3_4 = 0x8, + FE_CAN_FEC_4_5 = 0x10, + FE_CAN_FEC_5_6 = 0x20, + FE_CAN_FEC_6_7 = 0x40, + FE_CAN_FEC_7_8 = 0x80, + FE_CAN_FEC_8_9 = 0x100, + FE_CAN_FEC_AUTO = 0x200, + FE_CAN_QPSK = 0x400, + FE_CAN_QAM_16 = 0x800, + FE_CAN_QAM_32 = 0x1000, + FE_CAN_QAM_64 = 0x2000, + FE_CAN_QAM_128 = 0x4000, + FE_CAN_QAM_256 = 0x8000, + FE_CAN_QAM_AUTO = 0x10000, + FE_CAN_TRANSMISSION_MODE_AUTO = 0x20000, + FE_CAN_BANDWIDTH_AUTO = 0x40000, + FE_CAN_GUARD_INTERVAL_AUTO = 0x80000, + FE_CAN_HIERARCHY_AUTO = 0x100000, + FE_CAN_8VSB = 0x200000, + FE_CAN_16VSB = 0x400000, + FE_HAS_EXTENDED_CAPS = 0x800000, /* We need more bitspace for newer APIs, indicate this. */ + FE_CAN_2G_MODULATION = 0x10000000, /* frontend supports "2nd generation modulation" (DVB-S2) */ + FE_NEEDS_BENDING = 0x20000000, /* not supported anymore, don't use (frontend requires frequency bending) */ + FE_CAN_RECOVER = 0x40000000, /* frontend can recover from a cable unplug automatically */ + FE_CAN_MUTE_TS = 0x80000000 /* frontend can stop spurious TS data output */ +} fe_caps_t; + +typedef enum fe_sec_voltage { + SEC_VOLTAGE_13, + SEC_VOLTAGE_18, + SEC_VOLTAGE_OFF +} fe_sec_voltage_t; + + +typedef enum fe_sec_tone_mode { + SEC_TONE_ON, + SEC_TONE_OFF +} fe_sec_tone_mode_t; + + +typedef enum fe_sec_mini_cmd { + SEC_MINI_A, + SEC_MINI_B +} fe_sec_mini_cmd_t; + + +typedef enum fe_status { + FE_HAS_SIGNAL = 0x01, /* found something above the noise level */ + FE_HAS_CARRIER = 0x02, /* found a DVB signal */ + FE_HAS_VITERBI = 0x04, /* FEC is stable */ + FE_HAS_SYNC = 0x08, /* found sync bytes */ + FE_HAS_LOCK = 0x10, /* everything's working... */ + FE_TIMEDOUT = 0x20, /* no lock within the last ~2 seconds */ + FE_REINIT = 0x40 /* frontend was reinitialized, */ +} fe_status_t; /* application is recommended to reset */ + /* DiSEqC, tone and parameters */ + +typedef enum fe_spectral_inversion { + INVERSION_OFF, + INVERSION_ON, + INVERSION_AUTO +} fe_spectral_inversion_t; + + +typedef enum fe_code_rate { + FEC_NONE = 0, + FEC_1_2, + FEC_2_3, + FEC_3_4, + FEC_4_5, + FEC_5_6, + FEC_6_7, + FEC_7_8, + FEC_8_9, + FEC_AUTO, + FEC_3_5, + FEC_9_10, +} fe_code_rate_t; + +typedef enum fe_modulation { + QPSK, + QAM_16, + QAM_32, + QAM_64, + QAM_128, + QAM_256, + QAM_AUTO, + VSB_8, + VSB_16, + PSK_8, + APSK_16, + APSK_32, + DQPSK, +} fe_modulation_t; + +typedef enum fe_transmit_mode { + TRANSMISSION_MODE_2K, + TRANSMISSION_MODE_8K, + TRANSMISSION_MODE_AUTO +} fe_transmit_mode_t; + +typedef enum fe_bandwidth { + BANDWIDTH_8_MHZ, + BANDWIDTH_7_MHZ, + BANDWIDTH_6_MHZ, + BANDWIDTH_AUTO +} fe_bandwidth_t; + + +typedef enum fe_guard_interval { + GUARD_INTERVAL_1_32, + GUARD_INTERVAL_1_16, + GUARD_INTERVAL_1_8, + GUARD_INTERVAL_1_4, + GUARD_INTERVAL_AUTO +} fe_guard_interval_t; + + +typedef enum fe_hierarchy { + HIERARCHY_NONE, + HIERARCHY_1, + HIERARCHY_2, + HIERARCHY_4, + HIERARCHY_AUTO +} fe_hierarchy_t; + +typedef enum fe_pilot { + PILOT_ON, + PILOT_OFF, + PILOT_AUTO, +} fe_pilot_t; + +typedef enum fe_rolloff { + ROLLOFF_35, /* Implied value in DVB-S, default for DVB-S2 */ + ROLLOFF_20, + ROLLOFF_25, + ROLLOFF_AUTO, +} fe_rolloff_t; + +typedef enum fe_delivery_system { + SYS_UNDEFINED, + SYS_DVBC_ANNEX_AC, + SYS_DVBC_ANNEX_B, + SYS_DVBT, + SYS_DSS, + SYS_DVBS, + SYS_DVBS2, + SYS_DVBH, + SYS_ISDBT, + SYS_ISDBS, + SYS_ISDBC, + SYS_ATSC, + SYS_ATSCMH, + SYS_DMBTH, + SYS_CMMB, + SYS_DAB, +} fe_delivery_system_t; + +struct tChannelParameterMap { + int userValue; + int driverValue; + const char *userString; +}; + +class cChannel +{ +private: + char *name; + char *shortName; + char *provider; + char *portalName; + int __BeginData__; + int frequency; // MHz + int source; + int srate; + int vpid; + int ppid; + int vtype; + int apids[MAXAPIDS + 1]; // list is zero-terminated + char alangs[MAXAPIDS][MAXLANGCODE2]; + int dpids[MAXDPIDS + 1]; // list is zero-terminated + char dlangs[MAXDPIDS][MAXLANGCODE2]; + int spids[MAXSPIDS + 1]; // list is zero-terminated + char slangs[MAXSPIDS][MAXLANGCODE2]; + int tpid; + int caids[MAXCAIDS + 1]; // list is zero-terminated + int nid; + int tid; + int sid; + int rid; + int number; // Sequence number assigned on load + char polarization; + int inversion; + int bandwidth; + int coderateH; + int coderateL; + int modulation; + int system; + int transmission; + int guard; + int hierarchy; + int rollOff; + int __EndData__; + int modification; + + bool StringToParameters(const char *s); + +public: + cChannel(const PVR_CHANNEL *Channel); + cChannel(); + virtual ~cChannel(); + + bool ReadFromVTP(int channel); + bool Parse(const char *s); + const char *Name(void) const { return name; } + const char *ShortName(bool OrName = false) const { return (OrName && isempty(shortName)) ? name : shortName; } + const char *Provider(void) const { return provider; } + const char *PortalName(void) const { return portalName; } + int Frequency(void) const { return frequency; } ///< Returns the actual frequency, as given in 'channels.conf' + int Source(void) const { return source; } + int Srate(void) const { return srate; } + int Vpid(void) const { return vpid; } + int Ppid(void) const { return ppid; } + int Vtype(void) const { return vtype; } + const int *Apids(void) const { return apids; } + const int *Dpids(void) const { return dpids; } + const int *Spids(void) const { return spids; } + int Apid(int i) const { return (0 <= i && i < MAXAPIDS) ? apids[i] : 0; } + int Dpid(int i) const { return (0 <= i && i < MAXDPIDS) ? dpids[i] : 0; } + int Spid(int i) const { return (0 <= i && i < MAXSPIDS) ? spids[i] : 0; } + const char *Alang(int i) const { return (0 <= i && i < MAXAPIDS) ? alangs[i] : ""; } + const char *Dlang(int i) const { return (0 <= i && i < MAXDPIDS) ? dlangs[i] : ""; } + const char *Slang(int i) const { return (0 <= i && i < MAXSPIDS) ? slangs[i] : ""; } + int Tpid(void) const { return tpid; } + const int *Caids(void) const { return caids; } + int Ca(int Index = 0) const { return Index < MAXCAIDS ? caids[Index] : 0; } + int Nid(void) const { return nid; } + int Tid(void) const { return tid; } + int Sid(void) const { return sid; } + int Rid(void) const { return rid; } + int Number(void) const { return number; } + void SetNumber(int Number) { number = Number; } + char Polarization(void) const { return polarization; } + int Inversion(void) const { return inversion; } + int Bandwidth(void) const { return bandwidth; } + int CoderateH(void) const { return coderateH; } + int CoderateL(void) const { return coderateL; } + int Modulation(void) const { return modulation; } + int System(void) const { return system; } + int Transmission(void) const { return transmission; } + int Guard(void) const { return guard; } + int Hierarchy(void) const { return hierarchy; } + int RollOff(void) const { return rollOff; } + +}; + +#endif //__TIMERS_H diff --git a/xbmc/pvrclients/vdr-streamdev/channelscan.cpp b/xbmc/pvrclients/vdr-streamdev/channelscan.cpp new file mode 100644 index 0000000000..2711379408 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/channelscan.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "channelscan.h" + +cVDRChannelScan::cVDRChannelScan() +{ + +} + +cVDRChannelScan::~cVDRChannelScan() +{ + +} + +void cVDRChannelScan::Start() +{ + +} diff --git a/xbmc/pvrclients/vdr-streamdev/channelscan.h b/xbmc/pvrclients/vdr-streamdev/channelscan.h new file mode 100644 index 0000000000..7439195039 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/channelscan.h @@ -0,0 +1,33 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +class cVDRChannelScan +{ +public: + cVDRChannelScan(); + ~cVDRChannelScan(); + + void Start(); + bool Possible() { return true; } + + +}; diff --git a/xbmc/pvrclients/vdr-streamdev/client.cpp b/xbmc/pvrclients/vdr-streamdev/client.cpp new file mode 100644 index 0000000000..51e5c5e003 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/client.cpp @@ -0,0 +1,1225 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "client.h" +#include "tools.h" +#include "vtptransceiver.h" +#include "channelscan.h" +#include "ringbuffer.h" +#include "xbmc_pvr_dll.h" + +using namespace std; + +class cTSBuffer; +class cDataResp; + +#define SEEK_POSSIBLE 0x10 // flag used to check if protocol allows seeks + +/* User adjustable settings are saved here. + * Default values are defined inside client.h + * and exported to the other source files. + */ +CStdString g_szHostname = DEFAULT_HOST; ///< The Host name or IP of the VDR +int g_iPort = DEFAULT_PORT; ///< The VTP Port of the VDR (default is 2004) +int g_iPriority = DEFAULT_PRIORITY; ///< The Priority this client have in response to other clients +int g_iConnectTimeout = DEFAULT_TIMEOUT; ///< The Socket connection timeout +bool g_bOnlyFTA = DEFAULT_FTA_ONLY; ///< Send only Free-To-Air Channels inside Channel list to XBMC +bool g_bRadioEnabled = DEFAULT_RADIO; ///< Send also Radio channels list to XBMC +bool g_bCharsetConv = DEFAULT_CHARCONV; ///< Convert VDR's incoming strings to UTF8 character set +bool g_bNoBadChannels = DEFAULT_BADCHANNELS; ///< Ignore channels without a PID, APID and DPID +bool g_bHandleMessages = DEFAULT_HANDLE_MSG; ///< Send VDR's OSD status messages to XBMC OSD +bool g_bUseRecordingsDir = DEFAULT_USE_REC_DIR; ///< Use a normal directory if true for recordings +CStdString g_szRecordingsDir = DEFAULT_REC_DIR; ///< The path to the recordings directory +// +///* Client member variables */ +uint64_t m_currentPlayingRecordBytes; +uint32_t m_currentPlayingRecordFrames; +uint64_t m_currentPlayingRecordPosition; +uint64_t m_recordingTotalBytesReaded; +bool m_recordingFirstRead; +cPoller *m_recordingPoller = NULL; +char m_noSignalStreamData[ 6 + 0xffff ]; +long m_noSignalStreamSize = 0; +long m_noSignalStreamReadPos = 0; +bool m_bPlayingNoSignal = false; +int m_iCurrentChannel = 1; +ADDON_STATUS m_CurStatus = STATUS_UNKNOWN; +cTSBuffer *m_TSBuffer = NULL; +cDataResp *m_pDataResponse = NULL; +bool g_bCreated = false; +int g_iClientID = -1; +CStdString g_szUserPath = ""; +CStdString g_szClientPath = ""; +cHelper_libXBMC_addon *XBMC = NULL; +cHelper_libXBMC_pvr *PVR = NULL; + + + +/*********************************************************** + * Internal functions + ***********************************************************/ + +/// Derived cDevice classes that can receive channels will have to provide +/// Transport Stream (TS) packets one at a time. cTSBuffer implements a +/// simple buffer that allows the device to read a larger amount of data +/// from the driver with each call to Read(), thus avoiding the overhead +/// of getting each TS packet separately from the driver. It also makes +/// sure the returned data points to a TS packet and automatically +/// re-synchronizes after broken packets. + +class cTSBuffer : public cThread +{ +private: + int f; + bool delivered; + cRingBufferLinear *ringBuffer; + virtual void Action(void); +public: + cTSBuffer(int File, int Size); + ~cTSBuffer(); + unsigned char *Get(void); +}; + +cTSBuffer::cTSBuffer(int File, int Size) +{ + SetDescription("TS Live buffer"); + f = File; + delivered = false; + ringBuffer = new cRingBufferLinear(Size, TS_SIZE, true, "TS"); + ringBuffer->SetTimeouts(100, 100); + Start(); +} + +cTSBuffer::~cTSBuffer() +{ + Cancel(3); + delete ringBuffer; +} + +void cTSBuffer::Action(void) +{ + if (ringBuffer) + { + bool firstRead = true; + cPoller Poller(f); + while (Running()) + { + if (firstRead || Poller.Poll(100)) + { + firstRead = false; + int r = ringBuffer->Read(f); + if (r < 0 && FATALERRNO) + { + if (errno == EOVERFLOW) + XBMC->Log(LOG_ERROR, "driver buffer overflow"); + else + { + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); + break; + } + } + } + } + } +} + +unsigned char *cTSBuffer::Get(void) +{ + int Count = 0; + if (delivered) + { + ringBuffer->Del(TS_SIZE); + delivered = false; + } + unsigned char *p = ringBuffer->Get(Count); + if (p && Count >= TS_SIZE) + { + if (*p != TS_SYNC_BYTE) + { + for (int i = 1; i < Count; i++) + { + if (p[i] == TS_SYNC_BYTE) + { + Count = i; + break; + } + } + ringBuffer->Del(Count); + XBMC->Log(LOG_ERROR, "skipped %d bytes to sync on TS packet", Count); + return NULL; + } + delivered = true; + return p; + } + return NULL; +} + +class cDataResp : public cThread +{ +private: + virtual void Action(void); + bool VDRToXBMCCommand(char *Cmd); + bool CallBackMODT(const char *Option); + bool CallBackDELT(const char *Option); + bool CallBackADDT(const char *Option); + bool CallBackSMSG(const char *Option); + bool CallBackIMSG(const char *Option); + bool CallBackWMSG(const char *Option); + bool CallBackEMSG(const char *Option); + +public: + cDataResp(); + ~cDataResp(); +}; + + +cDataResp::cDataResp() +{ + /* Open Streamdev-Server VTP-Connection to VDR Backend Server */ + if (!VTPTransceiver.CheckConnection()) + return; + + if (!VTPTransceiver.CreateDataConnection(siDataRespond)) + { + XBMC->Log(LOG_ERROR, "Couldn't create socket for data response"); + return; + } + + SetDescription("VDR Data Response"); + Start(); +} + +cDataResp::~cDataResp() +{ + Cancel(3); + VTPTransceiver.CloseDataConnection(siDataRespond); +} + +void cDataResp::Action(void) +{ + char data[1024]; + fd_set set_r, set_e; + struct timeval tv; + int ret; + memset(data,0,1024); + + while (Running()) + { + tv.tv_sec = 5; + tv.tv_usec = 0; + + FD_ZERO(&set_r); + FD_ZERO(&set_e); + FD_SET(VTPTransceiver.DataSocket(siDataRespond), &set_r); + FD_SET(VTPTransceiver.DataSocket(siDataRespond), &set_e); + ret = __select(FD_SETSIZE, &set_r, NULL, &set_e, &tv); + if (ret < 0) + { + XBMC->Log(LOG_ERROR, "CallbackRcvThread - select failed"); + continue; + } + else if (ret == 0) + continue; + + ret = __recv(VTPTransceiver.DataSocket(siDataRespond), (char*)data, sizeof(data), 0); + if (ret < 0) + { + XBMC->Log(LOG_ERROR, "CallbackRcvThread - receive failed"); + continue; + } + else if (ret == 0) + continue; + + /* Check the received command and perform associated action*/ + VDRToXBMCCommand(data); + memset(data,0,1024); + } + return; +} + +bool cDataResp::VDRToXBMCCommand(char *Cmd) +{ + char *param = NULL; + + if (Cmd != NULL) + { + if ((param = strchr(Cmd, ' ')) != NULL) + *(param++) = '\0'; + else + param = Cmd + strlen(Cmd); + } + else + { + XBMC->Log(LOG_ERROR, "VDRToXBMCCommand - called without command from %s:%d", g_szHostname.c_str(), g_iPort); + return false; + } + +/* !!! DISABLED UNTIL STREAMDEV HAVE SUPPORT FOR IT INSIDE CVS !!! + if (strcasecmp(Cmd, "MODT") == 0) return CallBackMODT(param); + else if (strcasecmp(Cmd, "DELT") == 0) return CallBackDELT(param); + else if (strcasecmp(Cmd, "ADDT") == 0) return CallBackADDT(param); +*/ + + if (strcasecmp(Cmd, "IMSG") == 0) return CallBackIMSG(param); + else if (strcasecmp(Cmd, "WMSG") == 0) return CallBackWMSG(param); + else if (strcasecmp(Cmd, "EMSG") == 0) return CallBackEMSG(param); + else + { + XBMC->Log(LOG_ERROR, "VDRToXBMCCommand - Unkown respond command %s", Cmd); + return false; + } +} + +bool cDataResp::CallBackMODT(const char *Option) +{ +// PVR_event_callback(PVR_EVENT_TIMERS_CHANGE, ""); + return true; +} + +bool cDataResp::CallBackDELT(const char *Option) +{ +// PVR_event_callback(PVR_EVENT_TIMERS_CHANGE, ""); + return true; +} + +bool cDataResp::CallBackADDT(const char *Option) +{ +// PVR_event_callback(PVR_EVENT_TIMERS_CHANGE, ""); + return true; +} + +bool cDataResp::CallBackIMSG(const char *Option) +{ + if (*Option) + { + CStdString text = Option; + if (g_bCharsetConv) + XBMC->UnknownToUTF8(text); + XBMC->QueueNotification(QUEUE_INFO, text.c_str()); + return true; + } + else + { + XBMC->Log(LOG_ERROR, "CallBackIMSG - missing option"); + return false; + } +} + +bool cDataResp::CallBackWMSG(const char *Option) +{ + if (*Option) + { + CStdString text = Option; + if (g_bCharsetConv) + XBMC->UnknownToUTF8(text); + XBMC->QueueNotification(QUEUE_WARNING, text.c_str()); + return true; + } + else + { + XBMC->Log(LOG_ERROR, "CallBackWMSG - missing option"); + return false; + } +} + +bool cDataResp::CallBackEMSG(const char *Option) +{ + if (*Option) + { + CStdString text = Option; + if (g_bCharsetConv) + XBMC->UnknownToUTF8(text); + XBMC->QueueNotification(QUEUE_ERROR, text.c_str()); + return true; + } + else + { + XBMC->Log(LOG_ERROR, "CallBackEMSG - missing option"); + return false; + } +} + + +bool readNoSignalStream() +{ + CStdString noSignalFileName = g_szClientPath + "/resources/data/noSignal.mpg"; + + FILE *const f = fopen(noSignalFileName.c_str(), "rb"); + if (f) + { + m_noSignalStreamSize = fread(&m_noSignalStreamData[0] + 9, 1, sizeof (m_noSignalStreamData) - 9 - 9 - 4, f); + if (m_noSignalStreamSize == sizeof (m_noSignalStreamData) - 9 - 9 - 4) + { + XBMC->Log(LOG_ERROR, "readNoSignalStream - '%s' exeeds limit of %ld bytes!", noSignalFileName.c_str(), (long)(sizeof (m_noSignalStreamData) - 9 - 9 - 4 - 1)); + } + else if (m_noSignalStreamSize > 0) + { + m_noSignalStreamData[ 0 ] = 0x00; + m_noSignalStreamData[ 1 ] = 0x00; + m_noSignalStreamData[ 2 ] = 0x01; + m_noSignalStreamData[ 3 ] = 0xe0; + m_noSignalStreamData[ 4 ] = (m_noSignalStreamSize + 3) >> 8; + m_noSignalStreamData[ 5 ] = (m_noSignalStreamSize + 3) & 0xff; + m_noSignalStreamData[ 6 ] = 0x80; + m_noSignalStreamData[ 7 ] = 0x00; + m_noSignalStreamData[ 8 ] = 0x00; + m_noSignalStreamSize += 9; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x01; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0xe0; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x07; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x80; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x01; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0xb7; + } + fclose(f); + return true; + } + else + { + XBMC->Log(LOG_ERROR, "readNoSignalStream - couldn't open '%s'!", noSignalFileName.c_str()); + } + + return false; +} + +int writeNoSignalStream(unsigned char* buf, int buf_size) +{ + int sizeToWrite = m_noSignalStreamSize-m_noSignalStreamReadPos; + m_bPlayingNoSignal = true; + if (buf_size > sizeToWrite) + { + memcpy(buf, m_noSignalStreamData+m_noSignalStreamReadPos, sizeToWrite); + m_noSignalStreamReadPos = 0; + return sizeToWrite; + } + else + { + memcpy(buf, m_noSignalStreamData+m_noSignalStreamReadPos, buf_size); + m_noSignalStreamReadPos += buf_size; + return buf_size; + } +} + +extern "C" { + +/*********************************************************** + * Standart AddOn related public library functions + ***********************************************************/ + +ADDON_STATUS Create(void* hdl, void* props) +{ + if (!props) + return STATUS_UNKNOWN; + + PVR_PROPS* pvrprops = (PVR_PROPS*)props; + + XBMC = new cHelper_libXBMC_addon; + if (!XBMC->RegisterMe(hdl)) + return STATUS_UNKNOWN; + + PVR = new cHelper_libXBMC_pvr; + if (!PVR->RegisterMe(hdl)) + return STATUS_UNKNOWN; + + XBMC->Log(LOG_DEBUG, "Creating VDR PVR-Client"); + + m_CurStatus = STATUS_UNKNOWN; + g_iClientID = pvrprops->clientID; + g_szUserPath = pvrprops->userpath; + g_szClientPath = pvrprops->clientpath; + + /* Read setting "host" from settings.xml */ + char * buffer; + buffer = (char*) malloc (1024); + buffer[0] = 0; /* Set the end of string */ + + if (XBMC->GetSetting("host", buffer)) + g_szHostname = buffer; + else + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'host' setting, falling back to '127.0.0.1' as default"); + g_szHostname = DEFAULT_HOST; + } + free (buffer); + + /* Read setting "port" from settings.xml */ + if (!XBMC->GetSetting("port", &g_iPort)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'port' setting, falling back to '2004' as default"); + g_iPort = DEFAULT_PORT; + } + + /* Read setting "priority" from settings.xml */ + if (!XBMC->GetSetting("priority", &g_iPriority)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'priority' setting, falling back to %i as default", DEFAULT_PRIORITY); + g_iPriority = DEFAULT_PRIORITY; + } + + /* Read setting "ftaonly" from settings.xml */ + if (!XBMC->GetSetting("ftaonly", &g_bOnlyFTA)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'ftaonly' setting, falling back to 'false' as default"); + g_bOnlyFTA = DEFAULT_FTA_ONLY; + } + + /* Read setting "useradio" from settings.xml */ + if (!XBMC->GetSetting("useradio", &g_bRadioEnabled)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'useradio' setting, falling back to 'true' as default"); + g_bRadioEnabled = DEFAULT_RADIO; + } + + /* Read setting "convertchar" from settings.xml */ + if (!XBMC->GetSetting("convertchar", &g_bCharsetConv)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'convertchar' setting, falling back to 'false' as default"); + g_bCharsetConv = DEFAULT_CHARCONV; + } + + /* Read setting "timeout" from settings.xml */ + if (!XBMC->GetSetting("timeout", &g_iConnectTimeout)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'timeout' setting, falling back to %i seconds as default", DEFAULT_TIMEOUT); + g_iConnectTimeout = DEFAULT_TIMEOUT; + } + + /* Read setting "ignorechannels" from settings.xml */ + if (!XBMC->GetSetting("ignorechannels", &g_bNoBadChannels)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'ignorechannels' setting, falling back to 'true' as default"); + g_bNoBadChannels = DEFAULT_BADCHANNELS; + } + + /* Read setting "ignorechannels" from settings.xml */ + if (!XBMC->GetSetting("handlemessages", &g_bHandleMessages)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'handlemessages' setting, falling back to 'true' as default"); + g_bHandleMessages = DEFAULT_HANDLE_MSG; + } + + /* Read setting "ignorechannels" from settings.xml */ + if (!XBMC->GetSetting("usedirectory", &g_bUseRecordingsDir)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'usedirectory' setting, falling back to 'false' as default"); + g_bUseRecordingsDir = DEFAULT_USE_REC_DIR; + } + + if (g_bUseRecordingsDir) + { + /* Read setting "recordingdir" from settings.xml */ + buffer = (char*) malloc (2048); + buffer[0] = 0; /* Set the end of string */ + + if (XBMC->GetSetting("recordingdir", buffer)) + g_szRecordingsDir = buffer; + else + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'recordingdir' setting, directory not set"); + g_szRecordingsDir = DEFAULT_REC_DIR; + g_bUseRecordingsDir = false; + } + free (buffer); + } + + /* Create connection to streamdev-server */ + if (!VTPTransceiver.CheckConnection()) + { + m_CurStatus = STATUS_LOST_CONNECTION; + return m_CurStatus; + } + + /* Check VDR streamdev is patched by calling a newly added command */ + if (VTPTransceiver.GetNumChannels() == -1) + { + XBMC->Log(LOG_ERROR, "PCRClient-vdr: Detected unsupported Streamdev-Version"); + m_CurStatus = STATUS_UNKNOWN; + return STATUS_UNKNOWN; + } + m_CurStatus = STATUS_OK; + + readNoSignalStream(); + if (g_bHandleMessages) + m_pDataResponse = new cDataResp(); + + g_bCreated = true; + return m_CurStatus; +} + +void Destroy() +{ + if (g_bCreated) + { + DELETENULL(m_TSBuffer); + DELETENULL(m_pDataResponse); + VTPTransceiver.Quit(); + VTPTransceiver.Reset(); + g_bCreated = false; + } + m_CurStatus = STATUS_UNKNOWN; +} + +ADDON_STATUS GetStatus() +{ + return m_CurStatus; +} + +bool HasSettings() +{ + return true; +} + +unsigned int GetSettings(StructSetting ***sSet) +{ + return 0; +} + +ADDON_STATUS SetSetting(const char *settingName, const void *settingValue) +{ + string str = settingName; + if (str == "host") + { + string tmp_sHostname; + XBMC->Log(LOG_INFO, "Changed Setting 'host' from %s to %s", g_szHostname.c_str(), (const char*) settingValue); + tmp_sHostname = g_szHostname; + g_szHostname = (const char*) settingValue; + if (tmp_sHostname != g_szHostname) + return STATUS_NEED_RESTART; + } + else if (str == "port") + { + XBMC->Log(LOG_INFO, "Changed Setting 'port' from %u to %u", g_iPort, *(int*) settingValue); + if (g_iPort != *(int*) settingValue) + { + g_iPort = *(int*) settingValue; + return STATUS_NEED_RESTART; + } + } + else if (str == "priority") + { + XBMC->Log(LOG_INFO, "Changed Setting 'priority' from %u to %u", g_iPriority, *(int*) settingValue); + g_iPriority = *(int*) settingValue; + } + else if (str == "ftaonly") + { + XBMC->Log(LOG_INFO, "Changed Setting 'ftaonly' from %u to %u", g_bOnlyFTA, *(bool*) settingValue); + g_bOnlyFTA = *(bool*) settingValue; + } + else if (str == "useradio") + { + XBMC->Log(LOG_INFO, "Changed Setting 'useradio' from %u to %u", g_bRadioEnabled, *(bool*) settingValue); + g_bRadioEnabled = *(bool*) settingValue; + } + else if (str == "convertchar") + { + XBMC->Log(LOG_INFO, "Changed Setting 'convertchar' from %u to %u", g_bCharsetConv, *(bool*) settingValue); + g_bCharsetConv = *(bool*) settingValue; + } + else if (str == "timeout") + { + XBMC->Log(LOG_INFO, "Changed Setting 'timeout' from %u to %u", g_iConnectTimeout, *(int*) settingValue); + g_iConnectTimeout = *(int*) settingValue; + } + else if (str == "ignorechannels") + { + XBMC->Log(LOG_INFO, "Changed Setting 'ignorechannels' from %u to %u", g_bNoBadChannels, *(bool*) settingValue); + g_bNoBadChannels = *(bool*) settingValue; + } + else if (str == "handlemessages") + { + XBMC->Log(LOG_INFO, "Changed Setting 'handlemessages' from %u to %u", g_bHandleMessages, *(bool*) settingValue); + g_bHandleMessages = *(bool*) settingValue; + } + else if (str == "usedirectory") + { + XBMC->Log(LOG_INFO, "Changed Setting 'usedirectory' from %u to %u", g_bUseRecordingsDir, *(bool*) settingValue); + g_bUseRecordingsDir = *(bool*) settingValue; + } + else if (str == "recordingdir") + { + XBMC->Log(LOG_INFO, "Changed Setting 'recordingdir' from %s to %s", g_szRecordingsDir.c_str(), (const char*) settingValue); + g_bUseRecordingsDir = (const char*) settingValue; + } + + return STATUS_OK; +} + +void Stop() +{ +} + +void FreeSettings() +{ +} + + +/*********************************************************** + * PVR Client AddOn specific public library functions + ***********************************************************/ + +PVR_ERROR GetProperties(PVR_SERVERPROPS* props) +{ + props->SupportChannelLogo = false; + props->SupportTimeShift = false; + props->SupportEPG = true; + props->SupportRecordings = true; + props->SupportTimers = true; + props->SupportTV = true; + props->SupportRadio = g_bRadioEnabled; + props->SupportChannelSettings = false; + props->SupportDirector = false; + props->SupportBouquets = false; + props->HandleInputStream = true; + props->HandleDemuxing = false; + props->SupportChannelScan = false; + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR GetStreamProperties(PVR_STREAMPROPS* props) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +const char * GetBackendName() +{ + static CStdString BackendName = VTPTransceiver.GetBackendName(); + return BackendName.c_str(); +} + +const char * GetBackendVersion() +{ + static CStdString BackendName = VTPTransceiver.GetBackendVersion(); + return BackendName.c_str(); +} + +const char * GetConnectionString() +{ + static CStdString ConnectionString; + ConnectionString.Format("%s:%i%s", g_szHostname.c_str(), g_iPort, VTPTransceiver.CheckConnection() ? "" : " (Not connected!)"); + return ConnectionString.c_str(); +} + +PVR_ERROR GetDriveSpace(long long *total, long long *used) +{ + return VTPTransceiver.GetDriveSpace(total, used); +} + +PVR_ERROR GetBackendTime(time_t *localTime, int *gmtOffset) +{ + return VTPTransceiver.GetBackendTime(localTime, gmtOffset); +} + +PVR_ERROR DialogChannelScan() +{ + cVDRChannelScan *VDRChannelScan = new cVDRChannelScan; + if (VDRChannelScan->Possible()) + { + + return PVR_ERROR_NO_ERROR; + } + return PVR_ERROR_NOT_POSSIBLE; +} + +PVR_ERROR MenuHook(const PVR_MENUHOOK &menuhook) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +/*******************************************/ +/** PVR EPG Functions **/ + +PVR_ERROR RequestEPGForChannel(PVRHANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end) +{ + return VTPTransceiver.RequestEPGForChannel(channel, handle, start, end); +} + + +/*******************************************/ +/** PVR Bouquets Functions **/ + +int GetNumBouquets() +{ + return 0; +} + +PVR_ERROR RequestBouquetsList(PVRHANDLE handle, int radio) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + + +/*******************************************/ +/** PVR Channel Functions **/ + +int GetNumChannels() +{ + return VTPTransceiver.GetNumChannels(); +} + +PVR_ERROR RequestChannelList(PVRHANDLE handle, int radio) +{ + return VTPTransceiver.RequestChannelList(handle, radio); +} + +PVR_ERROR DeleteChannel(unsigned int number) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR RenameChannel(unsigned int number, const char *newname) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR MoveChannel(unsigned int number, unsigned int newnumber) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR DialogChannelSettings(const PVR_CHANNEL &channelinfo) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR DialogAddChannel(const PVR_CHANNEL &channelinfo) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + + +/*******************************************/ +/** PVR Recording Functions **/ + +int GetNumRecordings(void) +{ + return VTPTransceiver.GetNumRecordings(); +} + +PVR_ERROR RequestRecordingsList(PVRHANDLE handle) +{ + return VTPTransceiver.RequestRecordingsList(handle); +} + +PVR_ERROR DeleteRecording(const PVR_RECORDINGINFO &recinfo) +{ + return VTPTransceiver.DeleteRecording(recinfo); +} + +PVR_ERROR RenameRecording(const PVR_RECORDINGINFO &recinfo, const char *newname) +{ + return VTPTransceiver.RenameRecording(recinfo, newname); +} + + +/*******************************************/ +/** PVR Recording cut marks Functions **/ + +bool HaveCutmarks() +{ + return false; +} + +PVR_ERROR RequestCutMarksList(PVRHANDLE handle) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR AddCutMark(const PVR_CUT_MARK &cutmark) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR DeleteCutMark(const PVR_CUT_MARK &cutmark) +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + +PVR_ERROR StartCut() +{ + return PVR_ERROR_NOT_IMPLEMENTED; +} + + +/*******************************************/ +/** PVR Timer Functions **/ + +int GetNumTimers(void) +{ + return VTPTransceiver.GetNumTimers(); +} + +PVR_ERROR RequestTimerList(PVRHANDLE handle) +{ + return VTPTransceiver.RequestTimerList(handle); +} + +PVR_ERROR AddTimer(const PVR_TIMERINFO &timerinfo) +{ + return VTPTransceiver.AddTimer(timerinfo); +} + +PVR_ERROR DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force) +{ + return VTPTransceiver.DeleteTimer(timerinfo, force); +} + +PVR_ERROR RenameTimer(const PVR_TIMERINFO &timerinfo, const char *newname) +{ + return VTPTransceiver.RenameTimer(timerinfo, newname); +} + +PVR_ERROR UpdateTimer(const PVR_TIMERINFO &timerinfo) +{ + return VTPTransceiver.UpdateTimer(timerinfo); +} + + +/*******************************************/ +/** PVR Live Stream Functions **/ + +bool OpenLiveStream(const PVR_CHANNEL &channelinfo) +{ + if (!VTPTransceiver.CheckConnection()) + return false; + + if (!VTPTransceiver.ProvidesChannel(channelinfo.number, g_iPriority)) + { + XBMC->Log(LOG_ERROR, "VDR does not provide channel %i", channelinfo.number); + return false; + } + + if (!VTPTransceiver.SetChannelDevice(channelinfo.number)) + { + XBMC->Log(LOG_ERROR, "Could't tune to channel %i", channelinfo.number); + return false; + } + + if (!VTPTransceiver.CreateDataConnection(siLive)) + { + XBMC->Log(LOG_ERROR, "Could't create connection to VDR for Live streaming"); + return false; + } + + m_iCurrentChannel = channelinfo.number; + m_noSignalStreamReadPos = 0; + m_bPlayingNoSignal = false; + m_TSBuffer = new cTSBuffer(VTPTransceiver.DataSocket(siLive), MEGABYTE(2)); + return true; +} + +void CloseLiveStream() +{ + if (!VTPTransceiver.CheckConnection()) + XBMC->Log(LOG_DEBUG, "CloseLiveStream(): Control connection gone !"); + + VTPTransceiver.CloseDataConnection(siLive); + m_iCurrentChannel = 1; + DELETENULL(m_TSBuffer); + return; +} + +int ReadLiveStream(unsigned char* buf, int buf_size) +{ + bool tryReconnect = true; + int TSReadNeeded = buf_size / TS_SIZE; + int TSReadDone = 0; + static int read_timeouts = 0; + + while (TSReadDone < TSReadNeeded) + { + if (!m_TSBuffer) + return writeNoSignalStream(buf, buf_size); + + unsigned char *Data = m_TSBuffer->Get(); + if (!Data) + { + if (m_bPlayingNoSignal) + return writeNoSignalStream(buf, buf_size); + + if (VTPTransceiver.DataSocket(siLive) == INVALID_SOCKET) + { + if (tryReconnect) + { + tryReconnect = false; + XBMC->Log(LOG_INFO, "Streaming connections lost during ReadLiveStream, trying reconnect"); + + if (!VTPTransceiver.ProvidesChannel(m_iCurrentChannel, g_iPriority)) + return -1; + + DELETENULL(m_TSBuffer); + VTPTransceiver.CloseDataConnection(siLive); + + if (!VTPTransceiver.SetChannelDevice(m_iCurrentChannel)) + continue; /* Continue here to read NoSignal stream */ + + if (!VTPTransceiver.CreateDataConnection(siLive)) + continue; /* Continue here to read NoSignal stream */ + + m_noSignalStreamReadPos = 0; + m_bPlayingNoSignal = false; + m_TSBuffer = new cTSBuffer(VTPTransceiver.DataSocket(siLive), MEGABYTE(2)); + continue; + } + XBMC->Log(LOG_ERROR, "Reconnect in ReadLiveStream not possible"); + continue; /* Continue here to read NoSignal stream */ + } + + if (read_timeouts > 20) + { + XBMC->Log(LOG_INFO, "No data in 2 seconds, queuing no signal image"); + read_timeouts = 0; + return writeNoSignalStream(buf, buf_size); + } + read_timeouts++; + usleep(10*1000); + + continue; + } + memcpy(buf+TSReadDone*TS_SIZE, Data, TS_SIZE); + TSReadDone++; + } + read_timeouts = 0; + m_bPlayingNoSignal = false; + return TSReadDone*TS_SIZE; +} + +int GetCurrentClientChannel() +{ + return m_iCurrentChannel; +} + +bool SwitchChannel(const PVR_CHANNEL &channelinfo) +{ + if (!VTPTransceiver.CheckConnection()) + return false; + + if (!VTPTransceiver.ProvidesChannel(channelinfo.number, g_iPriority)) + { + XBMC->Log(LOG_ERROR, "VDR does not provide channel %i", channelinfo.number); + return false; + } + + DELETENULL(m_TSBuffer); + VTPTransceiver.CloseDataConnection(siLive); + + if (!VTPTransceiver.SetChannelDevice(channelinfo.number)) + { + XBMC->Log(LOG_ERROR, "Could't tune to channel %i", channelinfo.number); + return false; + } + + if (!VTPTransceiver.CreateDataConnection(siLive)) + { + XBMC->Log(LOG_ERROR, "Could't create connection to VDR for Live streaming"); + return false; + } + + m_iCurrentChannel = channelinfo.number; + m_noSignalStreamReadPos = 0; + m_bPlayingNoSignal = false; + m_TSBuffer = new cTSBuffer(VTPTransceiver.DataSocket(siLive), MEGABYTE(2)); + return true; +} + +PVR_ERROR SignalQuality(PVR_SIGNALQUALITY &qualityinfo) +{ + return VTPTransceiver.SignalQuality(qualityinfo, m_iCurrentChannel); +} + + +/*******************************************/ +/** PVR Secondary Stream Functions **/ + +bool SwapLiveTVSecondaryStream() +{ + return false; +} + +bool OpenSecondaryStream(const PVR_CHANNEL &channelinfo) +{ + return false; +} + +void CloseSecondaryStream() +{ + +} + +int ReadSecondaryStream(unsigned char* buf, int buf_size) +{ + return 0; +} + + +/*******************************************/ +/** PVR Recording Stream Functions **/ + +bool OpenRecordedStream(const PVR_RECORDINGINFO &recinfo) +{ + if (!VTPTransceiver.CheckConnection()) + return false; + + if (!VTPTransceiver.SetRecordingIndex(recinfo.index)) + { + XBMC->Log(LOG_ERROR, "Could't open recording %i", recinfo.index); + return false; + } + + if (!VTPTransceiver.CreateDataConnection(siReplay)) + { + XBMC->Log(LOG_ERROR, "Could't create connection to VDR for recording streaming"); + return false; + } + + m_currentPlayingRecordPosition = 0; + m_recordingTotalBytesReaded = 0; + m_recordingPoller = new cPoller(VTPTransceiver.DataSocket(siReplay)); + m_recordingFirstRead = true; + return true; +} + +void CloseRecordedStream(void) +{ + if (!VTPTransceiver.CheckConnection()) + XBMC->Log(LOG_DEBUG, "CloseRecordedStream(): Control connection gone !"); + + VTPTransceiver.CloseDataConnection(siReplay); + + DELETENULL(m_recordingPoller); +} + +int ReadRecordedStream(unsigned char* buf, int buf_size) +{ + if (VTPTransceiver.DataSocket(siReplay) == INVALID_SOCKET) + return 0; + + int res = 0; + if (m_recordingFirstRead || m_recordingPoller->Poll(100)) + { + m_recordingFirstRead = false; + res = safe_read(VTPTransceiver.DataSocket(siReplay), buf, buf_size); + if (res < 0 && FATALERRNO) + { + if (errno == EOVERFLOW) + XBMC->Log(LOG_ERROR, "driver buffer overflow"); + else + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); + return 0; + } + } + + m_currentPlayingRecordPosition += res; + m_recordingTotalBytesReaded += res; + return res; +} + +long long SeekRecordedStream(long long pos, int whence) +{ + if (!VTPTransceiver.CheckConnection()) + return -1; + + long long nextPos = m_currentPlayingRecordPosition; + + switch (whence) + { + case SEEK_SET: + nextPos = pos; + break; + + case SEEK_CUR: + nextPos += pos; + break; + + case SEEK_END: + if (m_currentPlayingRecordBytes) + nextPos = m_currentPlayingRecordBytes - pos; + else + return -1; + break; + + case SEEK_POSSIBLE: + return 1; + + default: + return -1; + } + + if (nextPos > (long long) m_currentPlayingRecordBytes) + return 0; + + m_currentPlayingRecordPosition = nextPos; + + /* The VDR streamdev seek command returns the amount which is sendet into the + network. */ + uint64_t bytesToFlush = VTPTransceiver.SeekRecordingPosition(nextPos); + /* I don't like the following code, but have no better idea todo this. + It flush the already by VDR transmitted data from the TCP stack, so if XBMC + perform the next Data read the right stream position is on top of the Socket. */ + while (m_recordingTotalBytesReaded < bytesToFlush) + { + unsigned char buffer[32768]; + ReadRecordedStream(&*buffer, sizeof(buffer)); + } + return nextPos; +} + +long long PositionRecordedStream(void) +{ + return m_currentPlayingRecordPosition; +} + +long long LengthRecordedStream(void) +{ + VTPTransceiver.GetPlayingRecordingSize(&m_currentPlayingRecordBytes, &m_currentPlayingRecordFrames); + return m_currentPlayingRecordBytes; +} + + +/** UNUSED API FUNCTIONS */ +DemuxPacket* DemuxRead() { return NULL; } +void DemuxAbort() {} +void DemuxReset() {} +void DemuxFlush() {} +long long SeekLiveStream(long long pos, int whence) { return -1; } +long long PositionLiveStream(void) { return -1; } +long long LengthLiveStream(void) { return -1; } +const char * GetLiveStreamURL(const PVR_CHANNEL &channelinfo) { return ""; } + +} //end extern "C" diff --git a/xbmc/pvrclients/vdr-streamdev/client.h b/xbmc/pvrclients/vdr-streamdev/client.h new file mode 100644 index 0000000000..76bc3a8522 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/client.h @@ -0,0 +1,62 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef CLIENT_H +#define CLIENT_H + +#include "StdString.h" +#include "../../../addons/library.xbmc.addon/libXBMC_addon.h" +#include "../../../addons/library.xbmc.pvr/libXBMC_pvr.h" + +#define DEFAULT_HOST "127.0.0.1" +#define DEFAULT_PORT 2004 +#define DEFAULT_FTA_ONLY false +#define DEFAULT_RADIO true +#define DEFAULT_CHARCONV false +#define DEFAULT_BADCHANNELS true +#define DEFAULT_HANDLE_MSG true +#define DEFAULT_PRIORITY 99 +#define DEFAULT_TIMEOUT 3 +#define DEFAULT_USE_REC_DIR false +#define DEFAULT_REC_DIR "" + +extern bool g_bCreated; ///< Shows that the Create function was successfully called +extern int g_iClientID; ///< The PVR client ID used by XBMC for this driver +extern CStdString g_szUserPath; ///< The Path to the user directory inside user profile +extern CStdString g_szClientPath; ///< The Path where this driver is located + +/* Client Settings */ +extern CStdString g_szHostname; ///< The Host name or IP of the VDR +extern int g_iPort; ///< The VTP Port of the VDR (default is 2004) +extern int g_iPriority; ///< The Priority this client have in response to other clients +extern int g_iConnectTimeout; ///< The Socket connection timeout +extern bool g_bOnlyFTA; ///< Send only Free-To-Air Channels inside Channel list to XBMC +extern bool g_bRadioEnabled; ///< Send also Radio channels list to XBMC +extern bool g_bCharsetConv; ///< Convert VDR's incoming strings to UTF8 character set +extern bool g_bNoBadChannels; ///< Ignore channels without a PID, APID and DPID +extern bool g_bHandleMessages; ///< Send VDR's OSD status messages to XBMC OSD +extern bool g_bUseRecordingsDir; ///< Use a normal directory if true for recordings +extern CStdString g_szRecordingsDir; ///< The path to the recordings directory +extern cHelper_libXBMC_addon *XBMC; +extern cHelper_libXBMC_pvr *PVR; + +#endif /* CLIENT_H */ diff --git a/xbmc/pvrclients/vdr-streamdev/epg.cpp b/xbmc/pvrclients/vdr-streamdev/epg.cpp new file mode 100644 index 0000000000..80108c2ca6 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/epg.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * This code is taken from epg.c in the Video Disk Recorder ('VDR') + */ + +#include "epg.h" + +cEpg::cEpg() +{ + m_uid = 0; + m_title = NULL; + m_shortText = NULL; + m_description = NULL; + m_aux = NULL; + m_StartTime = 0; + m_EndTime = 0; + m_Duration = 0; + m_genre = NULL; + m_genre_type = 0; + m_genre_sub_type = 0; + m_parental_rating = -1; + m_vps = 0; + +} + +cEpg::~cEpg() +{ + free(m_aux); + free(m_genre); + free(m_title); + free(m_shortText); + free(m_description); +} + +void cEpg::Reset() +{ + free(m_aux); + free(m_genre); + free(m_title); + free(m_shortText); + free(m_description); + + m_StartTime = 0; + m_EndTime = 0; + m_Duration = 0; + m_genre_type = 0; + m_genre_sub_type = 0; + m_parental_rating = -1; + m_vps = 0; + m_uid = 0; + m_title = NULL; + m_shortText = NULL; + m_description = NULL; + m_aux = NULL; + m_genre = NULL; +} + +bool cEpg::ParseLine(const char *s) +{ + char *t = skipspace(s + 1); + switch (*s) + { + case 'T': SetTitle(t); + break; + case 'S': SetShortText(t); + break; + case 'D': strreplace(t, '|', '\n'); + SetDescription(t); + break; + case 'E': + { + unsigned int EventID; + time_t StartTime; + int Duration; + unsigned int TableID = 0; + unsigned int Version = 0xFF; // actual value is ignored + int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version); + if (n >= 3 && n <= 5) + { + m_StartTime = StartTime; + m_EndTime = StartTime+Duration; + m_Duration = Duration; + m_uid = EventID; + } + } + break; + case 'G': + { + char genre[1024]; + int genreType; + int genreSubType; + int n = sscanf(t, "%u %u %[^\n]", &genreType, &genreSubType, genre); + if (n == 3) + { + SetGenre(genre, genreType, genreSubType); + } + } + break; + case 'X': break; + case 'C': break; + case 'c': break; + case 'V': SetVps(atoi(t)); + break; + case 'R': SetParentalRating(atoi(t)); + break; + case 'e': return true; + default: XBMC->Log(LOG_ERROR, "cEpg::ParseLine - unexpected tag while reading EPG data: %s", s); + return true; + } + return false; +} + +void cEpg::SetTitle(const char *Title) +{ + m_title = strcpyrealloc(m_title, Title); +} + +void cEpg::SetShortText(const char *ShortText) +{ + m_shortText = strcpyrealloc(m_shortText, ShortText); +} + +void cEpg::SetDescription(const char *Description) +{ + m_description = strcpyrealloc(m_description, Description); +} + +void cEpg::SetGenre(const char *Genre, int genreType, int genreSubType) +{ + m_genre_type = genreType; + m_genre_sub_type = genreSubType; + if (Genre) + m_genre = strcpyrealloc(m_genre, Genre); +} + +void cEpg::SetVps(time_t Vps) +{ + m_vps = Vps; +} + +void cEpg::SetParentalRating(int Rating) +{ + m_parental_rating = Rating; +} diff --git a/xbmc/pvrclients/vdr-streamdev/epg.h b/xbmc/pvrclients/vdr-streamdev/epg.h new file mode 100644 index 0000000000..15f6bc62ab --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/epg.h @@ -0,0 +1,76 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __EPG_H +#define __EPG_H + +#include +#include +#include +#include "tools.h" + +class cEpg +{ +private: + unsigned int m_uid; + char *m_title; + char *m_shortText; + char *m_description; + char *m_aux; + time_t m_StartTime; + time_t m_EndTime; + int m_Duration; + char *m_genre; + int m_genre_type; + int m_genre_sub_type; + int m_parental_rating; + time_t m_vps; // Video Programming Service timestamp (VPS, aka "Programme Identification Label", PIL) + +public: + cEpg(); + virtual ~cEpg(); + void Reset(); + + bool ParseLine(const char *s); + bool ParseEntryLine(const char *s); + const char *Aux(void) const { return m_aux; } + int UniqueId(void) const { return m_uid; } + time_t StartTime(void) const { return m_StartTime; } + time_t EndTime(void) const { return m_EndTime; } + time_t Duration(void) const { return m_Duration; } + time_t Vps(void) const { return m_vps; } + void SetVps(time_t Vps); + const char *Title(void) const { return m_title; } + const char *ShortText(void) const { return m_shortText; } + const char *Description(void) const { return m_description; } + const char *Genre(void) const { return m_genre; } + int GenreType(void) const { return m_genre_type; } + int GenreSubType(void) const { return m_genre_sub_type; } + int ParentalRating(void) const { return m_parental_rating; } + void SetParentalRating(int Rating); + void SetTitle(const char *Title); + void SetShortText(const char *ShortText); + void SetDescription(const char *Description); + void SetGenre(const char *Genre, int genreType, int genreSubType); +}; + +#endif //__EPG_H diff --git a/xbmc/pvrclients/vdr-streamdev/linux/pvrclient-vdr_os_posix.h b/xbmc/pvrclients/vdr-streamdev/linux/pvrclient-vdr_os_posix.h new file mode 100644 index 0000000000..7f6a56e55f --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/linux/pvrclient-vdr_os_posix.h @@ -0,0 +1,108 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef PVRCLIENT_VDR_OS_POSIX_H +#define PVRCLIENT_VDR_OS_POSIX_H + +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef __APPLE__ +#include +#endif +#include +#include +#include +#include +#include + +typedef int bool_t; +typedef int SOCKET; + +#define __close(a) close(a) +#define __select select +#define __recv recv +#define __shutdown shutdown +#define __socket socket +#define __bind bind +#define __getsockname getsockname +#define __connect connect +#define __getpeername getpeername +#define __send send +#define __getsockopt getsockopt +#define __listen listen +#define __accept accept +#define __setsockopt setsockopt +#define __fcntl fcntl +#define __gethostbyname gethostbyname +#define __read read +#define __write write +#define __poll poll +#define SOCKET_ERROR (-1) +#define INVALID_SOCKET (-1) +#define SD_BOTH SHUT_RDWR + +#define LIBTYPE +#define sock_getlasterror errno +#define sock_getlasterror_socktimeout (errno == EAGAIN) +#define console_vprintf vprintf +#define console_printf printf +#define THREAD_FUNC_PREFIX void * + +#ifndef __STL_CONFIG_H +template inline T min(T a, T b) { return a <= b ? a : b; } +template inline T max(T a, T b) { return a >= b ? a : b; } +template inline int sgn(T a) { return a < 0 ? -1 : a > 0 ? 1 : 0; } +template inline void swap(T &a, T &b) { T t = a; a = b; b = t; } +#endif + +#define Sleep(t) usleep(t*1000) + +static inline uint64_t getcurrenttime(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + return ((uint64_t)t.tv_sec * 1000) + (t.tv_usec / 1000); +} + +static inline int setsocktimeout(int s, int level, int optname, uint64_t timeout) +{ + struct timeval t; + t.tv_sec = timeout / 1000; + t.tv_usec = (timeout % 1000) * 1000; + return setsockopt(s, level, optname, (char *)&t, sizeof(t)); +} + +#endif diff --git a/xbmc/pvrclients/vdr-streamdev/patches/streamdev-cvs100210-ReplaceRecordingStreaming.patch b/xbmc/pvrclients/vdr-streamdev/patches/streamdev-cvs100210-ReplaceRecordingStreaming.patch new file mode 100644 index 0000000000..a5cc5d6689 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/patches/streamdev-cvs100210-ReplaceRecordingStreaming.patch @@ -0,0 +1,581 @@ +diff -NaurwB streamdev/Makefile streamdev-patched/Makefile +--- streamdev/Makefile 2009-11-04 12:12:20.000000000 +0100 ++++ streamdev-patched/Makefile 2010-02-09 15:58:13.000000000 +0100 +@@ -65,7 +65,7 @@ + server/server.o server/component.o server/connection.o \ + server/componentVTP.o server/componentHTTP.o server/componentIGMP.o \ + server/connectionVTP.o server/connectionHTTP.o server/connectionIGMP.o \ +- server/streamer.o server/livestreamer.o server/livefilter.o \ ++ server/streamer.o server/livestreamer.o server/livefilter.o server/recordingstreamer.o \ + server/suspend.o server/setup.o server/menuHTTP.o server/recplayer.o \ + remux/tsremux.o remux/ts2pes.o remux/ts2ps.o remux/ts2es.o remux/extern.o + +diff -NaurwB streamdev/server/connectionVTP.c streamdev-patched/server/connectionVTP.c +--- streamdev/server/connectionVTP.c 2010-01-29 13:03:02.000000000 +0100 ++++ streamdev-patched/server/connectionVTP.c 2010-02-10 08:56:22.000000000 +0100 +@@ -4,6 +4,7 @@ + + #include "server/connectionVTP.h" + #include "server/livestreamer.h" ++#include "server/recordingstreamer.h" + #include "server/suspend.h" + #include "setup.h" + +@@ -741,11 +742,11 @@ + m_FilterSocket(NULL), + m_FilterStreamer(NULL), + m_RecSocket(NULL), ++ m_RecStreamer(NULL), + m_DataSocket(NULL), + m_LastCommand(NULL), + m_StreamType(stTSPIDS), + m_FiltersSupport(false), +- m_RecPlayer(NULL), + m_LSTEHandler(NULL), + m_LSTCHandler(NULL), + m_LSTTHandler(NULL), +@@ -759,6 +760,7 @@ + free(m_LastCommand); + delete m_LiveStreamer; + delete m_LiveSocket; ++ delete m_RecStreamer; + delete m_RecSocket; + delete m_FilterStreamer; + delete m_FilterSocket; +@@ -767,7 +769,6 @@ + delete m_LSTCHandler; + delete m_LSTEHandler; + delete m_LSTRHandler; +- delete m_RecPlayer; + } + + inline bool cConnectionVTP::Abort(void) const +@@ -833,9 +834,10 @@ + if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(param); + else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(param); + else if (strcasecmp(Cmd, "PORT") == 0) return CmdPORT(param); +- else if (strcasecmp(Cmd, "READ") == 0) return CmdREAD(param); + else if (strcasecmp(Cmd, "TUNE") == 0) return CmdTUNE(param); + else if (strcasecmp(Cmd, "PLAY") == 0) return CmdPLAY(param); ++ else if (strcasecmp(Cmd, "SEEK") == 0) return CmdSEEK(param); ++ else if (strcasecmp(Cmd, "SIZE") == 0) return CmdSIZE(param); + else if (strcasecmp(Cmd, "ADDP") == 0) return CmdADDP(param); + else if (strcasecmp(Cmd, "DELP") == 0) return CmdDELP(param); + else if (strcasecmp(Cmd, "ADDF") == 0) return CmdADDF(param); +@@ -1012,6 +1014,8 @@ + + if (!m_RecSocket->SetDSCP()) + LOG_ERROR_STR("unable to set DSCP sockopt"); ++ if(m_RecSocket) ++ m_RecStreamer->Start(m_RecSocket); + + return Respond(220, "Port command ok, data connection opened"); + break; +@@ -1034,35 +1038,6 @@ + } + } + +-bool cConnectionVTP::CmdREAD(char *Opts) +-{ +- if (*Opts) { +- char *tail; +- uint64_t position = strtoll(Opts, &tail, 10); +- if (tail && tail != Opts) { +- tail = skipspace(tail); +- if (tail && tail != Opts) { +- int size = strtol(tail, NULL, 10); +- uint8_t* data = (uint8_t*)malloc(size+4); +- unsigned long count_readed = m_RecPlayer->getBlock(data, position, size); +- unsigned long count_written = m_RecSocket->SysWrite(data, count_readed); +- +- free(data); +- return Respond(220, "%lu Bytes submitted", count_written); +- } +- else { +- return Respond(501, "Missing position"); +- } +- } +- else { +- return Respond(501, "Missing size"); +- } +- } +- else { +- return Respond(501, "Missing position"); +- } +-} +- + bool cConnectionVTP::CmdTUNE(char *Opts) + { + const cChannel *chan; +@@ -1096,27 +1071,40 @@ + + bool cConnectionVTP::CmdPLAY(char *Opts) + { +- if (*Opts) { +- if (isnumber(Opts)) { +- cRecording *recording = Recordings.Get(strtol(Opts, NULL, 10) - 1); +- if (recording) { +- if (m_RecPlayer) { +- delete m_RecPlayer; +- } +- m_RecPlayer = new RecPlayer(recording); +- return Respond(220, "%llu (Bytes), %u (Frames)", (long long unsigned int) m_RecPlayer->getLengthBytes(), (unsigned int) m_RecPlayer->getLengthFrames()); +- } +- else { +- return Respond(550, "Recording \"%s\" not found", Opts); +- } +- } +- else { +- return Respond(500, "Use: PLAY record"); ++ const cRecording *recording; ++ cDevice *dev; ++ ++ if ((recording = Recordings.Get(strtol(Opts, NULL, 10) - 1)) == NULL) ++ return Respond(550, "Undefined recording \"%s\"", Opts); ++ ++ delete m_RecStreamer; ++ m_RecStreamer = new cStreamdevRecStreamer(recording); ++ if(m_RecSocket) ++ m_RecStreamer->Start(m_RecSocket); ++ ++ return Respond(220, "Recording opened"); + } ++ ++bool cConnectionVTP::CmdSEEK(char *Opts) ++{ ++ if (m_RecStreamer) ++ { ++ uint64_t TotalBytesWritten = m_RecStreamer->getTotalBytesWritten(); ++ uint64_t newPosition = atoll(Opts); ++ m_RecStreamer->seekPosition(newPosition); ++ ++ return Respond(220, "%llu (TCP Stack Position) %llu (New Position)", TotalBytesWritten, newPosition); + } +- else { +- return Respond(500, "Use: PLAY record"); ++ ++ return Respond(550, "Curretly no record playing"); + } ++ ++bool cConnectionVTP::CmdSIZE(char *Opts) ++{ ++ if (m_RecStreamer) ++ return Respond(220, "%llu (Bytes), %u (Frames)", (long long unsigned int) m_RecStreamer->getLengthBytes(), (unsigned int) m_RecStreamer->getLengthFrames()); ++ ++ return Respond(550, "Curretly no record playing"); + } + + bool cConnectionVTP::CmdADDP(char *Opts) +@@ -1215,7 +1203,7 @@ + DELETENULL(m_FilterSocket); + break; + case siReplay: +- DELETENULL(m_RecPlayer); ++ DELETENULL(m_RecStreamer); + DELETENULL(m_RecSocket); + break; + case siDataRespond: +diff -NaurwB streamdev/server/connectionVTP.h streamdev-patched/server/connectionVTP.h +--- streamdev/server/connectionVTP.h 2009-07-01 12:46:16.000000000 +0200 ++++ streamdev-patched/server/connectionVTP.h 2010-02-09 18:08:35.000000000 +0100 +@@ -7,6 +7,7 @@ + class cTBSocket; + class cStreamdevLiveStreamer; + class cStreamdevFilterStreamer; ++class cStreamdevRecStreamer; + class cLSTEHandler; + class cLSTCHandler; + class cLSTTHandler; +@@ -24,12 +25,12 @@ + cTBSocket *m_FilterSocket; + cStreamdevFilterStreamer *m_FilterStreamer; + cTBSocket *m_RecSocket; ++ cStreamdevRecStreamer *m_RecStreamer; + cTBSocket *m_DataSocket; + + char *m_LastCommand; + eStreamType m_StreamType; + bool m_FiltersSupport; +- RecPlayer *m_RecPlayer; + + // Members adopted for SVDRP + cLSTEHandler *m_LSTEHandler; +@@ -56,9 +57,10 @@ + bool CmdCAPS(char *Opts); + bool CmdPROV(char *Opts); + bool CmdPORT(char *Opts); +- bool CmdREAD(char *Opts); + bool CmdTUNE(char *Opts); + bool CmdPLAY(char *Opts); ++ bool CmdSEEK(char *Opts); ++ bool CmdSIZE(char *Opts); + bool CmdADDP(char *Opts); + bool CmdDELP(char *Opts); + bool CmdADDF(char *Opts); +diff -NaurwB streamdev/server/recordingstreamer.c streamdev-patched/server/recordingstreamer.c +--- streamdev/server/recordingstreamer.c 1970-01-01 01:00:00.000000000 +0100 ++++ streamdev-patched/server/recordingstreamer.c 2010-02-10 08:58:01.000000000 +0100 +@@ -0,0 +1,89 @@ ++#include ++ ++#include "tools/socket.h" ++#include "tools/select.h" ++ ++#include "server/recordingstreamer.h" ++#include "recplayer.h" ++#include "common.h" ++ ++// --- cStreamdevRecStreamer ------------------------------------------------- ++ ++cStreamdevRecStreamer::cStreamdevRecStreamer(const cRecording *Recording): ++ cThread("streamdev-recordingstreaming"), ++ m_Recording(Recording), ++ m_RecPlayer(new cRecPlayer(Recording)), ++ m_Position(0), ++ m_TotalBytesWritten(0) ++{ ++} ++ ++cStreamdevRecStreamer::~cStreamdevRecStreamer() ++{ ++ Dprintf("Desctructing Recording streamer\n"); ++ Stop(); ++ ++ DELETENULL(m_RecPlayer); ++} ++ ++void cStreamdevRecStreamer::Start(cTBSocket *Socket) ++{ ++ Dprintf("start streamer\n"); ++ m_Socket = Socket; ++ cThread::Start(); ++} ++ ++void cStreamdevRecStreamer::Stop(void) ++{ ++ if (Running()) { ++ Dprintf("stopping streamer\n"); ++ Cancel(3); ++ } ++} ++ ++uint64_t cStreamdevRecStreamer::getLengthBytes() ++{ ++ return m_RecPlayer->getLengthBytes(); ++} ++ ++uint32_t cStreamdevRecStreamer::getLengthFrames() ++{ ++ return m_RecPlayer->getLengthFrames(); ++} ++ ++void cStreamdevRecStreamer::seekPosition(uint64_t position) ++{ ++ m_Position = position; ++} ++ ++void cStreamdevRecStreamer::Action(void) ++{ ++ cTBSelect sel; ++ uint8_t data[32768]; ++ Dprintf("Writer start\n"); ++ ++ sel.Clear(); ++ sel.Add(*m_Socket, true); ++ while (Running()) { ++ ++ if (sel.Select(15000) == -1) { ++ esyslog("ERROR: streamdev-server: couldn't send recording data: %m"); ++ continue; /* Continue here instead of break, the recording playback can be paused */ ++ } ++ ++ if (sel.CanWrite(*m_Socket)) { ++ int written; ++ unsigned long readed = m_RecPlayer->getBlock(&*data, m_Position, sizeof(data)); ++ if (readed <= 0) ++ continue; ++ ++ if ((written = m_Socket->Write(&*data, readed)) == -1) { ++ esyslog("ERROR: streamdev-server: couldn't send %d bytes: %m", written); ++ break; ++ } ++ ++ m_Position += written; ++ m_TotalBytesWritten += written; ++ } ++ } ++} +diff -NaurwB streamdev/server/recordingstreamer.h streamdev-patched/server/recordingstreamer.h +--- streamdev/server/recordingstreamer.h 1970-01-01 01:00:00.000000000 +0100 ++++ streamdev-patched/server/recordingstreamer.h 2010-02-10 08:58:13.000000000 +0100 +@@ -0,0 +1,38 @@ ++#ifndef VDR_STREAMDEV_RECORDINGSTREAMER_H ++#define VDR_STREAMDEV_RECORDINGSTREAMER_H ++ ++#include ++#include ++#include ++ ++#include "common.h" ++ ++class cRecording; ++class cRecPlayer; ++ ++// --- cStreamdevRecStreamer ------------------------------------------------- ++ ++class cStreamdevRecStreamer: public cThread { ++private: ++ const cRecording *m_Recording; ++ cRecPlayer *m_RecPlayer; ++ cTBSocket *m_Socket; ++ uint64_t m_Position; ++ uint64_t m_TotalBytesWritten; ++ ++protected: ++ virtual void Action(void); ++ ++public: ++ cStreamdevRecStreamer(const cRecording *Recording); ++ virtual ~cStreamdevRecStreamer(); ++ ++ void Start(cTBSocket *Socket); ++ void Stop(void); ++ uint64_t getTotalBytesWritten() { return m_TotalBytesWritten; } ++ uint64_t getLengthBytes(); ++ uint32_t getLengthFrames(); ++ void seekPosition(uint64_t position); ++}; ++ ++#endif // VDR_STREAMDEV_RECORDINGSTREAMER_H +diff -NaurwB streamdev/server/recplayer.c streamdev-patched/server/recplayer.c +--- streamdev/server/recplayer.c 2009-07-01 13:00:49.000000000 +0200 ++++ streamdev-patched/server/recplayer.c 2010-02-10 08:24:01.000000000 +0100 +@@ -24,7 +24,7 @@ + #define _XOPEN_SOURCE 600 + #include + +-RecPlayer::RecPlayer(cRecording* rec) ++cRecPlayer::cRecPlayer(const cRecording* rec) + { + file = NULL; + fileOpen = 0; +@@ -44,7 +44,7 @@ + scan(); + } + +-void RecPlayer::scan() ++void cRecPlayer::scan() + { + if (file) fclose(file); + totalLength = 0; +@@ -60,7 +60,7 @@ + + #if APIVERSNUM < 10703 + snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i); +- //log->log("RecPlayer", Log::DEBUG, "FILENAME: %s", fileName); ++ //log->log("cRecPlayer", Log::DEBUG, "FILENAME: %s", fileName); + file = fopen(fileName, "r"); + #else + snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), i); +@@ -77,7 +77,7 @@ + fseek(file, 0, SEEK_END); + totalLength += ftell(file); + totalFrames = indexFile->Last(); +- //log->log("RecPlayer", Log::DEBUG, "File %i found, totalLength now %llu, numFrames = %lu", i, totalLength, totalFrames); ++ //log->log("cRecPlayer", Log::DEBUG, "File %i found, totalLength now %llu, numFrames = %lu", i, totalLength, totalFrames); + segments[i]->end = totalLength; + fclose(file); + } +@@ -85,15 +85,15 @@ + file = NULL; + } + +-RecPlayer::~RecPlayer() ++cRecPlayer::~cRecPlayer() + { +- //log->log("RecPlayer", Log::DEBUG, "destructor"); ++ //log->log("cRecPlayer", Log::DEBUG, "destructor"); + int i = 1; + while(segments[i++]) delete segments[i]; + if (file) fclose(file); + } + +-int RecPlayer::openFile(int index) ++int cRecPlayer::openFile(int index) + { + if (file) fclose(file); + +@@ -113,7 +113,7 @@ + + snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), index); + isyslog("openFile called for index %i string:%s", index, fileName); +- //log->log("RecPlayer", Log::DEBUG, "openFile called for index %i string:%s", index, fileName); ++ //log->log("cRecPlayer", Log::DEBUG, "openFile called for index %i string:%s", index, fileName); + + file = fopen(fileName, "r"); + if (file) +@@ -122,38 +122,58 @@ + return 1; + } + +- //log->log("RecPlayer", Log::DEBUG, "file failed to open"); ++ //log->log("cRecPlayer", Log::DEBUG, "file failed to open"); + fileOpen = 0; + return 0; + } + +-uint64_t RecPlayer::getLengthBytes() ++uint64_t cRecPlayer::getLengthBytes() + { ++ int totalLength = 0; ++ char fileName[2048]; ++ struct stat st; ++ ++ for(int i = 1; i < 1000; i++) ++ { ++#if APIVERSNUM < 10703 ++ snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i); ++#else ++ snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), i); ++ if (stat(fileName, &st) < 0) { ++ snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i); ++ } ++#endif ++ ++ if (stat(fileName, &st) == 0) { ++ totalLength += st.st_size; ++ } ++ } ++ + return totalLength; + } + +-uint32_t RecPlayer::getLengthFrames() ++uint32_t cRecPlayer::getLengthFrames() + { + return totalFrames; + } + +-unsigned long RecPlayer::getBlock(unsigned char* buffer, uint64_t position, unsigned long amount) ++unsigned long cRecPlayer::getBlock(uint8_t* buffer, uint64_t position, unsigned long amount) + { + if ((amount > totalLength) || (amount > 500000)) + { +- //log->log("RecPlayer", Log::DEBUG, "Amount %lu requested and rejected", amount); ++ //log->log("cRecPlayer", Log::DEBUG, "Amount %lu requested and rejected", amount); + return 0; + } + + if (position >= totalLength) + { +- //log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!"); ++ //log->log("cRecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!"); + return 0; + } + + if ((position + amount) > totalLength) + { +- //log->log("RecPlayer", Log::DEBUG, "Client asked for some data past the end of recording, adjusting amount"); ++ //log->log("cRecPlayer", Log::DEBUG, "Client asked for some data past the end of recording, adjusting amount"); + amount = totalLength - position; + } + +@@ -208,17 +228,17 @@ + return got; + } + +-uint64_t RecPlayer::getLastPosition() ++uint64_t cRecPlayer::getLastPosition() + { + return lastPosition; + } + +-cRecording* RecPlayer::getCurrentRecording() ++const cRecording* cRecPlayer::getCurrentRecording() + { + return recording; + } + +-uint64_t RecPlayer::positionFromFrameNumber(uint32_t frameNumber) ++uint64_t cRecPlayer::positionFromFrameNumber(uint32_t frameNumber) + { + if (!indexFile) return 0; + +@@ -235,21 +255,21 @@ + return 0; + } + +-// log->log("RecPlayer", Log::DEBUG, "FN: %u FO: %i", retFileNumber, retFileOffset); ++// log->log("cRecPlayer", Log::DEBUG, "FN: %u FO: %i", retFileNumber, retFileOffset); + if (!segments[retFileNumber]) return 0; + uint64_t position = segments[retFileNumber]->start + retFileOffset; +-// log->log("RecPlayer", Log::DEBUG, "Pos: %llu", position); ++// log->log("cRecPlayer", Log::DEBUG, "Pos: %llu", position); + + return position; + } + +-uint32_t RecPlayer::frameNumberFromPosition(uint64_t position) ++uint32_t cRecPlayer::frameNumberFromPosition(uint64_t position) + { + if (!indexFile) return 0; + + if (position >= totalLength) + { +- //log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!"); ++ //log->log("cRecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!"); + return 0; + } + +@@ -265,7 +285,7 @@ + } + + +-bool RecPlayer::getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength) ++bool cRecPlayer::getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength) + { + // 0 = backwards + // 1 = forwards +@@ -276,7 +296,7 @@ + int indexReturnFrameNumber; + + indexReturnFrameNumber = (uint32_t)indexFile->GetNextIFrame(frameNumber, (direction==1 ? true : false), NULL, NULL, &iframeLength); +- //log->log("RecPlayer", Log::DEBUG, "GNIF input framenumber:%lu, direction=%lu, output:framenumber=%i, framelength=%i", frameNumber, direction, indexReturnFrameNumber, iframeLength); ++ //log->log("cRecPlayer", Log::DEBUG, "GNIF input framenumber:%lu, direction=%lu, output:framenumber=%i, framelength=%i", frameNumber, direction, indexReturnFrameNumber, iframeLength); + + if (indexReturnFrameNumber == -1) return false; + +diff -NaurwB streamdev/server/recplayer.h streamdev-patched/server/recplayer.h +--- streamdev/server/recplayer.h 2009-07-01 13:00:49.000000000 +0200 ++++ streamdev-patched/server/recplayer.h 2010-02-09 17:18:23.000000000 +0100 +@@ -33,24 +33,24 @@ + uint64_t end; + }; + +-class RecPlayer ++class cRecPlayer + { + public: +- RecPlayer(cRecording* rec); +- ~RecPlayer(); ++ cRecPlayer(const cRecording* rec); ++ ~cRecPlayer(); + uint64_t getLengthBytes(); + uint32_t getLengthFrames(); +- unsigned long getBlock(unsigned char* buffer, uint64_t position, unsigned long amount); ++ unsigned long getBlock(uint8_t* buffer, uint64_t position, unsigned long amount); + int openFile(int index); + uint64_t getLastPosition(); +- cRecording* getCurrentRecording(); ++ const cRecording* getCurrentRecording(); + void scan(); + uint64_t positionFromFrameNumber(uint32_t frameNumber); + uint32_t frameNumberFromPosition(uint64_t position); + bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength); + + private: +- cRecording* recording; ++ const cRecording* recording; + cIndexFile* indexFile; + FILE* file; + int fileOpen; diff --git a/xbmc/pvrclients/vdr-streamdev/patches/streamdev-cvs221109-AddCallbackMsg.diff b/xbmc/pvrclients/vdr-streamdev/patches/streamdev-cvs221109-AddCallbackMsg.diff new file mode 100644 index 0000000000..7facc6109d --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/patches/streamdev-cvs221109-AddCallbackMsg.diff @@ -0,0 +1,120 @@ +diff -NaurwB streamdev-unpatched/common.h streamdev/common.h +--- streamdev-unpatched/common.h 2009-09-18 12:41:41.000000000 +0200 ++++ streamdev/common.h 2009-11-23 04:54:04.000000000 +0100 +@@ -57,6 +57,8 @@ + si_Count + }; + ++#define MAX_RESPONSE_SIZE 1024 ++ + extern const char *VERSION; + + class cMenuEditIpItem: public cMenuEditItem { +diff -NaurwB streamdev-unpatched/server/connectionVTP.c streamdev/server/connectionVTP.c +--- streamdev-unpatched/server/connectionVTP.c 2009-10-13 08:38:47.000000000 +0200 ++++ streamdev/server/connectionVTP.c 2009-11-23 14:23:33.000000000 +0100 +@@ -1714,3 +1714,69 @@ + Code < 0 ? -Code : Code, + Code < 0 ? '-' : ' ', *str); + } ++ ++void cConnectionVTP::TimerChange(const cTimer *Timer, eTimerChange Change) ++{ ++ if (m_DataSocket) { ++ char buf[MAX_RESPONSE_SIZE]; ++ if (Change == tcMod) { ++ snprintf(buf, MAX_RESPONSE_SIZE, "MODT %s\0", Timer ? *Timer->ToText(true) : "-"); ++ } ++ if (Change == tcAdd) { ++ snprintf(buf, MAX_RESPONSE_SIZE, "ADDT %s\0", Timer ? *Timer->ToText(true) : "-"); ++ } ++ if (Change == tcDel) { ++ snprintf(buf, MAX_RESPONSE_SIZE, "DELT %s\0", Timer ? *Timer->ToText(true) : "-"); ++ } ++ ++ m_DataSocket->SysWrite(buf, strlen(buf)); ++ } ++} ++ ++#ifdef USE_STATUS_EXTENSION ++void cConnectionVTP::OsdStatusMessage(eMessageType type, const char *Message) ++#else ++void cConnectionVTP::OsdStatusMessage(const char *Message) ++#endif ++{ ++ if (m_DataSocket && Message) { ++ char buf[MAX_RESPONSE_SIZE]; ++ ++ /* Ignore this messages */ ++ if (strcasecmp(Message, trVDR("Channel not available!")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Delete timer?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Delete recording?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Press any key to cancel shutdown")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Press any key to cancel restart")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Editing - shut down anyway?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Recording - shut down anyway?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("shut down anyway?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Recording - restart anyway?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Editing - restart anyway?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Delete channel?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Timer still recording - really delete?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Delete marks information?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Delete resume information?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("CAM is in use - really reset?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Really restart?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Stop recording?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Cancel editing?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Cutter already running - Add to cutting queue?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("No index-file found. Creating may take minutes. Create one?")) == 0) return; ++ ++#ifdef USE_STATUS_EXTENSION ++ if (type == mtStatus) ++ snprintf(buf, MAX_RESPONSE_SIZE, "SMSG %s\0", Message); ++ else if (type == mtInfo) ++ snprintf(buf, MAX_RESPONSE_SIZE, "IMSG %s\0", Message); ++ else if (type == mtWarning) ++ snprintf(buf, MAX_RESPONSE_SIZE, "WMSG %s\0", Message); ++ else if (type == mtError) ++ snprintf(buf, MAX_RESPONSE_SIZE, "EMSG %s\0", Message); ++ else ++#endif ++ snprintf(buf, MAX_RESPONSE_SIZE, "IMSG %s\0", Message); ++ ++ m_DataSocket->SysWrite(buf, strlen(buf)); ++ } ++} +diff -NaurwB streamdev-unpatched/server/connectionVTP.h streamdev/server/connectionVTP.h +--- streamdev-unpatched/server/connectionVTP.h 2009-07-01 12:46:16.000000000 +0200 ++++ streamdev/server/connectionVTP.h 2009-11-23 14:23:33.000000000 +0100 +@@ -1,6 +1,7 @@ + #ifndef VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H + #define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H + ++#include + #include "server/connection.h" + #include "server/recplayer.h" + +@@ -12,7 +13,8 @@ + class cLSTTHandler; + class cLSTRHandler; + +-class cConnectionVTP: public cServerConnection { ++class cConnectionVTP: public cServerConnection ++ , public cStatus { + friend class cLSTEHandler; + #if !defined __GNUC__ || __GNUC__ >= 3 + using cServerConnection::Respond; +@@ -41,6 +43,13 @@ + template + bool CmdLSTX(cHandler *&Handler, char *Option); + ++ virtual void TimerChange(const cTimer *Timer, eTimerChange Change); ++#ifdef USE_STATUS_EXTENSION ++ virtual void OsdStatusMessage(eMessageType type, const char *Message); ++#else ++ virtual void OsdStatusMessage(const char *Message); ++#endif ++ + public: + cConnectionVTP(void); + virtual ~cConnectionVTP(); diff --git a/xbmc/pvrclients/vdr-streamdev/patches/streamdev-cvs221109-AddFemonV1.diff b/xbmc/pvrclients/vdr-streamdev/patches/streamdev-cvs221109-AddFemonV1.diff new file mode 100644 index 0000000000..39872e4e2a --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/patches/streamdev-cvs221109-AddFemonV1.diff @@ -0,0 +1,210 @@ +diff -NaurwB streamdev-unpatched/server/connectionVTP.c streamdev/server/connectionVTP.c +--- streamdev-unpatched/server/connectionVTP.c 2009-10-13 08:38:47.000000000 +0200 ++++ streamdev/server/connectionVTP.c 2009-11-22 20:04:07.000000000 +0100 +@@ -7,6 +7,8 @@ + #include "server/suspend.h" + #include "setup.h" + ++#include "../services/femonservice.h" ++ + #include + #include + #include +@@ -710,6 +712,102 @@ + return false; + } + ++ ++// --- cLSTQHandler ----------------------------------------------------------- ++ ++class cLSTQHandler ++{ ++private: ++ enum eStates { Device, Status, Signal, SNR, BER, UNC, Video, ++ Audio, Dolby, EndQuality }; ++ cConnectionVTP *m_Client; ++ FemonService_v1_0 m_femon; ++ int m_Errno; ++ int m_Channel; ++ cString m_Error; ++ eStates m_State; ++public: ++ cLSTQHandler(cConnectionVTP *Client, const char *Option); ++ ~cLSTQHandler(); ++ bool Next(bool &Last); ++}; ++ ++cLSTQHandler::cLSTQHandler(cConnectionVTP *Client, const char *Option): ++ m_Client(Client), ++ m_Errno(0), ++ m_State(Device), ++ m_Channel(-1) ++{ ++// if (*Option) { ++// if (isnumber(Option)) { ++// m_Channel = atoi(Option); ++// } ++// } ++ ++ cPlugin *p; ++ p = cPluginManager::CallFirstService("FemonService-v1.0", &m_femon); ++ if (!p) { ++ m_Errno = 550; ++ m_Error = cString::sprintf("No support for Signal Quality found"); ++ } ++} ++ ++cLSTQHandler::~cLSTQHandler() ++{ ++} ++ ++bool cLSTQHandler::Next(bool &Last) ++{ ++ if (*m_Error != NULL) { ++ Last = true; ++ cString str(m_Error); ++ m_Error = NULL; ++ return m_Client->Respond(m_Errno, "%s", *str); ++ } ++ ++ Last = false; ++ switch (m_State) { ++ case Device: ++ m_State = Status; ++ if (*m_femon.fe_name != NULL) ++ return m_Client->Respond(-215, "Device : %s", *m_femon.fe_name); ++ else ++ return m_Client->Respond(-215, "Device : "); ++ case Status: ++ m_State = Signal; ++ if (*m_femon.fe_status != NULL) ++ return m_Client->Respond(-215, "Status : %s", *m_femon.fe_status); ++ else ++ return m_Client->Respond(-215, "Status : "); ++ case Signal: ++ m_State = SNR; ++ return m_Client->Respond(-215, "Signal : %04X (%2d%%)", m_femon.fe_signal, m_femon.fe_signal / 655); ++ case SNR: ++ m_State = BER; ++ return m_Client->Respond(-215, "SNR : %04X (%2d%%)", m_femon.fe_snr, m_femon.fe_snr / 655); ++ case BER: ++ m_State = UNC; ++ return m_Client->Respond(-215, "BER : %08X", m_femon.fe_ber); ++ case UNC: ++ m_State = Video; ++ return m_Client->Respond(-215, "UNC : %08X", m_femon.fe_unc); ++ case Video: ++ m_State = Audio; ++ return m_Client->Respond(-215, "Video : %.2f Mbit/s", m_femon.video_bitrate); ++ case Audio: ++ m_State = Dolby; ++ return m_Client->Respond(-215, "Audio : %.0f kbit/s", m_femon.audio_bitrate); ++ case Dolby: ++ m_State = EndQuality; ++ return m_Client->Respond(-215, "Dolby : %.0f kbit/s", m_femon.dolby_bitrate); ++ case EndQuality: ++ Last = true; ++ return m_Client->Respond(215, "End of quality information"); ++ } ++ return false; ++} ++ ++ + // --- cConnectionVTP --------------------------------------------------------- + + cConnectionVTP::cConnectionVTP(void): +@@ -727,7 +825,8 @@ + m_LSTEHandler(NULL), + m_LSTCHandler(NULL), + m_LSTTHandler(NULL), +- m_LSTRHandler(NULL) ++ m_LSTRHandler(NULL), ++ m_LSTQHandler(NULL) + { + } + +@@ -745,6 +844,7 @@ + delete m_LSTCHandler; + delete m_LSTEHandler; + delete m_LSTRHandler; ++ delete m_LSTQHandler; + delete m_RecPlayer; + } + +@@ -801,6 +901,7 @@ + else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(param); + else if (strcasecmp(Cmd, "LSTT") == 0) return CmdLSTT(param); + else if (strcasecmp(Cmd, "LSTC") == 0) return CmdLSTC(param); ++ else if (strcasecmp(Cmd, "LSTQ") == 0) return CmdLSTQ(param); + + if (param == NULL) { + esyslog("ERROR: streamdev: this seriously shouldn't happen at %s:%d", +@@ -1268,6 +1369,11 @@ + return CmdLSTX(m_LSTRHandler, Option); + } + ++bool cConnectionVTP::CmdLSTQ(char *Option) ++{ ++ return CmdLSTX(m_LSTQHandler, Option); ++} ++ + // Functions adopted from SVDRP + #define INIT_WRAPPER() bool _res + #define Reply(c,m...) _res = Respond(c,m) +diff -NaurwB streamdev-unpatched/server/connectionVTP.h streamdev/server/connectionVTP.h +--- streamdev-unpatched/server/connectionVTP.h 2009-07-01 12:46:16.000000000 +0200 ++++ streamdev/server/connectionVTP.h 2009-11-22 16:08:51.000000000 +0100 +@@ -11,6 +11,7 @@ + class cLSTCHandler; + class cLSTTHandler; + class cLSTRHandler; ++class cLSTQHandler; + + class cConnectionVTP: public cServerConnection { + friend class cLSTEHandler; +@@ -36,6 +37,7 @@ + cLSTCHandler *m_LSTCHandler; + cLSTTHandler *m_LSTTHandler; + cLSTRHandler *m_LSTRHandler; ++ cLSTQHandler *m_LSTQHandler; + + protected: + template +@@ -72,6 +74,7 @@ + bool CmdLSTC(char *Opts); + bool CmdLSTT(char *Opts); + bool CmdLSTR(char *Opts); ++ bool CmdLSTQ(char *Opts); + + // Commands adopted from SVDRP + bool CmdSTAT(const char *Option); +diff -NaurwB streamdev-unpatched/services/femonservice.h streamdev/services/femonservice.h +--- streamdev-unpatched/services/femonservice.h 1970-01-01 01:00:00.000000000 +0100 ++++ streamdev/services/femonservice.h 2009-10-01 03:20:00.000000000 +0200 +@@ -0,0 +1,26 @@ ++/* ++ * Frontend Status Monitor plugin for the Video Disk Recorder ++ * ++ * See the README file for copyright information and how to reach the author. ++ * ++ */ ++ ++#ifndef __FEMONSERVICE_H ++#define __FEMONSERVICE_H ++ ++#include ++ ++struct FemonService_v1_0 { ++ cString fe_name; ++ cString fe_status; ++ uint16_t fe_snr; ++ uint16_t fe_signal; ++ uint32_t fe_ber; ++ uint32_t fe_unc; ++ double video_bitrate; ++ double audio_bitrate; ++ double dolby_bitrate; ++ }; ++ ++#endif //__FEMONSERVICE_H ++ diff --git a/xbmc/pvrclients/vdr-streamdev/patches/vdr-1.7.10_extensionsAndXBMC.diff b/xbmc/pvrclients/vdr-streamdev/patches/vdr-1.7.10_extensionsAndXBMC.diff new file mode 100644 index 0000000000..622c464232 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/patches/vdr-1.7.10_extensionsAndXBMC.diff @@ -0,0 +1,19905 @@ +diff -NaurwB vdr-1.7.10/channels.c vdr-1.7.10-patched/channels.c +--- vdr-1.7.10/channels.c 2009-08-30 13:25:50.000000000 +0200 ++++ vdr-1.7.10-patched/channels.c 2009-12-18 06:29:02.000000000 +0100 +@@ -188,6 +188,9 @@ + shortName = strdup(""); + provider = strdup(""); + portalName = strdup(""); ++#ifdef USE_PLUGINPARAM ++ pluginParam = strdup(""); ++#endif /* PLUGINPARAM */ + memset(&__BeginData__, 0, (char *)&__EndData__ - (char *)&__BeginData__); + inversion = INVERSION_AUTO; + bandwidth = 8000000; +@@ -211,6 +214,9 @@ + shortName = NULL; + provider = NULL; + portalName = NULL; ++#ifdef USE_PLUGINPARAM ++ pluginParam = NULL; ++#endif /* PLUGINPARAM */ + schedule = NULL; + linkChannels = NULL; + refChannel = NULL; +@@ -239,6 +245,9 @@ + free(shortName); + free(provider); + free(portalName); ++#ifdef USE_PLUGINPARAM ++ free(pluginParam); ++#endif /* PLUGINPARAM */ + } + + cChannel& cChannel::operator= (const cChannel &Channel) +@@ -247,6 +256,9 @@ + shortName = strcpyrealloc(shortName, Channel.shortName); + provider = strcpyrealloc(provider, Channel.provider); + portalName = strcpyrealloc(portalName, Channel.portalName); ++#ifdef USE_PLUGINPARAM ++ pluginParam = strcpyrealloc(pluginParam, Channel.pluginParam); ++#endif /* PLUGINPARAM */ + memcpy(&__BeginData__, &Channel.__BeginData__, (char *)&Channel.__EndData__ - (char *)&Channel.__BeginData__); + return *this; + } +@@ -306,6 +318,9 @@ + guard = Channel->guard; + hierarchy = Channel->hierarchy; + rollOff = Channel->rollOff; ++#ifdef USE_PLUGINPARAM ++ if (IsPlug()) pluginParam = strcpyrealloc(pluginParam, Channel->pluginParam); ++#endif /* PLUGINPARAM */ + } + } + +@@ -343,6 +358,24 @@ + return true; + } + ++#ifdef USE_PLUGINPARAM ++bool cChannel::SetPlugTransponderData(int Source, int Frequency, const char *PluginParam) ++{ ++ if (source != Source || frequency != Frequency || (strcmp(pluginParam, PluginParam) != 0)) { ++ if (Number()) { ++ dsyslog("changing transponder data of channel %d from %s:%d:%s to %s:%d:%s", Number(), *cSource::ToString(source), frequency, pluginParam, *cSource::ToString(Source), Frequency, PluginParam); ++ modification |= CHANNELMOD_TRANSP; ++ Channels.SetModified(); ++ } ++ source = Source; ++ frequency = Frequency; ++ pluginParam = strcpyrealloc(pluginParam, PluginParam); ++ schedule = NULL; ++ } ++ return true; ++} ++#endif /* PLUGINPARAM */ ++ + bool cChannel::SetCableTransponderData(int Source, int Frequency, int Modulation, int Srate, int CoderateH) + { + if (source != Source || frequency != Frequency || modulation != Modulation || srate != Srate || coderateH != CoderateH) { +@@ -438,6 +471,20 @@ + } + } + ++#ifdef USE_PLUGINPARAM ++void cChannel::SetPluginParam(const char *PluginParam) ++{ ++ if (!isempty(PluginParam) && strcmp(pluginParam, PluginParam) != 0) { ++ if (Number()) { ++ dsyslog("changing plugin parameters of channel %d from '%s' to '%s'", Number(), pluginParam, PluginParam); ++ modification |= CHANNELMOD_TRANSP; ++ Channels.SetModified(); ++ } ++ pluginParam = strcpyrealloc(pluginParam, PluginParam); ++ } ++} ++#endif /* PLUGINPARAM */ ++ + #define STRDIFF 0x01 + #define VALDIFF 0x02 + +@@ -550,6 +597,17 @@ + } + } + ++#ifdef USE_TTXTSUBS ++void cChannel::SetTPidData(char TLangs[][MAXLANGCODE2], int TPages[]) ++{ ++ for (int i = 0; i < MAXTPAGES; i++) { ++ tpages[i] = TPages[i]; ++ strn0cpy(tlangs[i], TLangs[i], MAXLANGCODE2); ++ } ++ tpages[MAXTPAGES] = 0; ++} ++#endif /* TTXTSUBS */ ++ + void cChannel::SetCaIds(const int *CaIds) + { + if (caids[0] && caids[0] <= CA_USER_MAX) +@@ -651,7 +709,11 @@ + if (isdigit(type)) + type = 'S'; + #define ST(s) if (strchr(s, type)) ++#ifdef USE_PLUGINPARAM ++ char buffer[256]; ++#else + char buffer[64]; ++#endif /* PLUGINPARAM */ + char *q = buffer; + *q = 0; + ST(" S ") q += sprintf(q, "%c", polarization); +@@ -665,6 +727,9 @@ + ST(" S ") q += PrintParameter(q, 'S', MapToUser(system, SystemValues)); + ST(" T") q += PrintParameter(q, 'T', MapToUser(transmission, TransmissionValues)); + ST(" T") q += PrintParameter(q, 'Y', MapToUser(hierarchy, HierarchyValues)); ++#ifdef USE_PLUGINPARAM ++ ST("P ") snprintf(buffer, sizeof(buffer), "%s", pluginParam); ++#endif /* PLUGINPARAM */ + return buffer; + } + +@@ -693,7 +758,11 @@ + + bool cChannel::StringToParameters(const char *s) + { ++#ifdef USE_PLUGINPARAM ++ while (s && *s && !IsPlug()) { ++#else + while (s && *s) { ++#endif /* PLUGINPARAM */ + switch (toupper(*s)) { + case 'A': s = SkipDigits(s); break; // for compatibility with the "multiproto" approach - may be removed in future versions + case 'B': s = ParseParameter(s, bandwidth, BandwidthValues); break; +@@ -811,7 +880,11 @@ + dpids[0] = 0; + ok = false; + if (parambuf && sourcebuf && vpidbuf && apidbuf) { ++#ifdef USE_PLUGINPARAM ++ ok = ((source = cSource::FromString(sourcebuf)) >= 0) && StringToParameters(parambuf); ++#else + ok = StringToParameters(parambuf) && (source = cSource::FromString(sourcebuf)) >= 0; ++#endif /* PLUGINPARAM */ + + char *p; + if ((p = strchr(vpidbuf, '=')) != NULL) { +@@ -906,6 +979,9 @@ + shortName = strcpyrealloc(shortName, p); + } + name = strcpyrealloc(name, namebuf); ++#ifdef USE_PLUGINPARAM ++ if (IsPlug()) pluginParam = strcpyrealloc(pluginParam, parambuf); ++#endif /* PLUGINPARAM */ + + free(parambuf); + free(sourcebuf); +diff -NaurwB vdr-1.7.10/channels.h vdr-1.7.10-patched/channels.h +--- vdr-1.7.10/channels.h 2009-08-30 13:05:54.000000000 +0200 ++++ vdr-1.7.10-patched/channels.h 2009-12-18 06:25:25.000000000 +0100 +@@ -35,6 +35,9 @@ + #define MAXDPIDS 16 // dolby (AC3 + DTS) + #define MAXSPIDS 32 // subtitles + #define MAXCAIDS 8 // conditional access ++#ifdef USE_TTXTSUBS ++#define MAXTPAGES 8 // teletext pages ++#endif /* TTXTSUBS */ + + #define MAXLANGCODE1 4 // a 3 letter language code, zero terminated + #define MAXLANGCODE2 8 // up to two 3 letter language codes, separated by '+' and zero terminated +@@ -116,6 +119,9 @@ + char *shortName; + char *provider; + char *portalName; ++#ifdef USE_PLUGINPARAM ++ char *pluginParam; ++#endif /* PLUGINPARAM */ + int __BeginData__; + int frequency; // MHz + int source; +@@ -133,6 +139,10 @@ + uint16_t compositionPageIds[MAXSPIDS]; + uint16_t ancillaryPageIds[MAXSPIDS]; + int tpid; ++#ifdef USE_TTXTSUBS ++ char tlangs[MAXTPAGES][MAXLANGCODE2]; ++ int tpages[MAXTPAGES + 1]; // list is zero-terminated ++#endif /* TTXTSUBS */ + int caids[MAXCAIDS + 1]; // list is zero-terminated + int nid; + int tid; +@@ -174,6 +184,9 @@ + int Frequency(void) const { return frequency; } ///< Returns the actual frequency, as given in 'channels.conf' + int Transponder(void) const; ///< Returns the transponder frequency in MHz, plus the polarization in case of sat + static int Transponder(int Frequency, char Polarization); ///< builds the transponder from the given Frequency and Polarization ++#ifdef USE_PLUGINPARAM ++ const char *PluginParam(void) const { return pluginParam; } ++#endif /* PLUGINPARAM */ + int Source(void) const { return source; } + int Srate(void) const { return srate; } + int Vpid(void) const { return vpid; } +@@ -192,6 +205,10 @@ + uint16_t CompositionPageId(int i) const { return (0 <= i && i < MAXSPIDS) ? compositionPageIds[i] : 0; } + uint16_t AncillaryPageId(int i) const { return (0 <= i && i < MAXSPIDS) ? ancillaryPageIds[i] : 0; } + int Tpid(void) const { return tpid; } ++#ifdef USE_TTXTSUBS ++ const char *Tlang(int i) const { return (0 <= i && i < MAXTPAGES) ? tlangs[i] : ""; } ++ const int TPages(int i) const { return (0 <= i && i < MAXTPAGES) ? tpages[i] : 0; } ++#endif /* TTXTSUBS */ + const int *Caids(void) const { return caids; } + int Ca(int Index = 0) const { return Index < MAXCAIDS ? caids[Index] : 0; } + int Nid(void) const { return nid; } +@@ -214,6 +231,9 @@ + int RollOff(void) const { return rollOff; } + const cLinkChannels* LinkChannels(void) const { return linkChannels; } + const cChannel *RefChannel(void) const { return refChannel; } ++#ifdef USE_PLUGINPARAM ++ bool IsPlug(void) const { return cSource::IsPlug(source); } ++#endif /* PLUGINPARAM */ + bool IsCable(void) const { return cSource::IsCable(source); } + bool IsSat(void) const { return cSource::IsSat(source); } + bool IsTerr(void) const { return cSource::IsTerr(source); } +@@ -221,13 +241,22 @@ + bool HasTimer(void) const; + int Modification(int Mask = CHANNELMOD_ALL); + void CopyTransponderData(const cChannel *Channel); ++#ifdef USE_PLUGINPARAM ++ bool SetPlugTransponderData(int Source, int Frequency, const char *PluginParam); ++#endif /* PLUGINPARAM */ + bool SetSatTransponderData(int Source, int Frequency, char Polarization, int Srate, int CoderateH, int Modulation, int System, int RollOff); + bool SetCableTransponderData(int Source, int Frequency, int Modulation, int Srate, int CoderateH); + bool SetTerrTransponderData(int Source, int Frequency, int Bandwidth, int Modulation, int Hierarchy, int CodeRateH, int CodeRateL, int Guard, int Transmission); + void SetId(int Nid, int Tid, int Sid, int Rid = 0); + void SetName(const char *Name, const char *ShortName, const char *Provider); + void SetPortalName(const char *PortalName); ++#ifdef USE_PLUGINPARAM ++ void SetPluginParam(const char *PluginParam); ++#endif /* PLUGINPARAM */ + void SetPids(int Vpid, int Ppid, int Vtype, int *Apids, char ALangs[][MAXLANGCODE2], int *Dpids, char DLangs[][MAXLANGCODE2], int *Spids, char SLangs[][MAXLANGCODE2], int Tpid); ++#ifdef USE_TTXTSUBS ++ void SetTPidData(char TLangs[][MAXLANGCODE2], int TPages[]); ++#endif /* TTXTSUBS */ + void SetCaIds(const int *CaIds); // list must be zero-terminated + void SetCaDescriptors(int Level); + void SetLinkChannels(cLinkChannels *LinkChannels); +diff -NaurwB vdr-1.7.10/config.c vdr-1.7.10-patched/config.c +--- vdr-1.7.10/config.c 2009-06-13 12:25:05.000000000 +0200 ++++ vdr-1.7.10-patched/config.c 2009-12-18 06:25:25.000000000 +0100 +@@ -15,6 +15,9 @@ + #include "interface.h" + #include "plugin.h" + #include "recording.h" ++#ifdef USE_SOURCECAPS ++#include "sources.h" ++#endif /* SOURCECAPS */ + + // IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d' + // format characters in order to allow any number of blanks after a numeric +@@ -30,18 +33,32 @@ + { + title = command = NULL; + confirm = false; ++#ifdef USE_CMDSUBMENU ++ nIndent = 0; ++ childs = NULL; ++#endif /* CMDSUBMENU */ + } + + cCommand::~cCommand() + { + free(title); + free(command); ++#ifdef USE_CMDSUBMENU ++ delete childs; ++#endif /* CMDSUBMENU */ + } + + bool cCommand::Parse(const char *s) + { + const char *p = strchr(s, ':'); + if (p) { ++#ifdef USE_CMDSUBMENU ++ nIndent = 0; ++ while (*s == '-') { ++ nIndent++; ++ s++; ++ } ++#endif /* CMDSUBMENU */ + int l = p - s; + if (l > 0) { + title = MALLOC(char, l + 1); +@@ -87,6 +104,20 @@ + return result; + } + ++#ifdef USE_CMDSUBMENU ++int cCommand::getChildCount(void) ++{ ++ return childs ? childs->Count() : 0; ++} ++ ++void cCommand::addChild(cCommand *newChild) ++{ ++ if (!childs) ++ childs = new cCommands(); ++ childs->AddConfig(newChild); ++} ++#endif /* CMDSUBMENU */ ++ + // --- cSVDRPhost ------------------------------------------------------------ + + cSVDRPhost::cSVDRPhost(void) +@@ -127,6 +158,26 @@ + + cCommands Commands; + cCommands RecordingCommands; ++#ifdef USE_TIMERCMD ++cCommands TimerCommands; ++#endif /* TIMERCMD */ ++ ++#ifdef USE_CMDSUBMENU ++void cCommands::AddConfig(cCommand *Object) ++{ ++ if (!Object) ++ return; ++ //isyslog ("Indent %d %s\n", Object->getIndent(), Object->Title()); ++ for (int index = Count() - 1; index >= 0; index--) { ++ cCommand *parent = Get(index); ++ if (parent->getIndent() < Object->getIndent()) { ++ parent->addChild(Object); ++ return; ++ } ++ } ++ cConfig::Add(Object); ++} ++#endif /* CMDSUBMENU */ + + // --- cSVDRPhosts ----------------------------------------------------------- + +@@ -218,6 +269,12 @@ + strcpy(OSDLanguage, ""); // default is taken from environment + strcpy(OSDSkin, "sttng"); + strcpy(OSDTheme, "default"); ++#ifdef USE_WAREAGLEICON ++ WarEagleIcons = 1; ++#endif /* WAREAGLEICON */ ++#ifdef USE_VALIDINPUT ++ ShowValidInput = 0; ++#endif /* VALIDINPUT */ + PrimaryDVB = 1; + ShowInfoOnChSwitch = 1; + TimeoutRequChInfo = 1; +@@ -263,6 +320,15 @@ + VideoFormat = 0; + UpdateChannels = 5; + UseDolbyDigital = 1; ++#ifdef USE_DOLBYINREC ++ UseDolbyInRecordings = 1; ++#endif /* DOLBYINREC */ ++#ifdef USE_DVBSETUP ++ DolbyTransferFix = 1; ++ ChannelBlocker = 0; ++ ChannelBlockerMode = 0; ++ ChannelBlockerList = strdup(""); ++#endif /* DVBSETUP */ + ChannelInfoPos = 0; + ChannelInfoTime = 5; + OSDLeftP = 0.08; +@@ -287,24 +353,135 @@ + FontSmlSize = 18; + FontFixSize = 20; + MaxVideoFileSize = MAXVIDEOFILESIZEDEFAULT; ++#ifdef USE_HARDLINKCUTTER ++ MaxRecordingSize = DEFAULTRECORDINGSIZE; ++#endif /* HARDLINKCUTTER */ + SplitEditedFiles = 0; ++#ifdef USE_HARDLINKCUTTER ++ HardLinkCutter = 0; ++#endif /* HARDLINKCUTTER */ ++#ifdef USE_DELTIMESHIFTREC ++ DelTimeshiftRec = 0; ++#endif /* DELTIMESHIFTREC */ + MinEventTimeout = 30; + MinUserInactivity = 300; + NextWakeupTime = 0; + MultiSpeedMode = 0; + ShowReplayMode = 0; ++#ifdef USE_DDEPGENTRY ++ DoubleEpgTimeDelta = 15; ++ DoubleEpgAction = 0; ++ MixEpgAction = 0; ++ DisableVPS = 0; ++#endif /* DDEPGENTRY */ + ResumeID = 0; ++#ifdef USE_JUMPPLAY ++ JumpPlay = 0; ++ PlayJump = 0; ++ PauseLastMark = 0; ++ ReloadMarks = 0; ++#endif /* JUMPPLAY */ ++#ifdef USE_SOURCECAPS ++ memset(SourceCaps, 0, sizeof SourceCaps); ++ SourceCapsSet = false; ++#endif /* SOURCECAPS */ + CurrentChannel = -1; + CurrentVolume = MAXVOLUME; + CurrentDolby = 0; + InitialChannel = 0; + InitialVolume = -1; ++#ifdef USE_VOLCTRL ++ LRVolumeControl = 0; ++ LRChannelGroups = 1; ++ LRForwardRewind = 1; ++#endif /* VOLCTRL */ + EmergencyExit = 1; ++#ifdef USE_NOEPG ++ noEPGMode = 0; ++ noEPGList = strdup(""); ++#endif /* NOEPG */ ++#ifdef USE_LIRCSETTINGS ++ LircRepeatDelay = 350; ++ LircRepeatFreq = 100; ++ LircRepeatTimeout = 500; ++#endif /* LIRCSETTINGS */ ++#ifdef USE_LIEMIEXT ++ ShowRecDate = 1; ++ ShowRecTime = 1; ++ ShowRecLength = 0; ++ ShowProgressBar = 0; ++ MenuCmdPosition = 0; ++ JumpSeconds = 60; ++ JumpSecondsSlow = 10; ++ ShowTimerStop = 1; ++ MainMenuTitle = 0; ++ strcpy(CustomMainMenuTitle, "Video Disk Recorder"); ++#endif /* LIEMIEXT */ ++#ifdef USE_SORTRECORDS ++ RecordingsSortMode = 0; ++ RecordingsSortDirsFirst = 0; ++#endif /* SORTRECORDS */ ++#ifdef USE_CUTTERQUEUE ++ CutterAutoDelete = 0; ++#endif /* CUTTERQUEUE */ ++#ifdef USE_CUTTIME ++ CutTime = 1; ++#endif /* CUTTIME */ ++#ifdef USE_DVDARCHIVE ++ DvdDisplayMode = 1; ++ DvdDisplayZeros = 1; ++ DvdTrayMode = 0; ++ DvdSpeedLimit = 0; ++#endif /* DVDARCHIVE */ ++#ifdef USE_SOFTOSD ++ UseSoftOsd = 0; ++ SoftOsdRate = 50; ++ SoftOsdFadeinSteps = 6; ++ SoftOsdFadeoutSteps = 16; ++ SoftOsdPaletteOnly = 0; ++#endif /* SOFTOSD */ ++#ifdef USE_LNBSHARE ++ VerboseLNBlog = 0; ++ for (int i = 0; i < MAXDEVICES; i++) CardUsesLNBnr[i] = i + 1; ++#endif /* LNBSHARE */ ++#ifdef USE_DVLVIDPREFER ++ UseVidPrefer = 0; // default = disabled ++ nVidPrefer = 1; ++ for (int zz = 1; zz < DVLVIDPREFER_MAX; zz++) { ++ VidPreferPrio[ zz ] = 50; ++ VidPreferSize[ zz ] = 100; ++ } ++ VidPreferSize[ 0 ] = 800; ++ VidPreferPrio[ 0 ] = 50; ++#endif /* DVLVIDPREFER */ ++#ifdef USE_DVLFRIENDLYFNAMES ++ UseFriendlyFNames = 0; // default = disabled ++#endif /* DVLFRIENDLYFNAMES */ ++} ++ ++#if defined (USE_DVBSETUP) || defined (USE_NOEPG) ++cSetup::~cSetup() ++{ ++#ifdef USE_DVBSETUP ++ free(ChannelBlockerList); ++#endif /* DVBSETUP */ ++#ifdef USE_NOEPG ++ free(noEPGList); ++#endif /* NOEPG */ + } ++#endif /* DVBSETUP + NOEPG */ + + cSetup& cSetup::operator= (const cSetup &s) + { + memcpy(&__BeginData__, &s.__BeginData__, (char *)&s.__EndData__ - (char *)&s.__BeginData__); ++#ifdef USE_DVBSETUP ++ free(ChannelBlockerList); ++ ChannelBlockerList = strdup(s.ChannelBlockerList); ++#endif /* DVBSETUP */ ++#ifdef USE_NOEPG ++ free(noEPGList); ++ noEPGList = strdup(s.noEPGList); ++#endif /* NOEPG */ + return *this; + } + +@@ -400,11 +577,62 @@ + return true; + } + ++#ifdef USE_SOURCECAPS ++void cSetup::StoreSourceCaps(const char *Name) ++{ ++ cSetupLine *l; ++ while ((l = Get(Name)) != NULL) ++ Del(l); ++ ++ for (int i = 0; i < MAXDEVICES; i++) { ++ char buffer[MAXSOURCECAPS*8]={0,}, *q = buffer; ++ int j = 0; ++ while (SourceCaps[i][j] && j < MAXSOURCECAPS) { ++ if (j==0) ++ q += snprintf(buffer, sizeof(buffer), "%i ", i+1); ++ q += snprintf(q, sizeof(buffer) - (q-buffer), "%s ", *cSource::ToString(SourceCaps[i][j++])); ++ } ++ if (*buffer) ++ Store(Name, buffer, NULL, true); ++ } ++} ++ ++bool cSetup::ParseSourceCaps(const char *Value) ++{ ++ char *p; ++ int d = strtol(Value, &p, 10)-1, i = 0; ++ while (p < Value+strlen(Value)) { ++ if (*p==0) return true; ++ if (isblank(*p)) ++p; ++ if (isalpha(*p)) { ++ int source = cSource::FromString(p); ++ if (source != cSource::stNone) { ++ SourceCaps[d][i++] = source; ++ SourceCapsSet = true; ++ } ++ else ++ return false; ++ while (!isblank(*p) && *p) ++ ++p; ++ if (i>MAXSOURCECAPS) ++ return false; ++ } ++ } ++ return true; ++} ++#endif /* SOURCECAPS */ ++ + bool cSetup::Parse(const char *Name, const char *Value) + { + if (!strcasecmp(Name, "OSDLanguage")) { strn0cpy(OSDLanguage, Value, sizeof(OSDLanguage)); I18nSetLocale(OSDLanguage); } + else if (!strcasecmp(Name, "OSDSkin")) Utf8Strn0Cpy(OSDSkin, Value, MaxSkinName); + else if (!strcasecmp(Name, "OSDTheme")) Utf8Strn0Cpy(OSDTheme, Value, MaxThemeName); ++#ifdef USE_WAREAGLEICON ++ else if (!strcasecmp(Name, "WarEagleIcons")) WarEagleIcons = atoi(Value); ++#endif /* WAREAGLEICON */ ++#ifdef USE_VALIDINPUT ++ else if (!strcasecmp(Name, "ShowValidInput")) ShowValidInput = atoi(Value); ++#endif /* VALIDINPUT */ + else if (!strcasecmp(Name, "PrimaryDVB")) PrimaryDVB = atoi(Value); + else if (!strcasecmp(Name, "ShowInfoOnChSwitch")) ShowInfoOnChSwitch = atoi(Value); + else if (!strcasecmp(Name, "TimeoutRequChInfo")) TimeoutRequChInfo = atoi(Value); +@@ -450,6 +678,18 @@ + else if (!strcasecmp(Name, "VideoFormat")) VideoFormat = atoi(Value); + else if (!strcasecmp(Name, "UpdateChannels")) UpdateChannels = atoi(Value); + else if (!strcasecmp(Name, "UseDolbyDigital")) UseDolbyDigital = atoi(Value); ++#ifdef USE_DOLBYINREC ++ else if (!strcasecmp(Name, "UseDolbyInRecordings")) UseDolbyInRecordings = atoi(Value); ++#endif /* DOLBYINREC */ ++#ifdef USE_DVBSETUP ++ else if (!strcasecmp(Name, "DolbyTransferFix")) DolbyTransferFix = atoi(Value); ++ else if (!strcasecmp(Name, "ChannelBlocker")) ChannelBlocker = atoi(Value); ++ else if (!strcasecmp(Name, "ChannelBlockerMode")) ChannelBlockerMode = atoi(Value); ++ else if (!strcasecmp(Name, "ChannelBlockerList")) { ++ free(ChannelBlockerList); ++ ChannelBlockerList = strdup(Value ? Value : ""); ++ } ++#endif /* DVBSETUP */ + else if (!strcasecmp(Name, "ChannelInfoPos")) ChannelInfoPos = atoi(Value); + else if (!strcasecmp(Name, "ChannelInfoTime")) ChannelInfoTime = atoi(Value); + else if (!strcasecmp(Name, "OSDLeftP")) OSDLeftP = atof(Value); +@@ -474,21 +714,144 @@ + else if (!strcasecmp(Name, "FontSmlSize")) FontSmlSize = atoi(Value); + else if (!strcasecmp(Name, "FontFixSize")) FontFixSize = atoi(Value); + else if (!strcasecmp(Name, "MaxVideoFileSize")) MaxVideoFileSize = atoi(Value); ++#ifdef USE_HARDLINKCUTTER ++ else if (!strcasecmp(Name, "MaxRecordingSize")) MaxRecordingSize = atoi(Value); ++#endif /* HARDLINKCUTTER */ + else if (!strcasecmp(Name, "SplitEditedFiles")) SplitEditedFiles = atoi(Value); ++#ifdef USE_HARDLINKCUTTER ++ else if (!strcasecmp(Name, "HardLinkCutter")) HardLinkCutter = atoi(Value); ++#endif /* HARDLINKCUTTER */ ++#ifdef USE_DELTIMESHIFTREC ++ else if (!strcasecmp(Name, "DelTimeshiftRec")) DelTimeshiftRec = atoi(Value); ++#endif /* DELTIMESHIFTREC */ + else if (!strcasecmp(Name, "MinEventTimeout")) MinEventTimeout = atoi(Value); + else if (!strcasecmp(Name, "MinUserInactivity")) MinUserInactivity = atoi(Value); + else if (!strcasecmp(Name, "NextWakeupTime")) NextWakeupTime = atoi(Value); + else if (!strcasecmp(Name, "MultiSpeedMode")) MultiSpeedMode = atoi(Value); + else if (!strcasecmp(Name, "ShowReplayMode")) ShowReplayMode = atoi(Value); ++#ifdef USE_DDEPGENTRY ++ else if (!strcasecmp(Name, "DoubleEpgTimeDelta")) DoubleEpgTimeDelta = atoi(Value); ++ else if (!strcasecmp(Name, "DoubleEpgAction")) DoubleEpgAction = atoi(Value); ++ else if (!strcasecmp(Name, "MixEpgAction")) MixEpgAction = atoi(Value); ++ else if (!strcasecmp(Name, "DisableVPS")) DisableVPS = atoi(Value); ++#endif /* DDEPGENTRY */ + else if (!strcasecmp(Name, "ResumeID")) ResumeID = atoi(Value); ++#ifdef USE_JUMPPLAY ++ else if (!strcasecmp(Name, "JumpPlay")) JumpPlay = atoi(Value); ++ else if (!strcasecmp(Name, "PlayJump")) PlayJump = atoi(Value); ++ else if (!strcasecmp(Name, "PauseLastMark")) PauseLastMark = atoi(Value); ++ else if (!strcasecmp(Name, "ReloadMarks")) ReloadMarks = atoi(Value); ++#endif /* JUMPPLAY */ ++#ifdef USE_SOURCECAPS ++ else if (!strcasecmp(Name, "SourceCaps")) return ParseSourceCaps(Value); ++#endif /* SOURCECAPS */ + else if (!strcasecmp(Name, "CurrentChannel")) CurrentChannel = atoi(Value); + else if (!strcasecmp(Name, "CurrentVolume")) CurrentVolume = atoi(Value); + else if (!strcasecmp(Name, "CurrentDolby")) CurrentDolby = atoi(Value); + else if (!strcasecmp(Name, "InitialChannel")) InitialChannel = atoi(Value); + else if (!strcasecmp(Name, "InitialVolume")) InitialVolume = atoi(Value); ++#ifdef USE_VOLCTRL ++ else if (!strcasecmp(Name, "LRVolumeControl")) LRVolumeControl = atoi(Value); ++ else if (!strcasecmp(Name, "LRChannelGroups")) LRChannelGroups = atoi(Value); ++ else if (!strcasecmp(Name, "LRForwardRewind")) LRForwardRewind = atoi(Value); ++#endif /* VOLCTRL */ + else if (!strcasecmp(Name, "EmergencyExit")) EmergencyExit = atoi(Value); ++#ifdef USE_NOEPG ++ else if (!strcasecmp(Name, "noEPGMode")) noEPGMode = atoi(Value); ++ else if (!strcasecmp(Name, "noEPGList")) { ++ free(noEPGList); ++ noEPGList = strdup(Value ? Value : ""); ++ } ++#endif /* NOEPG */ ++#ifdef USE_LIRCSETTINGS ++ else if (!strcasecmp(Name, "LircRepeatDelay")) LircRepeatDelay = atoi(Value); ++ else if (!strcasecmp(Name, "LircRepeatFreq")) LircRepeatFreq = atoi(Value); ++ else if (!strcasecmp(Name, "LircRepeatTimeout")) LircRepeatTimeout = atoi(Value); ++#endif /* LIRCSETTINGS */ ++#ifdef USE_LIEMIEXT ++ else if (!strcasecmp(Name, "ShowRecDate")) ShowRecDate = atoi(Value); ++ else if (!strcasecmp(Name, "ShowRecTime")) ShowRecTime = atoi(Value); ++ else if (!strcasecmp(Name, "ShowRecLength")) ShowRecLength = atoi(Value); ++ else if (!strcasecmp(Name, "ShowProgressBar")) ShowProgressBar = atoi(Value); ++ else if (!strcasecmp(Name, "MenuCmdPosition")) MenuCmdPosition = atoi(Value); ++ else if (!strcasecmp(Name, "JumpSeconds")) JumpSeconds = atoi(Value); ++ else if (!strcasecmp(Name, "JumpSecondsSlow")) JumpSecondsSlow = atoi(Value); ++ else if (!strcasecmp(Name, "ShowTimerStop")) ShowTimerStop = atoi(Value); ++ else if (!strcasecmp(Name, "MainMenuTitle")) MainMenuTitle = atoi(Value); ++ else if (!strcasecmp(Name, "CustomMainMenuTitle")) Utf8Strn0Cpy(CustomMainMenuTitle, Value, MaxTitleName); ++#endif /* LIEMIEXT */ ++#ifdef USE_SORTRECORDS ++ else if (!strcasecmp(Name, "RecordingsSortMode")) RecordingsSortMode = atoi(Value); ++ else if (!strcasecmp(Name, "RecordingsSortDirsFirst")) RecordingsSortDirsFirst = atoi(Value); ++#endif /* SORTRECORDS */ ++#ifdef USE_CUTTERQUEUE ++ else if (!strcasecmp(Name, "CutterAutoDelete")) CutterAutoDelete = atoi(Value); ++#endif /* CUTTERQUEUE */ ++#ifdef USE_CUTTIME ++ else if (!strcasecmp(Name, "CutTime")) CutTime = atoi(Value); ++#endif /* CUTTIME */ ++#ifdef USE_DVDARCHIVE ++ else if (!strcasecmp(Name, "DvdDisplayMode")) DvdDisplayMode = atoi(Value); ++ else if (!strcasecmp(Name, "DvdDisplayZeros")) DvdDisplayZeros = atoi(Value); ++ else if (!strcasecmp(Name, "DvdTrayMode")) DvdTrayMode = atoi(Value); ++ else if (!strcasecmp(Name, "DvdSpeedLimit")) DvdSpeedLimit = atoi(Value); ++#endif /* DVDARCHIVE */ ++#ifdef USE_SOFTOSD ++ else if (!strcasecmp(Name, "UseSoftOsd")) UseSoftOsd = atoi(Value); ++ else if (!strcasecmp(Name, "SoftOsdRate")) SoftOsdRate = atoi(Value); ++ else if (!strcasecmp(Name, "SoftOsdFadeinSteps")) SoftOsdFadeinSteps = atoi(Value); ++ else if (!strcasecmp(Name, "SoftOsdFadeoutSteps")) SoftOsdFadeoutSteps = atoi(Value); ++ else if (!strcasecmp(Name, "SoftOsdPaletteOnly")) SoftOsdPaletteOnly = atoi(Value); ++#endif /* SOFTOSD */ ++#ifdef USE_DVLVIDPREFER ++ else if (strcasecmp(Name, "UseVidPrefer") == 0) UseVidPrefer = atoi(Value); ++ else if (strcasecmp(Name, "nVidPrefer") == 0) nVidPrefer = atoi(Value); ++ else if (strstr(Name, "VidPrefer") == Name) { ++ char *x = (char *)&Name[ strlen(Name) - 1 ]; ++ int vN; ++ ++ if (isdigit(*x) != 0) { ++ while (isdigit(*x) != 0) ++ x--; ++ x++; ++ } ++ ++ vN = atoi(x); ++ if (vN < DVLVIDPREFER_MAX) { ++ if (strstr(Name, "VidPreferPrio") == Name) { ++ VidPreferPrio[ vN ] = atoi(Value); ++ if (VidPreferPrio[ vN ] > 99) ++ VidPreferPrio[ vN ] = 99; ++ } ++ else if (strstr(Name, "VidPreferSize") == Name) { ++ VidPreferSize[ vN ] = atoi(Value); ++ } + else + return false; ++ } ++ } ++#endif /* DVLVIDPREFER */ ++#ifdef USE_DVLFRIENDLYFNAMES ++ else if (strcasecmp(Name, "UseFriendlyFNames") == 0) UseFriendlyFNames = atoi(Value); ++#endif /* DVLFRIENDLYFNAMES */ ++ else ++#ifdef USE_LNBSHARE ++ if (!strcasecmp(Name, "VerboseLNBlog")) VerboseLNBlog = atoi(Value); ++ else { ++ char tmp[20]; ++ bool result = false; ++ for (int i = 1; i <= MAXDEVICES; i++) { ++ sprintf(tmp, "Card%dusesLNBnr", i); ++ if (!strcasecmp(Name, tmp)) { ++ CardUsesLNBnr[i - 1] = atoi(Value); ++ result = true; ++ } ++ } ++ return result; ++ } ++#else ++ return false; ++#endif /* LNBSHARE */ + return true; + } + +@@ -497,6 +860,12 @@ + Store("OSDLanguage", OSDLanguage); + Store("OSDSkin", OSDSkin); + Store("OSDTheme", OSDTheme); ++#ifdef USE_WAREAGLEICON ++ Store("WarEagleIcons", WarEagleIcons); ++#endif /* WAREAGLEICON */ ++#ifdef USE_VALIDINPUT ++ Store("ShowValidInput", ShowValidInput); ++#endif /* VALIDINPUT */ + Store("PrimaryDVB", PrimaryDVB); + Store("ShowInfoOnChSwitch", ShowInfoOnChSwitch); + Store("TimeoutRequChInfo", TimeoutRequChInfo); +@@ -542,6 +911,15 @@ + Store("VideoFormat", VideoFormat); + Store("UpdateChannels", UpdateChannels); + Store("UseDolbyDigital", UseDolbyDigital); ++#ifdef USE_DOLBYINREC ++ Store("UseDolbyInRecordings", UseDolbyInRecordings); ++#endif /* DOLBYINREC */ ++#ifdef USE_DVBSETUP ++ Store("DolbyTransferFix", DolbyTransferFix); ++ Store("ChannelBlocker", ChannelBlocker); ++ Store("ChannelBlockerMode", ChannelBlockerMode); ++ Store("ChannelBlockerList", ChannelBlockerList); ++#endif /* DVBSETUP */ + Store("ChannelInfoPos", ChannelInfoPos); + Store("ChannelInfoTime", ChannelInfoTime); + Store("OSDLeftP", OSDLeftP); +@@ -566,25 +944,152 @@ + Store("FontSmlSize", FontSmlSize); + Store("FontFixSize", FontFixSize); + Store("MaxVideoFileSize", MaxVideoFileSize); ++#ifdef USE_HARDLINKCUTTER ++ Store("MaxRecordingSize", MaxRecordingSize); ++#endif /* HARDLINKCUTTER */ + Store("SplitEditedFiles", SplitEditedFiles); ++#ifdef USE_HARDLINKCUTTER ++ Store("HardLinkCutter", HardLinkCutter); ++#endif /* HARDLINKCUTTER */ ++#ifdef USE_DELTIMESHIFTREC ++ Store("DelTimeshiftRec", DelTimeshiftRec); ++#endif /* DELTIMESHIFTREC */ + Store("MinEventTimeout", MinEventTimeout); + Store("MinUserInactivity", MinUserInactivity); + Store("NextWakeupTime", NextWakeupTime); ++#ifdef USE_DDEPGENTRY ++ Store("DoubleEpgAction", DoubleEpgAction); ++ Store("MixEpgAction", MixEpgAction); ++ Store("DisableVPS", DisableVPS); ++ Store("DoubleEpgTimeDelta", DoubleEpgTimeDelta); ++#endif /* DDEPGENTRY */ + Store("MultiSpeedMode", MultiSpeedMode); + Store("ShowReplayMode", ShowReplayMode); + Store("ResumeID", ResumeID); ++#ifdef USE_JUMPPLAY ++ Store("JumpPlay", JumpPlay); ++ Store("PlayJump", PlayJump); ++ Store("PauseLastMark", PauseLastMark); ++ Store("ReloadMarks", ReloadMarks); ++#endif /* JUMPPLAY */ ++#ifdef USE_SOURCECAPS ++ if (SourceCapsSet) StoreSourceCaps("SourceCaps"); ++#endif /* SOURCECAPS */ + Store("CurrentChannel", CurrentChannel); + Store("CurrentVolume", CurrentVolume); + Store("CurrentDolby", CurrentDolby); + Store("InitialChannel", InitialChannel); + Store("InitialVolume", InitialVolume); ++#ifdef USE_VOLCTRL ++ Store("LRVolumeControl", LRVolumeControl); ++ Store("LRChannelGroups", LRChannelGroups); ++ Store("LRForwardRewind", LRForwardRewind); ++#endif /* VOLCTRL */ + Store("EmergencyExit", EmergencyExit); ++#ifdef USE_NOEPG ++ Store("noEPGMode", noEPGMode); ++ Store("noEPGList", noEPGList); ++#endif /* NOEPG */ ++#ifdef USE_LIRCSETTINGS ++ Store("LircRepeatDelay", LircRepeatDelay); ++ Store("LircRepeatFreq", LircRepeatFreq); ++ Store("LircRepeatTimeout", LircRepeatTimeout); ++#endif /* LIRCSETTINGS */ ++#ifdef USE_LIEMIEXT ++ Store("ShowRecDate", ShowRecDate); ++ Store("ShowRecTime", ShowRecTime); ++ Store("ShowRecLength", ShowRecLength); ++ Store("ShowProgressBar", ShowProgressBar); ++ Store("MenuCmdPosition", MenuCmdPosition); ++ Store("JumpSeconds", JumpSeconds); ++ Store("JumpSecondsSlow", JumpSecondsSlow); ++ Store("ShowTimerStop", ShowTimerStop); ++ Store("MainMenuTitle", MainMenuTitle); ++ Store("CustomMainMenuTitle",CustomMainMenuTitle); ++#endif /* LIEMIEXT */ ++#ifdef USE_SORTRECORDS ++ Store("RecordingsSortMode", RecordingsSortMode); ++ Store("RecordingsSortDirsFirst", RecordingsSortDirsFirst); ++#endif /* SORTRECORDS */ ++#ifdef USE_CUTTERQUEUE ++ Store("CutterAutoDelete", CutterAutoDelete); ++#endif /* CUTTERQUEUE */ ++#ifdef USE_CUTTIME ++ Store("CutTime", CutTime); ++#endif /* CUTTIME */ ++#ifdef USE_DVDARCHIVE ++ Store("DvdDisplayMode", DvdDisplayMode); ++ Store("DvdDisplayZeros", DvdDisplayZeros); ++ Store("DvdTrayMode", DvdTrayMode); ++ Store("DvdSpeedLimit", DvdSpeedLimit); ++#endif /* DVDARCHIVE */ ++#ifdef USE_SOFTOSD ++ Store("UseSoftOsd", UseSoftOsd); ++ Store("SoftOsdRate", SoftOsdRate); ++ Store("SoftOsdFadeinSteps", SoftOsdFadeinSteps); ++ Store("SoftOsdFadeoutSteps", SoftOsdFadeoutSteps); ++ Store("SoftOsdPaletteOnly", SoftOsdPaletteOnly); ++#endif /* SOFTOSD */ ++#ifdef USE_LNBSHARE ++ Store("VerboseLNBlog", VerboseLNBlog); ++ char tmp[20]; ++ if (cDevice::NumDevices() > 1) { ++ for (int i = 1; i <= cDevice::NumDevices(); i++) { ++ sprintf(tmp, "Card%dusesLNBnr", i); ++ Store(tmp, CardUsesLNBnr[i - 1]); ++ } ++ } ++#endif /* LNBSHARE */ ++#ifdef USE_DVLVIDPREFER ++ Store ("UseVidPrefer", UseVidPrefer); ++ Store ("nVidPrefer", nVidPrefer); ++ ++ char vidBuf[32]; ++ for (int zz = 0; zz < nVidPrefer; zz++) { ++ sprintf(vidBuf, "VidPreferPrio%d", zz); ++ Store (vidBuf, VidPreferPrio[zz]); ++ sprintf(vidBuf, "VidPreferSize%d", zz); ++ Store (vidBuf, VidPreferSize[zz]); ++ } ++#endif /* DVLVIDPREFER */ ++#ifdef USE_DVLFRIENDLYFNAMES ++ Store ("UseFriendlyFNames", UseFriendlyFNames); ++#endif /* DVLFRIENDLYFNAMES */ + + Sort(); + + if (cConfig::Save()) { + isyslog("saved setup to %s", FileName()); ++#ifdef USE_DVDARCHIVE ++ if (DvdDisplayMode >= 1) ::Recordings.Load(); ++#endif /* DVDARCHIVE */ + return true; + } + return false; + } ++ ++#ifdef USE_CMDRECCMDI18N ++bool LoadCommandsI18n(cCommands & cmds, const char *FileName, bool AllowComments, bool MustExist) ++{ ++ bool bRet = true; ++ bool bLoadDefault = (bool)strcmp(Setup.OSDLanguage, "en_US"); ++ if (bLoadDefault) { ++ // attempt to load a translated file ++ cString FullPath = cString::sprintf("%s.%s", FileName, Setup.OSDLanguage); ++ if (!cmds.Load((FullPath), AllowComments, true)) { ++ // require to exist, just to be able to log ++ // fallback ++ bLoadDefault = false; ++ esyslog("Failed to load translated '%s' for language (%s)", *FullPath, Setup.OSDLanguage); ++ esyslog("Falling back to default '%s' (if any)", FileName); ++ } ++ } ++ if (!bLoadDefault) { ++ // let's do it the normal way ++ bRet = cmds.Load(FileName, AllowComments, MustExist); ++ } ++ // return status only for the default commands file ++ return bRet; ++} ++#endif /* CMDRECCMDI18N */ ++ +diff -NaurwB vdr-1.7.10/config.h vdr-1.7.10-patched/config.h +--- vdr-1.7.10/config.h 2009-08-29 14:47:03.000000000 +0200 ++++ vdr-1.7.10-patched/config.h 2009-12-18 06:25:25.000000000 +0100 +@@ -36,23 +36,74 @@ + // plugins to work with newer versions of the core VDR as long as no + // VDR header files have changed. + ++#define VDREXTENSIONS 72 ++ ++#ifdef USE_JUMPPLAY ++#define JUMPPLAYVERSNUM 100 ++#endif /* JUMPPLAY */ ++ ++#ifdef USE_LIEMIEXT ++#define LIEMIKUUTIO 127 ++#define MAXMAINMENUTITLE 4 ++#define MaxTitleName 64 ++#endif /* LIEMIEXT */ ++ ++#ifdef USE_CMDSUBMENU ++#define CMDSUBMENUVERSNUM 7 ++#endif /* CMDSUBMENU */ ++ ++#ifdef USE_MAINMENUHOOKS ++#define MAINMENUHOOKSVERSNUM 1.0 ++#endif /* MAINMENUHOOKS */ ++ ++#ifdef USE_PINPLUGIN ++#define PIN_PLUGIN_PATCH 120 ++#endif /* PINPLUGIN */ ++ ++#ifdef USE_PLUGINPARAM ++#define PLUGINPARAMPATCHVERSNUM 1 ++#endif /* PLUGINPARAM */ ++ + #define MAXPRIORITY 99 + #define MAXLIFETIME 99 + ++#ifdef USE_DVLVIDPREFER ++#define DVLVIDPREFER_MAX 12 ++#endif /* DVLVIDPREFER */ ++ + #define MINOSDWIDTH 480 + #define MAXOSDWIDTH 1920 + #define MINOSDHEIGHT 324 + #define MAXOSDHEIGHT 1200 + ++#ifdef USE_SOURCECAPS ++#define MAXDEVICES 16 // the maximum number of devices in the system ++#define MAXSOURCECAPS 128 // the maximum number of different sources per device ++#endif /* SOURCECAPS */ ++ ++#ifdef USE_LNBSHARE ++#ifndef MAXDEVICES ++#define MAXDEVICES 16 // the maximum number of devices in the system ++#endif ++#endif /* LNBSHARE */ ++ + #define MaxFileName 256 + #define MaxSkinName 16 + #define MaxThemeName 16 + ++#ifdef USE_CMDSUBMENU ++class cCommands; ++#endif /* CMDSUBMENU */ ++ + class cCommand : public cListObject { + private: + char *title; + char *command; + bool confirm; ++#ifdef USE_CMDSUBMENU ++ int nIndent; ++ cCommands *childs; ++#endif /* CMDSUBMENU */ + static char *result; + public: + cCommand(void); +@@ -61,6 +112,14 @@ + const char *Title(void) { return title; } + bool Confirm(void) { return confirm; } + const char *Execute(const char *Parameters = NULL); ++#ifdef USE_CMDSUBMENU ++ int getIndent(void) { return nIndent; } ++ void setIndent(int nNewIndent) { nIndent = nNewIndent; } ++ cCommands *getChilds(void) { return childs; } ++ int getChildCount(void); ++ bool hasChilds(void) { return getChildCount() > 0; } ++ void addChild(cCommand *newChild); ++#endif /* CMDSUBMENU */ + }; + + typedef uint32_t in_addr_t; //XXX from /usr/include/netinet/in.h (apparently this is not defined on systems with glibc < 2.2) +@@ -88,6 +147,9 @@ + public: + cConfig(void) { fileName = NULL; } + virtual ~cConfig() { free(fileName); } ++#ifdef USE_CMDSUBMENU ++ virtual void AddConfig(T *Object) { cList::Add(Object); } ++#endif /* CMDSUBMENU */ + const char *FileName(void) { return fileName; } + bool Load(const char *FileName = NULL, bool AllowComments = false, bool MustExist = false) + { +@@ -117,7 +179,11 @@ + if (!isempty(s)) { + T *l = new T; + if (l->Parse(s)) ++#ifdef USE_CMDSUBMENU ++ AddConfig(l); ++#else + Add(l); ++#endif /* CMDSUBMENU */ + else { + esyslog("ERROR: error in %s, line %d", fileName, line); + delete l; +@@ -158,7 +224,14 @@ + } + }; + ++#ifdef USE_CMDSUBMENU ++class cCommands : public cConfig { ++public: ++ virtual void AddConfig(cCommand *Object); ++ }; ++#else + class cCommands : public cConfig {}; ++#endif /* CMDSUBMENU */ + + class cSVDRPhosts : public cConfig { + public: +@@ -167,6 +240,9 @@ + + extern cCommands Commands; + extern cCommands RecordingCommands; ++#ifdef USE_TIMERCMD ++extern cCommands TimerCommands; ++#endif /* TIMERCMD */ + extern cSVDRPhosts SVDRPhosts; + + class cSetupLine : public cListObject { +@@ -192,6 +268,10 @@ + void StoreLanguages(const char *Name, int *Values); + bool ParseLanguages(const char *Value, int *Values); + bool Parse(const char *Name, const char *Value); ++#ifdef USE_SOURCECAPS ++ void StoreSourceCaps(const char *Name); ++ bool ParseSourceCaps(const char *Value); ++#endif /* SOURCECAPS */ + cSetupLine *Get(const char *Name, const char *Plugin = NULL); + void Store(const char *Name, const char *Value, const char *Plugin = NULL, bool AllowMultiple = false); + void Store(const char *Name, int Value, const char *Plugin = NULL); +@@ -202,6 +282,12 @@ + char OSDLanguage[I18N_MAX_LOCALE_LEN]; + char OSDSkin[MaxSkinName]; + char OSDTheme[MaxThemeName]; ++#ifdef USE_WAREAGLEICON ++ int WarEagleIcons; ++#endif /* WAREAGLEICON */ ++#ifdef USE_VALIDINPUT ++ int ShowValidInput; ++#endif /* VALIDINPUT */ + int PrimaryDVB; + int ShowInfoOnChSwitch; + int TimeoutRequChInfo; +@@ -243,6 +329,14 @@ + int VideoFormat; + int UpdateChannels; + int UseDolbyDigital; ++#ifdef USE_DOLBYINREC ++ int UseDolbyInRecordings; ++#endif /* DOLBYINREC */ ++#ifdef USE_DVBSETUP ++ int DolbyTransferFix; ++ int ChannelBlocker; ++ int ChannelBlockerMode; ++#endif /* DVBSETUP */ + int ChannelInfoPos; + int ChannelInfoTime; + double OSDLeftP, OSDTopP, OSDWidthP, OSDHeightP; +@@ -261,20 +355,111 @@ + int FontSmlSize; + int FontFixSize; + int MaxVideoFileSize; ++#ifdef USE_HARDLINKCUTTER ++ int MaxRecordingSize; ++#endif /* HARDLINKCUTTER */ + int SplitEditedFiles; ++#ifdef USE_HARDLINKCUTTER ++ int HardLinkCutter; ++#endif /* HARDLINKCUTTER */ ++#ifdef USE_DELTIMESHIFTREC ++ int DelTimeshiftRec; ++#endif /* DELTIMESHIFTREC */ + int MinEventTimeout, MinUserInactivity; + time_t NextWakeupTime; + int MultiSpeedMode; + int ShowReplayMode; ++#ifdef USE_DDEPGENTRY ++ int DoubleEpgTimeDelta; ++ int DoubleEpgAction; ++ int MixEpgAction; ++ int DisableVPS; ++#endif /* DDEPGENTRY */ + int ResumeID; ++#ifdef USE_JUMPPLAY ++ int JumpPlay; ++ int PlayJump; ++ int PauseLastMark; ++ int ReloadMarks; ++#endif /* JUMPPLAY */ ++#ifdef USE_SOURCECAPS ++ int SourceCaps[MAXDEVICES][MAXSOURCECAPS]; ++ bool SourceCapsSet; ++#endif /* SOURCECAPS */ + int CurrentChannel; + int CurrentVolume; + int CurrentDolby; + int InitialChannel; + int InitialVolume; ++#ifdef USE_VOLCTRL ++ int LRVolumeControl; ++ int LRChannelGroups; ++ int LRForwardRewind; ++#endif /* VOLCTRL */ + int EmergencyExit; ++#ifdef USE_NOEPG ++ int noEPGMode; ++#endif /* NOEPG */ ++#ifdef USE_LIRCSETTINGS ++ int LircRepeatDelay; ++ int LircRepeatFreq; ++ int LircRepeatTimeout; ++#endif /* LIRCSETTINGS */ ++#ifdef USE_LIEMIEXT ++ int ShowRecDate, ShowRecTime, ShowRecLength, ShowProgressBar, MenuCmdPosition; ++ int JumpSeconds; ++ int JumpSecondsSlow; ++ int ShowTimerStop; ++ int MainMenuTitle; ++ char CustomMainMenuTitle[MaxTitleName]; ++#endif /* LIEMIEXT */ ++#ifdef USE_SORTRECORDS ++ int RecordingsSortMode; ++ int RecordingsSortDirsFirst; ++#endif /* SORTRECORDS */ ++#ifdef USE_CUTTERQUEUE ++ int CutterAutoDelete; ++#endif /* CUTTERQUEUE */ ++#ifdef USE_CUTTIME ++ int CutTime; ++#endif /* CUTTIME */ ++#ifdef USE_DVDARCHIVE ++ int DvdDisplayMode; ++ int DvdDisplayZeros; ++ int DvdTrayMode; ++ int DvdSpeedLimit; ++#endif /* DVDARCHIVE */ ++#ifdef USE_SOFTOSD ++ int UseSoftOsd; ++ int SoftOsdRate; ++ int SoftOsdFadeinSteps; ++ int SoftOsdFadeoutSteps; ++ int SoftOsdPaletteOnly; ++#endif /* SOFTOSD */ ++#ifdef USE_LNBSHARE ++ int VerboseLNBlog; ++ int CardUsesLNBnr[MAXDEVICES]; ++#endif /* LNBSHARE */ ++#ifdef USE_DVLVIDPREFER ++ int UseVidPrefer; // 0 = VDR's default, 1 = use ++ int nVidPrefer; ++ int VidPreferPrio[DVLVIDPREFER_MAX]; ++ int VidPreferSize[DVLVIDPREFER_MAX]; ++#endif /* DVLVIDPREFER */ ++#ifdef USE_DVLFRIENDLYFNAMES ++ int UseFriendlyFNames; ++#endif /* DVLFRIENDLYFNAMES */ + int __EndData__; ++#ifdef USE_DVBSETUP ++ char *ChannelBlockerList; ++#endif /* DVBSETUP */ ++#ifdef USE_NOEPG ++ char *noEPGList; // pointer not to be flat-copied ++#endif /* NOEPG */ + cSetup(void); ++#if defined (USE_DVBSETUP) || defined (USE_NOEPG) ++ ~cSetup(); ++#endif /* DVBSETUP + NOEPG */ + cSetup& operator= (const cSetup &s); + bool Load(const char *FileName); + bool Save(void); +@@ -282,4 +467,8 @@ + + extern cSetup Setup; + ++#ifdef USE_CMDRECCMDI18N ++bool LoadCommandsI18n(cCommands & cmds, const char *FileName = NULL, bool AllowComments = false, bool MustExist = false); ++#endif /* CMDRECCMDI18N */ ++ + #endif //__CONFIG_H +diff -NaurwB vdr-1.7.10/cutter.c vdr-1.7.10-patched/cutter.c +--- vdr-1.7.10/cutter.c 2009-04-19 12:56:33.000000000 +0200 ++++ vdr-1.7.10-patched/cutter.c 2009-12-18 06:25:25.000000000 +0100 +@@ -15,6 +15,19 @@ + + // --- cCuttingThread -------------------------------------------------------- + ++#ifdef USE_CUTTERLIMIT ++#ifndef CUTTER_MAX_BANDWIDTH ++# define CUTTER_MAX_BANDWIDTH MEGABYTE(10) // 10 MB/s ++#endif ++#ifndef CUTTER_REL_BANDWIDTH ++# define CUTTER_REL_BANDWIDTH 75 // % ++#endif ++#ifndef CUTTER_PRIORITY ++# define CUTTER_PRIORITY sched_get_priority_min(SCHED_OTHER) ++#endif ++#define CUTTER_TIMESLICE 100 // ms ++#endif /* CUTTERLIMIT */ ++ + class cCuttingThread : public cThread { + private: + const char *error; +@@ -67,6 +80,22 @@ + + void cCuttingThread::Action(void) + { ++#ifdef USE_CUTTERLIMIT ++#ifdef USE_HARDLINKCUTTER ++ if (!Setup.HardLinkCutter) ++#endif /* HARDLINKCUTTER */ ++ { ++ sched_param tmp; ++ tmp.sched_priority = CUTTER_PRIORITY; ++ if (!pthread_setschedparam(pthread_self(), SCHED_OTHER, &tmp)) ++ printf("cCuttingThread::Action: cant set priority\n"); ++ } ++ ++ int bytes = 0; ++ int __attribute__((unused)) burst_size = CUTTER_MAX_BANDWIDTH * CUTTER_TIMESLICE / 1000; // max bytes/timeslice ++ cTimeMs __attribute__((unused)) t; ++#endif /* CUTTERLIMIT */ ++ + cMark *Mark = fromMarks.First(); + if (Mark) { + fromFile = fromFileName->Open(); +@@ -78,6 +107,9 @@ + Mark = fromMarks.Next(Mark); + off_t FileSize = 0; + int CurrentFileNumber = 0; ++#ifdef USE_HARDLINKCUTTER ++ bool SkipThisSourceFile = false; ++#endif /* HARDLINKCUTTER */ + int LastIFrame = 0; + toMarks.Add(0); + toMarks.Save(); +@@ -96,12 +128,101 @@ + + // Read one frame: + ++#ifndef USE_HARDLINKCUTTER + if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &Independent, &Length)) { + if (FileNumber != CurrentFileNumber) { + fromFile = fromFileName->SetOffset(FileNumber, FileOffset); + fromFile->SetReadAhead(MEGABYTE(20)); + CurrentFileNumber = FileNumber; + } ++#else ++ if (!fromIndex->Get(Index++, &FileNumber, &FileOffset, &Independent, &Length)) { ++ // Error, unless we're past last cut-in and there's no cut-out ++ if (Mark || LastMark) ++ error = "index"; ++ break; ++ } ++ ++ if (FileNumber != CurrentFileNumber) { ++ fromFile = fromFileName->SetOffset(FileNumber, FileOffset); ++ fromFile->SetReadAhead(MEGABYTE(20)); ++ CurrentFileNumber = FileNumber; ++ if (SkipThisSourceFile) { ++ // At end of fast forward: Always skip to next file ++ toFile = toFileName->NextFile(); ++ if (!toFile) { ++ error = "toFile 4"; ++ break; ++ } ++ FileSize = 0; ++ SkipThisSourceFile = false; ++ } ++ ++ ++ if (Setup.HardLinkCutter && FileOffset == 0) { ++ // We are at the beginning of a new source file. ++ // Do we need to copy the whole file? ++ ++ // if !Mark && LastMark, then we're past the last cut-out and continue to next I-frame ++ // if !Mark && !LastMark, then there's just a cut-in, but no cut-out ++ // if Mark, then we're between a cut-in and a cut-out ++ ++ uint16_t MarkFileNumber; ++ off_t MarkFileOffset; ++ // Get file number of next cut mark ++ if (!Mark && !LastMark ++ || Mark ++ && fromIndex->Get(Mark->position, &MarkFileNumber, &MarkFileOffset) ++ && (MarkFileNumber != CurrentFileNumber)) { ++ // The current source file will be copied completely. ++ // Start new output file unless we did that already ++ if (FileSize != 0) { ++ toFile = toFileName->NextFile(); ++ if (!toFile) { ++ error = "toFile 3"; ++ break; ++ } ++ FileSize = 0; ++ } ++ ++ // Safety check that file has zero size ++ struct stat buf; ++ if (stat(toFileName->Name(), &buf) == 0) { ++ if (buf.st_size != 0) { ++ esyslog("cCuttingThread: File %s exists and has nonzero size", toFileName->Name()); ++ error = "nonzero file exist"; ++ break; ++ } ++ } ++ else if (errno != ENOENT) { ++ esyslog("cCuttingThread: stat failed on %s", toFileName->Name()); ++ error = "stat"; ++ break; ++ } ++ ++ // Clean the existing 0-byte file ++ toFileName->Close(); ++ cString ActualToFileName(ReadLink(toFileName->Name()), true); ++ unlink(ActualToFileName); ++ unlink(toFileName->Name()); ++ ++ // Try to create a hard link ++ if (HardLinkVideoFile(fromFileName->Name(), toFileName->Name())) { ++ // Success. Skip all data transfer for this file ++ SkipThisSourceFile = true; ++ cutIn = false; ++ toFile = NULL; // was deleted by toFileName->Close() ++ } ++ else { ++ // Fallback: Re-open the file if necessary ++ toFile = toFileName->Open(); ++ } ++ } ++ } ++ } ++ ++ if (!SkipThisSourceFile) { ++#endif /* HARDLINKCUTTER */ + if (fromFile) { + int len = ReadFrame(fromFile, buffer, Length, sizeof(buffer)); + if (len < 0) { +@@ -118,6 +239,7 @@ + break; + } + } ++#ifndef USE_HARDLINKCUTTER + else { + // Error, unless we're past the last cut-in and there's no cut-out + if (Mark || LastMark) +@@ -125,12 +247,17 @@ + break; + } + ++#endif /* HARDLINKCUTTER */ + // Write one frame: + + if (Independent) { // every file shall start with an independent frame + if (LastMark) // edited version shall end before next I-frame + break; ++#ifndef USE_HARDLINKCUTTER + if (FileSize > maxVideoFileSize) { ++#else ++ if (!SkipThisSourceFile && FileSize > toFileName->MaxFileSize()) { ++#endif /* HARDLINKCUTTER */ + toFile = toFileName->NextFile(); + if (!toFile) { + error = "toFile 1"; +@@ -140,7 +267,11 @@ + } + LastIFrame = 0; + ++#ifndef USE_HARDLINKCUTTER + if (cutIn) { ++#else ++ if (!SkipThisSourceFile && cutIn) { ++#endif /* HARDLINKCUTTER */ + if (isPesRecording) + cRemux::SetBrokenLink(buffer, Length); + else +@@ -148,7 +279,11 @@ + cutIn = false; + } + } ++#ifndef USE_HARDLINKCUTTER + if (toFile->Write(buffer, Length) < 0) { ++#else ++ if (!SkipThisSourceFile && toFile->Write(buffer, Length) < 0) { ++#endif /* HARDLINKCUTTER */ + error = "safe_write"; + break; + } +@@ -183,8 +318,44 @@ + } + } + else ++#ifndef USE_HARDLINKCUTTER + LastMark = true; ++#else ++ LastMark = true; // After last cut-out: Write on until next I-frame, then exit ++#endif /* HARDLINKCUTTER */ ++ } ++#ifdef USE_CUTTERLIMIT ++#ifdef USE_HARDLINKCUTTER ++ if (!Setup.HardLinkCutter) { ++#endif /* HARDLINKCUTTER */ ++ bytes += Length; ++ if (bytes >= burst_size) { ++ int elapsed = t.Elapsed(); ++ int sleep = 0; ++ ++#if CUTTER_REL_BANDWIDTH > 0 && CUTTER_REL_BANDWIDTH < 100 ++ // stay under max. relative bandwidth ++ ++ sleep = (elapsed * 100 / CUTTER_REL_BANDWIDTH) - elapsed; ++ //if (sleep<=0 && elapsed<=2) sleep = 1; ++ //if (sleep) esyslog("cutter: relative bandwidth limit, sleep %d ms (chunk %dk / %dms)", sleep, burst_size/1024, CUTTER_TIMESLICE); ++#endif ++ // stay under max. absolute bandwidth ++ if (elapsed < CUTTER_TIMESLICE) { ++ sleep = max(CUTTER_TIMESLICE - elapsed, sleep); ++ //if (sleep) esyslog("cutter: absolute bandwidth limit, sleep %d ms (chunk %dk / %dms)", sleep, burst_size/1024, CUTTER_TIMESLICE); ++ } ++ ++ if (sleep>0) ++ cCondWait::SleepMs(sleep); ++ t.Set(); ++ bytes = 0; ++ } ++#ifdef USE_HARDLINKCUTTER + } ++#endif /* HARDLINKCUTTER */ ++#endif /* CUTTERLIMIT */ ++ + } + Recordings.TouchUpdate(); + } +@@ -194,18 +365,74 @@ + + // --- cCutter --------------------------------------------------------------- + ++#ifdef USE_CUTTERQUEUE ++#define WAIT_BEFORE_NEXT_CUT (10*1000) // 10 seconds ++ ++class cStringListObject : public cListObject { ++ public: ++ cStringListObject(const char *s) { str = strdup(s); } ++ ~cStringListObject() { free(str); } ++ ++ const char *Value() { return str; } ++ operator const char * () { return str; } ++ ++ private: ++ char *str; ++}; ++#endif /* CUTTERQUEUE */ ++ + char *cCutter::editedVersionName = NULL; + cCuttingThread *cCutter::cuttingThread = NULL; + bool cCutter::error = false; + bool cCutter::ended = false; ++#ifdef USE_CUTTERQUEUE ++cMutex *cCutter::cutterLock = new cMutex(); ++static uint64_t /*cCutter::*/lastCuttingEndTime = 0; ++static cList /**cCutter::*/cutterQueue /*= new cList*/; ++#endif /* CUTTERQUEUE */ + + bool cCutter::Start(const char *FileName) + { ++#ifdef USE_CUTTERQUEUE ++ cMutexLock(cutterLock); ++ if (FileName) { ++ /* Add file to queue. ++ * If cutter is still active, next cutting will be started ++ * when vdr.c:main calls cCutter::Active and previous cutting has ++ * been stopped > 10 s before ++ */ ++ cutterQueue.Add(new cStringListObject(FileName)); ++ } ++ if (cuttingThread) ++ return true; ++ /* cut next file from queue */ ++ if (!(cutterQueue.First())) ++ return false; ++ FileName = cutterQueue.First()->Value(); ++#endif /* CUTTERQUEUE */ + if (!cuttingThread) { + error = false; + ended = false; + cRecording Recording(FileName); ++#ifdef USE_CUTTIME ++ if (Setup.CutTime) { ++ cMarks FromMarks; ++ FromMarks.Load(FileName); ++ cMark *First = FromMarks.First(); ++ if (First) Recording.SetStartTime(Recording.start + (int(First->position / Recording.FramesPerSecond() +30) / 60) * 60); ++ } ++#endif /* CUTTIME */ + const char *evn = Recording.PrefixFileName('%'); ++#ifdef USE_CUTTERQUEUE ++ if (!(Recordings.GetByName(FileName))) { ++ // Should _not_ remove any cutted recordings ++ // (original recording already deleted ?) ++ // so, just pop item from queue and return. ++ esyslog("can't cut non-existing recording %s", FileName); ++ cutterQueue.Del(cutterQueue.First()); ++ return true; // might be already queued recording ++ } ++#endif /* CUTTERQUEUE */ + if (evn && RemoveVideoFile(evn) && MakeDirs(evn, true)) { + // XXX this can be removed once RenameVideoFile() follows symlinks (see videodir.c) + // remove a possible deleted recording with the same name to avoid symlink mixups: +@@ -231,6 +458,9 @@ + + void cCutter::Stop(void) + { ++#ifdef USE_CUTTERQUEUE ++ cMutexLock(cutterLock); ++#endif /* CUTTERQUEUE */ + bool Interrupted = cuttingThread && cuttingThread->Active(); + const char *Error = cuttingThread ? cuttingThread->Error() : NULL; + delete cuttingThread; +@@ -242,11 +472,20 @@ + esyslog("ERROR: '%s' during editing process", Error); + RemoveVideoFile(editedVersionName); //XXX what if this file is currently being replayed? + Recordings.DelByName(editedVersionName); +- } ++#ifdef USE_CUTTERQUEUE ++ cutterQueue.Del(cutterQueue.First()); ++#endif /* CUTTERQUEUE */ ++ } ++#ifdef USE_CUTTERQUEUE ++ lastCuttingEndTime = cTimeMs::Now(); ++#endif /* CUTTERQUEUE */ + } + + bool cCutter::Active(void) + { ++#ifdef USE_CUTTERQUEUE ++ cMutexLock(cutterLock); ++#endif /* CUTTERQUEUE */ + if (cuttingThread) { + if (cuttingThread->Active()) + return true; +@@ -257,12 +496,40 @@ + free(editedVersionName); + editedVersionName = NULL; + ended = true; ++#ifdef USE_CUTTERQUEUE ++ if (Setup.CutterAutoDelete) { ++ /* Remove original (if cutting was successful) */ ++ if (!error) { ++ cRecording *recording = Recordings.GetByName(*cutterQueue.First()); ++ if (!recording) ++ esyslog("ERROR: Can't found '%s' after editing process", cutterQueue.First()->Value()); ++ else { ++ if (recording->Delete()) ++ Recordings.DelByName(recording->FileName()); ++ else ++ esyslog("ERROR: Can't delete '%s' after editing process", cutterQueue.First()->Value()); ++ } ++ } ++ lastCuttingEndTime = cTimeMs::Now(); ++ } ++ cutterQueue.Del(cutterQueue.First()); ++#endif /* CUTTERQUEUE */ ++ } ++#ifdef USE_CUTTERQUEUE ++ if (!cuttingThread && cutterQueue.First()) { ++ /* start next cutting from queue*/ ++ if (cTimeMs::Now() > lastCuttingEndTime + WAIT_BEFORE_NEXT_CUT) ++ Start(NULL); + } ++#endif /* CUTTERQUEUE */ + return false; + } + + bool cCutter::Error(void) + { ++#ifdef USE_CUTTERQUEUE ++ cMutexLock(cutterLock); ++#endif /* CUTTERQUEUE */ + bool result = error; + error = false; + return result; +@@ -270,6 +537,9 @@ + + bool cCutter::Ended(void) + { ++#ifdef USE_CUTTERQUEUE ++ cMutexLock(cutterLock); ++#endif /* CUTTERQUEUE */ + bool result = ended; + ended = false; + return result; +diff -NaurwB vdr-1.7.10/cutter.h vdr-1.7.10-patched/cutter.h +--- vdr-1.7.10/cutter.h 2002-06-22 12:03:15.000000000 +0200 ++++ vdr-1.7.10-patched/cutter.h 2009-12-18 06:25:25.000000000 +0100 +@@ -11,6 +11,9 @@ + #define __CUTTER_H + + class cCuttingThread; ++#ifdef USE_CUTTERQUEUE ++class cMutex; ++#endif /* CUTTERQUEUE */ + + class cCutter { + private: +@@ -18,6 +21,9 @@ + static cCuttingThread *cuttingThread; + static bool error; + static bool ended; ++#ifdef USE_CUTTERQUEUE ++ static cMutex *cutterLock; ++#endif /* CUTTERQUEUE */ + public: + static bool Start(const char *FileName); + static void Stop(void); +diff -NaurwB vdr-1.7.10/device.c vdr-1.7.10-patched/device.c +--- vdr-1.7.10/device.c 2009-11-22 14:19:03.000000000 +0100 ++++ vdr-1.7.10-patched/device.c 2009-12-18 06:25:25.000000000 +0100 +@@ -18,6 +18,17 @@ + #include "receiver.h" + #include "status.h" + #include "transfer.h" ++#ifdef USE_TTXTSUBS ++#include "vdrttxtsubshooks.h" ++#endif /* TTXTSUBS */ ++ ++#ifdef USE_LNBSHARE ++#include "diseqc.h" ++#endif /* LNBSHARE */ ++ ++#ifdef USE_CHANNELSCAN ++bool scanning_on_receiving_device = false; ++#endif /* CHANNELSCAN */ + + // --- cLiveSubtitle --------------------------------------------------------- + +@@ -69,6 +80,12 @@ + + SetVideoFormat(Setup.VideoFormat); + ++#ifdef USE_LNBSHARE ++ LNBstate = -1; ++ LNBnr = Setup.CardUsesLNBnr[cardIndex]; ++ LNBsource = NULL; ++#endif /* LNBSHARE */ ++ + mute = false; + volume = Setup.CurrentVolume; + +@@ -94,8 +111,15 @@ + for (int i = 0; i < MAXRECEIVERS; i++) + receiver[i] = NULL; + ++#ifdef USE_SOURCECAPS ++ if (numDevices < MAXDEVICES) { ++ device[numDevices++] = this; ++ SetSourceCaps(cardIndex); ++ } ++#else + if (numDevices < MAXDEVICES) + device[numDevices++] = this; ++#endif /* SOURCECAPS */ + else + esyslog("ERROR: too many devices!"); + } +@@ -130,6 +154,16 @@ + useDevice |= (1 << n); + } + ++#ifdef USE_LNBSHARE ++void cDevice::SetLNBnr(void) ++{ ++ for (int i = 0; i < numDevices; i++) { ++ device[i]->LNBnr = Setup.CardUsesLNBnr[i]; ++ isyslog("LNB-sharing: setting device %d to use LNB %d", i, device[i]->LNBnr); ++ } ++} ++#endif /* LNBSHARE */ ++ + int cDevice::NextCardIndex(int n) + { + if (n > 0) { +@@ -190,6 +224,98 @@ + return d; + } + ++#ifdef USE_LNBSHARE ++cDevice *cDevice::GetBadDevice(const cChannel *Channel) ++{ ++ if (!cSource::IsSat(Channel->Source())) ++ return NULL; ++ if (Setup.DiSEqC) { ++ cDiseqc *diseqc; ++ diseqc = Diseqcs.Get(Channel->Source(), Channel->Frequency(), Channel->Polarization()); ++ ++ for (int i = 0; i < numDevices; i++) { ++ if (this != device[i] && device[i]->GetLNBnr() == LNBnr && device[i]->GetLNBsource() != (int*) diseqc) { ++ if (Setup.VerboseLNBlog) ++ isyslog("LNB %d: Device check for channel %d on device %d. LNB or DiSEq conflict with device %d", LNBnr, Channel->Number(), this->DeviceNumber(), i); ++ return device[i]; ++ } ++ } ++ if (Setup.VerboseLNBlog) ++ isyslog("LNB %d: Device check for for channel %d on device %d. OK", LNBnr, Channel->Number(), this->DeviceNumber()); ++ } ++ else { ++ char requiredState; ++ ++ if (Channel->Frequency() >= Setup.LnbSLOF) ++ requiredState = 1 ; ++ else ++ requiredState = 0; ++ ++ if (Channel->Polarization() == 'v' || Channel->Polarization() == 'V') ++ requiredState += 2; ++ ++ for (int i = 0; i < numDevices; i++) { ++ if (this != device[i] && device[i]->GetLNBnr() == LNBnr && device[i]->GetLNBconf() != requiredState) { ++ if (Setup.VerboseLNBlog) ++ isyslog("LNB %d: Device check for channel %d, LNBstate %d on device %d, current LNBstate %d. Conflict with device %d, LNBstate %d", LNBnr, Channel->Number(), requiredState, this->DeviceNumber(), LNBstate, i, device[i]->GetLNBconf()); ++ return device[i]; ++ } ++ } ++ if (Setup.VerboseLNBlog) ++ isyslog("LNB %d: Device check for channel %d, LNBstate %d on device %d, current LNBstate %d. No other devices affected", LNBnr, Channel->Number(), requiredState, this->DeviceNumber(), LNBstate); ++ } ++ return NULL; ++} ++ ++int cDevice::GetMaxBadPriority(const cChannel *Channel) ++{ ++ if (!cSource::IsSat(Channel->Source())) return -2; ++ bool PrimaryIsBad = false; ++ int maxBadPriority = -2; ++ ++ if (Setup.DiSEqC) { ++ cDiseqc *diseqc; ++ diseqc = Diseqcs.Get(Channel->Source(), Channel->Frequency(), Channel->Polarization()); ++ ++ for (int i = 0; i < numDevices; i++) { ++ if (this != device[i] && device[i]->GetLNBnr() == LNBnr && device[i]->GetLNBsource() != (int*) diseqc) { ++ if (device[i]->Receiving() && device[i]->Priority() > maxBadPriority) ++ maxBadPriority = device[i]->Priority(); ++ if (i == ActualDevice()->CardIndex()) ++ PrimaryIsBad = true; ++ } ++ } ++ } ++ else { ++ char requiredState; ++ if (Channel->Frequency() >= Setup.LnbSLOF) ++ requiredState = 1 ; ++ else ++ requiredState = 0; ++ ++ if (Channel->Polarization() == 'v' || Channel->Polarization() == 'V') ++ requiredState += 2; ++ ++ for (int i = 0; i < numDevices; i++) { ++ if (this != device[i] && device[i]->GetLNBnr() == LNBnr && device[i]->GetLNBconf() != requiredState) { ++ if (device[i]->Receiving() && device[i]->Priority() > maxBadPriority) ++ maxBadPriority = device[i]->Priority(); ++ if (i == ActualDevice()->CardIndex()) ++ PrimaryIsBad = true; ++ } ++ } ++ } ++ ++ if (PrimaryIsBad && maxBadPriority == -2) ++ maxBadPriority = -1; ++ ++ if (Setup.VerboseLNBlog) ++ isyslog("LNB %d: Request for channel %d on device %d. MaxBadPriority is %d", LNBnr, Channel->Number(), this->DeviceNumber(), maxBadPriority); ++ ++ return maxBadPriority; ++} ++#endif /* LNBSHARE */ ++ + cDevice *cDevice::GetDevice(int Index) + { + return (0 <= Index && Index < numDevices) ? device[Index] : NULL; +@@ -239,6 +365,10 @@ + cCamSlot *s = NULL; + + uint32_t Impact = 0xFFFFFFFF; // we're looking for a device with the least impact ++#ifdef USE_LNBSHARE ++ int badPriority; ++ uint imp2; ++#endif /* LNBSHARE */ + for (int j = 0; j < NumCamSlots || !NumUsableSlots; j++) { + if (NumUsableSlots && SlotPriority[j] > MAXPRIORITY) + continue; // there is no CAM available in this slot +@@ -272,7 +402,31 @@ + imp <<= 1; imp |= NumUsableSlots ? 0 : device[i]->HasCi(); // avoid cards with Common Interface for FTA channels + imp <<= 1; imp |= device[i]->HasDecoder(); // avoid full featured cards + imp <<= 1; imp |= NumUsableSlots ? !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), j + 1) : 0; // prefer CAMs that are known to decrypt this channel ++#ifdef USE_LNBSHARE ++ badPriority = device[i]->GetMaxBadPriority(Channel); ++ if (badPriority >= Priority || (badPriority == -1 && Priority < Setup.PrimaryLimit)) { ++ // channel is not available for the requested prioity ++ imp = 0xFFFFFFFF; ++ } ++ else { ++ switch (badPriority) { ++ case -2: // not affected by LNB-sharing ++ imp2 = 0; ++ break; ++ case -1: // the primary device would need a channel switch ++ imp += 1 << 17; ++ imp2 = 0xFFFFFFFF | 1 << 17; ++ break; ++ default: // a device receiving with lower priority would need to be stopped ++ imp += badPriority << 8; ++ imp2 = 0xFFFFFFFF | badPriority << 8; ++ break; ++ } ++ } ++ if (imp < Impact && imp2 < Impact) { ++#else + if (imp < Impact) { ++#endif /* LNBSHARE */ + // This device has less impact than any previous one, so we take it. + Impact = imp; + d = device[i]; +@@ -313,6 +467,18 @@ + camSlot = CamSlot; + } + ++#ifdef USE_SOURCECAPS ++void cDevice::SetSourceCaps(int Index) ++{ ++ for (int d = 0; d < numDevices; d++) { ++ if (Index < 0 || Index == device[d]->CardIndex()) { ++ for (int i = 0; i < MAXSOURCECAPS; i++) ++ device[d]->sourceCaps[i] = Setup.SourceCaps[device[d]->CardIndex()][i]; ++ } ++ } ++} ++#endif /* SOURCECAPS */ ++ + void cDevice::Shutdown(void) + { + primaryDevice = NULL; +@@ -571,7 +737,11 @@ + bool cDevice::ProvidesTransponderExclusively(const cChannel *Channel) const + { + for (int i = 0; i < numDevices; i++) { ++#ifdef USE_LNBSHARE ++ if (device[i] && device[i] != this && device[i]->ProvidesTransponder(Channel) && device[i]->GetLNBnr() != LNBnr) ++#else + if (device[i] && device[i] != this && device[i]->ProvidesTransponder(Channel)) ++#endif /* LNBSHARE */ + return false; + } + return true; +@@ -599,6 +769,24 @@ + + bool cDevice::SwitchChannel(const cChannel *Channel, bool LiveView) + { ++#ifdef USE_LNBSHARE ++ cDevice *tmpDevice; ++ if (this->GetMaxBadPriority(Channel) >= 0) { ++ Skins.Message(mtInfo, tr("Channel locked by LNB!")); ++ return false; ++ } ++ while ((tmpDevice = GetBadDevice(Channel)) != NULL) { ++ if (tmpDevice->IsPrimaryDevice() && LiveView) ++ tmpDevice->SwitchChannelForced(Channel, true); ++ else ++ tmpDevice->SwitchChannelForced(Channel, false); ++ } ++ return SwitchChannelForced(Channel, LiveView); ++} ++ ++bool cDevice::SwitchChannelForced(const cChannel *Channel, bool LiveView) ++{ ++#endif /* LNBSHARE */ + if (LiveView) { + isyslog("switching to channel %d", Channel->Number()); + cControl::Shutdown(); // prevents old channel from being shown too long if GetDevice() takes longer +@@ -628,7 +816,14 @@ + cChannel *channel; + while ((channel = Channels.GetByNumber(n, Direction)) != NULL) { + // try only channels which are currently available ++#ifdef USE_PINPLUGIN ++ if (cStatus::MsgChannelProtected(0, channel) == false) ++#endif /* PINPLUGIN */ ++#ifdef USE_LNBSHARE ++ if (PrimaryDevice()->GetMaxBadPriority(channel) < 0 && (GetDevice(channel, 0, true))) ++#else + if (GetDevice(channel, 0, true)) ++#endif /* LNBSHARE */ + break; + n = channel->Number() + Direction; + } +@@ -649,6 +844,13 @@ + + eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) + { ++#ifdef USE_PINPLUGIN ++ // I hope 'LiveView = false' indicates a channel switch for recording, ++ // I really don't know, but it works ... ++ if (LiveView && cStatus::MsgChannelProtected(this, Channel) == true) ++ return scrNotAvailable; ++#endif /* PINPLUGIN */ ++ + if (LiveView) { + StopReplay(); + DELETENULL(liveSubtitle); +@@ -661,11 +863,37 @@ + + eSetChannelResult Result = scrOk; + ++#ifdef USE_LNBSHARE ++ char requiredState; ++ if (Channel->Frequency() >= Setup.LnbSLOF) ++ requiredState = 1; ++ else ++ requiredState = 0; ++ ++ if (Channel->Polarization() == 'v' || Channel->Polarization() == 'V') ++ requiredState += 2; ++ ++ if (Setup.VerboseLNBlog) ++ isyslog("LNB %d: Switching device %d to channel %d", LNBnr, this->DeviceNumber(), Channel->Number()); ++#endif /* LNBSHARE */ ++ + // If this DVB card can't receive this channel, let's see if we can + // use the card that actually can receive it and transfer data from there: + + if (NeedsTransferMode) { + if (Device && CanReplay()) { ++#ifdef USE_LNBSHARE ++ if (Device->GetLNBnr() == LNBnr) { ++ if (LNBstate != requiredState || (Setup.DiSEqC && LNBsource != (int*) Diseqcs.Get(Channel->Source(), Channel->Frequency(), Channel->Polarization())) ) { ++ if (IsPrimaryDevice()) ++ SetChannelDevice(Channel, true); ++ else ++ SetChannelDevice(Channel, false); ++ LNBstate = requiredState; ++ LNBsource = (int*) Diseqcs.Get(Channel->Source(), Channel->Frequency(), Channel->Polarization()); ++ } ++ } ++#endif /* LNBSHARE */ + cStatus::MsgChannelSwitch(this, 0); // only report status if we are actually going to switch the channel + if (Device->SetChannel(Channel, false) == scrOk) // calling SetChannel() directly, not SwitchChannel()! + cControl::Launch(new cTransferControl(Device, Channel->GetChannelID(), Channel->Vpid(), Channel->Apids(), Channel->Dpids(), Channel->Spids())); +@@ -683,6 +911,10 @@ + sectionHandler->SetStatus(false); + sectionHandler->SetChannel(NULL); + } ++#ifdef USE_LNBSHARE ++ LNBstate = requiredState; ++ LNBsource = (int*) Diseqcs.Get(Channel->Source(), Channel->Frequency(), Channel->Polarization()); ++#endif /* LNBSHARE */ + // Tell the camSlot about the channel switch and add all PIDs of this + // channel to it, for possible later decryption: + if (camSlot) +@@ -975,7 +1207,12 @@ + int LanguagePreference = INT_MAX; // higher than the maximum possible value + for (int i = ttSubtitleFirst; i <= ttSubtitleLast; i++) { + const tTrackId *TrackId = GetTrack(eTrackType(i)); ++#ifdef USE_LIEMIEXT ++ if (TrackId && TrackId->id && (I18nIsPreferredLanguage(Setup.SubtitleLanguages, TrackId->language, LanguagePreference) || ++ ((i == ttSubtitleFirst + 8) && !(*TrackId->language) && (LanguagePreference == INT_MAX)))) ++#else + if (TrackId && TrackId->id && I18nIsPreferredLanguage(Setup.SubtitleLanguages, TrackId->language, LanguagePreference)) ++#endif /* LIEMIEXT */ + PreferredTrack = eTrackType(i); + } + // Make sure we're set to an available subtitle track: +@@ -1182,6 +1419,15 @@ + } + break; + case 0xBD: { // private stream 1 ++#ifdef USE_TTXTSUBS ++ // EBU Teletext data, ETSI EN 300 472 ++ // if PES data header length = 24 and data_identifier = 0x10..0x1F (EBU Data) ++ if (Data[8] == 0x24 && Data[45] >= 0x10 && Data[45] < 0x20) { ++ cVDRTtxtsubsHookListener::Hook()->PlayerTeletextData((uint8_t*)Data, Length); ++ break; ++ } ++#endif /* TTXTSUBS */ ++ + int PayloadOffset = Data[8] + 9; + + // Compatibility mode for old subtitles plugin: +@@ -1209,7 +1455,11 @@ + w = PlaySubtitle(Start, d); + break; + case 0x80: // AC3 & DTS ++#ifdef USE_DOLBYINREC ++ if (Setup.UseDolbyInRecordings) { ++#else + if (Setup.UseDolbyDigital) { ++#endif /* DOLBYINREC */ + SetAvailableTrack(ttDolby, SubStreamIndex, SubStreamId); + if ((!VideoOnly || HasIBPTrickSpeed()) && SubStreamId == availableTracks[currentAudioTrack].id) { + w = PlayAudio(Start, d, SubStreamId); +@@ -1341,6 +1591,9 @@ + tsToPesVideo.Reset(); + tsToPesAudio.Reset(); + tsToPesSubtitle.Reset(); ++#ifdef USE_TTXTSUBS ++ tsToPesTeletext.Reset(); ++#endif /* TTXTSUBS */ + } + else if (Length < TS_SIZE) { + esyslog("ERROR: skipped %d bytes of TS fragment", Length); +@@ -1386,6 +1639,19 @@ + if (!VideoOnly || HasIBPTrickSpeed()) + PlayTsSubtitle(Data, TS_SIZE); + } ++#ifdef USE_TTXTSUBS ++ else if (Pid == patPmtParser.Tpid()) { ++ if (!VideoOnly || HasIBPTrickSpeed()) { ++ int l; ++ tsToPesTeletext.PutTs(Data, Length); ++ if (const uchar *p = tsToPesTeletext.GetPes(l)) { ++ if ((l > 45) && (p[0] == 0x00) && (p[1] == 0x00) && (p[2] == 0x01) && (p[3] == 0xbd) && (p[8] == 0x24) && (p[45] >= 0x10) && (p[45] < 0x20)) ++ cVDRTtxtsubsHookListener::Hook()->PlayerTeletextData((uchar *)p, l, false); ++ tsToPesTeletext.Reset(); ++ } ++ } ++ } ++#endif /* TTXTSUBS */ + } + } + Played += TS_SIZE; +diff -NaurwB vdr-1.7.10/device.h vdr-1.7.10-patched/device.h +--- vdr-1.7.10/device.h 2009-11-22 14:21:00.000000000 +0100 ++++ vdr-1.7.10-patched/device.h 2009-12-18 06:25:25.000000000 +0100 +@@ -24,13 +24,22 @@ + #include "spu.h" + #include "thread.h" + #include "tools.h" ++#ifdef USE_ROTOR ++#include ++#endif /* ROTOR */ + ++#ifndef USE_SOURCECAPS + #define MAXDEVICES 16 // the maximum number of devices in the system ++#endif /* SOURCECAPS */ + #define MAXPIDHANDLES 64 // the maximum number of different PIDs per device + #define MAXRECEIVERS 16 // the maximum number of receivers per device + #define MAXVOLUME 255 + #define VOLUMEDELTA 5 // used to increase/decrease the volume + ++#ifdef USE_CHANNELSCAN ++extern bool scanning_on_receiving_device; ++#endif /* CHANNELSCAN */ ++ + enum eSetChannelResult { scrOk, scrNotAvailable, scrNoTransfer, scrFailed }; + + enum ePlayMode { pmNone, // audio/video from decoder +@@ -142,6 +151,35 @@ + ///< this device/CAM combination will be skipped in the next call to + ///< GetDevice(). + ///< See also ProvidesChannel(). ++#ifdef USE_SOURCECAPS ++ static void SetSourceCaps(int Index = -1); ++ ///< Sets the SourceCaps of the given device according to the Setup data. ++#endif /* SOURCECAPS */ ++#ifdef USE_LNBSHARE ++private: ++ char LNBstate; // Current frequency band and polarization of the DVB-tuner ++// cDiseqc *LNBsource; // can not #include "diseqc.h". A workaround follows: ++ int *LNBsource; // [DiSEqC] DiSEqC-Source ++ int LNBnr; // Number of LNB used ++public: ++ char GetLNBconf(void) { return LNBstate; } ++ int *GetLNBsource(void) { return LNBsource; } ++ int GetLNBnr(void) { return LNBnr; } ++ static void SetLNBnr(void); ++ cDevice *GetBadDevice(const cChannel *Channel); ++ ///< Returns NULL if there is no device which uses the same LNB or if ++ ///< all of those devices are tuned to the same frequency band and ++ ///< polarization as of the requested channel. ++ ///< Otherwise returns the first device found. ++ int GetMaxBadPriority(const cChannel *Channel); ++ ///< Returns the highest priority of all receiving devices which use ++ ///< the same LNB and are tuned to a different frequency band or ++ ///< polarization as of the requested channel. ++ ///< Returns -1 if there are no such devices, but the primary device ++ ///< would be affected by switching to the requested channel. ++ ///< Returns -2 if there are no such devices and the primary device ++ ///< would not be affected by switching to the requested channel. ++#endif /* LNBSHARE */ + static void SetAvoidDevice(cDevice *Device) { avoidDevice = Device; } + ///< Sets the given Device to be temporarily avoided in the next call to + ///< GetDevice(const cChannel, int, bool). +@@ -152,6 +190,9 @@ + static int nextCardIndex; + int cardIndex; + protected: ++#ifdef USE_SOURCECAPS ++ int sourceCaps[MAXSOURCECAPS]; ++#endif /* SOURCECAPS */ + cDevice(void); + virtual ~cDevice(); + virtual bool Ready(void); +@@ -239,17 +280,30 @@ + bool SwitchChannel(const cChannel *Channel, bool LiveView); + ///< Switches the device to the given Channel, initiating transfer mode + ///< if necessary. ++ ++#ifdef USE_LNBSHARE ++ bool SwitchChannelForced(const cChannel *Channel, bool LiveView); ++ ///< Switches the device to the given channel, initiating transfer mode ++ ///< if necessary. Forces the switch without taking care of the LNB configuration. ++#endif /* LNBSHARE */ ++ + static bool SwitchChannel(int Direction); + ///< Switches the primary device to the next available channel in the given + ///< Direction (only the sign of Direction is evaluated, positive values + ///< switch to higher channel numbers). + private: ++#ifndef USE_YAEPG + eSetChannelResult SetChannel(const cChannel *Channel, bool LiveView); + ///< Sets the device to the given channel (general setup). ++#endif /* YAEPG */ + protected: + virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView); + ///< Sets the device to the given channel (actual physical setup). + public: ++#ifdef USE_YAEPG ++ eSetChannelResult SetChannel(const cChannel *Channel, bool LiveView); ++ ///< Sets the device to the given channel (general setup). ++#endif /* YAEPG */ + static int CurrentChannel(void) { return primaryDevice ? currentChannel : 0; } + ///< Returns the number of the current channel on the primary device. + static void SetCurrentChannel(const cChannel *Channel) { currentChannel = Channel ? Channel->Number() : 0; } +@@ -267,6 +321,9 @@ + virtual bool HasProgramme(void); + ///< Returns true if the device is currently showing any programme to + ///< the user, either through replaying or live. ++#ifdef USE_ROTOR ++ virtual bool SendDiseqcCmd(dvb_diseqc_master_cmd cmd) { return false; } ++#endif /* ROTOR */ + + // PID handle facilities + +@@ -496,6 +553,9 @@ + cTsToPes tsToPesVideo; + cTsToPes tsToPesAudio; + cTsToPes tsToPesSubtitle; ++#ifdef USE_TTXTSUBS ++ cTsToPes tsToPesTeletext; ++#endif /* TTXTSUBS */ + bool isPlayingVideo; + protected: + virtual bool CanReplay(void) const; +diff -NaurwB vdr-1.7.10/dvbdevice.c vdr-1.7.10-patched/dvbdevice.c +--- vdr-1.7.10/dvbdevice.c 2009-06-06 13:17:20.000000000 +0200 ++++ vdr-1.7.10-patched/dvbdevice.c 2009-12-18 06:25:25.000000000 +0100 +@@ -71,6 +71,9 @@ + class cDvbTuner : public cThread { + private: + enum eTunerStatus { tsIdle, tsSet, tsTuned, tsLocked }; ++#ifdef USE_ROTOR ++ bool SendDiseqc; ++#endif /* ROTOR */ + int fd_frontend; + int cardIndex; + int tuneTimeout; +@@ -83,6 +86,9 @@ + cMutex mutex; + cCondVar locked; + cCondVar newSet; ++#ifdef USE_ROTOR ++ dvb_diseqc_master_cmd diseqc_cmd; ++#endif /* ROTOR */ + bool GetFrontendStatus(fe_status_t &Status, int TimeoutMs = 0); + bool SetFrontend(void); + virtual void Action(void); +@@ -91,12 +97,18 @@ + virtual ~cDvbTuner(); + bool IsTunedTo(const cChannel *Channel) const; + void Set(const cChannel *Channel, bool Tune); ++#ifdef USE_ROTOR ++ bool SendDiseqcCmd(dvb_diseqc_master_cmd cmd); ++#endif /* ROTOR */ + bool Locked(int TimeoutMs = 0); + }; + + cDvbTuner::cDvbTuner(int Fd_Frontend, int CardIndex, fe_delivery_system FrontendType) + { + fd_frontend = Fd_Frontend; ++#ifdef USE_ROTOR ++ SendDiseqc = false; ++#endif /* ROTOR */ + cardIndex = CardIndex; + frontendType = FrontendType; + tuneTimeout = 0; +@@ -163,6 +175,19 @@ + return tunerStatus >= tsLocked; + } + ++#ifdef USE_ROTOR ++bool cDvbTuner::SendDiseqcCmd(dvb_diseqc_master_cmd cmd) ++{ ++ cMutexLock MutexLock(&mutex); ++ if ((!SYS_DVBS & !SYS_DVBS2) || SendDiseqc) ++ return false; ++ diseqc_cmd = cmd; ++ SendDiseqc = true; ++ newSet.Broadcast(); ++ return true; ++} ++#endif /* ROTOR */ ++ + bool cDvbTuner::GetFrontendStatus(fe_status_t &Status, int TimeoutMs) + { + if (TimeoutMs) { +@@ -241,6 +266,9 @@ + } + } + diseqcCommands = diseqc->Commands(); ++#ifdef USE_DVBSETUP ++ isyslog("Sent DISEQC command: %s", diseqcCommands); ++#endif /* DVBSETUP */ + } + frequency -= diseqc->Lof(); + } +@@ -303,6 +331,18 @@ + tuneTimeout = DVBC_TUNE_TIMEOUT; + lockTimeout = DVBC_LOCK_TIMEOUT; + } ++#ifdef USE_ATSC ++ else if (frontendType == SYS_ATSC) { ++ // ATSC ++ SETCMD(DTV_DELIVERY_SYSTEM, frontendType); ++ SETCMD(DTV_FREQUENCY, FrequencyToHz(channel.Frequency())); ++ SETCMD(DTV_INVERSION, channel.Inversion()); ++ SETCMD(DTV_MODULATION, channel.Modulation()); ++ ++ tuneTimeout = DVBT_TUNE_TIMEOUT; ++ lockTimeout = DVBT_LOCK_TIMEOUT; ++ } ++#endif /* ATSC */ + else if (frontendType == SYS_DVBT) { + // DVB-T + SETCMD(DTV_DELIVERY_SYSTEM, frontendType); +@@ -341,6 +381,12 @@ + if (GetFrontendStatus(NewStatus, 10)) + Status = NewStatus; + cMutexLock MutexLock(&mutex); ++#ifdef USE_ROTOR ++ if (SendDiseqc) { ++ CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_MASTER_CMD, &diseqc_cmd)); ++ SendDiseqc = false; ++ } ++#endif /* ROTOR */ + switch (tunerStatus) { + case tsIdle: + break; +@@ -483,6 +529,11 @@ + + if (fd_frontend >= 0) { + if (ioctl(fd_frontend, FE_GET_INFO, &frontendInfo) >= 0) { ++#ifdef USE_DVBSETUP ++ if (Setup.ChannelBlockerMode == 4) ++ frontendType = (n == Setup.PrimaryDVB - 1) ? SYS_UNDEFINED : frontendType; ++ else ++#endif /* DVBSETUP */ + switch (frontendInfo.type) { + case FE_QPSK: frontendType = (frontendInfo.caps & FE_CAN_2G_MODULATION) ? SYS_DVBS2 : SYS_DVBS; break; + case FE_OFDM: frontendType = SYS_DVBT; break; +@@ -796,6 +847,13 @@ + + bool cDvbDevice::SetAudioBypass(bool On) + { ++#ifdef USE_DVBSETUP ++ if (Setup.DolbyTransferFix && On) { ++ cChannel *c=Channels.GetByNumber(cDevice::CurrentChannel()); ++ if (c->Ca(0) != 0) ++ return false; ++ } ++#endif /* DVBSETUP */ + if (setTransferModeForDolbyDigital != 1) + return false; + return ioctl(fd_audio, AUDIO_SET_BYPASS_MODE, On) == 0; +@@ -901,14 +959,43 @@ + bool cDvbDevice::ProvidesSource(int Source) const + { + int type = Source & cSource::st_Mask; ++#ifdef USE_SOURCECAPS ++ if (Setup.SourceCapsSet && type == cSource::stSat && (frontendType == SYS_DVBS || frontendType == SYS_DVBS2)) { ++ for (int i = 0; i < MAXSOURCECAPS; i++) ++ if (sourceCaps[i] == Source) ++ return true; ++ return false; ++ } ++ else ++#endif /* SOURCECAPS */ + return type == cSource::stNone + || type == cSource::stCable && (frontendType == SYS_DVBC_ANNEX_AC || frontendType == SYS_DVBC_ANNEX_B) + || type == cSource::stSat && (frontendType == SYS_DVBS || frontendType == SYS_DVBS2) ++#ifdef USE_ATSC ++ || type == cSource::stTerr && (frontendType == SYS_DVBT || frontendType == SYS_ATSC); ++#else + || type == cSource::stTerr && (frontendType == SYS_DVBT); ++#endif /* ATSC */ + } + + bool cDvbDevice::ProvidesTransponder(const cChannel *Channel) const + { ++#ifdef USE_DVBSETUP ++ if (Setup.ChannelBlocker != 0) { ++ if ((Setup.ChannelBlockerMode == 0) || ++ (Setup.ChannelBlockerMode == 1 && HasDecoder()) || ++ (Setup.ChannelBlockerMode == 2 && IsPrimaryDevice()) || ++ (Setup.ChannelBlockerMode == 3 && IsPrimaryDevice() && HasDecoder())) { ++ if ((Setup.ChannelBlocker == 1 && cSource::IsCable(Channel->Source()) && Channel->Modulation() == QAM_256) || ++ (Setup.ChannelBlocker == 2 && cSource::IsCable(Channel->Source())) || ++ (Setup.ChannelBlocker == 3 && cSource::IsSat(Channel->Source())) || ++ (Setup.ChannelBlocker == 4 && strstr(::Setup.ChannelBlockerList, Channel->GetChannelID().ToString()) != NULL) || // blacklist ++ (Setup.ChannelBlocker == 5 && strstr(::Setup.ChannelBlockerList, Channel->GetChannelID().ToString()) == NULL) || // whitelist ++ (Setup.ChannelBlocker == 6)) ++ return false; ++ } ++ } ++#endif /* DVBSETUP */ + if (!ProvidesSource(Channel->Source())) + return false; // doesn't provide source + if (!cSource::IsSat(Channel->Source())) +@@ -920,10 +1007,31 @@ + + bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const + { ++#ifdef USE_DVBSETUP ++ if (Setup.ChannelBlocker != 0) { ++ if ((Setup.ChannelBlockerMode == 0) || ++ (Setup.ChannelBlockerMode == 1 && HasDecoder()) || ++ (Setup.ChannelBlockerMode == 2 && IsPrimaryDevice()) || ++ (Setup.ChannelBlockerMode == 3 && IsPrimaryDevice() && HasDecoder())) { ++ if ((Setup.ChannelBlocker == 1 && cSource::IsCable(Channel->Source()) && Channel->Modulation() == QAM_256) || ++ (Setup.ChannelBlocker == 2 && cSource::IsCable(Channel->Source())) || ++ (Setup.ChannelBlocker == 3 && cSource::IsSat(Channel->Source())) || ++ (Setup.ChannelBlocker == 4 && strstr(::Setup.ChannelBlockerList, Channel->GetChannelID().ToString()) != NULL) || // blacklist ++ (Setup.ChannelBlocker == 5 && strstr(::Setup.ChannelBlockerList, Channel->GetChannelID().ToString()) == NULL) || // whitelist ++ (Setup.ChannelBlocker == 6)) ++ return false; ++ } ++ } ++#endif /* DVBSETUP */ + bool result = false; + bool hasPriority = Priority < 0 || Priority > this->Priority(); + bool needsDetachReceivers = false; + ++#ifdef USE_ANALOGTV ++ if ((Channel->Ca(0) == 0xA0) || (Channel->Ca(0) == 0xA1) || (Channel->Ca(0) == 0xA2)) ++ return false; ++#endif /* ANALOGTV */ ++ + if (ProvidesTransponder(Channel)) { + result = hasPriority; + if (Priority >= 0 && Receiving(true)) { +@@ -1040,6 +1148,13 @@ + return dvbTuner ? dvbTuner->Locked(TimeoutMs) : false; + } + ++#ifdef USE_ROTOR ++bool cDvbDevice::SendDiseqcCmd(dvb_diseqc_master_cmd cmd) ++{ ++ return dvbTuner->SendDiseqcCmd(cmd); ++} ++#endif /* ROTOR */ ++ + int cDvbDevice::GetAudioChannelDevice(void) + { + if (HasDecoder()) { +diff -NaurwB vdr-1.7.10/dvbdevice.h vdr-1.7.10-patched/dvbdevice.h +--- vdr-1.7.10/dvbdevice.h 2009-10-25 14:58:20.000000000 +0100 ++++ vdr-1.7.10-patched/dvbdevice.h 2009-12-18 06:25:25.000000000 +0100 +@@ -75,6 +75,9 @@ + virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView); + public: + virtual bool HasLock(int TimeoutMs = 0); ++#ifdef USE_ROTOR ++ virtual bool SendDiseqcCmd(dvb_diseqc_master_cmd cmd); ++#endif /* ROTOR */ + + // PID handle facilities + +diff -NaurwB vdr-1.7.10/dvbosd.c vdr-1.7.10-patched/dvbosd.c +--- vdr-1.7.10/dvbosd.c 2007-09-16 10:55:54.000000000 +0200 ++++ vdr-1.7.10-patched/dvbosd.c 2009-12-18 06:25:25.000000000 +0100 +@@ -20,12 +20,26 @@ + #define MAXNUMWINDOWS 7 // OSD windows are counted 1...7 + #define MAXOSDMEMORY 92000 // number of bytes available to the OSD (for unmodified DVB cards) + ++#ifdef USE_SOFTOSD ++ #define SOFTOSD_MAXSIZE (720*576) ++ #define SOFTOSD_MINSIZE (720*64) ++#endif ++ + class cDvbOsd : public cOsd { + private: + int osdDev; + int osdMem; + bool shown; + void Cmd(OSD_Command cmd, int color = 0, int x0 = 0, int y0 = 0, int x1 = 0, int y1 = 0, const void *data = NULL); ++#ifdef USE_SOFTOSD ++ int GetOsdSize() { ++ int OsdSize = 0; ++ cBitmap *Bitmap; ++ for (int i = 0; (Bitmap = GetBitmap(i)) != NULL; i++) ++ OsdSize += Bitmap->Width() * Bitmap->Height(); ++ return OsdSize; ++ } ++#endif + protected: + virtual void SetActive(bool On); + public: +@@ -76,6 +90,44 @@ + } + else if (shown) { + cBitmap *Bitmap; ++#ifdef USE_SOFTOSD ++ if (Setup.UseSoftOsd) { ++ if (SOFTOSD_MAXSIZE > GetOsdSize() && SOFTOSD_MINSIZE < GetOsdSize()) { ++ for (int fade = Setup.SoftOsdFadeoutSteps - 1; fade > 0; fade--) { ++ int64_t flush_start = cTimeMs::Now(); ++ for (int i = 0; (Bitmap = GetBitmap(i)) != NULL; i++) { ++ Cmd(OSD_SetWindow, 0, i + 1); ++ int NumColors; ++ const tColor *Colors = Bitmap->Colors(NumColors); ++ if (Colors) { ++ tColor colors[NumColors]; ++ for (int i = 0; i < NumColors; i++) { ++ // convert AARRGGBB to AABBGGRR (the driver expects the colors the wrong way): ++ int alpha = (Colors[i] >> 24) & 0x000000FF; ++ alpha = (alpha * fade) / Setup.SoftOsdFadeoutSteps; ++ colors[i] = (alpha << 24) | ((Colors[i] & 0x0000FF) << 16) | (Colors[i] & 0x00FF00) | ((Colors[i] & 0xFF0000) >> 16); ++ } ++ Colors = colors; ++ Cmd(OSD_SetPalette, 0, NumColors - 1, 0, 0, 0, Colors); ++ if (!Setup.SoftOsdPaletteOnly) { ++ //Cmd(OSD_SetBlock, Bitmap->Width(), 0, 0, Bitmap->Width() - 1, Bitmap->Height() - 1, Bitmap->Data(0, 0)); ++ Cmd(OSD_SetBlock, Bitmap->Width(), 0, 0, 0, 0, Bitmap->Data(0, 0)); ++ } ++ } ++ } ++ int flush_time = cTimeMs::Now() - flush_start; ++ dsyslog("SOFTOSD: FadeOut Step %d from %d FlushTime = %d ms", ++ Setup.SoftOsdFadeoutSteps - fade, Setup.SoftOsdFadeoutSteps - 1, flush_time); ++ int wait_time = 1000 / Setup.SoftOsdRate; ++ while (flush_time > wait_time && fade > 2) { ++ fade--; ++ flush_time -= wait_time; ++ } ++ cCondWait::SleepMs(wait_time-flush_time); ++ } ++ } ++ } ++#endif /* SOFTOSD */ + for (int i = 0; (Bitmap = GetBitmap(i)) != NULL; i++) { + Cmd(OSD_SetWindow, 0, i + 1); + Cmd(OSD_Close); +@@ -83,6 +135,12 @@ + shown = false; + } + } ++#ifdef USE_YAEPG ++ if (vidWin.bpp != 0) { ++ Cmd(OSD_SetWindow, 0, 5); ++ Cmd(OSD_Close); ++ } ++#endif /* YAEPG */ + } + + eOsdError cDvbOsd::CanHandleAreas(const tArea *Areas, int NumAreas) +@@ -182,6 +240,12 @@ + for (int i = 0; i < NumColors; i++) { + // convert AARRGGBB to AABBGGRR (the driver expects the colors the wrong way): + colors[i] = (Colors[i] & 0xFF000000) | ((Colors[i] & 0x0000FF) << 16) | (Colors[i] & 0x00FF00) | ((Colors[i] & 0xFF0000) >> 16); ++#ifdef USE_SOFTOSD ++ if (Setup.UseSoftOsd && !shown) { ++ if (SOFTOSD_MAXSIZE > GetOsdSize() && SOFTOSD_MINSIZE < GetOsdSize()) ++ colors[i] = 0; ++ } ++#endif /* SOFTOSD */ + } + Colors = colors; + //TODO end of stuff that should be fixed in the driver +@@ -198,6 +262,51 @@ + Cmd(OSD_SetWindow, 0, i + 1); + Cmd(OSD_MoveWindow, 0, Left() + Bitmap->X0(), Top() + Bitmap->Y0()); + } ++#ifdef USE_YAEPG ++ if (vidWin.bpp != 0) { ++ Cmd(OSD_SetWindow, 0, 5); ++ Cmd(OSD_OpenRaw, vidWin.bpp, vidWin.x1, vidWin.y1, vidWin.x2, vidWin.y2, (void *)0); ++ } ++#endif /* YAEPG */ ++#ifdef USE_SOFTOSD ++ if (Setup.UseSoftOsd) { ++ if (SOFTOSD_MAXSIZE > GetOsdSize() && SOFTOSD_MINSIZE < GetOsdSize()) { ++ for (int fade = 1; fade <= Setup.SoftOsdFadeinSteps; fade++) { ++ int64_t flush_start = cTimeMs::Now(); ++ for (int i = 0; (Bitmap = GetBitmap(i)) != NULL; i++) { ++ Cmd(OSD_SetWindow, 0, i + 1); ++ int NumColors; ++ const tColor *Colors = Bitmap->Colors(NumColors); ++ if (Colors) { ++ tColor colors[NumColors]; ++ for (int i = 0; i < NumColors; i++) { ++ // convert AARRGGBB to AABBGGRR: ++ int alpha = (Colors[i]>>24) & 0x000000FF; ++ alpha = (alpha * fade) / Setup.SoftOsdFadeinSteps; ++ colors[i] = (alpha << 24) | ((Colors[i] & 0x0000FF) << 16) | (Colors[i] & 0x00FF00) | ((Colors[i] & 0xFF0000) >> 16); ++ } ++ Colors = colors; ++ Cmd(OSD_SetPalette, 0, NumColors - 1, 0, 0, 0, Colors); ++ if (!Setup.SoftOsdPaletteOnly) { ++ //Cmd(OSD_SetBlock, Bitmap->Width(), 0, 0, Bitmap->Width() - 1, Bitmap->Height() - 1, Bitmap->Data(0, 0)); ++ Cmd(OSD_SetBlock, Bitmap->Width(), 0, 0, 0, 0, Bitmap->Data(0, 0)); ++ } ++ } ++ } ++ int flush_time = cTimeMs::Now() - flush_start; ++ dsyslog("SOFTOSD: FadeIn Step %d from %d FlushTime = %d ms", fade, Setup.SoftOsdFadeinSteps, flush_time); ++ int wait_time = 1000 / Setup.SoftOsdRate; ++ while (flush_time > wait_time && fade < Setup.SoftOsdFadeinSteps - 1) { ++ fade++; ++ flush_time -= wait_time; ++ } ++ if (fade == Setup.SoftOsdFadeinSteps) /* last step, don't wait */ ++ break; ++ cCondWait::SleepMs(wait_time - flush_time); ++ } ++ } ++ } ++#endif /* SOFTOSD */ + shown = true; + } + } +diff -NaurwB vdr-1.7.10/dvbplayer.c vdr-1.7.10-patched/dvbplayer.c +--- vdr-1.7.10/dvbplayer.c 2009-05-31 16:12:42.000000000 +0200 ++++ vdr-1.7.10-patched/dvbplayer.c 2009-12-18 06:25:25.000000000 +0100 +@@ -204,6 +204,9 @@ + cNonBlockingFileReader *nonBlockingFileReader; + cRingBufferFrame *ringBuffer; + cPtsIndex ptsIndex; ++#ifdef USE_JUMPPLAY ++ cMarksReload marks; ++#endif /* JUMPPLAY */ + cFileName *fileName; + cIndexFile *index; + cUnbufferedFile *replayFile; +@@ -249,7 +252,11 @@ + int cDvbPlayer::Speeds[] = { 0, -2, -4, -8, 1, 2, 4, 12, 0 }; + + cDvbPlayer::cDvbPlayer(const char *FileName) ++#ifdef USE_JUMPPLAY ++:cThread("dvbplayer"), marks(FileName) ++#else + :cThread("dvbplayer") ++#endif /* JUMPPLAY */ + { + nonBlockingFileReader = NULL; + ringBuffer = NULL; +@@ -357,6 +364,11 @@ + if (index) { + int Index = ptsIndex.FindIndex(DeviceGetSTC()); + if (Index >= 0) { ++#ifdef USE_JUMPPLAY ++ // set resume position to 0 if replay stops at the first mark ++ if (Setup.PlayJump && marks.First() && abs(Index - marks.First()->position) <= int(round(RESUMEBACKUP * framesPerSecond))) ++ Index = 0; ++#endif /* JUMPPLAY */ + Index -= int(round(RESUMEBACKUP * framesPerSecond)); + if (Index > 0) + Index = index->GetNextIFrame(Index, false); +@@ -383,11 +395,29 @@ + { + uchar *p = NULL; + int pc = 0; ++#ifdef USE_JUMPPLAY ++ bool cutIn = false; ++ int total = -1; ++#endif /* JUMPPLAY */ + + readIndex = Resume(); + if (readIndex >= 0) + isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond)); + ++#ifdef USE_JUMPPLAY ++ if (Setup.PlayJump && readIndex <= 0 && marks.First() && index) { ++ int Index = marks.First()->position; ++ uint16_t FileNumber; ++ off_t FileOffset; ++ if (index->Get(Index, &FileNumber, &FileOffset) && NextFile(FileNumber, FileOffset)) { ++ isyslog("PlayJump: start replay at first mark %d (%s)", Index, *IndexToHMSF(Index, true, framesPerSecond)); ++ readIndex = Index; ++ } ++ } ++ ++ bool LastMarkPause = false; ++#endif /* JUMPPLAY */ ++ + nonBlockingFileReader = new cNonBlockingFileReader; + int Length = 0; + bool Sleep = false; +@@ -410,7 +440,11 @@ + + // Read the next frame from the file: + ++#ifdef USE_JUMPPLAY ++ if (playMode != pmStill && playMode != pmPause && !LastMarkPause) { ++#else + if (playMode != pmStill && playMode != pmPause) { ++#endif /* JUMPPLAY */ + if (!readFrame && (replayFile || readIndex >= 0)) { + if (!nonBlockingFileReader->Reading()) { + if (!SwitchToPlayFrame && (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward))) { +@@ -451,6 +485,42 @@ + readIndex++; + else + eof = true; ++#ifdef USE_JUMPPLAY ++ if (Setup.PlayJump || Setup.PauseLastMark) { ++ // check for end mark - jump to next mark or pause ++ readIndex++; ++ marks.Reload(); ++ cMark *m = marks.Get(readIndex); ++ if (m && (m->Index() & 0x01) != 0) { ++ m = marks.Next(m); ++ int Index; ++ if (m) ++ Index = m->position; ++ else if (Setup.PauseLastMark) { ++ // pause at last mark ++ isyslog("PauseLastMark: pause at position %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond)); ++ LastMarkPause = true; ++ Index = -1; ++ } ++ else if (total == index->Last()) ++ // at last mark jump to end of recording ++ Index = index->Last() - 1; ++ else ++ // jump but stay off end of live-recordings ++ Index = index->GetNextIFrame(index->Last() - int(round(MAXSTUCKATEOF * framesPerSecond)), true); ++ // don't jump in edited recordings ++ if (Setup.PlayJump && Index > readIndex && Index > index->GetNextIFrame(readIndex, true)) { ++ isyslog("PlayJump: %d frames to %d (%s)", Index - readIndex, Index, *IndexToHMSF(Index, true, framesPerSecond)); ++ readIndex = Index; ++ cutIn = true; ++ } ++ } ++ readIndex--; ++ } ++ // for detecting growing length of live-recordings ++ if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent) && readIndependent) ++ total = index->Last(); ++#endif /* JUMPPLAY */ + } + else // allows replay even if the index file is missing + Length = MAXFRAMESIZE; +@@ -491,6 +561,15 @@ + // Store the frame in the buffer: + + if (readFrame) { ++#ifdef USE_JUMPPLAY ++ if (cutIn) { ++ if (isPesRecording) ++ cRemux::SetBrokenLink(readFrame->Data(), readFrame->Count()); ++ else ++ TsSetTeiOnBrokenPackets(readFrame->Data(), readFrame->Count()); ++ cutIn = false; ++ } ++#endif /* JUMPPLAY */ + if (ringBuffer->Put(readFrame)) + readFrame = NULL; + else +@@ -550,8 +629,18 @@ + p = NULL; + } + } ++#ifdef USE_JUMPPLAY ++ else { ++ if (LastMarkPause) { ++ LastMarkPause = false; ++ playMode = pmPause; ++ } ++ Sleep = true; ++ } ++#else + else + Sleep = true; ++#endif /* JUMPPLAY */ + + // Handle hitting begin/end of recording: + +diff -NaurwB vdr-1.7.10/eit.c vdr-1.7.10-patched/eit.c +--- vdr-1.7.10/eit.c 2009-06-21 15:46:20.000000000 +0200 ++++ vdr-1.7.10-patched/eit.c 2009-12-18 06:25:25.000000000 +0100 +@@ -19,13 +19,40 @@ + + #define VALID_TIME (31536000 * 2) // two years + ++#ifdef USE_SETTIME ++extern char *SetTime; ++#endif /* SETTIME */ ++ + // --- cEIT ------------------------------------------------------------------ + + class cEIT : public SI::EIT { + public: + cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus = false); ++#ifdef USE_NOEPG ++private: ++ bool allowedEPG(tChannelID kanalID); ++#endif /* NOEPG */ + }; + ++#ifdef USE_NOEPG ++bool cEIT::allowedEPG(tChannelID kanalID) { ++ bool rc; ++ ++ if (Setup.noEPGMode == 1) { ++ rc = false; ++ if (strstr(::Setup.noEPGList, kanalID.ToString()) != NULL) ++ rc = true; ++ } ++ else { ++ rc = true; ++ if (strstr(::Setup.noEPGList, kanalID.ToString()) != NULL) ++ rc = false; ++ } ++ ++ return rc; ++} ++#endif /* NOEPG */ ++ + cEIT::cEIT(cSchedules *Schedules, int Source, u_char Tid, const u_char *Data, bool OnlyRunningStatus) + :SI::EIT(Data, false) + { +@@ -37,6 +64,14 @@ + if (!channel) + return; // only collect data for known channels + ++#ifdef USE_NOEPG ++ // only use epg from channels not blocked by noEPG-patch ++ tChannelID kanalID; ++ kanalID = channel->GetChannelID(); ++ if (!allowedEPG(kanalID)) ++ return; ++#endif /* NOEPG */ ++ + cSchedule *pSchedule = (cSchedule *)Schedules->GetSchedule(channel, true); + + bool Empty = true; +@@ -82,8 +117,74 @@ + // not be overwritten. + uchar TableID = pEvent->TableID(); + if (TableID == 0x00) { ++#ifdef USE_DDEPGENTRY ++ if (pEvent->Version() == getVersionNumber()) { ++ if (Setup.MixEpgAction == 0) ++ continue; ++ //printf("in"); ++ //printf("%s", pEvent->GetTimeString()); ++ // to use the info of the original epg, update the extern one, ++ // if it has less info ++ SI::Descriptor *d; ++ SI::ExtendedEventDescriptors *ExtendedEventDescriptors = NULL; ++ //SI::ExtendedEventDescriptor *eed = NULL; ++ SI::ShortEventDescriptor *ShortEventDescriptor = NULL; ++ //SI::ShortEventDescriptor *sed = NULL; ++ //SI::TimeShiftedEventDescriptor *tsed = NULL; ++ //cLinkChannels *LinkChannels = NULL; ++ for (SI::Loop::Iterator it2; (d = SiEitEvent.eventDescriptors.getNext(it2));) { ++ if (d->getDescriptorTag() == SI::ShortEventDescriptorTag) { ++ int LanguagePreferenceShort = -1; ++ SI::ShortEventDescriptor *sed = (SI::ShortEventDescriptor *)d; ++ if (I18nIsPreferredLanguage(Setup.EPGLanguages, sed->languageCode, LanguagePreferenceShort) || !ShortEventDescriptor) { ++ delete ShortEventDescriptor; ++ ShortEventDescriptor = sed; ++ d = NULL; // so that it is not deleted ++ } ++ } ++ else if (d->getDescriptorTag() == SI::ExtendedEventDescriptorTag) { ++ int LanguagePreferenceExt = -1; ++ bool UseExtendedEventDescriptor = false; ++ SI::ExtendedEventDescriptor *eed = (SI::ExtendedEventDescriptor *)d; ++ if (I18nIsPreferredLanguage(Setup.EPGLanguages, eed->languageCode, LanguagePreferenceExt) || !ExtendedEventDescriptors) { ++ delete ExtendedEventDescriptors; ++ ExtendedEventDescriptors = new SI::ExtendedEventDescriptors; ++ UseExtendedEventDescriptor = true; ++ } ++ if (UseExtendedEventDescriptor) { ++ ExtendedEventDescriptors->Add(eed); ++ d = NULL; // so that it is not deleted ++ } ++ if (eed->getDescriptorNumber() == eed->getLastDescriptorNumber()) ++ UseExtendedEventDescriptor = false; ++ } ++ delete d; ++ } ++ if (pEvent) { ++ if (ShortEventDescriptor) { ++ char buffer[256]; ++ if (ShortEventDescriptor->text.getText(buffer, sizeof(buffer)) && pEvent->ShortText() && (strlen(ShortEventDescriptor->text.getText(buffer, sizeof(buffer))) > strlen(pEvent->ShortText()))) { ++ pEvent->SetShortText(ShortEventDescriptor->text.getText(buffer, sizeof(buffer))); ++ pEvent->FixEpgBugs(); ++ } ++ } ++ if (ExtendedEventDescriptors) { ++ char buffer[ExtendedEventDescriptors->getMaximumTextLength(": ") + 1]; ++ //pEvent->SetDescription(ExtendedEventDescriptors->getText(buffer, sizeof(buffer), ": ")); ++ if (ExtendedEventDescriptors->getText(buffer, sizeof(buffer), ": ") && pEvent->Description() && (strlen(ExtendedEventDescriptors->getText(buffer, sizeof(buffer), ": ")) > strlen(pEvent->Description()))) { ++ pEvent->SetDescription(ExtendedEventDescriptors->getText(buffer, sizeof(buffer), ": ")); ++ pEvent->FixEpgBugs(); ++ } ++ } ++ } ++ delete ExtendedEventDescriptors; ++ delete ShortEventDescriptor; ++ continue; ++ } ++#else + if (pEvent->Version() == getVersionNumber()) + continue; ++#endif /* DDEPGENTRY */ + HasExternalData = ExternalData = true; + } + // If the new event has a higher table ID, let's skip it. +@@ -108,7 +209,11 @@ + if (newEvent) + pSchedule->AddEvent(newEvent); + if (Tid == 0x4E) { // we trust only the present/following info on the actual TS ++#ifdef USE_DDEPGENTRY ++ if (Setup.DisableVPS == 0 && SiEitEvent.getRunningStatus() >= SI::RunningStatusNotRunning) ++#else + if (SiEitEvent.getRunningStatus() >= SI::RunningStatusNotRunning) ++#endif /* DDEPGENTRY */ + pSchedule->SetRunningStatus(pEvent, SiEitEvent.getRunningStatus(), channel); + } + if (OnlyRunningStatus) +@@ -154,8 +259,46 @@ + } + break; + case SI::ContentDescriptorTag: ++#ifdef USE_PARENTALRATING ++ { ++ int NumContents = 0; ++ uchar Contents[MAXEVCONTENTS + 1] = { 0 }; ++ SI::ContentDescriptor *cd = (SI::ContentDescriptor *)d; ++ SI::ContentDescriptor::Nibble Nibble; ++ for (SI::Loop::Iterator it3; cd->nibbleLoop.getNext(Nibble, it3); ) { ++ if (NumContents < MAXEVCONTENTS) { ++ Contents[NumContents] = ((Nibble.getContentNibbleLevel1() & 0xF) << 4) | (Nibble.getContentNibbleLevel2() & 0xF); ++ NumContents++; ++ } ++ } ++ pEvent->SetContents(Contents); ++ } ++#endif /* PARENTALRATING */ + break; + case SI::ParentalRatingDescriptorTag: ++#ifdef USE_PARENTALRATING ++ { ++ int LanguagePreferenceRating = -1; ++ SI::ParentalRatingDescriptor *prd = (SI::ParentalRatingDescriptor *)d; ++ SI::ParentalRatingDescriptor::Rating Rating; ++ for (SI::Loop::Iterator it3; prd->ratingLoop.getNext(Rating, it3); ) { ++ if (I18nIsPreferredLanguage(Setup.EPGLanguages, Rating.languageCode, LanguagePreferenceRating)) { ++ int rate = (Rating.getRating() & 0xFF); ++ switch (rate) { ++ case 0x01 ... 0x0F: // minimum age = rating + 3 years ++ rate += 3; ++ break; ++ case 0: // undefined ++ case 0x10 ... 0xFF: // defined by the broadcaster ++ default: ++ rate = 0; ++ break; ++ } ++ pEvent->SetParentalRating(rate); ++ } ++ } ++ } ++#endif /* PARENTALRATING */ + break; + case SI::PDCDescriptorTag: { + SI::PDCDescriptor *pd = (SI::PDCDescriptor *)d; +@@ -266,6 +409,62 @@ + if (LinkChannels) + channel->SetLinkChannels(LinkChannels); + Modified = true; ++#ifdef USE_DDEPGENTRY ++ //to avoid double epg-entrys from ext and int epg sources :EW ++ if (pEvent && pEvent->TableID() != 0x00) { ++ cEvent *pPreviousEvent = (cEvent *)pSchedule->GetPreviousEvent(pEvent); ++ if (pPreviousEvent) { ++ if (Setup.DoubleEpgAction == 0) { ++ pPreviousEvent->SetStartTime(pEvent->StartTime()); ++ pPreviousEvent->SetDuration(pEvent->Duration()); ++ if (Setup.DisableVPS == 0) { ++ if (channel) ++ pPreviousEvent->SetRunningStatus(pEvent->RunningStatus(), channel); ++ else ++ pPreviousEvent->SetRunningStatus(pEvent->RunningStatus()); ++ } ++ // to use the info of the original epg, update the extern one, ++ // if it has less info ++ char buffer_short_intern[256]; ++ char buffer_short_extern[256]; ++ int len_short_intern = 0; ++ int len_short_extern = 0; ++ if (pEvent->ShortText()) ++ len_short_intern = snprintf (buffer_short_intern, sizeof(buffer_short_intern)-1, "%s", pEvent->ShortText()); ++ if (pPreviousEvent->ShortText()) ++ len_short_extern = snprintf (buffer_short_extern, sizeof(buffer_short_extern)-1, "%s", pPreviousEvent->ShortText()); ++ if (len_short_intern > 0) { ++ if (len_short_extern < 1) ++ pPreviousEvent->SetShortText(buffer_short_intern); ++ else if (len_short_intern > len_short_extern) ++ pPreviousEvent->SetShortText(buffer_short_intern); ++ } ++ if (pEvent->Description()) { ++ char buffer_title_intern[4096]; ++ char buffer_title_extern[4096]; ++ int len_title_intern = 0; ++ int len_title_extern = 0; ++ if (pEvent->Description()) ++ len_title_intern = snprintf (buffer_title_intern, sizeof(buffer_title_intern)-1, "%s", pEvent->Description()); ++ if (pPreviousEvent->Description()) ++ len_title_extern = snprintf (buffer_title_extern, sizeof(buffer_title_extern)-1, "%s", pPreviousEvent->Description()); ++ if (len_title_intern > 0) { ++ if (len_title_extern < 1) ++ pPreviousEvent->SetDescription(buffer_title_intern); ++ else if (len_title_intern > len_title_extern) ++ pPreviousEvent->SetDescription(buffer_title_intern); ++ } ++ } ++ if (pPreviousEvent->Vps() == 0 && pEvent->Vps() != 0) ++ pPreviousEvent->SetVps(pEvent->Vps()); ++ pSchedule->DelEvent(pEvent); ++ pPreviousEvent->FixEpgBugs(); ++ } ++ else ++ pSchedule->DelEvent(pPreviousEvent); ++ } ++ } ++#endif /* DDEPGENTRY */ + } + if (Tid == 0x4E) { + if (Empty && getSectionNumber() == 0) +@@ -304,10 +503,26 @@ + time_t sattim = getTime(); + time_t loctim = time(NULL); + ++#ifdef USE_SETTIME ++ char timestr[20]; ++ struct tm *ptm; ++ struct tm tm_r; ++ ptm = localtime_r(&sattim, &tm_r); ++#endif /* SETTIME */ ++ + int diff = abs(sattim - loctim); + if (diff > 2) { + mutex.Lock(); + if (abs(diff - lastDiff) < 3) { ++#ifdef USE_SETTIME ++ if (SetTime) { ++ strftime(timestr, 20, "%m%d%H%M%Y.%S", ptm); ++ cString cmd = cString::sprintf("%s %s %ld", SetTime, timestr, sattim); ++ dsyslog("Executing: %s", *cmd); ++ SystemExec(cmd); ++ } ++ else ++#endif /* SETTIME */ + if (stime(&sattim) == 0) + isyslog("system time changed from %s (%ld) to %s (%ld)", *TimeToString(loctim), loctim, *TimeToString(sattim), sattim); + else +diff -NaurwB vdr-1.7.10/eitscan.c vdr-1.7.10-patched/eitscan.c +--- vdr-1.7.10/eitscan.c 2006-01-07 15:10:17.000000000 +0100 ++++ vdr-1.7.10-patched/eitscan.c 2009-12-18 06:25:25.000000000 +0100 +@@ -151,9 +151,17 @@ + if (Device->ProvidesTransponder(Channel)) { + if (!Device->Receiving()) { + bool MaySwitchTransponder = Device->MaySwitchTransponder(); ++#ifdef USE_LNBSHARE ++ if (MaySwitchTransponder && Device->GetMaxBadPriority(Channel) == -2 || Device->ProvidesTransponderExclusively(Channel) && Device->GetMaxBadPriority(Channel) <= -1 && now - lastActivity > Setup.EPGScanTimeout * 3600) { ++#else + if (MaySwitchTransponder || Device->ProvidesTransponderExclusively(Channel) && now - lastActivity > Setup.EPGScanTimeout * 3600) { ++#endif /* LNBSHARE */ + if (!MaySwitchTransponder) { ++#ifdef USE_LNBSHARE ++ if ((Device == cDevice::ActualDevice() || Device->GetMaxBadPriority(Channel) == -1) && !currentChannel) { ++#else + if (Device == cDevice::ActualDevice() && !currentChannel) { ++#endif /* LNBSHARE */ + cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode + currentChannel = Device->CurrentChannel(); + Skins.Message(mtInfo, tr("Starting EPG scan")); +diff -NaurwB vdr-1.7.10/epg.c vdr-1.7.10-patched/epg.c +--- vdr-1.7.10/epg.c 2008-05-01 16:53:55.000000000 +0200 ++++ vdr-1.7.10-patched/epg.c 2009-12-18 06:38:20.000000000 +0100 +@@ -114,6 +114,11 @@ + components = NULL; + startTime = 0; + duration = 0; ++#ifdef USE_PARENTALRATING ++ for (int i = 0; i < MAXEVCONTENTS; i++) ++ contents[i] = 0; ++ parentalRating = 0; ++#endif /* PARENTALRATING */ + vps = 0; + SetSeen(); + } +@@ -202,6 +207,19 @@ + duration = Duration; + } + ++#ifdef USE_PARENTALRATING ++void cEvent::SetContents(uchar *Contents) ++{ ++ for (int i = 0; i < MAXEVCONTENTS; i++) ++ contents[i] = Contents[i]; ++} ++ ++void cEvent::SetParentalRating(uchar ParentalRating) ++{ ++ parentalRating = ParentalRating; ++} ++#endif /* PARENTALRATING */ ++ + void cEvent::SetVps(time_t Vps) + { + vps = Vps; +@@ -257,6 +275,340 @@ + return buf; + } + ++#ifdef USE_PARENTALRATING ++cString cEvent::GetContentsString(int i) const ++{ ++ cString str(""); ++ switch (contents[i] & 0xF0) { ++ case EVCONTENTMASK_MOVIEDRAMA: ++ switch (contents[i] & 0x0F) { ++ default: ++ case 0x00: ++ str = cString(tr("Content$Movie/Drama")); ++ break; ++ case 0x01: ++ str = cString(tr("Content$Detective/Thriller")); ++ break; ++ case 0x02: ++ str = cString(tr("Content$Adventure/Western/War")); ++ break; ++ case 0x03: ++ str = cString(tr("Content$Science Fiction/Fantasy/Horror")); ++ break; ++ case 0x04: ++ str = cString(tr("Content$Comedy")); ++ break; ++ case 0x05: ++ str = cString(tr("Content$Soap/Melodrama/Folkloric")); ++ break; ++ case 0x06: ++ str = cString(tr("Content$Romance")); ++ break; ++ case 0x07: ++ str = cString(tr("Content$Serious/Classical/Religious/Historical Movie/Drama")); ++ break; ++ case 0x08: ++ str = cString(tr("Content$Adult Movie/Drama")); ++ break; ++ } ++ break; ++ ++ case EVCONTENTMASK_NEWSCURRENTAFFAIRS: ++ switch (contents[i] & 0x0F) { ++ default: ++ case 0x00: ++ str = cString(tr("Content$News/Current Affairs")); ++ break; ++ case 0x01: ++ str = cString(tr("Content$News/Weather Report")); ++ break; ++ case 0x02: ++ str = cString(tr("Content$News Magazine")); ++ break; ++ case 0x03: ++ str = cString(tr("Content$Documentary")); ++ break; ++ case 0x04: ++ str = cString(tr("Content$Discussion/Inverview/Debate")); ++ break; ++ } ++ break; ++ ++ case EVCONTENTMASK_SHOW: ++ switch (contents[i] & 0x0F) { ++ default: ++ case 0x00: ++ str = cString(tr("Content$Show/Game Show")); ++ break; ++ case 0x01: ++ str = cString(tr("Content$Game Show/Quiz/Contest")); ++ break; ++ case 0x02: ++ str = cString(tr("Content$Variety Show")); ++ break; ++ case 0x03: ++ str = cString(tr("Content$Talk Show")); ++ break; ++ } ++ break; ++ ++ case EVCONTENTMASK_SPORTS: ++ switch (contents[i] & 0x0F) { ++ default: ++ case 0x00: ++ str = cString(tr("Content$Sports")); ++ break; ++ case 0x01: ++ str = cString(tr("Content$Special Event")); ++ break; ++ case 0x02: ++ str = cString(tr("Content$Sport Magazine")); ++ break; ++ case 0x03: ++ str = cString(tr("Content$Football")); ++ break; ++ case 0x04: ++ str = cString(tr("Content$Tennis/Squash")); ++ break; ++ case 0x05: ++ str = cString(tr("Content$Team Sports")); ++ break; ++ case 0x06: ++ str = cString(tr("Content$Athletics")); ++ break; ++ case 0x07: ++ str = cString(tr("Content$Motor Sport")); ++ break; ++ case 0x08: ++ str = cString(tr("Content$Water Sport")); ++ break; ++ case 0x09: ++ str = cString(tr("Content$Winter Sports")); ++ break; ++ case 0x0A: ++ str = cString(tr("Content$Equestrian")); ++ break; ++ case 0x0B: ++ str = cString(tr("Content$Martial Sports")); ++ break; ++ } ++ break; ++ ++ case EVCONTENTMASK_CHILDRENYOUTH: ++ switch (contents[i] & 0x0F) { ++ default: ++ case 0x00: ++ str = cString(tr("Content$Children's/Youth Programmes")); ++ break; ++ case 0x01: ++ str = cString(tr("Content$Pre-school Children's Programmes")); ++ break; ++ case 0x02: ++ str = cString(tr("Content$Entertainment Programmes for 6 to 14")); ++ break; ++ case 0x03: ++ str = cString(tr("Content$Entertainment Programmes for 10 to 16")); ++ break; ++ case 0x04: ++ str = cString(tr("Content$Informational/Educational/School Programme")); ++ break; ++ case 0x05: ++ str = cString(tr("Content$Cartoons/Puppets")); ++ break; ++ } ++ break; ++ ++ case EVCONTENTMASK_MUSICBALLETDANCE: ++ switch (contents[i] & 0x0F) { ++ default: ++ case 0x00: ++ str = cString(tr("Content$Music/Ballet/Dance")); ++ break; ++ case 0x01: ++ str = cString(tr("Content$Rock/Pop")); ++ break; ++ case 0x02: ++ str = cString(tr("Content$Serious/Classical Music")); ++ break; ++ case 0x03: ++ str = cString(tr("Content$Folk/Tradional Music")); ++ break; ++ case 0x04: ++ str = cString(tr("Content$Jazz")); ++ break; ++ case 0x05: ++ str = cString(tr("Content$Musical/Opera")); ++ break; ++ case 0x06: ++ str = cString(tr("Content$Ballet")); ++ break; ++ } ++ break; ++ ++ case EVCONTENTMASK_ARTSCULTURE: ++ switch (contents[i] & 0x0F) { ++ default: ++ case 0x00: ++ str = cString(tr("Content$Arts/Culture")); ++ break; ++ case 0x01: ++ str = cString(tr("Content$Performing Arts")); ++ break; ++ case 0x02: ++ str = cString(tr("Content$Fine Arts")); ++ break; ++ case 0x03: ++ str = cString(tr("Content$Religion")); ++ break; ++ case 0x04: ++ str = cString(tr("Content$Popular Culture/Traditional Arts")); ++ break; ++ case 0x05: ++ str = cString(tr("Content$Literature")); ++ break; ++ case 0x06: ++ str = cString(tr("Content$Film/Cinema")); ++ break; ++ case 0x07: ++ str = cString(tr("Content$Experimental Film/Video")); ++ break; ++ case 0x08: ++ str = cString(tr("Content$Broadcasting/Press")); ++ break; ++ case 0x09: ++ str = cString(tr("Content$New Media")); ++ break; ++ case 0x0A: ++ str = cString(tr("Content$Arts/Culture Magazines")); ++ break; ++ case 0x0B: ++ str = cString(tr("Content$Fashion")); ++ break; ++ } ++ break; ++ ++ case EVCONTENTMASK_SOCIALPOLITICALECONOMICS: ++ switch (contents[i] & 0x0F) { ++ default: ++ case 0x00: ++ str = cString(tr("Content$Social/Political/Economics")); ++ break; ++ case 0x01: ++ str = cString(tr("Content$Magazines/Reports/Documentary")); ++ break; ++ case 0x02: ++ str = cString(tr("Content$Economics/Social Advisory")); ++ break; ++ case 0x03: ++ str = cString(tr("Content$Remarkable People")); ++ break; ++ } ++ break; ++ ++ case EVCONTENTMASK_EDUCATIONALSCIENCE: ++ switch (contents[i] & 0x0F) { ++ default: ++ case 0x00: ++ str = cString(tr("Content$Education/Science/Factual")); ++ break; ++ case 0x01: ++ str = cString(tr("Content$Nature/Animals/Environment")); ++ break; ++ case 0x02: ++ str = cString(tr("Content$Technology/Natural Sciences")); ++ break; ++ case 0x03: ++ str = cString(tr("Content$Medicine/Physiology/Psychology")); ++ break; ++ case 0x04: ++ str = cString(tr("Content$Foreign Countries/Expeditions")); ++ break; ++ case 0x05: ++ str = cString(tr("Content$Social/Spiritual Sciences")); ++ break; ++ case 0x06: ++ str = cString(tr("Content$Further Education")); ++ break; ++ case 0x07: ++ str = cString(tr("Content$Languages")); ++ break; ++ } ++ break; ++ ++ case EVCONTENTMASK_LEISUREHOBBIES: ++ switch (contents[i] & 0x0F) { ++ default: ++ case 0x00: ++ str = cString(tr("Content$Leisure/Hobbies")); ++ break; ++ case 0x01: ++ str = cString(tr("Content$Tourism/Travel")); ++ break; ++ case 0x02: ++ str = cString(tr("Content$Handicraft")); ++ break; ++ case 0x03: ++ str = cString(tr("Content$Motoring")); ++ break; ++ case 0x04: ++ str = cString(tr("Content$Fitness & Health")); ++ break; ++ case 0x05: ++ str = cString(tr("Content$Cooking")); ++ break; ++ case 0x06: ++ str = cString(tr("Content$Advertisement/Shopping")); ++ break; ++ case 0x07: ++ str = cString(tr("Content$Gardening")); ++ break; ++ } ++ break; ++ ++ case EVCONTENTMASK_SPECIAL: ++ switch (contents[i] & 0x0F) { ++ case 0x00: ++ str = cString(tr("Content$Original Language")); ++ break; ++ case 0x01: ++ str = cString(tr("Content$Black & White")); ++ break; ++ case 0x02: ++ str = cString(tr("Content$Unpublished")); ++ break; ++ case 0x03: ++ str = cString(tr("Content$Live Broadcast")); ++ break; ++ default: ++ str = cString(tr("Content$Special Characteristics")); ++ break; ++ } ++ break; ++ ++ case EVCONTENTMASK_USERDEFINED: ++ switch (contents[i] & 0x0F) { ++ case 0x00: ++ str = cString(tr("Content$Drama")); // UK Freeview ++ break; ++ default: ++ break; ++ } ++ break; ++ ++ default: ++ break; ++ } ++ return str; ++} ++ ++cString cEvent::GetParentalRatingString(void) const ++{ ++ if (parentalRating > 0) ++ return cString::sprintf(tr("Suitable for those aged %d and over"), parentalRating); ++ return NULL; ++} ++#endif /* PARENTALRATING */ ++ + void cEvent::Dump(FILE *f, const char *Prefix, bool InfoOnly) const + { + if (InfoOnly || startTime + duration + Setup.EPGLinger * 60 >= time(NULL)) { +@@ -278,6 +630,17 @@ + fprintf(f, "%sX %s\n", Prefix, *p->ToString()); + } + } ++#ifdef USE_PARENTALRATING ++ if (!isempty(GetContentsString())) { ++ for (int i = 0; i < MAXEVCONTENTS; i++) { ++ if (!isempty(GetContentsString(i))) { ++ strreplace(description, '\n', '|'); ++ fprintf(f, "%sG %i %i %s\n",Prefix, Contents(i) & 0xF0, Contents(i) & 0x0F, (const char *)GetContentsString(i)); ++ strreplace(description, '|', '\n'); ++ } ++ } ++ } ++#endif /* PARENTALRATING */ + if (vps) + fprintf(f, "%sV %ld\n", Prefix, vps); + if (!InfoOnly) +@@ -345,6 +708,24 @@ + } + } + break; ++#ifdef USE_PARENTALRATING ++ case 'G': if (Event) { ++ unsigned int ContentID = 0; ++ unsigned int ContentSubID = 0; ++ int n = sscanf(t, "%u %u", &ContentID, &ContentSubID); ++ if (n == 2) { ++ if (ContentID != 0) { ++ for (int i = 0; i < MAXEVCONTENTS; i++) { ++ if (Event->Contents(i) == 0) { ++ Event->contents[i] = ContentID | ContentSubID; ++ break; ++ } ++ } ++ } ++ } ++ } ++ break; ++#endif /* PARENTALRATING */ + case 'e': if (Event && !Event->Title()) + Event->SetTitle(tr("No title")); + Event = NULL; +@@ -744,6 +1125,28 @@ + return pe; + } + ++#ifdef USE_DDEPGENTRY ++const cEvent *cSchedule::GetPreviousEvent(cEvent *Event) const ++{ ++ if (!Event || Event->Duration() == 0 || Event->StartTime() == 0) ++ return NULL; ++ // Returns either the event info to the previous/following event to the given EventID or, if that one can't be found NULL :EW ++ cEvent *pt = NULL; ++ int epgTimeDelta = Setup.DoubleEpgTimeDelta * 60 + 1; ++ for (pt = events.First(); pt; pt = events.Next(pt)) ++ if (pt && pt->TableID() == 0x00) ++ if ((Event->StartTime() - pt->StartTime()) > - epgTimeDelta && (Event->StartTime() - pt->StartTime()) < epgTimeDelta) { ++ if ((pt->Duration() + (pt->Duration()/ 5) + 1) > Event->Duration() && (pt->Duration() - (pt->Duration()/ 5) - 1) < Event->Duration()) ++ return pt; ++ else if (pt->Title() && Event->Title() && (strcmp(pt->Title(), ".") != 0 && strcmp(Event->Title(), ".") != 0)) { ++ if (strstr(pt->Title(), Event->Title()) != NULL || strstr(Event->Title(), pt->Title()) != NULL) ++ return pt; ++ } ++ } ++ return NULL; ++} ++#endif /* DDEPGENTRY */ ++ + void cSchedule::SetRunningStatus(cEvent *Event, int RunningStatus, cChannel *Channel) + { + hasRunning = false; +diff -NaurwB vdr-1.7.10/epg.h vdr-1.7.10-patched/epg.h +--- vdr-1.7.10/epg.h 2006-10-07 15:47:19.000000000 +0200 ++++ vdr-1.7.10-patched/epg.h 2009-12-18 06:25:25.000000000 +0100 +@@ -50,6 +50,22 @@ + + typedef u_int32_t tEventID; + ++#ifdef USE_PARENTALRATING ++#define MAXEVCONTENTS 4 ++#define EVCONTENTMASK_MOVIEDRAMA 0x10 ++#define EVCONTENTMASK_NEWSCURRENTAFFAIRS 0x20 ++#define EVCONTENTMASK_SHOW 0x30 ++#define EVCONTENTMASK_SPORTS 0x40 ++#define EVCONTENTMASK_CHILDRENYOUTH 0x50 ++#define EVCONTENTMASK_MUSICBALLETDANCE 0x60 ++#define EVCONTENTMASK_ARTSCULTURE 0x70 ++#define EVCONTENTMASK_SOCIALPOLITICALECONOMICS 0x80 ++#define EVCONTENTMASK_EDUCATIONALSCIENCE 0x90 ++#define EVCONTENTMASK_LEISUREHOBBIES 0xA0 ++#define EVCONTENTMASK_SPECIAL 0xB0 ++#define EVCONTENTMASK_USERDEFINED 0xF0 ++#endif /* PARENTALRATING */ ++ + class cEvent : public cListObject { + friend class cSchedule; + private: +@@ -64,6 +80,10 @@ + cComponents *components; // The stream components of this event + time_t startTime; // Start time of this event + int duration; // Duration of this event in seconds ++#ifdef USE_PARENTALRATING ++ uchar contents[MAXEVCONTENTS + 1]; // Contents of this event; list is zero-terminated ++ uchar parentalRating; // Parental rating of this event ++#endif /* PARENTALRATING */ + time_t vps; // Video Programming Service timestamp (VPS, aka "Programme Identification Label", PIL) + time_t seen; // When this event was last seen in the data stream + public: +@@ -83,6 +103,10 @@ + time_t StartTime(void) const { return startTime; } + time_t EndTime(void) const { return startTime + duration; } + int Duration(void) const { return duration; } ++#ifdef USE_PARENTALRATING ++ uchar Contents(int i = 0) const { return (0 <= i && i < MAXEVCONTENTS) ? contents[i] : 0; } ++ uchar ParentalRating(void) const { return parentalRating; } ++#endif /* PARENTALRATING */ + time_t Vps(void) const { return vps; } + time_t Seen(void) const { return seen; } + bool SeenWithin(int Seconds) const { return time(NULL) - seen < Seconds; } +@@ -92,6 +116,10 @@ + cString GetTimeString(void) const; + cString GetEndTimeString(void) const; + cString GetVpsString(void) const; ++#ifdef USE_PARENTALRATING ++ cString GetContentsString(int i = 0) const; ++ cString GetParentalRatingString(void) const; ++#endif /* PARENTALRATING */ + void SetEventID(tEventID EventID); + void SetTableID(uchar TableID); + void SetVersion(uchar Version); +@@ -102,6 +130,10 @@ + void SetComponents(cComponents *Components); // Will take ownership of Components! + void SetStartTime(time_t StartTime); + void SetDuration(int Duration); ++#ifdef USE_PARENTALRATING ++ void SetContents(uchar *Contents); ++ void SetParentalRating(uchar ParentalRating); ++#endif /* PARENTALRATING */ + void SetVps(time_t Vps); + void SetSeen(void); + cString ToDescr(void) const; +@@ -137,6 +169,9 @@ + void DropOutdated(time_t SegmentStart, time_t SegmentEnd, uchar TableID, uchar Version); + void Cleanup(time_t Time); + void Cleanup(void); ++#ifdef USE_DDEPGENTRY ++ const cEvent *GetPreviousEvent(cEvent *Event) const; //:EW ++#endif /* DDEPGENTRY */ + cEvent *AddEvent(cEvent *Event); + void DelEvent(cEvent *Event); + void HashEvent(cEvent *Event); +diff -NaurwB vdr-1.7.10/HISTORY-liemikuutio vdr-1.7.10-patched/HISTORY-liemikuutio +--- vdr-1.7.10/HISTORY-liemikuutio 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/HISTORY-liemikuutio 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,132 @@ ++----------------------------------- ++Liemikuutio for Video Disc Recorder ++ ++Maintainer: Rolf Ahrenberg ++----------------------------------- ++ ++2006-01-08: Version 1.0 ++ ++- Based on enAIO with these original patches: ++ Simple recordings sorting by Walter@VDRPortal ++ Alternate rename recordings by Ralf Müller ++ Menu selection by Peter Dittmann ++ Recording length by Tobias Faust ++ ++2006-01-15: Version 1.1 ++ ++- Removed patches already found in vdr-1.3.39. ++ ++2006-01-25: Version 1.2 ++ ++- Added "Main menu command position" feature. ++ ++2006-02-05: Version 1.3 ++ ++- Improved menu selection response. ++ ++2006-04-18: Version 1.4 ++ ++- Added Estonian translation (Thanks to Arthur Konovalov). ++ ++2006-04-30: Version 1.5 ++ ++- Added progress bar view into "What's on now?" menu. ++ ++2006-06-06: Version 1.6 ++ ++- Added French translation (Thanks to ECLiPSE). ++ ++2006-06-14: Version 1.7 ++ ++- Fixed RENR crash. ++ ++2006-07-14: Version 1.8 ++ ++- Fixed RENR/OSD bug. ++ ++2006-08-27: Version 1.9 ++ ++- Some modifications to the recording length and rename recordings ++ patches (Thanks to Firefly). ++- Added k1_k3_jumps_20s patch by Petri Hintukainen. ++ ++2006-08-29: Version 1.10 ++ ++- The cRecording:Title() method now defaults to original formatting. ++ ++2006-09-04: Version 1.11 ++ ++- Removed unused variable from cRecording::Title() method (Thanks to ++ C.Y.M.). ++- Some modifications to the rename recordings patch (Thanks to Firefly). ++ ++2006-09-13: Version 1.12 ++ ++- More modifications to the rename recordings patch (Thanks to Firefly). ++ ++2006-10-01: Version 1.13 ++ ++- Removed unnecessary syslog printing (Thanks to Firefly). ++ ++2007-08-14: Version 1.14 ++ ++- Updated for vdr-1.5.7. ++ ++2007-10-16: Version 1.15 ++ ++- Added recmenu play patch (Thanks to Ville Skyttä). ++- Updated French translation (Thanks to ECLiPSE). ++ ++2007-11-04: Version 1.16 ++ ++- Updated for vdr-1.5.11. ++ ++2007-12-08: Version 1.17 ++ ++- Added binary skip patch. ++- Removed k1_k3_jumps_20s patch. ++ ++2008-02-17: Version 1.18 ++ ++- Updated for vdr-1.5.15. ++ ++2008-03-02: Version 1.19 ++ ++- Modified binary skip to use kPrev and kNext keys and the skip is now ++ always shortened after a direction change (Thanks to Timo Eskola). ++- Readded k1_k3_jumps_20s patch. ++ ++2008-04-04: Version 1.20 ++ ++- Added bitrate information into rename menu. ++- Readded the path editing support of rename recordings patch (Thanks ++ to Firefly). ++ ++2008-05-08: Version 1.21 ++ ++- Fixed rename recordings (Thanks to Firefly). ++- Added a DVB subtitles hack for old recordings (Thanks to Anssi Hannula). ++ ++2009-01-08: Version 1.22 ++ ++- Updated for vdr-1.7.3. ++ ++2009-01-25: Version 1.23 ++ ++- Updated for vdr-1.7.4. ++ ++2009-02-27: Version 1.24 ++ ++- Fixed compilation under gcc-4.4. ++ ++2009-04-05: Version 1.25 ++ ++- Fixed the length detection of recordings (Thanks to Thomas Günther). ++ ++2009-04-17: Version 1.26 ++ ++- Fixed the length detection of audio recordings (Thanks to Thomas Günther). ++ ++2009-04-26: Version 1.27 ++ ++- Fixed the length detection of empty recordings (Thanks to Thomas Günther). +diff -NaurwB vdr-1.7.10/iconpatch.c vdr-1.7.10-patched/iconpatch.c +--- vdr-1.7.10/iconpatch.c 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/iconpatch.c 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,31 @@ ++#ifdef USE_WAREAGLEICON ++ ++#include "iconpatch.h" ++ ++#include ++#include ++#include ++#include ++#include ++ ++bool IsLangUtf8(void) ++{ ++ char *CodeSet = NULL; ++ if (setlocale(LC_CTYPE, "")) ++ CodeSet = nl_langinfo(CODESET); ++ else { ++ char *LangEnv = getenv("LANG"); // last resort in case locale stuff isn't installed ++ if (LangEnv) { ++ CodeSet = strchr(LangEnv, '.'); ++ if (CodeSet) ++ CodeSet++; // skip the dot ++ } ++ } ++ ++ if (CodeSet && strcasestr(CodeSet, "UTF-8") != 0) ++ return true; ++ ++ return false; ++} ++ ++#endif /* WAREAGLEICON */ +diff -NaurwB vdr-1.7.10/iconpatch.h vdr-1.7.10-patched/iconpatch.h +--- vdr-1.7.10/iconpatch.h 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/iconpatch.h 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,73 @@ ++#ifdef USE_WAREAGLEICON ++/* ++ * iconpatch.h: Information of iconpatch ++ * ++ * Diese Datei ist die Übersichtsdatei für den Iconpatch. ++ * Hier werden kleine Infos abgelegt. ++ * Der Iconpatch ändert die Dateien: ++ * iconpatch.h ++ * menu.c ++ * recording.c ++ * fontosd.c ++ * ++ */ ++ ++// Iconpatch-Variablen - Anfang ++#define ICON_NUMBERSIGN "\x23" ++#define ICON_ASTERISK "\x2A" ++#define ICON_GREATER "\x3E" ++#define ICON_EXCLAM "\x21" ++#define ICON_PLUSMINUS "\xB1" ++ ++#define ICON_RESUME "\x80" ++#define ICON_DVD "\x81" ++#define ICON_FOLDER "\x82" ++#define ICON_BLANK "\x83" ++#define ICON_CUTTING "\x84" ++#define ICON_MOVE_FILE "\x85" ++#define ICON_MOVE_FOLDER "\x86" ++#define ICON_BAR_START "\x87" ++#define ICON_BAR_FILLED "\x88" ++#define ICON_BAR_CLEAR "\x89" ++#define ICON_BAR_END "\x8A" ++#define ICON_REC "\x8B" ++#define ICON_CLOCK "\x8C" ++#define ICON_TV_CRYPTED "\x8D" ++#define ICON_RADIO "\x8E" ++#define ICON_TV "\x8F" ++#define ICON_NEW "\x90" ++#define ICON_ARROW "\x91" ++#define ICON_RUNNING "\x92" ++#define ICON_VPS "\x93" ++#define ICON_CLOCK_UH "\x94" ++#define ICON_CLOCK_LH "\x95" ++ ++// UTF-8 Icons ++#define ICON_RESUME_UTF8 "\uE000" ++#define ICON_DVD_UTF8 "\uE001" ++#define ICON_FOLDER_UTF8 "\uE002" ++#define ICON_BLANK_UTF8 "\uE003" ++#define ICON_CUTTING_UTF8 "\uE004" ++#define ICON_MOVE_FILE_UTF8 "\uE005" ++#define ICON_MOVE_FOLDER_UTF8 "\uE006" ++#define ICON_BAR_START_UTF8 "\uE007" ++#define ICON_BAR_FILLED_UTF8 "\uE008" ++#define ICON_BAR_EMPTY_UTF8 "\uE009" ++#define ICON_BAR_CLOSE_UTF8 "\uE00A" ++#define ICON_REC_UTF8 "\uE00B" ++#define ICON_CLOCK_UTF8 "\uE00C" ++#define ICON_TV_CRYPTED_UTF8 "\uE00D" ++#define ICON_RADIO_UTF8 "\uE00E" ++#define ICON_TV_UTF8 "\uE00F" ++#define ICON_NEW_UTF8 "\uE010" ++#define ICON_ARROW_UTF8 "\uE011" ++#define ICON_RUNNING_UTF8 "\uE012" ++#define ICON_VPS_UTF8 "\uE013" ++#define ICON_CLOCK_UH_UTF8 "\uE014" ++#define ICON_CLOCK_LH_UTF8 "\uE015" ++ ++// Iconpatch-Variablen - Ende ++ ++bool IsLangUtf8(void); ++ ++#endif /* WAREAGLEICON */ +diff -NaurwB vdr-1.7.10/keys.h vdr-1.7.10-patched/keys.h +--- vdr-1.7.10/keys.h 2007-08-26 14:34:50.000000000 +0200 ++++ vdr-1.7.10-patched/keys.h 2009-12-18 06:25:25.000000000 +0100 +@@ -71,6 +71,10 @@ + #define kEditCut k2 + #define kEditTest k8 + ++#ifdef USE_DVDARCHIVE ++#define kDvdChapterJumpForward k6 ++#define kDvdChapterJumpBack k4 ++#endif /* DVDARCHIVE */ + #define RAWKEY(k) (eKeys((k) & ~k_Flags)) + #define ISRAWKEY(k) ((k) != kNone && ((k) & k_Flags) == 0) + #define NORMALKEY(k) (eKeys((k) & ~k_Repeat)) +diff -NaurwB vdr-1.7.10/lirc.c vdr-1.7.10-patched/lirc.c +--- vdr-1.7.10/lirc.c 2006-05-28 10:48:13.000000000 +0200 ++++ vdr-1.7.10-patched/lirc.c 2009-12-18 06:25:25.000000000 +0100 +@@ -12,6 +12,9 @@ + #include "lirc.h" + #include + #include ++#ifdef USE_LIRCSETTINGS ++#include "config.h" ++#endif /* LIRCSETTINGS */ + + #define REPEATDELAY 350 // ms + #define REPEATFREQ 100 // ms +@@ -94,7 +97,11 @@ + continue; + } + if (count == 0) { ++#ifdef USE_LIRCSETTINGS ++ if (strcmp(KeyName, LastKeyName) == 0 && FirstTime.Elapsed() < (unsigned int)Setup.LircRepeatDelay) ++#else + if (strcmp(KeyName, LastKeyName) == 0 && FirstTime.Elapsed() < REPEATDELAY) ++#endif /* LIRCSETTINGS */ + continue; // skip keys coming in too fast + if (repeat) + Put(LastKeyName, false, true); +@@ -104,18 +111,34 @@ + timeout = -1; + } + else { ++#ifdef USE_LIRCSETTINGS ++ if (LastTime.Elapsed() < (unsigned int)Setup.LircRepeatFreq) ++#else + if (LastTime.Elapsed() < REPEATFREQ) ++#endif /* LIRCSETTINGS */ + continue; // repeat function kicks in after a short delay (after last key instead of first key) ++#ifdef USE_LIRCSETTINGS ++ if (FirstTime.Elapsed() < (unsigned int)Setup.LircRepeatDelay) ++#else + if (FirstTime.Elapsed() < REPEATDELAY) ++#endif /* LIRCSETTINGS */ + continue; // skip keys coming in too fast (for count != 0 as well) + repeat = true; ++#ifdef USE_LIRCSETTINGS ++ timeout = Setup.LircRepeatDelay; ++#else + timeout = REPEATDELAY; ++#endif /* LIRCSETTINGS */ + } + LastTime.Set(); + Put(KeyName, repeat); + } + else if (repeat) { // the last one was a repeat, so let's generate a release ++#ifdef USE_LIRCSETTINGS ++ if (LastTime.Elapsed() >= (unsigned int)Setup.LircRepeatTimeout) { ++#else + if (LastTime.Elapsed() >= REPEATTIMEOUT) { ++#endif /* LIRCSETTINGS */ + Put(LastKeyName, false, true); + repeat = false; + *LastKeyName = 0; +diff -NaurwB vdr-1.7.10/mainmenuitemsprovider.h vdr-1.7.10-patched/mainmenuitemsprovider.h +--- vdr-1.7.10/mainmenuitemsprovider.h 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/mainmenuitemsprovider.h 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,62 @@ ++#ifdef USE_MENUORG ++/* ++ * vdr-menuorg - A plugin for the Linux Video Disk Recorder ++ * Copyright (c) 2007 - 2008 Tobias Grimm ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by the ++ * Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ++ * details. ++ * ++ * You should have received a copy of the GNU General Public License along with ++ * this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ * ++ * $Id$ ++ * ++ */ ++ ++#ifndef __MAINMENUITEMSPROVIDER_H ++#define __MAINMENUITEMSPROVIDER_H ++ ++#include ++ ++class cOsdItem; ++class cOsdMenu; ++ ++class IMenuItemDefinition ++{ ++ public: ++ virtual ~IMenuItemDefinition() {}; ++ virtual bool IsCustomOsdItem() = 0; ++ virtual bool IsPluginItem() = 0; ++ virtual bool IsSeparatorItem() = 0; ++ virtual cOsdItem* CustomOsdItem() = 0; ++ virtual const char* PluginMenuEntry() = 0; ++ virtual bool IsSelected() = 0; ++ virtual int PluginIndex() = 0; ++}; ++ ++typedef std::vector MenuItemDefinitions; ++ ++#define MENU_ITEMS_PROVIDER_SERVICE_ID "MenuOrgPatch-v0.4.2::MainMenuItemsProvider" ++ ++class IMainMenuItemsProvider ++{ ++ public: ++ virtual ~IMainMenuItemsProvider() {}; ++ virtual bool IsCustomMenuAvailable() = 0; ++ virtual MenuItemDefinitions* MainMenuItems() = 0; ++ virtual void EnterRootMenu() = 0; ++ virtual void EnterSubMenu(cOsdItem* item) = 0; ++ virtual bool LeaveSubMenu() = 0; ++ virtual cOsdMenu* Execute(cOsdItem* item) = 0; ++}; ++ ++#endif //__MAINMENUITEMSPROVIDER_H ++#endif /* MENUORG */ +diff -NaurwB vdr-1.7.10/Makefile vdr-1.7.10-patched/Makefile +--- vdr-1.7.10/Makefile 2009-10-18 15:59:25.000000000 +0200 ++++ vdr-1.7.10-patched/Makefile 2009-12-18 06:25:25.000000000 +0100 +@@ -43,6 +43,18 @@ + skinclassic.o skins.o skinsttng.o sources.o spu.o status.o svdrp.o themes.o thread.o\ + timers.o tools.o transfer.o vdr.o videodir.o + ++ifdef WAREAGLEICON ++OBJS += iconpatch.o ++endif ++ ++ifdef SETUP ++OBJS += tinystr.o tinyxml.o tinyxmlerror.o tinyxmlparser.o submenu.o ++endif ++ ++ifdef TTXTSUBS ++OBJS += vdrttxtsubshooks.o ++endif ++ + ifndef NO_KBD + DEFINES += -DREMOTE_KBD + endif +@@ -72,6 +84,14 @@ + VDRVERSION = $(shell sed -ne '/define VDRVERSION/s/^.*"\(.*\)".*$$/\1/p' config.h) + APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' config.h) + ++ifdef DVDARCHIVE ++ifdef DVDCHAPJUMP ++LIBS += -ldvdread ++INCLUDES += -I/usr/include/dvdread ++DEFINES += -DUSE_DVDCHAPJUMP ++endif ++endif ++ + all: vdr i18n + + # Implicit rules: +@@ -137,6 +157,26 @@ + + # Plugins: + ++ifdef PLUGINAPI ++DEFINES += -DUSE_PLUGINAPI ++plugins: include-dir ++ @failed="";\ ++ noapiv="";\ ++ for i in `ls $(PLUGINDIR)/src | grep -v '[^a-z0-9]'`; do\ ++ echo "Plugin $$i:";\ ++ if ! grep -q "\$$(LIBDIR)/.*\$$(APIVERSION)" "$(PLUGINDIR)/src/$$i/Makefile" ; then\ ++ sed -i -e s/VDRVERSION/APIVERSION/g $(PLUGINDIR)/src/$$i/Makefile;\ ++ if ! grep -q "\$$(LIBDIR)/.*\$$(APIVERSION)" "$(PLUGINDIR)/src/$$i/Makefile" ; then\ ++ echo "ERROR: plugin $$i doesn't honor APIVERSION - not compiled!";\ ++ noapiv="$$noapiv $$i";\ ++ continue;\ ++ fi;\ ++ fi;\ ++ $(MAKE) -C "$(PLUGINDIR)/src/$$i" all || failed="$$failed $$i";\ ++ done;\ ++ if [ -n "$$noapiv" ] ; then echo; echo "*** plugins without APIVERSION:$$noapiv"; echo; fi;\ ++ if [ -n "$$failed" ] ; then echo; echo "*** failed plugins:$$failed"; echo; fi ++else + plugins: include-dir + @failed="";\ + noapiv="";\ +@@ -151,6 +191,7 @@ + done;\ + if [ -n "$$noapiv" ] ; then echo; echo "*** plugins without APIVERSION:$$noapiv"; echo; fi;\ + if [ -n "$$failed" ] ; then echo; echo "*** failed plugins:$$failed"; echo; exit 1; fi ++endif + + clean-plugins: + @for i in `ls $(PLUGINDIR)/src | grep -v '[^a-z0-9]'`; do $(MAKE) -C "$(PLUGINDIR)/src/$$i" clean; done +diff -NaurwB vdr-1.7.10/MANUAL vdr-1.7.10-patched/MANUAL +--- vdr-1.7.10/MANUAL 2009-11-22 15:28:15.000000000 +0100 ++++ vdr-1.7.10-patched/MANUAL 2009-12-18 06:25:25.000000000 +0100 +@@ -822,6 +822,30 @@ + 0 resulting in a file named 'resume', and any other + value resulting in 'resume.n'. + ++ Jump&Play = no Turns playing on or off after jumping forward to the ++ next editing mark with the '9' key. ++ ++ Play&Jump = no Turns automatic jumping over commercial breaks on or ++ off. This includes jumping to the first mark, if the ++ replay starts at the beginning of a recording - and ++ stopping the replay at the last mark. ++ With this setting enabled, the behaviour of the '8' ++ key during replay is changed too. It moves the actual ++ replay position not only three seconds before the ++ next "start" mark, but also before the next "end" ++ mark. This can be used to test, if the editing marks ++ are correctly positioned for a "smooth" jump over a ++ commercial break. ++ ++ Pause at last mark = no ++ Turns pausing of replay at the last editing mark on or ++ off. ++ ++ Reload marks = no Turns reloading of editing marks on or off. This can ++ be used if an external programme adjusts the editing ++ marks, e.g. noad in online mode. The marks are reloaded ++ in 10 seconds intervals. ++ + Miscellaneous: + + Min. event timeout = 30 +diff -NaurwB vdr-1.7.10/menu.c vdr-1.7.10-patched/menu.c +--- vdr-1.7.10/menu.c 2009-06-21 11:56:06.000000000 +0200 ++++ vdr-1.7.10-patched/menu.c 2009-12-18 06:30:06.000000000 +0100 +@@ -8,12 +8,18 @@ + */ + + #include "menu.h" ++#ifdef USE_WAREAGLEICON ++#include "iconpatch.h" ++#endif /* WAREAGLEICON */ + #include + #include + #include + #include + #include + #include ++#ifdef USE_LIEMIEXT ++#include ++#endif /* LIEMIEXT */ + #include "channels.h" + #include "config.h" + #include "cutter.h" +@@ -30,6 +36,13 @@ + #include "timers.h" + #include "transfer.h" + #include "videodir.h" ++#ifdef USE_MENUORG ++#include "menuorgpatch.h" ++#endif /* MENUORG */ ++ ++#ifdef USE_CMDRECCMDI18N ++extern const char *ConfigDirectory; ++#endif /* CMDRECCMDI18N */ + + #define MAXWAIT4EPGINFO 3 // seconds + #define MODETIMEOUT 3 // seconds +@@ -190,10 +203,16 @@ + cChannel *channel; + cChannel data; + char name[256]; ++#ifdef USE_PLUGINPARAM ++ char pluginParam[256]; ++#endif /* PLUGINPARAM */ + void Setup(void); + public: + cMenuEditChannel(cChannel *Channel, bool New = false); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuEditChannel"; } ++#endif /* GRAPHTFT */ + }; + + cMenuEditChannel::cMenuEditChannel(cChannel *Channel, bool New) +@@ -222,6 +241,9 @@ + + // Parameters for all types of sources: + strn0cpy(name, data.name, sizeof(name)); ++#ifdef USE_PLUGINPARAM ++ strn0cpy(pluginParam, data.pluginParam, sizeof(pluginParam)); ++#endif /* PLUGINPARAM */ + Add(new cMenuEditStrItem( tr("Name"), name, sizeof(name))); + Add(new cMenuEditSrcItem( tr("Source"), &data.source)); + Add(new cMenuEditIntItem( tr("Frequency"), &data.frequency)); +@@ -254,6 +276,9 @@ + ST(" T") Add(new cMenuEditMapItem( tr("Guard"), &data.guard, GuardValues)); + ST(" T") Add(new cMenuEditMapItem( tr("Hierarchy"), &data.hierarchy, HierarchyValues)); + ST(" S ") Add(new cMenuEditMapItem( tr("Rolloff"), &data.rollOff, RollOffValues)); ++#ifdef USE_PLUGINPARAM ++ ST("P ") Add(new cMenuEditStrItem( tr("Parameters"), pluginParam, sizeof(pluginParam), tr(FileNameChars))); ++#endif /* PLUGINPARAM */ + + SetCurrent(Get(current)); + Display(); +@@ -268,6 +293,9 @@ + if (Key == kOk) { + if (Channels.HasUniqueChannelID(&data, channel)) { + data.name = strcpyrealloc(data.name, name); ++#ifdef USE_PLUGINPARAM ++ data.pluginParam = strcpyrealloc(data.pluginParam, pluginParam); ++#endif /* PLUGINPARAM */ + if (channel) { + *channel = data; + isyslog("edited channel %d %s", channel->Number(), *data.ToText()); +@@ -341,6 +369,16 @@ + if (!channel->GroupSep()) { + if (sortMode == csmProvider) + buffer = cString::sprintf("%d\t%s - %s", channel->Number(), channel->Provider(), channel->Name()); ++#ifdef USE_WAREAGLEICON ++ else if (Setup.WarEagleIcons) { ++ if (channel->Vpid() == 1 || channel->Vpid() == 0) ++ buffer = cString::sprintf("%d\t%s %-30s", channel->Number(), IsLangUtf8() ? ICON_RADIO_UTF8 : ICON_RADIO, channel->Name()); ++ else if (channel->Ca() == 0) ++ buffer = cString::sprintf("%d\t%s %-30s", channel->Number(), IsLangUtf8() ? ICON_TV_UTF8 : ICON_TV, channel->Name()); ++ else ++ buffer = cString::sprintf("%d\t%s %-30s", channel->Number(), IsLangUtf8() ? ICON_TV_CRYPTED_UTF8 : ICON_TV_CRYPTED, channel->Name()); ++ } ++#endif /* WAREAGLEICON */ + else + buffer = cString::sprintf("%d\t%s", channel->Number(), channel->Name()); + } +@@ -371,6 +409,9 @@ + cMenuChannels(void); + ~cMenuChannels(); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuChannels"; } ++#endif /* GRAPHTFT */ + }; + + cMenuChannels::cMenuChannels(void) +@@ -637,14 +678,47 @@ + data.SetFlags(tfActive); + channel = data.Channel()->Number(); + Add(new cMenuEditBitItem( tr("Active"), &data.flags, tfActive)); ++#ifdef USE_PINPLUGIN ++ if (cOsd::pinValid) Add(new cMenuEditChanItem(tr("Channel"), &channel)); ++ else { ++ cString buf = cString::sprintf("%s\t%s", tr("Channel"), Channels.GetByNumber(channel)->Name()); ++ Add(new cOsdItem(buf)); ++ } ++#else + Add(new cMenuEditChanItem(tr("Channel"), &channel)); ++#endif /* PINPLUGIN */ + Add(new cMenuEditDateItem(tr("Day"), &data.day, &data.weekdays)); + Add(new cMenuEditTimeItem(tr("Start"), &data.start)); + Add(new cMenuEditTimeItem(tr("Stop"), &data.stop)); + Add(new cMenuEditBitItem( tr("VPS"), &data.flags, tfVps)); + Add(new cMenuEditIntItem( tr("Priority"), &data.priority, 0, MAXPRIORITY)); + Add(new cMenuEditIntItem( tr("Lifetime"), &data.lifetime, 0, MAXLIFETIME)); ++#ifdef USE_PINPLUGIN ++ if (cOsd::pinValid || !data.fskProtection) Add(new cMenuEditBoolItem(tr("Childlock"),&data.fskProtection)); ++ else { ++ cString buf = cString::sprintf("%s\t%s", tr("Childlock"), data.fskProtection ? tr("yes") : tr("no")); ++ Add(new cOsdItem(buf)); ++ } ++#endif /* PINPLUGIN */ ++#ifdef USE_LIEMIEXT ++ char* p = strrchr(data.file, '~'); ++ if (p) { ++ p++; ++ Utf8Strn0Cpy(name, p, sizeof(name)); ++ Utf8Strn0Cpy(path, data.file, sizeof(path)); ++ p = strrchr(path, '~'); ++ if (p) ++ p[0] = 0; ++ } ++ else { ++ Utf8Strn0Cpy(name, data.file, sizeof(name)); ++ Utf8Strn0Cpy(path, "", sizeof(path)); ++ } ++ Add(new cMenuEditStrItem( tr("File"), name, sizeof(name), tr(FileNameChars))); ++ Add(new cMenuEditRecPathItem(tr("Path"), path, sizeof(path))); ++#else + Add(new cMenuEditStrItem( tr("File"), data.file, sizeof(data.file))); ++#endif /* LIEMIEXT */ + SetFirstDayItem(); + } + Timers.IncBeingEdited(); +@@ -684,6 +758,12 @@ + Skins.Message(mtError, tr("*** Invalid Channel ***")); + break; + } ++#ifdef USE_LIEMIEXT ++ if (strlen(path)) ++ snprintf(data.file, sizeof(data.file), "%s~%s", path, name); ++ else ++ snprintf(data.file, sizeof(data.file), "%s", name); ++#endif /* LIEMIEXT */ + if (!*data.file) + strcpy(data.file, data.Channel()->ShortName(true)); + if (timer) { +@@ -711,13 +791,37 @@ + return state; + } + ++#ifdef USE_TIMERCMD ++// --- cMenuCommands --------------------------------------------------------- ++// declaration shifted so it can be used in cMenuTimers ++class cMenuCommands : public cOsdMenu { ++private: ++ cCommands *commands; ++ char *parameters; ++ eOSState Execute(void); ++public: ++ cMenuCommands(const char *Title, cCommands *Commands, const char *Parameters = NULL); ++ virtual ~cMenuCommands(); ++ virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuCommands"; } ++#endif /* GRAPHTFT - passt das so? */ ++ }; ++#endif /* TIMERCMD */ ++ + // --- cMenuTimerItem -------------------------------------------------------- + + class cMenuTimerItem : public cOsdItem { + private: + cTimer *timer; ++#ifdef USE_TIMERINFO ++ char diskStatus; ++#endif /* TIMERINFO */ + public: + cMenuTimerItem(cTimer *Timer); ++#ifdef USE_TIMERINFO ++ void SetDiskStatus(char DiskStatus); ++#endif /* TIMERINFO */ + virtual int Compare(const cListObject &ListObject) const; + virtual void Set(void); + cTimer *Timer(void) { return timer; } +@@ -726,6 +830,9 @@ + cMenuTimerItem::cMenuTimerItem(cTimer *Timer) + { + timer = Timer; ++#ifdef USE_TIMERINFO ++ diskStatus = ' '; ++#endif /* TIMERINFO */ + Set(); + } + +@@ -751,8 +858,56 @@ + strftime(buffer, sizeof(buffer), "%Y%m%d", &tm_r); + day = buffer; + } ++#ifdef USE_LIEMIEXT ++ if (!Setup.ShowTimerStop) { ++#ifdef USE_TIMERINFO ++#ifdef USE_WAREAGLEICON ++ SetText(cString::sprintf("%c%s\t%d\t%s%s%s\t%02d:%02d\t%s", ++#else ++ SetText(cString::sprintf("%c%c\t%d\t%s%s%s\t%02d:%02d\t%s", ++#endif /* WAREAGLEICON */ ++ diskStatus, ++#else ++#ifdef USE_WAREAGLEICON ++ SetText(cString::sprintf("%s\t%d\t%s%s%s\t%02d:%02d\t%s", ++#else ++ SetText(cString::sprintf("%c\t%d\t%s%s%s\t%02d:%02d\t%s", ++#endif /* WAREAGLEICON */ ++#endif /* TIMERINFO */ ++#ifdef USE_WAREAGLEICON ++ !(timer->HasFlags(tfActive)) ? " " : timer->FirstDay() ? Setup.WarEagleIcons ? IsLangUtf8() ? ICON_ARROW_UTF8 : ICON_ARROW : "!" : timer->Recording() ? Setup.WarEagleIcons ? IsLangUtf8() ? ICON_REC_UTF8 : ICON_REC : "#" : Setup.WarEagleIcons ? IsLangUtf8() ? ICON_CLOCK_UTF8 : ICON_CLOCK : ">", ++#else ++ !(timer->HasFlags(tfActive)) ? ' ' : timer->FirstDay() ? '!' : timer->Recording() ? '#' : '>', ++#endif /* WAREAGLEICON */ ++ timer->Channel()->Number(), ++ *name, ++ *name && **name ? " " : "", ++ *day, ++ timer->Start() / 100, ++ timer->Start() % 100, ++ timer->File())); ++ } ++ else { ++#endif /* LIEMIEXT */ ++#ifdef USE_TIMERINFO ++#ifdef USE_WAREAGLEICON ++ SetText(cString::sprintf("%c%s\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s", ++#else ++ SetText(cString::sprintf("%c%c\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s", ++#endif /* WAREAGLEICON */ ++ diskStatus, ++#else ++#ifdef USE_WAREAGLEICON ++ SetText(cString::sprintf("%s\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s", ++#else + SetText(cString::sprintf("%c\t%d\t%s%s%s\t%02d:%02d\t%02d:%02d\t%s", ++#endif /* WAREAGLEICON */ ++#endif /* TIMERINFO */ ++#ifdef USE_WAREAGLEICON ++ !(timer->HasFlags(tfActive)) ? " " : timer->FirstDay() ? Setup.WarEagleIcons ? IsLangUtf8() ? ICON_ARROW_UTF8 : ICON_ARROW : "!" : timer->Recording() ? Setup.WarEagleIcons ? IsLangUtf8() ? ICON_REC_UTF8 : ICON_REC : "#" : Setup.WarEagleIcons ? IsLangUtf8() ? ICON_CLOCK_UTF8 : ICON_CLOCK : ">", ++#else + !(timer->HasFlags(tfActive)) ? ' ' : timer->FirstDay() ? '!' : timer->Recording() ? '#' : '>', ++#endif /* WAREAGLEICON */ + timer->Channel()->Number(), + *name, + *name && **name ? " " : "", +@@ -762,8 +917,64 @@ + timer->Stop() / 100, + timer->Stop() % 100, + timer->File())); ++#ifdef USE_LIEMIEXT ++ } ++#endif /* LIEMIEXT */ ++} ++ ++#ifdef USE_TIMERINFO ++void cMenuTimerItem::SetDiskStatus(char DiskStatus) ++{ ++ diskStatus = DiskStatus; ++ Set(); + } + ++// --- cTimerEntry ----------------------------------------------------------- ++ ++class cTimerEntry : public cListObject { ++private: ++ cMenuTimerItem *item; ++ const cTimer *timer; ++ time_t start; ++public: ++ cTimerEntry(cMenuTimerItem *item) : item(item), timer(item->Timer()), start(timer->StartTime()) {} ++ cTimerEntry(const cTimer *timer, time_t start) : item(NULL), timer(timer), start(start) {} ++ virtual int Compare(const cListObject &ListObject) const; ++ bool active(void) const { return timer->HasFlags(tfActive); } ++ time_t startTime(void) const { return start; } ++ int priority(void) const { return timer->Priority(); } ++ int duration(void) const; ++ bool repTimer(void) const { return !timer->IsSingleEvent(); } ++ bool isDummy(void) const { return item == NULL; } ++ const cTimer *Timer(void) const { return timer; } ++ void SetDiskStatus(char DiskStatus); ++ }; ++ ++int cTimerEntry::Compare(const cListObject &ListObject) const ++{ ++ cTimerEntry *entry = (cTimerEntry *)&ListObject; ++ int r = startTime() - entry->startTime(); ++ if (r == 0) ++ r = entry->priority() - priority(); ++ return r; ++} ++ ++int cTimerEntry::duration(void) const ++{ ++ int dur = (timer->Stop() / 100 * 60 + timer->Stop() % 100) - ++ (timer->Start() / 100 * 60 + timer->Start() % 100); ++ if (dur < 0) ++ dur += 24 * 60; ++ return dur; ++} ++ ++void cTimerEntry::SetDiskStatus(char DiskStatus) ++{ ++ if (item) ++ item->SetDiskStatus(DiskStatus); ++} ++#endif /* TIMERINFO */ ++ + // --- cMenuTimers ----------------------------------------------------------- + + class cMenuTimers : public cOsdMenu { +@@ -776,14 +987,31 @@ + eOSState Info(void); + cTimer *CurrentTimer(void); + void SetHelpKeys(void); ++#ifdef USE_TIMERINFO ++ void ActualiseDiskStatus(void); ++ bool actualiseDiskStatus; ++#endif /* TIMERINFO */ ++#ifdef USE_TIMERCMD ++ eOSState Commands(eKeys Key = kNone); ++#endif /* TIMERCMD */ + public: + cMenuTimers(void); + virtual ~cMenuTimers(); ++#ifdef USE_TIMERINFO ++ virtual void Display(void); ++#endif /* TIMERINFO */ + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuTimers"; } ++#endif /* GRAPHTFT */ + }; + + cMenuTimers::cMenuTimers(void) ++#ifdef USE_TIMERINFO ++:cOsdMenu(tr("Timers"), 3, CHNUMWIDTH, 10, 6, 6) ++#else + :cOsdMenu(tr("Timers"), 2, CHNUMWIDTH, 10, 6, 6) ++#endif /* TIMERINFO */ + { + helpKeys = -1; + for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) { +@@ -794,6 +1022,9 @@ + SetCurrent(First()); + SetHelpKeys(); + Timers.IncBeingEdited(); ++#ifdef USE_TIMERINFO ++ actualiseDiskStatus = true; ++#endif /* TIMERINFO */ + } + + cMenuTimers::~cMenuTimers() +@@ -832,7 +1063,11 @@ + timer->OnOff(); + timer->SetEventFromSchedule(); + RefreshCurrent(); ++#ifdef USE_TIMERINFO ++ Display(); ++#else + DisplayCurrent(true); ++#endif /* TIMERINFO */ + if (timer->FirstDay()) + isyslog("timer %s first day set to %s", *timer->ToDescr(), *timer->PrintFirstDay()); + else +@@ -891,6 +1126,117 @@ + return osContinue; + } + ++#ifdef USE_TIMERCMD ++#define CHECK_2PTR_NULL(x_,y_) ((x_)? ((y_)? y_:""):"") ++ ++eOSState cMenuTimers::Commands(eKeys Key) ++{ ++ if (HasSubMenu() || Count() == 0) ++ return osContinue; ++ cTimer *ti = CurrentTimer(); ++ if (ti) { ++ const cEvent *pEvent = ti->Event(); ++ int iRecNumber=0; ++ ++ if (!pEvent) { ++ Timers.SetEvents(); ++ pEvent = ti->Event(); ++ } ++ if (pEvent) { ++ // create a dummy recording to get the real filename ++ cRecording *rc_dummy = new cRecording(ti, pEvent); ++ Recordings.Load(); ++ cRecording *rc = Recordings.GetByName(rc_dummy->FileName()); ++ ++ delete rc_dummy; ++ if (rc) ++ iRecNumber=rc->Index() + 1; ++ } ++ // TODO: Geht das so...? ++ // Parameter format TimerNumber 'ChannelId' Start Stop 'Titel' 'Subtitel' 'file' RecNumer ++ // 1 2 3 4 5 6 7 8 ++ cString parameter = cString::sprintf("%d '%s' %d %d '%s' '%s' '%s' %d", ti->Index(), ++ *ti->Channel()->GetChannelID().ToString(), ++ (int)ti->StartTime(), ++ (int)ti->StopTime(), ++ CHECK_2PTR_NULL(pEvent, pEvent->Title()), ++ CHECK_2PTR_NULL(pEvent, pEvent->ShortText()), ++ ti->File(), ++ iRecNumber); ++ isyslog("timercmd: %s", *parameter); ++ cMenuCommands *menu; ++ eOSState state = AddSubMenu(menu = new cMenuCommands(tr("Timer commands"), &TimerCommands, parameter)); ++ if (Key != kNone) ++ state = menu->ProcessKey(Key); ++ return state; ++ } ++ return osContinue; ++} ++#endif /* TIMERCMD */ ++ ++#ifdef USE_TIMERINFO ++void cMenuTimers::ActualiseDiskStatus(void) ++{ ++ if (!actualiseDiskStatus || !Count()) ++ return; ++ ++ // compute free disk space ++ int freeMB, freeMinutes, runshortMinutes; ++ VideoDiskSpace(&freeMB); ++ freeMinutes = int(double(freeMB) * 1.1 / MB_PER_MINUTE); // overestimate by 10 percent ++ runshortMinutes = freeMinutes / 5; // 20 Percent ++ ++ // fill entries list ++ cTimerEntry *entry; ++ cList entries; ++ for (cOsdItem *item = First(); item; item = Next(item)) ++ entries.Add(new cTimerEntry((cMenuTimerItem *)item)); ++ ++ // search last start time ++ time_t last = 0; ++ for (entry = entries.First(); entry; entry = entries.Next(entry)) ++ last = max(entry->startTime(), last); ++ ++ // add entries for repeating timers ++ for (entry = entries.First(); entry; entry = entries.Next(entry)) ++ if (entry->repTimer() && !entry->isDummy()) ++ for (time_t start = cTimer::IncDay(entry->startTime(), 1); ++ start <= last; ++ start = cTimer::IncDay(start, 1)) ++ if (entry->Timer()->DayMatches(start)) ++ entries.Add(new cTimerEntry(entry->Timer(), start)); ++ ++ // set the disk-status ++ entries.Sort(); ++ for (entry = entries.First(); entry; entry = entries.Next(entry)) { ++ char status = ' '; ++ if (entry->active()) { ++ freeMinutes -= entry->duration(); ++ status = freeMinutes > runshortMinutes ? '+' : freeMinutes > 0 ? '~' /* ± 177 +/- */ : '-'; ++ } ++ entry->SetDiskStatus(status); ++#ifdef DEBUG_TIMER_INFO ++ dsyslog("timer-info: %c | %d | %s | %s | %3d | %+5d -> %+5d", ++ status, ++ entry->startTime(), ++ entry->active() ? "aktiv " : "n.akt.", ++ entry->repTimer() ? entry->isDummy() ? " dummy " : "mehrmalig" : "einmalig ", ++ entry->duration(), ++ entry->active() ? freeMinutes + entry->duration() : freeMinutes, ++ freeMinutes); ++#endif ++ } ++ ++ actualiseDiskStatus = false; ++} ++ ++void cMenuTimers::Display(void) ++{ ++ ActualiseDiskStatus(); ++ cOsdMenu::Display(); ++} ++#endif /* TIMERINFO */ ++ + eOSState cMenuTimers::ProcessKey(eKeys Key) + { + int TimerNumber = HasSubMenu() ? Count() : -1; +@@ -899,18 +1245,40 @@ + if (state == osUnknown) { + switch (Key) { + case kOk: return Edit(); ++#ifdef USE_TIMERINFO ++ case kRed: actualiseDiskStatus = true; ++ state = OnOff(); break; // must go through SetHelpKeys()! ++#else + case kRed: state = OnOff(); break; // must go through SetHelpKeys()! ++#endif /* TIMERINFO */ + case kGreen: return New(); ++#ifdef USE_TIMERINFO ++ case kYellow: actualiseDiskStatus = true; ++ state = Delete(); break; ++#else + case kYellow: state = Delete(); break; ++#endif /* TIMERINFO */ + case kInfo: + case kBlue: return Info(); + break; ++#ifdef USE_TIMERCMD ++ case k1...k9: return Commands(Key); ++ case k0: return (TimerCommands.Count()? Commands():osContinue); ++#endif /* TIMERCMD */ + default: break; + } + } ++#ifdef USE_TIMERINFO ++ if (TimerNumber >= 0 && !HasSubMenu()) { ++ if (Timers.Get(TimerNumber)) // a newly created timer was confirmed with Ok ++ Add(new cMenuTimerItem(Timers.Get(TimerNumber)), true); ++ Sort(); ++ actualiseDiskStatus = true; ++#else + if (TimerNumber >= 0 && !HasSubMenu() && Timers.Get(TimerNumber)) { + // a newly created timer was confirmed with Ok + Add(new cMenuTimerItem(Timers.Get(TimerNumber)), true); ++#endif /* TIMERINFO */ + Display(); + } + if (Key != kNone) +@@ -940,6 +1308,9 @@ + { + cOsdMenu::Display(); + DisplayMenu()->SetEvent(event); ++#ifdef USE_GRAPHTFT ++ cStatus::MsgOsdSetEvent(event); ++#endif /* GRAPHTFT */ + if (event->Description()) + cStatus::MsgOsdTextItem(event->Description()); + } +@@ -987,7 +1358,12 @@ + const cChannel *channel; + bool withDate; + int timerMatch; ++#ifdef USE_LIEMIEXT ++ bool withBar; ++ cMenuScheduleItem(const cEvent *Event, cChannel *Channel = NULL, bool WithDate = false, bool WithBar = false); ++#else + cMenuScheduleItem(const cEvent *Event, cChannel *Channel = NULL, bool WithDate = false); ++#endif /* LIEMIEXT */ + static void SetSortMode(eScheduleSortMode SortMode) { sortMode = SortMode; } + static void IncSortMode(void) { sortMode = eScheduleSortMode((sortMode == ssmAllAll) ? ssmAllThis : sortMode + 1); } + static eScheduleSortMode SortMode(void) { return sortMode; } +@@ -997,12 +1373,19 @@ + + cMenuScheduleItem::eScheduleSortMode cMenuScheduleItem::sortMode = ssmAllThis; + ++#ifdef USE_LIEMIEXT ++cMenuScheduleItem::cMenuScheduleItem(const cEvent *Event, cChannel *Channel, bool WithDate, bool WithBar) ++#else + cMenuScheduleItem::cMenuScheduleItem(const cEvent *Event, cChannel *Channel, bool WithDate) ++#endif /* LIEMIEXT */ + { + event = Event; + channel = Channel; + withDate = WithDate; + timerMatch = tmNone; ++#ifdef USE_LIEMIEXT ++ withBar = WithBar; ++#endif /* LIEMIEXT */ + Update(true); + } + +@@ -1017,7 +1400,29 @@ + return r; + } + ++#ifdef USE_LIEMIEXT ++static const char * const ProgressBar[7] = ++{ ++ "[ ]", ++ "[| ]", ++ "[|| ]", ++ "[||| ]", ++ "[|||| ]", ++ "[||||| ]", ++ "[||||||]" ++}; ++#endif /* LIEMIEXT */ ++ ++#ifdef USE_WAREAGLEICON ++static const char *TimerMatchChars[9] = ++{ ++ " ", "t", "T", ++ ICON_BLANK, ICON_CLOCK_UH, ICON_CLOCK, ++ ICON_BLANK_UTF8, ICON_CLOCK_UH_UTF8, ICON_CLOCK_UTF8 ++}; ++#else + static const char *TimerMatchChars = " tT"; ++#endif /* WAREAGLEICON */ + + bool cMenuScheduleItem::Update(bool Force) + { +@@ -1026,17 +1431,54 @@ + Timers.GetMatch(event, &timerMatch); + if (Force || timerMatch != OldTimerMatch) { + cString buffer; ++#ifdef USE_WAREAGLEICON ++ const char *t = Setup.WarEagleIcons ? IsLangUtf8() ? TimerMatchChars[timerMatch+6] : TimerMatchChars[timerMatch+3] : TimerMatchChars[timerMatch]; ++ const char *v = event->Vps() && (event->Vps() - event->StartTime()) ? Setup.WarEagleIcons ? IsLangUtf8() ? ICON_VPS_UTF8 : ICON_VPS : "V" : " "; ++ const char *r = event->SeenWithin(30) && event->IsRunning() ? Setup.WarEagleIcons ? IsLangUtf8() ? ICON_RUNNING_UTF8 : ICON_RUNNING : "*" : " "; ++#else + char t = TimerMatchChars[timerMatch]; + char v = event->Vps() && (event->Vps() - event->StartTime()) ? 'V' : ' '; + char r = event->SeenWithin(30) && event->IsRunning() ? '*' : ' '; ++#endif /* WAREAGLEICON */ + const char *csn = channel ? channel->ShortName(true) : NULL; + cString eds = event->GetDateString(); + if (channel && withDate) ++#ifdef USE_WAREAGLEICON ++ buffer = cString::sprintf("%d\t%.*s\t%.*s\t%s\t%s%s%s\t%s", channel->Number(), Utf8SymChars(csn, 6), csn, Utf8SymChars(eds, 6), *eds, *event->GetTimeString(), t, v, r, event->Title()); ++#else + buffer = cString::sprintf("%d\t%.*s\t%.*s\t%s\t%c%c%c\t%s", channel->Number(), Utf8SymChars(csn, 6), csn, Utf8SymChars(eds, 6), *eds, *event->GetTimeString(), t, v, r, event->Title()); ++#endif /* WAREAGLEICON */ + else if (channel) ++#ifdef USE_LIEMIEXT ++ if (Setup.ShowProgressBar && withBar) { ++ int progress = (int)roundf( (float)(time(NULL) - event->StartTime()) / (float)(event->Duration()) * 6.0 ); ++ if (progress < 0) progress = 0; ++ else if (progress > 6) progress = 6; ++#ifdef USE_WAREAGLEICON ++ buffer = cString::sprintf("%d\t%.*s\t%s\t%s\t%s%s%s\t%s", channel->Number(), Utf8SymChars(csn, 6), csn, *event->GetTimeString(), ProgressBar[progress], t, v, r, event->Title()); ++#else ++ buffer = cString::sprintf("%d\t%.*s\t%s\t%s\t%c%c%c\t%s", channel->Number(), Utf8SymChars(csn, 6), csn, *event->GetTimeString(), ProgressBar[progress], t, v, r, event->Title()); ++#endif /* WAREAGLEICON */ ++ } ++ else ++#ifdef USE_WAREAGLEICON ++ buffer = cString::sprintf("%d\t%.*s\t%s\t%s%s%s\t%s", channel->Number(), Utf8SymChars(csn, 6), csn, *event->GetTimeString(), t, v, r, event->Title()); ++#else + buffer = cString::sprintf("%d\t%.*s\t%s\t%c%c%c\t%s", channel->Number(), Utf8SymChars(csn, 6), csn, *event->GetTimeString(), t, v, r, event->Title()); ++#endif /* WAREAGLEICON */ ++#else ++#ifdef USE_WAREAGLEICON ++ buffer = cString::sprintf("%d\t%.*s\t%s\t%s%s%s\t%s", channel->Number(), Utf8SymChars(csn, 6), csn, *event->GetTimeString(), t, v, r, event->Title()); ++#else ++ buffer = cString::sprintf("%d\t%.*s\t%s\t%c%c%c\t%s", channel->Number(), Utf8SymChars(csn, 6), csn, *event->GetTimeString(), t, v, r, event->Title()); ++#endif /* WAREAGLEICON */ ++#endif /* LIEMIEXT */ + else ++#ifdef USE_WAREAGLEICON ++ buffer = cString::sprintf("%.*s\t%s\t%s%s%s\t%s", Utf8SymChars(eds, 6), *eds, *event->GetTimeString(), t, v, r, event->Title()); ++#else + buffer = cString::sprintf("%.*s\t%s\t%c%c%c\t%s", Utf8SymChars(eds, 6), *eds, *event->GetTimeString(), t, v, r, event->Title()); ++#endif /* WAREAGLEICON */ + SetText(buffer); + result = true; + } +@@ -1062,13 +1504,21 @@ + static void SetCurrentChannel(int ChannelNr) { currentChannel = ChannelNr; } + static const cEvent *ScheduleEvent(void); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return now ? "MenuWhatsOnNow" : "MenuWhatsOnNext"; } ++ virtual void Display(void); ++#endif /* GRAPHTFT */ + }; + + int cMenuWhatsOn::currentChannel = 0; + const cEvent *cMenuWhatsOn::scheduleEvent = NULL; + + cMenuWhatsOn::cMenuWhatsOn(const cSchedules *Schedules, bool Now, int CurrentChannelNr) ++#ifdef USE_LIEMIEXT ++:cOsdMenu(Now ? tr("What's on now?") : tr("What's on next?"), CHNUMWIDTH, 7, 6, 4, 4) ++#else + :cOsdMenu(Now ? tr("What's on now?") : tr("What's on next?"), CHNUMWIDTH, 7, 6, 4) ++#endif /* LIEMIEXT */ + { + now = Now; + helpKeys = -1; +@@ -1080,7 +1530,11 @@ + if (Schedule) { + const cEvent *Event = Now ? Schedule->GetPresentEvent() : Schedule->GetFollowingEvent(); + if (Event) ++#ifdef USE_LIEMIEXT ++ Add(new cMenuScheduleItem(Event, Channel, false, Now), Channel->Number() == CurrentChannelNr); ++#else + Add(new cMenuScheduleItem(Event, Channel), Channel->Number() == CurrentChannelNr); ++#endif /* LIEMIEXT */ + } + } + } +@@ -1089,6 +1543,19 @@ + SetHelpKeys(); + } + ++#ifdef USE_GRAPHTFT ++void cMenuWhatsOn::Display(void) ++{ ++ cOsdMenu::Display(); ++ ++ if (Count() > 0) { ++ int ni = 0; ++ for (cOsdItem *item = First(); item; item = Next(item)) ++ cStatus::MsgOsdEventItem(((cMenuScheduleItem*)item)->event, item->Text(), ni++, Count()); ++ } ++} ++#endif /* GRAPHTFT */ ++ + bool cMenuWhatsOn::Update(void) + { + bool result = false; +@@ -1229,6 +1696,10 @@ + cMenuSchedule(void); + virtual ~cMenuSchedule(); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuSchedule"; } ++ virtual void Display(void); ++#endif /* GRAPHTFT */ + }; + + cMenuSchedule::cMenuSchedule(void) +@@ -1254,6 +1725,19 @@ + cMenuWhatsOn::ScheduleEvent(); // makes sure any posted data is cleared + } + ++#ifdef USE_GRAPHTFT ++void cMenuSchedule::Display(void) ++{ ++ cOsdMenu::Display(); ++ ++ if (Count() > 0) { ++ int ni = 0; ++ for (cOsdItem *item = First(); item; item = Next(item)) ++ cStatus::MsgOsdEventItem(((cMenuScheduleItem*)item)->event, item->Text(), ni++, Count()); ++ } ++} ++#endif /* GRAPHTFT */ ++ + void cMenuSchedule::PrepareScheduleAllThis(const cEvent *Event, const cChannel *Channel) + { + Clear(); +@@ -1487,6 +1971,7 @@ + + // --- cMenuCommands --------------------------------------------------------- + ++#ifndef USE_TIMERCMD + class cMenuCommands : public cOsdMenu { + private: + cCommands *commands; +@@ -1496,7 +1981,11 @@ + cMenuCommands(const char *Title, cCommands *Commands, const char *Parameters = NULL); + virtual ~cMenuCommands(); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuCommands"; } ++#endif /* GRAPHTFT */ + }; ++#endif /* TIMERCMD */ + + cMenuCommands::cMenuCommands(const char *Title, cCommands *Commands, const char *Parameters) + :cOsdMenu(Title) +@@ -1518,6 +2007,12 @@ + cCommand *command = commands->Get(Current()); + if (command) { + bool confirmed = true; ++#ifdef USE_CMDSUBMENU ++ if (command->hasChilds()) { ++ AddSubMenu(new cMenuCommands(command->Title(), command->getChilds(), parameters)); ++ return osContinue; ++ } ++#endif /* CMDSUBMENU */ + if (command->Confirm()) + confirmed = Interface->Confirm(cString::sprintf("%s?", command->Title())); + if (confirmed) { +@@ -1568,6 +2063,9 @@ + cMenuCam(cCamSlot *CamSlot); + virtual ~cMenuCam(); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuCam"; } ++#endif /* GRAPHTFT */ + }; + + cMenuCam::cMenuCam(cCamSlot *CamSlot) +@@ -1747,6 +2245,9 @@ + cMenuRecording(const cRecording *Recording, bool WithButtons = false); + virtual void Display(void); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuRecording"; } ++#endif /* GRAPHTFT */ + }; + + cMenuRecording::cMenuRecording(const cRecording *Recording, bool WithButtons) +@@ -1762,6 +2263,9 @@ + { + cOsdMenu::Display(); + DisplayMenu()->SetRecording(recording); ++#ifdef USE_GRAPHTFT ++ cStatus::MsgOsdSetRecording(recording); ++#endif /* GRAPHTFT */ + if (recording->Info()->Description()) + cStatus::MsgOsdTextItem(recording->Info()->Description()); + } +@@ -1822,7 +2326,11 @@ + fileName = strdup(Recording->FileName()); + name = NULL; + totalEntries = newEntries = 0; ++#ifdef USE_LIEMIEXT ++ SetText(Recording->Title('\t', true, Level, false)); ++#else + SetText(Recording->Title('\t', true, Level)); ++#endif /* LIEMIEXT */ + if (*Text() == '\t') + name = strdup(Text() + 2); // 'Text() + 2' to skip the two '\t' + } +@@ -1838,13 +2346,196 @@ + totalEntries++; + if (New) + newEntries++; ++#ifdef USE_LIEMIEXT ++#ifdef USE_WAREAGLEICON ++ switch (Setup.ShowRecTime + Setup.ShowRecDate + Setup.ShowRecLength) { ++ case 0: ++ if (Setup.WarEagleIcons) ++ SetText(cString::sprintf("%s %s", IsLangUtf8() ? ICON_FOLDER_UTF8 : ICON_FOLDER, name)); ++ else ++ SetText(cString::sprintf("%s", name)); ++ break; ++ case 1: ++ if (Setup.WarEagleIcons) ++ SetText(cString::sprintf("%s %d\t%s", IsLangUtf8() ? ICON_FOLDER_UTF8 : ICON_FOLDER, totalEntries, name)); ++ else ++ SetText(cString::sprintf("%d\t%s", totalEntries, name)); ++ break; ++ case 2: ++ default: ++ if (Setup.WarEagleIcons) ++ SetText(cString::sprintf("%s (%d/%d)\t%s", IsLangUtf8() ? ICON_FOLDER_UTF8 : ICON_FOLDER, totalEntries, newEntries, name)); ++ else ++ SetText(cString::sprintf("%d\t%d\t%s", totalEntries, newEntries, name)); ++ break; ++ case 3: ++ if (Setup.WarEagleIcons) ++ SetText(cString::sprintf("%s (%d/%d)\t\t%s", IsLangUtf8() ? ICON_FOLDER_UTF8 : ICON_FOLDER, totalEntries, newEntries, name)); ++ else ++ SetText(cString::sprintf("%d\t%d\t\t%s", totalEntries, newEntries, name)); ++ break; ++ } ++#else ++ switch (Setup.ShowRecTime + Setup.ShowRecDate + Setup.ShowRecLength) { ++ case 0: ++ SetText(cString::sprintf("%s", name)); ++ break; ++ case 1: ++ SetText(cString::sprintf("%d\t%s", totalEntries, name)); ++ break; ++ case 2: ++ default: ++ SetText(cString::sprintf("%d\t%d\t%s", totalEntries, newEntries, name)); ++ break; ++ case 3: ++ SetText(cString::sprintf("%d\t%d\t\t%s", totalEntries, newEntries, name)); ++ break; ++ } ++#endif /* WAREAGLEICON */ ++#else ++#ifdef USE_WAREAGLEICON ++ if (Setup.WarEagleIcons) ++ SetText(cString::sprintf("%s (%d/%d)\t%s", IsLangUtf8() ? ICON_FOLDER_UTF8 : ICON_FOLDER, totalEntries, newEntries, name)); ++ else ++#endif /* WAREAGLEICON */ + SetText(cString::sprintf("%d\t%d\t%s", totalEntries, newEntries, name)); ++#endif /* LIEMIEXT */ ++} ++ ++#ifdef USE_LIEMIEXT ++// --- cMenuRenameRecording -------------------------------------------------- ++ ++class cMenuRenameRecording : public cOsdMenu { ++private: ++ char name[MaxFileName]; ++ char path[MaxFileName]; ++ cOsdItem *marksItem, *resumeItem; ++ bool isResume, isMarks; ++ cRecording *recording; ++public: ++ cMenuRenameRecording(cRecording *Recording); ++ virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuRenameRecording"; } ++#endif /* GRAPHTFT */ ++}; ++ ++cMenuRenameRecording::cMenuRenameRecording(cRecording *Recording) ++:cOsdMenu(tr("Rename recording"), 12) ++{ ++ cMarks marks; ++ ++ recording = Recording; ++ ++ const char* pname = strrchr(recording->Name(), '~'); ++ if (pname) { ++ Utf8Strn0Cpy(name, pname + 1, sizeof(name)); ++ Utf8Strn0Cpy(path, recording->Name(), sizeof(path)); ++ char *ppath = strrchr(path, '~'); ++ if (ppath) ++ ppath[0] = 0; ++ } ++ else { ++ Utf8Strn0Cpy(name, recording->Name(), sizeof(name)); ++ Utf8Strn0Cpy(path, "", sizeof(path)); ++ } ++ Add(new cMenuEditStrItem(tr("Name"), name, sizeof(name), tr(FileNameChars))); ++ Add(new cMenuEditRecPathItem(tr("Path"), path, sizeof(path) )); ++ ++ Add(new cOsdItem("", osUnknown, false)); ++ ++ Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Date"), *DayDateTime(recording->start)), osUnknown, false)); ++ ++ cChannel *channel = Channels.GetByChannelID(((cRecordingInfo *)recording->Info())->ChannelID()); ++ if (channel) ++ Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Channel"), *ChannelString(channel, 0)), osUnknown, false)); ++ ++ int recLen = cIndexFile::Length(recording->FileName(), recording->IsPesRecording()); ++ if (recLen >= 0) ++ Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Length"), *IndexToHMSF(recLen, false, recording->FramesPerSecond())), osUnknown, false)); ++ else ++ recLen = 0; ++ ++ int dirSize = DirSizeMB(recording->FileName()); ++ double seconds = recLen / recording->FramesPerSecond(); ++ cString bitRate = seconds ? cString::sprintf(" (%.2f MBit/s)", 8.0 * dirSize / seconds) : cString(""); ++ Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Format"), recording->IsPesRecording() ? tr("PES") : tr("TS")), osUnknown, false)); ++ Add(new cOsdItem((dirSize > 9999) ? cString::sprintf("%s:\t%.2f GB%s", tr("Size"), dirSize / 1024.0, *bitRate) : cString::sprintf("%s:\t%d MB%s", tr("Size"), dirSize, *bitRate), osUnknown, false)); ++ ++ Add(new cOsdItem("", osUnknown, false)); ++ ++ isMarks = marks.Load(recording->FileName()) && marks.Count(); ++ marksItem = new cOsdItem(tr("Delete marks information?"), osUser1, isMarks); ++ Add(marksItem); ++ ++ cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording()); ++ isResume = (ResumeFile.Read() != -1); ++ resumeItem = new cOsdItem(tr("Delete resume information?"), osUser2, isResume); ++ Add(resumeItem); ++} ++ ++eOSState cMenuRenameRecording::ProcessKey(eKeys Key) ++{ ++ eOSState state = cOsdMenu::ProcessKey(Key); ++ ++ if (state == osUnknown) { ++ if (Key == kOk) { ++ char buffer[MaxFileName]; ++ if (Utf8StrLen(path)) ++ snprintf(buffer, sizeof(buffer), "%s~%s", path, name); ++ else ++ snprintf(buffer, sizeof(buffer), "%s", name); ++ if (recording->Rename(buffer)) { ++ Recordings.ChangeState(); ++ Recordings.TouchUpdate(); ++ return osRecordings; ++ } ++ else ++ Skins.Message(mtError, tr("Error while accessing recording!")); ++ } ++ return osContinue; ++ } ++ else if (state == osUser1) { ++ if (isMarks && Interface->Confirm(tr("Delete marks information?"))) { ++ cMarks marks; ++ marks.Load(recording->FileName()); ++ cMark *mark = marks.First(); ++ while (mark) { ++ cMark *nextmark = marks.Next(mark); ++ marks.Del(mark); ++ mark = nextmark; ++ } ++ marks.Save(); ++ isMarks = false; ++ marksItem->SetSelectable(isMarks); ++ SetCurrent(First()); ++ Display(); ++ } ++ return osContinue; ++ } ++ else if (state == osUser2) { ++ if (isResume && Interface->Confirm(tr("Delete resume information?"))) { ++ cResumeFile ResumeFile(recording->FileName(), recording->IsPesRecording()); ++ ResumeFile.Delete(); ++ isResume = false; ++ resumeItem->SetSelectable(isResume); ++ SetCurrent(First()); ++ Display(); ++ } ++ return osContinue; + } ++ return state; ++} ++#endif /* LIEMIEXT */ + + // --- cMenuRecordings ------------------------------------------------------- + + cMenuRecordings::cMenuRecordings(const char *Base, int Level, bool OpenSubMenus) ++#ifdef USE_LIEMIEXT ++:cOsdMenu(Base ? Base : tr("Recordings"), 9, 7, 7) ++#else + :cOsdMenu(Base ? Base : tr("Recordings"), 9, 7) ++#endif /* LIEMIEXT */ + { + base = Base ? strdup(Base) : NULL; + level = Setup.RecordingDirs ? Level : -1; +@@ -1921,7 +2612,12 @@ + for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) { + if (!base || (strstr(recording->Name(), base) == recording->Name() && recording->Name()[strlen(base)] == '~')) { + cMenuRecordingItem *Item = new cMenuRecordingItem(recording, level); ++#ifdef USE_PINPLUGIN ++ if ((*Item->Text() && (!LastItem || strcmp(Item->Text(), LastItemText) != 0)) ++ && (!cStatus::MsgReplayProtected(GetRecording(Item), Item->Name(), base, Item->IsDirectory(), true))) { ++#else + if (*Item->Text() && (!LastItem || strcmp(Item->Text(), LastItemText) != 0)) { ++#endif /* PINPLUGIN */ + Add(Item); + LastItem = Item; + free(LastItemText); +@@ -1971,13 +2667,43 @@ + { + cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); + if (ri) { ++#ifdef USE_PINPLUGIN ++ if (cStatus::MsgReplayProtected(GetRecording(ri), ri->Name(), base, ri->IsDirectory()) == true) ++ return osContinue; ++#endif /* PINPLUGIN */ + if (ri->IsDirectory()) + Open(); + else { + cRecording *recording = GetRecording(ri); + if (recording) { ++#ifdef USE_DVDARCHIVE ++ int mountRet = MOUNT_DVD_REPLAY; ++ if (recording->IsOnlyOnDvd()) { ++ mountRet = recording->MountDvd(); ++ } ++ if (mountRet == MOUNT_DVD_REPLAY) { ++ cReplayControl::SetRecording(recording->FileName(), recording->Title()); ++ return osReplay; ++ } ++ else if (mountRet == MOUNT_DVD_LAUNCH_DVD_PLUGIN) { ++ //launch DVD plugin here ++ cPlugin *p = cPluginManager::GetPlugin("dvd"); ++ cOsdObject *osd = NULL; ++ if (p) { ++ osd = p->MainMenuAction(); ++ delete osd; ++ osd = NULL; ++ return osEnd; ++ } ++ else { ++ Skins.Message(mtError, tr("DVD plugin is not installed!")); ++ Skins.Flush(); ++ } ++ } ++#else + cReplayControl::SetRecording(recording->FileName(), recording->Title()); + return osReplay; ++#endif /* DVDARCHIVE */ + } + } + } +@@ -2078,12 +2804,34 @@ + return osContinue; + } + ++#ifdef USE_LIEMIEXT ++eOSState cMenuRecordings::Rename(void) ++{ ++ if (HasSubMenu() || Count() == 0) ++ return osContinue; ++ cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); ++ if (ri && !ri->IsDirectory()) { ++ cRecording *recording = GetRecording(ri); ++ if (recording) ++ return AddSubMenu(new cMenuRenameRecording(recording)); ++ } ++ return osContinue; ++} ++#endif /* LIEMIEXT */ ++ + eOSState cMenuRecordings::ProcessKey(eKeys Key) + { + bool HadSubMenu = HasSubMenu(); + eOSState state = cOsdMenu::ProcessKey(Key); + + if (state == osUnknown) { ++#ifdef USE_SORTRECORDS ++ const char *RecordingsSortModeTexts[MAXSORTMODES]; ++ RecordingsSortModeTexts[0] = tr("main dir alphabetically, subdirs flexible"); ++ RecordingsSortModeTexts[1] = tr("main dir by date, subdirs flexible"); ++ RecordingsSortModeTexts[2] = tr("all alphabetically"); ++ RecordingsSortModeTexts[3] = tr("all by date"); ++#endif /* SORTRECORDS */ + switch (Key) { + case kPlay: + case kOk: return Play(); +@@ -2092,7 +2840,26 @@ + case kYellow: return Delete(); + case kInfo: + case kBlue: return Info(); ++#ifdef USE_SORTRECORDS ++ case k0: Setup.RecordingsSortMode = ++Setup.RecordingsSortMode % MAXSORTMODES; ++ Set(true); ++ Skins.Message(mtStatus, cString::sprintf("%s %d: %s", tr("Sorting"), Setup.RecordingsSortMode, RecordingsSortModeTexts[Setup.RecordingsSortMode])); ++ return osContinue; ++ case k1...k7: return Commands(Key); ++ case k8: return Rename(); ++ case k9: Recordings.ToggleSortOrder(); ++ Set(true); ++ return osContinue; ++#elif defined (USE_LIEMIEXT) ++ case k0: DirOrderState = !DirOrderState; ++ Set(true); ++ return osContinue; ++ case k8: return Rename(); ++ case k9: ++ case k1...k7: return Commands(Key); ++#else + case k1...k9: return Commands(Key); ++#endif /* LIEMIEXT & SORTRECORDS */ + case kNone: if (Recordings.StateChanged(recordingsState)) + Set(true); + break; +@@ -2142,6 +2909,9 @@ + class cMenuSetupOSD : public cMenuSetupBase { + private: + const char *useSmallFontTexts[3]; ++#ifdef USE_LIEMIEXT ++ const char *mainMenuTitle[MAXMAINMENUTITLE]; ++#endif /* LIEMIEXT */ + int osdLanguageIndex; + int numSkins; + int originalSkinIndex; +@@ -2157,6 +2927,9 @@ + cMenuSetupOSD(void); + virtual ~cMenuSetupOSD(); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuSetupOsd"; } ++#endif /* GRAPHTFT */ + }; + + cMenuSetupOSD::cMenuSetupOSD(void) +@@ -2192,12 +2965,21 @@ + useSmallFontTexts[0] = tr("never"); + useSmallFontTexts[1] = tr("skin dependent"); + useSmallFontTexts[2] = tr("always"); ++#ifdef USE_LIEMIEXT ++ mainMenuTitle[0]=tr("default"); ++ mainMenuTitle[1]=tr("VDR - text"); ++ mainMenuTitle[2]=tr("text"); ++ mainMenuTitle[3]=tr("VDR - version"); ++#endif /* LIEMIEXT */ + Clear(); + SetSection(tr("OSD")); + Add(new cMenuEditStraItem(tr("Setup.OSD$Language"), &osdLanguageIndex, I18nNumLanguagesWithLocale(), &I18nLanguages()->At(0))); + Add(new cMenuEditStraItem(tr("Setup.OSD$Skin"), &skinIndex, numSkins, skinDescriptions)); + if (themes.NumThemes()) + Add(new cMenuEditStraItem(tr("Setup.OSD$Theme"), &themeIndex, themes.NumThemes(), themes.Descriptions())); ++#ifdef USE_WAREAGLEICON ++ Add(new cMenuEditBoolItem(tr("Setup.OSD$WarEagle icons"), &data.WarEagleIcons)); ++#endif /* WAREAGLEICON */ + Add(new cMenuEditPrcItem( tr("Setup.OSD$Left (%)"), &data.OSDLeftP, 0.0, 0.5)); + Add(new cMenuEditPrcItem( tr("Setup.OSD$Top (%)"), &data.OSDTopP, 0.0, 0.5)); + Add(new cMenuEditPrcItem( tr("Setup.OSD$Width (%)"), &data.OSDWidthP, 0.5, 1.0)); +@@ -2219,6 +3001,24 @@ + Add(new cMenuEditBoolItem(tr("Setup.OSD$Scroll wraps"), &data.MenuScrollWrap)); + Add(new cMenuEditBoolItem(tr("Setup.OSD$Menu key closes"), &data.MenuKeyCloses)); + Add(new cMenuEditBoolItem(tr("Setup.OSD$Recording directories"), &data.RecordingDirs)); ++#ifdef USE_LIEMIEXT ++ Add(new cMenuEditStraItem(tr("Setup.OSD$Main menu title"), &data.MainMenuTitle, MAXMAINMENUTITLE, mainMenuTitle)); ++ if (data.MainMenuTitle == 1 || data.MainMenuTitle == 2) ++ Add(new cMenuEditStrItem(tr("Setup.OSD$- Text"), data.CustomMainMenuTitle, sizeof(data.CustomMainMenuTitle))); ++ Add(new cMenuEditBoolItem(tr("Setup.OSD$Main menu command position"), &data.MenuCmdPosition, tr("bottom"), tr("top"))); ++#endif /* LIEMIEXT */ ++#ifdef USE_VALIDINPUT ++ Add(new cMenuEditBoolItem(tr("Setup.OSD$Show valid input"), &data.ShowValidInput)); ++#endif /* VALIDINPUT */ ++#ifdef USE_SOFTOSD ++ Add(new cMenuEditBoolItem(tr("Setup.OSD$Use SoftOSD"), &data.UseSoftOsd)); ++ if (data.UseSoftOsd) { ++ Add(new cMenuEditIntItem( tr("Setup.OSD$SoftOSD Rate"), &data.SoftOsdRate, 10, 100)); ++ Add(new cMenuEditIntItem( tr("Setup.OSD$SoftOSD FadeIn Steps"), &data.SoftOsdFadeinSteps, 1, 100)); ++ Add(new cMenuEditIntItem( tr("Setup.OSD$SoftOSD FadeOut Steps"), &data.SoftOsdFadeoutSteps, 1, 100)); ++ Add(new cMenuEditBoolItem(tr("Setup.OSD$SoftOSD Palette Only"), &data.SoftOsdPaletteOnly)); ++ } ++#endif /* SOFTOSD */ + SetCurrent(Get(current)); + Display(); + } +@@ -2259,6 +3059,12 @@ + + int oldSkinIndex = skinIndex; + int oldOsdLanguageIndex = osdLanguageIndex; ++#ifdef USE_LIEMIEXT ++ int oldMainMenuTitle = data.MainMenuTitle; ++#endif /* LIEMIEXT */ ++#ifdef USE_SOFTOSD ++ bool newUseSoftOsd = data.UseSoftOsd; ++#endif /* SOFTOSD */ + eOSState state = cMenuSetupBase::ProcessKey(Key); + + if (ModifiedAppearance) { +@@ -2283,6 +3089,25 @@ + Set(); + I18nSetLanguage(OriginalOSDLanguage); + } ++ ++#ifdef USE_LIEMIEXT ++ if (data.MainMenuTitle != oldMainMenuTitle) ++ Set(); ++#endif /* LIEMIEXT */ ++#ifdef USE_SOFTOSD ++ if (data.UseSoftOsd != newUseSoftOsd) ++ Set(); ++#endif /* SOFTOSD */ ++#ifdef USE_CMDRECCMDI18N ++ if (Key == kOk) { ++ // try to load translated command files if available, otherwise fallback to defaults ++ LoadCommandsI18n(Commands, AddDirectory(ConfigDirectory, "commands.conf"), true); ++ LoadCommandsI18n(RecordingCommands, AddDirectory(ConfigDirectory, "reccmds.conf"), true); ++#ifdef USE_TIMERCMD ++ LoadCommandsI18n(TimerCommands, AddDirectory(ConfigDirectory, "timercmds.conf"), true); ++#endif /* TIMERCMD */ ++ } ++#endif /* CMDRECCMDI18N */ + return state; + } + +@@ -2290,12 +3115,18 @@ + + class cMenuSetupEPG : public cMenuSetupBase { + private: ++#ifdef USE_NOEPG ++ const char *noEPGModes[2]; ++#endif /* NOEPG */ + int originalNumLanguages; + int numLanguages; + void Setup(void); + public: + cMenuSetupEPG(void); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuSetupEpg"; } ++#endif /* GRAPHTFT */ + }; + + cMenuSetupEPG::cMenuSetupEPG(void) +@@ -2312,11 +3143,19 @@ + { + int current = Current(); + ++#ifdef USE_NOEPG ++ noEPGModes[0] = tr("blacklist"); ++ noEPGModes[1] = tr("whitelist"); ++#endif /* NOEPG */ ++ + Clear(); + + Add(new cMenuEditIntItem( tr("Setup.EPG$EPG scan timeout (h)"), &data.EPGScanTimeout)); + Add(new cMenuEditIntItem( tr("Setup.EPG$EPG bugfix level"), &data.EPGBugfixLevel, 0, MAXEPGBUGFIXLEVEL)); + Add(new cMenuEditIntItem( tr("Setup.EPG$EPG linger time (min)"), &data.EPGLinger, 0)); ++#ifdef USE_LIEMIEXT ++ Add(new cMenuEditBoolItem(tr("Setup.EPG$Show progress bar"), &data.ShowProgressBar)); ++#endif /* LIEMIEXT */ + Add(new cMenuEditBoolItem(tr("Setup.EPG$Set system time"), &data.SetSystemTime)); + if (data.SetSystemTime) + Add(new cMenuEditTranItem(tr("Setup.EPG$Use time from transponder"), &data.TimeTransponder, &data.TimeSource)); +@@ -2325,6 +3164,15 @@ + for (int i = 0; i < numLanguages; i++) + // TRANSLATORS: note the singular! + Add(new cMenuEditStraItem(tr("Setup.EPG$Preferred language"), &data.EPGLanguages[i], I18nLanguages()->Size(), &I18nLanguages()->At(0))); ++#ifdef USE_DDEPGENTRY ++ Add(new cMenuEditIntItem( tr("Setup.EPG$Period for double EPG search(min)"), &data.DoubleEpgTimeDelta)); ++ Add(new cMenuEditBoolItem(tr("Setup.EPG$Extern double Epg entry"), &data.DoubleEpgAction, tr("adjust"), tr("delete"))); ++ Add(new cMenuEditBoolItem(tr("Setup.EPG$Mix intern and extern EPG"), &data.MixEpgAction)); ++ Add(new cMenuEditBoolItem(tr("Setup.EPG$Disable running VPS event"), &data.DisableVPS)); ++#endif /* DDEPGENTRY */ ++#ifdef USE_NOEPG ++ Add(new cMenuEditStraItem(tr("Setup.EPG$Mode of noEPG-Patch"), &data.noEPGMode, 2, noEPGModes)); ++#endif /* NOEPG */ + + SetCurrent(Get(current)); + Display(); +@@ -2381,6 +3229,10 @@ + + class cMenuSetupDVB : public cMenuSetupBase { + private: ++#ifdef USE_DVBSETUP ++ const char *ChannelBlockers[7]; ++ const char *ChannelBlockerModes[4]; ++#endif /* DVBSETUP */ + int originalNumAudioLanguages; + int numAudioLanguages; + int originalNumSubtitleLanguages; +@@ -2391,6 +3243,9 @@ + public: + cMenuSetupDVB(void); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuSetupDvb"; } ++#endif /* GRAPHTFT */ + }; + + cMenuSetupDVB::cMenuSetupDVB(void) +@@ -2419,6 +3274,21 @@ + { + int current = Current(); + ++#ifdef USE_DVBSETUP ++ ChannelBlockers[0] = tr("none"); ++ ChannelBlockers[1] = tr("qam256"); ++ ChannelBlockers[2] = tr("dvb-c"); ++ ChannelBlockers[3] = tr("dvb-s"); ++ ChannelBlockers[4] = tr("blacklist"); ++ ChannelBlockers[5] = tr("whitelist"); ++ ChannelBlockers[6] = tr("all"); ++ ++ ChannelBlockerModes[0] = tr("none"); ++ ChannelBlockerModes[1] = tr("has decoder"); ++ ChannelBlockerModes[2] = tr("is primary"); ++ ChannelBlockerModes[3] = tr("has decoder + is primary"); ++#endif /* DVBSETUP */ ++ + Clear(); + + Add(new cMenuEditIntItem( tr("Setup.DVB$Primary DVB interface"), &data.PrimaryDVB, 1, cDevice::NumDevices())); +@@ -2439,6 +3309,11 @@ + Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle foreground transparency"), &data.SubtitleFgTransparency, 0, 9)); + Add(new cMenuEditIntItem( tr("Setup.DVB$Subtitle background transparency"), &data.SubtitleBgTransparency, 0, 10)); + } ++#ifdef USE_DVBSETUP ++ Add(new cMenuEditBoolItem(tr("Setup.DVB$Use AC3-Transfer Fix"), &data.DolbyTransferFix)); ++ Add(new cMenuEditStraItem(tr("Setup.DVB$Channel Blocker"), &data.ChannelBlocker, 7, ChannelBlockers)); ++ Add(new cMenuEditStraItem(tr("Setup.DVB$Channel Blocker Filter Mode"), &data.ChannelBlockerMode, 4, ChannelBlockerModes)); ++#endif /* DVBSETUP */ + + SetCurrent(Get(current)); + Display(); +@@ -2520,6 +3395,9 @@ + public: + cMenuSetupLNB(void); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuSetupLnb"; } ++#endif /* GRAPHTFT */ + }; + + cMenuSetupLNB::cMenuSetupLNB(void) +@@ -2534,6 +3412,23 @@ + + Clear(); + ++#ifdef USE_LNBSHARE ++ int numSatDevices = 0; ++ for (int i = 0; i < cDevice::NumDevices(); i++) { ++ if (cDevice::GetDevice(i)->ProvidesSource(cSource::stSat)) numSatDevices++; ++ } ++ if (numSatDevices > 1) { ++ char tmp[30]; ++ for (int i = 1; i <= cDevice::NumDevices(); i++) { ++ if (cDevice::GetDevice(i - 1)->ProvidesSource(cSource::stSat)) { ++ sprintf( tmp, tr("Setup.LNB$DVB device %d uses LNB No."), i); ++ Add(new cMenuEditIntItem( tmp, &data.CardUsesLNBnr[i - 1], 1, numSatDevices )); ++ } ++ } ++ } ++ Add(new cMenuEditBoolItem(tr("Setup.LNB$Log LNB usage"), &data.VerboseLNBlog)); ++#endif /* LNBSHARE */ ++ + Add(new cMenuEditBoolItem(tr("Setup.LNB$Use DiSEqC"), &data.DiSEqC)); + if (!data.DiSEqC) { + Add(new cMenuEditIntItem( tr("Setup.LNB$SLOF (MHz)"), &data.LnbSLOF)); +@@ -2550,6 +3445,10 @@ + int oldDiSEqC = data.DiSEqC; + eOSState state = cMenuSetupBase::ProcessKey(Key); + ++#ifdef USE_LNBSHARE ++ if (Key == kOk) cDevice::SetLNBnr(); ++#endif /* LNBSHARE */ ++ + if (Key != kNone && data.DiSEqC != oldDiSEqC) + Setup(); + return state; +@@ -2600,6 +3499,9 @@ + public: + cMenuSetupCAM(void); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuSetupCam"; } ++#endif /* GRAPHTFT */ + }; + + cMenuSetupCAM::cMenuSetupCAM(void) +@@ -2674,13 +3576,72 @@ + + class cMenuSetupRecord : public cMenuSetupBase { + private: ++#ifdef USE_SORTRECORDS ++ const char *RecordingsSortModeTexts[MAXSORTMODES]; ++#endif /* SORTRECORDS */ ++#ifdef USE_DELTIMESHIFTREC ++ const char *DelTimeshiftRecValues[3]; ++#endif /* DELTIMESHIFTREC */ + const char *pauseKeyHandlingTexts[3]; + public: + cMenuSetupRecord(void); ++#ifdef USE_DVLVIDPREFER ++ eOSState ProcessKey(eKeys key); ++ ++private: ++ void Set(void); ++ ++ int tmpNVidPrefer, ++ tmpUseVidPrefer; ++#endif /* DVLVIDPREFER */ + }; + ++#ifdef USE_DVLVIDPREFER + cMenuSetupRecord::cMenuSetupRecord(void) + { ++ Set(); ++} ++ ++eOSState cMenuSetupRecord::ProcessKey(eKeys key) ++{ ++ eOSState s = cMenuSetupBase::ProcessKey(key);; ++ ++ if (key != kNone) { ++ if (tmpNVidPrefer != data.nVidPrefer || tmpUseVidPrefer != data.UseVidPrefer) { ++ int cur = Current(); ++ ++ tmpNVidPrefer = data.nVidPrefer; ++ tmpUseVidPrefer = data.UseVidPrefer; ++ ++ Clear(); ++ Set(); ++ SetCurrent(Get(cur)); ++ Display(); ++ cMenuSetupBase::ProcessKey(kNone); ++ return osContinue; ++ } ++ } ++ return s; ++} ++ ++#else ++cMenuSetupRecord::cMenuSetupRecord(void) ++#endif /* DVLVIDPREFER */ ++#ifdef USE_DVLVIDPREFER ++void cMenuSetupRecord::Set(void) ++#endif /* DVLVIDPREFER */ ++{ ++#ifdef USE_SORTRECORDS ++ RecordingsSortModeTexts[0] = tr("main dir alphabetically, subdirs flexible"); ++ RecordingsSortModeTexts[1] = tr("main dir by date, subdirs flexible"); ++ RecordingsSortModeTexts[2] = tr("all alphabetically"); ++ RecordingsSortModeTexts[3] = tr("all by date"); ++#endif /* SORTRECORDS */ ++#ifdef USE_DELTIMESHIFTREC ++ DelTimeshiftRecValues[0] = tr("request"); ++ DelTimeshiftRecValues[1] = tr("no"); ++ DelTimeshiftRecValues[2] = tr("yes"); ++#endif /* DELTIMESHIFTREC */ + pauseKeyHandlingTexts[0] = tr("do not pause live video"); + pauseKeyHandlingTexts[1] = tr("confirm pause live video"); + pauseKeyHandlingTexts[2] = tr("pause live video"); +@@ -2693,14 +3654,61 @@ + Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"), &data.PauseKeyHandling, 3, pauseKeyHandlingTexts)); + Add(new cMenuEditIntItem( tr("Setup.Recording$Pause priority"), &data.PausePriority, 0, MAXPRIORITY)); + Add(new cMenuEditIntItem( tr("Setup.Recording$Pause lifetime (d)"), &data.PauseLifetime, 0, MAXLIFETIME)); ++#ifdef USE_DOLBYINREC ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Record Dolby Digital"), &data.UseDolbyInRecordings)); ++#endif /* DOLBYINREC */ ++#ifdef USE_DVLVIDPREFER ++ tmpNVidPrefer = data.nVidPrefer; ++ tmpUseVidPrefer = data.UseVidPrefer; ++ ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Video directory policy"), &data.UseVidPrefer)); ++ if (data.UseVidPrefer != 0) { ++ char tmp[ 64 ]; ++ Add(new cMenuEditIntItem(tr("Setup.Recording$Number of video directories"), &data.nVidPrefer, 1, DVLVIDPREFER_MAX)); ++ for (int zz = 0; zz < data.nVidPrefer; zz++) { ++ sprintf(tmp, tr("Setup.Recording$Video %d priority"), zz); ++ Add(new cMenuEditIntItem(tmp, &data.VidPreferPrio[ zz ], 0, 99)); ++ sprintf(tmp, tr("Setup.Recording$Video %d min. free MB"), zz); ++ Add(new cMenuEditIntItem(tmp, &data.VidPreferSize[ zz ], -1, 99999)); ++ } ++ } ++#endif /* DVLVIDPREFER */ + Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle)); ++#ifdef USE_DVLFRIENDLYFNAMES ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Friendly filenames"), &data.UseFriendlyFNames)); ++#endif /* DVLFRIENDLYFNAMES */ + Add(new cMenuEditBoolItem(tr("Setup.Recording$Use VPS"), &data.UseVps)); + Add(new cMenuEditIntItem( tr("Setup.Recording$VPS margin (s)"), &data.VpsMargin, 0)); + Add(new cMenuEditBoolItem(tr("Setup.Recording$Mark instant recording"), &data.MarkInstantRecord)); + Add(new cMenuEditStrItem( tr("Setup.Recording$Name instant recording"), data.NameInstantRecord, sizeof(data.NameInstantRecord))); + Add(new cMenuEditIntItem( tr("Setup.Recording$Instant rec. time (min)"), &data.InstantRecordTime, 1, MAXINSTANTRECTIME)); + Add(new cMenuEditIntItem( tr("Setup.Recording$Max. video file size (MB)"), &data.MaxVideoFileSize, MINVIDEOFILESIZE, MAXVIDEOFILESIZETS)); ++#ifdef USE_HARDLINKCUTTER ++ Add(new cMenuEditIntItem( tr("Setup.Recording$Max. recording size (GB)"), &data.MaxRecordingSize, MINRECORDINGSIZE, MAXRECORDINGSIZE)); ++#endif /* HARDLINKCUTTER */ + Add(new cMenuEditBoolItem(tr("Setup.Recording$Split edited files"), &data.SplitEditedFiles)); ++#ifdef USE_HARDLINKCUTTER ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Hard Link Cutter"), &data.HardLinkCutter)); ++#endif /* HARDLINKCUTTER */ ++#ifdef USE_DELTIMESHIFTREC ++ Add(new cMenuEditStraItem(tr("Setup.Recording$Delete timeshift recording"), &data.DelTimeshiftRec, 3, DelTimeshiftRecValues)); ++#endif /* DELTIMESHIFTREC */ ++#ifdef USE_LIEMIEXT ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Show date"), &data.ShowRecDate)); ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Show time"), &data.ShowRecTime)); ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Show length"), &data.ShowRecLength)); ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Show end of timer"), &data.ShowTimerStop)); ++#endif /* LIEMIEXT */ ++#ifdef USE_SORTRECORDS ++ Add(new cMenuEditStraItem(tr("Setup.Recording$Sort recordings by"), &data.RecordingsSortMode, MAXSORTMODES, RecordingsSortModeTexts)); ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Sort directories before recordings"), &data.RecordingsSortDirsFirst)); ++#endif /* SORTRECORDS */ ++#ifdef USE_CUTTERQUEUE ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Cutter auto delete"), &data.CutterAutoDelete)); ++#endif /* CUTTERQUEUE */ ++#ifdef USE_CUTTIME ++ Add(new cMenuEditBoolItem(tr("Setup.Recording$Cutter adjust starttime"), &data.CutTime)); ++#endif /* CUTTIME */ + } + + // --- cMenuSetupReplay ------------------------------------------------------ +@@ -2718,6 +3726,31 @@ + Add(new cMenuEditBoolItem(tr("Setup.Replay$Multi speed mode"), &data.MultiSpeedMode)); + Add(new cMenuEditBoolItem(tr("Setup.Replay$Show replay mode"), &data.ShowReplayMode)); + Add(new cMenuEditIntItem(tr("Setup.Replay$Resume ID"), &data.ResumeID, 0, 99)); ++#ifdef USE_JUMPPLAY ++ Add(new cMenuEditBoolItem(tr("Setup.Replay$Jump&Play"), &data.JumpPlay)); ++ Add(new cMenuEditBoolItem(tr("Setup.Replay$Play&Jump"), &data.PlayJump)); ++ Add(new cMenuEditBoolItem(tr("Setup.Replay$Pause at last mark"), &data.PauseLastMark)); ++ Add(new cMenuEditBoolItem(tr("Setup.Replay$Reload marks"), &data.ReloadMarks)); ++#endif /* JUMPPLAY */ ++#ifdef USE_LIEMIEXT ++ Add(new cMenuEditIntItem(tr("Setup.Replay$Skip Seconds"), &data.JumpSeconds)); ++ Add(new cMenuEditIntItem(tr("Setup.Replay$Skip Seconds Slow"), &data.JumpSecondsSlow)); ++#endif /* LIEMIEXT */ ++#ifdef USE_DVDARCHIVE ++ static const char *dvddisplaymode[3]; ++ dvddisplaymode[0]=tr("Setup.Replay$Length"); ++ dvddisplaymode[1]=tr("Setup.Replay$Length / Number"); ++ dvddisplaymode[2]=tr("Setup.Replay$Number"); ++ Add(new cMenuEditStraItem(tr("Setup.Replay$DVD display mode"), &data.DvdDisplayMode,3,dvddisplaymode)); ++ Add(new cMenuEditBoolItem(tr("Setup.Replay$DVD display leading zeros"), &data.DvdDisplayZeros)); ++ static const char *dvdtraymode[4]; ++ dvdtraymode[0]=tr("Setup.Replay$never"); ++ dvdtraymode[1]=tr("Setup.Replay$on begin"); ++ dvdtraymode[2]=tr("Setup.Replay$on end"); ++ dvdtraymode[3]=tr("Setup.Replay$on begin and end"); ++ Add(new cMenuEditStraItem(tr("Setup.Replay$Tray open"), &data.DvdTrayMode,4,dvdtraymode)); ++ Add(new cMenuEditIntItem( tr("Setup.Replay$Limit DVD to speed"), &data.DvdSpeedLimit, 0, 50)); ++#endif /* DVDARCHIVE */ + } + + void cMenuSetupReplay::Store(void) +@@ -2730,13 +3763,48 @@ + // --- cMenuSetupMisc -------------------------------------------------------- + + class cMenuSetupMisc : public cMenuSetupBase { ++#ifdef USE_VOLCTRL ++private: ++ const char *lrChannelGroupsTexts[3]; ++ const char *lrForwardRewindTexts[3]; ++ void Setup(void); ++#endif /* VOLCTRL */ + public: + cMenuSetupMisc(void); ++#ifdef USE_VOLCTRL ++ virtual eOSState ProcessKey(eKeys Key); ++#endif /* VOLCTRL */ + }; + + cMenuSetupMisc::cMenuSetupMisc(void) + { ++#ifdef USE_VOLCTRL ++ lrChannelGroupsTexts[0] = tr("no"); ++ lrChannelGroupsTexts[1] = tr("Setup.Miscellaneous$only in channelinfo"); ++ lrChannelGroupsTexts[2] = tr("yes"); ++ lrForwardRewindTexts[0] = tr("no"); ++ lrForwardRewindTexts[1] = tr("Setup.Miscellaneous$only in progress display"); ++ lrForwardRewindTexts[2] = tr("yes"); ++#endif /* VOLCTRL */ + SetSection(tr("Miscellaneous")); ++#ifdef USE_VOLCTRL ++ Setup(); ++} ++ ++eOSState cMenuSetupMisc::ProcessKey(eKeys Key) ++{ ++ int newLRVolumeControl = data.LRVolumeControl; ++ eOSState state = cMenuSetupBase::ProcessKey(Key); ++ if (Key != kNone && data.LRVolumeControl != newLRVolumeControl) ++ Setup(); ++ return state; ++} ++ ++void cMenuSetupMisc::Setup(void) ++{ ++ int current = Current(); ++ Clear(); ++#endif /* VOLCTRL */ + Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Min. event timeout (min)"), &data.MinEventTimeout)); + Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Min. user inactivity (min)"), &data.MinUserInactivity)); + Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$SVDRP timeout (s)"), &data.SVDRPTimeout)); +@@ -2744,7 +3812,21 @@ + Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Channel entry timeout (ms)"), &data.ChannelEntryTimeout, 0)); + Add(new cMenuEditChanItem(tr("Setup.Miscellaneous$Initial channel"), &data.InitialChannel, tr("Setup.Miscellaneous$as before"))); + Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Initial volume"), &data.InitialVolume, -1, 255, tr("Setup.Miscellaneous$as before"))); ++#ifdef USE_VOLCTRL ++ Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$Volume ctrl with left/right"), &data.LRVolumeControl)); ++ if (data.LRVolumeControl) { ++ Add(new cMenuEditStraItem(tr("Setup.Miscellaneous$Channelgroups with left/right"), &data.LRChannelGroups, 3, lrChannelGroupsTexts)); ++ Add(new cMenuEditStraItem(tr("Setup.Miscellaneous$Search fwd/back with left/right"), &data.LRForwardRewind, 3, lrForwardRewindTexts)); ++ } ++ SetCurrent(Get(current)); ++ Display(); ++#endif /* VOLCTRL */ + Add(new cMenuEditBoolItem(tr("Setup.Miscellaneous$Emergency exit"), &data.EmergencyExit)); ++#ifdef USE_LIRCSETTINGS ++ Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Lirc repeat delay"), &data.LircRepeatDelay, 0, 1000)); ++ Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Lirc repeat freq"), &data.LircRepeatFreq, 0, 1000)); ++ Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Lirc repeat timeout"), &data.LircRepeatTimeout, 0, 5000)); ++#endif /* LIRCSETTINGS */ + } + + // --- cMenuSetupPluginItem -------------------------------------------------- +@@ -2769,6 +3851,9 @@ + public: + cMenuSetupPlugins(void); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuSetupPlugins"; } ++#endif /* GRAPHTFT */ + }; + + cMenuSetupPlugins::cMenuSetupPlugins(void) +@@ -2818,6 +3903,9 @@ + public: + cMenuSetup(void); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuSetup"; } ++#endif /* GRAPHTFT */ + }; + + cMenuSetup::cMenuSetup(void) +@@ -2907,24 +3995,90 @@ + cMenuMain::cMenuMain(eOSState State) + :cOsdMenu("") + { ++#ifdef USE_SETUP ++ // Load Menu Configuration ++ cString menuXML = cString::sprintf("%s/setup/vdr-menu.%s.xml", cPlugin::ConfigDirectory(), Setup.OSDLanguage); ++ if (access(menuXML, 04) == -1) ++ menuXML = cString::sprintf("%s/setup/vdr-menu.xml", cPlugin::ConfigDirectory()); ++ subMenu.LoadXml(menuXML); ++ nrDynamicMenuEntries = 0; ++#endif /* SETUP */ ++ + replaying = false; + stopReplayItem = NULL; + cancelEditingItem = NULL; + stopRecordingItem = NULL; + recordControlsState = 0; ++ ++#ifdef USE_MENUORG ++ MenuOrgPatch::EnterRootMenu(); ++#endif /* MENUORG */ ++ + Set(); + + // Initial submenus: + ++#ifdef USE_MAINMENUHOOKS ++ cOsdMenu *menu = NULL; ++#endif /* MAINMENUHOOKS */ + switch (State) { ++#ifdef USE_MAINMENUHOOKS ++ case osSchedule: ++ { ++ cPlugin *p = cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osSchedule", &menu); ++ if (p && !menu) ++ isyslog("MainMenuHook::osSchedule: plugin %s claims to support service but didn't return menu", p->Name()); ++ ++ if (!menu) ++ menu = new cMenuSchedule; ++ } ++ break; ++ case osChannels: ++ { ++ cPlugin *p = cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osChannels", &menu); ++ if (p && !menu) ++ isyslog("MainMenuHook::osChannels: plugin %s claims to support service but didn't return menu", p->Name()); ++ ++ if (!menu) ++ menu = new cMenuChannels; ++ } ++ break; ++ case osTimers: ++ { ++ cPlugin *p = cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osTimers", &menu); ++ if (p && !menu) ++ isyslog("MainMenuHook::osTimers: plugin %s claims to support service but didn't return menu", p->Name()); ++ ++ if (!menu) ++ menu = new cMenuTimers; ++ } ++ break; ++ case osRecordings: ++ { ++ cPlugin *p = cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osRecordings", &menu); ++ if (p && !menu) ++ isyslog("MainMenuHook::osRecordings: plugin %s claims to support service but didn't return menu", p->Name()); ++ ++ if (!menu) ++ menu = new cMenuRecordings(NULL, 0, true); ++ } ++ break; ++ case osSetup: menu = new cMenuSetup; break; ++ case osCommands: menu = new cMenuCommands(tr("Commands"), &Commands); break; ++#else + case osSchedule: AddSubMenu(new cMenuSchedule); break; + case osChannels: AddSubMenu(new cMenuChannels); break; + case osTimers: AddSubMenu(new cMenuTimers); break; + case osRecordings: AddSubMenu(new cMenuRecordings(NULL, 0, true)); break; + case osSetup: AddSubMenu(new cMenuSetup); break; + case osCommands: AddSubMenu(new cMenuCommands(tr("Commands"), &Commands)); break; ++#endif /* MAINMENUHOOKS */ + default: break; + } ++#ifdef USE_MAINMENUHOOKS ++ if (menu) ++ AddSubMenu(menu); ++#endif /* MAINMENUHOOKS */ + } + + cOsdObject *cMenuMain::PluginOsdObject(void) +@@ -2934,38 +4088,159 @@ + return o; + } + ++#ifdef USE_SETUP ++void cMenuMain::Set(int current) ++#else + void cMenuMain::Set(void) ++#endif /* SETUP */ + { + Clear(); + SetTitle("VDR"); + SetHasHotkeys(); + ++#ifdef USE_MENUORG ++ if (MenuOrgPatch::IsCustomMenuAvailable()) { ++ MenuItemDefinitions* menuItems = MenuOrgPatch::MainMenuItems(); ++ for (MenuItemDefinitions::iterator i = menuItems->begin(); i != menuItems->end(); i++) { ++ cOsdItem* osdItem = NULL; ++ if ((*i)->IsCustomOsdItem()) { ++ osdItem = (*i)->CustomOsdItem(); ++ if (osdItem && !(*i)->IsSeparatorItem()) ++ osdItem->SetText(hk(osdItem->Text())); ++ } ++ else if ((*i)->IsPluginItem()) { ++ const char *item = (*i)->PluginMenuEntry(); ++ if (item) ++ osdItem = new cMenuPluginItem(hk(item), (*i)->PluginIndex()); ++ } ++ if (osdItem) { ++ Add(osdItem); ++ if ((*i)->IsSelected()) ++ SetCurrent(osdItem); ++ } ++ } ++ } ++ else { ++#endif /* MENUORG */ ++ ++#ifdef USE_SETUP ++ stopReplayItem = NULL; ++ cancelEditingItem = NULL; ++ stopRecordingItem = NULL; ++ ++ // remember initial dynamic MenuEntries added ++ nrDynamicMenuEntries = Count(); ++ for (cSubMenuNode *node = subMenu.GetMenuTree()->First(); node; node = subMenu.GetMenuTree()->Next(node)) { ++ cSubMenuNode::Type type = node->GetType(); ++ if (type==cSubMenuNode::PLUGIN) { ++ const char *item = node->GetPluginMainMenuEntry(); ++#ifdef USE_PINPLUGIN ++ if (item && !cStatus::MsgPluginProtected(cPluginManager::GetPlugin(node->GetPluginIndex()), true)) ++#else ++ if (item) ++#endif /* PINPLUGIN */ ++ Add(new cMenuPluginItem(hk(item), node->GetPluginIndex())); ++ } ++ else if (type==cSubMenuNode::MENU) { ++ cString item = cString::sprintf("%s%s", node->GetName(), *subMenu.GetMenuSuffix()); ++#ifdef USE_PINPLUGIN ++ if (!cStatus::MsgMenuItemProtected(item, true)) ++ Add(new cOsdItem(hk(item), osUnknown, node)); ++#else ++ Add(new cOsdItem(hk(item))); ++#endif /* PINPLUGIN */ ++ } ++ else if ((type==cSubMenuNode::COMMAND) || (type==cSubMenuNode::THREAD)) { ++#ifdef USE_PINPLUGIN ++ if (!cStatus::MsgMenuItemProtected(node->GetName(), true)) ++ Add(new cOsdItem(hk(node->GetName()), osUnknown, node)); ++#else ++ Add(new cOsdItem(hk(node->GetName()))); ++#endif /* PINPLUGIN */ ++ } ++ else if (type==cSubMenuNode::SYSTEM) { ++ const char *item = node->GetName(); ++#ifdef USE_PINPLUGIN ++ if (cStatus::MsgMenuItemProtected(item, true)) ++ ; // nothing to do ;) ++ else ++#endif /* PINPLUGIN */ ++ if (strcmp(item, "Schedule") == 0) ++ Add(new cOsdItem(hk(tr("Schedule")), osSchedule)); ++ else if (strcmp(item, "Channels") == 0) ++ Add(new cOsdItem(hk(tr("Channels")), osChannels)); ++ else if (strcmp(item, "Timers") == 0) ++ Add(new cOsdItem(hk(tr("Timers")), osTimers)); ++ else if (strcmp(item, "Recordings") == 0) ++ Add(new cOsdItem(hk(tr("Recordings")), osRecordings)); ++ else if (strcmp(item, "Setup") == 0) { ++ cString itemSetup = cString::sprintf("%s%s", tr("Setup"), *subMenu.GetMenuSuffix()); ++ Add(new cOsdItem(hk(itemSetup), osSetup)); ++ } ++ else if (strcmp(item, "Commands") == 0 && Commands.Count() > 0) { ++ cString itemCommands = cString::sprintf("%s%s", tr("Commands"), *subMenu.GetMenuSuffix()); ++ Add(new cOsdItem(hk(itemCommands), osCommands)); ++ } ++ } ++ } ++ if (current >=0 && currentMainMenuEntry(); + if (item) + Add(new cMenuPluginItem(hk(item), i)); + } ++#ifdef USE_PINPLUGIN ++ } ++#endif /* PINPLUGIN */ + else + break; + } + + // More basic menu items: + ++#ifdef USE_PINPLUGIN ++ if (!cStatus::MsgMenuItemProtected("Setup", true)) Add(new cOsdItem(hk(tr("Setup")), osSetup)); ++#else + Add(new cOsdItem(hk(tr("Setup")), osSetup)); ++#endif /* PINPLUGIN */ + if (Commands.Count()) ++#ifdef USE_PINPLUGIN ++ if (!cStatus::MsgMenuItemProtected("Commands", true)) ++#endif /* PINPLUGIN */ + Add(new cOsdItem(hk(tr("Commands")), osCommands)); + ++#endif /* SETUP */ ++ ++#ifdef USE_MENUORG ++ } ++#endif /* MENUORG */ ++ + Update(true); + + Display(); +@@ -2974,13 +4249,40 @@ + bool cMenuMain::Update(bool Force) + { + bool result = false; +- ++#ifdef USE_SETUP ++ cOsdItem *fMenu = NULL; ++ if (Force && subMenu.isTopMenu()) { ++ fMenu = First(); ++ nrDynamicMenuEntries = 0; ++ } ++ ++ if (subMenu.isTopMenu()) { ++#endif /* SETUP */ ++#ifdef USE_LIEMIEXT ++// this extension is not included in the original Liemikuutio ++ if (Setup.MainMenuTitle) { ++ if (Setup.MainMenuTitle == 1) ++ SetTitle(cString::sprintf("%s - %s", tr("VDR"), Setup.CustomMainMenuTitle)); ++ else if (Setup.MainMenuTitle == 2) ++ SetTitle(cString::sprintf("%s", Setup.CustomMainMenuTitle)); ++ else if (Setup.MainMenuTitle == 3) ++ SetTitle(cString::sprintf("%s %s", tr("VDR"), VDRVERSION)); ++ } ++ else ++#endif /* LIEMIEXT */ + // Title with disk usage: + if (FreeDiskSpace.HasChanged(Force)) { + //XXX -> skin function!!! + SetTitle(cString::sprintf("%s - %s", tr("VDR"), FreeDiskSpace.FreeDiskSpaceString())); + result = true; + } ++#ifdef USE_SETUP ++ } ++ else { ++ SetTitle(cString::sprintf("%s - %s", tr("VDR"), subMenu.GetParentMenuTitel())); ++ result = true; ++ } ++#endif /* SETUP */ + + bool NewReplaying = cControl::Control() != NULL; + if (Force || NewReplaying != replaying) { +@@ -2988,6 +4290,9 @@ + // Replay control: + if (replaying && !stopReplayItem) + // TRANSLATORS: note the leading blank! ++#ifdef USE_LIEMIEXT ++ if (Setup.MenuCmdPosition) Ins(stopReplayItem = new cOsdItem(tr(" Stop replaying"), osStopReplay)); else ++#endif /* LIEMIEXT */ + Add(stopReplayItem = new cOsdItem(tr(" Stop replaying"), osStopReplay)); + else if (stopReplayItem && !replaying) { + Del(stopReplayItem->Index()); +@@ -3002,6 +4307,9 @@ + bool CutterActive = cCutter::Active(); + if (CutterActive && !cancelEditingItem) { + // TRANSLATORS: note the leading blank! ++#ifdef USE_LIEMIEXT ++ if (Setup.MenuCmdPosition) Ins(cancelEditingItem = new cOsdItem(tr(" Cancel editing"), osCancelEdit)); else ++#endif /* LIEMIEXT */ + Add(cancelEditingItem = new cOsdItem(tr(" Cancel editing"), osCancelEdit)); + result = true; + } +@@ -3022,6 +4330,9 @@ + while ((s = cRecordControls::GetInstantId(s)) != NULL) { + cOsdItem *item = new cOsdItem(osStopRecord); + item->SetText(cString::sprintf("%s%s", tr(STOP_RECORDING), s)); ++#ifdef USE_LIEMIEXT ++ if (Setup.MenuCmdPosition) Ins(item); else ++#endif /* LIEMIEXT */ + Add(item); + if (!stopRecordingItem) + stopRecordingItem = item; +@@ -3029,6 +4340,12 @@ + result = true; + } + ++#ifdef USE_SETUP ++ // adjust nrDynamicMenuEntries ++ if (fMenu != NULL) ++ nrDynamicMenuEntries = fMenu->Index(); ++#endif /* SETUP */ ++ + return result; + } + +@@ -3039,13 +4356,69 @@ + eOSState state = cOsdMenu::ProcessKey(Key); + HadSubMenu |= HasSubMenu(); + ++#ifdef USE_PINPLUGIN ++ cOsdItem* item = Get(Current()); ++ ++ if (item && item->Text() && state != osBack && state != osContinue && Key != kNone) ++ if (cStatus::MsgMenuItemProtected(item->Text())) ++ return osContinue; ++#endif /* PINPLUGIN */ ++ ++#ifdef USE_MAINMENUHOOKS ++ cOsdMenu *menu = NULL; ++#endif /* MAINMENUHOOKS */ + switch (state) { ++#ifdef USE_MAINMENUHOOKS ++ case osSchedule: ++ { ++ cPlugin *p = cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osSchedule", &menu); ++ if (p && !menu) ++ isyslog("MainMenuHook::osSchedule: plugin %s claims to support service but didn't return menu", p->Name()); ++ ++ if (!menu) ++ menu = new cMenuSchedule; ++ } ++ break; ++ case osChannels: ++ { ++ cPlugin *p = cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osChannels", &menu); ++ if (p && !menu) ++ isyslog("MainMenuHook::osChannels: plugin %s claims to support service but didn't return menu", p->Name()); ++ ++ if (!menu) ++ menu = new cMenuChannels; ++ } ++ break; ++ case osTimers: ++ { ++ cPlugin *p = cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osTimers", &menu); ++ if (p && !menu) ++ isyslog("MainMenuHook::osTimers: plugin %s claims to support service but didn't return menu", p->Name()); ++ ++ if (!menu) ++ menu = new cMenuTimers; ++ } ++ break; ++ case osRecordings: ++ { ++ cPlugin *p = cPluginManager::CallFirstService("MainMenuHooksPatch-v1.0::osRecordings", &menu); ++ if (p && !menu) ++ isyslog("MainMenuHook::osRecordings: plugin %s claims to support service but didn't return menu", p->Name()); ++ ++ if (!menu) ++ menu = new cMenuRecordings; ++ } ++ break; ++ case osSetup: menu = new cMenuSetup; break; ++ case osCommands: menu = new cMenuCommands(tr("Commands"), &Commands); break; ++#else + case osSchedule: return AddSubMenu(new cMenuSchedule); + case osChannels: return AddSubMenu(new cMenuChannels); + case osTimers: return AddSubMenu(new cMenuTimers); + case osRecordings: return AddSubMenu(new cMenuRecordings); + case osSetup: return AddSubMenu(new cMenuSetup); + case osCommands: return AddSubMenu(new cMenuCommands(tr("Commands"), &Commands)); ++#endif /* MAINMENUHOOKS */ + case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) { + cOsdItem *item = Get(Current()); + if (item) { +@@ -3064,6 +4437,9 @@ + if (item) { + cPlugin *p = cPluginManager::GetPlugin(item->PluginIndex()); + if (p) { ++#ifdef USE_PINPLUGIN ++ if (!cStatus::MsgPluginProtected(p)) { ++#endif /* PINPLUGIN */ + cOsdObject *menu = p->MainMenuAction(); + if (menu) { + if (menu->IsMenu()) +@@ -3075,9 +4451,60 @@ + } + } + } ++#ifdef USE_PINPLUGIN ++ } ++#endif /* PINPLUGIN */ + state = osEnd; + } + break; ++#ifdef USE_SETUP ++ case osBack: { ++ int newCurrent = 0; ++ if (subMenu.Up(&newCurrent)) { ++ Set(newCurrent); ++ return osContinue; ++ } ++ else ++ return osEnd; ++ } ++ break; ++#endif /* SETUP */ ++#ifdef USE_MENUORG ++ case osBack: { ++ if (MenuOrgPatch::IsCustomMenuAvailable()) { ++ bool leavingMenuSucceeded = MenuOrgPatch::LeaveSubMenu(); ++ Set(); ++ stopReplayItem = NULL; ++ cancelEditingItem = NULL; ++ stopRecordingItem = NULL; ++ recordControlsState = 0; ++ Update(true); ++ Display(); ++ if (leavingMenuSucceeded) ++ return osContinue; ++ else ++ return osEnd; ++ } ++ } ++ break; ++ case osUser1: { ++ if (MenuOrgPatch::IsCustomMenuAvailable()) { ++ MenuOrgPatch::EnterSubMenu(Get(Current())); ++ Set(); ++ return osContinue; ++ } ++ } ++ break; ++ case osUser2: { ++ if (MenuOrgPatch::IsCustomMenuAvailable()) { ++ cOsdMenu* osdMenu = MenuOrgPatch::Execute(Get(Current())); ++ if (osdMenu) ++ return AddSubMenu(osdMenu); ++ return osEnd; ++ } ++ } ++ break; ++#endif /* MENUORG */ + default: switch (Key) { + case kRecord: + case kRed: if (!HadSubMenu) +@@ -3094,9 +4521,67 @@ + case kBlue: if (!HadSubMenu) + state = replaying ? osStopReplay : cReplayControl::LastReplayed() ? osReplay : osContinue; + break; ++#ifdef USE_SETUP ++ case kOk: if (state == osUnknown) { ++ cString buffer; ++#ifdef USE_PINPLUGIN ++ cSubMenuNode *node = Get(Current())->SubMenu(); ++#else ++ int index = Current()-nrDynamicMenuEntries; ++ cSubMenuNode *node = subMenu.GetNode(index); ++#endif /* PINPLUGIN */ ++ ++ if (node != NULL) { ++ if (node->GetType() == cSubMenuNode::MENU) { ++#ifdef USE_PINPLUGIN ++ subMenu.Down(node, Current()); ++#else ++ subMenu.Down(index); ++#endif /* PINPLUGIN */ ++ } ++ else if (node->GetType() == cSubMenuNode::COMMAND) { ++ bool confirmed = true; ++ if (node->CommandConfirm()) { ++ buffer = cString::sprintf("%s?", node->GetName()); ++ confirmed = Interface->Confirm(buffer); ++ } ++ if (confirmed) { ++ const char *Result = subMenu.ExecuteCommand(node->GetCommand()); ++ if (Result) ++ return AddSubMenu(new cMenuText(node->GetName(), Result, fontFix)); ++ return osEnd; ++ } ++ } ++ else if (node->GetType() == cSubMenuNode::THREAD) { ++ bool confirmed = true; ++ if (node->CommandConfirm()) { ++ buffer = cString::sprintf("%s?", node->GetName()); ++ confirmed = Interface->Confirm(buffer); ++ } ++ if (confirmed) { ++ buffer = cString::sprintf("%s", node->GetCommand()); ++ cExecCmdThread *execcmd = new cExecCmdThread(node->GetCommand()); ++ if (execcmd->Start()) ++ dsyslog("executing command '%s'", *buffer); ++ else ++ esyslog("ERROR: can't execute command '%s'", *buffer); ++ return osEnd; ++ } ++ } ++ } ++ ++ Set(); ++ return osContinue; ++ } ++ break; ++#endif /* SETUP */ + default: break; + } + } ++#ifdef USE_MAINMENUHOOKS ++ if (menu) ++ return AddSubMenu(menu); ++#endif /* MAINMENUHOOKS */ + if (!HasSubMenu() && Update(HadSubMenu)) + Display(); + if (Key != kNone) { +@@ -3242,7 +4727,14 @@ + if (Direction) { + while (Channel) { + Channel = Direction > 0 ? Channels.Next(Channel) : Channels.Prev(Channel); ++#ifdef USE_PINPLUGIN ++ if (cStatus::MsgChannelProtected(0, Channel) == false) ++#endif /* PINPLUGIN */ ++#ifdef USE_LNBSHARE ++ if (Channel && !Channel->GroupSep() && cDevice::GetDevice(Channel, 0, true) && cDevice::PrimaryDevice()->GetMaxBadPriority(Channel) < 0) ++#else + if (Channel && !Channel->GroupSep() && cDevice::GetDevice(Channel, 0, true)) ++#endif /* LNBSHARE */ + return Channel; + } + } +@@ -3300,6 +4792,13 @@ + case kLeft: + case kRight|k_Repeat: + case kRight: ++#ifdef USE_VOLCTRL ++ if (Setup.LRVolumeControl && !Setup.LRChannelGroups) { ++ cRemote::Put(NORMALKEY(Key) == kLeft ? kVolDn : kVolUp, true); ++ break; ++ } ++ // else fall through ++#endif /* VOLCTRL */ + case kNext|k_Repeat: + case kNext: + case kPrev|k_Repeat: +@@ -3459,6 +4958,17 @@ + eOSState cDisplayVolume::ProcessKey(eKeys Key) + { + switch (Key) { ++#ifdef USE_VOLCTRL ++ case kLeft|k_Repeat: ++ case kLeft: ++ case kRight|k_Repeat: ++ case kRight: ++ if (Setup.LRVolumeControl) { ++ cRemote::Put(NORMALKEY(Key) == kLeft ? kVolDn : kVolUp, true); ++ break; ++ } ++ // else fall through ++#endif /* VOLCTRL */ + case kVolUp|k_Repeat: + case kVolUp: + case kVolDn|k_Repeat: +@@ -3708,6 +5218,10 @@ + + cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause) + { ++#ifdef USE_DVLRECSCRIPTADDON ++ const cChannel *recChan = NULL; ++ char *chanName = NULL; ++#endif /* DVLRECSCRIPTADDON */ + // We're going to manipulate an event here, so we need to prevent + // others from modifying any EPG data: + cSchedulesLock SchedulesLock; +@@ -3752,11 +5266,26 @@ + return; + } + ++#ifdef USE_DVLRECSCRIPTADDON ++ if (timer) ++ if ((recChan = timer->Channel()) != NULL) ++ chanName = strdup(recChan->Name()); ++ if (chanName != NULL) { ++ cRecordingUserCommand::InvokeCommand(RUC_BEFORERECORDING, fileName, chanName); ++ free(chanName); ++ } ++ else ++#endif /* DVLRECSCRIPTADDON */ + cRecordingUserCommand::InvokeCommand(RUC_BEFORERECORDING, fileName); + isyslog("record %s", fileName); + if (MakeDirs(fileName, true)) { + const cChannel *ch = timer->Channel(); ++#ifdef USE_TTXTSUBS ++ int TPid[2] = { ch->Tpid(), 0 }; ++ recorder = new cRecorder(fileName, ch->GetChannelID(), timer->Priority(), ch->Vpid(), ch->Apids(), ch->Dpids(), ch->Spids(), TPid); ++#else + recorder = new cRecorder(fileName, ch->GetChannelID(), timer->Priority(), ch->Vpid(), ch->Apids(), ch->Dpids(), ch->Spids()); ++#endif /* TTXTSUBS */ + if (device->AttachReceiver(recorder)) { + Recording.WriteInfo(); + cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true); +@@ -3814,11 +5343,25 @@ + void cRecordControl::Stop(void) + { + if (timer) { ++#ifdef USE_DVLRECSCRIPTADDON ++ char *chanName = NULL; ++ const cChannel *recChan = NULL; ++ ++ recChan = timer -> Channel(); ++ if (recChan != NULL) ++ chanName = strdup(recChan -> Name()); ++#endif /* DVLRECSCRIPTADDON */ + DELETENULL(recorder); + timer->SetRecording(false); + timer = NULL; + cStatus::MsgRecording(device, NULL, fileName, false); ++#ifdef USE_DVLRECSCRIPTADDON ++ cRecordingUserCommand::InvokeCommand(RUC_AFTERRECORDING, fileName, chanName); ++ if (chanName != NULL) ++ free(chanName); ++#else + cRecordingUserCommand::InvokeCommand(RUC_AFTERRECORDING, fileName); ++#endif /* DVLRECSCRIPTADDON */ + } + } + +@@ -3865,6 +5408,19 @@ + int Priority = Timer ? Timer->Priority() : Pause ? Setup.PausePriority : Setup.DefaultPriority; + cDevice *device = cDevice::GetDevice(channel, Priority, false); + if (device) { ++#ifdef USE_LNBSHARE ++ cDevice *tmpDevice; ++ while ((tmpDevice = device->GetBadDevice(channel))) { ++ if (tmpDevice->Replaying() == false) { ++// Stop(tmpDevice); ++ if (tmpDevice->IsPrimaryDevice() ) ++ tmpDevice->SwitchChannelForced(channel, true); ++ else ++ tmpDevice->SwitchChannelForced(channel, false); ++ } else ++ tmpDevice->SwitchChannelForced(channel, false); ++ } ++#endif /* LNBSHARE */ + dsyslog("switching device %d to channel %d", device->DeviceNumber() + 1, channel->Number()); + if (!device->SwitchChannel(channel, false)) { + ShutdownHandler.RequestEmergencyExit(); +@@ -3874,6 +5430,9 @@ + for (int i = 0; i < MAXRECORDCONTROLS; i++) { + if (!RecordControls[i]) { + RecordControls[i] = new cRecordControl(device, Timer, Pause); ++#ifdef USE_PINPLUGIN ++ cStatus::MsgRecordingFile(RecordControls[i]->FileName()); ++#endif /* PINPLUGIN */ + return RecordControls[i]->Process(time(NULL)); + } + } +@@ -4004,12 +5563,22 @@ + + // --- cReplayControl -------------------------------------------------------- + ++#ifdef USE_LIEMIEXT ++#define REPLAYCONTROLSKIPLIMIT 9 // s ++#define REPLAYCONTROLSKIPSECONDS 90 // s ++#define REPLAYCONTROLSKIPTIMEOUT 5000 // ms ++#endif /* LIEMIEXT */ ++ + cReplayControl *cReplayControl::currentReplayControl = NULL; + char *cReplayControl::fileName = NULL; + char *cReplayControl::title = NULL; + + cReplayControl::cReplayControl(void) ++#ifdef USE_JUMPPLAY ++:cDvbPlayerControl(fileName), marks(fileName) ++#else + :cDvbPlayerControl(fileName) ++#endif /* JUMPPLAY */ + { + currentReplayControl = this; + displayReplay = NULL; +@@ -4017,23 +5586,96 @@ + lastCurrent = lastTotal = -1; + lastPlay = lastForward = false; + lastSpeed = -2; // an invalid value ++#ifdef USE_LIEMIEXT ++ lastSkipKey = kNone; ++ lastSkipSeconds = REPLAYCONTROLSKIPSECONDS; ++ lastSkipTimeout.Set(0); ++#endif /* LIEMIEXT */ + timeoutShow = 0; + timeSearchActive = false; + cRecording Recording(fileName); ++#ifdef USE_DVDARCHIVE ++ canJumpChapters = (Recording.GetDvdType() == DVD_VIDEO_ARCHIVE_TYPE); ++ dvdchapters = NULL; ++ if (canJumpChapters) { ++ const char *ret = Recording.GetDvdChapters(); ++ if (ret) ++ dvdchapters = strdup(ret); ++ else ++ canJumpChapters=false; ++ } ++#endif /* DVDARCHIVE */ + cStatus::MsgReplaying(this, Recording.Name(), Recording.FileName(), true); ++#ifndef USE_JUMPPLAY + marks.Load(fileName, Recording.FramesPerSecond(), Recording.IsPesRecording()); ++#endif /* JUMPPLAY */ + SetTrackDescriptions(false); + } + + cReplayControl::~cReplayControl() + { + Hide(); ++#ifdef USE_DVDARCHIVE ++ free(dvdchapters); ++#endif /* DVDARCHIVE */ + cStatus::MsgReplaying(this, NULL, fileName, false); + Stop(); + if (currentReplayControl == this) + currentReplayControl = NULL; + } + ++#ifdef USE_DELTIMESHIFTREC ++void cReplayControl::Stop(void) ++{ ++ int dummy; ++ bool playing = GetIndex(dummy, dummy, false); ++ cRecordControl* rc = cRecordControls::GetRecordControl(fileName); ++ ++ if (playing && rc && rc->InstantId()) { ++ isyslog("found Timeshiftrecording"); ++ ++ if ((Setup.DelTimeshiftRec != 0 ) || (Interface->Confirm(tr("Delete recording?")))) { ++ cRecordControl *rc = cRecordControls::GetRecordControl(fileName); ++ if (rc) { ++ cTimer *timer = rc->Timer(); ++ if (timer) { ++ const char* reccmd_backup = cRecordingUserCommand::GetCommand(); ++ cRecordingUserCommand::SetCommand(NULL); ++ ++ timer->Skip(); ++ cRecordControls::Process(time(NULL)); ++ if (timer->IsSingleEvent()) { ++ isyslog("deleting timer %s", *timer->ToDescr()); ++ Timers.Del(timer); ++ } ++ Timers.SetModified(); ++ ++ // restore reccmd ++ cRecordingUserCommand::SetCommand(reccmd_backup); ++ } ++ } ++ isyslog("stop replaying %s", fileName); ++ cDvbPlayerControl::Stop(); ++ ++ if (Setup.DelTimeshiftRec != 1) { ++ cRecording *recording = Recordings.GetByName(fileName);; ++ if (recording) { ++ if (recording->Delete()) { ++ Recordings.DelByName(fileName); ++ ClearLastReplayed(fileName); ++ return; ++ } ++ else ++ Skins.Message(mtError, tr("Error while deleting recording!")); ++ } ++ } ++ } ++ else ++ cDvbPlayerControl::Stop(); ++ } ++} ++#endif /* DELTIMESHIFTREC */ ++ + void cReplayControl::SetRecording(const char *FileName, const char *Title) + { + free(fileName); +@@ -4128,6 +5770,9 @@ + if (Initial) { + if (title) + displayReplay->SetTitle(title); ++#ifdef USE_OSDMAXITEMS ++ displayReplay->SetButtons(tr("Jump"), tr("Skip +60s"), tr("Skip -60s"), tr("Button$Stop")); ++#endif /* OSDMAXITEMS */ + lastCurrent = lastTotal = -1; + } + if (Total != lastTotal) { +@@ -4249,8 +5894,15 @@ + ShowTimed(2); + bool Play, Forward; + int Speed; ++#ifdef USE_JUMPPLAY ++ if (GetReplayMode(Play, Forward, Speed) && !Play) { ++ Goto(Current, true); ++ displayFrames = true; ++ } ++#else + if (GetReplayMode(Play, Forward, Speed) && !Play) + Goto(Current, true); ++#endif /* JUMPPLAY */ + } + marks.Save(); + } +@@ -4263,8 +5915,22 @@ + if (GetIndex(Current, Total)) { + cMark *m = Forward ? marks.GetNext(Current) : marks.GetPrev(Current); + if (m) { ++#ifdef USE_JUMPPLAY ++ bool Play2, Forward2; ++ int Speed; ++ if (Setup.JumpPlay && GetReplayMode(Play2, Forward2, Speed) && ++ Play2 && Forward && m->position < Total - SecondsToFrames(3, FramesPerSecond())) { ++ Goto(m->position); ++ Play(); ++ } ++ else { ++ Goto(m->position, true); ++ displayFrames = true; ++ } ++#else + Goto(m->position, true); + displayFrames = true; ++#endif /* JUMPPLAY */ + } + } + } +@@ -4293,11 +5959,43 @@ + } + } + ++#ifdef USE_DVDARCHIVE ++void cReplayControl::ChaptersJump(bool Forward) ++{ ++ int Current, Total; ++ if (GetIndex(Current, Total)) { ++ int position = -1; ++ char *buf, *pos; ++ cString old1("-1"); ++ cString old2("-1"); ++ buf = strdup(dvdchapters); ++ pos = strtok(buf, ","); ++ while (pos != NULL && position == -1) { ++ if (pos && atoi(pos) > Current) ++ position = Forward ? atoi(pos) : ((Current - atoi(old1)) <= (3 * FramesPerSecond())) ? atoi(old2) : atoi(old1); ++ old2 = old1; ++ old1 = strdup(pos); ++ if(position == -1) pos = strtok(NULL, ","); ++ } ++ if (!pos && !Forward) ++ position = ((Current - atoi(old1)) <= (3 * FramesPerSecond())) ? atoi(old2) : atoi(old1); ++ if (position >= 0) { ++ Goto(position); ++ Play(); ++ } ++ } ++} ++ ++#endif /* DVDARCHIVE */ + void cReplayControl::EditCut(void) + { + if (fileName) { + Hide(); ++#ifdef USE_CUTTERQUEUE ++ if (!cCutter::Active() || Interface->Confirm(tr("Cutter already running - Add to cutting queue?"))) { ++#else + if (!cCutter::Active()) { ++#endif /* CUTTERQUEUE */ + if (!marks.Count()) + Skins.Message(mtError, tr("No editing marks defined!")); + else if (!cCutter::Start(fileName)) +@@ -4319,7 +6017,11 @@ + if (!m) + m = marks.GetNext(Current); + if (m) { ++#ifdef USE_JUMPPLAY ++ if ((m->Index() & 0x01) != 0 && !Setup.PlayJump) ++#else + if ((m->Index() & 0x01) != 0) ++#endif /* JUMPPLAY */ + m = marks.Next(m); + if (m) { + Goto(m->position - SecondsToFrames(3, FramesPerSecond())); +@@ -4341,6 +6043,9 @@ + { + if (!Active()) + return osEnd; ++#ifdef USE_JUMPPLAY ++ marks.Reload(); ++#endif /* JUMPPLAY */ + if (visible) { + if (timeoutShow && time(NULL) > timeoutShow) { + Hide(); +@@ -4358,7 +6063,32 @@ + TimeSearchProcess(Key); + return osContinue; + } ++#ifdef USE_DVDARCHIVE ++ bool isOnMark = false; ++ if (canJumpChapters) { ++ int Current, Total; ++ GetIndex(Current, Total); ++ cMark *m = marks.Get(Current); ++ if (m && (m->position == Current)) isOnMark = true; ++ } ++#endif /* DVDARCHIVE */ + bool DoShowMode = true; ++#ifdef USE_VOLCTRL ++ if (Setup.LRVolumeControl && (!Setup.LRForwardRewind || (Setup.LRForwardRewind == 1 && !visible))) { ++ switch (Key) { ++ // Left/Right volume control ++ case kLeft|k_Repeat: ++ case kLeft: ++ case kRight|k_Repeat: ++ case kRight: ++ cRemote::Put(NORMALKEY(Key) == kLeft ? kVolDn : kVolUp, true); ++ return osContinue; ++ break; ++ default: ++ break; ++ } ++ } ++#endif /* VOLCTRL */ + switch (Key) { + // Positioning: + case kPlay: +@@ -4376,25 +6106,82 @@ + case kFastFwd: + case kRight: Forward(); break; + case kRed: TimeSearch(); break; ++#ifdef USE_LIEMIEXT ++ case kGreen|k_Repeat: ++ case kGreen: SkipSeconds(-Setup.JumpSeconds); break; ++ case kYellow|k_Repeat: ++ case kYellow: SkipSeconds( Setup.JumpSeconds); break; ++ case k1|k_Repeat: ++ case k1: SkipSeconds(-Setup.JumpSecondsSlow); break; ++ case k3|k_Repeat: ++ case k3: SkipSeconds( Setup.JumpSecondsSlow); break; ++ case kPrev|k_Repeat: ++ case kPrev: if (lastSkipTimeout.TimedOut()) { ++ lastSkipSeconds = REPLAYCONTROLSKIPSECONDS; ++ lastSkipKey = kPrev; ++ } ++ else if (RAWKEY(lastSkipKey) != kPrev && lastSkipSeconds > (2 * REPLAYCONTROLSKIPLIMIT)) { ++ lastSkipSeconds /= 2; ++ lastSkipKey = kNone; ++ } ++ lastSkipTimeout.Set(REPLAYCONTROLSKIPTIMEOUT); ++ SkipSeconds(-lastSkipSeconds); break; ++ case kNext|k_Repeat: ++ case kNext: if (lastSkipTimeout.TimedOut()) { ++ lastSkipSeconds = REPLAYCONTROLSKIPSECONDS; ++ lastSkipKey = kNext; ++ } ++ else if (RAWKEY(lastSkipKey) != kNext && lastSkipSeconds > (2 * REPLAYCONTROLSKIPLIMIT)) { ++ lastSkipSeconds /= 2; ++ lastSkipKey = kNone; ++ } ++ lastSkipTimeout.Set(REPLAYCONTROLSKIPTIMEOUT); ++ SkipSeconds(lastSkipSeconds); break; ++#else + case kGreen|k_Repeat: + case kGreen: SkipSeconds(-60); break; + case kYellow|k_Repeat: + case kYellow: SkipSeconds( 60); break; ++#endif /* LIEMIEXT */ + case kStop: + case kBlue: Hide(); + Stop(); + return osEnd; ++#ifdef USE_DVDARCHIVE ++ case kDvdChapterJumpForward|k_Repeat: ++ case kDvdChapterJumpForward: if (canJumpChapters && !isOnMark) { ++ ChaptersJump(true); ++ } ++ else { ++ DoShowMode = false; ++ MarkMove(true); ++ } ++ break; ++ case kDvdChapterJumpBack|k_Repeat: ++ case kDvdChapterJumpBack: if (canJumpChapters && !isOnMark) { ++ ChaptersJump(false); ++ } ++ else { ++ DoShowMode = false; ++ MarkMove(false); ++ } ++ break; ++#endif /* DVDARCHIVE */ + default: { + DoShowMode = false; + switch (Key) { + // Editing: + case kMarkToggle: MarkToggle(); break; ++#ifndef USE_LIEMIEXT + case kPrev|k_Repeat: + case kPrev: ++#endif /* LIEMIEXT */ + case kMarkJumpBack|k_Repeat: + case kMarkJumpBack: MarkJump(false); break; ++#ifndef USE_LIEMIEXT + case kNext|k_Repeat: + case kNext: ++#endif /* LIEMIEXT */ + case kMarkJumpForward|k_Repeat: + case kMarkJumpForward: MarkJump(true); break; + case kMarkMoveBack|k_Repeat: +@@ -4414,7 +6201,16 @@ + else + Show(); + break; ++#ifdef USE_DELTIMESHIFTREC ++ case kBack: { cRecordControl* rc = cRecordControls::GetRecordControl(fileName); ++ if (rc && rc->InstantId()) ++ return osEnd; ++ else ++ return osRecordings; ++ } ++#else + case kBack: return osRecordings; ++#endif /* DELTIMESHIFTREC */ + default: return osUnknown; + } + } +diff -NaurwB vdr-1.7.10/menu.h vdr-1.7.10-patched/menu.h +--- vdr-1.7.10/menu.h 2008-02-10 17:01:53.000000000 +0100 ++++ vdr-1.7.10-patched/menu.h 2009-12-18 06:25:25.000000000 +0100 +@@ -18,6 +18,9 @@ + #include "menuitems.h" + #include "recorder.h" + #include "skins.h" ++#ifdef USE_SETUP ++#include "submenu.h" ++#endif /* SETUP */ + + class cMenuText : public cOsdMenu { + private: +@@ -29,12 +32,19 @@ + void SetText(const char *Text); + virtual void Display(void); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuText"; } ++#endif /* GRAPHTFT */ + }; + + class cMenuEditTimer : public cOsdMenu { + private: + cTimer *timer; + cTimer data; ++#ifdef USE_LIEMIEXT ++ char name[MaxFileName]; ++ char path[MaxFileName]; ++#endif /* LIEMIEXT */ + int channel; + bool addIfConfirmed; + cMenuEditDateItem *firstday; +@@ -43,6 +53,9 @@ + cMenuEditTimer(cTimer *Timer, bool New = false); + virtual ~cMenuEditTimer(); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuTimerEdit"; } ++#endif /* GRAPHTFT */ + }; + + class cMenuEvent : public cOsdMenu { +@@ -52,22 +65,37 @@ + cMenuEvent(const cEvent *Event, bool CanSwitch = false, bool Buttons = false); + virtual void Display(void); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuEvent"; } ++#endif /* GRAPHTFT */ + }; + + class cMenuMain : public cOsdMenu { + private: ++#ifdef USE_SETUP ++ int nrDynamicMenuEntries; ++#endif /* SETUP */ + bool replaying; + cOsdItem *stopReplayItem; + cOsdItem *cancelEditingItem; + cOsdItem *stopRecordingItem; + int recordControlsState; + static cOsdObject *pluginOsdObject; ++#ifdef USE_SETUP ++ void Set(int current=0); ++ bool Update(bool Force = false); ++ cSubMenu subMenu; ++#else + void Set(void); + bool Update(bool Force = false); ++#endif /* SETUP */ + public: + cMenuMain(eOSState State = osUnknown); + virtual eOSState ProcessKey(eKeys Key); + static cOsdObject *PluginOsdObject(void); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuMain"; } ++#endif /* GRAPHTFT */ + }; + + class cDisplayChannel : public cOsdObject { +@@ -163,12 +191,18 @@ + eOSState Delete(void); + eOSState Info(void); + eOSState Commands(eKeys Key = kNone); ++#ifdef USE_LIEMIEXT ++ eOSState Rename(void); ++#endif /* LIEMIEXT */ + protected: + cRecording *GetRecording(cMenuRecordingItem *Item); + public: + cMenuRecordings(const char *Base = NULL, int Level = 0, bool OpenSubMenus = false); + ~cMenuRecordings(); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuRecordings"; } ++#endif /* GRAPHTFT */ + }; + + class cRecordControl { +@@ -212,11 +246,21 @@ + class cReplayControl : public cDvbPlayerControl { + private: + cSkinDisplayReplay *displayReplay; ++#ifdef USE_JUMPPLAY ++ cMarksReload marks; ++#else + cMarks marks; ++#endif /* JUMPPLAY */ + bool visible, modeOnly, shown, displayFrames; + int lastCurrent, lastTotal; + bool lastPlay, lastForward; + int lastSpeed; ++#ifdef USE_LIEMIEXT ++ int lastSkipSeconds; ++ int lastSkipSecondsSlow; ++ eKeys lastSkipKey; ++ cTimeMs lastSkipTimeout; ++#endif /* LIEMIEXT */ + time_t timeoutShow; + bool timeSearchActive, timeSearchHide; + int timeSearchTime, timeSearchPos; +@@ -234,9 +278,17 @@ + void MarkMove(bool Forward); + void EditCut(void); + void EditTest(void); ++#ifdef USE_DVDARCHIVE ++ void ChaptersJump(bool Forward); ++ bool canJumpChapters; ++ char *dvdchapters; ++#endif /* DVDARCHIVE */ + public: + cReplayControl(void); + virtual ~cReplayControl(); ++#ifdef USE_DELTIMESHIFTREC ++ void Stop(void); ++#endif /* DELTIMESHIFTREC */ + virtual cOsdObject *GetInfo(void); + virtual eOSState ProcessKey(eKeys Key); + virtual void Show(void); +diff -NaurwB vdr-1.7.10/menuitems.c vdr-1.7.10-patched/menuitems.c +--- vdr-1.7.10/menuitems.c 2009-05-03 15:37:55.000000000 +0200 ++++ vdr-1.7.10-patched/menuitems.c 2009-12-18 06:25:25.000000000 +0100 +@@ -33,9 +33,20 @@ + free(name); + } + ++#ifdef USE_VALIDINPUT ++void cMenuEditItem::SetValue(const char *Value, bool HasPre, bool HasSucc) ++#else + void cMenuEditItem::SetValue(const char *Value) ++#endif /* VALIDINPUT */ + { + cString buffer = cString::sprintf("%s:\t%s", name, Value); ++#ifdef USE_VALIDINPUT ++ if (Setup.ShowValidInput) { ++ if (HasPre && HasSucc) buffer = cString::sprintf("%s:\t<%s>", name, Value); ++ else if (HasPre) buffer = cString::sprintf("%s:\t<%s", name, Value); ++ else if (HasSucc) buffer = cString::sprintf("%s:\t%s>", name, Value); ++ } ++#endif /* VALIDINPUT */ + SetText(buffer); + cStatus::MsgOsdCurrentItem(buffer); + } +@@ -127,7 +138,11 @@ + { + char buf[16]; + snprintf(buf, sizeof(buf), "%s", *value ? trueString : falseString); ++#ifdef USE_VALIDINPUT ++ SetValue(buf, *value, !*value); ++#else + SetValue(buf); ++#endif /* VALIDINPUT */ + } + + // --- cMenuEditBitItem ------------------------------------------------------ +@@ -685,6 +700,170 @@ + return osContinue; + } + ++#ifdef USE_LIEMIEXT ++// --- cMenuEditRecPathItem -------------------------------------------------- ++ ++cMenuEditRecPathItem::cMenuEditRecPathItem(const char* Name, char* Path, ++ int Length): cMenuEditStrItem(Name, Path, Length, tr(FileNameChars)) ++{ ++ SetBase(Path); ++} ++ ++cMenuEditRecPathItem::~cMenuEditRecPathItem() ++{ ++} ++ ++void cMenuEditRecPathItem::SetBase(const char* Path) ++{ ++ if (!Path) ++ base[0] = 0; ++ Utf8Strn0Cpy(base, Path, sizeof(base)); ++ char* p = strrchr(base, '~'); ++ if (p) ++ p[0] = 0; ++ else ++ base[0] = 0; ++} ++ ++void cMenuEditRecPathItem::FindNextLevel() ++{ ++ char item[MaxFileName]; ++ ++ for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) ++ { ++ char* p; ++ Utf8Strn0Cpy(item, recording->Name(), sizeof(item)); ++ stripspace(value); ++ lengthUtf8 = Utf8ToArray(value, valueUtf8, length); ++ if (!lengthUtf8) ++ p = strchr(item, '~'); ++ else { ++ if (strstr(item, value) != item) ++ continue; ++ if (item[strlen(value)] != '~') ++ continue; ++ p = strchr(item + strlen(value) + 1, '~'); ++ } ++ if (!p) ++ continue; ++ p[0] = 0; ++ Utf8Strn0Cpy(base, value, length); ++ Utf8Strn0Cpy(value, item, length); ++ lengthUtf8 = Utf8ToArray(value, valueUtf8, length); ++ return; ++ } ++} ++ ++void cMenuEditRecPathItem::Find(bool Next) ++{ ++ char item[MaxFileName]; ++ char lastItem[MaxFileName] = ""; ++ ++ for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) ++ { ++ const char* recName = recording->Name(); ++ if (Utf8StrLen(base) && strstr(recName, base) != recName) ++ continue; ++ if (strlen(base) && recName[strlen(base)] != '~') ++ continue; ++ Utf8Strn0Cpy(item, recName, sizeof(item)); ++ char* p = strchr(item + strlen(base) + 1, '~'); ++ if (!p) ++ continue; ++ p[0] = 0; ++ if (!Next && (strcmp(item, value) == 0)) { ++ if (strlen(lastItem)) ++ Utf8Strn0Cpy(value, lastItem, length); ++ lengthUtf8 = Utf8ToArray(value, valueUtf8, length); ++ return; ++ } ++ if (strcmp(lastItem, item) != 0) { ++ if(Next && Utf8StrLen(lastItem) && strcmp(lastItem, value) == 0) { ++ Utf8Strn0Cpy(value, item, length); ++ lengthUtf8 = Utf8ToArray(value, valueUtf8, length); ++ return; ++ } ++ Utf8Strn0Cpy(lastItem, item, sizeof(lastItem)); ++ } ++ } ++} ++ ++void cMenuEditRecPathItem::SetHelpKeys(void) ++{ ++ cSkinDisplay::Current()->SetButtons(tr("Rename$Up"), tr("Rename$Down"), tr("Rename$Previous"), tr("Rename$Next")); ++} ++ ++eOSState cMenuEditRecPathItem::ProcessKey(eKeys Key) ++{ ++ switch (Key) { ++ case kLeft: ++ case kRed: // one level up ++ if (!InEditMode()) ++ return cMenuEditItem::ProcessKey(Key); ++ Utf8Strn0Cpy(value, base, lengthUtf8); ++ lengthUtf8 = Utf8ToArray(value, valueUtf8, length); ++ SetBase(base); ++ pos = Utf8StrLen(base); ++ if (pos) ++ pos++; ++ if (!lengthUtf8) { ++ Utf8Strn0Cpy(value, " ", length); ++ lengthUtf8 = Utf8ToArray(value, valueUtf8, length); ++ } ++ break; ++ case kRight: ++ case kGreen: // one level down ++ if (InEditMode()) ++ FindNextLevel(); ++ else ++ EnterEditMode(); ++ if (!lengthUtf8) { ++ Utf8Strn0Cpy(value, " ", length); ++ lengthUtf8 = Utf8ToArray(value, valueUtf8, length); ++ } ++ pos = Utf8StrLen(base); ++ if (pos) ++ pos++; ++ SetHelpKeys(); ++ break; ++ case kUp|k_Repeat: ++ case kUp: ++ case kYellow|k_Repeat: ++ case kYellow: // previous directory in list ++ if (!InEditMode()) ++ return cMenuEditItem::ProcessKey(Key); ++ Find(false); ++ pos = Utf8StrLen(base); ++ if (pos) ++ pos++; ++ break; ++ case kDown|k_Repeat: ++ case kDown: ++ case kBlue|k_Repeat: ++ case kBlue: // next directory in list ++ if (!InEditMode()) ++ return cMenuEditItem::ProcessKey(Key); ++ Find(true); ++ pos = Utf8StrLen(base); ++ if (pos) ++ pos++; ++ break; ++ case kOk: // done ++ if (!InEditMode()) ++ return cMenuEditItem::ProcessKey(Key); ++ stripspace(value); ++ lengthUtf8 = Utf8ToArray(value, valueUtf8, length); ++ cSkinDisplay::Current()->SetButtons(NULL); ++ LeaveEditMode(Key == kOk); ++ break; ++ default: ++ return cMenuEditItem::ProcessKey(Key); ++ } ++ Set(); ++ return osContinue; ++} ++#endif /* LIEMIEXT */ ++ + // --- cMenuEditStraItem ----------------------------------------------------- + + cMenuEditStraItem::cMenuEditStraItem(const char *Name, int *Value, int NumStrings, const char * const *Strings) +@@ -696,7 +875,11 @@ + + void cMenuEditStraItem::Set(void) + { ++#ifdef USE_VALIDINPUT ++ SetValue(strings[*value], (*value > min), (*value < max)); ++#else + SetValue(strings[*value]); ++#endif /* VALIDINPUT */ + } + + // --- cMenuEditChanItem ----------------------------------------------------- +diff -NaurwB vdr-1.7.10/menuitems.h vdr-1.7.10-patched/menuitems.h +--- vdr-1.7.10/menuitems.h 2009-05-03 14:50:34.000000000 +0200 ++++ vdr-1.7.10-patched/menuitems.h 2009-12-18 06:25:25.000000000 +0100 +@@ -21,7 +21,11 @@ + public: + cMenuEditItem(const char *Name); + ~cMenuEditItem(); ++#ifdef USE_VALIDINPUT ++ void SetValue(const char *Value, bool HasPre=false, bool HasSucc=false); ++#else + void SetValue(const char *Value); ++#endif /* VALIDINPUT */ + }; + + class cMenuEditIntItem : public cMenuEditItem { +@@ -90,26 +94,46 @@ + + class cMenuEditStrItem : public cMenuEditItem { + private: ++#ifdef USE_LIEMIEXT ++ int offset; ++#else + char *value; + int length; + const char *allowed; + int pos, offset; ++#endif /* LIEMIEXT */ + bool insert, newchar, uppercase; ++#ifndef USE_LIEMIEXT + int lengthUtf8; + uint *valueUtf8; ++#endif /* LIEMIEXT */ + uint *allowedUtf8; + uint *charMapUtf8; + uint *currentCharUtf8; + eKeys lastKey; + cTimeMs autoAdvanceTimeout; ++#ifndef USE_LIEMIEXT + void SetHelpKeys(void); ++#endif /* LIEMIEXT */ + uint *IsAllowed(uint c); + void AdvancePos(void); ++#ifndef USE_LIEMIEXT + virtual void Set(void); ++#endif /* LIEMIEXT */ + uint Inc(uint c, bool Up); + void Insert(void); + void Delete(void); + protected: ++#ifdef USE_LIEMIEXT ++ char *value; ++ int length; ++ uint *valueUtf8; ++ int lengthUtf8; ++ const char *allowed; ++ int pos; ++ void SetHelpKeys(void); ++ virtual void Set(void); ++#endif /* LIEMIEXT */ + void EnterEditMode(void); + void LeaveEditMode(bool SaveValue = false); + bool InEditMode(void) { return valueUtf8 != NULL; } +@@ -119,6 +143,21 @@ + virtual eOSState ProcessKey(eKeys Key); + }; + ++#ifdef USE_LIEMIEXT ++class cMenuEditRecPathItem : public cMenuEditStrItem { ++protected: ++ char base[MaxFileName]; ++ virtual void SetHelpKeys(void); ++ void SetBase(const char* Path); ++ void FindNextLevel(); ++ void Find(bool Next); ++public: ++ cMenuEditRecPathItem(const char* Name, char* Path, int Length); ++ ~cMenuEditRecPathItem(); ++ virtual eOSState ProcessKey(eKeys Key); ++ }; ++#endif /* LIEMIEXT */ ++ + class cMenuEditStraItem : public cMenuEditIntItem { + private: + const char * const *strings; +@@ -197,6 +236,9 @@ + cMenuSetupPage(void); + virtual eOSState ProcessKey(eKeys Key); + void SetPlugin(cPlugin *Plugin); ++#ifdef USE_GRAPHTFT ++ const char* MenuKind() { return "MenuSetupPage"; } ++#endif /* GRAPHTFT */ + }; + + #endif //__MENUITEMS_H +diff -NaurwB vdr-1.7.10/menuorgpatch.h vdr-1.7.10-patched/menuorgpatch.h +--- vdr-1.7.10/menuorgpatch.h 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/menuorgpatch.h 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,102 @@ ++#ifdef USE_MENUORG ++/* ++ * vdr-menuorg - A plugin for the Linux Video Disk Recorder ++ * Copyright (c) 2007 - 2008 Tobias Grimm ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by the ++ * Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ++ * details. ++ * ++ * You should have received a copy of the GNU General Public License along with ++ * this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ * ++ * $Id$ ++ * ++ */ ++ ++#ifndef __MENUORGPATCH_H ++#define __MENUORGPATCH_H ++ ++#include "mainmenuitemsprovider.h" ++ ++class MenuOrgPatch ++{ ++ private: ++ static IMainMenuItemsProvider* _mainMenuItemsProvider; ++ ++ private: ++ static IMainMenuItemsProvider* MainMenuItemsProvider() ++ { ++ if (!_mainMenuItemsProvider) ++ { ++ IMainMenuItemsProvider* mainMenuItemsProvider; ++ ++ if (cPluginManager::CallFirstService(MENU_ITEMS_PROVIDER_SERVICE_ID, &mainMenuItemsProvider)) ++ { ++ _mainMenuItemsProvider = mainMenuItemsProvider; ++ } ++ } ++ return _mainMenuItemsProvider; ++ } ++ ++ public: ++ static bool IsCustomMenuAvailable() ++ { ++ return (MainMenuItemsProvider() != NULL) && (MainMenuItemsProvider()->IsCustomMenuAvailable()); ++ } ++ ++ static void EnterRootMenu() ++ { ++ if (MainMenuItemsProvider()) ++ { ++ MainMenuItemsProvider()->EnterRootMenu(); ++ } ++ } ++ ++ static bool LeaveSubMenu() ++ { ++ if (MainMenuItemsProvider()) ++ { ++ return MainMenuItemsProvider()->LeaveSubMenu(); ++ } ++ return false; ++ } ++ ++ static void EnterSubMenu(cOsdItem* item) ++ { ++ if (MainMenuItemsProvider()) ++ { ++ MainMenuItemsProvider()->EnterSubMenu(item); ++ } ++ } ++ ++ static MenuItemDefinitions* MainMenuItems() ++ { ++ if (MainMenuItemsProvider()) ++ { ++ return MainMenuItemsProvider()->MainMenuItems(); ++ } ++ return NULL; ++ } ++ ++ static cOsdMenu* Execute(cOsdItem* item) ++ { ++ if (MainMenuItemsProvider()) ++ { ++ return MainMenuItemsProvider()->Execute(item); ++ } ++ return NULL; ++ } ++}; ++ ++IMainMenuItemsProvider* MenuOrgPatch::_mainMenuItemsProvider = NULL; ++ ++#endif //__MENUORGPATCH_H ++#endif /* MENUORG */ +diff -NaurwB vdr-1.7.10/osdbase.c vdr-1.7.10-patched/osdbase.c +--- vdr-1.7.10/osdbase.c 2009-06-01 13:54:50.000000000 +0200 ++++ vdr-1.7.10-patched/osdbase.c 2009-12-18 06:25:25.000000000 +0100 +@@ -22,6 +22,9 @@ + state = State; + selectable = true; + fresh = true; ++#if defined (USE_SETUP) && defined (USE_PINPLUGIN) ++ subMenu = 0; ++#endif /* SETUP & PINPLUGIN */ + } + + cOsdItem::cOsdItem(const char *Text, eOSState State, bool Selectable) +@@ -31,8 +34,23 @@ + selectable = Selectable; + fresh = true; + SetText(Text); ++#if defined (USE_SETUP) && defined (USE_PINPLUGIN) ++ subMenu = 0; ++#endif /* SETUP & PINPLUGIN */ + } + ++#if defined (USE_SETUP) && defined (USE_PINPLUGIN) ++cOsdItem::cOsdItem(const char *Text, eOSState State, cSubMenuNode* SubMenu) ++{ ++ text = NULL; ++ state = State; ++ selectable = true; ++ fresh = true; ++ SetText(Text); ++ subMenu = SubMenu; ++} ++#endif /* SETUP & PINPLUGIN */ ++ + cOsdItem::~cOsdItem() + { + free(text); +@@ -77,6 +95,9 @@ + { + isMenu = true; + digit = 0; ++#ifdef USE_LIEMIEXT ++ key_nr = -1; ++#endif /* LIEMIEXT */ + hasHotkeys = false; + title = NULL; + SetTitle(Title); +@@ -97,6 +118,9 @@ + free(status); + displayMenu->Clear(); + cStatus::MsgOsdClear(); ++#ifdef USE_GRAPHTFT ++ cStatus::MsgOsdMenuDestroy(); ++#endif /* GRAPHTFT */ + if (!--displayMenuCount) + DELETENULL(displayMenu); + } +@@ -119,7 +143,11 @@ + digit = -1; // prevents automatic hotkeys - input already has them + if (digit >= 0) { + digit++; ++#ifdef USE_LIEMIEXT ++ buffer = cString::sprintf(" %2d%s %s", digit, (digit > 9) ? "" : " ", s); ++#else + buffer = cString::sprintf(" %c %s", (digit < 10) ? '0' + digit : ' ' , s); ++#endif /* LIEMIEXT */ + s = buffer; + } + } +@@ -199,9 +227,15 @@ + subMenu->Display(); + return; + } ++#ifdef USE_OSDMAXITEMS ++ displayMenuItems = displayMenu->MaxItems(); ++#endif /* OSDMAXITEMS */ + displayMenu->SetMessage(mtStatus, NULL); + displayMenu->Clear(); + cStatus::MsgOsdClear(); ++#ifdef USE_GRAPHTFT ++ cStatus::MsgOsdMenuDisplay(MenuKind()); ++#endif /* GRAPHTFT */ + displayMenu->SetTabs(cols[0], cols[1], cols[2], cols[3], cols[4]);//XXX + displayMenu->SetTitle(title); + cStatus::MsgOsdTitle(title); +@@ -297,6 +331,9 @@ + + void cOsdMenu::CursorUp(void) + { ++#ifdef USE_OSDMAXITEMS ++ displayMenuItems = displayMenu->MaxItems(); ++#endif /* OSDMAXITEMS */ + int tmpCurrent = current; + int lastOnScreen = first + displayMenuItems - 1; + int last = Count() - 1; +@@ -335,6 +372,9 @@ + + void cOsdMenu::CursorDown(void) + { ++#ifdef USE_OSDMAXITEMS ++ displayMenuItems = displayMenu->MaxItems(); ++#endif /* OSDMAXITEMS */ + int tmpCurrent = current; + int lastOnScreen = first + displayMenuItems - 1; + int last = Count() - 1; +@@ -375,6 +415,9 @@ + + void cOsdMenu::PageUp(void) + { ++#ifdef USE_OSDMAXITEMS ++ displayMenuItems = displayMenu->MaxItems(); ++#endif /* OSDMAXITEMS */ + int oldCurrent = current; + int oldFirst = first; + current -= displayMenuItems; +@@ -409,6 +452,9 @@ + + void cOsdMenu::PageDown(void) + { ++#ifdef USE_OSDMAXITEMS ++ displayMenuItems = displayMenu->MaxItems(); ++#endif /* OSDMAXITEMS */ + int oldCurrent = current; + int oldFirst = first; + current += displayMenuItems; +@@ -449,6 +495,64 @@ + } + } + ++#ifdef USE_LIEMIEXT ++#define MENUKEY_TIMEOUT 1500 ++ ++eOSState cOsdMenu::HotKey(eKeys Key) ++{ ++ bool match = false; ++ bool highlight = false; ++ int item_nr; ++ int i; ++ ++ if (Key == kNone) { ++ if (lastActivity.TimedOut()) ++ Key = kOk; ++ else ++ return osContinue; ++ } ++ else { ++ lastActivity.Set(MENUKEY_TIMEOUT); ++ } ++ for (cOsdItem *item = Last(); item; item = Prev(item)) { ++ const char *s = item->Text(); ++ i = 0; ++ item_nr = 0; ++ if (s && (s = skipspace(s)) != '\0' && '0' <= s[i] && s[i] <= '9') { ++ do { ++ item_nr = item_nr * 10 + (s[i] - '0'); ++ } ++ while (!((s[++i] == '\t')||(s[i] == ' ')) && (s[i] != '\0') && ('0' <= s[i]) && (s[i] <= '9')); ++ if ((Key == kOk) && (item_nr == key_nr)) { ++ current = item->Index(); ++ RefreshCurrent(); ++ Display(); ++ cRemote::Put(kOk, true); ++ key_nr = -1; ++ break; ++ } ++ else if (Key != kOk) { ++ if (!highlight && (item_nr == (Key - k0))) { ++ highlight = true; ++ current = item->Index(); ++ } ++ if (!match && (key_nr == -1) && ((item_nr / 10) == (Key - k0))) { ++ match = true; ++ key_nr = (Key - k0); ++ } ++ else if (((key_nr == -1) && (item_nr == (Key - k0))) || (!match && (key_nr >= 0) && (item_nr == (10 * key_nr + Key - k0)))) { ++ current = item->Index(); ++ cRemote::Put(kOk, true); ++ key_nr = -1; ++ break; ++ } ++ } ++ } ++ } ++ if ((!match) && (Key != kNone)) { ++ key_nr = -1; ++ } ++#else + eOSState cOsdMenu::HotKey(eKeys Key) + { + for (cOsdItem *item = First(); item; item = Next(item)) { +@@ -463,6 +567,7 @@ + } + } + } ++#endif /* LIEMIEXT */ + return osContinue; + } + +@@ -501,8 +606,13 @@ + } + } + switch (Key) { ++#ifdef USE_LIEMIEXT ++ case kNone: ++ case k0...k9: return hasHotkeys ? HotKey(Key) : osUnknown; ++#else + case k0: return osUnknown; + case k1...k9: return hasHotkeys ? HotKey(Key) : osUnknown; ++#endif /* LIEMIEXT */ + case kUp|k_Repeat: + case kUp: CursorUp(); break; + case kDown|k_Repeat: +diff -NaurwB vdr-1.7.10/osdbase.h vdr-1.7.10-patched/osdbase.h +--- vdr-1.7.10/osdbase.h 2007-11-03 15:50:52.000000000 +0100 ++++ vdr-1.7.10-patched/osdbase.h 2009-12-18 06:25:25.000000000 +0100 +@@ -15,6 +15,10 @@ + #include "skins.h" + #include "tools.h" + ++#if defined (USE_SETUP) && defined (USE_PINPLUGIN) ++#include "submenu.h" ++#endif /* SETUP & PINPLUGIN */ ++ + enum eOSState { osUnknown, + osContinue, + osSchedule, +@@ -51,16 +55,26 @@ + char *text; + eOSState state; + bool selectable; ++#if defined (USE_SETUP) && defined (USE_PINPLUGIN) ++ cSubMenuNode* subMenu; ++#endif /* SETUP & PINPLUGIN */ + protected: + bool fresh; + public: + cOsdItem(eOSState State = osUnknown); + cOsdItem(const char *Text, eOSState State = osUnknown, bool Selectable = true); ++#if defined (USE_SETUP) && defined (USE_PINPLUGIN) ++ cOsdItem(const char *Text, eOSState State, cSubMenuNode* SubMenu); ++#endif /* SETUP & PINPLUGIN */ + virtual ~cOsdItem(); + bool Selectable(void) const { return selectable; } + void SetText(const char *Text, bool Copy = true); + void SetSelectable(bool Selectable); + void SetFresh(bool Fresh); ++#if defined (USE_SETUP) && defined (USE_PINPLUGIN) ++ void SetSubMenu(cSubMenuNode* SubMenu) { subMenu = SubMenu; } ++ cSubMenuNode* SubMenu() { return subMenu; } ++#endif /* SETUP & PINPLUGIN */ + const char *Text(void) const { return text; } + virtual void Set(void) {} + virtual eOSState ProcessKey(eKeys Key); +@@ -95,6 +109,10 @@ + char *status; + int digit; + bool hasHotkeys; ++#ifdef USE_LIEMIEXT ++ int key_nr; ++ cTimeMs lastActivity; ++#endif /* LIEMIEXT */ + protected: + void SetDisplayMenu(void); + cSkinDisplayMenu *DisplayMenu(void) { return displayMenu; } +@@ -129,6 +147,9 @@ + void Ins(cOsdItem *Item, bool Current = false, cOsdItem *Before = NULL); + virtual void Display(void); + virtual eOSState ProcessKey(eKeys Key); ++#ifdef USE_GRAPHTFT ++ virtual const char* MenuKind() { return "MenuUnknown"; } ++#endif /* GRAPHTFT */ + }; + + #endif //__OSDBASE_H +diff -NaurwB vdr-1.7.10/osd.c vdr-1.7.10-patched/osd.c +--- vdr-1.7.10/osd.c 2009-05-09 12:42:35.000000000 +0200 ++++ vdr-1.7.10-patched/osd.c 2009-12-18 06:25:25.000000000 +0100 +@@ -725,6 +725,9 @@ + int cOsd::osdWidth = 0; + int cOsd::osdHeight = 0; + cVector cOsd::Osds; ++#ifdef USE_PINPLUGIN ++bool cOsd::pinValid = false; ++#endif /* PINPLUGIN */ + + cOsd::cOsd(int Left, int Top, uint Level) + { +@@ -735,6 +738,9 @@ + width = height = 0; + level = Level; + active = false; ++#ifdef USE_YAEPG ++ vidWin.bpp = 0; ++#endif /* YAEPG */ + for (int i = 0; i < Osds.Size(); i++) { + if (Osds[i]->level > level) { + Osds.Insert(this, i); +diff -NaurwB vdr-1.7.10/osd.h vdr-1.7.10-patched/osd.h +--- vdr-1.7.10/osd.h 2009-05-08 15:41:03.000000000 +0200 ++++ vdr-1.7.10-patched/osd.h 2009-12-18 06:25:25.000000000 +0100 +@@ -401,6 +401,12 @@ + ///< 7: vertical, falling, upper + virtual void Flush(void); + ///< Actually commits all data to the OSD hardware. ++#ifdef USE_PINPLUGIN ++ static bool pinValid; ++#endif /* PINPLUGIN */ ++#ifdef USE_YAEPG ++ tArea vidWin; ++#endif /* YAEPG */ + }; + + class cOsdProvider { +diff -NaurwB vdr-1.7.10/pat.c vdr-1.7.10-patched/pat.c +--- vdr-1.7.10/pat.c 2009-08-16 17:01:03.000000000 +0200 ++++ vdr-1.7.10-patched/pat.c 2009-12-18 06:25:25.000000000 +0100 +@@ -13,6 +13,9 @@ + #include "libsi/section.h" + #include "libsi/descriptor.h" + #include "thread.h" ++#ifdef USE_TTXTSUBS ++#include "vdrttxtsubshooks.h" ++#endif /* TTXTSUBS */ + + #define PMT_SCAN_TIMEOUT 10 // seconds + +@@ -341,6 +344,11 @@ + char DLangs[MAXDPIDS][MAXLANGCODE2] = { "" }; + char SLangs[MAXSPIDS][MAXLANGCODE2] = { "" }; + int Tpid = 0; ++#ifdef USE_TTXTSUBS ++ char TLangs[MAXTPAGES][MAXLANGCODE2] = { "" }; ++ int TPages[MAXTPAGES + 1] = { 0 }; ++ int NumTPages = 0; ++#endif /* TTXTSUBS */ + int NumApids = 0; + int NumDpids = 0; + int NumSpids = 0; +@@ -422,8 +430,24 @@ + NumSpids++; + } + break; ++#ifdef USE_TTXTSUBS ++ case SI::TeletextDescriptorTag: { ++ Tpid = esPid; ++ SI::TeletextDescriptor *sd = (SI::TeletextDescriptor *)d; ++ SI::TeletextDescriptor::Teletext ttxt; ++ for (SI::Loop::Iterator it; sd->teletextLoop.getNext(ttxt, it); ) { ++ if ((NumTPages < MAXTPAGES) && ttxt.languageCode[0] && ((ttxt.getTeletextType() == 0x02) || (ttxt.getTeletextType() == 0x05))) { ++ char *s = TLangs[NumTPages]; ++ strn0cpy(s, I18nNormalizeLanguageCode(ttxt.languageCode), MAXLANGCODE1); ++ TPages[NumTPages] = (ttxt.getTeletextPageNumber() & 0xff) | ((ttxt.getTeletextMagazineNumber() & 0xff) << 8) | ((ttxt.getTeletextType() & 0xff) << 16); ++ NumTPages++; ++ } ++ } ++ } ++#else + case SI::TeletextDescriptorTag: + Tpid = esPid; ++#endif /* TTXTSUBS */ + break; + case SI::ISO639LanguageDescriptorTag: { + SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; +@@ -452,6 +476,18 @@ + } + if (Setup.UpdateChannels >= 2) { + Channel->SetPids(Vpid, Ppid, Vtype, Apids, ALangs, Dpids, DLangs, Spids, SLangs, Tpid); ++#ifdef USE_TTXTSUBS ++ if (NumTPages < MAXTPAGES) { ++ int manualPageNumber = cVDRTtxtsubsHookListener::Hook()->ManualPageNumber(Channel); ++ if (manualPageNumber) { ++ char *s = TLangs[NumTPages]; ++ strn0cpy(s, "man", MAXLANGCODE1); ++ TPages[NumTPages] = manualPageNumber; ++ NumTPages++; ++ } ++ } ++ Channel->SetTPidData(TLangs, TPages); ++#endif /* TTXTSUBS */ + Channel->SetCaIds(CaDescriptors->CaIds()); + Channel->SetSubtitlingDescriptors(SubtitlingTypes, CompositionPageIds, AncillaryPageIds); + } +diff -NaurwB vdr-1.7.10/plugin.c vdr-1.7.10-patched/plugin.c +--- vdr-1.7.10/plugin.c 2009-04-05 12:16:48.000000000 +0200 ++++ vdr-1.7.10-patched/plugin.c 2009-12-18 06:25:25.000000000 +0100 +@@ -317,6 +317,14 @@ + char *p = strchr(s, ' '); + if (p) + *p = 0; ++#ifdef USE_PLUGINMISSING ++ struct stat st; ++ if (stat (cString::sprintf("%s/%s%s%s%s", directory, LIBVDR_PREFIX, s, SO_INDICATOR, APIVERSION), &st) && errno == ENOENT) { ++ esyslog("WARN: missing plugin '%s'", s); ++ fprintf(stderr, "vdr: missing plugin '%s'\n", s); ++ } ++ else ++#endif /* PLUGINMISSING */ + dlls.Add(new cDll(cString::sprintf("%s/%s%s%s%s", directory, LIBVDR_PREFIX, s, SO_INDICATOR, APIVERSION), Args)); + free(s); + } +@@ -325,7 +333,11 @@ + { + for (cDll *dll = dlls.First(); dll; dll = dlls.Next(dll)) { + if (!dll->Load(Log)) ++#ifdef USE_PLUGINMISSING ++ ; ++#else + return false; ++#endif /* PLUGINMISSING */ + } + return true; + } +diff -NaurwB vdr-1.7.10/po/de_DE.po vdr-1.7.10-patched/po/de_DE.po +--- vdr-1.7.10/po/de_DE.po 2009-11-22 12:30:27.000000000 +0100 ++++ vdr-1.7.10-patched/po/de_DE.po 2009-12-18 06:25:25.000000000 +0100 +@@ -806,6 +806,30 @@ + msgid "Setup.Miscellaneous$Emergency exit" + msgstr "Notausstieg" + ++msgid "Setup.Miscellaneous$Volume ctrl with left/right" ++msgstr "Lautstärke mit Rechts/Links regeln" ++ ++msgid "Setup.Miscellaneous$Channelgroups with left/right" ++msgstr "Kanalgruppen mit Rechts/Links" ++ ++msgid "Setup.Miscellaneous$only in channelinfo" ++msgstr "nur in Kanalinfo" ++ ++msgid "Setup.Miscellaneous$Search fwd/back with left/right" ++msgstr "Vor-/Rücklauf mit Rechts/Links" ++ ++msgid "Setup.Miscellaneous$only in progress display" ++msgstr "nur in Fortschrittsanzeige" ++ ++msgid "Setup.Miscellaneous$Lirc repeat delay" ++msgstr "Lirc Verzögerung" ++ ++msgid "Setup.Miscellaneous$Lirc repeat freq" ++msgstr "Lirc Frequenz" ++ ++msgid "Setup.Miscellaneous$Lirc repeat timeout" ++msgstr "Lirc Zeitbeschränkung" ++ + msgid "Plugins" + msgstr "Plugins" + +@@ -1028,3 +1052,306 @@ + #, c-format + msgid "VDR will shut down in %s minutes" + msgstr "VDR wird in %s Minuten ausschalten" ++ ++msgid "Parameters" ++msgstr "Parameter" ++ ++msgid "Channel locked by LNB!" ++msgstr "Kanal durch LNB gesperrt!" ++ ++msgid "Childlock" ++msgstr "Kindersicherung" ++ ++msgid "Path" ++msgstr "Pfad" ++ ++msgid "Timer commands" ++msgstr "Befehle für Aufzeichnungen" ++ ++msgid "Rename recording" ++msgstr "Aufzeichnung umbenennen" ++ ++msgid "Date" ++msgstr "Datum" ++ ++msgid "Length" ++msgstr "Länge" ++ ++msgid "Size" ++msgstr "Größe" ++ ++msgid "Delete marks information?" ++msgstr "Marks löschen?" ++ ++msgid "Delete resume information?" ++msgstr "Resume löschen?" ++ ++msgid "DVD plugin is not installed!" ++msgstr "Das DVD-Plugin ist nicht installiert!" ++ ++msgid "main dir alphabetically, subdirs flexible" ++msgstr "Alphabet für Haupt-, flexibel für Unterverzeichnisse" ++ ++msgid "main dir by date, subdirs flexible" ++msgstr "Datum für Haupt-, flexibel für Unterverzeichnisse" ++ ++msgid "all alphabetically" ++msgstr "Alles alphabetisch" ++ ++msgid "all by date" ++msgstr "Alles nach Datum" ++ ++msgid "Sorting" ++msgstr "Sortierung" ++ ++msgid "Setup.OSD$WarEagle icons" ++msgstr "WarEagle icons verwenden" ++ ++msgid "Setup.OSD$Main menu title" ++msgstr "Hauptmenü Titel" ++ ++msgid "Setup.OSD$- Text" ++msgstr "- Text" ++ ++msgid "default" ++msgstr "Voreinstellung" ++ ++msgid "VDR - text" ++msgstr "VDR - Text" ++ ++msgid "text" ++msgstr "Text" ++ ++msgid "VDR - version" ++msgstr "VDR - Version" ++ ++msgid "Setup.OSD$Main menu command position" ++msgstr "Befehle Position im Hauptmenü" ++ ++msgid "Setup.OSD$Show valid input" ++msgstr "Zeige gültige Eingabe" ++ ++msgid "Setup.EPG$Show progress bar" ++msgstr "Zeitbalken anzeigen" ++ ++msgid "Setup.EPG$Period for double EPG search(min)" ++msgstr "Zeitspanne für dop. EPG-Suche (min)" ++ ++msgid "Setup.EPG$Extern double Epg entry" ++msgstr "Doppelten externen EPG-Eintrag" ++ ++msgid "Setup.EPG$Mode of noEPG-Patch" ++msgstr "Art des noEPG-Patches" ++ ++msgid "adjust" ++msgstr "anwenden" ++ ++msgid "delete" ++msgstr "löschen" ++ ++msgid "Setup.EPG$Mix intern and extern EPG" ++msgstr "Internes und externes EPG mischen" ++ ++msgid "Setup.EPG$Disable running VPS event" ++msgstr "Erk. des lauf. VPS-Events abschalten" ++ ++msgid "Setup.DVB$Use AC3-Transfer Fix" ++msgstr "AC3-Transfer Fix benutzen" ++ ++msgid "Setup.DVB$Channel Blocker" ++msgstr "Sperre Kanäle" ++ ++msgid "Setup.DVB$Channel Blocker Filter Mode" ++msgstr "Kanal Sperren Filter" ++ ++msgid "qam256" ++msgstr "qam256" ++ ++msgid "dvb-c" ++msgstr "dvb-c" ++ ++msgid "dvb-s" ++msgstr "dvb-s" ++ ++msgid "all" ++msgstr "alle" ++ ++msgid "blacklist" ++msgstr "Blacklist" ++ ++msgid "whitelist" ++msgstr "Whitelist" ++ ++msgid "has decoder" ++msgstr "mit Decoder" ++ ++msgid "is primary" ++msgstr "ist primär" ++ ++msgid "has decoder + is primary" ++msgstr "mit Decoder und primär" ++ ++msgid "Setup.LNB$DVB device %d uses LNB No." ++msgstr "DVB-Empfänger %d nutzt LNB Nr." ++ ++msgid "Setup.LNB$Log LNB usage" ++msgstr "LNB-Nutzung protokollieren" ++ ++msgid "Setup.Recording$Record Dolby Digital" ++msgstr "Dolby Digital Ton aufzeichnen" ++ ++msgid "Setup.Recording$Video directory policy" ++msgstr "Videoverzeichnispolitik" ++ ++msgid "Setup.Recording$Number of video directories" ++msgstr "Anzahl der Videoverzeichnisse" ++ ++msgid "Setup.Recording$Video %d priority" ++msgstr "Video %d Priorität" ++ ++msgid "Setup.Recording$Video %d min. free MB" ++msgstr "Video %d min. MB frei" ++ ++msgid "Setup.Recording$Friendly filenames" ++msgstr "Freundliche Dateinamen" ++ ++msgid "Setup.Recording$Max. recording size (GB)" ++msgstr "Max. Aufnahmengröße (GB)" ++ ++msgid "Setup.Recording$Hard Link Cutter" ++msgstr "Hard Link Cutter" ++ ++msgid "Setup.Recording$Delete timeshift recording" ++msgstr "Zeitversetzte Aufnahme löschen" ++ ++msgid "request" ++msgstr "abfragen" ++ ++msgid "Setup.Recording$Show date" ++msgstr "Aufnahmedatum anzeigen" ++ ++msgid "Setup.Recording$Show time" ++msgstr "Aufnahmezeit anzeigen" ++ ++msgid "Setup.Recording$Show length" ++msgstr "Länge der Aufnahme anzeigen" ++ ++msgid "Setup.Recording$Show end of timer" ++msgstr "Ende für Timer anzeigen" ++ ++msgid "Setup.Recording$Sort recordings by" ++msgstr "Aufnahmen sortieren nach" ++ ++msgid "Setup.Recording$Sort directories before recordings" ++msgstr "Verzeichnisse vor Aufnahmen einsortieren" ++ ++msgid "Setup.Recording$Cutter auto delete" ++msgstr "Aufnahmen nach dem Schneiden löschen" ++ ++msgid "Setup.Recording$Cutter adjust starttime" ++msgstr "Startzeit beim Schneiden anpassen" ++ ++msgid "Setup.Replay$Jump&Play" ++msgstr "Wiedergabe nach Sprung" ++ ++msgid "Setup.Replay$Play&Jump" ++msgstr "Sprung bei Schnittmarke" ++ ++msgid "Setup.Replay$Pause at last mark" ++msgstr "Pause bei letzter Marke" ++ ++msgid "Setup.Replay$Reload marks" ++msgstr "Marken aktualisieren" ++ ++msgid "Setup.Replay$Skip Seconds" ++msgstr "Sprungweite in Sekunden" ++ ++msgid "Setup.Replay$Skip Seconds Slow" ++msgstr "Sprungweite in Sekunden Langsam" ++ ++msgid "Setup.Replay$Length" ++msgstr "Länge" ++ ++msgid "Setup.Replay$Length / Number" ++msgstr "Länge / Nummer" ++ ++msgid "Setup.Replay$Number" ++msgstr "Nummer" ++ ++msgid "Setup.Replay$DVD display mode" ++msgstr "DVD Anzeige" ++ ++msgid "Setup.Replay$DVD display leading zeros" ++msgstr "DVD führende Nullen anzeigen" ++ ++msgid "Setup.Replay$never" ++msgstr "nie" ++ ++msgid "Setup.Replay$on begin" ++msgstr "am Anfang" ++ ++msgid "Setup.Replay$on end" ++msgstr "am Ende" ++ ++msgid "Setup.Replay$on begin and end" ++msgstr "am Anfang und Ende" ++ ++msgid "Setup.Replay$Tray open" ++msgstr "DVD-Schublade öffnen" ++ ++msgid "Setup.Replay$Limit DVD to speed" ++msgstr "DVD drosseln auf" ++ ++msgid "Jump" ++msgstr "Springen: " ++ ++msgid "Skip +60s" ++msgstr "Sprung +60s" ++ ++msgid "Skip -60s" ++msgstr "Sprung -60s" ++ ++msgid "Cutter already running - Add to cutting queue?" ++msgstr "Schnitt bereits aktiv - zur Schnitt-Liste hinzufügen?" ++ ++msgid "Format" ++msgstr "Format" ++ ++msgid "PES" ++msgstr "PES" ++ ++msgid "TS" ++msgstr "TS" ++ ++msgid "Rename$Up" ++msgstr "Höher" ++ ++msgid "Rename$Down" ++msgstr "Tiefer" ++ ++msgid "Rename$Previous" ++msgstr "Vorheriger" ++ ++msgid "Rename$Next" ++msgstr "Nächster" ++ ++msgid "Please mount %s" ++msgstr "Bitte %s einlegen!" ++ ++msgid "Please mount DVD %04d" ++msgstr "Bitte DVD %04d einlegen!" ++ ++msgid "Please mount DVD %d" ++msgstr "Bitte DVD %d einlegen!" ++ ++msgid "Please wait. Checking DVD..." ++msgstr "Bitte warten. Überprüfe DVD..." ++ ++msgid "No index-file found. Creating may take minutes. Create one?" ++msgstr "Keine Index-Datei gefunden. Erstellung kann Minuten dauern. Erstellen?" ++ ++msgid "Please wait. Creating index-file..." ++msgstr "Bitte warten. Index-Datei wird erstellt..." ++ ++msgid "Wrong DVD!" ++msgstr "Falsche DVD!" +diff -NaurwB vdr-1.7.10/po/et_EE.po vdr-1.7.10-patched/po/et_EE.po +--- vdr-1.7.10/po/et_EE.po 2009-11-22 12:28:38.000000000 +0100 ++++ vdr-1.7.10-patched/po/et_EE.po 2009-12-18 06:25:25.000000000 +0100 +@@ -1028,3 +1028,33 @@ + #, c-format + msgid "VDR will shut down in %s minutes" + msgstr "VDR lülitub välja %s minuti pärast" ++ ++msgid "Rename recording" ++msgstr "Ümbernimetamine" ++ ++msgid "Setup.OSD$Main menu command position" ++msgstr "Käsu asukoht peamenüüs" ++ ++msgid "Setup.EPG$Show progress bar" ++msgstr "Edenemisriba" ++ ++msgid "Setup.Recording$Show date" ++msgstr "Salvestuse kuupäev" ++ ++msgid "Setup.Recording$Show time" ++msgstr "Salvestuse kellaaeg" ++ ++msgid "Setup.Recording$Show length" ++msgstr "Salvestuse pikkus" ++ ++msgid "Rename$Up" ++msgstr "Üles" ++ ++msgid "Rename$Down" ++msgstr "Alla" ++ ++msgid "Rename$Previous" ++msgstr "Eelmine" ++ ++msgid "Rename$Next" ++msgstr "Järgmine" +diff -NaurwB vdr-1.7.10/po/fi_FI.po vdr-1.7.10-patched/po/fi_FI.po +--- vdr-1.7.10/po/fi_FI.po 2009-11-22 12:28:39.000000000 +0100 ++++ vdr-1.7.10-patched/po/fi_FI.po 2009-12-18 06:25:25.000000000 +0100 +@@ -42,6 +42,253 @@ + msgid "Starting EPG scan" + msgstr "Ohjelmaoppaan päivitys aloitettu" + ++msgid "Content$Movie/Drama" ++msgstr "Elokuva/draama" ++ ++msgid "Content$Detective/Thriller" ++msgstr "Etsivä/trilleri" ++ ++msgid "Content$Adventure/Western/War" ++msgstr "Seikkailu/western/sota" ++ ++msgid "Content$Science Fiction/Fantasy/Horror" ++msgstr "Scifi/fantasia/kauhu" ++ ++msgid "Content$Comedy" ++msgstr "Komedia" ++ ++msgid "Content$Soap/Melodrama/Folkloric" ++msgstr "Saippua/melodraama/kansanperinne" ++ ++msgid "Content$Romance" ++msgstr "Romanssi" ++ ++msgid "Content$Serious/Classical/Religious/Historical Movie/Drama" ++msgstr "Vakava/klassinen/uskonnollinen/historiallinen elokuva/draama" ++ ++msgid "Content$Adult Movie/Drama" ++msgstr "Aikuiselokuva/draama" ++ ++msgid "Content$News/Current Affairs" ++msgstr "Uutiset/ajankohtaisohjelma" ++ ++msgid "Content$News/Weather Report" ++msgstr "Uutiset/säätiedot" ++ ++msgid "Content$News Magazine" ++msgstr "Uutismakasiini" ++ ++msgid "Content$Documentary" ++msgstr "Dokumentti" ++ ++msgid "Content$Discussion/Inverview/Debate" ++msgstr "Keskustelu/haastattelu/väittely" ++ ++msgid "Content$Show/Game Show" ++msgstr "Show/visailu" ++ ++msgid "Content$Game Show/Quiz/Contest" ++msgstr "Visailu/kilpailu" ++ ++msgid "Content$Variety Show" ++msgstr "Varietee" ++ ++msgid "Content$Talk Show" ++msgstr "Keskusteluohjelma" ++ ++msgid "Content$Sports" ++msgstr "Urheilua" ++ ++msgid "Content$Special Event" ++msgstr "Erikoistapahtuma" ++ ++msgid "Content$Sport Magazine" ++msgstr "Urheilumakasiini" ++ ++msgid "Content$Football" ++msgstr "Jalkapallo" ++ ++msgid "Content$Tennis/Squash" ++msgstr "Tennis/Squash" ++ ++msgid "Content$Team Sports" ++msgstr "Joukkueurheilua" ++ ++msgid "Content$Athletics" ++msgstr "Yleisurheilua" ++ ++msgid "Content$Motor Sport" ++msgstr "Moottoriurheilua" ++ ++msgid "Content$Water Sport" ++msgstr "Vesiurheilua" ++ ++msgid "Content$Winter Sports" ++msgstr "Talviurheilua" ++ ++msgid "Content$Equestrian" ++msgstr "Ratsastusta" ++ ++msgid "Content$Martial Sports" ++msgstr "Kamppailu-urheilua" ++ ++msgid "Content$Children's/Youth Programmes" ++msgstr "Lasten ja nuorten ohjelma" ++ ++msgid "Content$Pre-school Children's Programmes" ++msgstr "Alle kouluikäisten ohjelma" ++ ++msgid "Content$Entertainment Programmes for 6 to 14" ++msgstr "Viihdeohjelma 6-14 vuotiaille" ++ ++msgid "Content$Entertainment Programmes for 10 to 16" ++msgstr "Viihdeohjelma 10-16 vuotiaille" ++ ++msgid "Content$Informational/Educational/School Programme" ++msgstr "Opetus/kouluohjelma" ++ ++msgid "Content$Cartoons/Puppets" ++msgstr "Piirretty/nukke-esitys" ++ ++msgid "Content$Music/Ballet/Dance" ++msgstr "Musiikki/baletti/tanssi" ++ ++msgid "Content$Rock/Pop" ++msgstr "Rock/pop" ++ ++msgid "Content$Serious/Classical Music" ++msgstr "Vakava/klassinen musiikki" ++ ++msgid "Content$Folk/Tradional Music" ++msgstr "Folk/kansanmusiikki" ++ ++msgid "Content$Jazz" ++msgstr "Jazz" ++ ++msgid "Content$Musical/Opera" ++msgstr "Musikaali/ooppera" ++ ++msgid "Content$Ballet" ++msgstr "Baletti" ++ ++msgid "Content$Arts/Culture" ++msgstr "Taide/kulttuuri" ++ ++msgid "Content$Performing Arts" ++msgstr "Performanssitaide" ++ ++msgid "Content$Fine Arts" ++msgstr "Kuvataide" ++ ++msgid "Content$Religion" ++msgstr "Uskonto" ++ ++msgid "Content$Popular Culture/Traditional Arts" ++msgstr "Populaarikulttuuri/perinnetaiteet" ++ ++msgid "Content$Literature" ++msgstr "Kirjallisuus" ++ ++msgid "Content$Film/Cinema" ++msgstr "Elokuvataide" ++ ++msgid "Content$Experimental Film/Video" ++msgstr "Kokeellinen elokuva/video" ++ ++msgid "Content$Broadcasting/Press" ++msgstr "Televisio/radio/lehdistö" ++ ++msgid "Content$New Media" ++msgstr "Uusmedia" ++ ++msgid "Content$Arts/Culture Magazines" ++msgstr "Taide/kulttuurimakasiini" ++ ++msgid "Content$Fashion" ++msgstr "Muoti" ++ ++msgid "Content$Social/Political/Economics" ++msgstr "Yhteiskunta/politiikka/talous" ++ ++msgid "Content$Magazines/Reports/Documentary" ++msgstr "Makasiini/reportaasi/dokumentti" ++ ++msgid "Content$Economics/Social Advisory" ++msgstr "Talous/yhteiskunnallinen neuvonta" ++ ++msgid "Content$Remarkable People" ++msgstr "Merkittävät henkilöt" ++ ++msgid "Content$Education/Science/Factual" ++msgstr "Koulutus/tiede" ++ ++msgid "Content$Nature/Animals/Environment" ++msgstr "Luonto/eläimet/ympäristö" ++ ++msgid "Content$Technology/Natural Sciences" ++msgstr "Teknologia/luonnontiede" ++ ++msgid "Content$Medicine/Physiology/Psychology" ++msgstr "Lääketiede/fysiologia/psykologia" ++ ++msgid "Content$Foreign Countries/Expeditions" ++msgstr "Vieraat maat/tutkimusretket" ++ ++msgid "Content$Social/Spiritual Sciences" ++msgstr "Yhteiskunta/hengelliset tieteet" ++ ++msgid "Content$Further Education" ++msgstr "Jatkokoulutus" ++ ++msgid "Content$Languages" ++msgstr "Kielet" ++ ++msgid "Content$Leisure/Hobbies" ++msgstr "Vapaa-aika ja harrastukset" ++ ++msgid "Content$Tourism/Travel" ++msgstr "Turismi/matkustaminen" ++ ++msgid "Content$Handicraft" ++msgstr "Käsityöt" ++ ++msgid "Content$Motoring" ++msgstr "Autoilu" ++ ++msgid "Content$Fitness & Health" ++msgstr "Kuntoilu & terveys" ++ ++msgid "Content$Cooking" ++msgstr "Ruuanlaitto" ++ ++msgid "Content$Advertisement/Shopping" ++msgstr "Mainostaminen/ostaminen" ++ ++msgid "Content$Gardening" ++msgstr "Puutarhanhoito" ++ ++msgid "Content$Original Language" ++msgstr "Alkuperäiskieli" ++ ++msgid "Content$Black & White" ++msgstr "Mustavalkoinen" ++ ++msgid "Content$Unpublished" ++msgstr "Julkaisematon" ++ ++msgid "Content$Live Broadcast" ++msgstr "Suoralähetys" ++ ++msgid "Content$Special Characteristics" ++msgstr "Erikoisominaisuus" ++ ++msgid "Content$Drama" ++msgstr "Draama" ++ ++#, c-format ++msgid "Suitable for those aged %d and over" ++msgstr "Kielletty alle %d vuotiailta" ++ + msgid "No title" + msgstr "Ei esitystä" + +@@ -1031,3 +1278,252 @@ + #, c-format + msgid "VDR will shut down in %s minutes" + msgstr "VDR sammuu %s minuutin kuluttua" ++ ++msgid "Channel locked by LNB!" ++msgstr "" ++ ++msgid "Childlock" ++msgstr "" ++ ++msgid "Path" ++msgstr "Polku" ++ ++msgid "Timer commands" ++msgstr "Ajastinkomennot" ++ ++msgid "Rename recording" ++msgstr "Nimeä tallenne" ++ ++msgid "Date" ++msgstr "Päiväys" ++ ++msgid "Length" ++msgstr "Pituus" ++ ++msgid "Size" ++msgstr "Koko" ++ ++msgid "Delete marks information?" ++msgstr "Poista tallenteen merkinnät" ++ ++msgid "Delete resume information?" ++msgstr "Poista tallenteen paluutiedot" ++ ++msgid "DVD plugin is not installed!" ++msgstr "" ++ ++msgid "main dir alphabetically, subdirs flexible" ++msgstr "" ++ ++msgid "main dir by date, subdirs flexible" ++msgstr "" ++ ++msgid "all alphabetically" ++msgstr "" ++ ++msgid "all by date" ++msgstr "" ++ ++msgid "Sorting" ++msgstr "" ++ ++msgid "Setup.OSD$WarEagle icons" ++msgstr "" ++ ++msgid "Setup.OSD$Main menu command position" ++msgstr "Komentojen sijainti päävalikossa" ++ ++msgid "Setup.OSD$Show valid input" ++msgstr "" ++ ++msgid "Setup.EPG$Show progress bar" ++msgstr "Näytä aikajana" ++ ++msgid "Setup.EPG$Period for double EPG search(min)" ++msgstr "" ++ ++msgid "Setup.EPG$Extern double Epg entry" ++msgstr "" ++ ++msgid "adjust" ++msgstr "" ++ ++msgid "delete" ++msgstr "" ++ ++msgid "Setup.EPG$Mix intern and extern EPG" ++msgstr "" ++ ++msgid "Setup.EPG$Disable running VPS event" ++msgstr "" ++ ++msgid "Setup.DVB$Use AC3-Transfer Fix" ++msgstr "" ++ ++msgid "Setup.DVB$Channel Blocker" ++msgstr "" ++ ++msgid "Setup.DVB$Channel Blocker Filter Mode" ++msgstr "" ++ ++msgid "Setup.LNB$DVB device %d uses LNB No." ++msgstr "" ++ ++msgid "Setup.LNB$Log LNB usage" ++msgstr "" ++ ++msgid "Setup.Recording$Record Dolby Digital" ++msgstr "Dolby Digital tallennus" ++ ++msgid "Setup.Recording$Video directory policy" ++msgstr "" ++ ++msgid "Setup.Recording$Number of video directories" ++msgstr "" ++ ++msgid "Setup.Recording$Video %d priority" ++msgstr "" ++ ++msgid "Setup.Recording$Video %d min. free MB" ++msgstr "" ++ ++msgid "Setup.Recording$Friendly filenames" ++msgstr "" ++ ++msgid "Setup.Recording$Max. recording size (GB)" ++msgstr "" ++ ++msgid "Setup.Recording$Hard Link Cutter" ++msgstr "" ++ ++msgid "Setup.Recording$Show date" ++msgstr "Näytä tallenteen päiväys" ++ ++msgid "Setup.Recording$Show time" ++msgstr "Näytä tallenteen ajankohta" ++ ++msgid "Setup.Recording$Show length" ++msgstr "Näytä tallenteen kesto" ++ ++msgid "Setup.OSD$Main menu title" ++msgstr "" ++ ++msgid "Setup.Recording$Show end of timer" ++msgstr "" ++ ++msgid "Setup.Recording$Sort recordings by" ++msgstr "" ++ ++msgid "Setup.Recording$Sort directories before recordings" ++msgstr "" ++ ++msgid "Setup.Recording$Cutter auto delete" ++msgstr "" ++ ++msgid "Setup.Replay$Jump&Play" ++msgstr "" ++ ++msgid "Setup.Replay$Play&Jump" ++msgstr "" ++ ++msgid "Setup.Replay$Pause at last mark" ++msgstr "" ++ ++msgid "Setup.Replay$Reload marks" ++msgstr "" ++ ++msgid "Setup.Replay$Skip Seconds" ++msgstr "" ++ ++msgid "Setup.Replay$Skip Seconds Slow" ++msgstr "" ++ ++msgid "Setup.Replay$Length" ++msgstr "" ++ ++msgid "Setup.Replay$Length / Number" ++msgstr "" ++ ++msgid "Setup.Replay$Number" ++msgstr "" ++ ++msgid "Setup.Replay$DVD display mode" ++msgstr "" ++ ++msgid "Setup.Replay$DVD display leading zeros" ++msgstr "" ++ ++msgid "Setup.Replay$never" ++msgstr "" ++ ++msgid "Setup.Replay$on begin" ++msgstr "" ++ ++msgid "Setup.Replay$on end" ++msgstr "" ++ ++msgid "Setup.Replay$on begin and end" ++msgstr "" ++ ++msgid "Setup.Replay$Tray open" ++msgstr "" ++ ++msgid "Setup.Replay$Limit DVD to speed" ++msgstr "" ++ ++msgid "Jump" ++msgstr "" ++ ++msgid "Skip +60s" ++msgstr "" ++ ++msgid "Skip -60s" ++msgstr "" ++ ++msgid "Cutter already running - Add to cutting queue?" ++msgstr "" ++ ++msgid "Format" ++msgstr "Tiedostomuoto" ++ ++msgid "PES" ++msgstr "PES" ++ ++msgid "TS" ++msgstr "TS" ++ ++msgid "Rename$Up" ++msgstr "Ylemmäs" ++ ++msgid "Rename$Down" ++msgstr "Alemmas" ++ ++msgid "Rename$Previous" ++msgstr "Edellinen" ++ ++msgid "Rename$Next" ++msgstr "Seuraava" ++ ++msgid "Please mount %s" ++msgstr "" ++ ++msgid "Please mount DVD %04d" ++msgstr "" ++ ++msgid "Please mount DVD %d" ++msgstr "" ++ ++msgid "Please wait. Checking DVD..." ++msgstr "" ++ ++msgid "No index-file found. Creating may take minutes. Create one?" ++msgstr "" ++ ++msgid "Please wait. Creating index-file..." ++msgstr "" ++ ++msgid "Wrong DVD!" ++msgstr "" ++ ++msgid "Parameters" ++msgstr "Parametrit" +diff -NaurwB vdr-1.7.10/po/fr_FR.po vdr-1.7.10-patched/po/fr_FR.po +--- vdr-1.7.10/po/fr_FR.po 2009-11-22 12:28:39.000000000 +0100 ++++ vdr-1.7.10-patched/po/fr_FR.po 2009-12-18 06:25:25.000000000 +0100 +@@ -8,6 +8,7 @@ + # Pierre Briec , 2006 + # Bruno Roussel , 2007 + # Michael Nival , 2007 ++# Patrice Staudt , 2007, 2008 + # + msgid "" + msgstr "" +@@ -1034,3 +1035,249 @@ + #, c-format + msgid "VDR will shut down in %s minutes" + msgstr "VDR s'arrêtera dans %s minutes" ++ ++msgid "Channel locked by LNB!" ++msgstr "Chaîne interdite par la LNB" ++ ++msgid "Childlock" ++msgstr "Adulte" ++ ++msgid "Path" ++msgstr "Dossiers" ++ ++msgid "Format" ++msgstr "Format" ++ ++msgid "PES" ++msgstr "PES" ++ ++msgid "TS" ++msgstr "TS" ++ ++msgid "Timer commands" ++msgstr "Commandes de programmation" ++ ++msgid "Rename recording" ++msgstr "Renommer l'enregistrement" ++ ++msgid "Date" ++msgstr "Date" ++ ++msgid "Length" ++msgstr "Longeur" ++ ++msgid "Size" ++msgstr "Taille" ++ ++msgid "Delete marks information?" ++msgstr "Effacer marque d'information?" ++ ++msgid "Delete resume information?" ++msgstr "Effacer resume information?" ++ ++msgid "DVD plugin is not installed!" ++msgstr "Le plugin de DVD n'est pas installé!" ++ ++msgid "main dir alphabetically, subdirs flexible" ++msgstr "Dir principal alphabétiquement, subdirs flexibles" ++ ++msgid "main dir by date, subdirs flexible" ++msgstr "Dir principal à la date, subdirs flexibles" ++ ++msgid "all alphabetically" ++msgstr "tous alphabétiquement" ++ ++msgid "all by date" ++msgstr "tous à la date" ++ ++msgid "Sorting" ++msgstr "Triage" ++ ++msgid "Setup.OSD$WarEagle icons" ++msgstr "Icônes WarEagle" ++ ++msgid "Setup.OSD$Main menu command position" ++msgstr "Position des commandes dans le menu" ++ ++msgid "Setup.OSD$Show valid input" ++msgstr "Afficher l'entrée valide" ++ ++msgid "Setup.EPG$Show progress bar" ++msgstr "Montrer la barre de progression" ++ ++msgid "Setup.EPG$Period for double EPG search(min)" ++msgstr "Intervalle de recherche EPG de double(min)" ++ ++msgid "Setup.EPG$Extern double Epg entry" ++msgstr "Double entrée EPG externe" ++ ++msgid "adjust" ++msgstr "ajuster" ++ ++msgid "delete" ++msgstr "effacer" ++ ++msgid "Setup.EPG$Mix intern and extern EPG" ++msgstr "Mélanger les EPG interne et externe" ++ ++msgid "Setup.EPG$Disable running VPS event" ++msgstr "Arreter la reconnaissance des événements VPS" ++ ++msgid "Setup.DVB$Use AC3-Transfer Fix" ++msgstr "Utiliser le correctif AC3-Transfer" ++ ++msgid "Setup.DVB$Channel Blocker" ++msgstr "" ++ ++msgid "Setup.DVB$Channel Blocker Filter Mode" ++msgstr "" ++ ++msgid "Setup.LNB$DVB device %d uses LNB No." ++msgstr "La carte DVB %d utilise la LNB No." ++ ++msgid "Setup.LNB$Log LNB usage" ++msgstr "Protocoller l'utilisation du LNB" ++ ++msgid "Setup.Recording$Record Dolby Digital" ++msgstr "Enregistrer en Dolby Digital" ++ ++msgid "Setup.Recording$Video directory policy" ++msgstr "Régles de répertoires vidéo" ++ ++msgid "Setup.Recording$Number of video directories" ++msgstr "Nombre de répertoires vidéo" ++ ++msgid "Setup.Recording$Video %d priority" ++msgstr "Video %d prioritaires" ++ ++msgid "Setup.Recording$Video %d min. free MB" ++msgstr "Video %d min. librei Mo" ++ ++msgid "Setup.Recording$Friendly filenames" ++msgstr "Noms de fichiers facile" ++ ++msgid "Setup.Recording$Max. recording size (GB)" ++msgstr "Taille max. de l'enregistrement (GB)" ++ ++msgid "Setup.Recording$Hard Link Cutter" ++msgstr "Découpe Hard links" ++ ++msgid "Setup.Recording$Show date" ++msgstr "Montrer la date d'enregistrement" ++ ++msgid "Setup.Recording$Show time" ++msgstr "Montrer l'heure d'enregistrement" ++ ++msgid "Setup.Recording$Show length" ++msgstr "Montrer la longueur de l'enregistrement" ++ ++msgid "Setup.OSD$Main menu title" ++msgstr "" ++ ++msgid "Setup.Recording$Show end of timer" ++msgstr "Montrer la fin du temporisateur" ++ ++msgid "Setup.Recording$Sort recordings by" ++msgstr "Ordonner les enregistrement suivant" ++ ++msgid "Setup.Recording$Sort directories before recordings" ++msgstr "Les dossiers devant les enregistrements" ++ ++msgid "Setup.Recording$Cutter auto delete" ++msgstr "Effacer l'enregistrement après la découpe" ++ ++msgid "Setup.Replay$Jump&Play" ++msgstr "Lecture après saut" ++ ++msgid "Setup.Replay$Play&Jump" ++msgstr "Saut sur les marques de découpes" ++ ++msgid "Setup.Replay$Pause at last mark" ++msgstr "Pause après la dernière marque" ++ ++msgid "Setup.Replay$Reload marks" ++msgstr "Actualiser les marques" ++ ++msgid "Setup.Replay$Skip Seconds" ++msgstr "Longueur de saut en secondes" ++ ++msgid "Setup.Replay$Skip Seconds Slow" ++msgstr "Longueur de saut lent en secondes" ++ ++msgid "Setup.Replay$Length" ++msgstr "Longueur" ++ ++msgid "Setup.Replay$Length / Number" ++msgstr "Longueur / Numéro" ++ ++msgid "Setup.Replay$Number" ++msgstr "Numéro" ++ ++msgid "Setup.Replay$DVD display mode" ++msgstr "Afficher le DVD" ++ ++msgid "Setup.Replay$DVD display leading zeros" ++msgstr "Afficher les zéros devant le numéro du DVD" ++ ++msgid "Setup.Replay$never" ++msgstr "jamais" ++ ++msgid "Setup.Replay$on begin" ++msgstr "au début" ++ ++msgid "Setup.Replay$on end" ++msgstr "à la fin" ++ ++msgid "Setup.Replay$on begin and end" ++msgstr "au début et à la fin" ++ ++msgid "Setup.Replay$Tray open" ++msgstr "Ouvrir le tiroir du lecteur DVD" ++ ++msgid "Setup.Replay$Limit DVD to speed" ++msgstr "Ralentir la vitesse du DVD à" ++ ++msgid "Jump" ++msgstr "Saut" ++ ++msgid "Skip +60s" ++msgstr "Avance +60s" ++ ++msgid "Skip -60s" ++msgstr "Recule -60s" ++ ++msgid "Cutter already running - Add to cutting queue?" ++msgstr "Découpe déjà active - l'ajouter à la liste de découpe?" ++ ++msgid "Rename$Up" ++msgstr "Haut" ++ ++msgid "Rename$Down" ++msgstr "Bas" ++ ++msgid "Rename$Previous" ++msgstr "Précédent" ++ ++msgid "Rename$Next" ++msgstr "Suivant" ++ ++msgid "Please mount %s" ++msgstr "Mettez %s dans le lecteur" ++ ++msgid "Please mount DVD %04d" ++msgstr "Mettez le DVD %04d dans le lecteur" ++ ++msgid "Please mount DVD %d" ++msgstr "Mettez le DVD %d dans le lecteur" ++ ++msgid "Please wait. Checking DVD..." ++msgstr "Un moment SVP. Vérification du DVD..." ++ ++msgid "No index-file found. Creating may take minutes. Create one?" ++msgstr "Pas trouvé de fichiers index. La création de ce ficher prends quelques minutes. Créer?" ++ ++msgid "Please wait. Creating index-file..." ++msgstr "Attendez, svp. Le fichier index est en cours de création..." ++ ++msgid "Wrong DVD!" ++msgstr "Mauvais DVD!" +diff -NaurwB vdr-1.7.10/po/it_IT.po vdr-1.7.10-patched/po/it_IT.po +--- vdr-1.7.10/po/it_IT.po 2009-11-22 12:28:39.000000000 +0100 ++++ vdr-1.7.10-patched/po/it_IT.po 2009-12-18 06:25:25.000000000 +0100 +@@ -37,6 +37,9 @@ + msgid "*** Invalid Channel ***" + msgstr "*** Canale NON valido ***" + ++msgid "Channel locked by LNB!" ++msgstr "Canale bloccato dal LNB!" ++ + msgid "Channel not available!" + msgstr "Canale non disponibile!" + +@@ -46,6 +49,253 @@ + msgid "Starting EPG scan" + msgstr "Inizio scansione EPG" + ++msgid "Content$Movie/Drama" ++msgstr "Film/Dramma" ++ ++msgid "Content$Detective/Thriller" ++msgstr "Investigativo/Giallo" ++ ++msgid "Content$Adventure/Western/War" ++msgstr "Avventura/Western/Guerra" ++ ++msgid "Content$Science Fiction/Fantasy/Horror" ++msgstr "Finzione/Fantasia/Horror" ++ ++msgid "Content$Comedy" ++msgstr "Commedia" ++ ++msgid "Content$Soap/Melodrama/Folkloric" ++msgstr "Telenovella/Melodramma/Folcloristico" ++ ++msgid "Content$Romance" ++msgstr "Romanzo" ++ ++msgid "Content$Serious/Classical/Religious/Historical Movie/Drama" ++msgstr "Serio/Classico/Religioso/Film storico/Dramma" ++ ++msgid "Content$Adult Movie/Drama" ++msgstr "Film per adulti/Dramma" ++ ++msgid "Content$News/Current Affairs" ++msgstr "Notizie/Ultima ora" ++ ++msgid "Content$News/Weather Report" ++msgstr "Notizie/Previsioni meteo" ++ ++msgid "Content$News Magazine" ++msgstr "Rivista di notizie" ++ ++msgid "Content$Documentary" ++msgstr "Documentario" ++ ++msgid "Content$Discussion/Inverview/Debate" ++msgstr "Discussione/Intervista/Dibattito" ++ ++msgid "Content$Show/Game Show" ++msgstr "Spettacolo/Gioco a premi" ++ ++msgid "Content$Game Show/Quiz/Contest" ++msgstr "Gioco a premi/Quiz/Gara" ++ ++msgid "Content$Variety Show" ++msgstr "Spettacolo di varietà" ++ ++msgid "Content$Talk Show" ++msgstr "Talk Show" ++ ++msgid "Content$Sports" ++msgstr "Sport" ++ ++msgid "Content$Special Event" ++msgstr "Evento speciale" ++ ++msgid "Content$Sport Magazine" ++msgstr "Rivista di sport" ++ ++msgid "Content$Football" ++msgstr "Calcio" ++ ++msgid "Content$Tennis/Squash" ++msgstr "Tennis/Squash" ++ ++msgid "Content$Team Sports" ++msgstr "Sport di squadra" ++ ++msgid "Content$Athletics" ++msgstr "Atletica" ++ ++msgid "Content$Motor Sport" ++msgstr "Sport motoristici" ++ ++msgid "Content$Water Sport" ++msgstr "Sport acquatici" ++ ++msgid "Content$Winter Sports" ++msgstr "Sport invernali" ++ ++msgid "Content$Equestrian" ++msgstr "Equitazione" ++ ++msgid "Content$Martial Sports" ++msgstr "Arti marziali" ++ ++msgid "Content$Children's/Youth Programmes" ++msgstr "Programmi per ragazzi/giovani" ++ ++msgid "Content$Pre-school Children's Programmes" ++msgstr "Programmi per ragazzi prescolastici" ++ ++msgid "Content$Entertainment Programmes for 6 to 14" ++msgstr "Programmi di intrattenimento da 6 a 14" ++ ++msgid "Content$Entertainment Programmes for 10 to 16" ++msgstr "Programmi di intrattenimento da 10 a 16" ++ ++msgid "Content$Informational/Educational/School Programme" ++msgstr "Informativo/Educativo/Programma scolastico" ++ ++msgid "Content$Cartoons/Puppets" ++msgstr "Cartoni/Pupazzi" ++ ++msgid "Content$Music/Ballet/Dance" ++msgstr "Musica/Balletto/Danza" ++ ++msgid "Content$Rock/Pop" ++msgstr "Rock/Pop" ++ ++msgid "Content$Serious/Classical Music" ++msgstr "Serio/Musica classica" ++ ++msgid "Content$Folk/Tradional Music" ++msgstr "Folclore/Musica tradizionale" ++ ++msgid "Content$Jazz" ++msgstr "Jazz" ++ ++msgid "Content$Musical/Opera" ++msgstr "Musical/Opera" ++ ++msgid "Content$Ballet" ++msgstr "Balletto" ++ ++msgid "Content$Arts/Culture" ++msgstr "Arte/Cultura" ++ ++msgid "Content$Performing Arts" ++msgstr "Arti di rendimento" ++ ++msgid "Content$Fine Arts" ++msgstr "Arti fine" ++ ++msgid "Content$Religion" ++msgstr "Religione" ++ ++msgid "Content$Popular Culture/Traditional Arts" ++msgstr "Cultura popolare/Arti tradizionali" ++ ++msgid "Content$Literature" ++msgstr "Letteratura" ++ ++msgid "Content$Film/Cinema" ++msgstr "Film/Cinema" ++ ++msgid "Content$Experimental Film/Video" ++msgstr "Film esperimentale/Video" ++ ++msgid "Content$Broadcasting/Press" ++msgstr "Trasmissione/Stampa" ++ ++msgid "Content$New Media" ++msgstr "Nuovo programma" ++ ++msgid "Content$Arts/Culture Magazines" ++msgstr "Arte/Riviste di cultura" ++ ++msgid "Content$Fashion" ++msgstr "Moda" ++ ++msgid "Content$Social/Political/Economics" ++msgstr "Società/Politica/Economia" ++ ++msgid "Content$Magazines/Reports/Documentary" ++msgstr "Riviste/Reportage/Documentari" ++ ++msgid "Content$Economics/Social Advisory" ++msgstr "Economia/Consulenza sociale" ++ ++msgid "Content$Remarkable People" ++msgstr "Personaggi importanti" ++ ++msgid "Content$Education/Science/Factual" ++msgstr "Educazione/Scienza/Fatti" ++ ++msgid "Content$Nature/Animals/Environment" ++msgstr "Natura/Animali/Ambiente" ++ ++msgid "Content$Technology/Natural Sciences" ++msgstr "Tecnologia/Scienze naturali" ++ ++msgid "Content$Medicine/Physiology/Psychology" ++msgstr "Medicina/Filosofia/Psicologia" ++ ++msgid "Content$Foreign Countries/Expeditions" ++msgstr "Paesi esteri/Spedizioni" ++ ++msgid "Content$Social/Spiritual Sciences" ++msgstr "Società/Scienze spirituali" ++ ++msgid "Content$Further Education" ++msgstr "Altra educazione" ++ ++msgid "Content$Languages" ++msgstr "Lingua" ++ ++msgid "Content$Leisure/Hobbies" ++msgstr "Tempo libero/Hobby" ++ ++msgid "Content$Tourism/Travel" ++msgstr "Turismo/Viaggi" ++ ++msgid "Content$Handicraft" ++msgstr "Artigianato" ++ ++msgid "Content$Motoring" ++msgstr "Motori" ++ ++msgid "Content$Fitness & Health" ++msgstr "Culturismo & Salute" ++ ++msgid "Content$Cooking" ++msgstr "Cucina" ++ ++msgid "Content$Advertisement/Shopping" ++msgstr "Pubblicità/Acquisti" ++ ++msgid "Content$Gardening" ++msgstr "Giardinaggio" ++ ++msgid "Content$Original Language" ++msgstr "Lingua madre" ++ ++msgid "Content$Black & White" ++msgstr "Bianco & Nero" ++ ++msgid "Content$Unpublished" ++msgstr "Non pubblicato" ++ ++msgid "Content$Live Broadcast" ++msgstr "Trasmissione dal vivo" ++ ++msgid "Content$Special Characteristics" ++msgstr "Caratteristiche speciali" ++ ++msgid "Content$Drama" ++msgstr "Dramma" ++ ++#, c-format ++msgid "Suitable for those aged %d and over" ++msgstr "Adatto per coloro con %d di età e oltre" ++ + msgid "No title" + msgstr "Senza titolo" + +@@ -325,6 +575,9 @@ + msgid "Rolloff" + msgstr "Rolloff" + ++msgid "Parameters" ++msgstr "Parametri" ++ + msgid "Channel settings are not unique!" + msgstr "Parametri canale non univoci!" + +@@ -376,9 +629,15 @@ + msgid "Lifetime" + msgstr "Scadenza" + ++msgid "Childlock" ++msgstr "Filtro fam." ++ + msgid "File" + msgstr "Nome" + ++msgid "Path" ++msgstr "Percorso" ++ + msgid "First day" + msgstr "1° giorno" + +@@ -397,6 +656,9 @@ + msgid "Timer still recording - really delete?" + msgstr "Timer in registrazione - eliminare?" + ++msgid "Timer commands" ++msgstr "Comandi timer" ++ + msgid "Event" + msgstr "Evento" + +@@ -457,6 +719,24 @@ + msgid "Button$Rewind" + msgstr "Riavvolgi" + ++msgid "Rename recording" ++msgstr "Rinomina registrazione" ++ ++msgid "Date" ++msgstr "Data" ++ ++msgid "Length" ++msgstr "Durata" ++ ++msgid "Size" ++msgstr "Dimensione" ++ ++msgid "Delete marks information?" ++msgstr "Eliminare informazione marcatori?" ++ ++msgid "Delete resume information?" ++msgstr "Eliminare informazione ripristino?" ++ + msgid "Recordings" + msgstr "Registrazioni" + +@@ -469,6 +749,9 @@ + msgid "Error while accessing recording!" + msgstr "Errore accesso alla registrazione!" + ++msgid "DVD plugin is not installed!" ++msgstr "Il plugin DVD non è installato!" ++ + msgid "Delete recording?" + msgstr "Eliminare la registrazione?" + +@@ -478,6 +761,21 @@ + msgid "Recording commands" + msgstr "Comandi di registrazione" + ++msgid "main dir alphabetically, subdirs flexible" ++msgstr "Dir. princ. in ordine alfabetico, sottodir. flessibili" ++ ++msgid "main dir by date, subdirs flexible" ++msgstr "Dir. princ. per data, sottodir. flessibili" ++ ++msgid "all alphabetically" ++msgstr "Tutte in ordine alfabetico" ++ ++msgid "all by date" ++msgstr "Tutte per data" ++ ++msgid "Sorting" ++msgstr "Ordinamento" ++ + msgid "never" + msgstr "mai" + +@@ -487,6 +785,18 @@ + msgid "always" + msgstr "sempre" + ++msgid "default" ++msgstr "predefinito" ++ ++msgid "VDR - text" ++msgstr "VDR - Testo" ++ ++msgid "text" ++msgstr "Testo" ++ ++msgid "VDR - version" ++msgstr "VDR - Versione" ++ + msgid "OSD" + msgstr "OSD" + +@@ -499,6 +809,9 @@ + msgid "Setup.OSD$Theme" + msgstr "Tema colori" + ++msgid "Setup.OSD$WarEagle icons" ++msgstr "Icone WarEagle" ++ + msgid "Setup.OSD$Left (%)" + msgstr "Sinistra (%)" + +@@ -568,12 +881,30 @@ + msgid "Setup.OSD$Recording directories" + msgstr "Directory di registrazione" + ++msgid "Setup.OSD$Main menu title" ++msgstr "Titolo menu principale" ++ ++msgid "Setup.OSD$- Text" ++msgstr "- Testo" ++ ++msgid "Setup.OSD$Main menu command position" ++msgstr "Posizione comandi menu princ." ++ ++msgid "Setup.OSD$Show valid input" ++msgstr "Mostra ingresso valido" ++ + msgid "EPG" + msgstr "Guida programmi EPG" + + msgid "Button$Scan" + msgstr "Scansione" + ++msgid "blacklist" ++msgstr "lista nera" ++ ++msgid "whitelist" ++msgstr "elenco autorizzati" ++ + msgid "Setup.EPG$EPG scan timeout (h)" + msgstr "Scadenza aggiorn. EPG (ore)" + +@@ -583,6 +914,9 @@ + msgid "Setup.EPG$EPG linger time (min)" + msgstr "Mostra vecchi dati EPG (min)" + ++msgid "Setup.EPG$Show progress bar" ++msgstr "Mostra barra avanzamento" ++ + msgid "Setup.EPG$Set system time" + msgstr "Imposta orario di sistema" + +@@ -597,6 +931,27 @@ + msgid "Setup.EPG$Preferred language" + msgstr "Lingua preferita" + ++msgid "Setup.EPG$Period for double EPG search(min)" ++msgstr "Tempo doppia ricerca EPG (min)" ++ ++msgid "Setup.EPG$Extern double Epg entry" ++msgstr "Doppio valore EPG esterno" ++ ++msgid "adjust" ++msgstr "regola" ++ ++msgid "delete" ++msgstr "elimina" ++ ++msgid "Setup.EPG$Mix intern and extern EPG" ++msgstr "Mescola EPG interno e esterno" ++ ++msgid "Setup.EPG$Disable running VPS event" ++msgstr "Disabilita esec. evento VPS" ++ ++msgid "Setup.EPG$Mode of noEPG-Patch" ++msgstr "Modalità di noEPG-Patch" ++ + msgid "pan&scan" + msgstr "pan&scan" + +@@ -627,6 +982,27 @@ + msgid "DVB" + msgstr "Scheda DVB" + ++msgid "qam256" ++msgstr "qam256" ++ ++msgid "dvb-c" ++msgstr "dvb-c" ++ ++msgid "dvb-s" ++msgstr "dvb-s" ++ ++msgid "all" ++msgstr "tutti" ++ ++msgid "has decoder" ++msgstr "con decoder" ++ ++msgid "is primary" ++msgstr "primaria" ++ ++msgid "has decoder + is primary" ++msgstr "con decoder e primaria" ++ + msgid "Setup.DVB$Primary DVB interface" + msgstr "Scheda DVB primaria" + +@@ -666,9 +1042,25 @@ + msgid "Setup.DVB$Subtitle background transparency" + msgstr "Trasparenza sfondo sottotitoli" + ++msgid "Setup.DVB$Use AC3-Transfer Fix" ++msgstr "Utilizza correzione AC3-Transfer" ++ ++msgid "Setup.DVB$Channel Blocker" ++msgstr "Blocco canale" ++ ++msgid "Setup.DVB$Channel Blocker Filter Mode" ++msgstr "Modalità filtro blocco canale" ++ + msgid "LNB" + msgstr "LNB" + ++#, c-format ++msgid "Setup.LNB$DVB device %d uses LNB No." ++msgstr "La scheda DVB %d utilizza LNB No." ++ ++msgid "Setup.LNB$Log LNB usage" ++msgstr "Registra utilizzo LNB" ++ + msgid "Setup.LNB$Use DiSEqC" + msgstr "Utilizza DiSEqC" + +@@ -720,6 +1112,9 @@ + msgid "pause live video" + msgstr "pausa video dal vivo" + ++msgid "request" ++msgstr "chiedi" ++ + msgid "Recording" + msgstr "Registrazione" + +@@ -747,9 +1142,29 @@ + msgid "Setup.Recording$Pause lifetime (d)" + msgstr "Scadenza pausa (gg)" + ++msgid "Setup.Recording$Record Dolby Digital" ++msgstr "Registra Dolby Digital" ++ ++msgid "Setup.Recording$Video directory policy" ++msgstr "Regole directory video" ++ ++msgid "Setup.Recording$Number of video directories" ++msgstr "Numero di directory video" ++ ++#, c-format ++msgid "Setup.Recording$Video %d priority" ++msgstr "Priorità video %d " ++ ++#, c-format ++msgid "Setup.Recording$Video %d min. free MB" ++msgstr "Min. MB disponibili video %d " ++ + msgid "Setup.Recording$Use episode name" + msgstr "Utilizza nome episodio" + ++msgid "Setup.Recording$Friendly filenames" ++msgstr "Nomi file semplici" ++ + msgid "Setup.Recording$Use VPS" + msgstr "Utilizza VPS" + +@@ -768,9 +1183,39 @@ + msgid "Setup.Recording$Max. video file size (MB)" + msgstr "Dim. massima file video (MB)" + ++msgid "Setup.Recording$Max. recording size (GB)" ++msgstr "Dim. massima reg. (GB)" ++ + msgid "Setup.Recording$Split edited files" + msgstr "Dividi i file modificati" + ++msgid "Setup.Recording$Hard Link Cutter" ++msgstr "Taglia collegamenti Hard" ++ ++msgid "Setup.Recording$Delete timeshift recording" ++msgstr "Elimina reg. timeshift" ++ ++msgid "Setup.Recording$Show date" ++msgstr "Mostra data registrazione" ++ ++msgid "Setup.Recording$Show time" ++msgstr "Mostra ora registrazione" ++ ++msgid "Setup.Recording$Show length" ++msgstr "Mostra durata registrazione" ++ ++msgid "Setup.Recording$Show end of timer" ++msgstr "Mostra fine del timer" ++ ++msgid "Setup.Recording$Sort recordings by" ++msgstr "Ordina registrazioni per" ++ ++msgid "Setup.Recording$Sort directories before recordings" ++msgstr "Ordina directory prima di reg." ++ ++msgid "Setup.Recording$Cutter auto delete" ++msgstr "Elimina taglio automatico" ++ + msgid "Replay" + msgstr "Riproduzione" + +@@ -783,6 +1228,63 @@ + msgid "Setup.Replay$Resume ID" + msgstr "ID di ripristino" + ++msgid "Setup.Replay$Jump&Play" ++msgstr "Vai a & Riproduci" ++ ++msgid "Setup.Replay$Play&Jump" ++msgstr "Riproduci & Vai a" ++ ++msgid "Setup.Replay$Pause at last mark" ++msgstr "Pausa all'ultimo marcatore" ++ ++msgid "Setup.Replay$Reload marks" ++msgstr "Ricarica marcatori" ++ ++msgid "Setup.Replay$Skip Seconds" ++msgstr "Salta secondi" ++ ++msgid "Setup.Replay$Skip Seconds Slow" ++msgstr "Salta secondi lentamente" ++ ++msgid "Setup.Replay$Length" ++msgstr "Durata" ++ ++msgid "Setup.Replay$Length / Number" ++msgstr "Durata / Numero" ++ ++msgid "Setup.Replay$Number" ++msgstr "Numero" ++ ++msgid "Setup.Replay$DVD display mode" ++msgstr "Mod. visualizzazione DVD" ++ ++msgid "Setup.Replay$DVD display leading zeros" ++msgstr "Mostra zeri davanti al DVD" ++ ++msgid "Setup.Replay$never" ++msgstr "mai" ++ ++msgid "Setup.Replay$on begin" ++msgstr "all'inizio" ++ ++msgid "Setup.Replay$on end" ++msgstr "alla fine" ++ ++msgid "Setup.Replay$on begin and end" ++msgstr "all'inizio e alla fine" ++ ++msgid "Setup.Replay$Tray open" ++msgstr "Apri vassoio" ++ ++msgid "Setup.Replay$Limit DVD to speed" ++msgstr "Limita velocità DVD a" ++ ++msgid "Setup.Miscellaneous$only in channelinfo" ++msgstr "solo nelle info canale" ++ ++msgid "Setup.Miscellaneous$only in progress display" ++msgstr "solo nel canale in esec." ++ + msgid "Miscellaneous" + msgstr "Generici" + +@@ -810,9 +1312,27 @@ + msgid "Setup.Miscellaneous$Initial volume" + msgstr "Volume iniziale" + ++msgid "Setup.Miscellaneous$Volume ctrl with left/right" ++msgstr "Controllo volume con Sinistra/Destra" ++ ++msgid "Setup.Miscellaneous$Channelgroups with left/right" ++msgstr "Gruppi canali con Sinistra/Destra" ++ ++msgid "Setup.Miscellaneous$Search fwd/back with left/right" ++msgstr "Cerca avanti/indietro con Sinistra/Destra" ++ + msgid "Setup.Miscellaneous$Emergency exit" + msgstr "Uscita di emergenza" + ++msgid "Setup.Miscellaneous$Lirc repeat delay" ++msgstr "Ritardo ripetizione LIRC" ++ ++msgid "Setup.Miscellaneous$Lirc repeat freq" ++msgstr "Frequenza ripetizione LIRC" ++ ++msgid "Setup.Miscellaneous$Lirc repeat timeout" ++msgstr "Scadenza ripetizione LIRC" ++ + msgid "Plugins" + msgstr "Plugins" + +@@ -885,10 +1405,22 @@ + msgid "Pausing live video..." + msgstr "Pausa del canale in visione..." + ++msgid "Jump" ++msgstr "Vai a" ++ ++msgid "Skip +60s" ++msgstr "Salta +60s" ++ ++msgid "Skip -60s" ++msgstr "Salta - 60s" ++ + #. TRANSLATORS: note the trailing blank! + msgid "Jump: " + msgstr "Vai a: " + ++msgid "Cutter already running - Add to cutting queue?" ++msgstr "Taglio in esecuzione - Aggiungere alla coda tagli?" ++ + msgid "No editing marks defined!" + msgstr "Nessun marcatore di modifica definito!" + +@@ -919,6 +1451,18 @@ + msgid "Button$Insert" + msgstr "Inserisci" + ++msgid "Rename$Up" ++msgstr "Su" ++ ++msgid "Rename$Down" ++msgstr "Giù" ++ ++msgid "Rename$Previous" ++msgstr "Precedente" ++ ++msgid "Rename$Next" ++msgstr "Successivo" ++ + msgid "Plugin" + msgstr "Plugin" + +@@ -937,6 +1481,30 @@ + msgid "Index file regeneration complete" + msgstr "" + ++#, c-format ++msgid "Please mount %s" ++msgstr "Monta %s" ++ ++#, c-format ++msgid "Please mount DVD %04d" ++msgstr "Monta il DVD %04d" ++ ++#, c-format ++msgid "Please mount DVD %d" ++msgstr "Monta il DVD %d" ++ ++msgid "Please wait. Checking DVD..." ++msgstr "Attendere prego. Verifica DVD..." ++ ++msgid "No index-file found. Creating may take minutes. Create one?" ++msgstr "Nessun indice trovato. La creazione può impiegare alcuni minuti. Crearne uno?" ++ ++msgid "Please wait. Creating index-file..." ++msgstr "Attendere prego. Creazione indice..." ++ ++msgid "Wrong DVD!" ++msgstr "DVD errato!" ++ + msgid "Can't shutdown - option '-s' not given!" + msgstr "Impossibile spegnere - parametro '-s' non assegnato!" + +diff -NaurwB vdr-1.7.10/po/nl_NL.po vdr-1.7.10-patched/po/nl_NL.po +--- vdr-1.7.10/po/nl_NL.po 2009-11-22 12:28:39.000000000 +0100 ++++ vdr-1.7.10-patched/po/nl_NL.po 2009-12-18 06:25:25.000000000 +0100 +@@ -1032,3 +1032,240 @@ + #, c-format + msgid "VDR will shut down in %s minutes" + msgstr "VDR zal na %s minuten uitschakelen" ++ ++msgid "Channel locked by LNB!" ++msgstr "Kanaal geblokkeerd door LNB" ++ ++msgid "Childlock" ++msgstr "Kinderslot" ++ ++msgid "Path" ++msgstr "Pad" ++ ++msgid "Timer commands" ++msgstr "Timer commando's" ++ ++msgid "Rename recording" ++msgstr "Hernoem opnamen" ++ ++msgid "Date" ++msgstr "Datum" ++ ++msgid "Length" ++msgstr "Lengte" ++ ++msgid "Size" ++msgstr "Grootte" ++ ++msgid "Delete marks information?" ++msgstr "Verwijder informatiemarkeringen?" ++ ++msgid "Delete resume information?" ++msgstr "Verwijder hervattingsmarkeringen?" ++ ++msgid "DVD plugin is not installed!" ++msgstr "DVd plugin is niet geÃnstalleerd!" ++ ++msgid "main dir alphabetically, subdirs flexible" ++msgstr "hoofdmap alfabetisch, submappen flexibel" ++ ++msgid "main dir by date, subdirs flexible" ++msgstr "hoofdmap op datum, submappen flexibel" ++ ++msgid "all alphabetically" ++msgstr "alles alfabetisch" ++ ++msgid "all by date" ++msgstr "alles op datum" ++ ++msgid "Sorting" ++msgstr "Sortering" ++ ++msgid "Setup.OSD$WarEagle icons" ++msgstr "WarEagle symbolen" ++ ++msgid "Setup.OSD$Main menu command position" ++msgstr "Positie commandobalk in hoofdmenu" ++ ++msgid "Setup.OSD$Show valid input" ++msgstr "Toon geldige invoer" ++ ++msgid "Setup.EPG$Show progress bar" ++msgstr "Toon voortschreidingsbalk" ++ ++msgid "Setup.EPG$Period for double EPG search(min)" ++msgstr "Tijdsduur starten dubbele EPG zoekactie (min)" ++ ++msgid "Setup.EPG$Extern double Epg entry" ++msgstr "externe dubbele EPG invoer" ++ ++msgid "adjust" ++msgstr "afstellen" ++ ++msgid "delete" ++msgstr "verwijderen" ++ ++msgid "Setup.EPG$Mix intern and extern EPG" ++msgstr "Mix in- en externe EPG" ++ ++msgid "Setup.EPG$Disable running VPS event" ++msgstr "Schakel aktieve VPS uitzending uit" ++ ++msgid "Setup.DVB$Use AC3-Transfer Fix" ++msgstr "Gebruik AC3-Transfer Fix" ++ ++msgid "Setup.DVB$Channel Blocker" ++msgstr "" ++ ++msgid "Setup.DVB$Channel Blocker Filter Mode" ++msgstr "" ++ ++msgid "Setup.LNB$DVB device %d uses LNB No." ++msgstr "LNB kaart %d gebruikt LNB Nr." ++ ++msgid "Setup.LNB$Log LNB usage" ++msgstr "Houd LNB gebruik bij" ++ ++msgid "Setup.Recording$Record Dolby Digital" ++msgstr "Neem Dolby Digital spoor op" ++ ++msgid "Setup.Recording$Video directory policy" ++msgstr "Omgang m.b.t. Videomappen" ++ ++msgid "Setup.Recording$Number of video directories" ++msgstr "Aantal videomappen" ++ ++msgid "Setup.Recording$Video %d priority" ++msgstr "Video %d prioriteit" ++ ++msgid "Setup.Recording$Video %d min. free MB" ++msgstr "Video %d min. vrij MB" ++ ++msgid "Setup.Recording$Friendly filenames" ++msgstr "Handzame bestandsnamen" ++ ++msgid "Setup.Recording$Max. recording size (GB)" ++msgstr "Max. opnamegrootte (GB)" ++ ++msgid "Setup.Recording$Hard Link Cutter" ++msgstr "'Hard link' bestandsbewerker" ++ ++msgid "Setup.Recording$Show date" ++msgstr "Toon datum" ++ ++msgid "Setup.Recording$Show time" ++msgstr "Toon tijd" ++ ++msgid "Setup.Recording$Show length" ++msgstr "Toon lengte" ++ ++msgid "Setup.OSD$Main menu title" ++msgstr "" ++ ++msgid "Setup.Recording$Show end of timer" ++msgstr "Toon einde van timers" ++ ++msgid "Setup.Recording$Sort recordings by" ++msgstr "Sorteer opnames op" ++ ++msgid "Setup.Recording$Sort directories before recordings" ++msgstr "Sorteer mappen vÃÃr opnames" ++ ++msgid "Setup.Recording$Cutter auto delete" ++msgstr "Bestandsbewerker automatisch wissen" ++ ++msgid "Setup.Replay$Jump&Play" ++msgstr "Spring&Speel" ++ ++msgid "Setup.Replay$Play&Jump" ++msgstr "Speel&Spring" ++ ++msgid "Setup.Replay$Pause at last mark" ++msgstr "Pauzeer bij laatste markering" ++ ++msgid "Setup.Replay$Reload marks" ++msgstr "Herlaadt markeringen" ++ ++msgid "Setup.Replay$Skip Seconds" ++msgstr "Spring seconden" ++ ++msgid "Setup.Replay$Skip Seconds Slow" ++msgstr "Spring seconden langzaam" ++ ++msgid "Setup.Replay$Length" ++msgstr "Lengte" ++ ++msgid "Setup.Replay$Length / Number" ++msgstr "Lengte / Nummer" ++ ++msgid "Setup.Replay$Number" ++msgstr "Nummer" ++ ++msgid "Setup.Replay$DVD display mode" ++msgstr "DVD displayinstellingen" ++ ++msgid "Setup.Replay$DVD display leading zeros" ++msgstr "DVD toon voorloopnullen" ++ ++msgid "Setup.Replay$never" ++msgstr "nooit" ++ ++msgid "Setup.Replay$on begin" ++msgstr "aan het begin" ++ ++msgid "Setup.Replay$on end" ++msgstr "bij het einde" ++ ++msgid "Setup.Replay$on begin and end" ++msgstr "bij het begin en aan het einde" ++ ++msgid "Setup.Replay$Tray open" ++msgstr "Open DVD lade" ++ ++msgid "Setup.Replay$Limit DVD to speed" ++msgstr "Beperk DVD omw. snelheid" ++ ++msgid "Jump" ++msgstr "Spring" ++ ++msgid "Skip +60s" ++msgstr "Spring +60s verder" ++ ++msgid "Skip -60s" ++msgstr "Spring -60s terug" ++ ++msgid "Cutter already running - Add to cutting queue?" ++msgstr "Bestandsbewerker aktief - aan wachtrij toeveogen?" ++ ++msgid "Rename$Up" ++msgstr "Boven" ++ ++msgid "Rename$Down" ++msgstr "Onder" ++ ++msgid "Rename$Previous" ++msgstr "Vorige" ++ ++msgid "Rename$Next" ++msgstr "Volgende" ++ ++msgid "Please mount %s" ++msgstr "Koppel %s aan a.u.b." ++ ++msgid "Please mount DVD %04d" ++msgstr "Koppel DVD %04d aan a.u.b." ++ ++msgid "Please mount DVD %d" ++msgstr "Koppel DVD %d aan a.u.b." ++ ++msgid "Please wait. Checking DVD..." ++msgstr "Even wachten, DVD wordt gecontroleerd..." ++ ++msgid "No index-file found. Creating may take minutes. Create one?" ++msgstr "Geen indexbestand gevonden. Aanmaken duurt even, doorgaan?" ++ ++msgid "Please wait. Creating index-file..." ++msgstr "Even wachten indexbestand wordt aangemaakt..." ++ ++msgid "Wrong DVD!" ++msgstr "Verkeerde DVD!" +diff -NaurwB vdr-1.7.10/po/ru_RU.po vdr-1.7.10-patched/po/ru_RU.po +--- vdr-1.7.10/po/ru_RU.po 2009-11-22 12:28:39.000000000 +0100 ++++ vdr-1.7.10-patched/po/ru_RU.po 2009-12-18 06:25:25.000000000 +0100 +@@ -1029,3 +1029,249 @@ + #, c-format + msgid "VDR will shut down in %s minutes" + msgstr "VDR ÒëÚÛîçØâáï çÕàÕ× %s ÜØÝãâ" ++ ++msgid "Channel locked by LNB!" ++msgstr "ºÞÝÒÕàâÕà ÑÛÞÚØàãÕâ ÚÐÝÐÛ" ++ ++msgid "Childlock" ++msgstr "´ÕâáÚÐï ÑÛÞÚØàÞÒÚÐ" ++ ++msgid "Path" ++msgstr "¿ãâì" ++ ++msgid "Timer commands" ++msgstr "ºÞÜÜÐÝÔë âÐÙÜÕàÐ" ++ ++msgid "Rename recording" ++msgstr "¿ÕàÕØÜÕÝÞÒÐâì ×ÐßØáì" ++ ++msgid "Date" ++msgstr "´ÐâÐ" ++ ++msgid "Length" ++msgstr "´ÛØÝÐ" ++ ++msgid "Size" ++msgstr "ÀÐ×ÜÕà" ++ ++msgid "Format" ++msgstr "" ++ ++msgid "PES" ++msgstr "" ++ ++msgid "TS" ++msgstr "" ++ ++msgid "Delete marks information?" ++msgstr "ÃÔÐÛØâì ÜÕâÚØ?" ++ ++msgid "Delete resume information?" ++msgstr "²ÞááâÐÝÞÒØâì ÜÕâÚØ?" ++ ++msgid "DVD plugin is not installed!" ++msgstr "¼ÞÔãÛì DVD ÝÕ ãáâÐÝÞÒÛÕÝ!" ++ ++msgid "main dir alphabetically, subdirs flexible" ++msgstr "ÓÛÐÒÝÐï ÔØàÕÚâÞàØï ßÞ ÐÛäÐÒØâã, ßÞÔÔØàÕÚâÞàØØ ÓØÑÚÞ" ++ ++msgid "main dir by date, subdirs flexible" ++msgstr "ÓÛÐÒÝÐï ÔØàÕÚâÞàØï ßÞ ÒàÕÜÕÝØ, ßÞÔÔØàÕÚâÞàØØ ÓØÑÚÞ" ++ ++msgid "all alphabetically" ++msgstr "ÒáÕ ßÞ ÐÛäÐÒØâã" ++ ++msgid "all by date" ++msgstr "ÒáÕ ßÞ ÒàÕÜÕÝØ" ++ ++msgid "Sorting" ++msgstr "ÁÞàâØàÞÒÚÐ" ++ ++msgid "Setup.OSD$WarEagle icons" ++msgstr "WarEagle ØÚÞÝÚØ" ++ ++msgid "Setup.OSD$Main menu command position" ++msgstr "ÀÐ×ÜÕéÕÝØÕ ÚÞÜÐÝÔ Ò ÓÛÐÒÝÞÜ ÜÕÝî" ++ ++msgid "Setup.OSD$Show valid input" ++msgstr "¿ÞÚÐ× ßàÐÒØÛìÝÞÓÞ ÒÒÞÔÐ" ++ ++msgid "Setup.EPG$Show progress bar" ++msgstr "¿ÞÚÐ× ÑÐÛÚØ ßàÞÓàÕááÐ" ++ ++msgid "Setup.EPG$Period for double EPG search(min)" ++msgstr "¿ÕàØÞÔ ÔÛï ßÞØáÚÐ ÔÒÞÙÝëå EPG(min)" ++ ++msgid "Setup.EPG$Extern double Epg entry" ++msgstr "²ÝÕèÝØÙ ØáâÞçÝØÚ ÔÒÞÙÝÞÓÞ EPG" ++ ++msgid "adjust" ++msgstr "ÝÐáâàÞØâì" ++ ++msgid "delete" ++msgstr "ãÔÐÛØâì" ++ ++msgid "Setup.EPG$Mix intern and extern EPG" ++msgstr "ÁÜÕèØÒÐÝØÕ ÒÝãâàÕÝÝÕÓÞ Ø ÒÝÕèÝÕÓÞ EPG" ++ ++msgid "Setup.EPG$Disable running VPS event" ++msgstr "·ÐßàÕâ VPS áÞÑëâØï" ++ ++msgid "Setup.DVB$Use AC3-Transfer Fix" ++msgstr "¸áßÞÛì×ÞÒÐâì ÚÞààÕÚâØàÞÒÚã AC3-Transfer" ++ ++msgid "Setup.DVB$Channel Blocker" ++msgstr "" ++ ++msgid "Setup.DVB$Channel Blocker Filter Mode" ++msgstr "" ++ ++msgid "Setup.LNB$DVB device %d uses LNB No." ++msgstr "DVB ãáâàÞÙáâÒÞ %d ØáßÞÛì×ãÕâ LNB No." ++ ++msgid "Setup.LNB$Log LNB usage" ++msgstr "»ÞÓØàÞÒÐÝØÕ ØáßÞÛì×ÞÒÐÝØï LNB" ++ ++msgid "Setup.Recording$Record Dolby Digital" ++msgstr "·ÐßØáì Dolby Digital" ++ ++msgid "Setup.Recording$Video directory policy" ++msgstr "¿ÞàïÔÞÚ ÒØÔÕÞ ÔØàÕÚâÞàØØ" ++ ++msgid "Setup.Recording$Number of video directories" ++msgstr "ºÞÛØçÕáâÒÞ ÒØÔÕÞ ÔØàÕÚâÞàØÙ" ++ ++msgid "Setup.Recording$Video %d priority" ++msgstr "Video %d ßàØÞàØâÕâ" ++ ++msgid "Setup.Recording$Video %d min. free MB" ++msgstr "Video %d ÜØÝ. áÒÞÑÞÔÝÞ MB" ++ ++msgid "Setup.Recording$Friendly filenames" ++msgstr "ÇØâÐÑÕÛìÝëÕ ØÜÕÝÐ äÐÙÛÞÒ" ++ ++msgid "Setup.Recording$Max. recording size (GB)" ++msgstr "¼ÐÚá. àÐ×ÜÕà ×ÐßØáØ (GB)" ++ ++msgid "Setup.Recording$Hard Link Cutter" ++msgstr "¼ÞÝâÐÖ á hard link" ++ ++msgid "Setup.Recording$Show date" ++msgstr "¿ÞÚÐ×ëÒÐâì ÔÐâã" ++ ++msgid "Setup.Recording$Show time" ++msgstr "¿ÞÚÐ×ëÒÐâì ÒàÕÜï ×ÐßØáØ" ++ ++msgid "Setup.Recording$Show length" ++msgstr "¿ÞÚÐ×ëÒÐâì ßàÞÔÞÛÖØâÕÛìÝÞáâì ×ÐßØáØ" ++ ++msgid "Setup.OSD$Main menu title" ++msgstr "½Ð×ÒÐÝØÕ ÓÛÐÒÝÞÓÞ ÜÕÝî" ++ ++msgid "Setup.Recording$Show end of timer" ++msgstr "¿ÞÚÐ× ÞÚÞÝçÐÝØï âÐÙÜÕàÐ" ++ ++msgid "Setup.Recording$Sort recordings by" ++msgstr "ÁÞàâØàÞÒÚÐ ×ÐßØáÕÙ ßÞ" ++ ++msgid "Setup.Recording$Sort directories before recordings" ++msgstr "ÁÞàâØàÞÒÚÐ ÔØàÕÚâÞàØÙ ÔÞ ×ÐßØáØ" ++ ++msgid "Setup.Recording$Cutter auto delete" ++msgstr "°ÒâÞÜÐâØçÕáÚÞÕ ãÔÐÛÕÝØÕ ÜÞÝâÐÖÐ" ++ ++msgid "Setup.Replay$Jump&Play" ++msgstr "Jump&Play" ++ ++msgid "Setup.Replay$Play&Jump" ++msgstr "Play&Jump" ++ ++msgid "Setup.Replay$Pause at last mark" ++msgstr "¿Ðã×Ð ÝÐ ßÞáÛÕÔÝÕÙ ÜÕâÚÕ" ++ ++msgid "Setup.Replay$Reload marks" ++msgstr "¿ÕàÕÓàã×Øâì ÜÕâÚØ" ++ ++msgid "Setup.Replay$Skip Seconds" ++msgstr "¿àÞßãáÚ áÕÚãÝÔ" ++ ++msgid "Setup.Replay$Skip Seconds Slow" ++msgstr "¿àÞßãáÚ áÕÚãÝÔ ÜÕÔÛÕÝÝÞ" ++ ++msgid "Setup.Replay$Length" ++msgstr "´ÛØÝÐ" ++ ++msgid "Setup.Replay$Length / Number" ++msgstr "´ÛØÝÐ / ½ÞÜÕà" ++ ++msgid "Setup.Replay$Number" ++msgstr "½ÞÜÕà" ++ ++msgid "Setup.Replay$DVD display mode" ++msgstr "ÀÕÖØÜ ßÞÚÐ×Ð DVD" ++ ++msgid "Setup.Replay$DVD display leading zeros" ++msgstr "ßÞÚÐ× ÒÕÔãéØå ÝãÛÕÙ" ++ ++msgid "Setup.Replay$never" ++msgstr "ÝØÚÞÓÔÐ" ++ ++msgid "Setup.Replay$on begin" ++msgstr "Ò ÝÐçÐÛÕ" ++ ++msgid "Setup.Replay$on end" ++msgstr "² ÚÞÝæÕ" ++ ++msgid "Setup.Replay$on begin and end" ++msgstr "Ò ÝÐçÐÛÕ Ø Ò ÚÞÝæÕ" ++ ++msgid "Setup.Replay$Tray open" ++msgstr "¾âÚàëâì" ++ ++msgid "Setup.Replay$Limit DVD to speed" ++msgstr "¿àÕÔÕÛ áÚÞàÞáâØ DVD" ++ ++msgid "Jump" ++msgstr "¿àëÖÞÚ" ++ ++msgid "Skip +60s" ++msgstr "¿àÞßãáÚ +60s" ++ ++msgid "Skip -60s" ++msgstr "¿àÞßãáÚ -60s" ++ ++msgid "Cutter already running - Add to cutting queue?" ++msgstr "¼ÞÝâÐÖÕà àÐÑÞâÐÕâ - ´ÞÑÐÒØâì Ò ÞçÕàÕÔì?" ++ ++msgid "Rename$Up" ++msgstr "²ÒÕàå" ++ ++msgid "Rename$Down" ++msgstr "²ÝØ×" ++ ++msgid "Rename$Previous" ++msgstr "¿àÕÔëÔãéØÙ" ++ ++msgid "Rename$Next" ++msgstr "ÁÛÕÔãîéØÙ" ++ ++msgid "Please mount %s" ++msgstr "¿ÞÖÐÛãÙáâÐ ßÞÔÜÞÝâØàãÙâÕ %s" ++ ++msgid "Please mount DVD %04d" ++msgstr "¿ÞÖÐÛãÙáâÐ ßÞÔÜÞÝâØàãÙâÕ DVD %04d" ++ ++msgid "Please mount DVD %d" ++msgstr "¿ÞÖÐÛãÙáâÐ ßÞÔÜÞÝâØàãÙâÕ DVD %d" ++ ++msgid "Please wait. Checking DVD..." ++msgstr "¿ÞÖÐÛãÙáâÐ ßÞÔÞÖÔØâÕ. ¿àÞÒÕàÚÐ DVD..." ++ ++msgid "No index-file found. Creating may take minutes. Create one?" ++msgstr "¸ÝÔÕÚá äÐÙÛ ÝÕ ÝÐÙÔÕÝ. ÁÞ×ÔÐÝØÕ âàÕÑãÕâ ÒàÕÜÕÝØ. ÁÞ×ÔÐâì?" ++ ++msgid "Please wait. Creating index-file..." ++msgstr "¿ÞÖÐÛãÙáâÐ ßÞÔÞÖÔØâÕ. ÁÞ×ÔÐÝØÕ ØÝÔÕÚá äÐÙÛÐ..." ++ ++msgid "Wrong DVD!" ++msgstr "¾èØÑÚÐ DVD!" +diff -NaurwB vdr-1.7.10/README.cmdsubmenu vdr-1.7.10-patched/README.cmdsubmenu +--- vdr-1.7.10/README.cmdsubmenu 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/README.cmdsubmenu 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,54 @@ ++CmdSubmenu patch for VDR ++------------------------ ++ ++With this patch the commands and recording commands menus can be organised ++hierarchically. To create a submenu entry, prefix the name by one ore more "-". ++ ++ ++Standard: ++ ++description_1 : cmd_1 ++description_2 : cmd_2 ++ ++ ++A submenu with two entries: ++ ++Submenu title ... : echo "submenu" ++-description_1 : cmd_1 ++-description_2 : cmd_2 ++ ++The dummy command in the title row is necessary. ++ ++ ++* History ++ ++ 2003-10-08: Version 0.1 - Albu at vdrportal.de ++ http://vdrportal.de/board/thread.php?threadid=6319 ++ ++ 2003-10-09: Version 0.2 - Tobias Grimm ++ - Added Define CMD_SUBMENUS in Makefile ++ ++ 2004-05-28: Version 0.3 - Thomas Günther ++ - Fixed compilation with gcc-3.3.3 ++ - Added new virtual method AddConfig in cConfig ++ - Redefining of method Add in cListBase to virtual no longer necessary ++ - Improved code in menu.c ++ http://toms-cafe.de/vdr/download/vdr-cmdsubmenu-0.3.diff ++ ++ 2004-12-20: Version 0.4 - Thomas Günther ++ - Solved conflict with jumpplay patch 0.6 ++ http://toms-cafe.de/vdr/download/vdr-cmdsubmenu-0.4.diff ++ ++ 2006-04-22: Version 0.5 - Thomas Günther ++ - Added version define CMDSUBMENUVERSNUM ++ - Reformated to VDR style indentions ++ - Added description in README.cmdsubmenu ++ http://toms-cafe.de/vdr/download/vdr-cmdsubmenu-0.5-1.3.47.diff ++ ++ 2006-04-23: Version 0.6 - Thomas Günther ++ - Fixed menus with more than one level ++ http://toms-cafe.de/vdr/download/vdr-cmdsubmenu-0.6-1.3.47.diff ++ ++ 2006-05-15: Version 0.7 - Thomas Günther ++ - Fixed build with G++ 4.1 (extra qualification) ++ http://toms-cafe.de/vdr/download/vdr-cmdsubmenu-0.7-1.4.0.diff +diff -NaurwB vdr-1.7.10/README-HLCUTTER vdr-1.7.10-patched/README-HLCUTTER +--- vdr-1.7.10/README-HLCUTTER 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/README-HLCUTTER 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,117 @@ ++ ++ VDR-HLCUTTER README ++ ++ ++Written by: Udo Richter ++Available at: http://www.udo-richter.de/vdr/patches.html#hlcutter ++ http://www.udo-richter.de/vdr/patches.en.html#hlcutter ++Contact: udo_richter@gmx.de ++ ++ ++ ++About ++----- ++ ++The hard link cutter patch changes the recording editing algorithms of VDR to ++use filesystem hard links to 'copy' recording files whenever possible to speed ++up editing recordings noticeably. ++ ++The patch has matured to be quite stable, at least I'm using it without issues. ++Nevertheless the patch is still in development and should be used with caution. ++The patch is EXPERIMENTAL for multiple /videoxx folders. The safety checks ++should prevent data loss, but you should always carefully check the results. ++ ++While editing a recording, the patch searches for any 00x.vdr files that dont ++contain editing marks and would normally be copied 1:1 unmodified to the edited ++recording. In this case the current target 00x.vdr file will be aborted, and ++the cutter process attempts to duplicate the source file as a hard link, so ++that both files share the same disk space. If this succeeds, the editing ++process fast-forwards through the duplicated file and continues normally ++beginning with the next source file. If hard linking fails, the cutter process ++continues with plain old copying. (but does not take up the aborted last file.) ++ ++After editing, the un-edited recording can be deleted as usual, the hard linked ++copies will continue to exist as the only remaining copy. ++ ++To be effective, the default 'Max. video file size (MB)' should be lowered. ++The patch lowers the smallest possible file size to 1mb. Since VDR only ++supports up to 255 files, this would limit the recording size to 255Mb or ++10 minutes, in other words: This setting is insane! ++ ++To make sure that the 255 file limit will not be reached, the patch also ++introduces "Max. recording size (GB)" with a default of 100Gb (66 hours), and ++increases the file size to 2000Mb early enough, so that 100Gb-recordings will ++fit into the 255 files. ++ ++Picking the right parameters can be tricky. The smaller the file size, the ++faster the editing process works. However, with a small file size, long ++recordings will fall back to 2000Mb files soon, that are slow on editing again. ++ ++Here are some examples: ++ ++Max file size: 100Gb 100Gb 100Gb 100Gb 100Gb 100Gb 100Gb ++Max recording size: 1Mb 10Mb 20Mb 30Mb 40Mb 50Mb 100Mb ++ ++Small files: 1-203 1-204 1-205 1-206 1-207 1-209 1-214 ++ GBytes: 0.2 2.0 4.0 6.0 8.1 10.2 20.9 ++ Hours: 0.13 1.3 2.65 4 5.4 6.8 13.9 ++ ++Big (2000mb) files: 204-255 204-255 206-255 207-255 208-255 210-255 215-255 ++ GBytes: 101.5 99.6 97.7 95.7 93.8 89.8 80.1 ++ Hours: 67 66 65 63 62 60 53 ++ ++A recording limit of 100Gb keeps plenty of reserve without blocking too much ++file numbers. And with a file size of 30-40Mb, recordings of 4-5 hours fit into ++small files completely. (depends on bit rate of course) ++ ++ ++ ++The patch must be enabled in Setup-> Recordings-> Hard Link Cutter. When ++disabled, the cutter process behaves identical to VDR's default cutter. ++ ++There's a //#define HARDLINK_TEST_ONLY in the videodir.c file that enables a ++test-mode that hard-links 00x.vdr_ files only, and continues the classic ++editing. The resulting 00x.vdr and 00x.vdr_ files should be identical. If you ++delete the un-edited recording, dont forget to delete the *.vdr_ files too, ++they will now eat real disk space. ++ ++Note: 'du' displays the disk space of hard links only on first appearance, and ++usually you will see a noticeably smaller size on the edited recording. ++ ++ ++History ++------- ++Version 0.2.0 ++ ++ New: Support for multiple /videoXX recording folders, using advanced searching ++ for matching file systems where a hard link can be created. ++ Also supports deep mounted file systems. ++ Fix: Do not fail if last mark is a cut-in. (Again.) ++ ++Version 0.1.4 ++ New: Dynamic increase of file size before running out of xxx.vdr files ++ Fix: Last edit mark is not a cut-out ++ Fix: Write error if link-copied file is smaller than allowed file size ++ Fix: Broken index/marks if cut-in is at the start of a new file ++ Fix: Clear dangeling pointer to free'd cUnbufferedFile, ++ thx to Matthias Schwarzott ++ ++Version 0.1.0 ++ Initial release ++ ++ ++ ++ ++Future plans ++------------ ++ ++Since original and edited copy share disk space, free space is wrong if one of ++them is moved to *.del. Free space should only count files with hard link ++count = 1. This still goes wrong if all copies get deleted. ++ ++ ++For more safety, the hard-linked files may be made read-only, as modifications ++to one copy will affect the other copy too. (except deleting, of course) ++ ++ ++SetBrokenLink may get lost on rare cases, this needs some more thoughts.Index: vdr-1.5.9/README.jumpplay +diff -NaurwB vdr-1.7.10/README.jumpplay vdr-1.7.10-patched/README.jumpplay +--- vdr-1.7.10/README.jumpplay 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/README.jumpplay 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,92 @@ ++JumpPlay patch for VDR ++---------------------- ++ ++This patch changes the replay behaviour for recordings that contain editing ++marks. It allows to immediately continue the replay after jumping forward to ++the next mark, and to automatically jump over the commercial break to the next ++"start" mark, if an "end" mark is reached. ++ ++The features of this patch can be turned on or off with parameters in the replay ++setup. See MANUAL for description of this parameters: "Jump&Play", "Play&Jump", ++"Pause at last mark" and "Reload marks". ++ ++ ++* History ++ ++ 2003-07-04: jumpandrun.diff - the Noad ++ Jump&Play ++ ++ 2003-12-06: Version 0.0 - Torsten Kunkel ++ Play&Jump (only if progressbar is visible) ++ Setup parameters Jump&Play and Play&Jump in the replay setup ++ ++ 2004-01-20: Version 0.1 - Thomas Günther ++ Jump&Play: ++ - fixed speed after jump ++ - fixed removing of marks ++ Play&Jump: ++ - jump only on "end" marks ++ ++ 2004-01-27: Version 0.2 - Thomas Günther ++ Jump&Play: ++ - fixed double jump ++ Play&Jump: ++ - fixed mark detection: fuzzy detection (until 3 seconds after mark) ++ - jump without progressbar ++ - mode "progressbar only" for old behaviour ++ ++ 2004-01-31: Version 0.3 - Thomas Günther ++ Jump&Play: ++ - fixed display frames ++ Play&Jump: ++ - fixed end of playing at last mark ++ ++ 2004-07-11: Version 0.4 - Thomas Günther ++ Jump&Play: ++ - don't play after jump to end ++ Play&Jump: ++ - don't prevent jumping after hide or show ++ Less conflicts with other patches (Elchi/AutoPID) ++ ++ 2004-08-21: Version 0.5 - Thomas Günther ++ Play&Jump: ++ - exact jumps, replay like edited recording (no fuzzy mark detection) ++ - jump to first mark if replay starts at the beginning ++ - check jump marks with '8' key ++ - mode "progressbar only" removed ++ Description in README.jumpplay ++ ++ 2004-12-28: Version 0.6 - Thomas Günther ++ Adapted noad extensions (from the Noad ) to ++ jumpplay-0.5: ++ - cyclic reloading of marks found by noad online-scan ++ - don't stop after the last mark in case of live-recordings ++ New setup parameter "Load marks interval (s)" ++ Updated description in README.jumpplay ++ ++ 2006-04-14: Version 0.7 - Thomas Günther ++ Fixed jump to first mark (crashed with plugin extrecmenu-0.9) ++ Added version define JUMPPLAYVERSNUM ++ Added placeholders for Czech language texts ++ Cleaned up i18n entries (support only VDR >= 1.3.29) ++ Improved description of i18n placeholders - hoping for real language texts ++ ++ 2006-05-12: Version 0.8 - Thomas Günther ++ Fixed segfault in dvbplayer thread while the replaycontrol thread is ++ reloading the marks (thanks to horchi at vdrportal.de for reporting this - ++ see http://vdrportal.de/board/thread.php?postid=450463#post450463): ++ New class cMarksReload checks the timestamp of marks.vdr in 10 seconds ++ intervals, so the marks in the threads dvbplayer and replaycontrol can be ++ reloaded independently ++ Changed setup parameter "Load marks interval (s)" to "Reload marks" ++ Updated description in README.jumpplay ++ ++ 2006-05-28: Version 0.9 - Thomas Günther ++ New setup parameter "Pause at last mark" ++ Updated description in README.jumpplay ++ Moved parameters description to MANUAL ++ ++ 2009-03-31: Version 1.0 - Thomas Günther ++ Play&Jump: ++ - set resume position to 0 if replay stops at the first mark ++ Added French language texts (thanks to Michaël Nival) +diff -NaurwB vdr-1.7.10/README.MainMenuHooks vdr-1.7.10-patched/README.MainMenuHooks +--- vdr-1.7.10/README.MainMenuHooks 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/README.MainMenuHooks 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,55 @@ ++This is a "patch" for the Video Disk Recorder (VDR). ++ ++* Authors: ++Tobias Grimm ++Martin Prochnow ++Frank Schmirler ++Christian Wieninger ++ ++* Description: ++This patch allows plugins to replace the VDR mainmenus "Schedule", ++"Channels", "Timers" and "Recordings" by a different implementation. ++ ++The patch is based on a suggestion of Christian Wieninger back in 2006 ++(http://www.linuxtv.org/pipermail/vdr/2006-March/008234.html). It is ++meant to be an interim solution for VDR 1.4 until (maybe) VDR 1.5 ++introduces an official API for this purpose. ++ ++* Installation ++Change into the VDR source directory, then issue ++ patch -p1 < path/to/MainMenuHooks-v1_0.patch ++and recompile. ++ ++* Notes for plugin authors ++The following code sample shows the required plugin code for replacing ++the original Schedule menu: ++ ++bool cMyPlugin::Service(const char *Id, void *Data) ++{ ++ cOsdMenu **menu = (cOsdMenu**) Data; ++ if (MySetup.replaceSchedule && ++ strcmp(Id, "MainMenuHooksPatch-v1.0::osSchedule") == 0) { ++ if (menu) ++ *menu = (cOsdMenu*) MainMenuAction(); ++ return true; ++ } ++ return false; ++} ++ ++A plugin can replace more than one menu at a time. Simply replace the ++call to MainMenuAction() in the sample above by appropriate code. ++ ++Note that a plugin *should* offer a setup option which allows the user ++to enable or disable the replacement. "Disabled" would be a reasonable ++default setting. By testing for define MAINMENUHOOKSVERSNUM, a plugin ++can leave the setup option out at compiletime. ++ ++In case there is an internal problem when trying to open the replacement ++menu, it is safe to return true even though Data is NULL. However an ++OSD message should indicate the problem to the user. ++ ++Feel free to ship this patch along with your plugin. However if you ++think you need to modify the patch, we'd encourage you to contact the ++authors first or at least use a service id which differs in more than ++just the version number. ++ +diff -NaurwB vdr-1.7.10/README.sortrec vdr-1.7.10-patched/README.sortrec +--- vdr-1.7.10/README.sortrec 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/README.sortrec 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,48 @@ ++Sort Recordings patch for VDR ++----------------------------- ++Copyright (C) 2005 Frank99 @vdr-portal.de ++Copyright (C) 2006-2008 Christoph Haubrich ++ ++Released under the same license as VDR itself, for details see vdr.c or ++http://firefly.vdr-developer.org/patches ++ ++This patch changes the sort behaviour of the recordings menu. It is based ++on the patch available here: http://www.vdr-portal.de/board/thread.php?threadid=36031 ++Required for this patch is the liemikuutio-patch for VDR which can be found here: ++http://www.saunalahti.fi/%7Erahrenbe/vdr/patches/ ++ ++There are four sorting modes available after this patch is applied: ++ ++mode behaviour for behaviour for ++ main directory sub directories ++-------------------------------------------------------------------------- ++ 0 alphabetically if special character(*) is found alphabetically, ++ else by date ++ 1 by date if special character(*) is found alphabetically, ++ else by date ++ 2 alphabetically alphabetically ++ 3 by date by date ++ ++(*) if the name of a subdirectory ends with one of ".-$" (dot, hyphen, dollar sign) ++ it is sorted alphabetically in sort mode 0 and 1 ++ ++Sort mode 0 with none of the special characters at the end of any subdir ++corresponds to the default sorting mode of the original VDR. ++ ++The sorting mode can be switched through in the recording menu with the '0' key ++(0->1->2->3->0->...), a default for startup can be set in the setup->recordings menu. ++ ++Additionally the sort order (ascending/descending) can be toggled by the '9' key ++(which is always set to ascending after a restart) ++ ++If you like the to see subdirectories before recordings you can select to put ++directories first in the setup->recordings menu. ++ ++If you would like the sorting to ignore a leading '%' (as normally displayed before ++cutted recordings) you can achive this by setting the environment variable LC_COLLATE ++properly (eg. LC_COLLATE=de_DE@euo in runvdr for germany). ++ ++History: ++2006-08-13 v3, sortrec release for VDR 1.4.1 and liemikuutio 1.8 ++2007-01-28 v3a, moved #ifdef from optimized-rename-patch to sortrec ++2008-03-29 v3b, removed ASCII-170 and ASCII-183 to make sortrec Utf8-ready +diff -NaurwB vdr-1.7.10/README.timer-info vdr-1.7.10-patched/README.timer-info +--- vdr-1.7.10/README.timer-info 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/README.timer-info 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,53 @@ +++------------------------------------------------------------------------------+ ++| Info about the timer-info-patch by Brougs78 | ++| brougs78@gmx.net / home.pages.at/brougs78 | +++------------------------------------------------------------------------------+ ++ ++ ++README timer-info: ++------------------ ++ ++Features: ++ - Shows info, if it is possible to record an event in the timer menu of vdr. ++ For calculations the free space incl. the deleted recordings is used, ++ considering an average consumtion of 25.75 MB/min (also used by vdr itself). ++ The first column in the timer-list shows: ++ ( + ) recording will be most probably possible (enough space) ++ (+/-) recording may be possible ++ ( - ) recording will most probably fail (to less space) ++ The calculations also consider repeating timers. ++ - It is possible to deactivate the patch in the OSD-menu of VDR. ++ ++ ++HISTORY timer-info: ++------------------- ++ ++25.11.2004: v0.1 ++ - Initial release ++ ++11.01.2005: v0.1b ++ - Bugfixes for vdr-1.3.18 ++ - In the menu the free recording-time no longer includes the space of the ++ deleted recordings, because this slowed the vdr down to much. ++ ++08.07.2005: v0.1c ++ - Made the patch configurable ++ ++29.01.2006: v0.2 - Thomas Günther ++ - Rewritten great parts for vdr-1.3.38+ ++ http://toms-cafe.de/vdr/download/vdr-timer-info-0.2-1.3.38+.diff ++ ++05.02.2006: v0.3 - Thomas Günther ++ - Fixed refresh of timer menu in cMenuTimers::OnOff ++ - Fixed check of repeating timers ++ - Syslog debug messages can be enabled with Define DEBUG_TIMER_INFO ++ http://toms-cafe.de/vdr/download/vdr-timer-info-0.3-1.3.38+.diff ++ ++03.03.2006: v0.4 - Thomas Günther ++ - Adapted to vdr-1.3.44 ++ - Removed setup parameter "Show timer-info" ++ http://toms-cafe.de/vdr/download/vdr-timer-info-0.4-1.3.44.diff ++ ++03.03.2006: v0.5 - Tobias Grimm ++ - Adapted to vdr-1.3.45 ++ http://toms-cafe.de/vdr/download/vdr-timer-info-0.4-1.3.45.diff +diff -NaurwB vdr-1.7.10/receiver.c vdr-1.7.10-patched/receiver.c +--- vdr-1.7.10/receiver.c 2007-08-12 13:52:59.000000000 +0200 ++++ vdr-1.7.10-patched/receiver.c 2009-12-18 06:25:25.000000000 +0100 +@@ -12,7 +12,11 @@ + #include + #include "tools.h" + ++#ifdef USE_TTXTSUBS ++cReceiver::cReceiver(tChannelID ChannelID, int Priority, int Pid, const int *Pids1, const int *Pids2, const int *Pids3, const int *Pids4) ++#else + cReceiver::cReceiver(tChannelID ChannelID, int Priority, int Pid, const int *Pids1, const int *Pids2, const int *Pids3) ++#endif /* TTXTSUBS */ + { + device = NULL; + channelID = ChannelID; +@@ -32,6 +36,12 @@ + while (*Pids3 && numPids < MAXRECEIVEPIDS) + pids[numPids++] = *Pids3++; + } ++#ifdef USE_TTXTSUBS ++ if (Pids4) { ++ while (*Pids4 && numPids < MAXRECEIVEPIDS) ++ pids[numPids++] = *Pids4++; ++ } ++#endif /* TTXTSUBS */ + if (numPids >= MAXRECEIVEPIDS) + dsyslog("too many PIDs in cReceiver"); + } +diff -NaurwB vdr-1.7.10/receiver.h vdr-1.7.10-patched/receiver.h +--- vdr-1.7.10/receiver.h 2007-01-05 12:00:36.000000000 +0100 ++++ vdr-1.7.10-patched/receiver.h 2009-12-18 06:25:25.000000000 +0100 +@@ -38,10 +38,15 @@ + ///< will be delivered only ONCE, so the cReceiver must make sure that + ///< it will be able to buffer the data if necessary. + public: ++#ifdef USE_TTXTSUBS ++ cReceiver(tChannelID ChannelID, int Priority, int Pid, const int *Pids1 = NULL, const int *Pids2 = NULL, const int *Pids3 = NULL, const int *Pids4 = NULL); ++#else + cReceiver(tChannelID ChannelID, int Priority, int Pid, const int *Pids1 = NULL, const int *Pids2 = NULL, const int *Pids3 = NULL); ++#endif /* TTXTSUBS */ + ///< Creates a new receiver for the channel with the given ChannelID with + ///< the given Priority. Pid is a single PID (typically the video PID), while + ///< Pids1...Pids3 are pointers to zero terminated lists of PIDs. ++ ///< ifdef USE_TTXTSUBS: Pids1...Pids4 are pointers to zero terminated lists of PIDs. + ///< If any of these PIDs are 0, they will be silently ignored. + ///< The total number of non-zero PIDs must not exceed MAXRECEIVEPIDS. + ///< Priority may be any value in the range -99..99. Negative values indicate +diff -NaurwB vdr-1.7.10/recorder.c vdr-1.7.10-patched/recorder.c +--- vdr-1.7.10/recorder.c 2009-11-21 16:58:12.000000000 +0100 ++++ vdr-1.7.10-patched/recorder.c 2009-12-18 06:25:25.000000000 +0100 +@@ -21,8 +21,21 @@ + + // --- cRecorder ------------------------------------------------------------- + ++#ifdef USE_TTXTSUBS ++cRecorder::cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids, const int *EPids) ++#ifdef USE_DOLBYINREC ++:cReceiver(ChannelID, Priority, VPid, APids, Setup.UseDolbyInRecordings ? DPids : NULL, SPids, EPids) ++#else ++:cReceiver(ChannelID, Priority, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids, EPids) ++#endif /* DOLBYINREC */ ++#else + cRecorder::cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids) ++#ifdef USE_DOLBYINREC ++:cReceiver(ChannelID, Priority, VPid, APids, Setup.UseDolbyInRecordings ? DPids : NULL, SPids) ++#else + :cReceiver(ChannelID, Priority, VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids) ++#endif /* DOLBYINREC */ ++#endif /* TTXTSUBS */ + ,cThread("recording") + ,recordingInfo(FileName) + { +@@ -87,7 +100,11 @@ + bool cRecorder::NextFile(void) + { + if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame ++#ifndef USE_HARDLINKCUTTER + if (fileSize > MEGABYTE(off_t(Setup.MaxVideoFileSize)) || RunningLowOnDiskSpace()) { ++#else ++ if (fileSize > fileName->MaxFileSize() || RunningLowOnDiskSpace()) { ++#endif /* HARDLINKCUTTER */ + recordFile = fileName->NextFile(); + fileSize = 0; + } +diff -NaurwB vdr-1.7.10/recorder.h vdr-1.7.10-patched/recorder.h +--- vdr-1.7.10/recorder.h 2009-01-06 11:44:58.000000000 +0100 ++++ vdr-1.7.10-patched/recorder.h 2009-12-18 06:25:25.000000000 +0100 +@@ -34,7 +34,11 @@ + virtual void Receive(uchar *Data, int Length); + virtual void Action(void); + public: ++#ifdef USE_TTXTSUBS ++ cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids, const int *EPids = NULL); ++#else + cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids); ++#endif /* TTXTSUBS */ + // Creates a new recorder for the channel with the given ChannelID and + // the given Priority that will record the given PIDs into the file FileName. + virtual ~cRecorder(); +diff -NaurwB vdr-1.7.10/recording.c vdr-1.7.10-patched/recording.c +--- vdr-1.7.10/recording.c 2009-11-22 12:20:53.000000000 +0100 ++++ vdr-1.7.10-patched/recording.c 2009-12-18 06:29:45.000000000 +0100 +@@ -8,6 +8,9 @@ + */ + + #include "recording.h" ++#ifdef USE_WAREAGLEICON ++#include "iconpatch.h" ++#endif /* WAREAGLEICON */ + #include + #include + #include +@@ -26,6 +29,14 @@ + #include "tools.h" + #include "videodir.h" + ++#if defined (USE_DVDCHAPJUMP) && defined (USE_DVDARCHIVE) ++#include ++/* libdvdread stuff */ ++#include ++#include ++#include ++#endif /* DVDCHAPJUMP & DVDARCHIVE */ ++ + #define SUMMARYFALLBACK + + #define RECEXT ".rec" +@@ -50,6 +61,9 @@ + #endif + #define INFOFILESUFFIX "/info" + #define MARKSFILESUFFIX "/marks" ++#ifdef USE_DVDARCHIVE /* ??? */ ++#define DVDARCHIVEFILENAME "/dvd.vdr" ++#endif /* DVDARCHIVE */ + + #define MINDISKSPACE 1024 // MB + +@@ -67,6 +81,13 @@ + + bool VfatFileSystem = false; + int InstanceId = 0; ++#ifdef USE_LIEMIEXT ++bool DirOrderState = false; ++#endif /* LIEMIEXT */ ++ ++#ifdef USE_DVLFRIENDLYFNAMES ++char *MakeFriendlyFilename(char **buf); ++#endif /* DVLFRIENDLYFNAMES */ + + cRecordings DeletedRecordings(true); + +@@ -602,9 +623,24 @@ + { + resume = RESUME_NOT_INITIALIZED; + titleBuffer = NULL; ++#ifdef USE_SORTRECORDS ++ for (int i = 0; i < MAXSORTMODES; i++) { ++ sortBuffer[i] = NULL; ++ lastDirsFirst[i] = -1; ++ } ++#else + sortBuffer = NULL; ++#endif /* SORTRECORDS */ + fileName = NULL; + name = NULL; ++#ifdef USE_DVDARCHIVE ++ dvdname = NULL; ++ dvdtrack = NULL; ++ dvdchapters = NULL; ++ isArchived = false; ++ isOnlyOnDvd = false; ++ dvdtype = DVD_TYPE_UNKNOWN; ++#endif /* DVDARCHIVE */ + fileSizeMB = -1; // unknown + channel = Timer->Channel()->Number(); + instanceId = InstanceId; +@@ -639,6 +675,11 @@ + break; + } + if (Timer->IsSingleEvent()) { ++#ifdef USE_DVLFRIENDLYFNAMES ++ if (Setup.UseFriendlyFNames == 1) ++ Timer -> SetFile(MakeFriendlyFilename(&name)); ++ else ++#endif /* DVLFRIENDLYFNAMES */ + Timer->SetFile(name); // this was an instant recording, so let's set the actual data + Timers.SetModified(); + } +@@ -649,6 +690,10 @@ + name = strdup(cString::sprintf("%s~%s", Timer->File(), Subtitle)); + // substitute characters that would cause problems in file names: + strreplace(name, '\n', ' '); ++#ifdef USE_DVLFRIENDLYFNAMES ++ if (Setup.UseFriendlyFNames == 1) ++ MakeFriendlyFilename(&name); ++#endif /* DVLFRIENDLYFNAMES */ + start = Timer->StartTime(); + priority = Timer->Priority(); + lifetime = Timer->Lifetime(); +@@ -671,12 +716,27 @@ + framesPerSecond = DEFAULTFRAMESPERSECOND; + deleted = 0; + titleBuffer = NULL; ++#ifdef USE_SORTRECORDS ++ for (int i = 0; i < MAXSORTMODES; i++) { ++ sortBuffer[i] = NULL; ++ lastDirsFirst[i] = -1; ++ } ++#else + sortBuffer = NULL; ++#endif /* SORTRECORDS */ + fileName = strdup(FileName); + FileName += strlen(VideoDirectory) + 1; + const char *p = strrchr(FileName, '/'); + + name = NULL; ++#ifdef USE_DVDARCHIVE ++ dvdname = NULL; ++ dvdtrack = NULL; ++ dvdchapters = NULL; ++ isArchived = false; ++ isOnlyOnDvd = false; ++ dvdtype = DVD_TYPE_NOT_READ; ++#endif /* DVDARCHIVE */ + info = new cRecordingInfo; + if (p) { + time_t now = time(NULL); +@@ -765,15 +825,34 @@ + LOG_ERROR_STR(*SummaryFileName); + } + #endif ++#ifdef USE_DVDARCHIVE ++ if (CheckFileExistence("dvd.vdr")) { ++ GetDvdName(fileName); ++ isArchived = true; ++ if (!CheckFileExistence("001.vdr")) ++ isOnlyOnDvd = true; ++ } ++#endif /* DVDARCHIVE */ + } + } + + cRecording::~cRecording() + { + free(titleBuffer); ++#ifdef USE_SORTRECORDS ++ for (int i = 0; i < MAXSORTMODES; i++) { ++ free(sortBuffer[i]); ++ } ++#else + free(sortBuffer); ++#endif /* SORTRECORDS */ + free(fileName); + free(name); ++#ifdef USE_DVDARCHIVE ++ free(dvdname); ++ free(dvdtrack); ++ free(dvdchapters); ++#endif /* DVDARCHIVE */ + delete info; + } + +@@ -793,21 +872,46 @@ + t++; + } + if (s1 && s2) ++#ifdef USE_SORTRECORDS ++ if (Setup.RecordingsSortDirsFirst) ++ *s1 = 'b'; ++ ++ if ((Setup.RecordingsSortMode <= 1 && s1 != s && !strchr(".-$ª·", *(s1 - 1))) || ++ (Setup.RecordingsSortMode == 1 && s1 == s) || ++ (Setup.RecordingsSortMode == 3)) ++#endif /* SORTRECORDS */ + memmove(s1 + 1, s2, t - s2 + 1); + return s; + } + + char *cRecording::SortName(void) const + { ++#ifdef USE_SORTRECORDS ++ if (!sortBuffer[Setup.RecordingsSortMode] || ++ lastDirsFirst[Setup.RecordingsSortMode] != Setup.RecordingsSortDirsFirst) { ++ free(sortBuffer[Setup.RecordingsSortMode]); ++ lastDirsFirst[Setup.RecordingsSortMode] = Setup.RecordingsSortDirsFirst; ++ char *s = StripEpisodeName(strdup(FileName() + strlen(VideoDirectory))); ++#else + if (!sortBuffer) { + char *s = StripEpisodeName(strdup(FileName() + strlen(VideoDirectory) + 1)); ++#endif /* SORTRECORDS */ + strreplace(s, '/', 'a'); // some locales ignore '/' when sorting + int l = strxfrm(NULL, s, 0) + 1; ++#ifdef USE_SORTRECORDS ++ sortBuffer[Setup.RecordingsSortMode] = MALLOC(char, l); ++ strxfrm(sortBuffer[Setup.RecordingsSortMode], s, l); ++#else + sortBuffer = MALLOC(char, l); + strxfrm(sortBuffer, s, l); ++#endif /* SORTRECORDS */ + free(s); + } ++#ifdef USE_SORTRECORDS ++ return sortBuffer[Setup.RecordingsSortMode]; ++#else + return sortBuffer; ++#endif /* SORTRECORDS */ + } + + int cRecording::GetResume(void) const +@@ -822,7 +926,15 @@ + int cRecording::Compare(const cListObject &ListObject) const + { + cRecording *r = (cRecording *)&ListObject; ++#ifdef USE_SORTRECORDS ++ return Recordings.GetSortOrder() * strcasecmp(SortName(), r->SortName()); ++#else ++#ifdef USE_LIEMIEXT ++ if (DirOrderState) ++ return strcasecmp(FileName(), r->FileName()); ++#endif /* LIEMIEXT */ + return strcasecmp(SortName(), r->SortName()); ++#endif /* USE_SORTRECORDS */ + } + + const char *cRecording::FileName(void) const +@@ -840,9 +952,359 @@ + return fileName; + } + ++#ifdef USE_DVDARCHIVE ++bool cRecording::CheckFileExistence(const char* FileNameToTest, const bool useVideoDir) const ++{ ++ if (!useVideoDir || (useVideoDir && FileName())) { ++ cString filename = cString::sprintf("%s%s%s", useVideoDir ? FileName() : "", ++ useVideoDir ? "/" : "", ++ FileNameToTest); ++ struct stat statBuf; ++ if (lstat(filename, &statBuf) == -1) return false; ++ return S_ISREG(statBuf.st_mode) || S_ISLNK(statBuf.st_mode); ++ } ++ return false; ++} ++ ++bool cRecording::GetDvdName(const char* Directory) const ++{ ++ char* filename = (char*)alloca(strlen(Directory) + strlen(DVDARCHIVEFILENAME) + 1); ++ if (filename) { ++ strcpy(filename, Directory); ++ char *end = filename + strlen(filename); ++ strcpy(end, DVDARCHIVEFILENAME); ++ FILE* file; ++ if ((file = fopen(filename, "r"))) { ++ cReadLine ReadLine; ++ char* buffer = (char*)alloca(BUFSIZ); ++ if (buffer) { ++ buffer = ReadLine.Read(file); ++ if (buffer) ++ ((cRecording*)this)->dvdname = strdup(buffer); ++ else ++ ((cRecording*)this)->dvdname = NULL; ++ ++ buffer = ReadLine.Read(file); ++ if (buffer) { ++ ((cRecording*)this)->dvdtrack = strdup(buffer); ++ if (atoi(buffer) == 0) ++ ((cRecording*)this)->dvdtype = DVD_VIDEO_TYPE; ++ else ++ ((cRecording*)this)->dvdtype = DVD_VIDEO_ARCHIVE_TYPE; ++ } ++ else { ++ ((cRecording*)this)->dvdtrack = NULL; ++ ((cRecording*)this)->dvdtype = DVD_ARCHIVE_TYPE; ++ } ++ ++ fclose(file); ++ return true; ++ } ++ } ++ } ++ return false; ++} ++ ++bool cRecording::GetDvdChaptersFromDvd(int title) const ++{ ++#ifdef USE_DVDCHAPJUMP ++ cString buf; ++ ++ dvd_reader_t *dvd; ++ ifo_handle_t *ifo_file; ++ tt_srpt_t *tt_srpt; ++ ifo_handle_t *vts_file; ++ pgc_t *cur_pgc; ++ ++ dvd = DVDOpen(DVD_DEVICE); ++ if (!dvd) { ++ esyslog("DVD-ARCHIVE: Couldn't open DVD device %s!", DVD_DEVICE); ++ return false; ++ } ++ ++ /* open title manager */ ++ ifo_file = ifoOpen(dvd,0); ++ if (!ifo_file) { ++ esyslog("DVD-ARCHIVE: Can't open VMG info."); ++ DVDClose(dvd); ++ return false; ++ } ++ ++ /* read total_title */ ++ tt_srpt = ifo_file->tt_srpt; ++ ++ /* get total chapters */ ++ int title_set_nr = tt_srpt->title[title-1].title_set_nr; ++ int total_chap = tt_srpt->title[title-1].nr_of_ptts; ++ int local_title_id = tt_srpt->title[title-1].vts_ttn - 1; ++ ++ /* access title set file */ ++ vts_file = ifoOpen(dvd, title_set_nr); ++ if (!vts_file) { ++ esyslog("DVD-ARCHIVE: Can't open info file for title set %d!",title_set_nr); ++ DVDClose(dvd); ++ return false; ++ } ++ ++ /* find program chain and check programs ++ all chapters should be in the same prog chain and ++ should be numbered from 1 to ++ */ ++ { ++ vts_ptt_srpt_t *vts_ptt_srpt = vts_file->vts_ptt_srpt; ++ int pgc_nr = vts_ptt_srpt->title[local_title_id].ptt[0].pgcn; ++ int pg = vts_ptt_srpt->title[local_title_id].ptt[0].pgn; ++ int p; ++ ++ assert(pg==1); ++ for (p=1; ptitle[local_title_id].ptt[p].pgcn; ++ assert(pgc_nr == this_pgc); ++ next_pg = vts_ptt_srpt->title[local_title_id].ptt[p].pgn; ++ assert(pg+1 == next_pg); ++ pg = next_pg; ++ } ++ ++ /* fetch program chain */ ++ cur_pgc = vts_file->vts_pgcit->pgci_srp[pgc_nr-1].pgc; ++ assert(cur_pgc->nr_of_programs == total_chap); ++ } ++ ++ /* --- main cell loop --- */ ++ { ++ pgc_program_map_t *chap_cell; ++ cell_playback_t *cell_pb; ++ int c; ++ int chap; ++ ++ /* total cells in chain */ ++ int total_cell = cur_pgc->nr_of_cells; ++ ++ /* get info */ ++ chap_cell = cur_pgc->program_map; ++ cell_pb = cur_pgc->cell_playback; ++ ++ /* loop through all cells */ ++ chap = -1; ++ int position = 0; ++ for (c=0; cplayback_time; ++ ++ int framerate = time->frame_u>>6; ++ assert(framerate == 1 || framerate == 3); ++ int frames_per_sec = (framerate == 1) ? 25 : 30; ++ ++ /* upper 4 bits are first digit, down 4 bits are second digit */ ++ int hour = (time->hour>>4) * 10 + (time->hour&15); ++ int minute = (time->minute>>4) * 10 + (time->minute&15); ++ int second = (time->second>>4) * 10 + (time->second&15); ++ /* upper 4 bits are first digit, down 4 bits are second digit */ ++ int frame = (time->frame_u>>4&3) * 10 + (time->frame_u&15); ++ ++ int frames = ((hour * 3600) + (minute * 60) + second) * frames_per_sec + frame; ++ ++ /* this cell is the begin of a new chapter! */ ++ if (chap_cell[chap+1] == c+1) { ++ cString oldbuf = cString::sprintf("%s", *buf); ++ buf = cString::sprintf("%s%d%s", *oldbuf, position, ((chap+2) < total_chap) ? "," : ""); ++ chap++; ++ } ++ ++ /* cell_mode: 0=normal, 1=first of angle, 2=in angle, 3=last of angle */ ++ cell_mode = cell_pb->block_mode; ++ if ((cell_mode==0) || (cell_mode==1)) { ++ /* only account for normal or begin of angle cells */ ++ position += frames; ++ mode = "counted"; ++ } ++ else ++ mode = "skipped"; ++ ++ cell_pb++; ++ } ++ } ++ ++ ifoClose(ifo_file); ++ ifoClose(vts_file); ++ DVDClose(dvd); ++ ++ ((cRecording*)this)->dvdchapters = strdup(buf); ++ ++ return true; ++#else ++ return false; ++#endif /* DVDCHAPJUMP */ ++} ++ ++const char *cRecording::GetDvdChapters(void) const ++{ ++ // Read chapters from dvd ++ if (dvdtype == DVD_VIDEO_ARCHIVE_TYPE) { ++ if (dvdtrack) { ++ if (!GetDvdChaptersFromDvd(atoi(dvdtrack))) ++ ((cRecording*)this)->dvdchapters = NULL; ++ else ++ isyslog("DVD-ARCHIVE: Using following positions for chapter jumping: %s", dvdchapters); ++ return dvdchapters; ++ } ++ } ++ return NULL; ++} ++ ++int cRecording::MountDvd(void) const ++{ ++ cString cmd; ++ if (Setup.DvdSpeedLimit > 0) { ++ cmd = cString::sprintf("speedcontrol -x %d %s", Setup.DvdSpeedLimit, DVD_DEVICE); ++ SystemExec(cmd); ++ } ++ ++ cString msg; ++ if (atoi(dvdname) == 0) ++ msg = cString::sprintf(tr("Please mount %s"), dvdname); ++ else { ++ if (Setup.DvdDisplayZeros) ++ msg = cString::sprintf(tr("Please mount DVD %04d"), atoi(dvdname)); ++ else ++ msg = cString::sprintf(tr("Please mount DVD %d"), atoi(dvdname)); ++ } ++ ++ bool rep = true; ++ while (rep) { ++ if (Setup.DvdTrayMode==1 || Setup.DvdTrayMode==3) ++ cmd = cString::sprintf("umount %s; eject %s", DVD_DEVICE, DVD_DEVICE); ++ else ++ cmd = cString::sprintf("umount %s", DVD_DEVICE); ++ SystemExec(cmd); ++ ++ if (Interface->Confirm(msg, 300)) { ++ Skins.Message(mtStatus, tr("Please wait. Checking DVD...")); ++ Skins.Flush(); ++ cmd = cString::sprintf("eject -t %s; mkdir -p %s; mount -o ro -t %s %s %s", ++ DVD_DEVICE, DVD_MOUNT_PATH, ++ (dvdtrack ? "udf" : "iso9660"), ++ DVD_DEVICE, DVD_MOUNT_PATH); ++ SystemExec(cmd); ++ ++ bool correctDvd = true; ++ ++ char *olddvdname, *olddvdtrack; ++ int olddvdtype; ++ olddvdname = dvdname; ++ olddvdtrack = dvdtrack; ++ olddvdtype = dvdtype; ++ if (GetDvdName(DVD_MOUNT_PATH)) { ++ if (atoi(dvdname) != atoi(olddvdname)) correctDvd = false; ++ } ++ ((cRecording*)this)->dvdname = olddvdname; ++ ((cRecording*)this)->dvdtrack = olddvdtrack; ++ ((cRecording*)this)->dvdtype = olddvdtype; ++ ++ if (correctDvd) { ++ if (dvdtrack == NULL) { ++ // Archived DVD in VDR format ++ char fn[BUFSIZ]; ++ strcpy(fn, FileName()); ++ char *p = strrchr(fn, '/'); ++ cmd = cString::sprintf("find '%s' -name '%s'", DVD_MOUNT_PATH, p+1); ++ } ++ else { ++ // Either archived DVD in DVD-Video format or DVD-Video which ++ // should be played with the DVD plugin ++ cmd = cString::sprintf("find '%s' -iname 'VIDEO_TS'", DVD_MOUNT_PATH); ++ } ++ ++ cReadLine pipe; ++ FILE* file; ++ char *dirname = NULL; ++ if ((file = popen(cmd, "r")) != (FILE *)NULL) { ++ if ((dirname = pipe.Read(file)) != NULL) { ++ pclose(file); ++ if (dvdtrack != NULL && atoi(dvdtrack) == 0) { ++ // It is a valid Video-DVD and DVD plugin can be started ++ return MOUNT_DVD_LAUNCH_DVD_PLUGIN; ++ } ++ else { ++ // It is a valid Archive-DVD or an archived Video-DVD ++ // and the links can now be established ++ cString srcFn; ++ int n = 1; ++ ++ do { ++ if (dvdtrack == NULL) ++ srcFn = cString::sprintf("%s/%03d.vdr", dirname, n); ++ else ++ srcFn = cString::sprintf("%s/VTS_%02d_%d.VOB", dirname, atoi(dvdtrack), n); ++ ++ if (!access(srcFn, R_OK)) { ++ cmd = cString::sprintf("ln -sf '%s' '%s/%03d.vdr'", *srcFn, FileName(), n); ++ SystemExec(cmd); ++ isyslog("DVD-ARCHIVE: Linking %s/%03d.vdr -> %s", FileName(), n, *srcFn); ++ } ++ else ++ break; ++ } while ( ++n < 999); ++ ++ if (!CheckFileExistence("index.vdr")) { ++ if (dvdtrack == NULL) ++ srcFn = cString::sprintf("%s/index.vdr", dirname); ++ else ++ srcFn = cString::sprintf("%s/index_%02d.vdr", dirname, atoi(dvdtrack)); ++ ++ if (!CheckFileExistence(srcFn, false)) { ++ msg = cString::sprintf(tr("No index-file found. Creating may take minutes. Create one?")); ++ if (Interface->Confirm(msg, 300)) { ++ Skins.Message(mtStatus, tr("Please wait. Creating index-file...")); ++ cmd = cString::sprintf("speedcontrol -x 999 %s; cd %s && genindex &", DVD_DEVICE, FileName()); ++ SystemExec(cmd); ++ return MOUNT_DVD_ABORT; ++ } ++ } ++ else { ++ cmd = cString::sprintf("ln -sf '%s' '%s/index.vdr'", *srcFn, FileName()); ++ SystemExec(cmd); ++ isyslog("DVD-ARCHIVE: Linking %s/index.vdr -> %s", FileName(), *srcFn); ++ } ++ } ++ return MOUNT_DVD_REPLAY; ++ } ++ } ++ else { ++ Skins.Message(mtError, tr("Wrong DVD!"), 3); ++ Skins.Flush(); ++ } ++ } ++ pclose(file); ++ } ++ else { ++ Skins.Message(mtError, tr("Wrong DVD!"), 3); ++ Skins.Flush(); ++ } ++ } ++ else { ++ rep = false; ++ } ++ } ++ return MOUNT_DVD_ABORT; ++} ++ ++#endif /* DVDARCHIVE */ ++#ifdef USE_LIEMIEXT ++const char *cRecording::Title(char Delimiter, bool NewIndicator, int Level, bool Original) const ++#else + const char *cRecording::Title(char Delimiter, bool NewIndicator, int Level) const ++#endif /* LIEMIEXT */ + { ++#ifdef USE_WAREAGLEICON ++ const char *New = NewIndicator && IsNew() ? Setup.WarEagleIcons ? IsLangUtf8() ? ICON_NEW_UTF8 : ICON_NEW : "*" : " "; ++#else + char New = NewIndicator && IsNew() ? '*' : ' '; ++#endif /* WAREAGLEICON */ + free(titleBuffer); + titleBuffer = NULL; + if (Level < 0 || Level == HierarchyLevels()) { +@@ -853,7 +1315,14 @@ + s++; + else + s = name; ++#ifdef USE_LIEMIEXT ++ if (Original) { ++#endif /* LIEMIEXT */ ++#ifdef USE_WAREAGLEICON ++ titleBuffer = strdup(cString::sprintf("%02d.%02d.%02d%c%02d:%02d%s%c%s", ++#else + titleBuffer = strdup(cString::sprintf("%02d.%02d.%02d%c%02d:%02d%c%c%s", ++#endif /* WAREAGLEICON */ + t->tm_mday, + t->tm_mon + 1, + t->tm_year % 100, +@@ -863,6 +1332,80 @@ + New, + Delimiter, + s)); ++#ifdef USE_LIEMIEXT ++ } ++ else { ++ cString RecLength("---"); ++ if (Setup.ShowRecLength && FileName()) { ++ int length = cIndexFile::Length(FileName(), IsPesRecording()); ++ if (length >= 0) ++ RecLength = cString::sprintf("%d'", length / SecondsToFrames(60, framesPerSecond)); ++ } ++#endif /* LIEMIEXT */ ++#ifdef USE_DVDARCHIVE ++#ifdef USE_WAREAGLEICON ++ if (isArchived && !isOnlyOnDvd) New = Setup.WarEagleIcons ? IsLangUtf8() ? ICON_DVD_UTF8 : ICON_DVD : "~"; ++#else ++ if (isArchived && !isOnlyOnDvd) New = '~'; ++#endif /* WAREAGLEICON */ ++ ++ if (isOnlyOnDvd && Setup.DvdDisplayMode >= 1) { ++ char oldLength[21]; ++ ++ if (strrchr(RecLength, '\'')) ++ sprintf(oldLength,"%s", *RecLength); ++ else ++ oldLength[0] = 0; ++ ++ if (dvdname) { ++ if (atoi(dvdname) != 0) { ++ cString tmp; ++ if (Setup.DvdDisplayZeros) ++ tmp = cString::sprintf("%04d", atoi(dvdname)); ++ else { ++ int num = atoi(dvdname); ++ bool displaySpace = !(Setup.DvdDisplayMode == 1 && oldLength[0] != 0); ++ // ugly hack to have 2 spaces instead of one 0 for each place ++ tmp = cString::sprintf("%s%s%s%d", displaySpace && (num < 1000) ? " " : "", ++ displaySpace && (num < 100) ? " " : "", ++ displaySpace && (num < 10) ? " " : "", ++ num); ++ } ++ ((cRecording*)this)->dvdname = strdup(tmp); ++ } ++ } ++ ++ RecLength = strdup(cString::sprintf("%s%s%s%s", (Setup.ShowRecLength && (Setup.DvdDisplayMode == 1) && (oldLength[0] != 0)) ? oldLength : "", ++ (dvdname && isArchived && isOnlyOnDvd && Setup.ShowRecLength && (Setup.DvdDisplayMode == 1) && (oldLength[0] != 0)) ? " / " : "", ++ (dvdname && isArchived && isOnlyOnDvd) ? dvdname : "", ++ (dvdname && isArchived && isOnlyOnDvd) ? " " : "" ++ )); ++ } ++#endif /* DVDARCHIVE */ ++#ifdef USE_LIEMIEXT ++ cString RecDate = cString::sprintf("%02d.%02d.%02d", t->tm_mday, t->tm_mon + 1, t->tm_year % 100); ++ cString RecTime = cString::sprintf("%02d:%02d", t->tm_hour, t->tm_min); ++ cString RecDelimiter = cString::sprintf("%c", Delimiter); ++#ifdef USE_WAREAGLEICON ++ titleBuffer = strdup(cString::sprintf("%s%s%s%s%s%s%s%s", ++#else ++ titleBuffer = strdup(cString::sprintf("%s%s%s%c%s%s%s%s", ++#endif /* WAREAGLEICON */ ++ (Setup.ShowRecDate ? *RecDate : ""), ++ (Setup.ShowRecDate && Setup.ShowRecTime ? *RecDelimiter : ""), ++ (Setup.ShowRecTime ? *RecTime : ""), ++ New, ++ (Setup.ShowRecTime || Setup.ShowRecDate ? *RecDelimiter : ""), ++#ifdef USE_DVDARCHIVE ++ (((Setup.ShowRecLength + Setup.DvdDisplayMode) > 0) ? *RecLength : ""), ++ (((Setup.ShowRecLength + Setup.DvdDisplayMode) > 0) ? *RecDelimiter : ""), ++#else ++ (Setup.ShowRecLength ? *RecLength : ""), ++ (Setup.ShowRecLength ? *RecDelimiter : ""), ++#endif /* DVDARCHIVE */ ++ s)); ++ } ++#endif /* LIEMIEXT */ + // let's not display a trailing '~': + if (!NewIndicator) + stripspace(titleBuffer); +@@ -891,6 +1434,17 @@ + return titleBuffer; + } + ++#ifdef USE_CUTTIME ++void cRecording::SetStartTime(time_t Start) ++{ ++ start = Start; ++ if (fileName) { ++ free(fileName); ++ fileName = NULL; ++ } ++} ++#endif /* CUTTIME */ ++ + const char *cRecording::PrefixFileName(char Prefix) + { + cString p = PrefixVideoFileName(FileName(), Prefix); +@@ -999,6 +1553,51 @@ + resume = RESUME_NOT_INITIALIZED; + } + ++#ifdef USE_LIEMIEXT ++bool cRecording::Rename(const char *newName) ++{ ++ bool result = false; ++ struct tm tm_r; ++ struct tm *t = localtime_r(&start, &tm_r); ++ char *localNewName = ExchangeChars(strdup(newName), true); ++ const char *fmt = isPesRecording ? NAMEFORMATPES : NAMEFORMATTS; ++ int ch = isPesRecording ? priority : channel; ++ int ri = isPesRecording ? lifetime : instanceId; ++ char *newFileName = strdup(cString::sprintf(fmt, VideoDirectory, localNewName, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, ch, ri)); ++ free(localNewName); ++ if (strcmp(FileName(), newFileName)) { ++ if (access(newFileName, F_OK) == 0) { ++ isyslog("recording %s already exists", newFileName); ++ } ++ else { ++ isyslog("renaming recording %s to %s", FileName(), newFileName); ++ result = MakeDirs(newFileName, true); ++ if (result) ++ result = RenameVideoFile(FileName(), newFileName); ++ if (result) { ++ free(fileName); ++ fileName = strdup(newFileName); ++ free(name); ++ name = strdup(newName); ++#ifdef USE_SORTRECORDS ++ for (int i = 0; i < MAXSORTMODES; i++) { ++ free(sortBuffer[i]); ++ sortBuffer[i] = NULL; ++ } ++#else ++ free(sortBuffer); ++ sortBuffer = NULL; ++#endif /* SORTRECORDS */ ++ free(titleBuffer); ++ titleBuffer = NULL; ++ } ++ } ++ } ++ free(newFileName); ++ return result; ++} ++#endif /* LIEMIEXT */ ++ + // --- cRecordings ----------------------------------------------------------- + + cRecordings Recordings; +@@ -1011,6 +1610,9 @@ + deleted = Deleted; + lastUpdate = 0; + state = 0; ++#ifdef USE_SORTRECORDS ++ SortOrder = 1; ++#endif /* SORTRECORDS */ + } + + cRecordings::~cRecordings() +@@ -1297,14 +1899,72 @@ + return NULL; + } + ++#ifdef USE_JUMPPLAY ++// --- cMarksReload ---------------------------------------------------------- ++ ++#define MARKS_RELOAD_MS 10000 ++ ++time_t cMarksReload::lastsavetime = 0; ++ ++cMarksReload::cMarksReload(const char *RecordingFileName) ++:recDir(RecordingFileName) ++{ ++ struct stat sbuf; ++ cRecording rec(recDir); ++ if (Load(recDir, rec.FramesPerSecond(), rec.IsPesRecording()) && stat(FileName(), &sbuf) == 0) ++ lastmodtime = sbuf.st_mtime; ++ else ++ lastmodtime = 0; ++ nextreload.Set(MARKS_RELOAD_MS - cTimeMs::Now() % MARKS_RELOAD_MS); ++} ++ ++bool cMarksReload::Reload(void) ++{ ++ // Check the timestamp of marks.vdr in 10 seconds intervals ++ // Independent but synchronized reloading of marks in two threads ++ if ((Setup.ReloadMarks && nextreload.TimedOut()) || lastsavetime > lastmodtime) { ++ nextreload.Set(MARKS_RELOAD_MS - cTimeMs::Now() % MARKS_RELOAD_MS); ++ struct stat sbuf; ++ if (stat(FileName(), &sbuf) == 0 && sbuf.st_mtime != lastmodtime) { ++ lastmodtime = sbuf.st_mtime; ++ cRecording rec(recDir); ++ if (Load(recDir, rec.FramesPerSecond(), rec.IsPesRecording())) ++ return true; ++ } ++ } ++ return false; ++} ++ ++bool cMarksReload::Save(void) ++{ ++ bool ok = cMarks::Save(); ++ struct stat sbuf; ++ if (ok && stat(FileName(), &sbuf) == 0) ++ lastsavetime = lastmodtime = sbuf.st_mtime; ++ return ok; ++} ++#endif /* JUMPPLAY */ ++ + // --- cRecordingUserCommand ------------------------------------------------- + + const char *cRecordingUserCommand::command = NULL; + ++#ifdef USE_DVLRECSCRIPTADDON ++void cRecordingUserCommand::InvokeCommand(const char *State, const char *RecordingFileName, char *chanName) ++#else + void cRecordingUserCommand::InvokeCommand(const char *State, const char *RecordingFileName) ++#endif /* DVLRECSCRIPTADDON */ + { + if (command) { ++#ifdef USE_DVLRECSCRIPTADDON ++ cString cmd; ++ if (chanName != NULL) ++ cmd = cString::sprintf("%s %s \"%s\" \"%s\"", command, State, *strescape(RecordingFileName, "\\\"$"), chanName); ++ else ++ cmd = cString::sprintf("%s %s \"%s\"", command, State, *strescape(RecordingFileName, "\\\"$")); ++#else + cString cmd = cString::sprintf("%s %s \"%s\"", command, State, *strescape(RecordingFileName, "\\\"$")); ++#endif /* DVLRECSCRIPTADDON */ + isyslog("executing '%s'", *cmd); + SystemExec(cmd); + } +@@ -1746,6 +2406,17 @@ + } + } + ++#ifdef USE_LIEMIEXT ++int cIndexFile::Length(const char *FileName, bool IsPesRecording) ++{ ++ struct stat buf; ++ cString fullname = cString::sprintf("%s%s", FileName, IsPesRecording ? INDEXFILESUFFIX ".vdr" : INDEXFILESUFFIX); ++ if (FileName && *fullname && access(fullname, R_OK) == 0 && stat(fullname, &buf) == 0) ++ return buf.st_size ? (buf.st_size - 1) / sizeof(tIndexTs) + 1 : 0; ++ return -1; ++} ++#endif /* LIEMIEXT */ ++ + // --- cFileName ------------------------------------------------------------- + + #define MAXFILESPERRECORDINGPES 255 +@@ -1775,6 +2446,48 @@ + cFileName::~cFileName() + { + Close(); ++#ifdef USE_DVDARCHIVE ++ ++ char fn[BUFSIZ]; ++ strcpy(fn, fileName); ++ ++ char *p; ++ if((p = strrchr(fn, '/'))) { ++ p[0] = 0; ++ } ++ ++ cString cmd = cString::sprintf("find \"%s\" -type l -lname \"%s/*\"", fn, DVD_MOUNT_PATH); ++ ++ bool isOnDvd = false; ++ ++ cReadLine pipe; ++ FILE* file; ++ char* filename; ++ if ((file = popen(cmd, "r")) != (FILE *)NULL) { ++ while ((filename = pipe.Read(file)) != NULL) { ++ isOnDvd = true; ++ unlink(filename); ++ isyslog("DVD-ARCHIVE: Deleting %s", filename); ++ } ++ pclose(file); ++ } ++ ++ if (isOnDvd) { ++ if (Setup.DvdTrayMode==2 || Setup.DvdTrayMode==3) ++ cmd = cString::sprintf("umount %s; eject %s", DVD_DEVICE, DVD_DEVICE); ++ else ++ cmd = cString::sprintf("umount %s", DVD_DEVICE); ++ ++ SystemExec(cmd); ++ ++ if (Setup.DvdSpeedLimit > 0) { ++ cmd = cString::sprintf("speedcontrol -x 999 %s", DVD_DEVICE); ++ SystemExec(cmd); ++ } ++ } ++ ++#endif /* DVDARCHIVE */ ++ + free(fileName); + } + +@@ -1904,6 +2617,22 @@ + return NULL; + } + ++#ifdef USE_HARDLINKCUTTER ++off_t cFileName::MaxFileSize() { ++ const int maxVideoFileSize = isPesRecording ? MAXVIDEOFILESIZEPES : MAXVIDEOFILESIZETS; ++ const int setupMaxVideoFileSize = min(maxVideoFileSize, Setup.MaxVideoFileSize); ++ const int maxFileNumber = isPesRecording ? 255 : 65535; ++ ++ const off_t smallFiles = (maxFileNumber * off_t(maxVideoFileSize) - 1024 * Setup.MaxRecordingSize) ++ / max(maxVideoFileSize - setupMaxVideoFileSize, 1); ++ ++ if (fileNumber <= smallFiles) ++ return MEGABYTE(off_t(setupMaxVideoFileSize)); ++ ++ return MEGABYTE(off_t(maxVideoFileSize)); ++} ++#endif /* HARDLINKCUTTER */ ++ + cUnbufferedFile *cFileName::NextFile(void) + { + return SetOffset(fileNumber + 1); +@@ -1955,3 +2684,113 @@ + LOG_ERROR; + return r; + } ++ ++#ifdef USE_DVLFRIENDLYFNAMES ++char *MakeFriendlyFilename(char **buf) ++{ ++ char *b, *x, *y; ++ ++ if (buf == NULL || *buf == NULL) ++ return(NULL); ++ ++ b = (char *)malloc(strlen(*buf) * 2); ++ x = *buf; ++ y = b; ++ ++ while (*x != 0) { ++ switch (*x) { ++ case 'Ä': ++ *y = 'A'; ++ y++; ++ *y = 'e'; ++ y++; x++; ++ break; ++ ++ case 'ä': ++ *y = 'a'; ++ y++; ++ *y = 'e'; ++ y++; x++; ++ break; ++ ++ case 'Ö': ++ *y = 'O'; ++ y++; ++ *y = 'e'; ++ y++; x++; ++ break; ++ ++ case 'ö': ++ *y = 'o'; ++ y++; ++ *y = 'e'; ++ y++; x++; ++ break; ++ ++ case 'Ü': ++ *y = 'U'; ++ y++; ++ *y = 'e'; ++ y++; x++; ++ break; ++ ++ case 'ü': ++ *y = 'u'; ++ y++; ++ *y = 'e'; ++ y++; x++; ++ break; ++ ++ case 'ß': ++ *y = 's'; ++ y++; ++ *y = 's'; ++ y++; x++; ++ break; ++ ++ // chars to replace ++ case ':': ++ case ';': ++ case '?': ++ case ' ': ++ case '\t': ++ *y = '_'; ++ y++; x++; ++ break; ++ ++ // chars to simply strip ++ case '\"': ++ case '*': ++ case '{': ++ case '}': ++ case '[': ++ case ']': ++ case '=': ++ case '<': ++ case '>': ++ case '#': ++ case '`': ++ case '|': ++ case '\\': ++ case '\n': ++ case '\r': ++ x++; ++ break; ++ ++ default: ++ *y = *x; ++ y++; x++; ++ break; ++ } ++ } ++ *y = 0; ++ ++ x = strdup(b); ++ free(b); ++ ++ free(*buf); ++ *buf = x; ++ ++ return(*buf); ++} ++#endif /* DVLFRIENDLYFNAMES */ +diff -NaurwB vdr-1.7.10/recording.h vdr-1.7.10-patched/recording.h +--- vdr-1.7.10/recording.h 2009-11-21 17:12:55.000000000 +0100 ++++ vdr-1.7.10-patched/recording.h 2009-12-18 06:25:25.000000000 +0100 +@@ -20,6 +20,9 @@ + + extern bool VfatFileSystem; + extern int InstanceId; ++#ifdef USE_LIEMIEXT ++extern bool DirOrderState; ++#endif /* LIEMIEXT */ + + void RemoveDeletedRecordings(void); + void AssertFreeDiskSpace(int Priority = 0, bool Force = false); +@@ -63,6 +66,9 @@ + const cEvent *GetEvent(void) const { return event; } + const char *Title(void) const { return event->Title(); } + const char *ShortText(void) const { return event->ShortText(); } ++#ifdef USE_GRAPHTFT ++ tEventID EventID(void) const { return event->EventID(); } ++#endif /* GRAPHTFT */ + const char *Description(void) const { return event->Description(); } + const cComponents *Components(void) const { return event->Components(); } + const char *Aux(void) const { return aux; } +@@ -74,12 +80,36 @@ + bool Write(void) const; + }; + ++#ifdef USE_SORTRECORDS ++#define SORTRECORDINGSVERSNUM 3 ++#define MAXSORTMODES 4 ++#endif /* SORTRECORDS */ ++ ++#ifdef USE_DVDARCHIVE ++#define MOUNT_DVD_ABORT 0 ++#define MOUNT_DVD_REPLAY 1 ++#define MOUNT_DVD_LAUNCH_DVD_PLUGIN 2 ++#define DVD_DEVICE "/dev/cdrom" ++#define DVD_MOUNT_PATH "/tmp/vdr.dvd" ++ ++#define DVD_TYPE_UNKNOWN -1 ++#define DVD_TYPE_NOT_READ 0 ++#define DVD_VIDEO_TYPE 1 ++#define DVD_ARCHIVE_TYPE 2 ++#define DVD_VIDEO_ARCHIVE_TYPE 3 ++#endif /* DVDARCHIVE */ ++ + class cRecording : public cListObject { + friend class cRecordings; + private: + mutable int resume; + mutable char *titleBuffer; ++#ifdef USE_SORTRECORDS ++ mutable char *sortBuffer[MAXSORTMODES]; ++ mutable char lastDirsFirst[MAXSORTMODES]; ++#else + mutable char *sortBuffer; ++#endif /* SORTRECORDS */ + mutable char *fileName; + mutable char *name; + mutable int fileSizeMB; +@@ -88,6 +118,15 @@ + bool isPesRecording; + double framesPerSecond; + cRecordingInfo *info; ++#ifdef USE_DVDARCHIVE ++ char *dvdname; ++ char *dvdtrack; ++ char *dvdchapters; ++ bool isArchived; ++ bool isOnlyOnDvd; ++ int dvdtype; ++ bool GetDvdChaptersFromDvd(int title) const; ++#endif /* DVDARCHIVE */ + cRecording(const cRecording&); // can't copy cRecording + cRecording &operator=(const cRecording &); // can't assign cRecording + static char *StripEpisodeName(char *s); +@@ -104,8 +143,23 @@ + virtual int Compare(const cListObject &ListObject) const; + const char *Name(void) const { return name; } + const char *FileName(void) const; ++#ifdef USE_DVDARCHIVE ++ bool CheckFileExistence(const char* FileNameToTest, const bool useVideoDir = true) const; ++ bool GetDvdName(const char* Directory) const; ++ bool IsOnlyOnDvd(void) const { return isOnlyOnDvd; } ++ int MountDvd(void) const; ++ int GetDvdType(void) const { return dvdtype; } ++ const char *GetDvdChapters(void) const ; ++#endif /* DVDARCHIVE */ ++#ifdef USE_LIEMIEXT ++ const char *Title(char Delimiter = ' ', bool NewIndicator = false, int Level = -1, bool Original = true) const; ++#else + const char *Title(char Delimiter = ' ', bool NewIndicator = false, int Level = -1) const; ++#endif /* LIEMIEXT */ + const cRecordingInfo *Info(void) const { return info; } ++#ifdef USE_CUTTIME ++ void SetStartTime(time_t Start); ++#endif /* CUTTIME */ + const char *PrefixFileName(char Prefix); + int HierarchyLevels(void) const; + void ResetResume(void) const; +@@ -124,6 +178,11 @@ + // Changes the file name so that it will be visible in the "Recordings" menu again and + // not processed by cRemoveDeletedRecordingsThread. + // Returns false in case of error ++#ifdef USE_LIEMIEXT ++ bool Rename(const char *newName); ++ // Changes the file name ++ // Returns false in case of error ++#endif /* LIEMIEXT */ + }; + + class cRecordings : public cList, public cThread { +@@ -132,6 +191,9 @@ + bool deleted; + time_t lastUpdate; + int state; ++#ifdef USE_SORTRECORDS ++ int SortOrder; ++#endif /* SORTRECORDS */ + const char *UpdateFileName(void); + void Refresh(bool Foreground = false); + void ScanVideoDir(const char *DirName, bool Foreground = false, int LinkLevel = 0); +@@ -162,6 +224,10 @@ + void AddByName(const char *FileName, bool TriggerUpdate = true); + void DelByName(const char *FileName); + int TotalFileSizeMB(void); ///< Only for deleted recordings! ++#ifdef USE_SORTRECORDS ++ void ToggleSortOrder(void) { SortOrder *= -1; } ++ const int GetSortOrder(void) { return SortOrder; } ++#endif /* SORTRECORDS */ + }; + + extern cRecordings Recordings; +@@ -194,6 +260,20 @@ + cMark *GetNext(int Position); + }; + ++#ifdef USE_JUMPPLAY ++class cMarksReload : public cMarks { ++private: ++ cString recDir; ++ cTimeMs nextreload; ++ time_t lastmodtime; ++ static time_t lastsavetime; ++public: ++ cMarksReload(const char *RecordingFileName); ++ bool Reload(void); ++ bool Save(void); ++ }; ++#endif /* JUMPPLAY */ ++ + #define RUC_BEFORERECORDING "before" + #define RUC_AFTERRECORDING "after" + #define RUC_EDITEDRECORDING "edited" +@@ -202,8 +282,15 @@ + private: + static const char *command; + public: ++#ifdef USE_DELTIMESHIFTREC ++ static const char *GetCommand(void) { return command; } ++#endif /* DELTIMESHIFTREC */ + static void SetCommand(const char *Command) { command = Command; } ++#ifdef USE_DVLRECSCRIPTADDON ++ static void InvokeCommand(const char *State, const char *RecordingFileName, char *chanName = NULL); ++#else + static void InvokeCommand(const char *State, const char *RecordingFileName); ++#endif /* DVLRECSCRIPTADDON */ + }; + + // The maximum size of a single frame (up to HDTV 1920x1080): +@@ -216,9 +303,23 @@ + // before the next independent frame, to have a complete Group Of Pictures): + #define MAXVIDEOFILESIZETS 1048570 // MB + #define MAXVIDEOFILESIZEPES 2000 // MB ++#ifdef USE_HARDLINKCUTTER ++#define MINVIDEOFILESIZE 1 // MB ++#else + #define MINVIDEOFILESIZE 100 // MB ++#endif /* HARDLINKCUTTER */ + #define MAXVIDEOFILESIZEDEFAULT MAXVIDEOFILESIZEPES + ++#ifdef USE_HARDLINKCUTTER ++#define MINRECORDINGSIZE 25 // GB ++#define MAXRECORDINGSIZE 500 // GB ++#define DEFAULTRECORDINGSIZE 100 // GB ++// Dynamic recording size: ++// Keep recording file size at Setup.MaxVideoFileSize for as long as possible, ++// but switch to MAXVIDEOFILESIZE early enough, so that Setup.MaxRecordingSize ++// will be reached, before recording to file 255.vdr ++#endif /* HARDLINKCUTTER */ ++ + struct tIndexTs; + class cIndexFileGenerator; + +@@ -235,6 +336,9 @@ + void ConvertFromPes(tIndexTs *IndexTs, int Count); + void ConvertToPes(tIndexTs *IndexTs, int Count); + bool CatchUp(int Index = -1); ++#ifdef USE_DVDARCHIVE ++ bool isOnDVD; ++#endif /* DVDARCHIVE */ + public: + cIndexFile(const char *FileName, bool Record, bool IsPesRecording = false); + ~cIndexFile(); +@@ -248,6 +352,10 @@ + bool StoreResume(int Index) { return resumeFile.Save(Index); } + bool IsStillRecording(void); + void Delete(void); ++ #ifdef USE_LIEMIEXT ++ static int Length(const char *FileName, bool IsPesRecording = false); ++ ///< Calculates the recording length without reading the index. ++#endif /* LIEMIEXT */ + }; + + class cFileName { +@@ -267,6 +375,9 @@ + cUnbufferedFile *Open(void); + void Close(void); + cUnbufferedFile *SetOffset(int Number, off_t Offset = 0); ++#ifdef USE_HARDLINKCUTTER ++ off_t MaxFileSize(); // Dynamic file size for this file ++#endif /* HARDLINKCUTTER */ + cUnbufferedFile *NextFile(void); + }; + +diff -NaurwB vdr-1.7.10/remux.c vdr-1.7.10-patched/remux.c +--- vdr-1.7.10/remux.c 2009-11-22 12:23:27.000000000 +0100 ++++ vdr-1.7.10-patched/remux.c 2009-12-18 06:25:25.000000000 +0100 +@@ -215,6 +215,32 @@ + return i; + } + ++#ifdef USE_TTXTSUBS ++int cPatPmtGenerator::MakeTeletextDescriptor(uchar *Target, cChannel *Channel) ++{ ++ int i = 0, j = 0; ++ Target[i++] = SI::TeletextDescriptorTag; ++ int l = i; ++ Target[i++] = 0x00; // length ++ for (int n = 0; Channel->TPages(n); n++) { ++ const char *Language = Channel->Tlang(n); ++ int Pages = Channel->TPages(n); ++ Target[i++] = *Language++; ++ Target[i++] = *Language++; ++ Target[i++] = *Language++; ++ Target[i++] = ((Pages >> 13) & 0xf8) | ((Pages >> 8) & 0x7); // teletext type & magazine number ++ Target[i++] = Pages & 0xff; // teletext page number ++ j++; ++ } ++ if (j > 0) { ++ Target[l] = j * 5; // update length ++ IncEsInfoLength(i); ++ return i; ++ } ++ return 0; ++} ++#endif /* TTXTSUBS */ ++ + int cPatPmtGenerator::MakeLanguageDescriptor(uchar *Target, const char *Language) + { + int i = 0; +@@ -295,6 +321,9 @@ + numPmtPackets = 0; + if (Channel) { + int Vpid = Channel->Vpid(); ++#ifdef USE_TTXTSUBS ++ int Tpid = Channel->Tpid(); ++#endif /* TTXTSUBS */ + int Ppid = Channel->Ppid(); + uchar *p = buf; + int i = 0; +@@ -330,6 +359,12 @@ + i += MakeStream(buf + i, 0x06, Channel->Spid(n)); + i += MakeSubtitlingDescriptor(buf + i, Channel->Slang(n), Channel->SubtitlingType(n), Channel->CompositionPageId(n), Channel->AncillaryPageId(n)); + } ++#ifdef USE_TTXTSUBS ++ if (Tpid) { ++ i += MakeStream(buf + i, 0x06, Tpid); ++ i += MakeTeletextDescriptor(buf + i, Channel); ++ } ++#endif /* TTXTSUBS */ + + int sl = i - SectionLength - 2 + 4; // -2 = SectionLength storage, +4 = length of CRC + buf[SectionLength] |= (sl >> 8) & 0x0F; +@@ -402,6 +437,9 @@ + patVersion = pmtVersion = -1; + pmtPid = -1; + vpid = vtype = 0; ++#ifdef USE_TTXTSUBS ++ tpid = 0; ++#endif /* TTXTSUBS */ + } + + void cPatPmtParser::ParsePat(const uchar *Data, int Length) +@@ -486,6 +524,9 @@ + int NumDpids = 0; + int NumSpids = 0; + vpid = vtype = 0; ++#ifdef USE_TTXTSUBS ++ tpid = 0; ++#endif /* TTXTSUBS */ + SI::PMT::Stream stream; + for (SI::Loop::Iterator it; Pmt.streamLoop.getNext(stream, it); ) { + dbgpatpmt(" stream type = %02X, pid = %d", stream.getStreamType(), stream.getPid()); +@@ -566,6 +607,12 @@ + NumSpids++; + } + break; ++#ifdef USE_TTXTSUBS ++ case SI::TeletextDescriptorTag: ++ dbgpatpmt(" teletext"); ++ tpid = stream.getPid(); ++ break; ++#endif /* TTXTSUBS */ + case SI::ISO639LanguageDescriptorTag: { + SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; + dbgpatpmt(" '%s'", ld->languageCode); +diff -NaurwB vdr-1.7.10/remux.h vdr-1.7.10-patched/remux.h +--- vdr-1.7.10/remux.h 2009-11-21 16:55:34.000000000 +0100 ++++ vdr-1.7.10-patched/remux.h 2009-12-18 06:25:25.000000000 +0100 +@@ -170,6 +170,9 @@ + int MakeStream(uchar *Target, uchar Type, int Pid); + int MakeAC3Descriptor(uchar *Target); + int MakeSubtitlingDescriptor(uchar *Target, const char *Language, uchar SubtitlingType, uint16_t CompositionPageId, uint16_t AncillaryPageId); ++#ifdef USE_TTXTSUBS ++ int MakeTeletextDescriptor(uchar *Target, cChannel *Channel); ++#endif /* TTXTSUBS */ + int MakeLanguageDescriptor(uchar *Target, const char *Language); + int MakeCRC(uchar *Target, const uchar *Data, int Length); + void GeneratePmtPid(cChannel *Channel); +@@ -215,6 +218,9 @@ + int vpid; + int vtype; + bool updatePrimaryDevice; ++#ifdef USE_TTXTSUBS ++ int tpid; ++#endif /* TTXTSUBS */ + protected: + int SectionLength(const uchar *Data, int Length) { return (Length >= 3) ? ((int(Data[1]) & 0x0F) << 8)| Data[2] : 0; } + public: +diff -NaurwB vdr-1.7.10/skinclassic.c vdr-1.7.10-patched/skinclassic.c +--- vdr-1.7.10/skinclassic.c 2008-02-23 11:31:58.000000000 +0100 ++++ vdr-1.7.10-patched/skinclassic.c 2009-12-18 06:25:25.000000000 +0100 +@@ -314,8 +314,52 @@ + for (int i = 0; i < MaxTabs; i++) { + const char *s = GetTabbedText(Text, i); + if (s) { ++#ifdef USE_LIEMIEXT ++ bool isprogressbar = false; ++ int now = 0, total = 0; ++ // check if progress bar: "[||||||| ]" ++ if ((strlen(s) > 5 && s[0] == '[' && s[strlen(s) - 1] == ']')) { ++ const char *p = s + 1; ++ // update status ++ isprogressbar = true; ++ for (; *p != ']'; ++p) { ++ // check if progressbar characters ++ if (*p == ' ' || *p == '|') { ++ // update counters ++ ++total; ++ if (*p == '|') ++ ++now; ++ } ++ else { ++ // wrong character detected; not a progressbar ++ isprogressbar = false; ++ break; ++ } ++ } ++ } ++ int xt = x0 + Tab(i); ++ if (Setup.ShowProgressBar && isprogressbar) { ++ // define x coordinates of progressbar ++ int px0 = xt; ++ int px1 = (Tab(i + 1)?Tab(i+1):x1) - 5; ++ int px = px0 + max((int)((float) now * (float) (px1 - px0) / (float) total), 1); ++ // define y coordinates of progressbar ++ int py0 = y + 4; ++ int py1 = y + lineHeight - 4; ++ // draw background ++ osd->DrawRectangle(px0, y, (Tab(i + 1)?Tab(i+1):x1) - 1, y + lineHeight - 1, ColorBg); ++ // draw progressbar ++ osd->DrawRectangle(px0, py0, px, py1, ColorFg); ++ osd->DrawRectangle(px + 1, py0, px1, py0 + 1, ColorFg); ++ osd->DrawRectangle(px + 1, py1 - 1, px1, py1, ColorFg); ++ osd->DrawRectangle(px1 - 1, py0, px1, py1, ColorFg); ++ } ++ else ++ osd->DrawText(xt, y, s, ColorFg, ColorBg, font, x2 - xt); ++#else + int xt = x0 + Tab(i); + osd->DrawText(xt, y, s, ColorFg, ColorBg, font, x2 - xt); ++#endif /* LIEMIEXT */ + } + if (!Tab(i + 1)) + break; +diff -NaurwB vdr-1.7.10/skins.c vdr-1.7.10-patched/skins.c +--- vdr-1.7.10/skins.c 2009-06-06 17:12:31.000000000 +0200 ++++ vdr-1.7.10-patched/skins.c 2009-12-18 06:37:57.000000000 +0100 +@@ -238,7 +238,11 @@ + } + cSkinDisplay::Current()->SetMessage(Type, s); + cSkinDisplay::Current()->Flush(); ++#ifdef USE_STATUS_EXTENSION ++ cStatus::MsgOsdStatusMessage(Type, s); ++#else + cStatus::MsgOsdStatusMessage(s); ++#endif + eKeys k = kNone; + if (Type != mtStatus) { + k = Interface->Wait(Seconds); +@@ -249,7 +253,11 @@ + } + else { + cSkinDisplay::Current()->SetMessage(Type, NULL); ++#ifdef USE_STATUS_EXTENSION ++ cStatus::MsgOsdStatusMessage(Type, NULL); ++#else + cStatus::MsgOsdStatusMessage(NULL); ++#endif + } + } + else if (!s && displayMessage) { +diff -NaurwB vdr-1.7.10/skinsttng.c vdr-1.7.10-patched/skinsttng.c +--- vdr-1.7.10/skinsttng.c 2008-02-23 11:23:44.000000000 +0100 ++++ vdr-1.7.10-patched/skinsttng.c 2009-12-18 06:25:25.000000000 +0100 +@@ -558,8 +558,52 @@ + for (int i = 0; i < MaxTabs; i++) { + const char *s = GetTabbedText(Text, i); + if (s) { ++#ifdef USE_LIEMIEXT ++ bool isprogressbar = false; ++ int now = 0, total = 0; ++ // check if progress bar: "[||||||| ]" ++ if ((strlen(s) > 5 && s[0] == '[' && s[strlen(s) - 1] == ']')) { ++ const char *p = s + 1; ++ // update status ++ isprogressbar = true; ++ for (; *p != ']'; ++p) { ++ // check if progressbar characters ++ if (*p == ' ' || *p == '|') { ++ // update counters ++ ++total; ++ if (*p == '|') ++ ++now; ++ } ++ else { ++ // wrong character detected; not a progressbar ++ isprogressbar = false; ++ break; ++ } ++ } ++ } + int xt = x3 + 5 + Tab(i); ++ if (Setup.ShowProgressBar && isprogressbar) { ++ // define x coordinates of progressbar ++ int px0 = xt; ++ int px1 = x3 + (Tab(i + 1)?Tab(i + 1):x4-x3-5) - 1; ++ int px = px0 + max((int)((float) now * (float) (px1 - px0) / (float) total), 1); ++ // define y coordinates of progressbar ++ int py0 = y + 4; ++ int py1 = y + lineHeight - 4; ++ // draw background ++ osd->DrawRectangle(px0, y, (Tab(i + 1)?Tab(i + 1):x4-x3-5) - 1, y + lineHeight - 1, ColorBg); ++ // draw progressbar ++ osd->DrawRectangle(px0, py0, px, py1, ColorFg); ++ osd->DrawRectangle(px + 1, py0, px1, py0 + 1, ColorFg); ++ osd->DrawRectangle(px + 1, py1 - 1, px1, py1, ColorFg); ++ osd->DrawRectangle(px1 - 1, py0, px1, py1, ColorFg); ++ } ++ else + osd->DrawText(xt, y, s, ColorFg, ColorBg, font, x4 - xt); ++#else ++ int xt = x3 + 5 + Tab(i); ++ osd->DrawText(xt, y, s, ColorFg, ColorBg, font, x4 - xt); ++#endif /* LIEMIEXT */ + } + if (!Tab(i + 1)) + break; +@@ -602,6 +646,21 @@ + y += ts.Height(); + } + y += font->Height(); ++#ifdef USE_PARENTALRATING ++ if (!isempty(Event->GetParentalRatingString())) { ++ const cFont *font = cFont::GetFont(fontSml); ++ ts.Set(osd, xl, y, x4 - xl, y4 - y, Event->GetParentalRatingString(), font, Theme.Color(clrMenuEventShortText), Theme.Color(clrBackground)); ++ y += ts.Height(); ++ } ++ int i = 0; ++ while (Event->Contents(i++)) { ++ if (!isempty(Event->GetContentsString())) { ++ const cFont *font = cFont::GetFont(fontSml); ++ ts.Set(osd, xl, y, x4 - xl, y4 - y, Event->GetContentsString(), font, Theme.Color(clrMenuEventShortText), Theme.Color(clrBackground)); ++ y += ts.Height(); ++ } ++ } ++#endif /* PARENTALRATING */ + if (!isempty(Event->Description())) { + int yt = y; + int yb = y4 - Roundness; +diff -NaurwB vdr-1.7.10/sources.c vdr-1.7.10-patched/sources.c +--- vdr-1.7.10/sources.c 2008-02-10 15:07:26.000000000 +0100 ++++ vdr-1.7.10-patched/sources.c 2009-12-18 06:25:25.000000000 +0100 +@@ -37,6 +37,9 @@ + char buffer[16]; + char *q = buffer; + switch (Code & st_Mask) { ++#ifdef USE_PLUGINPARAM ++ case stPlug: *q++ = 'P'; break; ++#endif /* PLUGINPARAM */ + case stCable: *q++ = 'C'; break; + case stSat: *q++ = 'S'; + { +@@ -56,6 +59,9 @@ + { + int type = stNone; + switch (toupper(*s)) { ++#ifdef USE_PLUGINPARAM ++ case 'P': type = stPlug; break; ++#endif /* PLUGINPARAM */ + case 'C': type = stCable; break; + case 'S': type = stSat; break; + case 'T': type = stTerr; break; +@@ -68,7 +74,11 @@ + int pos = 0; + bool dot = false; + bool neg = false; ++#ifdef USE_SOURCECAPS ++ while (*++s && !isblank(*s)) { ++#else + while (*++s) { ++#endif /* SOURCECAPS */ + switch (toupper(*s)) { + case '0' ... '9': pos *= 10; + pos += *s - '0'; +diff -NaurwB vdr-1.7.10/sources.conf vdr-1.7.10-patched/sources.conf +--- vdr-1.7.10/sources.conf 2009-10-18 16:08:53.000000000 +0200 ++++ vdr-1.7.10-patched/sources.conf 2009-12-18 06:25:25.000000000 +0100 +@@ -194,3 +194,7 @@ + # Terrestrial + + T Terrestrial ++ ++# Plugin PLUGINPARAM ++ ++#P Plugin +diff -NaurwB vdr-1.7.10/sources.h vdr-1.7.10-patched/sources.h +--- vdr-1.7.10/sources.h 2005-05-14 11:30:41.000000000 +0200 ++++ vdr-1.7.10-patched/sources.h 2009-12-18 06:25:25.000000000 +0100 +@@ -16,10 +16,17 @@ + public: + enum eSourceType { + stNone = 0x0000, ++#ifdef USE_PLUGINPARAM ++ stPlug = 0x2000, ++#endif /* PLUGINPARAM */ + stCable = 0x4000, + stSat = 0x8000, + stTerr = 0xC000, ++#ifdef USE_PLUGINPARAM ++ st_Mask = 0xE000, ++#else + st_Mask = 0xC000, ++#endif /* PLUGINPARAM */ + st_Neg = 0x0800, + st_Pos = 0x07FF, + }; +@@ -35,6 +42,9 @@ + static cString ToString(int Code); + static int FromString(const char *s); + static int FromData(eSourceType SourceType, int Position = 0, bool East = false); ++#ifdef USE_PLUGINPARAM ++ static bool IsPlug(int Code) { return (Code & st_Mask) == stPlug; } ++#endif /* PLUGINPARAM */ + static bool IsCable(int Code) { return (Code & st_Mask) == stCable; } + static bool IsSat(int Code) { return (Code & st_Mask) == stSat; } + static bool IsTerr(int Code) { return (Code & st_Mask) == stTerr; } +diff -NaurwB vdr-1.7.10/status.c vdr-1.7.10-patched/status.c +--- vdr-1.7.10/status.c 2008-02-16 15:46:31.000000000 +0100 ++++ vdr-1.7.10-patched/status.c 2009-12-18 06:37:57.000000000 +0100 +@@ -83,11 +83,22 @@ + sm->OsdTitle(Title); + } + ++#ifdef USE_STATUS_EXTENSION ++void cStatus::MsgOsdStatusMessage(eMessageType type, const char *Message) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ { ++ sm->OsdStatusMessage(type, Message); ++ sm->OsdStatusMessage(Message); // For comaptibilty ++ } ++} ++#else + void cStatus::MsgOsdStatusMessage(const char *Message) + { + for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) + sm->OsdStatusMessage(Message); + } ++#endif + + void cStatus::MsgOsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue) + { +@@ -124,3 +135,88 @@ + for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) + sm->OsdProgramme(PresentTime, PresentTitle, PresentSubtitle, FollowingTime, FollowingTitle, FollowingSubtitle); + } ++ ++#ifdef USE_PINPLUGIN ++bool cStatus::MsgChannelProtected(const cDevice* Device, const cChannel* Channel) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ if (sm->ChannelProtected(Device, Channel) == true) ++ return true; ++ return false; ++} ++ ++bool cStatus::MsgReplayProtected(const cRecording* Recording, const char* Name, const char* Base, bool isDirectory, int menuView) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ if (sm->ReplayProtected(Recording, Name, Base, isDirectory, menuView) == true) ++ return true; ++ return false; ++} ++ ++void cStatus::MsgRecordingFile(const char* FileName) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ sm->RecordingFile(FileName); ++} ++ ++void cStatus::MsgTimerCreation(cTimer* Timer, const cEvent *Event) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ sm->TimerCreation(Timer, Event); ++} ++ ++bool cStatus::MsgPluginProtected(cPlugin* Plugin, int menuView) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ if (sm->PluginProtected(Plugin, menuView) == true) ++ return true; ++ return false; ++} ++ ++void cStatus::MsgUserAction(const eKeys key, const cOsdObject* Interact) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ sm->UserAction(key, Interact); ++} ++ ++bool cStatus::MsgMenuItemProtected(const char* Name, int menuView) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ if (sm->MenuItemProtected(Name, menuView) == true) ++ return true; ++ return false; ++} ++#endif /* PINPLUGIN */ ++ ++#ifdef USE_GRAPHTFT ++void cStatus::MsgOsdSetEvent(const cEvent* event) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ sm->OsdSetEvent(event); ++} ++ ++void cStatus::MsgOsdSetRecording(const cRecording* recording) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ sm->OsdSetRecording(recording); ++} ++ ++void cStatus::MsgOsdMenuDisplay(const char* kind) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ sm->OsdMenuDisplay(kind); ++} ++ ++void cStatus::MsgOsdMenuDestroy() ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ sm->OsdMenuDestroy(); ++} ++ ++void cStatus::MsgOsdEventItem(const cEvent* Event, const char *Text, int Index, int Count) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ sm->OsdEventItem(Event, Text, Index, Count); ++} ++#endif /* GRAPHTFT */ ++ +diff -NaurwB vdr-1.7.10/status.h vdr-1.7.10-patched/status.h +--- vdr-1.7.10/status.h 2008-02-16 16:00:33.000000000 +0100 ++++ vdr-1.7.10-patched/status.h 2009-12-18 06:37:57.000000000 +0100 +@@ -14,6 +14,13 @@ + #include "device.h" + #include "player.h" + #include "tools.h" ++#ifdef USE_PINPLUGIN ++#include "plugin.h" ++#endif /* PINPLUGIN */ ++#ifdef USE_STATUS_EXTENSION ++#include "skins.h" ++#endif /* STATUS_EXTENSION */ ++ + + enum eTimerChange { tcMod, tcAdd, tcDel }; + +@@ -64,6 +71,11 @@ + virtual void OsdStatusMessage(const char *Message) {} + // Message has been displayed in the status line of the menu. + // If Message is NULL, the status line has been cleared. ++#ifdef USE_STATUS_EXTENSION ++ virtual void OsdStatusMessage(eMessageType type, const char *Message) {} ++ // Message has been displayed in the status line of the menu. ++ // If Message is NULL, the status line has been cleared. ++#endif + virtual void OsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue) {} + // The help keys have been set to the given values (may be NULL). + virtual void OsdItem(const char *Text, int Index) {} +@@ -80,6 +92,37 @@ + // The OSD displays the single line Text with the current channel information. + virtual void OsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle) {} + // The OSD displays the given programme information. ++#ifdef USE_PINPLUGIN ++ virtual bool ChannelProtected(const cDevice *Device, const cChannel* Channel) { return false; } ++ // Checks if a channel is protected. ++ virtual bool ReplayProtected(const cRecording* Recording, const char* Name, const char* Base, bool isDirectory, int menuView = false) { return false; } ++ // Checks if a recording is protected. ++ virtual void RecordingFile(const char* FileName) {} ++ // The given DVB device has started recording to FileName. FileName is the name of the ++ // recording directory ++ virtual void TimerCreation(cTimer* Timer, const cEvent *Event) {} ++ // The given timer is created ++ virtual bool PluginProtected(cPlugin* Plugin, int menuView = false) { return false; } ++ // Checks if a plugin is protected. ++ virtual void UserAction(const eKeys key, const cOsdObject* Interact) {} ++ // report user action ++ virtual bool MenuItemProtected(const char* Name, int menuView = false) { return false; } ++ // Checks if a menu entry is protected. ++#endif /* PINPLUGIN */ ++#ifdef USE_GRAPHTFT ++ virtual void OsdSetRecording(const cRecording* recording) {} ++ // The OSD displays the recording information. ++ virtual void OsdSetEvent(const cEvent* event) {} ++ // The OSD displays the event information. ++ virtual void OsdMenuDisplay(const char* kind) {} ++ // report menu creation ++ virtual void OsdMenuDestroy() {} ++ // report menu destruvtion ++ virtual void OsdEventItem(const cEvent* Event, const char *Text, int Index, int Count) {} ++ // The OSD displays the given single line Event as menu item at Index. ++ ++#endif /* GRAPHTFT */ ++ + public: + cStatus(void); + virtual ~cStatus(); +@@ -94,13 +137,33 @@ + static void MsgSetSubtitleTrack(int Index, const char * const *Tracks); + static void MsgOsdClear(void); + static void MsgOsdTitle(const char *Title); ++#ifdef USE_STATUS_EXTENSION ++ static void MsgOsdStatusMessage(eMessageType type, const char *Message); ++#else + static void MsgOsdStatusMessage(const char *Message); ++#endif + static void MsgOsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue); + static void MsgOsdItem(const char *Text, int Index); + static void MsgOsdCurrentItem(const char *Text); + static void MsgOsdTextItem(const char *Text, bool Scroll = false); + static void MsgOsdChannel(const char *Text); + static void MsgOsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle); ++#ifdef USE_PINPLUGIN ++ static bool MsgChannelProtected(const cDevice* Device, const cChannel* Channel); ++ static bool MsgReplayProtected(const cRecording* Recording, const char* Name, const char* Base, bool isDirectory, int menuView = false); ++ static void MsgRecordingFile(const char* FileName); ++ static void MsgTimerCreation(cTimer* Timer, const cEvent *Event); ++ static bool MsgPluginProtected(cPlugin* Plugin, int menuView = false); ++ static void MsgUserAction(const eKeys key, const cOsdObject* Interact); ++ static bool MsgMenuItemProtected(const char* Name, int menuView = false); ++#endif /* PINPLUGIN */ ++#ifdef USE_GRAPHTFT ++ static void MsgOsdSetEvent(const cEvent* event); ++ static void MsgOsdSetRecording(const cRecording* recording); ++ static void MsgOsdMenuDisplay(const char* kind); ++ static void MsgOsdMenuDestroy(); ++ static void MsgOsdEventItem(const cEvent* Event, const char *Text, int Index, int Count); ++#endif /* GRAPHTFT */ + }; + + #endif //__STATUS_H +diff -NaurwB vdr-1.7.10/submenu.c vdr-1.7.10-patched/submenu.c +--- vdr-1.7.10/submenu.c 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/submenu.c 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,949 @@ ++#ifdef USE_SETUP ++/**************************************************************************** ++ * DESCRIPTION: ++ * Submenu ++ * ++ * $Id: vdr-1.3.44-Setup-0.3.0.diff,v 1.1 2006/03/04 09:58:47 ralf Exp $ ++ * ++ * Contact: ranga@teddycats.de ++ * ++ * Copyright (C) 2004, 2005 by Ralf Dotzert ++ * ++ * modified for the VDR Extensions Patch by zulu @vdr-portal ++ ****************************************************************************/ ++ ++#ifndef SUBMENU_H ++#include "submenu.h" ++#include "plugin.h" ++#ifdef USE_WAREAGLEICON ++#include "iconpatch.h" ++#endif /* WAREAGLEICON */ ++ ++static const char* TAG_SYSTEM = "system"; ++static const char* TAG_PLUGIN = "plugin"; ++static const char* TAG_COMMAND = "command"; ++static const char* TAG_THREAD = "thread"; ++static const char* TAG_MENU = "menu"; ++static const char* TAG_UNDEFINED = "undefined"; ++static const char* TRUE_STR = "yes"; ++ ++ ++//################################################################################ ++//# SubMenuNode ++//################################################################################ ++ ++cSubMenuNode::cSubMenuNode(TiXmlElement *xml, int level, cSubMenuNodes *currentMenu, cSubMenuNodes *parentMenu) ++{ ++ init(); ++ _parentMenu = parentMenu; ++ _currentMenu = currentMenu; ++ _level = level; ++ ++ if (xml != NULL && xml->Type() == TiXmlNode::ELEMENT) { ++ const char *tag = xml->Value(); ++ ++ if (cSubMenuNode::IsType(tag) != cSubMenuNode::UNDEFINED) { ++ SetType(tag); ++ SetName(xml->Attribute("name")); ++ if ((_type == COMMAND) || (_type == THREAD)) { ++ SetCommand(xml->Attribute("execute")); ++ const char *confirmStr = xml->Attribute("confirm"); ++ if (confirmStr != NULL && strcmp(confirmStr, TRUE_STR) == 0) ++ _commandConfirm = true; ++ } ++ else if (_type == PLUGIN) { // Add Plugin Index ++ SetCustomTitle(xml->Attribute("title")); ++ SetPlugin(); ++ } ++ else if (_type == MENU && xml->NoChildren() == false) { ++ xml = xml->FirstChildElement(); ++ do { ++ cSubMenuNode *node = new cSubMenuNode(xml, level+1, &_subMenus, currentMenu); ++ _subMenus.Add(node); ++ } while ((xml=xml->NextSiblingElement()) != NULL); ++ } ++ } ++ } ++ else ++ throw "Invalid XML Node"; ++} ++ ++/** ++ * Construct new Node empty Node ++ * ++ * ++ */ ++cSubMenuNode::cSubMenuNode(cSubMenuNodes *currentMenu, cSubMenuNodes *parentMenu) ++{ ++ init(); ++ _parentMenu = parentMenu; ++ _currentMenu = currentMenu; ++ ++} ++ ++ ++/** ++ * ++ */ ++void cSubMenuNode::init() ++{ ++ _name = NULL; ++ _command = NULL; ++ _title = NULL; ++ _pluginMainMenuEntry = NULL; ++ _type = UNDEFINED; ++ _level = 0; ++ _parentMenu = NULL; ++ _currentMenu = NULL; ++ _pluginIndex = 0; ++ _commandConfirm = false; ++} ++ ++ ++cSubMenuNode::~ cSubMenuNode() ++{ ++ if (_name != NULL) ++ free((void*)_name); ++ if (_command != NULL) ++ free((void*)_command); ++ if (_title != NULL) ++ free((void*)_title); ++ if (_pluginMainMenuEntry != NULL) ++ free((void*)_pluginMainMenuEntry); ++} ++ ++/** ++ * ++ */ ++void cSubMenuNode::SetPlugin() ++{ ++ bool found = false; ++ for (int i = 0; ; i++) { ++ cPlugin *p = cPluginManager::GetPlugin(i); ++ if (p) { ++ if (strcmp(_name, p->Name()) == 0 && p->MainMenuEntry() != NULL) { ++ SetPluginMainMenuEntry(p->MainMenuEntry()); ++ _pluginIndex = i; ++ found = true; ++ break; ++ } ++ } ++ else ++ break; ++ } ++ ++ if (!found) ++ _type = UNDEFINED; ++} ++ ++ ++bool cSubMenuNode::SaveXml(TiXmlElement *root) ++{ ++ bool ok = true; ++ ++ if (root!=NULL) { ++ TiXmlElement *e = NULL; ++ switch(_type) { ++ case SYSTEM: ++ e = new TiXmlElement(TAG_SYSTEM); ++ e->SetAttribute("name", GetName()); ++ break; ++ case COMMAND: ++ e = new TiXmlElement(TAG_COMMAND); ++ e->SetAttribute("name", GetName()); ++ e->SetAttribute("execute", GetCommand()); ++ if (_commandConfirm) ++ e->SetAttribute("confirm", TRUE_STR); ++ break; ++ case THREAD: ++ e = new TiXmlElement(TAG_THREAD); ++ e->SetAttribute("name", GetName()); ++ e->SetAttribute("execute", GetCommand()); ++ if (_commandConfirm) ++ e->SetAttribute("confirm", TRUE_STR); ++ break; ++ case PLUGIN: ++ e = new TiXmlElement(TAG_PLUGIN); ++ e->SetAttribute("name", GetName()); ++ if (GetCustomTitle() != NULL && strcmp(GetCustomTitle(), "") != 0) ++ e->SetAttribute("title", GetCustomTitle()); ++ break; ++ case MENU: ++ e = new TiXmlElement(TAG_MENU); ++ e->SetAttribute("name", GetName()); ++ break; ++ case UNDEFINED: ++ default: ++ ok = false; ++ break; ++ } ++ if (ok) { ++ root->LinkEndChild(e); ++ if (HasSubMenus()) ++ for (cSubMenuNode *node = _subMenus.First(); node; node = _subMenus.Next(node)) ++ node->SaveXml(e); ++ } ++ } ++ ++ return(ok); ++} ++ ++ ++cSubMenuNode::Type cSubMenuNode::IsType(const char *name) ++{ ++ Type type = UNDEFINED; ++ ++ if (strcmp(name ,TAG_SYSTEM) == 0) ++ type = cSubMenuNode::SYSTEM; ++ else if (strcmp(name ,TAG_PLUGIN) == 0) ++ type = cSubMenuNode::PLUGIN; ++ else if (strcmp(name ,TAG_COMMAND) == 0) ++ type = cSubMenuNode::COMMAND; ++ else if (strcmp(name ,TAG_THREAD) == 0) ++ type = cSubMenuNode::THREAD; ++ else if (strcmp(name ,TAG_MENU) == 0) ++ type = cSubMenuNode::MENU; ++ ++ return(type); ++} ++ ++void cSubMenuNode::SetType(const char *name) ++{ ++ _type = IsType(name); ++} ++ ++void cSubMenuNode::SetType(enum Type type) ++{ ++ _type = type; ++} ++ ++ ++cSubMenuNode::Type cSubMenuNode::GetType() ++{ ++ return(_type); ++} ++ ++const char *cSubMenuNode::GetTypeAsString() ++{ ++ const char *str=NULL; ++ switch(_type) { ++ case SYSTEM: ++ str = TAG_SYSTEM; ++ break; ++ case COMMAND: ++ str = TAG_COMMAND; ++ break; ++ case THREAD: ++ str = TAG_THREAD; ++ break; ++ case PLUGIN: ++ str = TAG_PLUGIN; ++ break; ++ case MENU: ++ str = TAG_MENU; ++ break; ++ case UNDEFINED: ++ str = TAG_UNDEFINED; ++ default: ++ break; ++ } ++ ++ return(str); ++} ++ ++void cSubMenuNode::SetCommand(const char *command) ++{ ++ if (_command != NULL) ++ free((void*)_command); ++ ++ if (command != NULL) ++ _command = strdup(command); ++ else ++ _command = NULL; ++} ++ ++const char *cSubMenuNode::GetCommand() ++{ ++ return(_command); ++} ++ ++bool cSubMenuNode::CommandConfirm() ++{ ++ return(_commandConfirm); ++} ++ ++void cSubMenuNode::SetCommandConfirm(int val) ++{ ++ if (val == 1) ++ _commandConfirm = true; ++ else ++ _commandConfirm = false; ++} ++ ++void cSubMenuNode::SetCustomTitle(const char *title) ++{ ++ if (_title != NULL) ++ free((void*)_title); ++ ++ if (title != NULL) ++ _title = strdup(title); ++ else ++ _title = NULL; ++} ++ ++const char *cSubMenuNode::GetCustomTitle() ++{ ++ return(_title); ++} ++ ++void cSubMenuNode::SetName(const char *name) ++{ ++ if (_name) ++ free ((void*)_name); ++ ++ if (name != NULL) ++ _name = strdup(name); ++ else ++ _name = NULL; ++} ++ ++const char *cSubMenuNode::GetName() ++{ ++ return(_name); ++} ++ ++int cSubMenuNode::GetLevel() ++{ ++ return(_level); ++} ++ ++void cSubMenuNode::SetLevel(int level) ++{ ++ _level = level; ++ if (HasSubMenus()) { //Adjust Levels of Subnodes ++ for (cSubMenuNode *node = _subMenus.First(); node; node = _subMenus.Next(node)) ++ node->SetLevel(level+1); ++ } ++} ++ ++int cSubMenuNode::GetPluginIndex() ++{ ++ return(_pluginIndex); ++} ++ ++void cSubMenuNode::SetPluginIndex(int index) ++{ ++ _pluginIndex = index; ++} ++ ++void cSubMenuNode::SetPluginMainMenuEntry(const char *mainMenuEntry) ++{ ++ if (_pluginMainMenuEntry != NULL) ++ free((void*)_pluginMainMenuEntry); ++ ++ if (_title != NULL && strcmp(_title, "") != 0) ++ _pluginMainMenuEntry = strdup(_title); ++ else if (mainMenuEntry != NULL) ++ _pluginMainMenuEntry = strdup(mainMenuEntry); ++ else ++ _pluginMainMenuEntry = NULL; ++} ++ ++const char *cSubMenuNode::GetPluginMainMenuEntry() ++{ ++ return(_pluginMainMenuEntry); ++} ++ ++ ++cSubMenuNodes *cSubMenuNode::GetParentMenu() ++{ ++ return(_parentMenu); ++} ++ ++void cSubMenuNode::SetParentMenu(cSubMenuNodes *parent) ++{ ++ _parentMenu = parent; ++} ++ ++cSubMenuNodes *cSubMenuNode::GetCurrentMenu() ++{ ++ return(_currentMenu); ++} ++ ++void cSubMenuNode::SetCurrentMenu(cSubMenuNodes *current) ++{ ++ _currentMenu = current; ++} ++ ++ ++cSubMenuNodes *cSubMenuNode::GetSubMenus() ++{ ++ return(&_subMenus); ++} ++ ++bool cSubMenuNode::HasSubMenus() ++{ ++ if (_subMenus.Count() > 0) ++ return(true); ++ else ++ return(false); ++} ++ ++ ++void cSubMenuNode::Print(int index) ++{ ++ for (int i = 0; i < index; i++) ++ printf(" "); ++ ++ printf("Name=%s Type=%s Level=%d", _name, GetTypeAsString(), _level); ++ if (_type == COMMAND || _type == THREAD) ++ printf(" Command=%s", _command); ++ else if (_type == PLUGIN && _title != NULL) ++ printf(" Title=%s", _title); ++ printf("\n"); ++ ++ for (cSubMenuNode *node = _subMenus.First(); node; node = _subMenus.Next(node)) ++ node->Print(index+4); ++} ++ ++ ++//################################################################################ ++//# ++//################################################################################ ++cSubMenu::cSubMenu() ++{ ++ _commandResult = NULL; ++ _currentMenuTree = &_menuTree; ++ _currentParentMenuTree = NULL; ++#ifdef USE_PINPLUGIN ++ _currentParentIndex = -1; ++#endif /* PINPLUGIN */ ++ _nodeArray = NULL; ++ _nrNodes = 0; ++} ++ ++ ++cSubMenu::~cSubMenu() ++{ ++ if (_commandResult) ++ free(_commandResult); ++ if (_nodeArray) ++ free(_nodeArray); ++ _nrNodes = 0; ++} ++ ++ ++bool cSubMenu::LoadXml(cString fname) ++{ ++ TiXmlDocument xmlDoc = TiXmlDocument(fname); ++ TiXmlElement *root = NULL; ++ cSubMenuNode *node = NULL; ++ ++ bool ok = true; ++ // Clear previously loaded Menu ++ _menuTree.Clear(); ++ _fname = fname; ++ ++ if ((ok = xmlDoc.LoadFile())) { ++ if ((root = xmlDoc.FirstChildElement("menus")) != NULL) { ++ cString tmp = root->Attribute("suffix"); ++#ifdef USE_WAREAGLEICON ++ if (strcmp(tmp, "ICON_FOLDER") == 0) tmp = cString::sprintf(" %s", IsLangUtf8() ? ICON_FOLDER_UTF8 : ICON_FOLDER); ++ else if (strcmp(tmp, "ICON_MOVE_FOLDER") == 0) tmp = cString::sprintf(" %s", IsLangUtf8() ? ICON_MOVE_FOLDER_UTF8 : ICON_MOVE_FOLDER); ++#endif /* WAREAGLEICON */ ++ if (*tmp) ++ _menuSuffix = tmp; ++ else ++ _menuSuffix = cString::sprintf(" "); ++ ++ if ((root = root->FirstChildElement()) != NULL) { ++ do { ++ try { ++ node = new cSubMenuNode(root, 0, &_menuTree, NULL); ++ _menuTree.Add(node); ++ } ++ catch (char *message) { ++ esyslog("ERROR: while decoding XML Node"); ++ ok = false; ++ } ++ } while (ok == true && (root = root->NextSiblingElement()) != NULL); ++ addMissingPlugins(); ++ removeUndefinedNodes(); ++ } ++ } ++ else { ++ esyslog("ERROR: in %s, missing Tag \n", *fname); ++ ok = false; ++ } ++ } ++ else { ++ esyslog("ERROR: in %s : %s Col=%d Row=%d\n", ++ *fname, ++ xmlDoc.ErrorDesc(), ++ xmlDoc.ErrorCol(), ++ xmlDoc.ErrorRow()); ++ ok = false; ++ } ++ ++ return(ok); ++} ++ ++ ++bool cSubMenu::SaveXml() ++{ ++ return(SaveXml(_fname)); ++} ++ ++ ++bool cSubMenu::SaveXml(cString fname) ++{ ++ bool ok = true; ++ ++ if (*_fname) { ++ TiXmlDocument xml = TiXmlDocument(fname); ++ TiXmlComment comment; ++ comment.SetValue("\n\ ++- VDR Menu-Configuration File\n\ ++-\n\ ++-\n\ ++- Example:\n\ ++-\n\ ++ \n\ ++ \n\ ++ \n\ ++ \n\ ++ \n\ ++ \n\ ++ \n\ ++ \n\ ++ \n\ ++ \n\ ++ \n\ ++ \n\ ++ \n\ ++ \n\ ++ ...\n\ ++ \n\ ++ \n\ ++ \n\ ++ \n\ ++ ...\n\ ++ \n\ ++ \n\ ++"); ++ ++ TiXmlElement root("menus"); ++ root.SetAttribute("suffix", _menuSuffix); ++ for (cSubMenuNode *node = _menuTree.First(); node; node = _menuTree.Next(node)) ++ node->SaveXml(&root); ++ ++ if (xml.InsertEndChild(comment) != NULL && xml.InsertEndChild(root) != NULL) ++ ok = xml.SaveFile(fname); ++ } ++ else ++ ok = false; ++ ++ return(ok); ++} ++ ++ ++cSubMenuNodes *cSubMenu::GetMenuTree() ++{ ++ return(_currentMenuTree); ++} ++ ++ ++void cSubMenu::PrintMenuTree() ++{ ++ for (cSubMenuNode *node = _menuTree.First(); node; node = _menuTree.Next(node)) ++ node->Print(); ++} ++ ++ ++int cSubMenu::GetNrOfNodes() ++{ ++ if (_nrNodes == 0) { ++ if ((_nrNodes = countNodes(&_menuTree)) > 0) { ++ _nodeArray = (cSubMenuNode**) malloc(sizeof(cSubMenuNode*)*_nrNodes); ++ int index = 0; ++ tree2Array(&_menuTree, index); ++ } ++ } ++ ++ return(_nrNodes); ++} ++ ++ ++/** ++ * returns the specified node within the current menu ++ * @param index position in the current menu ++ * @return node or null if not found ++ */ ++cSubMenuNode *cSubMenu::GetNode(int index) ++{ ++ cSubMenuNode *node = NULL; ++ if (_currentMenuTree == NULL || (node=_currentMenuTree->Get(index)) == NULL) ++ esyslog("ERROR: illegal call of cSubMenu::GetNode(%d)", index); ++ ++ return(node); ++} ++ ++ ++/** ++ * Get the specified Node ++ * @param index specfies the absolut indes in the list of all nodes ++ * @return node or NULL if not found ++ */ ++cSubMenuNode *cSubMenu::GetAbsNode(int index) ++{ ++ cSubMenuNode *node = NULL; ++ GetNrOfNodes(); ++ if (_nrNodes > 0 && index >= 0 && index < _nrNodes) ++ node = _nodeArray[index]; ++ ++ return(node); ++} ++ ++ ++#ifdef USE_PINPLUGIN ++bool cSubMenu::Down(cSubMenuNode *node, int currentIndex) ++#else ++bool cSubMenu::Down(int index) ++#endif /* PINPLUGIN */ ++{ ++ bool ok = true; ++#ifdef USE_PINPLUGIN ++ if (_currentMenuTree != NULL && node && node->GetType() == cSubMenuNode::MENU) { ++#else ++ cSubMenuNode *node = NULL; ++ ++ if (_currentMenuTree != NULL && (node=_currentMenuTree->Get(index)) != NULL && node->GetType() == cSubMenuNode::MENU) { ++#endif /* PINPLUGIN */ ++ _currentParentMenuTree = _currentMenuTree; ++#ifdef USE_PINPLUGIN ++ _currentParentIndex = currentIndex; ++#endif /* PINPLUGIN */ ++ _currentMenuTree = node->GetSubMenus(); ++ } ++ else { ++ ok = false; ++#ifdef USE_PINPLUGIN ++ esyslog("ERROR: illegal call of cSubMenu::Down"); ++#else ++ esyslog("ERROR: illegal call of cSubMenu::Down(%d)", index); ++#endif /* PINPLUGIN */ ++ } ++ ++ return(ok); ++} ++ ++bool cSubMenu::Up(int *parentIndex) ++{ ++ bool ok = true; ++ ++ if (_currentMenuTree != NULL && parentIndex != NULL) { ++#ifndef USE_PINPLUGIN ++ cSubMenuNode *node = NULL; ++#endif /* PINPLUGIN */ ++ *parentIndex = 0; ++#ifdef USE_PINPLUGIN ++ if (_currentParentIndex >= 0) ++ *parentIndex = _currentParentIndex; ++#else ++ if (_currentParentMenuTree != NULL) ++ for (int i = 0; (node = _currentParentMenuTree->Get(i)) != NULL; i++) { ++ if (_currentMenuTree == node->GetSubMenus()) { ++ *parentIndex = i; ++ break; ++ } ++ } ++#endif /* PINPLUGIN */ ++ ++ _currentMenuTree = _currentParentMenuTree; ++ if (_currentMenuTree != NULL) ++ _currentParentMenuTree = _currentMenuTree->Get(0)->GetParentMenu(); ++ else ++ ok = false; ++ } ++ else { ++ ok = false; ++ esyslog("ERROR: illegal call of cSubMenu::Up()"); ++ } ++ ++ return(ok); ++} ++ ++const char *cSubMenu::ExecuteCommand(const char *cmd) ++{ ++ free(_commandResult); ++ _commandResult = NULL; ++ ++ dsyslog("executing command '%s'", cmd); ++ FILE *p = popen(cmd, "r"); ++ if (p) { ++ int l = 0; ++ int c; ++ while ((c = fgetc(p)) != EOF) { ++ if (l % 20 == 0) ++ _commandResult = (char *)realloc(_commandResult, l + 21); ++ _commandResult[l++] = c; ++ } ++ if (_commandResult) ++ _commandResult[l] = 0; ++ pclose(p); ++ } ++ else ++ esyslog("ERROR: can't open pipe for command '%s'", cmd); ++ ++ return _commandResult; ++} ++ ++/** ++ * Move Menu Entry to new Position ++ * @param index index of menu entry to move ++ * @param toIndex index of destination ++ * @param where After ore before the destination index ++ */ ++void cSubMenu::MoveMenu(int index, int toIndex, enum Where where) ++{ ++ if (index < 0 || index > _nrNodes || // invalid index is ignored ++ toIndex < 0 || toIndex > _nrNodes || index == toIndex) ++ return; ++ ++ cSubMenuNode *srcNode = GetAbsNode(index); ++ cSubMenuNode *destNode = GetAbsNode(toIndex); ++ ++ if (where == cSubMenu::INTO && destNode->GetType() != cSubMenuNode::MENU) ++ return; ++ ++ if (where == cSubMenu::INTO) { ++ if (destNode->GetType() == cSubMenuNode::MENU) { ++ srcNode->GetCurrentMenu()->Del(srcNode, false); ++ srcNode->SetLevel(destNode->GetLevel()+1); ++ srcNode->SetParentMenu(destNode->GetCurrentMenu()); ++ srcNode->SetCurrentMenu(destNode->GetSubMenus()); ++ ++ destNode->GetSubMenus()->Add(srcNode); ++ reloadNodeArray(); ++ } ++ } ++ else { ++ srcNode->GetCurrentMenu()->Del(srcNode, false); ++ srcNode->SetLevel(destNode->GetLevel()); ++ srcNode->SetParentMenu(destNode->GetParentMenu()); ++ srcNode->SetCurrentMenu(destNode->GetCurrentMenu()); ++ ++ if (where == cSubMenu::BEHIND) { ++ destNode->GetCurrentMenu()->Add(srcNode, GetAbsNode(toIndex)); ++ reloadNodeArray(); ++ } ++ else { ++ destNode->GetCurrentMenu()->Ins(srcNode, GetAbsNode(toIndex)); ++ reloadNodeArray(); ++ } ++ } ++} ++ ++/** ++ * Create a new Menu Entry ++ * @param index index of destination ++ * @param menuTitle Titel of new Menu entry ++ */ ++void cSubMenu::CreateMenu(int index, const char *menuTitle) ++{ ++ if (index >= 0 && index < _nrNodes) { ++ cSubMenuNode *srcNode = GetAbsNode(index); ++ if (srcNode != NULL) { ++ cSubMenuNode *newNode = new cSubMenuNode(srcNode->GetParentMenu(), srcNode->GetCurrentMenu()); ++ newNode->SetLevel(srcNode->GetLevel()); ++ newNode->SetName(menuTitle); ++ newNode->SetType(cSubMenuNode::MENU); ++ newNode->SetParentMenu(srcNode->GetParentMenu()); ++ newNode->SetCurrentMenu(srcNode->GetCurrentMenu()); ++ ++ srcNode->GetCurrentMenu()->Add(newNode, GetAbsNode(index)); ++ reloadNodeArray(); ++ } ++ } ++} ++ ++/** ++ * delete the specified entry, or subtree if the specified entry is a menu ++ * @param index destion index ++ */ ++void cSubMenu::DeleteMenu(int index) ++{ ++ if (index >= 0 && index < _nrNodes) { ++ cSubMenuNode *srcNode = GetAbsNode(index); ++ srcNode->GetCurrentMenu()->Del(srcNode, true); ++ reloadNodeArray(); ++ } ++} ++ ++ ++// Private Methods ++ ++int cSubMenu::countNodes(cSubMenuNodes *tree) ++{ ++ int count = 0; ++ if (tree != NULL) { ++ for (cSubMenuNode *node = tree->First(); node; node = tree->Next(node)) { ++ count++; ++ if (node->HasSubMenus()) ++ count += countNodes(node->GetSubMenus()); ++ } ++ } ++ return(count); ++} ++ ++ ++void cSubMenu::tree2Array(cSubMenuNodes *tree, int &index) ++{ ++ if (tree != NULL) { ++ for (cSubMenuNode *node = tree->First(); node; node = tree->Next(node)) { ++ _nodeArray[index++]=node; ++ if (node->HasSubMenus()) ++ tree2Array(node->GetSubMenus(), index); ++ } ++ } ++ ++} ++ ++bool cSubMenu::IsPluginInMenu(const char *name) ++{ ++ bool found = false; ++ for (int i = 0; i < _nrNodes && found == false; i++) { ++ cSubMenuNode *node = GetAbsNode(i); ++ if (node != NULL && node->GetType() == cSubMenuNode::PLUGIN && strcmp(name, node->GetName()) == 0) ++ found = true; ++ } ++ return(found); ++} ++ ++/** ++ * Adds the given plugin to the Menu-Tree if not allready in List ++ * @param name specifies the name of the plugin ++ */ ++void cSubMenu::AddPlugin(const char *name) ++{ ++ if (! IsPluginInMenu(name)) { ++ cSubMenuNode *node = new cSubMenuNode(&_menuTree, NULL); ++ node->SetName(name); ++ node->SetType("plugin"); ++ node->SetPlugin(); ++ _menuTree.Add(node); ++ } ++} ++ ++void cSubMenu::addMissingPlugins() ++{ ++ _nrNodes = GetNrOfNodes(); ++ for (int i = 0; ; i++) { ++ cPlugin *p = cPluginManager::GetPlugin(i); ++ if (p) ++ AddPlugin(p->Name()); ++ else ++ break; ++ } ++ reloadNodeArray(); ++} ++ ++/** ++ * Adds the given command to the Menu-Tree ++ * @param name specifies the name of the command ++ */ ++void cSubMenu::CreateCommand(int index, const char *name, const char *execute, int confirm) ++{ ++ if (index >= 0 && index < _nrNodes) { ++ cSubMenuNode *srcNode = GetAbsNode(index); ++ if (srcNode != NULL) { ++ cSubMenuNode *newNode = new cSubMenuNode(srcNode->GetParentMenu(), srcNode->GetCurrentMenu()); ++ newNode->SetLevel(srcNode->GetLevel()); ++ newNode->SetName(name); ++ newNode->SetType("command"); ++ newNode->SetCommand(execute); ++ newNode->SetCommandConfirm(confirm); ++ newNode->SetParentMenu(srcNode->GetParentMenu()); ++ newNode->SetCurrentMenu(srcNode->GetCurrentMenu()); ++ ++ srcNode->GetCurrentMenu()->Add(newNode, GetAbsNode(index)); ++ reloadNodeArray(); ++ } ++ } ++} ++ ++void cSubMenu::CreateThread(int index, const char *name, const char *execute, int confirm) ++{ ++ if (index >= 0 && index < _nrNodes) { ++ cSubMenuNode *srcNode = GetAbsNode(index); ++ if (srcNode != NULL) { ++ cSubMenuNode *newNode = new cSubMenuNode(srcNode->GetParentMenu(), srcNode->GetCurrentMenu()); ++ newNode->SetLevel(srcNode->GetLevel()); ++ newNode->SetName(name); ++ newNode->SetType("thread"); ++ newNode->SetCommand(execute); ++ newNode->SetCommandConfirm(confirm); ++ newNode->SetParentMenu(srcNode->GetParentMenu()); ++ newNode->SetCurrentMenu(srcNode->GetCurrentMenu()); ++ ++ srcNode->GetCurrentMenu()->Add(newNode, GetAbsNode(index)); ++ reloadNodeArray(); ++ } ++ } ++} ++ ++/** ++ * reloads the internal Array of Nodes ++ */ ++void cSubMenu::reloadNodeArray() ++{ ++ if (_nrNodes > 0) ++ free(_nodeArray); ++ _nodeArray = NULL; ++ _nrNodes = 0; ++ _nrNodes = GetNrOfNodes(); ++} ++ ++/** ++ * remove Undefined Nodes ++ */ ++void cSubMenu::removeUndefinedNodes() ++{ ++ bool remove = false; ++ ++ reloadNodeArray(); ++ for (int i = 0; i < _nrNodes; i++) { ++ cSubMenuNode *node = GetAbsNode(i); ++ if (node != NULL && node->GetType() == cSubMenuNode::UNDEFINED) { ++ cSubMenuNodes *pMenu = node->GetCurrentMenu(); ++ pMenu->Del(node, true); ++ remove = true; ++ } ++ } ++ if (remove) ++ reloadNodeArray(); ++} ++ ++ ++/** ++* Retrieves the Menutitel of the parent Menu ++*/ ++const char *cSubMenu::GetParentMenuTitel() ++{ ++ const char *result = ""; ++ ++ if (_currentMenuTree != NULL && _currentParentMenuTree != NULL) { ++ cSubMenuNode *node = NULL; ++ for (int i = 0; (node = _currentParentMenuTree->Get(i)) != NULL; i++) { ++ if (_currentMenuTree == node->GetSubMenus()) { ++ result = node->GetName(); ++ break; ++ } ++ } ++ } ++ ++ return(result); ++} ++ ++#endif ++#endif /* SETUP */ +diff -NaurwB vdr-1.7.10/submenu.h vdr-1.7.10-patched/submenu.h +--- vdr-1.7.10/submenu.h 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/submenu.h 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,159 @@ ++#ifdef USE_SETUP ++/**************************************************************************** ++ * DESCRIPTION: ++ * Submenu ++ * ++ * $Id: vdr-1.3.44-Setup-0.3.0.diff,v 1.1 2006/03/04 09:58:47 ralf Exp $ ++ * ++ * Contact: ranga@teddycats.de ++ * ++ * Copyright (C) 2004, 2005 by Ralf Dotzert ++ * ++ * modified for the VDR Extensions Patch by zulu @vdr-portal ++ ****************************************************************************/ ++ ++#ifndef SUBMENU_H ++#define SUBMENU_H ++ ++#include "thread.h" ++#include "tools.h" ++#include "tinystr.h" ++ ++class cSubMenuNode; ++class cSubMenuNodes; ++class cSubMenu; ++ ++ ++class cSubMenuNodes : public cList {}; ++ ++// execute cmd thread ++class cExecCmdThread : public cThread { ++private: ++ cString ExecCmd; ++protected: ++ virtual void Action(void) { ++ if (system(ExecCmd) == 0) ++ esyslog("%s - finished", *ExecCmd); ++ delete(this); ++ }; ++public: ++ cExecCmdThread(char *cmd) { ++ ExecCmd = cString::sprintf("%s", cmd); ++ } ++ cExecCmdThread(const char *cmd) { ++ ExecCmd = cString::sprintf("%s", cmd); ++ } ++ ~cExecCmdThread() { ++ }; ++ }; ++ ++//################################################################################ ++//# SubMenuNode ++//################################################################################ ++class cSubMenuNode : public cListObject { ++public: ++ enum Type { UNDEFINED, SYSTEM, COMMAND, THREAD, PLUGIN, MENU }; ++ cSubMenuNode(TiXmlElement *xml, int level, cSubMenuNodes *currentMenu, cSubMenuNodes *parentMenu); ++ cSubMenuNode(cSubMenuNodes *currentMenu, cSubMenuNodes *parentMenu); ++ ~cSubMenuNode(); ++ bool SaveXml(TiXmlElement *root); ++ static cSubMenuNode::Type IsType(const char *name); ++ void SetType(const char *name); ++ void SetType(enum Type type); ++ void SetPlugin(); ++ cSubMenuNode::Type GetType(); ++ const char *GetTypeAsString(); ++ void SetCommand(const char *command); ++ bool CommandConfirm(); ++ void SetCommandConfirm(int val); ++ const char *GetCommand(); ++ void SetCustomTitle(const char *title); ++ const char *GetCustomTitle(); ++ void SetName(const char *name); ++ const char*GetName(); ++ int GetLevel(); ++ void SetLevel(int level); ++ int GetPluginIndex(); ++ void SetPluginIndex(int index); ++ void SetPluginMainMenuEntry(const char *mainMenuEntry); ++ const char *GetPluginMainMenuEntry(); ++ cSubMenuNodes *GetParentMenu(); ++ void SetParentMenu(cSubMenuNodes *parent); ++ cSubMenuNodes *GetCurrentMenu(); ++ void SetCurrentMenu(cSubMenuNodes *current); ++ cSubMenuNodes *GetSubMenus(); ++ bool HasSubMenus(); ++ void Print(int index = 0); ++private: ++ Type _type; ++ int _level; ++ // Plugin Variables ++ int _pluginIndex; ++ const char *_pluginMainMenuEntry; ++ // common ++ const char *_name; ++ const char *_command; ++ bool _commandConfirm; ++ const char *_title; ++ cSubMenuNodes _subMenus; ++ cSubMenuNodes *_parentMenu; ++ cSubMenuNodes *_currentMenu; ++ void init(); ++ }; ++ ++ ++//################################################################################ ++//# SubMenu Class ++//################################################################################ ++class cSubMenu { ++public: ++ cSubMenu(); ++ ~cSubMenu(); ++ enum Where { BEFORE, BEHIND, INTO}; ++ bool LoadXml(cString fname); ++ bool SaveXml(cString fname); ++ bool SaveXml(); ++ cSubMenuNodes *GetMenuTree(); ++ bool Up(int *ParentIndex); ++#ifdef USE_PINPLUGIN ++ bool Down(cSubMenuNode* node, int currentIndex); ++#else ++ bool Down(int index); ++#endif /* PINPLUGIN */ ++ int GetNrOfNodes(); ++ cSubMenuNode* GetAbsNode(int index); ++ cSubMenuNode* GetNode(int index); ++ void PrintMenuTree(); ++ bool IsPluginInMenu(const char *name); ++ void AddPlugin(const char *name); ++ void CreateCommand(int index, const char *name, const char *execute, int confirm); ++ void CreateThread(int index, const char *name, const char *execute, int confirm); ++ const char *ExecuteCommand(const char *command); ++ void MoveMenu(int index, int toindex, enum Where); ++ void CreateMenu(int index, const char *menuTitle); ++ void DeleteMenu(int index); ++ cString GetMenuSuffix() { return _menuSuffix; } ++ void SetMenuSuffix(char *suffix) { _menuSuffix = suffix; } ++ bool isTopMenu() { return (_currentParentMenuTree == NULL); } ++ const char *GetParentMenuTitel(); ++private: ++ cSubMenuNodes _menuTree; ++ cSubMenuNodes *_currentMenuTree; ++ cSubMenuNodes *_currentParentMenuTree; ++#ifdef USE_PINPLUGIN ++ int _currentParentIndex; ++#endif /* PINPLUGIN */ ++ cString _fname; ++ char *_commandResult; ++ int _nrNodes; ++ cSubMenuNode **_nodeArray; ++ cString _menuSuffix; ++ int countNodes(cSubMenuNodes *tree); ++ void tree2Array(cSubMenuNodes *tree, int &index); ++ void addMissingPlugins(); ++ void reloadNodeArray(); ++ void removeUndefinedNodes(); ++ }; ++ ++#endif //__SUBMENU_H ++#endif /* SETUP */ +diff -NaurwB vdr-1.7.10/svdrp.c vdr-1.7.10-patched/svdrp.c +--- vdr-1.7.10/svdrp.c 2009-10-18 16:08:58.000000000 +0200 ++++ vdr-1.7.10-patched/svdrp.c 2009-12-18 06:27:38.000000000 +0100 +@@ -299,6 +299,10 @@ + "REMO [ on | off ]\n" + " Turns the remote control on or off. Without a parameter, the current\n" + " status of the remote control is reported.", ++#ifdef USE_LIEMIEXT ++ "RENR \n" ++ " Rename recording. Number must be the Number as returned by LSTR command.", ++#endif /* LIEMIEXT */ + "SCAN\n" + " Forces an EPG scan. If this is a single DVB device system, the scan\n" + " will be done on the primary device unless it is currently recording.", +@@ -1486,6 +1490,38 @@ + Reply(250, "EPG scan triggered"); + } + ++#ifdef USE_LIEMIEXT ++void cSVDRP::CmdRENR(const char *Option) ++{ ++ bool recordings = Recordings.Update(true); ++ if (recordings) { ++ if (*Option) { ++ char *tail; ++ int n = strtol(Option, &tail, 10); ++ cRecording *recording = Recordings.Get(n - 1); ++ if (recording && tail && tail != Option) { ++ char *oldName = strdup(recording->Name()); ++ tail = skipspace(tail); ++ if (recording->Rename(tail)) { ++ Reply(250, "Renamed \"%s\" to \"%s\"", oldName, recording->Name()); ++ Recordings.ChangeState(); ++ Recordings.TouchUpdate(); ++ } ++ else ++ Reply(501, "Renaming \"%s\" to \"%s\" failed", oldName, tail); ++ free(oldName); ++ } ++ else ++ Reply(501, "Recording not found or wrong syntax"); ++ } ++ else ++ Reply(501, "Missing Input settings"); ++ } ++ else ++ Reply(550, "No recordings available"); ++} ++#endif /* LIEMIEXT */ ++ + void cSVDRP::CmdSTAT(const char *Option) + { + if (*Option) { +@@ -1601,6 +1637,9 @@ + else if (CMD("PLUG")) CmdPLUG(s); + else if (CMD("PUTE")) CmdPUTE(s); + else if (CMD("REMO")) CmdREMO(s); ++#ifdef USE_LIEMIEXT ++ else if (CMD("RENR")) CmdRENR(s); ++#endif /* LIEMIEXT */ + else if (CMD("SCAN")) CmdSCAN(s); + else if (CMD("STAT")) CmdSTAT(s); + else if (CMD("UPDT")) CmdUPDT(s); +diff -NaurwB vdr-1.7.10/svdrp.h vdr-1.7.10-patched/svdrp.h +--- vdr-1.7.10/svdrp.h 2007-04-30 14:28:28.000000000 +0200 ++++ vdr-1.7.10-patched/svdrp.h 2009-12-18 06:25:25.000000000 +0100 +@@ -79,6 +79,9 @@ + void CmdPLUG(const char *Option); + void CmdPUTE(const char *Option); + void CmdREMO(const char *Option); ++#ifdef USE_LIEMIEXT ++ void CmdRENR(const char *Option); ++#endif /* LIEMIEXT */ + void CmdSCAN(const char *Option); + void CmdSTAT(const char *Option); + void CmdUPDT(const char *Option); +diff -NaurwB vdr-1.7.10/timers.c vdr-1.7.10-patched/timers.c +--- vdr-1.7.10/timers.c 2009-08-09 14:43:20.000000000 +0200 ++++ vdr-1.7.10-patched/timers.c 2009-12-18 06:29:49.000000000 +0100 +@@ -46,6 +46,9 @@ + stop -= 2400; + priority = Pause ? Setup.PausePriority : Setup.DefaultPriority; + lifetime = Pause ? Setup.PauseLifetime : Setup.DefaultLifetime; ++#ifdef USE_PINPLUGIN ++ fskProtection = 0; ++#endif /* PINPLUGIN */ + *file = 0; + aux = NULL; + event = NULL; +@@ -84,6 +87,9 @@ + stop -= 2400; + priority = Setup.DefaultPriority; + lifetime = Setup.DefaultLifetime; ++#ifdef USE_PINPLUGIN ++ fskProtection = 0; ++#endif /* PINPLUGIN */ + *file = 0; + const char *Title = Event->Title(); + if (!isempty(Title)) +@@ -95,6 +101,9 @@ + } + aux = NULL; + event = NULL; // let SetEvent() be called to get a log message ++#ifdef USE_PINPLUGIN ++ cStatus::MsgTimerCreation(this, Event); ++#endif /* PINPLUGIN */ + } + + cTimer::cTimer(const cTimer &Timer) +@@ -129,6 +138,9 @@ + stop = Timer.stop; + priority = Timer.priority; + lifetime = Timer.lifetime; ++#ifdef USE_PINPLUGIN ++ fskProtection = Timer.fskProtection; ++#endif /* PINPLUGIN */ + strncpy(file, Timer.file, sizeof(file)); + free(aux); + aux = Timer.aux ? strdup(Timer.aux) : NULL; +@@ -323,6 +335,9 @@ + result = false; + } + } ++#ifdef USE_PINPLUGIN ++ fskProtection = aux && strstr(aux, "yes"); ++#endif /* PINPLUGIN */ + free(channelbuffer); + free(daybuffer); + free(filebuffer); +@@ -632,6 +647,35 @@ + Matches(); // refresh start and end time + } + ++#ifdef USE_PINPLUGIN ++void cTimer::SetFskProtection(int aFlag) ++{ ++ char* p; ++ char* tmp = 0; ++ ++ fskProtection = aFlag; ++ ++ if (fskProtection && (!aux || !strstr(aux, "yes"))) ++ { ++ // add protection info to aux ++ ++ if (aux) { tmp = strdup(aux); free(aux); } ++ asprintf(&aux,"%syes", tmp ? tmp : ""); ++ } ++ else if (!fskProtection && aux && (p = strstr(aux, "yes"))) ++ { ++ // remove protection info to aux ++ ++ asprintf(&tmp, "%.*s%s", p-aux, aux, p+strlen("yes")); ++ free(aux); ++ aux = strdup(tmp); ++ } ++ ++ if (tmp) ++ free(tmp); ++} ++#endif /* PINPLUGIN */ ++ + // --- cTimers --------------------------------------------------------------- + + cTimers Timers; +diff -NaurwB vdr-1.7.10/timers.h vdr-1.7.10-patched/timers.h +--- vdr-1.7.10/timers.h 2008-02-16 15:33:23.000000000 +0100 ++++ vdr-1.7.10-patched/timers.h 2009-12-18 06:25:25.000000000 +0100 +@@ -37,6 +37,9 @@ + int start; + int stop; + int priority; ++#ifdef USE_PINPLUGIN ++ int fskProtection; ++#endif /* PINPLUGIN */ + int lifetime; + mutable char file[MaxFileName]; + char *aux; +@@ -58,6 +61,9 @@ + int Start(void) const { return start; } + int Stop(void) const { return stop; } + int Priority(void) const { return priority; } ++#ifdef USE_PINPLUGIN ++ int FskProtection(void) const { return fskProtection; } ++#endif /* PINPLUGIN */ + int Lifetime(void) const { return lifetime; } + const char *File(void) const { return file; } + time_t FirstDay(void) const { return weekdays ? day : 0; } +@@ -86,6 +92,9 @@ + void SetInVpsMargin(bool InVpsMargin); + void SetPriority(int Priority); + void SetFlags(uint Flags); ++#ifdef USE_PINPLUGIN ++ void SetFskProtection(int aFlag); ++#endif /* PINPLUGIN */ + void ClrFlags(uint Flags); + void InvFlags(uint Flags); + bool HasFlags(uint Flags) const; +diff -NaurwB vdr-1.7.10/tinystr.c vdr-1.7.10-patched/tinystr.c +--- vdr-1.7.10/tinystr.c 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/tinystr.c 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,301 @@ ++#ifdef USE_SETUP ++/* ++www.sourceforge.net/projects/tinyxml ++Original file by Yves Berquin. ++ ++This software is provided 'as-is', without any express or implied ++warranty. In no event will the authors be held liable for any ++damages arising from the use of this software. ++ ++Permission is granted to anyone to use this software for any ++purpose, including commercial applications, and to alter it and ++redistribute it freely, subject to the following restrictions: ++ ++1. The origin of this software must not be misrepresented; you must ++not claim that you wrote the original software. If you use this ++software in a product, an acknowledgment in the product documentation ++would be appreciated but is not required. ++ ++2. Altered source versions must be plainly marked as such, and ++must not be misrepresented as being the original software. ++ ++3. This notice may not be removed or altered from any source ++distribution. ++*/ ++ ++#include "tinyxml.h" ++ ++#ifndef TIXML_USE_STL ++ ++ ++#include ++#include ++#include ++ ++#include "tinystr.h" ++ ++// TiXmlString constructor, based on a C string ++TiXmlString::TiXmlString (const char* instring) ++{ ++ unsigned newlen; ++ char * newstring; ++ ++ if (!instring) ++ { ++ allocated = 0; ++ cstring = NULL; ++ current_length = 0; ++ return; ++ } ++ newlen = strlen (instring) + 1; ++ newstring = new char [newlen]; ++ memcpy (newstring, instring, newlen); ++ // strcpy (newstring, instring); ++ allocated = newlen; ++ cstring = newstring; ++ current_length = newlen - 1; ++} ++ ++// TiXmlString copy constructor ++TiXmlString::TiXmlString (const TiXmlString& copy) ++{ ++ unsigned newlen; ++ char * newstring; ++ ++ // Prevent copy to self! ++ if ( © == this ) ++ return; ++ ++ if (! copy . allocated) ++ { ++ allocated = 0; ++ cstring = NULL; ++ current_length = 0; ++ return; ++ } ++ newlen = copy . length () + 1; ++ newstring = new char [newlen]; ++ // strcpy (newstring, copy . cstring); ++ memcpy (newstring, copy . cstring, newlen); ++ allocated = newlen; ++ cstring = newstring; ++ current_length = newlen - 1; ++} ++ ++// TiXmlString = operator. Safe when assign own content ++void TiXmlString ::operator = (const char * content) ++{ ++ unsigned newlen; ++ char * newstring; ++ ++ if (! content) ++ { ++ empty_it (); ++ return; ++ } ++ newlen = strlen (content) + 1; ++ newstring = new char [newlen]; ++ // strcpy (newstring, content); ++ memcpy (newstring, content, newlen); ++ empty_it (); ++ allocated = newlen; ++ cstring = newstring; ++ current_length = newlen - 1; ++} ++ ++// = operator. Safe when assign own content ++void TiXmlString ::operator = (const TiXmlString & copy) ++{ ++ unsigned newlen; ++ char * newstring; ++ ++ if (! copy . length ()) ++ { ++ empty_it (); ++ return; ++ } ++ newlen = copy . length () + 1; ++ newstring = new char [newlen]; ++ // strcpy (newstring, copy . c_str ()); ++ memcpy (newstring, copy . c_str (), newlen); ++ empty_it (); ++ allocated = newlen; ++ cstring = newstring; ++ current_length = newlen - 1; ++} ++ ++ ++// append a const char * to an existing TiXmlString ++void TiXmlString::append( const char* str, int len ) ++{ ++ char * new_string; ++ unsigned new_alloc, new_size, size_suffix; ++ ++ // don't use strlen - it can overrun the len passed in! ++ const char* p = str; ++ size_suffix = 0; ++ ++ while ( *p && size_suffix < (unsigned)len ) ++ { ++ ++p; ++ ++size_suffix; ++ } ++ if ( !size_suffix) ++ return; ++ ++ new_size = length () + size_suffix + 1; ++ // check if we need to expand ++ if (new_size > allocated) ++ { ++ // compute new size ++ new_alloc = assign_new_size (new_size); ++ ++ // allocate new buffer ++ new_string = new char [new_alloc]; ++ new_string [0] = 0; ++ ++ // copy the previous allocated buffer into this one ++ if (allocated && cstring) ++ // strcpy (new_string, cstring); ++ memcpy (new_string, cstring, length ()); ++ ++ // append the suffix. It does exist, otherwize we wouldn't be expanding ++ // strncat (new_string, str, len); ++ memcpy (new_string + length (), ++ str, ++ size_suffix); ++ ++ // return previsously allocated buffer if any ++ if (allocated && cstring) ++ delete [] cstring; ++ ++ // update member variables ++ cstring = new_string; ++ allocated = new_alloc; ++ } ++ else ++ { ++ // we know we can safely append the new string ++ // strncat (cstring, str, len); ++ memcpy (cstring + length (), ++ str, ++ size_suffix); ++ } ++ current_length = new_size - 1; ++ cstring [current_length] = 0; ++} ++ ++ ++// append a const char * to an existing TiXmlString ++void TiXmlString::append( const char * suffix ) ++{ ++ char * new_string; ++ unsigned new_alloc, new_size; ++ ++ new_size = length () + strlen (suffix) + 1; ++ // check if we need to expand ++ if (new_size > allocated) ++ { ++ // compute new size ++ new_alloc = assign_new_size (new_size); ++ ++ // allocate new buffer ++ new_string = new char [new_alloc]; ++ new_string [0] = 0; ++ ++ // copy the previous allocated buffer into this one ++ if (allocated && cstring) ++ memcpy (new_string, cstring, 1 + length ()); ++ // strcpy (new_string, cstring); ++ ++ // append the suffix. It does exist, otherwize we wouldn't be expanding ++ // strcat (new_string, suffix); ++ memcpy (new_string + length (), ++ suffix, ++ strlen (suffix) + 1); ++ ++ // return previsously allocated buffer if any ++ if (allocated && cstring) ++ delete [] cstring; ++ ++ // update member variables ++ cstring = new_string; ++ allocated = new_alloc; ++ } ++ else ++ { ++ // we know we can safely append the new string ++ // strcat (cstring, suffix); ++ memcpy (cstring + length (), ++ suffix, ++ strlen (suffix) + 1); ++ } ++ current_length = new_size - 1; ++} ++ ++// Check for TiXmlString equuivalence ++//bool TiXmlString::operator == (const TiXmlString & compare) const ++//{ ++// return (! strcmp (c_str (), compare . c_str ())); ++//} ++ ++//unsigned TiXmlString::length () const ++//{ ++// if (allocated) ++// // return strlen (cstring); ++// return current_length; ++// return 0; ++//} ++ ++ ++unsigned TiXmlString::find (char tofind, unsigned offset) const ++{ ++ char * lookup; ++ ++ if (offset >= length ()) ++ return (unsigned) notfound; ++ for (lookup = cstring + offset; * lookup; lookup++) ++ if (* lookup == tofind) ++ return lookup - cstring; ++ return (unsigned) notfound; ++} ++ ++ ++bool TiXmlString::operator == (const TiXmlString & compare) const ++{ ++ if ( allocated && compare.allocated ) ++ { ++ assert( cstring ); ++ assert( compare.cstring ); ++ return ( strcmp( cstring, compare.cstring ) == 0 ); ++ } ++ return false; ++} ++ ++ ++bool TiXmlString::operator < (const TiXmlString & compare) const ++{ ++ if ( allocated && compare.allocated ) ++ { ++ assert( cstring ); ++ assert( compare.cstring ); ++ return ( strcmp( cstring, compare.cstring ) > 0 ); ++ } ++ return false; ++} ++ ++ ++bool TiXmlString::operator > (const TiXmlString & compare) const ++{ ++ if ( allocated && compare.allocated ) ++ { ++ assert( cstring ); ++ assert( compare.cstring ); ++ return ( strcmp( cstring, compare.cstring ) < 0 ); ++ } ++ return false; ++} ++ ++ ++#endif // TIXML_USE_STL ++#endif /* SETUP */ +diff -NaurwB vdr-1.7.10/tinystr.h vdr-1.7.10-patched/tinystr.h +--- vdr-1.7.10/tinystr.h 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/tinystr.h 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,244 @@ ++#ifdef USE_SETUP ++/* ++www.sourceforge.net/projects/tinyxml ++Original file by Yves Berquin. ++ ++This software is provided 'as-is', without any express or implied ++warranty. In no event will the authors be held liable for any ++damages arising from the use of this software. ++ ++Permission is granted to anyone to use this software for any ++purpose, including commercial applications, and to alter it and ++redistribute it freely, subject to the following restrictions: ++ ++1. The origin of this software must not be misrepresented; you must ++not claim that you wrote the original software. If you use this ++software in a product, an acknowledgment in the product documentation ++would be appreciated but is not required. ++ ++2. Altered source versions must be plainly marked as such, and ++must not be misrepresented as being the original software. ++ ++3. This notice may not be removed or altered from any source ++distribution. ++*/ ++ ++#include "tinyxml.h" ++ ++ ++#ifndef TIXML_USE_STL ++ ++#ifndef TIXML_STRING_INCLUDED ++#define TIXML_STRING_INCLUDED ++ ++#ifdef _MSC_VER ++#pragma warning( disable : 4786 ) // Debugger truncating names. ++#endif ++ ++#include ++ ++/* ++ TiXmlString is an emulation of the std::string template. ++ Its purpose is to allow compiling TinyXML on compilers with no or poor STL support. ++ Only the member functions relevant to the TinyXML project have been implemented. ++ The buffer allocation is made by a simplistic power of 2 like mechanism : if we increase ++ a string and there's no more room, we allocate a buffer twice as big as we need. ++*/ ++class TiXmlString ++{ ++ public : ++ // TiXmlString constructor, based on a string ++ TiXmlString (const char * instring); ++ ++ // TiXmlString empty constructor ++ TiXmlString () ++ { ++ allocated = 0; ++ cstring = NULL; ++ current_length = 0; ++ } ++ ++ // TiXmlString copy constructor ++ TiXmlString (const TiXmlString& copy); ++ ++ // TiXmlString destructor ++ ~ TiXmlString () ++ { ++ empty_it (); ++ } ++ ++ // Convert a TiXmlString into a classical char * ++ const char * c_str () const ++ { ++ if (allocated) ++ return cstring; ++ return ""; ++ } ++ ++ // Return the length of a TiXmlString ++ unsigned length () const ++ { ++ return ( allocated ) ? current_length : 0; ++ } ++ ++ // TiXmlString = operator ++ void operator = (const char * content); ++ ++ // = operator ++ void operator = (const TiXmlString & copy); ++ ++ // += operator. Maps to append ++ TiXmlString& operator += (const char * suffix) ++ { ++ append (suffix); ++ return *this; ++ } ++ ++ // += operator. Maps to append ++ TiXmlString& operator += (char single) ++ { ++ append (single); ++ return *this; ++ } ++ ++ // += operator. Maps to append ++ TiXmlString& operator += (TiXmlString & suffix) ++ { ++ append (suffix); ++ return *this; ++ } ++ bool operator == (const TiXmlString & compare) const; ++ bool operator < (const TiXmlString & compare) const; ++ bool operator > (const TiXmlString & compare) const; ++ ++ // Checks if a TiXmlString is empty ++ bool empty () const ++ { ++ return length () ? false : true; ++ } ++ ++ // single char extraction ++ const char& at (unsigned index) const ++ { ++ assert( index < length ()); ++ return cstring [index]; ++ } ++ ++ // find a char in a string. Return TiXmlString::notfound if not found ++ unsigned find (char lookup) const ++ { ++ return find (lookup, 0); ++ } ++ ++ // find a char in a string from an offset. Return TiXmlString::notfound if not found ++ unsigned find (char tofind, unsigned offset) const; ++ ++ /* Function to reserve a big amount of data when we know we'll need it. Be aware that this ++ function clears the content of the TiXmlString if any exists. ++ */ ++ void reserve (unsigned size) ++ { ++ empty_it (); ++ if (size) ++ { ++ allocated = size; ++ cstring = new char [size]; ++ cstring [0] = 0; ++ current_length = 0; ++ } ++ } ++ ++ // [] operator ++ char& operator [] (unsigned index) const ++ { ++ assert( index < length ()); ++ return cstring [index]; ++ } ++ ++ // Error value for find primitive ++ enum { notfound = 0xffffffff, ++ npos = notfound }; ++ ++ void append (const char *str, int len ); ++ ++ protected : ++ ++ // The base string ++ char * cstring; ++ // Number of chars allocated ++ unsigned allocated; ++ // Current string size ++ unsigned current_length; ++ ++ // New size computation. It is simplistic right now : it returns twice the amount ++ // we need ++ unsigned assign_new_size (unsigned minimum_to_allocate) ++ { ++ return minimum_to_allocate * 2; ++ } ++ ++ // Internal function that clears the content of a TiXmlString ++ void empty_it () ++ { ++ if (cstring) ++ delete [] cstring; ++ cstring = NULL; ++ allocated = 0; ++ current_length = 0; ++ } ++ ++ void append (const char *suffix ); ++ ++ // append function for another TiXmlString ++ void append (const TiXmlString & suffix) ++ { ++ append (suffix . c_str ()); ++ } ++ ++ // append for a single char. ++ void append (char single) ++ { ++ if ( cstring && current_length < (allocated-1) ) ++ { ++ cstring[ current_length ] = single; ++ ++current_length; ++ cstring[ current_length ] = 0; ++ } ++ else ++ { ++ char smallstr [2]; ++ smallstr [0] = single; ++ smallstr [1] = 0; ++ append (smallstr); ++ } ++ } ++ ++} ; ++ ++/* ++ TiXmlOutStream is an emulation of std::ostream. It is based on TiXmlString. ++ Only the operators that we need for TinyXML have been developped. ++*/ ++class TiXmlOutStream : public TiXmlString ++{ ++public : ++ TiXmlOutStream () : TiXmlString () {} ++ ++ // TiXmlOutStream << operator. Maps to TiXmlString::append ++ TiXmlOutStream & operator << (const char * in) ++ { ++ append (in); ++ return (* this); ++ } ++ ++ // TiXmlOutStream << operator. Maps to TiXmlString::append ++ TiXmlOutStream & operator << (const TiXmlString & in) ++ { ++ append (in . c_str ()); ++ return (* this); ++ } ++} ; ++ ++#endif // TIXML_STRING_INCLUDED ++#endif // TIXML_USE_STL ++#endif /* SETUP */ +diff -NaurwB vdr-1.7.10/tinyxml.c vdr-1.7.10-patched/tinyxml.c +--- vdr-1.7.10/tinyxml.c 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/tinyxml.c 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,1429 @@ ++#ifdef USE_SETUP ++/* ++www.sourceforge.net/projects/tinyxml ++Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com) ++ ++This software is provided 'as-is', without any express or implied ++warranty. In no event will the authors be held liable for any ++damages arising from the use of this software. ++ ++Permission is granted to anyone to use this software for any ++purpose, including commercial applications, and to alter it and ++redistribute it freely, subject to the following restrictions: ++ ++1. The origin of this software must not be misrepresented; you must ++not claim that you wrote the original software. If you use this ++software in a product, an acknowledgment in the product documentation ++would be appreciated but is not required. ++ ++2. Altered source versions must be plainly marked as such, and ++must not be misrepresented as being the original software. ++ ++3. This notice may not be removed or altered from any source ++distribution. ++*/ ++ ++#include ++#include "tinyxml.h" ++ ++#ifdef TIXML_USE_STL ++#include ++#endif ++ ++ ++bool TiXmlBase::condenseWhiteSpace = true; ++ ++void TiXmlBase::PutString( const TIXML_STRING& str, TIXML_OSTREAM* stream ) ++{ ++ TIXML_STRING buffer; ++ PutString( str, &buffer ); ++ (*stream) << buffer; ++} ++ ++void TiXmlBase::PutString( const TIXML_STRING& str, TIXML_STRING* outString ) ++{ ++ int i=0; ++ ++ while( i<(int)str.length() ) ++ { ++ unsigned char c = (unsigned char) str[i]; ++ ++ if ( c == '&' ++ && i < ( (int)str.length() - 2 ) ++ && str[i+1] == '#' ++ && str[i+2] == 'x' ) ++ { ++ // Hexadecimal character reference. ++ // Pass through unchanged. ++ // © -- copyright symbol, for example. ++ // ++ // The -1 is a bug fix from Rob Laveaux. It keeps ++ // an overflow from happening if there is no ';'. ++ // There are actually 2 ways to exit this loop - ++ // while fails (error case) and break (semicolon found). ++ // However, there is no mechanism (currently) for ++ // this function to return an error. ++ while ( i<(int)str.length()-1 ) ++ { ++ outString->append( str.c_str() + i, 1 ); ++ ++i; ++ if ( str[i] == ';' ) ++ break; ++ } ++ } ++ else if ( c == '&' ) ++ { ++ outString->append( entity[0].str, entity[0].strLength ); ++ ++i; ++ } ++ else if ( c == '<' ) ++ { ++ outString->append( entity[1].str, entity[1].strLength ); ++ ++i; ++ } ++ else if ( c == '>' ) ++ { ++ outString->append( entity[2].str, entity[2].strLength ); ++ ++i; ++ } ++ else if ( c == '\"' ) ++ { ++ outString->append( entity[3].str, entity[3].strLength ); ++ ++i; ++ } ++ else if ( c == '\'' ) ++ { ++ outString->append( entity[4].str, entity[4].strLength ); ++ ++i; ++ } ++ else if ( c < 32 ) ++ { ++ // Easy pass at non-alpha/numeric/symbol ++ // Below 32 is symbolic. ++ char buf[ 32 ]; ++ sprintf( buf, "&#x%02X;", (unsigned) ( c & 0xff ) ); ++ outString->append( buf, strlen( buf ) ); ++ ++i; ++ } ++ else ++ { ++ //char realc = (char) c; ++ //outString->append( &realc, 1 ); ++ *outString += (char) c; // somewhat more efficient function call. ++ ++i; ++ } ++ } ++} ++ ++ ++// <-- Strange class for a bug fix. Search for STL_STRING_BUG ++TiXmlBase::StringToBuffer::StringToBuffer( const TIXML_STRING& str ) ++{ ++ buffer = new char[ str.length()+1 ]; ++ if ( buffer ) ++ { ++ strcpy( buffer, str.c_str() ); ++ } ++} ++ ++ ++TiXmlBase::StringToBuffer::~StringToBuffer() ++{ ++ delete [] buffer; ++} ++// End strange bug fix. --> ++ ++ ++TiXmlNode::TiXmlNode( NodeType _type ) : TiXmlBase() ++{ ++ parent = 0; ++ type = _type; ++ firstChild = 0; ++ lastChild = 0; ++ prev = 0; ++ next = 0; ++} ++ ++ ++TiXmlNode::~TiXmlNode() ++{ ++ TiXmlNode* node = firstChild; ++ TiXmlNode* temp = 0; ++ ++ while ( node ) ++ { ++ temp = node; ++ node = node->next; ++ delete temp; ++ } ++} ++ ++ ++void TiXmlNode::CopyTo( TiXmlNode* target ) const ++{ ++ target->SetValue (value.c_str() ); ++ target->userData = userData; ++} ++ ++ ++void TiXmlNode::Clear() ++{ ++ TiXmlNode* node = firstChild; ++ TiXmlNode* temp = 0; ++ ++ while ( node ) ++ { ++ temp = node; ++ node = node->next; ++ delete temp; ++ } ++ ++ firstChild = 0; ++ lastChild = 0; ++} ++ ++ ++TiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node ) ++{ ++ node->parent = this; ++ ++ node->prev = lastChild; ++ node->next = 0; ++ ++ if ( lastChild ) ++ lastChild->next = node; ++ else ++ firstChild = node; // it was an empty list. ++ ++ lastChild = node; ++ return node; ++} ++ ++ ++TiXmlNode* TiXmlNode::InsertEndChild( const TiXmlNode& addThis ) ++{ ++ TiXmlNode* node = addThis.Clone(); ++ if ( !node ) ++ return 0; ++ ++ return LinkEndChild( node ); ++} ++ ++ ++TiXmlNode* TiXmlNode::InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ) ++{ ++ if ( !beforeThis || beforeThis->parent != this ) ++ return 0; ++ ++ TiXmlNode* node = addThis.Clone(); ++ if ( !node ) ++ return 0; ++ node->parent = this; ++ ++ node->next = beforeThis; ++ node->prev = beforeThis->prev; ++ if ( beforeThis->prev ) ++ { ++ beforeThis->prev->next = node; ++ } ++ else ++ { ++ assert( firstChild == beforeThis ); ++ firstChild = node; ++ } ++ beforeThis->prev = node; ++ return node; ++} ++ ++ ++TiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ) ++{ ++ if ( !afterThis || afterThis->parent != this ) ++ return 0; ++ ++ TiXmlNode* node = addThis.Clone(); ++ if ( !node ) ++ return 0; ++ node->parent = this; ++ ++ node->prev = afterThis; ++ node->next = afterThis->next; ++ if ( afterThis->next ) ++ { ++ afterThis->next->prev = node; ++ } ++ else ++ { ++ assert( lastChild == afterThis ); ++ lastChild = node; ++ } ++ afterThis->next = node; ++ return node; ++} ++ ++ ++TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ) ++{ ++ if ( replaceThis->parent != this ) ++ return 0; ++ ++ TiXmlNode* node = withThis.Clone(); ++ if ( !node ) ++ return 0; ++ ++ node->next = replaceThis->next; ++ node->prev = replaceThis->prev; ++ ++ if ( replaceThis->next ) ++ replaceThis->next->prev = node; ++ else ++ lastChild = node; ++ ++ if ( replaceThis->prev ) ++ replaceThis->prev->next = node; ++ else ++ firstChild = node; ++ ++ delete replaceThis; ++ node->parent = this; ++ return node; ++} ++ ++ ++bool TiXmlNode::RemoveChild( TiXmlNode* removeThis ) ++{ ++ if ( removeThis->parent != this ) ++ { ++ assert( 0 ); ++ return false; ++ } ++ ++ if ( removeThis->next ) ++ removeThis->next->prev = removeThis->prev; ++ else ++ lastChild = removeThis->prev; ++ ++ if ( removeThis->prev ) ++ removeThis->prev->next = removeThis->next; ++ else ++ firstChild = removeThis->next; ++ ++ delete removeThis; ++ return true; ++} ++ ++TiXmlNode* TiXmlNode::FirstChild( const char * _value ) const ++{ ++ TiXmlNode* node; ++ for ( node = firstChild; node; node = node->next ) ++ { ++ if ( node->SValue() == TIXML_STRING( _value )) ++ return node; ++ } ++ return 0; ++} ++ ++TiXmlNode* TiXmlNode::LastChild( const char * _value ) const ++{ ++ TiXmlNode* node; ++ for ( node = lastChild; node; node = node->prev ) ++ { ++ if ( node->SValue() == TIXML_STRING (_value)) ++ return node; ++ } ++ return 0; ++} ++ ++TiXmlNode* TiXmlNode::IterateChildren( TiXmlNode* previous ) const ++{ ++ if ( !previous ) ++ { ++ return FirstChild(); ++ } ++ else ++ { ++ assert( previous->parent == this ); ++ return previous->NextSibling(); ++ } ++} ++ ++TiXmlNode* TiXmlNode::IterateChildren( const char * val, TiXmlNode* previous ) const ++{ ++ if ( !previous ) ++ { ++ return FirstChild( val ); ++ } ++ else ++ { ++ assert( previous->parent == this ); ++ return previous->NextSibling( val ); ++ } ++} ++ ++TiXmlNode* TiXmlNode::NextSibling( const char * _value ) const ++{ ++ TiXmlNode* node; ++ for ( node = next; node; node = node->next ) ++ { ++ if ( node->SValue() == TIXML_STRING (_value)) ++ return node; ++ } ++ return 0; ++} ++ ++ ++TiXmlNode* TiXmlNode::PreviousSibling( const char * _value ) const ++{ ++ TiXmlNode* node; ++ for ( node = prev; node; node = node->prev ) ++ { ++ if ( node->SValue() == TIXML_STRING (_value)) ++ return node; ++ } ++ return 0; ++} ++ ++void TiXmlElement::RemoveAttribute( const char * name ) ++{ ++ TiXmlAttribute* node = attributeSet.Find( name ); ++ if ( node ) ++ { ++ attributeSet.Remove( node ); ++ delete node; ++ } ++} ++ ++TiXmlElement* TiXmlNode::FirstChildElement() const ++{ ++ TiXmlNode* node; ++ ++ for ( node = FirstChild(); ++ node; ++ node = node->NextSibling() ) ++ { ++ if ( node->ToElement() ) ++ return node->ToElement(); ++ } ++ return 0; ++} ++ ++TiXmlElement* TiXmlNode::FirstChildElement( const char * _value ) const ++{ ++ TiXmlNode* node; ++ ++ for ( node = FirstChild( _value ); ++ node; ++ node = node->NextSibling( _value ) ) ++ { ++ if ( node->ToElement() ) ++ return node->ToElement(); ++ } ++ return 0; ++} ++ ++ ++TiXmlElement* TiXmlNode::NextSiblingElement() const ++{ ++ TiXmlNode* node; ++ ++ for ( node = NextSibling(); ++ node; ++ node = node->NextSibling() ) ++ { ++ if ( node->ToElement() ) ++ return node->ToElement(); ++ } ++ return 0; ++} ++ ++TiXmlElement* TiXmlNode::NextSiblingElement( const char * _value ) const ++{ ++ TiXmlNode* node; ++ ++ for ( node = NextSibling( _value ); ++ node; ++ node = node->NextSibling( _value ) ) ++ { ++ if ( node->ToElement() ) ++ return node->ToElement(); ++ } ++ return 0; ++} ++ ++ ++ ++TiXmlDocument* TiXmlNode::GetDocument() const ++{ ++ const TiXmlNode* node; ++ ++ for( node = this; node; node = node->parent ) ++ { ++ if ( node->ToDocument() ) ++ return node->ToDocument(); ++ } ++ return 0; ++} ++ ++ ++TiXmlElement::TiXmlElement (const char * _value) ++ : TiXmlNode( TiXmlNode::ELEMENT ) ++{ ++ firstChild = lastChild = 0; ++ value = _value; ++} ++ ++ ++#ifdef TIXML_USE_STL ++TiXmlElement::TiXmlElement( const std::string& _value ) ++ : TiXmlNode( TiXmlNode::ELEMENT ) ++{ ++ firstChild = lastChild = 0; ++ value = _value; ++} ++#endif ++ ++ ++TiXmlElement::TiXmlElement( const TiXmlElement& copy) ++ : TiXmlNode( TiXmlNode::ELEMENT ) ++{ ++ firstChild = lastChild = 0; ++ copy.CopyTo( this ); ++} ++ ++ ++void TiXmlElement::operator=( const TiXmlElement& base ) ++{ ++ ClearThis(); ++ base.CopyTo( this ); ++} ++ ++ ++TiXmlElement::~TiXmlElement() ++{ ++ ClearThis(); ++} ++ ++ ++void TiXmlElement::ClearThis() ++{ ++ Clear(); ++ while( attributeSet.First() ) ++ { ++ TiXmlAttribute* node = attributeSet.First(); ++ attributeSet.Remove( node ); ++ delete node; ++ } ++} ++ ++ ++const char * TiXmlElement::Attribute( const char * name ) const ++{ ++ TiXmlAttribute* node = attributeSet.Find( name ); ++ ++ if ( node ) ++ return node->Value(); ++ ++ return 0; ++} ++ ++ ++const char * TiXmlElement::Attribute( const char * name, int* i ) const ++{ ++ const char * s = Attribute( name ); ++ if ( i ) ++ { ++ if ( s ) ++ *i = atoi( s ); ++ else ++ *i = 0; ++ } ++ return s; ++} ++ ++ ++const char * TiXmlElement::Attribute( const char * name, double* d ) const ++{ ++ const char * s = Attribute( name ); ++ if ( d ) ++ { ++ if ( s ) ++ *d = atof( s ); ++ else ++ *d = 0; ++ } ++ return s; ++} ++ ++ ++int TiXmlElement::QueryIntAttribute( const char* name, int* ival ) const ++{ ++ TiXmlAttribute* node = attributeSet.Find( name ); ++ if ( !node ) ++ return TIXML_NO_ATTRIBUTE; ++ ++ return node->QueryIntValue( ival ); ++} ++ ++ ++int TiXmlElement::QueryDoubleAttribute( const char* name, double* dval ) const ++{ ++ TiXmlAttribute* node = attributeSet.Find( name ); ++ if ( !node ) ++ return TIXML_NO_ATTRIBUTE; ++ ++ return node->QueryDoubleValue( dval ); ++} ++ ++ ++void TiXmlElement::SetAttribute( const char * name, int val ) ++{ ++ char buf[64]; ++ sprintf( buf, "%d", val ); ++ SetAttribute( name, buf ); ++} ++ ++ ++void TiXmlElement::SetDoubleAttribute( const char * name, double val ) ++{ ++ char buf[128]; ++ sprintf( buf, "%f", val ); ++ SetAttribute( name, buf ); ++} ++ ++ ++void TiXmlElement::SetAttribute( const char * name, const char * _value ) ++{ ++ TiXmlAttribute* node = attributeSet.Find( name ); ++ if ( node ) ++ { ++ node->SetValue( _value ); ++ return; ++ } ++ ++ TiXmlAttribute* attrib = new TiXmlAttribute( name, _value ); ++ if ( attrib ) ++ { ++ attributeSet.Add( attrib ); ++ } ++ else ++ { ++ TiXmlDocument* document = GetDocument(); ++ if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN ); ++ } ++} ++ ++void TiXmlElement::Print( FILE* cfile, int depth ) const ++{ ++ int i; ++ for ( i=0; iNext() ) ++ { ++ fprintf( cfile, " " ); ++ attrib->Print( cfile, depth ); ++ } ++ ++ // There are 3 different formatting approaches: ++ // 1) An element without children is printed as a node ++ // 2) An element with only a text child is printed as text ++ // 3) An element with children is printed on multiple lines. ++ TiXmlNode* node; ++ if ( !firstChild ) ++ { ++ fprintf( cfile, " />" ); ++ } ++ else if ( firstChild == lastChild && firstChild->ToText() ) ++ { ++ fprintf( cfile, ">" ); ++ firstChild->Print( cfile, depth + 1 ); ++ fprintf( cfile, "", value.c_str() ); ++ } ++ else ++ { ++ fprintf( cfile, ">" ); ++ ++ for ( node = firstChild; node; node=node->NextSibling() ) ++ { ++ if ( !node->ToText() ) ++ { ++ fprintf( cfile, "\n" ); ++ } ++ node->Print( cfile, depth+1 ); ++ } ++ fprintf( cfile, "\n" ); ++ for( i=0; i", value.c_str() ); ++ } ++} ++ ++void TiXmlElement::StreamOut( TIXML_OSTREAM * stream ) const ++{ ++ (*stream) << "<" << value; ++ ++ TiXmlAttribute* attrib; ++ for ( attrib = attributeSet.First(); attrib; attrib = attrib->Next() ) ++ { ++ (*stream) << " "; ++ attrib->StreamOut( stream ); ++ } ++ ++ // If this node has children, give it a closing tag. Else ++ // make it an empty tag. ++ TiXmlNode* node; ++ if ( firstChild ) ++ { ++ (*stream) << ">"; ++ ++ for ( node = firstChild; node; node=node->NextSibling() ) ++ { ++ node->StreamOut( stream ); ++ } ++ (*stream) << ""; ++ } ++ else ++ { ++ (*stream) << " />"; ++ } ++} ++ ++ ++void TiXmlElement::CopyTo( TiXmlElement* target ) const ++{ ++ // superclass: ++ TiXmlNode::CopyTo( target ); ++ ++ // Element class: ++ // Clone the attributes, then clone the children. ++ TiXmlAttribute* attribute = 0; ++ for( attribute = attributeSet.First(); ++ attribute; ++ attribute = attribute->Next() ) ++ { ++ target->SetAttribute( attribute->Name(), attribute->Value() ); ++ } ++ ++ TiXmlNode* node = 0; ++ for ( node = firstChild; node; node = node->NextSibling() ) ++ { ++ target->LinkEndChild( node->Clone() ); ++ } ++} ++ ++ ++TiXmlNode* TiXmlElement::Clone() const ++{ ++ TiXmlElement* clone = new TiXmlElement( Value() ); ++ if ( !clone ) ++ return 0; ++ ++ CopyTo( clone ); ++ return clone; ++} ++ ++ ++TiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::DOCUMENT ) ++{ ++ tabsize = 4; ++ ClearError(); ++} ++ ++TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::DOCUMENT ) ++{ ++ tabsize = 4; ++ value = documentName; ++ ClearError(); ++} ++ ++ ++#ifdef TIXML_USE_STL ++TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::DOCUMENT ) ++{ ++ tabsize = 4; ++ value = documentName; ++ ClearError(); ++} ++#endif ++ ++ ++TiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::DOCUMENT ) ++{ ++ copy.CopyTo( this ); ++} ++ ++ ++void TiXmlDocument::operator=( const TiXmlDocument& copy ) ++{ ++ Clear(); ++ copy.CopyTo( this ); ++} ++ ++ ++bool TiXmlDocument::LoadFile( TiXmlEncoding encoding ) ++{ ++ // See STL_STRING_BUG below. ++ StringToBuffer buf( value ); ++ ++ if ( buf.buffer && LoadFile( buf.buffer, encoding ) ) ++ return true; ++ ++ return false; ++} ++ ++ ++bool TiXmlDocument::SaveFile() const ++{ ++ // See STL_STRING_BUG below. ++ StringToBuffer buf( value ); ++ ++ if ( buf.buffer && SaveFile( buf.buffer ) ) ++ return true; ++ ++ return false; ++} ++ ++bool TiXmlDocument::LoadFile( const char* filename, TiXmlEncoding encoding ) ++{ ++ // Delete the existing data: ++ Clear(); ++ location.Clear(); ++ ++ // There was a really terrifying little bug here. The code: ++ // value = filename ++ // in the STL case, cause the assignment method of the std::string to ++ // be called. What is strange, is that the std::string had the same ++ // address as it's c_str() method, and so bad things happen. Looks ++ // like a bug in the Microsoft STL implementation. ++ // See STL_STRING_BUG above. ++ // Fixed with the StringToBuffer class. ++ value = filename; ++ ++ FILE* file = fopen( value.c_str (), "r" ); ++ ++ if ( file ) ++ { ++ // Get the file size, so we can pre-allocate the string. HUGE speed impact. ++ long length = 0; ++ fseek( file, 0, SEEK_END ); ++ length = ftell( file ); ++ fseek( file, 0, SEEK_SET ); ++ ++ // Strange case, but good to handle up front. ++ if ( length == 0 ) ++ { ++ fclose( file ); ++ return false; ++ } ++ ++ // If we have a file, assume it is all one big XML file, and read it in. ++ // The document parser may decide the document ends sooner than the entire file, however. ++ TIXML_STRING data; ++ data.reserve( length ); ++ ++ const int BUF_SIZE = 2048; ++ char buf[BUF_SIZE]; ++ ++ while( fgets( buf, BUF_SIZE, file ) ) ++ { ++ data += buf; ++ } ++ fclose( file ); ++ ++ Parse( data.c_str(), 0, encoding ); ++ ++ if ( Error() ) ++ return false; ++ else ++ return true; ++ } ++ SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); ++ return false; ++} ++ ++bool TiXmlDocument::SaveFile( const char * filename ) const ++{ ++ // The old c stuff lives on... ++ FILE* fp = fopen( filename, "w" ); ++ if ( fp ) ++ { ++ Print( fp, 0 ); ++ fclose( fp ); ++ return true; ++ } ++ return false; ++} ++ ++ ++void TiXmlDocument::CopyTo( TiXmlDocument* target ) const ++{ ++ TiXmlNode::CopyTo( target ); ++ ++ target->error = error; ++ target->errorDesc = errorDesc.c_str (); ++ ++ TiXmlNode* node = 0; ++ for ( node = firstChild; node; node = node->NextSibling() ) ++ { ++ target->LinkEndChild( node->Clone() ); ++ } ++} ++ ++ ++TiXmlNode* TiXmlDocument::Clone() const ++{ ++ TiXmlDocument* clone = new TiXmlDocument(); ++ if ( !clone ) ++ return 0; ++ ++ CopyTo( clone ); ++ return clone; ++} ++ ++ ++void TiXmlDocument::Print( FILE* cfile, int depth ) const ++{ ++ TiXmlNode* node; ++ for ( node=FirstChild(); node; node=node->NextSibling() ) ++ { ++ node->Print( cfile, depth ); ++ fprintf( cfile, "\n" ); ++ } ++} ++ ++void TiXmlDocument::StreamOut( TIXML_OSTREAM * out ) const ++{ ++ TiXmlNode* node; ++ for ( node=FirstChild(); node; node=node->NextSibling() ) ++ { ++ node->StreamOut( out ); ++ ++ // Special rule for streams: stop after the root element. ++ // The stream in code will only read one element, so don't ++ // write more than one. ++ if ( node->ToElement() ) ++ break; ++ } ++} ++ ++ ++TiXmlAttribute* TiXmlAttribute::Next() const ++{ ++ // We are using knowledge of the sentinel. The sentinel ++ // have a value or name. ++ if ( next->value.empty() && next->name.empty() ) ++ return 0; ++ return next; ++} ++ ++ ++TiXmlAttribute* TiXmlAttribute::Previous() const ++{ ++ // We are using knowledge of the sentinel. The sentinel ++ // have a value or name. ++ if ( prev->value.empty() && prev->name.empty() ) ++ return 0; ++ return prev; ++} ++ ++ ++void TiXmlAttribute::Print( FILE* cfile, int /*depth*/ ) const ++{ ++ TIXML_STRING n, v; ++ ++ PutString( name, &n ); ++ PutString( value, &v ); ++ ++ if (value.find ('\"') == TIXML_STRING::npos) ++ fprintf (cfile, "%s=\"%s\"", n.c_str(), v.c_str() ); ++ else ++ fprintf (cfile, "%s='%s'", n.c_str(), v.c_str() ); ++} ++ ++ ++void TiXmlAttribute::StreamOut( TIXML_OSTREAM * stream ) const ++{ ++ if (value.find( '\"' ) != TIXML_STRING::npos) ++ { ++ PutString( name, stream ); ++ (*stream) << "=" << "'"; ++ PutString( value, stream ); ++ (*stream) << "'"; ++ } ++ else ++ { ++ PutString( name, stream ); ++ (*stream) << "=" << "\""; ++ PutString( value, stream ); ++ (*stream) << "\""; ++ } ++} ++ ++int TiXmlAttribute::QueryIntValue( int* ival ) const ++{ ++ if ( sscanf( value.c_str(), "%d", ival ) == 1 ) ++ return TIXML_SUCCESS; ++ return TIXML_WRONG_TYPE; ++} ++ ++int TiXmlAttribute::QueryDoubleValue( double* dval ) const ++{ ++ if ( sscanf( value.c_str(), "%lf", dval ) == 1 ) ++ return TIXML_SUCCESS; ++ return TIXML_WRONG_TYPE; ++} ++ ++void TiXmlAttribute::SetIntValue( int _value ) ++{ ++ char buf [64]; ++ sprintf (buf, "%d", _value); ++ SetValue (buf); ++} ++ ++void TiXmlAttribute::SetDoubleValue( double _value ) ++{ ++ char buf [64]; ++ sprintf (buf, "%lf", _value); ++ SetValue (buf); ++} ++ ++const int TiXmlAttribute::IntValue() const ++{ ++ return atoi (value.c_str ()); ++} ++ ++const double TiXmlAttribute::DoubleValue() const ++{ ++ return atof (value.c_str ()); ++} ++ ++ ++TiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::COMMENT ) ++{ ++ copy.CopyTo( this ); ++} ++ ++ ++void TiXmlComment::operator=( const TiXmlComment& base ) ++{ ++ Clear(); ++ base.CopyTo( this ); ++} ++ ++ ++void TiXmlComment::Print( FILE* cfile, int depth ) const ++{ ++ for ( int i=0; i", value.c_str() ); ++} ++ ++void TiXmlComment::StreamOut( TIXML_OSTREAM * stream ) const ++{ ++ (*stream) << ""; ++} ++ ++ ++void TiXmlComment::CopyTo( TiXmlComment* target ) const ++{ ++ TiXmlNode::CopyTo( target ); ++} ++ ++ ++TiXmlNode* TiXmlComment::Clone() const ++{ ++ TiXmlComment* clone = new TiXmlComment(); ++ ++ if ( !clone ) ++ return 0; ++ ++ CopyTo( clone ); ++ return clone; ++} ++ ++ ++void TiXmlText::Print( FILE* cfile, int /*depth*/ ) const ++{ ++ TIXML_STRING buffer; ++ PutString( value, &buffer ); ++ fprintf( cfile, "%s", buffer.c_str() ); ++} ++ ++ ++void TiXmlText::StreamOut( TIXML_OSTREAM * stream ) const ++{ ++ PutString( value, stream ); ++} ++ ++ ++void TiXmlText::CopyTo( TiXmlText* target ) const ++{ ++ TiXmlNode::CopyTo( target ); ++} ++ ++ ++TiXmlNode* TiXmlText::Clone() const ++{ ++ TiXmlText* clone = 0; ++ clone = new TiXmlText( "" ); ++ ++ if ( !clone ) ++ return 0; ++ ++ CopyTo( clone ); ++ return clone; ++} ++ ++ ++TiXmlDeclaration::TiXmlDeclaration( const char * _version, ++ const char * _encoding, ++ const char * _standalone ) ++ : TiXmlNode( TiXmlNode::DECLARATION ) ++{ ++ version = _version; ++ encoding = _encoding; ++ standalone = _standalone; ++} ++ ++ ++#ifdef TIXML_USE_STL ++TiXmlDeclaration::TiXmlDeclaration( const std::string& _version, ++ const std::string& _encoding, ++ const std::string& _standalone ) ++ : TiXmlNode( TiXmlNode::DECLARATION ) ++{ ++ version = _version; ++ encoding = _encoding; ++ standalone = _standalone; ++} ++#endif ++ ++ ++TiXmlDeclaration::TiXmlDeclaration( const TiXmlDeclaration& copy ) ++ : TiXmlNode( TiXmlNode::DECLARATION ) ++{ ++ copy.CopyTo( this ); ++} ++ ++ ++void TiXmlDeclaration::operator=( const TiXmlDeclaration& copy ) ++{ ++ Clear(); ++ copy.CopyTo( this ); ++} ++ ++ ++void TiXmlDeclaration::Print( FILE* cfile, int /*depth*/ ) const ++{ ++ fprintf (cfile, ""); ++} ++ ++void TiXmlDeclaration::StreamOut( TIXML_OSTREAM * stream ) const ++{ ++ (*stream) << ""; ++} ++ ++ ++void TiXmlDeclaration::CopyTo( TiXmlDeclaration* target ) const ++{ ++ TiXmlNode::CopyTo( target ); ++ ++ target->version = version; ++ target->encoding = encoding; ++ target->standalone = standalone; ++} ++ ++ ++TiXmlNode* TiXmlDeclaration::Clone() const ++{ ++ TiXmlDeclaration* clone = new TiXmlDeclaration(); ++ ++ if ( !clone ) ++ return 0; ++ ++ CopyTo( clone ); ++ return clone; ++} ++ ++ ++void TiXmlUnknown::Print( FILE* cfile, int depth ) const ++{ ++ for ( int i=0; i", value.c_str() ); ++} ++ ++ ++void TiXmlUnknown::StreamOut( TIXML_OSTREAM * stream ) const ++{ ++ (*stream) << "<" << value << ">"; // Don't use entities here! It is unknown. ++} ++ ++ ++void TiXmlUnknown::CopyTo( TiXmlUnknown* target ) const ++{ ++ TiXmlNode::CopyTo( target ); ++} ++ ++ ++TiXmlNode* TiXmlUnknown::Clone() const ++{ ++ TiXmlUnknown* clone = new TiXmlUnknown(); ++ ++ if ( !clone ) ++ return 0; ++ ++ CopyTo( clone ); ++ return clone; ++} ++ ++ ++TiXmlAttributeSet::TiXmlAttributeSet() ++{ ++ sentinel.next = &sentinel; ++ sentinel.prev = &sentinel; ++} ++ ++ ++TiXmlAttributeSet::~TiXmlAttributeSet() ++{ ++ assert( sentinel.next == &sentinel ); ++ assert( sentinel.prev == &sentinel ); ++} ++ ++ ++void TiXmlAttributeSet::Add( TiXmlAttribute* addMe ) ++{ ++ assert( !Find( addMe->Name() ) ); // Shouldn't be multiply adding to the set. ++ ++ addMe->next = &sentinel; ++ addMe->prev = sentinel.prev; ++ ++ sentinel.prev->next = addMe; ++ sentinel.prev = addMe; ++} ++ ++void TiXmlAttributeSet::Remove( TiXmlAttribute* removeMe ) ++{ ++ TiXmlAttribute* node; ++ ++ for( node = sentinel.next; node != &sentinel; node = node->next ) ++ { ++ if ( node == removeMe ) ++ { ++ node->prev->next = node->next; ++ node->next->prev = node->prev; ++ node->next = 0; ++ node->prev = 0; ++ return; ++ } ++ } ++ assert( 0 ); // we tried to remove a non-linked attribute. ++} ++ ++TiXmlAttribute* TiXmlAttributeSet::Find( const char * name ) const ++{ ++ TiXmlAttribute* node; ++ ++ for( node = sentinel.next; node != &sentinel; node = node->next ) ++ { ++ if ( node->name == name ) ++ return node; ++ } ++ return 0; ++} ++ ++ ++#ifdef TIXML_USE_STL ++TIXML_ISTREAM & operator >> (TIXML_ISTREAM & in, TiXmlNode & base) ++{ ++ TIXML_STRING tag; ++ tag.reserve( 8 * 1000 ); ++ base.StreamIn( &in, &tag ); ++ ++ base.Parse( tag.c_str(), 0, TIXML_DEFAULT_ENCODING ); ++ return in; ++} ++#endif ++ ++ ++TIXML_OSTREAM & operator<< (TIXML_OSTREAM & out, const TiXmlNode & base) ++{ ++ base.StreamOut (& out); ++ return out; ++} ++ ++ ++#ifdef TIXML_USE_STL ++std::string & operator<< (std::string& out, const TiXmlNode& base ) ++{ ++ std::ostringstream os_stream( std::ostringstream::out ); ++ base.StreamOut( &os_stream ); ++ ++ out.append( os_stream.str() ); ++ return out; ++} ++#endif ++ ++ ++TiXmlHandle TiXmlHandle::FirstChild() const ++{ ++ if ( node ) ++ { ++ TiXmlNode* child = node->FirstChild(); ++ if ( child ) ++ return TiXmlHandle( child ); ++ } ++ return TiXmlHandle( 0 ); ++} ++ ++ ++TiXmlHandle TiXmlHandle::FirstChild( const char * value ) const ++{ ++ if ( node ) ++ { ++ TiXmlNode* child = node->FirstChild( value ); ++ if ( child ) ++ return TiXmlHandle( child ); ++ } ++ return TiXmlHandle( 0 ); ++} ++ ++ ++TiXmlHandle TiXmlHandle::FirstChildElement() const ++{ ++ if ( node ) ++ { ++ TiXmlElement* child = node->FirstChildElement(); ++ if ( child ) ++ return TiXmlHandle( child ); ++ } ++ return TiXmlHandle( 0 ); ++} ++ ++ ++TiXmlHandle TiXmlHandle::FirstChildElement( const char * value ) const ++{ ++ if ( node ) ++ { ++ TiXmlElement* child = node->FirstChildElement( value ); ++ if ( child ) ++ return TiXmlHandle( child ); ++ } ++ return TiXmlHandle( 0 ); ++} ++ ++ ++TiXmlHandle TiXmlHandle::Child( int count ) const ++{ ++ if ( node ) ++ { ++ int i; ++ TiXmlNode* child = node->FirstChild(); ++ for ( i=0; ++ child && iNextSibling(), ++i ) ++ { ++ // nothing ++ } ++ if ( child ) ++ return TiXmlHandle( child ); ++ } ++ return TiXmlHandle( 0 ); ++} ++ ++ ++TiXmlHandle TiXmlHandle::Child( const char* value, int count ) const ++{ ++ if ( node ) ++ { ++ int i; ++ TiXmlNode* child = node->FirstChild( value ); ++ for ( i=0; ++ child && iNextSibling( value ), ++i ) ++ { ++ // nothing ++ } ++ if ( child ) ++ return TiXmlHandle( child ); ++ } ++ return TiXmlHandle( 0 ); ++} ++ ++ ++TiXmlHandle TiXmlHandle::ChildElement( int count ) const ++{ ++ if ( node ) ++ { ++ int i; ++ TiXmlElement* child = node->FirstChildElement(); ++ for ( i=0; ++ child && iNextSiblingElement(), ++i ) ++ { ++ // nothing ++ } ++ if ( child ) ++ return TiXmlHandle( child ); ++ } ++ return TiXmlHandle( 0 ); ++} ++ ++ ++TiXmlHandle TiXmlHandle::ChildElement( const char* value, int count ) const ++{ ++ if ( node ) ++ { ++ int i; ++ TiXmlElement* child = node->FirstChildElement( value ); ++ for ( i=0; ++ child && iNextSiblingElement( value ), ++i ) ++ { ++ // nothing ++ } ++ if ( child ) ++ return TiXmlHandle( child ); ++ } ++ return TiXmlHandle( 0 ); ++} ++#endif /* SETUP */ +diff -NaurwB vdr-1.7.10/tinyxmlerror.c vdr-1.7.10-patched/tinyxmlerror.c +--- vdr-1.7.10/tinyxmlerror.c 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/tinyxmlerror.c 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,53 @@ ++#ifdef USE_SETUP ++/* ++www.sourceforge.net/projects/tinyxml ++Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com) ++ ++This software is provided 'as-is', without any express or implied ++warranty. In no event will the authors be held liable for any ++damages arising from the use of this software. ++ ++Permission is granted to anyone to use this software for any ++purpose, including commercial applications, and to alter it and ++redistribute it freely, subject to the following restrictions: ++ ++1. The origin of this software must not be misrepresented; you must ++not claim that you wrote the original software. If you use this ++software in a product, an acknowledgment in the product documentation ++would be appreciated but is not required. ++ ++2. Altered source versions must be plainly marked as such, and ++must not be misrepresented as being the original software. ++ ++3. This notice may not be removed or altered from any source ++distribution. ++*/ ++ ++#include "tinyxml.h" ++ ++// The goal of the seperate error file is to make the first ++// step towards localization. tinyxml (currently) only supports ++// latin-1, but at least the error messages could now be translated. ++// ++// It also cleans up the code a bit. ++// ++ ++const char* TiXmlBase::errorString[ TIXML_ERROR_STRING_COUNT ] = ++{ ++ "No error", ++ "Error", ++ "Failed to open file", ++ "Memory allocation failed.", ++ "Error parsing Element.", ++ "Failed to read Element name", ++ "Error reading Element value.", ++ "Error reading Attributes.", ++ "Error: empty tag.", ++ "Error reading end tag.", ++ "Error parsing Unknown.", ++ "Error parsing Comment.", ++ "Error parsing Declaration.", ++ "Error document empty.", ++ "Error null (0) or unexpected EOF found in input stream.", ++}; ++#endif /* SETUP */ +diff -NaurwB vdr-1.7.10/tinyxml.h vdr-1.7.10-patched/tinyxml.h +--- vdr-1.7.10/tinyxml.h 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/tinyxml.h 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,1372 @@ ++#ifdef USE_SETUP ++/* ++www.sourceforge.net/projects/tinyxml ++Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com) ++ ++This software is provided 'as-is', without any express or implied ++warranty. In no event will the authors be held liable for any ++damages arising from the use of this software. ++ ++Permission is granted to anyone to use this software for any ++purpose, including commercial applications, and to alter it and ++redistribute it freely, subject to the following restrictions: ++ ++1. The origin of this software must not be misrepresented; you must ++not claim that you wrote the original software. If you use this ++software in a product, an acknowledgment in the product documentation ++would be appreciated but is not required. ++ ++2. Altered source versions must be plainly marked as such, and ++must not be misrepresented as being the original software. ++ ++3. This notice may not be removed or altered from any source ++distribution. ++*/ ++ ++ ++#ifndef TINYXML_INCLUDED ++#define TINYXML_INCLUDED ++ ++#ifdef _MSC_VER ++#pragma warning( disable : 4530 ) ++#pragma warning( disable : 4786 ) ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++ ++// Help out windows: ++#if defined( _DEBUG ) && !defined( DEBUG ) ++#define DEBUG ++#endif ++ ++#if defined( DEBUG ) && defined( _MSC_VER ) ++#include ++#define TIXML_LOG OutputDebugString ++#else ++#define TIXML_LOG printf ++#endif ++ ++#ifdef TIXML_USE_STL ++ #include ++ #include ++ #define TIXML_STRING std::string ++ #define TIXML_ISTREAM std::istream ++ #define TIXML_OSTREAM std::ostream ++#else ++ #include "tinystr.h" ++ #define TIXML_STRING TiXmlString ++ #define TIXML_OSTREAM TiXmlOutStream ++#endif ++ ++class TiXmlDocument; ++class TiXmlElement; ++class TiXmlComment; ++class TiXmlUnknown; ++class TiXmlAttribute; ++class TiXmlText; ++class TiXmlDeclaration; ++class TiXmlParsingData; ++ ++const int TIXML_MAJOR_VERSION = 2; ++const int TIXML_MINOR_VERSION = 3; ++const int TIXML_PATCH_VERSION = 2; ++ ++/* Internal structure for tracking location of items ++ in the XML file. ++*/ ++struct TiXmlCursor ++{ ++ TiXmlCursor() { Clear(); } ++ void Clear() { row = col = -1; } ++ ++ int row; // 0 based. ++ int col; // 0 based. ++}; ++ ++ ++// Only used by Attribute::Query functions ++enum ++{ ++ TIXML_SUCCESS, ++ TIXML_NO_ATTRIBUTE, ++ TIXML_WRONG_TYPE ++}; ++ ++ ++// Used by the parsing routines. ++enum TiXmlEncoding ++{ ++ TIXML_ENCODING_UNKNOWN, ++ TIXML_ENCODING_UTF8, ++ TIXML_ENCODING_LEGACY ++}; ++ ++const TiXmlEncoding TIXML_DEFAULT_ENCODING = TIXML_ENCODING_UNKNOWN; ++ ++/** TiXmlBase is a base class for every class in TinyXml. ++ It does little except to establish that TinyXml classes ++ can be printed and provide some utility functions. ++ ++ In XML, the document and elements can contain ++ other elements and other types of nodes. ++ ++ @verbatim ++ A Document can contain: Element (container or leaf) ++ Comment (leaf) ++ Unknown (leaf) ++ Declaration( leaf ) ++ ++ An Element can contain: Element (container or leaf) ++ Text (leaf) ++ Attributes (not on tree) ++ Comment (leaf) ++ Unknown (leaf) ++ ++ A Decleration contains: Attributes (not on tree) ++ @endverbatim ++*/ ++class TiXmlBase ++{ ++ friend class TiXmlNode; ++ friend class TiXmlElement; ++ friend class TiXmlDocument; ++ ++public: ++ TiXmlBase() : userData(0) {} ++ virtual ~TiXmlBase() {} ++ ++ /** All TinyXml classes can print themselves to a filestream. ++ This is a formatted print, and will insert tabs and newlines. ++ ++ (For an unformatted stream, use the << operator.) ++ */ ++ virtual void Print( FILE* cfile, int depth ) const = 0; ++ ++ /** The world does not agree on whether white space should be kept or ++ not. In order to make everyone happy, these global, static functions ++ are provided to set whether or not TinyXml will condense all white space ++ into a single space or not. The default is to condense. Note changing this ++ values is not thread safe. ++ */ ++ static void SetCondenseWhiteSpace( bool condense ) { condenseWhiteSpace = condense; } ++ ++ /// Return the current white space setting. ++ static bool IsWhiteSpaceCondensed() { return condenseWhiteSpace; } ++ ++ /** Return the position, in the original source file, of this node or attribute. ++ The row and column are 1-based. (That is the first row and first column is ++ 1,1). If the returns values are 0 or less, then the parser does not have ++ a row and column value. ++ ++ Generally, the row and column value will be set when the TiXmlDocument::Load(), ++ TiXmlDocument::LoadFile(), or any TiXmlNode::Parse() is called. It will NOT be set ++ when the DOM was created from operator>>. ++ ++ The values reflect the initial load. Once the DOM is modified programmatically ++ (by adding or changing nodes and attributes) the new values will NOT update to ++ reflect changes in the document. ++ ++ There is a minor performance cost to computing the row and column. Computation ++ can be disabled if TiXmlDocument::SetTabSize() is called with 0 as the value. ++ ++ @sa TiXmlDocument::SetTabSize() ++ */ ++ int Row() const { return location.row + 1; } ++ int Column() const { return location.col + 1; } ///< See Row() ++ ++ void SetUserData( void* user ) { userData = user; } ++ void* GetUserData() { return userData; } ++ ++ // Table that returs, for a given lead byte, the total number of bytes ++ // in the UTF-8 sequence. ++ static const int utf8ByteTable[256]; ++ ++ virtual const char* Parse( const char* p, ++ TiXmlParsingData* data, ++ TiXmlEncoding encoding /*= TIXML_ENCODING_UNKNOWN */ ) = 0; ++ ++protected: ++ ++ // See STL_STRING_BUG ++ // Utility class to overcome a bug. ++ class StringToBuffer ++ { ++ public: ++ StringToBuffer( const TIXML_STRING& str ); ++ ~StringToBuffer(); ++ char* buffer; ++ }; ++ ++ static const char* SkipWhiteSpace( const char*, TiXmlEncoding encoding ); ++ inline static bool IsWhiteSpace( char c ) ++ { ++ return ( isspace( (unsigned char) c ) || c == '\n' || c == '\r' ); ++ } ++ ++ virtual void StreamOut (TIXML_OSTREAM *) const = 0; ++ ++ #ifdef TIXML_USE_STL ++ static bool StreamWhiteSpace( TIXML_ISTREAM * in, TIXML_STRING * tag ); ++ static bool StreamTo( TIXML_ISTREAM * in, int character, TIXML_STRING * tag ); ++ #endif ++ ++ /* Reads an XML name into the string provided. Returns ++ a pointer just past the last character of the name, ++ or 0 if the function has an error. ++ */ ++ static const char* ReadName( const char* p, TIXML_STRING* name, TiXmlEncoding encoding ); ++ ++ /* Reads text. Returns a pointer past the given end tag. ++ Wickedly complex options, but it keeps the (sensitive) code in one place. ++ */ ++ static const char* ReadText( const char* in, // where to start ++ TIXML_STRING* text, // the string read ++ bool ignoreWhiteSpace, // whether to keep the white space ++ const char* endTag, // what ends this text ++ bool ignoreCase, // whether to ignore case in the end tag ++ TiXmlEncoding encoding ); // the current encoding ++ ++ // If an entity has been found, transform it into a character. ++ static const char* GetEntity( const char* in, char* value, int* length, TiXmlEncoding encoding ); ++ ++ // Get a character, while interpreting entities. ++ // The length can be from 0 to 4 bytes. ++ inline static const char* GetChar( const char* p, char* _value, int* length, TiXmlEncoding encoding ) ++ { ++ assert( p ); ++ if ( encoding == TIXML_ENCODING_UTF8 ) ++ { ++ *length = utf8ByteTable[ *((unsigned char*)p) ]; ++ assert( *length >= 0 && *length < 5 ); ++ } ++ else ++ { ++ *length = 1; ++ } ++ ++ if ( *length == 1 ) ++ { ++ if ( *p == '&' ) ++ return GetEntity( p, _value, length, encoding ); ++ *_value = *p; ++ return p+1; ++ } ++ else if ( *length ) ++ { ++ strncpy( _value, p, *length ); ++ return p + (*length); ++ } ++ else ++ { ++ // Not valid text. ++ return 0; ++ } ++ } ++ ++ // Puts a string to a stream, expanding entities as it goes. ++ // Note this should not contian the '<', '>', etc, or they will be transformed into entities! ++ static void PutString( const TIXML_STRING& str, TIXML_OSTREAM* out ); ++ ++ static void PutString( const TIXML_STRING& str, TIXML_STRING* out ); ++ ++ // Return true if the next characters in the stream are any of the endTag sequences. ++ // Ignore case only works for english, and should only be relied on when comparing ++ // to Engilish words: StringEqual( p, "version", true ) is fine. ++ static bool StringEqual( const char* p, ++ const char* endTag, ++ bool ignoreCase, ++ TiXmlEncoding encoding ); ++ ++ ++ enum ++ { ++ TIXML_NO_ERROR = 0, ++ TIXML_ERROR, ++ TIXML_ERROR_OPENING_FILE, ++ TIXML_ERROR_OUT_OF_MEMORY, ++ TIXML_ERROR_PARSING_ELEMENT, ++ TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, ++ TIXML_ERROR_READING_ELEMENT_VALUE, ++ TIXML_ERROR_READING_ATTRIBUTES, ++ TIXML_ERROR_PARSING_EMPTY, ++ TIXML_ERROR_READING_END_TAG, ++ TIXML_ERROR_PARSING_UNKNOWN, ++ TIXML_ERROR_PARSING_COMMENT, ++ TIXML_ERROR_PARSING_DECLARATION, ++ TIXML_ERROR_DOCUMENT_EMPTY, ++ TIXML_ERROR_EMBEDDED_NULL, ++ ++ TIXML_ERROR_STRING_COUNT ++ }; ++ static const char* errorString[ TIXML_ERROR_STRING_COUNT ]; ++ ++ TiXmlCursor location; ++ ++ /// Field containing a generic user pointer ++ void* userData; ++ ++ // None of these methods are reliable for any language except English. ++ // Good for approximation, not great for accuracy. ++ static int IsAlpha( unsigned char anyByte, TiXmlEncoding encoding ); ++ static int IsAlphaNum( unsigned char anyByte, TiXmlEncoding encoding ); ++ inline static int ToLower( int v, TiXmlEncoding encoding ) ++ { ++ if ( encoding == TIXML_ENCODING_UTF8 ) ++ { ++ if ( v < 128 ) return tolower( v ); ++ return v; ++ } ++ else ++ { ++ return tolower( v ); ++ } ++ } ++ static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ); ++ ++private: ++ TiXmlBase( const TiXmlBase& ); // not implemented. ++ void operator=( const TiXmlBase& base ); // not allowed. ++ ++ struct Entity ++ { ++ const char* str; ++ unsigned int strLength; ++ char chr; ++ }; ++ enum ++ { ++ NUM_ENTITY = 5, ++ MAX_ENTITY_LENGTH = 6 ++ ++ }; ++ static Entity entity[ NUM_ENTITY ]; ++ static bool condenseWhiteSpace; ++}; ++ ++ ++/** The parent class for everything in the Document Object Model. ++ (Except for attributes). ++ Nodes have siblings, a parent, and children. A node can be ++ in a document, or stand on its own. The type of a TiXmlNode ++ can be queried, and it can be cast to its more defined type. ++*/ ++class TiXmlNode : public TiXmlBase ++{ ++ friend class TiXmlDocument; ++ friend class TiXmlElement; ++ ++public: ++ #ifdef TIXML_USE_STL ++ ++ /** An input stream operator, for every class. Tolerant of newlines and ++ formatting, but doesn't expect them. ++ */ ++ friend std::istream& operator >> (std::istream& in, TiXmlNode& base); ++ ++ /** An output stream operator, for every class. Note that this outputs ++ without any newlines or formatting, as opposed to Print(), which ++ includes tabs and new lines. ++ ++ The operator<< and operator>> are not completely symmetric. Writing ++ a node to a stream is very well defined. You'll get a nice stream ++ of output, without any extra whitespace or newlines. ++ ++ But reading is not as well defined. (As it always is.) If you create ++ a TiXmlElement (for example) and read that from an input stream, ++ the text needs to define an element or junk will result. This is ++ true of all input streams, but it's worth keeping in mind. ++ ++ A TiXmlDocument will read nodes until it reads a root element, and ++ all the children of that root element. ++ */ ++ friend std::ostream& operator<< (std::ostream& out, const TiXmlNode& base); ++ ++ /// Appends the XML node or attribute to a std::string. ++ friend std::string& operator<< (std::string& out, const TiXmlNode& base ); ++ ++ #else ++ // Used internally, not part of the public API. ++ friend TIXML_OSTREAM& operator<< (TIXML_OSTREAM& out, const TiXmlNode& base); ++ #endif ++ ++ /** The types of XML nodes supported by TinyXml. (All the ++ unsupported types are picked up by UNKNOWN.) ++ */ ++ enum NodeType ++ { ++ DOCUMENT, ++ ELEMENT, ++ COMMENT, ++ UNKNOWN, ++ TEXT, ++ DECLARATION, ++ TYPECOUNT ++ }; ++ ++ virtual ~TiXmlNode(); ++ ++ /** The meaning of 'value' changes for the specific type of ++ TiXmlNode. ++ @verbatim ++ Document: filename of the xml file ++ Element: name of the element ++ Comment: the comment text ++ Unknown: the tag contents ++ Text: the text string ++ @endverbatim ++ ++ The subclasses will wrap this function. ++ */ ++ const char * Value() const { return value.c_str (); } ++ ++ /** Changes the value of the node. Defined as: ++ @verbatim ++ Document: filename of the xml file ++ Element: name of the element ++ Comment: the comment text ++ Unknown: the tag contents ++ Text: the text string ++ @endverbatim ++ */ ++ void SetValue(const char * _value) { value = _value;} ++ ++ #ifdef TIXML_USE_STL ++ /// STL std::string form. ++ void SetValue( const std::string& _value ) ++ { ++ StringToBuffer buf( _value ); ++ SetValue( buf.buffer ? buf.buffer : "" ); ++ } ++ #endif ++ ++ /// Delete all the children of this node. Does not affect 'this'. ++ void Clear(); ++ ++ /// One step up the DOM. ++ TiXmlNode* Parent() const { return parent; } ++ ++ TiXmlNode* FirstChild() const { return firstChild; } ///< The first child of this node. Will be null if there are no children. ++ TiXmlNode* FirstChild( const char * value ) const; ///< The first child of this node with the matching 'value'. Will be null if none found. ++ ++ TiXmlNode* LastChild() const { return lastChild; } /// The last child of this node. Will be null if there are no children. ++ TiXmlNode* LastChild( const char * value ) const; /// The last child of this node matching 'value'. Will be null if there are no children. ++ ++ #ifdef TIXML_USE_STL ++ TiXmlNode* FirstChild( const std::string& _value ) const { return FirstChild (_value.c_str ()); } ///< STL std::string form. ++ TiXmlNode* LastChild( const std::string& _value ) const { return LastChild (_value.c_str ()); } ///< STL std::string form. ++ #endif ++ ++ /** An alternate way to walk the children of a node. ++ One way to iterate over nodes is: ++ @verbatim ++ for( child = parent->FirstChild(); child; child = child->NextSibling() ) ++ @endverbatim ++ ++ IterateChildren does the same thing with the syntax: ++ @verbatim ++ child = 0; ++ while( child = parent->IterateChildren( child ) ) ++ @endverbatim ++ ++ IterateChildren takes the previous child as input and finds ++ the next one. If the previous child is null, it returns the ++ first. IterateChildren will return null when done. ++ */ ++ TiXmlNode* IterateChildren( TiXmlNode* previous ) const; ++ ++ /// This flavor of IterateChildren searches for children with a particular 'value' ++ TiXmlNode* IterateChildren( const char * value, TiXmlNode* previous ) const; ++ ++ #ifdef TIXML_USE_STL ++ TiXmlNode* IterateChildren( const std::string& _value, TiXmlNode* previous ) const { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. ++ #endif ++ ++ /** Add a new node related to this. Adds a child past the LastChild. ++ Returns a pointer to the new object or NULL if an error occured. ++ */ ++ TiXmlNode* InsertEndChild( const TiXmlNode& addThis ); ++ ++ ++ /** Add a new node related to this. Adds a child past the LastChild. ++ ++ NOTE: the node to be added is passed by pointer, and will be ++ henceforth owned (and deleted) by tinyXml. This method is efficient ++ and avoids an extra copy, but should be used with care as it ++ uses a different memory model than the other insert functions. ++ ++ @sa InsertEndChild ++ */ ++ TiXmlNode* LinkEndChild( TiXmlNode* addThis ); ++ ++ /** Add a new node related to this. Adds a child before the specified child. ++ Returns a pointer to the new object or NULL if an error occured. ++ */ ++ TiXmlNode* InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ); ++ ++ /** Add a new node related to this. Adds a child after the specified child. ++ Returns a pointer to the new object or NULL if an error occured. ++ */ ++ TiXmlNode* InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ); ++ ++ /** Replace a child of this node. ++ Returns a pointer to the new object or NULL if an error occured. ++ */ ++ TiXmlNode* ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ); ++ ++ /// Delete a child of this node. ++ bool RemoveChild( TiXmlNode* removeThis ); ++ ++ /// Navigate to a sibling node. ++ TiXmlNode* PreviousSibling() const { return prev; } ++ ++ /// Navigate to a sibling node. ++ TiXmlNode* PreviousSibling( const char * ) const; ++ ++ #ifdef TIXML_USE_STL ++ TiXmlNode* PreviousSibling( const std::string& _value ) const { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. ++ TiXmlNode* NextSibling( const std::string& _value) const { return NextSibling (_value.c_str ()); } ///< STL std::string form. ++ #endif ++ ++ /// Navigate to a sibling node. ++ TiXmlNode* NextSibling() const { return next; } ++ ++ /// Navigate to a sibling node with the given 'value'. ++ TiXmlNode* NextSibling( const char * ) const; ++ ++ /** Convenience function to get through elements. ++ Calls NextSibling and ToElement. Will skip all non-Element ++ nodes. Returns 0 if there is not another element. ++ */ ++ TiXmlElement* NextSiblingElement() const; ++ ++ /** Convenience function to get through elements. ++ Calls NextSibling and ToElement. Will skip all non-Element ++ nodes. Returns 0 if there is not another element. ++ */ ++ TiXmlElement* NextSiblingElement( const char * ) const; ++ ++ #ifdef TIXML_USE_STL ++ TiXmlElement* NextSiblingElement( const std::string& _value) const { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. ++ #endif ++ ++ /// Convenience function to get through elements. ++ TiXmlElement* FirstChildElement() const; ++ ++ /// Convenience function to get through elements. ++ TiXmlElement* FirstChildElement( const char * value ) const; ++ ++ #ifdef TIXML_USE_STL ++ TiXmlElement* FirstChildElement( const std::string& _value ) const { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. ++ #endif ++ ++ /** Query the type (as an enumerated value, above) of this node. ++ The possible types are: DOCUMENT, ELEMENT, COMMENT, ++ UNKNOWN, TEXT, and DECLARATION. ++ */ ++ virtual int Type() const { return type; } ++ ++ /** Return a pointer to the Document this node lives in. ++ Returns null if not in a document. ++ */ ++ TiXmlDocument* GetDocument() const; ++ ++ /// Returns true if this node has no children. ++ bool NoChildren() const { return !firstChild; } ++ ++ TiXmlDocument* ToDocument() const { return ( this && type == DOCUMENT ) ? (TiXmlDocument*) this : 0; } ///< Cast to a more defined type. Will return null not of the requested type. ++ TiXmlElement* ToElement() const { return ( this && type == ELEMENT ) ? (TiXmlElement*) this : 0; } ///< Cast to a more defined type. Will return null not of the requested type. ++ TiXmlComment* ToComment() const { return ( this && type == COMMENT ) ? (TiXmlComment*) this : 0; } ///< Cast to a more defined type. Will return null not of the requested type. ++ TiXmlUnknown* ToUnknown() const { return ( this && type == UNKNOWN ) ? (TiXmlUnknown*) this : 0; } ///< Cast to a more defined type. Will return null not of the requested type. ++ TiXmlText* ToText() const { return ( this && type == TEXT ) ? (TiXmlText*) this : 0; } ///< Cast to a more defined type. Will return null not of the requested type. ++ TiXmlDeclaration* ToDeclaration() const { return ( this && type == DECLARATION ) ? (TiXmlDeclaration*) this : 0; } ///< Cast to a more defined type. Will return null not of the requested type. ++ ++ /** Create an exact duplicate of this node and return it. The memory must be deleted ++ by the caller. ++ */ ++ virtual TiXmlNode* Clone() const = 0; ++ ++protected: ++ TiXmlNode( NodeType _type ); ++ ++ // Copy to the allocated object. Shared functionality between Clone, Copy constructor, ++ // and the assignment operator. ++ void CopyTo( TiXmlNode* target ) const; ++ ++ #ifdef TIXML_USE_STL ++ // The real work of the input operator. ++ virtual void StreamIn( TIXML_ISTREAM* in, TIXML_STRING* tag ) = 0; ++ #endif ++ ++ // Figure out what is at *p, and parse it. Returns null if it is not an xml node. ++ TiXmlNode* Identify( const char* start, TiXmlEncoding encoding ); ++ ++ // Internal Value function returning a TIXML_STRING ++ const TIXML_STRING& SValue() const { return value ; } ++ ++ TiXmlNode* parent; ++ NodeType type; ++ ++ TiXmlNode* firstChild; ++ TiXmlNode* lastChild; ++ ++ TIXML_STRING value; ++ ++ TiXmlNode* prev; ++ TiXmlNode* next; ++ ++private: ++ TiXmlNode( const TiXmlNode& ); // not implemented. ++ void operator=( const TiXmlNode& base ); // not allowed. ++}; ++ ++ ++/** An attribute is a name-value pair. Elements have an arbitrary ++ number of attributes, each with a unique name. ++ ++ @note The attributes are not TiXmlNodes, since they are not ++ part of the tinyXML document object model. There are other ++ suggested ways to look at this problem. ++*/ ++class TiXmlAttribute : public TiXmlBase ++{ ++ friend class TiXmlAttributeSet; ++ ++public: ++ /// Construct an empty attribute. ++ TiXmlAttribute() : TiXmlBase() ++ { ++ document = 0; ++ prev = next = 0; ++ } ++ ++ #ifdef TIXML_USE_STL ++ /// std::string constructor. ++ TiXmlAttribute( const std::string& _name, const std::string& _value ) ++ { ++ name = _name; ++ value = _value; ++ document = 0; ++ prev = next = 0; ++ } ++ #endif ++ ++ /// Construct an attribute with a name and value. ++ TiXmlAttribute( const char * _name, const char * _value ) ++ { ++ name = _name; ++ value = _value; ++ document = 0; ++ prev = next = 0; ++ } ++ ++ const char* Name() const { return name.c_str (); } ///< Return the name of this attribute. ++ const char* Value() const { return value.c_str (); } ///< Return the value of this attribute. ++ const int IntValue() const; ///< Return the value of this attribute, converted to an integer. ++ const double DoubleValue() const; ///< Return the value of this attribute, converted to a double. ++ ++ /** QueryIntValue examines the value string. It is an alternative to the ++ IntValue() method with richer error checking. ++ If the value is an integer, it is stored in 'value' and ++ the call returns TIXML_SUCCESS. If it is not ++ an integer, it returns TIXML_WRONG_TYPE. ++ ++ A specialized but useful call. Note that for success it returns 0, ++ which is the opposite of almost all other TinyXml calls. ++ */ ++ int QueryIntValue( int* value ) const; ++ /// QueryDoubleValue examines the value string. See QueryIntValue(). ++ int QueryDoubleValue( double* value ) const; ++ ++ void SetName( const char* _name ) { name = _name; } ///< Set the name of this attribute. ++ void SetValue( const char* _value ) { value = _value; } ///< Set the value. ++ ++ void SetIntValue( int value ); ///< Set the value from an integer. ++ void SetDoubleValue( double value ); ///< Set the value from a double. ++ ++ #ifdef TIXML_USE_STL ++ /// STL std::string form. ++ void SetName( const std::string& _name ) ++ { ++ StringToBuffer buf( _name ); ++ SetName ( buf.buffer ? buf.buffer : "error" ); ++ } ++ /// STL std::string form. ++ void SetValue( const std::string& _value ) ++ { ++ StringToBuffer buf( _value ); ++ SetValue( buf.buffer ? buf.buffer : "error" ); ++ } ++ #endif ++ ++ /// Get the next sibling attribute in the DOM. Returns null at end. ++ TiXmlAttribute* Next() const; ++ /// Get the previous sibling attribute in the DOM. Returns null at beginning. ++ TiXmlAttribute* Previous() const; ++ ++ bool operator==( const TiXmlAttribute& rhs ) const { return rhs.name == name; } ++ bool operator<( const TiXmlAttribute& rhs ) const { return name < rhs.name; } ++ bool operator>( const TiXmlAttribute& rhs ) const { return name > rhs.name; } ++ ++ /* Attribute parsing starts: first letter of the name ++ returns: the next char after the value end quote ++ */ ++ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); ++ ++ // Prints this Attribute to a FILE stream. ++ virtual void Print( FILE* cfile, int depth ) const; ++ ++ virtual void StreamOut( TIXML_OSTREAM * out ) const; ++ // [internal use] ++ // Set the document pointer so the attribute can report errors. ++ void SetDocument( TiXmlDocument* doc ) { document = doc; } ++ ++private: ++ TiXmlAttribute( const TiXmlAttribute& ); // not implemented. ++ void operator=( const TiXmlAttribute& base ); // not allowed. ++ ++ TiXmlDocument* document; // A pointer back to a document, for error reporting. ++ TIXML_STRING name; ++ TIXML_STRING value; ++ TiXmlAttribute* prev; ++ TiXmlAttribute* next; ++}; ++ ++ ++/* A class used to manage a group of attributes. ++ It is only used internally, both by the ELEMENT and the DECLARATION. ++ ++ The set can be changed transparent to the Element and Declaration ++ classes that use it, but NOT transparent to the Attribute ++ which has to implement a next() and previous() method. Which makes ++ it a bit problematic and prevents the use of STL. ++ ++ This version is implemented with circular lists because: ++ - I like circular lists ++ - it demonstrates some independence from the (typical) doubly linked list. ++*/ ++class TiXmlAttributeSet ++{ ++public: ++ TiXmlAttributeSet(); ++ ~TiXmlAttributeSet(); ++ ++ void Add( TiXmlAttribute* attribute ); ++ void Remove( TiXmlAttribute* attribute ); ++ ++ TiXmlAttribute* First() const { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } ++ TiXmlAttribute* Last() const { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } ++ TiXmlAttribute* Find( const char * name ) const; ++ ++private: ++ TiXmlAttribute sentinel; ++}; ++ ++ ++/** The element is a container class. It has a value, the element name, ++ and can contain other elements, text, comments, and unknowns. ++ Elements also contain an arbitrary number of attributes. ++*/ ++class TiXmlElement : public TiXmlNode ++{ ++public: ++ /// Construct an element. ++ TiXmlElement (const char * in_value); ++ ++ #ifdef TIXML_USE_STL ++ /// std::string constructor. ++ TiXmlElement( const std::string& _value ); ++ #endif ++ ++ TiXmlElement( const TiXmlElement& ); ++ ++ void operator=( const TiXmlElement& base ); ++ ++ virtual ~TiXmlElement(); ++ ++ /** Given an attribute name, Attribute() returns the value ++ for the attribute of that name, or null if none exists. ++ */ ++ const char* Attribute( const char* name ) const; ++ ++ /** Given an attribute name, Attribute() returns the value ++ for the attribute of that name, or null if none exists. ++ If the attribute exists and can be converted to an integer, ++ the integer value will be put in the return 'i', if 'i' ++ is non-null. ++ */ ++ const char* Attribute( const char* name, int* i ) const; ++ ++ /** Given an attribute name, Attribute() returns the value ++ for the attribute of that name, or null if none exists. ++ If the attribute exists and can be converted to an double, ++ the double value will be put in the return 'd', if 'd' ++ is non-null. ++ */ ++ const char* Attribute( const char* name, double* d ) const; ++ ++ /** QueryIntAttribute examines the attribute - it is an alternative to the ++ Attribute() method with richer error checking. ++ If the attribute is an integer, it is stored in 'value' and ++ the call returns TIXML_SUCCESS. If it is not ++ an integer, it returns TIXML_WRONG_TYPE. If the attribute ++ does not exist, then TIXML_NO_ATTRIBUTE is returned. ++ */ ++ int QueryIntAttribute( const char* name, int* value ) const; ++ /// QueryDoubleAttribute examines the attribute - see QueryIntAttribute(). ++ int QueryDoubleAttribute( const char* name, double* value ) const; ++ ++ /** Sets an attribute of name to a given value. The attribute ++ will be created if it does not exist, or changed if it does. ++ */ ++ void SetAttribute( const char* name, const char * value ); ++ ++ #ifdef TIXML_USE_STL ++ const char* Attribute( const std::string& name ) const { return Attribute( name.c_str() ); } ++ const char* Attribute( const std::string& name, int* i ) const { return Attribute( name.c_str(), i ); } ++ const char* Attribute( const std::string& name, double* d ) const { return Attribute( name.c_str(), d ); } ++ int QueryIntAttribute( const std::string& name, int* value ) const { return QueryIntAttribute( name.c_str(), value ); } ++ int QueryDoubleAttribute( const std::string& name, double* value ) const { return QueryDoubleAttribute( name.c_str(), value ); } ++ ++ /// STL std::string form. ++ void SetAttribute( const std::string& name, const std::string& _value ) ++ { ++ StringToBuffer n( name ); ++ StringToBuffer v( _value ); ++ if ( n.buffer && v.buffer ) ++ SetAttribute (n.buffer, v.buffer ); ++ } ++ ///< STL std::string form. ++ void SetAttribute( const std::string& name, int _value ) ++ { ++ StringToBuffer n( name ); ++ if ( n.buffer ) ++ SetAttribute (n.buffer, _value); ++ } ++ #endif ++ ++ /** Sets an attribute of name to a given value. The attribute ++ will be created if it does not exist, or changed if it does. ++ */ ++ void SetAttribute( const char * name, int value ); ++ ++ /** Sets an attribute of name to a given value. The attribute ++ will be created if it does not exist, or changed if it does. ++ */ ++ void SetDoubleAttribute( const char * name, double value ); ++ ++ /** Deletes an attribute with the given name. ++ */ ++ void RemoveAttribute( const char * name ); ++ #ifdef TIXML_USE_STL ++ void RemoveAttribute( const std::string& name ) { RemoveAttribute (name.c_str ()); } ///< STL std::string form. ++ #endif ++ ++ TiXmlAttribute* FirstAttribute() const { return attributeSet.First(); } ///< Access the first attribute in this element. ++ TiXmlAttribute* LastAttribute() const { return attributeSet.Last(); } ///< Access the last attribute in this element. ++ ++ /// Creates a new Element and returns it - the returned element is a copy. ++ virtual TiXmlNode* Clone() const; ++ // Print the Element to a FILE stream. ++ virtual void Print( FILE* cfile, int depth ) const; ++ ++ /* Attribtue parsing starts: next char past '<' ++ returns: next char past '>' ++ */ ++ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); ++ ++protected: ++ ++ void CopyTo( TiXmlElement* target ) const; ++ void ClearThis(); // like clear, but initializes 'this' object as well ++ ++ // Used to be public [internal use] ++ #ifdef TIXML_USE_STL ++ virtual void StreamIn( TIXML_ISTREAM * in, TIXML_STRING * tag ); ++ #endif ++ virtual void StreamOut( TIXML_OSTREAM * out ) const; ++ ++ /* [internal use] ++ Reads the "value" of the element -- another element, or text. ++ This should terminate with the current end tag. ++ */ ++ const char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding ); ++ ++private: ++ ++ TiXmlAttributeSet attributeSet; ++}; ++ ++ ++/** An XML comment. ++*/ ++class TiXmlComment : public TiXmlNode ++{ ++public: ++ /// Constructs an empty comment. ++ TiXmlComment() : TiXmlNode( TiXmlNode::COMMENT ) {} ++ TiXmlComment( const TiXmlComment& ); ++ void operator=( const TiXmlComment& base ); ++ ++ virtual ~TiXmlComment() {} ++ ++ /// Returns a copy of this Comment. ++ virtual TiXmlNode* Clone() const; ++ /// Write this Comment to a FILE stream. ++ virtual void Print( FILE* cfile, int depth ) const; ++ ++ /* Attribtue parsing starts: at the ! of the !-- ++ returns: next char past '>' ++ */ ++ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); ++ ++protected: ++ void CopyTo( TiXmlComment* target ) const; ++ ++ // used to be public ++ #ifdef TIXML_USE_STL ++ virtual void StreamIn( TIXML_ISTREAM * in, TIXML_STRING * tag ); ++ #endif ++ virtual void StreamOut( TIXML_OSTREAM * out ) const; ++ ++private: ++ ++}; ++ ++ ++/** XML text. Contained in an element. ++*/ ++class TiXmlText : public TiXmlNode ++{ ++ friend class TiXmlElement; ++public: ++ /// Constructor. ++ TiXmlText (const char * initValue) : TiXmlNode (TiXmlNode::TEXT) ++ { ++ SetValue( initValue ); ++ } ++ virtual ~TiXmlText() {} ++ ++ #ifdef TIXML_USE_STL ++ /// Constructor. ++ TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TEXT) ++ { ++ SetValue( initValue ); ++ } ++ #endif ++ ++ TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TEXT ) { copy.CopyTo( this ); } ++ void operator=( const TiXmlText& base ) { base.CopyTo( this ); } ++ ++ /// Write this text object to a FILE stream. ++ virtual void Print( FILE* cfile, int depth ) const; ++ ++ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); ++ ++protected : ++ /// [internal use] Creates a new Element and returns it. ++ virtual TiXmlNode* Clone() const; ++ void CopyTo( TiXmlText* target ) const; ++ ++ virtual void StreamOut ( TIXML_OSTREAM * out ) const; ++ bool Blank() const; // returns true if all white space and new lines ++ // [internal use] ++ #ifdef TIXML_USE_STL ++ virtual void StreamIn( TIXML_ISTREAM * in, TIXML_STRING * tag ); ++ #endif ++ ++private: ++}; ++ ++ ++/** In correct XML the declaration is the first entry in the file. ++ @verbatim ++ ++ @endverbatim ++ ++ TinyXml will happily read or write files without a declaration, ++ however. There are 3 possible attributes to the declaration: ++ version, encoding, and standalone. ++ ++ Note: In this version of the code, the attributes are ++ handled as special cases, not generic attributes, simply ++ because there can only be at most 3 and they are always the same. ++*/ ++class TiXmlDeclaration : public TiXmlNode ++{ ++public: ++ /// Construct an empty declaration. ++ TiXmlDeclaration() : TiXmlNode( TiXmlNode::DECLARATION ) {} ++ ++#ifdef TIXML_USE_STL ++ /// Constructor. ++ TiXmlDeclaration( const std::string& _version, ++ const std::string& _encoding, ++ const std::string& _standalone ); ++#endif ++ ++ /// Construct. ++ TiXmlDeclaration( const char* _version, ++ const char* _encoding, ++ const char* _standalone ); ++ ++ TiXmlDeclaration( const TiXmlDeclaration& copy ); ++ void operator=( const TiXmlDeclaration& copy ); ++ ++ virtual ~TiXmlDeclaration() {} ++ ++ /// Version. Will return an empty string if none was found. ++ const char *Version() const { return version.c_str (); } ++ /// Encoding. Will return an empty string if none was found. ++ const char *Encoding() const { return encoding.c_str (); } ++ /// Is this a standalone document? ++ const char *Standalone() const { return standalone.c_str (); } ++ ++ /// Creates a copy of this Declaration and returns it. ++ virtual TiXmlNode* Clone() const; ++ /// Print this declaration to a FILE stream. ++ virtual void Print( FILE* cfile, int depth ) const; ++ ++ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); ++ ++protected: ++ void CopyTo( TiXmlDeclaration* target ) const; ++ // used to be public ++ #ifdef TIXML_USE_STL ++ virtual void StreamIn( TIXML_ISTREAM * in, TIXML_STRING * tag ); ++ #endif ++ virtual void StreamOut ( TIXML_OSTREAM * out) const; ++ ++private: ++ ++ TIXML_STRING version; ++ TIXML_STRING encoding; ++ TIXML_STRING standalone; ++}; ++ ++ ++/** Any tag that tinyXml doesn't recognize is saved as an ++ unknown. It is a tag of text, but should not be modified. ++ It will be written back to the XML, unchanged, when the file ++ is saved. ++ ++ DTD tags get thrown into TiXmlUnknowns. ++*/ ++class TiXmlUnknown : public TiXmlNode ++{ ++public: ++ TiXmlUnknown() : TiXmlNode( TiXmlNode::UNKNOWN ) {} ++ virtual ~TiXmlUnknown() {} ++ ++ TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::UNKNOWN ) { copy.CopyTo( this ); } ++ void operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); } ++ ++ /// Creates a copy of this Unknown and returns it. ++ virtual TiXmlNode* Clone() const; ++ /// Print this Unknown to a FILE stream. ++ virtual void Print( FILE* cfile, int depth ) const; ++ ++ virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); ++ ++protected: ++ void CopyTo( TiXmlUnknown* target ) const; ++ ++ #ifdef TIXML_USE_STL ++ virtual void StreamIn( TIXML_ISTREAM * in, TIXML_STRING * tag ); ++ #endif ++ virtual void StreamOut ( TIXML_OSTREAM * out ) const; ++ ++private: ++ ++}; ++ ++ ++/** Always the top level node. A document binds together all the ++ XML pieces. It can be saved, loaded, and printed to the screen. ++ The 'value' of a document node is the xml file name. ++*/ ++class TiXmlDocument : public TiXmlNode ++{ ++public: ++ /// Create an empty document, that has no name. ++ TiXmlDocument(); ++ /// Create a document with a name. The name of the document is also the filename of the xml. ++ TiXmlDocument( const char * documentName ); ++ ++ #ifdef TIXML_USE_STL ++ /// Constructor. ++ TiXmlDocument( const std::string& documentName ); ++ #endif ++ ++ TiXmlDocument( const TiXmlDocument& copy ); ++ void operator=( const TiXmlDocument& copy ); ++ ++ virtual ~TiXmlDocument() {} ++ ++ /** Load a file using the current document value. ++ Returns true if successful. Will delete any existing ++ document data before loading. ++ */ ++ bool LoadFile( TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); ++ /// Save a file using the current document value. Returns true if successful. ++ bool SaveFile() const; ++ /// Load a file using the given filename. Returns true if successful. ++ bool LoadFile( const char * filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); ++ /// Save a file using the given filename. Returns true if successful. ++ bool SaveFile( const char * filename ) const; ++ ++ #ifdef TIXML_USE_STL ++ bool LoadFile( const std::string& filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ) ///< STL std::string version. ++ { ++ StringToBuffer f( filename ); ++ return ( f.buffer && LoadFile( f.buffer, encoding )); ++ } ++ bool SaveFile( const std::string& filename ) const ///< STL std::string version. ++ { ++ StringToBuffer f( filename ); ++ return ( f.buffer && SaveFile( f.buffer )); ++ } ++ #endif ++ ++ /** Parse the given null terminated block of xml data. Passing in an encoding to this ++ method (either TIXML_ENCODING_LEGACY or TIXML_ENCODING_UTF8 will force TinyXml ++ to use that encoding, regardless of what TinyXml might otherwise try to detect. ++ */ ++ virtual const char* Parse( const char* p, TiXmlParsingData* data = 0, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); ++ ++ /** Get the root element -- the only top level element -- of the document. ++ In well formed XML, there should only be one. TinyXml is tolerant of ++ multiple elements at the document level. ++ */ ++ TiXmlElement* RootElement() const { return FirstChildElement(); } ++ ++ /** If an error occurs, Error will be set to true. Also, ++ - The ErrorId() will contain the integer identifier of the error (not generally useful) ++ - The ErrorDesc() method will return the name of the error. (very useful) ++ - The ErrorRow() and ErrorCol() will return the location of the error (if known) ++ */ ++ bool Error() const { return error; } ++ ++ /// Contains a textual (english) description of the error if one occurs. ++ const char * ErrorDesc() const { return errorDesc.c_str (); } ++ ++ /** Generally, you probably want the error string ( ErrorDesc() ). But if you ++ prefer the ErrorId, this function will fetch it. ++ */ ++ const int ErrorId() const { return errorId; } ++ ++ /** Returns the location (if known) of the error. The first column is column 1, ++ and the first row is row 1. A value of 0 means the row and column wasn't applicable ++ (memory errors, for example, have no row/column) or the parser lost the error. (An ++ error in the error reporting, in that case.) ++ ++ @sa SetTabSize, Row, Column ++ */ ++ int ErrorRow() { return errorLocation.row+1; } ++ int ErrorCol() { return errorLocation.col+1; } ///< The column where the error occured. See ErrorRow() ++ ++ /** By calling this method, with a tab size ++ greater than 0, the row and column of each node and attribute is stored ++ when the file is loaded. Very useful for tracking the DOM back in to ++ the source file. ++ ++ The tab size is required for calculating the location of nodes. If not ++ set, the default of 4 is used. The tabsize is set per document. Setting ++ the tabsize to 0 disables row/column tracking. ++ ++ Note that row and column tracking is not supported when using operator>>. ++ ++ The tab size needs to be enabled before the parse or load. Correct usage: ++ @verbatim ++ TiXmlDocument doc; ++ doc.SetTabSize( 8 ); ++ doc.Load( "myfile.xml" ); ++ @endverbatim ++ ++ @sa Row, Column ++ */ ++ void SetTabSize( int _tabsize ) { tabsize = _tabsize; } ++ ++ int TabSize() const { return tabsize; } ++ ++ /** If you have handled the error, it can be reset with this call. The error ++ state is automatically cleared if you Parse a new XML block. ++ */ ++ void ClearError() { error = false; ++ errorId = 0; ++ errorDesc = ""; ++ errorLocation.row = errorLocation.col = 0; ++ //errorLocation.last = 0; ++ } ++ ++ /** Dump the document to standard out. */ ++ void Print() const { Print( stdout, 0 ); } ++ ++ /// Print this Document to a FILE stream. ++ virtual void Print( FILE* cfile, int depth = 0 ) const; ++ // [internal use] ++ void SetError( int err, const char* errorLocation, TiXmlParsingData* prevData, TiXmlEncoding encoding ); ++ ++protected : ++ virtual void StreamOut ( TIXML_OSTREAM * out) const; ++ // [internal use] ++ virtual TiXmlNode* Clone() const; ++ #ifdef TIXML_USE_STL ++ virtual void StreamIn( TIXML_ISTREAM * in, TIXML_STRING * tag ); ++ #endif ++ ++private: ++ void CopyTo( TiXmlDocument* target ) const; ++ ++ bool error; ++ int errorId; ++ TIXML_STRING errorDesc; ++ int tabsize; ++ TiXmlCursor errorLocation; ++}; ++ ++ ++/** ++ A TiXmlHandle is a class that wraps a node pointer with null checks; this is ++ an incredibly useful thing. Note that TiXmlHandle is not part of the TinyXml ++ DOM structure. It is a separate utility class. ++ ++ Take an example: ++ @verbatim ++ ++ ++ ++ ++ ++ ++ @endverbatim ++ ++ Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very ++ easy to write a *lot* of code that looks like: ++ ++ @verbatim ++ TiXmlElement* root = document.FirstChildElement( "Document" ); ++ if ( root ) ++ { ++ TiXmlElement* element = root->FirstChildElement( "Element" ); ++ if ( element ) ++ { ++ TiXmlElement* child = element->FirstChildElement( "Child" ); ++ if ( child ) ++ { ++ TiXmlElement* child2 = child->NextSiblingElement( "Child" ); ++ if ( child2 ) ++ { ++ // Finally do something useful. ++ @endverbatim ++ ++ And that doesn't even cover "else" cases. TiXmlHandle addresses the verbosity ++ of such code. A TiXmlHandle checks for null pointers so it is perfectly safe ++ and correct to use: ++ ++ @verbatim ++ TiXmlHandle docHandle( &document ); ++ TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).Element(); ++ if ( child2 ) ++ { ++ // do something useful ++ @endverbatim ++ ++ Which is MUCH more concise and useful. ++ ++ It is also safe to copy handles - internally they are nothing more than node pointers. ++ @verbatim ++ TiXmlHandle handleCopy = handle; ++ @endverbatim ++ ++ What they should not be used for is iteration: ++ ++ @verbatim ++ int i=0; ++ while ( true ) ++ { ++ TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", i ).Element(); ++ if ( !child ) ++ break; ++ // do something ++ ++i; ++ } ++ @endverbatim ++ ++ It seems reasonable, but it is in fact two embedded while loops. The Child method is ++ a linear walk to find the element, so this code would iterate much more than it needs ++ to. Instead, prefer: ++ ++ @verbatim ++ TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).FirstChild( "Child" ).Element(); ++ ++ for( child; child; child=child->NextSiblingElement() ) ++ { ++ // do something ++ } ++ @endverbatim ++*/ ++class TiXmlHandle ++{ ++public: ++ /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. ++ TiXmlHandle( TiXmlNode* node ) { this->node = node; } ++ /// Copy constructor ++ TiXmlHandle( const TiXmlHandle& ref ) { this->node = ref.node; } ++ TiXmlHandle operator=( const TiXmlHandle& ref ) { this->node = ref.node; return *this; } ++ ++ /// Return a handle to the first child node. ++ TiXmlHandle FirstChild() const; ++ /// Return a handle to the first child node with the given name. ++ TiXmlHandle FirstChild( const char * value ) const; ++ /// Return a handle to the first child element. ++ TiXmlHandle FirstChildElement() const; ++ /// Return a handle to the first child element with the given name. ++ TiXmlHandle FirstChildElement( const char * value ) const; ++ ++ /** Return a handle to the "index" child with the given name. ++ The first child is 0, the second 1, etc. ++ */ ++ TiXmlHandle Child( const char* value, int index ) const; ++ /** Return a handle to the "index" child. ++ The first child is 0, the second 1, etc. ++ */ ++ TiXmlHandle Child( int index ) const; ++ /** Return a handle to the "index" child element with the given name. ++ The first child element is 0, the second 1, etc. Note that only TiXmlElements ++ are indexed: other types are not counted. ++ */ ++ TiXmlHandle ChildElement( const char* value, int index ) const; ++ /** Return a handle to the "index" child element. ++ The first child element is 0, the second 1, etc. Note that only TiXmlElements ++ are indexed: other types are not counted. ++ */ ++ TiXmlHandle ChildElement( int index ) const; ++ ++ #ifdef TIXML_USE_STL ++ TiXmlHandle FirstChild( const std::string& _value ) const { return FirstChild( _value.c_str() ); } ++ TiXmlHandle FirstChildElement( const std::string& _value ) const { return FirstChildElement( _value.c_str() ); } ++ ++ TiXmlHandle Child( const std::string& _value, int index ) const { return Child( _value.c_str(), index ); } ++ TiXmlHandle ChildElement( const std::string& _value, int index ) const { return ChildElement( _value.c_str(), index ); } ++ #endif ++ ++ /// Return the handle as a TiXmlNode. This may return null. ++ TiXmlNode* Node() const { return node; } ++ /// Return the handle as a TiXmlElement. This may return null. ++ TiXmlElement* Element() const { return ( ( node && node->ToElement() ) ? node->ToElement() : 0 ); } ++ /// Return the handle as a TiXmlText. This may return null. ++ TiXmlText* Text() const { return ( ( node && node->ToText() ) ? node->ToText() : 0 ); } ++ /// Return the handle as a TiXmlUnknown. This may return null; ++ TiXmlUnknown* Unknown() const { return ( ( node && node->ToUnknown() ) ? node->ToUnknown() : 0 ); } ++ ++private: ++ TiXmlNode* node; ++}; ++ ++ ++#endif ++#endif /* SETUP */ +diff -NaurwB vdr-1.7.10/tinyxmlparser.c vdr-1.7.10-patched/tinyxmlparser.c +--- vdr-1.7.10/tinyxmlparser.c 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/tinyxmlparser.c 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,1494 @@ ++#ifdef USE_SETUP ++/* ++www.sourceforge.net/projects/tinyxml ++Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com) ++ ++This software is provided 'as-is', without any express or implied ++warranty. In no event will the authors be held liable for any ++damages arising from the use of this software. ++ ++Permission is granted to anyone to use this software for any ++purpose, including commercial applications, and to alter it and ++redistribute it freely, subject to the following restrictions: ++ ++1. The origin of this software must not be misrepresented; you must ++not claim that you wrote the original software. If you use this ++software in a product, an acknowledgment in the product documentation ++would be appreciated but is not required. ++ ++2. Altered source versions must be plainly marked as such, and ++must not be misrepresented as being the original software. ++ ++3. This notice may not be removed or altered from any source ++distribution. ++*/ ++ ++#include "tinyxml.h" ++#include ++ ++//#define DEBUG_PARSER ++ ++// Note tha "PutString" hardcodes the same list. This ++// is less flexible than it appears. Changing the entries ++// or order will break putstring. ++TiXmlBase::Entity TiXmlBase::entity[ NUM_ENTITY ] = ++{ ++ { "&", 5, '&' }, ++ { "<", 4, '<' }, ++ { ">", 4, '>' }, ++ { """, 6, '\"' }, ++ { "'", 6, '\'' } ++}; ++ ++// Bunch of unicode info at: ++// http://www.unicode.org/faq/utf_bom.html ++// Including the basic of this table, which determines the #bytes in the ++// sequence from the lead byte. 1 placed for invalid sequences -- ++// although the result will be junk, pass it through as much as possible. ++// Beware of the non-characters in UTF-8: ++// ef bb bf (Microsoft "lead bytes") ++// ef bf be ++// ef bf bf ++ ++ ++ ++const int TiXmlBase::utf8ByteTable[256] = ++{ ++ // 0 1 2 3 4 5 6 7 8 9 a b c d e f ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 End of ASCII range ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 0x80 to 0xc1 invalid ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90 ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0 ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0 ++ 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 0xc2 to 0xdf 2 byte ++ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 ++ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xe0 0xe0 to 0xef 3 byte ++ 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid ++}; ++ ++ ++void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ) ++{ ++ const unsigned long BYTE_MASK = 0xBF; ++ const unsigned long BYTE_MARK = 0x80; ++ const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; ++ ++ if (input < 0x80) ++ *length = 1; ++ else if ( input < 0x800 ) ++ *length = 2; ++ else if ( input < 0x10000 ) ++ *length = 3; ++ else if ( input < 0x200000 ) ++ *length = 4; ++ else ++ { *length = 0; return; } // This code won't covert this correctly anyway. ++ ++ output += *length; ++ ++ // Scary scary fall throughs. ++ switch (*length) ++ { ++ case 4: ++ --output; ++ *output = (char)((input | BYTE_MARK) & BYTE_MASK); ++ input >>= 6; ++ case 3: ++ --output; ++ *output = (char)((input | BYTE_MARK) & BYTE_MASK); ++ input >>= 6; ++ case 2: ++ --output; ++ *output = (char)((input | BYTE_MARK) & BYTE_MASK); ++ input >>= 6; ++ case 1: ++ --output; ++ *output = (char)(input | FIRST_BYTE_MARK[*length]); ++ } ++} ++ ++ ++/*static*/ int TiXmlBase::IsAlpha( unsigned char anyByte, TiXmlEncoding encoding ) ++{ ++ // This will only work for low-ascii, everything else is assumed to be a valid ++ // letter. I'm not sure this is the best approach, but it is quite tricky trying ++ // to figure out alhabetical vs. not across encoding. So take a very ++ // conservative approach. ++ ++// if ( encoding == TIXML_ENCODING_UTF8 ) ++// { ++ if ( anyByte < 127 ) ++ return isalpha( anyByte ); ++ else ++ return 1; // What else to do? The unicode set is huge...get the english ones right. ++// } ++// else ++// { ++// return isalpha( anyByte ); ++// } ++} ++ ++ ++/*static*/ int TiXmlBase::IsAlphaNum( unsigned char anyByte, TiXmlEncoding encoding ) ++{ ++ // This will only work for low-ascii, everything else is assumed to be a valid ++ // letter. I'm not sure this is the best approach, but it is quite tricky trying ++ // to figure out alhabetical vs. not across encoding. So take a very ++ // conservative approach. ++ ++// if ( encoding == TIXML_ENCODING_UTF8 ) ++// { ++ if ( anyByte < 127 ) ++ return isalnum( anyByte ); ++ else ++ return 1; // What else to do? The unicode set is huge...get the english ones right. ++// } ++// else ++// { ++// return isalnum( anyByte ); ++// } ++} ++ ++ ++class TiXmlParsingData ++{ ++ friend class TiXmlDocument; ++ public: ++ void Stamp( const char* now, TiXmlEncoding encoding ); ++ ++ const TiXmlCursor& Cursor() { return cursor; } ++ ++ private: ++ // Only used by the document! ++ TiXmlParsingData( const char* start, int _tabsize, int row, int col ) ++ { ++ assert( start ); ++ stamp = start; ++ tabsize = _tabsize; ++ cursor.row = row; ++ cursor.col = col; ++ } ++ ++ TiXmlCursor cursor; ++ const char* stamp; ++ int tabsize; ++}; ++ ++ ++void TiXmlParsingData::Stamp( const char* now, TiXmlEncoding encoding ) ++{ ++ assert( now ); ++ ++ // Do nothing if the tabsize is 0. ++ if ( tabsize < 1 ) ++ { ++ return; ++ } ++ ++ // Get the current row, column. ++ int row = cursor.row; ++ int col = cursor.col; ++ const char* p = stamp; ++ assert( p ); ++ ++ while ( p < now ) ++ { ++ // Code contributed by Fletcher Dunn: (modified by lee) ++ switch (*p) { ++ case 0: ++ // We *should* never get here, but in case we do, don't ++ // advance past the terminating null character, ever ++ return; ++ ++ case '\r': ++ // bump down to the next line ++ ++row; ++ col = 0; ++ // Eat the character ++ ++p; ++ ++ // Check for \r\n sequence, and treat this as a single character ++ if (*p == '\n') { ++ ++p; ++ } ++ break; ++ ++ case '\n': ++ // bump down to the next line ++ ++row; ++ col = 0; ++ ++ // Eat the character ++ ++p; ++ ++ // Check for \n\r sequence, and treat this as a single ++ // character. (Yes, this bizarre thing does occur still ++ // on some arcane platforms...) ++ if (*p == '\r') { ++ ++p; ++ } ++ break; ++ ++ case '\t': ++ // Eat the character ++ ++p; ++ ++ // Skip to next tab stop ++ col = (col / tabsize + 1) * tabsize; ++ break; ++ ++ case (char)(0xef): ++ if ( encoding == TIXML_ENCODING_UTF8 ) ++ { ++ if ( *(p+1) && *(p+2) ) ++ { ++ // In these cases, don't advance the column. These are ++ // 0-width spaces. ++ if ( *(p+1)==(char)(0xbb) && *(p+2)==(char)(0xbf) ) ++ p += 3; ++ else if ( *(p+1)==(char)(0xbf) && *(p+2)==(char)(0xbe) ) ++ p += 3; ++ else if ( *(p+1)==(char)(0xbf) && *(p+2)==(char)(0xbf) ) ++ p += 3; ++ else ++ { p +=3; ++col; } // A normal character. ++ } ++ } ++ else ++ { ++ ++p; ++ ++col; ++ } ++ break; ++ ++ default: ++ if ( encoding == TIXML_ENCODING_UTF8 ) ++ { ++ // Eat the 1 to 4 byte utf8 character. ++ int step = TiXmlBase::utf8ByteTable[*((unsigned char*)p)]; ++ if ( step == 0 ) ++ step = 1; // Error case from bad encoding, but handle gracefully. ++ p += step; ++ ++ // Just advance one column, of course. ++ ++col; ++ } ++ else ++ { ++ ++p; ++ ++col; ++ } ++ break; ++ } ++ } ++ cursor.row = row; ++ cursor.col = col; ++ assert( cursor.row >= -1 ); ++ assert( cursor.col >= -1 ); ++ stamp = p; ++ assert( stamp ); ++} ++ ++ ++const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding ) ++{ ++ if ( !p || !*p ) ++ { ++ return 0; ++ } ++ if ( encoding == TIXML_ENCODING_UTF8 ) ++ { ++ while ( *p ) ++ { ++ // Skip the stupid Microsoft UTF-8 Byte order marks ++ if ( *(p+0)==(char) 0xef ++ && *(p+1)==(char) 0xbb ++ && *(p+2)==(char) 0xbf ) ++ { ++ p += 3; ++ continue; ++ } ++ else if(*(p+0)==(char) 0xef ++ && *(p+1)==(char) 0xbf ++ && *(p+2)==(char) 0xbe ) ++ { ++ p += 3; ++ continue; ++ } ++ else if(*(p+0)==(char) 0xef ++ && *(p+1)==(char) 0xbf ++ && *(p+2)==(char) 0xbf ) ++ { ++ p += 3; ++ continue; ++ } ++ ++ if ( IsWhiteSpace( *p ) || *p == '\n' || *p =='\r' ) // Still using old rules for white space. ++ ++p; ++ else ++ break; ++ } ++ } ++ else ++ { ++ while ( *p && IsWhiteSpace( *p ) || *p == '\n' || *p =='\r' ) ++ ++p; ++ } ++ ++ return p; ++} ++ ++#ifdef TIXML_USE_STL ++/*static*/ bool TiXmlBase::StreamWhiteSpace( TIXML_ISTREAM * in, TIXML_STRING * tag ) ++{ ++ for( ;; ) ++ { ++ if ( !in->good() ) return false; ++ ++ int c = in->peek(); ++ // At this scope, we can't get to a document. So fail silently. ++ if ( !IsWhiteSpace( c ) || c <= 0 ) ++ return true; ++ ++ *tag += (char) in->get(); ++ } ++} ++ ++/*static*/ bool TiXmlBase::StreamTo( TIXML_ISTREAM * in, int character, TIXML_STRING * tag ) ++{ ++ //assert( character > 0 && character < 128 ); // else it won't work in utf-8 ++ while ( in->good() ) ++ { ++ int c = in->peek(); ++ if ( c == character ) ++ return true; ++ if ( c <= 0 ) // Silent failure: can't get document at this scope ++ return false; ++ ++ in->get(); ++ *tag += (char) c; ++ } ++ return false; ++} ++#endif ++ ++const char* TiXmlBase::ReadName( const char* p, TIXML_STRING * name, TiXmlEncoding encoding ) ++{ ++ *name = ""; ++ assert( p ); ++ ++ // Names start with letters or underscores. ++ // Of course, in unicode, tinyxml has no idea what a letter *is*. The ++ // algorithm is generous. ++ // ++ // After that, they can be letters, underscores, numbers, ++ // hyphens, or colons. (Colons are valid ony for namespaces, ++ // but tinyxml can't tell namespaces from names.) ++ if ( p && *p ++ && ( IsAlpha( (unsigned char) *p, encoding ) || *p == '_' ) ) ++ { ++ while( p && *p ++ && ( IsAlphaNum( (unsigned char ) *p, encoding ) ++ || *p == '_' ++ || *p == '-' ++ || *p == '.' ++ || *p == ':' ) ) ++ { ++ (*name) += *p; ++ ++p; ++ } ++ return p; ++ } ++ return 0; ++} ++ ++const char* TiXmlBase::GetEntity( const char* p, char* value, int* length, TiXmlEncoding encoding ) ++{ ++ // Presume an entity, and pull it out. ++ TIXML_STRING ent; ++ int i; ++ *length = 0; ++ ++ if ( *(p+1) && *(p+1) == '#' && *(p+2) ) ++ { ++ unsigned long ucs = 0; ++ unsigned delta = 0; ++ unsigned mult = 1; ++ ++ if ( *(p+2) == 'x' ) ++ { ++ // Hexadecimal. ++ if ( !*(p+3) ) return 0; ++ ++ const char* q = p+3; ++ q = strchr( q, ';' ); ++ ++ if ( !q || !*q ) return 0; ++ ++ delta = q-p; ++ --q; ++ ++ while ( *q != 'x' ) ++ { ++ if ( *q >= '0' && *q <= '9' ) ++ ucs += mult * (*q - '0'); ++ else if ( *q >= 'a' && *q <= 'f' ) ++ ucs += mult * (*q - 'a' + 10); ++ else if ( *q >= 'A' && *q <= 'F' ) ++ ucs += mult * (*q - 'A' + 10 ); ++ else ++ return 0; ++ mult *= 16; ++ --q; ++ } ++ } ++ else ++ { ++ // Decimal. ++ if ( !*(p+2) ) return 0; ++ ++ const char* q = p+2; ++ q = strchr( q, ';' ); ++ ++ if ( !q || !*q ) return 0; ++ ++ delta = q-p; ++ --q; ++ ++ while ( *q != '#' ) ++ { ++ if ( *q >= '0' && *q <= '9' ) ++ ucs += mult * (*q - '0'); ++ else ++ return 0; ++ mult *= 10; ++ --q; ++ } ++ } ++ if ( encoding == TIXML_ENCODING_UTF8 ) ++ { ++ // convert the UCS to UTF-8 ++ ConvertUTF32ToUTF8( ucs, value, length ); ++ } ++ else ++ { ++ *value = (char)ucs; ++ *length = 1; ++ } ++ return p + delta + 1; ++ } ++ ++ // Now try to match it. ++ for( i=0; iappend( cArr, len ); ++ } ++ } ++ else ++ { ++ bool whitespace = false; ++ ++ // Remove leading white space: ++ p = SkipWhiteSpace( p, encoding ); ++ while ( p && *p ++ && !StringEqual( p, endTag, caseInsensitive, encoding ) ) ++ { ++ if ( *p == '\r' || *p == '\n' ) ++ { ++ whitespace = true; ++ ++p; ++ } ++ else if ( IsWhiteSpace( *p ) ) ++ { ++ whitespace = true; ++ ++p; ++ } ++ else ++ { ++ // If we've found whitespace, add it before the ++ // new character. Any whitespace just becomes a space. ++ if ( whitespace ) ++ { ++ (*text) += ' '; ++ whitespace = false; ++ } ++ int len; ++ char cArr[4] = { 0, 0, 0, 0 }; ++ p = GetChar( p, cArr, &len, encoding ); ++ if ( len == 1 ) ++ (*text) += cArr[0]; // more efficient ++ else ++ text->append( cArr, len ); ++ } ++ } ++ } ++ return p + strlen( endTag ); ++} ++ ++#ifdef TIXML_USE_STL ++ ++void TiXmlDocument::StreamIn( TIXML_ISTREAM * in, TIXML_STRING * tag ) ++{ ++ // The basic issue with a document is that we don't know what we're ++ // streaming. Read something presumed to be a tag (and hope), then ++ // identify it, and call the appropriate stream method on the tag. ++ // ++ // This "pre-streaming" will never read the closing ">" so the ++ // sub-tag can orient itself. ++ ++ if ( !StreamTo( in, '<', tag ) ) ++ { ++ SetError( TIXML_ERROR_PARSING_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); ++ return; ++ } ++ ++ while ( in->good() ) ++ { ++ int tagIndex = (int) tag->length(); ++ while ( in->good() && in->peek() != '>' ) ++ { ++ int c = in->get(); ++ if ( c <= 0 ) ++ { ++ SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); ++ break; ++ } ++ (*tag) += (char) c; ++ } ++ ++ if ( in->good() ) ++ { ++ // We now have something we presume to be a node of ++ // some sort. Identify it, and call the node to ++ // continue streaming. ++ TiXmlNode* node = Identify( tag->c_str() + tagIndex, TIXML_DEFAULT_ENCODING ); ++ ++ if ( node ) ++ { ++ node->StreamIn( in, tag ); ++ bool isElement = node->ToElement() != 0; ++ delete node; ++ node = 0; ++ ++ // If this is the root element, we're done. Parsing will be ++ // done by the >> operator. ++ if ( isElement ) ++ { ++ return; ++ } ++ } ++ else ++ { ++ SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); ++ return; ++ } ++ } ++ } ++ // We should have returned sooner. ++ SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); ++} ++ ++#endif ++ ++const char* TiXmlDocument::Parse( const char* p, TiXmlParsingData* prevData, TiXmlEncoding encoding ) ++{ ++ ClearError(); ++ ++ // Parse away, at the document level. Since a document ++ // contains nothing but other tags, most of what happens ++ // here is skipping white space. ++ if ( !p || !*p ) ++ { ++ SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); ++ return 0; ++ } ++ ++ // Note that, for a document, this needs to come ++ // before the while space skip, so that parsing ++ // starts from the pointer we are given. ++ location.Clear(); ++ if ( prevData ) ++ { ++ location.row = prevData->cursor.row; ++ location.col = prevData->cursor.col; ++ } ++ else ++ { ++ location.row = 0; ++ location.col = 0; ++ } ++ TiXmlParsingData data( p, TabSize(), location.row, location.col ); ++ location = data.Cursor(); ++ ++ if ( encoding == TIXML_ENCODING_UNKNOWN ) ++ { ++ // Check for the Microsoft UTF-8 lead bytes. ++ if ( *(p+0) && *(p+0) == (char)(0xef) ++ && *(p+1) && *(p+1) == (char)(0xbb) ++ && *(p+2) && *(p+2) == (char)(0xbf) ) ++ { ++ encoding = TIXML_ENCODING_UTF8; ++ } ++ } ++ ++ p = SkipWhiteSpace( p, encoding ); ++ if ( !p ) ++ { ++ SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); ++ return 0; ++ } ++ ++ while ( p && *p ) ++ { ++ TiXmlNode* node = Identify( p, encoding ); ++ if ( node ) ++ { ++ p = node->Parse( p, &data, encoding ); ++ LinkEndChild( node ); ++ } ++ else ++ { ++ break; ++ } ++ ++ // Did we get encoding info? ++ if ( encoding == TIXML_ENCODING_UNKNOWN ++ && node->ToDeclaration() ) ++ { ++ TiXmlDeclaration* dec = node->ToDeclaration(); ++ const char* enc = dec->Encoding(); ++ assert( enc ); ++ ++ if ( *enc == 0 ) ++ encoding = TIXML_ENCODING_UTF8; ++ else if ( StringEqual( enc, "UTF-8", true, TIXML_ENCODING_UNKNOWN ) ) ++ encoding = TIXML_ENCODING_UTF8; ++ else if ( StringEqual( enc, "UTF8", true, TIXML_ENCODING_UNKNOWN ) ) ++ encoding = TIXML_ENCODING_UTF8; // incorrect, but be nice ++ else ++ encoding = TIXML_ENCODING_LEGACY; ++ } ++ ++ p = SkipWhiteSpace( p, encoding ); ++ } ++ ++ // All is well. ++ return p; ++} ++ ++void TiXmlDocument::SetError( int err, const char* pError, TiXmlParsingData* data, TiXmlEncoding encoding ) ++{ ++ // The first error in a chain is more accurate - don't set again! ++ if ( error ) ++ return; ++ ++ assert( err > 0 && err < TIXML_ERROR_STRING_COUNT ); ++ error = true; ++ errorId = err; ++ errorDesc = errorString[ errorId ]; ++ ++ errorLocation.Clear(); ++ if ( pError && data ) ++ { ++ //TiXmlParsingData data( pError, prevData ); ++ data->Stamp( pError, encoding ); ++ errorLocation = data->Cursor(); ++ } ++} ++ ++ ++TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding ) ++{ ++ TiXmlNode* returnNode = 0; ++ ++ p = SkipWhiteSpace( p, encoding ); ++ if( !p || !*p || *p != '<' ) ++ { ++ return 0; ++ } ++ ++ TiXmlDocument* doc = GetDocument(); ++ p = SkipWhiteSpace( p, encoding ); ++ ++ if ( !p || !*p ) ++ { ++ return 0; ++ } ++ ++ // What is this thing? ++ // - Elements start with a letter or underscore, but xml is reserved. ++ // - Comments: "; ++ ++ if ( !StringEqual( p, startTag, false, encoding ) ) ++ { ++ document->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding ); ++ return 0; ++ } ++ p += strlen( startTag ); ++ p = ReadText( p, &value, false, endTag, false, encoding ); ++ return p; ++} ++ ++ ++const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) ++{ ++ p = SkipWhiteSpace( p, encoding ); ++ if ( !p || !*p ) return 0; ++ ++ int tabsize = 4; ++ if ( document ) ++ tabsize = document->TabSize(); ++ ++// TiXmlParsingData data( p, prevData ); ++ if ( data ) ++ { ++ data->Stamp( p, encoding ); ++ location = data->Cursor(); ++ } ++ // Read the name, the '=' and the value. ++ const char* pErr = p; ++ p = ReadName( p, &name, encoding ); ++ if ( !p || !*p ) ++ { ++ if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding ); ++ return 0; ++ } ++ p = SkipWhiteSpace( p, encoding ); ++ if ( !p || !*p || *p != '=' ) ++ { ++ if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); ++ return 0; ++ } ++ ++ ++p; // skip '=' ++ p = SkipWhiteSpace( p, encoding ); ++ if ( !p || !*p ) ++ { ++ if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); ++ return 0; ++ } ++ ++ const char* end; ++ ++ if ( *p == '\'' ) ++ { ++ ++p; ++ end = "\'"; ++ p = ReadText( p, &value, false, end, false, encoding ); ++ } ++ else if ( *p == '"' ) ++ { ++ ++p; ++ end = "\""; ++ p = ReadText( p, &value, false, end, false, encoding ); ++ } ++ else ++ { ++ // All attribute values should be in single or double quotes. ++ // But this is such a common error that the parser will try ++ // its best, even without them. ++ value = ""; ++ while ( p && *p // existence ++ && !IsWhiteSpace( *p ) && *p != '\n' && *p != '\r' // whitespace ++ && *p != '/' && *p != '>' ) // tag end ++ { ++ value += *p; ++ ++p; ++ } ++ } ++ return p; ++} ++ ++#ifdef TIXML_USE_STL ++void TiXmlText::StreamIn( TIXML_ISTREAM * in, TIXML_STRING * tag ) ++{ ++ while ( in->good() ) ++ { ++ int c = in->peek(); ++ if ( c == '<' ) ++ return; ++ if ( c <= 0 ) ++ { ++ TiXmlDocument* document = GetDocument(); ++ if ( document ) ++ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); ++ return; ++ } ++ ++ (*tag) += (char) c; ++ in->get(); ++ } ++} ++#endif ++ ++const char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) ++{ ++ value = ""; ++// TiXmlParsingData data( p, prevData ); ++ if ( data ) ++ { ++ data->Stamp( p, encoding ); ++ location = data->Cursor(); ++ } ++ bool ignoreWhite = true; ++ ++ const char* end = "<"; ++ p = ReadText( p, &value, ignoreWhite, end, false, encoding ); ++ if ( p ) ++ return p-1; // don't truncate the '<' ++ return 0; ++} ++ ++#ifdef TIXML_USE_STL ++void TiXmlDeclaration::StreamIn( TIXML_ISTREAM * in, TIXML_STRING * tag ) ++{ ++ while ( in->good() ) ++ { ++ int c = in->get(); ++ if ( c <= 0 ) ++ { ++ TiXmlDocument* document = GetDocument(); ++ if ( document ) ++ document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); ++ return; ++ } ++ (*tag) += (char) c; ++ ++ if ( c == '>' ) ++ { ++ // All is well. ++ return; ++ } ++ } ++} ++#endif ++ ++const char* TiXmlDeclaration::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding _encoding ) ++{ ++ p = SkipWhiteSpace( p, _encoding ); ++ // Find the beginning, find the end, and look for ++ // the stuff in-between. ++ TiXmlDocument* document = GetDocument(); ++ if ( !p || !*p || !StringEqual( p, "SetError( TIXML_ERROR_PARSING_DECLARATION, 0, 0, _encoding ); ++ return 0; ++ } ++// TiXmlParsingData data( p, prevData ); ++ if ( data ) ++ { ++ data->Stamp( p, _encoding ); ++ location = data->Cursor(); ++ } ++ p += 5; ++ ++ version = ""; ++ encoding = ""; ++ standalone = ""; ++ ++ while ( p && *p ) ++ { ++ if ( *p == '>' ) ++ { ++ ++p; ++ return p; ++ } ++ ++ p = SkipWhiteSpace( p, _encoding ); ++ if ( StringEqual( p, "version", true, _encoding ) ) ++ { ++ TiXmlAttribute attrib; ++ p = attrib.Parse( p, data, _encoding ); ++ version = attrib.Value(); ++ } ++ else if ( StringEqual( p, "encoding", true, _encoding ) ) ++ { ++ TiXmlAttribute attrib; ++ p = attrib.Parse( p, data, _encoding ); ++ encoding = attrib.Value(); ++ } ++ else if ( StringEqual( p, "standalone", true, _encoding ) ) ++ { ++ TiXmlAttribute attrib; ++ p = attrib.Parse( p, data, _encoding ); ++ standalone = attrib.Value(); ++ } ++ else ++ { ++ // Read over whatever it is. ++ while( p && *p && *p != '>' && !IsWhiteSpace( *p ) ) ++ ++p; ++ } ++ } ++ return 0; ++} ++ ++bool TiXmlText::Blank() const ++{ ++ for ( unsigned i=0; i 0) +@@ -574,8 +798,20 @@ + Diseqcs.Load(AddDirectory(ConfigDirectory, "diseqc.conf"), true, Setup.DiSEqC); + Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true); + Timers.Load(AddDirectory(ConfigDirectory, "timers.conf")); ++#ifdef USE_CMDRECCMDI18N ++ LoadCommandsI18n(Commands, AddDirectory(ConfigDirectory, "commands.conf"), true); ++ LoadCommandsI18n(RecordingCommands, AddDirectory(ConfigDirectory, "reccmds.conf"), true); ++#else + Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"), true); + RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf"), true); ++#endif /* CMDRECCMDI18N */ ++#ifdef USE_TIMERCMD ++#ifdef USE_CMDRECCMDI18N ++ LoadCommandsI18n(TimerCommands, AddDirectory(ConfigDirectory, "timercmds.conf"), true); ++#else ++ TimerCommands.Load(AddDirectory(ConfigDirectory, "timercmds.conf"), true); ++#endif /* CMDRECCMDI18N */ ++#endif /* TIMERCMD */ + SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true); + Keys.Load(AddDirectory(ConfigDirectory, "remote.conf")); + KeyMacros.Load(AddDirectory(ConfigDirectory, "keymacros.conf"), true); +@@ -736,7 +972,11 @@ + // Make sure we have a visible programme in case device usage has changed: + if (!EITScanner.Active() && cDevice::PrimaryDevice()->HasDecoder() && !cDevice::PrimaryDevice()->HasProgramme()) { + static time_t lastTime = 0; ++#ifdef USE_CHANNELSCAN ++ if (!scanning_on_receiving_device && (!Menu || CheckHasProgramme) && Now - lastTime > MINCHANNELWAIT) { ++#else + if ((!Menu || CheckHasProgramme) && Now - lastTime > MINCHANNELWAIT) { // !Menu to avoid interfering with the CAM if a CAM menu is open ++#endif /* CHANNELSCAN */ + cChannel *Channel = Channels.GetByNumber(cDevice::CurrentChannel()); + if (Channel && (Channel->Vpid() || Channel->Apid(0))) { + if (!Channels.SwitchTo(cDevice::CurrentChannel()) // try to switch to the original channel... +@@ -919,6 +1159,9 @@ + cOsdObject *Interact = Menu ? Menu : cControl::Control(); + eKeys key = Interface->GetKey(!Interact || !Interact->NeedsFastResponse()); + if (ISREALKEY(key)) { ++#ifdef USE_PINPLUGIN ++ cStatus::MsgUserAction(key, Interact); ++#endif /* PINPLUGIN */ + EITScanner.Activity(); + // Cancel shutdown countdown: + if (ShutdownHandler.countdown) +@@ -991,10 +1234,16 @@ + cControl::Control()->Hide(); + cPlugin *plugin = cPluginManager::GetPlugin(PluginName); + if (plugin) { ++#ifdef USE_PINPLUGIN ++ if (!cStatus::MsgPluginProtected(plugin)) { ++#endif /* PINPLUGIN */ + Menu = plugin->MainMenuAction(); + if (Menu) + Menu->Show(); + } ++#ifdef USE_PINPLUGIN ++ } ++#endif /* PINPLUGIN */ + else + esyslog("ERROR: unknown plugin '%s'", PluginName); + } +@@ -1184,13 +1433,26 @@ + Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]); + break; + } ++#ifdef USE_VOLCTRL ++ // Left/Right volume control ++#else + // Direct Channel Select: + case k1 ... k9: + // Left/Right rotates through channel groups: ++#endif /* VOLCTRL */ + case kLeft|k_Repeat: + case kLeft: + case kRight|k_Repeat: + case kRight: ++#ifdef USE_VOLCTRL ++ if (Setup.LRVolumeControl && Setup.LRChannelGroups < 2) { ++ cRemote::Put(NORMALKEY(key) == kLeft ? kVolDn : kVolUp, true); ++ break; ++ } ++ // else fall through ++ // Direct Channel Select: ++ case k1 ... k9: ++#endif /* VOLCTRL */ + // Previous/Next rotates through channel groups: + case kPrev|k_Repeat: + case kPrev: +@@ -1208,9 +1470,15 @@ + // Instant resume of the last viewed recording: + case kPlay: + if (cReplayControl::LastReplayed()) { ++#ifdef USE_PINPLUGIN ++ if (cStatus::MsgReplayProtected(0, cReplayControl::LastReplayed(), 0, false) == false) { ++#endif /* PINPLUGIN */ + cControl::Shutdown(); + cControl::Launch(new cReplayControl); + } ++#ifdef USE_PINPLUGIN ++ } ++#endif /* PINPLUGIN */ + break; + default: break; + } +diff -NaurwB vdr-1.7.10/vdrttxtsubshooks.c vdr-1.7.10-patched/vdrttxtsubshooks.c +--- vdr-1.7.10/vdrttxtsubshooks.c 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/vdrttxtsubshooks.c 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,65 @@ ++#ifdef USE_TTXTSUBS ++/* ++ * vdr-ttxtsubs - A plugin for the Linux Video Disk Recorder ++ * Copyright (c) 2003 - 2008 Ragnar Sundblad ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by the ++ * Free Software Foundation; either version 2 of the License, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ++ * details. ++ * ++ * You should have received a copy of the GNU General Public License along with ++ * this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ * ++ */ ++ ++#include ++#include ++#include ++ ++#include "vdrttxtsubshooks.h" ++ ++// XXX Really should be a list... ++static cVDRTtxtsubsHookListener *gListener; ++ ++// ------ class cVDRTtxtsubsHookProxy ------ ++ ++class cVDRTtxtsubsHookProxy : public cVDRTtxtsubsHookListener ++{ ++ public: ++ virtual void HideOSD(void) { if(gListener) gListener->HideOSD(); }; ++ virtual void ShowOSD(void) { if(gListener) gListener->ShowOSD(); }; ++ virtual void PlayerTeletextData(uint8_t *p, int length, bool IsPesRecording) ++ { if(gListener) gListener->PlayerTeletextData(p, length, IsPesRecording); }; ++ virtual int ManualPageNumber(const cChannel *channel) ++ { if(gListener) return gListener->ManualPageNumber(channel); else return 0; }; ++}; ++ ++ ++// ------ class cVDRTtxtsubsHookListener ------ ++ ++cVDRTtxtsubsHookListener::~cVDRTtxtsubsHookListener() ++{ ++ gListener = 0; ++} ++ ++void cVDRTtxtsubsHookListener::HookAttach(void) ++{ ++ gListener = this; ++ //printf("cVDRTtxtsubsHookListener::HookAttach\n"); ++} ++ ++static cVDRTtxtsubsHookProxy gProxy; ++ ++cVDRTtxtsubsHookListener *cVDRTtxtsubsHookListener::Hook(void) ++{ ++ return &gProxy; ++} ++#endif /* TTXTSUBS */ ++ +diff -NaurwB vdr-1.7.10/vdrttxtsubshooks.h vdr-1.7.10-patched/vdrttxtsubshooks.h +--- vdr-1.7.10/vdrttxtsubshooks.h 1970-01-01 01:00:00.000000000 +0100 ++++ vdr-1.7.10-patched/vdrttxtsubshooks.h 2009-12-18 06:25:25.000000000 +0100 +@@ -0,0 +1,50 @@ ++#ifdef USE_TTXTSUBS ++/* ++ * vdr-ttxtsubs - A plugin for the Linux Video Disk Recorder ++ * Copyright (c) 2003 - 2008 Ragnar Sundblad ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by the ++ * Free Software Foundation; either version 2 of the License, or (at your option) ++ * any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ++ * details. ++ * ++ * You should have received a copy of the GNU General Public License along with ++ * this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ * ++ */ ++ ++#ifndef __VDRTTXTSUBSHOOKS_H ++#define __VDRTTXTSUBSHOOKS_H ++ ++#define TTXTSUBSVERSNUM 1 ++ ++class cDevice; ++class cChannel; ++ ++#define VDRTTXTSUBSHOOKS ++ ++class cVDRTtxtsubsHookListener { ++ public: ++ cVDRTtxtsubsHookListener(void) {}; ++ virtual ~cVDRTtxtsubsHookListener(); ++ ++ void HookAttach(void); ++ ++ virtual void HideOSD(void) {}; ++ virtual void ShowOSD(void) {}; ++ virtual void PlayerTeletextData(uint8_t *p, int length, bool IsPesRecording = true) {}; ++ virtual int ManualPageNumber(const cChannel *channel) ++ { return 0; }; ++ ++ // used by VDR to call hook listeners ++ static cVDRTtxtsubsHookListener *Hook(void); ++}; ++ ++#endif ++#endif /* TTXTSUBS */ +diff -NaurwB vdr-1.7.10/videodir.c vdr-1.7.10-patched/videodir.c +--- vdr-1.7.10/videodir.c 2008-02-16 14:00:03.000000000 +0100 ++++ vdr-1.7.10-patched/videodir.c 2009-12-18 06:29:15.000000000 +0100 +@@ -19,6 +19,10 @@ + #include "recording.h" + #include "tools.h" + ++#ifdef USE_HARDLINKCUTTER ++//#define HARDLINK_TEST_ONLY ++#endif /* HARDLINKCUTTER */ ++ + const char *VideoDirectory = VIDEODIR; + + class cVideoDirectory { +@@ -36,6 +40,11 @@ + bool Next(void); + void Store(void); + const char *Adjust(const char *FileName); ++#ifdef USE_DVLVIDPREFER ++ char *GetVidPath(int nVid); ++ bool GetPreferedVideoDir(void); ++ bool IsVidDirOK(int nVid, int *freeMB = NULL); ++#endif /* DVLVIDPREFER */ + }; + + cVideoDirectory::cVideoDirectory(void) +@@ -117,6 +126,9 @@ + if ((Flags & O_CREAT) != 0) { + cVideoDirectory Dir; + if (Dir.IsDistributed()) { ++#ifdef USE_DVLVIDPREFER ++ if (Setup.UseVidPrefer == 0) { ++#endif /* DVLVIDPREFER */ + // Find the directory with the most free space: + int MaxFree = Dir.FreeMB(); + while (Dir.Next()) { +@@ -126,14 +138,24 @@ + MaxFree = Free; + } + } ++#ifdef USE_DVLVIDPREFER ++ } ++ else Dir.GetPreferedVideoDir(); ++#endif /* DVLVIDPREFER */ + if (Dir.Stored()) { + ActualFileName = Dir.Adjust(FileName); + if (!MakeDirs(ActualFileName, false)) + return NULL; // errno has been set by MakeDirs() ++#ifdef USE_DVLVIDPREFER ++ if (strcmp(ActualFileName, FileName) != 0) { ++#endif /* DVLVIDPREFER */ + if (symlink(ActualFileName, FileName) < 0) { + LOG_ERROR_STR(FileName); + return NULL; + } ++#ifdef USE_DVLVIDPREFER ++ } ++#endif /* DVLVIDPREFER */ + ActualFileName = strdup(ActualFileName); // must survive Dir! + } + } +@@ -168,6 +190,122 @@ + return RemoveFileOrDir(FileName, true); + } + ++#ifdef USE_HARDLINKCUTTER ++static bool StatNearestDir(const char *FileName, struct stat *Stat) ++{ ++ cString Name(FileName); ++ char *p; ++ while ((p = strrchr((const char*)Name + 1, '/')) != NULL) { ++ *p = 0; // truncate at last '/' ++ if (stat(Name, Stat) == 0) { ++ isyslog("StatNearestDir: Stating %s", (const char*)Name); ++ return true; ++ } ++ } ++ return false; ++} ++ ++bool HardLinkVideoFile(const char *OldName, const char *NewName) ++{ ++ // Incoming name must be in base video directory: ++ if (strstr(OldName, VideoDirectory) != OldName) { ++ esyslog("ERROR: %s not in %s", OldName, VideoDirectory); ++ return false; ++ } ++ if (strstr(NewName, VideoDirectory) != NewName) { ++ esyslog("ERROR: %s not in %s", NewName, VideoDirectory); ++ return false; ++ } ++ ++ const char *ActualNewName = NewName; ++ cString ActualOldName(ReadLink(OldName), true); ++ ++ // Some safety checks: ++ struct stat StatOldName; ++ if (lstat(ActualOldName, &StatOldName) == 0) { ++ if (S_ISLNK(StatOldName.st_mode)) { ++ esyslog("HardLinkVideoFile: Failed to resolve symbolic link %s", (const char*)ActualOldName); ++ return false; ++ } ++ } ++ else { ++ esyslog("HardLinkVideoFile: lstat failed on %s", (const char*)ActualOldName); ++ return false; ++ } ++ isyslog("HardLinkVideoFile: %s is on %i", (const char*)ActualOldName, (int)StatOldName.st_dev); ++ ++ // Find the video directory where ActualOldName is located ++ ++ cVideoDirectory Dir; ++ struct stat StatDir; ++ if (!StatNearestDir(NewName, &StatDir)) { ++ esyslog("HardLinkVideoFile: stat failed on %s", NewName); ++ return false; ++ } ++ ++ isyslog("HardLinkVideoFile: %s is on %i", NewName, (int)StatDir.st_dev); ++ if (StatDir.st_dev != StatOldName.st_dev) { ++ // Not yet found. ++ ++ if (!Dir.IsDistributed()) { ++ esyslog("HardLinkVideoFile: No matching video folder to hard link %s", (const char*)ActualOldName); ++ return false; ++ } ++ ++ // Search in video01 and upwards ++ bool found = false; ++ while (Dir.Next()) { ++ Dir.Store(); ++ const char *TmpNewName = Dir.Adjust(NewName); ++ if (StatNearestDir(TmpNewName, &StatDir) && StatDir.st_dev == StatOldName.st_dev) { ++ isyslog("HardLinkVideoFile: %s is on %i (match)", TmpNewName, (int)StatDir.st_dev); ++ ActualNewName = TmpNewName; ++ found = true; ++ break; ++ } ++ isyslog("HardLinkVideoFile: %s is on %i", TmpNewName, (int)StatDir.st_dev); ++ } ++ if (ActualNewName == NewName) { ++ esyslog("HardLinkVideoFile: No matching video folder to hard link %s", (const char*)ActualOldName); ++ return false; ++ } ++ ++ // Looking good, we have a match. Create necessary folders. ++ if (!MakeDirs(ActualNewName, false)) ++ return false; ++ // There's no guarantee that the directory of ActualNewName ++ // is on the same device as the dir that StatNearestDir found. ++ // But worst case is that the link fails. ++ } ++ ++#ifdef HARDLINK_TEST_ONLY ++ // Do the hard link to *.vdr_ for testing only ++ char *name = NULL; ++ asprintf(&name, "%s_",ActualNewName); ++ link(ActualOldName, name); ++ free(name); ++ return false; ++#endif // HARDLINK_TEST_ONLY ++ ++ // Try creating the hard link ++ if (link(ActualOldName, ActualNewName) != 0) { ++ // Failed to hard link. Maybe not allowed on file system. ++ LOG_ERROR_STR(ActualNewName); ++ isyslog("HardLinkVideoFile: failed to hard link from %s to %s", (const char*)ActualOldName, ActualNewName); ++ return false; ++ } ++ ++ if (ActualNewName != NewName) { ++ // video01 and up. Do the remaining symlink ++ if (symlink(ActualNewName, NewName) < 0) { ++ LOG_ERROR_STR(NewName); ++ return false; ++ } ++ } ++ return true; ++} ++#endif /* HARDLINKCUTTER */ ++ + bool VideoFileSpaceAvailable(int SizeMB) + { + cVideoDirectory Dir; +@@ -232,6 +370,129 @@ + } while (Dir.Next()); + } + ++#ifdef USE_DVLVIDPREFER ++// returns path to nVid'th video directory or NULL if not existing ++char *cVideoDirectory::GetVidPath(int nVid) ++{ ++ char *b = strdup(VideoDirectory); ++ int l = strlen(b), di, n; ++ ++ while (l-- > 0 && isdigit(b[ l ])); ++ ++ l++; ++ di = strlen(b) - l; ++ ++ // di == number of digits ++ n = atoi(&b[ l ]); ++ if (n != 0) ++ return NULL; ++ ++ // add requested number to dir name ++ sprintf(&b[ l ], "%0*d", di, nVid); ++ ++ if (DirectoryOk(b) == true) ++ return b; ++ ++ free(b); ++ return NULL; ++} ++ ++// checks if a video dir is 'valid' ++bool cVideoDirectory::IsVidDirOK(int nVid, int *freeMB) ++{ ++ char *dn; ++ int fMB; ++ ++ if (nVid >= Setup.nVidPrefer) ++ return false; ++ ++ if (Setup.VidPreferSize[ nVid ] == -1) ++ return false; ++ ++ dn = GetVidPath(nVid); ++ if (dn == NULL) ++ return false; ++ ++ fMB = FreeDiskSpaceMB(dn, NULL); ++ if (freeMB != NULL) ++ *freeMB = fMB; ++ ++ free(dn); ++ ++ if (Setup.VidPreferSize[ nVid ] >= fMB) ++ return false; ++ return true; ++} ++ ++ ++// calculates which video dir to use ++bool cVideoDirectory::GetPreferedVideoDir(void) ++{ ++ cVideoDirectory d; ++ int nDirs = 1, ++ vidUse = Setup.nVidPrefer; ++ int i, top, topFree, x; ++ ++ if (name == NULL) ++ return(false); ++ ++ // count available video dirs ++ while (d.Next() == true) ++ nDirs++; ++ ++ if (vidUse > nDirs) ++ vidUse = nDirs; ++ ++ // check for prefered video dir ++ for (i = 0, top = -1, topFree = 0; i < vidUse; i++) { ++ if (IsVidDirOK(i, &x) == true) { ++ if (top == -1) { ++ // nothing set yet, use first 'ok' dir ++ top = i; ++ topFree = x; ++ } ++ else { ++ // check if we got a higher priority ++ if (Setup.VidPreferPrio[ i ] >= Setup.VidPreferPrio[ top ]) { ++ top = i; ++ topFree = x; ++ } ++ // check if we got same priority but more space ++ else if (Setup.VidPreferPrio[ i ] == Setup.VidPreferPrio[ top ] && x >= topFree) { ++ top = i; ++ topFree = x; ++ } ++ } ++ } ++ } ++ ++ if (top == -1) { ++ isyslog("VidPrefer: no prefered video directory could be determined!"); ++ ++ // something went wrong here... ++ // let VDR determine the video directory ++ int MaxFree = FreeMB(); ++ ++ while (Next()) { ++ int Free = FreeDiskSpaceMB(Name()); ++ ++ if (Free > MaxFree) { ++ Store(); ++ MaxFree = Free; ++ } ++ } ++ } ++ else { ++ isyslog("VidPrefer: prefered video directory '%d' set.", top); ++ if (stored != NULL) ++ free(stored); ++ stored = GetVidPath(top); ++ } ++ ++ return true; ++} ++#endif /* DVLVIDPREFER */ ++ + bool IsOnVideoDirectoryFileSystem(const char *FileName) + { + cVideoDirectory Dir; +diff -NaurwB vdr-1.7.10/videodir.h vdr-1.7.10-patched/videodir.h +--- vdr-1.7.10/videodir.h 2008-02-16 13:53:11.000000000 +0100 ++++ vdr-1.7.10-patched/videodir.h 2009-12-18 06:25:25.000000000 +0100 +@@ -19,6 +19,9 @@ + int CloseVideoFile(cUnbufferedFile *File); + bool RenameVideoFile(const char *OldName, const char *NewName); + bool RemoveVideoFile(const char *FileName); ++#ifdef USE_HARDLINKCUTTER ++bool HardLinkVideoFile(const char *OldName, const char *NewName); ++#endif /* HARDLINKCUTTER */ + bool VideoFileSpaceAvailable(int SizeMB); + int VideoDiskSpace(int *FreeMB = NULL, int *UsedMB = NULL); // returns the used disk space in percent + cString PrefixVideoFileName(const char *FileName, char Prefix); +diff -NaurwB vdr-1.7.10/Make.config.template vdr-1.7.10-patched/Make.config.template +--- vdr-1.7.10/Make.config.template 2009-01-18 11:46:13.000000000 +0100 ++++ vdr-1.7.10-patched/Make.config.template 2009-12-18 06:37:57.000000000 +0100 +@@ -42,8 +42,247 @@ + ## Define if you want vdr to not run as root + #VDR_USER = vdr + ++### VDR-Extensions: ++# Comment the patches you don't need ++# DVDCHAPJUMP needs DVDARCHIVE enabled ++# DVDARCHIVE needs LIEMIEXT enabled ++# SORTRECORDS needs LIEMIEXT enabled ++# you can only enable MENUORG or SETUP ++ ++#ANALOGTV = 1 ++#ATSC = 1 ++#CHANNELSCAN = 1 ++CMDRECCMDI18N = 1 ++CMDSUBMENU = 1 ++#CUTTERLIMIT = 1 ++#CUTTERQUEUE = 1 ++CUTTIME = 1 ++DDEPGENTRY = 1 ++#DELTIMESHIFTREC = 1 ++DOLBYINREC = 1 ++#DVBSETUP = 1 ++#DVDARCHIVE = 1 ++#DVDCHAPJUMP = 1 ++#DVLFRIENDLYFNAMES = 1 ++#DVLRECSCRIPTADDON = 1 ++#DVLVIDPREFER = 1 ++#EM84XX = 1 ++#GRAPHTFT = 1 ++#HARDLINKCUTTER = 1 ++#JUMPPLAY = 1 ++LIEMIEXT = 1 ++#LIRCSETTINGS = 1 ++#LNBSHARE = 1 ++#MAINMENUHOOKS = 1 ++#MENUORG = 1 ++#NOEPG = 1 ++#OSDMAXITEMS = 1 ++PARENTALRATING = 1 ++#PINPLUGIN = 1 ++PLUGINAPI = 1 ++PLUGINMISSING = 1 ++#PLUGINPARAM = 1 ++#ROTOR = 1 ++SETTIME = 1 ++#SETUP = 1 ++#SOFTOSD = 1 ++#SOURCECAPS = 1 ++#SORTRECORDS = 1 ++STATUS_EXTENSION = 1 ++#TIMERCMD = 1 ++#TIMERINFO = 1 ++#TTXTSUBS = 1 ++#VALIDINPUT = 1 ++#VOLCTRL = 1 ++WAREAGLEICON = 1 ++#YAEPG = 1 ++ + ### You don't need to touch the following: + + ifdef DVBDIR + INCLUDES += -I$(DVBDIR)/include + endif ++ ++ifdef ANALOGTV ++DEFINES += -DUSE_ANALOGTV ++endif ++ ++ifdef ATSC ++DEFINES += -DUSE_ATSC ++endif ++ ++ifdef CHANNELSCAN ++DEFINES += -DUSE_CHANNELSCAN ++endif ++ ++ifdef CMDRECCMDI18N ++DEFINES += -DUSE_CMDRECCMDI18N ++endif ++ ++ifdef CMDSUBMENU ++DEFINES += -DUSE_CMDSUBMENU ++endif ++ ++ifdef CUTTERLIMIT ++DEFINES += -DUSE_CUTTERLIMIT ++endif ++ ++ifdef CUTTERQUEUE ++DEFINES += -DUSE_CUTTERQUEUE ++endif ++ ++ifdef CUTTIME ++DEFINES += -DUSE_CUTTIME ++endif ++ ++ifdef DDEPGENTRY ++DEFINES += -DUSE_DDEPGENTRY ++endif ++ ++ifdef DELTIMESHIFTREC ++DEFINES += -DUSE_DELTIMESHIFTREC ++endif ++ ++ifdef DOLBYINREC ++DEFINES += -DUSE_DOLBYINREC ++endif ++ ++ifdef DVBSETUP ++DEFINES += -DUSE_DVBSETUP ++endif ++ ++ifdef DVDARCHIVE ++ifdef LIEMIEXT ++DEFINES += -DUSE_DVDARCHIVE ++endif ++endif ++ ++ifdef DVLRECSCRIPTADDON ++DEFINES += -DUSE_DVLRECSCRIPTADDON ++endif ++ ++ifdef DVLVIDPREFER ++DEFINES += -DUSE_DVLVIDPREFER ++endif ++ ++ifdef DVLFRIENDLYFNAMES ++DEFINES += -DUSE_DVLFRIENDLYFNAMES ++endif ++ ++ifdef EM84XX ++DEFINES += -DUSE_EM84XX ++endif ++ ++ifdef GRAPHTFT ++DEFINES += -DUSE_GRAPHTFT ++endif ++ ++ifdef HARDLINKCUTTER ++DEFINES += -DUSE_HARDLINKCUTTER ++endif ++ ++ifdef JUMPPLAY ++DEFINES += -DUSE_JUMPPLAY ++endif ++ ++ifdef LIEMIEXT ++DEFINES += -DUSE_LIEMIEXT ++endif ++ ++ifdef LIRCSETTINGS ++DEFINES += -DUSE_LIRCSETTINGS ++endif ++ ++ifdef LNBSHARE ++DEFINES += -DUSE_LNBSHARE ++endif ++ ++ifdef MAINMENUHOOKS ++DEFINES += -DUSE_MAINMENUHOOKS ++endif ++ ++ifdef MENUORG ++DEFINES += -DUSE_MENUORG ++else ++ifdef SETUP ++DEFINES += -DUSE_SETUP ++endif ++endif ++ ++ifdef NOEPG ++DEFINES += -DUSE_NOEPG ++endif ++ ++ifdef OSDMAXITEMS ++DEFINES += -DUSE_OSDMAXITEMS ++endif ++ ++ifdef PARENTALRATING ++DEFINES += -DUSE_PARENTALRATING ++endif ++ ++ifdef PINPLUGIN ++DEFINES += -DUSE_PINPLUGIN ++endif ++ ++ifdef PLUGINMISSING ++DEFINES += -DUSE_PLUGINMISSING ++endif ++ ++ifdef PLUGINPARAM ++DEFINES += -DUSE_PLUGINPARAM ++endif ++ ++ifdef ROTOR ++DEFINES += -DUSE_ROTOR ++endif ++ ++ifdef SETTIME ++DEFINES += -DUSE_SETTIME ++endif ++ ++ifdef STATUS_EXTENSION ++DEFINES += -DUSE_STATUS_EXTENSION ++endif ++ ++ifdef SOFTOSD ++DEFINES += -DUSE_SOFTOSD ++endif ++ ++ifdef SOURCECAPS ++DEFINES += -DUSE_SOURCECAPS ++endif ++ ++ifdef SORTRECORDS ++ifdef LIEMIEXT ++DEFINES += -DUSE_SORTRECORDS ++endif ++endif ++ ++ifdef TIMERCMD ++DEFINES += -DUSE_TIMERCMD ++endif ++ ++ifdef TIMERINFO ++DEFINES += -DUSE_TIMERINFO ++endif ++ ++ifdef TTXTSUBS ++DEFINES += -DUSE_TTXTSUBS ++endif ++ ++ifdef VALIDINPUT ++DEFINES += -DUSE_VALIDINPUT ++endif ++ ++ifdef VOLCTRL ++DEFINES += -DUSE_VOLCTRL ++endif ++ ++ifdef WAREAGLEICON ++DEFINES += -DUSE_WAREAGLEICON ++endif ++ ++ifdef YAEPG ++DEFINES += -DUSE_YAEPG ++endif diff --git a/xbmc/pvrclients/vdr-streamdev/patches/vdr-1.7.7-ExtendetStatusMessage.diff b/xbmc/pvrclients/vdr-streamdev/patches/vdr-1.7.7-ExtendetStatusMessage.diff new file mode 100644 index 0000000000..04b3600ae8 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/patches/vdr-1.7.7-ExtendetStatusMessage.diff @@ -0,0 +1,113 @@ +diff -NaurwB vdr-1.7.7/skins.c vdr-1.7.7-patched/skins.c +--- vdr-1.7.7/skins.c 2008-02-17 12:31:09.000000000 +0100 ++++ vdr-1.7.7-patched/skins.c 2009-11-23 05:13:30.000000000 +0100 +@@ -237,7 +237,11 @@ + } + cSkinDisplay::Current()->SetMessage(Type, s); + cSkinDisplay::Current()->Flush(); ++#ifdef USE_STATUS_EXTENSION ++ cStatus::MsgOsdStatusMessage(Type, s); ++#else + cStatus::MsgOsdStatusMessage(s); ++#endif + eKeys k = kNone; + if (Type != mtStatus) { + k = Interface->Wait(Seconds); +@@ -248,7 +252,11 @@ + } + else { + cSkinDisplay::Current()->SetMessage(Type, NULL); ++#ifdef USE_STATUS_EXTENSION ++ cStatus::MsgOsdStatusMessage(Type, NULL); ++#else + cStatus::MsgOsdStatusMessage(NULL); ++#endif + } + } + else if (!s && displayMessage) { +diff -NaurwB vdr-1.7.7/status.c vdr-1.7.7-patched/status.c +--- vdr-1.7.7/status.c 2009-06-13 01:01:25.000000000 +0200 ++++ vdr-1.7.7-patched/status.c 2009-11-23 04:49:48.000000000 +0100 +@@ -97,11 +97,22 @@ + sm->OsdTitle(Title); + } + ++#ifdef USE_STATUS_EXTENSION ++void cStatus::MsgOsdStatusMessage(eMessageType type, const char *Message) ++{ ++ for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) ++ { ++ sm->OsdStatusMessage(type, Message); ++ sm->OsdStatusMessage(Message); // For comaptibilty ++ } ++} ++#else + void cStatus::MsgOsdStatusMessage(const char *Message) + { + for (cStatus *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) + sm->OsdStatusMessage(Message); + } ++#endif + + void cStatus::MsgOsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue) + { +diff -NaurwB vdr-1.7.7/status.h vdr-1.7.7-patched/status.h +--- vdr-1.7.7/status.h 2009-06-13 01:01:25.000000000 +0200 ++++ vdr-1.7.7-patched/status.h 2009-11-23 04:50:34.000000000 +0100 +@@ -17,6 +17,10 @@ + #ifdef USE_PINPLUGIN + #include "plugin.h" + #endif /* PINPLUGIN */ ++#ifdef USE_STATUS_EXTENSION ++#include "skins.h" ++#endif /* STATUS_EXTENSION */ ++ + + enum eTimerChange { tcMod, tcAdd, tcDel }; + #ifdef USE_STREAMDEVEXT +@@ -80,6 +84,11 @@ + virtual void OsdStatusMessage(const char *Message) {} + // Message has been displayed in the status line of the menu. + // If Message is NULL, the status line has been cleared. ++#ifdef USE_STATUS_EXTENSION ++ virtual void OsdStatusMessage(eMessageType type, const char *Message) {} ++ // Message has been displayed in the status line of the menu. ++ // If Message is NULL, the status line has been cleared. ++#endif + virtual void OsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue) {} + // The help keys have been set to the given values (may be NULL). + virtual void OsdItem(const char *Text, int Index) {} +@@ -145,7 +154,11 @@ + static void MsgSetSubtitleTrack(int Index, const char * const *Tracks); + static void MsgOsdClear(void); + static void MsgOsdTitle(const char *Title); ++#ifdef USE_STATUS_EXTENSION ++ static void MsgOsdStatusMessage(eMessageType type, const char *Message); ++#else + static void MsgOsdStatusMessage(const char *Message); ++#endif + static void MsgOsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue); + static void MsgOsdItem(const char *Text, int Index); + static void MsgOsdCurrentItem(const char *Text); +diff -NaurwB vdr-1.7.7/Make.config.template vdr-1.7.7-patched/Make.config.template +--- vdr-1.7.7/Make.config.template 2009-06-13 01:05:04.000000000 +0200 ++++ vdr-1.7.7-patched/Make.config.template 2009-11-23 04:51:28.000000000 +0100 +@@ -87,6 +87,7 @@ + #SOFTOSD = 1 + #SOURCECAPS = 1 + #SORTRECORDS = 1 ++STATUS_EXTENSION = 1 + #STREAMDEVEXT = 1 + #TIMERCMD = 1 + #TIMERINFO = 1 +@@ -240,6 +241,10 @@ + DEFINES += -DUSE_SETTIME + endif + ++ifdef STATUS_EXTENSION ++DEFINES += -DUSE_STATUS_EXTENSION ++endif ++ + ifdef SOFTOSD + DEFINES += -DUSE_SOFTOSD + endif diff --git a/xbmc/pvrclients/vdr-streamdev/patches/vdr-1.7.7-GenreToFromEpgDat.diff b/xbmc/pvrclients/vdr-streamdev/patches/vdr-1.7.7-GenreToFromEpgDat.diff new file mode 100644 index 0000000000..4ff0f85f0e --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/patches/vdr-1.7.7-GenreToFromEpgDat.diff @@ -0,0 +1,46 @@ +diff -NaurwB vdr-1.7.7/epg.c vdr-1.7.7-patched/epg.c +--- vdr-1.7.7/epg.c 2009-06-13 01:01:25.000000000 +0200 ++++ vdr-1.7.7-patched/epg.c 2009-11-07 20:40:34.000000000 +0100 +@@ -630,6 +630,17 @@ + fprintf(f, "%sX %s\n", Prefix, *p->ToString()); + } + } ++#ifdef USE_PARENTALRATING ++ if (!isempty(GetContentsString())) { ++ for (int i = 0; i < MAXEVCONTENTS; i++) { ++ if (!isempty(GetContentsString(i))) { ++ strreplace(description, '\n', '|'); ++ fprintf(f, "%sG %i %i %s\n",Prefix, Contents(i) & 0xF0, Contents(i) & 0x0F, (const char *)GetContentsString(i)); ++ strreplace(description, '|', '\n'); ++ } ++ } ++ } ++#endif /* PARENTALRATING */ + if (vps) + fprintf(f, "%sV %ld\n", Prefix, vps); + if (!InfoOnly) +@@ -697,6 +708,24 @@ + } + } + break; ++#ifdef USE_PARENTALRATING ++ case 'G': if (Event) { ++ unsigned int ContentID = 0; ++ unsigned int ContentSubID = 0; ++ int n = sscanf(t, "%u %u", &ContentID, &ContentSubID); ++ if (n == 2) { ++ if (ContentID != 0) { ++ for (int i = 0; i < MAXEVCONTENTS; i++) { ++ if (Event->Contents(i) == 0) { ++ Event->contents[i] = ContentID | ContentSubID; ++ break; ++ } ++ } ++ } ++ } ++ } ++ break; ++#endif /* PARENTALRATING */ + case 'e': if (Event && !Event->Title()) + Event->SetTitle(tr("No title")); + Event = NULL; diff --git a/xbmc/pvrclients/vdr-streamdev/project/VS2008Express/XBMC_VDR.sln b/xbmc/pvrclients/vdr-streamdev/project/VS2008Express/XBMC_VDR.sln new file mode 100644 index 0000000000..41a4731724 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/project/VS2008Express/XBMC_VDR.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XBMC_VDR", "XBMC_VDR.vcproj", "{838CDE27-AFDF-449F-9981-A78903C9C71E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {838CDE27-AFDF-449F-9981-A78903C9C71E}.Debug|Win32.ActiveCfg = Debug|Win32 + {838CDE27-AFDF-449F-9981-A78903C9C71E}.Debug|Win32.Build.0 = Debug|Win32 + {838CDE27-AFDF-449F-9981-A78903C9C71E}.Release|Win32.ActiveCfg = Release|Win32 + {838CDE27-AFDF-449F-9981-A78903C9C71E}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/xbmc/pvrclients/vdr-streamdev/project/VS2008Express/XBMC_VDR.vcproj b/xbmc/pvrclients/vdr-streamdev/project/VS2008Express/XBMC_VDR.vcproj new file mode 100644 index 0000000000..b6d0061973 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/project/VS2008Express/XBMC_VDR.vcproj @@ -0,0 +1,334 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xbmc/pvrclients/vdr-streamdev/project/VS2008Express/inttypes.h b/xbmc/pvrclients/vdr-streamdev/project/VS2008Express/inttypes.h new file mode 100644 index 0000000000..41c334771c --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/project/VS2008Express/inttypes.h @@ -0,0 +1,305 @@ +// ISO C9x compliant inttypes.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. The name of the author may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_INTTYPES_H_ // [ +#define _MSC_INTTYPES_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include + +// 7.8 Format conversion of integer types + +typedef struct { + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +// 7.8.1 Macros for format specifiers + +#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198 + +// The fprintf macros for signed integers are: +#define PRId8 "d" +#define PRIi8 "i" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIdLEAST16 "hd" +#define PRIiLEAST16 "hi" +#define PRIdFAST16 "hd" +#define PRIiFAST16 "hi" + +#define PRId32 "I32d" +#define PRIi32 "I32i" +#define PRIdLEAST32 "I32d" +#define PRIiLEAST32 "I32i" +#define PRIdFAST32 "I32d" +#define PRIiFAST32 "I32i" + +#define PRId64 "I64d" +#define PRIi64 "I64i" +#define PRIdLEAST64 "I64d" +#define PRIiLEAST64 "I64i" +#define PRIdFAST64 "I64d" +#define PRIiFAST64 "I64i" + +#define PRIdMAX "I64d" +#define PRIiMAX "I64i" + +#define PRIdPTR "Id" +#define PRIiPTR "Ii" + +// The fprintf macros for unsigned integers are: +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" + +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" +#define PRIoLEAST16 "ho" +#define PRIuLEAST16 "hu" +#define PRIxLEAST16 "hx" +#define PRIXLEAST16 "hX" +#define PRIoFAST16 "ho" +#define PRIuFAST16 "hu" +#define PRIxFAST16 "hx" +#define PRIXFAST16 "hX" + +#define PRIo32 "I32o" +#define PRIu32 "I32u" +#define PRIx32 "I32x" +#define PRIX32 "I32X" +#define PRIoLEAST32 "I32o" +#define PRIuLEAST32 "I32u" +#define PRIxLEAST32 "I32x" +#define PRIXLEAST32 "I32X" +#define PRIoFAST32 "I32o" +#define PRIuFAST32 "I32u" +#define PRIxFAST32 "I32x" +#define PRIXFAST32 "I32X" + +#define PRIo64 "I64o" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#define PRIX64 "I64X" +#define PRIoLEAST64 "I64o" +#define PRIuLEAST64 "I64u" +#define PRIxLEAST64 "I64x" +#define PRIXLEAST64 "I64X" +#define PRIoFAST64 "I64o" +#define PRIuFAST64 "I64u" +#define PRIxFAST64 "I64x" +#define PRIXFAST64 "I64X" + +#define PRIoMAX "I64o" +#define PRIuMAX "I64u" +#define PRIxMAX "I64x" +#define PRIXMAX "I64X" + +#define PRIoPTR "Io" +#define PRIuPTR "Iu" +#define PRIxPTR "Ix" +#define PRIXPTR "IX" + +// The fscanf macros for signed integers are: +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" + +#define SCNd16 "hd" +#define SCNi16 "hi" +#define SCNdLEAST16 "hd" +#define SCNiLEAST16 "hi" +#define SCNdFAST16 "hd" +#define SCNiFAST16 "hi" + +#define SCNd32 "ld" +#define SCNi32 "li" +#define SCNdLEAST32 "ld" +#define SCNiLEAST32 "li" +#define SCNdFAST32 "ld" +#define SCNiFAST32 "li" + +#define SCNd64 "I64d" +#define SCNi64 "I64i" +#define SCNdLEAST64 "I64d" +#define SCNiLEAST64 "I64i" +#define SCNdFAST64 "I64d" +#define SCNiFAST64 "I64i" + +#define SCNdMAX "I64d" +#define SCNiMAX "I64i" + +#ifdef _WIN64 // [ +# define SCNdPTR "I64d" +# define SCNiPTR "I64i" +#else // _WIN64 ][ +# define SCNdPTR "ld" +# define SCNiPTR "li" +#endif // _WIN64 ] + +// The fscanf macros for unsigned integers are: +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNX8 "X" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNXLEAST8 "X" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNXFAST8 "X" + +#define SCNo16 "ho" +#define SCNu16 "hu" +#define SCNx16 "hx" +#define SCNX16 "hX" +#define SCNoLEAST16 "ho" +#define SCNuLEAST16 "hu" +#define SCNxLEAST16 "hx" +#define SCNXLEAST16 "hX" +#define SCNoFAST16 "ho" +#define SCNuFAST16 "hu" +#define SCNxFAST16 "hx" +#define SCNXFAST16 "hX" + +#define SCNo32 "lo" +#define SCNu32 "lu" +#define SCNx32 "lx" +#define SCNX32 "lX" +#define SCNoLEAST32 "lo" +#define SCNuLEAST32 "lu" +#define SCNxLEAST32 "lx" +#define SCNXLEAST32 "lX" +#define SCNoFAST32 "lo" +#define SCNuFAST32 "lu" +#define SCNxFAST32 "lx" +#define SCNXFAST32 "lX" + +#define SCNo64 "I64o" +#define SCNu64 "I64u" +#define SCNx64 "I64x" +#define SCNX64 "I64X" +#define SCNoLEAST64 "I64o" +#define SCNuLEAST64 "I64u" +#define SCNxLEAST64 "I64x" +#define SCNXLEAST64 "I64X" +#define SCNoFAST64 "I64o" +#define SCNuFAST64 "I64u" +#define SCNxFAST64 "I64x" +#define SCNXFAST64 "I64X" + +#define SCNoMAX "I64o" +#define SCNuMAX "I64u" +#define SCNxMAX "I64x" +#define SCNXMAX "I64X" + +#ifdef _WIN64 // [ +# define SCNoPTR "I64o" +# define SCNuPTR "I64u" +# define SCNxPTR "I64x" +# define SCNXPTR "I64X" +#else // _WIN64 ][ +# define SCNoPTR "lo" +# define SCNuPTR "lu" +# define SCNxPTR "lx" +# define SCNXPTR "lX" +#endif // _WIN64 ] + +#endif // __STDC_FORMAT_MACROS ] + +// 7.8.2 Functions for greatest-width integer types + +// 7.8.2.1 The imaxabs function +#define imaxabs _abs64 + +// 7.8.2.2 The imaxdiv function + +// This is modified version of div() function from Microsoft's div.c found +// in %MSVC.NET%\crt\src\div.c +#ifdef STATIC_IMAXDIV // [ +static +#else // STATIC_IMAXDIV ][ +_inline +#endif // STATIC_IMAXDIV ] +imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) +{ + imaxdiv_t result; + + result.quot = numer / denom; + result.rem = numer % denom; + + if (numer < 0 && result.rem > 0) { + // did division wrong; must fix up + ++result.quot; + result.rem -= denom; + } + + return result; +} + +// 7.8.2.3 The strtoimax and strtoumax functions +#define strtoimax _strtoi64 +#define strtoumax _strtoui64 + +// 7.8.2.4 The wcstoimax and wcstoumax functions +#define wcstoimax _wcstoi64 +#define wcstoumax _wcstoui64 + + +#endif // _MSC_INTTYPES_H_ ] diff --git a/xbmc/pvrclients/vdr-streamdev/project/VS2008Express/stdint.h b/xbmc/pvrclients/vdr-streamdev/project/VS2008Express/stdint.h new file mode 100644 index 0000000000..d0daff3197 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/project/VS2008Express/stdint.h @@ -0,0 +1,247 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2008 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. The name of the author may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we should wrap include with 'extern "C++" {}' +// or compiler give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#ifdef __cplusplus +extern "C" { +#endif +# include +#ifdef __cplusplus +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef char int8_t; + typedef short int16_t; + typedef int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +#define INTMAX_C INT64_C +#define UINTMAX_C UINT64_C + +#endif // __STDC_CONSTANT_MACROS ] + + +#endif // _MSC_STDINT_H_ ] diff --git a/xbmc/pvrclients/vdr-streamdev/pthread_win32/pthread.h b/xbmc/pvrclients/vdr-streamdev/pthread_win32/pthread.h new file mode 100644 index 0000000000..9f2868bccb --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/pthread_win32/pthread.h @@ -0,0 +1,1368 @@ +/* This is an implementation of the threads API of POSIX 1003.1-2001. + * + * -------------------------------------------------------------------------- + * + * Pthreads-win32 - POSIX Threads Library for Win32 + * Copyright(C) 1998 John E. Bossom + * Copyright(C) 1999,2005 Pthreads-win32 contributors + * + * Contact Email: rpj@callisto.canberra.edu.au + * + * The current list of contributors is contained + * in the file CONTRIBUTORS included with the source + * code distribution. The list can also be seen at the + * following World Wide Web location: + * http://sources.redhat.com/pthreads-win32/contributors.html + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library in the file COPYING.LIB; + * if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#if !defined( PTHREAD_H ) +#define PTHREAD_H + +/* + * See the README file for an explanation of the pthreads-win32 version + * numbering scheme and how the DLL is named etc. + */ +#define PTW32_VERSION 2,8,0,0 +#define PTW32_VERSION_STRING "2, 8, 0, 0\0" + +/* There are three implementations of cancel cleanup. + * Note that pthread.h is included in both application + * compilation units and also internally for the library. + * The code here and within the library aims to work + * for all reasonable combinations of environments. + * + * The three implementations are: + * + * WIN32 SEH + * C + * C++ + * + * Please note that exiting a push/pop block via + * "return", "exit", "break", or "continue" will + * lead to different behaviour amongst applications + * depending upon whether the library was built + * using SEH, C++, or C. For example, a library built + * with SEH will call the cleanup routine, while both + * C++ and C built versions will not. + */ + +/* + * Define defaults for cleanup code. + * Note: Unless the build explicitly defines one of the following, then + * we default to standard C style cleanup. This style uses setjmp/longjmp + * in the cancelation and thread exit implementations and therefore won't + * do stack unwinding if linked to applications that have it (e.g. + * C++ apps). This is currently consistent with most/all commercial Unix + * POSIX threads implementations. + */ +#if !defined( __CLEANUP_SEH ) && !defined( __CLEANUP_CXX ) && !defined( __CLEANUP_C ) +# define __CLEANUP_C +#endif + +#if defined( __CLEANUP_SEH ) && ( !defined( _MSC_VER ) && !defined(PTW32_RC_MSC)) +#error ERROR [__FILE__, line __LINE__]: SEH is not supported for this compiler. +#endif + +/* + * Stop here if we are being included by the resource compiler. + */ +#ifndef RC_INVOKED + +#undef PTW32_LEVEL + +#if defined(_POSIX_SOURCE) +#define PTW32_LEVEL 0 +/* Early POSIX */ +#endif + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309 +#undef PTW32_LEVEL +#define PTW32_LEVEL 1 +/* Include 1b, 1c and 1d */ +#endif + +#if defined(INCLUDE_NP) +#undef PTW32_LEVEL +#define PTW32_LEVEL 2 +/* Include Non-Portable extensions */ +#endif + +#define PTW32_LEVEL_MAX 3 + +#if !defined(PTW32_LEVEL) +#define PTW32_LEVEL PTW32_LEVEL_MAX +/* Include everything */ +#endif + +#ifdef _UWIN +# define HAVE_STRUCT_TIMESPEC 1 +# define HAVE_SIGNAL_H 1 +# undef HAVE_CONFIG_H +# pragma comment(lib, "pthread") +#endif + +/* + * ------------------------------------------------------------- + * + * + * Module: pthread.h + * + * Purpose: + * Provides an implementation of PThreads based upon the + * standard: + * + * POSIX 1003.1-2001 + * and + * The Single Unix Specification version 3 + * + * (these two are equivalent) + * + * in order to enhance code portability between Windows, + * various commercial Unix implementations, and Linux. + * + * See the ANNOUNCE file for a full list of conforming + * routines and defined constants, and a list of missing + * routines and constants not defined in this implementation. + * + * Authors: + * There have been many contributors to this library. + * The initial implementation was contributed by + * John Bossom, and several others have provided major + * sections or revisions of parts of the implementation. + * Often significant effort has been contributed to + * find and fix important bugs and other problems to + * improve the reliability of the library, which sometimes + * is not reflected in the amount of code which changed as + * result. + * As much as possible, the contributors are acknowledged + * in the ChangeLog file in the source code distribution + * where their changes are noted in detail. + * + * Contributors are listed in the CONTRIBUTORS file. + * + * As usual, all bouquets go to the contributors, and all + * brickbats go to the project maintainer. + * + * Maintainer: + * The code base for this project is coordinated and + * eventually pre-tested, packaged, and made available by + * + * Ross Johnson + * + * QA Testers: + * Ultimately, the library is tested in the real world by + * a host of competent and demanding scientists and + * engineers who report bugs and/or provide solutions + * which are then fixed or incorporated into subsequent + * versions of the library. Each time a bug is fixed, a + * test case is written to prove the fix and ensure + * that later changes to the code don't reintroduce the + * same error. The number of test cases is slowly growing + * and therefore so is the code reliability. + * + * Compliance: + * See the file ANNOUNCE for the list of implemented + * and not-implemented routines and defined options. + * Of course, these are all defined is this file as well. + * + * Web site: + * The source code and other information about this library + * are available from + * + * http://sources.redhat.com/pthreads-win32/ + * + * ------------------------------------------------------------- + */ + +/* Try to avoid including windows.h */ +#if defined(__MINGW32__) && defined(__cplusplus) +#define PTW32_INCLUDE_WINDOWS_H +#endif + +#ifdef PTW32_INCLUDE_WINDOWS_H +#include +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1300 || defined(__DMC__) +/* + * VC++6.0 or early compiler's header has no DWORD_PTR type. + */ +typedef unsigned long DWORD_PTR; +#endif +/* + * ----------------- + * autoconf switches + * ----------------- + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#ifndef NEED_FTIME +#include +#else /* NEED_FTIME */ +/* use native WIN32 time API */ +#endif /* NEED_FTIME */ + +#if HAVE_SIGNAL_H +#include +#endif /* HAVE_SIGNAL_H */ + +#include +#include + +/* + * Boolean values to make us independent of system includes. + */ +enum { + PTW32_FALSE = 0, + PTW32_TRUE = (! PTW32_FALSE) +}; + +/* + * This is a duplicate of what is in the autoconf config.h, + * which is only used when building the pthread-win32 libraries. + */ + +#ifndef PTW32_CONFIG_H +# if defined(WINCE) +# define NEED_ERRNO +# define NEED_SEM +# endif +# if defined(_UWIN) || defined(__MINGW32__) +# define HAVE_MODE_T +# endif +#endif + +/* + * + */ + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX +#ifdef NEED_ERRNO +#include "need_errno.h" +#else +#include +#endif +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +/* + * Several systems don't define some error numbers. + */ +#ifndef ENOTSUP +# define ENOTSUP 48 /* This is the value in Solaris. */ +#endif + +#ifndef ETIMEDOUT +# define ETIMEDOUT 10060 /* This is the value in winsock.h. */ +#endif + +#ifndef ENOSYS +# define ENOSYS 140 /* Semi-arbitrary value */ +#endif + +#ifndef EDEADLK +# ifdef EDEADLOCK +# define EDEADLK EDEADLOCK +# else +# define EDEADLK 36 /* This is the value in MSVC. */ +# endif +#endif + +#include + +/* + * To avoid including windows.h we define only those things that we + * actually need from it. + */ +#ifndef PTW32_INCLUDE_WINDOWS_H +#ifndef HANDLE +# define PTW32__HANDLE_DEF +# define HANDLE void * +#endif +#ifndef DWORD +# define PTW32__DWORD_DEF +# define DWORD unsigned long +#endif +#endif + +#ifndef HAVE_STRUCT_TIMESPEC +#define HAVE_STRUCT_TIMESPEC 1 +struct timespec { + long tv_sec; + long tv_nsec; +}; +#endif /* HAVE_STRUCT_TIMESPEC */ + +#ifndef SIG_BLOCK +#define SIG_BLOCK 0 +#endif /* SIG_BLOCK */ + +#ifndef SIG_UNBLOCK +#define SIG_UNBLOCK 1 +#endif /* SIG_UNBLOCK */ + +#ifndef SIG_SETMASK +#define SIG_SETMASK 2 +#endif /* SIG_SETMASK */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * ------------------------------------------------------------- + * + * POSIX 1003.1-2001 Options + * ========================= + * + * Options are normally set in , which is not provided + * with pthreads-win32. + * + * For conformance with the Single Unix Specification (version 3), all of the + * options below are defined, and have a value of either -1 (not supported) + * or 200112L (supported). + * + * These options can neither be left undefined nor have a value of 0, because + * either indicates that sysconf(), which is not implemented, may be used at + * runtime to check the status of the option. + * + * _POSIX_THREADS (== 200112L) + * If == 200112L, you can use threads + * + * _POSIX_THREAD_ATTR_STACKSIZE (== 200112L) + * If == 200112L, you can control the size of a thread's + * stack + * pthread_attr_getstacksize + * pthread_attr_setstacksize + * + * _POSIX_THREAD_ATTR_STACKADDR (== -1) + * If == 200112L, you can allocate and control a thread's + * stack. If not supported, the following functions + * will return ENOSYS, indicating they are not + * supported: + * pthread_attr_getstackaddr + * pthread_attr_setstackaddr + * + * _POSIX_THREAD_PRIORITY_SCHEDULING (== -1) + * If == 200112L, you can use realtime scheduling. + * This option indicates that the behaviour of some + * implemented functions conforms to the additional TPS + * requirements in the standard. E.g. rwlocks favour + * writers over readers when threads have equal priority. + * + * _POSIX_THREAD_PRIO_INHERIT (== -1) + * If == 200112L, you can create priority inheritance + * mutexes. + * pthread_mutexattr_getprotocol + + * pthread_mutexattr_setprotocol + + * + * _POSIX_THREAD_PRIO_PROTECT (== -1) + * If == 200112L, you can create priority ceiling mutexes + * Indicates the availability of: + * pthread_mutex_getprioceiling + * pthread_mutex_setprioceiling + * pthread_mutexattr_getprioceiling + * pthread_mutexattr_getprotocol + + * pthread_mutexattr_setprioceiling + * pthread_mutexattr_setprotocol + + * + * _POSIX_THREAD_PROCESS_SHARED (== -1) + * If set, you can create mutexes and condition + * variables that can be shared with another + * process.If set, indicates the availability + * of: + * pthread_mutexattr_getpshared + * pthread_mutexattr_setpshared + * pthread_condattr_getpshared + * pthread_condattr_setpshared + * + * _POSIX_THREAD_SAFE_FUNCTIONS (== 200112L) + * If == 200112L you can use the special *_r library + * functions that provide thread-safe behaviour + * + * _POSIX_READER_WRITER_LOCKS (== 200112L) + * If == 200112L, you can use read/write locks + * + * _POSIX_SPIN_LOCKS (== 200112L) + * If == 200112L, you can use spin locks + * + * _POSIX_BARRIERS (== 200112L) + * If == 200112L, you can use barriers + * + * + These functions provide both 'inherit' and/or + * 'protect' protocol, based upon these macro + * settings. + * + * ------------------------------------------------------------- + */ + +/* + * POSIX Options + */ +#undef _POSIX_THREADS +#define _POSIX_THREADS 200112L + +#undef _POSIX_READER_WRITER_LOCKS +#define _POSIX_READER_WRITER_LOCKS 200112L + +#undef _POSIX_SPIN_LOCKS +#define _POSIX_SPIN_LOCKS 200112L + +#undef _POSIX_BARRIERS +#define _POSIX_BARRIERS 200112L + +#undef _POSIX_THREAD_SAFE_FUNCTIONS +#define _POSIX_THREAD_SAFE_FUNCTIONS 200112L + +#undef _POSIX_THREAD_ATTR_STACKSIZE +#define _POSIX_THREAD_ATTR_STACKSIZE 200112L + +/* + * The following options are not supported + */ +#undef _POSIX_THREAD_ATTR_STACKADDR +#define _POSIX_THREAD_ATTR_STACKADDR -1 + +#undef _POSIX_THREAD_PRIO_INHERIT +#define _POSIX_THREAD_PRIO_INHERIT -1 + +#undef _POSIX_THREAD_PRIO_PROTECT +#define _POSIX_THREAD_PRIO_PROTECT -1 + +/* TPS is not fully supported. */ +#undef _POSIX_THREAD_PRIORITY_SCHEDULING +#define _POSIX_THREAD_PRIORITY_SCHEDULING -1 + +#undef _POSIX_THREAD_PROCESS_SHARED +#define _POSIX_THREAD_PROCESS_SHARED -1 + + +/* + * POSIX 1003.1-2001 Limits + * =========================== + * + * These limits are normally set in , which is not provided with + * pthreads-win32. + * + * PTHREAD_DESTRUCTOR_ITERATIONS + * Maximum number of attempts to destroy + * a thread's thread-specific data on + * termination (must be at least 4) + * + * PTHREAD_KEYS_MAX + * Maximum number of thread-specific data keys + * available per process (must be at least 128) + * + * PTHREAD_STACK_MIN + * Minimum supported stack size for a thread + * + * PTHREAD_THREADS_MAX + * Maximum number of threads supported per + * process (must be at least 64). + * + * SEM_NSEMS_MAX + * The maximum number of semaphores a process can have. + * (must be at least 256) + * + * SEM_VALUE_MAX + * The maximum value a semaphore can have. + * (must be at least 32767) + * + */ +#undef _POSIX_THREAD_DESTRUCTOR_ITERATIONS +#define _POSIX_THREAD_DESTRUCTOR_ITERATIONS 4 + +#undef PTHREAD_DESTRUCTOR_ITERATIONS +#define PTHREAD_DESTRUCTOR_ITERATIONS _POSIX_THREAD_DESTRUCTOR_ITERATIONS + +#undef _POSIX_THREAD_KEYS_MAX +#define _POSIX_THREAD_KEYS_MAX 128 + +#undef PTHREAD_KEYS_MAX +#define PTHREAD_KEYS_MAX _POSIX_THREAD_KEYS_MAX + +#undef PTHREAD_STACK_MIN +#define PTHREAD_STACK_MIN 0 + +#undef _POSIX_THREAD_THREADS_MAX +#define _POSIX_THREAD_THREADS_MAX 64 + + /* Arbitrary value */ +#undef PTHREAD_THREADS_MAX +#define PTHREAD_THREADS_MAX 2019 + +#undef _POSIX_SEM_NSEMS_MAX +#define _POSIX_SEM_NSEMS_MAX 256 + + /* Arbitrary value */ +#undef SEM_NSEMS_MAX +#define SEM_NSEMS_MAX 1024 + +#undef _POSIX_SEM_VALUE_MAX +#define _POSIX_SEM_VALUE_MAX 32767 + +#undef SEM_VALUE_MAX +#define SEM_VALUE_MAX INT_MAX + + +#if __GNUC__ && ! defined (__declspec) +# error Please upgrade your GNU compiler to one that supports __declspec. +#endif + +/* + * When building the DLL code, you should define PTW32_BUILD so that + * the variables/functions are exported correctly. When using the DLL, + * do NOT define PTW32_BUILD, and then the variables/functions will + * be imported correctly. + */ +#ifndef PTW32_STATIC_LIB +# ifdef PTW32_BUILD +# define PTW32_DLLPORT __declspec (dllexport) +# else +# define PTW32_DLLPORT __declspec (dllimport) +# endif +#else +# define PTW32_DLLPORT +#endif + +/* + * The Open Watcom C/C++ compiler uses a non-standard calling convention + * that passes function args in registers unless __cdecl is explicitly specified + * in exposed function prototypes. + * + * We force all calls to cdecl even though this could slow Watcom code down + * slightly. If you know that the Watcom compiler will be used to build both + * the DLL and application, then you can probably define this as a null string. + * Remember that pthread.h (this file) is used for both the DLL and application builds. + */ +#define PTW32_CDECL __cdecl + +#if defined(_UWIN) && PTW32_LEVEL >= PTW32_LEVEL_MAX +# include +#else +/* + * Generic handle type - intended to extend uniqueness beyond + * that available with a simple pointer. It should scale for either + * IA-32 or IA-64. + */ +typedef struct { + void * p; /* Pointer to actual object */ + unsigned int x; /* Extra information - reuse count etc */ +} ptw32_handle_t; + +typedef ptw32_handle_t pthread_t; +typedef struct pthread_attr_t_ * pthread_attr_t; +typedef struct pthread_once_t_ pthread_once_t; +typedef struct pthread_key_t_ * pthread_key_t; +typedef struct pthread_mutex_t_ * pthread_mutex_t; +typedef struct pthread_mutexattr_t_ * pthread_mutexattr_t; +typedef struct pthread_cond_t_ * pthread_cond_t; +typedef struct pthread_condattr_t_ * pthread_condattr_t; +#endif +typedef struct pthread_rwlock_t_ * pthread_rwlock_t; +typedef struct pthread_rwlockattr_t_ * pthread_rwlockattr_t; +typedef struct pthread_spinlock_t_ * pthread_spinlock_t; +typedef struct pthread_barrier_t_ * pthread_barrier_t; +typedef struct pthread_barrierattr_t_ * pthread_barrierattr_t; + +/* + * ==================== + * ==================== + * POSIX Threads + * ==================== + * ==================== + */ + +enum { +/* + * pthread_attr_{get,set}detachstate + */ + PTHREAD_CREATE_JOINABLE = 0, /* Default */ + PTHREAD_CREATE_DETACHED = 1, + +/* + * pthread_attr_{get,set}inheritsched + */ + PTHREAD_INHERIT_SCHED = 0, + PTHREAD_EXPLICIT_SCHED = 1, /* Default */ + +/* + * pthread_{get,set}scope + */ + PTHREAD_SCOPE_PROCESS = 0, + PTHREAD_SCOPE_SYSTEM = 1, /* Default */ + +/* + * pthread_setcancelstate paramters + */ + PTHREAD_CANCEL_ENABLE = 0, /* Default */ + PTHREAD_CANCEL_DISABLE = 1, + +/* + * pthread_setcanceltype parameters + */ + PTHREAD_CANCEL_ASYNCHRONOUS = 0, + PTHREAD_CANCEL_DEFERRED = 1, /* Default */ + +/* + * pthread_mutexattr_{get,set}pshared + * pthread_condattr_{get,set}pshared + */ + PTHREAD_PROCESS_PRIVATE = 0, + PTHREAD_PROCESS_SHARED = 1, + +/* + * pthread_barrier_wait + */ + PTHREAD_BARRIER_SERIAL_THREAD = -1 +}; + +/* + * ==================== + * ==================== + * Cancelation + * ==================== + * ==================== + */ +#define PTHREAD_CANCELED ((void *) -1) + + +/* + * ==================== + * ==================== + * Once Key + * ==================== + * ==================== + */ +#define PTHREAD_ONCE_INIT { PTW32_FALSE, 0, 0, 0} + +struct pthread_once_t_ +{ + int done; /* indicates if user function has been executed */ + void * lock; + int reserved1; + int reserved2; +}; + + +/* + * ==================== + * ==================== + * Object initialisers + * ==================== + * ==================== + */ +#define PTHREAD_MUTEX_INITIALIZER ((pthread_mutex_t) -1) +#define PTHREAD_RECURSIVE_MUTEX_INITIALIZER ((pthread_mutex_t) -2) +#define PTHREAD_ERRORCHECK_MUTEX_INITIALIZER ((pthread_mutex_t) -3) + +/* + * Compatibility with LinuxThreads + */ +#define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP PTHREAD_RECURSIVE_MUTEX_INITIALIZER +#define PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP PTHREAD_ERRORCHECK_MUTEX_INITIALIZER + +#define PTHREAD_COND_INITIALIZER ((pthread_cond_t) -1) + +#define PTHREAD_RWLOCK_INITIALIZER ((pthread_rwlock_t) -1) + +#define PTHREAD_SPINLOCK_INITIALIZER ((pthread_spinlock_t) -1) + + +/* + * Mutex types. + */ +enum +{ + /* Compatibility with LinuxThreads */ + PTHREAD_MUTEX_FAST_NP, + PTHREAD_MUTEX_RECURSIVE_NP, + PTHREAD_MUTEX_ERRORCHECK_NP, + PTHREAD_MUTEX_TIMED_NP = PTHREAD_MUTEX_FAST_NP, + PTHREAD_MUTEX_ADAPTIVE_NP = PTHREAD_MUTEX_FAST_NP, + /* For compatibility with POSIX */ + PTHREAD_MUTEX_NORMAL = PTHREAD_MUTEX_FAST_NP, + PTHREAD_MUTEX_RECURSIVE = PTHREAD_MUTEX_RECURSIVE_NP, + PTHREAD_MUTEX_ERRORCHECK = PTHREAD_MUTEX_ERRORCHECK_NP, + PTHREAD_MUTEX_DEFAULT = PTHREAD_MUTEX_NORMAL +}; + + +typedef struct ptw32_cleanup_t ptw32_cleanup_t; + +#if defined(_MSC_VER) +/* Disable MSVC 'anachronism used' warning */ +#pragma warning( disable : 4229 ) +#endif + +typedef void (* PTW32_CDECL ptw32_cleanup_callback_t)(void *); + +#if defined(_MSC_VER) +#pragma warning( default : 4229 ) +#endif + +struct ptw32_cleanup_t +{ + ptw32_cleanup_callback_t routine; + void *arg; + struct ptw32_cleanup_t *prev; +}; + +#ifdef __CLEANUP_SEH + /* + * WIN32 SEH version of cancel cleanup. + */ + +#define pthread_cleanup_push( _rout, _arg ) \ + { \ + ptw32_cleanup_t _cleanup; \ + \ + _cleanup.routine = (ptw32_cleanup_callback_t)(_rout); \ + _cleanup.arg = (_arg); \ + __try \ + { \ + +#define pthread_cleanup_pop( _execute ) \ + } \ + __finally \ + { \ + if( _execute || AbnormalTermination()) \ + { \ + (*(_cleanup.routine))( _cleanup.arg ); \ + } \ + } \ + } + +#else /* __CLEANUP_SEH */ + +#ifdef __CLEANUP_C + + /* + * C implementation of PThreads cancel cleanup + */ + +#define pthread_cleanup_push( _rout, _arg ) \ + { \ + ptw32_cleanup_t _cleanup; \ + \ + ptw32_push_cleanup( &_cleanup, (ptw32_cleanup_callback_t) (_rout), (_arg) ); \ + +#define pthread_cleanup_pop( _execute ) \ + (void) ptw32_pop_cleanup( _execute ); \ + } + +#else /* __CLEANUP_C */ + +#ifdef __CLEANUP_CXX + + /* + * C++ version of cancel cleanup. + * - John E. Bossom. + */ + + class PThreadCleanup { + /* + * PThreadCleanup + * + * Purpose + * This class is a C++ helper class that is + * used to implement pthread_cleanup_push/ + * pthread_cleanup_pop. + * The destructor of this class automatically + * pops the pushed cleanup routine regardless + * of how the code exits the scope + * (i.e. such as by an exception) + */ + ptw32_cleanup_callback_t cleanUpRout; + void * obj; + int executeIt; + + public: + PThreadCleanup() : + cleanUpRout( 0 ), + obj( 0 ), + executeIt( 0 ) + /* + * No cleanup performed + */ + { + } + + PThreadCleanup( + ptw32_cleanup_callback_t routine, + void * arg ) : + cleanUpRout( routine ), + obj( arg ), + executeIt( 1 ) + /* + * Registers a cleanup routine for 'arg' + */ + { + } + + ~PThreadCleanup() + { + if ( executeIt && ((void *) cleanUpRout != (void *) 0) ) + { + (void) (*cleanUpRout)( obj ); + } + } + + void execute( int exec ) + { + executeIt = exec; + } + }; + + /* + * C++ implementation of PThreads cancel cleanup; + * This implementation takes advantage of a helper + * class who's destructor automatically calls the + * cleanup routine if we exit our scope weirdly + */ +#define pthread_cleanup_push( _rout, _arg ) \ + { \ + PThreadCleanup cleanup((ptw32_cleanup_callback_t)(_rout), \ + (void *) (_arg) ); + +#define pthread_cleanup_pop( _execute ) \ + cleanup.execute( _execute ); \ + } + +#else + +#error ERROR [__FILE__, line __LINE__]: Cleanup type undefined. + +#endif /* __CLEANUP_CXX */ + +#endif /* __CLEANUP_C */ + +#endif /* __CLEANUP_SEH */ + +/* + * =============== + * =============== + * Methods + * =============== + * =============== + */ + +/* + * PThread Attribute Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_attr_init (pthread_attr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_destroy (pthread_attr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getdetachstate (const pthread_attr_t * attr, + int *detachstate); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getstackaddr (const pthread_attr_t * attr, + void **stackaddr); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getstacksize (const pthread_attr_t * attr, + size_t * stacksize); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setdetachstate (pthread_attr_t * attr, + int detachstate); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setstackaddr (pthread_attr_t * attr, + void *stackaddr); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setstacksize (pthread_attr_t * attr, + size_t stacksize); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getschedparam (const pthread_attr_t *attr, + struct sched_param *param); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setschedparam (pthread_attr_t *attr, + const struct sched_param *param); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setschedpolicy (pthread_attr_t *, + int); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getschedpolicy (pthread_attr_t *, + int *); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setinheritsched(pthread_attr_t * attr, + int inheritsched); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getinheritsched(pthread_attr_t * attr, + int * inheritsched); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_setscope (pthread_attr_t *, + int); + +PTW32_DLLPORT int PTW32_CDECL pthread_attr_getscope (const pthread_attr_t *, + int *); + +/* + * PThread Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_create (pthread_t * tid, + const pthread_attr_t * attr, + void *(*start) (void *), + void *arg); + +PTW32_DLLPORT int PTW32_CDECL pthread_detach (pthread_t tid); + +PTW32_DLLPORT int PTW32_CDECL pthread_equal (pthread_t t1, + pthread_t t2); + +PTW32_DLLPORT void PTW32_CDECL pthread_exit (void *value_ptr); + +PTW32_DLLPORT int PTW32_CDECL pthread_join (pthread_t thread, + void **value_ptr); + +PTW32_DLLPORT pthread_t PTW32_CDECL pthread_self (void); + +PTW32_DLLPORT int PTW32_CDECL pthread_cancel (pthread_t thread); + +PTW32_DLLPORT int PTW32_CDECL pthread_setcancelstate (int state, + int *oldstate); + +PTW32_DLLPORT int PTW32_CDECL pthread_setcanceltype (int type, + int *oldtype); + +PTW32_DLLPORT void PTW32_CDECL pthread_testcancel (void); + +PTW32_DLLPORT int PTW32_CDECL pthread_once (pthread_once_t * once_control, + void (*init_routine) (void)); + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX +PTW32_DLLPORT ptw32_cleanup_t * PTW32_CDECL ptw32_pop_cleanup (int execute); + +PTW32_DLLPORT void PTW32_CDECL ptw32_push_cleanup (ptw32_cleanup_t * cleanup, + void (*routine) (void *), + void *arg); +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +/* + * Thread Specific Data Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_key_create (pthread_key_t * key, + void (*destructor) (void *)); + +PTW32_DLLPORT int PTW32_CDECL pthread_key_delete (pthread_key_t key); + +PTW32_DLLPORT int PTW32_CDECL pthread_setspecific (pthread_key_t key, + const void *value); + +PTW32_DLLPORT void * PTW32_CDECL pthread_getspecific (pthread_key_t key); + + +/* + * Mutex Attribute Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_init (pthread_mutexattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_destroy (pthread_mutexattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_getpshared (const pthread_mutexattr_t + * attr, + int *pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_setpshared (pthread_mutexattr_t * attr, + int pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_settype (pthread_mutexattr_t * attr, int kind); +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_gettype (pthread_mutexattr_t * attr, int *kind); + +/* + * Barrier Attribute Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_barrierattr_init (pthread_barrierattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrierattr_destroy (pthread_barrierattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrierattr_getpshared (const pthread_barrierattr_t + * attr, + int *pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrierattr_setpshared (pthread_barrierattr_t * attr, + int pshared); + +/* + * Mutex Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_init (pthread_mutex_t * mutex, + const pthread_mutexattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_destroy (pthread_mutex_t * mutex); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_lock (pthread_mutex_t * mutex); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_timedlock(pthread_mutex_t *mutex, + const struct timespec *abstime); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_trylock (pthread_mutex_t * mutex); + +PTW32_DLLPORT int PTW32_CDECL pthread_mutex_unlock (pthread_mutex_t * mutex); + +/* + * Spinlock Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_spin_init (pthread_spinlock_t * lock, int pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_spin_destroy (pthread_spinlock_t * lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_spin_lock (pthread_spinlock_t * lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_spin_trylock (pthread_spinlock_t * lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_spin_unlock (pthread_spinlock_t * lock); + +/* + * Barrier Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_barrier_init (pthread_barrier_t * barrier, + const pthread_barrierattr_t * attr, + unsigned int count); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrier_destroy (pthread_barrier_t * barrier); + +PTW32_DLLPORT int PTW32_CDECL pthread_barrier_wait (pthread_barrier_t * barrier); + +/* + * Condition Variable Attribute Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_condattr_init (pthread_condattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_condattr_destroy (pthread_condattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_condattr_getpshared (const pthread_condattr_t * attr, + int *pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_condattr_setpshared (pthread_condattr_t * attr, + int pshared); + +/* + * Condition Variable Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_cond_init (pthread_cond_t * cond, + const pthread_condattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_destroy (pthread_cond_t * cond); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_wait (pthread_cond_t * cond, + pthread_mutex_t * mutex); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_timedwait (pthread_cond_t * cond, + pthread_mutex_t * mutex, + const struct timespec *abstime); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_signal (pthread_cond_t * cond); + +PTW32_DLLPORT int PTW32_CDECL pthread_cond_broadcast (pthread_cond_t * cond); + +/* + * Scheduling + */ +PTW32_DLLPORT int PTW32_CDECL pthread_setschedparam (pthread_t thread, + int policy, + const struct sched_param *param); + +PTW32_DLLPORT int PTW32_CDECL pthread_getschedparam (pthread_t thread, + int *policy, + struct sched_param *param); + +PTW32_DLLPORT int PTW32_CDECL pthread_setconcurrency (int); + +PTW32_DLLPORT int PTW32_CDECL pthread_getconcurrency (void); + +/* + * Read-Write Lock Functions + */ +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_init(pthread_rwlock_t *lock, + const pthread_rwlockattr_t *attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_destroy(pthread_rwlock_t *lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_tryrdlock(pthread_rwlock_t *); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_trywrlock(pthread_rwlock_t *); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_rdlock(pthread_rwlock_t *lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_timedrdlock(pthread_rwlock_t *lock, + const struct timespec *abstime); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_wrlock(pthread_rwlock_t *lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_timedwrlock(pthread_rwlock_t *lock, + const struct timespec *abstime); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlock_unlock(pthread_rwlock_t *lock); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlockattr_init (pthread_rwlockattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlockattr_destroy (pthread_rwlockattr_t * attr); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlockattr_getpshared (const pthread_rwlockattr_t * attr, + int *pshared); + +PTW32_DLLPORT int PTW32_CDECL pthread_rwlockattr_setpshared (pthread_rwlockattr_t * attr, + int pshared); + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX - 1 + +/* + * Signal Functions. Should be defined in but MSVC and MinGW32 + * already have signal.h that don't define these. + */ +PTW32_DLLPORT int PTW32_CDECL pthread_kill(pthread_t thread, int sig); + +/* + * Non-portable functions + */ + +/* + * Compatibility with Linux. + */ +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_setkind_np(pthread_mutexattr_t * attr, + int kind); +PTW32_DLLPORT int PTW32_CDECL pthread_mutexattr_getkind_np(pthread_mutexattr_t * attr, + int *kind); + +/* + * Possibly supported by other POSIX threads implementations + */ +PTW32_DLLPORT int PTW32_CDECL pthread_delay_np (struct timespec * interval); +PTW32_DLLPORT int PTW32_CDECL pthread_num_processors_np(void); + +/* + * Useful if an application wants to statically link + * the lib rather than load the DLL at run-time. + */ +PTW32_DLLPORT int PTW32_CDECL pthread_win32_process_attach_np(void); +PTW32_DLLPORT int PTW32_CDECL pthread_win32_process_detach_np(void); +PTW32_DLLPORT int PTW32_CDECL pthread_win32_thread_attach_np(void); +PTW32_DLLPORT int PTW32_CDECL pthread_win32_thread_detach_np(void); + +/* + * Features that are auto-detected at load/run time. + */ +PTW32_DLLPORT int PTW32_CDECL pthread_win32_test_features_np(int); +enum ptw32_features { + PTW32_SYSTEM_INTERLOCKED_COMPARE_EXCHANGE = 0x0001, /* System provides it. */ + PTW32_ALERTABLE_ASYNC_CANCEL = 0x0002 /* Can cancel blocked threads. */ +}; + +/* + * Register a system time change with the library. + * Causes the library to perform various functions + * in response to the change. Should be called whenever + * the application's top level window receives a + * WM_TIMECHANGE message. It can be passed directly to + * pthread_create() as a new thread if desired. + */ +PTW32_DLLPORT void * PTW32_CDECL pthread_timechange_handler_np(void *); + +#endif /*PTW32_LEVEL >= PTW32_LEVEL_MAX - 1 */ + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX + +/* + * Returns the Win32 HANDLE for the POSIX thread. + */ +PTW32_DLLPORT HANDLE PTW32_CDECL pthread_getw32threadhandle_np(pthread_t thread); + + +/* + * Protected Methods + * + * This function blocks until the given WIN32 handle + * is signaled or pthread_cancel had been called. + * This function allows the caller to hook into the + * PThreads cancel mechanism. It is implemented using + * + * WaitForMultipleObjects + * + * on 'waitHandle' and a manually reset WIN32 Event + * used to implement pthread_cancel. The 'timeout' + * argument to TimedWait is simply passed to + * WaitForMultipleObjects. + */ +PTW32_DLLPORT int PTW32_CDECL pthreadCancelableWait (HANDLE waitHandle); +PTW32_DLLPORT int PTW32_CDECL pthreadCancelableTimedWait (HANDLE waitHandle, + DWORD timeout); + +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +/* + * Thread-Safe C Runtime Library Mappings. + */ +#ifndef _UWIN +# if defined(NEED_ERRNO) + PTW32_DLLPORT int * PTW32_CDECL _errno( void ); +# else +# ifndef errno +# if (defined(_MT) || defined(_DLL)) + __declspec(dllimport) extern int * __cdecl _errno(void); +# define errno (*_errno()) +# endif +# endif +# endif +#endif + +/* + * WIN32 C runtime library had been made thread-safe + * without affecting the user interface. Provide + * mappings from the UNIX thread-safe versions to + * the standard C runtime library calls. + * Only provide function mappings for functions that + * actually exist on WIN32. + */ + +#if !defined(__MINGW32__) +#define strtok_r( _s, _sep, _lasts ) \ + ( *(_lasts) = strtok( (_s), (_sep) ) ) +#endif /* !__MINGW32__ */ + +#define asctime_r( _tm, _buf ) \ + ( strcpy( (_buf), asctime( (_tm) ) ), \ + (_buf) ) + +#define ctime_r( _clock, _buf ) \ + ( strcpy( (_buf), ctime( (_clock) ) ), \ + (_buf) ) + +#define gmtime_r( _clock, _result ) \ + ( *(_result) = *gmtime( (_clock) ), \ + (_result) ) + +#define localtime_r( _clock, _result ) \ + ( *(_result) = *localtime( (_clock) ), \ + (_result) ) + +#define rand_r( _seed ) \ + ( _seed == _seed? rand() : rand() ) + + +/* + * Some compiler environments don't define some things. + */ +#if defined(__BORLANDC__) +# define _ftime ftime +# define _timeb timeb +#endif + +#ifdef __cplusplus + +/* + * Internal exceptions + */ +class ptw32_exception {}; +class ptw32_exception_cancel : public ptw32_exception {}; +class ptw32_exception_exit : public ptw32_exception {}; + +#endif + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX + +/* FIXME: This is only required if the library was built using SEH */ +/* + * Get internal SEH tag + */ +PTW32_DLLPORT DWORD PTW32_CDECL ptw32_get_exception_services_code(void); + +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +#ifndef PTW32_BUILD + +#ifdef __CLEANUP_SEH + +/* + * Redefine the SEH __except keyword to ensure that applications + * propagate our internal exceptions up to the library's internal handlers. + */ +#define __except( E ) \ + __except( ( GetExceptionCode() == ptw32_get_exception_services_code() ) \ + ? EXCEPTION_CONTINUE_SEARCH : ( E ) ) + +#endif /* __CLEANUP_SEH */ + +#ifdef __CLEANUP_CXX + +/* + * Redefine the C++ catch keyword to ensure that applications + * propagate our internal exceptions up to the library's internal handlers. + */ +#ifdef _MSC_VER + /* + * WARNING: Replace any 'catch( ... )' with 'PtW32CatchAll' + * if you want Pthread-Win32 cancelation and pthread_exit to work. + */ + +#ifndef PtW32NoCatchWarn + +#pragma message("Specify \"/DPtW32NoCatchWarn\" compiler flag to skip this message.") +#pragma message("------------------------------------------------------------------") +#pragma message("When compiling applications with MSVC++ and C++ exception handling:") +#pragma message(" Replace any 'catch( ... )' in routines called from POSIX threads") +#pragma message(" with 'PtW32CatchAll' or 'CATCHALL' if you want POSIX thread") +#pragma message(" cancelation and pthread_exit to work. For example:") +#pragma message("") +#pragma message(" #ifdef PtW32CatchAll") +#pragma message(" PtW32CatchAll") +#pragma message(" #else") +#pragma message(" catch(...)") +#pragma message(" #endif") +#pragma message(" {") +#pragma message(" /* Catchall block processing */") +#pragma message(" }") +#pragma message("------------------------------------------------------------------") + +#endif + +#define PtW32CatchAll \ + catch( ptw32_exception & ) { throw; } \ + catch( ... ) + +#else /* _MSC_VER */ + +#define catch( E ) \ + catch( ptw32_exception & ) { throw; } \ + catch( E ) + +#endif /* _MSC_VER */ + +#endif /* __CLEANUP_CXX */ + +#endif /* ! PTW32_BUILD */ + +#ifdef __cplusplus +} /* End of extern "C" */ +#endif /* __cplusplus */ + +#ifdef PTW32__HANDLE_DEF +# undef HANDLE +#endif +#ifdef PTW32__DWORD_DEF +# undef DWORD +#endif + +#undef PTW32_LEVEL +#undef PTW32_LEVEL_MAX + +#endif /* ! RC_INVOKED */ + +#endif /* PTHREAD_H */ diff --git a/xbmc/pvrclients/vdr-streamdev/pthread_win32/pthreadVC2.lib b/xbmc/pvrclients/vdr-streamdev/pthread_win32/pthreadVC2.lib new file mode 100644 index 0000000000000000000000000000000000000000..bc36770d52c3a3880e1fbd6924387256ca17829e GIT binary patch literal 29052 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 literal 0 HcmV?d00001 diff --git a/xbmc/pvrclients/vdr-streamdev/pthread_win32/pthreadVC2d.lib b/xbmc/pvrclients/vdr-streamdev/pthread_win32/pthreadVC2d.lib new file mode 100644 index 0000000000000000000000000000000000000000..0df71c7dfbf37c046823c184ed0f576b1d2b47c0 GIT binary patch literal 29172 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 199309 +#undef PTW32_LEVEL +#define PTW32_LEVEL 1 +/* Include 1b, 1c and 1d */ +#endif + +#if defined(INCLUDE_NP) +#undef PTW32_LEVEL +#define PTW32_LEVEL 2 +/* Include Non-Portable extensions */ +#endif + +#define PTW32_LEVEL_MAX 3 + +#if !defined(PTW32_LEVEL) +#define PTW32_LEVEL PTW32_LEVEL_MAX +/* Include everything */ +#endif + + +#if __GNUC__ && ! defined (__declspec) +# error Please upgrade your GNU compiler to one that supports __declspec. +#endif + +/* + * When building the DLL code, you should define PTW32_BUILD so that + * the variables/functions are exported correctly. When using the DLL, + * do NOT define PTW32_BUILD, and then the variables/functions will + * be imported correctly. + */ +#ifndef PTW32_STATIC_LIB +# ifdef PTW32_BUILD +# define PTW32_DLLPORT __declspec (dllexport) +# else +# define PTW32_DLLPORT __declspec (dllimport) +# endif +#else +# define PTW32_DLLPORT +#endif + +/* + * This is a duplicate of what is in the autoconf config.h, + * which is only used when building the pthread-win32 libraries. + */ + +#ifndef PTW32_CONFIG_H +# if defined(WINCE) +# define NEED_ERRNO +# define NEED_SEM +# endif +# if defined(_UWIN) || defined(__MINGW32__) +# define HAVE_MODE_T +# endif +#endif + +/* + * + */ + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX +#ifdef NEED_ERRNO +#include "need_errno.h" +#else +#include +#endif +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +#if defined(__MINGW32__) || defined(_UWIN) +#if PTW32_LEVEL >= PTW32_LEVEL_MAX +/* For pid_t */ +# include +/* Required by Unix 98 */ +# include +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ +#else +typedef int pid_t; +#endif + +/* Thread scheduling policies */ + +enum { + SCHED_OTHER = 0, + SCHED_FIFO, + SCHED_RR, + SCHED_MIN = SCHED_OTHER, + SCHED_MAX = SCHED_RR +}; + +struct sched_param { + int sched_priority; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PTW32_DLLPORT int __cdecl sched_yield (void); + +PTW32_DLLPORT int __cdecl sched_get_priority_min (int policy); + +PTW32_DLLPORT int __cdecl sched_get_priority_max (int policy); + +PTW32_DLLPORT int __cdecl sched_setscheduler (pid_t pid, int policy); + +PTW32_DLLPORT int __cdecl sched_getscheduler (pid_t pid); + +/* + * Note that this macro returns ENOTSUP rather than + * ENOSYS as might be expected. However, returning ENOSYS + * should mean that sched_get_priority_{min,max} are + * not implemented as well as sched_rr_get_interval. + * This is not the case, since we just don't support + * round-robin scheduling. Therefore I have chosen to + * return the same value as sched_setscheduler when + * SCHED_RR is passed to it. + */ +#define sched_rr_get_interval(_pid, _interval) \ + ( errno = ENOTSUP, (int) -1 ) + + +#ifdef __cplusplus +} /* End of extern "C" */ +#endif /* __cplusplus */ + +#undef PTW32_LEVEL +#undef PTW32_LEVEL_MAX + +#endif /* !_SCHED_H */ + diff --git a/xbmc/pvrclients/vdr-streamdev/pthread_win32/semaphore.h b/xbmc/pvrclients/vdr-streamdev/pthread_win32/semaphore.h new file mode 100644 index 0000000000..ea42ce3703 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/pthread_win32/semaphore.h @@ -0,0 +1,166 @@ +/* + * Module: semaphore.h + * + * Purpose: + * Semaphores aren't actually part of the PThreads standard. + * They are defined by the POSIX Standard: + * + * POSIX 1003.1b-1993 (POSIX.1b) + * + * -------------------------------------------------------------------------- + * + * Pthreads-win32 - POSIX Threads Library for Win32 + * Copyright(C) 1998 John E. Bossom + * Copyright(C) 1999,2005 Pthreads-win32 contributors + * + * Contact Email: rpj@callisto.canberra.edu.au + * + * The current list of contributors is contained + * in the file CONTRIBUTORS included with the source + * code distribution. The list can also be seen at the + * following World Wide Web location: + * http://sources.redhat.com/pthreads-win32/contributors.html + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library in the file COPYING.LIB; + * if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ +#if !defined( SEMAPHORE_H ) +#define SEMAPHORE_H + +#undef PTW32_LEVEL + +#if defined(_POSIX_SOURCE) +#define PTW32_LEVEL 0 +/* Early POSIX */ +#endif + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309 +#undef PTW32_LEVEL +#define PTW32_LEVEL 1 +/* Include 1b, 1c and 1d */ +#endif + +#if defined(INCLUDE_NP) +#undef PTW32_LEVEL +#define PTW32_LEVEL 2 +/* Include Non-Portable extensions */ +#endif + +#define PTW32_LEVEL_MAX 3 + +#if !defined(PTW32_LEVEL) +#define PTW32_LEVEL PTW32_LEVEL_MAX +/* Include everything */ +#endif + +#if __GNUC__ && ! defined (__declspec) +# error Please upgrade your GNU compiler to one that supports __declspec. +#endif + +/* + * When building the DLL code, you should define PTW32_BUILD so that + * the variables/functions are exported correctly. When using the DLL, + * do NOT define PTW32_BUILD, and then the variables/functions will + * be imported correctly. + */ +#ifndef PTW32_STATIC_LIB +# ifdef PTW32_BUILD +# define PTW32_DLLPORT __declspec (dllexport) +# else +# define PTW32_DLLPORT __declspec (dllimport) +# endif +#else +# define PTW32_DLLPORT +#endif + +/* + * This is a duplicate of what is in the autoconf config.h, + * which is only used when building the pthread-win32 libraries. + */ + +#ifndef PTW32_CONFIG_H +# if defined(WINCE) +# define NEED_ERRNO +# define NEED_SEM +# endif +# if defined(_UWIN) || defined(__MINGW32__) +# define HAVE_MODE_T +# endif +#endif + +/* + * + */ + +#if PTW32_LEVEL >= PTW32_LEVEL_MAX +#ifdef NEED_ERRNO +#include "need_errno.h" +#else +#include +#endif +#endif /* PTW32_LEVEL >= PTW32_LEVEL_MAX */ + +#define _POSIX_SEMAPHORES + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#ifndef HAVE_MODE_T +typedef unsigned int mode_t; +#endif + + +typedef struct sem_t_ * sem_t; + +PTW32_DLLPORT int __cdecl sem_init (sem_t * sem, + int pshared, + unsigned int value); + +PTW32_DLLPORT int __cdecl sem_destroy (sem_t * sem); + +PTW32_DLLPORT int __cdecl sem_trywait (sem_t * sem); + +PTW32_DLLPORT int __cdecl sem_wait (sem_t * sem); + +PTW32_DLLPORT int __cdecl sem_timedwait (sem_t * sem, + const struct timespec * abstime); + +PTW32_DLLPORT int __cdecl sem_post (sem_t * sem); + +PTW32_DLLPORT int __cdecl sem_post_multiple (sem_t * sem, + int count); + +PTW32_DLLPORT int __cdecl sem_open (const char * name, + int oflag, + mode_t mode, + unsigned int value); + +PTW32_DLLPORT int __cdecl sem_close (sem_t * sem); + +PTW32_DLLPORT int __cdecl sem_unlink (const char * name); + +PTW32_DLLPORT int __cdecl sem_getvalue (sem_t * sem, + int * sval); + +#ifdef __cplusplus +} /* End of extern "C" */ +#endif /* __cplusplus */ + +#undef PTW32_LEVEL +#undef PTW32_LEVEL_MAX + +#endif /* !SEMAPHORE_H */ diff --git a/xbmc/pvrclients/vdr-streamdev/pvrclient-vdr_os.h b/xbmc/pvrclients/vdr-streamdev/pvrclient-vdr_os.h new file mode 100644 index 0000000000..883cb1737e --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/pvrclient-vdr_os.h @@ -0,0 +1,44 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef PVRCLIENT_VDR_OS_H +#define PVRCLIENT_VDR_OS_H + +#if defined(_WIN32) || defined(_WIN64) +#define __WINDOWS__ +#endif + +#if defined(__WINDOWS__) +#include "windows/pvrclient-vdr_os_windows.h" +#else +#include "linux/pvrclient-vdr_os_posix.h" +#endif + +#if !defined(TRUE) +#define TRUE 1 +#endif + +#if !defined(FALSE) +#define FALSE 0 +#endif + +#endif diff --git a/xbmc/pvrclients/vdr-streamdev/recordings.cpp b/xbmc/pvrclients/vdr-streamdev/recordings.cpp new file mode 100644 index 0000000000..a91c891b80 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/recordings.cpp @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * This code is taken from recordings.c in the Video Disk Recorder ('VDR') + */ + +#include "StdString.h" +#include "recordings.h" +#include "client.h" + +#define RESUME_NOT_INITIALIZED (-2) + +cRecording::cRecording() +{ + m_channelName = NULL; + m_aux = NULL; + m_title = NULL; + m_shortText = NULL; + m_description = NULL; + m_fileName = NULL; + m_directory = NULL; + m_framesPerSecond = DEFAULTFRAMESPERSECOND; + m_priority = MAXPRIORITY; + m_lifetime = MAXLIFETIME; + m_EventID = 0; + m_StartTime = 0; + m_Duration = 0; + m_TableID = 0; + m_Version = 0xFF; + m_vps = 0; + m_Index = -1; + m_isPesRecording = false; + m_resume = RESUME_NOT_INITIALIZED; + m_fileSizeMB = -1; // unknown + m_deleted = false; +} + +cRecording::cRecording(const PVR_RECORDINGINFO *Recording) +{ + +} + +cRecording::cRecording(const char *FileName) +{ + m_channelName = NULL; + m_aux = NULL; + m_title = NULL; + m_shortText = NULL; + m_description = NULL; + m_fileName = NULL; + m_directory = NULL; + m_resume = RESUME_NOT_INITIALIZED; + m_fileSizeMB = -1; // unknown + m_priority = MAXPRIORITY; + m_lifetime = MAXLIFETIME; + m_isPesRecording = false; + m_framesPerSecond = DEFAULTFRAMESPERSECOND; + m_deleted = false; + + FileName = m_fileName = strdup(FileName); + if (*(m_fileName + strlen(m_fileName) - 1) == '/') + *(m_fileName + strlen(m_fileName) - 1) = 0; + FileName += g_szRecordingsDir.length() + 1; + const char *p = strrchr(FileName, '/'); + if (p) + { + time_t now = time(NULL); + struct tm tm_r; + struct tm t = *localtime_r(&now, &tm_r); // this initializes the time zone in 't' + t.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting + int channel; + int instanceId; + if (7 == sscanf(p + 1, DATAFORMATTS, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &channel, &instanceId) + || 7 == sscanf(p + 1, DATAFORMATPES, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &m_priority, &m_lifetime)) + { + t.tm_year -= 1900; + t.tm_mon--; + t.tm_sec = 0; + m_StartTime = mktime(&t); + char *path = MALLOC(char, p - FileName + 1); + strncpy(path, FileName, p - FileName); + path[p - FileName] = 0; + + /* Make some default title based upon directory names */ + p = strrchr(path, '/'); + if (p) + m_title = strcpyrealloc(m_title, p + 1); + else + m_title = strcpyrealloc(m_title, path); + + m_directory = MALLOC(char, strlen(path) - strlen(m_title) + 1); + strncpy(m_directory, path, strlen(path) - strlen(m_title)); + m_directory[strlen(path) - strlen(m_title)] = 0; + + if (g_bCharsetConv) + { + CStdString str_result = m_directory; + XBMC->UnknownToUTF8(str_result); + m_directory = strcpyrealloc(m_directory, str_result.c_str()); + } + + m_isPesRecording = instanceId < 0; + m_stream_url.Format("%s/%s", m_fileName, m_isPesRecording ? "*.vdr" : "*.ts"); + free(path); + } + else + return; + // read info file: + CStdString InfoFileName; + InfoFileName.Format("%s%s", m_fileName, m_isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX); + FILE *f = fopen(InfoFileName.c_str(), "r"); + if (f) + { + if (!Read(f)) + XBMC->Log(LOG_ERROR, "EPG data problem in file %s", InfoFileName.c_str()); + fclose(f); + } + else if (errno != ENOENT) + XBMC->Log(LOG_ERROR, "ERROR (%s,%d,s): %m", __FILE__, __LINE__, InfoFileName.c_str()); + } +} + +cRecording::~cRecording() +{ + free(m_aux); + free(m_channelName); + free(m_title); + free(m_shortText); + free(m_description); + free(m_fileName); + free(m_directory); +} + +bool cRecording::Read(FILE *f) +{ + cReadLine ReadLine; + char *s; + int line = 0; + while ((s = ReadLine.Read(f)) != NULL) + { + ++line; + CStdString str_result = s; + if (g_bCharsetConv) + XBMC->UnknownToUTF8(str_result); + if (!ParseLine(str_result.c_str())) + return false; + } + return true; +} + +bool cRecording::ParseLine(const char *s) +{ + char *t = skipspace(s + 1); + switch (*s) + { + case 'C': + { + char *p = strchr(t, ' '); + if (p) + { + free(m_channelName); + m_channelName = strdup(compactspace(p)); + *p = 0; // strips optional channel name + } + // if (*t) + // channelID = tChannelID::FromString(t); + } + return true; + case 'D': + strreplace(t, '|', '\n'); + SetDescription(t); + return true; + case 'E': + { + unsigned int EventID; + time_t StartTime; + int Duration; + unsigned int TableID = 0; + unsigned int Version = 0xFF; + int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version); + if (n >= 3 && n <= 5) + { + m_EventID = EventID; + m_StartTime = StartTime; + m_Duration = Duration; + m_TableID = TableID; + m_Version = Version; + } + } + return true; + case 'F': + m_framesPerSecond = atof(t); + return true; + case 'L': + m_lifetime = atoi(t); + return true; + case 'P': + m_priority = atoi(t); + return true; + case 'S': + SetShortText(t); + return true; + case 'T': + SetTitle(t); + return true; + case 'V': + m_vps = atoi(t); + return true; + case '@': + free(m_aux); + m_aux = strdup(t); + return true; + case '#': + return true; // comments are ignored + + + default: + break; + } + return false; +} + +bool cRecording::ParseEntryLine(const char *s) +{ + if (*s >= '0' && *s <= '9') + { + char recdate[256]; + char rectime[256]; + char rectext[1024]; + + if (sscanf(s, " %u %[^ ] %[^ ] %[^\n]", &m_Index, recdate, rectime, rectext) >= 3) + { + CStdString fileName = rectext; + fileName.Replace('/', '_'); + fileName.Replace('\\', '_'); + fileName.Replace('?', '_'); +#if defined(_WIN32) || defined(_WIN64) + // just filter out some illegal characters on windows + fileName.Replace(':', '_'); + fileName.Replace('*', '_'); + fileName.Replace('?', '_'); + fileName.Replace('\"', '_'); + fileName.Replace('<', '_'); + fileName.Replace('>', '_'); + fileName.Replace('|', '_'); + fileName.TrimRight("."); + fileName.TrimRight(" "); +#endif + size_t found = fileName.find_last_of("~"); + if (found != CStdString::npos) + { + CStdString dir = fileName.substr(0,found); + dir.Replace('~','/'); + m_directory = strcpyrealloc(m_directory, dir.c_str()); + m_fileName = strcpyrealloc(m_fileName, fileName.substr(found+1).c_str()); + } + else + { + m_fileName = strcpyrealloc(m_fileName, fileName.c_str()); + m_directory = strcpyrealloc(m_directory, ""); + } + } + return true; + } + + return false; +} + +// case 'X': if (!components) +// components = new cComponents; +// components->SetComponent(components->NumComponents(), t); +// break; + + +void cRecording::SetFramesPerSecond(double FramesPerSecond) +{ + m_framesPerSecond = FramesPerSecond; +} + +void cRecording::SetTitle(const char *Title) +{ + m_title = strcpyrealloc(m_title, Title); +} + +void cRecording::SetShortText(const char *ShortText) +{ + m_shortText = strcpyrealloc(m_shortText, ShortText); +} + +void cRecording::SetDescription(const char *Description) +{ + m_description = strcpyrealloc(m_description, Description); +} diff --git a/xbmc/pvrclients/vdr-streamdev/recordings.h b/xbmc/pvrclients/vdr-streamdev/recordings.h new file mode 100644 index 0000000000..b467500588 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/recordings.h @@ -0,0 +1,101 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __RECORDINGS_H +#define __RECORDINGS_H + +#include +#include +#include +#include "tools.h" + +#define DEFAULTFRAMESPERSECOND 25.0 +#define MAXPRIORITY 99 +#define MAXLIFETIME 99 +#define RECEXT ".rec" +#define DELEXT ".del" +#define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT +#define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT +#define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT +#define NAMEFORMATTS "%s/%s/" DATAFORMATTS +#define RESUMEFILESUFFIX "/resume%s%s" +#define SUMMARYFILESUFFIX "/summary.vdr" +#define INFOFILESUFFIX "/info" +#define MARKSFILESUFFIX "/marks" + +class cRecording +{ +private: + int m_Index; + int m_resume; + int m_fileSizeMB; + char *m_channelName; + char *m_fileName; + char *m_directory; + CStdString m_stream_url; + double m_framesPerSecond; + int m_priority; + int m_lifetime; + char *m_aux; + unsigned int m_EventID; + time_t m_StartTime; + int m_Duration; + unsigned int m_TableID; + unsigned int m_Version; + bool m_isPesRecording; + bool m_deleted; + char *m_title; // Title of this event + char *m_shortText; // Short description of this event (typically the episode name in case of a series) + char *m_description; // Description of this event + time_t m_vps; // Video Programming Service timestamp (VPS, aka "Programme Identification Label", PIL) + bool Read(FILE *f); + +public: + cRecording(const char *FileName); + cRecording(const PVR_RECORDINGINFO *Recording); + cRecording(); + virtual ~cRecording(); + + bool ParseLine(const char *s); + bool ParseEntryLine(const char *s); + const char *ChannelName(void) const { return m_channelName; } + const char *Aux(void) const { return m_aux; } + double FramesPerSecond(void) const { return m_framesPerSecond; } + void SetFramesPerSecond(double m_FramesPerSecond); + int Index(void) const { return m_Index; } + int Priority(void) const { return m_priority; } + int Lifetime(void) const { return m_lifetime; } + time_t StartTime(void) const { return m_StartTime; } + time_t Duration(void) const { return m_Duration; } + time_t Vps(void) const { return m_vps; } + const char *Title(void) const { return m_title; } + const char *ShortText(void) const { return m_shortText; } + const char *Description(void) const { return m_description; } + const char *FileName(void) const { return m_fileName; } + const char *Directory(void) const { return m_directory; } + const char *StreamURL(void) const { return m_stream_url.c_str(); } + void SetTitle(const char *Title); + void SetShortText(const char *ShortText); + void SetDescription(const char *Description); +}; + +#endif //__RECORDINGS_H diff --git a/xbmc/pvrclients/vdr-streamdev/ringbuffer.cpp b/xbmc/pvrclients/vdr-streamdev/ringbuffer.cpp new file mode 100644 index 0000000000..44c9bd6927 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/ringbuffer.cpp @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * Most of this code is taken from ringbuffer.c in the Video Disk Recorder ('VDR') + */ + +#include "tools.h" +#include "ringbuffer.h" +#include + +// --- cRingBuffer ----------------------------------------------------------- + +#define OVERFLOWREPORTDELTA 5 // seconds between reports +#define PERCENTAGEDELTA 10 +#define PERCENTAGETHRESHOLD 70 + +cRingBuffer::cRingBuffer(int Size, bool Statistics) +{ + size = Size; + statistics = Statistics; + getThreadTid = 0; + maxFill = 0; + lastPercent = 0; + putTimeout = getTimeout = 0; + lastOverflowReport = 0; + overflowCount = overflowBytes = 0; +} + +cRingBuffer::~cRingBuffer() +{ + if (statistics) + XBMC->Log(LOG_DEBUG, "buffer stats: %d (%d%%) used", maxFill, maxFill * 100 / (size - 1)); +} + +void cRingBuffer::UpdatePercentage(int Fill) +{ + if (Fill > maxFill) + maxFill = Fill; + int percent = Fill * 100 / (Size() - 1) / PERCENTAGEDELTA * PERCENTAGEDELTA; // clamp down to nearest quantum + if (percent != lastPercent) { + if (percent >= PERCENTAGETHRESHOLD && percent > lastPercent || percent < PERCENTAGETHRESHOLD && lastPercent >= PERCENTAGETHRESHOLD) { + XBMC->Log(LOG_DEBUG, "buffer usage: %d%% (tid=%d)", percent, getThreadTid); + lastPercent = percent; + } + } +} + +void cRingBuffer::WaitForPut(void) +{ + if (putTimeout) + readyForPut.Wait(putTimeout); +} + +void cRingBuffer::WaitForGet(void) +{ + if (getTimeout) + readyForGet.Wait(getTimeout); +} + +void cRingBuffer::EnablePut(void) +{ + if (putTimeout && Free() > Size() / 3) + readyForPut.Signal(); +} + +void cRingBuffer::EnableGet(void) +{ + if (getTimeout && Available() > Size() / 3) + readyForGet.Signal(); +} + +void cRingBuffer::SetTimeouts(int PutTimeout, int GetTimeout) +{ + putTimeout = PutTimeout; + getTimeout = GetTimeout; +} + +void cRingBuffer::ReportOverflow(int Bytes) +{ + overflowCount++; + overflowBytes += Bytes; + if (time(NULL) - lastOverflowReport > OVERFLOWREPORTDELTA) { + XBMC->Log(LOG_ERROR, "ERROR: %d ring buffer overflow%s (%d bytes dropped)", overflowCount, overflowCount > 1 ? "s" : "", overflowBytes); + overflowCount = overflowBytes = 0; + lastOverflowReport = time(NULL); + } +} + +// --- cRingBufferLinear ----------------------------------------------------- + +#ifdef DEBUGRINGBUFFERS +#define MAXRBLS 30 +#define DEBUGRBLWIDTH 45 + +cRingBufferLinear *cRingBufferLinear::RBLS[MAXRBLS] = { NULL }; + +void cRingBufferLinear::AddDebugRBL(cRingBufferLinear *RBL) +{ + for (int i = 0; i < MAXRBLS; i++) { + if (!RBLS[i]) { + RBLS[i] = RBL; + break; + } + } +} + +void cRingBufferLinear::DelDebugRBL(cRingBufferLinear *RBL) +{ + for (int i = 0; i < MAXRBLS; i++) { + if (RBLS[i] == RBL) { + RBLS[i] = NULL; + break; + } + } +} + +void cRingBufferLinear::PrintDebugRBL(void) +{ + bool printed = false; + for (int i = 0; i < MAXRBLS; i++) { + cRingBufferLinear *p = RBLS[i]; + if (p) { + printed = true; + int lh = p->lastHead; + int lt = p->lastTail; + int h = lh * DEBUGRBLWIDTH / p->Size(); + int t = lt * DEBUGRBLWIDTH / p->Size(); + char buf[DEBUGRBLWIDTH + 10]; + memset(buf, '-', DEBUGRBLWIDTH); + if (lt <= lh) + memset(buf + t, '*', max(h - t, 1)); + else { + memset(buf, '*', h); + memset(buf + t, '*', DEBUGRBLWIDTH - t); + } + buf[t] = '<'; + buf[h] = '>'; + buf[DEBUGRBLWIDTH] = 0; + printf("%2d %s %8d %8d %s\n", i, buf, p->lastPut, p->lastGet, p->description); + } + } + if (printed) + printf("\n"); + } +#endif + +cRingBufferLinear::cRingBufferLinear(int Size, int Margin, bool Statistics, const char *Description) +:cRingBuffer(Size, Statistics) +{ + description = Description ? strdup(Description) : NULL; + tail = head = margin = Margin; + gotten = 0; + buffer = NULL; + if (Size > 1) { // 'Size - 1' must not be 0! + if (Margin <= Size / 2) { + buffer = MALLOC(unsigned char, Size); + if (!buffer) + XBMC->Log(LOG_ERROR, "ERROR: can't allocate ring buffer (size=%d)", Size); + Clear(); + } + else + XBMC->Log(LOG_ERROR, "ERROR: invalid margin for ring buffer (%d > %d)", Margin, Size / 2); + } + else + XBMC->Log(LOG_ERROR, "ERROR: invalid size for ring buffer (%d)", Size); +#ifdef DEBUGRINGBUFFERS + lastHead = head; + lastTail = tail; + lastPut = lastGet = -1; + AddDebugRBL(this); +#endif +} + +cRingBufferLinear::~cRingBufferLinear() +{ +#ifdef DEBUGRINGBUFFERS + DelDebugRBL(this); +#endif + free(buffer); + free(description); +} + +int cRingBufferLinear::DataReady(const unsigned char *Data, int Count) +{ + return Count >= margin ? Count : 0; +} + +int cRingBufferLinear::Available(void) +{ + int diff = head - tail; + return (diff >= 0) ? diff : Size() + diff - margin; +} + +void cRingBufferLinear::Clear(void) +{ + tail = head = margin; +#ifdef DEBUGRINGBUFFERS + lastHead = head; + lastTail = tail; + lastPut = lastGet = -1; +#endif + maxFill = 0; + EnablePut(); +} + +int cRingBufferLinear::Read(int FileHandle, int Max) +{ + int Tail = tail; + int diff = Tail - head; + int free = (diff > 0) ? diff - 1 : Size() - head; + if (Tail <= margin) + free--; + int Count = -1; + errno = EAGAIN; + if (free > 0) { + if (0 < Max && Max < free) + free = Max; + Count = safe_read(FileHandle, buffer + head, free); + if (Count > 0) { + int Head = head + Count; + if (Head >= Size()) + Head = margin; + head = Head; + if (statistics) { + int fill = head - Tail; + if (fill < 0) + fill = Size() + fill; + else if (fill >= Size()) + fill = Size() - 1; + UpdatePercentage(fill); + } + } + } +#ifdef DEBUGRINGBUFFERS + lastHead = head; + lastPut = Count; +#endif + EnableGet(); + if (free == 0) + WaitForPut(); + return Count; +} + +int cRingBufferLinear::Read(cUnbufferedFile *File, int Max) +{ + int Tail = tail; + int diff = Tail - head; + int free = (diff > 0) ? diff - 1 : Size() - head; + if (Tail <= margin) + free--; + int Count = -1; + errno = EAGAIN; + if (free > 0) { + if (0 < Max && Max < free) + free = Max; + Count = File->Read(buffer + head, free); + if (Count > 0) { + int Head = head + Count; + if (Head >= Size()) + Head = margin; + head = Head; + if (statistics) { + int fill = head - Tail; + if (fill < 0) + fill = Size() + fill; + else if (fill >= Size()) + fill = Size() - 1; + UpdatePercentage(fill); + } + } + } +#ifdef DEBUGRINGBUFFERS + lastHead = head; + lastPut = Count; +#endif + EnableGet(); + if (free == 0) + WaitForPut(); + return Count; +} + +int cRingBufferLinear::Put(const unsigned char *Data, int Count) +{ + if (Count > 0) { + int Tail = tail; + int rest = Size() - head; + int diff = Tail - head; + int free = ((Tail < margin) ? rest : (diff > 0) ? diff : Size() + diff - margin) - 1; + if (statistics) { + int fill = Size() - free - 1 + Count; + if (fill >= Size()) + fill = Size() - 1; + UpdatePercentage(fill); + } + if (free > 0) { + if (free < Count) + Count = free; + if (Count >= rest) { + memcpy(buffer + head, Data, rest); + if (Count - rest) + memcpy(buffer + margin, Data + rest, Count - rest); + head = margin + Count - rest; + } + else { + memcpy(buffer + head, Data, Count); + head += Count; + } + } + else + Count = 0; +#ifdef DEBUGRINGBUFFERS + lastHead = head; + lastPut = Count; +#endif + EnableGet(); + if (Count == 0) + WaitForPut(); + } + return Count; +} + +unsigned char *cRingBufferLinear::Get(int &Count) +{ + int Head = head; + if (getThreadTid <= 0) + getThreadTid = cThread::ThreadId(); + int rest = Size() - tail; + if (rest < margin && Head < tail) { + int t = margin - rest; + memcpy(buffer + t, buffer + tail, rest); + tail = t; + rest = Head - tail; + } + int diff = Head - tail; + int cont = (diff >= 0) ? diff : Size() + diff - margin; + if (cont > rest) + cont = rest; + unsigned char *p = buffer + tail; + if ((cont = DataReady(p, cont)) > 0) { + Count = gotten = cont; + return p; + } + WaitForGet(); + return NULL; +} + +void cRingBufferLinear::Del(int Count) +{ + if (Count > gotten) { + XBMC->Log(LOG_ERROR, "ERROR: invalid Count in cRingBufferLinear::Del: %d (limited to %d)", Count, gotten); + Count = gotten; + } + if (Count > 0) { + int Tail = tail; + Tail += Count; + gotten -= Count; + if (Tail >= Size()) + Tail = margin; + tail = Tail; + EnablePut(); + } +#ifdef DEBUGRINGBUFFERS + lastTail = tail; + lastGet = Count; +#endif +} diff --git a/xbmc/pvrclients/vdr-streamdev/ringbuffer.h b/xbmc/pvrclients/vdr-streamdev/ringbuffer.h new file mode 100644 index 0000000000..198f48e971 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/ringbuffer.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __RINGBUFFER_H +#define __RINGBUFFER_H + +#include "thread.h" +#include "tools.h" + +class cRingBuffer { +private: + cCondWait readyForPut, readyForGet; + int putTimeout; + int getTimeout; + int size; + time_t lastOverflowReport; + int overflowCount; + int overflowBytes; +protected: + tThreadId getThreadTid; + int maxFill;//XXX + int lastPercent; + bool statistics;//XXX + void UpdatePercentage(int Fill); + void WaitForPut(void); + void WaitForGet(void); + void EnablePut(void); + void EnableGet(void); + virtual void Clear(void) = 0; + virtual int Available(void) = 0; + virtual int Free(void) { return Size() - Available() - 1; } + int Size(void) { return size; } +public: + cRingBuffer(int Size, bool Statistics = false); + virtual ~cRingBuffer(); + void SetTimeouts(int PutTimeout, int GetTimeout); + void ReportOverflow(int Bytes); + }; + +class cRingBufferLinear : public cRingBuffer { +//#define DEBUGRINGBUFFERS +#ifdef DEBUGRINGBUFFERS +private: + int lastHead, lastTail; + int lastPut, lastGet; + static cRingBufferLinear *RBLS[]; + static void AddDebugRBL(cRingBufferLinear *RBL); + static void DelDebugRBL(cRingBufferLinear *RBL); +public: + static void PrintDebugRBL(void); +#endif +private: + int margin, head, tail; + int gotten; + unsigned char *buffer; + char *description; +protected: + virtual int DataReady(const unsigned char *Data, int Count); + ///< By default a ring buffer has data ready as soon as there are at least + ///< 'margin' bytes available. A derived class can reimplement this function + ///< if it has other conditions that define when data is ready. + ///< The return value is either 0 if there is not yet enough data available, + ///< or the number of bytes from the beginning of Data that are "ready". +public: + cRingBufferLinear(int Size, int Margin = 0, bool Statistics = false, const char *Description = NULL); + ///< Creates a linear ring buffer. + ///< The buffer will be able to hold at most Size-Margin-1 bytes of data, and will + ///< be guaranteed to return at least Margin bytes in one consecutive block. + ///< The optional Description is used for debugging only. + virtual ~cRingBufferLinear(); + virtual int Available(void); + virtual int Free(void) { return Size() - Available() - 1 - margin; } + virtual void Clear(void); + ///< Immediately clears the ring buffer. + int Read(int FileHandle, int Max = 0); + ///< Reads at most Max bytes from FileHandle and stores them in the + ///< ring buffer. If Max is 0, reads as many bytes as possible. + ///< Only one actual read() call is done. + ///< \return Returns the number of bytes actually read and stored, or + ///< an error value from the actual read() call. + int Read(cUnbufferedFile *File, int Max = 0); + ///< Like Read(int FileHandle, int Max), but reads fom a cUnbufferedFile). + int Put(const unsigned char *Data, int Count); + ///< Puts at most Count bytes of Data into the ring buffer. + ///< \return Returns the number of bytes actually stored. + unsigned char *Get(int &Count); + ///< Gets data from the ring buffer. + ///< The data will remain in the buffer until a call to Del() deletes it. + ///< \return Returns a pointer to the data, and stores the number of bytes + ///< actually available in Count. If the returned pointer is NULL, Count has no meaning. + void Del(int Count); + ///< Deletes at most Count bytes from the ring buffer. + ///< Count must be less or equal to the number that was returned by a previous + ///< call to Get(). + }; + +#endif // __RINGBUFFER_H diff --git a/xbmc/pvrclients/vdr-streamdev/select.cpp b/xbmc/pvrclients/vdr-streamdev/select.cpp new file mode 100644 index 0000000000..a963f962e5 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/select.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + + /* NOTE: + * + * The following code is taken and copied from VDR streamdev plugin. + */ + +#include "select.h" +#include "tools.h" +#include +#include +#include + +cTBSelect::cTBSelect(void) +{ + Clear(); +} + +cTBSelect::~cTBSelect() +{ +} + +int cTBSelect::Select(unsigned int TimeoutMs) +{ + struct timeval tv; + ssize_t res = 0; + int ms; + + tv.tv_usec = (TimeoutMs % 1000) * 1000; + tv.tv_sec = TimeoutMs / 1000; + memcpy(m_FdsResult, m_FdsQuery, sizeof(m_FdsResult)); + + if (TimeoutMs == 0) + return __select(m_MaxFiled + 1, &m_FdsResult[0], &m_FdsResult[1], NULL, &tv); + + cTimeMs starttime; + ms = TimeoutMs; + while (ms > 0 && (res = __select(m_MaxFiled + 1, &m_FdsResult[0], &m_FdsResult[1], NULL, &tv)) == -1 && errno == EINTR) + { + ms = TimeoutMs - starttime.Elapsed(); + tv.tv_usec = (ms % 1000) * 1000; + tv.tv_sec = ms / 1000; + memcpy(m_FdsResult, m_FdsQuery, sizeof(m_FdsResult)); + } + if (ms <= 0 || res == 0) + { + errno = ETIMEDOUT; + return -1; + } + return res; +} + +int cTBSelect::Select(void) +{ + ssize_t res; + do + { + memcpy(m_FdsResult, m_FdsQuery, sizeof(m_FdsResult)); + } while ((res = __select(m_MaxFiled + 1, &m_FdsResult[0], &m_FdsResult[1], NULL, NULL)) == -1 && errno == EINTR); + return res; +} diff --git a/xbmc/pvrclients/vdr-streamdev/select.h b/xbmc/pvrclients/vdr-streamdev/select.h new file mode 100644 index 0000000000..dcce4defbb --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/select.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef TOOLBOX_SELECT_H +#define TOOLBOX_SELECT_H + +#include "tools.h" + +#include + +/* cTBSelect provides an interface for polling UNIX-like file descriptors. */ + +class cTBSelect +{ +private: + int m_MaxFiled; + + fd_set m_FdsQuery[2]; + fd_set m_FdsResult[2]; + +public: + cTBSelect(void); + virtual ~cTBSelect(); + + /* Clear() resets the object for use in a new Select() call. All file + descriptors and their previous states are invalidated. */ + virtual void Clear(void); + + /* Add() adds a file descriptor to be polled in the next Select() call. + That call polls if the file is readable if Output is set to false, + writeable otherwise. */ + virtual bool Add(int Filed, bool Output = false); + + /* Select() polls all descriptors added by Add() and returns as soon as + one of those changes state (gets readable/writeable), or after + TimeoutMs milliseconds, whichever happens first. It returns the number + of filedescriptors that have changed state. On error, -1 is returned + and errno is set appropriately. */ + virtual int Select(unsigned int TimeoutMs); + + /* Select() polls all descriptors added by Add() and returns as soon as + one of those changes state (gets readable/writeable). It returns the + number of filedescriptors that have changed state. On error, -1 is + returned and errno is set appropriately. */ + virtual int Select(void); + + /* CanRead() returns true if the descriptor has changed to readable during + the last Select() call. Otherwise false is returned. */ + virtual bool CanRead(int FileNo) const; + + /* CanWrite() returns true if the descriptor has changed to writeable + during the last Select() call. Otherwise false is returned. */ + virtual bool CanWrite(int FileNo) const; +}; + +inline void cTBSelect::Clear(void) +{ + FD_ZERO(&m_FdsQuery[0]); + FD_ZERO(&m_FdsQuery[1]); + m_MaxFiled = -1; +} + +inline bool cTBSelect::Add(int Filed, bool Output /* = false */) +{ + if (Filed < 0) return false; + FD_SET(Filed, &m_FdsQuery[Output ? 1 : 0]); + if (Filed > m_MaxFiled) m_MaxFiled = Filed; + return true; +} + +inline bool cTBSelect::CanRead(int FileNo) const +{ + if (FileNo < 0) return false; + return FD_ISSET(FileNo, &m_FdsResult[0]); +} + +inline bool cTBSelect::CanWrite(int FileNo) const +{ + if (FileNo < 0) return false; + return FD_ISSET(FileNo, &m_FdsResult[1]); +} + +#endif // TOOLBOX_SELECT_H diff --git a/xbmc/pvrclients/vdr-streamdev/thread.cpp b/xbmc/pvrclients/vdr-streamdev/thread.cpp new file mode 100644 index 0000000000..708bf07097 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/thread.cpp @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * Most of this code is taken from thread.c in the Video Disk Recorder ('VDR') + */ + +#include "tools.h" +#include "thread.h" +#include +#ifndef __APPLE__ +#include +#endif + +#include +#include +#include "StdString.h" + +static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow) +{ + struct timeval now; + if (gettimeofday(&now, NULL) == 0) { // get current time + now.tv_sec += MillisecondsFromNow / 1000; // add full seconds + now.tv_usec += (MillisecondsFromNow % 1000) * 1000; // add microseconds + if (now.tv_usec >= 1000000) { // take care of an overflow + now.tv_sec++; + now.tv_usec -= 1000000; + } + Abstime->tv_sec = now.tv_sec; // seconds + Abstime->tv_nsec = now.tv_usec * 1000; // nano seconds + return true; + } + return false; +} + +// --- cCondWait ------------------------------------------------------------- + +cCondWait::cCondWait(void) +{ + signaled = false; + pthread_mutex_init(&mutex, NULL); + pthread_cond_init(&cond, NULL); +} + +cCondWait::~cCondWait() +{ + pthread_cond_broadcast(&cond); // wake up any sleepers + pthread_cond_destroy(&cond); + pthread_mutex_destroy(&mutex); +} + +void cCondWait::SleepMs(int TimeoutMs) +{ + cCondWait w; + w.Wait(max(TimeoutMs, 3)); // making sure the time is >2ms to avoid a possible busy wait +} + +bool cCondWait::Wait(int TimeoutMs) +{ + pthread_mutex_lock(&mutex); + if (!signaled) { + if (TimeoutMs) { + struct timespec abstime; + if (GetAbsTime(&abstime, TimeoutMs)) { + while (!signaled) { + if (pthread_cond_timedwait(&cond, &mutex, &abstime) == ETIMEDOUT) + break; + } + } + } + else + pthread_cond_wait(&cond, &mutex); + } + bool r = signaled; + signaled = false; + pthread_mutex_unlock(&mutex); + return r; +} + +void cCondWait::Signal(void) +{ + pthread_mutex_lock(&mutex); + signaled = true; + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mutex); +} + +// --- cCondVar -------------------------------------------------------------- + +cCondVar::cCondVar(void) +{ + pthread_cond_init(&cond, 0); +} + +cCondVar::~cCondVar() +{ + pthread_cond_broadcast(&cond); // wake up any sleepers + pthread_cond_destroy(&cond); +} + +void cCondVar::Wait(cMutex &Mutex) +{ + if (Mutex.locked) { + int locked = Mutex.locked; + Mutex.locked = 0; // have to clear the locked count here, as pthread_cond_wait + // does an implicit unlock of the mutex + pthread_cond_wait(&cond, &Mutex.mutex); + Mutex.locked = locked; + } +} + +bool cCondVar::TimedWait(cMutex &Mutex, int TimeoutMs) +{ + bool r = true; // true = condition signaled, false = timeout + + if (Mutex.locked) { + struct timespec abstime; + if (GetAbsTime(&abstime, TimeoutMs)) { + int locked = Mutex.locked; + Mutex.locked = 0; // have to clear the locked count here, as pthread_cond_timedwait + // does an implicit unlock of the mutex. + if (pthread_cond_timedwait(&cond, &Mutex.mutex, &abstime) == ETIMEDOUT) + r = false; + Mutex.locked = locked; + } + } + return r; +} + +void cCondVar::Broadcast(void) +{ + pthread_cond_broadcast(&cond); +} + +// --- cMutex ---------------------------------------------------------------- + +cMutex::cMutex(void) +{ + locked = 0; + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); +#ifndef __APPLE__ + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK_NP); +#else + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); +#endif + pthread_mutex_init(&mutex, &attr); +} + +cMutex::~cMutex() +{ + pthread_mutex_destroy(&mutex); +} + +void cMutex::Lock(void) +{ + pthread_mutex_lock(&mutex); + locked++; +} + +void cMutex::Unlock(void) +{ + if (!--locked) + pthread_mutex_unlock(&mutex); +} + +// --- cThread --------------------------------------------------------------- + +tThreadId cThread::mainThreadId = 0; + +cThread::cThread(const char *Description) +{ + active = running = false; +#if !defined(__WINDOWS__) + childTid = 0; +#endif + childThreadId = 0; + description = NULL; + if (Description) + SetDescription("%s", Description); +} + +cThread::~cThread() +{ + Cancel(); // just in case the derived class didn't call it + free(description); +} + +void cThread::SetPriority(int Priority) +{ +#if !defined(__WINDOWS__) + if (setpriority(PRIO_PROCESS, 0, Priority) < 0) + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); +#endif +} + +void cThread::SetIOPriority(int Priority) +{ +#if !defined(__WINDOWS__) +#ifdef HAVE_LINUXIOPRIO + if (syscall(SYS_ioprio_set, 1, 0, (Priority & 0xff) | (2 << 13)) < 0) // best effort class + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); +#endif +#endif +} + +void cThread::SetDescription(const char *Description, ...) +{ + free(description); + description = NULL; + if (Description) + { + va_list ap; + va_start(ap, Description); + CStdString desc; + desc.FormatV(Description, ap); + description = strdup(desc.c_str()); + va_end(ap); + } +} + +void *cThread::StartThread(cThread *Thread) +{ + Thread->childThreadId = ThreadId(); + if (Thread->description) { + XBMC->Log(LOG_DEBUG, "%s thread started (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId); +#ifdef PR_SET_NAME + if (prctl(PR_SET_NAME, Thread->description, 0, 0, 0) < 0) + XBMC->Log(LOG_ERROR, "%s thread naming failed (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId); +#endif + } + Thread->Action(); + if (Thread->description) + XBMC->Log(LOG_DEBUG, "%s thread ended (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId); + Thread->running = false; + Thread->active = false; + return NULL; +} + +#define THREAD_STOP_TIMEOUT 3000 // ms to wait for a thread to stop before newly starting it +#define THREAD_STOP_SLEEP 30 // ms to sleep while waiting for a thread to stop + +bool cThread::Start(void) +{ + if (!running) { + if (active) { + // Wait until the previous incarnation of this thread has completely ended + // before starting it newly: + cTimeMs RestartTimeout; + while (!running && active && RestartTimeout.Elapsed() < THREAD_STOP_TIMEOUT) + cCondWait::SleepMs(THREAD_STOP_SLEEP); + } + if (!active) { + active = running = true; + if (pthread_create(&childTid, NULL, (void *(*) (void *))&StartThread, (void *)this) == 0) { + pthread_detach(childTid); // auto-reap + } + else { + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); + active = running = false; + return false; + } + } + } + return true; +} + +bool cThread::Active(void) +{ + if (active) { + // + // Single UNIX Spec v2 says: + // + // The pthread_kill() function is used to request + // that a signal be delivered to the specified thread. + // + // As in kill(), if sig is zero, error checking is + // performed but no signal is actually sent. + // + int err; + if ((err = pthread_kill(childTid, 0)) != 0) { + if (err != ESRCH) + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); +#if !defined(__WINDOWS__) + childTid = 0; +#endif + active = running = false; + } + else + return true; + } + return false; +} + +void cThread::Cancel(int WaitSeconds) +{ + running = false; + if (active && WaitSeconds > -1) + { + if (WaitSeconds > 0) + { + for (time_t t0 = time(NULL) + WaitSeconds; time(NULL) < t0; ) + { + if (!Active()) + return; + cCondWait::SleepMs(10); + } + XBMC->Log(LOG_ERROR, "ERROR: %s thread %d won't end (waited %d seconds) - canceling it...", description ? description : "", childThreadId, WaitSeconds); + } + pthread_cancel(childTid); +#if !defined(__WINDOWS__) + childTid = 0; +#endif + active = false; + } +} + +tThreadId cThread::ThreadId(void) +{ +#ifdef __APPLE__ + return (int)pthread_self(); +#else +#ifdef __WINDOWS__ + return GetCurrentThreadId(); +#else + return syscall(__NR_gettid); +#endif +#endif +} + +void cThread::SetMainThreadId(void) +{ + if (mainThreadId == 0) + mainThreadId = ThreadId(); + else + XBMC->Log(LOG_ERROR, "ERROR: attempt to set main thread id to %d while it already is %d", ThreadId(), mainThreadId); +} + +// --- cMutexLock ------------------------------------------------------------ + +cMutexLock::cMutexLock(cMutex *Mutex) +{ + mutex = NULL; + locked = false; + Lock(Mutex); +} + +cMutexLock::~cMutexLock() +{ + if (mutex && locked) + mutex->Unlock(); +} + +bool cMutexLock::Lock(cMutex *Mutex) +{ + if (Mutex && !mutex) + { + mutex = Mutex; + Mutex->Lock(); + locked = true; + return true; + } + return false; +} + +// --- cThreadLock ----------------------------------------------------------- + +cThreadLock::cThreadLock(cThread *Thread) +{ + thread = NULL; + locked = false; + Lock(Thread); +} + +cThreadLock::~cThreadLock() +{ + if (thread && locked) + thread->Unlock(); +} + +bool cThreadLock::Lock(cThread *Thread) +{ + if (Thread && !thread) + { + thread = Thread; + Thread->Lock(); + locked = true; + return true; + } + return false; +} diff --git a/xbmc/pvrclients/vdr-streamdev/thread.h b/xbmc/pvrclients/vdr-streamdev/thread.h new file mode 100644 index 0000000000..960a6e9594 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/thread.h @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __THREAD_H +#define __THREAD_H + +#include +#include +#include "tools.h" + +class cCondWait { +private: + pthread_mutex_t mutex; + pthread_cond_t cond; + bool signaled; +public: + cCondWait(void); + ~cCondWait(); + static void SleepMs(int TimeoutMs); + ///< Creates a cCondWait object and uses it to sleep for TimeoutMs + ///< milliseconds, immediately giving up the calling thread's time + ///< slice and thus avoiding a "busy wait". + ///< In order to avoid a possible busy wait, TimeoutMs will be automatically + ///< limited to values >2. + bool Wait(int TimeoutMs = 0); + ///< Waits at most TimeoutMs milliseconds for a call to Signal(), or + ///< forever if TimeoutMs is 0. + ///< \return Returns true if Signal() has been called, false it the given + ///< timeout has expired. + void Signal(void); + ///< Signals a caller of Wait() that the condition it is waiting for is met. + }; + +class cMutex; + +class cCondVar { +private: + pthread_cond_t cond; +public: + cCondVar(void); + ~cCondVar(); + void Wait(cMutex &Mutex); + bool TimedWait(cMutex &Mutex, int TimeoutMs); + void Broadcast(void); + }; + +class cMutex { + friend class cCondVar; +private: + pthread_mutex_t mutex; + int locked; +public: + cMutex(void); + ~cMutex(); + void Lock(void); + void Unlock(void); + }; + +typedef pid_t tThreadId; + +class cThread { + friend class cThreadLock; +private: + bool active; + bool running; + pthread_t childTid; + tThreadId childThreadId; + cMutex mutex; + char *description; + static tThreadId mainThreadId; + static void *StartThread(cThread *Thread); +protected: + void SetPriority(int Priority); + void SetIOPriority(int Priority); + void Lock(void) { mutex.Lock(); } + void Unlock(void) { mutex.Unlock(); } + virtual void Action(void) = 0; + ///< A derived cThread class must implement the code it wants to + ///< execute as a separate thread in this function. If this is + ///< a loop, it must check Running() repeatedly to see whether + ///< it's time to stop. + bool Running(void) { return running; } + ///< Returns false if a derived cThread object shall leave its Action() + ///< function. + void Cancel(int WaitSeconds = 0); + ///< Cancels the thread by first setting 'running' to false, so that + ///< the Action() loop can finish in an orderly fashion and then waiting + ///< up to WaitSeconds seconds for the thread to actually end. If the + ///< thread doesn't end by itself, it is killed. + ///< If WaitSeconds is -1, only 'running' is set to false and Cancel() + ///< returns immediately, without killing the thread. +public: + cThread(const char *Description = NULL); + ///< Creates a new thread. + ///< If Description is present, a log file entry will be made when + ///< the thread starts and stops. The Start() function must be called + ///< to actually start the thread. + virtual ~cThread(); +#ifdef __WINDOWS__ + void SetDescription(const char *Description, ...); +#else + void SetDescription(const char *Description, ...) __attribute__ ((format (printf, 2, 3))); +#endif + bool Start(void); + ///< Actually starts the thread. + ///< If the thread is already running, nothing happens. + bool Active(void); + ///< Checks whether the thread is still alive. + static tThreadId ThreadId(void); + static tThreadId IsMainThread(void) { return ThreadId() == mainThreadId; } + static void SetMainThreadId(void); + }; + +// cMutexLock can be used to easily set a lock on mutex and make absolutely +// sure that it will be unlocked when the block will be left. Several locks can +// be stacked, so a function that makes many calls to another function which uses +// cMutexLock may itself use a cMutexLock to make one longer lock instead of many +// short ones. + +class cMutexLock { +private: + cMutex *mutex; + bool locked; +public: + cMutexLock(cMutex *Mutex = NULL); + ~cMutexLock(); + bool Lock(cMutex *Mutex); + }; + +// cThreadLock can be used to easily set a lock in a thread and make absolutely +// sure that it will be unlocked when the block will be left. Several locks can +// be stacked, so a function that makes many calls to another function which uses +// cThreadLock may itself use a cThreadLock to make one longer lock instead of many +// short ones. + +class cThreadLock { +private: + cThread *thread; + bool locked; +public: + cThreadLock(cThread *Thread = NULL); + ~cThreadLock(); + bool Lock(cThread *Thread); + }; + +#define LOCK_THREAD cThreadLock ThreadLock(this) + +#endif //__THREAD_H diff --git a/xbmc/pvrclients/vdr-streamdev/timers.cpp b/xbmc/pvrclients/vdr-streamdev/timers.cpp new file mode 100644 index 0000000000..e23cb63fcd --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/timers.cpp @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * This code is taken from timers.c in the Video Disk Recorder ('VDR') + */ + +#include "tools.h" +#include "timers.h" + +#define SECSINDAY 86400 + +cTimer::cTimer() +{ + startTime = 0; + stopTime = 0; + index = 0; + recording = false; + pending = false; + inVpsMargin = false; + flags = tfActive; + weekdays = 0; + aux = NULL; +} + +cTimer::cTimer(const PVR_TIMERINFO *Timer) +{ + aux = NULL; + index = Timer->index; + startTime = Timer->starttime; + stopTime = Timer->endtime; + flags = Timer->active; + if (Timer->repeat) + { + if (Timer->firstday) + day = Timer->firstday; + weekdays = Timer->repeatflags; + } + else + { + day = 0; + weekdays = 0; + } + priority = Timer->priority; + channel = Timer->channelNum; + lifetime = Timer->lifetime; + + CStdString directory = Timer->directory; + directory.Replace('/','~'); + if (directory[directory.size()-1] != '~') // this is a file + directory += "~"; + strn0cpy(dir, directory.c_str(), 256); + strn0cpy(name, Timer->title, 256); + directory += Timer->title; + strn0cpy(file, directory.c_str(), 256); + + struct tm tm_r; + struct tm *time = localtime_r(&startTime, &tm_r); + day = SetTime(startTime, 0); + start = time->tm_hour * 100 + time->tm_min; + time = localtime_r(&stopTime, &tm_r); + stop = time->tm_hour * 100 + time->tm_min; +} + +cTimer::~cTimer() +{ + free(aux); +} + +bool cTimer::Parse(const char *s) +{ + char channelbuffer[256]; + char daybuffer[256]; + char filebuffer[512]; + free(aux); + aux = (char*) malloc(256); + + char *s2 = NULL; + int l2 = strlen(s); + while (l2 > 0 && isspace(s[l2 - 1])) + l2--; + + if (s[l2 - 1] == ':') + { + s2 = (char *)malloc(sizeof(char) * (l2 + 3)); + strcat(strn0cpy(s2, s, l2 + 1), " \n"); + s = s2; + } + bool result = false; + if (9 <= sscanf(s, "%u %u :%[^:]:%[^:]:%d :%d :%d :%d :%[^:\n]:%[^\n]", &index, &flags, channelbuffer, daybuffer, &start, &stop, &priority, &lifetime, filebuffer, aux)) + { + if (aux && !*skipspace(aux)) + { + free(aux); + aux = NULL; + } + //TODO add more plausibility checks + result = ParseDay(daybuffer, day, weekdays); + + CStdString fileName = filebuffer; + fileName.Replace('/', '_'); + fileName.Replace('\\', '_'); + fileName.Replace('?', '_'); +#if defined(_WIN32) || defined(_WIN64) + // just filter out some illegal characters on windows + fileName.Replace(':', '_'); + fileName.Replace('*', '_'); + fileName.Replace('?', '_'); + fileName.Replace('\"', '_'); + fileName.Replace('<', '_'); + fileName.Replace('>', '_'); + fileName.Replace('|', '_'); + fileName.TrimRight("."); + fileName.TrimRight(" "); +#endif + size_t found = fileName.find_last_of("~"); + if (found != CStdString::npos) + { + CStdString directory = fileName.substr(0,found); + directory.Replace('~','/'); + strn0cpy(dir, directory.c_str(), 256); + strn0cpy(name, fileName.substr(found+1).c_str(), 256); + } + else + { + dir[0] = 0; + strn0cpy(name, fileName.c_str(), 256); + } + + strn0cpy(file, filebuffer, 256); + strreplace(file, '|', ':'); + + if (IsNumber(channelbuffer)) + channel = atoi(channelbuffer); + else + { + XBMC->Log(LOG_ERROR, "PCRClient-vdr: channel not defined for timer"); + result = false; + } + } + free(s2); + return result; +} + +bool cTimer::ParseDay(const char *s, time_t &Day, int &WeekDays) +{ + // possible formats are: + // 19 + // 2005-03-19 + // MTWTFSS + // MTWTFSS@19 + // MTWTFSS@2005-03-19 + + Day = 0; + WeekDays = 0; + s = skipspace(s); + if (!*s) + return false; + const char *a = strchr(s, '@'); + const char *d = a ? a + 1 : isdigit(*s) ? s : NULL; + if (d) + { + if (strlen(d) == 10) + { + struct tm tm_r; + if (3 == sscanf(d, "%d-%d-%d", &tm_r.tm_year, &tm_r.tm_mon, &tm_r.tm_mday)) + { + tm_r.tm_year -= 1900; + tm_r.tm_mon--; + tm_r.tm_hour = tm_r.tm_min = tm_r.tm_sec = 0; + tm_r.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting + Day = mktime(&tm_r); + } + else + return false; + } + else + { + // handle "day of month" for compatibility with older versions: + char *tail = NULL; + int day = strtol(d, &tail, 10); + if (tail && *tail || day < 1 || day > 31) + return false; + time_t t = time(NULL); + int DaysToCheck = 61; // 61 to handle months with 31/30/31 + for (int i = -1; i <= DaysToCheck; i++) + { + time_t t0 = IncDay(t, i); + if (GetMDay(t0) == day) + { + Day = SetTime(t0, 0); + break; + } + } + } + } + if (a || !isdigit(*s)) + { + if ((a && a - s == 7) || strlen(s) == 7) + { + for (const char *p = s + 6; p >= s; p--) + { + WeekDays <<= 1; + WeekDays |= (*p != '-'); + } + } + else + return false; + } + return true; +} + +bool cTimer::IsSingleEvent(void) const +{ + return !weekdays; +} + +int cTimer::GetMDay(time_t t) +{ + struct tm tm_r; + return localtime_r(&t, &tm_r)->tm_mday; +} + +int cTimer::GetWDay(time_t t) +{ + struct tm tm_r; + int weekday = localtime_r(&t, &tm_r)->tm_wday; + return weekday == 0 ? 6 : weekday - 1; // we start with Monday==0! +} + +bool cTimer::DayMatches(time_t t) const +{ + return IsSingleEvent() ? SetTime(t, 0) == day : (weekdays & (1 << GetWDay(t))) != 0; +} + +time_t cTimer::IncDay(time_t t, int Days) +{ + struct tm tm_r; + tm tm = *localtime_r(&t, &tm_r); + tm.tm_mday += Days; // now tm_mday may be out of its valid range + int h = tm.tm_hour; // save original hour to compensate for DST change + tm.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting + t = mktime(&tm); // normalize all values + tm.tm_hour = h; // compensate for DST change + return mktime(&tm); // calculate final result +} + +time_t cTimer::SetTime(time_t t, int SecondsFromMidnight) +{ + struct tm tm_r; + tm tm = *localtime_r(&t, &tm_r); + tm.tm_hour = SecondsFromMidnight / 3600; + tm.tm_min = (SecondsFromMidnight % 3600) / 60; + tm.tm_sec = SecondsFromMidnight % 60; + tm.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting + return mktime(&tm); +} + +void cTimer::ClrFlags(unsigned int Flags) +{ + flags &= ~Flags; +} + +void cTimer::InvFlags(unsigned int Flags) +{ + flags ^= Flags; +} + +bool cTimer::HasFlags(unsigned int Flags) const +{ + return (flags & Flags) == Flags; +} + +bool cTimer::Matches(time_t t, bool Directly, int Margin) const +{ + startTime = stopTime = 0; + if (t == 0) + t = time(NULL); + + int begin = TimeToInt(start); // seconds from midnight + int length = TimeToInt(stop) - begin; + if (length < 0) + length += SECSINDAY; + + if (IsSingleEvent()) + { + startTime = SetTime(day, begin); + stopTime = startTime + length; + } + else + { + for (int i = -1; i <= 7; i++) + { + time_t t0 = IncDay(day ? max(day, t) : t, i); + if (DayMatches(t0)) + { + time_t a = SetTime(t0, begin); + time_t b = a + length; + if ((!day || a >= day) && t < b) + { + startTime = a; + stopTime = b; + break; + } + } + } + if (!startTime) + startTime = IncDay(t, 7); // just to have something that's more than a week in the future + else if (!Directly && (t > startTime || t > day + SECSINDAY + 3600)) // +3600 in case of DST change + day = 0; + } + + if (HasFlags(tfActive)) + { + return startTime <= t + Margin && t < stopTime; // must stop *before* stopTime to allow adjacent timers + } + return false; +} + +time_t cTimer::StartTime(void) const +{ + if (!startTime) + Matches(); + return startTime; +} + +time_t cTimer::StopTime(void) const +{ + if (!stopTime) + Matches(); + return stopTime; +} + +int cTimer::TimeToInt(int t) +{ + return (t / 100 * 60 + t % 100) * 60; +} + +CStdString cTimer::ToText() const +{ + strreplace(file, ':', '|'); + CStdString buffer; + buffer.Format("%u:%u:%s:%04d:%04d:%d:%d:%s:%s", flags, channel, PrintDay(day, weekdays), start, stop, priority, lifetime, file, aux ? aux : ""); + strreplace(file, '|', ':'); + return buffer; +} + +CStdString cTimer::PrintDay(time_t Day, int WeekDays) +{ +#define DAYBUFFERSIZE 64 + char buffer[DAYBUFFERSIZE]; + char *b = buffer; + if (WeekDays) + { + const char *w = "MTWTFSS"; + for (int i = 0; i < 7; i++) + { + if (WeekDays & 1) + *b++ = w[i]; + else + *b++ = '-'; + WeekDays >>= 1; + } + if (Day) + *b++ = '@'; + } + if (Day) + { + struct tm tm_r; + localtime_r(&Day, &tm_r); + b += strftime(b, DAYBUFFERSIZE - (b - buffer), "%Y-%m-%d", &tm_r); + } + *b = 0; + return buffer; +} + diff --git a/xbmc/pvrclients/vdr-streamdev/timers.h b/xbmc/pvrclients/vdr-streamdev/timers.h new file mode 100644 index 0000000000..91a2bf18eb --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/timers.h @@ -0,0 +1,100 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __TIMERS_H +#define __TIMERS_H + +#include +#include +#include +#include "StdString.h" + +enum eTimerFlags { tfNone = 0x0000, + tfActive = 0x0001, + tfInstant = 0x0002, + tfVps = 0x0004, + tfRecording = 0x0008, + tfAll = 0xFFFF, + }; + +class cTimer +{ +private: + mutable time_t startTime, stopTime; + time_t lastSetEvent; + bool recording, pending, inVpsMargin; + unsigned int flags; + mutable time_t day; ///< midnight of the day this timer shall hit, or of the first day it shall hit in case of a repeating timer + int weekdays; ///< bitmask, lowest bits: SSFTWTM (the 'M' is the LSB) + int start; + int stop; + int priority; + int channel; + int lifetime; + mutable char file[256]; + mutable char name[256]; + mutable char dir[256]; + int index; + char *aux; + +public: + cTimer(const PVR_TIMERINFO *Timer); + cTimer(); + virtual ~cTimer(); + + int Index(void) const { return index; } + bool Recording(void) const { return recording; } + bool Pending(void) const { return pending; } + bool InVpsMargin(void) const { return inVpsMargin; } + unsigned int Flags(void) const { return flags; } + unsigned int Channel(void) const { return channel; } + time_t Day(void) const { return day; } + int WeekDays(void) const { return weekdays; } + int Start(void) const { return start; } + int Stop(void) const { return stop; } + int Priority(void) const { return priority; } + int Lifetime(void) const { return lifetime; } + time_t FirstDay(void) const { return weekdays ? day : 0; } + const char *Aux(void) const { return aux; } + const char *File(void) const { return file; } + const char *Title(void) const { return name; } + const char *Dir(void) const { return dir; } + bool Matches(time_t t = 0, bool Directly = false, int Margin = 0) const; + time_t StartTime(void) const; + time_t StopTime(void) const; + bool Parse(const char *s); + bool IsSingleEvent(void) const; + static bool ParseDay(const char *s, time_t &Day, int &WeekDays); + static int GetMDay(time_t t); + static int GetWDay(time_t t); + bool DayMatches(time_t t) const; + static time_t IncDay(time_t t, int Days); + static time_t SetTime(time_t t, int SecondsFromMidnight); + void ClrFlags(unsigned int Flags); + void InvFlags(unsigned int Flags); + bool HasFlags(unsigned int Flags) const; + static int TimeToInt(int t); + CStdString ToText() const; + static CStdString PrintDay(time_t Day, int WeekDays); +}; + +#endif //__TIMERS_H diff --git a/xbmc/pvrclients/vdr-streamdev/tools.cpp b/xbmc/pvrclients/vdr-streamdev/tools.cpp new file mode 100644 index 0000000000..0b2828d889 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/tools.cpp @@ -0,0 +1,715 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * Most of this code is taken from tools.c in the Video Disk Recorder ('VDR') + */ + +#include "tools.h" + +ssize_t safe_read(int filedes, void *buffer, size_t size) +{ + for (;;) { + ssize_t p = __read(filedes, buffer, size); + if (p < 0 && errno == EINTR) { + XBMC->Log(LOG_DEBUG, "EINTR while reading from file handle %d - retrying", filedes); + continue; + } + return p; + } +} + +ssize_t safe_write(int filedes, const void *buffer, size_t size) +{ + ssize_t p = 0; + ssize_t written = size; + const unsigned char *ptr = (const unsigned char *)buffer; + while (size > 0) { + p = __write(filedes, ptr, size); + if (p < 0) { + if (errno == EINTR) { + XBMC->Log(LOG_DEBUG, "EINTR while writing to file handle %d - retrying", filedes); + continue; + } + break; + } + ptr += p; + size -= p; + } + return p < 0 ? p : written; +} + +void writechar(int filedes, char c) +{ + safe_write(filedes, &c, sizeof(c)); +} + +int WriteAllOrNothing(int fd, const unsigned char *Data, int Length, int TimeoutMs, int RetryMs) +{ + int written = 0; + while (Length > 0) { + int w = write(fd, Data + written, Length); + if (w > 0) { + Length -= w; + written += w; + } + else if (written > 0 && !FATALERRNO) { + // we've started writing, so we must finish it! + cTimeMs t; + cPoller Poller(fd, true); + Poller.Poll(RetryMs); + if (TimeoutMs > 0 && (TimeoutMs -= t.Elapsed()) <= 0) + break; + } + else + // nothing written yet (or fatal error), so we can just return the error code: + return w; + } + return written; +} + +char *strcpyrealloc(char *dest, const char *src) +{ + if (src) { + int l = max(dest ? strlen(dest) : 0, strlen(src)) + 1; // don't let the block get smaller! + dest = (char *)realloc(dest, l); + if (dest) + strcpy(dest, src); + else + XBMC->Log(LOG_ERROR, "ERROR: out of memory"); + } + else { + free(dest); + dest = NULL; + } + return dest; +} + +char *strn0cpy(char *dest, const char *src, size_t n) +{ + char *s = dest; + for ( ; --n && (*dest = *src) != 0; dest++, src++) ; + *dest = 0; + return s; +} + +char *strreplace(char *s, char c1, char c2) +{ + if (s) { + char *p = s; + while (*p) { + if (*p == c1) + *p = c2; + p++; + } + } + return s; +} + +char *strreplace(char *s, const char *s1, const char *s2) +{ + char *p = strstr(s, s1); + if (p) { + int of = p - s; + int l = strlen(s); + int l1 = strlen(s1); + int l2 = strlen(s2); + if (l2 > l1) + s = (char *)realloc(s, l + l2 - l1 + 1); + char *sof = s + of; + if (l2 != l1) + memmove(sof + l2, sof + l1, l - of - l1 + 1); + strncpy(sof, s2, l2); + } + return s; +} + +char *stripspace(char *s) +{ + if (s && *s) { + for (char *p = s + strlen(s) - 1; p >= s; p--) { + if (!isspace(*p)) + break; + *p = 0; + } + } + return s; +} + +char *compactspace(char *s) +{ + if (s && *s) { + char *t = stripspace(skipspace(s)); + char *p = t; + while (p && *p) { + char *q = skipspace(p); + if (q - p > 1) + memmove(p + 1, q, strlen(q) + 1); + p++; + } + if (t != s) + memmove(s, t, strlen(t) + 1); + } + return s; +} + +bool startswith(const char *s, const char *p) +{ + while (*p) { + if (*p++ != *s++) + return false; + } + return true; +} + +bool endswith(const char *s, const char *p) +{ + const char *se = s + strlen(s) - 1; + const char *pe = p + strlen(p) - 1; + while (pe >= p) { + if (*pe-- != *se-- || (se < s && pe >= p)) + return false; + } + return true; +} + +bool isempty(const char *s) +{ + return !(s && *skipspace(s)); +} + +int numdigits(int n) +{ + int res = 1; + while (n >= 10) { + n /= 10; + res++; + } + return res; +} + +bool IsNumber(const char *s) +{ + if (!*s) + return false; + do { + if (!isdigit(*s)) + return false; + } while (*++s); + return true; +} + +CStdString AddDirectory(const char *DirName, const char *FileName) +{ + CStdString ret; + ret.Format("%s/%s", DirName && *DirName ? DirName : ".", FileName); + return ret; +} + +char *ReadLink(const char *FileName) +{ +#if defined(__WINDOWS__) + return NULL; +#else + if (!FileName) + return NULL; + char *TargetName = NULL; + char *res = realpath(FileName, TargetName); + if (!res) + { + if (errno == ENOENT) // file doesn't exist + TargetName = strdup(FileName); + else // some other error occurred + XBMC->Log(LOG_ERROR, "ERROR (%s,%d,s): %m", __FILE__, __LINE__, FileName); + } + return TargetName; +#endif +} + + +// --- cTimeMs --------------------------------------------------------------- + +cTimeMs::cTimeMs(int Ms) +{ + Set(Ms); +} + +uint64_t cTimeMs::Now(void) +{ +#if _POSIX_TIMERS > 0 && defined(_POSIX_MONOTONIC_CLOCK) +#define MIN_RESOLUTION 5 // ms + static bool initialized = false; + static bool monotonic = false; + struct timespec tp; + if (!initialized) { + // check if monotonic timer is available and provides enough accurate resolution: + if (clock_getres(CLOCK_MONOTONIC, &tp) == 0) { + long Resolution = tp.tv_nsec; + // require a minimum resolution: + if (tp.tv_sec == 0 && tp.tv_nsec <= MIN_RESOLUTION * 1000000) { + if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) { + XBMC->Log(LOG_DEBUG, "cTimeMs: using monotonic clock (resolution is %ld ns)", Resolution); + monotonic = true; + } + else + XBMC->Log(LOG_ERROR, "cTimeMs: clock_gettime(CLOCK_MONOTONIC) failed"); + } + else + XBMC->Log(LOG_DEBUG, "cTimeMs: not using monotonic clock - resolution is too bad (%ld s %ld ns)", tp.tv_sec, tp.tv_nsec); + } + else + XBMC->Log(LOG_ERROR, "cTimeMs: clock_getres(CLOCK_MONOTONIC) failed"); + initialized = true; + } + if (monotonic) { + if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) + return (uint64_t(tp.tv_sec)) * 1000 + tp.tv_nsec / 1000000; + XBMC->Log(LOG_ERROR, "cTimeMs: clock_gettime(CLOCK_MONOTONIC) failed"); + monotonic = false; + // fall back to gettimeofday() + } +#else +#if !defined(__WINDOWS__) +# warning Posix monotonic clock not available +#endif +#endif + struct timeval t; + if (gettimeofday(&t, NULL) == 0) + return (uint64_t(t.tv_sec)) * 1000 + t.tv_usec / 1000; + return 0; +} + +void cTimeMs::Set(int Ms) +{ + begin = Now() + Ms; +} + +bool cTimeMs::TimedOut(void) +{ + return Now() >= begin; +} + +uint64_t cTimeMs::Elapsed(void) +{ + return Now() - begin; +} + +// --- cPoller --------------------------------------------------------------- + +cPoller::cPoller(int FileHandle, bool Out) +{ + numFileHandles = 0; + Add(FileHandle, Out); +} + +bool cPoller::Add(int FileHandle, bool Out) +{ + if (FileHandle >= 0) { + for (int i = 0; i < numFileHandles; i++) { + if (pfd[i].fd == FileHandle && pfd[i].events == (Out ? POLLOUT : POLLIN)) + return true; + } + if (numFileHandles < MaxPollFiles) { + pfd[numFileHandles].fd = FileHandle; + pfd[numFileHandles].events = Out ? POLLOUT : POLLIN; + pfd[numFileHandles].revents = 0; + numFileHandles++; + return true; + } + XBMC->Log(LOG_ERROR, "ERROR: too many file handles in cPoller"); + } + return false; +} + +bool cPoller::Poll(int TimeoutMs) +{ + if (numFileHandles) { + if (__poll(pfd, numFileHandles, TimeoutMs) != 0) + return true; // returns true even in case of an error, to let the caller + // access the file and thus see the error code + } + return false; +} + +// --- cFile ----------------------------------------------------------------- + +bool cFile::files[FD_SETSIZE] = { false }; +int cFile::maxFiles = 0; + +cFile::cFile(void) +{ + f = -1; +} + +cFile::~cFile() +{ + Close(); +} + +bool cFile::Open(const char *FileName, int Flags, mode_t Mode) +{ + if (!IsOpen()) +#ifdef __WINDOWS__ + return Open(open(FileName, Flags, 0)); +#else + return Open(open(FileName, Flags, Mode)); +#endif + XBMC->Log(LOG_ERROR, "ERROR: attempt to re-open %s", FileName); + return false; +} + +bool cFile::Open(int FileDes) +{ + if (FileDes >= 0) { + if (!IsOpen()) { + f = FileDes; + if (f >= 0) { + if (f < FD_SETSIZE) { + if (f >= maxFiles) + maxFiles = f + 1; + if (!files[f]) + files[f] = true; + else + XBMC->Log(LOG_ERROR, "ERROR: file descriptor %d already in files[]", f); + return true; + } + else + XBMC->Log(LOG_ERROR, "ERROR: file descriptor %d is larger than FD_SETSIZE (%d)", f, FD_SETSIZE); + } + } + else + XBMC->Log(LOG_ERROR, "ERROR: attempt to re-open file descriptor %d", FileDes); + } + return false; +} + +void cFile::Close(void) +{ + if (f >= 0) { + close(f); + files[f] = false; + f = -1; + } +} + +bool cFile::Ready(bool Wait) +{ + return f >= 0 && AnyFileReady(f, Wait ? 1000 : 0); +} + +bool cFile::AnyFileReady(int FileDes, int TimeoutMs) +{ + fd_set set; + FD_ZERO(&set); + for (int i = 0; i < maxFiles; i++) { + if (files[i]) + FD_SET(i, &set); + } + if (0 <= FileDes && FileDes < FD_SETSIZE && !files[FileDes]) + FD_SET(FileDes, &set); // in case we come in with an arbitrary descriptor + if (TimeoutMs == 0) + TimeoutMs = 10; // load gets too heavy with 0 + struct timeval timeout; + timeout.tv_sec = TimeoutMs / 1000; + timeout.tv_usec = (TimeoutMs % 1000) * 1000; + return select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 0 && (FileDes < 0 || FD_ISSET(FileDes, &set)); +} + +bool cFile::FileReady(int FileDes, int TimeoutMs) +{ + fd_set set; + struct timeval timeout; + FD_ZERO(&set); + FD_SET(FileDes, &set); + if (TimeoutMs >= 0) { + if (TimeoutMs < 100) + TimeoutMs = 100; + timeout.tv_sec = TimeoutMs / 1000; + timeout.tv_usec = (TimeoutMs % 1000) * 1000; + } + return select(FD_SETSIZE, &set, NULL, NULL, (TimeoutMs >= 0) ? &timeout : NULL) > 0 && FD_ISSET(FileDes, &set); +} + +bool cFile::FileReadyForWriting(int FileDes, int TimeoutMs) +{ + fd_set set; + struct timeval timeout; + FD_ZERO(&set); + FD_SET(FileDes, &set); + if (TimeoutMs < 100) + TimeoutMs = 100; + timeout.tv_sec = 0; + timeout.tv_usec = TimeoutMs * 1000; + return select(FD_SETSIZE, NULL, &set, NULL, &timeout) > 0 && FD_ISSET(FileDes, &set); +} + + +// --- cUnbufferedFile ------------------------------------------------------- + +#if !defined(__WINDOWS__) +#if !defined(__APPLE__) +#define USE_FADVISE +#endif +#endif + +#define WRITE_BUFFER KILOBYTE(800) + +cUnbufferedFile::cUnbufferedFile(void) +{ + fd = -1; +} + +cUnbufferedFile::~cUnbufferedFile() +{ + Close(); +} + +int cUnbufferedFile::Open(const char *FileName, int Flags, mode_t Mode) +{ + Close(); + fd = open(FileName, Flags, Mode); + curpos = 0; +#ifdef USE_FADVISE + begin = lastpos = ahead = 0; + cachedstart = 0; + cachedend = 0; + readahead = KILOBYTE(128); + written = 0; + totwritten = 0; + if (fd >= 0) + posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM); // we could use POSIX_FADV_SEQUENTIAL, but we do our own readahead, disabling the kernel one. +#endif + return fd; +} + +int cUnbufferedFile::Close(void) +{ + if (fd >= 0) { +#ifdef USE_FADVISE + if (totwritten) // if we wrote anything make sure the data has hit the disk before + fdatasync(fd); // calling fadvise, as this is our last chance to un-cache it. + posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED); +#endif + int OldFd = fd; + fd = -1; + return close(OldFd); + } + errno = EBADF; + return -1; +} + +// When replaying and going e.g. FF->PLAY the position jumps back 2..8M +// hence we do not want to drop recently accessed data at once. +// We try to handle the common cases such as PLAY->FF->PLAY, small +// jumps, moving editing marks etc. + +#define FADVGRAN KILOBYTE(4) // AKA fadvise-chunk-size; PAGE_SIZE or getpagesize(2) would also work. +#define READCHUNK MEGABYTE(8) + +void cUnbufferedFile::SetReadAhead(size_t ra) +{ + readahead = ra; +} + +int cUnbufferedFile::FadviseDrop(off_t Offset, off_t Len) +{ +#ifdef USE_FADVISE + // rounding up the window to make sure that not PAGE_SIZE-aligned data gets freed. + return posix_fadvise(fd, Offset - (FADVGRAN - 1), Len + (FADVGRAN - 1) * 2, POSIX_FADV_DONTNEED); +#else + return 0; +#endif +} + +off_t cUnbufferedFile::Seek(off_t Offset, int Whence) +{ + if (Whence == SEEK_SET && Offset == curpos) + return curpos; + curpos = lseek(fd, Offset, Whence); + return curpos; +} + +ssize_t cUnbufferedFile::Read(void *Data, size_t Size) +{ + if (fd >= 0) { +#ifdef USE_FADVISE + off_t jumped = curpos-lastpos; // nonzero means we're not at the last offset + if ((cachedstart < cachedend) && (curpos < cachedstart || curpos > cachedend)) { + // current position is outside the cached window -- invalidate it. + FadviseDrop(cachedstart, cachedend-cachedstart); + cachedstart = curpos; + cachedend = curpos; + } + cachedstart = min(cachedstart, curpos); +#endif + ssize_t bytesRead = safe_read(fd, Data, Size); +#ifdef USE_FADVISE + if (bytesRead > 0) { + curpos += bytesRead; + cachedend = max(cachedend, curpos); + + // Read ahead: + // no jump? (allow small forward jump still inside readahead window). + if (jumped >= 0 && jumped <= (off_t)readahead) { + // Trigger the readahead IO, but only if we've used at least + // 1/2 of the previously requested area. This avoids calling + // fadvise() after every read() call. + if (ahead - curpos < (off_t)(readahead / 2)) { + posix_fadvise(fd, curpos, readahead, POSIX_FADV_WILLNEED); + ahead = curpos + readahead; + cachedend = max(cachedend, ahead); + } + if (readahead < Size * 32) { // automagically tune readahead size. + readahead = Size * 32; + } + } + else + ahead = curpos; // jumped -> we really don't want any readahead, otherwise e.g. fast-rewind gets in trouble. + } + + if (cachedstart < cachedend) { + if (curpos - cachedstart > READCHUNK * 2) { + // current position has moved forward enough, shrink tail window. + FadviseDrop(cachedstart, curpos - READCHUNK - cachedstart); + cachedstart = curpos - READCHUNK; + } + else if (cachedend > ahead && cachedend - curpos > READCHUNK * 2) { + // current position has moved back enough, shrink head window. + FadviseDrop(curpos + READCHUNK, cachedend - (curpos + READCHUNK)); + cachedend = curpos + READCHUNK; + } + } + lastpos = curpos; +#endif + return bytesRead; + } + return -1; +} + +ssize_t cUnbufferedFile::Write(const void *Data, size_t Size) +{ + if (fd >=0) { + ssize_t bytesWritten = safe_write(fd, Data, Size); +#ifdef USE_FADVISE + if (bytesWritten > 0) { + begin = min(begin, curpos); + curpos += bytesWritten; + written += bytesWritten; + lastpos = max(lastpos, curpos); + if (written > WRITE_BUFFER) { + if (lastpos > begin) { + // Now do three things: + // 1) Start writeback of begin..lastpos range + // 2) Drop the already written range (by the previous fadvise call) + // 3) Handle nonpagealigned data. + // This is why we double the WRITE_BUFFER; the first time around the + // last (partial) page might be skipped, writeback will start only after + // second call; the third call will still include this page and finally + // drop it from cache. + off_t headdrop = min(begin, off_t(WRITE_BUFFER * 2)); + posix_fadvise(fd, begin - headdrop, lastpos - begin + headdrop, POSIX_FADV_DONTNEED); + } + begin = lastpos = curpos; + totwritten += written; + written = 0; + // The above fadvise() works when writing slowly (recording), but could + // leave cached data around when writing at a high rate, e.g. when cutting, + // because by the time we try to flush the cached pages (above) the data + // can still be dirty - we are faster than the disk I/O. + // So we do another round of flushing, just like above, but at larger + // intervals -- this should catch any pages that couldn't be released + // earlier. + if (totwritten > MEGABYTE(32)) { + // It seems in some setups, fadvise() does not trigger any I/O and + // a fdatasync() call would be required do all the work (reiserfs with some + // kind of write gathering enabled), but the syncs cause (io) load.. + // Uncomment the next line if you think you need them. + //fdatasync(fd); + off_t headdrop = min(off_t(curpos - totwritten), off_t(totwritten * 2)); + posix_fadvise(fd, curpos - totwritten - headdrop, totwritten + headdrop, POSIX_FADV_DONTNEED); + totwritten = 0; + } + } + } +#endif + return bytesWritten; + } + return -1; +} + +cUnbufferedFile *cUnbufferedFile::Create(const char *FileName, int Flags, mode_t Mode) +{ + cUnbufferedFile *File = new cUnbufferedFile; + if (File->Open(FileName, Flags, Mode) < 0) { + delete File; + File = NULL; + } + return File; +} + +// --- cReadDir -------------------------------------------------------------- + +cReadDir::cReadDir(const char *Directory) +{ + directory = opendir(Directory); +} + +cReadDir::~cReadDir() +{ + if (directory) + closedir(directory); +} + +struct dirent *cReadDir::Next(void) +{ + return directory && readdir_r(directory, &u.d, &result) == 0 ? result : NULL; +} + +// --- cReadLine ------------------------------------------------------------- + +cReadLine::cReadLine(void) +{ + size = 0; + buffer = NULL; +} + +cReadLine::~cReadLine() +{ + free(buffer); +} + +char *cReadLine::Read(FILE *f) +{ + int n = getline(&buffer, &size, f); + if (n > 0) { + n--; + if (buffer[n] == '\n') { + buffer[n] = 0; + if (n > 0) { + n--; + if (buffer[n] == '\r') + buffer[n] = 0; + } + } + return buffer; + } + return NULL; +} diff --git a/xbmc/pvrclients/vdr-streamdev/tools.h b/xbmc/pvrclients/vdr-streamdev/tools.h new file mode 100644 index 0000000000..a4d78bde54 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/tools.h @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __TOOLS_H +#define __TOOLS_H + +#include "pvrclient-vdr_os.h" +#include "client.h" +#include "StdString.h" +#include +#include +#include +#include +#include +#include +#include +#if !defined(__WINDOWS__) +#include +#endif + +#ifdef __APPLE__ +#include +#endif + +#define TS_SYNC_BYTE 0x47 +#define TS_SIZE 188 +#define TS_ERROR 0x80 +#define TS_PAYLOAD_START 0x40 +#define TS_TRANSPORT_PRIORITY 0x20 +#define TS_PID_MASK_HI 0x1F +#define TS_SCRAMBLING_CONTROL 0xC0 +#define TS_ADAPT_FIELD_EXISTS 0x20 +#define TS_PAYLOAD_EXISTS 0x10 +#define TS_CONT_CNT_MASK 0x0F +#define TS_ADAPT_DISCONT 0x80 +#define TS_ADAPT_RANDOM_ACC 0x40 // would be perfect for detecting independent frames, but unfortunately not used by all broadcasters +#define TS_ADAPT_ELEM_PRIO 0x20 +#define TS_ADAPT_PCR 0x10 +#define TS_ADAPT_OPCR 0x08 +#define TS_ADAPT_SPLICING 0x04 +#define TS_ADAPT_TP_PRIVATE 0x02 +#define TS_ADAPT_EXTENSION 0x01 + +#define ERRNUL(e) {errno=e;return 0;} +#define ERRSYS(e) {errno=e;return -1;} + +#define SECSINDAY 86400 + +#define KILOBYTE(n) ((n) * 1024) +#define MEGABYTE(n) ((n) * 1024LL * 1024LL) + +#define MALLOC(type, size) (type *)malloc(sizeof(type) * (size)) + +#define DELETENULL(p) (delete (p), p = NULL) +// +//#define CHECK(s) { if ((s) < 0) LOG_ERROR; } // used for 'ioctl()' calls +#define FATALERRNO (errno && errno != EAGAIN && errno != EINTR) + +ssize_t safe_read(int filedes, void *buffer, size_t size); +ssize_t safe_write(int filedes, const void *buffer, size_t size); +void writechar(int filedes, char c); +int WriteAllOrNothing(int fd, const unsigned char *Data, int Length, int TimeoutMs = 0, int RetryMs = 0); + ///< Writes either all Data to the given file descriptor, or nothing at all. + ///< If TimeoutMs is greater than 0, it will only retry for that long, otherwise + ///< it will retry forever. RetryMs defines the time between two retries. +char *strcpyrealloc(char *dest, const char *src); +char *strn0cpy(char *dest, const char *src, size_t n); +char *strreplace(char *s, char c1, char c2); +char *strreplace(char *s, const char *s1, const char *s2); ///< re-allocates 's' and deletes the original string if necessary! +inline char *skipspace(const char *s) +{ + if ((unsigned char)*s > ' ') // most strings don't have any leading space, so handle this case as fast as possible + return (char *)s; + while (*s && (unsigned char)*s <= ' ') // avoiding isspace() here, because it is much slower + s++; + return (char *)s; +} +char *stripspace(char *s); +char *compactspace(char *s); +bool startswith(const char *s, const char *p); +bool endswith(const char *s, const char *p); +bool isempty(const char *s); +int numdigits(int n); +bool IsNumber(const char *s); +CStdString AddDirectory(const char *DirName, const char *FileName); +char *ReadLink(const char *FileName); ///< returns a new string allocated on the heap, which the caller must delete (or NULL in case of an error) + +class cTimeMs +{ +private: + uint64_t begin; +public: + cTimeMs(int Ms = 0); + ///< Creates a timer with ms resolution and an initial timeout of Ms. + static uint64_t Now(void); + void Set(int Ms = 0); + bool TimedOut(void); + uint64_t Elapsed(void); +}; + +class cPoller +{ +private: + enum { MaxPollFiles = 16 }; + pollfd pfd[MaxPollFiles]; + int numFileHandles; +public: + cPoller(int FileHandle = -1, bool Out = false); + bool Add(int FileHandle, bool Out); + bool Poll(int TimeoutMs = 0); +}; + +class cFile +{ +private: + static bool files[]; + static int maxFiles; + int f; +public: + cFile(void); + ~cFile(); + operator int () { return f; } + bool Open(const char *FileName, int Flags, mode_t Mode = DEFFILEMODE); + bool Open(int FileDes); + void Close(void); + bool IsOpen(void) { return f >= 0; } + bool Ready(bool Wait = true); + static bool AnyFileReady(int FileDes = -1, int TimeoutMs = 1000); + static bool FileReady(int FileDes, int TimeoutMs = 1000); + static bool FileReadyForWriting(int FileDes, int TimeoutMs = 1000); +}; + +/// cUnbufferedFile is used for large files that are mainly written or read +/// in a streaming manner, and thus should not be cached. + +class cUnbufferedFile +{ +private: + int fd; + off_t curpos; + off_t cachedstart; + off_t cachedend; + off_t begin; + off_t lastpos; + off_t ahead; + size_t readahead; + size_t written; + size_t totwritten; + int FadviseDrop(off_t Offset, off_t Len); +public: + cUnbufferedFile(void); + ~cUnbufferedFile(); + int Open(const char *FileName, int Flags, mode_t Mode = DEFFILEMODE); + int Close(void); + void SetReadAhead(size_t ra); + off_t Seek(off_t Offset, int Whence); + ssize_t Read(void *Data, size_t Size); + ssize_t Write(const void *Data, size_t Size); + static cUnbufferedFile *Create(const char *FileName, int Flags, mode_t Mode = DEFFILEMODE); +}; + +class cReadDir +{ +private: + DIR *directory; + struct dirent *result; + union // according to "The GNU C Library Reference Manual" + { + struct dirent d; + char b[offsetof(struct dirent, d_name) + NAME_MAX + 1]; + } u; +public: + cReadDir(const char *Directory); + ~cReadDir(); + bool Ok(void) { return directory != NULL; } + struct dirent *Next(void); +}; + +class cReadLine +{ +private: + size_t size; + char *buffer; +public: + cReadLine(void); + ~cReadLine(); + char *Read(FILE *f); +}; + +inline int CompareStrings(const void *a, const void *b) +{ + return strcmp(*(const char **)a, *(const char **)b); +} + + +#endif //__TOOLS_H diff --git a/xbmc/pvrclients/vdr-streamdev/vtptransceiver.cpp b/xbmc/pvrclients/vdr-streamdev/vtptransceiver.cpp new file mode 100644 index 0000000000..457a3678b4 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/vtptransceiver.cpp @@ -0,0 +1,1433 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include +#include +#include +#include "vtptransceiver.h" +#include "select.h" +#include "client.h" +#include "timers.h" +#include "channels.h" +#include "recordings.h" +#include "epg.h" +#include "client.h" + +//#define DEBUG_VTP +#define MINLOGREPEAT 10 //don't log connect failures too often (seconds) +#define MAX_LINK_LEVEL 6 + +using namespace std; + +CVTPTransceiver VTPTransceiver; + +CVTPTransceiver::CVTPTransceiver() + : m_VTPSocket(INVALID_SOCKET) +{ + memset(m_DataSockets, INVALID_SOCKET, sizeof(SOCKET) * si_Count); +} + +CVTPTransceiver::~CVTPTransceiver() +{ + Reset(); + if (IsOpen()) Quit(); +} + +void CVTPTransceiver::Reset(void) +{ + for (int it = 0; it < si_Count; ++it) + { + if (m_DataSockets[it] != INVALID_SOCKET) + { + close(m_DataSockets[it]); + m_DataSockets[it] = INVALID_SOCKET; + } + } +} + +bool CVTPTransceiver::OpenStreamSocket(SOCKET& sock, struct sockaddr_in& address2) +{ + struct sockaddr_in address(address2); + + sock = __socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if(sock == INVALID_SOCKET) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::OpenStreamSocket - invalid socket"); + return false; + } + + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = 0; + + if(__bind(sock, (struct sockaddr*) &address, sizeof(address)) == SOCKET_ERROR) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::OpenStreamSocket - bind failed"); + return false; + } + + socklen_t len = sizeof(address); + if(__getsockname(sock, (struct sockaddr*) &address, &len) == SOCKET_ERROR) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::OpenStreamSocket - bind failed"); + return false; + } + + if(__listen(sock, 1) == SOCKET_ERROR) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::OpenStreamSocket - listen failed"); + return false; + } + + address2.sin_port = address.sin_port; + + XBMC->Log(LOG_DEBUG, "CVTPTransceiver::OpenStreamSocket - listening on %s:%d", inet_ntoa(address.sin_addr), address.sin_port); + return true; +} + +bool CVTPTransceiver::AcceptStreamSocket(SOCKET& sock2) +{ + SOCKET sock; + sock = __accept(sock2, NULL, NULL); + if(sock == INVALID_SOCKET) + { + XBMC->Log(LOG_ERROR, "CVTPStream::Accept - failed to accept incomming connection"); + return false; + } + + __close(sock2); + + const char sol=1; + // Ignore possible errors here, proceed as usual + __setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &sol, sizeof(sol)); + + sock2 = sock; + return true; +} + +void CVTPTransceiver::Close() +{ + if(m_VTPSocket != INVALID_SOCKET) + __close(m_VTPSocket); +} + +bool CVTPTransceiver::Connect(const string &host, int port) +{ + socklen_t len; + struct hostent *hp; + + if ((m_VTPSocket = __socket(PF_INET, SOCK_STREAM, IPPROTO_IP)) == INVALID_SOCKET) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::Connect - Can't open socket '%s:%u'", host.c_str(), port); + return false; + } + +#ifdef _LINUX + int flags = fcntl(m_VTPSocket, F_GETFL); + if (__fcntl(m_VTPSocket, F_SETFL, O_NONBLOCK) == -1) +#elif defined(_WIN32) || defined(_WIN64) + u_long iMode = 1; + if (__ioctlsocket(m_VTPSocket, FIONBIO, &iMode) == -1) +#endif + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::Connect - Can't set socket to non blocking mode"); + Close(); + return false; + } + + if ((hp = __gethostbyname(host.c_str())) == NULL) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::Connect - failed to resolve hostname: %s", host.c_str()); + Close(); + return false; + } + + m_LocalAddr.sin_family = AF_INET; + m_LocalAddr.sin_port = 0; + m_LocalAddr.sin_addr.s_addr = INADDR_ANY; + if (__bind(m_VTPSocket, (struct sockaddr*)&m_LocalAddr, sizeof(m_LocalAddr)) == -1) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::Connect - Can't bind requested socket '%s:%u'", host.c_str(), port); + Close(); + return false; + } + + m_RemoteAddr.sin_family = AF_INET; + m_RemoteAddr.sin_port = htons(port); + memcpy(&m_RemoteAddr.sin_addr.s_addr, hp->h_addr_list[0], hp->h_length); + if (__connect(m_VTPSocket, (struct sockaddr*)&m_RemoteAddr, sizeof(m_RemoteAddr)) < 0) + { + fd_set set_r, set_w, set_e; + timeval timeout; + + timeout.tv_sec = g_iConnectTimeout; + timeout.tv_usec = 0; + + // fill with new data + FD_ZERO(&set_r); + FD_ZERO(&set_w); + FD_ZERO(&set_e); + FD_SET(m_VTPSocket, &set_r); + FD_SET(m_VTPSocket, &set_w); + FD_SET(m_VTPSocket, &set_e); + int result = __select(FD_SETSIZE, &set_r, &set_w, &set_e, &timeout); + if (result < 0) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::Connect - select failed '%s:%u'", host.c_str(), port); + Close(); + return false; + } + else if (result == 0) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::Connect - connect timed out '%s:%u'", host.c_str(), port); + Close(); + return false; + } + else if (!IsConnected(m_VTPSocket, &set_r, &set_w, &set_e)) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::Connect - failed to connect to IP '%d.%d.%d.%d", + (ntohl(m_RemoteAddr.sin_addr.s_addr) >> 24) & 0xff, + (ntohl(m_RemoteAddr.sin_addr.s_addr) >> 16) & 0xff, + (ntohl(m_RemoteAddr.sin_addr.s_addr) >> 8) & 0xff, + (ntohl(m_RemoteAddr.sin_addr.s_addr) & 0xff)); + Close(); + return false; + } + } + +#ifdef _LINUX + if (__fcntl(m_VTPSocket, F_SETFL, flags) == -1) +#elif defined(_WIN32) || defined(_WIN64) + iMode = 0; + if (__ioctlsocket(m_VTPSocket, FIONBIO, &iMode) == -1) +#endif + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::Connect - Can't set socket initial condition"); + Close(); + return false; + } + + len = sizeof(struct sockaddr_in); + if (__getpeername(m_VTPSocket, (struct sockaddr*)&m_RemoteAddr, &len) == -1) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::Connect - Can't get the name of the peer socket '%s:%u'", host.c_str(), port); + Close(); + return false; + } + + len = sizeof(struct sockaddr_in); + if (__getsockname(m_VTPSocket, (struct sockaddr*)&m_LocalAddr, &len) == -1) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::Connect - Can't get the socket name '%s:%u'", host.c_str(), port); + Close(); + return false; + } + + // VTP Server will send a greeting + string line; + int code; + if (!ReadResponse(code, line)) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::Connect - Failed reading response"); + Close(); + return false; + } + + XBMC->Log(LOG_INFO, "CVTPTransceiver::Connect - server greeting: %s", line.c_str()); + return true; +} + +#if defined(_WIN32) || defined(_WIN64) +bool CVTPTransceiver::IsConnected(SOCKET socket, fd_set *rd, fd_set *wr, fd_set *ex) +{ + WSASetLastError(0); + if (!FD_ISSET(socket, rd) && !FD_ISSET(socket, wr)) + return false; + if (FD_ISSET(socket, ex)) + return false; + return true; +} +#else +bool CVTPTransceiver::IsConnected(SOCKET socket, fd_set *rd, fd_set *wr, fd_set *ex) +{ + int err; + socklen_t len = sizeof(err); + + errno = 0; /* assume no error */ + if (!FD_ISSET(socket, rd ) && !FD_ISSET(socket, wr)) + return false; + if (__getsockopt(socket, SOL_SOCKET, SO_ERROR, &err, &len) < 0) + return false; + errno = err; /* in case we're not connected */ + return err == 0; +} +#endif + +bool CVTPTransceiver::ReadResponse(int &code, string &line) +{ + vector lines; + if(ReadResponse(code, lines)) + { + line = lines[lines.size()-1]; + return true; + } + return false; +} + +bool CVTPTransceiver::ReadResponse(int &code, vector &lines) +{ + fd_set set_r, set_e; + timeval timeout; + int result; + int retries = 5; + char buffer[2048]; + char cont = 0; + string line; + size_t pos1 = 0, pos2 = 0, pos3 = 0; + + while(true) + { + while( (pos1 = line.find("\r\n", pos3)) != std::string::npos) + { + if(sscanf(line.c_str(), "%d%c", &code, &cont) != 2) + { + XBMC->Log(LOG_DEBUG, "CVTPTransceiver::ReadResponse - unknown line format: %s", line.c_str()); + line.erase(0, pos1 + 2); + continue; + } + + pos2 = line.find(cont); + + lines.push_back(line.substr(pos2+1, pos1-pos2-1)); + + line.erase(0, pos1 + 2); + pos3 = 0; + } + + // we only need to recheck 1 byte + if(line.size() > 0) + pos3 = line.size() - 1; + else + pos3 = 0; + + if(cont == ' ') + break; + + //TODO set 10 seconds timeout value?? + timeout.tv_sec = 10; + timeout.tv_usec = 0; + + // fill with new data + FD_ZERO(&set_r); + FD_ZERO(&set_e); + FD_SET(m_VTPSocket, &set_r); + FD_SET(m_VTPSocket, &set_e); + result = __select(FD_SETSIZE, &set_r, NULL, &set_e, &timeout); + if(result < 0) + { + XBMC->Log(LOG_DEBUG, "CVTPTransceiver::ReadResponse - select failed"); + m_VTPSocket = INVALID_SOCKET; + return false; + } + + if(result == 0) + { + XBMC->Log(LOG_DEBUG, "CVTPTransceiver::ReadResponse - timeout waiting for response, retrying..."); + if (retries != 0) { + retries--; + continue; + } + else { + m_VTPSocket = INVALID_SOCKET; + return false; + } + } + + result = __recv(m_VTPSocket, buffer, sizeof(buffer) - 1, 0); + if(result < 0) + { + XBMC->Log(LOG_DEBUG, "CVTPTransceiver::ReadResponse - recv failed"); + m_VTPSocket = INVALID_SOCKET; + return false; + } + buffer[result] = 0; + + line.append(buffer); + } + + return true; +} + +bool CVTPTransceiver::SendCommand(const string &command) +{ + fd_set set_w, set_e; + struct timeval tv; + int result; + char buffer[1024]; + int len; + + len = sprintf(buffer, "%s\r\n", command.c_str()); + + // fill with new data + tv.tv_sec = 0; + tv.tv_usec = 0; + + FD_ZERO(&set_w); + FD_ZERO(&set_e); + FD_SET(m_VTPSocket, &set_w); + FD_SET(m_VTPSocket, &set_e); + result = __select(FD_SETSIZE, &set_w, NULL, &set_e, &tv); + if(result < 0) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::SendCommand - select failed"); + m_VTPSocket = INVALID_SOCKET; + return false; + } + if (FD_ISSET(m_VTPSocket, &set_w)) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::SendCommand - failed to send data"); + m_VTPSocket = INVALID_SOCKET; + return false; + } + if(__send(m_VTPSocket, buffer, len, 0) != len) + { + XBMC->Log(LOG_ERROR, "CVTPTransceiver::SendCommand - failed to send data"); + m_VTPSocket = INVALID_SOCKET; + return false; + } + return true; +} + +bool CVTPTransceiver::SendCommand(const string &command, int &code, string line) +{ + vector lines; + if(SendCommand(command, code, lines)) + { + line = lines[lines.size()-1]; + return true; + } + return false; +} + +bool CVTPTransceiver::SendCommand(const string &command, int &code, vector &lines) +{ + if(!SendCommand(command)) + return false; + + if(!ReadResponse(code, lines)) + return false; + + if(code < 200 || code > 299) + { + if (code == 550 && lines[lines.size()-1] == "No schedule found") // Ignore error for missing EPG + return true; + + XBMC->Log(LOG_ERROR, "CVTPTransceiver::SendCommand - Failed with code: %d (%s)", code, lines[lines.size()-1].c_str()); + return false; + } + + return true; +} + +bool CVTPTransceiver::CheckConnection() +{ + CMD_LOCK; + + if (IsOpen()) + { + cTBSelect select; + +#ifdef DEBUG_VTP + XBMC->Log(LOG_DEBUG, "connection open"); +#endif + + // XXX+ check if connection is still alive (is there a better way?) + // There REALLY shouldn't be anything readable according to PROTOCOL here + // If there is, assume it's an eof signal (subseq. read would return 0) + select.Add(m_VTPSocket, false); + int res; + if ((res = select.Select(0)) == 0) + { +#ifdef DEBUG_VTP + XBMC->Log(LOG_DEBUG, "select said nothing happened"); +#endif + return true; + } + XBMC->Log(LOG_DEBUG, "closing connection (res was %d)", res); + Close(); + } + + if (!Connect(g_szHostname, g_iPort)) + { + static time_t lastTime = 0; + if (time(NULL) - lastTime > MINLOGREPEAT) + { + XBMC->Log(LOG_ERROR, "Couldn't connect to %s:%d: %s", g_szHostname.c_str(), g_iPort, strerror(errno)); + lastTime = time(NULL); + } + return false; + } + + string line; + int code = 220; + if(!SendCommand("CAPS TS", code, line) || code != 220) + { + if (errno == 0) + XBMC->Log(LOG_ERROR, "Couldn't negotiate capabilities on %s:%d", g_szHostname.c_str(), g_iPort); + Close(); + return false; + } + + const char *Filters = ""; + if(SendCommand("CAPS FILTERS", code, line) || code != 220) + Filters = ",FILTERS"; + + XBMC->Log(LOG_INFO, "Connected to server %s:%d using capabilities TS%s", g_szHostname.c_str(), g_iPort, Filters); + return true; +} + +bool CVTPTransceiver::ProvidesChannel(unsigned int Channel, int Priority) +{ + if (!CheckConnection()) return false; + + CMD_LOCK; + + string line; + int code; + + CStdString command; + command.Format("PROV %i %d", Priority, Channel); + if(!SendCommand(command, code, line)) + { + if (command != "560" && errno == 0) + XBMC->Log(LOG_ERROR, "Couldn't check if %s:%d provides channel %d", g_szHostname.c_str(), g_iPort, Channel); + return false; + } + + return true; +} + +bool CVTPTransceiver::CreateDataConnection(eSocketId Id) +{ + if (!CheckConnection()) return false; + + if (m_DataSockets[Id] != INVALID_SOCKET) + close(m_DataSockets[Id]); + + sockaddr_in address; + SOCKET sock; + socklen_t len = sizeof(address); + string result; + int code; + + if(__getsockname(m_VTPSocket, (struct sockaddr*) &address, &len) == SOCKET_ERROR) + { + XBMC->Log(LOG_ERROR, "Couldn't get socket name: %s", strerror(errno)); + return false; + } + + XBMC->Log(LOG_DEBUG, "CVTPTransceiver::CreateDataConnection - local address %s:%d", inet_ntoa(address.sin_addr), ntohs(address.sin_port) ); + + if(!OpenStreamSocket(sock, address)) + { + XBMC->Log(LOG_ERROR, "Couldn't create data connection: %s", strerror(errno)); + return false; + } + + int port = ntohs(address.sin_port); + int addr = ntohl(address.sin_addr.s_addr); + + CStdString command; + command.Format("PORT %d %d,%d,%d,%d,%d,%d", Id + , (addr & 0xFF000000)>>24 + , (addr & 0x00FF0000)>>16 + , (addr & 0x0000FF00)>>8 + , (addr & 0x000000FF)>>0 + , (port & 0xFF00)>>8 + , (port & 0x00FF)>>0); + + CMD_LOCK; + + if(!SendCommand(command, code, result) || code != 220) + { + XBMC->Log(LOG_DEBUG, "error: %m"); + if (errno == 0) + XBMC->Log(LOG_ERROR, "Couldn't establish data connection to %s:%d", g_szHostname.c_str(), g_iPort); + return false; + } + + if(!AcceptStreamSocket(sock)) + { + XBMC->Log(LOG_ERROR, "Couldn't establish data connection to %s:%d%s%s", g_szHostname.c_str(), g_iPort, errno == 0 ? "" : ": ", errno == 0 ? "" : strerror(errno)); + close(sock); + return false; + } + + m_DataSockets[Id] = sock; + return true; +} + +bool CVTPTransceiver::CloseDataConnection(eSocketId Id) +{ + //if (!CheckConnection()) return false; + + CMD_LOCK; + + if(Id == siLive || Id == siLiveFilter || Id == siReplay || Id == siDataRespond) + { + if (m_DataSockets[Id] != INVALID_SOCKET) + { + string line; + int code; + CStdString command; + command.Format("ABRT %i", Id); + if (!SendCommand(command, code, line) || code != 220) + { + if (errno == 0) + XBMC->Log(LOG_ERROR, "Couldn't cleanly close data connection"); + //return false; + } + close(m_DataSockets[Id]); + m_DataSockets[Id] = INVALID_SOCKET; + } + } + return true; +} + +bool CVTPTransceiver::SetChannelDevice(unsigned int Channel) +{ + if (!CheckConnection()) return false; + + CMD_LOCK; + + string result; + int code; + CStdString command; + command.Format("TUNE %d", Channel); + if(!SendCommand(command, code, result) || code != 220) + { + if (errno == 0) + XBMC->Log(LOG_ERROR, "Couldn't tune %s:%d to channel %d", g_szHostname.c_str(), g_iPort, Channel); + return false; + } + return true; +} + +bool CVTPTransceiver::SetRecordingIndex(unsigned int Recording) +{ + if (!CheckConnection()) return false; + + CMD_LOCK; + + string result; + int code; + CStdString command; + command.Format("PLAY %d", Recording); + if (!SendCommand(command, code, result) || code != 220) + { + if (errno == 0) + XBMC->Log(LOG_ERROR, "Couldn't open recording %d on %s:%d", Recording, g_szHostname.c_str(), g_iPort); + return false; + } + return true; +} + +bool CVTPTransceiver::GetPlayingRecordingSize(uint64_t *size, uint32_t *frames) +{ + vector lines; + int code; + + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (!SendCommand("SIZE", code, lines) || code != 220) + return false; + + vector::iterator it = lines.begin(); + string& data(*it); + + *size = atoll(data.c_str()); + *frames = atol(data.substr(data.find(" ") + 1).c_str()); + return true; +} + +uint64_t CVTPTransceiver::SeekRecordingPosition(uint64_t position) +{ + vector lines; + int code; + + if (!CheckConnection()) return 0; + + CMD_LOCK; + + CStdString command; + command.Format("SEEK %llu", position); + if (!SendCommand(command, code, lines) || code != 220) + { + if (errno == 0) + XBMC->Log(LOG_ERROR, "Couldn't seek to position %llu on %s:%d", position, g_szHostname.c_str(), g_iPort); + return 0; + } + + vector::iterator it = lines.begin(); + string& data(*it); + return atoll(data.c_str());; +} + +CStdString CVTPTransceiver::GetBackendName() +{ + vector lines; + int code; + + if (!CheckConnection()) + return ""; + + CMD_LOCK; + + if (!SendCommand("STAT name", code, lines)) + return ""; + + vector::iterator it = lines.begin(); + string& data(*it); + return data; +} + +CStdString CVTPTransceiver::GetBackendVersion() +{ + vector lines; + int code; + + if (!CheckConnection()) + return ""; + + CMD_LOCK; + + if (!SendCommand("STAT version", code, lines)) + return ""; + + vector::iterator it = lines.begin(); + string& data(*it); + return data; +} + +PVR_ERROR CVTPTransceiver::GetDriveSpace(long long *total, long long *used) +{ + vector lines; + int code; + + if (!CheckConnection()) + return PVR_ERROR_SERVER_ERROR; + + CMD_LOCK; + + if (!SendCommand("STAT disk", code, lines)) + return PVR_ERROR_SERVER_ERROR; + + vector::iterator it = lines.begin(); + string& data(*it); + size_t found = data.find("MB"); + if (found != CStdString::npos) + { + *total = atol(data.c_str()) * 1024; + data.erase(0, found + 3); + *used = atol(data.c_str()) * 1024; + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR CVTPTransceiver::GetBackendTime(time_t *localTime, int *gmtOffset) +{ + vector lines; + int code; + + if (!CheckConnection()) + return PVR_ERROR_SERVER_ERROR; + + CMD_LOCK; + + if (!SendCommand("STAT time", code, lines)) + return PVR_ERROR_SERVER_ERROR; + + vector::iterator it = lines.begin(); + string& data(*it); + + *localTime = atol(data.c_str()); + *gmtOffset = atol(data.substr(data.find(" ") + 1).c_str()); + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR CVTPTransceiver::RequestEPGForChannel(const PVR_CHANNEL &channel, PVRHANDLE handle, time_t start, time_t end) +{ + vector lines; + int code; + cEpg epg; + + if (!CheckConnection()) return PVR_ERROR_SERVER_ERROR; + + CMD_LOCK; + + CStdString command; + if (start != 0) + command.Format("LSTE %d from %lu to %lu", channel.number, (long)start, (long)end); + else + command.Format("LSTE %d", channel.number); + while (!SendCommand(command, code, lines) || code != 215) + { + if (code == 550) + return PVR_ERROR_NO_ERROR; + else if (code != 451) + return PVR_ERROR_SERVER_ERROR; + Sleep(100); + } + + for (vector::iterator it = lines.begin(); it != lines.end(); it++) + { + string& data(*it); + CStdString str_result = data; + + if (g_bCharsetConv) + XBMC->UnknownToUTF8(str_result); + + bool isEnd = epg.ParseLine(str_result.c_str()); + if (isEnd && epg.StartTime() != 0) + { + PVR_PROGINFO broadcast; + broadcast.channum = channel.number; + broadcast.uid = epg.UniqueId(); + broadcast.title = epg.Title(); + broadcast.subtitle = epg.ShortText(); + broadcast.description = epg.Description(); + broadcast.starttime = epg.StartTime(); + broadcast.endtime = epg.EndTime(); + broadcast.genre_type = epg.GenreType(); + broadcast.genre_sub_type = epg.GenreSubType(); + broadcast.parental_rating = epg.ParentalRating(); + PVR->TransferEpgEntry(handle, &broadcast); + epg.Reset(); + } + } + + return PVR_ERROR_NO_ERROR; +} + +int CVTPTransceiver::GetNumChannels() +{ + vector lines; + int code; + + if (!CheckConnection()) return -1; + + CMD_LOCK; + + if (!SendCommand("STAT channels", code, lines)) + return -1; + + vector::iterator it = lines.begin(); + string& data(*it); + return atol(data.c_str()); +} + +PVR_ERROR CVTPTransceiver::RequestChannelList(PVRHANDLE handle, bool radio) +{ + vector lines; + int code; + + if (!g_bRadioEnabled && radio) return PVR_ERROR_NO_ERROR; + if (!CheckConnection()) return PVR_ERROR_SERVER_ERROR; + + CMD_LOCK; + + while (!SendCommand("LSTC", code, lines)) + { + if (code != 451) + return PVR_ERROR_SERVER_ERROR; + Sleep(10); + } + + for (vector::iterator it = lines.begin(); it < lines.end(); it++) + { + string& data(*it); + CStdString str_result = data; + + if (g_bCharsetConv) + XBMC->UnknownToUTF8(str_result); + + cChannel channel; + channel.Parse(str_result.c_str()); + + /* Ignore channels without streams */ + if ((g_bNoBadChannels && channel.Vpid() == 0 && channel.Apid(0) == 0 && channel.Dpid(0) == 0) || (g_bOnlyFTA && channel.Ca() != 0)) + continue; + + PVR_CHANNEL tag; + tag.uid = channel.Sid(); + tag.number = channel.Number(); + tag.name = channel.Name(); + tag.callsign = channel.Name(); + tag.iconpath = ""; + tag.encryption = channel.Ca(); + tag.radio = (channel.Vpid() == 0) && (channel.Apid(0) != 0) ? true : false; + tag.hide = false; + tag.recording = false; + tag.bouquet = 0; + tag.multifeed = false; + tag.input_format = "mpegts"; + tag.stream_url = ""; + + if (radio == tag.radio) + PVR->TransferChannelEntry(handle, &tag); + } + + return PVR_ERROR_NO_ERROR; +} + +int CVTPTransceiver::GetNumRecordings(void) +{ + vector lines; + int code; + + if (!CheckConnection()) return -1; + + CMD_LOCK; + + if (!SendCommand("STAT records", code, lines)) + return -1; + + vector::iterator it = lines.begin(); + string& data(*it); + return atol(data.c_str()); +} + +void CVTPTransceiver::ScanVideoDir(PVRHANDLE handle, const char *DirName, bool Deleted, int LinkLevel) +{ + cReadDir d(DirName); + struct dirent *e; + while ((e = d.Next()) != NULL) + { + if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) + { + char *buffer = strdup(AddDirectory(DirName, e->d_name)); + struct stat st; + if (stat(buffer, &st) == 0) + { + int Link = 0; + if (S_ISLNK(st.st_mode)) + { + if (LinkLevel > MAX_LINK_LEVEL) + { + XBMC->Log(LOG_ERROR, "max link level exceeded - not scanning %s", buffer); + continue; + } + Link = 1; + char *old = buffer; + buffer = ReadLink(old); + free(old); + if (!buffer) + continue; + if (stat(buffer, &st) != 0) + { + free(buffer); + continue; + } + } + if (S_ISDIR(st.st_mode)) + { + if (endswith(buffer, Deleted ? DELEXT : RECEXT)) + { + cRecording recording(buffer); + + PVR_RECORDINGINFO tag; + tag.index = m_recIndex++; + tag.channel_name = recording.ChannelName(); + tag.lifetime = recording.Lifetime(); + tag.priority = recording.Priority(); + tag.recording_time = recording.StartTime(); + tag.duration = recording.Duration(); + tag.subtitle = recording.ShortText(); + tag.description = recording.Description(); + tag.title = recording.Title(); + tag.directory = recording.Directory(); + tag.stream_url = recording.StreamURL(); + + PVR->TransferRecordingEntry(handle, &tag); + } + else + ScanVideoDir(handle, buffer, Deleted, LinkLevel + Link); + } + } + free(buffer); + } + } +} + +PVR_ERROR CVTPTransceiver::RequestRecordingsList(PVRHANDLE handle) +{ + if (g_bUseRecordingsDir && g_szRecordingsDir != "") + { + m_recIndex = 1; + ScanVideoDir(handle, g_szRecordingsDir.c_str()); + } + else + { + vector linesShort; + int code; + + if (!CheckConnection()) return PVR_ERROR_SERVER_ERROR; + + CMD_LOCK; + + if (!SendCommand("LSTR", code, linesShort)) + return PVR_ERROR_SERVER_ERROR; + + for (vector::iterator it = linesShort.begin(); it != linesShort.end(); it++) + { + string& data(*it); + CStdString str_result = data; + + /* Convert to UTF8 string format */ + if (g_bCharsetConv) + XBMC->UnknownToUTF8(str_result); + + cRecording recording; + if (recording.ParseEntryLine(str_result.c_str())) + { + vector linesDetails; + + CStdString command; + command.Format("LSTR %i", recording.Index()); + if (!SendCommand(command, code, linesDetails)) + continue; + + for (vector::iterator it2 = linesDetails.begin(); it2 != linesDetails.end(); it2++) + { + string& data2(*it2); + CStdString str_details = data2; + + /* Convert to UTF8 string format */ + if (g_bCharsetConv) + XBMC->UnknownToUTF8(str_details); + + recording.ParseLine(str_details.c_str()); + } + + PVR_RECORDINGINFO tag; + tag.index = recording.Index(); + tag.channel_name = recording.ChannelName(); + tag.lifetime = recording.Lifetime(); + tag.priority = recording.Priority(); + tag.recording_time = recording.StartTime(); + tag.duration = recording.Duration(); + tag.subtitle = recording.ShortText(); + tag.description = recording.Description(); + tag.stream_url = ""; + tag.title = recording.FileName(); + tag.directory = recording.Directory(); + + PVR->TransferRecordingEntry(handle, &tag); + } + } + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR CVTPTransceiver::DeleteRecording(const PVR_RECORDINGINFO &recinfo) +{ + vector lines; + int code; + + if (!CheckConnection()) return PVR_ERROR_SERVER_ERROR; + + CMD_LOCK; + + CStdString command; + command.Format("LSTR %i", recinfo.index); + if (!VTPTransceiver.SendCommand(command, code, lines)) + return PVR_ERROR_SERVER_ERROR; + if (code != 215) + return PVR_ERROR_NOT_SYNC; + + command.Format("DELR %i", recinfo.index); + if (!SendCommand(command, code, lines)) + return PVR_ERROR_SERVER_ERROR; + if (code != 250) + return PVR_ERROR_NOT_DELETED; + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR CVTPTransceiver::RenameRecording(const PVR_RECORDINGINFO &recinfo, const char *newname) +{ + vector lines; + int code; + + if (!CheckConnection()) return PVR_ERROR_SERVER_ERROR; + + CMD_LOCK; + + CStdString command; + command.Format("LSTR %i", recinfo.index); + if (!SendCommand(command, code, lines)) + return PVR_ERROR_SERVER_ERROR; + if (code != 215) + return PVR_ERROR_NOT_SYNC; + + CStdString renamedName = recinfo.directory; + if (renamedName != "" && renamedName[renamedName.size()-1] != '/') + renamedName += "/"; + renamedName += newname; + renamedName.Replace('/','~');; + + command.Format("RENR %d %s", recinfo.index, renamedName.c_str()); + if (!SendCommand(command, code, lines)) + return PVR_ERROR_SERVER_ERROR; + if (code != 250) + return PVR_ERROR_NOT_DELETED; + + return PVR_ERROR_NO_ERROR; +} + +int CVTPTransceiver::GetNumTimers(void) +{ + vector lines; + int code; + + if (!CheckConnection()) return -1; + + CMD_LOCK; + + if (!SendCommand("STAT timers", code, lines)) + return -1; + + vector::iterator it = lines.begin(); + string& data(*it); + return atol(data.c_str()); +} + +PVR_ERROR CVTPTransceiver::RequestTimerList(PVRHANDLE handle) +{ + vector lines; + int code; + + if (!CheckConnection()) return PVR_ERROR_SERVER_ERROR; + + CMD_LOCK; + + if (!SendCommand("LSTT", code, lines)) + return PVR_ERROR_SERVER_ERROR; + + for (vector::iterator it = lines.begin(); it != lines.end(); it++) + { + string& data(*it); + CStdString str_result = data; + + /** + * VDR Format given by LSTT: + * 250-1 1:6:2008-10-27:0013:0055:50:99:Zeiglers wunderbare Welt des Fußballs: + * 250 2 0:15:2008-10-26:2000:2138:50:99:ZDFtheaterkanal: + * 250 3 1:6:MTWTFS-:2000:2129:50:99:WDR Köln: + */ + + if (g_bCharsetConv) + XBMC->UnknownToUTF8(str_result); + + cTimer timer; + timer.Parse(str_result.c_str()); + + PVR_TIMERINFO tag; + tag.index = timer.Index(); + tag.active = timer.HasFlags(tfActive); + tag.channelNum = timer.Channel(); + tag.firstday = timer.FirstDay(); + tag.starttime = timer.StartTime(); + tag.endtime = timer.StopTime(); + tag.recording = timer.HasFlags(tfRecording) || timer.HasFlags(tfInstant); + tag.title = timer.Title(); + tag.directory = timer.Dir(); + tag.priority = timer.Priority(); + tag.lifetime = timer.Lifetime(); + tag.repeat = timer.WeekDays() == 0 ? false : true; + tag.repeatflags = timer.WeekDays(); + + PVR->TransferTimerEntry(handle, &tag); + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR CVTPTransceiver::GetTimerInfo(unsigned int timernumber, PVR_TIMERINFO &tag) +{ + vector lines; + int code; + + if (!VTPTransceiver.CheckConnection()) return PVR_ERROR_SERVER_ERROR; + + CMD_LOCK; + + CStdString command; + command.Format("LSTT %i", timernumber); + if (!VTPTransceiver.SendCommand(command, code, lines)) + return PVR_ERROR_SERVER_ERROR; + + vector::iterator it = lines.begin(); + string& data(*it); + CStdString str_result = data; + + if (g_bCharsetConv) + XBMC->UnknownToUTF8(str_result); + + cTimer timer; + timer.Parse(str_result.c_str()); + + tag.index = timer.Index(); + tag.active = timer.HasFlags(tfActive); + tag.channelNum = timer.Channel(); + tag.firstday = timer.FirstDay(); + tag.starttime = timer.StartTime(); + tag.endtime = timer.StopTime(); + tag.recording = timer.HasFlags(tfRecording) || timer.HasFlags(tfInstant); + tag.title = timer.File(); + tag.priority = timer.Priority(); + tag.lifetime = timer.Lifetime(); + tag.repeat = timer.WeekDays() == 0 ? false : true; + tag.repeatflags = timer.WeekDays(); + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR CVTPTransceiver::AddTimer(const PVR_TIMERINFO &timerinfo) +{ + vector lines; + int code; + + if (!CheckConnection()) + return PVR_ERROR_SERVER_ERROR; + + cTimer timer(&timerinfo); + + CMD_LOCK; + + CStdString command; + if (timerinfo.index == -1) + { + command.Format("NEWT %s", timer.ToText().c_str()); + if (!SendCommand(command, code, lines)) + return PVR_ERROR_NOT_SAVED; + if (code != 250) + return PVR_ERROR_NOT_SYNC; + } + else + { + // Modified timer + command.Format("LSTT %i", timerinfo.index); + if (!SendCommand(command, code, lines)) + return PVR_ERROR_SERVER_ERROR; + if (code != 250) + return PVR_ERROR_NOT_SYNC; + + command.Format("MODT %d %s", timerinfo.index, timer.ToText().c_str()); + if (!SendCommand(command, code, lines)) + return PVR_ERROR_NOT_SAVED; + if (code != 250) + return PVR_ERROR_NOT_SYNC; + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR CVTPTransceiver::DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force) +{ + vector lines; + int code; + + if (!CheckConnection()) return PVR_ERROR_SERVER_ERROR; + + CMD_LOCK; + + CStdString command; + command.Format("LSTT %d", timerinfo.index); + if (!SendCommand(command, code, lines)) + return PVR_ERROR_SERVER_ERROR; + if (code != 250) + return PVR_ERROR_NOT_SYNC; + + lines.erase(lines.begin(), lines.end()); + + if (force) + command.Format("DELT %d FORCE", timerinfo.index); + else + command.Format("DELT %d", timerinfo.index); + if (!SendCommand(command, code, lines)) + { + vector::iterator it = lines.begin(); + string& data(*it); + CStdString str_result = data; + if (str_result.find("is recording", 0) != std::string::npos) + return PVR_ERROR_RECORDING_RUNNING; + else + return PVR_ERROR_NOT_DELETED; + } + if (code != 250) + return PVR_ERROR_NOT_SYNC; + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR CVTPTransceiver::RenameTimer(const PVR_TIMERINFO &timerinfo, const char *newname) +{ + PVR_TIMERINFO timerinfo1; + PVR_ERROR ret = GetTimerInfo(timerinfo.index, timerinfo1); + if (ret != PVR_ERROR_NO_ERROR) + return ret; + + timerinfo1.title = newname; + return UpdateTimer(timerinfo1); +} + +PVR_ERROR CVTPTransceiver::UpdateTimer(const PVR_TIMERINFO &timerinfo) +{ + vector lines; + int code; + + if (!CheckConnection()) return PVR_ERROR_SERVER_ERROR; + if (timerinfo.index == -1) return PVR_ERROR_NOT_SAVED; + + cTimer timer(&timerinfo); + + CMD_LOCK; + + CStdString command; + command.Format("LSTT %i", timerinfo.index); + if (!VTPTransceiver.SendCommand(command, code, lines)) + return PVR_ERROR_SERVER_ERROR; + if (code != 250) + return PVR_ERROR_NOT_SYNC; + + command.Format("MODT %d %s", timerinfo.index, timer.ToText().c_str()); + if (!VTPTransceiver.SendCommand(command, code, lines)) + return PVR_ERROR_NOT_SAVED; + if (code != 250) + return PVR_ERROR_NOT_SYNC; + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR CVTPTransceiver::SignalQuality(PVR_SIGNALQUALITY &qualityinfo, unsigned int channel) +{ + vector lines; + int code; + + if (!CheckConnection()) return PVR_ERROR_SERVER_ERROR; + + CMD_LOCK; + + CStdString command; + command.Format("LSTQ %i", channel); + if (!SendCommand(command, code, lines) || code != 215) + return PVR_ERROR_SERVER_ERROR; + + for (vector::iterator it = lines.begin(); it < lines.end(); it++) + { + string& data(*it); + const char *s = data.c_str(); + if (!strncasecmp(s, "Device", 6)) + strncpy(qualityinfo.frontend_name, s + 9, sizeof(qualityinfo.frontend_name)); + else if (!strncasecmp(s, "Status", 6)) + strncpy(qualityinfo.frontend_status, s + 9, sizeof(qualityinfo.frontend_status)); + else if (!strncasecmp(s, "Signal", 6)) + qualityinfo.signal = (uint16_t)strtol(s + 9, NULL, 16); + else if (!strncasecmp(s, "SNR", 3)) + qualityinfo.snr = (uint16_t)strtol(s + 9, NULL, 16); + else if (!strncasecmp(s, "BER", 3)) + qualityinfo.ber = (uint32_t)strtol(s + 9, NULL, 16); + else if (!strncasecmp(s, "UNC", 3)) + qualityinfo.unc = (uint32_t)strtol(s + 9, NULL, 16); + else if (!strncasecmp(s, "Video", 5)) + qualityinfo.video_bitrate = strtod(s + 9, NULL); + else if (!strncasecmp(s, "Audio", 5)) + qualityinfo.audio_bitrate = strtod(s + 9, NULL); + else if (!strncasecmp(s, "Dolby", 5)) + qualityinfo.dolby_bitrate = strtod(s + 9, NULL); + } + + return PVR_ERROR_NO_ERROR; +} + +int CVTPTransceiver::TransferRecordingToSocket(uint64_t position, int size) +{ + if (!CheckConnection()) return 0; + + CMD_LOCK; + + vector lines; + int code; + + CStdString command; + command.Format("READ %llu %u", (unsigned long long)position, size); + if (!SendCommand(command, code, lines) || code != 220) + return 0; + + vector::iterator it = lines.begin(); + string& data(*it); + + return atol(data.c_str()); +} + +bool CVTPTransceiver::Quit(void) +{ + vector lines; + int code; + bool ret; + + if (!CheckConnection()) return false; + + if (!(ret = SendCommand("QUIT", code, lines)) || code != 221) + { + if (errno == 0) + XBMC->Log(LOG_ERROR, "ERROR: Streamdev: Couldn't quit command connection to %s:%d", g_szHostname.c_str(), g_iPort); + } + Close(); + return ret; + +} + +bool CVTPTransceiver::SuspendServer(void) +{ + vector lines; + int code; + + if (!CheckConnection()) return 0; + + CMD_LOCK; + + if (!SendCommand("SUSP", code, lines) || code != 220) + { + XBMC->Log(LOG_ERROR, "Couldn't suspend server"); + return false; + } + return true; +} + + diff --git a/xbmc/pvrclients/vdr-streamdev/vtptransceiver.h b/xbmc/pvrclients/vdr-streamdev/vtptransceiver.h new file mode 100644 index 0000000000..d586f8c277 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/vtptransceiver.h @@ -0,0 +1,104 @@ +#pragma once +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include "StdString.h" +#include "thread.h" +#include "tools.h" + +#define CMD_LOCK cMutexLock CmdLock((cMutex*)&m_Mutex) + +enum eSocketId { + siLive, + siReplay, + siLiveFilter, + siDataRespond, + si_Count +}; + +class CVTPTransceiver +{ +public: + bool ReadResponse(int &code, std::string &line); + bool ReadResponse(int &code, std::vector &lines); + + bool SendCommand(const std::string &command); + bool SendCommand(const std::string &command, int &code, std::string line); + bool SendCommand(const std::string &command, int &code, std::vector &lines); + +private: + SOCKET m_DataSockets[si_Count]; + SOCKET m_VTPSocket; + cMutex m_Mutex; + int m_recIndex; + + struct sockaddr_in m_LocalAddr; + struct sockaddr_in m_RemoteAddr; + + bool OpenStreamSocket(SOCKET& socket, struct sockaddr_in& address); + bool AcceptStreamSocket(SOCKET& socket); + bool Connect(const std::string &host, int port); + void Close(); + bool IsConnected(SOCKET socket, fd_set *rd, fd_set *wr, fd_set *ex); + void ScanVideoDir(PVRHANDLE handle, const char *DirName, bool Deleted = false, int LinkLevel = 0); + +public: + CVTPTransceiver(); + ~CVTPTransceiver(); + + void Reset(void); + + bool IsOpen(void) const { return m_VTPSocket != INVALID_SOCKET; } + bool CheckConnection(); + bool ProvidesChannel(unsigned int Channel, int Priority); + bool CreateDataConnection(eSocketId Id); + bool CloseDataConnection(eSocketId Id); + SOCKET DataSocket(eSocketId Id) const { return m_DataSockets[Id]; } + bool SetChannelDevice(unsigned int Channel); + bool SetRecordingIndex(unsigned int Recording); + bool GetPlayingRecordingSize(uint64_t *size, uint32_t *frames); + uint64_t SeekRecordingPosition(uint64_t position); + CStdString GetBackendName(); + CStdString GetBackendVersion(); + PVR_ERROR GetDriveSpace(long long *total, long long *used); + PVR_ERROR GetBackendTime(time_t *localTime, int *gmtOffset); + PVR_ERROR RequestEPGForChannel(const PVR_CHANNEL &channel, PVRHANDLE handle, time_t start = NULL, time_t end = NULL); + int GetNumChannels(void); + PVR_ERROR RequestChannelList(PVRHANDLE handle, bool radio = false); + int GetNumRecordings(void); + PVR_ERROR RequestRecordingsList(PVRHANDLE handle); + PVR_ERROR DeleteRecording(const PVR_RECORDINGINFO &recinfo); + PVR_ERROR RenameRecording(const PVR_RECORDINGINFO &recinfo, const char *newname); + int GetNumTimers(void); + PVR_ERROR RequestTimerList(PVRHANDLE handle); + PVR_ERROR GetTimerInfo(unsigned int timernumber, PVR_TIMERINFO &tag); + PVR_ERROR AddTimer(const PVR_TIMERINFO &timerinfo); + PVR_ERROR DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force = false); + PVR_ERROR RenameTimer(const PVR_TIMERINFO &timerinfo, const char *newname); + PVR_ERROR UpdateTimer(const PVR_TIMERINFO &timerinfo); + PVR_ERROR SignalQuality(PVR_SIGNALQUALITY &qualityinfo, unsigned int channel); + int TransferRecordingToSocket(uint64_t position, int size); + bool SuspendServer(void); + bool Quit(void); +}; + +extern class CVTPTransceiver VTPTransceiver; diff --git a/xbmc/pvrclients/vdr-streamdev/windows/dirent.cpp b/xbmc/pvrclients/vdr-streamdev/windows/dirent.cpp new file mode 100644 index 0000000000..312b29fd55 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/windows/dirent.cpp @@ -0,0 +1,161 @@ +//#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pvrclient-vdr_os.h" +#include + +/********************************************************************** + * Implement dirent-style opendir/readdir/rewinddir/closedir on Win32 + * + * Functions defined are opendir(), readdir(), rewinddir() and + * closedir() with the same prototypes as the normal dirent.h + * implementation. + * + * Does not implement telldir(), seekdir(), or scandir(). The dirent + * struct is compatible with Unix, except that d_ino is always 1 and + * d_off is made up as we go along. + * + * The DIR typedef is not compatible with Unix. + **********************************************************************/ + +DIR *opendir(const char *dir) +{ + DIR *dirp; + char *filespec; + long handle; + int index; + + filespec = (char *)malloc(strlen(dir) + 2 + 1); + strcpy(filespec, dir); + index = (int)strlen(filespec) - 1; + if (index >= 0 && (filespec[index] == '/' || + (filespec[index] == '\\' && !IsDBCSLeadByte(filespec[index-1])))) + filespec[index] = '\0'; + strcat(filespec, "/*"); + + dirp = (DIR *) malloc(sizeof(DIR)); + dirp->offset = 0; + dirp->finished = 0; + + if ((handle = _findfirst(filespec, &(dirp->fileinfo))) < 0) + { + if (errno == ENOENT || errno == EINVAL) + dirp->finished = 1; + else + { + free(dirp); + free(filespec); + return NULL; + } + } + dirp->dirname = strdup(dir); + dirp->handle = handle; + free(filespec); + + return dirp; +} + +int closedir(DIR *dp) +{ + int iret = -1; + if (!dp) + return iret; + iret = _findclose(dp->handle); + if (iret == 0 && dp->dirname) + free(dp->dirname); + if (iret == 0 && dp) + free(dp); + + return iret; +} + +struct dirent *readdir(DIR *dp) +{ + if (!dp || dp->finished) + return NULL; + + if (dp->offset != 0) + { + if (_findnext(dp->handle, &(dp->fileinfo)) < 0) + { + dp->finished = 1; + return NULL; + } + } + dp->offset++; + + strcpy(dp->dent.d_name, dp->fileinfo.name);/*, _MAX_FNAME+1);*/ + dp->dent.d_ino = 1; + dp->dent.d_reclen = (unsigned short)strlen(dp->dent.d_name); + dp->dent.d_off = dp->offset; + + return &(dp->dent); +} + +int readdir_r(DIR *dp, struct dirent *entry, struct dirent **result) +{ + if (!dp || dp->finished) + { + *result = NULL; + return -1; + } + + if (dp->offset != 0) + { + if (_findnext(dp->handle, &(dp->fileinfo)) < 0) + { + dp->finished = 1; + *result = NULL; + return -1; + } + } + dp->offset++; + + strcpy(dp->dent.d_name, dp->fileinfo.name);/*, _MAX_FNAME+1);*/ + dp->dent.d_ino = 1; + dp->dent.d_reclen = (unsigned short)strlen(dp->dent.d_name); + dp->dent.d_off = dp->offset; + + memcpy(entry, &dp->dent, sizeof(*entry)); + + *result = &dp->dent; + + return 0; +} + +int rewinddir(DIR *dp) +{ + char *filespec; + long handle; + int index; + + _findclose(dp->handle); + + dp->offset = 0; + dp->finished = 0; + + filespec = (char *)malloc(strlen(dp->dirname) + 2 + 1); + strcpy(filespec, dp->dirname); + index = (int)(strlen(filespec) - 1); + if (index >= 0 && (filespec[index] == '/' || filespec[index] == '\\')) + filespec[index] = '\0'; + strcat(filespec, "/*"); + + if ((handle = _findfirst(filespec, &(dp->fileinfo))) < 0) + { + if (errno == ENOENT || errno == EINVAL) + dp->finished = 1; + } + dp->handle = handle; + free(filespec); + + return 0; +} diff --git a/xbmc/pvrclients/vdr-streamdev/windows/dirent.h b/xbmc/pvrclients/vdr-streamdev/windows/dirent.h new file mode 100644 index 0000000000..85db1344a0 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/windows/dirent.h @@ -0,0 +1,103 @@ +/***************************************************************************** + * dirent.h - dirent API for Microsoft Visual Studio + * + * Copyright (C) 2006 Toni Ronkko + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * ``Software''), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL TONI RONKKO BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Dec 15, 2009, John Cunningham + * Added rewinddir member function + * + * Jan 18, 2008, Toni Ronkko + * Using FindFirstFileA and WIN32_FIND_DATAA to avoid converting string + * between multi-byte and unicode representations. This makes the + * code simpler and also allows the code to be compiled under MingW. Thanks + * to Azriel Fasten for the suggestion. + * + * Mar 4, 2007, Toni Ronkko + * Bug fix: due to the strncpy_s() function this file only compiled in + * Visual Studio 2005. Using the new string functions only when the + * compiler version allows. + * + * Nov 2, 2006, Toni Ronkko + * Major update: removed support for Watcom C, MS-DOS and Turbo C to + * simplify the file, updated the code to compile cleanly on Visual + * Studio 2005 with both unicode and multi-byte character strings, + * removed rewinddir() as it had a bug. + * + * Aug 20, 2006, Toni Ronkko + * Removed all remarks about MSVC 1.0, which is antiqued now. Simplified + * comments by removing SGML tags. + * + * May 14 2002, Toni Ronkko + * Embedded the function definitions directly to the header so that no + * source modules need to be included in the Visual Studio project. Removed + * all the dependencies to other projects so that this very header can be + * used independently. + * + * May 28 1998, Toni Ronkko + * First version. + *****************************************************************************/ +#ifndef DIRENT_H +#define DIRENT_H + +#include +#include +#include + +/* struct dirent - same as Unix dirent.h */ +struct dirent +{ + long d_ino; /* inode number (always 1 in WIN32) */ + off_t d_off; /* offset to this dirent */ + unsigned short d_reclen; /* length of d_name */ + char d_name[_MAX_FNAME + 1]; /* filename (null terminated) */ + /*unsigned char d_type;*/ /*type of file*/ +}; + + +/* def struct DIR - different from Unix DIR */ +typedef struct +{ + long handle; /* _findfirst/_findnext handle */ + short offset; /* offset into directory */ + short finished; /* 1 if there are not more files */ + struct _finddata_t fileinfo; /* from _findfirst/_findnext */ + char *dirname; /* the dir we are reading */ + struct dirent dent; /* the dirent to return */ +} DIR; + +/* Function prototypes */ +DIR *opendir(const char *); +struct dirent *readdir(DIR *); +int readdir_r(DIR *, struct dirent *, struct dirent **); +int closedir(DIR *); +int rewinddir(DIR *); + + +/* Use the new safe string functions introduced in Visual Studio 2005 */ +#if defined(_MSC_VER) && _MSC_VER >= 1400 +# define STRNCPY(dest,src,size) strncpy_s((dest),(size),(src),_TRUNCATE) +#else +# define STRNCPY(dest,src,size) strncpy((dest),(src),(size)) +#endif + + +#endif /*DIRENT_H*/ diff --git a/xbmc/pvrclients/vdr-streamdev/windows/getline.cpp b/xbmc/pvrclients/vdr-streamdev/windows/getline.cpp new file mode 100644 index 0000000000..c7daea5284 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/windows/getline.cpp @@ -0,0 +1,149 @@ +/* getline.c -- Replacement for GNU C library function getline + +Copyright (C) 1993 Free Software Foundation, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. */ + +/* Written by Jan Brittenson, bson@gnu.ai.mit.edu. */ + +#include +#include +#include +#include +#include +#include "getline.h" + +/* Always add at least this many bytes when extending the buffer. */ +#define MIN_CHUNK 64 + +/* Read up to (and including) a TERMINATOR from STREAM into *LINEPTR + + OFFSET (and null-terminate it). If LIMIT is non-negative, then + read no more than LIMIT chars. + + *LINEPTR is a pointer returned from malloc (or NULL), pointing to + *N characters of space. It is realloc'd as necessary. + + Return the number of characters read (not including the null + terminator), or -1 on error or EOF. On a -1 return, the caller + should check feof(), if not then errno has been set to indicate the + error. */ + +int getstr(char **lineptr, size_t *n, FILE *stream, int terminator, int offset, int limit) +{ + int nchars_avail; /* Allocated but unused chars in *LINEPTR. */ + char *read_pos; /* Where we're reading into *LINEPTR. */ + int ret; + + if (!lineptr || !n || !stream) + { + errno = EINVAL; + return -1; + } + + if (!*lineptr) + { + *n = MIN_CHUNK; + *lineptr = (char *)malloc (*n); + if (!*lineptr) + { + errno = ENOMEM; + return -1; + } + *lineptr[0] = '\0'; + } + + nchars_avail = *n - offset; + read_pos = *lineptr + offset; + + for (;;) + { + int save_errno; + register int c; + + if (limit == 0) + break; + else + { + c = getc (stream); + + /* If limit is negative, then we shouldn't pay attention to + it, so decrement only if positive. */ + if (limit > 0) + limit--; + } + + save_errno = errno; + + /* We always want at least one char left in the buffer, since we + always (unless we get an error while reading the first char) + NUL-terminate the line buffer. */ + + assert((*lineptr + *n) == (read_pos + nchars_avail)); + if (nchars_avail < 2) + { + if (*n > MIN_CHUNK) + *n *= 2; + else + *n += MIN_CHUNK; + + nchars_avail = *n + *lineptr - read_pos; + *lineptr = (char *)realloc (*lineptr, *n); + if (!*lineptr) + { + errno = ENOMEM; + return -1; + } + read_pos = *n - nchars_avail + *lineptr; + assert((*lineptr + *n) == (read_pos + nchars_avail)); + } + + if (ferror (stream)) + { + /* Might like to return partial line, but there is no + place for us to store errno. And we don't want to just + lose errno. */ + errno = save_errno; + return -1; + } + + if (c == EOF) + { + /* Return partial line, if any. */ + if (read_pos == *lineptr) + return -1; + else + break; + } + + *read_pos++ = c; + nchars_avail--; + + if (c == terminator) + /* Return the line. */ + break; + } + + /* Done - NUL terminate and return the number of chars read. */ + *read_pos = '\0'; + + ret = read_pos - (*lineptr + offset); + return ret; +} + +int getline(char **lineptr, size_t *n, FILE *stream) +{ + return getstr (lineptr, n, stream, '\n', 0, GETLINE_NO_LIMIT); +} + +int getline_safe(char **lineptr, size_t *n, FILE *stream, int limit) +{ + return getstr (lineptr, n, stream, '\n', 0, limit); +} diff --git a/xbmc/pvrclients/vdr-streamdev/windows/getline.h b/xbmc/pvrclients/vdr-streamdev/windows/getline.h new file mode 100644 index 0000000000..ce1b49c68c --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/windows/getline.h @@ -0,0 +1,12 @@ +#ifndef _getline_h_ +#define _getline_h_ + +#include + +#define GETLINE_NO_LIMIT -1 + +int getline(char **_lineptr, size_t *_n, FILE *_stream); +int getline_safe(char **_lineptr, size_t *_n, FILE *_stream, int limit); +int getstr(char **_lineptr, size_t *_n, FILE *_stream, int _terminator, int _offset, int limit); + +#endif /* _getline_h_ */ diff --git a/xbmc/pvrclients/vdr-streamdev/windows/pvrclient-vdr_os_windows.cpp b/xbmc/pvrclients/vdr-streamdev/windows/pvrclient-vdr_os_windows.cpp new file mode 100644 index 0000000000..aac13a19a6 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/windows/pvrclient-vdr_os_windows.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "pvrclient-vdr_os_windows.h" +#include + +THREADLOCAL int ws32_result; +THREADLOCAL int _so_err; +THREADLOCAL int _so_err_siz = sizeof(int); + +int gettimeofday(struct timeval *pcur_time, struct timezone *tz) +{ + struct _timeb current; + + _ftime(¤t); + + pcur_time->tv_sec = current.time; + pcur_time->tv_usec = current.millitm * 1000L; + if (tz) + { + tz->tz_minuteswest = current.timezone; /* minutes west of Greenwich */ + tz->tz_dsttime = current.dstflag; /* type of dst correction */ + } + return 0; +} diff --git a/xbmc/pvrclients/vdr-streamdev/windows/pvrclient-vdr_os_windows.h b/xbmc/pvrclients/vdr-streamdev/windows/pvrclient-vdr_os_windows.h new file mode 100644 index 0000000000..98fad8b5d5 --- /dev/null +++ b/xbmc/pvrclients/vdr-streamdev/windows/pvrclient-vdr_os_windows.h @@ -0,0 +1,365 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef PVRCLIENT_VDR_OS_WIN_H +#define PVRCLIENT_VDR_OS_WIN_H + +#if defined _FILE_OFFSET_BITS && _FILE_OFFSET_BITS == 64 +# define __USE_FILE_OFFSET64 1 +#endif + +#include "getline.h" + +typedef int ssize_t; +typedef int mode_t; +typedef int bool_t; +typedef signed __int8 int8_t; +typedef signed __int16 int16_t; +typedef signed __int32 int32_t; +typedef signed __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; + +#if defined __USE_FILE_OFFSET64 +typedef int64_t off_t; +typedef uint64_t ino_t; +#else +typedef long off_t; +#endif + +#define NAME_MAX 255 /* # chars in a file name */ +#define MAXPATHLEN 255 +#define INT64_MAX _I64_MAX +#define INT64_MIN _I64_MIN + +#ifndef S_ISLNK +# define S_ISLNK(x) 0 +#endif + +#ifndef S_ISREG +#define S_ISREG(x) (((x) & S_IFMT) == S_IFREG) +#endif + +#ifndef S_ISDIR +#define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR) +#endif + +/* Some tricks for MS Compilers */ +#define THREADLOCAL __declspec(thread) + +#ifndef DEFFILEMODE +#define DEFFILEMODE 0 +#endif + +#define alloca _alloca +#define chdir _chdir +#define dup _dup +#define dup2 _dup2 +#define fdopen _fdopen +#define fileno _fileno +#define getcwd _getcwd +#define getpid _getpid +#define ioctl ioctlsocket +#define mkdir(p) _mkdir(p) +#define mktemp _mktemp +#define open _open +#define pclose _pclose +#define popen _popen +#define putenv _putenv +#define setmode _setmode +#define sleep(t) Sleep((t)*1000) +#define usleep(t) Sleep((t)/1000) +#define snprintf _snprintf +#define strcasecmp _stricmp +#define strdup _strdup +#define strlwr _strlwr +#define strncasecmp _strnicmp +#define tempnam _tempnam +#define umask _umask +#define unlink _unlink +#define close _close + +#define O_RDONLY _O_RDONLY +#define O_WRONLY _O_WRONLY +#define O_RDWR _O_RDWR +#define O_APPEND _O_APPEND + +#define O_CREAT _O_CREAT +#define O_TRUNC _O_TRUNC +#define O_EXCL _O_EXCL + +#define O_TEXT _O_TEXT +#define O_BINARY _O_BINARY +#define O_RAW _O_BINARY +#define O_TEMPORARY _O_TEMPORARY +#define O_NOINHERIT _O_NOINHERIT +#define O_SEQUENTIAL _O_SEQUENTIAL +#define O_RANDOM _O_RANDOM +#define O_NDELAY 0 + +#define S_IRWXO 007 +#define S_ISDIR(m) (((m) & _S_IFDIR) == _S_IFDIR) +#define S_ISREG(m) (((m) & _S_IFREG) == _S_IFREG) + +#ifndef SIGHUP +#define SIGHUP 1 /* hangup */ +#endif +#ifndef SIGBUS +#define SIGBUS 7 /* bus error */ +#endif +#ifndef SIGKILL +#define SIGKILL 9 /* kill (cannot be caught or ignored) */ +#endif +#ifndef SIGSEGV +#define SIGSEGV 11 /* segment violation */ +#endif +#ifndef SIGPIPE +#define SIGPIPE 13 /* write on a pipe with no one to read it */ +#endif +#ifndef SIGCHLD +#define SIGCHLD 20 /* to parent on child stop or exit */ +#endif +#ifndef SIGUSR1 +#define SIGUSR1 30 /* user defined signal 1 */ +#endif +#ifndef SIGUSR2 +#define SIGUSR2 31 /* user defined signal 2 */ +#endif + +typedef unsigned short in_port_t; +typedef unsigned short int ushort; +typedef unsigned int in_addr_t; +typedef int socklen_t; +typedef int uid_t; +typedef int gid_t; + +#if defined __USE_FILE_OFFSET64 +#define stat _stati64 +#define lseek _lseeki64 +#define fstat _fstati64 +#define tell _telli64 +#else +#define stat _stat +#define lseek _lseek +#define fstat _fstat +#define tell _tell +#endif + +#define atoll _atoi64 +#ifndef va_copy +#define va_copy(x, y) x = y +#endif + +#include +#include +#if defined(_MSC_VER) /* Microsoft C Compiler ONLY */ +/* Hack to suppress compiler warnings on FD_SET() & FD_CLR() */ +#pragma warning (push) +#pragma warning (disable:4142) +#endif +/* prevent inclusion of wingdi.h */ +#define NOGDI +#include +#include +#if defined(_MSC_VER) /* Microsoft C Compiler ONLY */ +#pragma warning (pop) +#endif +#include +#include +#include +#include "pthread_win32/pthread.h" + +typedef char * caddr_t; + +#undef FD_CLOSE +#undef FD_OPEN +#undef FD_READ +#undef FD_WRITE +#define EISCONN WSAEISCONN +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define EALREADY WSAEALREADY +#define ETIMEDOUT WSAETIMEDOUT +#define ECONNABORTED WSAECONNABORTED +#define ECONNREFUSED WSAECONNREFUSED +#define ECONNRESET WSAECONNRESET +#define ERESTART WSATRY_AGAIN +#define ENOTCONN WSAENOTCONN +#define ENOBUFS WSAENOBUFS +#define EOVERFLOW 2006 + +#undef h_errno +#define h_errno errno /* we'll set it ourselves */ + +struct timezone +{ + int tz_minuteswest; /* minutes west of Greenwich */ + int tz_dsttime; /* type of dst correction */ +}; + +extern int gettimeofday(struct timeval *, struct timezone *); + +/* Unix socket emulation macros */ +#define __close closesocket + +#undef FD_CLR +#define FD_CLR(fd, set) do { \ + u_int __i; \ + SOCKET __sock = _get_osfhandle(fd); \ + for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count ; __i++) { \ + if (((fd_set FAR *)(set))->fd_array[__i] == __sock) { \ + while (__i < ((fd_set FAR *)(set))->fd_count-1) { \ + ((fd_set FAR *)(set))->fd_array[__i] = \ + ((fd_set FAR *)(set))->fd_array[__i+1]; \ + __i++; \ + } \ + ((fd_set FAR *)(set))->fd_count--; \ + break; \ + } \ + } \ +} while(0) + +#undef FD_SET +#define FD_SET(fd, set) do { \ + u_int __i; \ + SOCKET __sock = _get_osfhandle(fd); \ + for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \ + if (((fd_set FAR *)(set))->fd_array[__i] == (__sock)) { \ + break; \ + } \ + } \ + if (__i == ((fd_set FAR *)(set))->fd_count) { \ + if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \ + ((fd_set FAR *)(set))->fd_array[__i] = (__sock); \ + ((fd_set FAR *)(set))->fd_count++; \ + } \ + } \ +} while(0) + +#undef FD_ISSET +#define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(_get_osfhandle(fd)), (fd_set FAR *)(set)) + +extern THREADLOCAL int ws32_result; +#define __poll(f,n,t) \ + (SOCKET_ERROR == WSAPoll(f,n,t) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __socket(f,t,p) \ + (INVALID_SOCKET == ((SOCKET)(ws32_result = (int)socket(f,t,p))) ? \ + ((WSAEMFILE == (errno = WSAGetLastError()) ? errno = EMFILE : -1), -1) : \ + (SOCKET)_open_osfhandle(ws32_result,0)) +#define __accept(s,a,l) \ + (INVALID_SOCKET == ((SOCKET)(ws32_result = (int)accept(_get_osfhandle(s),a,l))) ? \ + ((WSAEMFILE == (errno = WSAGetLastError()) ? errno = EMFILE : -1), -1) : \ + (SOCKET)_open_osfhandle(ws32_result,0)) +#define __bind(s,n,l) \ + (SOCKET_ERROR == bind(_get_osfhandle(s),n,l) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __connect(s,n,l) \ + (SOCKET_ERROR == connect(_get_osfhandle(s),n,l) ? \ + (WSAEMFILE == (errno = WSAGetLastError()) ? errno = EMFILE : -1, -1) : 0) +#define __listen(s,b) \ + (SOCKET_ERROR == listen(_get_osfhandle(s),b) ? \ + (WSAEMFILE == (errno = WSAGetLastError()) ? errno = EMFILE : -1, -1) : 0) +#define __shutdown(s,h) \ + (SOCKET_ERROR == shutdown(_get_osfhandle(s),h) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __select(n,r,w,e,t) \ + (SOCKET_ERROR == (ws32_result = select(n,r,w,e,t)) ? \ + (errno = WSAGetLastError()), -1 : ws32_result) +#define __recv(s,b,l,f) \ + (SOCKET_ERROR == (ws32_result = recv(_get_osfhandle(s),b,l,f)) ? \ + (errno = WSAGetLastError()), -1 : ws32_result) +#define __recvfrom(s,b,l,f,fr,frl) \ + (SOCKET_ERROR == (ws32_result = recvfrom(_get_osfhandle(s),b,l,f,fr,frl)) ? \ + (errno = WSAGetLastError()), -1 : ws32_result) +#define __send(s,b,l,f) \ + (SOCKET_ERROR == (ws32_result = send(_get_osfhandle(s),b,l,f)) ? \ + (errno = WSAGetLastError()), -1 : ws32_result) +#define __sendto(s,b,l,f,t,tl) \ + (SOCKET_ERROR == (ws32_result = sendto(_get_osfhandle(s),b,l,f,t,tl)) ? \ + (errno = WSAGetLastError()), -1 : ws32_result) +#define __getsockname(s,n,l) \ + (SOCKET_ERROR == getsockname(_get_osfhandle(s),n,l) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __getpeername(s,n,l) \ + (SOCKET_ERROR == getpeername(_get_osfhandle(s),n,l) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __getsockopt(s,l,o,v,n) \ + (Sleep(1), SOCKET_ERROR == getsockopt(_get_osfhandle(s),l,o,(char*)v,n) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __setsockopt(s,l,o,v,n) \ + (SOCKET_ERROR == setsockopt(_get_osfhandle(s),l,o,v,n) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __ioctlsocket(s,c,a) \ + (SOCKET_ERROR == ioctlsocket(_get_osfhandle(s),c,a) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __gethostname(n,l) \ + (SOCKET_ERROR == gethostname(n,l) ? \ + (errno = WSAGetLastError()), -1 : 0) +#define __gethostbyname(n) \ + (NULL == ((HOSTENT FAR*)(ws32_result = (int)gethostbyname(n))) ? \ + (errno = WSAGetLastError()), NULL : (HOSTENT FAR*)ws32_result) +#define __getservbyname(n,p) \ + (NULL == ((SERVENT FAR*)(ws32_result = (int)getservbyname(n,p))) ? \ + (errno = WSAGetLastError()), NULL : (SERVENT FAR*)ws32_result) +#define __gethostbyaddr(a,l,t) \ + (NULL == ((HOSTENT FAR*)(ws32_result = (int)gethostbyaddr(a,l,t))) ? \ + (errno = WSAGetLastError()), NULL : (HOSTENT FAR*)ws32_result) +extern THREADLOCAL int _so_err; +extern THREADLOCAL int _so_err_siz; +#define __read(fd,buf,siz) \ + (_so_err_siz = sizeof(_so_err), \ + __getsockopt((fd),SOL_SOCKET,SO_ERROR,&_so_err,&_so_err_siz) \ + == 0 ? __recv((fd),(char *)(buf),(siz),0) : _read((fd),(char *)(buf),(siz))) +#define __write(fd,buf,siz) \ + (_so_err_siz = sizeof(_so_err), \ + __getsockopt((fd),SOL_SOCKET,SO_ERROR,&_so_err,&_so_err_siz) \ + == 0 ? __send((fd),(const char *)(buf),(siz),0) : _write((fd),(const char *)(buf),(siz))) + + +#if !defined(__MINGW32__) +#define strtok_r( _s, _sep, _lasts ) \ + ( *(_lasts) = strtok( (_s), (_sep) ) ) +#endif /* !__MINGW32__ */ + +#define asctime_r( _tm, _buf ) \ + ( strcpy( (_buf), asctime( (_tm) ) ), \ + (_buf) ) + +#define ctime_r( _clock, _buf ) \ + ( strcpy( (_buf), ctime( (_clock) ) ), \ + (_buf) ) + +#define gmtime_r( _clock, _result ) \ + ( *(_result) = *gmtime( (_clock) ), \ + (_result) ) + +#define localtime_r( _clock, _result ) \ + ( *(_result) = *localtime( (_clock) ), \ + (_result) ) + +#define rand_r( _seed ) \ + ( _seed == _seed? rand() : rand() ) + +#endif diff --git a/xbmc/pvrclients/vdr-vnsi/COPYING b/xbmc/pvrclients/vdr-vnsi/COPYING new file mode 100644 index 0000000000..f90922eea3 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/xbmc/pvrclients/vdr-vnsi/Makefile b/xbmc/pvrclients/vdr-vnsi/Makefile new file mode 100644 index 0000000000..80260cfdeb --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/Makefile @@ -0,0 +1,70 @@ +# +# Makefile for the XBMC Video Disk Recorder PVR AddOn +# +# See the README for copyright information and +# how to reach the author. +# + +.DELETE_ON_ERROR: + +DESTDIR ?= +PREFIX ?= /usr/local +ADDONDIR = $(PREFIX)/share/xbmc/addons +LIBS = -ldl +INCLUDES = -I. -I../../../xbmc/cores/dvdplayer/DVDDemuxers -I../../../xbmc/addons/include -I../../../xbmc/cores/dvdplayer/Codecs/ffmpeg +ifeq ($(findstring Darwin,$(shell uname -a)), Darwin) +INCLUDES += -I/opt/local/include -I../../ +DEFINES += -isysroot /Developer/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4 -fno-common +endif +DEFINES += -D_LINUX -fPIC -DUSE_DEMUX +LIBDIR = ../../../addons/pvr.vdr.vnsi +LIB = ../../../addons/pvr.vdr.vnsi/XBMC_VDR_vnsi.pvr + +CC ?= gcc +CFLAGS ?= -g -O2 -Wall + +CXX ?= g++ +ifeq ($(findstring Darwin,$(shell uname -a)), Darwin) +CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses -Wl,-no_compact_linkedit -dynamiclib -single_module -undefined dynamic_lookup +else +CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses +endif + +-include Make.config + +OBJS = client.o VNSIChannelScan.o VNSIData.o VNSIDemux.o VNSIRecording.o VNSISession.o recordings.o requestpacket.o responsepacket.o thread.o tools.o + +all: $(LIB) + +# Implicit rules: + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< + +# Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.cpp) > $@ + +-include $(DEPFILE) + +# The main library: + +$(LIB): $(OBJS) $(SILIB) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -g $(OBJS) $(LIBS) $(LIBDIRS) $(SILIB) -o $(LIB) + +# Install the files: + +install: install-lib + +# PVR library: + +install-lib: $(LIB) + @mkdir -p $(DESTDIR)$(ADDONDIR) + @cp --remove-destination -r $(LIBDIR) $(DESTDIR)$(ADDONDIR) + +clean: + -rm -f $(OBJS) $(DEPFILE) $(LIB) *~ +CLEAN: clean diff --git a/xbmc/pvrclients/vdr-vnsi/README b/xbmc/pvrclients/vdr-vnsi/README new file mode 100644 index 0000000000..c4a8676db8 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/README @@ -0,0 +1,71 @@ +XBMC Video Disk Recorder ('VDR') PVR Add-on +------------------------------------------ + +THIS IS A PRELIMINARY README AND IS SUBJECT TO CHANGE!!! + +Written by: Alwin Esch (Team XBMC) + +Project's homepage: + +Latest version available at: + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +See the file COPYING for more information. + +------------------------------------------ + +This is a PVR Add-on for XBMC to add VDR (http://www.cadsoft.de/vdr) as a TV/PVR Backend to +XBMC based upon the new VNSI Protocol. + +It want to add support for Live TV watching, replaying of Recordings, programming Timers and. +EPG TV Guide to use on same computer or over the Network. + +The connection of this AddOn depend upon a installed VNSI-Server-Plugin on the VDR which +is included inside the "vdr-plugin-vnsiserver" directory of this Addon. +VDR itself need no patches or modification to use all current features. + +VDR Versions older as 1.6.0 are not supported by this plugin. + +------------------------------------------ +PLUGIN INSTALLATION INSTRUCTIONS: + +Copy the "vdr-plugin-vnsiserver" directory to your "VDR/plugins/src" directory and +rename it to "vnsiserver". + +Run a "make plugins" and depented on your environment a "make install". + +And add "-P'vnsiserver'" to your startup options. + + +------------------------------------------ +CHANNEL SCANNING + +For channel scan's a modified version of the wirbelscan plugin for VDR (Version dev-0.0.5-pre11e) is +required. You can download the orginal source from here "http://wirbel.htpc-forum.de/wirbelscan/index2.html". + +The VNSI communicate with wirbelscan over VDR's plugin service interface, to add this feature +you must patch wirbelscan with the file in "patches/vdr-wirbelscan-0.0.5-pre11e-AddServiceInterface.diff". + +Please load VDR with VNSI plugin together with the modified wirbelscan plugin and you can access the +"Search Channels" option inside "Settings->TV->General" + +Scanning can take up to 50 minutes dependet on signal type and signal quality. + * DVB-T ~ 5 min + * DVB-C ~ 30 min (Symbolrate=AUTO, QAM=AUTO) + * DVB-S/S2 ~ 50 min (depend on Satellite, Beam, Hardware) + * Analog ~ 5 min + +Note: Please notice the warning on the wirbelscan plugin homepage: + "Development Version - kein Support. Benutzung auf eigene Verantwortung." + "Development Version - no support. Use at your own risk." + This means, the time how long the scan run and problems on VDR side are dependet on wirbelscan + and not part of VNSI. + + +------------------------------------------ + +Links: +VDR: http://www.cadsoft.de/vdr diff --git a/xbmc/pvrclients/vdr-vnsi/StdString.h b/xbmc/pvrclients/vdr-vnsi/StdString.h new file mode 100644 index 0000000000..8ee30603da --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/StdString.h @@ -0,0 +1,4333 @@ +#pragma once +#include +#include +#if !defined(_LINUX) +#include +#include "pvrclient-vdr_os.h" +#endif + +// ============================================================================= +// FILE: StdString.h +// AUTHOR: Joe O'Leary (with outside help noted in comments) +// +// If you find any bugs in this code, please let me know: +// +// jmoleary@earthlink.net +// http://www.joeo.net/stdstring.htm (a bit outdated) +// +// The latest version of this code should always be available at the +// following link: +// +// http://www.joeo.net/code/StdString.zip (Dec 6, 2003) +// +// +// REMARKS: +// This header file declares the CStdStr template. This template derives +// the Standard C++ Library basic_string<> template and add to it the +// the following conveniences: +// - The full MFC CString set of functions (including implicit cast) +// - writing to/reading from COM IStream interfaces +// - Functional objects for use in STL algorithms +// +// From this template, we intstantiate two classes: CStdStringA and +// CStdStringW. The name "CStdString" is just a #define of one of these, +// based upone the UNICODE macro setting +// +// This header also declares our own version of the MFC/ATL UNICODE-MBCS +// conversion macros. Our version looks exactly like the Microsoft's to +// facilitate portability. +// +// NOTE: +// If you you use this in an MFC or ATL build, you should include either +// afx.h or atlbase.h first, as appropriate. +// +// PEOPLE WHO HAVE CONTRIBUTED TO THIS CLASS: +// +// Several people have helped me iron out problems and othewise improve +// this class. OK, this is a long list but in my own defense, this code +// has undergone two major rewrites. Many of the improvements became +// necessary after I rewrote the code as a template. Others helped me +// improve the CString facade. +// +// Anyway, these people are (in chronological order): +// +// - Pete the Plumber (???) +// - Julian Selman +// - Chris (of Melbsys) +// - Dave Plummer +// - John C Sipos +// - Chris Sells +// - Nigel Nunn +// - Fan Xia +// - Matthew Williams +// - Carl Engman +// - Mark Zeren +// - Craig Watson +// - Rich Zuris +// - Karim Ratib +// - Chris Conti +// - Baptiste Lepilleur +// - Greg Pickles +// - Jim Cline +// - Jeff Kohn +// - Todd Heckel +// - Ullrich Poll�hne +// - Joe Vitaterna +// - Joe Woodbury +// - Aaron (no last name) +// - Joldakowski (???) +// - Scott Hathaway +// - Eric Nitzche +// - Pablo Presedo +// - Farrokh Nejadlotfi +// - Jason Mills +// - Igor Kholodov +// - Mike Crusader +// - John James +// - Wang Haifeng +// - Tim Dowty +// - Arnt Witteveen +// - Glen Maynard +// - Paul DeMarco +// - Bagira (full name?) +// - Ronny Schulz +// - Jakko Van Hunen +// - Charles Godwin +// - Henk Demper +// - Greg Marr +// - Bill Carducci +// - Brian Groose +// - MKingman +// - Don Beusee +// +// REVISION HISTORY +// +// 2005-JAN-10 - Thanks to Don Beusee for pointing out the danger in mapping +// length-checked formatting functions to non-length-checked +// CRT equivalents. Also thanks to him for motivating me to +// optimize my implementation of Replace() +// +// 2004-APR-22 - A big, big thank you to "MKingman" (whoever you are) for +// finally spotting a silly little error in StdCodeCvt that +// has been causing me (and users of CStdString) problems for +// years in some relatively rare conversions. I had reversed +// two length arguments. +// +// 2003-NOV-24 - Thanks to a bunch of people for helping me clean up many +// compiler warnings (and yes, even a couple of actual compiler +// errors). These include Henk Demper for figuring out how +// to make the Intellisense work on with CStdString on VC6, +// something I was never able to do. Greg Marr pointed out +// a compiler warning about an unreferenced symbol and a +// problem with my version of Load in MFC builds. Bill +// Carducci took a lot of time with me to help me figure out +// why some implementations of the Standard C++ Library were +// returning error codes for apparently successful conversions +// between ASCII and UNICODE. Finally thanks to Brian Groose +// for helping me fix compiler signed unsigned warnings in +// several functions. +// +// 2003-JUL-10 - Thanks to Charles Godwin for making me realize my 'FmtArg' +// fixes had inadvertently broken the DLL-export code (which is +// normally commented out. I had to move it up higher. Also +// this helped me catch a bug in ssicoll that would prevent +// compilation, otherwise. +// +// 2003-MAR-14 - Thanks to Jakko Van Hunen for pointing out a copy-and-paste +// bug in one of the overloads of FmtArg. +// +// 2003-MAR-10 - Thanks to Ronny Schulz for (twice!) sending me some changes +// to help CStdString build on SGI and for pointing out an +// error in placement of my preprocessor macros for ssfmtmsg. +// +// 2002-NOV-26 - Thanks to Bagira for pointing out that my implementation of +// SpanExcluding was not properly handling the case in which +// the string did NOT contain any of the given characters +// +// 2002-OCT-21 - Many thanks to Paul DeMarco who was invaluable in helping me +// get this code working with Borland's free compiler as well +// as the Dev-C++ compiler (available free at SourceForge). +// +// 2002-SEP-13 - Thanks to Glen Maynard who helped me get rid of some loud +// but harmless warnings that were showing up on g++. Glen +// also pointed out that some pre-declarations of FmtArg<> +// specializations were unnecessary (and no good on G++) +// +// 2002-JUN-26 - Thanks to Arnt Witteveen for pointing out that I was using +// static_cast<> in a place in which I should have been using +// reinterpret_cast<> (the ctor for unsigned char strings). +// That's what happens when I don't unit-test properly! +// Arnt also noticed that CString was silently correcting the +// 'nCount' argument to Left() and Right() where CStdString was +// not (and crashing if it was bad). That is also now fixed! +// +// 2002-FEB-25 - Thanks to Tim Dowty for pointing out (and giving me the fix +// for) a conversion problem with non-ASCII MBCS characters. +// CStdString is now used in my favorite commercial MP3 player! +// +// 2001-DEC-06 - Thanks to Wang Haifeng for spotting a problem in one of the +// assignment operators (for _bstr_t) that would cause compiler +// errors when refcounting protection was turned off. +// +// 2001-NOV-27 - Remove calls to operator!= which involve reverse_iterators +// due to a conflict with the rel_ops operator!=. Thanks to +// John James for pointing this out. +// +// 2001-OCT-29 - Added a minor range checking fix for the Mid function to +// make it as forgiving as CString's version is. Thanks to +// Igor Kholodov for noticing this. +// - Added a specialization of std::swap for CStdString. Thanks +// to Mike Crusader for suggesting this! It's commented out +// because you're not supposed to inject your own code into the +// 'std' namespace. But if you don't care about that, it's +// there if you want it +// - Thanks to Jason Mills for catching a case where CString was +// more forgiving in the Delete() function than I was. +// +// 2001-JUN-06 - I was violating the Standard name lookup rules stated +// in [14.6.2(3)]. None of the compilers I've tried so +// far apparently caught this but HP-UX aCC 3.30 did. The +// fix was to add 'this->' prefixes in many places. +// Thanks to Farrokh Nejadlotfi for this! +// +// 2001-APR-27 - StreamLoad was calculating the number of BYTES in one +// case, not characters. Thanks to Pablo Presedo for this. +// +// 2001-FEB-23 - Replace() had a bug which caused infinite loops if the +// source string was empty. Fixed thanks to Eric Nitzsche. +// +// 2001-FEB-23 - Scott Hathaway was a huge help in providing me with the +// ability to build CStdString on Sun Unix systems. He +// sent me detailed build reports about what works and what +// does not. If CStdString compiles on your Unix box, you +// can thank Scott for it. +// +// 2000-DEC-29 - Joldakowski noticed one overload of Insert failed to do a +// range check as CString's does. Now fixed -- thanks! +// +// 2000-NOV-07 - Aaron pointed out that I was calling static member +// functions of char_traits via a temporary. This was not +// technically wrong, but it was unnecessary and caused +// problems for poor old buggy VC5. Thanks Aaron! +// +// 2000-JUL-11 - Joe Woodbury noted that the CString::Find docs don't match +// what the CString::Find code really ends up doing. I was +// trying to match the docs. Now I match the CString code +// - Joe also caught me truncating strings for GetBuffer() calls +// when the supplied length was less than the current length. +// +// 2000-MAY-25 - Better support for STLPORT's Standard library distribution +// - Got rid of the NSP macro - it interfered with Koenig lookup +// - Thanks to Joe Woodbury for catching a TrimLeft() bug that +// I introduced in January. Empty strings were not getting +// trimmed +// +// 2000-APR-17 - Thanks to Joe Vitaterna for pointing out that ReverseFind +// is supposed to be a const function. +// +// 2000-MAR-07 - Thanks to Ullrich Poll�hne for catching a range bug in one +// of the overloads of assign. +// +// 2000-FEB-01 - You can now use CStdString on the Mac with CodeWarrior! +// Thanks to Todd Heckel for helping out with this. +// +// 2000-JAN-23 - Thanks to Jim Cline for pointing out how I could make the +// Trim() function more efficient. +// - Thanks to Jeff Kohn for prompting me to find and fix a typo +// in one of the addition operators that takes _bstr_t. +// - Got rid of the .CPP file - you only need StdString.h now! +// +// 1999-DEC-22 - Thanks to Greg Pickles for helping me identify a problem +// with my implementation of CStdString::FormatV in which +// resulting string might not be properly NULL terminated. +// +// 1999-DEC-06 - Chris Conti pointed yet another basic_string<> assignment +// bug that MS has not fixed. CStdString did nothing to fix +// it either but it does now! The bug was: create a string +// longer than 31 characters, get a pointer to it (via c_str()) +// and then assign that pointer to the original string object. +// The resulting string would be empty. Not with CStdString! +// +// 1999-OCT-06 - BufferSet was erasing the string even when it was merely +// supposed to shrink it. Fixed. Thanks to Chris Conti. +// - Some of the Q172398 fixes were not checking for assignment- +// to-self. Fixed. Thanks to Baptiste Lepilleur. +// +// 1999-AUG-20 - Improved Load() function to be more efficient by using +// SizeOfResource(). Thanks to Rich Zuris for this. +// - Corrected resource ID constructor, again thanks to Rich. +// - Fixed a bug that occurred with UNICODE characters above +// the first 255 ANSI ones. Thanks to Craig Watson. +// - Added missing overloads of TrimLeft() and TrimRight(). +// Thanks to Karim Ratib for pointing them out +// +// 1999-JUL-21 - Made all calls to GetBuf() with no args check length first. +// +// 1999-JUL-10 - Improved MFC/ATL independence of conversion macros +// - Added SS_NO_REFCOUNT macro to allow you to disable any +// reference-counting your basic_string<> impl. may do. +// - Improved ReleaseBuffer() to be as forgiving as CString. +// Thanks for Fan Xia for helping me find this and to +// Matthew Williams for pointing it out directly. +// +// 1999-JUL-06 - Thanks to Nigel Nunn for catching a very sneaky bug in +// ToLower/ToUpper. They should call GetBuf() instead of +// data() in order to ensure the changed string buffer is not +// reference-counted (in those implementations that refcount). +// +// 1999-JUL-01 - Added a true CString facade. Now you can use CStdString as +// a drop-in replacement for CString. If you find this useful, +// you can thank Chris Sells for finally convincing me to give +// in and implement it. +// - Changed operators << and >> (for MFC CArchive) to serialize +// EXACTLY as CString's do. So now you can send a CString out +// to a CArchive and later read it in as a CStdString. I have +// no idea why you would want to do this but you can. +// +// 1999-JUN-21 - Changed the CStdString class into the CStdStr template. +// - Fixed FormatV() to correctly decrement the loop counter. +// This was harmless bug but a bug nevertheless. Thanks to +// Chris (of Melbsys) for pointing it out +// - Changed Format() to try a normal stack-based array before +// using to _alloca(). +// - Updated the text conversion macros to properly use code +// pages and to fit in better in MFC/ATL builds. In other +// words, I copied Microsoft's conversion stuff again. +// - Added equivalents of CString::GetBuffer, GetBufferSetLength +// - new sscpy() replacement of CStdString::CopyString() +// - a Trim() function that combines TrimRight() and TrimLeft(). +// +// 1999-MAR-13 - Corrected the "NotSpace" functional object to use _istpace() +// instead of _isspace() Thanks to Dave Plummer for this. +// +// 1999-FEB-26 - Removed errant line (left over from testing) that #defined +// _MFC_VER. Thanks to John C Sipos for noticing this. +// +// 1999-FEB-03 - Fixed a bug in a rarely-used overload of operator+() that +// caused infinite recursion and stack overflow +// - Added member functions to simplify the process of +// persisting CStdStrings to/from DCOM IStream interfaces +// - Added functional objects (e.g. StdStringLessNoCase) that +// allow CStdStrings to be used as keys STL map objects with +// case-insensitive comparison +// - Added array indexing operators (i.e. operator[]). I +// originally assumed that these were unnecessary and would be +// inherited from basic_string. However, without them, Visual +// C++ complains about ambiguous overloads when you try to use +// them. Thanks to Julian Selman to pointing this out. +// +// 1998-FEB-?? - Added overloads of assign() function to completely account +// for Q172398 bug. Thanks to "Pete the Plumber" for this +// +// 1998-FEB-?? - Initial submission +// +// COPYRIGHT: +// 2002 Joseph M. O'Leary. This code is 100% free. Use it anywhere you +// want. Rewrite it, restructure it, whatever. If you can write software +// that makes money off of it, good for you. I kinda like capitalism. +// Please don't blame me if it causes your $30 billion dollar satellite +// explode in orbit. If you redistribute it in any form, I'd appreciate it +// if you would leave this notice here. +// ============================================================================= + +// Avoid multiple inclusion + +#ifndef STDSTRING_H +#define STDSTRING_H + +// When using VC, turn off browser references +// Turn off unavoidable compiler warnings + +#if defined(_MSC_VER) && (_MSC_VER > 1100) + #pragma component(browser, off, references, "CStdString") + #pragma warning (disable : 4290) // C++ Exception Specification ignored + #pragma warning (disable : 4127) // Conditional expression is constant + #pragma warning (disable : 4097) // typedef name used as synonym for class name +#endif + +// Borland warnings to turn off + +#ifdef __BORLANDC__ + #pragma option push -w-inl +// #pragma warn -inl // Turn off inline function warnings +#endif + +// SS_IS_INTRESOURCE +// ----------------- +// A copy of IS_INTRESOURCE from VC7. Because old VC6 version of winuser.h +// doesn't have this. + +#define SS_IS_INTRESOURCE(_r) (false) + +#if !defined (SS_ANSI) && defined(_MSC_VER) + #undef SS_IS_INTRESOURCE + #if defined(_WIN64) + #define SS_IS_INTRESOURCE(_r) (((unsigned __int64)(_r) >> 16) == 0) + #else + #define SS_IS_INTRESOURCE(_r) (((unsigned long)(_r) >> 16) == 0) + #endif +#endif + + +// MACRO: SS_UNSIGNED +// ------------------ +// This macro causes the addition of a constructor and assignment operator +// which take unsigned characters. CString has such functions and in order +// to provide maximum CString-compatability, this code needs them as well. +// In practice you will likely never need these functions... + +//#define SS_UNSIGNED + +#ifdef SS_ALLOW_UNSIGNED_CHARS + #define SS_UNSIGNED +#endif + +// MACRO: SS_SAFE_FORMAT +// --------------------- +// This macro provides limited compatability with a questionable CString +// "feature". You can define it in order to avoid a common problem that +// people encounter when switching from CString to CStdString. +// +// To illustrate the problem -- With CString, you can do this: +// +// CString sName("Joe"); +// CString sTmp; +// sTmp.Format("My name is %s", sName); // WORKS! +// +// However if you were to try this with CStdString, your program would +// crash. +// +// CStdString sName("Joe"); +// CStdString sTmp; +// sTmp.Format("My name is %s", sName); // CRASHES! +// +// You must explicitly call c_str() or cast the object to the proper type +// +// sTmp.Format("My name is %s", sName.c_str()); // WORKS! +// sTmp.Format("My name is %s", static_cast(sName));// WORKS! +// sTmp.Format("My name is %s", (PCSTR)sName); // WORKS! +// +// This is because it is illegal to pass anything but a POD type as a +// variadic argument to a variadic function (i.e. as one of the "..." +// arguments). The type const char* is a POD type. The type CStdString +// is not. Of course, neither is the type CString, but CString lets you do +// it anyway due to the way they laid out the class in binary. I have no +// control over this in CStdString since I derive from whatever +// implementation of basic_string is available. +// +// However if you have legacy code (which does this) that you want to take +// out of the MFC world and you don't want to rewrite all your calls to +// Format(), then you can define this flag and it will no longer crash. +// +// Note however that this ONLY works for Format(), not sprintf, fprintf, +// etc. If you pass a CStdString object to one of those functions, your +// program will crash. Not much I can do to get around this, short of +// writing substitutes for those functions as well. + +#define SS_SAFE_FORMAT // use new template style Format() function + + +// MACRO: SS_NO_IMPLICIT_CAST +// -------------------------- +// Some people don't like the implicit cast to const char* (or rather to +// const CT*) that CStdString (and MFC's CString) provide. That was the +// whole reason I created this class in the first place, but hey, whatever +// bakes your cake. Just #define this macro to get rid of the the implicit +// cast. + +//#define SS_NO_IMPLICIT_CAST // gets rid of operator const CT*() + + +// MACRO: SS_NO_REFCOUNT +// --------------------- +// turns off reference counting at the assignment level. Only needed +// for the version of basic_string<> that comes with Visual C++ versions +// 6.0 or earlier, and only then in some heavily multithreaded scenarios. +// Uncomment it if you feel you need it. + +//#define SS_NO_REFCOUNT + +// MACRO: SS_WIN32 +// --------------- +// When this flag is set, we are building code for the Win32 platform and +// may use Win32 specific functions (such as LoadString). This gives us +// a couple of nice extras for the code. +// +// Obviously, Microsoft's is not the only compiler available for Win32 out +// there. So I can't just check to see if _MSC_VER is defined to detect +// if I'm building on Win32. So for now, if you use MS Visual C++ or +// Borland's compiler, I turn this on. Otherwise you may turn it on +// yourself, if you prefer + +#if defined(_MSC_VER) || defined(__BORLANDC__) || defined(_WIN32) + #define SS_WIN32 +#endif + +// MACRO: SS_ANSI +// -------------- +// When this macro is defined, the code attempts only to use ANSI/ISO +// standard library functions to do it's work. It will NOT attempt to use +// any Win32 of Visual C++ specific functions -- even if they are +// available. You may define this flag yourself to prevent any Win32 +// of VC++ specific functions from being called. + +// If we're not on Win32, we MUST use an ANSI build + +#ifndef SS_WIN32 + #if !defined(SS_NO_ANSI) + #define SS_ANSI + #endif +#endif + +// MACRO: SS_ALLOCA +// ---------------- +// Some implementations of the Standard C Library have a non-standard +// function known as alloca(). This functions allows one to allocate a +// variable amount of memory on the stack. It is needed to implement +// the ASCII/MBCS conversion macros. +// +// I wanted to find some way to determine automatically if alloca() is +// available on this platform via compiler flags but that is asking for +// trouble. The crude test presented here will likely need fixing on +// other platforms. Therefore I'll leave it up to you to fiddle with +// this test to determine if it exists. Just make sure SS_ALLOCA is or +// is not defined as appropriate and you control this feature. + +#if defined(_MSC_VER) && !defined(SS_ANSI) + #define SS_ALLOCA +#endif + + +// MACRO: SS_MBCS +// -------------- +// Setting this macro means you are using MBCS characters. In MSVC builds, +// this macro gets set automatically by detection of the preprocessor flag +// _MBCS. For other platforms you may set it manually if you wish. The +// only effect it currently has is to cause the allocation of more space +// for wchar_t --> char conversions. +// Note that MBCS does not mean UNICODE. +// +// #define SS_MBCS +// + +#ifdef _MBCS + #define SS_MBCS +#endif + + +// MACRO SS_NO_LOCALE +// ------------------ +// If your implementation of the Standard C++ Library lacks the header, +// you can #define this macro to make your code build properly. Note that this +// is some of my newest code and frankly I'm not very sure of it, though it does +// pass my unit tests. + +// #define SS_NO_LOCALE + + +// Compiler Error regarding _UNICODE and UNICODE +// ----------------------------------------------- +// Microsoft header files are screwy. Sometimes they depend on a preprocessor +// flag named "_UNICODE". Other times they check "UNICODE" (note the lack of +// leading underscore in the second version". In several places, they silently +// "synchronize" these two flags this by defining one of the other was defined. +// In older version of this header, I used to try to do the same thing. +// +// However experience has taught me that this is a bad idea. You get weird +// compiler errors that seem to indicate things like LPWSTR and LPTSTR not being +// equivalent in UNICODE builds, stuff like that (when they MUST be in a proper +// UNICODE build). You end up scratching your head and saying, "But that HAS +// to compile!". +// +// So what should you do if you get this error? +// +// Make sure that both macros (_UNICODE and UNICODE) are defined before this +// file is included. You can do that by either +// +// a) defining both yourself before any files get included +// b) including the proper MS headers in the proper order +// c) including this file before any other file, uncommenting +// the #defines below, and commenting out the #errors +// +// Personally I recommend solution a) but it's your call. + +#ifdef _MSC_VER + #if defined (_UNICODE) && !defined (UNICODE) + #error UNICODE defined but not UNICODE + // #define UNICODE // no longer silently fix this + #endif + #if defined (UNICODE) && !defined (_UNICODE) + #error Warning, UNICODE defined but not _UNICODE + // #define _UNICODE // no longer silently fix this + #endif +#endif + + +// ----------------------------------------------------------------------------- +// MIN and MAX. The Standard C++ template versions go by so many names (at +// at least in the MS implementation) that you never know what's available +// ----------------------------------------------------------------------------- +template +inline const Type& SSMIN(const Type& arg1, const Type& arg2) +{ + return arg2 < arg1 ? arg2 : arg1; +} +template +inline const Type& SSMAX(const Type& arg1, const Type& arg2) +{ + return arg2 > arg1 ? arg2 : arg1; +} + +// If they have not #included W32Base.h (part of my W32 utility library) then +// we need to define some stuff. Otherwise, this is all defined there. + +#if !defined(W32BASE_H) + + // If they want us to use only standard C++ stuff (no Win32 stuff) + + #ifdef SS_ANSI + + // On Win32 we have TCHAR.H so just include it. This is NOT violating + // the spirit of SS_ANSI as we are not calling any Win32 functions here. + + #ifdef SS_WIN32 + + #include + #include + #ifndef STRICT + #define STRICT + #endif + + // ... but on non-Win32 platforms, we must #define the types we need. + + #else + + typedef const char* PCSTR; + typedef char* PSTR; + typedef const wchar_t* PCWSTR; + typedef wchar_t* PWSTR; + #ifdef UNICODE + typedef wchar_t TCHAR; + #else + typedef char TCHAR; + #endif + typedef wchar_t OLECHAR; + + #endif // #ifndef _WIN32 + + + // Make sure ASSERT and verify are defined using only ANSI stuff + + #ifndef ASSERT + #include + #define ASSERT(f) assert((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #else // ...else SS_ANSI is NOT defined + + #include + #include + #ifndef STRICT + #define STRICT + #endif + + // Make sure ASSERT and verify are defined + + #ifndef ASSERT + #include + #define ASSERT(f) _ASSERTE((f)) + #endif + #ifndef VERIFY + #ifdef _DEBUG + #define VERIFY(x) ASSERT((x)) + #else + #define VERIFY(x) x + #endif + #endif + + #endif // #ifdef SS_ANSI + + #ifndef UNUSED + #define UNUSED(x) x + #endif + +#endif // #ifndef W32BASE_H + +// Standard headers needed + +#include // basic_string +#include // for_each, etc. +#include // for StdStringLessNoCase, et al +#ifndef SS_NO_LOCALE + #include // for various facets +#endif + +// If this is a recent enough version of VC include comdef.h, so we can write +// member functions to deal with COM types & compiler support classes e.g. +// _bstr_t + +#if defined (_MSC_VER) && (_MSC_VER >= 1100) + #include + #define SS_INC_COMDEF // signal that we #included MS comdef.h file + #define STDSTRING_INC_COMDEF + #define SS_NOTHROW __declspec(nothrow) +#else + #define SS_NOTHROW +#endif + +#ifndef TRACE + #define TRACE_DEFINED_HERE + #define TRACE +#endif + +// Microsoft defines PCSTR, PCWSTR, etc, but no PCTSTR. I hate to use the +// versions with the "L" in front of them because that's a leftover from Win 16 +// days, even though it evaluates to the same thing. Therefore, Define a PCSTR +// as an LPCTSTR. + +#if !defined(PCTSTR) && !defined(PCTSTR_DEFINED) + typedef const TCHAR* PCTSTR; + #define PCTSTR_DEFINED +#endif + +#if !defined(PCOLESTR) && !defined(PCOLESTR_DEFINED) + typedef const OLECHAR* PCOLESTR; + #define PCOLESTR_DEFINED +#endif + +#if !defined(POLESTR) && !defined(POLESTR_DEFINED) + typedef OLECHAR* POLESTR; + #define POLESTR_DEFINED +#endif + +#if !defined(PCUSTR) && !defined(PCUSTR_DEFINED) + typedef const unsigned char* PCUSTR; + typedef unsigned char* PUSTR; + #define PCUSTR_DEFINED +#endif + + +// SGI compiler 7.3 doesnt know these types - oh and btw, remember to use +// -LANG:std in the CXX Flags +#if defined(__sgi) + typedef unsigned long DWORD; + typedef void * LPCVOID; +#endif + + +// SS_USE_FACET macro and why we need it: +// +// Since I'm a good little Standard C++ programmer, I use locales. Thus, I +// need to make use of the use_facet<> template function here. Unfortunately, +// this need is complicated by the fact the MS' implementation of the Standard +// C++ Library has a non-standard version of use_facet that takes more +// arguments than the standard dictates. Since I'm trying to write CStdString +// to work with any version of the Standard library, this presents a problem. +// +// The upshot of this is that I can't do 'use_facet' directly. The MS' docs +// tell me that I have to use a macro, _USE() instead. Since _USE obviously +// won't be available in other implementations, this means that I have to write +// my OWN macro -- SS_USE_FACET -- that evaluates either to _USE or to the +// standard, use_facet. +// +// If you are having trouble with the SS_USE_FACET macro, in your implementation +// of the Standard C++ Library, you can define your own version of SS_USE_FACET. + +#ifndef schMSG + #define schSTR(x) #x + #define schSTR2(x) schSTR(x) + #define schMSG(desc) message(__FILE__ "(" schSTR2(__LINE__) "):" #desc) +#endif + +#ifndef SS_USE_FACET + + // STLPort #defines a macro (__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) for + // all MSVC builds, erroneously in my opinion. It causes problems for + // my SS_ANSI builds. In my code, I always comment out that line. You'll + // find it in \stlport\config\stl_msvc.h + + #if defined(__SGI_STL_PORT) && (__SGI_STL_PORT >= 0x400 ) + + #if defined(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) && defined(_MSC_VER) + #ifdef SS_ANSI + #pragma schMSG(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS defined!!) + #endif + #endif + #define SS_USE_FACET(loc, fac) std::use_facet(loc) + + #elif defined(_MSC_VER ) + + #define SS_USE_FACET(loc, fac) std::_USE(loc, fac) + + // ...and + #elif defined(_RWSTD_NO_TEMPLATE_ON_RETURN_TYPE) + + #define SS_USE_FACET(loc, fac) std::use_facet(loc, (fac*)0) + + #else + + #define SS_USE_FACET(loc, fac) std::use_facet(loc) + + #endif + +#endif + +// ============================================================================= +// UNICODE/MBCS conversion macros. Made to work just like the MFC/ATL ones. +// ============================================================================= + +#include // Added to Std Library with Amendment #1. + +// First define the conversion helper functions. We define these regardless of +// any preprocessor macro settings since their names won't collide. + +// Not sure if we need all these headers. I believe ANSI says we do. + +#include +#include +#include +#include +#include +#ifndef va_start + #include +#endif + + +#ifdef SS_NO_LOCALE + + #if defined(_WIN32) || defined (_WIN32_WCE) + + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCSTR pSrcA, int nSrc, + UINT acp=CP_ACP) + { + ASSERT(0 != pSrcA); + ASSERT(0 != pDstW); + pDstW[0] = '\0'; + MultiByteToWideChar(acp, 0, pSrcA, nSrc, pDstW, nDst); + return pDstW; + } + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCUSTR pSrcA, int nSrc, + UINT acp=CP_ACP) + { + return StdCodeCvt(pDstW, nDst, (PCSTR)pSrcA, nSrc, acp); + } + + inline PSTR StdCodeCvt(PSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + UINT acp=CP_ACP) + { + ASSERT(0 != pDstA); + ASSERT(0 != pSrcW); + pDstA[0] = '\0'; + WideCharToMultiByte(acp, 0, pSrcW, nSrc, pDstA, nDst, 0, 0); + return pDstA; + } + inline PUSTR StdCodeCvt(PUSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + UINT acp=CP_ACP) + { + return (PUSTR)StdCodeCvt((PSTR)pDstA, nDst, pSrcW, nSrc, acp); + } + #else + #endif + +#else + + // StdCodeCvt - made to look like Win32 functions WideCharToMultiByte + // and MultiByteToWideChar but uses locales in SS_ANSI + // builds. There are a number of overloads. + // First argument is the destination buffer. + // Second argument is the source buffer + //#if defined (SS_ANSI) || !defined (SS_WIN32) + + // 'SSCodeCvt' - shorthand name for the codecvt facet we use + + typedef std::codecvt SSCodeCvt; + + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCSTR pSrcA, int nSrc, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pSrcA); + ASSERT(0 != pDstW); + + pDstW[0] = '\0'; + + if ( nSrc > 0 ) + { + PCSTR pNextSrcA = pSrcA; + PWSTR pNextDstW = pDstW; + SSCodeCvt::result res = SSCodeCvt::ok; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= { 0 }; + res = conv.in(st, + pSrcA, pSrcA + nSrc, pNextSrcA, + pDstW, pDstW + nDst, pNextDstW); +#ifdef _LINUX +#define ASSERT2(a) if (!(a)) {fprintf(stderr, "StdString: Assertion Failed on line %d\n", __LINE__);} +#else +#define ASSERT2 ASSERT +#endif + ASSERT2(SSCodeCvt::ok == res); + ASSERT2(SSCodeCvt::error != res); + ASSERT2(pNextDstW >= pDstW); + ASSERT2(pNextSrcA >= pSrcA); +#undef ASSERT2 + // Null terminate the converted string + + if ( pNextDstW - pDstW > nDst ) + *(pDstW + nDst) = '\0'; + else + *pNextDstW = '\0'; + } + return pDstW; + } + inline PWSTR StdCodeCvt(PWSTR pDstW, int nDst, PCUSTR pSrcA, int nSrc, + const std::locale& loc=std::locale()) + { + return StdCodeCvt(pDstW, nDst, (PCSTR)pSrcA, nSrc, loc); + } + + inline PSTR StdCodeCvt(PSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + const std::locale& loc=std::locale()) + { + ASSERT(0 != pDstA); + ASSERT(0 != pSrcW); + + pDstA[0] = '\0'; + + if ( nSrc > 0 ) + { + PSTR pNextDstA = pDstA; + PCWSTR pNextSrcW = pSrcW; + SSCodeCvt::result res = SSCodeCvt::ok; + const SSCodeCvt& conv = SS_USE_FACET(loc, SSCodeCvt); + SSCodeCvt::state_type st= { 0 }; + res = conv.out(st, + pSrcW, pSrcW + nSrc, pNextSrcW, + pDstA, pDstA + nDst, pNextDstA); +#ifdef _LINUX +#define ASSERT2(a) if (!(a)) {fprintf(stderr, "StdString: Assertion Failed on line %d\n", __LINE__);} +#else +#define ASSERT2 ASSERT +#endif + ASSERT2(SSCodeCvt::error != res); + ASSERT2(SSCodeCvt::ok == res); // strict, comment out for sanity + ASSERT2(pNextDstA >= pDstA); + ASSERT2(pNextSrcW >= pSrcW); +#undef ASSERT2 + + // Null terminate the converted string + + if ( pNextDstA - pDstA > nDst ) + *(pDstA + nDst) = '\0'; + else + *pNextDstA = '\0'; + } + return pDstA; + } + + inline PUSTR StdCodeCvt(PUSTR pDstA, int nDst, PCWSTR pSrcW, int nSrc, + const std::locale& loc=std::locale()) + { + return (PUSTR)StdCodeCvt((PSTR)pDstA, nDst, pSrcW, nSrc, loc); + } + +#endif + + + +// Unicode/MBCS conversion macros are only available on implementations of +// the "C" library that have the non-standard _alloca function. As far as I +// know that's only Microsoft's though I've heard that the function exists +// elsewhere. + +#if defined(SS_ALLOCA) && !defined SS_NO_CONVERSION + + #include // needed for _alloca + + // Define our conversion macros to look exactly like Microsoft's to + // facilitate using this stuff both with and without MFC/ATL + + #ifdef _CONVERSION_USES_THREAD_LOCALE + + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=GetACP(); \ + _acp; PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=GetACP();\ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)),\ + StdCodeCvt((PWSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pa, _cvt, _acp))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = sslen(_pw), \ + StdCodeCvt((LPSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pw, _cvt, _acp))) + #else + + #ifndef _DEBUG + #define SSCVT int _cvt; _cvt; UINT _acp=CP_ACP; _acp;\ + PCWSTR _pw; _pw; PCSTR _pa; _pa + #else + #define SSCVT int _cvt = 0; _cvt; UINT _acp=CP_ACP; \ + _acp; PCWSTR _pw=0; _pw; PCSTR _pa=0; _pa + #endif + #define SSA2W(pa) (\ + ((_pa = pa) == 0) ? 0 : (\ + _cvt = (sslen(_pa)),\ + StdCodeCvt((PWSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pa, _cvt))) + #define SSW2A(pw) (\ + ((_pw = pw) == 0) ? 0 : (\ + _cvt = (sslen(_pw)),\ + StdCodeCvt((LPSTR) _alloca((_cvt+1)*2), (_cvt+1)*2, \ + _pw, _cvt))) + #endif + + #define SSA2CW(pa) ((PCWSTR)SSA2W((pa))) + #define SSW2CA(pw) ((PCSTR)SSW2A((pw))) + + #ifdef UNICODE + #define SST2A SSW2A + #define SSA2T SSA2W + #define SST2CA SSW2CA + #define SSA2CT SSA2CW + // (Did you get a compiler error here about not being able to convert + // PTSTR into PWSTR? Then your _UNICODE and UNICODE flags are messed + // up. Best bet: #define BOTH macros before including any MS headers.) + inline PWSTR SST2W(PTSTR p) { return p; } + inline PTSTR SSW2T(PWSTR p) { return p; } + inline PCWSTR SST2CW(PCTSTR p) { return p; } + inline PCTSTR SSW2CT(PCWSTR p) { return p; } + #else + #define SST2W SSA2W + #define SSW2T SSW2A + #define SST2CW SSA2CW + #define SSW2CT SSW2CA + inline PSTR SST2A(PTSTR p) { return p; } + inline PTSTR SSA2T(PSTR p) { return p; } + inline PCSTR SST2CA(PCTSTR p) { return p; } + inline PCTSTR SSA2CT(PCSTR p) { return p; } + #endif // #ifdef UNICODE + + #if defined(UNICODE) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #elif defined(OLE2ANSI) + // in these cases the default (TCHAR) is the same as OLECHAR + inline PCOLESTR SST2COLE(PCTSTR p) { return p; } + inline PCTSTR SSOLE2CT(PCOLESTR p) { return p; } + inline POLESTR SST2OLE(PTSTR p) { return p; } + inline PTSTR SSOLE2T(POLESTR p) { return p; } + #else + //CharNextW doesn't work on Win95 so we use this + #define SST2COLE(pa) SSA2CW((pa)) + #define SST2OLE(pa) SSA2W((pa)) + #define SSOLE2CT(po) SSW2CA((po)) + #define SSOLE2T(po) SSW2A((po)) + #endif + + #ifdef OLE2ANSI + #define SSW2OLE SSW2A + #define SSOLE2W SSA2W + #define SSW2COLE SSW2CA + #define SSOLE2CW SSA2CW + inline POLESTR SSA2OLE(PSTR p) { return p; } + inline PSTR SSOLE2A(POLESTR p) { return p; } + inline PCOLESTR SSA2COLE(PCSTR p) { return p; } + inline PCSTR SSOLE2CA(PCOLESTR p){ return p; } + #else + #define SSA2OLE SSA2W + #define SSOLE2A SSW2A + #define SSA2COLE SSA2CW + #define SSOLE2CA SSW2CA + inline POLESTR SSW2OLE(PWSTR p) { return p; } + inline PWSTR SSOLE2W(POLESTR p) { return p; } + inline PCOLESTR SSW2COLE(PCWSTR p) { return p; } + inline PCWSTR SSOLE2CW(PCOLESTR p){ return p; } + #endif + + // Above we've defined macros that look like MS' but all have + // an 'SS' prefix. Now we need the real macros. We'll either + // get them from the macros above or from MFC/ATL. + + #if defined (USES_CONVERSION) + + #define _NO_STDCONVERSION // just to be consistent + + #else + + #ifdef _MFC_VER + + #include + #define _NO_STDCONVERSION // just to be consistent + + #else + + #define USES_CONVERSION SSCVT + #define A2CW SSA2CW + #define W2CA SSW2CA + #define T2A SST2A + #define A2T SSA2T + #define T2W SST2W + #define W2T SSW2T + #define T2CA SST2CA + #define A2CT SSA2CT + #define T2CW SST2CW + #define W2CT SSW2CT + #define ocslen sslen + #define ocscpy sscpy + #define T2COLE SST2COLE + #define OLE2CT SSOLE2CT + #define T2OLE SST2COLE + #define OLE2T SSOLE2CT + #define A2OLE SSA2OLE + #define OLE2A SSOLE2A + #define W2OLE SSW2OLE + #define OLE2W SSOLE2W + #define A2COLE SSA2COLE + #define OLE2CA SSOLE2CA + #define W2COLE SSW2COLE + #define OLE2CW SSOLE2CW + + #endif // #ifdef _MFC_VER + #endif // #ifndef USES_CONVERSION +#endif // #ifndef SS_NO_CONVERSION + +// Define ostring - generic name for std::basic_string + +#if !defined(ostring) && !defined(OSTRING_DEFINED) + typedef std::basic_string ostring; + #define OSTRING_DEFINED +#endif + +// StdCodeCvt when there's no conversion to be done +template +inline T* StdCodeCvt(T* pDst, int nDst, const T* pSrc, int nSrc) +{ + int nChars = SSMIN(nSrc, nDst); + + if ( nChars > 0 ) + { + pDst[0] = '\0'; + std::basic_string::traits_type::copy(pDst, pSrc, nChars); +// std::char_traits::copy(pDst, pSrc, nChars); + pDst[nChars] = '\0'; + } + + return pDst; +} +inline PSTR StdCodeCvt(PSTR pDst, int nDst, PCUSTR pSrc, int nSrc) +{ + return StdCodeCvt(pDst, nDst, (PCSTR)pSrc, nSrc); +} +inline PUSTR StdCodeCvt(PUSTR pDst, int nDst, PCSTR pSrc, int nSrc) +{ + return (PUSTR)StdCodeCvt((PSTR)pDst, nDst, pSrc, nSrc); +} + +// Define tstring -- generic name for std::basic_string + +#if !defined(tstring) && !defined(TSTRING_DEFINED) + typedef std::basic_string tstring; + #define TSTRING_DEFINED +#endif + +// a very shorthand way of applying the fix for KB problem Q172398 +// (basic_string assignment bug) + +#if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + #define Q172398(x) (x).erase() +#else + #define Q172398(x) +#endif + +// ============================================================================= +// INLINE FUNCTIONS ON WHICH CSTDSTRING RELIES +// +// Usually for generic text mapping, we rely on preprocessor macro definitions +// to map to string functions. However the CStdStr<> template cannot use +// macro-based generic text mappings because its character types do not get +// resolved until template processing which comes AFTER macro processing. In +// other words, the preprocessor macro UNICODE is of little help to us in the +// CStdStr template +// +// Therefore, to keep the CStdStr declaration simple, we have these inline +// functions. The template calls them often. Since they are inline (and NOT +// exported when this is built as a DLL), they will probably be resolved away +// to nothing. +// +// Without these functions, the CStdStr<> template would probably have to broken +// out into two, almost identical classes. Either that or it would be a huge, +// convoluted mess, with tons of "if" statements all over the place checking the +// size of template parameter CT. +// ============================================================================= + +#ifdef SS_NO_LOCALE + + // -------------------------------------------------------------------------- + // Win32 GetStringTypeEx wrappers + // -------------------------------------------------------------------------- + inline bool wsGetStringType(LCID lc, DWORD dwT, PCSTR pS, int nSize, + WORD* pWd) + { + return FALSE != GetStringTypeExA(lc, dwT, pS, nSize, pWd); + } + inline bool wsGetStringType(LCID lc, DWORD dwT, PCWSTR pS, int nSize, + WORD* pWd) + { + return FALSE != GetStringTypeExW(lc, dwT, pS, nSize, pWd); + } + + + template + inline bool ssisspace (CT t) + { + WORD toYourMother; + return wsGetStringType(GetThreadLocale(), CT_CTYPE1, &t, 1, &toYourMother) + && 0 != (C1_BLANK & toYourMother); + } + +#endif + +// If they defined SS_NO_REFCOUNT, then we must convert all assignments + +#if defined (_MSC_VER) && (_MSC_VER < 1300) + #ifdef SS_NO_REFCOUNT + #define SSREF(x) (x).c_str() + #else + #define SSREF(x) (x) + #endif +#else + #define SSREF(x) (x) +#endif + +// ----------------------------------------------------------------------------- +// sslen: strlen/wcslen wrappers +// ----------------------------------------------------------------------------- +template inline int sslen(const CT* pT) +{ + return 0 == pT ? 0 : (int)std::basic_string::traits_type::length(pT); +// return 0 == pT ? 0 : std::char_traits::length(pT); +} +inline SS_NOTHROW int sslen(const std::string& s) +{ + return static_cast(s.length()); +} +inline SS_NOTHROW int sslen(const std::wstring& s) +{ + return static_cast(s.length()); +} + +// ----------------------------------------------------------------------------- +// sstolower/sstoupper -- convert characters to upper/lower case +// ----------------------------------------------------------------------------- + +#ifdef SS_NO_LOCALE + inline char sstoupper(char ch) { return (char)::toupper(ch); } + inline wchar_t sstoupper(wchar_t ch){ return (wchar_t)::towupper(ch); } + inline char sstolower(char ch) { return (char)::tolower(ch); } + inline wchar_t sstolower(wchar_t ch){ return (wchar_t)::tolower(ch); } +#else + template + inline CT sstolower(const CT& t, const std::locale& loc = std::locale()) + { + return std::tolower(t, loc); + } + template + inline CT sstoupper(const CT& t, const std::locale& loc = std::locale()) + { + return std::toupper(t, loc); + } +#endif + +// ----------------------------------------------------------------------------- +// ssasn: assignment functions -- assign "sSrc" to "sDst" +// ----------------------------------------------------------------------------- +typedef std::string::size_type SS_SIZETYPE; // just for shorthand, really +typedef std::string::pointer SS_PTRTYPE; +typedef std::wstring::size_type SW_SIZETYPE; +typedef std::wstring::pointer SW_PTRTYPE; + + +template +inline void ssasn(std::basic_string& sDst, const std::basic_string& sSrc) +{ + if ( sDst.c_str() != sSrc.c_str() ) + { + sDst.erase(); + sDst.assign(SSREF(sSrc)); + } +} +template +inline void ssasn(std::basic_string& sDst, const T *pA) +{ + // Watch out for NULLs, as always. + + if ( 0 == pA ) + { + sDst.erase(); + } + + // If pA actually points to part of sDst, we must NOT erase(), but + // rather take a substring + + else if ( pA >= sDst.c_str() && pA <= sDst.c_str() + sDst.size() ) + { + sDst =sDst.substr(static_cast::size_type>(pA-sDst.c_str())); + } + + // Otherwise (most cases) apply the assignment bug fix, if applicable + // and do the assignment + + else + { + Q172398(sDst); + sDst.assign(pA); + } +} +inline void ssasn(std::string& sDst, const std::wstring& sSrc) +{ + if ( sSrc.empty() ) + { + sDst.erase(); + } + else + { + int nDst = static_cast(sSrc.size()); + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + // In MBCS builds, we don't know how long the destination string will be. + nDst = static_cast(static_cast(nDst) * 1.3); + sDst.resize(nDst+1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), static_cast(sSrc.size())); + sDst.resize(sslen(szCvt)); +#else + sDst.resize(nDst+1); + StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), static_cast(sSrc.size())); + sDst.resize(sSrc.size()); +#endif + } +} +inline void ssasn(std::string& sDst, PCWSTR pW) +{ + int nSrc = sslen(pW); + if ( nSrc > 0 ) + { + int nSrc = sslen(pW); + int nDst = nSrc; + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + nDst = static_cast(static_cast(nDst) * 1.3); + // In MBCS builds, we don't know how long the destination string will be. + sDst.resize(nDst + 1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + pW, nSrc); + sDst.resize(sslen(szCvt)); +#else + sDst.resize(nDst + 1); + StdCodeCvt(const_cast(sDst.data()), nDst, pW, nSrc); + sDst.resize(nDst); +#endif + } + else + { + sDst.erase(); + } +} +inline void ssasn(std::string& sDst, const int nNull) +{ + //UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(""); +} +#undef StrSizeType +inline void ssasn(std::wstring& sDst, const std::string& sSrc) +{ + if ( sSrc.empty() ) + { + sDst.erase(); + } + else + { + int nSrc = static_cast(sSrc.size()); + int nDst = nSrc; + + sDst.resize(nSrc+1); + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, + sSrc.c_str(), nSrc); + + sDst.resize(sslen(szCvt)); + } +} +inline void ssasn(std::wstring& sDst, PCSTR pA) +{ + int nSrc = sslen(pA); + + if ( 0 == nSrc ) + { + sDst.erase(); + } + else + { + int nDst = nSrc; + sDst.resize(nDst+1); + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()), nDst, pA, + nSrc); + + sDst.resize(sslen(szCvt)); + } +} +inline void ssasn(std::wstring& sDst, const int nNull) +{ + //UNUSED(nNull); + ASSERT(nNull==0); + sDst.assign(L""); +} + +// ----------------------------------------------------------------------------- +// ssadd: string object concatenation -- add second argument to first +// ----------------------------------------------------------------------------- +inline void ssadd(std::string& sDst, const std::wstring& sSrc) +{ + int nSrc = static_cast(sSrc.size()); + + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + int nAdd = nSrc; + + // In MBCS builds, pad the buffer to account for the possibility of + // some 3 byte characters. Not perfect but should get most cases. + +#ifdef SS_MBCS + nAdd = static_cast(static_cast(nAdd) * 1.3); + sDst.resize(nDst+nAdd+1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nAdd, sSrc.c_str(), nSrc); + sDst.resize(nDst + sslen(szCvt)); +#else + sDst.resize(nDst+nAdd+1); + StdCodeCvt(const_cast(sDst.data()+nDst), nAdd, sSrc.c_str(), nSrc); + sDst.resize(nDst + nAdd); +#endif + } +} +template +inline void ssadd(typename std::basic_string& sDst, const typename std::basic_string& sSrc) +{ + sDst += sSrc; +} +inline void ssadd(std::string& sDst, PCWSTR pW) +{ + int nSrc = sslen(pW); + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + int nAdd = nSrc; + +#ifdef SS_MBCS + nAdd = static_cast(static_cast(nAdd) * 1.3); + sDst.resize(nDst + nAdd + 1); + PCSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nAdd, pW, nSrc); + sDst.resize(nDst + sslen(szCvt)); +#else + sDst.resize(nDst + nAdd + 1); + StdCodeCvt(const_cast(sDst.data()+nDst), nAdd, pW, nSrc); + sDst.resize(nDst + nSrc); +#endif + } +} +template +inline void ssadd(typename std::basic_string& sDst, const T *pA) +{ + if ( pA ) + { + // If the string being added is our internal string or a part of our + // internal string, then we must NOT do any reallocation without + // first copying that string to another object (since we're using a + // direct pointer) + + if ( pA >= sDst.c_str() && pA <= sDst.c_str()+sDst.length()) + { + if ( sDst.capacity() <= sDst.size()+sslen(pA) ) + sDst.append(std::basic_string(pA)); + else + sDst.append(pA); + } + else + { + sDst.append(pA); + } + } +} +inline void ssadd(std::wstring& sDst, const std::string& sSrc) +{ + if ( !sSrc.empty() ) + { + int nSrc = static_cast(sSrc.size()); + int nDst = static_cast(sDst.size()); + + sDst.resize(nDst + nSrc + 1); +#ifdef SS_MBCS + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nSrc, sSrc.c_str(), nSrc+1); + sDst.resize(nDst + sslen(szCvt)); +#else + StdCodeCvt(const_cast(sDst.data()+nDst), nSrc, sSrc.c_str(), nSrc+1); + sDst.resize(nDst + nSrc); +#endif + } +} +inline void ssadd(std::wstring& sDst, PCSTR pA) +{ + int nSrc = sslen(pA); + + if ( nSrc > 0 ) + { + int nDst = static_cast(sDst.size()); + + sDst.resize(nDst + nSrc + 1); +#ifdef SS_MBCS + PCWSTR szCvt = StdCodeCvt(const_cast(sDst.data()+nDst), + nSrc, pA, nSrc+1); + sDst.resize(nDst + sslen(szCvt)); +#else + StdCodeCvt(const_cast(sDst.data()+nDst), nSrc, pA, nSrc+1); + sDst.resize(nDst + nSrc); +#endif + } +} + +// ----------------------------------------------------------------------------- +// sscmp: comparison (case sensitive, not affected by locale) +// ----------------------------------------------------------------------------- +template +inline int sscmp(const CT* pA1, const CT* pA2) +{ + CT f; + CT l; + + do + { + f = *(pA1++); + l = *(pA2++); + } while ( (f) && (f == l) ); + + return (int)(f - l); +} + +// ----------------------------------------------------------------------------- +// ssicmp: comparison (case INsensitive, not affected by locale) +// ----------------------------------------------------------------------------- +template +inline int ssicmp(const CT* pA1, const CT* pA2) +{ + // Using the "C" locale = "not affected by locale" + + std::locale loc = std::locale::classic(); + const std::ctype& ct = SS_USE_FACET(loc, std::ctype); + CT f; + CT l; + + do + { + f = ct.tolower(*(pA1++)); + l = ct.tolower(*(pA2++)); + } while ( (f) && (f == l) ); + + return (int)(f - l); +} + +// ----------------------------------------------------------------------------- +// ssupr/sslwr: Uppercase/Lowercase conversion functions +// ----------------------------------------------------------------------------- + +template +inline void sslwr(CT* pT, size_t nLen, const std::locale& loc=std::locale()) +{ + SS_USE_FACET(loc, std::ctype).tolower(pT, pT+nLen); +} +template +inline void ssupr(CT* pT, size_t nLen, const std::locale& loc=std::locale()) +{ + SS_USE_FACET(loc, std::ctype).toupper(pT, pT+nLen); +} + +// ----------------------------------------------------------------------------- +// vsprintf/vswprintf or _vsnprintf/_vsnwprintf equivalents. In standard +// builds we can't use _vsnprintf/_vsnwsprintf because they're MS extensions. +// +// ----------------------------------------------------------------------------- +// Borland's headers put some ANSI "C" functions in the 'std' namespace. +// Promote them to the global namespace so we can use them here. + +#if defined(__BORLANDC__) + using std::vsprintf; + using std::vswprintf; +#endif + + // GNU is supposed to have vsnprintf and vsnwprintf. But only the newer + // distributions do. + +#if defined(__GNUC__) + + inline int ssvsprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + return vsnprintf(pA, nCount, pFmtA, vl); + } + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + return vswprintf(pW, nCount, pFmtW, vl); + } + + // Microsofties can use +#elif defined(_MSC_VER) && !defined(SS_ANSI) + + inline int ssvsprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + return _vsnprintf(pA, nCount, pFmtA, vl); + } + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + return _vsnwprintf(pW, nCount, pFmtW, vl); + } + +#elif defined (SS_DANGEROUS_FORMAT) // ignore buffer size parameter if needed? + + inline int ssvsprintf(PSTR pA, size_t /*nCount*/, PCSTR pFmtA, va_list vl) + { + return vsprintf(pA, pFmtA, vl); + } + + inline int ssvsprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + // JMO: Some distributions of the "C" have a version of vswprintf that + // takes 3 arguments (e.g. Microsoft, Borland, GNU). Others have a + // version which takes 4 arguments (an extra "count" argument in the + // second position. The best stab I can take at this so far is that if + // you are NOT running with MS, Borland, or GNU, then I'll assume you + // have the version that takes 4 arguments. + // + // I'm sure that these checks don't catch every platform correctly so if + // you get compiler errors on one of the lines immediately below, it's + // probably because your implemntation takes a different number of + // arguments. You can comment out the offending line (and use the + // alternate version) or you can figure out what compiler flag to check + // and add that preprocessor check in. Regardless, if you get an error + // on these lines, I'd sure like to hear from you about it. + // + // Thanks to Ronny Schulz for the SGI-specific checks here. + +// #if !defined(__MWERKS__) && !defined(__SUNPRO_CC_COMPAT) && !defined(__SUNPRO_CC) + #if !defined(_MSC_VER) \ + && !defined (__BORLANDC__) \ + && !defined(__GNUC__) \ + && !defined(__sgi) + + return vswprintf(pW, nCount, pFmtW, vl); + + // suddenly with the current SGI 7.3 compiler there is no such function as + // vswprintf and the substitute needs explicit casts to compile + + #elif defined(__sgi) + + nCount; + return vsprintf( (char *)pW, (char *)pFmtW, vl); + + #else + + nCount; + return vswprintf(pW, pFmtW, vl); + + #endif + + } + +#endif + + // GOT COMPILER PROBLEMS HERE? + // --------------------------- + // Does your compiler choke on one or more of the following 2 functions? It + // probably means that you don't have have either vsnprintf or vsnwprintf in + // your version of the CRT. This is understandable since neither is an ANSI + // "C" function. However it still leaves you in a dilemma. In order to make + // this code build, you're going to have to to use some non-length-checked + // formatting functions that every CRT has: vsprintf and vswprintf. + // + // This is very dangerous. With the proper erroneous (or malicious) code, it + // can lead to buffer overlows and crashing your PC. Use at your own risk + // In order to use them, just #define SS_DANGEROUS_FORMAT at the top of + // this file. + // + // Even THEN you might not be all the way home due to some non-conforming + // distributions. More on this in the comments below. + + inline int ssnprintf(PSTR pA, size_t nCount, PCSTR pFmtA, va_list vl) + { + #ifdef _MSC_VER + return _vsnprintf(pA, nCount, pFmtA, vl); + #else + return vsnprintf(pA, nCount, pFmtA, vl); + #endif + } + inline int ssnprintf(PWSTR pW, size_t nCount, PCWSTR pFmtW, va_list vl) + { + #ifdef _MSC_VER + return _vsnwprintf(pW, nCount, pFmtW, vl); + #else + return vswprintf(pW, nCount, pFmtW, vl); + #endif + } + + + + +// ----------------------------------------------------------------------------- +// ssload: Type safe, overloaded ::LoadString wrappers +// There is no equivalent of these in non-Win32-specific builds. However, I'm +// thinking that with the message facet, there might eventually be one +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline int ssload(HMODULE hInst, UINT uId, PSTR pBuf, int nMax) + { + return ::LoadStringA(hInst, uId, pBuf, nMax); + } + inline int ssload(HMODULE hInst, UINT uId, PWSTR pBuf, int nMax) + { + return ::LoadStringW(hInst, uId, pBuf, nMax); + } +#if defined ( _MSC_VER ) && ( _MSC_VER >= 1500 ) + inline int ssload(HMODULE hInst, UINT uId, uint16_t *pBuf, int nMax) + { + return 0; + } + inline int ssload(HMODULE hInst, UINT uId, uint32_t *pBuf, int nMax) + { + return 0; + } +#endif +#endif + + +// ----------------------------------------------------------------------------- +// sscoll/ssicoll: Collation wrappers +// Note -- with MSVC I have reversed the arguments order here because the +// functions appear to return the opposite of what they should +// ----------------------------------------------------------------------------- +#ifndef SS_NO_LOCALE +template +inline int sscoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::collate& coll = + SS_USE_FACET(std::locale(), std::collate); + + return coll.compare(sz2, sz2+nLen2, sz1, sz1+nLen1); +} +template +inline int ssicoll(const CT* sz1, int nLen1, const CT* sz2, int nLen2) +{ + const std::locale loc; + const std::collate& coll = SS_USE_FACET(loc, std::collate); + + // Some implementations seem to have trouble using the collate<> + // facet typedefs so we'll just default to basic_string and hope + // that's what the collate facet uses (which it generally should) + +// std::collate::string_type s1(sz1); +// std::collate::string_type s2(sz2); + const std::basic_string sEmpty; + std::basic_string s1(sz1 ? sz1 : sEmpty.c_str()); + std::basic_string s2(sz2 ? sz2 : sEmpty.c_str()); + + sslwr(const_cast(s1.c_str()), nLen1, loc); + sslwr(const_cast(s2.c_str()), nLen2, loc); + return coll.compare(s2.c_str(), s2.c_str()+nLen2, + s1.c_str(), s1.c_str()+nLen1); +} +#endif + + +// ----------------------------------------------------------------------------- +// ssfmtmsg: FormatMessage equivalents. Needed because I added a CString facade +// Again -- no equivalent of these on non-Win32 builds but their might one day +// be one if the message facet gets implemented +// ----------------------------------------------------------------------------- +#if defined (SS_WIN32) && !defined(SS_ANSI) + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageA(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } + inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId, + DWORD dwLangId, PWSTR pBuf, DWORD nSize, + va_list* vlArgs) + { + return FormatMessageW(dwFlags, pSrc, dwMsgId, dwLangId, + pBuf, nSize,vlArgs); + } +#else +#endif + + + +// FUNCTION: sscpy. Copies up to 'nMax' characters from pSrc to pDst. +// ----------------------------------------------------------------------------- +// FUNCTION: sscpy +// inline int sscpy(PSTR pDst, PCSTR pSrc, int nMax=-1); +// inline int sscpy(PUSTR pDst, PCSTR pSrc, int nMax=-1) +// inline int sscpy(PSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCWSTR pSrc, int nMax=-1); +// inline int sscpy(PWSTR pDst, PCSTR pSrc, int nMax=-1); +// +// DESCRIPTION: +// This function is very much (but not exactly) like strcpy. These +// overloads simplify copying one C-style string into another by allowing +// the caller to specify two different types of strings if necessary. +// +// The strings must NOT overlap +// +// "Character" is expressed in terms of the destination string, not +// the source. If no 'nMax' argument is supplied, then the number of +// characters copied will be sslen(pSrc). A NULL terminator will +// also be added so pDst must actually be big enough to hold nMax+1 +// characters. The return value is the number of characters copied, +// not including the NULL terminator. +// +// PARAMETERS: +// pSrc - the string to be copied FROM. May be a char based string, an +// MBCS string (in Win32 builds) or a wide string (wchar_t). +// pSrc - the string to be copied TO. Also may be either MBCS or wide +// nMax - the maximum number of characters to be copied into szDest. Note +// that this is expressed in whatever a "character" means to pDst. +// If pDst is a wchar_t type string than this will be the maximum +// number of wchar_ts that my be copied. The pDst string must be +// large enough to hold least nMaxChars+1 characters. +// If the caller supplies no argument for nMax this is a signal to +// the routine to copy all the characters in pSrc, regardless of +// how long it is. +// +// RETURN VALUE: none +// ----------------------------------------------------------------------------- + +template +inline int sscpycvt(CT1* pDst, const CT2* pSrc, int nMax) +{ + // Note -- we assume pDst is big enough to hold pSrc. If not, we're in + // big trouble. No bounds checking. Caveat emptor. + + int nSrc = sslen(pSrc); + + const CT1* szCvt = StdCodeCvt(pDst, nMax, pSrc, nSrc); + + // If we're copying the same size characters, then all the "code convert" + // just did was basically memcpy so the #of characters copied is the same + // as the number requested. I should probably specialize this function + // template to achieve this purpose as it is silly to do a runtime check + // of a fact known at compile time. I'll get around to it. + + return sslen(szCvt); +} + +template +inline int sscpycvt(T* pDst, const T* pSrc, int nMax) +{ + int nCount = nMax; + for (; nCount > 0 && *pSrc; ++pSrc, ++pDst, --nCount) + std::basic_string::traits_type::assign(*pDst, *pSrc); + + *pDst = 0; + return nMax - nCount; +} + +inline int sscpycvt(PWSTR pDst, PCSTR pSrc, int nMax) +{ + // Note -- we assume pDst is big enough to hold pSrc. If not, we're in + // big trouble. No bounds checking. Caveat emptor. + + const PWSTR szCvt = StdCodeCvt(pDst, nMax, pSrc, nMax); + return sslen(szCvt); +} + +template +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax, int nLen) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, nLen)); +} +template +inline int sscpy(CT1* pDst, const CT2* pSrc, int nMax) +{ + return sscpycvt(pDst, pSrc, SSMIN(nMax, sslen(pSrc))); +} +template +inline int sscpy(CT1* pDst, const CT2* pSrc) +{ + return sscpycvt(pDst, pSrc, sslen(pSrc)); +} +template +inline int sscpy(CT1* pDst, const std::basic_string& sSrc, int nMax) +{ + return sscpycvt(pDst, sSrc.c_str(), SSMIN(nMax, (int)sSrc.length())); +} +template +inline int sscpy(CT1* pDst, const std::basic_string& sSrc) +{ + return sscpycvt(pDst, sSrc.c_str(), (int)sSrc.length()); +} + +#ifdef SS_INC_COMDEF + template + inline int sscpy(CT1* pDst, const _bstr_t& bs, int nMax) + { + return sscpycvt(pDst, static_cast(bs), + SSMIN(nMax, static_cast(bs.length()))); + } + template + inline int sscpy(CT1* pDst, const _bstr_t& bs) + { + return sscpy(pDst, bs, static_cast(bs.length())); + } +#endif + + +// ----------------------------------------------------------------------------- +// Functional objects for changing case. They also let you pass locales +// ----------------------------------------------------------------------------- + +#ifdef SS_NO_LOCALE + template + struct SSToUpper : public std::unary_function + { + inline CT operator()(const CT& t) const + { + return sstoupper(t); + } + }; + template + struct SSToLower : public std::unary_function + { + inline CT operator()(const CT& t) const + { + return sstolower(t); + } + }; +#else + template + struct SSToUpper : public std::binary_function + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstoupper(t, loc); + } + }; + template + struct SSToLower : public std::binary_function + { + inline CT operator()(const CT& t, const std::locale& loc) const + { + return sstolower(t, loc); + } + }; +#endif + +// This struct is used for TrimRight() and TrimLeft() function implementations. +//template +//struct NotSpace : public std::unary_function +//{ +// const std::locale& loc; +// inline NotSpace(const std::locale& locArg) : loc(locArg) {} +// inline bool operator() (CT t) { return !std::isspace(t, loc); } +//}; +template +struct NotSpace : public std::unary_function +{ + // DINKUMWARE BUG: + // Note -- using std::isspace in a COM DLL gives us access violations + // because it causes the dynamic addition of a function to be called + // when the library shuts down. Unfortunately the list is maintained + // in DLL memory but the function is in static memory. So the COM DLL + // goes away along with the function that was supposed to be called, + // and then later when the DLL CRT shuts down it unloads the list and + // tries to call the long-gone function. + // This is DinkumWare's implementation problem. If you encounter this + // problem, you may replace the calls here with good old isspace() and + // iswspace() from the CRT unless they specify SS_ANSI + +#ifdef SS_NO_LOCALE + + bool operator() (CT t) const { return !ssisspace(t); } + +#else + const std::locale loc; + NotSpace(const std::locale& locArg=std::locale()) : loc(locArg) {} + bool operator() (CT t) const { return !std::isspace(t, loc); } +#endif +}; + + + + +// Now we can define the template (finally!) +// ============================================================================= +// TEMPLATE: CStdStr +// template class CStdStr : public std::basic_string +// +// REMARKS: +// This template derives from basic_string and adds some MFC CString- +// like functionality +// +// Basically, this is my attempt to make Standard C++ library strings as +// easy to use as the MFC CString class. +// +// Note that although this is a template, it makes the assumption that the +// template argument (CT, the character type) is either char or wchar_t. +// ============================================================================= + +//#define CStdStr _SS // avoid compiler warning 4786 + +// template ARG& FmtArg(ARG& arg) { return arg; } +// PCSTR FmtArg(const std::string& arg) { return arg.c_str(); } +// PCWSTR FmtArg(const std::wstring& arg) { return arg.c_str(); } + +template +struct FmtArg +{ + explicit FmtArg(const ARG& arg) : a_(arg) {} + const ARG& operator()() const { return a_; } + const ARG& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; + +template +class CStdStr : public std::basic_string +{ + // Typedefs for shorter names. Using these names also appears to help + // us avoid some ambiguities that otherwise arise on some platforms + + #define MYBASE std::basic_string // my base class + //typedef typename std::basic_string MYBASE; // my base class + typedef CStdStr MYTYPE; // myself + typedef typename MYBASE::const_pointer PCMYSTR; // PCSTR or PCWSTR + typedef typename MYBASE::pointer PMYSTR; // PSTR or PWSTR + typedef typename MYBASE::iterator MYITER; // my iterator type + typedef typename MYBASE::const_iterator MYCITER; // you get the idea... + typedef typename MYBASE::reverse_iterator MYRITER; + typedef typename MYBASE::size_type MYSIZE; + typedef typename MYBASE::value_type MYVAL; + typedef typename MYBASE::allocator_type MYALLOC; + +public: + // shorthand conversion from PCTSTR to string resource ID + #define SSRES(pctstr) LOWORD(reinterpret_cast(pctstr)) + + bool TryLoad(const void* pT) + { + bool bLoaded = false; + +#if defined(SS_WIN32) && !defined(SS_ANSI) + if ( ( pT != NULL ) && SS_IS_INTRESOURCE(pT) ) + { + UINT nId = LOWORD(reinterpret_cast(pT)); + if ( !LoadString(nId) ) + { + TRACE(_T("Can't load string %u\n"), SSRES(pT)); + } + bLoaded = true; + } +#endif + + return bLoaded; + } + + + // CStdStr inline constructors + CStdStr() + { + } + + CStdStr(const MYTYPE& str) : MYBASE(SSREF(str)) + { + } + + CStdStr(const std::string& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(const std::wstring& str) + { + ssasn(*this, SSREF(str)); + } + + CStdStr(PCMYSTR pT, MYSIZE n) : MYBASE(pT, n) + { + } + +#ifdef SS_UNSIGNED + CStdStr(PCUSTR pU) + { + *this = reinterpret_cast(pU); + } +#endif + + CStdStr(PCSTR pA) + { + #ifdef SS_ANSI + *this = pA; + #else + if ( !TryLoad(pA) ) + *this = pA; + #endif + } + + CStdStr(PCWSTR pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(uint16_t* pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(uint32_t* pW) + { + #ifdef SS_ANSI + *this = pW; + #else + if ( !TryLoad(pW) ) + *this = pW; + #endif + } + + CStdStr(MYCITER first, MYCITER last) + : MYBASE(first, last) + { + } + + CStdStr(MYSIZE nSize, MYVAL ch, const MYALLOC& al=MYALLOC()) + : MYBASE(nSize, ch, al) + { + } + + #ifdef SS_INC_COMDEF + CStdStr(const _bstr_t& bstr) + { + if ( bstr.length() > 0 ) + this->append(static_cast(bstr), bstr.length()); + } + #endif + + // CStdStr inline assignment operators -- the ssasn function now takes care + // of fixing the MSVC assignment bug (see knowledge base article Q172398). + MYTYPE& operator=(const MYTYPE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::string& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(const std::wstring& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& operator=(PCSTR pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(PCWSTR pW) + { + ssasn(*this, pW); + return *this; + } + +#ifdef SS_UNSIGNED + MYTYPE& operator=(PCUSTR pU) + { + ssasn(*this, reinterpret_cast(pU)); + return *this; + } +#endif + + MYTYPE& operator=(uint16_t* pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(uint32_t* pA) + { + ssasn(*this, pA); + return *this; + } + + MYTYPE& operator=(CT t) + { + Q172398(*this); + this->assign(1, t); + return *this; + } + + #ifdef SS_INC_COMDEF + MYTYPE& operator=(const _bstr_t& bstr) + { + if ( bstr.length() > 0 ) + { + this->assign(static_cast(bstr), bstr.length()); + return *this; + } + else + { + this->erase(); + return *this; + } + } + #endif + + + // Overloads also needed to fix the MSVC assignment bug (KB: Q172398) + // *** Thanks to Pete The Plumber for catching this one *** + // They also are compiled if you have explicitly turned off refcounting + #if ( defined(_MSC_VER) && ( _MSC_VER < 1200 ) ) || defined(SS_NO_REFCOUNT) + + MYTYPE& assign(const MYTYPE& str) + { + Q172398(*this); + sscpy(GetBuffer(str.size()+1), SSREF(str)); + this->ReleaseBuffer(str.size()); + return *this; + } + + MYTYPE& assign(const MYTYPE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Poll�hne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + MYTYPE strTemp(str.c_str()+nStart, nChars); + Q172398(*this); + this->assign(strTemp); + return *this; + } + + MYTYPE& assign(const MYBASE& str) + { + ssasn(*this, str); + return *this; + } + + MYTYPE& assign(const MYBASE& str, MYSIZE nStart, MYSIZE nChars) + { + // This overload of basic_string::assign is supposed to assign up to + // or the NULL terminator, whichever comes first. Since we + // are about to call a less forgiving overload (in which + // must be a valid length), we must adjust the length here to a safe + // value. Thanks to Ullrich Poll�hne for catching this bug + + nChars = SSMIN(nChars, str.length() - nStart); + + // Watch out for assignment to self + + if ( this == &str ) + { + MYTYPE strTemp(str.c_str() + nStart, nChars); + static_cast(this)->assign(strTemp); + } + else + { + Q172398(*this); + static_cast(this)->assign(str.c_str()+nStart, nChars); + } + return *this; + } + + MYTYPE& assign(const CT* pC, MYSIZE nChars) + { + // Q172398 only fix -- erase before assigning, but not if we're + // assigning from our own buffer + + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + if ( !this->empty() && + ( pC < this->data() || pC > this->data() + this->capacity() ) ) + { + this->erase(); + } + #endif + Q172398(*this); + static_cast(this)->assign(pC, nChars); + return *this; + } + + MYTYPE& assign(MYSIZE nChars, MYVAL val) + { + Q172398(*this); + static_cast(this)->assign(nChars, val); + return *this; + } + + MYTYPE& assign(const CT* pT) + { + return this->assign(pT, MYBASE::traits_type::length(pT)); + } + + MYTYPE& assign(MYCITER iterFirst, MYCITER iterLast) + { + #if defined ( _MSC_VER ) && ( _MSC_VER < 1200 ) + // Q172398 fix. don't call erase() if we're assigning from ourself + if ( iterFirst < this->begin() || + iterFirst > this->begin() + this->size() ) + { + this->erase() + } + #endif + this->replace(this->begin(), this->end(), iterFirst, iterLast); + return *this; + } + #endif + + + // ------------------------------------------------------------------------- + // CStdStr inline concatenation. + // ------------------------------------------------------------------------- + MYTYPE& operator+=(const MYTYPE& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::string& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(const std::wstring& str) + { + ssadd(*this, str); + return *this; + } + + MYTYPE& operator+=(PCSTR pA) + { + ssadd(*this, pA); + return *this; + } + + MYTYPE& operator+=(PCWSTR pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(uint16_t* pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(uint32_t* pW) + { + ssadd(*this, pW); + return *this; + } + + MYTYPE& operator+=(CT t) + { + this->append(1, t); + return *this; + } + #ifdef SS_INC_COMDEF // if we have _bstr_t, define a += for it too. + MYTYPE& operator+=(const _bstr_t& bstr) + { + return this->operator+=(static_cast(bstr)); + } + #endif + + + // ------------------------------------------------------------------------- + // Case changing functions + // ------------------------------------------------------------------------- + + MYTYPE& ToUpper(const std::locale& loc=std::locale()) + { + // Note -- if there are any MBCS character sets in which the lowercase + // form a character takes up a different number of bytes than the + // uppercase form, this would probably not work... + + std::transform(this->begin(), + this->end(), + this->begin(), +#ifdef SS_NO_LOCALE + SSToUpper()); +#else + std::bind2nd(SSToUpper(), loc)); +#endif + + // ...but if it were, this would probably work better. Also, this way + // seems to be a bit faster when anything other then the "C" locale is + // used... + +// if ( !empty() ) +// { +// ssupr(this->GetBuf(), this->size(), loc); +// this->RelBuf(); +// } + + return *this; + } + + MYTYPE& ToLower(const std::locale& loc=std::locale()) + { + // Note -- if there are any MBCS character sets in which the lowercase + // form a character takes up a different number of bytes than the + // uppercase form, this would probably not work... + + std::transform(this->begin(), + this->end(), + this->begin(), +#ifdef SS_NO_LOCALE + SSToLower()); +#else + std::bind2nd(SSToLower(), loc)); +#endif + + // ...but if it were, this would probably work better. Also, this way + // seems to be a bit faster when anything other then the "C" locale is + // used... + +// if ( !empty() ) +// { +// sslwr(this->GetBuf(), this->size(), loc); +// this->RelBuf(); +// } + return *this; + } + + + MYTYPE& Normalize() + { + return Trim().ToLower(); + } + + + // ------------------------------------------------------------------------- + // CStdStr -- Direct access to character buffer. In the MS' implementation, + // the at() function that we use here also calls _Freeze() providing us some + // protection from multithreading problems associated with ref-counting. + // In VC 7 and later, of course, the ref-counting stuff is gone. + // ------------------------------------------------------------------------- + + CT* GetBuf(int nMinLen=-1) + { + if ( static_cast(this->size()) < nMinLen ) + this->resize(static_cast(nMinLen)); + + return this->empty() ? const_cast(this->data()) : &(this->at(0)); + } + + CT* SetBuf(int nLen) + { + nLen = ( nLen > 0 ? nLen : 0 ); + if ( this->capacity() < 1 && nLen == 0 ) + this->resize(1); + + this->resize(static_cast(nLen)); + return const_cast(this->data()); + } + void RelBuf(int nNewLen=-1) + { + this->resize(static_cast(nNewLen > -1 ? nNewLen : + sslen(this->c_str()))); + } + + void BufferRel() { RelBuf(); } // backwards compatability + CT* Buffer() { return GetBuf(); } // backwards compatability + CT* BufferSet(int nLen) { return SetBuf(nLen);}// backwards compatability + + bool Equals(const CT* pT, bool bUseCase=false) const + { + return 0 == (bUseCase ? this->compare(pT) : ssicmp(this->c_str(), pT)); + } + + // ------------------------------------------------------------------------- + // FUNCTION: CStdStr::Load + // REMARKS: + // Loads string from resource specified by nID + // + // PARAMETERS: + // nID - resource Identifier. Purely a Win32 thing in this case + // + // RETURN VALUE: + // true if successful, false otherwise + // ------------------------------------------------------------------------- + +#ifndef SS_ANSI + + bool Load(UINT nId, HMODULE hModule=NULL) + { + bool bLoaded = false; // set to true of we succeed. + + #ifdef _MFC_VER // When in Rome (or MFC land)... + + // If they gave a resource handle, use it. Note - this is archaic + // and not really what I would recommend. But then again, in MFC + // land, you ought to be using CString for resources anyway since + // it walks the resource chain for you. + + HMODULE hModuleOld = NULL; + + if ( NULL != hModule ) + { + hModuleOld = AfxGetResourceHandle(); + AfxSetResourceHandle(hModule); + } + + // ...load the string + + CString strRes; + bLoaded = FALSE != strRes.LoadString(nId); + + // ...and if we set the resource handle, restore it. + + if ( NULL != hModuleOld ) + AfxSetResourceHandle(hModule); + + if ( bLoaded ) + *this = strRes; + + #else // otherwise make our own hackneyed version of CString's Load + + // Get the resource name and module handle + + if ( NULL == hModule ) + hModule = GetResourceHandle(); + + PCTSTR szName = MAKEINTRESOURCE((nId>>4)+1); // lifted + DWORD dwSize = 0; + + // No sense continuing if we can't find the resource + + HRSRC hrsrc = ::FindResource(hModule, szName, RT_STRING); + + if ( NULL == hrsrc ) + { + TRACE(_T("Cannot find resource %d: 0x%X"), nId, ::GetLastError()); + } + else if ( 0 == (dwSize = ::SizeofResource(hModule, hrsrc) / sizeof(CT))) + { + TRACE(_T("Cant get size of resource %d 0x%X\n"),nId,GetLastError()); + } + else + { + bLoaded = 0 != ssload(hModule, nId, GetBuf(dwSize), dwSize); + ReleaseBuffer(); + } + + #endif // #ifdef _MFC_VER + + if ( !bLoaded ) + TRACE(_T("String not loaded 0x%X\n"), ::GetLastError()); + + return bLoaded; + } + +#endif // #ifdef SS_ANSI + + // ------------------------------------------------------------------------- + // FUNCTION: CStdStr::Format + // void _cdecl Formst(CStdStringA& PCSTR szFormat, ...) + // void _cdecl Format(PCSTR szFormat); + // + // DESCRIPTION: + // This function does sprintf/wsprintf style formatting on CStdStringA + // objects. It looks a lot like MFC's CString::Format. Some people + // might even call this identical. Fortunately, these people are now + // dead... heh heh. + // + // PARAMETERS: + // nId - ID of string resource holding the format string + // szFormat - a PCSTR holding the format specifiers + // argList - a va_list holding the arguments for the format specifiers. + // + // RETURN VALUE: None. + // ------------------------------------------------------------------------- + // formatting (using wsprintf style formatting) + + // If they want a Format() function that safely handles string objects + // without casting + +#ifdef SS_SAFE_FORMAT + + // Question: Joe, you wacky coder you, why do you have so many overloads + // of the Format() function + // Answer: One reason only - CString compatability. In short, by making + // the Format() function a template this way, I can do strong typing + // and allow people to pass CStdString arguments as fillers for + // "%s" format specifiers without crashing their program! The downside + // is that I need to overload on the number of arguments. If you are + // passing more arguments than I have listed below in any of my + // overloads, just add another one. + // + // Yes, yes, this is really ugly. In essence what I am doing here is + // protecting people from a bad (and incorrect) programming practice + // that they should not be doing anyway. I am protecting them from + // themselves. Why am I doing this? Well, if you had any idea the + // number of times I've been emailed by people about this + // "incompatability" in my code, you wouldn't ask. + + void Fmt(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + FormatV(szFmt, argList); + va_end(argList); + } + +#ifndef SS_ANSI + + void Format(UINT nId) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + this->swap(strFmt); + } + template + void Format(UINT nId, const A1& v) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + Fmt(strFmt, FmtArg(v)()); + } + template + void Format(UINT nId, const A1& v1, const A2& v2) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)()); + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(),FmtArg(v5)(), + FmtArg(v6)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(),FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(),FmtArg(v10)(),FmtArg(v11)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)(),FmtArg(v14)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(), FmtArg(v16)()); + } + } + template + void Format(UINT nId, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16, const A17& v17) + { + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + { + Fmt(strFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(),FmtArg(v16)(),FmtArg(v17)()); + } + } + +#endif // #ifndef SS_ANSI + + // ...now the other overload of Format: the one that takes a string literal + + void Format(const CT* szFmt) + { + *this = szFmt; + } + template + void Format(const CT* szFmt, const A1& v) + { + Fmt(szFmt, FmtArg(v)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(),FmtArg(v10)(),FmtArg(v11)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(), FmtArg(v13)(),FmtArg(v14)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(), FmtArg(v16)()); + } + template + void Format(const CT* szFmt, const A1& v1, const A2& v2, const A3& v3, + const A4& v4, const A5& v5, const A6& v6, const A7& v7, + const A8& v8, const A9& v9, const A10& v10, const A11& v11, + const A12& v12, const A13& v13, const A14& v14, const A15& v15, + const A16& v16, const A17& v17) + { + Fmt(szFmt, FmtArg(v1)(), FmtArg(v2)(), + FmtArg(v3)(), FmtArg(v4)(), FmtArg(v5)(), + FmtArg(v6)(), FmtArg(v7)(), FmtArg(v8)(), + FmtArg(v9)(), FmtArg(v10)(),FmtArg(v11)(), + FmtArg(v12)(),FmtArg(v13)(),FmtArg(v14)(), + FmtArg(v15)(),FmtArg(v16)(),FmtArg(v17)()); + } + +#else // #ifdef SS_SAFE_FORMAT + + +#ifndef SS_ANSI + + void Format(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + MYTYPE strFmt; + if ( strFmt.Load(nId) ) + FormatV(strFmt, argList); + + va_end(argList); + } + +#endif // #ifdef SS_ANSI + + void Format(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + FormatV(szFmt, argList); + va_end(argList); + } + +#endif // #ifdef SS_SAFE_FORMAT + + void AppendFormat(const CT* szFmt, ...) + { + va_list argList; + va_start(argList, szFmt); + AppendFormatV(szFmt, argList); + va_end(argList); + } + + #define MAX_FMT_TRIES 5 // #of times we try + #define FMT_BLOCK_SIZE 2048 // # of bytes to increment per try + #define BUFSIZE_1ST 256 + #define BUFSIZE_2ND 512 + #define STD_BUF_SIZE 1024 + + // an efficient way to add formatted characters to the string. You may only + // add up to STD_BUF_SIZE characters at a time, though + void AppendFormatV(const CT* szFmt, va_list argList) + { + CT szBuf[STD_BUF_SIZE]; + int nLen = ssnprintf(szBuf, STD_BUF_SIZE-1, szFmt, argList); + + if ( 0 < nLen ) + this->append(szBuf, nLen); + } + + // ------------------------------------------------------------------------- + // FUNCTION: FormatV + // void FormatV(PCSTR szFormat, va_list, argList); + // + // DESCRIPTION: + // This function formats the string with sprintf style format-specs. + // It makes a general guess at required buffer size and then tries + // successively larger buffers until it finds one big enough or a + // threshold (MAX_FMT_TRIES) is exceeded. + // + // PARAMETERS: + // szFormat - a PCSTR holding the format of the output + // argList - a Microsoft specific va_list for variable argument lists + // + // RETURN VALUE: + // ------------------------------------------------------------------------- + + // NOTE: Changed by JM to actually function under non-win32, + // and to remove the upper limit on size. + void FormatV(const CT* szFormat, va_list argList) + { + // try and grab a sufficient buffersize + int nChars = FMT_BLOCK_SIZE; + va_list argCopy; + + CT *p = reinterpret_cast(malloc(sizeof(CT)*nChars)); + if (!p) return; + + while (1) + { + va_copy(argCopy, argList); + + int nActual = ssvsprintf(p, nChars, szFormat, argCopy); + /* If that worked, return the string. */ + if (nActual > -1 && nActual < nChars) + { /* make sure it's NULL terminated */ + p[nActual] = '\0'; + this->assign(p, nActual); + free(p); + va_end(argCopy); + return; + } + /* Else try again with more space. */ + if (nActual > -1) /* glibc 2.1 */ + nChars = nActual + 1; /* precisely what is needed */ + else /* glibc 2.0 */ + nChars *= 2; /* twice the old size */ + + CT *np = reinterpret_cast(realloc(p, sizeof(CT)*nChars)); + if (np == NULL) + { + free(p); + va_end(argCopy); + return; // failed :( + } + p = np; + va_end(argCopy); + } + } + + // ------------------------------------------------------------------------- + // CString Facade Functions: + // + // The following methods are intended to allow you to use this class as a + // near drop-in replacement for CString. + // ------------------------------------------------------------------------- + #ifdef SS_WIN32 + BSTR AllocSysString() const + { + ostring os; + ssasn(os, *this); + return ::SysAllocString(os.c_str()); + } + #endif + +#ifndef SS_NO_LOCALE + int Collate(PCMYSTR szThat) const + { + return sscoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } + + int CollateNoCase(PCMYSTR szThat) const + { + return ssicoll(this->c_str(), this->length(), szThat, sslen(szThat)); + } +#endif + int Compare(PCMYSTR szThat) const + { + return this->compare(szThat); + } + + int CompareNoCase(PCMYSTR szThat) const + { + return ssicmp(this->c_str(), szThat); + } + + int Delete(int nIdx, int nCount=1) + { + if ( nIdx < 0 ) + nIdx = 0; + + if ( nIdx < this->GetLength() ) + this->erase(static_cast(nIdx), static_cast(nCount)); + + return GetLength(); + } + + void Empty() + { + this->erase(); + } + + int Find(CT ch) const + { + MYSIZE nIdx = this->find_first_of(ch); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(PCMYSTR szSub) const + { + MYSIZE nIdx = this->find(szSub); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(CT ch, int nStart) const + { + // CString::Find docs say add 1 to nStart when it's not zero + // CString::Find code doesn't do that however. We'll stick + // with what the code does + + MYSIZE nIdx = this->find_first_of(ch, static_cast(nStart)); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int Find(PCMYSTR szSub, int nStart) const + { + // CString::Find docs say add 1 to nStart when it's not zero + // CString::Find code doesn't do that however. We'll stick + // with what the code does + + MYSIZE nIdx = this->find(szSub, static_cast(nStart)); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + int FindOneOf(PCMYSTR szCharSet) const + { + MYSIZE nIdx = this->find_first_of(szCharSet); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + +#ifndef SS_ANSI + void FormatMessage(PCMYSTR szFormat, ...) throw(std::exception) + { + va_list argList; + va_start(argList, szFormat); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + szFormat, 0, 0, + reinterpret_cast(&szTemp), 0, &argList) == 0 || + szTemp == 0 ) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } + + void FormatMessage(UINT nFormatId, ...) throw(std::exception) + { + MYTYPE sFormat; + VERIFY(sFormat.LoadString(nFormatId)); + va_list argList; + va_start(argList, nFormatId); + PMYSTR szTemp; + if ( ssfmtmsg(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, + sFormat, 0, 0, + reinterpret_cast(&szTemp), 0, &argList) == 0 || + szTemp == 0) + { + throw std::runtime_error("out of memory"); + } + *this = szTemp; + LocalFree(szTemp); + va_end(argList); + } +#endif + + // GetAllocLength -- an MSVC7 function but it costs us nothing to add it. + + int GetAllocLength() + { + return static_cast(this->capacity()); + } + + // ------------------------------------------------------------------------- + // GetXXXX -- Direct access to character buffer + // ------------------------------------------------------------------------- + CT GetAt(int nIdx) const + { + return this->at(static_cast(nIdx)); + } + + CT* GetBuffer(int nMinLen=-1) + { + return GetBuf(nMinLen); + } + + CT* GetBufferSetLength(int nLen) + { + return BufferSet(nLen); + } + + // GetLength() -- MFC docs say this is the # of BYTES but + // in truth it is the number of CHARACTERs (chars or wchar_ts) + int GetLength() const + { + return static_cast(this->length()); + } + + int Insert(int nIdx, CT ch) + { + if ( static_cast(nIdx) > this->size()-1 ) + this->append(1, ch); + else + this->insert(static_cast(nIdx), 1, ch); + + return GetLength(); + } + int Insert(int nIdx, PCMYSTR sz) + { + if ( static_cast(nIdx) >= this->size() ) + this->append(sz, static_cast(sslen(sz))); + else + this->insert(static_cast(nIdx), sz); + + return GetLength(); + } + + bool IsEmpty() const + { + return this->empty(); + } + + MYTYPE Left(int nCount) const + { + // Range check the count. + + nCount = SSMAX(0, SSMIN(nCount, static_cast(this->size()))); + return this->substr(0, static_cast(nCount)); + } + +#ifndef SS_ANSI + bool LoadString(UINT nId) + { + return this->Load(nId); + } +#endif + + void MakeLower() + { + ToLower(); + } + + void MakeReverse() + { + std::reverse(this->begin(), this->end()); + } + + void MakeUpper() + { + ToUpper(); + } + + MYTYPE Mid(int nFirst) const + { + return Mid(nFirst, this->GetLength()-nFirst); + } + + MYTYPE Mid(int nFirst, int nCount) const + { + // CString does range checking here. Since we're trying to emulate it, + // we must check too. + + if ( nFirst < 0 ) + nFirst = 0; + if ( nCount < 0 ) + nCount = 0; + + int nSize = static_cast(this->size()); + + if ( nFirst + nCount > nSize ) + nCount = nSize - nFirst; + + if ( nFirst > nSize ) + return MYTYPE(); + + ASSERT(nFirst >= 0); + ASSERT(nFirst + nCount <= nSize); + + return this->substr(static_cast(nFirst), + static_cast(nCount)); + } + + void ReleaseBuffer(int nNewLen=-1) + { + RelBuf(nNewLen); + } + + int Remove(CT ch) + { + MYSIZE nIdx = 0; + int nRemoved = 0; + while ( (nIdx=this->find_first_of(ch)) != MYBASE::npos ) + { + this->erase(nIdx, 1); + nRemoved++; + } + return nRemoved; + } + + int Replace(CT chOld, CT chNew) + { + int nReplaced = 0; + + for ( MYITER iter=this->begin(); iter != this->end(); iter++ ) + { + if ( *iter == chOld ) + { + *iter = chNew; + nReplaced++; + } + } + + return nReplaced; + } + + int Replace(PCMYSTR szOld, PCMYSTR szNew) + { + int nReplaced = 0; + MYSIZE nIdx = 0; + MYSIZE nOldLen = sslen(szOld); + + if ( 0 != nOldLen ) + { + // If the replacement string is longer than the one it replaces, this + // string is going to have to grow in size, Figure out how much + // and grow it all the way now, rather than incrementally + + MYSIZE nNewLen = sslen(szNew); + if ( nNewLen > nOldLen ) + { + int nFound = 0; + while ( nIdx < this->length() && + (nIdx=this->find(szOld, nIdx)) != MYBASE::npos ) + { + nFound++; + nIdx += nOldLen; + } + this->reserve(this->size() + nFound * (nNewLen - nOldLen)); + } + + + static const CT ch = CT(0); + PCMYSTR szRealNew = szNew == 0 ? &ch : szNew; + nIdx = 0; + + while ( nIdx < this->length() && + (nIdx=this->find(szOld, nIdx)) != MYBASE::npos ) + { + this->replace(this->begin()+nIdx, this->begin()+nIdx+nOldLen, + szRealNew); + + nReplaced++; + nIdx += nNewLen; + } + } + + return nReplaced; + } + + int ReverseFind(CT ch) const + { + MYSIZE nIdx = this->find_last_of(ch); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + // ReverseFind overload that's not in CString but might be useful + int ReverseFind(PCMYSTR szFind, MYSIZE pos=MYBASE::npos) const + { + //yuvalt - this does not compile with g++ since MYTTYPE() is different type + //MYSIZE nIdx = this->rfind(0 == szFind ? MYTYPE() : szFind, pos); + MYSIZE nIdx = this->rfind(0 == szFind ? "" : szFind, pos); + return static_cast(MYBASE::npos == nIdx ? -1 : nIdx); + } + + MYTYPE Right(int nCount) const + { + // Range check the count. + + nCount = SSMAX(0, SSMIN(nCount, static_cast(this->size()))); + return this->substr(this->size()-static_cast(nCount)); + } + + void SetAt(int nIndex, CT ch) + { + ASSERT(this->size() > static_cast(nIndex)); + this->at(static_cast(nIndex)) = ch; + } + +#ifndef SS_ANSI + BSTR SetSysString(BSTR* pbstr) const + { + ostring os; + ssasn(os, *this); + if ( !::SysReAllocStringLen(pbstr, os.c_str(), os.length()) ) + throw std::runtime_error("out of memory"); + + ASSERT(*pbstr != 0); + return *pbstr; + } +#endif + + MYTYPE SpanExcluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + + MYTYPE SpanIncluding(PCMYSTR szCharSet) const + { + MYSIZE pos = this->find_first_not_of(szCharSet); + return pos == MYBASE::npos ? *this : Left(pos); + } + +#if defined SS_WIN32 && !defined(UNICODE) && !defined(SS_ANSI) + + // CString's OemToAnsi and AnsiToOem functions are available only in + // Unicode builds. However since we're a template we also need a + // runtime check of CT and a reinterpret_cast to account for the fact + // that CStdStringW gets instantiated even in non-Unicode builds. + + void AnsiToOem() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::CharToOem(reinterpret_cast(this->c_str()), + reinterpret_cast(GetBuf())); + } + else + { + ASSERT(false); + } + } + + void OemToAnsi() + { + if ( sizeof(CT) == sizeof(char) && !empty() ) + { + ::OemToChar(reinterpret_cast(this->c_str()), + reinterpret_cast(GetBuf())); + } + else + { + ASSERT(false); + } + } + +#endif + + + // ------------------------------------------------------------------------- + // Trim and its variants + // ------------------------------------------------------------------------- + MYTYPE& Trim() + { + return TrimLeft().TrimRight(); + } + + MYTYPE& TrimLeft() + { + this->erase(this->begin(), + std::find_if(this->begin(), this->end(), NotSpace())); + + return *this; + } + + MYTYPE& TrimLeft(CT tTrim) + { + this->erase(0, this->find_first_not_of(tTrim)); + return *this; + } + + MYTYPE& TrimLeft(PCMYSTR szTrimChars) + { + this->erase(0, this->find_first_not_of(szTrimChars)); + return *this; + } + + MYTYPE& TrimRight() + { + // NOTE: When comparing reverse_iterators here (MYRITER), I avoid using + // operator!=. This is because namespace rel_ops also has a template + // operator!= which conflicts with the global operator!= already defined + // for reverse_iterator in the header . + // Thanks to John James for alerting me to this. + + MYRITER it = std::find_if(this->rbegin(), this->rend(), NotSpace()); + if ( !(this->rend() == it) ) + this->erase(this->rend() - it); + + this->erase(!(it == this->rend()) ? this->find_last_of(*it) + 1 : 0); + return *this; + } + + MYTYPE& TrimRight(CT tTrim) + { + MYSIZE nIdx = this->find_last_not_of(tTrim); + this->erase(MYBASE::npos == nIdx ? 0 : ++nIdx); + return *this; + } + + MYTYPE& TrimRight(PCMYSTR szTrimChars) + { + MYSIZE nIdx = this->find_last_not_of(szTrimChars); + this->erase(MYBASE::npos == nIdx ? 0 : ++nIdx); + return *this; + } + + void FreeExtra() + { + MYTYPE mt; + this->swap(mt); + if ( !mt.empty() ) + this->assign(mt.c_str(), mt.size()); + } + + // I have intentionally not implemented the following CString + // functions. You cannot make them work without taking advantage + // of implementation specific behavior. However if you absolutely + // MUST have them, uncomment out these lines for "sort-of-like" + // their behavior. You're on your own. + +// CT* LockBuffer() { return GetBuf(); }// won't really lock +// void UnlockBuffer(); { } // why have UnlockBuffer w/o LockBuffer? + + // Array-indexing operators. Required because we defined an implicit cast + // to operator const CT* (Thanks to Julian Selman for pointing this out) + + CT& operator[](int nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](int nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + CT& operator[](unsigned int nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](unsigned int nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + CT& operator[](unsigned long nIdx) + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + + const CT& operator[](unsigned long nIdx) const + { + return static_cast(this)->operator[](static_cast(nIdx)); + } + +#ifndef SS_NO_IMPLICIT_CAST + operator const CT*() const + { + return this->c_str(); + } +#endif + + // IStream related functions. Useful in IPersistStream implementations + +#ifdef SS_INC_COMDEF + + // struct SSSHDR - useful for non Std C++ persistence schemes. + typedef struct SSSHDR + { + BYTE byCtrl; + ULONG nChars; + } SSSHDR; // as in "Standard String Stream Header" + + #define SSSO_UNICODE 0x01 // the string is a wide string + #define SSSO_COMPRESS 0x02 // the string is compressed + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSize + // REMARKS: + // Returns how many bytes it will take to StreamSave() this CStdString + // object to an IStream. + // ------------------------------------------------------------------------- + ULONG StreamSize() const + { + // Control header plus string + ASSERT(this->size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + return (this->size() * sizeof(CT)) + sizeof(SSSHDR); + } + + // ------------------------------------------------------------------------- + // FUNCTION: StreamSave + // REMARKS: + // Saves this CStdString object to a COM IStream. + // ------------------------------------------------------------------------- + HRESULT StreamSave(IStream* pStream) const + { + ASSERT(this->size()*sizeof(CT) < 0xffffffffUL - sizeof(SSSHDR)); + HRESULT hr = E_FAIL; + ASSERT(pStream != 0); + SSSHDR hdr; + hdr.byCtrl = sizeof(CT) == 2 ? SSSO_UNICODE : 0; + hdr.nChars = this->size(); + + + if ( FAILED(hr=pStream->Write(&hdr, sizeof(SSSHDR), 0)) ) + { + TRACE(_T("StreamSave: Cannot write control header, ERR=0x%X\n"),hr); + } + else if ( empty() ) + { + ; // nothing to write + } + else if ( FAILED(hr=pStream->Write(this->c_str(), + this->size()*sizeof(CT), 0)) ) + { + TRACE(_T("StreamSave: Cannot write string to stream 0x%X\n"), hr); + } + + return hr; + } + + + // ------------------------------------------------------------------------- + // FUNCTION: StreamLoad + // REMARKS: + // This method loads the object from an IStream. + // ------------------------------------------------------------------------- + HRESULT StreamLoad(IStream* pStream) + { + ASSERT(pStream != 0); + SSSHDR hdr; + HRESULT hr = E_FAIL; + + if ( FAILED(hr=pStream->Read(&hdr, sizeof(SSSHDR), 0)) ) + { + TRACE(_T("StreamLoad: Cant read control header, ERR=0x%X\n"), hr); + } + else if ( hdr.nChars > 0 ) + { + ULONG nRead = 0; + PMYSTR pMyBuf = BufferSet(hdr.nChars); + + // If our character size matches the character size of the string + // we're trying to read, then we can read it directly into our + // buffer. Otherwise, we have to read into an intermediate buffer + // and convert. + + if ( (hdr.byCtrl & SSSO_UNICODE) != 0 ) + { + ULONG nBytes = hdr.nChars * sizeof(wchar_t); + if ( sizeof(CT) == sizeof(wchar_t) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PWSTR pBufW = reinterpret_cast(_alloca((nBytes)+1)); + if ( FAILED(hr=pStream->Read(pBufW, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufW, hdr.nChars); + } + } + else + { + ULONG nBytes = hdr.nChars * sizeof(char); + if ( sizeof(CT) == sizeof(char) ) + { + if ( FAILED(hr=pStream->Read(pMyBuf, nBytes, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + } + else + { + PSTR pBufA = reinterpret_cast(_alloca(nBytes)); + if ( FAILED(hr=pStream->Read(pBufA, hdr.nChars, &nRead)) ) + TRACE(_T("StreamLoad: Cannot read string: 0x%X\n"), hr); + else + sscpy(pMyBuf, pBufA, hdr.nChars); + } + } + } + else + { + this->erase(); + } + return hr; + } +#endif // #ifdef SS_INC_COMDEF + +#ifndef SS_ANSI + + // SetResourceHandle/GetResourceHandle. In MFC builds, these map directly + // to AfxSetResourceHandle and AfxGetResourceHandle. In non-MFC builds they + // point to a single static HINST so that those who call the member + // functions that take resource IDs can provide an alternate HINST of a DLL + // to search. This is not exactly the list of HMODULES that MFC provides + // but it's better than nothing. + + #ifdef _MFC_VER + static void SetResourceHandle(HMODULE hNew) + { + AfxSetResourceHandle(hNew); + } + static HMODULE GetResourceHandle() + { + return AfxGetResourceHandle(); + } + #else + static void SetResourceHandle(HMODULE hNew) + { + SSResourceHandle() = hNew; + } + static HMODULE GetResourceHandle() + { + return SSResourceHandle(); + } + #endif + +#endif +}; + +// ----------------------------------------------------------------------------- +// MSVC USERS: HOW TO EXPORT CSTDSTRING FROM A DLL +// +// If you are using MS Visual C++ and you want to export CStdStringA and +// CStdStringW from a DLL, then all you need to +// +// 1. make sure that all components link to the same DLL version +// of the CRT (not the static one). +// 2. Uncomment the 3 lines of code below +// 3. #define 2 macros per the instructions in MS KnowledgeBase +// article Q168958. The macros are: +// +// MACRO DEFINTION WHEN EXPORTING DEFINITION WHEN IMPORTING +// ----- ------------------------ ------------------------- +// SSDLLEXP (nothing, just #define it) extern +// SSDLLSPEC __declspec(dllexport) __declspec(dllimport) +// +// Note that these macros must be available to ALL clients who want to +// link to the DLL and use the class. If they +// +// A word of advice: Don't bother. +// +// Really, it is not necessary to export CStdString functions from a DLL. I +// never do. In my projects, I do generally link to the DLL version of the +// Standard C++ Library, but I do NOT attempt to export CStdString functions. +// I simply include the header where it is needed and allow for the code +// redundancy. +// +// That redundancy is a lot less than you think. This class does most of its +// work via the Standard C++ Library, particularly the base_class basic_string<> +// member functions. Most of the functions here are small enough to be inlined +// anyway. Besides, you'll find that in actual practice you use less than 1/2 +// of the code here, even in big projects and different modules will use as +// little as 10% of it. That means a lot less functions actually get linked +// your binaries. If you export this code from a DLL, it ALL gets linked in. +// +// I've compared the size of the binaries from exporting vs NOT exporting. Take +// my word for it -- exporting this code is not worth the hassle. +// +// ----------------------------------------------------------------------------- +//#pragma warning(disable:4231) // non-standard extension ("extern template") +// SSDLLEXP template class SSDLLSPEC CStdStr; +// SSDLLEXP template class SSDLLSPEC CStdStr; + + +// ============================================================================= +// END OF CStdStr INLINE FUNCTION DEFINITIONS +// ============================================================================= + +// Now typedef our class names based upon this humongous template + +typedef CStdStr CStdStringA; // a better std::string +typedef CStdStr CStdStringW; // a better std::wstring +typedef CStdStr CStdString16; // a 16bit char string +typedef CStdStr CStdString32; // a 32bit char string +typedef CStdStr CStdStringO; // almost always CStdStringW + +// ----------------------------------------------------------------------------- +// CStdStr addition functions defined as inline +// ----------------------------------------------------------------------------- + + +inline CStdStringA operator+(const CStdStringA& s1, const CStdStringA& s2) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(s2); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, CStdStringA::value_type t) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(1, t); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, PCSTR pA) +{ + CStdStringA sRet(SSREF(s1)); + sRet.append(pA); + return sRet; +} +inline CStdStringA operator+(PCSTR pA, const CStdStringA& sA) +{ + CStdStringA sRet; + CStdStringA::size_type nObjSize = sA.size(); + CStdStringA::size_type nLitSize = + static_cast(sslen(pA)); + + sRet.reserve(nLitSize + nObjSize); + sRet.assign(pA); + sRet.append(sA); + return sRet; +} + + +inline CStdStringA operator+(const CStdStringA& s1, const CStdStringW& s2) +{ + return s1 + CStdStringA(s2); +} +inline CStdStringW operator+(const CStdStringW& s1, const CStdStringW& s2) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(s2); + return sRet; +} +inline CStdStringA operator+(const CStdStringA& s1, PCWSTR pW) +{ + return s1 + CStdStringA(pW); +} + +#ifdef UNICODE + inline CStdStringW operator+(PCWSTR pW, const CStdStringA& sA) + { + return CStdStringW(pW) + CStdStringW(SSREF(sA)); + } + inline CStdStringW operator+(PCSTR pA, const CStdStringW& sW) + { + return CStdStringW(pA) + sW; + } +#else + inline CStdStringA operator+(PCWSTR pW, const CStdStringA& sA) + { + return CStdStringA(pW) + sA; + } + inline CStdStringA operator+(PCSTR pA, const CStdStringW& sW) + { + return pA + CStdStringA(sW); + } +#endif + +// ...Now the wide string versions. +inline CStdStringW operator+(const CStdStringW& s1, CStdStringW::value_type t) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(1, t); + return sRet; +} +inline CStdStringW operator+(const CStdStringW& s1, PCWSTR pW) +{ + CStdStringW sRet(SSREF(s1)); + sRet.append(pW); + return sRet; +} +inline CStdStringW operator+(PCWSTR pW, const CStdStringW& sW) +{ + CStdStringW sRet; + CStdStringW::size_type nObjSize = sW.size(); + CStdStringA::size_type nLitSize = + static_cast(sslen(pW)); + + sRet.reserve(nLitSize + nObjSize); + sRet.assign(pW); + sRet.append(sW); + return sRet; +} + +inline CStdStringW operator+(const CStdStringW& s1, const CStdStringA& s2) +{ + return s1 + CStdStringW(s2); +} +inline CStdStringW operator+(const CStdStringW& s1, PCSTR pA) +{ + return s1 + CStdStringW(pA); +} + + +// New-style format function is a template + +#ifdef SS_SAFE_FORMAT + +template<> +struct FmtArg +{ + explicit FmtArg(const CStdStringA& arg) : a_(arg) {} + PCSTR operator()() const { return a_.c_str(); } + const CStdStringA& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; +template<> +struct FmtArg +{ + explicit FmtArg(const CStdStringW& arg) : a_(arg) {} + PCWSTR operator()() const { return a_.c_str(); } + const CStdStringW& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; + +template<> +struct FmtArg +{ + explicit FmtArg(const std::string& arg) : a_(arg) {} + PCSTR operator()() const { return a_.c_str(); } + const std::string& a_; +private: + FmtArg& operator=(const FmtArg&) { return *this; } +}; +template<> +struct FmtArg +{ + explicit FmtArg(const std::wstring& arg) : a_(arg) {} + PCWSTR operator()() const { return a_.c_str(); } + const std::wstring& a_; +private: + FmtArg& operator=(const FmtArg&) {return *this;} +}; +#endif // #ifdef SS_SAFEFORMAT + +#ifndef SS_ANSI + // SSResourceHandle: our MFC-like resource handle + inline HMODULE& SSResourceHandle() + { + static HMODULE hModuleSS = GetModuleHandle(0); + return hModuleSS; + } +#endif + + +// In MFC builds, define some global serialization operators +// Special operators that allow us to serialize CStdStrings to CArchives. +// Note that we use an intermediate CString object in order to ensure that +// we use the exact same format. + +#ifdef _MFC_VER + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringA& strA) + { + CString strTemp = strA; + return ar << strTemp; + } + inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringW& strW) + { + CString strTemp = strW; + return ar << strTemp; + } + + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringA& strA) + { + CString strTemp; + ar >> strTemp; + strA = strTemp; + return ar; + } + inline CArchive& AFXAPI operator>>(CArchive& ar, CStdStringW& strW) + { + CString strTemp; + ar >> strTemp; + strW = strTemp; + return ar; + } +#endif // #ifdef _MFC_VER -- (i.e. is this MFC?) + + + +// ----------------------------------------------------------------------------- +// GLOBAL FUNCTION: WUFormat +// CStdStringA WUFormat(UINT nId, ...); +// CStdStringA WUFormat(PCSTR szFormat, ...); +// +// REMARKS: +// This function allows the caller for format and return a CStdStringA +// object with a single line of code. +// ----------------------------------------------------------------------------- +#ifdef SS_ANSI +#else + inline CStdStringA WUFormatA(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + CStdStringA strFmt; + CStdStringA strOut; + if ( strFmt.Load(nId) ) + strOut.FormatV(strFmt, argList); + + va_end(argList); + return strOut; + } + inline CStdStringA WUFormatA(PCSTR szFormat, ...) + { + va_list argList; + va_start(argList, szFormat); + CStdStringA strOut; + strOut.FormatV(szFormat, argList); + va_end(argList); + return strOut; + } + inline CStdStringW WUFormatW(UINT nId, ...) + { + va_list argList; + va_start(argList, nId); + + CStdStringW strFmt; + CStdStringW strOut; + if ( strFmt.Load(nId) ) + strOut.FormatV(strFmt, argList); + + va_end(argList); + return strOut; + } + inline CStdStringW WUFormatW(PCWSTR szwFormat, ...) + { + va_list argList; + va_start(argList, szwFormat); + CStdStringW strOut; + strOut.FormatV(szwFormat, argList); + va_end(argList); + return strOut; + } +#endif // #ifdef SS_ANSI + + + +#if defined(SS_WIN32) && !defined (SS_ANSI) + // ------------------------------------------------------------------------- + // FUNCTION: WUSysMessage + // CStdStringA WUSysMessageA(DWORD dwError, DWORD dwLangId=SS_DEFLANGID); + // CStdStringW WUSysMessageW(DWORD dwError, DWORD dwLangId=SS_DEFLANGID); + // + // DESCRIPTION: + // This function simplifies the process of obtaining a string equivalent + // of a system error code returned from GetLastError(). You simply + // supply the value returned by GetLastError() to this function and the + // corresponding system string is returned in the form of a CStdStringA. + // + // PARAMETERS: + // dwError - a DWORD value representing the error code to be translated + // dwLangId - the language id to use. defaults to english. + // + // RETURN VALUE: + // a CStdStringA equivalent of the error code. Currently, this function + // only returns either English of the system default language strings. + // ------------------------------------------------------------------------- + #define SS_DEFLANGID MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT) + inline CStdStringA WUSysMessageA(DWORD dwError, DWORD dwLangId=SS_DEFLANGID) + { + CHAR szBuf[512]; + + if ( 0 != ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, + dwLangId, szBuf, 511, NULL) ) + return WUFormatA("%s (0x%X)", szBuf, dwError); + else + return WUFormatA("Unknown error (0x%X)", dwError); + } + inline CStdStringW WUSysMessageW(DWORD dwError, DWORD dwLangId=SS_DEFLANGID) + { + WCHAR szBuf[512]; + + if ( 0 != ::FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, + dwLangId, szBuf, 511, NULL) ) + return WUFormatW(L"%s (0x%X)", szBuf, dwError); + else + return WUFormatW(L"Unknown error (0x%X)", dwError); + } +#endif + +// Define TCHAR based friendly names for some of these functions + +#ifdef UNICODE + //#define CStdString CStdStringW + typedef CStdStringW CStdString; + #define WUSysMessage WUSysMessageW + #define WUFormat WUFormatW +#else + //#define CStdString CStdStringA + typedef CStdStringA CStdString; + #define WUSysMessage WUSysMessageA + #define WUFormat WUFormatA +#endif + +// ...and some shorter names for the space-efficient + +#define WUSysMsg WUSysMessage +#define WUSysMsgA WUSysMessageA +#define WUSysMsgW WUSysMessageW +#define WUFmtA WUFormatA +#define WUFmtW WUFormatW +#define WUFmt WUFormat +#define WULastErrMsg() WUSysMessage(::GetLastError()) +#define WULastErrMsgA() WUSysMessageA(::GetLastError()) +#define WULastErrMsgW() WUSysMessageW(::GetLastError()) + + +// ----------------------------------------------------------------------------- +// FUNCTIONAL COMPARATORS: +// REMARKS: +// These structs are derived from the std::binary_function template. They +// give us functional classes (which may be used in Standard C++ Library +// collections and algorithms) that perform case-insensitive comparisons of +// CStdString objects. This is useful for maps in which the key may be the +// proper string but in the wrong case. +// ----------------------------------------------------------------------------- +#define StdStringLessNoCaseW SSLNCW // avoid VC compiler warning 4786 +#define StdStringEqualsNoCaseW SSENCW +#define StdStringLessNoCaseA SSLNCA +#define StdStringEqualsNoCaseA SSENCA + +#ifdef UNICODE + #define StdStringLessNoCase SSLNCW + #define StdStringEqualsNoCase SSENCW +#else + #define StdStringLessNoCase SSLNCA + #define StdStringEqualsNoCase SSENCA +#endif + +struct StdStringLessNoCaseW + : std::binary_function +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseW + : std::binary_function +{ + inline + bool operator()(const CStdStringW& sLeft, const CStdStringW& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; +struct StdStringLessNoCaseA + : std::binary_function +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) < 0; } +}; +struct StdStringEqualsNoCaseA + : std::binary_function +{ + inline + bool operator()(const CStdStringA& sLeft, const CStdStringA& sRight) const + { return ssicmp(sLeft.c_str(), sRight.c_str()) == 0; } +}; + +// If we had to define our own version of TRACE above, get rid of it now + +#ifdef TRACE_DEFINED_HERE + #undef TRACE + #undef TRACE_DEFINED_HERE +#endif + + +// These std::swap specializations come courtesy of Mike Crusader. + +//namespace std +//{ +// inline void swap(CStdStringA& s1, CStdStringA& s2) throw() +// { +// s1.swap(s2); +// } +// template<> +// inline void swap(CStdStringW& s1, CStdStringW& s2) throw() +// { +// s1.swap(s2); +// } +//} + +// Turn back on any Borland warnings we turned off. + +#ifdef __BORLANDC__ + #pragma option pop // Turn back on inline function warnings +// #pragma warn +inl // Turn back on inline function warnings +#endif + +#endif // #ifndef STDSTRING_H diff --git a/xbmc/pvrclients/vdr-vnsi/VNSIChannelScan.cpp b/xbmc/pvrclients/vdr-vnsi/VNSIChannelScan.cpp new file mode 100644 index 0000000000..803ed273df --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/VNSIChannelScan.cpp @@ -0,0 +1,711 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "VNSIChannelScan.h" +#include +#include "tools.h" +#include "responsepacket.h" +#include "requestpacket.h" +#include "vdrcommand.h" + +#define BUTTON_START 5 +#define BUTTON_BACK 6 +#define BUTTON_CANCEL 7 +#define HEADER_LABEL 8 + +#define SPIN_CONTROL_SOURCE_TYPE 10 +#define CONTROL_RADIO_BUTTON_TV 11 +#define CONTROL_RADIO_BUTTON_RADIO 12 +#define CONTROL_RADIO_BUTTON_FTA 13 +#define CONTROL_RADIO_BUTTON_SCRAMBLED 14 +#define CONTROL_RADIO_BUTTON_HD 15 +#define CONTROL_SPIN_COUNTRIES 16 +#define CONTROL_SPIN_SATELLITES 17 +#define CONTROL_SPIN_DVBC_INVERSION 18 +#define CONTROL_SPIN_DVBC_SYMBOLRATE 29 +#define CONTROL_SPIN_DVBC_QAM 20 +#define CONTROL_SPIN_DVBT_INVERSION 21 +#define CONTROL_SPIN_ATSC_TYPE 22 + +#define LABEL_TYPE 30 +#define LABEL_DEVICE 31 +#define PROGRESS_DONE 32 +#define LABEL_TRANSPONDER 33 +#define LABEL_SIGNAL 34 +#define PROGRESS_SIGNAL 35 +#define LABEL_STATUS 36 + +#define CMD_LOCK cMutexLock CmdLock((cMutex*)&m_Mutex) + +cVNSIChannelScan::cVNSIChannelScan() +{ +} + +cVNSIChannelScan::~cVNSIChannelScan() +{ +} + +bool cVNSIChannelScan::Open() +{ + m_running = false; + m_Canceled = false; + m_stopped = true; + m_progressDone = NULL; + m_progressSignal = NULL; + + if(!m_session.Open(g_szHostname, g_iPort, g_iConnectTimeout, "XBMC channel scanner")) + return false; + + SetDescription("VNSI channel scan listener"); + Start(); + + /* Load the Window as Dialog */ + m_window = GUI->Window_create("ChannelScan.xml", "Confluence", false, true); + m_window->m_cbhdl = this; + m_window->CBOnInit = OnInitCB; + m_window->CBOnFocus = OnFocusCB; + m_window->CBOnClick = OnClickCB; + m_window->CBOnAction= OnActionCB; + m_window->DoModal(); + + GUI->Window_destroy(m_window); + Cancel(1); + m_session.Close(); + + return true; +} + +void cVNSIChannelScan::StartScan() +{ + m_header = XBMC->GetLocalizedString(30025); + m_Signal = XBMC->GetLocalizedString(30029); + SetProgress(0); + SetSignal(0, false); + + int source = m_spinSourceType->GetValue(); + switch (source) + { + case DVB_TERR: + m_window->SetControlLabel(LABEL_TYPE, "DVB-T"); + break; + case DVB_CABLE: + m_window->SetControlLabel(LABEL_TYPE, "DVB-C"); + break; + case DVB_SAT: + m_window->SetControlLabel(LABEL_TYPE, "DVB-S/S2"); + break; + case PVRINPUT: + m_window->SetControlLabel(LABEL_TYPE, XBMC->GetLocalizedString(30032).c_str()); + break; + case PVRINPUT_FM: + m_window->SetControlLabel(LABEL_TYPE, XBMC->GetLocalizedString(30033).c_str()); + break; + case DVB_ATSC: + m_window->SetControlLabel(LABEL_TYPE, "ATSC"); + break; + } + + cRequestPacket vrp; + cResponsePacket* vresp = NULL; + uint32_t retCode = VDR_RET_ERROR; + if (!vrp.init(VDR_SCAN_START)) goto SCANError; + if (!vrp.add_U32(source)) goto SCANError; + if (!vrp.add_U8(m_radioButtonTV->IsSelected())) goto SCANError; + if (!vrp.add_U8(m_radioButtonRadio->IsSelected())) goto SCANError; + if (!vrp.add_U8(m_radioButtonFTA->IsSelected())) goto SCANError; + if (!vrp.add_U8(m_radioButtonScrambled->IsSelected())) goto SCANError; + if (!vrp.add_U8(m_radioButtonHD->IsSelected())) goto SCANError; + if (!vrp.add_U32(m_spinCountries->GetValue())) goto SCANError; + if (!vrp.add_U32(m_spinDVBCInversion->GetValue())) goto SCANError; + if (!vrp.add_U32(m_spinDVBCSymbolrates->GetValue())) goto SCANError; + if (!vrp.add_U32(m_spinDVBCqam->GetValue())) goto SCANError; + if (!vrp.add_U32(m_spinDVBTInversion->GetValue())) goto SCANError; + if (!vrp.add_U32(m_spinSatellites->GetValue())) goto SCANError; + if (!vrp.add_U32(m_spinATSCType->GetValue())) goto SCANError; + + vresp = ReadResult(&vrp); + if (!vresp) + goto SCANError; + + retCode = vresp->extract_U32(); + if (retCode != VDR_RET_OK) + goto SCANError; + + return; + +SCANError: + XBMC->Log(LOG_ERROR, "cVNSIChannelScan::StartScan() - Return error after start (%i)", retCode); + m_window->SetControlLabel(LABEL_STATUS, XBMC->GetLocalizedString(24071).c_str()); + m_window->SetControlLabel(BUTTON_START, XBMC->GetLocalizedString(30024).c_str()); + m_window->SetControlLabel(HEADER_LABEL, XBMC->GetLocalizedString(30043).c_str()); + m_stopped = true; +} + +void cVNSIChannelScan::StopScan() +{ + cRequestPacket vrp; + if (!vrp.init(VDR_SCAN_STOP)) + return; + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + return; + + uint32_t retCode = vresp->extract_U32(); + if (retCode != VDR_RET_OK) + { + XBMC->Log(LOG_ERROR, "cVNSIChannelScan::StopScan() - Return error after stop (%i)", retCode); + m_window->SetControlLabel(LABEL_STATUS, XBMC->GetLocalizedString(24071).c_str()); + m_window->SetControlLabel(BUTTON_START, XBMC->GetLocalizedString(30024).c_str()); + m_window->SetControlLabel(HEADER_LABEL, XBMC->GetLocalizedString(30043).c_str()); + m_stopped = true; + } + return; +} + +void cVNSIChannelScan::ReturnFromProcessView() +{ + if (m_running) + { + m_running = false; + m_window->ClearProperties(); + m_window->SetControlLabel(BUTTON_START, XBMC->GetLocalizedString(30010).c_str()); + m_window->SetControlLabel(HEADER_LABEL, XBMC->GetLocalizedString(30009).c_str()); + + if (m_progressDone) + { + GUI->Control_releaseProgress(m_progressDone); + m_progressDone = NULL; + } + if (m_progressSignal) + { + GUI->Control_releaseProgress(m_progressSignal); + m_progressSignal = NULL; + } + } +} + +void cVNSIChannelScan::SetProgress(int procent) +{ + if (!m_progressDone) + m_progressDone = GUI->Control_getProgress(m_window, PROGRESS_DONE); + + CStdString header; + header.Format(m_header, procent); + m_window->SetControlLabel(HEADER_LABEL, header.c_str()); + m_progressDone->SetPercentage((float)procent); +} + +void cVNSIChannelScan::SetSignal(int procent, bool locked) +{ + if (!m_progressSignal) + m_progressSignal = GUI->Control_getProgress(m_window, PROGRESS_SIGNAL); + + CStdString signal; + signal.Format(m_Signal, procent); + m_window->SetControlLabel(LABEL_SIGNAL, signal.c_str()); + m_progressSignal->SetPercentage((float)procent); + + if (locked) + m_window->SetProperty("Locked", "true"); + else + m_window->SetProperty("Locked", ""); +} + +bool cVNSIChannelScan::OnClick(int controlId) +{ + if (controlId == SPIN_CONTROL_SOURCE_TYPE) + { + int value = m_spinSourceType->GetValue(); + SetControlsVisible((scantype_t)value); + } + else if (controlId == BUTTON_BACK) + { + m_window->Close(); + GUI->Control_releaseSpin(m_spinSourceType); + GUI->Control_releaseSpin(m_spinCountries); + GUI->Control_releaseSpin(m_spinSatellites); + GUI->Control_releaseSpin(m_spinDVBCInversion); + GUI->Control_releaseSpin(m_spinDVBCSymbolrates); + GUI->Control_releaseSpin(m_spinDVBCqam); + GUI->Control_releaseSpin(m_spinDVBTInversion); + GUI->Control_releaseSpin(m_spinATSCType); + GUI->Control_releaseRadioButton(m_radioButtonTV); + GUI->Control_releaseRadioButton(m_radioButtonRadio); + GUI->Control_releaseRadioButton(m_radioButtonFTA); + GUI->Control_releaseRadioButton(m_radioButtonScrambled); + GUI->Control_releaseRadioButton(m_radioButtonHD); + if (m_progressDone) + { + GUI->Control_releaseProgress(m_progressDone); + m_progressDone = NULL; + } + if (m_progressSignal) + { + GUI->Control_releaseProgress(m_progressSignal); + m_progressSignal = NULL; + } + } + else if (controlId == BUTTON_START) + { + if (!m_running) + { + m_running = true; + m_stopped = false; + m_Canceled = false; + m_window->SetProperty("Scanning", "running"); + m_window->SetControlLabel(BUTTON_START, XBMC->GetLocalizedString(222).c_str()); + StartScan(); + } + else if (!m_stopped) + { + m_stopped = true; + m_Canceled = true; + StopScan(); + } + else + ReturnFromProcessView(); + } + return true; +} + +bool cVNSIChannelScan::OnFocus(int controlId) +{ + return true; +} + +bool cVNSIChannelScan::OnInit() +{ + m_spinSourceType = GUI->Control_getSpin(m_window, SPIN_CONTROL_SOURCE_TYPE); + m_spinSourceType->Clear(); + m_spinSourceType->AddLabel("DVB-T", DVB_TERR); + m_spinSourceType->AddLabel("DVB-C", DVB_CABLE); + m_spinSourceType->AddLabel("DVB-S/S2", DVB_SAT); + m_spinSourceType->AddLabel("Analog TV", PVRINPUT); + m_spinSourceType->AddLabel("Analog Radio", PVRINPUT_FM); + m_spinSourceType->AddLabel("ATSC", DVB_ATSC); + + m_spinDVBCInversion = GUI->Control_getSpin(m_window, CONTROL_SPIN_DVBC_INVERSION); + m_spinDVBCInversion->Clear(); + m_spinDVBCInversion->AddLabel("Auto", 0); + m_spinDVBCInversion->AddLabel("On", 1); + m_spinDVBCInversion->AddLabel("Off", 2); + + m_spinDVBCSymbolrates = GUI->Control_getSpin(m_window, CONTROL_SPIN_DVBC_SYMBOLRATE); + m_spinDVBCSymbolrates->Clear(); + m_spinDVBCSymbolrates->AddLabel("AUTO", 0); + m_spinDVBCSymbolrates->AddLabel("6900", 1); + m_spinDVBCSymbolrates->AddLabel("6875", 2); + m_spinDVBCSymbolrates->AddLabel("6111", 3); + m_spinDVBCSymbolrates->AddLabel("6250", 4); + m_spinDVBCSymbolrates->AddLabel("6790", 5); + m_spinDVBCSymbolrates->AddLabel("6811", 6); + m_spinDVBCSymbolrates->AddLabel("5900", 7); + m_spinDVBCSymbolrates->AddLabel("5000", 8); + m_spinDVBCSymbolrates->AddLabel("3450", 9); + m_spinDVBCSymbolrates->AddLabel("4000", 10); + m_spinDVBCSymbolrates->AddLabel("6950", 11); + m_spinDVBCSymbolrates->AddLabel("7000", 12); + m_spinDVBCSymbolrates->AddLabel("6952", 13); + m_spinDVBCSymbolrates->AddLabel("5156", 14); + m_spinDVBCSymbolrates->AddLabel("4583", 15); + m_spinDVBCSymbolrates->AddLabel("ALL (slow)", 16); + + m_spinDVBCqam = GUI->Control_getSpin(m_window, CONTROL_SPIN_DVBC_QAM); + m_spinDVBCqam->Clear(); + m_spinDVBCqam->AddLabel("AUTO", 0); + m_spinDVBCqam->AddLabel("64", 1); + m_spinDVBCqam->AddLabel("128", 2); + m_spinDVBCqam->AddLabel("256", 3); + m_spinDVBCqam->AddLabel("ALL (slow)", 4); + + m_spinDVBTInversion = GUI->Control_getSpin(m_window, CONTROL_SPIN_DVBT_INVERSION); + m_spinDVBTInversion->Clear(); + m_spinDVBTInversion->AddLabel("Auto", 0); + m_spinDVBTInversion->AddLabel("On", 1); + m_spinDVBTInversion->AddLabel("Off", 2); + + m_spinATSCType = GUI->Control_getSpin(m_window, CONTROL_SPIN_ATSC_TYPE); + m_spinATSCType->Clear(); + m_spinATSCType->AddLabel("VSB (aerial)", 0); + m_spinATSCType->AddLabel("QAM (cable)", 1); + m_spinATSCType->AddLabel("VSB + QAM (aerial + cable)", 2); + + m_radioButtonTV = GUI->Control_getRadioButton(m_window, CONTROL_RADIO_BUTTON_TV); + m_radioButtonTV->SetSelected(true); + + m_radioButtonRadio = GUI->Control_getRadioButton(m_window, CONTROL_RADIO_BUTTON_RADIO); + m_radioButtonRadio->SetSelected(true); + + m_radioButtonFTA = GUI->Control_getRadioButton(m_window, CONTROL_RADIO_BUTTON_FTA); + m_radioButtonFTA->SetSelected(true); + + m_radioButtonScrambled = GUI->Control_getRadioButton(m_window, CONTROL_RADIO_BUTTON_SCRAMBLED); + m_radioButtonScrambled->SetSelected(true); + + m_radioButtonHD = GUI->Control_getRadioButton(m_window, CONTROL_RADIO_BUTTON_HD); + m_radioButtonHD->SetSelected(true); + + if (!ReadCountries()) + return false; + + if (!ReadSatellites()) + return false; + + SetControlsVisible(DVB_TERR); + return true; +} + +bool cVNSIChannelScan::OnAction(int actionId) +{ + if (actionId == ADDON_ACTION_CLOSE_DIALOG || actionId == ADDON_ACTION_PREVIOUS_MENU) + OnClick(BUTTON_BACK); + + return true; +} + +bool cVNSIChannelScan::OnInitCB(GUIHANDLE cbhdl) +{ + cVNSIChannelScan* scanner = static_cast(cbhdl); + return scanner->OnInit(); +} + +bool cVNSIChannelScan::OnClickCB(GUIHANDLE cbhdl, int controlId) +{ + cVNSIChannelScan* scanner = static_cast(cbhdl); + return scanner->OnClick(controlId); +} + +bool cVNSIChannelScan::OnFocusCB(GUIHANDLE cbhdl, int controlId) +{ + cVNSIChannelScan* scanner = static_cast(cbhdl); + return scanner->OnFocus(controlId); +} + +bool cVNSIChannelScan::OnActionCB(GUIHANDLE cbhdl, int actionId) +{ + cVNSIChannelScan* scanner = static_cast(cbhdl); + return scanner->OnAction(actionId); +} + +bool cVNSIChannelScan::ReadCountries() +{ + m_spinCountries = GUI->Control_getSpin(m_window, CONTROL_SPIN_COUNTRIES); + m_spinCountries->Clear(); + + CStdString dvdlang = XBMC->GetDVDMenuLanguage(); + dvdlang = dvdlang.ToUpper(); + + cRequestPacket vrp; + if (!vrp.init(VDR_SCAN_GETCOUNTRIES)) + return false; + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + return false; + + int startIndex = -1; + uint32_t retCode = vresp->extract_U32(); + if (retCode == VDR_RET_OK) + { + while (!vresp->end()) + { + uint32_t index = vresp->extract_U32(); + const char *isoName = vresp->extract_String(); + const char *longName = vresp->extract_String(); + m_spinCountries->AddLabel(longName, index); + if (dvdlang == isoName) + startIndex = index; + + delete[] longName; + delete[] isoName; + } + if (startIndex >= 0) + m_spinCountries->SetValue(startIndex); + } + else + { + XBMC->Log(LOG_ERROR, "cVNSIChannelScan::ReadCountries() - Return error after reading countries (%i)", retCode); + } + delete vresp; + return retCode == VDR_RET_OK; +} + +bool cVNSIChannelScan::ReadSatellites() +{ + m_spinSatellites = GUI->Control_getSpin(m_window, CONTROL_SPIN_SATELLITES); + m_spinSatellites->Clear(); + + cRequestPacket vrp; + if (!vrp.init(VDR_SCAN_GETSATELLITES)) + return false; + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + return false; + + uint32_t retCode = vresp->extract_U32(); + if (retCode == VDR_RET_OK) + { + while (!vresp->end()) + { + uint32_t index = vresp->extract_U32(); + const char *shortName = vresp->extract_String(); + const char *longName = vresp->extract_String(); + m_spinSatellites->AddLabel(longName, index); + delete[] longName; + delete[] shortName; + } + m_spinSatellites->SetValue(6); /* default to Astra 19.2 */ + } + else + { + XBMC->Log(LOG_ERROR, "cVNSIChannelScan::ReadSatellites() - Return error after reading satellites (%i)", retCode); + } + delete vresp; + return retCode == VDR_RET_OK; +} + +void cVNSIChannelScan::SetControlsVisible(scantype_t type) +{ + m_spinCountries->SetVisible(type == DVB_TERR || type == DVB_CABLE || type == PVRINPUT); + m_spinSatellites->SetVisible(type == DVB_SAT || type == DVB_ATSC); + m_spinDVBCInversion->SetVisible(type == DVB_CABLE); + m_spinDVBCSymbolrates->SetVisible(type == DVB_CABLE); + m_spinDVBCqam->SetVisible(type == DVB_CABLE); + m_spinDVBTInversion->SetVisible(type == DVB_TERR); + m_spinATSCType->SetVisible(type == DVB_ATSC); + m_radioButtonTV->SetVisible(type == DVB_TERR || type == DVB_CABLE || type == DVB_SAT || type == DVB_ATSC); + m_radioButtonRadio->SetVisible(type == DVB_TERR || type == DVB_CABLE || type == DVB_SAT || type == DVB_ATSC); + m_radioButtonFTA->SetVisible(type == DVB_TERR || type == DVB_CABLE || type == DVB_SAT || type == DVB_ATSC); + m_radioButtonScrambled->SetVisible(type == DVB_TERR || type == DVB_CABLE || type == DVB_SAT || type == DVB_ATSC); + m_radioButtonHD->SetVisible(type == DVB_TERR || type == DVB_CABLE || type == DVB_SAT || type == DVB_ATSC); +} + +void cVNSIChannelScan::Action() +{ + uint32_t channelID; + uint32_t requestID; + uint32_t userDataLength; + uint8_t* userData; + + bool readSuccess; + + cResponsePacket* vresp; + + while (Running()) + { + readSuccess = readData((uint8_t*)&channelID, sizeof(uint32_t)); // 2s timeout atm + if (!readSuccess && !IsClientConnected()) + return; // return to stop this thread + + if (!readSuccess) continue; // no data was read but the connection is ok. + + // Data was read + channelID = ntohl(channelID); + if (channelID == CHANNEL_REQUEST_RESPONSE) + { + if (!readData((uint8_t*)&requestID, sizeof(uint32_t))) break; + requestID = ntohl(requestID); + if (!readData((uint8_t*)&userDataLength, sizeof(uint32_t))) break; + userDataLength = ntohl(userDataLength); + if (userDataLength > 5000000) break; // how big can these packets get? + userData = NULL; + if (userDataLength > 0) + { + userData = (uint8_t*)malloc(userDataLength); + if (!userData) break; + if (!readData(userData, userDataLength)) break; + } + + vresp = new cResponsePacket(); + vresp->setResponse(requestID, userData, userDataLength); + + CMD_LOCK; + SMessages::iterator it = m_queue.find(requestID); + if (it != m_queue.end()) + { + it->second.pkt = vresp; + it->second.event->Signal(); + } + else + { + delete vresp; + } + } + else if (channelID == CHANNEL_SCAN) + { + if (!readData((uint8_t*)&requestID, sizeof(uint32_t))) break; + requestID = ntohl(requestID); + if (!readData((uint8_t*)&userDataLength, sizeof(uint32_t))) break; + userDataLength = ntohl(userDataLength); + if (userDataLength > 5000000) break; // how big can these packets get? + userData = NULL; + if (userDataLength > 0) + { + userData = (uint8_t*)malloc(userDataLength); + if (!userData) break; + if (!readData(userData, userDataLength)) break; + } + + if (requestID == VDR_SCANNER_PERCENTAGE) + { + uint32_t percent = ntohl(*(uint32_t*)&userData[0]); + if (percent >= 0 && percent <= 100) + SetProgress(percent); + } + else if (requestID == VDR_SCANNER_SIGNAL) + { + uint32_t strength = ntohl(*(uint32_t*)&userData[0]); + uint32_t locked = ntohl(*(uint32_t*)&userData[4]); + SetSignal(strength, locked); + } + else if (requestID == VDR_SCANNER_DEVICE) + { + int length = strlen((char*)&userData[0]); + char* str = new char[length + 1]; + strcpy(str, (char*)&userData[0]); + m_window->SetControlLabel(LABEL_DEVICE, str); + delete[] str; + } + else if (requestID == VDR_SCANNER_TRANSPONDER) + { + int length = strlen((char*)&userData[0]); + char* str = new char[length + 1]; + strcpy(str, (char*)&userData[0]); + m_window->SetControlLabel(LABEL_TRANSPONDER, str); + delete[] str; + } + else if (requestID == VDR_SCANNER_NEWCHANNEL) + { + uint32_t isRadio = ntohl(*(uint32_t*)&userData[0]); + uint32_t isEncrypted = ntohl(*(uint32_t*)&userData[4]); + uint32_t isHD = ntohl(*(uint32_t*)&userData[8]); + int length = strlen((char*)&userData[12]); + char* str = new char[length + 1]; + strcpy(str, (char*)&userData[12]); + + cListItem* item = GUI->ListItem_create(str, NULL, NULL, NULL, NULL); + if (isEncrypted) + item->SetProperty("IsEncrypted", "yes"); + if (isRadio) + item->SetProperty("IsRadio", "yes"); + if (isHD) + item->SetProperty("IsHD", "yes"); + m_window->AddItem(item, 0); + GUI->ListItem_destroy(item); + + delete[] str; + } + else if (requestID == VDR_SCANNER_FINISHED) + { + if (!m_Canceled) + { + m_window->SetControlLabel(HEADER_LABEL, XBMC->GetLocalizedString(30036).c_str()); + m_window->SetControlLabel(BUTTON_START, XBMC->GetLocalizedString(30024).c_str()); + m_window->SetControlLabel(LABEL_STATUS, XBMC->GetLocalizedString(30041).c_str()); + } + else + { + m_window->SetControlLabel(HEADER_LABEL, XBMC->GetLocalizedString(30042).c_str()); + } + } + else if (requestID == VDR_SCANNER_STATUS) + { + uint32_t status = ntohl(*(uint32_t*)&userData[0]); + if (status == 0) + { + if (m_Canceled) + m_window->SetControlLabel(LABEL_STATUS, XBMC->GetLocalizedString(16200).c_str()); + else + m_window->SetControlLabel(LABEL_STATUS, XBMC->GetLocalizedString(30040).c_str()); + m_window->SetControlLabel(BUTTON_START, XBMC->GetLocalizedString(30024).c_str()); + m_stopped = true; + } + else if (status == 1) + { + m_window->SetControlLabel(LABEL_STATUS, XBMC->GetLocalizedString(30039).c_str()); + } + else if (status == 2) + { + m_window->SetControlLabel(LABEL_STATUS, XBMC->GetLocalizedString(30037).c_str()); + m_window->SetControlLabel(BUTTON_START, XBMC->GetLocalizedString(30024).c_str()); + m_window->SetControlLabel(HEADER_LABEL, XBMC->GetLocalizedString(30043).c_str()); + m_stopped = true; + } + else if (status == 3) + { + m_window->SetControlLabel(LABEL_STATUS, XBMC->GetLocalizedString(30038).c_str()); + } + } + + if (userData) + free(userData); + } + else + { + XBMC->Log(LOG_ERROR, "cVNSIChannelScan::Action() - Rxd a wrong response packet on channel %lu !!", channelID); + break; + } + } +} + +cResponsePacket* cVNSIChannelScan::ReadResult(cRequestPacket* vrp) +{ + m_Mutex.Lock(); + + SMessage &message(m_queue[vrp->getSerial()]); + message.event = new cCondWait(); + message.pkt = NULL; + + m_Mutex.Unlock(); + + if(!m_session.SendMessage(vrp)) + { + m_queue.erase(vrp->getSerial()); + return NULL; + } + + message.event->Wait(2000); + + m_Mutex.Lock(); + + cResponsePacket* vresp = message.pkt; + delete message.event; + + m_queue.erase(vrp->getSerial()); + + m_Mutex.Unlock(); + + return vresp; +} + +bool cVNSIChannelScan::readData(uint8_t* buffer, int totalBytes, int TimeOut) +{ + int ret = m_session.readData(buffer, totalBytes, TimeOut); + if (ret == 1) + return true; + else if (ret == 0) + return false; + + SetClientConnected(false); + return false; +} diff --git a/xbmc/pvrclients/vdr-vnsi/VNSIChannelScan.h b/xbmc/pvrclients/vdr-vnsi/VNSIChannelScan.h new file mode 100644 index 0000000000..4cdb9b69db --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/VNSIChannelScan.h @@ -0,0 +1,104 @@ +#pragma once +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "client.h" +#include "VNSISession.h" +#include "thread.h" + +typedef enum scantype +{ + DVB_TERR = 0, + DVB_CABLE = 1, + DVB_SAT = 2, + PVRINPUT = 3, + PVRINPUT_FM = 4, + DVB_ATSC = 5, +} scantype_t; + + +class cVNSIChannelScan : public cThread +{ +public: + cVNSIChannelScan(); + ~cVNSIChannelScan(); + + bool Open(); + + bool OnClick(int controlId); + bool OnFocus(int controlId); + bool OnInit(); + bool OnAction(int actionId); + + static bool OnClickCB(GUIHANDLE cbhdl, int controlId); + static bool OnFocusCB(GUIHANDLE cbhdl, int controlId); + static bool OnInitCB(GUIHANDLE cbhdl); + static bool OnActionCB(GUIHANDLE cbhdl, int actionId); + +protected: + virtual void Action(void); + +private: + bool ReadCountries(); + bool ReadSatellites(); + void SetControlsVisible(scantype_t type); + void StartScan(); + void StopScan(); + void ReturnFromProcessView(); + void SetProgress(int procent); + void SetSignal(int procent, bool locked); + + cResponsePacket* ReadResult(cRequestPacket* vrp); + bool readData(uint8_t* buffer, int totalBytes, int TimeOut = 2); + + struct SMessage + { + cCondWait *event; + cResponsePacket *pkt; + }; + typedef std::map SMessages; + SMessages m_queue; + cMutex m_Mutex; + cVNSISession m_session; + CStdString m_header; + CStdString m_Signal; + bool m_running; + bool m_stopped; + bool m_Canceled; + + cGUIWindow *m_window; + cGUISpinControl *m_spinSourceType; + cGUISpinControl *m_spinCountries; + cGUISpinControl *m_spinSatellites; + cGUISpinControl *m_spinDVBCInversion; + cGUISpinControl *m_spinDVBCSymbolrates; + cGUISpinControl *m_spinDVBCqam; + cGUISpinControl *m_spinDVBTInversion; + cGUISpinControl *m_spinATSCType; + cGUIRadioButton *m_radioButtonTV; + cGUIRadioButton *m_radioButtonRadio; + cGUIRadioButton *m_radioButtonFTA; + cGUIRadioButton *m_radioButtonScrambled; + cGUIRadioButton *m_radioButtonHD; + cGUIProgressControl *m_progressDone; + cGUIProgressControl *m_progressSignal; + +}; diff --git a/xbmc/pvrclients/vdr-vnsi/VNSIData.cpp b/xbmc/pvrclients/vdr-vnsi/VNSIData.cpp new file mode 100644 index 0000000000..8388be7bea --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/VNSIData.cpp @@ -0,0 +1,1050 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "VNSIData.h" +#include "responsepacket.h" +#include "requestpacket.h" +#include "vdrcommand.h" +#include "recordings.h" +#include "tools.h" + +#define CMD_LOCK cMutexLock CmdLock((cMutex*)&m_Mutex) + +cVNSIData::cVNSIData() +{ +} + +cVNSIData::~cVNSIData() +{ + Close(); +} + +bool cVNSIData::Open(CStdString hostname, int port, long timeout) +{ + if(!m_session.Open(hostname, port, timeout)) + return false; + + SetDescription("VNSI Data Listener"); + Start(); + SetClientConnected(true); + return true; +} + +void cVNSIData::Close() +{ + Cancel(1); + m_session.Abort(); + m_session.Close(); +} + +bool cVNSIData::CheckConnection() +{ + return true; +} + +cResponsePacket* cVNSIData::ReadResult(cRequestPacket* vrp) +{ + m_Mutex.Lock(); + + SMessage &message(m_queue[vrp->getSerial()]); + message.event = new cCondWait(); + message.pkt = NULL; + + m_Mutex.Unlock(); + + if(!m_session.SendMessage(vrp)) + { + m_queue.erase(vrp->getSerial()); + return NULL; + } + + message.event->Wait(2000); + + m_Mutex.Lock(); + + cResponsePacket* vresp = message.pkt; + delete message.event; + + m_queue.erase(vrp->getSerial()); + + m_Mutex.Unlock(); + + return vresp; +} + +bool cVNSIData::GetTime(time_t *localTime, int *gmtOffset) +{ + cRequestPacket vrp; + if (!vrp.init(VDR_GETTIME)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetTime - Can't init cRequestPacket"); + return false; + } + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetTime - Can't get response packed"); + return false; + } + + uint32_t vdrTime = vresp->extract_U32(); + int32_t vdrTimeOffset = vresp->extract_S32(); + + *localTime = vdrTime; + *gmtOffset = vdrTimeOffset; + + delete vresp; + return true; +} + +bool cVNSIData::GetDriveSpace(long long *total, long long *used) +{ + cRequestPacket vrp; + if (!vrp.init(VDR_RECORDINGS_DISKSIZE)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetDriveSpace - Can't init cRequestPacket"); + return false; + } + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetDriveSpace - Can't get response packed"); + return false; + } + + uint32_t totalspace = vresp->extract_U32(); + uint32_t freespace = vresp->extract_U32(); + /* vresp->extract_U32(); percent not used */ + + *total = totalspace; + *used = (totalspace - freespace); + + /* Convert from kBytes to Bytes */ + *total *= 1024; + *used *= 1024; + + delete vresp; + return true; +} + +bool cVNSIData::SupportChannelScan() +{ + cRequestPacket vrp; + if (!vrp.init(VDR_SCAN_SUPPORTED)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::SupportChannelScan - Can't init cRequestPacket"); + return false; + } + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + XBMC->Log(LOG_ERROR, "cVNSIData::SupportChannelScan - Can't get response packed"); + return false; + } + + uint32_t ret = vresp->extract_U32(); + delete vresp; + return ret == VDR_RET_OK ? true : false; +} + +bool cVNSIData::EnableStatusInterface(bool onOff) +{ + cRequestPacket vrp; + if (!vrp.init(VDR_ENABLESTATUSINTERFACE)) return false; + if (!vrp.add_U8(onOff)) return false; + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + XBMC->Log(LOG_ERROR, "cVNSIData::EnableStatusInterface - Can't get response packed"); + return false; + } + + uint32_t ret = vresp->extract_U32(); + delete vresp; + return ret == VDR_RET_OK ? true : false; +} + +bool cVNSIData::EnableOSDInterface(bool onOff) +{ + cRequestPacket vrp; + if (!vrp.init(VDR_ENABLEOSDINTERFACE)) return false; + if (!vrp.add_U8(onOff)) return false; + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + XBMC->Log(LOG_ERROR, "cVNSIData::EnableStatusInterface - Can't get response packed"); + return false; + } + + uint32_t ret = vresp->extract_U32(); + delete vresp; + return ret == VDR_RET_OK ? true : false; +} + +int cVNSIData::GetGroupsCount() +{ + cRequestPacket vrp; + if (!vrp.init(VDR_CHANNELS_GROUPSCOUNT)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetGroupsCount - Can't init cRequestPacket"); + return -1; + } + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetGroupsCount - Can't get response packed"); + return -1; + } + + uint32_t count = vresp->extract_U32(); + + delete vresp; + return count; +} + +int cVNSIData::GetChannelsCount() +{ + cRequestPacket vrp; + if (!vrp.init(VDR_CHANNELS_GETCOUNT)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetChannelsCount - Can't init cRequestPacket"); + return -1; + } + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetChannelsCount - Can't get response packed"); + return -1; + } + + uint32_t count = vresp->extract_U32(); + + delete vresp; + return count; +} + +bool cVNSIData::GetGroupsList(PVRHANDLE handle, bool radio) +{ + cRequestPacket vrp; + if (!vrp.init(VDR_CHANNELS_GETGROUPS)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetGroupsList - Can't init cRequestPacket"); + return false; + } + if (!vrp.add_U32(radio)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetGroupsList - Can't add parameter to cRequestPacket"); + return false; + } + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetGroupsList - Can't get response packed"); + return false; + } + + while (!vresp->end()) + { + uint32_t index = vresp->extract_U32(); + uint32_t count = vresp->extract_U32(); + const char *name = vresp->extract_String(); + +// LOGDBG("Have added a group to list. %lu %lu %s", index, count, name); + delete name; + } + + delete vresp; + return true; +} + +bool cVNSIData::GetChannelsList(PVRHANDLE handle, bool radio) +{ + cRequestPacket vrp; + if (!vrp.init(VDR_CHANNELS_GETCHANNELS)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetChannelsList - Can't init cRequestPacket"); + return false; + } + if (!vrp.add_U32(radio)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetChannelsList - Can't add parameter to cRequestPacket"); + return false; + } + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetChannelsList - Can't get response packed"); + return false; + } + + while (!vresp->end()) + { + PVR_CHANNEL tag; + memset(&tag, 0 , sizeof(tag)); + + tag.number = vresp->extract_U32(); + tag.name = vresp->extract_String(); + tag.callsign = tag.name; + tag.uid = vresp->extract_U32(); + tag.bouquet = vresp->extract_U32(); + tag.encryption = vresp->extract_U32(); + uint32_t vtype = vresp->extract_U32(); + tag.radio = radio; + tag.input_format = ""; + tag.stream_url = ""; + + PVR->TransferChannelEntry(handle, &tag); + delete tag.name; + } + + delete vresp; + return true; +} + +bool cVNSIData::GetEPGForChannel(PVRHANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end) +{ + cRequestPacket vrp; + if (!vrp.init(VDR_EPG_GETFORCHANNEL)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetEPGForChannel - Can't init cRequestPacket"); + return false; + } + if (!vrp.add_U32(channel.number) || !vrp.add_U32(start) || !vrp.add_U32(end - start)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetEPGForChannel - Can't add parameter to cRequestPacket"); + return false; + } + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetEPGForChannel - Can't get response packed"); + return false; + } + + while (!vresp->end()) + { + PVR_PROGINFO tag; + memset(&tag, 0 , sizeof(tag)); + + tag.channum = channel.number; + tag.uid = vresp->extract_U32(); + tag.starttime = vresp->extract_U32(); + tag.endtime = tag.starttime + vresp->extract_U32(); + uint32_t content = vresp->extract_U32(); + tag.genre_type = content & 0xF0; + tag.genre_sub_type = content & 0x0F; + tag.parental_rating = vresp->extract_U32(); + tag.title = vresp->extract_String(); + tag.subtitle = vresp->extract_String(); + tag.description = vresp->extract_String(); + + PVR->TransferEpgEntry(handle, &tag); + delete tag.title; + delete tag.subtitle; + delete tag.description; + } + + delete vresp; + return true; +} + + +/** OPCODE's 60 - 69: VNSI network functions for timer access */ + +int cVNSIData::GetTimersCount() +{ + cRequestPacket vrp; + if (!vrp.init(VDR_TIMER_GETCOUNT)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetTimersCount - Can't init cRequestPacket"); + return -1; + } + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetTimersCount - Can't get response packed"); + return -1; + } + + uint32_t count = vresp->extract_U32(); + + delete vresp; + return count; +} + +PVR_ERROR cVNSIData::GetTimerInfo(unsigned int timernumber, PVR_TIMERINFO &tag) +{ + cRequestPacket vrp; + if (!vrp.init(VDR_TIMER_GET)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timernumber)) return PVR_ERROR_UNKOWN; + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + delete vresp; + return PVR_ERROR_UNKOWN; + } + + uint32_t returnCode = vresp->extract_U32(); + if (returnCode != VDR_RET_OK) + { + delete vresp; + if (returnCode == VDR_RET_DATAUNKNOWN) + return PVR_ERROR_NOT_POSSIBLE; + else if (returnCode == VDR_RET_ERROR) + return PVR_ERROR_SERVER_ERROR; + } + + tag.index = vresp->extract_U32(); + tag.active = vresp->extract_U32(); + uint32_t recording = vresp->extract_U32(); + uint32_t pending = vresp->extract_U32(); + tag.priority = vresp->extract_U32(); + tag.lifetime = vresp->extract_U32(); + tag.channelNum = vresp->extract_U32(); + tag.starttime = vresp->extract_U32(); + tag.endtime = vresp->extract_U32(); + tag.firstday = vresp->extract_U32(); + tag.repeatflags = vresp->extract_U32(); + tag.repeat = tag.repeatflags == 0 ? false : true; + tag.title = vresp->extract_String(); + tag.directory = ""; + + delete tag.title; + delete vresp; + return PVR_ERROR_NO_ERROR; +} + +bool cVNSIData::GetTimersList(PVRHANDLE handle) +{ + cRequestPacket vrp; + if (!vrp.init(VDR_TIMER_GETLIST)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetTimersList - Can't init cRequestPacket"); + return false; + } + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + delete vresp; + XBMC->Log(LOG_ERROR, "cVNSIData::GetTimersList - Can't get response packed"); + return false; + } + + uint32_t numTimers = vresp->extract_U32(); + if (numTimers > 0) + { + while (!vresp->end()) + { + PVR_TIMERINFO tag; + tag.index = vresp->extract_U32(); + tag.active = vresp->extract_U32(); + uint32_t recording = vresp->extract_U32(); + uint32_t pending = vresp->extract_U32(); + tag.priority = vresp->extract_U32(); + tag.lifetime = vresp->extract_U32(); + tag.channelNum = vresp->extract_U32(); + tag.starttime = vresp->extract_U32(); + tag.endtime = vresp->extract_U32(); + tag.firstday = vresp->extract_U32(); + tag.repeatflags = vresp->extract_U32(); + tag.repeat = tag.repeatflags == 0 ? false : true; + tag.title = vresp->extract_String(); + tag.directory = ""; + + PVR->TransferTimerEntry(handle, &tag); + delete tag.title; + } + } + delete vresp; + return true; +} + +PVR_ERROR cVNSIData::AddTimer(const PVR_TIMERINFO &timerinfo) +{ + cRequestPacket vrp; + if (!vrp.init(VDR_TIMER_ADD)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::AddTimer - Can't init cRequestPacket"); + return PVR_ERROR_UNKOWN; + } + if (!vrp.add_U32(timerinfo.active)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.priority)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.lifetime)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.channelNum)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.starttime)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.endtime)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.repeat ? timerinfo.firstday : 0)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.repeatflags))return PVR_ERROR_UNKOWN; + if (!vrp.add_String(timerinfo.title)) return PVR_ERROR_UNKOWN; + if (!vrp.add_String("")) return PVR_ERROR_UNKOWN; + + cResponsePacket* vresp = ReadResult(&vrp); + if (vresp->noResponse()) + { + delete vresp; + XBMC->Log(LOG_ERROR, "cVNSIData::AddTimer - Can't get response packed"); + return PVR_ERROR_UNKOWN; + } + uint32_t returnCode = vresp->extract_U32(); + delete vresp; + if (returnCode == VDR_RET_DATALOCKED) + return PVR_ERROR_ALREADY_PRESENT; + else if (returnCode == VDR_RET_DATAINVALID) + return PVR_ERROR_NOT_SAVED; + else if (returnCode == VDR_RET_ERROR) + return PVR_ERROR_SERVER_ERROR; + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR cVNSIData::DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force) +{ + cRequestPacket vrp; + if (!vrp.init(VDR_TIMER_DELETE)) + return PVR_ERROR_UNKOWN; + + if (!vrp.add_U32(timerinfo.index)) + return PVR_ERROR_UNKOWN; + + if (!vrp.add_U32(force)) + return PVR_ERROR_UNKOWN; + + cResponsePacket* vresp = ReadResult(&vrp); + if (vresp->noResponse()) + { + delete vresp; + return PVR_ERROR_UNKOWN; + } + + uint32_t returnCode = vresp->extract_U32(); + delete vresp; + + if (returnCode == VDR_RET_DATALOCKED) + return PVR_ERROR_NOT_DELETED; + if (returnCode == VDR_RET_RECRUNNING) + return PVR_ERROR_RECORDING_RUNNING; + else if (returnCode == VDR_RET_DATAINVALID) + return PVR_ERROR_NOT_POSSIBLE; + else if (returnCode == VDR_RET_ERROR) + return PVR_ERROR_SERVER_ERROR; + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR cVNSIData::RenameTimer(const PVR_TIMERINFO &timerinfo, const char *newname) +{ + PVR_TIMERINFO timerinfo1; + PVR_ERROR ret = GetTimerInfo(timerinfo.index, timerinfo1); + if (ret != PVR_ERROR_NO_ERROR) + return ret; + + timerinfo1.title = newname; + return UpdateTimer(timerinfo1); +} + +PVR_ERROR cVNSIData::UpdateTimer(const PVR_TIMERINFO &timerinfo) +{ + cRequestPacket vrp; + if (!vrp.init(VDR_TIMER_UPDATE)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.index)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.active)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.priority)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.lifetime)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.channelNum)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.starttime)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.endtime)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.repeat ? timerinfo.firstday : 0)) return PVR_ERROR_UNKOWN; + if (!vrp.add_U32(timerinfo.repeatflags))return PVR_ERROR_UNKOWN; + if (!vrp.add_String(timerinfo.title)) return PVR_ERROR_UNKOWN; + if (!vrp.add_String("")) return PVR_ERROR_UNKOWN; + + cResponsePacket* vresp = ReadResult(&vrp); + if (vresp->noResponse()) + { + delete vresp; + return PVR_ERROR_UNKOWN; + } + uint32_t returnCode = vresp->extract_U32(); + delete vresp; + if (returnCode == VDR_RET_DATAUNKNOWN) + return PVR_ERROR_NOT_POSSIBLE; + else if (returnCode == VDR_RET_DATAINVALID) + return PVR_ERROR_NOT_SAVED; + else if (returnCode == VDR_RET_ERROR) + return PVR_ERROR_SERVER_ERROR; + + return PVR_ERROR_NO_ERROR; +} + +int cVNSIData::GetRecordingsCount() +{ + cRequestPacket vrp; + if (!vrp.init(VDR_RECORDINGS_GETCOUNT)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetRecordingsCount - Can't init cRequestPacket"); + return -1; + } + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetRecordingsCount - Can't get response packed"); + return -1; + } + + uint32_t count = vresp->extract_U32(); + + delete vresp; + return count; +} + +PVR_ERROR cVNSIData::GetRecordingsList(PVRHANDLE handle) +{ + m_recIndex = 1; + if (g_bUseRecordingsDir && g_szRecordingsDir != "") + { + ScanVideoDir(handle, g_szRecordingsDir.c_str(), g_szRecordingsDir.c_str()); + } + else + { + bool haveCheckedLocalAccess = true /*false*/; + m_RecordsPaths.clear(); + + cRequestPacket vrp; + if (!vrp.init(VDR_RECORDINGS_GETLIST)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetRecordingsList - Can't init cRequestPacket"); + return PVR_ERROR_UNKOWN; + } + + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + { + XBMC->Log(LOG_ERROR, "cVNSIData::GetRecordingsList - Can't get response packed"); + return PVR_ERROR_UNKOWN; + } + + char* videodir = vresp->extract_String(); + m_videodir = videodir; + delete[] videodir; + + while (!vresp->end()) + { + CStdString title; + + PVR_RECORDINGINFO tag; + tag.index = m_recIndex++; + tag.recording_time = vresp->extract_U32(); + tag.duration = vresp->extract_U32(); + tag.priority = vresp->extract_U32(); + tag.lifetime = vresp->extract_U32(); + tag.channel_name = vresp->extract_String(); + char* name = vresp->extract_String(); + title = name; + tag.subtitle = vresp->extract_String(); + tag.description = vresp->extract_String(); + + char* fileName = vresp->extract_String(); + + /* Save the given path name for later to translate the + index numbers to the path name. */ + m_RecordsPaths.push_back(fileName); + + /* Cleanup now the path name and remove VDR's 2 top + directories and the strip the base path */ + CStdString path = fileName+m_videodir.size()+1; + path = path.substr(0, path.find_last_of("/\\")); + + size_t found = path.find_last_of("/\\"); + if (found != CStdString::npos) + { + /* If no title is present use recording dir name + as title */ + if (title.IsEmpty()) + title = path.substr(found+1); + path = path.substr(0, found); + } + else + { + /* If no title is present use recording dir name + as title */ + if (title.IsEmpty()) + title = path; + path = ""; + } + + tag.title = title.c_str(); + tag.directory = path.c_str(); + tag.stream_url = ""; + + /* Check if we can open the first given recording dir on + local filesystem, if yes, scan the files itself and + ignore VNSI server for access them */ + if (!haveCheckedLocalAccess) + { + XBMC->Log(LOG_NOTICE, "Trying to open '%s'", fileName); + DIR *vdrrecdir = opendir(fileName); + if (vdrrecdir) + { + closedir(vdrrecdir); + delete[] tag.channel_name; + delete[] tag.subtitle; + delete[] tag.description; + delete[] name; + delete[] fileName; + delete vresp; + + XBMC->Log(LOG_NOTICE, "Found recordings on local disk, ignoring VNSI Server and scanning directories byself"); + + ScanVideoDir(handle, m_videodir.c_str(), m_videodir.c_str()); + return PVR_ERROR_NO_ERROR; + } + + haveCheckedLocalAccess = true; + } + + PVR->TransferRecordingEntry(handle, &tag); + + delete[] tag.channel_name; + delete[] tag.subtitle; + delete[] tag.description; + delete[] name; + delete[] fileName; + } + + delete vresp; + } + + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR cVNSIData::DeleteRecording(CStdString path) +{ + cRequestPacket vrp; + if (!vrp.init(VDR_RECORDINGS_DELETE)) + return PVR_ERROR_UNKOWN; + + if (!vrp.add_String(path)) + return PVR_ERROR_UNKOWN; + + cResponsePacket* vresp = ReadResult(&vrp); + if (vresp->noResponse()) + { + delete vresp; + return PVR_ERROR_UNKOWN; + } + + uint32_t returnCode = vresp->extract_U32(); + delete vresp; + + if (returnCode == VDR_RET_DATALOCKED) + return PVR_ERROR_NOT_DELETED; + if (returnCode == VDR_RET_RECRUNNING) + return PVR_ERROR_RECORDING_RUNNING; + else if (returnCode == VDR_RET_DATAINVALID) + return PVR_ERROR_NOT_POSSIBLE; + else if (returnCode == VDR_RET_ERROR) + return PVR_ERROR_SERVER_ERROR; + + return PVR_ERROR_NO_ERROR; +} + +void cVNSIData::Action() +{ + uint32_t channelID; + uint32_t requestID; + uint32_t userDataLength; + uint8_t* userData; + + uint32_t timeNow; + uint32_t lastKAsent = 0; + uint32_t lastKArecv = time(NULL); + bool readSuccess; + + cResponsePacket* vresp; + + while (Running()) + { + timeNow = time(NULL); + + readSuccess = readData((uint8_t*)&channelID, sizeof(uint32_t)); // 2s timeout atm + if (!readSuccess && !IsClientConnected()) + return; // return to stop this thread + + // Error or timeout. + if (!lastKAsent) // have not sent a KA + { + if (lastKArecv < (timeNow - 5)) + { + DEVDBG("cVNSIData::Action() - Sending KA packet"); + if (!sendKA(timeNow)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::Action() - Could not send KA, calling connectionDied"); + SetClientConnected(false); + return; + } + lastKAsent = timeNow; + } + } + else + { + if (lastKAsent <= (timeNow - 10)) + { + XBMC->Log(LOG_ERROR, "cVNSIData::Action() - lastKA over 10s ago, calling connectionDied"); + SetClientConnected(false); + return; + } + } + + if (!readSuccess) continue; // no data was read but the connection is ok. + + // Data was read + channelID = ntohl(channelID); + if (channelID == CHANNEL_REQUEST_RESPONSE) + { + if (!readData((uint8_t*)&requestID, sizeof(uint32_t))) break; + requestID = ntohl(requestID); + if (!readData((uint8_t*)&userDataLength, sizeof(uint32_t))) break; + userDataLength = ntohl(userDataLength); + if (userDataLength > 5000000) break; // how big can these packets get? + userData = NULL; + if (userDataLength > 0) + { + userData = (uint8_t*)malloc(userDataLength); + if (!userData) break; + if (!readData(userData, userDataLength)) break; + } + + vresp = new cResponsePacket(); + vresp->setResponse(requestID, userData, userDataLength); + DEVDBG("cVNSIData::Action() - Rxd a response packet, requestID=%lu, len=%lu", requestID, userDataLength); + + CMD_LOCK; + SMessages::iterator it = m_queue.find(requestID); + if (it != m_queue.end()) + { + it->second.pkt = vresp; + it->second.event->Signal(); + } + else + { + delete vresp; + } + } + else if (channelID == CHANNEL_STATUS) + { + if (!readData((uint8_t*)&requestID, sizeof(uint32_t))) break; + requestID = ntohl(requestID); + if (!readData((uint8_t*)&userDataLength, sizeof(uint32_t))) break; + userDataLength = ntohl(userDataLength); + if (userDataLength > 5000000) break; // how big can these packets get? + userData = NULL; + if (userDataLength > 0) + { + userData = (uint8_t*)malloc(userDataLength); + if (!userData) break; + if (!readData(userData, userDataLength)) break; + } + + if (requestID == VDR_STATUS_MESSAGE) + { + uint32_t type = ntohl(*(uint32_t*)&userData[0]); + int length = strlen((char*)&userData[4]); + char* str = new char[length + 1]; + strcpy(str, (char*)&userData[4]); + + CStdString text = str; + if (g_bCharsetConv) + XBMC->UnknownToUTF8(text); + + if (type == 2) + XBMC->QueueNotification(QUEUE_ERROR, text.c_str()); + if (type == 1) + XBMC->QueueNotification(QUEUE_WARNING, text.c_str()); + else + XBMC->QueueNotification(QUEUE_INFO, text.c_str()); + + delete[] str; + } + else if (requestID == VDR_STATUS_RECORDING) + { + uint32_t device = ntohl(*(uint32_t*)&userData[0]); + uint32_t on = ntohl(*(uint32_t*)&userData[4]); + + int length = strlen((char*)&userData[8]); + char* str = NULL; + if (length > 1) + { + str = new char[length + 1]; + strcpy(str, (char*)&userData[8]); + } + + int length2 = strlen((char*)&userData[8+length]); + char* str2 = NULL; + if (length2 > 1) + { + str2 = new char[length2 + 1]; + strcpy(str2, (char*)&userData[8+length]); + } + + PVR->Recording(str, str2, on); + PVR->TriggerTimerUpdate(); + + delete[] str; + delete[] str2; + } + else if (requestID == VDR_STATUS_TIMERCHANGE) + { + uint32_t status = ntohl(*(uint32_t*)&userData[0]); + int length = strlen((char*)&userData[4]); + char* str = new char[length + 1]; + strcpy(str, (char*)&userData[4]); + delete[] str; + PVR->TriggerTimerUpdate(); + } + + if (userData) + free(userData); + } + else if (channelID == CHANNEL_KEEPALIVE) + { + uint32_t KAreply = 0; + if (!readData((uint8_t*)&KAreply, sizeof(uint32_t))) break; + KAreply = (uint32_t)ntohl(KAreply); + if (KAreply == lastKAsent) // successful KA response + { + lastKAsent = 0; + lastKArecv = KAreply; + DEVDBG("cVNSIData::Action() - Rxd correct KA reply"); + } + } + else + { + XBMC->Log(LOG_ERROR, "cVNSIData::Action() - Rxd a response packet on channel %lu !!", channelID); + break; + } + } +} + +bool cVNSIData::sendKA(uint32_t timeStamp) +{ + char buffer[8]; + *(uint32_t*)&buffer[0] = htonl(CHANNEL_KEEPALIVE); + *(uint32_t*)&buffer[4] = htonl(timeStamp); + if ((uint32_t)m_session.sendData(buffer, 8) != 8) return false; + return true; +} + +bool cVNSIData::readData(uint8_t* buffer, int totalBytes, int TimeOut) +{ + int ret = m_session.readData(buffer, totalBytes, TimeOut); + if (ret == 1) + return true; + else if (ret == 0) + return false; + + SetClientConnected(false); + return false; +} + +CStdString cVNSIData::GetRecordingPath(int index) +{ + if (index <= 0 || index > m_RecordsPaths.size()) + return ""; + + return m_RecordsPaths[index-1]; +} + +#define MAX_LINK_LEVEL 6 +void cVNSIData::ScanVideoDir(PVRHANDLE handle, const char *DirName, const char *DirBase, bool Deleted, int LinkLevel) +{ + cReadDir d(DirName); + struct dirent *e; + while ((e = d.Next()) != NULL) + { + if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) + { + char *buffer = strdup(AddDirectory(DirName, e->d_name)); + struct stat st; + if (stat(buffer, &st) == 0) + { + int Link = 0; + if (S_ISLNK(st.st_mode)) + { + if (LinkLevel > MAX_LINK_LEVEL) + { + XBMC->Log(LOG_ERROR, "max link level exceeded - not scanning %s", buffer); + continue; + } + Link = 1; + char *old = buffer; + buffer = ReadLink(old); + free(old); + if (!buffer) + continue; + if (stat(buffer, &st) != 0) + { + free(buffer); + continue; + } + } + if (S_ISDIR(st.st_mode)) + { + if (endswith(buffer, Deleted ? DELEXT : RECEXT)) + { + cRecording recording(buffer, DirBase); + + PVR_RECORDINGINFO tag; + tag.index = m_recIndex++; + tag.channel_name = recording.ChannelName(); + tag.lifetime = recording.Lifetime(); + tag.priority = recording.Priority(); + tag.recording_time = recording.StartTime(); + tag.duration = recording.Duration(); + tag.subtitle = recording.ShortText(); + tag.description = recording.Description(); + tag.title = recording.Title(); + tag.directory = recording.Directory(); + tag.stream_url = recording.StreamURL(); + + PVR->TransferRecordingEntry(handle, &tag); + } + else + ScanVideoDir(handle, buffer, DirBase, Deleted, LinkLevel + Link); + } + } + free(buffer); + } + } +} diff --git a/xbmc/pvrclients/vdr-vnsi/VNSIData.h b/xbmc/pvrclients/vdr-vnsi/VNSIData.h new file mode 100644 index 0000000000..eac787747e --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/VNSIData.h @@ -0,0 +1,91 @@ +#pragma once +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "client.h" +#include "thread.h" +#include "VNSISession.h" + +class cResponsePacket; +class cRequestPacket; + +class cVNSIData : public cThread +{ +public: + cVNSIData(); + ~cVNSIData(); + + bool Open(CStdString hostname, int port, long timeout); + void Close(); + bool CheckConnection(); + + cResponsePacket* ReadResult(cRequestPacket* vrp); + int GetProtocol() { return m_session.GetProtocol(); } + CStdString GetServerName() { return m_session.GetServerName(); } + CStdString GetVersion() { return m_session.GetVersion(); } + bool SupportChannelScan(); + bool EnableStatusInterface(bool onOff); + bool EnableOSDInterface(bool onOff); + bool GetTime(time_t *localTime, int *gmtOffset); + bool GetDriveSpace(long long *total, long long *used); + int GetGroupsCount(); + int GetChannelsCount(); + bool GetGroupsList(PVRHANDLE handle, bool radio = false); + bool GetChannelsList(PVRHANDLE handle, bool radio = false); + bool GetEPGForChannel(PVRHANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end); + bool GetTimersList(PVRHANDLE handle); + + int GetTimersCount(); + PVR_ERROR AddTimer(const PVR_TIMERINFO &timerinfo); + PVR_ERROR GetTimerInfo(unsigned int timernumber, PVR_TIMERINFO &tag); + PVR_ERROR DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force = false); + PVR_ERROR RenameTimer(const PVR_TIMERINFO &timerinfo, const char *newname); + PVR_ERROR UpdateTimer(const PVR_TIMERINFO &timerinfo); + + int GetRecordingsCount(); + PVR_ERROR GetRecordingsList(PVRHANDLE handle); + CStdString GetRecordingPath(int index); + PVR_ERROR DeleteRecording(CStdString path); + + +protected: + virtual void Action(void); + +private: + bool readData(uint8_t* buffer, int totalBytes, int TimeOut = 2); + bool sendKA(uint32_t timeStamp); + void ScanVideoDir(PVRHANDLE handle, const char *DirName, const char *DirBase, bool Deleted = false, int LinkLevel = 0); + + struct SMessage + { + cCondWait *event; + cResponsePacket *pkt; + }; + typedef std::map SMessages; + typedef std::vector RecordPaths; + + cVNSISession m_session; + cMutex m_Mutex; + SMessages m_queue; + RecordPaths m_RecordsPaths; + CStdString m_videodir; + int m_recIndex; +}; diff --git a/xbmc/pvrclients/vdr-vnsi/VNSIDemux.cpp b/xbmc/pvrclients/vdr-vnsi/VNSIDemux.cpp new file mode 100644 index 0000000000..23a2397d3e --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/VNSIDemux.cpp @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#define __STDC_CONSTANT_MACROS +#include +#include +#include // For codec id's +#include "VNSIDemux.h" +#include "tools.h" +#include "responsepacket.h" +#include "requestpacket.h" +#include "vdrcommand.h" + +cVNSIDemux::cVNSIDemux() + : m_startup(false) + , m_channel(0) + , m_StatusCount(0) +{ + m_Streams.nstreams = 0; +} + +cVNSIDemux::~cVNSIDemux() +{ + Close(); +} + +bool cVNSIDemux::Open(const PVR_CHANNEL &channelinfo) +{ + m_channel = channelinfo.number; + + if(!m_session.Open(g_szHostname, g_iPort, g_iConnectTimeout, "XBMC Live stream receiver")) + return false; + + cRequestPacket vrp; + if (!vrp.init(VDR_CHANNELSTREAM_OPEN) || + !vrp.add_U32(m_channel) || + !m_session.ReadSuccess(&vrp)) + { + XBMC->Log(LOG_ERROR, "cVNSIDemux::Open - Can't open channel %i - %s", m_channel, channelinfo.name); + return false; + } + + m_StatusCount = 0; + m_startup = true; + + while (m_Streams.nstreams == 0 && m_StatusCount == 0) + { + DemuxPacket* pkg = Read(); + if(!pkg) + { + Close(); + return false; + } + PVR->FreeDemuxPacket(pkg); + } + + return true; +} + +void cVNSIDemux::Close() +{ + cRequestPacket vrp; + m_session.Close(); +} + +bool cVNSIDemux::GetStreamProperties(PVR_STREAMPROPS* props) +{ + props->nstreams = m_Streams.nstreams; + for (int i = 0; i < m_Streams.nstreams; i++) + { + props->stream[i].id = m_Streams.stream[i].id; + props->stream[i].physid = m_Streams.stream[i].physid; + props->stream[i].codec_type = m_Streams.stream[i].codec_type; + props->stream[i].codec_id = m_Streams.stream[i].codec_id; + props->stream[i].height = m_Streams.stream[i].height; + props->stream[i].width = m_Streams.stream[i].width; + props->stream[i].language[0] = m_Streams.stream[i].language[0]; + props->stream[i].language[1] = m_Streams.stream[i].language[1]; + props->stream[i].language[2] = m_Streams.stream[i].language[2]; + props->stream[i].language[3] = m_Streams.stream[i].language[3]; + props->stream[i].identifier = m_Streams.stream[i].identifier; + } + return (props->nstreams > 0); +} + +void cVNSIDemux::Abort() +{ + m_Streams.nstreams = 0; + m_session.Abort(); +} + +DemuxPacket* cVNSIDemux::Read() +{ + cResponsePacket *resp; + while ((resp = m_session.ReadMessage(3))) + { + if (resp->getChannelID() == CHANNEL_STREAM) + { + if (resp->getOpCodeID() == VDR_STREAM_CHANGE) + { + StreamChange(resp); + if (!m_startup) + { + DemuxPacket* pkt = PVR->AllocateDemuxPacket(0); + pkt->iStreamId = DMX_SPECIALID_STREAMCHANGE; + delete resp; + return pkt; + } + else + m_startup = false; + } + else if (resp->getOpCodeID() == VDR_STREAM_STATUS) + StreamStatus(resp); + else if (resp->getOpCodeID() == VDR_STREAM_SIGNALINFO) + StreamSignalInfo(resp); + else if (resp->getOpCodeID() == VDR_STREAM_CONTENTINFO) + { + StreamContentInfo(resp); + DemuxPacket* pkt = PVR->AllocateDemuxPacket(sizeof(PVR_STREAMPROPS)); + memcpy(pkt->pData, &m_Streams, sizeof(PVR_STREAMPROPS)); + pkt->iStreamId = DMX_SPECIALID_STREAMINFO; + pkt->iSize = sizeof(PVR_STREAMPROPS); + delete resp; + return pkt; + } + else if (resp->getOpCodeID() == VDR_STREAM_MUXPKT) + { + void *bin = resp->getUserData(); + size_t binlen = resp->getUserDataLength(); + + DemuxPacket* pkt = PVR->AllocateDemuxPacket(binlen); + + memcpy(pkt->pData, bin, binlen); + + pkt->iSize = binlen; + pkt->duration = (double)resp->getDuration() * DVD_TIME_BASE / 1000000; + pkt->dts = (double)resp->getDTS() * DVD_TIME_BASE / 1000000; + pkt->pts = (double)resp->getPTS() * DVD_TIME_BASE / 1000000; + pkt->iStreamId = -1; + for(int i = 0; i < m_Streams.nstreams; i++) + { + if(m_Streams.stream[i].physid == (int)resp->getStreamID()) + { + pkt->iStreamId = i; + break; + } + } + + free(bin); + delete resp; + return pkt; + } + } + + break; + } + + if (resp) + { + delete resp; + return PVR->AllocateDemuxPacket(0); + } + return NULL; +} + +bool cVNSIDemux::SwitchChannel(const PVR_CHANNEL &channelinfo) +{ + XBMC->Log(LOG_DEBUG, "changing to channel %d", channelinfo.number); + + cRequestPacket vrp; + if (!vrp.init(VDR_CHANNELSTREAM_OPEN) || !vrp.add_U32(channelinfo.number) || !m_session.ReadSuccess(&vrp)) + { + XBMC->Log(LOG_ERROR, "cVNSIDemux::SetChannel - failed to set channel"); + } + else + { + m_channel = channelinfo.number; + m_Streams.nstreams = 0; + m_startup = true; + while (m_Streams.nstreams == 0 && m_StatusCount == 0) + { + DemuxPacket* pkg = Read(); + if(!pkg) + return false; + PVR->FreeDemuxPacket(pkg); + } + return true; + } + return false; +} + +bool cVNSIDemux::GetSignalStatus(PVR_SIGNALQUALITY &qualityinfo) +{ + if (m_Quality.fe_name.IsEmpty()) + return false; + + strncpy(qualityinfo.frontend_name, m_Quality.fe_name.c_str(), sizeof(qualityinfo.frontend_name)); + strncpy(qualityinfo.frontend_status, m_Quality.fe_status.c_str(), sizeof(qualityinfo.frontend_status)); + qualityinfo.signal = (uint16_t)m_Quality.fe_signal; + qualityinfo.snr = (uint16_t)m_Quality.fe_snr; + qualityinfo.ber = (uint32_t)m_Quality.fe_ber; + qualityinfo.unc = (uint32_t)m_Quality.fe_unc; + qualityinfo.video_bitrate = 0; + qualityinfo.audio_bitrate = 0; + qualityinfo.dolby_bitrate = 0; + + return true; +} + +void cVNSIDemux::StreamChange(cResponsePacket *resp) +{ + m_Streams.nstreams = 0; + + while (!resp->end()) + { + uint32_t index = resp->extract_U32(); + const char* type = resp->extract_String(); + + DEVDBG("cVNSIDemux::StreamChange - id: %d, type: %s", index, type); + + m_Streams.stream[m_Streams.nstreams].fpsscale = 0; + m_Streams.stream[m_Streams.nstreams].fpsrate = 0; + m_Streams.stream[m_Streams.nstreams].height = 0; + m_Streams.stream[m_Streams.nstreams].width = 0; + m_Streams.stream[m_Streams.nstreams].aspect = 0.0; + + m_Streams.stream[m_Streams.nstreams].channels = 0; + m_Streams.stream[m_Streams.nstreams].samplerate = 0; + m_Streams.stream[m_Streams.nstreams].blockalign = 0; + m_Streams.stream[m_Streams.nstreams].bitrate = 0; + m_Streams.stream[m_Streams.nstreams].bits_per_sample = 0; + + if(!strcmp(type, "AC3")) + { + const char *language = resp->extract_String(); + + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_AUDIO; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_AC3; + m_Streams.stream[m_Streams.nstreams].language[0]= language[0]; + m_Streams.stream[m_Streams.nstreams].language[1]= language[1]; + m_Streams.stream[m_Streams.nstreams].language[2]= language[2]; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "MPEG2AUDIO")) + { + const char *language = resp->extract_String(); + + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_AUDIO; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_MP2; + m_Streams.stream[m_Streams.nstreams].language[0]= language[0]; + m_Streams.stream[m_Streams.nstreams].language[1]= language[1]; + m_Streams.stream[m_Streams.nstreams].language[2]= language[2]; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "AAC")) + { + const char *language = resp->extract_String(); + + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_AUDIO; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_AAC; + m_Streams.stream[m_Streams.nstreams].language[0]= language[0]; + m_Streams.stream[m_Streams.nstreams].language[1]= language[1]; + m_Streams.stream[m_Streams.nstreams].language[2]= language[2]; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "DTS")) + { + const char *language = resp->extract_String(); + + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_AUDIO; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_DTS; + m_Streams.stream[m_Streams.nstreams].language[0]= language[0]; + m_Streams.stream[m_Streams.nstreams].language[1]= language[1]; + m_Streams.stream[m_Streams.nstreams].language[2]= language[2]; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "EAC3")) + { + const char *language = resp->extract_String(); + + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_AUDIO; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_EAC3; + m_Streams.stream[m_Streams.nstreams].language[0]= language[0]; + m_Streams.stream[m_Streams.nstreams].language[1]= language[1]; + m_Streams.stream[m_Streams.nstreams].language[2]= language[2]; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "MPEG2VIDEO")) + { + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_VIDEO; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_MPEG2VIDEO; + m_Streams.stream[m_Streams.nstreams].fpsscale = resp->extract_U32(); + m_Streams.stream[m_Streams.nstreams].fpsrate = resp->extract_U32(); + m_Streams.stream[m_Streams.nstreams].height = resp->extract_U32(); + m_Streams.stream[m_Streams.nstreams].width = resp->extract_U32(); + m_Streams.stream[m_Streams.nstreams].aspect = resp->extract_Double(); + m_Streams.stream[m_Streams.nstreams].language[0]= 0; + m_Streams.stream[m_Streams.nstreams].language[1]= 0; + m_Streams.stream[m_Streams.nstreams].language[2]= 0; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "H264")) + { + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_VIDEO; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_H264; + m_Streams.stream[m_Streams.nstreams].fpsscale = resp->extract_U32(); + m_Streams.stream[m_Streams.nstreams].fpsrate = resp->extract_U32(); + m_Streams.stream[m_Streams.nstreams].height = resp->extract_U32(); + m_Streams.stream[m_Streams.nstreams].width = resp->extract_U32(); + m_Streams.stream[m_Streams.nstreams].aspect = resp->extract_Double(); + m_Streams.stream[m_Streams.nstreams].language[0]= 0; + m_Streams.stream[m_Streams.nstreams].language[1]= 0; + m_Streams.stream[m_Streams.nstreams].language[2]= 0; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "DVBSUB")) + { + const char *language = resp->extract_String(); + uint32_t composition_id = resp->extract_U32(); + uint32_t ancillary_id = resp->extract_U32(); + + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_SUBTITLE; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_DVB_SUBTITLE; + m_Streams.stream[m_Streams.nstreams].language[0]= language[0]; + m_Streams.stream[m_Streams.nstreams].language[1]= language[1]; + m_Streams.stream[m_Streams.nstreams].language[2]= language[2]; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + m_Streams.stream[m_Streams.nstreams].identifier = (composition_id & 0xffff) | ((ancillary_id & 0xffff) << 16); + m_Streams.nstreams++; + } + else if(!strcmp(type, "TEXTSUB")) + { + const char *language = resp->extract_String(); + + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_SUBTITLE; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_TEXT; + m_Streams.stream[m_Streams.nstreams].language[0]= language[0]; + m_Streams.stream[m_Streams.nstreams].language[1]= language[1]; + m_Streams.stream[m_Streams.nstreams].language[2]= language[2]; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + else if(!strcmp(type, "TELETEXT")) + { + m_Streams.stream[m_Streams.nstreams].id = m_Streams.nstreams; + m_Streams.stream[m_Streams.nstreams].physid = index; + m_Streams.stream[m_Streams.nstreams].codec_type = CODEC_TYPE_SUBTITLE; + m_Streams.stream[m_Streams.nstreams].codec_id = CODEC_ID_DVB_TELETEXT; + m_Streams.stream[m_Streams.nstreams].language[0]= 0; + m_Streams.stream[m_Streams.nstreams].language[1]= 0; + m_Streams.stream[m_Streams.nstreams].language[2]= 0; + m_Streams.stream[m_Streams.nstreams].language[3]= 0; + m_Streams.stream[m_Streams.nstreams].identifier = -1; + m_Streams.nstreams++; + } + + if (m_Streams.nstreams >= PVR_STREAM_MAX_STREAMS) + { + XBMC->Log(LOG_ERROR, "cVNSIDemux::StreamChange - max amount of streams reached"); + break; + } + } +} + +void cVNSIDemux::StreamStatus(cResponsePacket *resp) +{ + const char* status = resp->extract_String(); + if(status == NULL) + m_Status = ""; + else + { + m_StatusCount++; + m_Status = status; + XBMC->Log(LOG_DEBUG, "cVNSIDemux::StreamStatus - %s", status); + XBMC->QueueNotification(QUEUE_INFO, status); + } +} + +void cVNSIDemux::StreamSignalInfo(cResponsePacket *resp) +{ + m_Quality.fe_name = resp->extract_String(); + m_Quality.fe_status = resp->extract_String(); + m_Quality.fe_snr = resp->extract_U32(); + m_Quality.fe_signal = resp->extract_U32(); + m_Quality.fe_ber = resp->extract_U32(); + m_Quality.fe_unc = resp->extract_U32(); +} + +void cVNSIDemux::StreamContentInfo(cResponsePacket *resp) +{ + for (int i = 0; i < m_Streams.nstreams && !resp->end(); i++) + { + uint32_t index = resp->extract_U32(); + if (index == m_Streams.stream[i].physid) + { + if (m_Streams.stream[i].codec_type == CODEC_TYPE_AUDIO) + { + const char *language = resp->extract_String(); + m_Streams.stream[i].channels = resp->extract_U32(); + m_Streams.stream[i].samplerate = resp->extract_U32(); + m_Streams.stream[i].blockalign = resp->extract_U32(); + m_Streams.stream[i].bitrate = resp->extract_U32(); + m_Streams.stream[i].bits_per_sample = resp->extract_U32(); + m_Streams.stream[i].language[0] = language[0]; + m_Streams.stream[i].language[1] = language[1]; + m_Streams.stream[i].language[2] = language[2]; + m_Streams.stream[i].language[3] = 0; + } + else if (m_Streams.stream[i].codec_type == CODEC_TYPE_VIDEO) + { + m_Streams.stream[i].fpsscale = resp->extract_U32(); + m_Streams.stream[i].fpsrate = resp->extract_U32(); + m_Streams.stream[i].height = resp->extract_U32(); + m_Streams.stream[i].width = resp->extract_U32(); + m_Streams.stream[i].aspect = resp->extract_Double(); + } + else if (m_Streams.stream[i].codec_type == CODEC_TYPE_SUBTITLE) + { + const char *language = resp->extract_String(); + uint32_t composition_id = resp->extract_U32(); + uint32_t ancillary_id = resp->extract_U32(); + m_Streams.stream[i].identifier = (composition_id & 0xffff) | ((ancillary_id & 0xffff) << 16); + m_Streams.stream[i].language[0]= language[0]; + m_Streams.stream[i].language[1]= language[1]; + m_Streams.stream[i].language[2]= language[2]; + m_Streams.stream[i].language[3]= 0; + } + } + } +} diff --git a/xbmc/pvrclients/vdr-vnsi/VNSIDemux.h b/xbmc/pvrclients/vdr-vnsi/VNSIDemux.h new file mode 100644 index 0000000000..dc563eff9c --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/VNSIDemux.h @@ -0,0 +1,68 @@ +#pragma once +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "client.h" +#include "VNSISession.h" +#include "thread.h" + +class cResponsePacket; + +struct SQuality +{ + CStdString fe_name; + CStdString fe_status; + uint32_t fe_snr; + uint32_t fe_signal; + uint32_t fe_ber; + uint32_t fe_unc; +}; + +class cVNSIDemux +{ +public: + cVNSIDemux(); + ~cVNSIDemux(); + + bool Open(const PVR_CHANNEL &channelinfo); + void Close(); + bool GetStreamProperties(PVR_STREAMPROPS* props); + void Abort(); + DemuxPacket* Read(); + bool SwitchChannel(const PVR_CHANNEL &channelinfo); + int CurrentChannel() { return m_channel; } + bool GetSignalStatus(PVR_SIGNALQUALITY &qualityinfo); + +protected: + void StreamChange(cResponsePacket *resp); + void StreamStatus(cResponsePacket *resp); + void StreamSignalInfo(cResponsePacket *resp); + void StreamContentInfo(cResponsePacket *resp); + +private: + bool m_startup; + cVNSISession m_session; + int m_channel; + int m_StatusCount; + CStdString m_Status; + PVR_STREAMPROPS m_Streams; + SQuality m_Quality; +}; diff --git a/xbmc/pvrclients/vdr-vnsi/VNSIRecording.cpp b/xbmc/pvrclients/vdr-vnsi/VNSIRecording.cpp new file mode 100644 index 0000000000..265b603f5e --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/VNSIRecording.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "VNSIRecording.h" +#include +#include "tools.h" +#include "responsepacket.h" +#include "requestpacket.h" +#include "vdrcommand.h" + +#define SEEK_POSSIBLE 0x10 // flag used to check if protocol allows seeks + +cVNSIRecording::cVNSIRecording() +{ +} + +cVNSIRecording::~cVNSIRecording() +{ + Close(); +} + +bool cVNSIRecording::Open(CStdString path) +{ + bool ret = false; + + if(!m_session.Open(g_szHostname, g_iPort, g_iConnectTimeout, "XBMC Recording stream receiver")) + return ret; + + cRequestPacket vrp; + if (!vrp.init(VDR_RECSTREAM_OPEN) || + !vrp.add_String(path)) + { + return ret; + } + + cResponsePacket* vresp = m_session.ReadResult(&vrp); + if (!vresp) + return ret; + + uint32_t returnCode = vresp->extract_U32(); + if (returnCode == VDR_RET_OK) + { + m_currentPlayingRecordFrames = vresp->extract_U32(); + m_currentPlayingRecordBytes = vresp->extract_U64(); + m_currentPlayingRecordPosition = 0; + + ret = true; + } + else + { + XBMC->Log(LOG_ERROR, "cVNSIDemux::Open - Can't open recording %s", path.c_str()); + ret = false; + } + + delete vresp; + return ret; +} + +void cVNSIRecording::Close() +{ + cRequestPacket vrp; + vrp.init(VDR_RECSTREAM_CLOSE); + m_session.ReadSuccess(&vrp); + m_session.Close(); +} + +int cVNSIRecording::Read(unsigned char* buf, int buf_size) +{ + if (m_currentPlayingRecordPosition >= m_currentPlayingRecordBytes) + return 0; + + cRequestPacket vrp; + if (!vrp.init(VDR_RECSTREAM_GETBLOCK) || + !vrp.add_U64(m_currentPlayingRecordPosition) || + !vrp.add_U32(buf_size)) + { + return 0; + } + + if (!IsClientConnected()) + return -1; + + cResponsePacket* vresp = m_session.ReadResult(&vrp); + if (!vresp) + return 0; + + uint32_t length = vresp->getUserDataLength(); + uint8_t *data = vresp->getUserData(); + if (length > buf_size) + { + XBMC->Log(LOG_ERROR, "cVNSIRecording::Read: PANIC - Received more bytes as requested"); + free(data); + delete vresp; + return 0; + } + + memcpy(buf, data, length); + m_currentPlayingRecordPosition += length; + free(data); + delete vresp; + return length; +} + +long long cVNSIRecording::Seek(long long pos, int whence) +{ + int64_t nextPos = m_currentPlayingRecordPosition; + + switch (whence) + { + case SEEK_SET: + nextPos = pos; + break; + + case SEEK_CUR: + nextPos += pos; + break; + + case SEEK_END: + if (m_currentPlayingRecordBytes) + nextPos = m_currentPlayingRecordBytes - pos; + else + return -1; + + break; + + case SEEK_POSSIBLE: + return 1; + + default: + return -1; + } + + if (nextPos >= m_currentPlayingRecordBytes) + { + return 0; + } + + m_currentPlayingRecordPosition = nextPos; + + return m_currentPlayingRecordPosition; +} + +long long cVNSIRecording::Position(void) +{ + return m_currentPlayingRecordPosition; +} + +long long cVNSIRecording::Length(void) +{ + return m_currentPlayingRecordBytes; +} + diff --git a/xbmc/pvrclients/vdr-vnsi/VNSIRecording.h b/xbmc/pvrclients/vdr-vnsi/VNSIRecording.h new file mode 100644 index 0000000000..21fdb053b2 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/VNSIRecording.h @@ -0,0 +1,49 @@ +#pragma once +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "client.h" +#include "VNSISession.h" +#include "thread.h" + +class cResponsePacket; + +class cVNSIRecording +{ +public: + cVNSIRecording(); + ~cVNSIRecording(); + + bool Open(CStdString path); + void Close(); + + int Read(unsigned char* buf, int buf_size); + long long Seek(long long pos, int whence); + long long Position(void); + long long Length(void); + +private: + cVNSISession m_session; + uint64_t m_currentPlayingRecordBytes; + uint32_t m_currentPlayingRecordFrames; + uint64_t m_currentPlayingRecordPosition; + +}; diff --git a/xbmc/pvrclients/vdr-vnsi/VNSISession.cpp b/xbmc/pvrclients/vdr-vnsi/VNSISession.cpp new file mode 100644 index 0000000000..7ecc10d5b4 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/VNSISession.cpp @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "VNSISession.h" +#include "client.h" +#ifdef _MSC_VER +#include +#define SHUT_RDWR SD_BOTH +#define ETIMEDOUT WSAETIMEDOUT +#else +#include +#include +#endif + +#include "responsepacket.h" +#include "requestpacket.h" +#include "vdrcommand.h" +#include "tools.h" + +/* Needed on Mac OS/X */ + +#ifndef SOL_TCP +#define SOL_TCP IPPROTO_TCP +#endif +using namespace std; + +cVNSISession::cVNSISession() + : m_fd(INVALID_SOCKET) + , m_protocol(0) + , m_queue_size(1000) +{ +} + +cVNSISession::~cVNSISession() +{ + Close(); +} + +void cVNSISession::Abort() +{ + shutdown(m_fd, SHUT_RDWR); +} + +void cVNSISession::Close() +{ + if(m_fd != INVALID_SOCKET) + { + closesocket(m_fd); + m_fd = INVALID_SOCKET; + } +} + +bool cVNSISession::Open(CStdString hostname, int port, long timeout, const char *name) +{ + struct hostent hostbuf, *hp; + int herr, fd, r, res, err; + struct sockaddr_in6 in6; + struct sockaddr_in in; + socklen_t errlen = sizeof(int); + size_t hstbuflen = 1024; + char *tmphstbuf = (char*)malloc(hstbuflen); + + if (port == 0) + port = 34890; + + while((res = gethostbyname_r(hostname.c_str(), &hostbuf, tmphstbuf, hstbuflen, &hp, &herr)) == ERANGE) + { + hstbuflen *= 2; + tmphstbuf = (char*)realloc(tmphstbuf, hstbuflen); + } + + if(res != 0) + { + XBMC->Log(LOG_ERROR, "cVNSISession::Open - Resolver internal error"); + free(tmphstbuf); + return false; + } + else if(herr != 0) + { + switch(herr) + { + case HOST_NOT_FOUND: + XBMC->Log(LOG_ERROR, "cVNSISession::Open - The specified host is unknown"); + break; + case NO_ADDRESS: + XBMC->Log(LOG_ERROR, "cVNSISession::Open - The requested name is valid but does not have an IP address"); + break; + case NO_RECOVERY: + XBMC->Log(LOG_ERROR, "cVNSISession::Open - A non-recoverable name server error occurred"); + break; + case TRY_AGAIN: + XBMC->Log(LOG_ERROR, "cVNSISession::Open - A temporary error occurred on an authoritative name server"); + break; + default: + XBMC->Log(LOG_ERROR, "cVNSISession::Open - Unknown error"); + break; + } + + free(tmphstbuf); + return false; + } + else if(hp == NULL) + { + XBMC->Log(LOG_ERROR, "cVNSISession::Open - Resolver internal error"); + free(tmphstbuf); + return false; + } + + fd = socket(hp->h_addrtype, SOCK_STREAM, 0); + if (fd == -1) + { + XBMC->Log(LOG_ERROR, "cVNSISession::Open - Unable to create socket: %s", strerror(errno)); + free(tmphstbuf); + return false; + } + + /** + * Switch to nonblocking + */ + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); + + switch(hp->h_addrtype) + { + case AF_INET: + memset(&in, 0, sizeof(in)); + in.sin_family = AF_INET; + in.sin_port = htons(port); + memcpy(&in.sin_addr, hp->h_addr_list[0], sizeof(struct in_addr)); + r = connect(fd, (struct sockaddr *)&in, sizeof(struct sockaddr_in)); + break; + + case AF_INET6: + memset(&in6, 0, sizeof(in6)); + in6.sin6_family = AF_INET6; + in6.sin6_port = htons(port); + memcpy(&in6.sin6_addr, hp->h_addr_list[0], sizeof(struct in6_addr)); + r = connect(fd, (struct sockaddr *)&in, sizeof(struct sockaddr_in6)); + break; + + default: + XBMC->Log(LOG_ERROR, "cVNSISession::Open - Invalid protocol family"); + free(tmphstbuf); + return false; + } + + free(tmphstbuf); + + if (r == -1) + { + if (errno == EINPROGRESS) + { + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = POLLOUT; + pfd.revents = 0; + + r = poll(&pfd, 1, timeout*1000); + if (r == 0) /* Timeout */ + XBMC->Log(LOG_ERROR, "Connection attempt timed out %i", timeout); + + if (r == -1) + { + XBMC->Log(LOG_ERROR, "poll() error: %s", strerror(errno)); + return false; + } + + getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&err, &errlen); + } + else + { + err = errno; + } + } + else + { + err = 0; + } + + if (err != 0) + { + XBMC->Log(LOG_ERROR, "%s", strerror(err)); + return false; + } + + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK); + + int val = 1; + setsockopt(fd, SOL_TCP, TCP_NODELAY, &val, sizeof(val)); + + try + { + m_fd = fd; + if (m_fd == INVALID_SOCKET) + throw "Can't connect to VSNI Server"; + + cRequestPacket vrp; + if (!vrp.init(VDR_LOGIN)) throw "Can't init cRequestPacket"; + if (!vrp.add_U32(VNSIProtocolVersion)) throw "Can't add protocol version to RequestPacket"; + if (!vrp.add_U8(false)) throw "Can't add netlog flag"; + if (name && strlen(name) > 0) + if (!vrp.add_String(name)) throw "Can't add client name to RequestPacket"; + else + if (!vrp.add_String("XBMC Media Center")) throw "Can't add client name to RequestPacket"; + + // read welcome + cResponsePacket* vresp = ReadResult(&vrp); + if (!vresp) + throw "failed to read greeting from server"; + + uint32_t protocol = vresp->extract_U32(); + uint32_t vdrTime = vresp->extract_U32(); + int32_t vdrTimeOffset = vresp->extract_S32(); + const char *ServerName = vresp->extract_String(); + const char *ServerVersion = vresp->extract_String(); + + m_server = ServerName; + m_version = ServerVersion; + m_protocol = protocol; + + if (!name || strlen(name) <= 0) + XBMC->Log(LOG_NOTICE, "Logged in at '%lu+%lu' to '%s' Version: '%s' with protocol version '%lu'", vdrTime, vdrTimeOffset, ServerName, ServerVersion, protocol); + + delete vresp; + } + catch (const char * str) + { + XBMC->Log(LOG_ERROR, "cVNSISession::Open - %s", str); + close(m_fd); + m_fd = INVALID_SOCKET; + return false; + } + + return true; +} + +cResponsePacket* cVNSISession::ReadMessage(int timeout) +{ + uint32_t channelID; + uint32_t requestID; + uint32_t userDataLength; + uint8_t* userData; + uint32_t streamID; + uint32_t duration; + uint32_t opCodeID; + int64_t dts; + int64_t pts; + + cResponsePacket* vresp = NULL; + + bool readSuccess = readData((uint8_t*)&channelID, sizeof(uint32_t), timeout) > 0; // 2s timeout atm + if (!readSuccess) + return NULL; + + // Data was read + + channelID = ntohl(channelID); + if (channelID == CHANNEL_REQUEST_RESPONSE) + { + if (!readData((uint8_t*)&requestID, sizeof(uint32_t))) return vresp; + requestID = ntohl(requestID); + if (!readData((uint8_t*)&userDataLength, sizeof(uint32_t))) return vresp; + userDataLength = ntohl(userDataLength); + if (userDataLength > 5000000) return vresp; // how big can these packets get? + userData = NULL; + if (userDataLength > 0) + { + userData = (uint8_t*)malloc(userDataLength); + if (!userData) return vresp; + if (!readData(userData, userDataLength)) return vresp; + } + + vresp = new cResponsePacket(); + vresp->setResponse(requestID, userData, userDataLength); + DEVDBG("cVNSISession::ReadMessage() - Rxd a response packet, requestID=%lu, len=%lu", requestID, userDataLength); + } + else if (channelID == CHANNEL_STREAM) + { + if (!readData((uint8_t*)&opCodeID, sizeof(uint32_t))) return vresp; + opCodeID = ntohl(opCodeID); + + if (!readData((uint8_t*)&streamID, sizeof(uint32_t))) return vresp; + streamID = ntohl(streamID); + + if (!readData((uint8_t*)&duration, sizeof(uint32_t))) return vresp; + duration = ntohl(duration); + + if (!readData((uint8_t*)&pts, sizeof(int64_t))) return vresp; + pts = ntohll(pts); + + if (!readData((uint8_t*)&dts, sizeof(int64_t))) return vresp; + dts = ntohll(dts); + + if (!readData((uint8_t*)&userDataLength, sizeof(uint32_t))) return vresp; + userDataLength = ntohl(userDataLength); + userData = NULL; + if (userDataLength > 0) + { + userData = (uint8_t*)malloc(userDataLength); + if (!userData) return vresp; + if (!readData(userData, userDataLength)) return vresp; + } + + vresp = new cResponsePacket(); + vresp->setStream(opCodeID, streamID, duration, dts, pts, userData, userDataLength); + } + else + { + XBMC->Log(LOG_ERROR, "cVNSISession::ReadMessage() - Rxd a response packet on channel %lu !!", channelID); + } + + return vresp; +} + +bool cVNSISession::SendMessage(cRequestPacket* vrp) +{ + if (sendData(vrp->getPtr(), vrp->getLen()) != vrp->getLen()) + return false; + + return true; +} + +cResponsePacket* cVNSISession::ReadResult(cRequestPacket* vrp, bool sequence) +{ + cResponsePacket *pkt = NULL; + + if(!SendMessage(vrp)) + return NULL; + + std::deque queue; + m_queue.swap(queue); + + while((pkt = ReadMessage())) + { + if (!pkt) + return NULL; + + /* Discard everything other as response packets until it is received */ + if (pkt->getChannelID() != CHANNEL_REQUEST_RESPONSE) + { + delete pkt; + continue; + } + + if(!sequence) + break; + + if (pkt->getRequestID() == vrp->getSerial()) + break; + + queue.push_back(pkt); + if(queue.size() >= m_queue_size) + { + XBMC->Log(LOG_ERROR, "cVNSISession::ReadResult - maximum queue size (%u) reached", m_queue_size); + m_queue.swap(queue); + return NULL; + } + } + + m_queue.swap(queue); + + return pkt; +} + +bool cVNSISession::ReadSuccess(cRequestPacket* vrp, bool sequence, std::string action) +{ + cResponsePacket *pkt = NULL; + if((pkt = ReadResult(vrp, sequence)) == NULL) + { + DEVDBG("cVNSISession::ReadSuccess - failed to %s", action.c_str()); + return false; + } + uint32_t retCode = pkt->extract_U32(); + delete pkt; + + if(retCode != VDR_RET_OK) + { + XBMC->Log(LOG_ERROR, "cVNSISession::ReadSuccess - failed with error code '%i' to %s", retCode, action.c_str()); + return false; + } + return true; +} + +int cVNSISession::sendData(void* bufR, size_t count) +{ + size_t bytes_sent = 0; + int this_write; + int temp_write; + + unsigned char* buf = (unsigned char*)bufR; + + while (bytes_sent < count) + { +#ifndef WIN32 + do + { + temp_write = this_write = write(m_fd, buf, count - bytes_sent); + } while ( (this_write < 0) && (errno == EINTR) ); +#else + do + { + temp_write = this_write = send(m_fd,(char*) buf, count- bytes_sent,0); + } while ( (this_write == SOCKET_ERROR) && (WSAGetLastError() == WSAEINTR) ); +#endif + if (this_write <= 0) + { + return(this_write); + } + bytes_sent += this_write; + buf += this_write; + } + + return(count); +} + +int cVNSISession::readData(uint8_t* buffer, int totalBytes, int TimeOut) +{ + int bytesRead = 0; + int thisRead; + int readTries = 0; + int success; + fd_set readSet; + struct timeval timeout; + + while(1) + { + FD_ZERO(&readSet); + FD_SET(m_fd, &readSet); + timeout.tv_sec = TimeOut; + timeout.tv_usec = 0; + success = select(m_fd + 1, &readSet, NULL, NULL, &timeout); + if (success < 1) + { + return 0; // error, or timeout + } +#ifndef WIN32 + thisRead = read(m_fd, &buffer[bytesRead], totalBytes - bytesRead); +#else + thisRead = recv(m_fd, (char*)&buffer[bytesRead], totalBytes - bytesRead, 0); +#endif + if (!thisRead) + { + // if read returns 0 then connection is closed + // in non-blocking mode if read is called with no data available, it returns -1 + // and sets errno to EGAGAIN. but we use select so it wouldn't do that anyway. + XBMC->Log(LOG_ERROR, "cVNSISession::readData - Detected connection closed"); + SetClientConnected(false); + return -1; + } + bytesRead += thisRead; + if (bytesRead == totalBytes) + { + return 1; + } + else + { + if (++readTries == 100) + { + XBMC->Log(LOG_ERROR, "cVNSISession::readData - Too many reads"); + // return 0; + } + } + } +} diff --git a/xbmc/pvrclients/vdr-vnsi/VNSISession.h b/xbmc/pvrclients/vdr-vnsi/VNSISession.h new file mode 100644 index 0000000000..77b24ad423 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/VNSISession.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#pragma once +#include +#include "pvrclient-vdrVNSI_os.h" +#include "StdString.h" + +#include +#include +#include + +class cResponsePacket; +class cRequestPacket; + +class cVNSISession +{ +public: + cVNSISession(); + ~cVNSISession(); + + bool Open(CStdString hostname, int port, long timeout, const char *name = ""); + void Close(); + void Abort(); + + cResponsePacket* ReadMessage(int timeout = 10); + bool SendMessage(cRequestPacket* vrp); + int sendData(void* bufR, size_t count); + int readData(uint8_t* buffer, int totalBytes, int TimeOut = 2); + + cResponsePacket* ReadResult(cRequestPacket* vrp, bool sequence = true); + bool ReadSuccess(cRequestPacket* m, bool sequence = true, std::string action = ""); + int GetProtocol() { return m_protocol; } + CStdString GetServerName() { return m_server; } + CStdString GetVersion() { return m_version; } + +private: + SOCKET m_fd; + int m_protocol; + CStdString m_server; + CStdString m_version; + + std::deque m_queue; + const unsigned int m_queue_size; +}; diff --git a/xbmc/pvrclients/vdr-vnsi/client.cpp b/xbmc/pvrclients/vdr-vnsi/client.cpp new file mode 100644 index 0000000000..baa5dbfbc7 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/client.cpp @@ -0,0 +1,675 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "client.h" +#include "xbmc_pvr_dll.h" +#include "VNSIDemux.h" +#include "VNSIRecording.h" +#include "VNSIData.h" +#include "VNSIChannelScan.h" + +using namespace std; + +bool m_bCreated = false; +bool m_connected = false; +int m_retries = 0; +ADDON_STATUS m_CurStatus = STATUS_UNKNOWN; +int g_clientID = -1; + +/* User adjustable settings are saved here. + * Default values are defined inside client.h + * and exported to the other source files. + */ +CStdString g_szHostname = DEFAULT_HOST; +int g_iPort = DEFAULT_PORT; +bool g_bCharsetConv = DEFAULT_CHARCONV; ///< Convert VDR's incoming strings to UTF8 character set +bool g_bHandleMessages = DEFAULT_HANDLE_MSG; ///< Send VDR's OSD status messages to XBMC OSD +bool g_bUseRecordingsDir = DEFAULT_USE_REC_DIR; ///< Use a normal directory if true for recordings +CStdString g_szRecordingsDir = DEFAULT_REC_DIR; ///< The path to the recordings directory +int g_iConnectTimeout = DEFAULT_TIMEOUT; ///< The Socket connection timeout +int g_iPriority = DEFAULT_PRIORITY; ///< The Priority this client have in response to other clients +CStdString g_szUserPath = ""; +CStdString g_szClientPath = ""; +cHelper_libXBMC_addon *XBMC = NULL; +cHelper_libXBMC_gui *GUI = NULL; +cHelper_libXBMC_pvr *PVR = NULL; +cVNSIDemux *VNSIDemuxer = NULL; +cVNSIData *VNSIData = NULL; +cVNSIRecording *VNSIRecording = NULL; + +bool IsClientConnected(bool forceReconnect) +{ + if (forceReconnect) + m_retries = 0; + + if (!m_connected && m_bCreated) + { + while (m_retries < 5) + { + m_retries++; + if (VNSIDemuxer) + { + delete VNSIDemuxer; + VNSIDemuxer = NULL; + } + if (VNSIRecording) + { + delete VNSIRecording; + VNSIRecording = NULL; + } + if (VNSIData) + { + XBMC->Log(LOG_NOTICE, "Trying to reconnect to VNSI Server (try %i)", m_retries+1); + sleep(2); + if (VNSIData->Open(g_szHostname, g_iPort, g_iConnectTimeout)) + { + XBMC->Log(LOG_NOTICE, "Reconnect to VNSI Server succesfull"); + m_CurStatus = STATUS_OK; + m_connected = true; + m_retries = 0; + return true; + } + } + } + } + return m_connected; +} + +void SetClientConnected(bool yesNo) +{ + if (yesNo) + { + m_connected = true; + } + else + { + XBMC->Log(LOG_ERROR, "Lost connection to VNSI Server"); + + if (VNSIData) + VNSIData->Close(); + if (VNSIDemuxer) + VNSIDemuxer->Close(); + if (VNSIRecording) + VNSIRecording->Close(); + + m_connected = false; + m_CurStatus = STATUS_LOST_CONNECTION; + } +} + +extern "C" { + +/*********************************************************** + * Standart AddOn related public library functions + ***********************************************************/ + +ADDON_STATUS Create(void* hdl, void* props) +{ + if (!hdl || !props) + return STATUS_UNKNOWN; + + PVR_PROPS* pvrprops = (PVR_PROPS*)props; + + XBMC = new cHelper_libXBMC_addon; + if (!XBMC->RegisterMe(hdl)) + return STATUS_UNKNOWN; + + GUI = new cHelper_libXBMC_gui; + if (!GUI->RegisterMe(hdl)) + return STATUS_UNKNOWN; + + PVR = new cHelper_libXBMC_pvr; + if (!PVR->RegisterMe(hdl)) + return STATUS_UNKNOWN; + + XBMC->Log(LOG_DEBUG, "Creating VDR VNSI PVR-Client"); + + m_CurStatus = STATUS_UNKNOWN; + g_clientID = pvrprops->clientID; + g_szUserPath = pvrprops->userpath; + g_szClientPath = pvrprops->clientpath; + + /* Read setting "host" from settings.xml */ + char * buffer; + buffer = (char*) malloc (1024); + buffer[0] = 0; /* Set the end of string */ + + if (XBMC->GetSetting("host", buffer)) + g_szHostname = buffer; + else + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'host' setting, falling back to '%s' as default", DEFAULT_HOST); + g_szHostname = DEFAULT_HOST; + } + buffer[0] = 0; /* Set the end of string */ + + /* Read setting "port" from settings.xml */ + if (!XBMC->GetSetting("port", &g_iPort)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'port' setting, falling back to '%i' as default", DEFAULT_PORT); + g_iPort = DEFAULT_PORT; + } + + /* Read setting "priority" from settings.xml */ + if (!XBMC->GetSetting("priority", &g_iPriority)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'priority' setting, falling back to %i as default", DEFAULT_PRIORITY); + g_iPriority = DEFAULT_PRIORITY; + } + + /* Read setting "convertchar" from settings.xml */ + if (!XBMC->GetSetting("convertchar", &g_bCharsetConv)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'convertchar' setting, falling back to 'false' as default"); + g_bCharsetConv = DEFAULT_CHARCONV; + } + + /* Read setting "timeout" from settings.xml */ + if (!XBMC->GetSetting("timeout", &g_iConnectTimeout)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'timeout' setting, falling back to %i seconds as default", DEFAULT_TIMEOUT); + g_iConnectTimeout = DEFAULT_TIMEOUT; + } + + /* Read setting "handlemessages" from settings.xml */ + if (!XBMC->GetSetting("handlemessages", &g_bHandleMessages)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'handlemessages' setting, falling back to 'true' as default"); + g_bHandleMessages = DEFAULT_HANDLE_MSG; + } + + /* Read setting "usedirectory" from settings.xml */ + if (!XBMC->GetSetting("usedirectory", &g_bUseRecordingsDir)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'usedirectory' setting, falling back to 'false' as default"); + g_bUseRecordingsDir = DEFAULT_USE_REC_DIR; + } + + if (g_bUseRecordingsDir) + { + /* Read setting "recordingdir" from settings.xml */ + buffer = (char*) malloc (2048); + buffer[0] = 0; /* Set the end of string */ + + if (XBMC->GetSetting("recordingdir", buffer)) + g_szRecordingsDir = buffer; + else + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'recordingdir' setting, directory not set"); + g_szRecordingsDir = DEFAULT_REC_DIR; + g_bUseRecordingsDir = false; + } + free (buffer); + } + + VNSIData = new cVNSIData; + if (!VNSIData->Open(g_szHostname, g_iPort, g_iConnectTimeout)) + { + m_CurStatus = STATUS_LOST_CONNECTION; + return m_CurStatus; + } + + VNSIData->EnableStatusInterface(g_bHandleMessages); + + m_CurStatus = STATUS_OK; + m_bCreated = true; + return m_CurStatus; +} + +ADDON_STATUS GetStatus() +{ + return m_CurStatus; +} + +void Destroy() +{ + if (m_bCreated) + { + delete VNSIData; + VNSIData = NULL; + } + m_CurStatus = STATUS_UNKNOWN; +} + +bool HasSettings() +{ + return true; +} + +unsigned int GetSettings(StructSetting ***sSet) +{ + return 0; +} + +ADDON_STATUS SetSetting(const char *settingName, const void *settingValue) +{ + string str = settingName; + if (str == "host") + { + string tmp_sHostname; + XBMC->Log(LOG_INFO, "Changed Setting 'host' from %s to %s", g_szHostname.c_str(), (const char*) settingValue); + tmp_sHostname = g_szHostname; + g_szHostname = (const char*) settingValue; + if (tmp_sHostname != g_szHostname) + return STATUS_NEED_RESTART; + } + else if (str == "port") + { + XBMC->Log(LOG_INFO, "Changed Setting 'port' from %u to %u", g_iPort, *(int*) settingValue); + if (g_iPort != *(int*) settingValue) + { + g_iPort = *(int*) settingValue; + return STATUS_NEED_RESTART; + } + } + else if (str == "priority") + { + XBMC->Log(LOG_INFO, "Changed Setting 'priority' from %u to %u", g_iPriority, *(int*) settingValue); + g_iPriority = *(int*) settingValue; + } + else if (str == "convertchar") + { + XBMC->Log(LOG_INFO, "Changed Setting 'convertchar' from %u to %u", g_bCharsetConv, *(bool*) settingValue); + g_bCharsetConv = *(bool*) settingValue; + } + else if (str == "timeout") + { + XBMC->Log(LOG_INFO, "Changed Setting 'timeout' from %u to %u", g_iConnectTimeout, *(int*) settingValue); + g_iConnectTimeout = *(int*) settingValue; + } + else if (str == "handlemessages") + { + XBMC->Log(LOG_INFO, "Changed Setting 'handlemessages' from %u to %u", g_bHandleMessages, *(bool*) settingValue); + g_bHandleMessages = *(bool*) settingValue; + if (VNSIData) VNSIData->EnableStatusInterface(g_bHandleMessages); + } + else if (str == "usedirectory") + { + XBMC->Log(LOG_INFO, "Changed Setting 'usedirectory' from %u to %u", g_bUseRecordingsDir, *(bool*) settingValue); + g_bUseRecordingsDir = *(bool*) settingValue; + } + else if (str == "recordingdir") + { + XBMC->Log(LOG_INFO, "Changed Setting 'recordingdir' from %s to %s", g_szRecordingsDir.c_str(), (const char*) settingValue); + g_bUseRecordingsDir = (const char*) settingValue; + } + + return STATUS_OK; +} + +void Stop() +{ +} + +void FreeSettings() +{ + +} + +/*********************************************************** + * PVR Client AddOn specific public library functions + ***********************************************************/ + +PVR_ERROR GetProperties(PVR_SERVERPROPS* props) +{ + props->SupportChannelLogo = false; + props->SupportTimeShift = false; + props->SupportEPG = true; + props->SupportRecordings = true; + props->SupportTimers = true; + props->SupportTV = true; + props->SupportRadio = true; + props->SupportChannelSettings = false; + props->SupportDirector = false; + props->SupportBouquets = false; + props->HandleInputStream = true; + props->HandleDemuxing = true; + if (VNSIData && VNSIData->SupportChannelScan()) + props->SupportChannelScan = true; + else + props->SupportChannelScan = false; + + return PVR_ERROR_NO_ERROR; +} + +const char * GetBackendName() +{ + static CStdString BackendName = VNSIData ? VNSIData->GetServerName() : "unknown"; + return BackendName.c_str(); +} + +const char * GetBackendVersion() +{ + static CStdString BackendVersion; + if (VNSIData) + BackendVersion.Format("%s (Protocol: %i)", VNSIData->GetVersion(), VNSIData->GetProtocol()); + return BackendVersion.c_str(); +} + +const char * GetConnectionString() +{ + static CStdString ConnectionString; + if (VNSIData) + ConnectionString.Format("%s:%i%s", g_szHostname.c_str(), g_iPort, IsClientConnected() ? "" : " (Not connected!)"); + else + ConnectionString.Format("%s:%i (addon error!)", g_szHostname.c_str(), g_iPort); + return ConnectionString.c_str(); +} + +PVR_ERROR GetDriveSpace(long long *total, long long *used) +{ + if (IsClientConnected() && VNSIData && VNSIData->GetDriveSpace(total, used)) + return PVR_ERROR_NO_ERROR; + + return PVR_ERROR_SERVER_ERROR; +} + +PVR_ERROR GetBackendTime(time_t *localTime, int *gmtOffset) +{ + if (IsClientConnected() && VNSIData && VNSIData->GetTime(localTime, gmtOffset)) + return PVR_ERROR_NO_ERROR; + + return PVR_ERROR_SERVER_ERROR; +} + +PVR_ERROR DialogChannelScan() +{ + cVNSIChannelScan scanner; + scanner.Open(); + return PVR_ERROR_NO_ERROR; +} + +/*******************************************/ +/** PVR EPG Functions **/ + +PVR_ERROR RequestEPGForChannel(PVRHANDLE handle, const PVR_CHANNEL &channel, time_t start, time_t end) +{ + if (IsClientConnected() && VNSIData && VNSIData->GetEPGForChannel(handle, channel, start, end)) + return PVR_ERROR_NO_ERROR; + + return PVR_ERROR_SERVER_ERROR; +} + + +/*******************************************/ +/** PVR Channel Functions **/ + +int GetNumChannels() +{ + if (!IsClientConnected(true) || !VNSIData) + return 0; + + return VNSIData->GetChannelsCount(); +} + +PVR_ERROR RequestChannelList(PVRHANDLE handle, int radio) +{ + if (IsClientConnected(true) && VNSIData && VNSIData->GetChannelsList(handle, radio)) + return PVR_ERROR_NO_ERROR; + + return PVR_ERROR_SERVER_ERROR; +} + + +/*******************************************/ +/** PVR Timer Functions **/ + +int GetNumTimers(void) +{ + if (!IsClientConnected() || !VNSIData) + return 0; + + return VNSIData->GetTimersCount(); +} + +PVR_ERROR RequestTimerList(PVRHANDLE handle) +{ + if (IsClientConnected() && VNSIData && VNSIData->GetTimersList(handle)) + return PVR_ERROR_NO_ERROR; + + return PVR_ERROR_SERVER_ERROR; +} + +PVR_ERROR AddTimer(const PVR_TIMERINFO &timerinfo) +{ + if (!IsClientConnected() || !VNSIData) + return PVR_ERROR_SERVER_ERROR; + + return VNSIData->AddTimer(timerinfo); +} + +PVR_ERROR DeleteTimer(const PVR_TIMERINFO &timerinfo, bool force) +{ + if (!IsClientConnected() || !VNSIData) + return PVR_ERROR_SERVER_ERROR; + + return VNSIData->DeleteTimer(timerinfo, force); +} + +PVR_ERROR RenameTimer(const PVR_TIMERINFO &timerinfo, const char *newname) +{ + if (!IsClientConnected() || !VNSIData) + return PVR_ERROR_SERVER_ERROR; + + return VNSIData->RenameTimer(timerinfo, newname); +} + +PVR_ERROR UpdateTimer(const PVR_TIMERINFO &timerinfo) +{ + if (!IsClientConnected() || !VNSIData) + return PVR_ERROR_SERVER_ERROR; + + return VNSIData->UpdateTimer(timerinfo); +} + + +/*******************************************/ +/** PVR Recording Functions **/ + +int GetNumRecordings(void) +{ + if (!IsClientConnected() || !VNSIData) + return 0; + + return VNSIData->GetRecordingsCount(); +} + +PVR_ERROR RequestRecordingsList(PVRHANDLE handle) +{ + if (!VNSIData || !IsClientConnected()) + return PVR_ERROR_SERVER_ERROR; + + return VNSIData->GetRecordingsList(handle); +} + +PVR_ERROR DeleteRecording(const PVR_RECORDINGINFO &recinfo) +{ + if (IsClientConnected() && VNSIData && VNSIData->DeleteRecording(VNSIData->GetRecordingPath(recinfo.index))) + return PVR_ERROR_NO_ERROR; + + return PVR_ERROR_SERVER_ERROR; +} + +/*******************************************/ +/** PVR Live Stream Functions **/ + +bool OpenLiveStream(const PVR_CHANNEL &channelinfo) +{ + if (!IsClientConnected(true)) + return false; + + CloseLiveStream(); + + VNSIDemuxer = new cVNSIDemux; + return VNSIDemuxer->Open(channelinfo); +} + +void CloseLiveStream() +{ + if (IsClientConnected() && VNSIDemuxer) + { + VNSIDemuxer->Close(); + delete VNSIDemuxer; + VNSIDemuxer = NULL; + } +} + +PVR_ERROR GetStreamProperties(PVR_STREAMPROPS* props) +{ + if (IsClientConnected() && VNSIDemuxer && VNSIDemuxer->GetStreamProperties(props)) + return PVR_ERROR_NO_ERROR; + + return PVR_ERROR_SERVER_ERROR; +} + +void DemuxAbort() +{ + if (IsClientConnected() && VNSIDemuxer) VNSIDemuxer->Abort(); +} + +DemuxPacket* DemuxRead() +{ + if (!IsClientConnected()) + return NULL; + + return VNSIDemuxer->Read(); +} + +int GetCurrentClientChannel() +{ + if (IsClientConnected() && VNSIDemuxer) + return VNSIDemuxer->CurrentChannel(); + + return -1; +} + +bool SwitchChannel(const PVR_CHANNEL &channelinfo) +{ + if (IsClientConnected() && VNSIDemuxer) + return VNSIDemuxer->SwitchChannel(channelinfo); + + return false; +} + +PVR_ERROR SignalQuality(PVR_SIGNALQUALITY &qualityinfo) +{ + if (IsClientConnected() && VNSIDemuxer && VNSIDemuxer->GetSignalStatus(qualityinfo)) + return PVR_ERROR_NO_ERROR; + + return PVR_ERROR_SERVER_ERROR; +} + + +/*******************************************/ +/** PVR Recording Stream Functions **/ + +bool OpenRecordedStream(const PVR_RECORDINGINFO &recinfo) +{ + if (!IsClientConnected(true)) + return false; + + CloseRecordedStream(); + + CStdString name = VNSIData->GetRecordingPath(recinfo.index); + VNSIRecording = new cVNSIRecording; + return VNSIRecording->Open(name); +} + +void CloseRecordedStream(void) +{ + if (IsClientConnected() && VNSIRecording) + { + VNSIRecording->Close(); + delete VNSIRecording; + VNSIRecording = NULL; + } +} + +int ReadRecordedStream(unsigned char* buf, int buf_size) +{ + if (!IsClientConnected()) + return -1; + + return VNSIRecording->Read(buf, buf_size); +} + +long long SeekRecordedStream(long long pos, int whence) +{ + if (IsClientConnected() && VNSIRecording) + return VNSIRecording->Seek(pos, whence); + + return -1; +} + +long long PositionRecordedStream(void) +{ + if (IsClientConnected() && VNSIRecording) + return VNSIRecording->Position(); + + return 0; +} + +long long LengthRecordedStream(void) +{ + if (IsClientConnected() && VNSIRecording) + return VNSIRecording->Length(); + + return 0; +} + + + +/** UNUSED API FUNCTIONS */ +PVR_ERROR MenuHook(const PVR_MENUHOOK &menuhook) { return PVR_ERROR_NOT_IMPLEMENTED; } +int GetNumBouquets() { return 0; } +PVR_ERROR RequestBouquetsList(PVRHANDLE handle, int radio) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR DeleteChannel(unsigned int number) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR RenameChannel(unsigned int number, const char *newname) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR MoveChannel(unsigned int number, unsigned int newnumber) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR DialogChannelSettings(const PVR_CHANNEL &channelinfo) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR DialogAddChannel(const PVR_CHANNEL &channelinfo) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR RenameRecording(const PVR_RECORDINGINFO &recinfo, const char *newname) { return PVR_ERROR_NOT_IMPLEMENTED; } +bool HaveCutmarks() { return false; } +PVR_ERROR RequestCutMarksList(PVRHANDLE handle) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR AddCutMark(const PVR_CUT_MARK &cutmark) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR DeleteCutMark(const PVR_CUT_MARK &cutmark) { return PVR_ERROR_NOT_IMPLEMENTED; } +PVR_ERROR StartCut() { return PVR_ERROR_NOT_IMPLEMENTED; } +bool SwapLiveTVSecondaryStream() { return false; } +bool OpenSecondaryStream(const PVR_CHANNEL &channelinfo) { return false; } +void CloseSecondaryStream() {} +int ReadSecondaryStream(unsigned char* buf, int buf_size) { return 0; } +void DemuxReset(){} +void DemuxFlush(){} +int ReadLiveStream(unsigned char* buf, int buf_size) { return 0; } +long long SeekLiveStream(long long pos, int whence) { return -1; } +long long PositionLiveStream(void) { return -1; } +long long LengthLiveStream(void) { return -1; } +const char * GetLiveStreamURL(const PVR_CHANNEL &channelinfo) { return ""; } + +} diff --git a/xbmc/pvrclients/vdr-vnsi/client.h b/xbmc/pvrclients/vdr-vnsi/client.h new file mode 100644 index 0000000000..f5f94090ac --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/client.h @@ -0,0 +1,59 @@ +#pragma once +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef CLIENT_H +#define CLIENT_H + +#include "StdString.h" +#include "../../../addons/library.xbmc.addon/libXBMC_addon.h" +#include "../../../addons/library.xbmc.gui/libXBMC_gui.h" +#include "../../../addons/library.xbmc.pvr/libXBMC_pvr.h" + +#define DEFAULT_HOST "127.0.0.1" +#define DEFAULT_PORT 34890 +#define DEFAULT_CHARCONV false +#define DEFAULT_HANDLE_MSG true +#define DEFAULT_PRIORITY 99 +#define DEFAULT_TIMEOUT 3 +#define DEFAULT_USE_REC_DIR false +#define DEFAULT_REC_DIR "" + +extern bool IsClientConnected(bool forceReconnect = false); +extern void SetClientConnected(bool yesNo); + +extern bool m_bCreated; +extern CStdString g_szHostname; +extern int g_iPort; +extern int g_iConnectTimeout; +extern int g_clientID; +extern CStdString g_szUserPath; +extern CStdString g_szClientPath; +extern int g_iPriority; ///< The Priority this client have in response to other clients +extern bool g_bCharsetConv; ///< Convert VDR's incoming strings to UTF8 character set +extern bool g_bHandleMessages; ///< Send VDR's OSD status messages to XBMC OSD +extern bool g_bUseRecordingsDir; ///< Use a normal directory if true for recordings +extern CStdString g_szRecordingsDir; ///< The path to the recordings directory +extern cHelper_libXBMC_addon *XBMC; +extern cHelper_libXBMC_gui *GUI; +extern cHelper_libXBMC_pvr *PVR; + +#endif /* CLIENT_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/linux/os_posix.h b/xbmc/pvrclients/vdr-vnsi/linux/os_posix.h new file mode 100644 index 0000000000..0adf70d2dc --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/linux/os_posix.h @@ -0,0 +1,93 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef PVRCLIENT_VDR_OS_POSIX_H +#define PVRCLIENT_VDR_OS_POSIX_H + +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef __APPLE__ +#include +#endif +#include +#include +#include +#include +#include + +typedef int bool_t; +typedef int SOCKET; + +#ifndef closesocket +#define closesocket(a) close(a) +#endif + +#define SOCKET_ERROR (-1) +#define INVALID_SOCKET (-1) +#define SD_BOTH SHUT_RDWR + +#define LIBTYPE +#define sock_getlasterror errno +#define sock_getlasterror_socktimeout (errno == EAGAIN) +#define console_vprintf vprintf +#define console_printf printf +#define THREAD_FUNC_PREFIX void * + +#ifndef __STL_CONFIG_H +template inline T min(T a, T b) { return a <= b ? a : b; } +template inline T max(T a, T b) { return a >= b ? a : b; } +template inline int sgn(T a) { return a < 0 ? -1 : a > 0 ? 1 : 0; } +template inline void swap(T &a, T &b) { T t = a; a = b; b = t; } +#endif + +#define Sleep(t) usleep(t*1000) + +static inline uint64_t getcurrenttime(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + return ((uint64_t)t.tv_sec * 1000) + (t.tv_usec / 1000); +} + +static inline int setsocktimeout(int s, int level, int optname, uint64_t timeout) +{ + struct timeval t; + t.tv_sec = timeout / 1000; + t.tv_usec = (timeout % 1000) * 1000; + return setsockopt(s, level, optname, (char *)&t, sizeof(t)); +} + +#endif diff --git a/xbmc/pvrclients/vdr-vnsi/pvrclient-vdrVNSI_os.h b/xbmc/pvrclients/vdr-vnsi/pvrclient-vdrVNSI_os.h new file mode 100644 index 0000000000..adbf167aa3 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/pvrclient-vdrVNSI_os.h @@ -0,0 +1,44 @@ +#pragma once +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef PVRCLIENT_VNSI_OS_H +#define PVRCLIENT_VNSI_OS_H + +#if defined(_WIN32) || defined(_WIN64) +#define __WINDOWS__ +#endif + +#if defined(__WINDOWS__) +#include "windows/os_windows.h" +#else +#include "linux/os_posix.h" +#endif + +#if !defined(TRUE) +#define TRUE 1 +#endif + +#if !defined(FALSE) +#define FALSE 0 +#endif + +#endif /* PVRCLIENT_VNSI_OS_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/recordings.cpp b/xbmc/pvrclients/vdr-vnsi/recordings.cpp new file mode 100644 index 0000000000..1e065ca94a --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/recordings.cpp @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * This code is taken from recordings.c in the Video Disk Recorder ('VDR') + */ + +#include "StdString.h" +#include "recordings.h" +#include "client.h" + +#define RESUME_NOT_INITIALIZED (-2) + +cRecording::cRecording() +{ + m_channelName = NULL; + m_aux = NULL; + m_title = NULL; + m_shortText = NULL; + m_description = NULL; + m_fileName = NULL; + m_directory = NULL; + m_framesPerSecond = DEFAULTFRAMESPERSECOND; + m_priority = MAXPRIORITY; + m_lifetime = MAXLIFETIME; + m_EventID = 0; + m_StartTime = 0; + m_Duration = 0; + m_TableID = 0; + m_Version = 0xFF; + m_vps = 0; + m_Index = -1; + m_isPesRecording = false; + m_resume = RESUME_NOT_INITIALIZED; + m_fileSizeMB = -1; // unknown + m_deleted = false; +} + +cRecording::cRecording(const PVR_RECORDINGINFO *Recording) +{ + +} + +cRecording::cRecording(const char *FileName, const char *BaseDir) +{ + m_channelName = NULL; + m_aux = NULL; + m_title = NULL; + m_shortText = NULL; + m_description = NULL; + m_fileName = NULL; + m_directory = NULL; + m_resume = RESUME_NOT_INITIALIZED; + m_fileSizeMB = -1; // unknown + m_priority = MAXPRIORITY; + m_lifetime = MAXLIFETIME; + m_isPesRecording = false; + m_framesPerSecond = DEFAULTFRAMESPERSECOND; + m_deleted = false; + + FileName = m_fileName = strdup(FileName); + if (*(m_fileName + strlen(m_fileName) - 1) == '/') + *(m_fileName + strlen(m_fileName) - 1) = 0; + FileName += strlen(BaseDir) + 1; + const char *p = strrchr(FileName, '/'); + if (p) + { + time_t now = time(NULL); + struct tm tm_r; + struct tm t = *localtime_r(&now, &tm_r); // this initializes the time zone in 't' + t.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting + int channel; + int instanceId; + if (7 == sscanf(p + 1, DATAFORMATTS, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &channel, &instanceId) + || 7 == sscanf(p + 1, DATAFORMATPES, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &m_priority, &m_lifetime)) + { + t.tm_year -= 1900; + t.tm_mon--; + t.tm_sec = 0; + m_StartTime = mktime(&t); + char *path = MALLOC(char, p - FileName + 1); + strncpy(path, FileName, p - FileName); + path[p - FileName] = 0; + + /* Make some default title based upon directory names */ + p = strrchr(path, '/'); + if (p) + m_title = strcpyrealloc(m_title, p + 1); + else + m_title = strcpyrealloc(m_title, path); + + m_directory = MALLOC(char, strlen(path) - strlen(m_title) + 1); + strncpy(m_directory, path, strlen(path) - strlen(m_title)); + m_directory[strlen(path) - strlen(m_title)] = 0; + + if (g_bCharsetConv) + { + CStdString str_result = m_directory; + XBMC->UnknownToUTF8(str_result); + m_directory = strcpyrealloc(m_directory, str_result.c_str()); + } + + m_isPesRecording = instanceId < 0; + m_stream_url.Format("%s/%s", m_fileName, m_isPesRecording ? "*.vdr" : "*.ts"); + free(path); + } + else + return; + // read info file: + CStdString InfoFileName; + InfoFileName.Format("%s%s", m_fileName, m_isPesRecording ? INFOFILESUFFIX ".vdr" : INFOFILESUFFIX); + FILE *f = fopen(InfoFileName.c_str(), "r"); + if (f) + { + if (!Read(f)) + XBMC->Log(LOG_ERROR, "EPG data problem in file %s", InfoFileName.c_str()); + fclose(f); + } + else if (errno != ENOENT) + XBMC->Log(LOG_ERROR, "ERROR (%s,%d,s): %m", __FILE__, __LINE__, InfoFileName.c_str()); + } +} + +cRecording::~cRecording() +{ + free(m_aux); + free(m_channelName); + free(m_title); + free(m_shortText); + free(m_description); + free(m_fileName); + free(m_directory); +} + +bool cRecording::Read(FILE *f) +{ + cReadLine ReadLine; + char *s; + int line = 0; + while ((s = ReadLine.Read(f)) != NULL) + { + ++line; + CStdString str_result = s; + if (g_bCharsetConv) + XBMC->UnknownToUTF8(str_result); + if (!ParseLine(str_result.c_str())) + return false; + } + return true; +} + +bool cRecording::ParseLine(const char *s) +{ + char *t = skipspace(s + 1); + switch (*s) + { + case 'C': + { + char *p = strchr(t, ' '); + if (p) + { + free(m_channelName); + m_channelName = strdup(compactspace(p)); + *p = 0; // strips optional channel name + } + // if (*t) + // channelID = tChannelID::FromString(t); + } + return true; + case 'D': + strreplace(t, '|', '\n'); + SetDescription(t); + return true; + case 'E': + { + unsigned int EventID; + time_t StartTime; + int Duration; + unsigned int TableID = 0; + unsigned int Version = 0xFF; + int n = sscanf(t, "%u %ld %d %X %X", &EventID, &StartTime, &Duration, &TableID, &Version); + if (n >= 3 && n <= 5) + { + m_EventID = EventID; + m_StartTime = StartTime; + m_Duration = Duration; + m_TableID = TableID; + m_Version = Version; + } + } + return true; + case 'F': + m_framesPerSecond = atof(t); + return true; + case 'L': + m_lifetime = atoi(t); + return true; + case 'P': + m_priority = atoi(t); + return true; + case 'S': + SetShortText(t); + return true; + case 'T': + SetTitle(t); + return true; + case 'V': + m_vps = atoi(t); + return true; + case '@': + free(m_aux); + m_aux = strdup(t); + return true; + case '#': + return true; // comments are ignored + + + default: + break; + } + return false; +} + +bool cRecording::ParseEntryLine(const char *s) +{ + if (*s >= '0' && *s <= '9') + { + char recdate[256]; + char rectime[256]; + char rectext[1024]; + + if (sscanf(s, " %u %[^ ] %[^ ] %[^\n]", &m_Index, recdate, rectime, rectext) >= 3) + { + CStdString fileName = rectext; + fileName.Replace('/', '_'); + fileName.Replace('\\', '_'); + fileName.Replace('?', '_'); +#if defined(_WIN32) || defined(_WIN64) + // just filter out some illegal characters on windows + fileName.Replace(':', '_'); + fileName.Replace('*', '_'); + fileName.Replace('?', '_'); + fileName.Replace('\"', '_'); + fileName.Replace('<', '_'); + fileName.Replace('>', '_'); + fileName.Replace('|', '_'); + fileName.TrimRight("."); + fileName.TrimRight(" "); +#endif + size_t found = fileName.find_last_of("~"); + if (found != CStdString::npos) + { + CStdString dir = fileName.substr(0,found); + dir.Replace('~','/'); + m_directory = strcpyrealloc(m_directory, dir.c_str()); + m_fileName = strcpyrealloc(m_fileName, fileName.substr(found+1).c_str()); + } + else + { + m_fileName = strcpyrealloc(m_fileName, fileName.c_str()); + m_directory = strcpyrealloc(m_directory, ""); + } + } + return true; + } + + return false; +} + +// case 'X': if (!components) +// components = new cComponents; +// components->SetComponent(components->NumComponents(), t); +// break; + + +void cRecording::SetFramesPerSecond(double FramesPerSecond) +{ + m_framesPerSecond = FramesPerSecond; +} + +void cRecording::SetTitle(const char *Title) +{ + m_title = strcpyrealloc(m_title, Title); +} + +void cRecording::SetShortText(const char *ShortText) +{ + m_shortText = strcpyrealloc(m_shortText, ShortText); +} + +void cRecording::SetDescription(const char *Description) +{ + m_description = strcpyrealloc(m_description, Description); +} diff --git a/xbmc/pvrclients/vdr-vnsi/recordings.h b/xbmc/pvrclients/vdr-vnsi/recordings.h new file mode 100644 index 0000000000..83d15e3444 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/recordings.h @@ -0,0 +1,101 @@ +#pragma once +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __RECORDINGS_H +#define __RECORDINGS_H + +#include +#include +#include +#include "tools.h" + +#define DEFAULTFRAMESPERSECOND 25.0 +#define MAXPRIORITY 99 +#define MAXLIFETIME 99 +#define RECEXT ".rec" +#define DELEXT ".del" +#define DATAFORMATPES "%4d-%02d-%02d.%02d%*c%02d.%02d.%02d" RECEXT +#define NAMEFORMATPES "%s/%s/" "%4d-%02d-%02d.%02d.%02d.%02d.%02d" RECEXT +#define DATAFORMATTS "%4d-%02d-%02d.%02d.%02d.%d-%d" RECEXT +#define NAMEFORMATTS "%s/%s/" DATAFORMATTS +#define RESUMEFILESUFFIX "/resume%s%s" +#define SUMMARYFILESUFFIX "/summary.vdr" +#define INFOFILESUFFIX "/info" +#define MARKSFILESUFFIX "/marks" + +class cRecording +{ +private: + int m_Index; + int m_resume; + int m_fileSizeMB; + char *m_channelName; + char *m_fileName; + char *m_directory; + CStdString m_stream_url; + double m_framesPerSecond; + int m_priority; + int m_lifetime; + char *m_aux; + unsigned int m_EventID; + time_t m_StartTime; + int m_Duration; + unsigned int m_TableID; + unsigned int m_Version; + bool m_isPesRecording; + bool m_deleted; + char *m_title; // Title of this event + char *m_shortText; // Short description of this event (typically the episode name in case of a series) + char *m_description; // Description of this event + time_t m_vps; // Video Programming Service timestamp (VPS, aka "Programme Identification Label", PIL) + bool Read(FILE *f); + +public: + cRecording(const char *FileName, const char *BaseDir); + cRecording(const PVR_RECORDINGINFO *Recording); + cRecording(); + virtual ~cRecording(); + + bool ParseLine(const char *s); + bool ParseEntryLine(const char *s); + const char *ChannelName(void) const { return m_channelName; } + const char *Aux(void) const { return m_aux; } + double FramesPerSecond(void) const { return m_framesPerSecond; } + void SetFramesPerSecond(double m_FramesPerSecond); + int Index(void) const { return m_Index; } + int Priority(void) const { return m_priority; } + int Lifetime(void) const { return m_lifetime; } + time_t StartTime(void) const { return m_StartTime; } + time_t Duration(void) const { return m_Duration; } + time_t Vps(void) const { return m_vps; } + const char *Title(void) const { return m_title; } + const char *ShortText(void) const { return m_shortText; } + const char *Description(void) const { return m_description; } + const char *FileName(void) const { return m_fileName; } + const char *Directory(void) const { return m_directory; } + const char *StreamURL(void) const { return m_stream_url.c_str(); } + void SetTitle(const char *Title); + void SetShortText(const char *ShortText); + void SetDescription(const char *Description); +}; + +#endif //__RECORDINGS_H diff --git a/xbmc/pvrclients/vdr-vnsi/requestpacket.cpp b/xbmc/pvrclients/vdr-vnsi/requestpacket.cpp new file mode 100644 index 0000000000..c2523f5a66 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/requestpacket.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef WIN32 +#include +#else + +#endif +#include +#include + +#include "requestpacket.h" +#include "vdrcommand.h" +#include "tools.h" + +uint32_t cRequestPacket::serialNumberCounter = 1; + +cRequestPacket::cRequestPacket() +{ + buffer = NULL; + bufSize = 0; + bufUsed = 0; + lengthSet = false; + serialNumber = 0; + opcode = 0; +} + +cRequestPacket::~cRequestPacket() +{ + free(buffer); +} + +bool cRequestPacket::init(uint32_t topcode, bool stream, bool setUserDataLength, uint32_t userDataLength) +{ + if (buffer) return false; + + if (setUserDataLength) + { + bufSize = headerLength + userDataLength; + lengthSet = true; + } + else + { + bufSize = 512; + userDataLength = 0; // so the below will write a zero + } + + buffer = (uint8_t*)malloc(bufSize); + if (!buffer) return false; + + if (!stream) + channel = CHANNEL_REQUEST_RESPONSE; + else + channel = CHANNEL_STREAM; + serialNumber = serialNumberCounter++; + opcode = topcode; + + *(uint32_t*)&buffer[0] = htonl(channel); + *(uint32_t*)&buffer[4] = htonl(serialNumber); + *(uint32_t*)&buffer[8] = htonl(opcode); + *(uint32_t*)&buffer[userDataLenPos] = htonl(userDataLength); + bufUsed = headerLength; + + return true; +} + +bool cRequestPacket::copyin(const uint8_t* src, uint32_t len) +{ + if (!checkExtend(len)) return false; + memcpy(buffer + bufUsed, src, len); + bufUsed += len; + if (!lengthSet) *(uint32_t*)&buffer[userDataLenPos] = htonl(bufUsed - headerLength); + return true; +} + +bool cRequestPacket::add_String(const char* string) +{ + uint32_t len = strlen(string) + 1; + if (!checkExtend(len)) return false; + memcpy(buffer + bufUsed, string, len); + bufUsed += len; + if (!lengthSet) *(uint32_t*)&buffer[userDataLenPos] = htonl(bufUsed - headerLength); + return true; +} + +bool cRequestPacket::add_U8(uint8_t c) +{ + if (!checkExtend(sizeof(uint8_t))) return false; + buffer[bufUsed] = c; + bufUsed += sizeof(uint8_t); + if (!lengthSet) *(uint32_t*)&buffer[userDataLenPos] = htonl(bufUsed - headerLength); + return true; +} + +bool cRequestPacket::add_S32(int32_t l) +{ + if (!checkExtend(sizeof(int32_t))) return false; + *(int32_t*)&buffer[bufUsed] = htonl(l); + bufUsed += sizeof(int32_t); + if (!lengthSet) *(uint32_t*)&buffer[userDataLenPos] = htonl(bufUsed - headerLength); + return true; +} + +bool cRequestPacket::add_U32(uint32_t ul) +{ + if (!checkExtend(sizeof(uint32_t))) return false; + *(uint32_t*)&buffer[bufUsed] = htonl(ul); + bufUsed += sizeof(uint32_t); + if (!lengthSet) *(uint32_t*)&buffer[userDataLenPos] = htonl(bufUsed - headerLength); + return true; +} + +bool cRequestPacket::add_U64(uint64_t ull) +{ + if (!checkExtend(sizeof(uint64_t))) return false; + *(uint64_t*)&buffer[bufUsed] = htonll(ull); + bufUsed += sizeof(uint64_t); + if (!lengthSet) *(uint32_t*)&buffer[userDataLenPos] = htonl(bufUsed - headerLength); + return true; +} + +bool cRequestPacket::checkExtend(uint32_t by) +{ + if (lengthSet) return true; + if ((bufUsed + by) < bufSize) return true; + uint8_t* newBuf = (uint8_t*)realloc(buffer, bufSize + 512); + if (!newBuf) return false; + buffer = newBuf; + bufSize += 512; + return true; +} + diff --git a/xbmc/pvrclients/vdr-vnsi/requestpacket.h b/xbmc/pvrclients/vdr-vnsi/requestpacket.h new file mode 100644 index 0000000000..3ccb4e4471 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/requestpacket.h @@ -0,0 +1,69 @@ +#pragma once +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#ifndef WIN32 +#include +#else + +#endif +#include + +class cRequestPacket +{ + public: + cRequestPacket(); + ~cRequestPacket(); + + bool init(uint32_t opcode, bool stream = false, bool setUserDataLength = false, uint32_t userDataLength = 0); + bool copyin(const uint8_t* src, uint32_t len); + bool add_String(const char* string); + bool add_U8(uint8_t c); + bool add_U32(uint32_t ul); + bool add_S32(int32_t l); + bool add_U64(uint64_t ull); + + uint8_t* getPtr() { return buffer; } + uint32_t getLen() { return bufUsed; } + uint32_t getChannel() { return channel; } + uint32_t getSerial() { return serialNumber; } + + uint32_t getOpcode() { return opcode; } + + private: + static uint32_t serialNumberCounter; + + uint8_t* buffer; + uint32_t bufSize; + uint32_t bufUsed; + bool lengthSet; + + uint32_t channel; + uint32_t serialNumber; + + uint32_t opcode; + + bool checkExtend(uint32_t by); + + const static uint32_t headerLength = 16; + const static uint32_t userDataLenPos = 12; +}; diff --git a/xbmc/pvrclients/vdr-vnsi/responsepacket.cpp b/xbmc/pvrclients/vdr-vnsi/responsepacket.cpp new file mode 100644 index 0000000000..13eb3437fe --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/responsepacket.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "responsepacket.h" +#include "vdrcommand.h" +#include "tools.h" + +cResponsePacket::cResponsePacket() +{ + userDataLength = 0; + packetPos = 0; + userData = NULL; + ownBlock = true; + channelID = 0; + requestID = 0; + streamID = 0; +} + +cResponsePacket::~cResponsePacket() +{ + if (!ownBlock) return; // don't free if it's a getblock + + if (userData) free(userData); +} + +void cResponsePacket::setResponse(uint32_t trequestID, uint8_t* tuserData, uint32_t tuserDataLength) +{ + channelID = CHANNEL_REQUEST_RESPONSE; + requestID = trequestID; + userData = tuserData; + userDataLength = tuserDataLength; +} + +void cResponsePacket::setStream(uint32_t topcodeID, uint32_t tstreamID, uint32_t tduration, int64_t tdts, int64_t tpts, uint8_t* tuserData, uint32_t tuserDataLength) +{ + channelID = CHANNEL_STREAM; + opcodeID = topcodeID; + streamID = tstreamID; + duration = tduration; + dts = tdts; + pts = tpts; + userData = tuserData; + userDataLength = tuserDataLength; +} + +bool cResponsePacket::end() +{ + return (packetPos >= userDataLength); +} + +int cResponsePacket::serverError() +{ + if ((packetPos == 0) && (userDataLength == 4) && !ntohl(*(uint32_t*)userData)) return 1; + else return 0; +} + +char* cResponsePacket::extract_String() +{ + if (serverError()) return NULL; + + int length = strlen((char*)&userData[packetPos]); + if ((packetPos + length) > userDataLength) return NULL; + char* str = new char[length + 1]; + strcpy(str, (char*)&userData[packetPos]); + packetPos += length + 1; + return str; +} + +uint8_t cResponsePacket::extract_U8() +{ + if ((packetPos + sizeof(uint8_t)) > userDataLength) return 0; + uint8_t uc = userData[packetPos]; + packetPos += sizeof(uint8_t); + return uc; +} + +uint32_t cResponsePacket::extract_U32() +{ + if ((packetPos + sizeof(uint32_t)) > userDataLength) return 0; + uint32_t ul = ntohl(*(uint32_t*)&userData[packetPos]); + packetPos += sizeof(uint32_t); + return ul; +} + +uint64_t cResponsePacket::extract_U64() +{ + if ((packetPos + sizeof(uint64_t)) > userDataLength) return 0; + uint64_t ull = ntohll(*(uint64_t*)&userData[packetPos]); + packetPos += sizeof(uint64_t); + return ull; +} + +double cResponsePacket::extract_Double() +{ + if ((packetPos + sizeof(uint64_t)) > userDataLength) return 0; + uint64_t ull = ntohll(*(uint64_t*)&userData[packetPos]); + double d; + memcpy(&d,&ull,sizeof(double)); + packetPos += sizeof(uint64_t); + return d; +} + +int32_t cResponsePacket::extract_S32() +{ + if ((packetPos + sizeof(int32_t)) > userDataLength) return 0; + int32_t l = ntohl(*(int32_t*)&userData[packetPos]); + packetPos += sizeof(int32_t); + return l; +} + +int64_t cResponsePacket::extract_S64() +{ + if ((packetPos + sizeof(int64_t)) > userDataLength) return 0; + int64_t ll = ntohll(*(int64_t*)&userData[packetPos]); + packetPos += sizeof(int64_t); + return ll; +} + +uint8_t* cResponsePacket::getUserData() +{ + ownBlock = false; + return userData; +} + diff --git a/xbmc/pvrclients/vdr-vnsi/responsepacket.h b/xbmc/pvrclients/vdr-vnsi/responsepacket.h new file mode 100644 index 0000000000..768117cc1e --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/responsepacket.h @@ -0,0 +1,83 @@ +#pragma once +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef WIN32 +#include +#else + +#endif + +#include +#include + +class cResponsePacket +{ + public: + cResponsePacket(); + ~cResponsePacket(); + + void setResponse(uint32_t requestID, uint8_t* packet, uint32_t packetLength); + void setStream(uint32_t opcodeID, uint32_t streamID, uint32_t duration, int64_t dts, int64_t pts, uint8_t* packet, uint32_t packetLength); + + bool noResponse() { return (userData == NULL); }; + int serverError(); + + uint32_t getUserDataLength() { return userDataLength; } + uint32_t getChannelID() { return channelID; } + uint32_t getRequestID() { return requestID; } + uint32_t getStreamID() { return streamID; } + uint32_t getOpCodeID() { return opcodeID; } + uint32_t getDuration() { return duration; } + int64_t getDTS() { return dts; } + int64_t getPTS() { return pts; } + + uint32_t getPacketPos() { return packetPos; } + + char* extract_String(); + uint8_t extract_U8(); + uint32_t extract_U32(); + uint64_t extract_U64(); + int32_t extract_S32(); + int64_t extract_S64(); + double extract_Double(); + + bool end(); + + // If you call this, the memory becomes yours. Free with free() + uint8_t* getUserData(); + + private: + uint8_t* userData; + uint32_t userDataLength; + uint32_t packetPos; + + uint32_t channelID; + + uint32_t requestID; + uint32_t streamID; + uint32_t opcodeID; + uint32_t duration; + int64_t dts; + int64_t pts; + + bool ownBlock; +}; diff --git a/xbmc/pvrclients/vdr-vnsi/thread.cpp b/xbmc/pvrclients/vdr-vnsi/thread.cpp new file mode 100644 index 0000000000..069c1bdb1d --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/thread.cpp @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * Most of this code is taken from thread.c in the Video Disk Recorder ('VDR') + */ + +#include "tools.h" +#include "thread.h" +#include +#ifndef __APPLE__ +#include +#endif + +#include +#include +#include "StdString.h" + +static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow) +{ + struct timeval now; + if (gettimeofday(&now, NULL) == 0) { // get current time + now.tv_sec += MillisecondsFromNow / 1000; // add full seconds + now.tv_usec += (MillisecondsFromNow % 1000) * 1000; // add microseconds + if (now.tv_usec >= 1000000) { // take care of an overflow + now.tv_sec++; + now.tv_usec -= 1000000; + } + Abstime->tv_sec = now.tv_sec; // seconds + Abstime->tv_nsec = now.tv_usec * 1000; // nano seconds + return true; + } + return false; +} + +// --- cCondWait ------------------------------------------------------------- + +cCondWait::cCondWait(void) +{ + signaled = false; + pthread_mutex_init(&mutex, NULL); + pthread_cond_init(&cond, NULL); +} + +cCondWait::~cCondWait() +{ + pthread_cond_broadcast(&cond); // wake up any sleepers + pthread_cond_destroy(&cond); + pthread_mutex_destroy(&mutex); +} + +void cCondWait::SleepMs(int TimeoutMs) +{ + cCondWait w; + w.Wait(max(TimeoutMs, 3)); // making sure the time is >2ms to avoid a possible busy wait +} + +bool cCondWait::Wait(int TimeoutMs) +{ + pthread_mutex_lock(&mutex); + if (!signaled) { + if (TimeoutMs) { + struct timespec abstime; + if (GetAbsTime(&abstime, TimeoutMs)) { + while (!signaled) { + if (pthread_cond_timedwait(&cond, &mutex, &abstime) == ETIMEDOUT) + break; + } + } + } + else + pthread_cond_wait(&cond, &mutex); + } + bool r = signaled; + signaled = false; + pthread_mutex_unlock(&mutex); + return r; +} + +void cCondWait::Signal(void) +{ + pthread_mutex_lock(&mutex); + signaled = true; + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mutex); +} + +// --- cCondVar -------------------------------------------------------------- + +cCondVar::cCondVar(void) +{ + pthread_cond_init(&cond, 0); +} + +cCondVar::~cCondVar() +{ + pthread_cond_broadcast(&cond); // wake up any sleepers + pthread_cond_destroy(&cond); +} + +void cCondVar::Wait(cMutex &Mutex) +{ + if (Mutex.locked) { + int locked = Mutex.locked; + Mutex.locked = 0; // have to clear the locked count here, as pthread_cond_wait + // does an implicit unlock of the mutex + pthread_cond_wait(&cond, &Mutex.mutex); + Mutex.locked = locked; + } +} + +bool cCondVar::TimedWait(cMutex &Mutex, int TimeoutMs) +{ + bool r = true; // true = condition signaled, false = timeout + + if (Mutex.locked) { + struct timespec abstime; + if (GetAbsTime(&abstime, TimeoutMs)) { + int locked = Mutex.locked; + Mutex.locked = 0; // have to clear the locked count here, as pthread_cond_timedwait + // does an implicit unlock of the mutex. + if (pthread_cond_timedwait(&cond, &Mutex.mutex, &abstime) == ETIMEDOUT) + r = false; + Mutex.locked = locked; + } + } + return r; +} + +void cCondVar::Broadcast(void) +{ + pthread_cond_broadcast(&cond); +} + +// --- cMutex ---------------------------------------------------------------- + +cMutex::cMutex(void) +{ + locked = 0; + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); +#ifndef __APPLE__ + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK_NP); +#else + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); +#endif + pthread_mutex_init(&mutex, &attr); +} + +cMutex::~cMutex() +{ + pthread_mutex_destroy(&mutex); +} + +void cMutex::Lock(void) +{ + pthread_mutex_lock(&mutex); + locked++; +} + +void cMutex::Unlock(void) +{ + if (!--locked) + pthread_mutex_unlock(&mutex); +} + +// --- cThread --------------------------------------------------------------- + +tThreadId cThread::mainThreadId = 0; + +cThread::cThread(const char *Description) +{ + active = running = false; +#if !defined(__WINDOWS__) + childTid = 0; +#endif + childThreadId = 0; + description = NULL; + if (Description) + SetDescription("%s", Description); +} + +cThread::~cThread() +{ + Cancel(); // just in case the derived class didn't call it + free(description); +} + +void cThread::SetPriority(int Priority) +{ +#if !defined(__WINDOWS__) + if (setpriority(PRIO_PROCESS, 0, Priority) < 0) + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); +#endif +} + +void cThread::SetIOPriority(int Priority) +{ +#if !defined(__WINDOWS__) +#ifdef HAVE_LINUXIOPRIO + if (syscall(SYS_ioprio_set, 1, 0, (Priority & 0xff) | (2 << 13)) < 0) // best effort class + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); +#endif +#endif +} + +void cThread::SetDescription(const char *Description, ...) +{ + free(description); + description = NULL; + if (Description) + { + va_list ap; + va_start(ap, Description); + CStdString desc; + desc.FormatV(Description, ap); + description = strdup(desc.c_str()); + va_end(ap); + } +} + +void *cThread::StartThread(cThread *Thread) +{ + Thread->childThreadId = ThreadId(); + if (Thread->description) { + XBMC->Log(LOG_DEBUG, "%s thread started (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId); +#ifdef PR_SET_NAME + if (prctl(PR_SET_NAME, Thread->description, 0, 0, 0) < 0) + XBMC->Log(LOG_ERROR, "%s thread naming failed (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId); +#endif + } + Thread->Action(); + if (Thread->description) + XBMC->Log(LOG_DEBUG, "%s thread ended (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId); + Thread->running = false; + Thread->active = false; + return NULL; +} + +#define THREAD_STOP_TIMEOUT 3000 // ms to wait for a thread to stop before newly starting it +#define THREAD_STOP_SLEEP 30 // ms to sleep while waiting for a thread to stop + +bool cThread::Start(void) +{ + if (!running) { + if (active) { + // Wait until the previous incarnation of this thread has completely ended + // before starting it newly: + cTimeMs RestartTimeout; + while (!running && active && RestartTimeout.Elapsed() < THREAD_STOP_TIMEOUT) + cCondWait::SleepMs(THREAD_STOP_SLEEP); + } + if (!active) { + active = running = true; + if (pthread_create(&childTid, NULL, (void *(*) (void *))&StartThread, (void *)this) == 0) { + pthread_detach(childTid); // auto-reap + } + else { + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); + active = running = false; + return false; + } + } + } + return true; +} + +bool cThread::Active(void) +{ + if (active) { + // + // Single UNIX Spec v2 says: + // + // The pthread_kill() function is used to request + // that a signal be delivered to the specified thread. + // + // As in kill(), if sig is zero, error checking is + // performed but no signal is actually sent. + // + int err; + if ((err = pthread_kill(childTid, 0)) != 0) { + if (err != ESRCH) + XBMC->Log(LOG_ERROR, "ERROR (%s,%d): %m", __FILE__, __LINE__); +#if !defined(__WINDOWS__) + childTid = 0; +#endif + active = running = false; + } + else + return true; + } + return false; +} + +void cThread::Cancel(int WaitSeconds) +{ + running = false; + if (active && WaitSeconds > -1) + { + if (WaitSeconds > 0) + { + for (time_t t0 = time(NULL) + WaitSeconds; time(NULL) < t0; ) + { + if (!Active()) + return; + cCondWait::SleepMs(10); + } + XBMC->Log(LOG_ERROR, "ERROR: %s thread %d won't end (waited %d seconds) - canceling it...", description ? description : "", childThreadId, WaitSeconds); + } + pthread_cancel(childTid); +#if !defined(__WINDOWS__) + childTid = 0; +#endif + active = false; + } +} + +tThreadId cThread::ThreadId(void) +{ +#ifdef __APPLE__ + return (int)pthread_self(); +#else +#ifdef __WINDOWS__ + return GetCurrentThreadId(); +#else + return syscall(__NR_gettid); +#endif +#endif +} + +void cThread::SetMainThreadId(void) +{ + if (mainThreadId == 0) + mainThreadId = ThreadId(); + else + XBMC->Log(LOG_ERROR, "ERROR: attempt to set main thread id to %d while it already is %d", ThreadId(), mainThreadId); +} + +// --- cMutexLock ------------------------------------------------------------ + +cMutexLock::cMutexLock(cMutex *Mutex) +{ + mutex = NULL; + locked = false; + Lock(Mutex); +} + +cMutexLock::~cMutexLock() +{ + if (mutex && locked) + mutex->Unlock(); +} + +bool cMutexLock::Lock(cMutex *Mutex) +{ + if (Mutex && !mutex) + { + mutex = Mutex; + Mutex->Lock(); + locked = true; + return true; + } + return false; +} + +// --- cThreadLock ----------------------------------------------------------- + +cThreadLock::cThreadLock(cThread *Thread) +{ + thread = NULL; + locked = false; + Lock(Thread); +} + +cThreadLock::~cThreadLock() +{ + if (thread && locked) + thread->Unlock(); +} + +bool cThreadLock::Lock(cThread *Thread) +{ + if (Thread && !thread) + { + thread = Thread; + Thread->Lock(); + locked = true; + return true; + } + return false; +} diff --git a/xbmc/pvrclients/vdr-vnsi/thread.h b/xbmc/pvrclients/vdr-vnsi/thread.h new file mode 100644 index 0000000000..0fdc6c4e6c --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/thread.h @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __THREAD_H +#define __THREAD_H + +#include +#include +#include "tools.h" + +class cCondWait { +private: + pthread_mutex_t mutex; + pthread_cond_t cond; + bool signaled; +public: + cCondWait(void); + ~cCondWait(); + static void SleepMs(int TimeoutMs); + ///< Creates a cCondWait object and uses it to sleep for TimeoutMs + ///< milliseconds, immediately giving up the calling thread's time + ///< slice and thus avoiding a "busy wait". + ///< In order to avoid a possible busy wait, TimeoutMs will be automatically + ///< limited to values >2. + bool Wait(int TimeoutMs = 0); + ///< Waits at most TimeoutMs milliseconds for a call to Signal(), or + ///< forever if TimeoutMs is 0. + ///< \return Returns true if Signal() has been called, false it the given + ///< timeout has expired. + void Signal(void); + ///< Signals a caller of Wait() that the condition it is waiting for is met. + }; + +class cMutex; + +class cCondVar { +private: + pthread_cond_t cond; +public: + cCondVar(void); + ~cCondVar(); + void Wait(cMutex &Mutex); + bool TimedWait(cMutex &Mutex, int TimeoutMs); + void Broadcast(void); + }; + +class cMutex { + friend class cCondVar; +private: + pthread_mutex_t mutex; + int locked; +public: + cMutex(void); + ~cMutex(); + void Lock(void); + void Unlock(void); + }; + +typedef pid_t tThreadId; + +class cThread { + friend class cThreadLock; +private: + bool active; + bool running; + pthread_t childTid; + tThreadId childThreadId; + cMutex mutex; + char *description; + static tThreadId mainThreadId; + static void *StartThread(cThread *Thread); +protected: + void SetPriority(int Priority); + void SetIOPriority(int Priority); + void Lock(void) { mutex.Lock(); } + void Unlock(void) { mutex.Unlock(); } + virtual void Action(void) = 0; + ///< A derived cThread class must implement the code it wants to + ///< execute as a separate thread in this function. If this is + ///< a loop, it must check Running() repeatedly to see whether + ///< it's time to stop. + bool Running(void) { return running; } + ///< Returns false if a derived cThread object shall leave its Action() + ///< function. + void Cancel(int WaitSeconds = 0); + ///< Cancels the thread by first setting 'running' to false, so that + ///< the Action() loop can finish in an orderly fashion and then waiting + ///< up to WaitSeconds seconds for the thread to actually end. If the + ///< thread doesn't end by itself, it is killed. + ///< If WaitSeconds is -1, only 'running' is set to false and Cancel() + ///< returns immediately, without killing the thread. +public: + cThread(const char *Description = NULL); + ///< Creates a new thread. + ///< If Description is present, a log file entry will be made when + ///< the thread starts and stops. The Start() function must be called + ///< to actually start the thread. + virtual ~cThread(); +#ifdef __WINDOWS__ + void SetDescription(const char *Description, ...); +#else + void SetDescription(const char *Description, ...) __attribute__ ((format (printf, 2, 3))); +#endif + bool Start(void); + ///< Actually starts the thread. + ///< If the thread is already running, nothing happens. + bool Active(void); + ///< Checks whether the thread is still alive. + static tThreadId ThreadId(void); + static tThreadId IsMainThread(void) { return ThreadId() == mainThreadId; } + static void SetMainThreadId(void); + }; + +// cMutexLock can be used to easily set a lock on mutex and make absolutely +// sure that it will be unlocked when the block will be left. Several locks can +// be stacked, so a function that makes many calls to another function which uses +// cMutexLock may itself use a cMutexLock to make one longer lock instead of many +// short ones. + +class cMutexLock { +private: + cMutex *mutex; + bool locked; +public: + cMutexLock(cMutex *Mutex = NULL); + ~cMutexLock(); + bool Lock(cMutex *Mutex); + }; + +// cThreadLock can be used to easily set a lock in a thread and make absolutely +// sure that it will be unlocked when the block will be left. Several locks can +// be stacked, so a function that makes many calls to another function which uses +// cThreadLock may itself use a cThreadLock to make one longer lock instead of many +// short ones. + +class cThreadLock { +private: + cThread *thread; + bool locked; +public: + cThreadLock(cThread *Thread = NULL); + ~cThreadLock(); + bool Lock(cThread *Thread); + }; + +#define LOCK_THREAD cThreadLock ThreadLock(this) + +#endif //__THREAD_H diff --git a/xbmc/pvrclients/vdr-vnsi/tools.cpp b/xbmc/pvrclients/vdr-vnsi/tools.cpp new file mode 100644 index 0000000000..6ba005cc1f --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/tools.cpp @@ -0,0 +1,740 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * Most of this code is taken from tools.c in the Video Disk Recorder ('VDR') + */ + +#include "tools.h" + +uint64_t ntohll(uint64_t a) +{ + return htonll(a); +} + +uint64_t htonll(uint64_t a) +{ +#if BYTE_ORDER == BIG_ENDIAN + return a; +#else + uint64_t b = 0; + + b = ((a << 56) & 0xFF00000000000000ULL) + | ((a << 40) & 0x00FF000000000000ULL) + | ((a << 24) & 0x0000FF0000000000ULL) + | ((a << 8) & 0x000000FF00000000ULL) + | ((a >> 8) & 0x00000000FF000000ULL) + | ((a >> 24) & 0x0000000000FF0000ULL) + | ((a >> 40) & 0x000000000000FF00ULL) + | ((a >> 56) & 0x00000000000000FFULL) ; + + return b; +#endif +} + +ssize_t safe_read(int filedes, void *buffer, size_t size) +{ + for (;;) { + ssize_t p = read(filedes, buffer, size); + if (p < 0 && errno == EINTR) { + XBMC->Log(LOG_DEBUG, "EINTR while reading from file handle %d - retrying", filedes); + continue; + } + return p; + } +} + +ssize_t safe_write(int filedes, const void *buffer, size_t size) +{ + ssize_t p = 0; + ssize_t written = size; + const unsigned char *ptr = (const unsigned char *)buffer; + while (size > 0) { + p = write(filedes, ptr, size); + if (p < 0) { + if (errno == EINTR) { + XBMC->Log(LOG_DEBUG, "EINTR while writing to file handle %d - retrying", filedes); + continue; + } + break; + } + ptr += p; + size -= p; + } + return p < 0 ? p : written; +} + +void writechar(int filedes, char c) +{ + safe_write(filedes, &c, sizeof(c)); +} + +int WriteAllOrNothing(int fd, const unsigned char *Data, int Length, int TimeoutMs, int RetryMs) +{ + int written = 0; + while (Length > 0) { + int w = write(fd, Data + written, Length); + if (w > 0) { + Length -= w; + written += w; + } + else if (written > 0 && !FATALERRNO) { + // we've started writing, so we must finish it! + cTimeMs t; + cPoller Poller(fd, true); + Poller.Poll(RetryMs); + if (TimeoutMs > 0 && (TimeoutMs -= t.Elapsed()) <= 0) + break; + } + else + // nothing written yet (or fatal error), so we can just return the error code: + return w; + } + return written; +} + +char *strcpyrealloc(char *dest, const char *src) +{ + if (src) { + int l = max(dest ? strlen(dest) : 0, strlen(src)) + 1; // don't let the block get smaller! + dest = (char *)realloc(dest, l); + if (dest) + strcpy(dest, src); + else + XBMC->Log(LOG_ERROR, "ERROR: out of memory"); + } + else { + free(dest); + dest = NULL; + } + return dest; +} + +char *strn0cpy(char *dest, const char *src, size_t n) +{ + char *s = dest; + for ( ; --n && (*dest = *src) != 0; dest++, src++) ; + *dest = 0; + return s; +} + +char *strreplace(char *s, char c1, char c2) +{ + if (s) { + char *p = s; + while (*p) { + if (*p == c1) + *p = c2; + p++; + } + } + return s; +} + +char *strreplace(char *s, const char *s1, const char *s2) +{ + char *p = strstr(s, s1); + if (p) { + int of = p - s; + int l = strlen(s); + int l1 = strlen(s1); + int l2 = strlen(s2); + if (l2 > l1) + s = (char *)realloc(s, l + l2 - l1 + 1); + char *sof = s + of; + if (l2 != l1) + memmove(sof + l2, sof + l1, l - of - l1 + 1); + strncpy(sof, s2, l2); + } + return s; +} + +char *stripspace(char *s) +{ + if (s && *s) { + for (char *p = s + strlen(s) - 1; p >= s; p--) { + if (!isspace(*p)) + break; + *p = 0; + } + } + return s; +} + +char *compactspace(char *s) +{ + if (s && *s) { + char *t = stripspace(skipspace(s)); + char *p = t; + while (p && *p) { + char *q = skipspace(p); + if (q - p > 1) + memmove(p + 1, q, strlen(q) + 1); + p++; + } + if (t != s) + memmove(s, t, strlen(t) + 1); + } + return s; +} + +bool startswith(const char *s, const char *p) +{ + while (*p) { + if (*p++ != *s++) + return false; + } + return true; +} + +bool endswith(const char *s, const char *p) +{ + const char *se = s + strlen(s) - 1; + const char *pe = p + strlen(p) - 1; + while (pe >= p) { + if (*pe-- != *se-- || (se < s && pe >= p)) + return false; + } + return true; +} + +bool isempty(const char *s) +{ + return !(s && *skipspace(s)); +} + +int numdigits(int n) +{ + int res = 1; + while (n >= 10) { + n /= 10; + res++; + } + return res; +} + +bool IsNumber(const char *s) +{ + if (!*s) + return false; + do { + if (!isdigit(*s)) + return false; + } while (*++s); + return true; +} + +CStdString AddDirectory(const char *DirName, const char *FileName) +{ + CStdString ret; + ret.Format("%s/%s", DirName && *DirName ? DirName : ".", FileName); + return ret; +} + +char *ReadLink(const char *FileName) +{ +#if defined(__WINDOWS__) + return NULL; +#else + if (!FileName) + return NULL; + char *TargetName = NULL; + char *res = realpath(FileName, TargetName); + if (!res) + { + if (errno == ENOENT) // file doesn't exist + TargetName = strdup(FileName); + else // some other error occurred + XBMC->Log(LOG_ERROR, "ERROR (%s,%d,s): %m", __FILE__, __LINE__, FileName); + } + return TargetName; +#endif +} + + +// --- cTimeMs --------------------------------------------------------------- + +cTimeMs::cTimeMs(int Ms) +{ + Set(Ms); +} + +uint64_t cTimeMs::Now(void) +{ +#if _POSIX_TIMERS > 0 && defined(_POSIX_MONOTONIC_CLOCK) +#define MIN_RESOLUTION 5 // ms + static bool initialized = false; + static bool monotonic = false; + struct timespec tp; + if (!initialized) { + // check if monotonic timer is available and provides enough accurate resolution: + if (clock_getres(CLOCK_MONOTONIC, &tp) == 0) { + long Resolution = tp.tv_nsec; + // require a minimum resolution: + if (tp.tv_sec == 0 && tp.tv_nsec <= MIN_RESOLUTION * 1000000) { + if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) { + XBMC->Log(LOG_DEBUG, "cTimeMs: using monotonic clock (resolution is %ld ns)", Resolution); + monotonic = true; + } + else + XBMC->Log(LOG_ERROR, "cTimeMs: clock_gettime(CLOCK_MONOTONIC) failed"); + } + else + XBMC->Log(LOG_DEBUG, "cTimeMs: not using monotonic clock - resolution is too bad (%ld s %ld ns)", tp.tv_sec, tp.tv_nsec); + } + else + XBMC->Log(LOG_ERROR, "cTimeMs: clock_getres(CLOCK_MONOTONIC) failed"); + initialized = true; + } + if (monotonic) { + if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) + return (uint64_t(tp.tv_sec)) * 1000 + tp.tv_nsec / 1000000; + XBMC->Log(LOG_ERROR, "cTimeMs: clock_gettime(CLOCK_MONOTONIC) failed"); + monotonic = false; + // fall back to gettimeofday() + } +#else +#if !defined(__WINDOWS__) +# warning Posix monotonic clock not available +#endif +#endif + struct timeval t; + if (gettimeofday(&t, NULL) == 0) + return (uint64_t(t.tv_sec)) * 1000 + t.tv_usec / 1000; + return 0; +} + +void cTimeMs::Set(int Ms) +{ + begin = Now() + Ms; +} + +bool cTimeMs::TimedOut(void) +{ + return Now() >= begin; +} + +uint64_t cTimeMs::Elapsed(void) +{ + return Now() - begin; +} + +// --- cPoller --------------------------------------------------------------- + +cPoller::cPoller(int FileHandle, bool Out) +{ + numFileHandles = 0; + Add(FileHandle, Out); +} + +bool cPoller::Add(int FileHandle, bool Out) +{ + if (FileHandle >= 0) { + for (int i = 0; i < numFileHandles; i++) { + if (pfd[i].fd == FileHandle && pfd[i].events == (Out ? POLLOUT : POLLIN)) + return true; + } + if (numFileHandles < MaxPollFiles) { + pfd[numFileHandles].fd = FileHandle; + pfd[numFileHandles].events = Out ? POLLOUT : POLLIN; + pfd[numFileHandles].revents = 0; + numFileHandles++; + return true; + } + XBMC->Log(LOG_ERROR, "ERROR: too many file handles in cPoller"); + } + return false; +} + +bool cPoller::Poll(int TimeoutMs) +{ + if (numFileHandles) { + if (poll(pfd, numFileHandles, TimeoutMs) != 0) + return true; // returns true even in case of an error, to let the caller + // access the file and thus see the error code + } + return false; +} + +// --- cFile ----------------------------------------------------------------- + +bool cFile::files[FD_SETSIZE] = { false }; +int cFile::maxFiles = 0; + +cFile::cFile(void) +{ + f = -1; +} + +cFile::~cFile() +{ + Close(); +} + +bool cFile::Open(const char *FileName, int Flags, mode_t Mode) +{ + if (!IsOpen()) +#ifdef __WINDOWS__ + return Open(open(FileName, Flags, 0)); +#else + return Open(open(FileName, Flags, Mode)); +#endif + XBMC->Log(LOG_ERROR, "ERROR: attempt to re-open %s", FileName); + return false; +} + +bool cFile::Open(int FileDes) +{ + if (FileDes >= 0) { + if (!IsOpen()) { + f = FileDes; + if (f >= 0) { + if (f < FD_SETSIZE) { + if (f >= maxFiles) + maxFiles = f + 1; + if (!files[f]) + files[f] = true; + else + XBMC->Log(LOG_ERROR, "ERROR: file descriptor %d already in files[]", f); + return true; + } + else + XBMC->Log(LOG_ERROR, "ERROR: file descriptor %d is larger than FD_SETSIZE (%d)", f, FD_SETSIZE); + } + } + else + XBMC->Log(LOG_ERROR, "ERROR: attempt to re-open file descriptor %d", FileDes); + } + return false; +} + +void cFile::Close(void) +{ + if (f >= 0) { + close(f); + files[f] = false; + f = -1; + } +} + +bool cFile::Ready(bool Wait) +{ + return f >= 0 && AnyFileReady(f, Wait ? 1000 : 0); +} + +bool cFile::AnyFileReady(int FileDes, int TimeoutMs) +{ + fd_set set; + FD_ZERO(&set); + for (int i = 0; i < maxFiles; i++) { + if (files[i]) + FD_SET(i, &set); + } + if (0 <= FileDes && FileDes < FD_SETSIZE && !files[FileDes]) + FD_SET(FileDes, &set); // in case we come in with an arbitrary descriptor + if (TimeoutMs == 0) + TimeoutMs = 10; // load gets too heavy with 0 + struct timeval timeout; + timeout.tv_sec = TimeoutMs / 1000; + timeout.tv_usec = (TimeoutMs % 1000) * 1000; + return select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 0 && (FileDes < 0 || FD_ISSET(FileDes, &set)); +} + +bool cFile::FileReady(int FileDes, int TimeoutMs) +{ + fd_set set; + struct timeval timeout; + FD_ZERO(&set); + FD_SET(FileDes, &set); + if (TimeoutMs >= 0) { + if (TimeoutMs < 100) + TimeoutMs = 100; + timeout.tv_sec = TimeoutMs / 1000; + timeout.tv_usec = (TimeoutMs % 1000) * 1000; + } + return select(FD_SETSIZE, &set, NULL, NULL, (TimeoutMs >= 0) ? &timeout : NULL) > 0 && FD_ISSET(FileDes, &set); +} + +bool cFile::FileReadyForWriting(int FileDes, int TimeoutMs) +{ + fd_set set; + struct timeval timeout; + FD_ZERO(&set); + FD_SET(FileDes, &set); + if (TimeoutMs < 100) + TimeoutMs = 100; + timeout.tv_sec = 0; + timeout.tv_usec = TimeoutMs * 1000; + return select(FD_SETSIZE, NULL, &set, NULL, &timeout) > 0 && FD_ISSET(FileDes, &set); +} + + +// --- cUnbufferedFile ------------------------------------------------------- + +#if !defined(__WINDOWS__) +#if !defined(__APPLE__) +#define USE_FADVISE +#endif +#endif + +#define WRITE_BUFFER KILOBYTE(800) + +cUnbufferedFile::cUnbufferedFile(void) +{ + fd = -1; +} + +cUnbufferedFile::~cUnbufferedFile() +{ + Close(); +} + +int cUnbufferedFile::Open(const char *FileName, int Flags, mode_t Mode) +{ + Close(); + fd = open(FileName, Flags, Mode); + curpos = 0; +#ifdef USE_FADVISE + begin = lastpos = ahead = 0; + cachedstart = 0; + cachedend = 0; + readahead = KILOBYTE(128); + written = 0; + totwritten = 0; + if (fd >= 0) + posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM); // we could use POSIX_FADV_SEQUENTIAL, but we do our own readahead, disabling the kernel one. +#endif + return fd; +} + +int cUnbufferedFile::Close(void) +{ + if (fd >= 0) { +#ifdef USE_FADVISE + if (totwritten) // if we wrote anything make sure the data has hit the disk before + fdatasync(fd); // calling fadvise, as this is our last chance to un-cache it. + posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED); +#endif + int OldFd = fd; + fd = -1; + return close(OldFd); + } + errno = EBADF; + return -1; +} + +// When replaying and going e.g. FF->PLAY the position jumps back 2..8M +// hence we do not want to drop recently accessed data at once. +// We try to handle the common cases such as PLAY->FF->PLAY, small +// jumps, moving editing marks etc. + +#define FADVGRAN KILOBYTE(4) // AKA fadvise-chunk-size; PAGE_SIZE or getpagesize(2) would also work. +#define READCHUNK MEGABYTE(8) + +void cUnbufferedFile::SetReadAhead(size_t ra) +{ + readahead = ra; +} + +int cUnbufferedFile::FadviseDrop(off_t Offset, off_t Len) +{ +#ifdef USE_FADVISE + // rounding up the window to make sure that not PAGE_SIZE-aligned data gets freed. + return posix_fadvise(fd, Offset - (FADVGRAN - 1), Len + (FADVGRAN - 1) * 2, POSIX_FADV_DONTNEED); +#else + return 0; +#endif +} + +off_t cUnbufferedFile::Seek(off_t Offset, int Whence) +{ + if (Whence == SEEK_SET && Offset == curpos) + return curpos; + curpos = lseek(fd, Offset, Whence); + return curpos; +} + +ssize_t cUnbufferedFile::Read(void *Data, size_t Size) +{ + if (fd >= 0) { +#ifdef USE_FADVISE + off_t jumped = curpos-lastpos; // nonzero means we're not at the last offset + if ((cachedstart < cachedend) && (curpos < cachedstart || curpos > cachedend)) { + // current position is outside the cached window -- invalidate it. + FadviseDrop(cachedstart, cachedend-cachedstart); + cachedstart = curpos; + cachedend = curpos; + } + cachedstart = min(cachedstart, curpos); +#endif + ssize_t bytesRead = safe_read(fd, Data, Size); +#ifdef USE_FADVISE + if (bytesRead > 0) { + curpos += bytesRead; + cachedend = max(cachedend, curpos); + + // Read ahead: + // no jump? (allow small forward jump still inside readahead window). + if (jumped >= 0 && jumped <= (off_t)readahead) { + // Trigger the readahead IO, but only if we've used at least + // 1/2 of the previously requested area. This avoids calling + // fadvise() after every read() call. + if (ahead - curpos < (off_t)(readahead / 2)) { + posix_fadvise(fd, curpos, readahead, POSIX_FADV_WILLNEED); + ahead = curpos + readahead; + cachedend = max(cachedend, ahead); + } + if (readahead < Size * 32) { // automagically tune readahead size. + readahead = Size * 32; + } + } + else + ahead = curpos; // jumped -> we really don't want any readahead, otherwise e.g. fast-rewind gets in trouble. + } + + if (cachedstart < cachedend) { + if (curpos - cachedstart > READCHUNK * 2) { + // current position has moved forward enough, shrink tail window. + FadviseDrop(cachedstart, curpos - READCHUNK - cachedstart); + cachedstart = curpos - READCHUNK; + } + else if (cachedend > ahead && cachedend - curpos > READCHUNK * 2) { + // current position has moved back enough, shrink head window. + FadviseDrop(curpos + READCHUNK, cachedend - (curpos + READCHUNK)); + cachedend = curpos + READCHUNK; + } + } + lastpos = curpos; +#endif + return bytesRead; + } + return -1; +} + +ssize_t cUnbufferedFile::Write(const void *Data, size_t Size) +{ + if (fd >=0) { + ssize_t bytesWritten = safe_write(fd, Data, Size); +#ifdef USE_FADVISE + if (bytesWritten > 0) { + begin = min(begin, curpos); + curpos += bytesWritten; + written += bytesWritten; + lastpos = max(lastpos, curpos); + if (written > WRITE_BUFFER) { + if (lastpos > begin) { + // Now do three things: + // 1) Start writeback of begin..lastpos range + // 2) Drop the already written range (by the previous fadvise call) + // 3) Handle nonpagealigned data. + // This is why we double the WRITE_BUFFER; the first time around the + // last (partial) page might be skipped, writeback will start only after + // second call; the third call will still include this page and finally + // drop it from cache. + off_t headdrop = min(begin, off_t(WRITE_BUFFER * 2)); + posix_fadvise(fd, begin - headdrop, lastpos - begin + headdrop, POSIX_FADV_DONTNEED); + } + begin = lastpos = curpos; + totwritten += written; + written = 0; + // The above fadvise() works when writing slowly (recording), but could + // leave cached data around when writing at a high rate, e.g. when cutting, + // because by the time we try to flush the cached pages (above) the data + // can still be dirty - we are faster than the disk I/O. + // So we do another round of flushing, just like above, but at larger + // intervals -- this should catch any pages that couldn't be released + // earlier. + if (totwritten > MEGABYTE(32)) { + // It seems in some setups, fadvise() does not trigger any I/O and + // a fdatasync() call would be required do all the work (reiserfs with some + // kind of write gathering enabled), but the syncs cause (io) load.. + // Uncomment the next line if you think you need them. + //fdatasync(fd); + off_t headdrop = min(off_t(curpos - totwritten), off_t(totwritten * 2)); + posix_fadvise(fd, curpos - totwritten - headdrop, totwritten + headdrop, POSIX_FADV_DONTNEED); + totwritten = 0; + } + } + } +#endif + return bytesWritten; + } + return -1; +} + +cUnbufferedFile *cUnbufferedFile::Create(const char *FileName, int Flags, mode_t Mode) +{ + cUnbufferedFile *File = new cUnbufferedFile; + if (File->Open(FileName, Flags, Mode) < 0) { + delete File; + File = NULL; + } + return File; +} + +// --- cReadDir -------------------------------------------------------------- + +cReadDir::cReadDir(const char *Directory) +{ + directory = opendir(Directory); +} + +cReadDir::~cReadDir() +{ + if (directory) + closedir(directory); +} + +struct dirent *cReadDir::Next(void) +{ + return directory && readdir_r(directory, &u.d, &result) == 0 ? result : NULL; +} + +// --- cReadLine ------------------------------------------------------------- + +cReadLine::cReadLine(void) +{ + size = 0; + buffer = NULL; +} + +cReadLine::~cReadLine() +{ + free(buffer); +} + +char *cReadLine::Read(FILE *f) +{ + int n = getline(&buffer, &size, f); + if (n > 0) { + n--; + if (buffer[n] == '\n') { + buffer[n] = 0; + if (n > 0) { + n--; + if (buffer[n] == '\r') + buffer[n] = 0; + } + } + return buffer; + } + return NULL; +} diff --git a/xbmc/pvrclients/vdr-vnsi/tools.h b/xbmc/pvrclients/vdr-vnsi/tools.h new file mode 100644 index 0000000000..df2323f503 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/tools.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __TOOLS_H +#define __TOOLS_H + +#include "pvrclient-vdrVNSI_os.h" +#include "client.h" +#include "StdString.h" +#include +#include +#include +#include +#include +#include +#include +#if !defined(__WINDOWS__) +#include +#endif + +#ifdef __APPLE__ +#include +#endif + +#define TS_SYNC_BYTE 0x47 +#define TS_SIZE 188 +#define TS_ERROR 0x80 +#define TS_PAYLOAD_START 0x40 +#define TS_TRANSPORT_PRIORITY 0x20 +#define TS_PID_MASK_HI 0x1F +#define TS_SCRAMBLING_CONTROL 0xC0 +#define TS_ADAPT_FIELD_EXISTS 0x20 +#define TS_PAYLOAD_EXISTS 0x10 +#define TS_CONT_CNT_MASK 0x0F +#define TS_ADAPT_DISCONT 0x80 +#define TS_ADAPT_RANDOM_ACC 0x40 // would be perfect for detecting independent frames, but unfortunately not used by all broadcasters +#define TS_ADAPT_ELEM_PRIO 0x20 +#define TS_ADAPT_PCR 0x10 +#define TS_ADAPT_OPCR 0x08 +#define TS_ADAPT_SPLICING 0x04 +#define TS_ADAPT_TP_PRIVATE 0x02 +#define TS_ADAPT_EXTENSION 0x01 + +#define ERRNUL(e) {errno=e;return 0;} +#define ERRSYS(e) {errno=e;return -1;} + +#define SECSINDAY 86400 + +#define KILOBYTE(n) ((n) * 1024) +#define MEGABYTE(n) ((n) * 1024LL * 1024LL) + +#define MALLOC(type, size) (type *)malloc(sizeof(type) * (size)) + +#define DELETENULL(p) (delete (p), p = NULL) +// +#define FATALERRNO (errno && errno != EAGAIN && errno != EINTR) + +#define DEVDBG(x...)// do{ printf("DBG - "); printf(x); printf("\n"); } while(0) + +uint64_t ntohll(uint64_t a); +uint64_t htonll(uint64_t a); + +ssize_t safe_read(int filedes, void *buffer, size_t size); +ssize_t safe_write(int filedes, const void *buffer, size_t size); +void writechar(int filedes, char c); +int WriteAllOrNothing(int fd, const unsigned char *Data, int Length, int TimeoutMs = 0, int RetryMs = 0); + ///< Writes either all Data to the given file descriptor, or nothing at all. + ///< If TimeoutMs is greater than 0, it will only retry for that long, otherwise + ///< it will retry forever. RetryMs defines the time between two retries. +char *strcpyrealloc(char *dest, const char *src); +char *strn0cpy(char *dest, const char *src, size_t n); +char *strreplace(char *s, char c1, char c2); +char *strreplace(char *s, const char *s1, const char *s2); ///< re-allocates 's' and deletes the original string if necessary! +inline char *skipspace(const char *s) +{ + if ((unsigned char)*s > ' ') // most strings don't have any leading space, so handle this case as fast as possible + return (char *)s; + while (*s && (unsigned char)*s <= ' ') // avoiding isspace() here, because it is much slower + s++; + return (char *)s; +} +char *stripspace(char *s); +char *compactspace(char *s); +bool startswith(const char *s, const char *p); +bool endswith(const char *s, const char *p); +bool isempty(const char *s); +int numdigits(int n); +bool IsNumber(const char *s); +CStdString AddDirectory(const char *DirName, const char *FileName); +char *ReadLink(const char *FileName); ///< returns a new string allocated on the heap, which the caller must delete (or NULL in case of an error) + +class cTimeMs +{ +private: + uint64_t begin; +public: + cTimeMs(int Ms = 0); + ///< Creates a timer with ms resolution and an initial timeout of Ms. + static uint64_t Now(void); + void Set(int Ms = 0); + bool TimedOut(void); + uint64_t Elapsed(void); +}; + +class cPoller +{ +private: + enum { MaxPollFiles = 16 }; + pollfd pfd[MaxPollFiles]; + int numFileHandles; +public: + cPoller(int FileHandle = -1, bool Out = false); + bool Add(int FileHandle, bool Out); + bool Poll(int TimeoutMs = 0); +}; + +class cFile +{ +private: + static bool files[]; + static int maxFiles; + int f; +public: + cFile(void); + ~cFile(); + operator int () { return f; } + bool Open(const char *FileName, int Flags, mode_t Mode = DEFFILEMODE); + bool Open(int FileDes); + void Close(void); + bool IsOpen(void) { return f >= 0; } + bool Ready(bool Wait = true); + static bool AnyFileReady(int FileDes = -1, int TimeoutMs = 1000); + static bool FileReady(int FileDes, int TimeoutMs = 1000); + static bool FileReadyForWriting(int FileDes, int TimeoutMs = 1000); +}; + +/// cUnbufferedFile is used for large files that are mainly written or read +/// in a streaming manner, and thus should not be cached. + +class cUnbufferedFile +{ +private: + int fd; + off_t curpos; + off_t cachedstart; + off_t cachedend; + off_t begin; + off_t lastpos; + off_t ahead; + size_t readahead; + size_t written; + size_t totwritten; + int FadviseDrop(off_t Offset, off_t Len); +public: + cUnbufferedFile(void); + ~cUnbufferedFile(); + int Open(const char *FileName, int Flags, mode_t Mode = DEFFILEMODE); + int Close(void); + void SetReadAhead(size_t ra); + off_t Seek(off_t Offset, int Whence); + ssize_t Read(void *Data, size_t Size); + ssize_t Write(const void *Data, size_t Size); + static cUnbufferedFile *Create(const char *FileName, int Flags, mode_t Mode = DEFFILEMODE); +}; + +class cReadDir +{ +private: + DIR *directory; + struct dirent *result; + union // according to "The GNU C Library Reference Manual" + { + struct dirent d; + char b[offsetof(struct dirent, d_name) + NAME_MAX + 1]; + } u; +public: + cReadDir(const char *Directory); + ~cReadDir(); + bool Ok(void) { return directory != NULL; } + struct dirent *Next(void); +}; + +class cReadLine +{ +private: + size_t size; + char *buffer; +public: + cReadLine(void); + ~cReadLine(); + char *Read(FILE *f); +}; + +inline int CompareStrings(const void *a, const void *b) +{ + return strcmp(*(const char **)a, *(const char **)b); +} + + +#endif //__TOOLS_H diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/COPYING b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/COPYING new file mode 100644 index 0000000000..f90922eea3 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/HISTORY b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/HISTORY new file mode 100644 index 0000000000..756fa448fe --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/HISTORY @@ -0,0 +1,6 @@ +VDR Plugin 'vnsiserver' Revision History +---------------------------------------- + +2010-03-23: Version 0.0.1 + +- Initial revision. diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/Makefile b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/Makefile new file mode 100644 index 0000000000..9d8bafaba0 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/Makefile @@ -0,0 +1,123 @@ +# +# Makefile for a Video Disk Recorder plugin +# +# $Id$ + +# Allow console debug messages +#CONSOLEDEBUG = 1 + +# The official name of this plugin. +# This name will be used in the '-P...' option of VDR to load the plugin. +# By default the main source file also carries this name. +# IMPORTANT: the presence of this macro is important for the Make.config +# file. So it must be defined, even if it is not used here! +# +PLUGIN = vnsiserver + +### The version number of this plugin (taken from the main source file): + +VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g') + +### The C++ compiler and options: + +CXX ?= g++ +CXXFLAGS ?= -fPIC -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses + +### The directory environment: + +VDRDIR = ../../.. +LIBDIR = ../../lib +TMPDIR = /tmp + +### Allow user defined options to overwrite defaults: + +-include $(VDRDIR)/Make.config +-include $(VDRDIR)/Make.global + +### The version number of VDR's plugin API (taken from VDR's "config.h"): + +APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h) + +### The name of the distribution archive: + +ARCHIVE = $(PLUGIN)-$(VERSION) +PACKAGE = vdr-$(ARCHIVE) + +### Includes and Defines (add further entries here): + +INCLUDES += -I$(VDRDIR)/include + +DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' + +DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -DVNSI_SERVER_VERSION='"$(VERSION)"' +ifdef CONSOLEDEBUG +DEFINES += -DCONSOLEDEBUG +endif +LIBS = + +### The object files (add further files here): + +OBJS = $(PLUGIN).o bitstream.o cmdcontrol.o connection.o config.o cxsocket.o demuxer.o demuxer_AAC.o \ + demuxer_AC3.o demuxer_DTS.o demuxer_h264.o demuxer_MPEGAudio.o demuxer_MPEGVideo.o \ + demuxer_Subtitle.o demuxer_Teletext.o receiver.o recplayer.o requestpacket.o responsepacket.o \ + server.o suspend.o tools.o + +### The main target: + +all: libvdr-$(PLUGIN).so i18n + +### Implicit rules: + +%.o: %.c + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< + +### Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ + +-include $(DEPFILE) + +### Internationalization (I18N): + +PODIR = po +LOCALEDIR = $(VDRDIR)/locale +I18Npo = $(wildcard $(PODIR)/*.po) +I18Nmsgs = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file)))))) +I18Npot = $(PODIR)/$(PLUGIN).pot + +%.mo: %.po + msgfmt -c -o $@ $< + +$(I18Npot): $(wildcard *.c) + xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --msgid-bugs-address='' -o $@ $^ + +%.po: $(I18Npot) + msgmerge -U --no-wrap --no-location --backup=none -q $@ $< + @touch $@ + +$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo + @mkdir -p $(dir $@) + cp $< $@ + +.PHONY: i18n +i18n: $(I18Nmsgs) $(I18Npot) + +### Targets: + +libvdr-$(PLUGIN).so: $(OBJS) + $(CXX) $(CXXFLAGS) -shared $(LIBS) $(OBJS) -o $@ + @cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION) + +dist: clean + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @mkdir $(TMPDIR)/$(ARCHIVE) + @cp -a * $(TMPDIR)/$(ARCHIVE) + @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE) + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @echo Distribution package created as $(PACKAGE).tgz + +clean: + @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/README b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/README new file mode 100644 index 0000000000..c0a93a0aa1 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/README @@ -0,0 +1,43 @@ +This is a "plugin" for the Video Disk Recorder (VDR). + +Written by: Your Name + +Project's homepage: URL + +Latest version available at: URL + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +See the file COPYING for more information. + +Description: + + +VNSI support also dynamic PID switching of the received DVB-TS stream. Further it detect and demuxing several +not by VDR implemented Audio Streams, this are: +- Enhanced AC3 (not tested) +- Advanced Audio Coding (AAC) (not tested) +- DTS (demuxer not finished now, and does not work) + + +CHANNEL SCANNING +---------------- +For channel scan's a modified version of the wirbelscan plugin (Version dev-0.0.5-pre11e) is +required. You can download the orginal source from here "http://wirbel.htpc-forum.de/wirbelscan/index2.html". + +The VNSI communicate with wirbelscan over VDR's plugin service interface, to add this feature +you must patch wirbelscan with the file in "patches/vdr-wirbelscan-0.0.5-pre11e-AddServiceInterface.diff". + +Scanning can take up to 50 minutes dependet on signal type and signal quality. + * DVB-T ~ 5 min + * DVB-C ~ 30 min (Symbolrate=AUTO, QAM=AUTO) + * DVB-S/S2 ~ 50 min (depend on Satellite, Beam, Hardware) + * Analog ~ 5 min + +Note: Please notice the warning on the wirbelscan plugin homepage: + "Development Version - kein Support. Benutzung auf eigene Verantwortung." + "Development Version - no support. Use at your own risk." + This means, the time how long the scan run and problems on VDR side are dependet on wirbelscan + and not part of VNSI. \ No newline at end of file diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/bitstream.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/bitstream.c new file mode 100644 index 0000000000..9a699bb995 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/bitstream.c @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include "bitstream.h" + +cBitstream::cBitstream(uint8_t *data, int bits) +{ + m_data = data; + m_offset = 0; + m_len = bits; +} + +void cBitstream::setBitstream(uint8_t *data, int bits) +{ + m_data = data; + m_offset = 0; + m_len = bits; +} + +void cBitstream::skipBits(int num) +{ + m_offset += num; +} + +unsigned int cBitstream::readBits(int num) +{ + int r = 0; + + while(num > 0) + { + if(m_offset >= m_len) + return 0; + + num--; + + if(m_data[m_offset / 8] & (1 << (7 - (m_offset & 7)))) + r |= 1 << num; + + m_offset++; + } + return r; +} + +unsigned int cBitstream::showBits(int num) +{ + int r = 0; + int offs = m_offset; + + while(num > 0) + { + if(offs >= m_len) + return 0; + + num--; + + if(m_data[offs / 8] & (1 << (7 - (offs & 7)))) + r |= 1 << num; + + offs++; + } + return r; +} + +unsigned int cBitstream::readGolombUE() +{ + int lzb = -1; + + for(int b = 0; !b; lzb++) + b = readBits1(); + + return (1 << lzb) - 1 + readBits(lzb); +} + +signed int cBitstream::readGolombSE() +{ + int v, neg; + v = readGolombUE(); + if(v == 0) + return 0; + + neg = v & 1; + v = (v + 1) >> 1; + return neg ? -v : v; +} + + +unsigned int cBitstream::remainingBits() +{ + return m_len - m_offset; +} + + +void cBitstream::putBits(int val, int num) +{ + while(num > 0) { + if(m_offset >= m_len) + return; + + num--; + + if(val & (1 << num)) + m_data[m_offset / 8] |= 1 << (7 - (m_offset & 7)); + else + m_data[m_offset / 8] &= ~(1 << (7 - (m_offset & 7))); + + m_offset++; + } +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/bitstream.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/bitstream.h new file mode 100644 index 0000000000..e1fa361552 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/bitstream.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef BITSTREAM_H_ +#define BITSTREAM_H_ + +class cBitstream +{ +private: + uint8_t *m_data; + int m_offset; + int m_len; + +public: + cBitstream(uint8_t *data, int bits); + + void setBitstream(uint8_t *data, int bits); + void skipBits(int num); + unsigned int readBits(int num); + unsigned int showBits(int num); + unsigned int readBits1() { return readBits(1); } + unsigned int readGolombUE(); + signed int readGolombSE(); + unsigned int remainingBits(); + void putBits(int val, int num); + int length() { return m_len; } +}; + +#endif /* BITSTREAM_H_ */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cmdcontrol.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cmdcontrol.c new file mode 100644 index 0000000000..8f8f9da83e --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cmdcontrol.c @@ -0,0 +1,1675 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "cmdcontrol.h" +#include "connection.h" +#include "recplayer.h" +#include "vdrcommand.h" +#include "wirbelscanservice.h" /// copied from modified wirbelscan plugin + /// must be hold up to date with wirbelscan + +cCmdControl::cCmdControl() +{ + m_req = NULL; + m_resp = NULL; + m_processSCAN_Response = NULL; + m_processSCAN_Socket = NULL; + + Start(); +} + +cCmdControl::~cCmdControl() +{ + Cancel(10); +} + +bool cCmdControl::recvRequest(cRequestPacket* newRequest) +{ + m_mutex.Lock(); + m_req_queue.push(newRequest); + m_mutex.Unlock(); + m_Wait.Signal(); + return true; +} + +void cCmdControl::Action(void) +{ + while (Running()) + { + LOGCONSOLE("threadMethod waiting"); + m_Wait.Wait(500); // unlocks, waits, relocks + + m_mutex.Lock(); + size_t s = m_req_queue.size(); + m_mutex.Unlock(); + + while(s > 0) + { + m_mutex.Lock(); + m_req = m_req_queue.front(); + m_req_queue.pop(); + s = m_req_queue.size(); + m_mutex.Unlock(); + + if (!processPacket()) + { + esyslog("VNSI-Error: Response handler failed during processPacket, exiting thread"); + continue; + } + } + } +} + +bool cCmdControl::processPacket() +{ + m_resp = new cResponsePacket(); + if (!m_resp->init(m_req->getRequestID())) + { + esyslog("VNSI-Error: Response packet init fail"); + delete m_resp; + delete m_req; + m_resp = NULL; + return false; + } + + bool result = false; + switch(m_req->getOpCode()) + { + /** OPCODE 1 - 19: VNSI network functions for general purpose */ + case VDR_LOGIN: + result = process_Login(); + break; + + case VDR_GETTIME: + result = process_GetTime(); + break; + + case VDR_ENABLESTATUSINTERFACE: + result = process_EnableStatusInterface(); + break; + + case VDR_ENABLEOSDINTERFACE: + result = process_EnableOSDInterface(); + break; + + + /** OPCODE 20 - 39: VNSI network functions for live streaming */ + /** NOTE: Live streaming opcodes are handled by cConnection::Action(void) */ + + + /** OPCODE 40 - 59: VNSI network functions for recording streaming */ + case VDR_RECSTREAM_OPEN: + result = processRecStream_Open(); + break; + + case VDR_RECSTREAM_CLOSE: + result = processRecStream_Close(); + break; + + case VDR_RECSTREAM_GETBLOCK: + result = processRecStream_GetBlock(); + break; + + case VDR_RECSTREAM_POSTOFRAME: + result = processRecStream_PositionFromFrameNumber(); + break; + + case VDR_RECSTREAM_FRAMETOPOS: + result = processRecStream_FrameNumberFromPosition(); + break; + + case VDR_RECSTREAM_GETIFRAME: + result = processRecStream_GetIFrame(); + break; + + + /** OPCODE 60 - 79: VNSI network functions for channel access */ + case VDR_CHANNELS_GROUPSCOUNT: + result = processCHANNELS_GroupsCount(); + break; + + case VDR_CHANNELS_GETCOUNT: + result = processCHANNELS_ChannelsCount(); + break; + + case VDR_CHANNELS_GETGROUPS: + result = processCHANNELS_GroupList(); + break; + + case VDR_CHANNELS_GETCHANNELS: + result = processCHANNELS_GetChannels(); + break; + + + /** OPCODE 80 - 99: VNSI network functions for timer access */ + case VDR_TIMER_GETCOUNT: + result = processTIMER_GetCount(); + break; + + case VDR_TIMER_GET: + result = processTIMER_Get(); + break; + + case VDR_TIMER_GETLIST: + result = processTIMER_GetList(); + break; + + case VDR_TIMER_ADD: + result = processTIMER_Add(); + break; + + case VDR_TIMER_DELETE: + result = processTIMER_Delete(); + break; + + case VDR_TIMER_UPDATE: + result = processTIMER_Update(); + break; + + + /** OPCODE 100 - 119: VNSI network functions for recording access */ + case VDR_RECORDINGS_DISKSIZE: + result = processRECORDINGS_GetDiskSpace(); + break; + + case VDR_RECORDINGS_GETCOUNT: + result = processRECORDINGS_GetCount(); + break; + + case VDR_RECORDINGS_GETLIST: + result = processRECORDINGS_GetList(); + break; + + case VDR_RECORDINGS_GETINFO: + result = processRECORDINGS_GetInfo(); + break; + + case VDR_RECORDINGS_DELETE: + result = processRECORDINGS_Delete(); + break; + + case VDR_RECORDINGS_MOVE: + result = processRECORDINGS_Move(); + break; + + + /** OPCODE 120 - 139: VNSI network functions for epg access and manipulating */ + case VDR_EPG_GETFORCHANNEL: + result = processEPG_GetForChannel(); + break; + + + /** OPCODE 140 - 159: VNSI network functions for channel scanning */ + case VDR_SCAN_SUPPORTED: + result = processSCAN_ScanSupported(); + break; + + case VDR_SCAN_GETCOUNTRIES: + result = processSCAN_GetCountries(); + break; + + case VDR_SCAN_GETSATELLITES: + result = processSCAN_GetSatellites(); + break; + + case VDR_SCAN_START: + result = processSCAN_Start(); + break; + + case VDR_SCAN_STOP: + result = processSCAN_Stop(); + break; + } + + delete m_resp; + m_resp = NULL; + + delete m_req; + m_req = NULL; + + return result; +} + + +/** OPCODE 1 - 19: VNSI network functions for general purpose */ + +bool cCmdControl::process_Login() /* OPCODE 1 */ +{ + if (m_req->getDataLength() <= 4) return false; + + uint32_t protocolVersion = m_req->extract_U32(); + bool netLog = m_req->extract_U8(); + const char *clientName = m_req->extract_String(); + + if (protocolVersion != VNSIProtocolVersion) + { + esyslog("VNSI-Error: Client '%s' have a not allowed protocol version '%u', terminating client", clientName, protocolVersion); + delete[] clientName; + return false; + } + + isyslog("VNSI: Welcome client '%s' with protocol version '%u'", clientName, protocolVersion); + + if (netLog) + m_req->getClient()->EnableNetLog(true, clientName); + + // Send the login reply + time_t timeNow = time(NULL); + struct tm* timeStruct = localtime(&timeNow); + int timeOffset = timeStruct->tm_gmtoff; + + m_resp->add_U32(VNSIProtocolVersion); + m_resp->add_U32(timeNow); + m_resp->add_S32(timeOffset); + m_resp->add_String("VDR-Network-Streaming-Interface (VNSI) Server"); + m_resp->add_String(VNSI_SERVER_VERSION); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + + m_req->getClient()->SetLoggedIn(true); + delete[] clientName; + + return true; +} + +bool cCmdControl::process_GetTime() /* OPCODE 2 */ +{ + time_t timeNow = time(NULL); + struct tm* timeStruct = localtime(&timeNow); + int timeOffset = timeStruct->tm_gmtoff; + + m_resp->add_U32(timeNow); + m_resp->add_S32(timeOffset); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::process_EnableStatusInterface() +{ + bool enabled = m_req->extract_U8(); + + m_req->getClient()->SetStatusInterface(enabled); + + m_resp->add_U32(VDR_RET_OK); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::process_EnableOSDInterface() +{ + bool enabled = m_req->extract_U8(); + + m_req->getClient()->SetOSDInterface(enabled); + + m_resp->add_U32(VDR_RET_OK); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + + +/** OPCODE 20 - 39: VNSI network functions for live streaming */ +/** NOTE: Live streaming opcodes are handled by cConnection::Action(void) */ + + +/** OPCODE 40 - 59: VNSI network functions for recording streaming */ + +bool cCmdControl::processRecStream_Open() /* OPCODE 40 */ +{ + const char *fileName = m_req->extract_String(); + cRecording *recording = Recordings.GetByName(fileName); + + LOGCONSOLE("%s: recording pointer %p", fileName, recording); + + if (recording) + { + m_req->getClient()->m_RecPlayer = new cRecPlayer(recording); + + m_resp->add_U32(VDR_RET_OK); + m_resp->add_U32(m_req->getClient()->m_RecPlayer->getLengthFrames()); + m_resp->add_U64(m_req->getClient()->m_RecPlayer->getLengthBytes()); + +#if VDRVERSNUM < 10703 + m_resp->add_U8(true);//added for TS +#else + m_resp->add_U8(recording->IsPesRecording());//added for TS +#endif + + LOGCONSOLE("written totalLength"); + } + else + { + m_resp->add_U32(VDR_RET_DATAUNKNOWN); + + LOGCONSOLE("recording '%s' not found", fileName); + } + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + delete[] fileName; + + return true; +} + +bool cCmdControl::processRecStream_Close() /* OPCODE 41 */ +{ + if (m_req->getClient()->m_RecPlayer) + { + delete m_req->getClient()->m_RecPlayer; + m_req->getClient()->m_RecPlayer = NULL; + } + + m_resp->add_U32(VDR_RET_OK); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processRecStream_GetBlock() /* OPCODE 42 */ +{ + if (m_req->getClient()->IsStreaming()) + { + esyslog("VNSI-Error: Get block called during live streaming"); + return false; + } + + if (!m_req->getClient()->m_RecPlayer) + { + esyslog("VNSI-Error: Get block called when no recording open"); + return false; + } + + uint64_t position = m_req->extract_U64(); + uint32_t amount = m_req->extract_U32(); + +// LOGCONSOLE("getblock pos = %llu length = %lu", position, amount); + + uint8_t* p = m_resp->reserve(amount); + uint32_t amountReceived = m_req->getClient()->m_RecPlayer->getBlock(p, position, amount); + + if(amount > amountReceived) m_resp->unreserve(amount - amountReceived); + + if (!amountReceived) + { + m_resp->add_U32(0); + LOGCONSOLE("written 4(0) as getblock got 0"); + } + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); +// LOGCONSOLE("Finished getblock, have sent %lu", m_resp->getLen()); + return true; +} + +bool cCmdControl::processRecStream_PositionFromFrameNumber() /* OPCODE 43 */ +{ + uint64_t retval = 0; + uint32_t frameNumber = m_req->extract_U32(); + + if (m_req->getClient()->m_RecPlayer) + retval = m_req->getClient()->m_RecPlayer->positionFromFrameNumber(frameNumber); + + m_resp->add_U64(retval); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + + LOGCONSOLE("Wrote posFromFrameNum reply to client"); + return true; +} + +bool cCmdControl::processRecStream_FrameNumberFromPosition() /* OPCODE 44 */ +{ + uint32_t retval = 0; + uint64_t position = m_req->extract_U64(); + + if (m_req->getClient()->m_RecPlayer) + retval = m_req->getClient()->m_RecPlayer->frameNumberFromPosition(position); + + m_resp->add_U32(retval); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + + LOGCONSOLE("Wrote frameNumFromPos reply to client"); + return true; +} + +bool cCmdControl::processRecStream_GetIFrame() /* OPCODE 45 */ +{ + bool success = false; + uint32_t frameNumber = m_req->extract_U32(); + uint32_t direction = m_req->extract_U32(); + uint64_t rfilePosition = 0; + uint32_t rframeNumber = 0; + uint32_t rframeLength = 0; + + if (m_req->getClient()->m_RecPlayer) + success = m_req->getClient()->m_RecPlayer->getNextIFrame(frameNumber, direction, &rfilePosition, &rframeNumber, &rframeLength); + + // returns file position, frame number, length + if (success) + { + m_resp->add_U64(rfilePosition); + m_resp->add_U32(rframeNumber); + m_resp->add_U32(rframeLength); + } + else + { + m_resp->add_U32(0); + } + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + + LOGCONSOLE("Wrote GNIF reply to client %llu %lu %lu", rfilePosition, rframeNumber, rframeLength); + return true; +} + + +/** OPCODE 60 - 79: VNSI network functions for channel access */ + +bool cCmdControl::processCHANNELS_GroupsCount() /* OPCODE 60 */ +{ + int count = 0; + for (int curr = Channels.GetNextGroup(-1); curr >= 0; curr = Channels.GetNextGroup(curr)) + count++; + + m_resp->add_U32(count); + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processCHANNELS_ChannelsCount() /* OPCODE 61 */ +{ + int count = Channels.MaxNumber(); + + m_resp->add_U32(count); + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processCHANNELS_GroupList() /* OPCODE 62 */ +{ + if (m_req->getDataLength() != 4) return false; + + bool radio = m_req->extract_U32(); + + int countInGroup = 0; + int index = 0; + const cChannel* group = NULL; + for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) + { + if (channel->GroupSep()) + { + if (countInGroup) + { + m_resp->add_U32(index); + m_resp->add_U32(countInGroup); + m_resp->add_String(m_toUTF8.Convert(group->Name())); + } + group = channel; + countInGroup = 0; + index++; + } + else if (group) + { + if (radio) + { + if (!channel->Vpid() && channel->Apid(0)) + countInGroup++; + } + else + { + if (channel->Vpid()) + countInGroup++; + } + } + } + + if (countInGroup) + { + m_resp->add_U32(index); + m_resp->add_U32(countInGroup); + m_resp->add_String(group->Name()); + } + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processCHANNELS_GetChannels() /* OPCODE 63 */ +{ + if (m_req->getDataLength() != 4) return false; + + bool radio = m_req->extract_U32(); + + int groupIndex = 0; + const cChannel* group = NULL; + for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) + { + if (channel->GroupSep()) + { + group = channel; + groupIndex++; + } + else + { + bool isRadio = false; + + if (channel->Vpid()) + isRadio = false; + else if (channel->Apid(0)) + isRadio = true; + else + continue; + + if (radio != isRadio) + continue; + + m_resp->add_U32(channel->Number()); + m_resp->add_String(m_toUTF8.Convert(channel->Name())); + m_resp->add_U32(channel->Sid()); + m_resp->add_U32(groupIndex); + m_resp->add_U32(channel->Ca()); +#if APIVERSNUM >= 10701 + m_resp->add_U32(channel->Vtype()); +#else + m_resp->add_U32(2); +#endif + } + } + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + + +/** OPCODE 80 - 99: VNSI network functions for timer access */ + +bool cCmdControl::processTIMER_GetCount() /* OPCODE 80 */ +{ + int count = Timers.Count(); + + m_resp->add_U32(count); + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processTIMER_Get() /* OPCODE 81 */ +{ + uint32_t number = m_req->extract_U32(); + + int numTimers = Timers.Count(); + if (numTimers > 0) + { + cTimer *timer = Timers.Get(number-1); + if (timer) + { + m_resp->add_U32(VDR_RET_OK); + + m_resp->add_U32(timer->Index()+1); + m_resp->add_U32(timer->HasFlags(tfActive)); + m_resp->add_U32(timer->Recording()); + m_resp->add_U32(timer->Pending()); + m_resp->add_U32(timer->Priority()); + m_resp->add_U32(timer->Lifetime()); + m_resp->add_U32(timer->Channel()->Number()); + m_resp->add_U32(timer->StartTime()); + m_resp->add_U32(timer->StopTime()); + m_resp->add_U32(timer->Day()); + m_resp->add_U32(timer->WeekDays()); + m_resp->add_String(m_toUTF8.Convert(timer->File())); + } + else + m_resp->add_U32(VDR_RET_DATAUNKNOWN); + } + else + m_resp->add_U32(VDR_RET_DATAUNKNOWN); + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processTIMER_GetList() /* OPCODE 82 */ +{ + cTimer *timer; + int numTimers = Timers.Count(); + + m_resp->add_U32(numTimers); + + for (int i = 0; i < numTimers; i++) + { + timer = Timers.Get(i); + if (!timer) + continue; + + m_resp->add_U32(timer->Index()+1); + m_resp->add_U32(timer->HasFlags(tfActive)); + m_resp->add_U32(timer->Recording()); + m_resp->add_U32(timer->Pending()); + m_resp->add_U32(timer->Priority()); + m_resp->add_U32(timer->Lifetime()); + m_resp->add_U32(timer->Channel()->Number()); + m_resp->add_U32(timer->StartTime()); + m_resp->add_U32(timer->StopTime()); + m_resp->add_U32(timer->Day()); + m_resp->add_U32(timer->WeekDays()); + m_resp->add_String(m_toUTF8.Convert(timer->File())); + } + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processTIMER_Add() /* OPCODE 83 */ +{ + uint32_t flags = m_req->extract_U32() > 0 ? tfActive : tfNone; + uint32_t priority = m_req->extract_U32(); + uint32_t lifetime = m_req->extract_U32(); + uint32_t number = m_req->extract_U32(); + time_t startTime = m_req->extract_U32(); + time_t stopTime = m_req->extract_U32(); + time_t day = m_req->extract_U32(); + uint32_t weekdays = m_req->extract_U32(); + const char *file = m_req->extract_String(); + const char *aux = m_req->extract_String(); + + struct tm tm_r; + struct tm *time = localtime_r(&startTime, &tm_r); + if (day <= 0) + day = cTimer::SetTime(startTime, 0); + int start = time->tm_hour * 100 + time->tm_min; + time = localtime_r(&stopTime, &tm_r); + int stop = time->tm_hour * 100 + time->tm_min; + + cString buffer = cString::sprintf("%u:%i:%s:%04d:%04d:%d:%d:%s:%s\n", flags, number, *cTimer::PrintDay(day, weekdays, true), start, stop, priority, lifetime, file, aux); + + delete[] file; + delete[] aux; + + cTimer *timer = new cTimer; + if (timer->Parse(buffer)) + { + cTimer *t = Timers.GetTimer(timer); + if (!t) + { + Timers.Add(timer); + Timers.SetModified(); + isyslog("VNSI: Timer %s added", *timer->ToDescr()); + m_resp->add_U32(VDR_RET_OK); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; + } + else + { + esyslog("VNSI-Error: Timer already defined: %d %s", t->Index() + 1, *t->ToText()); + m_resp->add_U32(VDR_RET_DATALOCKED); + } + } + else + { + esyslog("VNSI-Error: Error in timer settings"); + m_resp->add_U32(VDR_RET_DATAINVALID); + } + + delete timer; + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processTIMER_Delete() /* OPCODE 84 */ +{ + uint32_t number = m_req->extract_U32(); + bool force = m_req->extract_U32(); + + if (number <= 0 || number > (uint32_t)Timers.Count()) + { + esyslog("VNSI-Error: Unable to delete timer - invalid timer identifier"); + m_resp->add_U32(VDR_RET_DATAINVALID); + } + else + { + cTimer *timer = Timers.Get(number-1); + if (timer) + { + if (!Timers.BeingEdited()) + { + if (timer->Recording()) + { + if (force) + { + timer->Skip(); + cRecordControls::Process(time(NULL)); + } + else + { + esyslog("VNSI-Error: Timer \"%i\" is recording and can be deleted (use force=1 to stop it)", number); + m_resp->add_U32(VDR_RET_RECRUNNING); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; + } + } + isyslog("VNSI: Deleting timer %s", *timer->ToDescr()); + Timers.Del(timer); + Timers.SetModified(); + m_resp->add_U32(VDR_RET_OK); + } + else + { + esyslog("VNSI-Error: Unable to delete timer - timers being edited at VDR"); + m_resp->add_U32(VDR_RET_DATALOCKED); + } + } + else + { + esyslog("VNSI-Error: Unable to delete timer - invalid timer identifier"); + m_resp->add_U32(VDR_RET_DATAINVALID); + } + } + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processTIMER_Update() /* OPCODE 85 */ +{ + int length = m_req->getDataLength(); + uint32_t index = m_req->extract_U32(); + bool active = m_req->extract_U32(); + + cTimer *timer = Timers.Get(index - 1); + if (!timer) + { + esyslog("VNSI-Error: Timer \"%u\" not defined", index); + m_resp->add_U32(VDR_RET_DATAUNKNOWN); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; + } + + cTimer t = *timer; + + if (length == 8) + { + if (active) + t.SetFlags(tfActive); + else + t.ClrFlags(tfActive); + } + else + { + uint32_t flags = active ? tfActive : tfNone; + uint32_t priority = m_req->extract_U32(); + uint32_t lifetime = m_req->extract_U32(); + uint32_t number = m_req->extract_U32(); + time_t startTime = m_req->extract_U32(); + time_t stopTime = m_req->extract_U32(); + time_t day = m_req->extract_U32(); + uint32_t weekdays = m_req->extract_U32(); + const char *file = m_req->extract_String(); + const char *aux = m_req->extract_String(); + + struct tm tm_r; + struct tm *time = localtime_r(&startTime, &tm_r); + if (day <= 0) + day = cTimer::SetTime(startTime, 0); + int start = time->tm_hour * 100 + time->tm_min; + time = localtime_r(&stopTime, &tm_r); + int stop = time->tm_hour * 100 + time->tm_min; + + cString buffer = cString::sprintf("%u:%i:%s:%04d:%04d:%d:%d:%s:%s\n", flags, number, *cTimer::PrintDay(day, weekdays, true), start, stop, priority, lifetime, file, aux); + + delete[] file; + delete[] aux; + + if (!t.Parse(buffer)) + { + esyslog("VNSI-Error: Error in timer settings"); + m_resp->add_U32(VDR_RET_DATAINVALID); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; + } + } + + *timer = t; + Timers.SetModified(); + + m_resp->add_U32(VDR_RET_OK); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + + +/** OPCODE 100 - 119: VNSI network functions for recording access */ + +bool cCmdControl::processRECORDINGS_GetDiskSpace() /* OPCODE 100 */ +{ + int FreeMB; + int Percent = VideoDiskSpace(&FreeMB); + int Total = (FreeMB / (100 - Percent)) * 100; + + m_resp->add_U32(Total); + m_resp->add_U32(FreeMB); + m_resp->add_U32(Percent); + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processRECORDINGS_GetCount() /* OPCODE 101 */ +{ + int count = 0; + bool recordings = Recordings.Load(); + Recordings.Sort(); + if (recordings) + { + cRecording *recording = Recordings.Last(); + count = recording->Index() + 1; + } + + m_resp->add_U32(count); + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processRECORDINGS_GetList() /* OPCODE 102 */ +{ + cRecordings Recordings; + Recordings.Load(); + + m_resp->add_String(VideoDirectory); + for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) + { + #if APIVERSNUM >= 10705 + const cEvent *event = recording->Info()->GetEvent(); + #else + const cEvent *event = NULL; + #endif + + time_t recordingStart = 0; + int recordingDuration = 0; + if (event) + { + recordingStart = event->StartTime(); + recordingDuration = event->Duration(); + } + else + { + cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName()); + if (rc) + { + recordingStart = rc->Timer()->StartTime(); + recordingDuration = rc->Timer()->StopTime() - recordingStart; + } + else + { + recordingStart = recording->start; + } + } + LOGCONSOLE("GRI: RC: recordingStart=%lu recordingDuration=%lu", recordingStart, recordingDuration); + + m_resp->add_U32(recordingStart); + m_resp->add_U32(recordingDuration); + m_resp->add_U32(recording->priority); + m_resp->add_U32(recording->lifetime); + m_resp->add_String(recording->Info()->ChannelName() ? m_toUTF8.Convert(recording->Info()->ChannelName()) : ""); + const char* fullname = recording->Name(); + const char* recname = strrchr(fullname, '~'); + if(recname != NULL) { + recname++; + m_resp->add_String(m_toUTF8.Convert(recname)); + } + else if (!isempty(recording->Info()->Title())) { + m_resp->add_String(m_toUTF8.Convert(recording->Info()->Title())); + } + else + m_resp->add_String(""); + if (!isempty(recording->Info()->ShortText())) + m_resp->add_String(m_toUTF8.Convert(recording->Info()->ShortText())); + else + m_resp->add_String(""); + if (!isempty(recording->Info()->Description())) + m_resp->add_String(m_toUTF8.Convert(recording->Info()->Description())); + else + m_resp->add_String(""); + + m_resp->add_String(m_toUTF8.Convert(recording->FileName())); + } + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processRECORDINGS_GetInfo() /* OPCODE 103 */ +{ + const char *fileName = m_req->extract_String(); + + cRecordings Recordings; + Recordings.Load(); // probably have to do this + + cRecording *recording = Recordings.GetByName(fileName); +#if APIVERSNUM >= 10705 + const cEvent *event = recording->Info()->GetEvent(); +#else + const cEvent *event = NULL; +#endif + + time_t recordingStart = 0; + int recordingDuration = 0; + if (event) + { + recordingStart = event->StartTime(); + recordingDuration = event->Duration(); + } + else + { + cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName()); + if (rc) + { + recordingStart = rc->Timer()->StartTime(); + recordingDuration = rc->Timer()->StopTime() - recordingStart; + } + else + { + recordingStart = recording->start; + } + } + LOGCONSOLE("GRI: RC: recordingStart=%lu recordingDuration=%lu", recordingStart, recordingDuration); + + m_resp->add_U32(recordingStart); + m_resp->add_U32(recordingDuration); + m_resp->add_U32(recording->priority); + m_resp->add_U32(recording->lifetime); + m_resp->add_String(recording->Info()->ChannelName() ? m_toUTF8.Convert(recording->Info()->ChannelName()) : ""); + if (!isempty(recording->Info()->Title())) + m_resp->add_String(m_toUTF8.Convert(recording->Info()->Title())); + else + m_resp->add_String(""); + if (!isempty(recording->Info()->ShortText())) + m_resp->add_String(m_toUTF8.Convert(recording->Info()->ShortText())); + else + m_resp->add_String(""); + if (!isempty(recording->Info()->Description())) + m_resp->add_String(m_toUTF8.Convert(recording->Info()->Description())); + else + m_resp->add_String(""); + +#if APIVERSNUM < 10703 + m_resp->add_double((double)FRAMESPERSEC); +#else + m_resp->add_double((double)recording->Info()->FramesPerSecond()); +#endif + + if (event != NULL) + { + if (event->Vps()) + m_resp->add_U32(event->Vps()); + else + m_resp->add_U32(0); + } + else + m_resp->add_U32(0); + + const cComponents* components = recording->Info()->Components(); + if (components) + { + m_resp->add_U32(components->NumComponents()); + + tComponent* component; + for (int i = 0; i < components->NumComponents(); i++) + { + component = components->Component(i); + + LOGCONSOLE("GRI: C: %i %u %u %s %s", i, component->stream, component->type, component->language, component->description); + + m_resp->add_U8(component->stream); + m_resp->add_U8(component->type); + + if (component->language) + m_resp->add_String(component->language); + else + m_resp->add_String(""); + + if (component->description) + m_resp->add_String(component->description); + else + m_resp->add_String(""); + } + } + else + m_resp->add_U32(0); + + // Done. send it + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + + delete[] fileName; + LOGCONSOLE("Written getrecinfo"); + return true; +} + +bool cCmdControl::processRECORDINGS_Delete() /* OPCODE 104 */ +{ + const char *recName = m_req->extract_String(); + + cRecordings Recordings; + Recordings.Load(); // probably have to do this + + cRecording* recording = Recordings.GetByName(recName); + + LOGCONSOLE("recording pointer %p", recording); + + if (recording) + { + LOGCONSOLE("deleting recording: %s", recording->Name()); + + cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName()); + if (!rc) + { + if (recording->Delete()) + { + // Copy svdrdeveldevelp's way of doing this, see if it works + Recordings.DelByName(recording->FileName()); + isyslog("VNSI: Recording \"%s\" deleted", recording->FileName()); + m_resp->add_U32(VDR_RET_OK); + } + else + { + esyslog("VNSI-Error: Error while deleting recording!"); + m_resp->add_U32(VDR_RET_ERROR); + } + } + else + { + esyslog("VNSI-Error: Recording \"%s\" is in use by timer %d", recording->Name(), rc->Timer()->Index() + 1); + m_resp->add_U32(VDR_RET_DATALOCKED); + } + } + else + { + esyslog("VNSI-Error: Error in recording name \"%s\"", recName); + m_resp->add_U32(VDR_RET_DATAUNKNOWN); + } + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + + delete[] recName; + return true; +} + +bool cCmdControl::processRECORDINGS_Move() /* OPCODE 105 */ +{ + LOGCONSOLE("Process move recording"); + const char *fileName = m_req->extract_String(); + const char *newPath = m_req->extract_String(); + + cRecordings Recordings; + Recordings.Load(); // probably have to do this + + cRecording* recording = Recordings.GetByName(fileName); + + LOGCONSOLE("recording pointer %p", recording); + + if (recording) + { + cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName()); + if (!rc) + { + LOGCONSOLE("moving recording: %s", recording->Name()); + LOGCONSOLE("moving recording: %s", recording->FileName()); + LOGCONSOLE("to: %s", newPath); + + const char* t = recording->FileName(); + + char* dateDirName = NULL; int k; + char* titleDirName = NULL; int j; + + // Find the datedirname + for(k = strlen(t) - 1; k >= 0; k--) + { + if (t[k] == '/') + { + LOGCONSOLE("l1: %i", strlen(&t[k+1]) + 1); + dateDirName = new char[strlen(&t[k+1]) + 1]; + strcpy(dateDirName, &t[k+1]); + break; + } + } + + // Find the titledirname + for(j = k-1; j >= 0; j--) + { + if (t[j] == '/') + { + LOGCONSOLE("l2: %i", (k - j - 1) + 1); + titleDirName = new char[(k - j - 1) + 1]; + memcpy(titleDirName, &t[j+1], k - j - 1); + titleDirName[k - j - 1] = '\0'; + break; + } + } + + LOGCONSOLE("datedirname: %s", dateDirName); + LOGCONSOLE("titledirname: %s", titleDirName); + LOGCONSOLE("viddir: %s", VideoDirectory); + + char* newPathConv = new char[strlen(newPath)+1]; + strcpy(newPathConv, newPath); + ExchangeChars(newPathConv, true); + LOGCONSOLE("EC: %s", newPathConv); + + char* newContainer = new char[strlen(VideoDirectory) + strlen(newPathConv) + strlen(titleDirName) + 1]; + LOGCONSOLE("l10: %i", strlen(VideoDirectory) + strlen(newPathConv) + strlen(titleDirName) + 1); + sprintf(newContainer, "%s%s%s", VideoDirectory, newPathConv, titleDirName); + delete[] newPathConv; + + LOGCONSOLE("%s", newContainer); + + struct stat dstat; + int statret = stat(newContainer, &dstat); + if ((statret == -1) && (errno == ENOENT)) // Dir does not exist + { + LOGCONSOLE("new dir does not exist"); + int mkdirret = mkdir(newContainer, 0755); + if (mkdirret != 0) + { + delete[] dateDirName; + delete[] titleDirName; + delete[] newContainer; + + m_resp->add_U32(VDR_RET_ERROR); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; + } + } + else if ((statret == 0) && (! (dstat.st_mode && S_IFDIR))) // Something exists but it's not a dir + { + delete[] dateDirName; + delete[] titleDirName; + delete[] newContainer; + + m_resp->add_U32(VDR_RET_ERROR); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; + } + + // Ok, the directory container has been made, or it pre-existed. + + char* newDir = new char[strlen(newContainer) + 1 + strlen(dateDirName) + 1]; + sprintf(newDir, "%s/%s", newContainer, dateDirName); + + LOGCONSOLE("doing rename '%s' '%s'", t, newDir); + int renameret = rename(t, newDir); + if (renameret == 0) + { + // Success. Test for remove old dir containter + char* oldTitleDir = new char[k+1]; + memcpy(oldTitleDir, t, k); + oldTitleDir[k] = '\0'; + LOGCONSOLE("len: %i, cp: %i, strlen: %i, oldtitledir: %s", k+1, k, strlen(oldTitleDir), oldTitleDir); + rmdir(oldTitleDir); // can't do anything about a fail result at this point. + delete[] oldTitleDir; + } + if (renameret == 0) + { + // Tell VDR + Recordings.Update(); + // Success. Send a different packet from just a ulong + m_resp->add_U32(VDR_RET_OK); // success + m_resp->add_String(newDir); + } + else + { + m_resp->add_U32(VDR_RET_ERROR); + } + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + + delete[] dateDirName; + delete[] titleDirName; + delete[] newContainer; + delete[] newDir; + } + else + { + m_resp->add_U32(VDR_RET_DATALOCKED); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + } + } + else + { + m_resp->add_U32(VDR_RET_DATAUNKNOWN); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + } + + delete[] fileName; + delete[] newPath; + + return true; +} + + +/** OPCODE 120 - 139: VNSI network functions for epg access and manipulating */ + +bool cCmdControl::processEPG_GetForChannel() /* OPCODE 120 */ +{ + uint32_t channelNumber = m_req->extract_U32(); + uint32_t startTime = m_req->extract_U32(); + uint32_t duration = m_req->extract_U32(); + + LOGCONSOLE("get schedule called for channel %lu", channelNumber); + + cChannel* channel = Channels.GetByNumber(channelNumber); + if (!channel) + { + m_resp->add_U32(0); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + + LOGCONSOLE("written 0 because channel = NULL"); + return true; + } + + cSchedulesLock MutexLock; + const cSchedules *Schedules = cSchedules::Schedules(MutexLock); + if (!Schedules) + { + m_resp->add_U32(0); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + + LOGCONSOLE("written 0 because Schedule!s! = NULL"); + return true; + } + + const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID()); + if (!Schedule) + { + m_resp->add_U32(0); + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + + LOGCONSOLE("written 0 because Schedule = NULL"); + return true; + } + + const char* empty = ""; + bool atLeastOneEvent = false; + + uint32_t thisEventID; + uint32_t thisEventTime; + uint32_t thisEventDuration; + uint32_t thisEventContent; + uint32_t thisEventRating; + const char* thisEventTitle; + const char* thisEventSubTitle; + const char* thisEventDescription; + + for (const cEvent* event = Schedule->Events()->First(); event; event = Schedule->Events()->Next(event)) + { + thisEventID = event->EventID(); + thisEventTitle = event->Title(); + thisEventSubTitle = event->ShortText(); + thisEventDescription = event->Description(); + thisEventTime = event->StartTime(); + thisEventDuration = event->Duration(); +#if defined(USE_PARENTALRATING) || defined(PARENTALRATINGCONTENTVERSNUM) + thisEventContent = event->Contents(); + thisEventRating = 0; +#elif APIVERSNUM >= 10711 + thisEventContent = event->Contents(); + thisEventRating = event->ParentalRating(); +#else + thisEventContent = 0; + thisEventRating = 0; +#endif + + //in the past filter + if ((thisEventTime + thisEventDuration) < (uint32_t)time(NULL)) continue; + + //start time filter + if ((thisEventTime + thisEventDuration) <= startTime) continue; + + //duration filter + if (duration != 0 && thisEventTime >= (startTime + duration)) continue; + + if (!thisEventTitle) thisEventTitle = empty; + if (!thisEventSubTitle) thisEventSubTitle = empty; + if (!thisEventDescription) thisEventDescription = empty; + + m_resp->add_U32(thisEventID); + m_resp->add_U32(thisEventTime); + m_resp->add_U32(thisEventDuration); + m_resp->add_U32(thisEventContent); + m_resp->add_U32(thisEventRating); + + m_resp->add_String(m_toUTF8.Convert(thisEventTitle)); + m_resp->add_String(m_toUTF8.Convert(thisEventSubTitle)); + m_resp->add_String(m_toUTF8.Convert(thisEventDescription)); + + atLeastOneEvent = true; + } + + LOGCONSOLE("Got all event data"); + + if (!atLeastOneEvent) + { + m_resp->add_U32(0); + LOGCONSOLE("Written 0 because no data"); + } + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + + LOGCONSOLE("written schedules packet"); + + return true; +} + + +/** OPCODE 140 - 169: VNSI network functions for channel scanning */ + +bool cCmdControl::processSCAN_ScanSupported() /* OPCODE 140 */ +{ + /** Note: Using "WirbelScanService-StopScan-v1.0" to detect + a present service interface in wirbelscan plugin, + it returns true if supported */ + cPlugin *p = cPluginManager::GetPlugin("wirbelscan"); + if (p && p->Service("WirbelScanService-StopScan-v1.0", NULL)) + m_resp->add_U32(VDR_RET_OK); + else + m_resp->add_U32(VDR_RET_NOTSUPPORTED); + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processSCAN_GetCountries() /* OPCODE 141 */ +{ + if (!m_processSCAN_Response) + { + m_processSCAN_Response = m_resp; + cPlugin *p = cPluginManager::GetPlugin("wirbelscan"); + if (p) + { + m_resp->add_U32(VDR_RET_OK); + p->Service("WirbelScanService-GetCountries-v1.0", (void*) processSCAN_AddCountry); + } + else + { + m_resp->add_U32(VDR_RET_NOTSUPPORTED); + } + m_processSCAN_Response = NULL; + } + else + { + m_resp->add_U32(VDR_RET_DATALOCKED); + } + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processSCAN_GetSatellites() /* OPCODE 142 */ +{ + if (!m_processSCAN_Response) + { + m_processSCAN_Response = m_resp; + cPlugin *p = cPluginManager::GetPlugin("wirbelscan"); + if (p) + { + m_resp->add_U32(VDR_RET_OK); + p->Service("WirbelScanService-GetSatellites-v1.0", (void*) processSCAN_AddSatellite); + } + else + { + m_resp->add_U32(VDR_RET_NOTSUPPORTED); + } + m_processSCAN_Response = NULL; + } + else + { + m_resp->add_U32(VDR_RET_DATALOCKED); + } + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processSCAN_Start() /* OPCODE 143 */ +{ + WirbelScanService_DoScan_v1_0 svc; + svc.type = (scantype_t)m_req->extract_U32(); + svc.scan_tv = (bool)m_req->extract_U8(); + svc.scan_radio = (bool)m_req->extract_U8(); + svc.scan_fta = (bool)m_req->extract_U8(); + svc.scan_scrambled = (bool)m_req->extract_U8(); + svc.scan_hd = (bool)m_req->extract_U8(); + svc.CountryIndex = (int)m_req->extract_U32(); + svc.DVBC_Inversion = (int)m_req->extract_U32(); + svc.DVBC_Symbolrate = (int)m_req->extract_U32(); + svc.DVBC_QAM = (int)m_req->extract_U32(); + svc.DVBT_Inversion = (int)m_req->extract_U32(); + svc.SatIndex = (int)m_req->extract_U32(); + svc.ATSC_Type = (int)m_req->extract_U32(); + svc.SetPercentage = processSCAN_SetPercentage; + svc.SetSignalStrength = processSCAN_SetSignalStrength; + svc.SetDeviceInfo = processSCAN_SetDeviceInfo; + svc.SetTransponder = processSCAN_SetTransponder; + svc.NewChannel = processSCAN_NewChannel; + svc.IsFinished = processSCAN_IsFinished; + svc.SetStatus = processSCAN_SetStatus; + m_processSCAN_Socket = m_req->getClient()->GetSocket(); + + cPlugin *p = cPluginManager::GetPlugin("wirbelscan"); + if (p) + { + if (p->Service("WirbelScanService-DoScan-v1.0", (void*) &svc)) + m_resp->add_U32(VDR_RET_OK); + else + m_resp->add_U32(VDR_RET_ERROR); + } + else + { + m_resp->add_U32(VDR_RET_NOTSUPPORTED); + } + + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +bool cCmdControl::processSCAN_Stop() /* OPCODE 144 */ +{ + cPlugin *p = cPluginManager::GetPlugin("wirbelscan"); + if (p) + { + p->Service("WirbelScanService-StopScan-v1.0", NULL); + m_resp->add_U32(VDR_RET_OK); + } + else + { + m_resp->add_U32(VDR_RET_NOTSUPPORTED); + } + m_resp->finalise(); + m_req->getClient()->GetSocket()->write(m_resp->getPtr(), m_resp->getLen()); + return true; +} + +cResponsePacket *cCmdControl::m_processSCAN_Response = NULL; +cxSocket *cCmdControl::m_processSCAN_Socket = NULL; + +void cCmdControl::processSCAN_AddCountry(int index, const char *isoName, const char *longName) +{ + m_processSCAN_Response->add_U32(index); + m_processSCAN_Response->add_String(isoName); + m_processSCAN_Response->add_String(longName); +} + +void cCmdControl::processSCAN_AddSatellite(int index, const char *shortName, const char *longName) +{ + m_processSCAN_Response->add_U32(index); + m_processSCAN_Response->add_String(shortName); + m_processSCAN_Response->add_String(longName); +} + +void cCmdControl::processSCAN_SetPercentage(int percent) +{ + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initScan(VDR_SCANNER_PERCENTAGE)) + { + delete resp; + return; + } + resp->add_U32(percent); + resp->finalise(); + m_processSCAN_Socket->write(resp->getPtr(), resp->getLen()); + delete resp; +} + +void cCmdControl::processSCAN_SetSignalStrength(int strength, bool locked) +{ + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initScan(VDR_SCANNER_SIGNAL)) + { + delete resp; + return; + } + strength *= 100; + strength /= 0xFFFF; + resp->add_U32(strength); + resp->add_U32(locked); + resp->finalise(); + m_processSCAN_Socket->write(resp->getPtr(), resp->getLen()); + delete resp; +} + +void cCmdControl::processSCAN_SetDeviceInfo(const char *Info) +{ + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initScan(VDR_SCANNER_DEVICE)) + { + delete resp; + return; + } + resp->add_String(Info); + resp->finalise(); + m_processSCAN_Socket->write(resp->getPtr(), resp->getLen()); + delete resp; +} + +void cCmdControl::processSCAN_SetTransponder(const char *Info) +{ + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initScan(VDR_SCANNER_TRANSPONDER)) + { + delete resp; + return; + } + resp->add_String(Info); + resp->finalise(); + m_processSCAN_Socket->write(resp->getPtr(), resp->getLen()); + delete resp; +} + +void cCmdControl::processSCAN_NewChannel(const char *Name, bool isRadio, bool isEncrypted, bool isHD) +{ + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initScan(VDR_SCANNER_NEWCHANNEL)) + { + delete resp; + return; + } + resp->add_U32(isRadio); + resp->add_U32(isEncrypted); + resp->add_U32(isHD); + resp->add_String(Name); + resp->finalise(); + m_processSCAN_Socket->write(resp->getPtr(), resp->getLen()); + delete resp; +} + +void cCmdControl::processSCAN_IsFinished() +{ + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initScan(VDR_SCANNER_FINISHED)) + { + delete resp; + return; + } + resp->finalise(); + m_processSCAN_Socket->write(resp->getPtr(), resp->getLen()); + m_processSCAN_Socket = NULL; + delete resp; +} + +void cCmdControl::processSCAN_SetStatus(int status) +{ + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initScan(VDR_SCANNER_STATUS)) + { + delete resp; + return; + } + resp->add_U32(status); + resp->finalise(); + m_processSCAN_Socket->write(resp->getPtr(), resp->getLen()); + delete resp; +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cmdcontrol.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cmdcontrol.h new file mode 100644 index 0000000000..96556ce787 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cmdcontrol.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef CMD_CONTROL_H +#define CMD_CONTROL_H + +#include +#include + +#include "responsepacket.h" +#include "requestpacket.h" + +typedef std::queue RequestPacketQueue; + +class cxSocket; + +class cCmdControl : public cThread +{ +public: + cCmdControl(); + ~cCmdControl(); + + bool init(); + bool recvRequest(cRequestPacket*); + +private: + bool processPacket(); + + bool process_Login(); + bool process_GetTime(); + bool process_EnableStatusInterface(); + bool process_EnableOSDInterface(); + + bool processRecStream_Open(); + bool processRecStream_Close(); + bool processRecStream_GetBlock(); + bool processRecStream_PositionFromFrameNumber(); + bool processRecStream_FrameNumberFromPosition(); + bool processRecStream_GetIFrame(); + + bool processCHANNELS_GroupsCount(); + bool processCHANNELS_ChannelsCount(); + bool processCHANNELS_GroupList(); + bool processCHANNELS_GetChannels(); + + bool processTIMER_GetCount(); + bool processTIMER_Get(); + bool processTIMER_GetList(); + bool processTIMER_Add(); + bool processTIMER_Delete(); + bool processTIMER_Update(); + + bool processRECORDINGS_GetDiskSpace(); + bool processRECORDINGS_GetCount(); + bool processRECORDINGS_GetList(); + bool processRECORDINGS_GetInfo(); + bool processRECORDINGS_Delete(); + bool processRECORDINGS_Move(); + + bool processEPG_GetForChannel(); + + bool processSCAN_ScanSupported(); + bool processSCAN_GetCountries(); + bool processSCAN_GetSatellites(); + bool processSCAN_Start(); + bool processSCAN_Stop(); + + /** Static callback functions to interact with wirbelscan plugin over + the plugin service interface */ + static void processSCAN_AddCountry(int index, const char *isoName, const char *longName); + static void processSCAN_AddSatellite(int index, const char *shortName, const char *longName); + static void processSCAN_SetPercentage(int percent); + static void processSCAN_SetSignalStrength(int strength, bool locked); + static void processSCAN_SetDeviceInfo(const char *Info); + static void processSCAN_SetTransponder(const char *Info); + static void processSCAN_NewChannel(const char *Name, bool isRadio, bool isEncrypted, bool isHD); + static void processSCAN_IsFinished(); + static void processSCAN_SetStatus(int status); + static cResponsePacket *m_processSCAN_Response; + static cxSocket *m_processSCAN_Socket; + + virtual void Action(void); + + cRequestPacket *m_req; + RequestPacketQueue m_req_queue; + cResponsePacket *m_resp; + cCondWait m_Wait; + cCharSetConv m_toUTF8; + cMutex m_mutex; +}; + + +#endif /* CMD_CONTROL_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/config.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/config.c new file mode 100644 index 0000000000..40d36e03d4 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/config.c @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include + +#include + +#include "config.h" + +cVNSIServerConfig::cVNSIServerConfig() +{ + memset(this, 0, sizeof(cVNSIServerConfig)); + + listen_port = LISTEN_PORT; + SuspendMode = smAlways; + ConfigDirectory = NULL; +} + +void cVNSIServerConfig::readNoSignalStream() +{ + m_noSignalStreamSize = 0; + + cString noSignalFileName = cString::sprintf("%s/"NO_SIGNAL_FILE, *ConfigDirectory); + + FILE *const f = fopen(*noSignalFileName, "rb"); + if (f) + { + m_noSignalStreamSize = fread(&m_noSignalStreamData[0] + 9, 1, sizeof (m_noSignalStreamData) - 9 - 9 - 4, f); + if (m_noSignalStreamSize == sizeof (m_noSignalStreamData) - 9 - 9 - 4) + { + esyslog("VNSI-Error: '%s' exeeds limit of %ld bytes!", *noSignalFileName, (long)(sizeof (m_noSignalStreamData) - 9 - 9 - 4 - 1)); + } + else if (m_noSignalStreamSize > 0) + { + m_noSignalStreamData[ 0 ] = 0x00; + m_noSignalStreamData[ 1 ] = 0x00; + m_noSignalStreamData[ 2 ] = 0x01; + m_noSignalStreamData[ 3 ] = 0xe0; + m_noSignalStreamData[ 4 ] = (m_noSignalStreamSize + 3) >> 8; + m_noSignalStreamData[ 5 ] = (m_noSignalStreamSize + 3) & 0xff; + m_noSignalStreamData[ 6 ] = 0x80; + m_noSignalStreamData[ 7 ] = 0x00; + m_noSignalStreamData[ 8 ] = 0x00; + m_noSignalStreamSize += 9; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x01; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0xe0; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x07; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x80; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x00; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0x01; + m_noSignalStreamData[ m_noSignalStreamSize++ ] = 0xb7; + } + fclose(f); + return; + } + else + { + esyslog("VNSI-Error: couldn't open '%s'!", *noSignalFileName); + } + + return; +} + +/* Global instance */ +cVNSIServerConfig VNSIServerConfig; diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/config.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/config.h new file mode 100644 index 0000000000..7cd3cb3015 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/config.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef _STREAMERNG_CONFIG_H_ +#define _STREAMERNG_CONFIG_H_ + +#include +#include + +#include + +#if CONSOLEDEBUG + #define LOGCONSOLE(x...) do{ fprintf(stderr, "VERBOSE %s - ", __FUNCTION__); fprintf(stderr, x); fprintf(stderr, "\n"); } while(0) +#else + #define LOGCONSOLE(x...) +#endif + +#define ALLOWED_HOSTS_FILE "allowed_hosts.conf" +#define NO_SIGNAL_FILE "noSignal.mpg" +#define FRONTEND_DEVICE "/dev/dvb/adapter%d/frontend%d" + +#define LISTEN_PORT 34890 +#define LISTEN_PORT_S "34890" +#define DISCOVERY_PORT 34890 + +enum eSuspendMode +{ + smOffer, + smAlways, + smNever, + sm_Count +}; + + +class cVNSIServerConfig +{ +public: + cVNSIServerConfig(); + + void readNoSignalStream(); + + // Remote server settings + int listen_port; // Port of remote server + int SuspendMode; + + cString ConfigDirectory; + + uint8_t m_noSignalStreamData[ 6 + 0xffff ]; + long m_noSignalStreamSize; +}; + +// Global instance +extern cVNSIServerConfig VNSIServerConfig; + +#endif /* _STREAMERNG_CONFIG_H_ */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/connection.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/connection.c new file mode 100644 index 0000000000..49b1436a5e --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/connection.c @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "connection.h" +#include "receiver.h" +#include "server.h" +#include "tools.h" +#include "vdrcommand.h" +#include "recplayer.h" +#include "responsepacket.h" + +cConnection::cConnection(cServer *server, int fd, unsigned int id, const char *ClientAdr) +{ + m_Id = id; + m_server = server; + m_Streamer = NULL; + m_isStreaming = false; + m_Channel = NULL; + m_NetLogFile = NULL; + m_ClientAddress = ClientAdr; + m_StatusInterfaceEnabled = false; + m_OSDInterfaceEnabled = false; + + m_socket.set_handle(fd); + + Start(); +} + +cConnection::~cConnection() +{ + isyslog("VNSI: cConnection::~cConnection()"); + StopChannelStreaming(); + m_socket.close(); // force closing connection + isyslog("VNSI: stopping cConnection thread ..."); + Cancel(10); + isyslog("VNSI: done"); + + if (m_NetLogFile) + { + fclose(m_NetLogFile); + m_NetLogFile = NULL; + } +} + +void cConnection::Action(void) +{ + uint32_t kaTimeStamp; + uint32_t logStringLen; + uint32_t channelID; + uint32_t requestID; + uint32_t opcode; + uint32_t dataLength; + uint8_t* data; + + while (Running()) + { + if (!m_socket.read((uint8_t*)&channelID, sizeof(uint32_t))) break; + channelID = ntohl(channelID); + + if (channelID == 1) + { + if (!m_socket.read((uint8_t*)&requestID, sizeof(uint32_t), 10000)) break; + requestID = ntohl(requestID); + + if (!m_socket.read((uint8_t*)&opcode, sizeof(uint32_t), 10000)) break; + opcode = ntohl(opcode); + + if (!m_socket.read((uint8_t*)&dataLength, sizeof(uint32_t), 10000)) break; + dataLength = ntohl(dataLength); + if (dataLength > 200000) // a random sanity limit + { + esyslog("VNSI-Error: dataLength > 200000!"); + break; + } + + if (dataLength) + { + data = (uint8_t*)malloc(dataLength); + if (!data) + { + esyslog("VNSI-Error: Extra data buffer malloc error"); + break; + } + + if (!m_socket.read(data, dataLength, 10000)) + { + esyslog("VNSI-Error: Could not read data"); + free(data); + break; + } + } + else + { + data = NULL; + } + + //LOGCONSOLE("Received chan=%lu, ser=%lu, op=%lu, edl=%lu", channelID, requestID, opcode, dataLength); + + if (!m_loggedIn && (opcode != 1)) + { + esyslog("VNSI-Error: Not logged in and opcode != 1"); + if (data) free(data); + break; + } + + /* Handle channel open and close inside this thread */ + if (opcode == VDR_CHANNELSTREAM_OPEN) + { + cResponsePacket *resp = new cResponsePacket(); + if (!resp->init(requestID)) + { + esyslog("VNSI-Error: response packet init fail"); + delete resp; + continue; + } + + uint32_t number = ntohl(*(uint32_t*)&data[0]); + free(data); + + if (m_isStreaming) + StopChannelStreaming(); + + const cChannel *channel = Channels.GetByNumber(number); + if (channel != NULL) + { + if (StartChannelStreaming(channel, resp)) + { + isyslog("VNSI: Started streaming of channel %i - %s", number, channel->Name()); + continue; + } + else + { + LOGCONSOLE("Can't stream channel %i - %s", number, channel->Name()); + resp->add_U32(VDR_RET_DATALOCKED); + } + } + else + { + esyslog("VNSI-Error: Can't find channel %i", number); + resp->add_U32(VDR_RET_DATAINVALID); + } + + resp->finalise(); + m_socket.write(resp->getPtr(), resp->getLen()); + } + else if (opcode == VDR_CHANNELSTREAM_CLOSE) + { + if (m_isStreaming) + StopChannelStreaming(); + } + else + { + cRequestPacket* req = new cRequestPacket(requestID, opcode, data, dataLength, this); + m_cmdcontrol.recvRequest(req); + } + } + else if (channelID == 3) + { + if (!m_socket.read((uint8_t*)&kaTimeStamp, sizeof(uint32_t), 1000)) break; + kaTimeStamp = ntohl(kaTimeStamp); + + //LOGCONSOLE("Received chan=%lu kats=%lu", channelID, kaTimeStamp); + + uint8_t buffer[8]; + *(uint32_t*)&buffer[0] = htonl(3); // KA CHANNEL + *(uint32_t*)&buffer[4] = htonl(kaTimeStamp); + if (!m_socket.write(buffer, 8)) + { + esyslog("VNSI-Error: Could not send back KA reply"); + break; + } + } + else if (channelID == 4) + { + if (!m_socket.read((uint8_t*)&logStringLen, sizeof(uint32_t), 1000)) break; + logStringLen = ntohl(logStringLen); + + LOGCONSOLE("Received chan=%lu loglen=%lu", channelID, logStringLen); + + uint8_t buffer[logStringLen + 1]; + if (!m_socket.read((uint8_t*)&buffer, logStringLen, 1000)) break; + buffer[logStringLen] = '\0'; + + LOGCONSOLE("Client said: '%s'", buffer); + if (m_NetLogFile) + { + if (fputs((const char*)buffer, m_NetLogFile) == EOF) + { + fclose(m_NetLogFile); + m_NetLogFile = NULL; + } + fflush(NULL); + } + } + else + { + esyslog("VNSI-Error: Incoming channel number unknown"); + break; + } + } + + /* If thread is ended due to closed connection delete a + possible running stream here */ + StopChannelStreaming(); +} + +void cConnection::EnableNetLog(bool yesNo, const char* ClientName) +{ + if (yesNo) + { + cString Base = cString::sprintf("%s/vnsi-server/%s-%s.log", *VNSIServerConfig.ConfigDirectory, *m_ClientAddress, ClientName); + + m_NetLogFile = fopen(*Base, "a"); + if (m_NetLogFile) + isyslog("VNSI: Client network logging started"); + } + else + { + if (m_NetLogFile) + { + fclose(m_NetLogFile); + m_NetLogFile = NULL; + isyslog("VNSI: Client network logging stopped"); + } + } +} + +bool cConnection::StartChannelStreaming(const cChannel *channel, cResponsePacket *resp) +{ + m_Channel = channel; + m_Streamer = new cLiveStreamer; + m_isStreaming = m_Streamer->StreamChannel(m_Channel, 50, &m_socket, resp); + return m_isStreaming; +} + +void cConnection::StopChannelStreaming() +{ + m_isStreaming = false; + if (m_Streamer) + { + delete m_Streamer; + m_Streamer = NULL; + m_Channel = NULL; + } +} + +void cConnection::TimerChange(const cTimer *Timer, eTimerChange Change) +{ + if (m_StatusInterfaceEnabled) + { + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initStatus(VDR_STATUS_TIMERCHANGE)) + { + delete resp; + return; + } + + resp->add_U32((int)Change); + resp->add_String(Timer ? *Timer->ToText(true) : "-"); + + resp->finalise(); + m_socket.write(resp->getPtr(), resp->getLen()); + delete resp; + } +} + +//void cConnection::ChannelSwitch(const cDevice *Device, int ChannelNumber) +//{ +// +//} + +void cConnection::Recording(const cDevice *Device, const char *Name, const char *FileName, bool On) +{ + if (m_StatusInterfaceEnabled) + { + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initStatus(VDR_STATUS_RECORDING)) + { + delete resp; + return; + } + + resp->add_U32(Device->CardIndex()); + resp->add_U32(On); + if (Name) + resp->add_String(Name); + else + resp->add_String(""); + + if (FileName) + resp->add_String(FileName); + else + resp->add_String(""); + + resp->finalise(); + m_socket.write(resp->getPtr(), resp->getLen()); + delete resp; + } +} + +//void cConnection::Replaying(const cControl *Control, const char *Name, const char *FileName, bool On) +//{ +// +//} +// +//void cConnection::SetVolume(int Volume, bool Absolute) +//{ +// +//} +// +//void cConnection::SetAudioTrack(int Index, const char * const *Tracks) +//{ +// +//} +// +//void cConnection::SetAudioChannel(int AudioChannel) +//{ +// +//} +// +//void cConnection::SetSubtitleTrack(int Index, const char * const *Tracks) +//{ +// +//} +// +//void cConnection::OsdClear(void) +//{ +// +//} +// +//void cConnection::OsdTitle(const char *Title) +//{ +// +//} + +void cConnection::OsdStatusMessage(const char *Message) +{ + if (m_StatusInterfaceEnabled && Message) + { + /* Ignore this messages */ + if (strcasecmp(Message, trVDR("Channel not available!")) == 0) return; + else if (strcasecmp(Message, trVDR("Delete timer?")) == 0) return; + else if (strcasecmp(Message, trVDR("Delete recording?")) == 0) return; + else if (strcasecmp(Message, trVDR("Press any key to cancel shutdown")) == 0) return; + else if (strcasecmp(Message, trVDR("Press any key to cancel restart")) == 0) return; + else if (strcasecmp(Message, trVDR("Editing - shut down anyway?")) == 0) return; + else if (strcasecmp(Message, trVDR("Recording - shut down anyway?")) == 0) return; + else if (strcasecmp(Message, trVDR("shut down anyway?")) == 0) return; + else if (strcasecmp(Message, trVDR("Recording - restart anyway?")) == 0) return; + else if (strcasecmp(Message, trVDR("Editing - restart anyway?")) == 0) return; + else if (strcasecmp(Message, trVDR("Delete channel?")) == 0) return; + else if (strcasecmp(Message, trVDR("Timer still recording - really delete?")) == 0) return; + else if (strcasecmp(Message, trVDR("Delete marks information?")) == 0) return; + else if (strcasecmp(Message, trVDR("Delete resume information?")) == 0) return; + else if (strcasecmp(Message, trVDR("CAM is in use - really reset?")) == 0) return; + else if (strcasecmp(Message, trVDR("Really restart?")) == 0) return; + else if (strcasecmp(Message, trVDR("Stop recording?")) == 0) return; + else if (strcasecmp(Message, trVDR("Cancel editing?")) == 0) return; + else if (strcasecmp(Message, trVDR("Cutter already running - Add to cutting queue?")) == 0) return; + else if (strcasecmp(Message, trVDR("No index-file found. Creating may take minutes. Create one?")) == 0) return; + + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initStatus(VDR_STATUS_MESSAGE)) + { + delete resp; + return; + } + +// else if (type == mtWarning) +// resp->add_U32(1); +// else if (type == mtError) +// resp->add_U32(2); +// else + resp->add_U32(0); + resp->add_String(Message); + resp->finalise(); + m_socket.write(resp->getPtr(), resp->getLen()); + delete resp; + } +} + +//void cConnection::OsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue) +//{ +// +//} +// +//void cConnection::OsdItem(const char *Text, int Index) +//{ +// +//} +// +//void cConnection::OsdCurrentItem(const char *Text) +//{ +// +//} +// +//void cConnection::OsdTextItem(const char *Text, bool Scroll) +//{ +// +//} +// +//void cConnection::OsdChannel(const char *Text) +//{ +// +//} +// +//void cConnection::OsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle) +//{ +// +//} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/connection.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/connection.h new file mode 100644 index 0000000000..fd79117fcb --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/connection.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef CONNECTION_H +#define CONNECTION_H + +#include +#include +#include + +#include "config.h" +#include "cxsocket.h" +#include "cmdcontrol.h" + +class cServer; +class cChannel; +class cDevice; +class cLiveStreamer; +class cResponsePacket; +class cRecPlayer; +class cCmdControl; + +class cConnection : public cThread + , public cStatus +{ +private: + friend class cCmdControl; + + unsigned int m_Id; + cxSocket m_socket; + cServer *m_server; + bool m_loggedIn; + bool m_StatusInterfaceEnabled; + bool m_OSDInterfaceEnabled; + cLiveStreamer *m_Streamer; + const cChannel *m_Channel; + bool m_isStreaming; + FILE *m_NetLogFile; + cString m_ClientAddress; + cRecPlayer *m_RecPlayer; + cCmdControl m_cmdcontrol; + +protected: + virtual void Action(void); + + virtual void TimerChange(const cTimer *Timer, eTimerChange Change); +// virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber); + virtual void Recording(const cDevice *Device, const char *Name, const char *FileName, bool On); +// virtual void Replaying(const cControl *Control, const char *Name, const char *FileName, bool On); +// virtual void SetVolume(int Volume, bool Absolute); +// virtual void SetAudioTrack(int Index, const char * const *Tracks); +// virtual void SetAudioChannel(int AudioChannel); +// virtual void SetSubtitleTrack(int Index, const char * const *Tracks); +// virtual void OsdClear(void); +// virtual void OsdTitle(const char *Title); + virtual void OsdStatusMessage(const char *Message); +// virtual void OsdHelpKeys(const char *Red, const char *Green, const char *Yellow, const char *Blue); +// virtual void OsdItem(const char *Text, int Index); +// virtual void OsdCurrentItem(const char *Text); +// virtual void OsdTextItem(const char *Text, bool Scroll); +// virtual void OsdChannel(const char *Text); +// virtual void OsdProgramme(time_t PresentTime, const char *PresentTitle, const char *PresentSubtitle, time_t FollowingTime, const char *FollowingTitle, const char *FollowingSubtitle); + +public: + cConnection(cServer *server, int fd, unsigned int id, const char *ClientAdr); + virtual ~cConnection(); + + unsigned int GetID() { return m_Id; } + void SetLoggedIn(bool yesNo) { m_loggedIn = yesNo; } + void SetStatusInterface(bool yesNo) { m_StatusInterfaceEnabled = yesNo; } + void SetOSDInterface(bool yesNo) { m_OSDInterfaceEnabled = yesNo; } + void EnableNetLog(bool yesNo, const char* ClientName = ""); + cxSocket *GetSocket() { return &m_socket; } + bool StartChannelStreaming(const cChannel *channel, cResponsePacket *resp); + void StopChannelStreaming(); + bool IsStreaming() { return m_isStreaming; } + cRecPlayer *GetRecPlayer() { return m_RecPlayer; } +}; + +#endif /* CONNECTION_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cxsocket.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cxsocket.c new file mode 100644 index 0000000000..343a2fcfee --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cxsocket.c @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * Socket wrapper classes + * + * Code is taken from xineliboutput plugin. + * + */ + +#define __STDC_FORMAT_MACROS +#include + +#include +#include +#include +#include +#include +#include +#ifndef __APPLE__ +# include +#endif +#include + +#include +#include + +#include "config.h" +#include "cxsocket.h" + +cxSocket::~cxSocket() +{ + CLOSESOCKET(m_fd); + delete m_pollerRead; + delete m_pollerWrite; +} + +bool cxSocket::connect(struct sockaddr *addr, socklen_t len) +{ + return ::connect(m_fd, addr, len) == 0; +} + +bool cxSocket::connect(const char *ip, int port) +{ + struct sockaddr_in sin; + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = inet_addr(ip); + return connect((struct sockaddr *)&sin, sizeof(sin)); +} + +bool cxSocket::set_blocking(bool state) +{ + int flags = fcntl (m_fd, F_GETFL); + + if(flags == -1) + { + esyslog("VNSI-Error: cxSocket::SetBlocking: fcntl(F_GETFL) failed"); + return false; + } + + flags = state ? (flags&(~O_NONBLOCK)) : (flags|O_NONBLOCK); + + if(fcntl (m_fd, F_SETFL, flags) == -1) + { + esyslog("VNSI-Error: cxSocket::SetBlocking: fcntl(F_SETFL) failed"); + return false; + } + + return true; +} + +bool cxSocket::set_buffers(int Tx, int Rx) +{ + int max_buf = Tx; + /*while(max_buf) {*/ + errno = 0; + if(setsockopt(m_fd, SOL_SOCKET, SO_SNDBUF, &max_buf, sizeof(int))) + { + esyslog("VNSI-Error: cxSocket: setsockopt(SO_SNDBUF,%d) failed", max_buf); + /*max_buf >>= 1;*/ + } + /*else {*/ + int tmp = 0; + int len = sizeof(int); + errno = 0; + if(getsockopt(m_fd, SOL_SOCKET, SO_SNDBUF, &tmp, (socklen_t*)&len)) + { + esyslog("VNSI-Error: cxSocket: getsockopt(SO_SNDBUF,%d) failed", max_buf); + /*break;*/ + } + else if(tmp != max_buf) + { + dsyslog("VNSI: cxSocket: setsockopt(SO_SNDBUF): got %d bytes", tmp); + /*max_buf >>= 1;*/ + /*continue;*/ + } + /*}*/ + /*}*/ + + max_buf = Rx; + setsockopt(m_fd, SOL_SOCKET, SO_RCVBUF, &max_buf, sizeof(int)); + + return true; +} + +bool cxSocket::set_multicast(int ttl) +{ + int iReuse = 1, iLoop = 1, iTtl = ttl; + + errno = 0; + + if(setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, &iReuse, sizeof(int)) < 0) + { + esyslog("VNSI-Error: cxSocket: setsockopt(SO_REUSEADDR) failed"); + return false; + } + + if(setsockopt(m_fd, IPPROTO_IP, IP_MULTICAST_TTL, &iTtl, sizeof(int))) + { + esyslog("VNSI-Error: cxSocket: setsockopt(IP_MULTICAST_TTL, %d) failed", iTtl); + return false; + } + + if(setsockopt(m_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &iLoop, sizeof(int))) + { + esyslog("VNSI-Error: cxSocket: setsockopt(IP_MULTICAST_LOOP) failed"); + return false; + } + + return true; +} + +/*ssize_t cxSocket::sendfile(int fd_file, off_t *offset, size_t count) +{ + int r; +#ifndef __APPLE__ + r = ::sendfile(m_fd, fd_file, offset, count); + if(r<0 && (errno == ENOSYS || errno == EINVAL)) + { + // fall back to read/write + esyslog("VNSI-Error: sendfile failed - using simple read/write"); +#endif + cxPoller p(*this, true); + char buf[0x10000]; + int todor = count, todow, done = 0; + if(offset) + { + if((r=::lseek(fd_file, *offset, SEEK_SET)) < 0) + return r; + } + todow = ::read(fd_file, buf, count>sizeof(buf) ? sizeof(buf) : count); + if(todow <= 0) + return todow; + todor -= todow; + while(todow > 0) + { + if(p.Poll(100)) + { + r = write(buf+done, todow); + if(r <= 0) + return r; + todow -= r; + done += r; + } + } + return done; +#ifndef __APPLE__ + } + return r; +#endif +}*/ + +bool cxSocket::set_cork(bool state) +{ +#ifdef __APPLE__ + return false; +#else + int iCork = state ? 1 : 0; + if(setsockopt(m_fd, IPPROTO_TCP, TCP_CORK, &iCork, sizeof(int))) + { + esyslog("VNSI-Error: cxSocket: setsockopt(TCP_CORK) failed"); + return false; + } + return true; +#endif +} + +bool cxSocket::set_nodelay(bool state) +{ + int i = state ? 1 : 0; + if(setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, &i, sizeof(int))) + { + esyslog("VNSI-Error: cxSocket: setsockopt(TCP_NODELAY) failed"); + return false; + } + return true; +} + +ssize_t cxSocket::tx_buffer_size(void) +{ + socklen_t l = sizeof(int); + int wmem = -1; + if(getsockopt(m_fd, SOL_SOCKET, SO_SNDBUF, &wmem, &l)) + { + esyslog("VNSI-Error: getsockopt(SO_SNDBUF) failed"); + return (ssize_t)-1; + } + return (ssize_t)wmem; +} + +ssize_t cxSocket::tx_buffer_free(void) +{ + int wmem = tx_buffer_size(); + int size = -1; + if(ioctl(m_fd, TIOCOUTQ, &size)) + { + esyslog("VNSI-Error: ioctl(TIOCOUTQ) failed"); + return (ssize_t)-1; + } + + return (ssize_t)(wmem - size); +} + +int cxSocket::getsockname(struct sockaddr *name, socklen_t *namelen) +{ + return ::getsockname(m_fd, name, namelen); +} + +int cxSocket::getpeername(struct sockaddr *name, socklen_t *namelen) +{ + return ::getpeername(m_fd, name, namelen); +} + +ssize_t cxSocket::send(const void *buf, size_t size, int flags, const struct sockaddr *to, socklen_t tolen) +{ + return ::sendto(m_fd, buf, size, flags, to, tolen); +} + +ssize_t cxSocket::recv(void *buf, size_t size, int flags, struct sockaddr *from, socklen_t *fromlen) +{ + return ::recvfrom(m_fd, buf, size, flags, from, fromlen); +} + +ssize_t cxSocket::write(const void *buffer, size_t size, int timeout_ms) +{ + cMutexLock CmdLock((cMutex*)&m_MutexWrite); + + if(m_fd == -1) return -1; + + ssize_t written = (ssize_t)size; + const unsigned char *ptr = (const unsigned char *)buffer; + + while (size > 0) + { + errno = 0; + if(!m_pollerWrite->Poll(timeout_ms)) + { + esyslog("VNSI-Error: cxSocket::write: poll() failed"); + return written-size; + } + + errno = 0; + ssize_t p = ::write(m_fd, ptr, size); + + if (p <= 0) + { + if (errno == EINTR || errno == EAGAIN) + { + dsyslog("VNSI: cxSocket::write: EINTR during write(), retrying"); + continue; + } + esyslog("VNSI-Error: cxSocket::write: write() error"); + return p; + } + + ptr += p; + size -= p; + } + + return written; +} + +ssize_t cxSocket::read(void *buffer, size_t size, int timeout_ms) +{ + cMutexLock CmdLock((cMutex*)&m_MutexRead); + + if(m_fd == -1) return -1; + + ssize_t missing = (ssize_t)size; + unsigned char *ptr = (unsigned char *)buffer; + + while (missing > 0) + { + if(!m_pollerRead->Poll(timeout_ms)) + { + esyslog("VNSI-Error: cxSocket::read: poll() failed at %d/%d", (int)(size-missing), (int)size); + return size-missing; + } + + errno = 0; + ssize_t p = ::read(m_fd, ptr, missing); + + if (p <= 0) + { + if (errno == EINTR || errno == EAGAIN) + { + dsyslog("VNSI: cxSocket::read: EINTR/EAGAIN during read(), retrying"); + continue; + } + esyslog("VNSI-Error: cxSocket::read: read() error at %d/%d", (int)(size-missing), (int)size); + return size-missing; + } + + ptr += p; + missing -= p; + } + + return size; +} + +void cxSocket::set_handle(int h) +{ + if(h != m_fd) + { + close(); + m_fd = h; + delete m_pollerRead; + delete m_pollerWrite; + m_pollerRead = new cPoller(m_fd); + m_pollerWrite = new cPoller(m_fd, true); + } +} + +ssize_t cxSocket::printf(const char *fmt, ...) +{ + va_list argp; + char buf[1024]; + int r; + + va_start(argp, fmt); + r = vsnprintf(buf, sizeof(buf), fmt, argp); + if (r<0) + { + esyslog("VNSI-Error: cxSocket::printf: vsnprintf failed"); + } + else if(r >= (int)sizeof(buf)) + { + dsyslog("VNSI: cxSocket::printf: vsnprintf overflow (%20s)", buf); + } + else + return write(buf, r); + + return (ssize_t)-1; +} + +/* readline return value: + * <0 : failed + * >=maxsize : buffer overflow + * >=0 : if errno = EAGAIN -> line is not complete (there was timeout) + * if errno = 0 -> succeed + * (return value 0 indicates empty line "\r\n") + */ +ssize_t cxSocket::readline(char *buf, int bufsize, int timeout, int bufpos) +{ + int n = -1, cnt = bufpos; + + do + { + if(timeout>0 && !m_pollerRead->Poll(timeout)) + { + errno = EAGAIN; + return cnt; + } + + while((n = ::read(m_fd, buf+cnt, 1)) == 1) + { + buf[++cnt] = 0; + + if( cnt > 1 && buf[cnt - 2] == '\r' && buf[cnt - 1] == '\n') + { + cnt -= 2; + buf[cnt] = 0; + errno = 0; + return cnt; + } + + if( cnt >= bufsize) + { + dsyslog("VNSI: cxSocket::readline: too long control message (%d bytes): %20s", cnt, buf); + errno = 0; + return bufsize; + } + } + + /* connection closed ? */ + if (n == 0) + { + dsyslog("VNSI: cxSocket::readline: disconnected"); + if(errno == EAGAIN) + errno = ENOTCONN; + return -1; + } + + } while (timeout>0 && n<0 && errno == EAGAIN); + + if(errno == EAGAIN) + return cnt; + + esyslog("VNSI-Error: cxSocket::readline: read failed"); + return n; +} + +#include +#include + +uint32_t cxSocket::get_local_address(char *ip_address) +{ + uint32_t local_addr = 0; + struct ifconf conf; + struct ifreq buf[3]; + unsigned int n; + + struct sockaddr_in sin; + socklen_t len = sizeof(sin); + + if(!getsockname((struct sockaddr *)&sin, &len)) + { + local_addr = sin.sin_addr.s_addr; + } + else + { + //esyslog("VNSI-Error: getsockname failed"); + + // scan network interfaces + conf.ifc_len = sizeof(buf); + conf.ifc_req = buf; + memset(buf, 0, sizeof(buf)); + + errno = 0; + if(ioctl(m_fd, SIOCGIFCONF, &conf) < 0) + { + esyslog("VNSI-Error: cxSocket: can't obtain socket local address"); + } + else + { + for(n=0; nsin_addr.s_addr); + dsyslog("VNSI: Local address %6s %d.%d.%d.%d", + conf.ifc_req[n].ifr_name, + ((tmp>>24)&0xff), ((tmp>>16)&0xff), + ((tmp>>8)&0xff), ((tmp)&0xff)); +# endif + if(n==0 || local_addr == htonl(INADDR_LOOPBACK)) + local_addr = in->sin_addr.s_addr; + else + break; + } + } + } + + if(!local_addr) + esyslog("VNSI-Error: No local address found"); + + if(ip_address) + cxSocket::ip2txt(local_addr, 0, ip_address); + + return local_addr; +} + +char *cxSocket::ip2txt(uint32_t ip, unsigned int port, char *str) +{ + // inet_ntoa is not thread-safe (?) + if(str) + { + unsigned int iph =(unsigned int)ntohl(ip); + unsigned int porth =(unsigned int)ntohs(port); + if(!porth) + sprintf(str, "%d.%d.%d.%d", ((iph>>24)&0xff), ((iph>>16)&0xff), ((iph>>8)&0xff), ((iph)&0xff)); + else + sprintf(str, "%u.%u.%u.%u:%u", ((iph>>24)&0xff), ((iph>>16)&0xff), ((iph>>8)&0xff), ((iph)&0xff), porth); + } + return str; +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cxsocket.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cxsocket.h new file mode 100644 index 0000000000..c0e25c6294 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/cxsocket.h @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * Socket wrapper classes + * + * Code is taken from xineliboutput plugin. + * + */ + +#ifndef __CXSOCKET_H +#define __CXSOCKET_H + +#include +#include +#include +#include +#include + +#define CLOSESOCKET(fd) do { if(fd>=0) { ::close(fd); fd=-1; } } while(0) + +class cxSocket { + private: + int m_fd; + cMutex m_MutexWrite; + cMutex m_MutexRead; + + cPoller *m_pollerRead; + cPoller *m_pollerWrite; + + cxSocket(const cxSocket& s);//{ m_fd = s.m_fd>=0 ? dup(s.m_fd) : -1; } + cxSocket &operator=(const cxSocket &S);// { close(); m_fd = S.m_fd >= 0 ? dup(S.m_fd) : -1; return *this; }; + + public: + + typedef enum + { + estSTREAM = SOCK_STREAM, + estDGRAM = SOCK_DGRAM + } eSockType; + + cxSocket() : m_fd(-1), m_pollerRead(NULL), m_pollerWrite(NULL) {} + cxSocket(eSockType type) : m_fd(::socket(PF_INET, (int)type, 0)) {} + + ~cxSocket(); + + //operator int () const { return Handle(); } + //operator bool () const { return open(); } + //bool operator==(const cxSocket &s) { return m_fd == s.m_fd; } + + int handle(bool take_ownership=false) + { int r=m_fd; if(take_ownership) m_fd=-1; return r; } + + void set_handle(int h); + + bool create(eSockType type) { close(); return (m_fd=::socket(PF_INET, (int)type, 0)) >= 0; } + bool open(void) const { return m_fd>0; } + void close(void) { CLOSESOCKET(m_fd); } + + ssize_t send(const void *buf, size_t size, int flags=0, + const struct sockaddr *to = NULL, socklen_t tolen = 0); + ssize_t recv(void *buf, size_t size, int flags = 0, + struct sockaddr *from = NULL, socklen_t *fromlen = NULL); + //ssize_t sendfile(int fd_file, off_t *offset, size_t count); + + ssize_t read(void *buffer, size_t size, int timeout_ms = -1); + ssize_t write(const void *buffer, size_t size, int timeout_ms = -1); + + ssize_t printf(const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); + ssize_t write_str(const char *str, int timeout_ms=-1, int len=0) + { return write(str, len ?: strlen(str), timeout_ms); } + ssize_t write_cmd(const char *str, int len=0) + { return write(str, len ?: strlen(str), 10); } + +/* readline return value: + * <0 : failed + * >=maxsize : buffer overflow + * >=0 : if errno = EAGAIN -> line is not complete (there was timeout) + * if errno = 0 -> succeed + * (return value 0 indicates empty line "\r\n") + */ + ssize_t readline(char *buf, int bufsize, int timeout=0, int bufpos=0); + + bool set_buffers(int Tx, int Rx); + bool set_multicast(int ttl); + bool set_blocking(bool state); + bool set_cork(bool state); + bool flush_cork(void) { return set_nodelay(true); }; + bool set_nodelay(bool state); + ssize_t tx_buffer_size(void); + ssize_t tx_buffer_free(void); + int getsockname(struct sockaddr *name, socklen_t *namelen); + int getpeername(struct sockaddr *name, socklen_t *namelen); + + + bool connect(struct sockaddr *addr, socklen_t len); + bool connect(const char *ip, int port); + + uint32_t get_local_address(char *ip_address); + + static char *ip2txt(uint32_t ip, unsigned int port, char *str); +}; + +// +// Set socket buffers +// +static inline void set_socket_buffers(int s, int txbuf, int rxbuf) +{ + int max_buf = txbuf; + /*while(max_buf) {*/ + errno = 0; + if(setsockopt(s, SOL_SOCKET, SO_SNDBUF, &max_buf, sizeof(int))) { + esyslog("VNSI-Error: setsockopt(SO_SNDBUF,%d) failed", max_buf); + /*max_buf >>= 1;*/ + } + /*else {*/ + int tmp = 0; + int len = sizeof(int); + errno = 0; + if(getsockopt(s, SOL_SOCKET, SO_SNDBUF, &tmp, (socklen_t*)&len)) + { + esyslog("VNSI-Error: getsockopt(SO_SNDBUF,%d) failed", max_buf); + /*break;*/ + } + else if(tmp != max_buf) + { + dsyslog("VNSI: setsockopt(SO_SNDBUF): got %d bytes", tmp); + /*max_buf >>= 1;*/ + /*continue;*/ + } + /*}*/ + /*}*/ + + max_buf = rxbuf; + setsockopt(s, SOL_SOCKET, SO_RCVBUF, &max_buf, sizeof(int)); +} + +// +// Connect data socket to client (take address from fd_control) +// +static inline int sock_connect(int fd_control, int port, int type) +{ + struct sockaddr_in sin; + socklen_t len = sizeof(sin); + int s, one = 1; + + if(getpeername(fd_control, (struct sockaddr *)&sin, &len)) { + esyslog("VNSI-Error: sock_connect: getpeername failed"); + return -1; + } + + uint32_t tmp = ntohl(sin.sin_addr.s_addr); + dsyslog("VNSI: Client address: %d.%d.%d.%d", + ((tmp>>24)&0xff), ((tmp>>16)&0xff), + ((tmp>>8)&0xff), ((tmp)&0xff)); + +#if 0 + if ((h = gethostbyname(tmp)) == NULL) { + LOGDBG("sock_connect: unable to resolve host name", tmp); + } +#endif + + if ((s = socket(PF_INET, type, type==SOCK_DGRAM?IPPROTO_UDP:IPPROTO_TCP)) < 0) { + esyslog("VNSI-Error: sock_connect: failed to create socket"); + return -1; + } + + // Set socket buffers: large send buffer, small receive buffer + set_socket_buffers(s, KILOBYTE(256), 2048); + + if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) < 0) + esyslog("VNSI-Error: sock_connect: setsockopt(SO_REUSEADDR) failed"); + + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + + if (connect(s, (struct sockaddr *)&sin, sizeof(sin))==-1 && + errno != EINPROGRESS) { + esyslog("VNSI-Error: connect() failed"); + CLOSESOCKET(s); + } + + if (fcntl (s, F_SETFL, fcntl (s, F_GETFL) | O_NONBLOCK) == -1) { + esyslog("VNSI-Error: can't put socket in non-blocking mode"); + CLOSESOCKET(s); + return -1; + } + + return s; +} + +#endif // __CXSOCKET_H diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer.c new file mode 100644 index 0000000000..1ce392d730 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer.c @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include +#include +#include "config.h" +#include "receiver.h" +#include "demuxer.h" +#include "demuxer_AAC.h" +#include "demuxer_AC3.h" +#include "demuxer_DTS.h" +#include "demuxer_h264.h" +#include "demuxer_MPEGAudio.h" +#include "demuxer_MPEGVideo.h" +#include "demuxer_Subtitle.h" +#include "demuxer_Teletext.h" + +#define PTS_MASK 0x1ffffffffLL +//#define PTS_MASK 0x7ffffLL + +#ifndef INT64_MIN +#define INT64_MIN (-0x7fffffffffffffffLL-1) +#endif + +int64_t PesGetPTS(const uint8_t *buf, int len) +{ + /* assume mpeg2 pes header ... */ + if (PesIsVideoPacket(buf) || PesIsAudioPacket(buf)) { + + if ((buf[6] & 0xC0) != 0x80) + return DVD_NOPTS_VALUE; + if ((buf[6] & 0x30) != 0) + return DVD_NOPTS_VALUE; + + if ((len > 13) && (buf[7] & 0x80)) { /* pts avail */ + int64_t pts; + pts = ((int64_t)(buf[ 9] & 0x0E)) << 29 ; + pts |= ((int64_t) buf[10]) << 22 ; + pts |= ((int64_t)(buf[11] & 0xFE)) << 14 ; + pts |= ((int64_t) buf[12]) << 7 ; + pts |= ((int64_t)(buf[13] & 0xFE)) >> 1 ; + return pts; + } + } + return DVD_NOPTS_VALUE; +} + +int64_t PesGetDTS(const uint8_t *buf, int len) +{ + if (PesIsVideoPacket(buf) || PesIsAudioPacket(buf)) + { + if ((buf[6] & 0xC0) != 0x80) + return DVD_NOPTS_VALUE; + if ((buf[6] & 0x30) != 0) + return DVD_NOPTS_VALUE; + + if (len > 18 && (buf[7] & 0x40)) { /* dts avail */ + int64_t dts; + dts = ((int64_t)( buf[14] & 0x0E)) << 29 ; + dts |= (int64_t)( buf[15] << 22 ); + dts |= (int64_t)((buf[16] & 0xFE) << 14 ); + dts |= (int64_t)( buf[17] << 7 ); + dts |= (int64_t)((buf[18] & 0xFE) >> 1 ); + return dts; + } + } + return DVD_NOPTS_VALUE; +} + +int64_t cParser::m_startDTS; + +// --- cParser ------------------------------------------------- + +cParser::cParser(cLiveStreamer *streamer, int streamID) + : m_Streamer(streamer) + , m_streamID(streamID) +{ + m_curPTS = DVD_NOPTS_VALUE; + m_curDTS = DVD_NOPTS_VALUE; + m_LastDTS = DVD_NOPTS_VALUE; + if (streamer->IsAudioOnly()) + m_startDTS = 0; + else + m_startDTS = DVD_NOPTS_VALUE; + m_epochDTS = 0; + m_badDTS = 0; +} + +int64_t cParser::Rescale(int64_t a) +{ + int64_t b = DVD_TIME_BASE; + int64_t c = 90000; + int64_t r = c/2; + + if (b<=INT_MAX && c<=INT_MAX){ + if (a<=INT_MAX) + return (a * b + r)/c; + else + return a/c*b + (a%c*b + r)/c; + } + else + { + uint64_t a0= a&0xFFFFFFFF; + uint64_t a1= a>>32; + uint64_t b0= b&0xFFFFFFFF; + uint64_t b1= b>>32; + uint64_t t1= a0*b1 + a1*b0; + uint64_t t1a= t1<<32; + + a0 = a0*b0 + t1a; + a1 = a1*b1 + (t1>>32) + (a0=0; i--) + { + a1+= a1 + ((a0>>i)&1); + t1+=t1; + if (c <= a1) + { + a1 -= c; + t1++; + } + } + return t1; + } +} + +/* + * Extract DTS and PTS and update current values in stream + */ +int cParser::ParsePESHeader(uint8_t *buf, size_t len) +{ + /* parse PES header */ + unsigned int hdr_len = PesHeaderLength(buf); + unsigned int pes_pid = buf[3]; + unsigned int pes_len = (buf[4] << 8) | buf[5]; + + /* parse PTS */ + int64_t pts = PesGetPTS(buf, len); + int64_t dts = PesGetDTS(buf, len); + if (dts == DVD_NOPTS_VALUE) + dts = pts; + + m_curDTS = dts & PTS_MASK; + m_curPTS = pts & PTS_MASK; + return hdr_len; +} + +void cParser::SendPacket(sStreamPacket *pkt, bool checkTimestamp) +{ + if (!m_Streamer->IsReady()) + return; + + assert(pkt->dts != DVD_NOPTS_VALUE); + assert(pkt->pts != DVD_NOPTS_VALUE); + + if (m_startDTS == DVD_NOPTS_VALUE) + return; + + int64_t dts = pkt->dts; + int64_t pts = pkt->pts; + + /* Compute delta between PTS and DTS (and watch out for 33 bit wrap) */ + int64_t ptsoff = (pts - dts) & PTS_MASK; + + /* Subtract the transport wide start offset */ + dts -= m_startDTS; + + if (m_LastDTS == DVD_NOPTS_VALUE) + { + if (dts < 0) + { + /* Early packet with negative time stamp, drop those */ + return; + } + } + else if(checkTimestamp) + { + int d = dts + m_epochDTS - m_LastDTS; + + if (d < 0 || d > 90000) { + + if (d < -PTS_MASK || d > -PTS_MASK + 180000) + { + m_badDTS++; + + if (m_badDTS < 5) + { + dsyslog("VNSI-Error: DTS discontinuity. DTS = %llu, last = %llu", dts, m_LastDTS); + } + } + else + { + /* DTS wrapped, increase upper bits */ + m_epochDTS += PTS_MASK + 1; + m_badDTS = 0; + } + } + else + { + m_badDTS = 0; + } + } + m_badDTS++; + + dts += m_epochDTS; + m_LastDTS = dts; + + pts = dts + ptsoff; + + /* Rescale to tvheadned internal 1MHz clock */ + pkt->dts = Rescale(dts); + pkt->pts = Rescale(pts); + pkt->duration = Rescale(pkt->duration); + + m_Streamer->sendStreamPacket(pkt); +} + + +// --- cTSDemuxer ---------------------------------------------------- + +cTSDemuxer::cTSDemuxer(cLiveStreamer *streamer, int id, eStreamType type, int pid) + : m_Streamer(streamer) + , m_streamID(id) + , m_streamType(type) + , m_pID(pid) +{ + m_pesError = false; + m_pesParser = NULL; + m_language[0] = 0; + m_FpsScale = 0; + m_FpsRate = 0; + m_Height = 0; + m_Width = 0; + m_Aspect = 0.0f; + m_Channels = 0; + m_SampleRate = 0; + m_BitRate = 0; + m_BitsPerSample = 0; + m_BlockAlign = 0; + + if (m_streamType == stMPEG2VIDEO) + m_pesParser = new cParserMPEG2Video(this, m_Streamer, m_streamID); + else if (m_streamType == stH264) + m_pesParser = new cParserH264(this, m_Streamer, m_streamID); + else if (m_streamType == stMPEG2AUDIO) + m_pesParser = new cParserMPEG2Audio(this, m_Streamer, m_streamID); + else if (m_streamType == stAAC) + m_pesParser = new cParserAAC(this, m_Streamer, m_streamID); + else if (m_streamType == stAC3) + m_pesParser = new cParserAC3(this, m_Streamer, m_streamID); + else if (m_streamType == stDTS) + m_pesParser = new cParserDTS(this, m_Streamer, m_streamID); + else if (m_streamType == stEAC3) + m_pesParser = new cParserAC3(this, m_Streamer, m_streamID); + else if (m_streamType == stTELETEXT) + m_pesParser = new cParserTeletext(this, m_Streamer, m_streamID); + else if (m_streamType == stDVBSUB) + m_pesParser = new cParserSubtitle(this, m_Streamer, m_streamID); + else + { + esyslog("VNSI-Error: Unrecognised type %i inside stream %i", m_streamType, m_streamID); + return; + } +} + +cTSDemuxer::~cTSDemuxer() +{ + if (m_pesParser) + { + delete m_pesParser; + m_pesParser = NULL; + } +} + +bool cTSDemuxer::ProcessTSPacket(unsigned char *data) +{ + if (!data) + return false; + + bool pusi = TsPayloadStart(data); + int bytes = TS_SIZE - TsPayloadOffset(data); + + if(bytes < 0 || bytes > TS_SIZE) + return false; + + if (TsError(data)) + { + dsyslog("VNSI-Error: transport error"); + return false; + } + + if (!TsHasPayload(data)) + { + LOGCONSOLE("VNSI-Error: no payload, size %d", bytes); + return true; + } + + /* drop broken PES packets */ + if (m_pesError && !pusi) + { + dsyslog("VNSI-Error: dropping broken PES packet"); + return false; + } + + /* strip ts header */ + data += TS_SIZE - bytes; + + /* handle new payload unit */ + if (pusi) + { + if (!PesIsHeader(data)) + { + esyslog("VNSI-Error: payload not PES ?"); + m_pesError = true; + return false; + } + m_pesError = false; + } + + /* Parse the data */ + if (m_pesParser) + m_pesParser->Parse(data, bytes, pusi); + + return true; +} + +void cTSDemuxer::SetLanguage(const char *language) +{ + m_language[0] = language[0]; + m_language[1] = language[1]; + m_language[2] = language[2]; + m_language[3] = 0; +} + +void cTSDemuxer::SetVideoInformation(int FpsScale, int FpsRate, int Height, int Width, float Aspect) +{ + m_FpsScale = FpsScale; + m_FpsRate = FpsRate; + m_Height = Height; + m_Width = Width; + m_Aspect = Aspect; +} + +void cTSDemuxer::SetAudioInformation(int Channels, int SampleRate, int BitRate, int BitsPerSample, int BlockAlign) +{ + m_Channels = Channels; + m_SampleRate = SampleRate; + m_BlockAlign = BlockAlign; + m_BitRate = BitRate; + m_BitsPerSample = BitsPerSample; +} + +void cTSDemuxer::SetSubtitlingDescriptor(unsigned char SubtitlingType, uint16_t CompositionPageId, uint16_t AncillaryPageId) +{ + m_subtitlingType = SubtitlingType; + m_compositionPageId = CompositionPageId; + m_ancillaryPageId = AncillaryPageId; +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer.h new file mode 100644 index 0000000000..1564ca4b81 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer.h @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef VNSIDEMUXER_H +#define VNSIDEMUXER_H + +#include + +#define DVD_TIME_BASE 1000000 +#define DVD_NOPTS_VALUE (-1LL<<52) // should be possible to represent in both double and __int64 + +/* PES PIDs */ +#define PRIVATE_STREAM1 0xBD +#define PADDING_STREAM 0xBE +#define PRIVATE_STREAM2 0xBF +#define AUDIO_STREAM_S 0xC0 /* 1100 0000 */ +#define AUDIO_STREAM_E 0xDF /* 1101 1111 */ +#define VIDEO_STREAM_S 0xE0 /* 1110 0000 */ +#define VIDEO_STREAM_E 0xEF /* 1110 1111 */ + +#define AUDIO_STREAM_MASK 0x1F /* 0001 1111 */ +#define VIDEO_STREAM_MASK 0x0F /* 0000 1111 */ +#define AUDIO_STREAM 0xC0 /* 1100 0000 */ +#define VIDEO_STREAM 0xE0 /* 1110 0000 */ + +#define ECM_STREAM 0xF0 +#define EMM_STREAM 0xF1 +#define DSM_CC_STREAM 0xF2 +#define ISO13522_STREAM 0xF3 +#define PROG_STREAM_DIR 0xFF + +inline bool PesIsHeader(const uchar *p) +{ + return !(p)[0] && !(p)[1] && (p)[2] == 1; +} + +inline int PesHeaderLength(const uchar *p) +{ + return 8 + (p)[8] + 1; +} + +inline bool PesIsVideoPacket(const uchar *p) +{ + return (((p)[3] & ~VIDEO_STREAM_MASK) == VIDEO_STREAM); +} + +inline bool PesIsMPEGAudioPacket(const uchar *p) +{ + return (((p)[3] & ~AUDIO_STREAM_MASK) == AUDIO_STREAM); +} + +inline bool PesIsPS1Packet(const uchar *p) +{ + return ((p)[3] == PRIVATE_STREAM1); +} + +inline bool PesIsPaddingPacket(const uchar *p) +{ + return ((p)[3] == PADDING_STREAM); +} + +inline bool PesIsAudioPacket(const uchar *p) +{ + return (PesIsMPEGAudioPacket(p) || PesIsPS1Packet(p)); +} + +#if APIVERSNUM < 10701 + +#define TS_ERROR 0x80 +#define TS_PAYLOAD_START 0x40 +#define TS_TRANSPORT_PRIORITY 0x20 +#define TS_PID_MASK_HI 0x1F +#define TS_SCRAMBLING_CONTROL 0xC0 +#define TS_ADAPT_FIELD_EXISTS 0x20 +#define TS_PAYLOAD_EXISTS 0x10 +#define TS_CONT_CNT_MASK 0x0F +#define TS_ADAPT_DISCONT 0x80 +#define TS_ADAPT_RANDOM_ACC 0x40 // would be perfect for detecting independent frames, but unfortunately not used by all broadcasters +#define TS_ADAPT_ELEM_PRIO 0x20 +#define TS_ADAPT_PCR 0x10 +#define TS_ADAPT_OPCR 0x08 +#define TS_ADAPT_SPLICING 0x04 +#define TS_ADAPT_TP_PRIVATE 0x02 +#define TS_ADAPT_EXTENSION 0x01 + +inline bool TsHasPayload(const uchar *p) +{ + return p[3] & TS_PAYLOAD_EXISTS; +} + +inline bool TsHasAdaptationField(const uchar *p) +{ + return p[3] & TS_ADAPT_FIELD_EXISTS; +} + +inline bool TsPayloadStart(const uchar *p) +{ + return p[1] & TS_PAYLOAD_START; +} + +inline bool TsError(const uchar *p) +{ + return p[1] & TS_ERROR; +} + +inline int TsPid(const uchar *p) +{ + return (p[1] & TS_PID_MASK_HI) * 256 + p[2]; +} + +inline bool TsIsScrambled(const uchar *p) +{ + return p[3] & TS_SCRAMBLING_CONTROL; +} + +inline int TsPayloadOffset(const uchar *p) +{ + return (p[3] & TS_ADAPT_FIELD_EXISTS) ? p[4] + 5 : 4; +} + +inline int TsGetPayload(const uchar **p) +{ + int o = TsPayloadOffset(*p); + *p += o; + return TS_SIZE - o; +} + +inline int TsContinuityCounter(const uchar *p) +{ + return p[3] & TS_CONT_CNT_MASK; +} + +inline int TsGetAdaptationField(const uchar *p) +{ + return TsHasAdaptationField(p) ? p[5] : 0x00; +} +#endif + +enum eStreamContent +{ + scVIDEO, + scAUDIO, + scSUBTITLE, + scTELETEXT, + scPROGRAMM +}; + +enum eStreamType +{ + stNone, + stAC3, + stMPEG2AUDIO, + stEAC3, + stAAC, + stDTS, + stMPEG2VIDEO, + stH264, + stDVBSUB, + stTEXTSUB, + stTELETEXT, +}; + +#define PKT_I_FRAME 1 +#define PKT_P_FRAME 2 +#define PKT_B_FRAME 3 +#define PKT_NTYPES 4 +struct sStreamPacket +{ + int64_t id; + int64_t dts; + int64_t pts; + int duration; + + uint8_t commercial; + uint8_t componentindex; + uint8_t frametype; + + uint8_t *data; + int size; +}; + +class cLiveStreamer; + +class cParser +{ +public: + cParser(cLiveStreamer *streamer, int streamID); + virtual ~cParser() {}; + + virtual void Parse(unsigned char *data, int size, bool pusi) = 0; + + int ParsePESHeader(uint8_t *buf, size_t len); + void SendPacket(sStreamPacket *pkt, bool checkTimestamp = true); + int64_t Rescale(int64_t a); + + cLiveStreamer *m_Streamer; + + static int64_t m_startDTS; + int64_t m_LastDTS; + int64_t m_curPTS; + int64_t m_curDTS; + int64_t m_epochDTS; + + int m_badDTS; + int m_streamID; +}; + + +class cTSDemuxer +{ +private: + cLiveStreamer *m_Streamer; + const int m_streamID; + const int m_pID; + eStreamContent m_streamContent; + eStreamType m_streamType; + + bool m_pesError; + cParser *m_pesParser; + + char m_language[4]; // ISO 639 3-letter language code (empty string if undefined) + + int m_FpsScale; // scale of 1000 and a rate of 29970 will result in 29.97 fps + int m_FpsRate; + int m_Height; // height of the stream reported by the demuxer + int m_Width; // width of the stream reported by the demuxer + float m_Aspect; // display aspect of stream + + int m_Channels; + int m_SampleRate; + int m_BitRate; + int m_BitsPerSample; + int m_BlockAlign; + + unsigned char m_subtitlingType; + uint16_t m_compositionPageId; + uint16_t m_ancillaryPageId; + +public: + cTSDemuxer(cLiveStreamer *streamer, int id, eStreamType type, int pid); + virtual ~cTSDemuxer(); + + bool ProcessTSPacket(unsigned char *data); + + void SetLanguage(const char *language); + const char *GetLanguage() { return m_language; } + const eStreamContent Content() const { return m_streamContent; } + const eStreamType Type() const { return m_streamType; } + const int GetPID() const { return m_pID; } + const int GetStreamID() const { return m_streamID; } + + /* Video Stream Information */ + void SetVideoInformation(int FpsScale, int FpsRate, int Height, int Width, float Aspect); + uint32_t GetFpsScale() const { return m_FpsScale; } + uint32_t GetFpsRate() const { return m_FpsRate; } + uint32_t GetHeight() const { return m_Height; } + uint32_t GetWidth() const { return m_Width; } + double GetAspect() const { return m_Aspect; } + + /* Audio Stream Information */ + void SetAudioInformation(int Channels, int SampleRate, int BitRate, int BitsPerSample, int BlockAlign); + uint32_t GetChannels() const { return m_Channels; } + uint32_t GetSampleRate() const { return m_SampleRate; } + uint32_t GetBlockAlign() const { return m_BlockAlign; } + uint32_t GetBitRate() const { return m_BitRate; } + uint32_t GetBitsPerSample() const { return m_BitsPerSample; } + + /* Subtitle related stream information */ + void SetSubtitlingDescriptor(unsigned char SubtitlingType, uint16_t CompositionPageId, uint16_t AncillaryPageId); + unsigned char SubtitlingType() const { return m_subtitlingType; } + uint16_t CompositionPageId() const { return m_compositionPageId; } + uint16_t AncillaryPageId() const { return m_ancillaryPageId; } +}; + +#endif /* VNSIDEMUXER_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AAC.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AAC.c new file mode 100644 index 0000000000..e0a96775cb --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AAC.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include "config.h" + +#include "demuxer_AAC.h" + +static int aac_sample_rates[16] = +{ + 96000, 88200, 64000, 48000, 44100, 32000, + 24000, 22050, 16000, 12000, 11025, 8000, 7350 +}; + + +cParserAAC::cParserAAC(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID) + : cParser(streamer, streamID) +{ + m_demuxer = demuxer; + m_streamBuffer = NULL; + m_streamBufferSize = 0; + m_streamBufferPtr = 0; + m_streamParserPtr = 0; + m_firstPUSIseen = false; + + m_Configured = false; + m_FrameLengthType = 0; + m_SampleRate = 0; +} + +cParserAAC::~cParserAAC() +{ + if (m_streamBuffer) + free(m_streamBuffer); +} + +void cParserAAC::Parse(unsigned char *data, int size, bool pusi) +{ + if (pusi) + { + /* Payload unit start */ + m_firstPUSIseen = true; + m_streamBufferPtr = 0; + m_streamParserPtr = 0; + } + + if (!m_firstPUSIseen) + return; + + if (m_streamBuffer == NULL) + { + m_streamBufferSize = 4000; + m_streamBuffer = (uint8_t*)malloc(m_streamBufferSize); + } + + if(m_streamBufferPtr + size >= m_streamBufferSize) + { + m_streamBufferSize += size * 4; + m_streamBuffer = (uint8_t*)realloc(m_streamBuffer, m_streamBufferSize); + } + + memcpy(m_streamBuffer + m_streamBufferPtr, data, size); + m_streamBufferPtr += size; + + if (m_streamParserPtr == 0) + { + if (m_streamBufferPtr < 9) + return; + + int hlen = ParsePESHeader(data, size); + if (hlen < 0) + return; + + m_streamParserPtr += hlen; + } + + int p = m_streamParserPtr; + + int l; + while ((l = m_streamBufferPtr - p) > 3) + { + if(m_streamBuffer[p] == 0x56 && (m_streamBuffer[p + 1] & 0xe0) == 0xe0) + { + int muxlen = (m_streamBuffer[p + 1] & 0x1f) << 8 | m_streamBuffer[p + 2]; + + if(l < muxlen + 3) + break; + + ParseLATMAudioMuxElement(m_streamBuffer + p + 3, muxlen); + p += muxlen + 3; + } + else + { + p++; + } + } + m_streamParserPtr = p; +} + +void cParserAAC::ParseLATMAudioMuxElement(uint8_t *data, int len) +{ + cBitstream bs(data, len * 8); + + if (!bs.readBits1()) + ReadStreamMuxConfig(&bs); + + if (!m_Configured) + return; + + if (m_FrameLengthType != 0) + return; + + int tmp; + int slotLen = 0; + do + { + tmp = bs.readBits(8); + slotLen += tmp; + } while (tmp == 255); + + if (slotLen * 8 > bs.remainingBits()) + return; + + if (m_curDTS == DVD_NOPTS_VALUE) + return; + + sStreamPacket pkt; + pkt.id = m_streamID; + pkt.size = slotLen + 7; + pkt.data = (uint8_t*)malloc(pkt.size); + pkt.dts = m_curDTS; + pkt.pts = m_curDTS; + pkt.duration = m_FrameDuration; + + /* 7 bytes of ADTS header */ + cBitstream out(pkt.data, 56); + + out.putBits(0xfff, 12); // Sync marker + out.putBits(0, 1); // ID 0 = MPEG 4 + out.putBits(0, 2); // Layer + out.putBits(1, 1); // Protection absent + out.putBits(2, 2); // AOT + out.putBits(m_SampleRateIndex, 4); + out.putBits(1, 1); // Private bit + out.putBits(m_ChannelConfig, 3); + out.putBits(1, 1); // Original + out.putBits(1, 1); // Copy + out.putBits(1, 1); // Copyright identification bit + out.putBits(1, 1); // Copyright identification start + out.putBits(slotLen, 13); + out.putBits(0, 11); // Buffer fullness + out.putBits(0, 2); // RDB in frame + + assert(out.remainingBits() == 0); + + /* AAC RDB */ + uint8_t *buf = pkt.data + 7; + for (int i = 0; i < slotLen; i++) + *buf++ = bs.readBits(8); + + m_curDTS += m_FrameDuration; + + SendPacket(&pkt); + return; +} + +void cParserAAC::ReadStreamMuxConfig(cBitstream *bs) +{ + int AudioMuxVersion = bs->readBits(1); + m_AudioMuxVersion_A = 0; + if (AudioMuxVersion) // audioMuxVersion + m_AudioMuxVersion_A = bs->readBits(1); + + if(m_AudioMuxVersion_A) + return; + + if (AudioMuxVersion) + LATMGetValue(bs); // taraFullness + + bs->skipBits(1); // allStreamSameTimeFraming = 1 + bs->skipBits(6); // numSubFrames = 0 + bs->skipBits(4); // numPrograms = 0 + + // for each program (which there is only on in DVB) + bs->skipBits(3); // numLayer = 0 + + // for each layer (which there is only on in DVB) + if (!AudioMuxVersion) + ReadAudioSpecificConfig(bs); + else + return; + + // these are not needed... perhaps + m_FrameLengthType = bs->readBits(3); + switch (m_FrameLengthType) + { + case 0: + bs->readBits(8); + break; + case 1: + bs->readBits(9); + break; + case 3: + case 4: + case 5: + bs->readBits(6); // celp_table_index + break; + case 6: + case 7: + bs->readBits(1); // hvxc_table_index + break; + } + + if (bs->readBits(1)) + { // other data? + if (AudioMuxVersion) + { + LATMGetValue(bs); // other_data_bits + } + else + { + int esc; + do + { + esc = bs->readBits(1); + bs->skipBits(8); + } while (esc); + } + } + + if (bs->readBits(1)) // crc present? + bs->skipBits(8); // config_crc + m_Configured = true; +} + +void cParserAAC::ReadAudioSpecificConfig(cBitstream *bs) +{ + int aot = bs->readBits(5); + if (aot != 2) + return; + + m_SampleRateIndex = bs->readBits(4); + + if (m_SampleRateIndex == 0xf) + return; + + m_SampleRate = aac_sample_rates[m_SampleRateIndex]; + m_FrameDuration = 1024 * 90000 / m_SampleRate; + m_ChannelConfig = bs->readBits(4); + + bs->skipBits(1); //framelen_flag + if (bs->readBits1()) // depends_on_coder + bs->skipBits(14); + + if (bs->readBits(1)) // ext_flag + bs->skipBits(1); // ext3_flag + + m_demuxer->SetAudioInformation(m_ChannelConfig, m_SampleRate, 0, 0, 0); +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AAC.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AAC.h new file mode 100644 index 0000000000..392e6b6f11 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AAC.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef VNSIDEMUXER_AAC_H +#define VNSIDEMUXER_AAC_H + +#include "demuxer.h" +#include "bitstream.h" + +// --- cParserAAC ------------------------------------------------- + +class cParserAAC : public cParser +{ +private: + cTSDemuxer *m_demuxer; + uint8_t *m_streamBuffer; + int m_streamBufferSize; + int m_streamBufferPtr; + int m_streamParserPtr; + bool m_firstPUSIseen; + + bool m_Configured; + int m_AudioMuxVersion_A; + int m_FrameLengthType; + int m_SampleRateIndex; + int m_ChannelConfig; + int m_FrameDuration; + int m_SampleRate; + +public: + cParserAAC(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID); + virtual ~cParserAAC(); + + virtual void Parse(unsigned char *data, int size, bool pusi); + void ParseLATMAudioMuxElement(uint8_t *data, int len); + void ReadStreamMuxConfig(cBitstream *bs); + void ReadAudioSpecificConfig(cBitstream *bs); + uint32_t LATMGetValue(cBitstream *bs) { return bs->readBits(bs->readBits(2) * 8); } +}; + +#endif /* VNSIDEMUXER_AAC_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AC3.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AC3.c new file mode 100644 index 0000000000..c65625b00a --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AC3.c @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include "config.h" + +#include "demuxer_AC3.h" +#include "bitstream.h" + +#define AC3_HEADER_SIZE 7 + +/** Channel mode (audio coding mode) */ +typedef enum +{ + AC3_CHMODE_DUALMONO = 0, + AC3_CHMODE_MONO, + AC3_CHMODE_STEREO, + AC3_CHMODE_3F, + AC3_CHMODE_2F1R, + AC3_CHMODE_3F1R, + AC3_CHMODE_2F2R, + AC3_CHMODE_3F2R +} AC3ChannelMode; + +/* possible frequencies */ +const uint16_t AC3SampleRateTable[3] = { 48000, 44100, 32000 }; + +/* possible bitrates */ +const uint16_t AC3BitrateTable[19] = { + 32, 40, 48, 56, 64, 80, 96, 112, 128, + 160, 192, 224, 256, 320, 384, 448, 512, 576, 640 +}; + +const uint8_t AC3ChannelsTable[8] = { + 2, 1, 2, 3, 3, 4, 4, 5 +}; + +const uint16_t AC3FrameSizeTable[38][3] = { + { 64, 69, 96 }, + { 64, 70, 96 }, + { 80, 87, 120 }, + { 80, 88, 120 }, + { 96, 104, 144 }, + { 96, 105, 144 }, + { 112, 121, 168 }, + { 112, 122, 168 }, + { 128, 139, 192 }, + { 128, 140, 192 }, + { 160, 174, 240 }, + { 160, 175, 240 }, + { 192, 208, 288 }, + { 192, 209, 288 }, + { 224, 243, 336 }, + { 224, 244, 336 }, + { 256, 278, 384 }, + { 256, 279, 384 }, + { 320, 348, 480 }, + { 320, 349, 480 }, + { 384, 417, 576 }, + { 384, 418, 576 }, + { 448, 487, 672 }, + { 448, 488, 672 }, + { 512, 557, 768 }, + { 512, 558, 768 }, + { 640, 696, 960 }, + { 640, 697, 960 }, + { 768, 835, 1152 }, + { 768, 836, 1152 }, + { 896, 975, 1344 }, + { 896, 976, 1344 }, + { 1024, 1114, 1536 }, + { 1024, 1115, 1536 }, + { 1152, 1253, 1728 }, + { 1152, 1254, 1728 }, + { 1280, 1393, 1920 }, + { 1280, 1394, 1920 }, +}; + +const uint8_t EAC3Blocks[4] = { + 1, 2, 3, 6 +}; + +typedef enum { + EAC3_FRAME_TYPE_INDEPENDENT = 0, + EAC3_FRAME_TYPE_DEPENDENT, + EAC3_FRAME_TYPE_AC3_CONVERT, + EAC3_FRAME_TYPE_RESERVED +} EAC3FrameType; + +cParserAC3::cParserAC3(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID) + : cParser(streamer, streamID) +{ + m_CurrentFrameStartIndex = 0; + m_Offset = 0; + m_PTS = 0; + m_DTS = 0; + m_FrameSize = 0; + m_SampleRate = 0; + m_Channels = 0; + m_BitRate = 0; + m_demuxer = demuxer; + m_firstPUSIseen = false; + m_PESStart = false; + m_FetchTimestamp = true; + m_NextDTS = 0; + m_AC3BufferPtr = 0; + m_HeaderFound = false; + + for (int i = 0; i < AV_PARSER_PTS_NB; i++) + { + m_CurrentFrameDTS[i] = DVD_NOPTS_VALUE; + m_CurrentFramePTS[i] = DVD_NOPTS_VALUE; + m_CurrentFrameEnd[i] = 0; + m_CurrentFrameOffset[i] = 0; + } +} + +cParserAC3::~cParserAC3() +{ +} + +void cParserAC3::Parse(unsigned char *data, int size, bool pusi) +{ + if (pusi) + { + /* Payload unit start */ + m_PESStart = true; + m_firstPUSIseen = true; + } + + /* Wait for first pusi */ + if (!m_firstPUSIseen) + return; + + if (m_PESStart) + { + if (!PesIsPS1Packet(data)) + { + esyslog("VNSI-Error: AC3 PES packet contains no valid private stream 1, ignored this packet"); + m_firstPUSIseen = false; + return; + } + + int hlen = ParsePESHeader(data, size); + if (hlen <= 0) + return; + + data += hlen; + size -= hlen; + + m_PESStart = false; + + assert(size >= 0); + if(size == 0) + return; + } + + while (size > 0) + { + uint8_t *outbuf; + int outlen; + + int rlen = FindHeaders(&outbuf, &outlen, data, size, m_curPTS, m_curDTS); + if (rlen < 0) + break; + + m_curPTS = DVD_NOPTS_VALUE; + m_curDTS = DVD_NOPTS_VALUE; + + if (outlen) + { + sStreamPacket pkt; + pkt.id = m_streamID; + pkt.data = outbuf; + pkt.size = outlen; + pkt.duration = 90000 * 1536 / m_SampleRate; + pkt.dts = m_DTS; + if (pkt.dts == DVD_NOPTS_VALUE) + pkt.dts = m_NextDTS; + pkt.pts = pkt.dts; + m_NextDTS = pkt.dts + pkt.duration; + + m_demuxer->SetAudioInformation(m_Channels, m_SampleRate, m_BitRate, 0, 0); + + SendPacket(&pkt); + } + data += rlen; + size -= rlen; + } +} + +int cParserAC3::FindHeaders(uint8_t **poutbuf, int *poutbuf_size, + uint8_t *buf, int buf_size, + int64_t pts, int64_t dts) +{ + *poutbuf = NULL; + *poutbuf_size = 0; + + /* add a new packet descriptor */ + if (pts != DVD_NOPTS_VALUE || dts != DVD_NOPTS_VALUE) + { + int i = (m_CurrentFrameStartIndex + 1) & (AV_PARSER_PTS_NB - 1); + m_CurrentFrameStartIndex = i; + m_CurrentFrameOffset[i] = m_CurrentOffset; + m_CurrentFrameEnd[i] = m_CurrentOffset + buf_size; + m_CurrentFramePTS[i] = pts; + m_CurrentFrameDTS[i] = dts; + } + + if (m_FetchTimestamp) + { + m_FetchTimestamp = false; + FetchTimestamp(0, false); + } + + uint8_t *buf_ptr = buf; + while (buf_size > 0) + { + if (buf_ptr[0] == 0x0b && buf_ptr[1] == 0x77 && !m_HeaderFound) + { + cBitstream bs(buf_ptr + 2, AC3_HEADER_SIZE * 8); + + /* read ahead to bsid to distinguish between AC-3 and E-AC-3 */ + int bsid = bs.showBits(29) & 0x1F; + if (bsid > 16) + return -1; + + if (bsid <= 10) + { + /* Normal AC-3 */ + bs.skipBits(16); + int fscod = bs.readBits(2); + int frmsizecod = bs.readBits(6); + bs.skipBits(5); // skip bsid, already got it + bs.skipBits(3); // skip bitstream mode + int acmod = bs.readBits(3); + + if (fscod == 3 || frmsizecod > 37) + return -1; + + if (acmod == AC3_CHMODE_STEREO) + { + bs.skipBits(2); // skip dsurmod + } + else + { + if ((acmod & 1) && acmod != AC3_CHMODE_MONO) + bs.skipBits(2); + if (acmod & 4) + bs.skipBits(2); + } + int lfeon = bs.readBits(1); + + int srShift = max(bsid, 8) - 8; + m_SampleRate = AC3SampleRateTable[fscod] >> srShift; + m_BitRate = (AC3BitrateTable[frmsizecod>>1] * 1000) >> srShift; + m_Channels = AC3ChannelsTable[acmod] + lfeon; + m_FrameSize = AC3FrameSizeTable[frmsizecod][fscod] * 2; + } + else + { + /* Enhanced AC-3 */ + int frametype = bs.readBits(2); + if (frametype == EAC3_FRAME_TYPE_RESERVED) + return -1; + + int substreamid = bs.readBits(3); + + int framesize = (bs.readBits(11) + 1) << 1; + if (framesize < AC3_HEADER_SIZE) + return -1; + + int numBlocks = 6; + int sr_code = bs.readBits(2); + if (sr_code == 3) + { + int sr_code2 = bs.readBits(2); + if (sr_code2 == 3) + return -1; + m_SampleRate = AC3SampleRateTable[sr_code2] / 2; + } + else + { + numBlocks = EAC3Blocks[bs.readBits(2)]; + m_SampleRate = AC3SampleRateTable[sr_code]; + } + + int channelMode = bs.readBits(3); + int lfeon = bs.readBits(1); + + m_BitRate = (uint32_t)(8.0 * framesize * m_SampleRate / (numBlocks * 256.0)); + m_Channels = AC3ChannelsTable[channelMode] + lfeon; + } + m_HeaderFound = true; + } + + if (m_HeaderFound) + m_AC3Buffer[m_AC3BufferPtr++] = buf_ptr[0]; + + if (m_FrameSize && m_AC3BufferPtr >= m_FrameSize) + { + *poutbuf = m_AC3Buffer; + *poutbuf_size = m_AC3BufferPtr; + + m_AC3BufferPtr = 0; + m_HeaderFound = false; + break; + } + + buf_ptr++; + buf_size--; + } + int index = buf_ptr - buf; + + /* update the file pointer */ + if (*poutbuf_size) + { + /* fill the data for the current frame */ + m_FrameOffset = m_NextFrameOffset; + + /* offset of the next frame */ + m_NextFrameOffset = m_CurrentOffset + index; + m_FetchTimestamp = true; + } + if (index < 0) + index = 0; + m_CurrentOffset += index; + return index; +} + +void cParserAC3::FetchTimestamp(int off, bool remove) +{ + m_DTS = DVD_NOPTS_VALUE; + m_PTS = DVD_NOPTS_VALUE; + m_Offset = 0; + for (int i = 0; i < AV_PARSER_PTS_NB; i++) + { + if ( m_NextFrameOffset + off >= m_CurrentFrameOffset[i] + &&(m_FrameOffset < m_CurrentFrameOffset[i] || !m_FrameOffset) + && m_CurrentFrameEnd[i]) + { + m_DTS = m_CurrentFrameDTS[i]; + m_PTS = m_CurrentFramePTS[i]; + m_Offset = m_NextFrameOffset - m_CurrentFrameOffset[i]; + if (remove) + m_CurrentFrameOffset[i] = -1; + } + } +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AC3.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AC3.h new file mode 100644 index 0000000000..605b2054f7 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_AC3.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef VNSIDEMUXER_AC3_H +#define VNSIDEMUXER_AC3_H + +#include "demuxer.h" + +// --- cParserAC3 ------------------------------------------------- + +class cParserAC3 : public cParser +{ +private: + cTSDemuxer *m_demuxer; + bool m_firstPUSIseen; + bool m_PESStart; + int m_SampleRate; + int m_Channels; + int m_BitRate; + + bool m_FetchTimestamp; + int64_t m_FrameOffset; /* offset of the current frame */ + int64_t m_CurrentOffset; /* current offset (incremented by each av_parser_parse()) */ + int64_t m_NextFrameOffset; /* offset of the next frame */ + +#define AV_PARSER_PTS_NB 4 + int m_CurrentFrameStartIndex; + int64_t m_CurrentFrameOffset[AV_PARSER_PTS_NB]; + int64_t m_CurrentFramePTS[AV_PARSER_PTS_NB]; + int64_t m_CurrentFrameDTS[AV_PARSER_PTS_NB]; + int64_t m_CurrentFrameEnd[AV_PARSER_PTS_NB]; + int64_t m_Offset; /* byte offset from starting packet start */ + int64_t m_PTS; /* pts of the current frame */ + int64_t m_DTS; /* dts of the current frame */ + int64_t m_NextDTS; + + int m_FrameSize; + bool m_HeaderFound; + +#define AC3_MAX_CODED_FRAME_SIZE 1920*2 + uint8_t m_AC3Buffer[AC3_MAX_CODED_FRAME_SIZE]; /* input buffer */ + int m_AC3BufferSize; + int m_AC3BufferPtr; + + int FindHeaders(uint8_t **poutbuf, int *poutbuf_size, + uint8_t *buf, int buf_size, + int64_t pts, int64_t dts); + void FetchTimestamp(int off, bool remove); + +public: + cParserAC3(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID); + virtual ~cParserAC3(); + + virtual void Parse(unsigned char *data, int size, bool pusi); +}; + + +#endif /* VNSIDEMUXER_AC3_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_DTS.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_DTS.c new file mode 100644 index 0000000000..eb4f18fb15 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_DTS.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include "config.h" + +#include "demuxer_DTS.h" +#include "bitstream.h" + +cParserDTS::cParserDTS(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID) + : cParser(streamer, streamID) +{ + m_demuxer = demuxer; +} + +cParserDTS::~cParserDTS() +{ +} + +void cParserDTS::Parse(unsigned char *data, int size, bool pusi) +{ + +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_DTS.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_DTS.h new file mode 100644 index 0000000000..68b23affdd --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_DTS.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef VNSIDEMUXER_DTS_H +#define VNSIDEMUXER_DTS_H + +#include "demuxer.h" + +// --- cParserDTS ------------------------------------------------- + +class cParserDTS : public cParser +{ +private: + cTSDemuxer *m_demuxer; + +public: + cParserDTS(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID); + virtual ~cParserDTS(); + + virtual void Parse(unsigned char *data, int size, bool pusi); +}; + + +#endif /* VNSIDEMUXER_DTS_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGAudio.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGAudio.c new file mode 100644 index 0000000000..b34f916fc7 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGAudio.c @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include "config.h" + +#include "demuxer_MPEGAudio.h" + +cParserMPEG2Audio::cParserMPEG2Audio(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID) + : cParser(streamer, streamID) +{ + m_FrameOffset = 0; + m_CurrentOffset = 0; + m_NextFrameOffset = 0; + m_CurrentFrameStartIndex = 0; + m_Offset = 0; + m_PTS = 0; + m_DTS = 0; + m_FrameSize = 0; + m_FrameFreeFormatFrameSize = 0; + m_FrameFreeFormatNextHeader = 0; + m_FrameHeader = 0; + m_HeaderCount = 0; + m_SampleRate = 0; + m_Channels = 0; + m_BitRate = 0; + m_demuxer = demuxer; + m_firstPUSIseen = false; + m_PESStart = false; + m_FetchTimestamp = true; + m_FrameInBufferPtr = m_FrameInBuffer; + m_NextDTS = 0; + + for (int i = 0; i < AV_PARSER_PTS_NB; i++) + { + m_CurrentFrameDTS[i] = DVD_NOPTS_VALUE; + m_CurrentFramePTS[i] = DVD_NOPTS_VALUE; + m_CurrentFrameEnd[i] = 0; + m_CurrentFrameOffset[i] = 0; + } + + for (int i = 0; i < MPA_MAX_CODED_FRAME_SIZE; i++) + { + m_FrameInBuffer[i] = 0; + } +} + +cParserMPEG2Audio::~cParserMPEG2Audio() +{ +} + +void cParserMPEG2Audio::Parse(unsigned char *data, int size, bool pusi) +{ + if (pusi) + { + /* Payload unit start */ + m_PESStart = true; + m_firstPUSIseen = true; + } + + /* Wait for first pusi */ + if (!m_firstPUSIseen) + return; + + if (m_PESStart) + { + int hlen = ParsePESHeader(data, size); + if (hlen <= 0) + return; + + data += hlen; + size -= hlen; + + m_PESStart = false; + + assert(size >= 0); + if(size == 0) + return; + } + + while (size > 0) + { + uint8_t *outbuf; + int outlen; + + int rlen = FindHeaders(&outbuf, &outlen, data, size, m_curPTS, m_curDTS); + if (rlen < 0) + break; + + m_curPTS = DVD_NOPTS_VALUE; + m_curDTS = DVD_NOPTS_VALUE; + + if (outlen) + { + sStreamPacket pkt; + pkt.id = m_streamID; + pkt.data = outbuf; + pkt.size = outlen; + pkt.duration = 90000 * 1152 / m_SampleRate; + pkt.dts = m_DTS; + if (pkt.dts == DVD_NOPTS_VALUE) + pkt.dts = m_NextDTS; + pkt.pts = pkt.dts; + m_NextDTS = pkt.dts + pkt.duration; + + SendPacket(&pkt); + } + data += rlen; + size -= rlen; + } +} + +int cParserMPEG2Audio::FindHeaders(uint8_t **poutbuf, int *poutbuf_size, + uint8_t *buf, int buf_size, + int64_t pts, int64_t dts) +{ + *poutbuf = NULL; + *poutbuf_size = 0; + + /* add a new packet descriptor */ + if (pts != DVD_NOPTS_VALUE || dts != DVD_NOPTS_VALUE) + { + int i = (m_CurrentFrameStartIndex + 1) & (AV_PARSER_PTS_NB - 1); + m_CurrentFrameStartIndex = i; + m_CurrentFrameOffset[i] = m_CurrentOffset; + m_CurrentFrameEnd[i] = m_CurrentOffset + buf_size; + m_CurrentFramePTS[i] = pts; + m_CurrentFrameDTS[i] = dts; + } + + if (m_FetchTimestamp) + { + m_FetchTimestamp = false; + FetchTimestamp(0, false); + } + + const uint8_t *buf_ptr = buf; + while (buf_size > 0) + { + int len = m_FrameInBufferPtr - m_FrameInBuffer; + if (m_FrameSize == 0) + { + /* special case for next header for first frame in free + format case (XXX: find a simpler method) */ + if (m_FrameFreeFormatNextHeader != 0) + { + m_FrameInBuffer[3] = m_FrameFreeFormatNextHeader; + m_FrameInBuffer[2] = m_FrameFreeFormatNextHeader>>8; + m_FrameInBuffer[1] = m_FrameFreeFormatNextHeader>>16; + m_FrameInBuffer[0] = m_FrameFreeFormatNextHeader>>24; + m_FrameInBufferPtr = m_FrameInBuffer + 4; + m_FrameFreeFormatNextHeader = 0; + goto got_header; + } + /* no header seen : find one. We need at least MPA_HEADER_SIZE + bytes to parse it */ + len = min(MPA_HEADER_SIZE - len, buf_size); + if (len > 0) + { + memcpy(m_FrameInBufferPtr, buf_ptr, len); + buf_ptr += len; + buf_size -= len; + m_FrameInBufferPtr += len; + } + if ((m_FrameInBufferPtr - m_FrameInBuffer) >= MPA_HEADER_SIZE) + { + got_header: + MPADecodeHeader s; + uint32_t header = ((m_FrameInBuffer[0] << 24) | (m_FrameInBuffer[1] << 16) | (m_FrameInBuffer[2] << 8) | m_FrameInBuffer[3]); + if (CheckHeader(header) && DecodeHeader(&s, header)) + { + if ((header&SAME_HEADER_MASK) != (m_FrameHeader&SAME_HEADER_MASK) && m_FrameHeader) + m_HeaderCount = -3; + m_FrameHeader = header; + m_FrameSize = s.frame_size; + m_SampleRate = s.sample_rate; + m_Channels = s.nb_channels; + m_BitRate = s.bit_rate; + m_HeaderCount++; + } + else + { + m_HeaderCount = -2; + /* no sync found : move by one byte (inefficient, but simple!) */ + memmove(m_FrameInBuffer, m_FrameInBuffer + 1, m_FrameInBufferPtr - m_FrameInBuffer - 1); + m_FrameInBufferPtr--; + //LOGDBG("skip %x", header); + /* reset free format frame size to give a chance + to get a new bitrate */ + m_FrameFreeFormatFrameSize = 0; + } + } + } + else if (len < m_FrameSize) + { + if (m_FrameSize > MPA_MAX_CODED_FRAME_SIZE) + m_FrameSize = MPA_MAX_CODED_FRAME_SIZE; + len = min(m_FrameSize - len, buf_size); + memcpy(m_FrameInBufferPtr, buf_ptr, len); + buf_ptr += len; + m_FrameInBufferPtr += len; + buf_size -= len; + } + + if(m_FrameSize > 0 && buf_ptr - buf == m_FrameInBufferPtr - m_FrameInBuffer && buf_size + buf_ptr - buf >= m_FrameSize) + { + if(m_HeaderCount > 0) + { + *poutbuf = buf; + *poutbuf_size = m_FrameSize; + } + buf_ptr = buf + m_FrameSize; + m_FrameInBufferPtr = m_FrameInBuffer; + m_FrameSize = 0; + break; + } + + // next_data: + if (m_FrameSize > 0 && (m_FrameInBufferPtr - m_FrameInBuffer) >= m_FrameSize) + { + if (m_HeaderCount > 0) + { + *poutbuf = m_FrameInBuffer; + *poutbuf_size = m_FrameInBufferPtr - m_FrameInBuffer; + } + m_FrameInBufferPtr = m_FrameInBuffer; + m_FrameSize = 0; + break; + } + } + int index = buf_ptr - buf; + + /* update the file pointer */ + if (*poutbuf_size) + { + /* fill the data for the current frame */ + m_FrameOffset = m_NextFrameOffset; + + /* offset of the next frame */ + m_NextFrameOffset = m_CurrentOffset + index; + m_FetchTimestamp = true; + } + if (index < 0) + index = 0; + m_CurrentOffset += index; + return index; +} + +void cParserMPEG2Audio::FetchTimestamp(int off, bool remove) +{ + m_DTS = DVD_NOPTS_VALUE; + m_PTS = DVD_NOPTS_VALUE; + m_Offset = 0; + for (int i = 0; i < AV_PARSER_PTS_NB; i++) + { + if ( m_NextFrameOffset + off >= m_CurrentFrameOffset[i] + &&(m_FrameOffset < m_CurrentFrameOffset[i] || !m_FrameOffset) + && m_CurrentFrameEnd[i]) + { + m_DTS = m_CurrentFrameDTS[i]; + m_PTS = m_CurrentFramePTS[i]; + m_Offset = m_NextFrameOffset - m_CurrentFrameOffset[i]; + if (remove) + m_CurrentFrameOffset[i] = -1; + } + } +} + +bool cParserMPEG2Audio::DecodeHeader(MPADecodeHeader *s, uint32_t header) +{ + const uint16_t FrequencyTable[3] = { 44100, 48000, 32000 }; + const uint16_t BitrateTable[2][3][15] = + { + { + {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 }, + {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 }, + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 } + }, + { + {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160} + } + }; + + int sample_rate, frame_size, mpeg25, padding; + int sample_rate_index, bitrate_index; + if (header & (1<<20)) + { + s->lsf = (header & (1<<19)) ? 0 : 1; + mpeg25 = 0; + } + else + { + s->lsf = 1; + mpeg25 = 1; + } + + s->layer = 4 - ((header >> 17) & 3); + /* extract frequency */ + sample_rate_index = (header >> 10) & 3; + sample_rate = FrequencyTable[sample_rate_index] >> (s->lsf + mpeg25); + sample_rate_index += 3 * (s->lsf + mpeg25); + s->sample_rate_index = sample_rate_index; + s->error_protection = ((header >> 16) & 1) ^ 1; + s->sample_rate = sample_rate; + + bitrate_index = (header >> 12) & 0xf; + padding = (header >> 9) & 1; + //extension = (header >> 8) & 1; + s->mode = (header >> 6) & 3; + s->mode_ext = (header >> 4) & 3; + //copyright = (header >> 3) & 1; + //original = (header >> 2) & 1; + //emphasis = header & 3; + + if (s->mode == MPA_MONO) + s->nb_channels = 1; + else + s->nb_channels = 2; + + if (bitrate_index != 0) + { + frame_size = BitrateTable[s->lsf][s->layer - 1][bitrate_index]; + s->bit_rate = frame_size * 1000; + switch(s->layer) + { + case 1: + frame_size = (frame_size * 12000) / sample_rate; + frame_size = (frame_size + padding) * 4; + break; + case 2: + frame_size = (frame_size * 144000) / sample_rate; + frame_size += padding; + break; + default: + case 3: + frame_size = (frame_size * 144000) / (sample_rate << s->lsf); + frame_size += padding; + break; + } + s->frame_size = frame_size; + } + else + { + /* if no frame size computed, signal it */ + return false; + } + + m_demuxer->SetAudioInformation(s->nb_channels, s->sample_rate, s->bit_rate, 0, 0); + +#if defined(DEBUG) +#define MODE_EXT_MS_STEREO 2 +#define MODE_EXT_I_STEREO 1 + printf("layer%d, %d Hz, %d kbits/s, ", s->layer, s->sample_rate, s->bit_rate); + if (s->nb_channels == 2) + { + if (s->layer == 3) + { + if (s->mode_ext & MODE_EXT_MS_STEREO) + printf("ms-"); + if (s->mode_ext & MODE_EXT_I_STEREO) + printf("i-"); + } + printf("stereo"); + } + else + { + printf("mono"); + } + printf("\n"); +#endif + return true; +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGAudio.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGAudio.h new file mode 100644 index 0000000000..8f5cb51c3a --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGAudio.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef VNSIDEMUXER_MPEGAUDIO_H +#define VNSIDEMUXER_MPEGAUDIO_H + +#include "demuxer.h" + +// --- cParserMPEG2Audio ------------------------------------------------- + +class cParserMPEG2Audio : public cParser +{ +private: + typedef struct MPADecodeHeader + { + int frame_size; + int error_protection; + int layer; + int sample_rate; + int sample_rate_index; /* between 0 and 8 */ + int bit_rate; + int nb_channels; + int mode; + int mode_ext; + int lsf; + } MPADecodeHeader; + + cTSDemuxer *m_demuxer; + bool m_firstPUSIseen; + bool m_PESStart; + + bool m_FetchTimestamp; + int64_t m_FrameOffset; /* offset of the current frame */ + int64_t m_CurrentOffset; /* current offset (incremented by each av_parser_parse()) */ + int64_t m_NextFrameOffset; /* offset of the next frame */ + +#define AV_PARSER_PTS_NB 4 + int m_CurrentFrameStartIndex; + int64_t m_CurrentFrameOffset[AV_PARSER_PTS_NB]; + int64_t m_CurrentFramePTS[AV_PARSER_PTS_NB]; + int64_t m_CurrentFrameDTS[AV_PARSER_PTS_NB]; + int64_t m_CurrentFrameEnd[AV_PARSER_PTS_NB]; + int64_t m_Offset; /* byte offset from starting packet start */ + int64_t m_PTS; /* pts of the current frame */ + int64_t m_DTS; /* dts of the current frame */ + int64_t m_NextDTS; + +#define SAME_HEADER_MASK (0xffe00000 | (3 << 17) | (3 << 10) | (3 << 19)) +#define MPA_HEADER_SIZE 4 +#define MPA_MAX_CODED_FRAME_SIZE 1792 +#define MPA_STEREO 0 +#define MPA_JSTEREO 1 +#define MPA_DUAL 2 +#define MPA_MONO 3 + int m_FrameSize; + int m_FrameFreeFormatFrameSize; + int m_FrameFreeFormatNextHeader; + uint32_t m_FrameHeader; + int m_HeaderCount; + int m_SampleRate; + int m_Channels; + int m_BitRate; + uint8_t m_FrameInBuffer[MPA_MAX_CODED_FRAME_SIZE]; /* input buffer */ + uint8_t *m_FrameInBufferPtr; + + /* fast header check for resync */ + void FetchTimestamp(int off, bool remove); + int FindHeaders(uint8_t **poutbuf, int *poutbuf_size, + uint8_t *buf, int buf_size, + int64_t pts, int64_t dts); + static inline bool CheckHeader(uint32_t header) + { + /* header */ + if ((header & 0xffe00000) != 0xffe00000) + return false; + /* layer check */ + if ((header & (3<<17)) == 0) + return false; + /* bit rate */ + if ((header & (0xf<<12)) == 0xf<<12) + return false; + /* frequency */ + if ((header & (3<<10)) == 3<<10) + return false; + return true; + } + bool DecodeHeader(MPADecodeHeader *s, uint32_t header); + +public: + cParserMPEG2Audio(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID); + virtual ~cParserMPEG2Audio(); + + virtual void Parse(unsigned char *data, int size, bool pusi); +}; + + +#endif /* VNSIDEMUXER_MPEGAUDIO_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGVideo.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGVideo.c new file mode 100644 index 0000000000..6483cd9c7c --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGVideo.c @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include "config.h" +#include "bitstream.h" +#include "receiver.h" + +#include "demuxer_MPEGVideo.h" + +using namespace std; + +#define MPEG_PICTURE_START 0x00000100 +#define MPEG_SEQUENCE_START 0x000001b3 +#define MPEG_SEQUENCE_EXTENSION 0x000001b5 +#define MPEG_SLICE_S 0x00000101 +#define MPEG_SLICE_E 0x000001af + +/** + * MPEG2VIDEO frame duration table (in 90kHz clock domain) + */ +const unsigned int mpeg2video_framedurations[16] = { + 0, + 3753, + 3750, + 3600, + 3003, + 3000, + 1800, + 1501, + 1500, +}; + +cParserMPEG2Video::cParserMPEG2Video(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID) + : cParser(streamer, streamID) +{ + m_pictureBuffer = NULL; + m_pictureBufferSize = 0; + m_pictureBufferPtr = 0; + m_StartCond = 0; + m_StartCode = 0; + m_StartCodeOffset = 0; + m_FrameDuration = 0; + m_startDTS = DVD_NOPTS_VALUE; + m_vbvDelay = -1; + m_vbvSize = 0; + m_Height = 0; + m_Width = 0; + m_StreamPacket = NULL; + m_demuxer = demuxer; + + /* Set the streamer ready here to increase switch times, but if XBMC use also + VDPAU for SD content it must be changed */ + streamer->SetReady(); +} + +cParserMPEG2Video::~cParserMPEG2Video() +{ + while (!m_PTSQueue.empty()) + { + sStreamPacket* pkt = m_PTSQueue.front(); + m_PTSQueue.pop_front(); + free(pkt->data); + delete pkt; + } + while (!m_DurationQueue.empty()) + { + sStreamPacket* pkt = m_DurationQueue.front(); + m_DurationQueue.pop_front(); + free(pkt->data); + delete pkt; + } + if (m_pictureBuffer) + { + free(m_pictureBuffer); + m_pictureBuffer = NULL; + } +} + +void cParserMPEG2Video::Parse(unsigned char *data, int size, bool pusi) +{ + uint32_t startcode = m_StartCond; + + /* Parse PES header here for MPEG PS streams like from pvrinput */ + if (pusi && m_Streamer->IsMPEGPS()) + { + int hlen; + + hlen = ParsePESHeader(data, size); + #if 0 + int i; + for(i = 0; i < 16; i++) + printf("%02x.", data[i]); + printf(" %d\n", hlen); + #endif + data += hlen; + size -= hlen; + + if(size < 1) + return; + } + + if (m_pictureBuffer == NULL) + { + m_pictureBufferSize = 4000; + m_pictureBuffer = (uint8_t*)malloc(m_pictureBufferSize); + } + + if (m_pictureBufferPtr + size + 4 >= m_pictureBufferSize) + { + m_pictureBufferSize += size * 4; + m_pictureBuffer = (uint8_t*)realloc(m_pictureBuffer, m_pictureBufferSize); + } + + for (int i = 0; i < size; i++) + { + if (!m_pictureBuffer) + break; + + m_pictureBuffer[m_pictureBufferPtr++] = data[i]; + startcode = startcode << 8 | data[i]; + + if ((startcode & 0xffffff00) != 0x00000100) + continue; + + bool reset = true; + if (m_pictureBufferPtr - 4 > 0 && m_StartCode != 0) + { + reset = Parse_MPEG2Video(m_pictureBufferPtr - 4, startcode, m_StartCodeOffset); + } + + if (reset) + { + /* Reset packet parser upon length error or if parser tells us so */ + m_pictureBufferPtr = 0; + m_pictureBuffer[m_pictureBufferPtr++] = startcode >> 24; + m_pictureBuffer[m_pictureBufferPtr++] = startcode >> 16; + m_pictureBuffer[m_pictureBufferPtr++] = startcode >> 8; + m_pictureBuffer[m_pictureBufferPtr++] = startcode >> 0; + } + m_StartCode = startcode; + m_StartCodeOffset = m_pictureBufferPtr - 4; + } + m_StartCond = startcode; +} + +bool cParserMPEG2Video::Parse_MPEG2Video(size_t len, uint32_t next_startcode, int sc_offset) +{ + int frametype; + uint8_t *buf = m_pictureBuffer + sc_offset; + cBitstream bs(buf + 4, (len - 4) * 8); + + switch (m_StartCode) + { + case 0x000001e0 ... 0x000001ef: + /* System start codes for video */ + if (len >= 9) + ParsePESHeader(buf, len); + return true; + + case 0x00000100: + /* Picture start code */ + if (m_FrameDuration == 0 || m_curDTS == DVD_NOPTS_VALUE) + return true; + + if (Parse_MPEG2Video_PicStart(&frametype, &bs)) + return true; + + if (m_StreamPacket != NULL) + { + esyslog("VNSI-Error: MPEG2 Video got a new picture start code with already openend steam packed"); + } + + m_StreamPacket = new sStreamPacket; + m_StreamPacket->id = m_streamID; + m_StreamPacket->pts = m_curPTS; + m_StreamPacket->dts = m_curDTS; + m_StreamPacket->frametype = frametype; + m_StreamPacket->duration = 0; + break; + + case 0x000001b3: + /* Sequence start code */ + if (Parse_MPEG2Video_SeqStart(&bs)) + return true; + + break; + + case 0x000001b5: + if(len < 5) + return true; + switch(buf[4] >> 4) { + case 0x1: + /* sequence extension */ + // printf("Sequence extension, len = %d\n", len); + if(len < 10) + return true; + // printf("Profile = %d\n", buf[4] & 0x7); + // printf(" Level = %d\n", buf[5] >> 4); + break; + } + break; + + case 0x00000101 ... 0x000001af: + /* Slices */ + + if (next_startcode == 0x100 || next_startcode > 0x1af) + { + /* Last picture slice (because next not a slice) */ + if(m_StreamPacket == NULL) + { + /* no packet, may've been discarded by sanity checks here */ + return true; + } + + m_StreamPacket->data = m_pictureBuffer; + m_StreamPacket->size = m_pictureBufferPtr - 4; + m_StreamPacket->duration = m_FrameDuration; + + Parse_ComputePTS(m_StreamPacket); + m_StreamPacket = NULL; + + m_pictureBuffer = (uint8_t*)malloc(m_pictureBufferSize); + + /* If we know the frame duration, increase DTS accordingly */ + m_curDTS += m_FrameDuration; + + /* PTS cannot be extrapolated (it's not linear) */ + m_curPTS = DVD_NOPTS_VALUE; + return true; + } + break; + + default: + break; + } + + return false; +} + +bool cParserMPEG2Video::Parse_MPEG2Video_SeqStart(cBitstream *bs) +{ + if (bs->length() < 61) + return true; + + m_Width = bs->readBits(12); + m_Height = bs->readBits(12); + bs->skipBits(4); + m_FrameDuration = mpeg2video_framedurations[bs->readBits(4)]; + bs->skipBits(18); + bs->skipBits(1); + + m_vbvSize = bs->readBits(10) * 16 * 1024 / 8; + m_demuxer->SetVideoInformation(0,0, m_Height, m_Width, 0); + + return false; +} + +bool cParserMPEG2Video::Parse_MPEG2Video_PicStart(int *frametype, cBitstream *bs) +{ + if (bs->length() < 29) + return true; + + bs->skipBits(10); /* temporal reference */ + + int pct = bs->readBits(3); + if (pct < PKT_I_FRAME || pct > PKT_B_FRAME) + return true; /* Illegal picture_coding_type */ + + *frametype = pct; + + /* If this is the first I-frame seen, set dts_start as a reference offset */ + if (pct == PKT_I_FRAME && m_startDTS == DVD_NOPTS_VALUE) + m_startDTS = m_curDTS; + + int vbvDelay = bs->readBits(16); /* vbv_delay */ + if (vbvDelay == 0xffff) + m_vbvDelay = -1; + else + m_vbvDelay = Rescale(vbvDelay); + + return false; +} + +void cParserMPEG2Video::Parse_ComputePTS(sStreamPacket *pkt) +{ + bool validpts = pkt->pts != DVD_NOPTS_VALUE && m_PTSQueue.size() == 0; + + /* PTS known and no other packets in queue, deliver at once */ + if (validpts && pkt->duration) + { + SendPacket(pkt); + free(pkt->data); + delete pkt; + return; + } + + if (validpts) + return Parse_ComputeDuration(pkt); + + m_PTSQueue.push_back(pkt); + + while (!m_PTSQueue.empty()) + { + pkt = m_PTSQueue.front(); + + switch (pkt->frametype) + { + case PKT_B_FRAME: + /* B-frames have same PTS as DTS, pass them on */ + pkt->pts = pkt->dts; + break; + + case PKT_I_FRAME: + case PKT_P_FRAME: + /* Presentation occures at DTS of next I or P frame, try to find it */ + deque::iterator it; + it = m_PTSQueue.begin()+1; + while (1) + { + if (it >= m_PTSQueue.end()) + return; /* not arrived yet, wait */ + + sStreamPacket* pkt2 = *it++; + if (pkt2->frametype <= PKT_P_FRAME) + { + pkt->pts = pkt2->dts; + break; + } + } + break; + } + + m_PTSQueue.pop_front(); + + if (pkt->duration == 0) + { + Parse_ComputeDuration(pkt); + } + else + { + SendPacket(pkt); + free(pkt->data); + delete pkt; + } + } +} + +void cParserMPEG2Video::Parse_ComputeDuration(sStreamPacket *pkt) +{ + m_DurationQueue.push_back(pkt); + + pkt = m_DurationQueue.front(); + if (m_DurationQueue.size() <= 1) + return; + + sStreamPacket *next = m_DurationQueue[1]; + + int64_t duration = next->dts - pkt->dts; + m_DurationQueue.pop_front(); + + if (duration >= 10) + { + pkt->duration = duration; + SendPacket(pkt); + } + free(pkt->data); +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGVideo.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGVideo.h new file mode 100644 index 0000000000..3be470e589 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_MPEGVideo.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef VNSIDEMUXER_MPEGVIDEO_H +#define VNSIDEMUXER_MPEGVIDEO_H + +#include +#include "demuxer.h" + +class cBitstream; + +// --- cParserMPEG2Video ------------------------------------------------- + +class cParserMPEG2Video : public cParser +{ +private: + std::deque m_DurationQueue; + std::deque m_PTSQueue; + + cTSDemuxer *m_demuxer; + uint8_t *m_pictureBuffer; + int m_pictureBufferSize; + int m_pictureBufferPtr; + int m_FrameDuration; + uint32_t m_StartCond; + uint32_t m_StartCode; + int m_StartCodeOffset; + sStreamPacket *m_StreamPacket; + int m_vbvDelay; /* -1 if CBR */ + int m_vbvSize; /* Video buffer size (in bytes) */ + int m_Height; + int m_Width; + + bool Parse_MPEG2Video(size_t len, uint32_t next_startcode, int sc_offset); + bool Parse_MPEG2Video_SeqStart(cBitstream *bs); + bool Parse_MPEG2Video_PicStart(int *frametype, cBitstream *bs); + void Parse_ComputePTS(sStreamPacket* pkt); + void Parse_ComputeDuration(sStreamPacket* pkt); + +public: + cParserMPEG2Video(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID); + virtual ~cParserMPEG2Video(); + + virtual void Parse(unsigned char *data, int size, bool pusi); +}; + +#endif /* VNSIDEMUXER_MPEGVIDEO_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Subtitle.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Subtitle.c new file mode 100644 index 0000000000..581141edce --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Subtitle.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include "config.h" + +#include "demuxer_Subtitle.h" + +cParserSubtitle::cParserSubtitle(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID) + : cParser(streamer, streamID) +{ + m_firstPUSIseen = false; + m_PESStart = false; + m_subtitleBuffer = NULL; + m_subtitleBufferSize = 0; + m_subtitleBufferPtr = 0; + m_lastDTS = DVD_NOPTS_VALUE; + m_lastPTS = DVD_NOPTS_VALUE; + m_lastLength = 0; + m_curLength = 0; +} + +cParserSubtitle::~cParserSubtitle() +{ + if (m_subtitleBuffer) + free(m_subtitleBuffer); +} + +void cParserSubtitle::Parse(unsigned char *data, int size, bool pusi) +{ + if (pusi) + { + /* Payload unit start */ + m_subtitleBufferPtr = 0; + m_firstPUSIseen = true; + } + + /* Wait for first pusi */ + if (!m_firstPUSIseen) + return; + + if (m_subtitleBuffer == NULL) + { + m_subtitleBufferSize = 4000; + m_subtitleBuffer = (uint8_t*)malloc(m_subtitleBufferSize); + } + + if (m_subtitleBufferPtr + size + 4 >= m_subtitleBufferSize) + { + m_subtitleBufferSize += size * 4; + m_subtitleBuffer = (uint8_t*)realloc(m_subtitleBuffer, m_subtitleBufferSize); + } + + memcpy(m_subtitleBuffer+m_subtitleBufferPtr, data, size); + m_subtitleBufferPtr += size; + + if (m_subtitleBufferPtr < 6) + return; + + uint32_t startcode = + (m_subtitleBuffer[0] << 24) | + (m_subtitleBuffer[1] << 16) | + (m_subtitleBuffer[2] << 8) | + (m_subtitleBuffer[3]); + + if (startcode == 0x1be) + { + m_firstPUSIseen = false; + return; + } + + int psize = m_subtitleBuffer[4] << 8 | m_subtitleBuffer[5]; + + if (m_subtitleBufferPtr != psize + 6) + return; + + m_firstPUSIseen = false; + + int hlen = ParsePESHeader(m_subtitleBuffer, m_subtitleBufferPtr); + if (hlen < 0) + return; + + if (m_lastDTS == DVD_NOPTS_VALUE) + { + m_lastDTS = m_curDTS; + m_lastPTS = m_curPTS; + } + + psize -= hlen - 6; + uint8_t *buf = m_subtitleBuffer + hlen; + + if (psize < 2 || buf[0] != 0x20 || buf[1] != 0x00) + return; + + psize -= 2; + buf += 2; + + if (psize >= 6) + { + // end_of_PES_data_field_marker + if (buf[psize - 1] == 0xff) + { + sStreamPacket pkt; + pkt.id = m_streamID; + pkt.data = buf; + pkt.size = psize - 1; + pkt.duration = m_curDTS-m_lastDTS; + pkt.dts = m_curPTS; + pkt.pts = m_curPTS; + SendPacket(&pkt); + + m_lastDTS = m_curDTS; + m_lastPTS = m_curPTS; + } + } + +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Subtitle.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Subtitle.h new file mode 100644 index 0000000000..2372600273 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Subtitle.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef VNSIDEMUXER_SUBTITLE_H +#define VNSIDEMUXER_SUBTITLE_H + +#include "demuxer.h" + +// --- cParserSubtitle ------------------------------------------------- + +class cParserSubtitle : public cParser +{ +private: + bool m_firstPUSIseen; + bool m_PESStart; + uint8_t *m_subtitleBuffer; + int m_subtitleBufferSize; + int m_subtitleBufferPtr; + int64_t m_lastDTS; + int64_t m_lastPTS; + int m_lastLength; + int m_curLength; + +public: + cParserSubtitle(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID); + virtual ~cParserSubtitle(); + + virtual void Parse(unsigned char *data, int size, bool pusi); +}; + + +#endif /* VNSIDEMUXER_SUBTITLE_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Teletext.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Teletext.c new file mode 100644 index 0000000000..86a532cc8e --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Teletext.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include "config.h" + +#include "demuxer_Teletext.h" + +cParserTeletext::cParserTeletext(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID) + : cParser(streamer, streamID) +{ + m_firstPUSIseen = false; + m_PESStart = false; + m_teletextBuffer = NULL; + m_teletextBufferSize = 0; + m_teletextBufferPtr = 0; + m_lastDTS = DVD_NOPTS_VALUE; + m_lastPTS = DVD_NOPTS_VALUE; +} + +cParserTeletext::~cParserTeletext() +{ + if (m_teletextBuffer) + free(m_teletextBuffer); +} + +void cParserTeletext::Parse(unsigned char *data, int size, bool pusi) +{ + if (pusi) + { + /* Payload unit start */ + m_PESStart = true; + m_firstPUSIseen = true; + } + + /* Wait for first pusi */ + if (!m_firstPUSIseen) + return; + + if (m_PESStart) + { + if (!PesIsPS1Packet(data)) + { + esyslog("VNSI-Error: Teletext PES packet contains no valid private stream 1, ignored this packet"); + m_firstPUSIseen = false; + return; + } + + m_lastDTS = m_curDTS; + m_lastPTS = m_curPTS; + int hlen = ParsePESHeader(data, size); + + m_PESStart = false; + data += hlen; + size -= hlen; + + if (data[0] < 0x10 || data[0] > 0x1F) + { + esyslog("VNSI-Error: Teletext PES packet contains no valid identifier, ignored this packet"); + m_firstPUSIseen = false; + return; + } + + if (m_teletextBuffer && m_teletextBufferPtr > 0) + { + sStreamPacket pkt; + pkt.id = m_streamID; + pkt.data = m_teletextBuffer; + pkt.size = m_teletextBufferPtr; + pkt.duration = m_curDTS-m_lastDTS; + pkt.dts = m_lastDTS; + pkt.pts = m_lastPTS; + SendPacket(&pkt); + m_teletextBufferPtr = 0; + } + } + + if (m_teletextBuffer == NULL) + { + m_teletextBufferSize = 4000; + m_teletextBuffer = (uint8_t*)malloc(m_teletextBufferSize); + } + + if (m_teletextBufferPtr + size + 4 >= m_teletextBufferSize) + { + m_teletextBufferSize += size * 4; + m_teletextBuffer = (uint8_t*)realloc(m_teletextBuffer, m_teletextBufferSize); + } + + memcpy(m_teletextBuffer+m_teletextBufferPtr, data, size); + m_teletextBufferPtr += size; +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Teletext.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Teletext.h new file mode 100644 index 0000000000..2cc5e7ce43 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_Teletext.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef VNSIDEMUXER_TELETEXT_H +#define VNSIDEMUXER_TELETEXT_H + +#include "demuxer.h" + +// --- cParserTeletext ------------------------------------------------- + +class cParserTeletext : public cParser +{ +private: + bool m_firstPUSIseen; + bool m_PESStart; + uint8_t *m_teletextBuffer; + int m_teletextBufferSize; + int m_teletextBufferPtr; + int64_t m_lastDTS; + int64_t m_lastPTS; + +public: + cParserTeletext(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID); + virtual ~cParserTeletext(); + + virtual void Parse(unsigned char *data, int size, bool pusi); +}; + +#endif /* VNSIDEMUXER_TELETEXT_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_h264.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_h264.c new file mode 100644 index 0000000000..a5e6cfd97f --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_h264.c @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include "config.h" +#include "bitstream.h" +#include "receiver.h" + +#include "demuxer_h264.h" + +static const int h264_lev2cpbsize[][2] = +{ + {10, 175}, + {11, 500}, + {12, 1000}, + {13, 2000}, + {20, 2000}, + {21, 4000}, + {22, 4000}, + {30, 10000}, + {31, 14000}, + {32, 20000}, + {40, 25000}, + {41, 62500}, + {42, 62500}, + {50, 135000}, + {51, 240000}, + {-1, -1}, +}; + +cParserH264::cParserH264(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID) + : cParser(streamer, streamID) +{ + m_pictureBuffer = NULL; + m_pictureBufferSize = 0; + m_pictureBufferPtr = 0; + m_StartCond = 0; + m_StartCode = 0; + m_StartCodeOffset = 0; + m_startDTS = DVD_NOPTS_VALUE; + m_PrevDTS = DVD_NOPTS_VALUE; + m_Height = 0; + m_Width = 0; + m_FrameDuration = 0; + m_demuxer = demuxer; + m_FoundFrame = false; + m_vbvDelay = -1; + m_vbvSize = 0; + m_firstPUSIseen = false; + m_PixelAspect.den = 1; + m_PixelAspect.num = 1; + memset(&m_streamData, 0, sizeof(m_streamData)); +} + +cParserH264::~cParserH264() +{ + if (m_pictureBuffer) + free(m_pictureBuffer); +} + +void cParserH264::Parse(unsigned char *data, int size, bool pusi) +{ + uint32_t startcode = m_StartCond; + + if (m_pictureBuffer == NULL) + { + m_pictureBufferSize = 80000; + m_pictureBuffer = (uint8_t*)malloc(m_pictureBufferSize); + } + + if (m_pictureBufferPtr + size + 4 >= m_pictureBufferSize) + { + m_pictureBufferSize += size * 4; + m_pictureBuffer = (uint8_t*)realloc(m_pictureBuffer, m_pictureBufferSize); + } + + for (int i = 0; i < size; i++) + { + m_pictureBuffer[m_pictureBufferPtr++] = data[i]; + startcode = startcode << 8 | data[i]; + + if ((startcode & 0xffffff00) != 0x00000100) + continue; + + bool reset = true; + if (m_pictureBufferPtr - 4 > 0 && m_StartCode != 0) + { + reset = Parse_H264(m_pictureBufferPtr - 4, startcode, m_StartCodeOffset); + } + + if (reset) + { + /* Reset packet parser upon length error or if parser tells us so */ + m_pictureBufferPtr = 0; + m_pictureBuffer[m_pictureBufferPtr++] = startcode >> 24; + m_pictureBuffer[m_pictureBufferPtr++] = startcode >> 16; + m_pictureBuffer[m_pictureBufferPtr++] = startcode >> 8; + m_pictureBuffer[m_pictureBufferPtr++] = startcode >> 0; + } + m_StartCode = startcode; + m_StartCodeOffset = m_pictureBufferPtr - 4; + } + m_StartCond = startcode; +} + +bool cParserH264::Parse_H264(size_t len, uint32_t next_startcode, int sc_offset) +{ + uint8_t nal_data[len]; + int pkttype; + uint8_t *buf = m_pictureBuffer + sc_offset; + uint32_t startcode = m_StartCode; + + if (startcode == 0x10c) + { + /* RBSP padding, we don't want this */ + int length = len - sc_offset; + memcpy(buf, buf + length, 4); /* Move down new start code */ + m_pictureBufferPtr -= length; /* Drop buffer */ + } + + if (startcode >= 0x000001e0 && startcode <= 0x000001ef) + { + /* System start codes for video */ + if (len >= 9) + ParsePESHeader(buf, len); + + if (m_PrevDTS != DVD_NOPTS_VALUE) + { + int64_t duration = (m_curDTS - m_PrevDTS) & 0x1ffffffffLL; + + if (duration < 90000) + m_FrameDuration = duration; + } + m_PrevDTS = m_curDTS; + return true; + } + + switch(startcode & 0x1f) + { + case NAL_SPS: + { + int nal_len = nalUnescape(nal_data, buf + 4, len - 4); + if (!Parse_SPS(nal_data, nal_len)) + return true; + + m_demuxer->SetVideoInformation(0,0, m_Height, m_Width, m_PixelAspect.num/m_PixelAspect.den); + break; + } + + case NAL_PPS: + { + int nal_len = nalUnescape(nal_data, buf + 4, len - 4); + if (!Parse_PPS(nal_data, nal_len)) + return true; + + break; + } + + case 5: /* IDR+SLICE */ + case NAL_SLH: + { + if (m_FoundFrame || m_FrameDuration == 0 || m_curDTS == DVD_NOPTS_VALUE) + break; + + if (m_startDTS == DVD_NOPTS_VALUE) + m_startDTS = m_curDTS; + + int nal_len = nalUnescape(nal_data, buf + 4, len - 4 > 64 ? 64 : len - 3); + if (!Parse_SLH(nal_data, nal_len, &pkttype)) + return true; + + m_StreamPacket.id = m_streamID; + m_StreamPacket.pts = m_curPTS; + m_StreamPacket.dts = m_curDTS; + m_StreamPacket.frametype = pkttype; + m_StreamPacket.duration = m_FrameDuration; + m_FoundFrame = true; + break; + } + + default: + break; + } + + if (next_startcode >= 0x000001e0 && next_startcode <= 0x000001ef) + { + /* Complete frame */ + if (!m_FoundFrame) + return true; + + /* Discard Packets until we have the picture size (XBMC can't enable VDPAU without it) */ + if (!m_Width) + return true; + else + m_Streamer->SetReady(); + + m_FoundFrame = false; + m_StreamPacket.data = m_pictureBuffer; + m_StreamPacket.size = m_pictureBufferPtr; + SendPacket(&m_StreamPacket); + m_firstPUSIseen = true; + return true; + } + + return false; +} + +int cParserH264::nalUnescape(uint8_t *dst, const uint8_t *src, int len) +{ + int s = 0, d = 0; + + while (s < len) + { + if (!src[s] && !src[s + 1]) + { + // hit 00 00 xx + dst[d] = dst[d + 1] = 0; + s += 2; + d += 2; + if (src[s] == 3) + { + s++; // 00 00 03 xx --> 00 00 xx + if (s >= len) + return d; + } + } + dst[d++] = src[s++]; + } + + return d; +} + +bool cParserH264::Parse_PPS(uint8_t *buf, int len) +{ + cBitstream bs(buf, len*8); + + int pps_id = bs.readGolombUE(); + int sps_id = bs.readGolombUE(); + m_streamData.pps[pps_id].sps = sps_id; + return true; +} + +bool cParserH264::Parse_SLH(uint8_t *buf, int len, int *pkttype) +{ + cBitstream bs(buf, len*8); + + bs.readGolombUE(); /* first_mb_in_slice */ + int slice_type = bs.readGolombUE(); + + if (slice_type > 4) + slice_type -= 5; /* Fixed slice type per frame */ + + switch (slice_type) + { + case 0: + *pkttype = PKT_P_FRAME; + break; + case 1: + *pkttype = PKT_B_FRAME; + break; + case 2: + *pkttype = PKT_I_FRAME; + break; + default: + return false; + } + +// /* If this is the first I-frame seen, set dts_start as a reference offset */ +// if (*pkttype == PKT_I_FRAME && m_startDTS == DVD_NOPTS_VALUE) +// m_startDTS = m_curDTS; + + int pps_id = bs.readGolombUE(); + int sps_id = m_streamData.pps[pps_id].sps; + if (m_streamData.sps[sps_id].cbpsize == 0) + return false; + + m_vbvSize = m_streamData.sps[sps_id].cbpsize; + m_vbvDelay = -1; + return true; +} + +bool cParserH264::Parse_SPS(uint8_t *buf, int len) +{ + cBitstream bs(buf, len*8); + unsigned int tmp, frame_mbs_only; + int cbpsize = -1; + + int profile_idc = bs.readBits(8); + /* constraint_set0_flag = bs.readBits1(); */ + /* constraint_set1_flag = bs.readBits1(); */ + /* constraint_set2_flag = bs.readBits1(); */ + /* constraint_set3_flag = bs.readBits1(); */ + /* reserved = bs.readBits(4); */ + bs.skipBits(8); + int level_idc = bs.readBits(8); + unsigned int seq_parameter_set_id = bs.readGolombUE(); + + unsigned int i = 0; + while (h264_lev2cpbsize[i][0] != -1) + { + if (h264_lev2cpbsize[i][0] >= level_idc) + { + cbpsize = h264_lev2cpbsize[i][1]; + break; + } + i++; + } + if (cbpsize < 0) + return false; + + m_streamData.sps[seq_parameter_set_id].cbpsize = cbpsize * 125; /* Convert from kbit to bytes */ + + if (profile_idc >= 100) /* high profile */ + { + if(bs.readGolombUE() == 3) /* chroma_format_idc */ + bs.skipBits(1); /* residual_colour_transform_flag */ + bs.readGolombUE(); /* bit_depth_luma - 8 */ + bs.readGolombUE(); /* bit_depth_chroma - 8 */ + bs.skipBits(1); /* transform_bypass */ + if (bs.readBits1()) /* seq_scaling_matrix_present */ + { + for (int i = 0; i < 8; i++) + { + if (bs.readBits1()) /* seq_scaling_list_present */ + { + int last = 8, next = 8, size = (i<6) ? 16 : 64; + for (int j = 0; j < size; j++) + { + if (next) + next = (last + bs.readGolombSE()) & 0xff; + last = next ?: last; + } + } + } + } + } + + bs.readGolombUE(); /* log2_max_frame_num - 4 */ + int pic_order_cnt_type = bs.readGolombUE(); + if (pic_order_cnt_type == 0) + bs.readGolombUE(); /* log2_max_poc_lsb - 4 */ + else if (pic_order_cnt_type == 1) + { + bs.skipBits(1); /* delta_pic_order_always_zero */ + bs.readGolombSE(); /* offset_for_non_ref_pic */ + bs.readGolombSE(); /* offset_for_top_to_bottom_field */ + tmp = bs.readGolombUE(); /* num_ref_frames_in_pic_order_cnt_cycle */ + for (unsigned int i = 0; i < tmp; i++) + bs.readGolombSE(); /* offset_for_ref_frame[i] */ + } + else if(pic_order_cnt_type != 2) + { + /* Illegal poc */ + return false; + } + + bs.readGolombUE(); /* ref_frames */ + bs.skipBits(1); /* gaps_in_frame_num_allowed */ + m_Width /* mbs */ = bs.readGolombUE() + 1; + m_Height /* mbs */ = bs.readGolombUE() + 1; + frame_mbs_only = bs.readBits1(); + LOGCONSOLE("H.264 SPS: pic_width: %u mbs", (unsigned) m_Width); + LOGCONSOLE("H.264 SPS: pic_height: %u mbs", (unsigned) m_Height); + LOGCONSOLE("H.264 SPS: frame only flag: %d", frame_mbs_only); + + m_Width *= 16; + m_Height *= 16 * (2-frame_mbs_only); + + if (!frame_mbs_only) + { + if (bs.readBits1()) /* mb_adaptive_frame_field_flag */ + LOGCONSOLE("H.264 SPS: MBAFF"); + } + bs.skipBits(1); /* direct_8x8_inference_flag */ + if (bs.readBits1()) /* frame_cropping_flag */ + { + uint32_t crop_left = bs.readGolombUE(); + uint32_t crop_right = bs.readGolombUE(); + uint32_t crop_top = bs.readGolombUE(); + uint32_t crop_bottom = bs.readGolombUE(); + LOGCONSOLE("H.264 SPS: cropping %d %d %d %d", crop_left, crop_top, crop_right, crop_bottom); + + m_Width -= 2*(crop_left + crop_right); + if (frame_mbs_only) + m_Height -= 2*(crop_top + crop_bottom); + else + m_Height -= 4*(crop_top + crop_bottom); + } + + /* VUI parameters */ + m_PixelAspect.num = 0; + if (bs.readBits1()) /* vui_parameters_present flag */ + { + if (bs.readBits1()) /* aspect_ratio_info_present */ + { + uint32_t aspect_ratio_idc = bs.readBits(8); + LOGCONSOLE("H.264 SPS: aspect_ratio_idc %d", aspect_ratio_idc); + + if (aspect_ratio_idc == 255 /* Extended_SAR */) + { + m_PixelAspect.num = bs.readBits(16); /* sar_width */ + m_PixelAspect.den = bs.readBits(16); /* sar_height */ + LOGCONSOLE("H.264 SPS: -> sar %dx%d", m_PixelAspect.num, m_PixelAspect.den); + } + else + { + static const mpeg_rational_t aspect_ratios[] = + { /* page 213: */ + /* 0: unknown */ + {0, 1}, + /* 1...16: */ + { 1, 1}, {12, 11}, {10, 11}, {16, 11}, { 40, 33}, {24, 11}, {20, 11}, {32, 11}, + {80, 33}, {18, 11}, {15, 11}, {64, 33}, {160, 99}, { 4, 3}, { 3, 2}, { 2, 1} + }; + + if (aspect_ratio_idc < sizeof(aspect_ratios)/sizeof(aspect_ratios[0])) + { + memcpy(&m_PixelAspect, &aspect_ratios[aspect_ratio_idc], sizeof(mpeg_rational_t)); + LOGCONSOLE("H.264 SPS: -> aspect ratio %d / %d", m_PixelAspect.num, m_PixelAspect.den); + } + else + { + LOGCONSOLE("H.264 SPS: aspect_ratio_idc out of range !"); + } + } + } + } + + LOGCONSOLE("H.264 SPS: -> video size %dx%d, aspect %d:%d", m_Width, m_Height, m_PixelAspect.num, m_PixelAspect.den); + return true; +} + diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_h264.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_h264.h new file mode 100644 index 0000000000..3d564647a6 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/demuxer_h264.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef VNSIDEMUXER_H264_H +#define VNSIDEMUXER_H264_H + +#include "demuxer.h" + +class cBitstream; + +// --- cParserH264 ------------------------------------------------- + +class cParserH264 : public cParser +{ +private: + typedef struct h264_private + { + struct + { + int frame_duration; + int cbpsize; + } sps[256]; + + struct + { + int sps; + } pps[256]; + + } h264_private_t; + + typedef struct mpeg_rational_s { + int num; + int den; + } mpeg_rational_t; + + enum + { + NAL_SLH = 0x01, // Slice Header + NAL_SEI = 0x06, // Supplemental Enhancement Information + NAL_SPS = 0x07, // Sequence Parameter Set + NAL_PPS = 0x08, // Picture Parameter Set + NAL_AUD = 0x09, // Access Unit Delimiter + NAL_END_SEQ = 0x0A // End of Sequence + }; + + cTSDemuxer *m_demuxer; + uint8_t *m_pictureBuffer; + int m_pictureBufferSize; + int m_pictureBufferPtr; + uint32_t m_StartCond; + uint32_t m_StartCode; + int m_StartCodeOffset; + int m_Height; + int m_Width; + mpeg_rational_t m_PixelAspect; + int64_t m_PrevDTS; + int m_FrameDuration; + sStreamPacket m_StreamPacket; + bool m_FoundFrame; + h264_private m_streamData; + int m_vbvDelay; /* -1 if CBR */ + int m_vbvSize; /* Video buffer size (in bytes) */ + bool m_firstPUSIseen; + + bool Parse_H264(size_t len, uint32_t next_startcode, int sc_offset); + bool Parse_PPS(uint8_t *buf, int len); + bool Parse_SLH(uint8_t *buf, int len, int *pkttype); + bool Parse_SPS(uint8_t *buf, int len); + int nalUnescape(uint8_t *dst, const uint8_t *src, int len); + +public: + cParserH264(cTSDemuxer *demuxer, cLiveStreamer *streamer, int streamID); + virtual ~cParserH264(); + + virtual void Parse(unsigned char *data, int size, bool pusi); +}; + + +#endif /* VNSIDEMUXER_H264_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/global.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/global.h new file mode 100644 index 0000000000..23f7a1a923 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/global.h @@ -0,0 +1,128 @@ +/* + * global.h: A program for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + */ + +#ifndef __global_h_ +#define __global_h_ + +#include + +#ifndef uchar +typedef unsigned char uchar; +#endif + +#define MA_I_TYPE 1 +#define MA_P_TYPE 2 +#define MA_B_TYPE 3 +#define MA_D_TYPE 4 +#define MA_SI_TYPE 5 +#define MA_SP_TYPE 6 +#define MA_BI_TYPE 7 + +#define MT_COMMON 0x10 +#define MT_ASPECTCHANGE 0x20 +#define MT_CHANNELCHANGE 0x30 +#define MT_CHANNELSTART 0x30 +#define MT_CHANNELSTOP 0x31 +#define MT_LOGOSTART 0x40 +#define MT_LOGOSTOP 0x41 +#define MT_BORDERSTART 0x50 +#define MT_BORDERSTOP 0x51 +#define MT_SILENCECHANGE 0x60 +#define MT_ALL 0xFF + +typedef struct MarkAdMark +{ + int Type; + int Position; + char *Comment; +} MarkAdMark; + +typedef struct MarkAdAspectRatio +{ + int Num; + int Den; +} MarkAdAspectRatio; + +#define MARKAD_PIDTYPE_VIDEO_H262 0x10 +#define MARKAD_PIDTYPE_VIDEO_H264 0x11 +#define MARKAD_PIDTYPE_AUDIO_AC3 0x20 +#define MARKAD_PIDTYPE_AUDIO_MP2 0x21 + +typedef struct MarkAdPid +{ + int Num; + int Type; +} MarkAdPid; + +typedef struct MarkAdContext +{ + char *LogoDir; // Logo Directory, default /var/lib/markad + + struct StandAlone + { + int LogoExtraction; + int LogoWidth; + int LogoHeight; + bool ASD; + } StandAlone; + + struct General + { + char *ChannelID; + MarkAdPid VPid; + MarkAdPid APid; + MarkAdPid DPid; + } General; + + struct Video + { + struct Options + { + bool IgnoreAspectRatio; + } Options; + + struct Info + { + int Width; // width of pic + int Height; // height of pic + int Pict_Type; // picture type (I,P,B,S,SI,SP,BI) + MarkAdAspectRatio AspectRatio; + double FramesPerSecond; + bool Interlaced; + } Info; + + struct Data + { + bool Valid; // flag, if true data is valid + uchar *Plane[4]; // picture planes (YUV420) + int PlaneLinesize[4]; // size int bytes of each picture plane line + } Data; + } Video; + + struct Audio + { + struct Options + { + bool AudioSilenceDetection; + } Options; + + struct Info + { + int Channels; // number of audio channels + int SampleRate; + } Info; + struct Data + { + bool Valid; + short *SampleBuf; + int SampleBufLen; + } Data; + } Audio; + +} MarkAdContext; + +#endif diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/patches/vdr-wirbelscan-0.0.5-pre11e-AddServiceInterface.diff b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/patches/vdr-wirbelscan-0.0.5-pre11e-AddServiceInterface.diff new file mode 100644 index 0000000000..f2ce0d9128 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/patches/vdr-wirbelscan-0.0.5-pre11e-AddServiceInterface.diff @@ -0,0 +1,426 @@ +diff -NaurwB wirbelscan-0.0.5-pre11e/common.h wirbelscan-patched/common.h +--- wirbelscan-0.0.5-pre11e/common.h 2010-03-17 11:32:34.000000000 +0100 ++++ wirbelscan-patched/common.h 2010-04-19 00:55:36.000000000 +0200 +@@ -11,16 +11,7 @@ + + #include + #include +- +-typedef enum scantype { +- DVB_TERR = 0, +- DVB_CABLE = 1, +- DVB_SAT = 2, +- PVRINPUT = 3, +- PVRINPUT_FM = 4, +- DVB_ATSC = 5, +-} scantype_t; +- ++#include "wirbelscanservice.h" + + + /* generic functions */ +diff -NaurwB wirbelscan-0.0.5-pre11e/dvb_wrapper.c wirbelscan-patched/dvb_wrapper.c +--- wirbelscan-0.0.5-pre11e/dvb_wrapper.c 2010-03-18 11:06:33.000000000 +0100 ++++ wirbelscan-patched/dvb_wrapper.c 2010-04-23 03:59:28.000000000 +0200 +@@ -1,5 +1,4 @@ + +-#include //either API version 3.2 or 5.0 + #include + #include + #include +diff -NaurwB wirbelscan-0.0.5-pre11e/menusetup.c wirbelscan-patched/menusetup.c +--- wirbelscan-0.0.5-pre11e/menusetup.c 2010-03-17 13:10:15.000000000 +0100 ++++ wirbelscan-patched/menusetup.c 2010-04-23 03:59:41.000000000 +0200 +@@ -7,7 +7,6 @@ + */ + + +-#include + #include + #include + #include +@@ -39,6 +38,7 @@ + cOsdItem * ChanNew = NULL; + cOsdItem * ChanAll = NULL; + cOsdItem * ScanType = NULL; ++sRemoteMenuScanning * RemoteMenuScanning = NULL; + + #define LOGLEN 8 + cOsdItem * LogMsg[LOGLEN]; +diff -NaurwB wirbelscan-0.0.5-pre11e/menusetup.h wirbelscan-patched/menusetup.h +--- wirbelscan-0.0.5-pre11e/menusetup.h 2010-03-17 11:32:34.000000000 +0100 ++++ wirbelscan-patched/menusetup.h 2010-04-23 02:04:08.000000000 +0200 +@@ -49,6 +49,16 @@ + bool DoScan (int DVB_Type); + void DoStop (void); + ++struct sRemoteMenuScanning { ++ void (*SetPercentage)(int percent); ++ void (*SetSignalStrength)(int strenght, bool locked); ++ void (*SetDeviceInfo)(const char *Info); ++ void (*SetTransponder)(const char *Info); ++ void (*NewChannel)(const char *Name, bool isRadio, bool isEncrypted, bool isHD); ++ void (*IsFinished)(); ++ void (*SetStatus)(int status); ++}; ++ + class cWirbelscan { + private: + public: +@@ -91,5 +101,6 @@ + void AddLogMsg(const char * Msg); + }; + extern cMenuScanning * MenuScanning; ++extern sRemoteMenuScanning * RemoteMenuScanning; + + #endif +diff -NaurwB wirbelscan-0.0.5-pre11e/scanfilter.c wirbelscan-patched/scanfilter.c +--- wirbelscan-0.0.5-pre11e/scanfilter.c 2010-03-17 11:32:34.000000000 +0100 ++++ wirbelscan-patched/scanfilter.c 2010-04-23 01:55:30.000000000 +0200 +@@ -1000,6 +1000,8 @@ + dlog(4, " SDT: old %s", *PrintChannel(channel)); + channel->SetName(pn, ps, pp); + dlog(2, " Upd: %s", *PrintChannel(channel)); ++ if (RemoteMenuScanning) ++ RemoteMenuScanning->NewChannel(channel->Name(), (channel->Vpid() == 0 && (channel->Apid(0) != 0 || channel->Dpid(0) != 0)), channel->Ca() != 0, sd->getServiceType() == 0x19); + } + } + else { +@@ -1009,6 +1011,8 @@ + transponder->CopyTransponderData(Channel()); + dlog(3, " SDT: Add: %s", *PrintTransponder(transponder)); + NewTransponders.Add(transponder); ++ if (RemoteMenuScanning) ++ RemoteMenuScanning->NewChannel(channel->Name(), (channel->Vpid() == 0 && (channel->Apid(0) != 0 || channel->Dpid(0) != 0)), channel->Ca() != 0, sd->getServiceType() == 0x19); + } + dlog(2, " SDT: Add %s", *PrintChannel(channel)); + } +diff -NaurwB wirbelscan-0.0.5-pre11e/scanner.c wirbelscan-patched/scanner.c +--- wirbelscan-0.0.5-pre11e/scanner.c 2010-03-18 10:52:52.000000000 +0100 ++++ wirbelscan-patched/scanner.c 2010-04-23 04:04:27.000000000 +0200 +@@ -289,6 +289,8 @@ + } + + dlog(1, "%s", *PrintChannel(channel)); ++ if (RemoteMenuScanning) ++ RemoteMenuScanning->NewChannel(channel->Name(), false, false, false); + Channels.IncBeingEdited(); + Channels.Add(channel); + Channels.DecBeingEdited(); +@@ -358,6 +360,7 @@ + if ((dev = GetPreferredDevice(aChannel)) == NULL) { + dlog(0, "No device available - exiting!"); + if (MenuScanning) MenuScanning->SetStatus((status = 2)); ++ if (RemoteMenuScanning) RemoteMenuScanning->SetStatus((status = 2)); + DeleteAndNull(aChannel); + return; + } +@@ -366,6 +369,7 @@ + GetTerrCapabilities(dev->CardIndex(), &crAuto, &modAuto, &invAuto, &bwAuto, &hAuto, &tmAuto, &gAuto); + dlog(1, "frontend %s supports", *GetFeName(dev->CardIndex())); + if (MenuScanning) MenuScanning->SetDeviceInfo(cString::sprintf("%s", *GetFeName(dev->CardIndex()))); ++ if (RemoteMenuScanning) RemoteMenuScanning->SetDeviceInfo(*cString::sprintf("%s", *GetFeName(dev->CardIndex()))); + + if (invAuto) { + dlog(1, "INVERSION_AUTO"); +@@ -439,6 +443,7 @@ + if ((dev = GetPreferredDevice(aChannel)) == NULL) { + dlog(0, "No device available - exiting!"); + if (MenuScanning) MenuScanning->SetStatus((status = 2)); ++ if (RemoteMenuScanning) RemoteMenuScanning->SetStatus((status = 2)); + DeleteAndNull(aChannel); + return; + } +@@ -447,6 +452,7 @@ + GetCableCapabilities(dev->CardIndex(), &crAuto, &modAuto, &invAuto); + dlog(1, "frontend %s supports", *GetFeName(dev->CardIndex())); + if (MenuScanning) MenuScanning->SetDeviceInfo(cString::sprintf("%s", *GetFeName(dev->CardIndex()))); ++ if (RemoteMenuScanning) RemoteMenuScanning->SetDeviceInfo(*cString::sprintf("%s", *GetFeName(dev->CardIndex()))); + if (invAuto) { + dlog(1, "INVERSION_AUTO"); + caps_inversion = INVERSION_AUTO; +@@ -526,6 +532,7 @@ + #endif + dlog(0, "No DVB-S2 device available - trying fallback to DVB-S"); + if (MenuScanning) MenuScanning->SetStatus(3); ++ if (RemoteMenuScanning) RemoteMenuScanning->SetStatus(3); + // SetSatTransponderDataFromVDR(aChannel, cSource::FromString(sat_list[this_channellist].source_id), 11112, eHorizontal, 27500, eCoderate56, eSatModulationQpsk, eDvbs, eRolloff35); + SetSatTransponderDataFromDVB(aChannel, + cSource::FromString(sat_list[this_channellist].source_id), +@@ -536,6 +543,7 @@ + if ((dev = GetPreferredDevice(aChannel)) == NULL) { + dlog(0, "No device available - exiting!"); + if (MenuScanning) MenuScanning->SetStatus((status = 2)); ++ if (RemoteMenuScanning) RemoteMenuScanning->SetStatus((status = 2)); + DeleteAndNull(aChannel); + return; + } +@@ -545,6 +553,7 @@ + GetSatCapabilities(dev->CardIndex(), &crAuto, &modAuto, &roAuto, &s2Support); + dlog(1, "frontend %s supports", *GetFeName(dev->CardIndex())); + if (MenuScanning) MenuScanning->SetDeviceInfo(cString::sprintf("%s", *GetFeName(dev->CardIndex()))); ++ if (RemoteMenuScanning) RemoteMenuScanning->SetDeviceInfo(*cString::sprintf("%s", *GetFeName(dev->CardIndex()))); + caps_inversion = INVERSION_AUTO; + if (crAuto) { + dlog(1, "FEC_AUTO"); +@@ -578,6 +587,7 @@ + if ((dev = GetPreferredDevice(aChannel)) == NULL) { + dlog(0, "No device available - exiting!"); + if (MenuScanning) MenuScanning->SetStatus((status = 2)); ++ if (RemoteMenuScanning) RemoteMenuScanning->SetStatus((status = 2)); + DeleteAndNull(aChannel); + return; + } +@@ -586,6 +596,7 @@ + GetAtscCapabilities(dev->CardIndex(), &crAuto, &modAuto, &invAuto, &vsbSupport, &qamSupport); + dlog(1, "frontend %s supports", *GetFeName(dev->CardIndex())); + if (MenuScanning) MenuScanning->SetDeviceInfo(cString::sprintf("%s", *GetFeName(dev->CardIndex()))); ++ if (RemoteMenuScanning) RemoteMenuScanning->SetDeviceInfo(*cString::sprintf("%s", *GetFeName(dev->CardIndex()))); + if (invAuto) { + dlog(1, "INVERSION_AUTO\n"); + caps_inversion = INVERSION_AUTO; +@@ -660,6 +671,7 @@ + #endif + dlog(0, "No device available - exiting! (pvrinput not running?)"); + if (MenuScanning) MenuScanning->SetStatus((status = 2)); ++ if (RemoteMenuScanning) RemoteMenuScanning->SetStatus((status = 2)); + DeleteAndNull(aChannel); + return; + } +@@ -707,6 +719,8 @@ + } + if (MenuScanning) + MenuScanning->SetDeviceInfo(cString::sprintf("%s", vcap.card)); ++ if (RemoteMenuScanning) ++ RemoteMenuScanning->SetDeviceInfo(*cString::sprintf("%s", vcap.card)); + dev->DetachAllReceivers(); + break; + } +@@ -716,6 +730,7 @@ + } // end switch type + + if (MenuScanning) MenuScanning->SetStatus(1); ++ if (RemoteMenuScanning) RemoteMenuScanning->SetStatus(1); + + //count channels. + +@@ -933,6 +948,11 @@ + type, (lastChannel - thisChannel)); + MenuScanning->SetTransponder(aChannel); + } ++ if (RemoteMenuScanning) ++ { ++ RemoteMenuScanning->SetPercentage((int) (thisChannel * 100) / lastChannel); ++ RemoteMenuScanning->SetTransponder(*PrintTransponder(aChannel)); ++ } + dev->SwitchChannel(aChannel, false); + SwReceiver = new cSwReceiver::cSwReceiver(aChannel); + dev->AttachReceiver(SwReceiver); +@@ -952,6 +972,7 @@ + lock = false; + + if (MenuScanning) MenuScanning->SetStr(GetFrontendStrength(dev->CardIndex()), lock); ++ if (RemoteMenuScanning) RemoteMenuScanning->SetSignalStrength(GetFrontendStrength(dev->CardIndex()), lock); + if (! lock) { + continue; + } +@@ -971,6 +992,8 @@ + cChannel * newChannel = new cChannel; + if (MenuScanning) + MenuScanning->SetStr(s, true); ++ if (RemoteMenuScanning) ++ RemoteMenuScanning->SetSignalStrength(s, true); + + newChannel->Parse(*aChannel->ToText()); + newChannel->SetName(*channelname, *shortname, (const char *) "analog"); +@@ -988,6 +1011,8 @@ + else { + if (MenuScanning) + MenuScanning->SetStr(0, false); ++ if (RemoteMenuScanning) ++ RemoteMenuScanning->SetSignalStrength(0, false); + } + break; + } +@@ -1108,6 +1133,13 @@ + + stop: + if (MenuScanning) MenuScanning->SetStatus((status = 0)); ++ if (RemoteMenuScanning) ++ { ++ RemoteMenuScanning->SetStatus((status = 0)); ++ RemoteMenuScanning->IsFinished(); ++ delete RemoteMenuScanning; ++ RemoteMenuScanning = NULL; ++ } + dlog(3, "leaving scanner"); + Cancel(0); + } +diff -NaurwB wirbelscan-0.0.5-pre11e/statemachine.c wirbelscan-patched/statemachine.c +--- wirbelscan-0.0.5-pre11e/statemachine.c 2010-03-17 11:32:34.000000000 +0100 ++++ wirbelscan-patched/statemachine.c 2010-04-22 17:07:02.000000000 +0200 +@@ -129,6 +129,11 @@ + MenuScanning->SetTransponder(Transponder); + MenuScanning->SetProgress(-1, DVB_TERR, -1); + } ++ if (RemoteMenuScanning) ++ { ++ RemoteMenuScanning->SetPercentage(-1); ++ RemoteMenuScanning->SetTransponder(*PrintTransponder(Transponder)); ++ } + + ScannedTransponder = new cChannel(* Transponder); + ScannedTransponders.Add(ScannedTransponder); +@@ -143,6 +148,8 @@ + } + if (MenuScanning) + MenuScanning->SetStr(GetFrontendStrength(dev->CardIndex()), dev->HasLock(1)); ++ if (RemoteMenuScanning) ++ RemoteMenuScanning->SetSignalStrength(GetFrontendStrength(dev->CardIndex()), dev->HasLock(1)); + break; + + case eNextTransponder: +diff -NaurwB wirbelscan-0.0.5-pre11e/wirbelscan.c wirbelscan-patched/wirbelscan.c +--- wirbelscan-0.0.5-pre11e/wirbelscan.c 2010-03-17 11:32:34.000000000 +0100 ++++ wirbelscan-patched/wirbelscan.c 2010-04-23 03:17:50.000000000 +0200 +@@ -9,6 +9,8 @@ + #include + #include + #include "menusetup.h" ++#include "countries.h" ++#include "satellites.h" + #if VDRVERSNUM < 10507 + #include "i18n.h" + #endif +@@ -141,7 +143,69 @@ + + bool cPluginWirbelscan::Service(const char *Id, void *Data) + { +- // Handle custom service requests from other plugins ++ if (strcmp(Id,"WirbelScanService-DoScan-v1.0") == 0) ++ { ++ if (Data) ++ { ++ WirbelScanService_DoScan_v1_0 *svc = (WirbelScanService_DoScan_v1_0*)Data; ++ ++ Wirbelscan.scanflags = svc->scan_tv ? SCAN_TV : 0; ++ Wirbelscan.scanflags |= svc->scan_radio ? SCAN_RADIO : 0; ++ Wirbelscan.scanflags |= svc->scan_scrambled ? SCAN_SCRAMBLED : 0; ++ Wirbelscan.scanflags |= svc->scan_fta ? SCAN_FTA : 0; ++ Wirbelscan.scanflags |= svc->scan_hd ? SCAN_HD : 0; ++ Wirbelscan.CountryIndex = svc->CountryIndex; ++ Wirbelscan.DVBC_Inversion = svc->DVBC_Inversion; ++ Wirbelscan.DVBC_Symbolrate = svc->DVBC_Symbolrate; ++ Wirbelscan.DVBC_QAM = svc->DVBC_QAM; ++ Wirbelscan.DVBT_Inversion = svc->DVBT_Inversion; ++ Wirbelscan.SatIndex = svc->SatIndex; ++ Wirbelscan.ATSC_type = svc->ATSC_Type; ++ ++ RemoteMenuScanning = new sRemoteMenuScanning; ++ RemoteMenuScanning->SetPercentage = svc->SetPercentage; ++ RemoteMenuScanning->SetSignalStrength = svc->SetSignalStrength; ++ RemoteMenuScanning->SetDeviceInfo = svc->SetDeviceInfo; ++ RemoteMenuScanning->SetTransponder = svc->SetTransponder; ++ RemoteMenuScanning->NewChannel = svc->NewChannel; ++ RemoteMenuScanning->IsFinished = svc->IsFinished; ++ RemoteMenuScanning->SetStatus = svc->SetStatus; ++ ++ return DoScan(svc->type); ++ } ++ } ++ else if (strcmp(Id,"WirbelScanService-StopScan-v1.0") == 0) ++ { ++ DoStop(); ++ return true; ++ } ++ else if (strcmp(Id,"WirbelScanService-GetCountries-v1.0") == 0) ++ { ++ if (Data) ++ { ++ WirbelScanService_GetCountries_v1_0 SetCountry = (WirbelScanService_GetCountries_v1_0) Data; ++ for (int i=0; i < COUNTRY::country_count(); i++) ++ { ++ SetCountry(COUNTRY::country_list[i].id, COUNTRY::country_list[i].short_name, COUNTRY::country_list[i].full_name); ++ } ++ return true; ++ } ++ } ++ else if (strcmp(Id,"WirbelScanService-GetSatellites-v1.0") == 0) ++ { ++ if (Data) ++ { ++ WirbelScanService_GetSatellites_v1_0 SetSatellite = (WirbelScanService_GetSatellites_v1_0) Data; ++ for (int i=0; i < sat_count(); i++) ++ { ++ SetSatellite(sat_list[i].id, sat_list[i].short_name, sat_list[i].full_name); ++ } ++ return true; ++ } ++ ++ return true; ++ } ++ + return false; + } + +diff -NaurwB wirbelscan-0.0.5-pre11e/wirbelscanservice.h wirbelscan-patched/wirbelscanservice.h +--- wirbelscan-0.0.5-pre11e/wirbelscanservice.h 1970-01-01 01:00:00.000000000 +0100 ++++ wirbelscan-patched/wirbelscanservice.h 2010-04-23 02:03:56.000000000 +0200 +@@ -0,0 +1,57 @@ ++/* ++ * wirbelscan.c: A plugin for the Video Disk Recorder ++ * ++ * See the README file for copyright information and how to reach the author. ++ * ++ * $Id$ ++ */ ++ ++#ifndef __WIRBELSCAN_SERVICE_H ++#define __WIRBELSCAN_SERVICE_H ++ ++typedef enum scantype ++{ ++ DVB_TERR = 0, ++ DVB_CABLE = 1, ++ DVB_SAT = 2, ++ PVRINPUT = 3, ++ PVRINPUT_FM = 4, ++ DVB_ATSC = 5, ++} scantype_t; ++ ++typedef void (*WirbelScanService_GetCountries_v1_0)(int index, const char *isoName, const char *longName); ++typedef void (*WirbelScanService_GetSatellites_v1_0)(int index, const char *shortName, const char *longName); ++ ++struct WirbelScanService_DoScan_v1_0 ++{ ++ scantype_t type; ++ ++ bool scan_tv; ++ bool scan_radio; ++ bool scan_fta; ++ bool scan_scrambled; ++ bool scan_hd; ++ ++ int CountryIndex; ++ ++ int DVBC_Inversion; ++ int DVBC_Symbolrate; ++ int DVBC_QAM; ++ ++ int DVBT_Inversion; ++ ++ int SatIndex; ++ ++ int ATSC_Type; ++ ++ void (*SetPercentage)(int percent); ++ void (*SetSignalStrength)(int strenght, bool locked); ++ void (*SetDeviceInfo)(const char *Info); ++ void (*SetTransponder)(const char *Info); ++ void (*NewChannel)(const char *Name, bool isRadio, bool isEncrypted, bool isHD); ++ void (*IsFinished)(); ++ void (*SetStatus)(int status); ++}; ++ ++#endif //__WIRBELSCAN_SERVICE_H ++ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/project/VNSI Server.cbp b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/project/VNSI Server.cbp new file mode 100644 index 0000000000..bd059b1c23 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/project/VNSI Server.cbp @@ -0,0 +1,118 @@ + + + + + + diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/project/VNSI Server.layout b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/project/VNSI Server.layout new file mode 100644 index 0000000000..debfd79019 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/project/VNSI Server.layout @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/receiver.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/receiver.c new file mode 100644 index 0000000000..28de2a47b7 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/receiver.c @@ -0,0 +1,1279 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include + +#include +#include + +#include +#include + +#include "config.h" +#include "receiver.h" +#include "cxsocket.h" +#include "vdrcommand.h" +#include "suspend.h" +#include "tools.h" +#include "responsepacket.h" + +#if VDRVERSNUM < 10713 +#ifndef PLUGINPARAMPATCHVERSNUM +#error "You must apply the pluginparam patch for VDR!" +#endif +#endif + +// --- cLiveReceiver ------------------------------------------------- + +class cLiveReceiver: public cReceiver +{ + friend class cLiveStreamer; + +private: + cLiveStreamer *m_Streamer; + +protected: + virtual void Activate(bool On); + virtual void Receive(uchar *Data, int Length); + +public: + cLiveReceiver(cLiveStreamer *Streamer, tChannelID ChannelID, int Priority, const int *Pids); + virtual ~cLiveReceiver(); +}; + +cLiveReceiver::cLiveReceiver(cLiveStreamer *Streamer, tChannelID ChannelID, int Priority, const int *Pids) + : cReceiver(ChannelID, Priority, 0, Pids) + , m_Streamer(Streamer) +{ + LOGCONSOLE("Starting live receiver"); +} + +cLiveReceiver::~cLiveReceiver() +{ + LOGCONSOLE("Killing live receiver"); +} + +void cLiveReceiver::Receive(uchar *Data, int Length) +{ + int p = m_Streamer->Put(Data, Length); + + if (p != Length) + m_Streamer->ReportOverflow(Length - p); +} + +inline void cLiveReceiver::Activate(bool On) +{ + m_Streamer->Activate(On); +} + +// --- cLivePatFilter ---------------------------------------------------- + +class cLivePatFilter : public cFilter +{ +private: + int m_pmtPid; + int m_pmtSid; + int m_pmtVersion; + const cChannel *m_Channel; + cLiveStreamer *m_Streamer; + + int GetPid(SI::PMT::Stream& stream, eStreamType *type, char *langs, int *subtitlingType, int *compositionPageId, int *ancillaryPageId); + void GetLanguage(SI::PMT::Stream& stream, char *langs); + virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length); + +public: + cLivePatFilter(cLiveStreamer *Streamer, const cChannel *Channel); +}; + +cLivePatFilter::cLivePatFilter(cLiveStreamer *Streamer, const cChannel *Channel) +{ + LOGCONSOLE("cStreamdevPatFilter(\"%s\")", Channel->Name()); + m_Channel = Channel; + m_Streamer = Streamer; + m_pmtPid = 0; + m_pmtSid = 0; + m_pmtVersion = -1; + Set(0x00, 0x00); // PAT + +} + +static const char * const psStreamTypes[] = { + "UNKNOWN", + "ISO/IEC 11172 Video", + "ISO/IEC 13818-2 Video", + "ISO/IEC 11172 Audio", + "ISO/IEC 13818-3 Audio", + "ISO/IEC 13818-1 Privete sections", + "ISO/IEC 13818-1 Private PES data", + "ISO/IEC 13512 MHEG", + "ISO/IEC 13818-1 Annex A DSM CC", + "0x09", + "ISO/IEC 13818-6 Multiprotocol encapsulation", + "ISO/IEC 13818-6 DSM-CC U-N Messages", + "ISO/IEC 13818-6 Stream Descriptors", + "ISO/IEC 13818-6 Sections (any type, including private data)", + "ISO/IEC 13818-1 auxiliary", + "ISO/IEC 13818-7 Audio with ADTS transport sytax", + "ISO/IEC 14496-2 Visual (MPEG-4)", + "ISO/IEC 14496-3 Audio with LATM transport syntax", + "0x12", "0x13", "0x14", "0x15", "0x16", "0x17", "0x18", "0x19", "0x1a", + "ISO/IEC 14496-10 Video (MPEG-4 part 10/AVC, aka H.264)", + "", +}; + +void cLivePatFilter::GetLanguage(SI::PMT::Stream& stream, char *langs) +{ + SI::Descriptor *d; + for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) + { + switch (d->getDescriptorTag()) + { + case SI::ISO639LanguageDescriptorTag: + { + SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; + strn0cpy(langs, I18nNormalizeLanguageCode(ld->languageCode), MAXLANGCODE1); + break; + } + default: ; + } + delete d; + } +} + +int cLivePatFilter::GetPid(SI::PMT::Stream& stream, eStreamType *type, char *langs, int *subtitlingType, int *compositionPageId, int *ancillaryPageId) +{ + SI::Descriptor *d; + *langs = 0; + + if (!stream.getPid()) + return 0; + + switch (stream.getStreamType()) + { + case 0x01: // ISO/IEC 11172 Video + case 0x02: // ISO/IEC 13818-2 Video + LOGCONSOLE("cStreamdevPatFilter PMT scanner adding PID %d (%s)\n", stream.getPid(), psStreamTypes[stream.getStreamType()]); + *type = stMPEG2VIDEO; + return stream.getPid(); + case 0x03: // ISO/IEC 11172 Audio + case 0x04: // ISO/IEC 13818-3 Audio + *type = stMPEG2AUDIO; + GetLanguage(stream, langs); + LOGCONSOLE("cStreamdevPatFilter PMT scanner adding PID %d (%s) (%s)\n", stream.getPid(), psStreamTypes[stream.getStreamType()], langs); + return stream.getPid(); +#if 1 + case 0x07: // ISO/IEC 13512 MHEG + case 0x08: // ISO/IEC 13818-1 Annex A DSM CC + case 0x0a: // ISO/IEC 13818-6 Multiprotocol encapsulation + case 0x0b: // ISO/IEC 13818-6 DSM-CC U-N Messages + case 0x0c: // ISO/IEC 13818-6 Stream Descriptors + case 0x0d: // ISO/IEC 13818-6 Sections (any type, including private data) + case 0x0e: // ISO/IEC 13818-1 auxiliary +#endif + case 0x0f: // ISO/IEC 13818-7 Audio with ADTS transport syntax + case 0x10: // ISO/IEC 14496-2 Visual (MPEG-4) + case 0x11: // ISO/IEC 14496-3 Audio with LATM transport syntax + LOGCONSOLE("cStreamdevPatFilter PMT scanner: Not adding PID %d (%s) (skipped)\n", stream.getPid(), psStreamTypes[stream.getStreamType()]); + break; + case 0x1b: // ISO/IEC 14496-10 Video (MPEG-4 part 10/AVC, aka H.264) + LOGCONSOLE("cStreamdevPatFilter PMT scanner adding PID %d (%s)\n", stream.getPid(), psStreamTypes[stream.getStreamType()]); + *type = stH264; + return stream.getPid(); + case 0x05: // ISO/IEC 13818-1 private sections + case 0x06: // ISO/IEC 13818-1 PES packets containing private data + for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) + { + switch (d->getDescriptorTag()) + { + case SI::AC3DescriptorTag: + LOGCONSOLE("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s (%s)\n", stream.getPid(), psStreamTypes[stream.getStreamType()], "AC3", langs); + *type = stAC3; + GetLanguage(stream, langs); + delete d; + return stream.getPid(); + case SI::EnhancedAC3DescriptorTag: + LOGCONSOLE("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s (%s)\n", stream.getPid(), psStreamTypes[stream.getStreamType()], "EAC3", langs); + *type = stEAC3; + GetLanguage(stream, langs); + delete d; + return stream.getPid(); + case SI::DTSDescriptorTag: + LOGCONSOLE("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s (%s)\n", stream.getPid(), psStreamTypes[stream.getStreamType()], "DTS", langs); + *type = stDTS; + GetLanguage(stream, langs); + delete d; + return stream.getPid(); + case SI::AACDescriptorTag: + LOGCONSOLE("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s (%s)\n", stream.getPid(), psStreamTypes[stream.getStreamType()], "AAC", langs); + *type = stAAC; + GetLanguage(stream, langs); + delete d; + return stream.getPid(); + case SI::TeletextDescriptorTag: + LOGCONSOLE("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n", stream.getPid(), psStreamTypes[stream.getStreamType()], "Teletext"); + *type = stTELETEXT; + delete d; + return stream.getPid(); + case SI::SubtitlingDescriptorTag: + { + *type = stDVBSUB; + *langs = 0; + *subtitlingType = 0; + *compositionPageId = 0; + *ancillaryPageId = 0; + SI::SubtitlingDescriptor *sd = (SI::SubtitlingDescriptor *)d; + SI::SubtitlingDescriptor::Subtitling sub; + char *s = langs; + int n = 0; + for (SI::Loop::Iterator it; sd->subtitlingLoop.getNext(sub, it); ) + { + if (sub.languageCode[0]) + { + *subtitlingType = sub.getSubtitlingType(); + *compositionPageId = sub.getCompositionPageId(); + *ancillaryPageId = sub.getAncillaryPageId(); + if (n > 0) + *s++ = '+'; + strn0cpy(s, I18nNormalizeLanguageCode(sub.languageCode), MAXLANGCODE1); + s += strlen(s); + if (n++ > 1) + break; + } + } + delete d; + LOGCONSOLE("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n", stream.getPid(), psStreamTypes[stream.getStreamType()], "DVBSUB"); + return stream.getPid(); + } + default: + LOGCONSOLE("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s (%i)\n", stream.getPid(), psStreamTypes[stream.getStreamType()], "UNKNOWN", d->getDescriptorTag()); + break; + } + delete d; + } + break; + default: + /* This following section handles all the cases where the audio track + * info is stored in PMT user info with stream id >= 0x80 + * we check the registration format identifier to see if it + * holds "AC-3" + */ + if (stream.getStreamType() >= 0x80) + { + bool found = false; + for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) + { + switch (d->getDescriptorTag()) + { + case SI::RegistrationDescriptorTag: + /* unfortunately libsi does not implement RegistrationDescriptor */ + if (d->getLength() >= 4) + { + found = true; + SI::CharArray rawdata = d->getData(); + if (/*rawdata[0] == 5 && rawdata[1] >= 4 && */ + rawdata[2] == 'A' && rawdata[3] == 'C' && + rawdata[4] == '-' && rawdata[5] == '3') + { + LOGCONSOLE("cStreamdevPatFilter PMT scanner: Adding pid %d (type 0x%x) RegDesc len %d (%c%c%c%c)\n", + stream.getPid(), stream.getStreamType(), d->getLength(), rawdata[2], rawdata[3], rawdata[4], rawdata[5]); + *type = stAC3; + delete d; + return stream.getPid(); + } + } + break; + default: + break; + } + delete d; + } + if (!found) + { + LOGCONSOLE("Adding pid %d (type 0x%x) RegDesc not found -> assume AC-3\n", stream.getPid(), stream.getStreamType()); + *type = stAC3; + return stream.getPid(); + } + } + LOGCONSOLE("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s\n", stream.getPid(), psStreamTypes[stream.getStreamType()<0x1c?stream.getStreamType():0], "UNKNOWN"); + break; + } + *type = stNone; + return 0; +} + +void cLivePatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length) +{ + if (Pid == 0x00) + { + if (Tid == 0x00) + { + SI::PAT pat(Data, false); + if (!pat.CheckCRCAndParse()) + return; + SI::PAT::Association assoc; + for (SI::Loop::Iterator it; pat.associationLoop.getNext(assoc, it); ) + { + if (!assoc.isNITPid()) + { + const cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), assoc.getServiceId()); + if (Channel && (Channel == m_Channel)) + { + int prevPmtPid = m_pmtPid; + if (0 != (m_pmtPid = assoc.getPid())) + { + m_pmtSid = assoc.getServiceId(); + if (m_pmtPid != prevPmtPid) + { + Add(m_pmtPid, 0x02); + m_pmtVersion = -1; + } + return; + } + } + } + } + } + } + else if (Pid == m_pmtPid && Tid == SI::TableIdPMT && Source() && Transponder()) + { + SI::PMT pmt(Data, false); + if (!pmt.CheckCRCAndParse()) + return; + if (pmt.getServiceId() != m_pmtSid) + return; // skip broken PMT records + if (m_pmtVersion != -1) + { + if (m_pmtVersion != pmt.getVersionNumber()) + { +// printf("cStreamdevPatFilter: PMT version changed, detaching all pids\n"); + cFilter::Del(m_pmtPid, 0x02); + m_pmtPid = 0; // this triggers PAT scan + } + return; + } + m_pmtVersion = pmt.getVersionNumber(); + + SI::PMT::Stream stream; + int pids[MAXRECEIVEPIDS + 1]; + eStreamType types[MAXRECEIVEPIDS + 1]; + char langs[MAXRECEIVEPIDS + 1][MAXLANGCODE2]; + int subtitlingType[MAXRECEIVEPIDS + 1]; + int compositionPageId[MAXRECEIVEPIDS + 1]; + int ancillaryPageId[MAXRECEIVEPIDS + 1]; + int streams = 0; + for (SI::Loop::Iterator it; pmt.streamLoop.getNext(stream, it); ) + { + eStreamType type; + int pid = GetPid(stream, &type, langs[streams], &subtitlingType[streams], &compositionPageId[streams], &ancillaryPageId[streams]); + if (0 != pid && streams < MAXRECEIVEPIDS) + { + pids[streams] = pid; + types[streams] = type; + streams++; + } + } + pids[streams] = 0; + + int newstreams = 0; + for (int i = 0; i < streams; i++) + { + if (m_Streamer->HaveStreamDemuxer(pids[i], types[i]) == -1) + newstreams++; + } + + if (newstreams > 0) + { + if (m_Streamer->m_Receiver) + { + LOGCONSOLE("Detaching Live Receiver"); + m_Streamer->m_Device->Detach(m_Streamer->m_Receiver); + DELETENULL(m_Streamer->m_Receiver); + } + + for (int idx = 0; idx < MAXRECEIVEPIDS; ++idx) + { + if (m_Streamer->m_Streams[idx]) + { + DELETENULL(m_Streamer->m_Streams[idx]); + m_Streamer->m_Pids[idx] = 0; + } + } + m_Streamer->m_NumStreams = 0; + m_Streamer->m_streamReady = true; + + for (int i = 0; i < streams; i++) + { + switch (types[i]) + { + case stMPEG2AUDIO: + { + m_Streamer->m_Streams[m_Streamer->m_NumStreams] = new cTSDemuxer(m_Streamer, m_Streamer->m_NumStreams, stMPEG2AUDIO, pids[i]); + m_Streamer->m_Pids[m_Streamer->m_NumStreams] = pids[i]; + m_Streamer->m_NumStreams++; + break; + } + case stMPEG2VIDEO: + { + m_Streamer->m_Streams[m_Streamer->m_NumStreams] = new cTSDemuxer(m_Streamer, m_Streamer->m_NumStreams, stMPEG2VIDEO, pids[i]); + m_Streamer->m_Pids[m_Streamer->m_NumStreams] = pids[i]; + m_Streamer->m_NumStreams++; + break; + } + case stH264: + { + m_Streamer->m_Streams[m_Streamer->m_NumStreams] = new cTSDemuxer(m_Streamer, m_Streamer->m_NumStreams, stH264, pids[i]); + m_Streamer->m_Pids[m_Streamer->m_NumStreams] = pids[i]; + m_Streamer->m_NumStreams++; + break; + } + case stAC3: + { + m_Streamer->m_Streams[m_Streamer->m_NumStreams] = new cTSDemuxer(m_Streamer, m_Streamer->m_NumStreams, stAC3, pids[i]); + m_Streamer->m_Pids[m_Streamer->m_NumStreams] = pids[i]; + m_Streamer->m_NumStreams++; + break; + } + case stEAC3: + { + m_Streamer->m_Streams[m_Streamer->m_NumStreams] = new cTSDemuxer(m_Streamer, m_Streamer->m_NumStreams, stEAC3, pids[i]); + m_Streamer->m_Pids[m_Streamer->m_NumStreams] = pids[i]; + m_Streamer->m_NumStreams++; + break; + } + case stDTS: + { + m_Streamer->m_Streams[m_Streamer->m_NumStreams] = new cTSDemuxer(m_Streamer, m_Streamer->m_NumStreams, stDTS, pids[i]); + m_Streamer->m_Pids[m_Streamer->m_NumStreams] = pids[i]; + m_Streamer->m_NumStreams++; + break; + } + case stAAC: + { + m_Streamer->m_Streams[m_Streamer->m_NumStreams] = new cTSDemuxer(m_Streamer, m_Streamer->m_NumStreams, stAAC, pids[i]); + m_Streamer->m_Pids[m_Streamer->m_NumStreams] = pids[i]; + m_Streamer->m_NumStreams++; + break; + } + case stDVBSUB: + { + m_Streamer->m_Streams[m_Streamer->m_NumStreams] = new cTSDemuxer(m_Streamer, m_Streamer->m_NumStreams, stDVBSUB, pids[i]); + m_Streamer->m_Streams[m_Streamer->m_NumStreams]->SetLanguage(langs[i]); + m_Streamer->m_Streams[m_Streamer->m_NumStreams]->SetSubtitlingDescriptor(subtitlingType[i], compositionPageId[i], ancillaryPageId[i]); + m_Streamer->m_Pids[m_Streamer->m_NumStreams] = pids[i]; + m_Streamer->m_NumStreams++; + break; + } + case stTELETEXT: + { + m_Streamer->m_Streams[m_Streamer->m_NumStreams] = new cTSDemuxer(m_Streamer, m_Streamer->m_NumStreams, stTELETEXT, pids[i]); + m_Streamer->m_Pids[m_Streamer->m_NumStreams] = pids[i]; + m_Streamer->m_NumStreams++; + break; + } + } + } + + m_Streamer->m_Receiver = new cLiveReceiver(m_Streamer, m_Channel->GetChannelID(), m_Streamer->m_Priority, m_Streamer->m_Pids); + m_Streamer->m_Device->AttachReceiver(m_Streamer->m_Receiver); + isyslog("VNSI: Currently unknown new streams found, receiver and demuxers reinited\n"); + } + } +} + +// --- cLiveStreamer ------------------------------------------------- + +cLiveStreamer::cLiveStreamer() + : cThread("cLiveStreamer stream processor") + , cRingBufferLinear(MEGABYTE(3), TS_SIZE, true) +{ + m_Channel = NULL; + m_Priority = NULL; + m_Socket = NULL; + m_Device = NULL; + m_Receiver = NULL; + m_PatFilter = NULL; + m_Frontend = -1; + m_NumStreams = 0; + m_streamReady = false; + m_IsAudioOnly = false; + m_IsMPEGPS = false; + m_streamChangeSendet = false; + m_lastInfoSendet = time(NULL); + memset(&m_FrontendInfo, 0, sizeof(m_FrontendInfo)); + for (int idx = 0; idx < MAXRECEIVEPIDS; ++idx) + { + m_Streams[idx] = NULL; + m_Pids[idx] = 0; + } + + SetTimeouts(0, 500); +} + +cLiveStreamer::~cLiveStreamer() +{ + LOGCONSOLE("Started to delete live streamer"); + + Cancel(-1); + + if (m_Device) + { + if (m_Receiver) + { + LOGCONSOLE("Detaching Live Receiver"); + m_Device->Detach(m_Receiver); + } + else + { + LOGCONSOLE("No live receiver present"); + } + + if (m_PatFilter) + { + LOGCONSOLE("Detaching Live Filter"); + m_Device->Detach(m_PatFilter); + } + else + { + LOGCONSOLE("No live filter present"); + } + + for (int idx = 0; idx < MAXRECEIVEPIDS; ++idx) + { + if (m_Streams[idx]) + { + LOGCONSOLE("Deleting stream demuxer %i for pid=%i and type=%i", m_Streams[idx]->GetStreamID(), m_Streams[idx]->GetPID(), m_Streams[idx]->Type()); + DELETENULL(m_Streams[idx]); + m_Pids[idx] = 0; + } + } + + if (m_Receiver) + { + LOGCONSOLE("Deleting Live Receiver"); + DELETENULL(m_Receiver); + } + + if (m_PatFilter) + { + LOGCONSOLE("Deleting Live Filter"); + DELETENULL(m_PatFilter); + } + } + if (m_Frontend >= 0) + { + close(m_Frontend); + m_Frontend = -1; + } + + LOGCONSOLE("Finished to delete live streamer"); +} + +void cLiveStreamer::Action(void) +{ + int signalInfoCnt = 90; + time_t last_data = time(NULL); + int size = 0; + int used = 0; + unsigned char *buf = NULL; + + while (Running()) + { + size = 0; + used = 0; + buf = Get(size); + + if (!m_Receiver->IsAttached()) + { + isyslog("VNSI: returning from streamer thread, receiver is no more attached"); + break; + } + + // no data + if (buf == NULL) + { + if(time(NULL) - last_data > 10*1000) { + isyslog("VNSI: returning from streamer thread, timout on reading data"); + break; + } + continue; + } + + if(size <= TS_SIZE) { + continue; + } + + /* Make sure we are looking at a TS packet */ + while (size > TS_SIZE) + { + if (buf[0] == TS_SYNC_BYTE && buf[TS_SIZE] == TS_SYNC_BYTE) + break; + used++; + buf++; + size--; + } + + while (size >= TS_SIZE) + { + if(!Running()) + { + break; + } + + unsigned int ts_pid = TsPid(buf); + cTSDemuxer *demuxer = FindStreamDemuxer(ts_pid); + if (demuxer) + { + demuxer->ProcessTSPacket(buf); + last_data = time(NULL); + } + + buf += TS_SIZE; + size -= TS_SIZE; + used += TS_SIZE; + } + Del(used); + + /* Additional Information and NO_SIGNAL timers */ + signalInfoCnt++; + + if (time(NULL) - m_lastInfoSendet > 10) + { + m_lastInfoSendet = time(NULL); + sendSignalInfo(); + } + else if (signalInfoCnt >= 100) + { + /* Send stream information every 100 packets expect the first one is sendet + after 10 packets */ + sendStreamInfo(); + signalInfoCnt = 0; + } + } +} + +bool cLiveStreamer::StreamChannel(const cChannel *channel, int priority, cxSocket *Socket, cResponsePacket *resp) +{ + if (channel == NULL) + { + esyslog("VNSI-Error: Starting streaming of channel without valid channel"); + return false; + } + + m_Channel = channel; + m_Priority = priority; + m_Socket = Socket; + m_Device = GetDevice(channel, m_Priority); + if (m_Device != NULL) + { + dsyslog("VNSI: Successfully found following device: %p (%d) for receiving", m_Device, m_Device ? m_Device->CardIndex() + 1 : 0); + + if (m_Device->SwitchChannel(m_Channel, false)) + { + if (m_Channel->Vpid()) + { +#if APIVERSNUM >= 10701 + if (m_Channel->Vtype() == 0x1B) + m_Streams[m_NumStreams] = new cTSDemuxer(this, m_NumStreams, stH264, m_Channel->Vpid()); + else +#endif + m_Streams[m_NumStreams] = new cTSDemuxer(this, m_NumStreams, stMPEG2VIDEO, m_Channel->Vpid()); + + m_Pids[m_NumStreams] = m_Channel->Vpid(); + m_NumStreams++; + } + else + { + /* m_streamReady is set by the Video demuxers, to have always valid stream informations + * like height and width. But if no Video PID is present like for radio channels + * VNSI will deadlock + */ + m_streamReady = true; + m_IsAudioOnly = true; + } + + const int *APids = m_Channel->Apids(); + for ( ; *APids && m_NumStreams < MAXRECEIVEPIDS; APids++) + { + int index = 0; + if (!FindStreamDemuxer(*APids)) + { + m_Pids[m_NumStreams] = *APids; + m_Streams[m_NumStreams] = new cTSDemuxer(this, m_NumStreams, stMPEG2AUDIO, *APids); + m_Streams[m_NumStreams]->SetLanguage(m_Channel->Alang(index)); + m_NumStreams++; + } + index++; + } + + const int *DPids = m_Channel->Dpids(); + for ( ; *DPids && m_NumStreams < MAXRECEIVEPIDS; DPids++) + { + int index = 0; + if (!FindStreamDemuxer(*DPids)) + { + m_Pids[m_NumStreams] = *DPids; + m_Streams[m_NumStreams] = new cTSDemuxer(this, m_NumStreams, stAC3, *DPids); + m_Streams[m_NumStreams]->SetLanguage(m_Channel->Dlang(index)); + m_NumStreams++; + } + index++; + } + + const int *SPids = m_Channel->Spids(); + if (SPids) + { + int index = 0; + for ( ; *SPids && m_NumStreams < MAXRECEIVEPIDS; SPids++) + { + if (!FindStreamDemuxer(*SPids)) + { + m_Pids[m_NumStreams] = *SPids; + m_Streams[m_NumStreams] = new cTSDemuxer(this, m_NumStreams, stDVBSUB, *SPids); + m_Streams[m_NumStreams]->SetLanguage(m_Channel->Slang(index)); +#if APIVERSNUM >= 10709 + m_Streams[m_NumStreams]->SetSubtitlingDescriptor(m_Channel->SubtitlingType(index), + m_Channel->CompositionPageId(index), + m_Channel->AncillaryPageId(index)); +#endif + m_NumStreams++; + } + index++; + } + } + + if (m_Channel->Tpid()) + { + m_Streams[m_NumStreams] = new cTSDemuxer(this, m_NumStreams, stTELETEXT, m_Channel->Tpid()); + m_Pids[m_NumStreams] = m_Channel->Tpid(); + m_NumStreams++; + } + + m_Streams[m_NumStreams] = NULL; + m_Pids[m_NumStreams] = 0; + + /* Send the OK response here, that it is before the Stream end message */ + resp->add_U32(VDR_RET_OK); + resp->finalise(); + m_Socket->write(resp->getPtr(), resp->getLen()); + +#if VDRVERSNUM < 10713 + if (m_Channel && m_Channel->IsPlug()) m_IsMPEGPS = true; +#else + // TODO: rework cPvrSourceParams for use with older vdr + if (m_Channel && ((m_Channel->Source() >> 24) == 'V')) m_IsMPEGPS = true; +#endif + + if (m_NumStreams > 0 && m_Socket) + { + dsyslog("VNSI: Creating new live Receiver"); + m_Receiver = new cLiveReceiver(this, m_Channel->GetChannelID(), m_Priority, m_Pids); + m_PatFilter = new cLivePatFilter(this, m_Channel); + m_Device->AttachReceiver(m_Receiver); + m_Device->AttachFilter(m_PatFilter); + } + + isyslog("VNSI: Successfully switched to channel %i - %s", m_Channel->Number(), m_Channel->Name()); + return true; + } + else + { + dsyslog("VNSI: Can't switch to channel %i - %s", m_Channel->Number(), m_Channel->Name()); + } + } + else + { + esyslog("VNSI-Error: Can't get device for channel %i - %s", m_Channel->Number(), m_Channel->Name()); + } + return false; +} + +cTSDemuxer *cLiveStreamer::FindStreamDemuxer(int Pid) +{ + int idx; + for (idx = 0; idx < m_NumStreams; ++idx) + if (m_Streams[idx] && m_Streams[idx]->GetPID() == Pid) + return m_Streams[idx]; + return NULL; +} + +int cLiveStreamer::HaveStreamDemuxer(int Pid, eStreamType streamType) +{ + int idx; + for (idx = 0; idx < m_NumStreams; ++idx) + if (m_Streams[idx] && (Pid == 0 || m_Streams[idx]->GetPID() == Pid) && m_Streams[idx]->Type() == streamType) + return idx; + return -1; +} + +inline void cLiveStreamer::Activate(bool On) +{ + if (On) + { + LOGCONSOLE("VDR active, sending stream start message"); + m_streamChangeSendet = false; + Start(); + } + else + { + LOGCONSOLE("VDR inactive, sending stream end message"); + Cancel(5); + } +} + +void cLiveStreamer::Attach(void) +{ + LOGCONSOLE("cLiveStreamer::Attach()"); + if (m_Device) + { + if (m_Receiver) + { + m_Device->Detach(m_Receiver); + m_Device->AttachReceiver(m_Receiver); + } + } +} + +void cLiveStreamer::Detach(void) +{ + LOGCONSOLE("cLiveStreamer::Detach()"); + if (m_Device) + { + if (m_Receiver) + m_Device->Detach(m_Receiver); + } +} + +cDevice *cLiveStreamer::GetDevice(const cChannel *Channel, int Priority) +{ + cDevice *device = NULL; + + LOGCONSOLE("+ Statistics:"); + LOGCONSOLE("+ Current Channel: %d", cDevice::CurrentChannel()); + LOGCONSOLE("+ Current Device: %d", cDevice::ActualDevice()->CardIndex()); + LOGCONSOLE("+ Transfer Mode: %s", cDevice::ActualDevice() == cDevice::PrimaryDevice() ? "false" : "true"); + LOGCONSOLE("+ Replaying: %s", cDevice::PrimaryDevice()->Replaying() ? "true" : "false"); + LOGCONSOLE(" * GetDevice(const cChannel*, int)"); + LOGCONSOLE(" * -------------------------------"); + + device = cDevice::GetDevice(Channel, Priority, false); + + LOGCONSOLE(" * Found following device: %p (%d)", device, device ? device->CardIndex() + 1 : 0); + + return device; + /* +#if CONSOLEDEBUG + if (device == cDevice::ActualDevice()) + { + LOGCONSOLE(" * is actual device"); + } + if (!cSuspendCtl::IsActive() && VNSIServerConfig.SuspendMode != smAlways) + { + LOGCONSOLE(" * NOT suspended"); + } +#endif + + if (!device || (device == cDevice::ActualDevice() + && !cSuspendCtl::IsActive() + && VNSIServerConfig.SuspendMode != smAlways)) + { + // mustn't switch actual device + // maybe a device would be free if THIS connection did turn off its streams? + LOGCONSOLE(" * trying again..."); + const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel()); + isyslog("VNSI: Detaching current receiver"); + Detach(); + device = cDevice::GetDevice(Channel, Priority, false); + Attach(); + LOGCONSOLE(" * Found following device: %p (%d)", device, device ? device->CardIndex() + 1 : 0); + +#if CONSOLEDEBUG + if (device == cDevice::ActualDevice()) + { + LOGCONSOLE(" * is actual device"); + } + if (!cSuspendCtl::IsActive() && VNSIServerConfig.SuspendMode != smAlways) + { + LOGCONSOLE(" * NOT suspended"); + } + if (current && !TRANSPONDER(Channel, current)) + { + LOGCONSOLE(" * NOT same transponder"); + } +#endif + + if (device && (device == cDevice::ActualDevice() + && !cSuspendCtl::IsActive() + && VNSIServerConfig.SuspendMode != smAlways + && current != NULL + && !TRANSPONDER(Channel, current))) + { + // now we would have to switch away live tv...let's see if live tv + // can be handled by another device + cDevice *newdev = NULL; + for (int i = 0; i < cDevice::NumDevices(); ++i) + { + cDevice *dev = cDevice::GetDevice(i); + if (dev->ProvidesChannel(current, 0) && dev != device) + { + newdev = dev; + break; + } + } + LOGCONSOLE(" * Found device for live tv: %p (%d)", newdev, newdev ? newdev->CardIndex() + 1 : 0); + if (newdev == NULL || newdev == device) + // no suitable device to continue live TV, giving up... + device = NULL; + else + newdev->SwitchChannel(current, true); + } + } + + return device;*/ +} + +void cLiveStreamer::sendStreamPacket(sStreamPacket *pkt) +{ + if (!m_streamChangeSendet) + { + sendStreamChange(); + m_streamChangeSendet = true; + } + + if (pkt) + { +#if 0 + LOGCONSOLE("sendet: %d %d %10lu %10lu %10d %10d", pkt->id, pkt->frametype, pkt->dts, pkt->pts, pkt->duration, pkt->size); +#endif + uint32_t bufferLength = sizeof(uint32_t) * 5 + sizeof(int64_t) * 2; + uint8_t buffer[bufferLength]; + *(uint32_t*)&buffer[0] = htonl(CHANNEL_STREAM); // stream channel + *(uint32_t*)&buffer[4] = htonl(VDR_STREAM_MUXPKT); // Stream packet operation code + *(uint32_t*)&buffer[8] = htonl(pkt->id); // Stream ID + *(uint32_t*)&buffer[12] = htonl(pkt->duration); // Duration + *(int64_t*) &buffer[16] = htonll(pkt->pts); // DTS + *(int64_t*) &buffer[24] = htonll(pkt->dts); // PTS + *(uint32_t*)&buffer[32] = htonl(pkt->size); // Data length + m_Socket->write(&buffer, bufferLength); + m_Socket->write(pkt->data, pkt->size); + } +} + +void cLiveStreamer::sendStreamChange() +{ + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initStream(VDR_STREAM_CHANGE, 0, 0, 0, 0)) + { + esyslog("VNSI-Error: stream response packet init fail"); + delete resp; + return; + } + + for (int idx = 0; idx < m_NumStreams; ++idx) + { + if (m_Streams[idx]) + { + resp->add_U32(m_Streams[idx]->GetStreamID()); + if (m_Streams[idx]->Type() == stMPEG2AUDIO) + { + resp->add_String("MPEG2AUDIO"); + resp->add_String(m_Streams[idx]->GetLanguage()); + } + else if (m_Streams[idx]->Type() == stMPEG2VIDEO) + { + resp->add_String("MPEG2VIDEO"); + resp->add_U32(m_Streams[idx]->GetFpsScale()); + resp->add_U32(m_Streams[idx]->GetFpsRate()); + resp->add_U32(m_Streams[idx]->GetHeight()); + resp->add_U32(m_Streams[idx]->GetWidth()); + resp->add_double(m_Streams[idx]->GetAspect()); + } + else if (m_Streams[idx]->Type() == stAC3) + { + resp->add_String("AC3"); + resp->add_String(m_Streams[idx]->GetLanguage()); + } + else if (m_Streams[idx]->Type() == stH264) + { + resp->add_String("H264"); + resp->add_U32(m_Streams[idx]->GetFpsScale()); + resp->add_U32(m_Streams[idx]->GetFpsRate()); + resp->add_U32(m_Streams[idx]->GetHeight()); + resp->add_U32(m_Streams[idx]->GetWidth()); + resp->add_double(m_Streams[idx]->GetAspect()); + } + else if (m_Streams[idx]->Type() == stDVBSUB) + { + resp->add_String("DVBSUB"); + resp->add_String(m_Streams[idx]->GetLanguage()); + resp->add_U32(m_Streams[idx]->CompositionPageId()); + resp->add_U32(m_Streams[idx]->AncillaryPageId()); + } + else if (m_Streams[idx]->Type() == stTELETEXT) + resp->add_String("TELETEXT"); + else if (m_Streams[idx]->Type() == stAAC) + { + resp->add_String("AAC"); + resp->add_String(m_Streams[idx]->GetLanguage()); + } + else if (m_Streams[idx]->Type() == stEAC3) + { + resp->add_String("EAC3"); + resp->add_String(m_Streams[idx]->GetLanguage()); + } + else if (m_Streams[idx]->Type() == stDTS) + { + resp->add_String("DTS"); + resp->add_String(m_Streams[idx]->GetLanguage()); + } + } + } + + resp->finaliseStream(); + m_Socket->write(resp->getPtr(), resp->getLen()); + delete resp; +} + +#define MINSIGNALSTRENGTH 16383 +void cLiveStreamer::sendSignalInfo() +{ + /* If no frontend is found m_Frontend is set to -2, in this case + return a empty signalinfo package */ + if (m_Frontend == -2) + { + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initStream(VDR_STREAM_SIGNALINFO, 0, 0, 0, 0)) + { + esyslog("VNSI-Error: stream response packet init fail"); + delete resp; + return; + } + + resp->add_String(*cString::sprintf("Unknown")); + resp->add_String(*cString::sprintf("Unknown")); + resp->add_U32(0); + resp->add_U32(0); + resp->add_U32(0); + resp->add_U32(0); + + resp->finaliseStream(); + m_Socket->write(resp->getPtr(), resp->getLen()); + delete resp; + return; + } + +#if VDRVERSNUM < 10713 + if (m_Channel && m_Channel->IsPlug()) +#else + // TODO: rework cPvrSourceParams for use with older vdr + if (m_Channel && ((m_Channel->Source() >> 24) == 'V')) +#endif + { + struct v4l2_tuner tuner; + if (m_Frontend < 0) + { + for (int i = 0; i < 8; i++) + { + m_DeviceString = cString::sprintf("/dev/video%d", i); + m_Frontend = open(m_DeviceString, O_RDONLY | O_NONBLOCK); + if (m_Frontend >= 0) + { + if (ioctl(m_Frontend, VIDIOC_QUERYCAP, &m_vcap) < 0) + { + esyslog("VNSI-Error: cannot read analog frontend info."); + close(m_Frontend); + m_Frontend = -1; + memset(&m_vcap, 0, sizeof(m_vcap)); + continue; + } + break; + } + } + if (m_Frontend < 0) + m_Frontend = -2; + } + + if (m_Frontend >= 0) + { + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initStream(VDR_STREAM_SIGNALINFO, 0, 0, 0, 0)) + { + esyslog("VNSI-Error: stream response packet init fail"); + delete resp; + return; + } +/* + memset(&tuner, 0, sizeof(tuner)); + tuner.index = 0; + tuner.type = V4L2_TUNER_ANALOG_TV; + + if (ioctl(m_Frontend, VIDIOC_G_TUNER, &tuner) == 0) + { + int timeout = 1000; + while (timeout > 0) + { + cCondWait::SleepMs(10); + timeout -= 10; + ioctl(m_Frontend, VIDIOC_G_TUNER, &tuner); + if (tuner.signal > MINSIGNALSTRENGTH) + { + break; + } + } + } +*/ + resp->add_String(*cString::sprintf("Analog #%s - %s (%s)", *m_DeviceString, (char *) m_vcap.card, m_vcap.driver)); +// resp->add_String(*cString::sprintf("%s", (tuner.signal > MINSIGNALSTRENGTH) ? "LOCKED" : "-")); + resp->add_String(""); + resp->add_U32(0); + resp->add_U32(0); +// resp->add_U32(tuner.signal); + resp->add_U32(0); + resp->add_U32(0); + + resp->finaliseStream(); + m_Socket->write(resp->getPtr(), resp->getLen()); + delete resp; + } + } + else + { + if (m_Frontend < 0) + { + m_DeviceString = cString::sprintf(FRONTEND_DEVICE, m_Device->CardIndex(), 0); + m_Frontend = open(m_DeviceString, O_RDONLY | O_NONBLOCK); + if (m_Frontend >= 0) + { + if (ioctl(m_Frontend, FE_GET_INFO, &m_FrontendInfo) < 0) + { + esyslog("VNSI-Error: cannot read frontend info."); + close(m_Frontend); + m_Frontend = -2; + memset(&m_FrontendInfo, 0, sizeof(m_FrontendInfo)); + return; + } + } + } + + if (m_Frontend >= 0) + { + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initStream(VDR_STREAM_SIGNALINFO, 0, 0, 0, 0)) + { + esyslog("VNSI-Error: stream response packet init fail"); + delete resp; + return; + } + + fe_status_t status; + uint16_t fe_snr; + uint16_t fe_signal; + uint32_t fe_ber; + uint32_t fe_unc; + + memset(&status, 0, sizeof(status)); + ioctl(m_Frontend, FE_READ_STATUS, &status); + + if (ioctl(m_Frontend, FE_READ_SIGNAL_STRENGTH, &fe_signal) == -1) + fe_signal = -2; + if (ioctl(m_Frontend, FE_READ_SNR, &fe_snr) == -1) + fe_snr = -2; + if (ioctl(m_Frontend, FE_READ_BER, &fe_ber) == -1) + fe_ber = -2; + if (ioctl(m_Frontend, FE_READ_UNCORRECTED_BLOCKS, &fe_unc) == -1) + fe_unc = -2; + + switch (m_Channel->Source() & cSource::st_Mask) + { + case cSource::stSat: + resp->add_String(*cString::sprintf("DVB-S%s #%d - %s", (m_FrontendInfo.caps & 0x10000000) ? "2" : "", cDevice::ActualDevice()->CardIndex(), m_FrontendInfo.name)); + break; + case cSource::stCable: + resp->add_String(*cString::sprintf("DVB-C #%d - %s", cDevice::ActualDevice()->CardIndex(), m_FrontendInfo.name)); + break; + case cSource::stTerr: + resp->add_String(*cString::sprintf("DVB-T #%d - %s", cDevice::ActualDevice()->CardIndex(), m_FrontendInfo.name)); + break; + } + resp->add_String(*cString::sprintf("%s:%s:%s:%s:%s", (status & FE_HAS_LOCK) ? "LOCKED" : "-", (status & FE_HAS_SIGNAL) ? "SIGNAL" : "-", (status & FE_HAS_CARRIER) ? "CARRIER" : "-", (status & FE_HAS_VITERBI) ? "VITERBI" : "-", (status & FE_HAS_SYNC) ? "SYNC" : "-")); + resp->add_U32(fe_snr); + resp->add_U32(fe_signal); + resp->add_U32(fe_ber); + resp->add_U32(fe_unc); + + resp->finaliseStream(); + m_Socket->write(resp->getPtr(), resp->getLen()); + delete resp; + } + } +} + +void cLiveStreamer::sendStreamInfo() +{ + cResponsePacket *resp = new cResponsePacket(); + if (!resp->initStream(VDR_STREAM_CONTENTINFO, 0, 0, 0, 0)) + { + esyslog("VNSI-Error: stream response packet init fail"); + delete resp; + return; + } + + for (int idx = 0; idx < m_NumStreams; ++idx) + { + if (m_Streams[idx]) + { + if (m_Streams[idx]->Type() == stMPEG2AUDIO || + m_Streams[idx]->Type() == stAC3 || + m_Streams[idx]->Type() == stEAC3 || + m_Streams[idx]->Type() == stDTS || + m_Streams[idx]->Type() == stAAC) + { + resp->add_U32(m_Streams[idx]->GetStreamID()); + resp->add_String(m_Streams[idx]->GetLanguage()); + resp->add_U32(m_Streams[idx]->GetChannels()); + resp->add_U32(m_Streams[idx]->GetSampleRate()); + resp->add_U32(m_Streams[idx]->GetBlockAlign()); + resp->add_U32(m_Streams[idx]->GetBitRate()); + resp->add_U32(m_Streams[idx]->GetBitsPerSample()); + } + else if (m_Streams[idx]->Type() == stMPEG2VIDEO || m_Streams[idx]->Type() == stH264) + { + resp->add_U32(m_Streams[idx]->GetStreamID()); + resp->add_U32(m_Streams[idx]->GetFpsScale()); + resp->add_U32(m_Streams[idx]->GetFpsRate()); + resp->add_U32(m_Streams[idx]->GetHeight()); + resp->add_U32(m_Streams[idx]->GetWidth()); + resp->add_double(m_Streams[idx]->GetAspect()); + } + else if (m_Streams[idx]->Type() == stDVBSUB) + { + resp->add_U32(m_Streams[idx]->GetStreamID()); + resp->add_String(m_Streams[idx]->GetLanguage()); + resp->add_U32(m_Streams[idx]->CompositionPageId()); + resp->add_U32(m_Streams[idx]->AncillaryPageId()); + } + } + } + + resp->finaliseStream(); + m_Socket->write(resp->getPtr(), resp->getLen()); + delete resp; +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/receiver.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/receiver.h new file mode 100644 index 0000000000..16c273edc2 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/receiver.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef VNSIRECEIVER_H +#define VNSIRECEIVER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "demuxer.h" + +class cxSocket; +class cChannel; +class cLiveReceiver; +class cTSDemuxer; +class cResponsePacket; +class cLivePatFilter; + +class cLiveStreamer : public cThread + , public cRingBufferLinear +{ +private: + friend class cParser; + friend class cLivePatFilter; + + cDevice *GetDevice(const cChannel *Channel, int Priority); + void Detach(void); + void Attach(void); + cTSDemuxer *FindStreamDemuxer(int Pid); + + void sendStreamPacket(sStreamPacket *pkt); + void sendStreamChange(); + void sendSignalInfo(); + void sendStreamInfo(); + + const cChannel *m_Channel; /*!> Channel to stream */ + cDevice *m_Device; /*!> The receiving device the channel depents to */ + cLiveReceiver *m_Receiver; /*!> Our stream transceiver */ + cLivePatFilter *m_PatFilter; /*!> Filter processor to get changed pid's */ + int m_Priority; /*!> The priority over other streamers */ + int m_Pids[MAXRECEIVEPIDS + 1]; /*!> PID for cReceiver also as extra array */ + cTSDemuxer *m_Streams[MAXRECEIVEPIDS + 1]; /*!> Stream information data (partly filled, rest is done by cLiveReceiver */ + int m_NumStreams; /*!> Number of streams selected */ + cxSocket *m_Socket; /*!> The socket class to communicate with client */ + int m_Frontend; /*!> File descriptor to access used receiving device */ + dvb_frontend_info m_FrontendInfo; /*!> DVB Information about the receiving device (DVB only) */ + v4l2_capability m_vcap; /*!> PVR Information about the receiving device (pvrinput only) */ + cString m_DeviceString; /*!> The name of the receiving device */ + time_t m_lastInfoSendet; /*!> Last queue status report sent */ + bool m_streamChangeSendet; /*!> Is false until the stream change message is sendet (no packets are sendet until this is set) */ + bool m_streamReady; /*!> Set by the video demuxer after we got video information */ + bool m_IsAudioOnly; /*!> Set to true if streams contains only audio */ + bool m_IsMPEGPS; /*!> TS Stream contains MPEG PS data like from pvrinput */ + +protected: + virtual void Action(void); + +public: + cLiveStreamer(); + virtual ~cLiveStreamer(); + + void Activate(bool On); + + bool StreamChannel(const cChannel *channel, int priority, cxSocket *Socket, cResponsePacket *resp); + void SetReady() { m_streamReady = true; } + bool IsReady() { return m_streamReady; } + bool IsAudioOnly() { return m_IsAudioOnly; } + bool IsMPEGPS() { return m_IsMPEGPS; } + int HaveStreamDemuxer(int Pid, eStreamType streamType); +}; + +#endif /* VNSIRECEIVER_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/recplayer.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/recplayer.c new file mode 100644 index 0000000000..7156b1042d --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/recplayer.c @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2004-2005 Chris Tallon + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * This code is taken from VOMP for VDR plugin. + */ + +#include "recplayer.h" + +#define _XOPEN_SOURCE 600 +#include + +cRecPlayer::cRecPlayer(cRecording* rec) +{ + m_file = NULL; + m_fileOpen = -1; + m_lastPosition = 0; + m_recordingFilename = strdup(rec->FileName()); + + // FIXME find out max file path / name lengths +#if VDRVERSNUM < 10703 + m_pesrecording = false; + m_indexFile = new cIndexFile(m_recordingFilename, false); +#else + m_pesrecording = rec->IsPesRecording(); + m_indexFile = new cIndexFile(m_recordingFilename, false, m_pesrecording); +#endif + esyslog("VNSI-Error: Failed to create indexfile!"); + + scan(); +} + +void cRecPlayer::cleanup() { + for(int i = 0; i != m_segments.Size(); i++) { + delete m_segments[i]; + } + m_segments.Clear(); +} + +void cRecPlayer::scan() +{ + if (m_file) fclose(m_file); + m_totalLength = 0; + m_fileOpen = -1; + m_totalFrames = 0; + + cleanup(); + + for(int i = 0; i < 65535; i++) // i think we only need one possible loop + { + fileNameFromIndex(i); + LOGCONSOLE("FILENAME: %s", m_fileName); + m_file = fopen(m_fileName, "r"); + if (!m_file) break; + + cSegment* s = new cSegment(); + s->start = m_totalLength; + fseek(m_file, 0, SEEK_END); + m_totalLength += ftell(m_file); + m_totalFrames = m_indexFile->Last(); + s->end = m_totalLength; + m_segments.Append(s); + LOGCONSOLE("File %i found, totalLength now %llu, numFrames = %lu", i, m_totalLength, m_totalFrames); + fclose(m_file); + } + + m_file = NULL; +} + +cRecPlayer::~cRecPlayer() +{ + LOGCONSOLE("destructor"); + cleanup(); + if (m_file) fclose(m_file); + free(m_recordingFilename); +} + +char* cRecPlayer::fileNameFromIndex(int index) { + if (m_pesrecording) + snprintf(m_fileName, sizeof(m_fileName), "%s/%03i.vdr", m_recordingFilename, index+1); + else + snprintf(m_fileName, sizeof(m_fileName), "%s/%05i.ts", m_recordingFilename, index+1); + + return m_fileName; +} +bool cRecPlayer::openFile(int index) +{ + if (m_file) fclose(m_file); + + fileNameFromIndex(index); + LOGCONSOLE("openFile called for index %i string:%s", index, m_fileName); + + m_file = fopen(m_fileName, "r"); + if (!m_file) + { + LOGCONSOLE("file failed to open"); + m_fileOpen = -1; + return false; + } + m_fileOpen = index; + return true; +} + +uint64_t cRecPlayer::getLengthBytes() +{ + return m_totalLength; +} + +uint32_t cRecPlayer::getLengthFrames() +{ + return m_totalFrames; +} + +unsigned long cRecPlayer::getBlock(unsigned char* buffer, uint64_t position, unsigned long amount) +{ + if ((amount > m_totalLength) || (amount > 500000)) + { + LOGCONSOLE("Amount %lu requested and rejected", amount); + return 0; + } + + if (position >= m_totalLength) + { + LOGCONSOLE("Client asked for data starting past end of recording!"); + return 0; + } + + if ((position + amount) > m_totalLength) + { + LOGCONSOLE("Client asked for some data past the end of recording, adjusting amount"); + amount = m_totalLength - position; + } + + // work out what block position is in + int segmentNumber; + for(segmentNumber = 0; segmentNumber < m_segments.Size(); segmentNumber++) + { + if ((position >= m_segments[segmentNumber]->start) && (position < m_segments[segmentNumber]->end)) break; + // position is in this block + } + + // we could be seeking around + if (segmentNumber != m_fileOpen) + { + if (!openFile(segmentNumber)) return 0; + } + + uint64_t currentPosition = position; + uint32_t yetToGet = amount; + uint32_t got = 0; + uint32_t getFromThisSegment = 0; + uint32_t filePosition; + + while(got < amount) + { + if (got) + { + // if(got) then we have already got some and we are back around + // advance the file pointer to the next file + if (!openFile(++segmentNumber)) return 0; + } + + // is the request completely in this block? + if ((currentPosition + yetToGet) <= m_segments[segmentNumber]->end) + getFromThisSegment = yetToGet; + else + getFromThisSegment = m_segments[segmentNumber]->end - currentPosition; + + filePosition = currentPosition - m_segments[segmentNumber]->start; + fseek(m_file, filePosition, SEEK_SET); + fread(&buffer[got], getFromThisSegment, 1, m_file); + + // Tell linux not to bother keeping the data in the FS cache + posix_fadvise(m_file->_fileno, filePosition, getFromThisSegment, POSIX_FADV_DONTNEED); + + got += getFromThisSegment; + currentPosition += getFromThisSegment; + yetToGet -= getFromThisSegment; + } + + m_lastPosition = position; + return got; +} + +uint64_t cRecPlayer::getLastPosition() +{ + return m_lastPosition; +} + +/*cRecording* cRecPlayer::getCurrentRecording() +{ + return NULL; +}*/ + +uint64_t cRecPlayer::positionFromFrameNumber(uint32_t frameNumber) +{ + if (!m_indexFile) return 0; +#if VDRVERSNUM < 10703 + unsigned char retFileNumber; + int retFileOffset; + unsigned char retPicType; +#else + uint16_t retFileNumber; + off_t retFileOffset; + bool retPicType; +#endif + int retLength; + + + if (!m_indexFile->Get((int)frameNumber, &retFileNumber, &retFileOffset, &retPicType, &retLength)) + { + return 0; + } + +// LOGCONSOLE("FN: %u FO: %i", retFileNumber, retFileOffset); + if (retFileNumber >= m_segments.Size()) return 0; +// if (!m_segments[retFileNumber]) return 0; + uint64_t position = m_segments[retFileNumber]->start + retFileOffset; +// LOGCONSOLE("Pos: %llu", position); + + return position; +} + +uint32_t cRecPlayer::frameNumberFromPosition(uint64_t position) +{ + if (!m_indexFile) return 0; + + if (position >= m_totalLength) + { + LOGCONSOLE("Client asked for data starting past end of recording!"); + return 0; + } + + unsigned char segmentNumber; + for(segmentNumber = 0; segmentNumber < m_segments.Size(); segmentNumber++) + { + if ((position >= m_segments[segmentNumber]->start) && (position < m_segments[segmentNumber]->end)) break; + // position is in this block + } + uint32_t askposition = position - m_segments[segmentNumber]->start; + return m_indexFile->Get((int)segmentNumber, askposition); +} + + +bool cRecPlayer::getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength) +{ + // 0 = backwards + // 1 = forwards + + if (!m_indexFile) return false; + +#if VDRVERSNUM < 10703 + unsigned char waste1; + int waste2; +#else + uint16_t waste1; + off_t waste2; +#endif + + int iframeLength; + int indexReturnFrameNumber; + + indexReturnFrameNumber = (uint32_t)m_indexFile->GetNextIFrame(frameNumber, (direction==1 ? true : false), &waste1, &waste2, &iframeLength); + LOGCONSOLE("GNIF input framenumber:%lu, direction=%lu, output:framenumber=%i, framelength=%i", frameNumber, direction, indexReturnFrameNumber, iframeLength); + + if (indexReturnFrameNumber == -1) return false; + + *rfilePosition = positionFromFrameNumber(indexReturnFrameNumber); + *rframeNumber = (uint32_t)indexReturnFrameNumber; + *rframeLength = (uint32_t)iframeLength; + + return true; +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/recplayer.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/recplayer.h new file mode 100644 index 0000000000..d748100db3 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/recplayer.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2004-2005 Chris Tallon + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * This code is taken from VOMP for VDR plugin. + */ + +#ifndef RECPLAYER_H +#define RECPLAYER_H + +#include +#include +#include + +#include "config.h" + +class cSegment +{ + public: + uint64_t start; + uint64_t end; +}; + +class cRecPlayer +{ +public: + cRecPlayer(cRecording* rec); + ~cRecPlayer(); + uint64_t getLengthBytes(); + uint32_t getLengthFrames(); + unsigned long getBlock(unsigned char* buffer, uint64_t position, unsigned long amount); + bool openFile(int index); + uint64_t getLastPosition(); + void scan(); + uint64_t positionFromFrameNumber(uint32_t frameNumber); + uint32_t frameNumberFromPosition(uint64_t position); + bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength); + +private: + void cleanup(); + char* fileNameFromIndex(int index); + void checkBufferSize(int s); + + char m_fileName[512]; + cIndexFile *m_indexFile; + FILE *m_file; + int m_fileOpen; + cVector m_segments; + uint64_t m_totalLength; + uint64_t m_lastPosition; + uint32_t m_totalFrames; + char *m_recordingFilename; + bool m_pesrecording; +}; + +#endif diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/requestpacket.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/requestpacket.c new file mode 100644 index 0000000000..64c9ae9fa3 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/requestpacket.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include + +#include "config.h" +#include "requestpacket.h" +#include "server.h" +#include "vdrcommand.h" +#include "tools.h" + +cRequestPacket::cRequestPacket(uint32_t requestID, uint32_t opcode, uint8_t* data, uint32_t dataLength, cConnection *cli) + : requestID(requestID), opCode(opcode), userData(data), userDataLength(dataLength), client(cli) +{ + packetPos = 0; + ownBlock = true; + channelID = 0; + streamID = 0; + flag = 0; +} + +cRequestPacket::~cRequestPacket() +{ + if (!ownBlock) return; // don't free if it's a getblock + + if (userData) free(userData); +} + +bool cRequestPacket::end() +{ + return (packetPos >= userDataLength); +} + +int cRequestPacket::serverError() +{ + if ((packetPos == 0) && (userDataLength == 4) && !ntohl(*(uint32_t*)userData)) return 1; + else return 0; +} + +char* cRequestPacket::extract_String() +{ + if (serverError()) return NULL; + + int length = strlen((char*)&userData[packetPos]); + if ((packetPos + length) > userDataLength) return NULL; + char* str = new char[length + 1]; + strcpy(str, (char*)&userData[packetPos]); + packetPos += length + 1; + return str; +} + +uint8_t cRequestPacket::extract_U8() +{ + if ((packetPos + sizeof(uint8_t)) > userDataLength) return 0; + uint8_t uc = userData[packetPos]; + packetPos += sizeof(uint8_t); + return uc; +} + +uint32_t cRequestPacket::extract_U32() +{ + if ((packetPos + sizeof(uint32_t)) > userDataLength) return 0; + uint32_t ul = ntohl(*(uint32_t*)&userData[packetPos]); + packetPos += sizeof(uint32_t); + return ul; +} + +uint64_t cRequestPacket::extract_U64() +{ + if ((packetPos + sizeof(uint64_t)) > userDataLength) return 0; + uint64_t ull = ntohll(*(uint64_t*)&userData[packetPos]); + packetPos += sizeof(uint64_t); + return ull; +} + +double cRequestPacket::extract_Double() +{ + if ((packetPos + sizeof(uint64_t)) > userDataLength) return 0; + uint64_t ull = ntohll(*(uint64_t*)&userData[packetPos]); + double d; + memcpy(&d,&ull,sizeof(double)); + packetPos += sizeof(uint64_t); + return d; +} + +int32_t cRequestPacket::extract_S32() +{ + if ((packetPos + sizeof(int32_t)) > userDataLength) return 0; + int32_t l = ntohl(*(int32_t*)&userData[packetPos]); + packetPos += sizeof(int32_t); + return l; +} + +uint8_t* cRequestPacket::getData() +{ + ownBlock = false; + return userData; +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/requestpacket.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/requestpacket.h new file mode 100644 index 0000000000..c1c37de0a0 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/requestpacket.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef REQUEST_PACKET_H +#define REQUEST_PACKET_H + +class cConnection; + +class cRequestPacket +{ +public: + cRequestPacket(uint32_t requestID, uint32_t opcode, uint8_t* data, uint32_t dataLength, cConnection *cli); + ~cRequestPacket(); + + int serverError(); + + uint32_t getDataLength() { return userDataLength; } + uint32_t getChannelID() { return channelID; } + uint32_t getRequestID() { return requestID; } + uint32_t getStreamID() { return streamID; } + uint32_t getFlag() { return flag; } + uint32_t getOpCode() { return opCode; } + cConnection *getClient() { return client; } + + char* extract_String(); + uint8_t extract_U8(); + uint32_t extract_U32(); + uint64_t extract_U64(); + int32_t extract_S32(); + double extract_Double(); + + bool end(); + + // If you call this, the memory becomes yours. Free with free() + uint8_t* getData(); + +private: + uint8_t* userData; + uint32_t userDataLength; + uint32_t packetPos; + uint32_t opCode; + + uint32_t channelID; + + uint32_t requestID; + uint32_t streamID; + + uint32_t flag; // stream only + + bool ownBlock; + cConnection *client; +}; + +#endif /* REQUEST_PACKET_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/responsepacket.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/responsepacket.c new file mode 100644 index 0000000000..fe9b757016 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/responsepacket.c @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2007 Chris Tallon + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * This code is taken from VOMP for VDR plugin. + */ + +#include +#include +#include + +#include "responsepacket.h" +#include "vdrcommand.h" +#include "config.h" +#include "tools.h" + +/* Packet format for an RR channel response: + +4 bytes = channel ID = 1 (request/response channel) +4 bytes = request ID (from serialNumber) +4 bytes = length of the rest of the packet +? bytes = rest of packet. depends on packet +*/ + +cResponsePacket::cResponsePacket() +{ + buffer = NULL; + bufSize = 0; + bufUsed = 0; +} + +cResponsePacket::~cResponsePacket() +{ + if (buffer) free(buffer); +} + +bool cResponsePacket::init(uint32_t requestID) +{ + if (buffer == NULL) { + bufSize = 512; + buffer = (uint8_t*)malloc(bufSize); + } + + *(uint32_t*)&buffer[0] = htonl(CHANNEL_REQUEST_RESPONSE); // RR channel + *(uint32_t*)&buffer[4] = htonl(requestID); + *(uint32_t*)&buffer[userDataLenPos] = 0; + bufUsed = headerLength; + + return true; +} + +bool cResponsePacket::initScan(uint32_t opCode) +{ + if(buffer == NULL) { + bufSize = 512; + buffer = (uint8_t*)malloc(bufSize); + } + + *(uint32_t*)&buffer[0] = htonl(CHANNEL_SCAN); // RR channel + *(uint32_t*)&buffer[4] = htonl(opCode); + *(uint32_t*)&buffer[userDataLenPos] = 0; + bufUsed = headerLength; + + return true; +} + +bool cResponsePacket::initStatus(uint32_t opCode) +{ + if(buffer == NULL) { + bufSize = 512; + buffer = (uint8_t*)malloc(bufSize); + } + + *(uint32_t*)&buffer[0] = htonl(CHANNEL_STATUS); // RR channel + *(uint32_t*)&buffer[4] = htonl(opCode); + *(uint32_t*)&buffer[userDataLenPos] = 0; + bufUsed = headerLength; + + return true; +} + +bool cResponsePacket::initStream(uint32_t opCode, uint32_t streamID, uint32_t duration, int64_t dts, int64_t pts) +{ + if(buffer == NULL) { + bufSize = 512; + buffer = (uint8_t*)malloc(bufSize); + } + + *(uint32_t*)&buffer[0] = htonl(CHANNEL_STREAM); // stream channel + *(uint32_t*)&buffer[4] = htonl(opCode); // Stream packet operation code + *(uint32_t*)&buffer[8] = htonl(streamID); // Stream ID (unused here) + *(uint32_t*)&buffer[12] = htonl(duration); // Duration (unused here) + *(int64_t*) &buffer[16] = htonl(dts); // DTS (unused here) + *(int64_t*) &buffer[24] = htonl(pts); // PTS (unused here) + *(uint32_t*)&buffer[userDataLenPosStream] = 0; + bufUsed = headerLengthStream; + + return true; +} + +void cResponsePacket::finalise() +{ + *(uint32_t*)&buffer[userDataLenPos] = htonl(bufUsed - headerLength); + //Log::getInstance()->log("Client", Log::DEBUG, "RP finalise %lu", bufUsed - headerLength); +} + +void cResponsePacket::finaliseStream() +{ + *(uint32_t*)&buffer[userDataLenPosStream] = htonl(bufUsed - headerLengthStream); +} + + +bool cResponsePacket::copyin(const uint8_t* src, uint32_t len) +{ + if (!checkExtend(len)) return false; + memcpy(buffer + bufUsed, src, len); + bufUsed += len; + return true; +} + +uint8_t* cResponsePacket::reserve(uint32_t len) { + if (!checkExtend(len)) return false; + uint8_t* result = buffer + bufUsed; + bufUsed += len; + return result; +} + +bool cResponsePacket::unreserve(uint32_t len) { + if(bufUsed < len) return false; + bufUsed -= len; + return true; +} + +bool cResponsePacket::add_String(const char* string) +{ + uint32_t len = strlen(string) + 1; + if (!checkExtend(len)) return false; + memcpy(buffer + bufUsed, string, len); + bufUsed += len; + return true; +} + +bool cResponsePacket::add_U32(uint32_t ul) +{ + if (!checkExtend(sizeof(uint32_t))) return false; + *(uint32_t*)&buffer[bufUsed] = htonl(ul); + bufUsed += sizeof(uint32_t); + return true; +} + +bool cResponsePacket::add_U8(uint8_t c) +{ + if (!checkExtend(sizeof(uint8_t))) return false; + buffer[bufUsed] = c; + bufUsed += sizeof(uint8_t); + return true; +} + +bool cResponsePacket::add_S32(int32_t l) +{ + if (!checkExtend(sizeof(int32_t))) return false; + *(int32_t*)&buffer[bufUsed] = htonl(l); + bufUsed += sizeof(int32_t); + return true; +} + +bool cResponsePacket::add_U64(uint64_t ull) +{ + if (!checkExtend(sizeof(uint64_t))) return false; + *(uint64_t*)&buffer[bufUsed] = htonll(ull); + bufUsed += sizeof(uint64_t); + return true; +} + +bool cResponsePacket::add_double(double d) +{ + if (!checkExtend(sizeof(double))) return false; + uint64_t ull; + memcpy(&ull,&d,sizeof(double)); + *(uint64_t*)&buffer[bufUsed] = htonll(ull); + bufUsed += sizeof(uint64_t); + return true; +} + + +bool cResponsePacket::checkExtend(uint32_t by) +{ + if ((bufUsed + by) < bufSize) return true; + if (512 > by) by = 512; + uint8_t* newBuf = (uint8_t*)realloc(buffer, bufSize + by); + if (!newBuf) return false; + buffer = newBuf; + bufSize += by; + return true; +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/responsepacket.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/responsepacket.h new file mode 100644 index 0000000000..36edf138d0 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/responsepacket.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 Chris Tallon + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * This code is taken from VOMP for VDR plugin. + */ + +#ifndef RESPONSEPACKET_H +#define RESPONSEPACKET_H + +class cResponsePacket +{ +public: + cResponsePacket(); + ~cResponsePacket(); + + bool init(uint32_t requestID); + bool initScan(uint32_t opCode); + bool initStatus(uint32_t opCode); + bool initStream(uint32_t opCode, uint32_t streamID, uint32_t duration, int64_t dts, int64_t pts); + void finalise(); + void finaliseStream(); + bool copyin(const uint8_t* src, uint32_t len); + uint8_t* reserve(uint32_t len); + bool unreserve(uint32_t len); + + bool add_String(const char* string); + bool add_U32(uint32_t ul); + bool add_S32(int32_t l); + bool add_U8(uint8_t c); + bool add_U64(uint64_t ull); + bool add_double(double d); + + uint8_t* getPtr() { return buffer; } + uint32_t getLen() { return bufUsed; } + +private: + uint8_t* buffer; + uint32_t bufSize; + uint32_t bufUsed; + + bool checkExtend(uint32_t by); + + const static uint32_t headerLength = 12; + const static uint32_t userDataLenPos = 8; + const static uint32_t headerLengthStream = 36; + const static uint32_t userDataLenPosStream = 32; +}; + +#endif + diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/server.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/server.c new file mode 100644 index 0000000000..c1f0873e50 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/server.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "server.h" +#include "connection.h" + +unsigned int cServer::m_IdCnt = 0; + +class cAllowedHosts : public cSVDRPhosts +{ +public: + cAllowedHosts(const cString& AllowedHostsFile) + { + if (!Load(AllowedHostsFile, true, true)) + { + isyslog("VNSI-Error: Invalid or missing '%s'. falling back to 'svdrphosts.conf'.", *AllowedHostsFile); + cString Base = cString::sprintf("%s/../svdrphosts.conf", *VNSIServerConfig.ConfigDirectory); + if (!Load(Base, true, true)) + { + esyslog("VNSI-Error: Invalid or missing %s. Adding 127.0.0.1 to list of allowed hosts.", *Base); + cSVDRPhost *localhost = new cSVDRPhost; + if (localhost->Parse("127.0.0.1")) + Add(localhost); + else + delete localhost; + } + } + } +}; + +cServer::cServer(int listenPort) : cThread("VDR VNSI Server") +{ + m_ServerPort = listenPort; + m_ServerId = time(NULL) ^ getpid(); + + if(*VNSIServerConfig.ConfigDirectory) + { + m_AllowedHostsFile = cString::sprintf("%s/" ALLOWED_HOSTS_FILE, *VNSIServerConfig.ConfigDirectory); + } + else + { + esyslog("VNSI-Error: cServer: missing ConfigDirectory!"); + m_AllowedHostsFile = cString::sprintf("/video/" ALLOWED_HOSTS_FILE); + } + + m_ServerFD = socket(AF_INET, SOCK_STREAM, 0); + if(m_ServerFD == -1) + return; + + fcntl(m_ServerFD, F_SETFD, fcntl(m_ServerFD, F_GETFD) | FD_CLOEXEC); + + int one = 1; + setsockopt(m_ServerFD, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)); + + struct sockaddr_in s; + memset(&s, 0, sizeof(s)); + s.sin_family = AF_INET; + s.sin_port = htons(m_ServerPort); + + int x = bind(m_ServerFD, (struct sockaddr *)&s, sizeof(s)); + if (x < 0) + { + close(m_ServerFD); + isyslog("VNSI: Unable to start VNSI Server, port already in use ?"); + m_ServerFD = -1; + return; + } + + listen(m_ServerFD, 10); + + Start(); + + isyslog("VNSI: VNSI Server started"); + return; +} + +cServer::~cServer() +{ + Cancel(-1); + for (Connections::iterator i = m_Connections.begin(); i != m_Connections.end(); i++) + { + delete (*i); + } + m_Connections.erase(m_Connections.begin(), m_Connections.end()); + Cancel(); + isyslog("VNSI: VNSI Server stopped"); +} + +void cServer::NewClientConnected(int fd) +{ + char buf[64]; + struct sockaddr_in sin; + socklen_t len = sizeof(sin); + + if (getpeername(fd, (struct sockaddr *)&sin, &len)) + { + esyslog("VNSI-Error: getpeername() failed, dropping new incoming connection %d", m_IdCnt); + close(fd); + return; + } + + cAllowedHosts AllowedHosts(m_AllowedHostsFile); + if (!AllowedHosts.Acceptable(sin.sin_addr.s_addr)) + { + esyslog("VNSI: Address not allowed to connect (%s)", *m_AllowedHostsFile); + close(fd); + return; + } + + if (fcntl(fd, F_SETFL, fcntl (fd, F_GETFL) | O_NONBLOCK) == -1) + { + esyslog("VNSI-Error: Error setting control socket to nonblocking mode"); + close(fd); + return; + } + + int val = 1; + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); + + val = 30; + setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &val, sizeof(val)); + + val = 15; + setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &val, sizeof(val)); + + val = 5; + setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &val, sizeof(val)); + + val = 1; + setsockopt(fd, SOL_TCP, TCP_NODELAY, &val, sizeof(val)); + + isyslog("VNSI: Client with ID %d connected: %s", m_IdCnt, cxSocket::ip2txt(sin.sin_addr.s_addr, sin.sin_port, buf)); + cConnection *connection = new cConnection(this, fd, m_IdCnt, cxSocket::ip2txt(sin.sin_addr.s_addr, sin.sin_port, buf)); + m_Connections.push_back(connection); + m_IdCnt++; +} + +void cServer::Action(void) +{ + fd_set fds; + struct timeval tv; + + while (Running()) + { + FD_ZERO(&fds); + FD_SET(m_ServerFD, &fds); + + tv.tv_sec = 5; + tv.tv_usec = 0; + + int r = select(m_ServerFD + 1, &fds, NULL, NULL, &tv); + if (r == -1) + { + esyslog("VNSI-Error: failed during select"); + continue; + } + if (r == 0) + { + for (Connections::iterator i = m_Connections.begin(); i != m_Connections.end();) + { + if (!(*i)->Active()) + { + isyslog("VNSI: Client with ID %u seems to be disconnected, removing from client list", (*i)->GetID()); + delete (*i); + i = m_Connections.erase(i); + } + else + { + i++; + } + } + continue; + } + + int fd = accept(m_ServerFD, 0, 0); + if (fd >= 0) + { + NewClientConnected(fd); + } + else + { + esyslog("VNSI-Error: accept failed"); + } + } + return; +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/server.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/server.h new file mode 100644 index 0000000000..21030944f5 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/server.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef SERVER_H +#define SERVER_H + +#include +#include + +#include "config.h" +//#include "cmdcontrol.h" + +class cConnection; + +typedef std::list Connections; + +class cServer : public cThread +{ +protected: + virtual void Action(void); + void NewClientConnected(int fd); + + int m_ServerPort; + int m_ServerId; + int m_ServerFD; + cString m_AllowedHostsFile; + Connections m_Connections; + //cCmdControl m_CmdControl; + static unsigned int m_IdCnt; + +public: + cServer(int listenPort); + virtual ~cServer(); + + //cCmdControl *GetCmdControl() { return &m_CmdControl; } +}; + +#endif /* SERVER_H */ diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/suspend.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/suspend.c new file mode 100644 index 0000000000..11c3fa7143 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/suspend.c @@ -0,0 +1,57 @@ +/* + * $Id: suspend.c,v 1.3 2008/10/22 11:59:32 schmirl Exp $ + */ + +#include "suspend.h" +#include "suspend.dat" +#include "config.h" + +cSuspendLive::cSuspendLive(void) + : cThread("Streamdev: server suspend") +{ +} + +cSuspendLive::~cSuspendLive() { + Stop(); + Detach(); +} + +void cSuspendLive::Activate(bool On) { + LOGCONSOLE("Activate cSuspendLive %d\n", On); + if (On) + Start(); + else + Stop(); +} + +void cSuspendLive::Stop(void) { + if (Running()) + Cancel(3); +} + +void cSuspendLive::Action(void) { + while (Running()) { + DeviceStillPicture(suspend_mpg, sizeof(suspend_mpg)); + cCondWait::SleepMs(100); + } +} + +bool cSuspendCtl::m_Active = false; + +cSuspendCtl::cSuspendCtl(void): + cControl(m_Suspend = new cSuspendLive) { + m_Active = true; +} + +cSuspendCtl::~cSuspendCtl() { + m_Active = false; + DELETENULL(m_Suspend); +} + +eOSState cSuspendCtl::ProcessKey(eKeys Key) { + if (!m_Suspend->Active() || Key == kBack) { + DELETENULL(m_Suspend); + return osEnd; + } + return osContinue; +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/suspend.dat b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/suspend.dat new file mode 100644 index 0000000000..7b1b8906ab --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/suspend.dat @@ -0,0 +1,1206 @@ +const unsigned char suspend_mpg[] = { + 0x00, 0x00, 0x01, 0xb3, 0x2d, 0x02, 0x40, 0x83, 0x02, 0xd0, 0x20, 0xa0, + 0x00, 0x00, 0x01, 0xb2, 0x4d, 0x50, 0x45, 0x47, 0x0a, 0x00, 0x00, 0x01, + 0xb8, 0x00, 0x08, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0a, 0xbb, + 0x58, 0x00, 0x00, 0x01, 0x01, 0x52, 0x97, 0xe6, 0x54, 0xa5, 0x2f, 0xdc, + 0xaf, 0x9a, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x45, 0xe2, + 0x2c, 0x52, 0x67, 0x98, 0x6c, 0x9a, 0x2e, 0xb9, 0x9e, 0xb4, 0x6c, 0xdd, + 0x34, 0x8c, 0x8b, 0xab, 0xa6, 0x61, 0xb5, 0xbd, 0x33, 0x8d, 0xa7, 0x22, + 0x6f, 0x75, 0x74, 0xcc, 0x36, 0xb4, 0x6c, 0xfd, 0x34, 0x64, 0x4d, 0xee, + 0x91, 0xb3, 0xf4, 0xd6, 0xf4, 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, 0x1b, + 0x37, 0x4d, 0x77, 0x4c, 0xdd, 0x34, 0xe4, 0x43, 0xdd, 0x43, 0x66, 0xe9, + 0xad, 0x1b, 0x38, 0xda, 0x32, 0x26, 0xf7, 0x4f, 0x4c, 0xe3, 0x6b, 0x46, + 0xce, 0x36, 0x8c, 0x89, 0xbd, 0xd3, 0xd3, 0x3f, 0x4d, 0x68, 0xd9, 0x86, + 0xd3, 0x91, 0x37, 0xba, 0x86, 0xcc, 0x36, 0xbb, 0xa6, 0x6e, 0x9a, 0x32, + 0x26, 0xf7, 0x4f, 0x4c, 0xfd, 0x35, 0xbd, 0x33, 0xf4, 0xd1, 0x91, 0x37, + 0xba, 0x46, 0xcf, 0xd3, 0x5a, 0x36, 0x7e, 0x9a, 0x72, 0x26, 0xf7, 0x4f, + 0x4c, 0xfd, 0x35, 0xa3, 0x66, 0x1b, 0x46, 0x44, 0xde, 0xea, 0xe9, 0x9c, + 0x6d, 0x67, 0x4c, 0xfd, 0x34, 0x64, 0x43, 0xdd, 0x43, 0x66, 0xe9, 0xad, + 0xe9, 0x9c, 0x6d, 0x39, 0x13, 0x7b, 0xa7, 0xa6, 0x7e, 0x9a, 0xd1, 0xb3, + 0x0d, 0xa3, 0x22, 0x6f, 0x75, 0x74, 0xcd, 0xd3, 0x5c, 0x36, 0x61, 0xb4, + 0x0c, 0x9b, 0xdd, 0x43, 0x66, 0x1b, 0x5c, 0x36, 0x6e, 0x9a, 0x72, 0x26, + 0xf7, 0x50, 0xd9, 0xba, 0x6b, 0x86, 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, + 0x1b, 0x30, 0xda, 0xd1, 0xb3, 0x8d, 0xa3, 0x22, 0x6f, 0x74, 0x8d, 0x9c, + 0x6d, 0x68, 0xd9, 0xba, 0x69, 0xc8, 0x87, 0xba, 0xba, 0x66, 0x1b, 0x5c, + 0x36, 0x6e, 0x9a, 0x32, 0x26, 0xf7, 0x57, 0x4c, 0xdd, 0x35, 0xdd, 0x33, + 0x74, 0xd1, 0x91, 0x37, 0xba, 0x9e, 0x6e, 0x9a, 0xde, 0x99, 0xfa, 0x68, + 0xc8, 0x9b, 0xdd, 0x23, 0x67, 0xe9, 0xad, 0x79, 0xc6, 0xd3, 0x91, 0x37, + 0xba, 0x7a, 0x67, 0xe9, 0xad, 0x1b, 0x30, 0xda, 0x32, 0x26, 0xf7, 0x50, + 0xd9, 0xba, 0x6b, 0x7a, 0x67, 0x1b, 0x46, 0x44, 0x3d, 0xd4, 0x36, 0x61, + 0xb5, 0xbd, 0x33, 0xbd, 0x39, 0x13, 0x7b, 0xa7, 0xa6, 0x71, 0xb5, 0xbf, + 0xcc, 0x36, 0x8c, 0x89, 0xbd, 0xd4, 0xf3, 0x0d, 0xae, 0xe9, 0x9b, 0xa6, + 0x9c, 0x89, 0xbd, 0xd4, 0xf3, 0x74, 0xd7, 0x74, 0xcc, 0x36, 0x8c, 0x89, + 0xbd, 0xd2, 0x36, 0x7e, 0x9a, 0xd1, 0xb3, 0x0d, 0xa3, 0x22, 0x6f, 0x75, + 0x74, 0xcf, 0xd3, 0x5b, 0xd3, 0x37, 0x4d, 0x19, 0x10, 0xf7, 0x53, 0xcc, + 0x36, 0xb7, 0xa6, 0x71, 0xb4, 0xe4, 0x4d, 0xee, 0x91, 0xb3, 0xf4, 0xd6, + 0x8d, 0x98, 0x6d, 0x03, 0x26, 0xf0, 0x00, 0x00, 0x01, 0x02, 0x2b, 0xf9, + 0x95, 0x29, 0x4b, 0xf7, 0x2b, 0xe6, 0xae, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x45, 0xe2, 0x2c, 0x52, 0x71, 0xb3, 0x74, 0xc9, 0xa2, + 0xeb, 0x79, 0x86, 0xd6, 0xf4, 0xcc, 0x36, 0x9c, 0x88, 0xba, 0xba, 0x66, + 0xe9, 0xad, 0x1b, 0x38, 0xda, 0x32, 0x26, 0xf7, 0x50, 0xd9, 0xba, 0x6b, + 0x7a, 0x67, 0xe9, 0xa7, 0x22, 0x6f, 0x74, 0x8d, 0x9f, 0xa6, 0xb4, 0x6c, + 0xc3, 0x68, 0xc8, 0x9b, 0xdd, 0x5d, 0x33, 0x0d, 0xae, 0x1b, 0x37, 0x4d, + 0x19, 0x13, 0x7b, 0xab, 0xa6, 0x61, 0xb5, 0xc3, 0x66, 0xe9, 0xa3, 0x22, + 0x1e, 0xea, 0x1b, 0x30, 0xda, 0xde, 0x99, 0xfa, 0x69, 0xc8, 0x9b, 0xdd, + 0x23, 0x67, 0x1b, 0x5b, 0xd3, 0x37, 0x4d, 0x19, 0x13, 0x7b, 0xa8, 0x6c, + 0xc3, 0x6b, 0x86, 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, 0xe9, 0x9b, 0xa6, + 0xb7, 0xa6, 0x7e, 0x9a, 0x72, 0x26, 0xf7, 0x4f, 0x4c, 0xfd, 0x35, 0xa3, + 0x67, 0xe9, 0xa3, 0x22, 0x6f, 0x74, 0xf4, 0xce, 0x36, 0xb7, 0xa6, 0x67, + 0xa3, 0x22, 0x6f, 0x75, 0x74, 0xcc, 0xf5, 0xc3, 0x66, 0x1b, 0x4e, 0x44, + 0xde, 0xea, 0xe9, 0x9b, 0xa6, 0xb4, 0x6c, 0xfd, 0x34, 0x0c, 0x87, 0xba, + 0x46, 0xcf, 0xd3, 0x5a, 0x36, 0x61, 0xb4, 0x64, 0x4d, 0xee, 0xa1, 0xb3, + 0xf4, 0xd6, 0x8d, 0x9b, 0xa6, 0x9c, 0x89, 0xbd, 0xd5, 0xd3, 0x30, 0xda, + 0xee, 0x99, 0x86, 0xd1, 0x91, 0x37, 0xba, 0x86, 0xcc, 0x36, 0xb8, 0x6c, + 0xc3, 0x68, 0xc8, 0x9b, 0xdd, 0x5d, 0x33, 0x74, 0xd6, 0x8d, 0x9c, 0x6d, + 0x39, 0x13, 0x7b, 0xa4, 0x6c, 0xe3, 0x6b, 0x7a, 0x66, 0xe9, 0xa3, 0x22, + 0x6f, 0x75, 0x3c, 0xdd, 0x35, 0xdd, 0x33, 0x0d, 0xa3, 0x22, 0x1e, 0xea, + 0xe9, 0x9b, 0xa6, 0xb8, 0x6c, 0xdd, 0x34, 0xe4, 0x4d, 0xee, 0xa1, 0xb3, + 0x0d, 0xad, 0x1b, 0x3f, 0x4d, 0x19, 0x13, 0x7b, 0xa4, 0x6c, 0xe3, 0x6b, + 0x7a, 0x67, 0x1b, 0x46, 0x44, 0xde, 0xe9, 0x1b, 0x38, 0xda, 0xd1, 0xb3, + 0x3d, 0x39, 0x13, 0x7b, 0xab, 0xa6, 0x61, 0xb5, 0xdd, 0x33, 0x74, 0xd1, + 0x91, 0x37, 0xba, 0x5e, 0x7e, 0x9a, 0xee, 0x99, 0x9e, 0x8c, 0x88, 0x7b, + 0xa4, 0x6c, 0xfd, 0x35, 0xbf, 0xcc, 0x36, 0x9c, 0x89, 0xbd, 0xd5, 0xd3, + 0x33, 0xd7, 0x0d, 0x9b, 0xa6, 0x8c, 0x89, 0xbd, 0xd5, 0xd3, 0x37, 0x4d, + 0x68, 0xd9, 0xc6, 0xd1, 0x91, 0x37, 0xba, 0xba, 0x66, 0x1b, 0x5b, 0xd3, + 0x33, 0xd3, 0x91, 0x37, 0xba, 0x86, 0xce, 0x36, 0xb7, 0xa6, 0x61, 0xb4, + 0x64, 0x4d, 0xee, 0xa1, 0xb3, 0x74, 0xd7, 0x0d, 0x98, 0x6d, 0x19, 0x13, + 0x7b, 0xab, 0xa6, 0x6e, 0x9a, 0xd1, 0xb3, 0xf4, 0xd3, 0x91, 0x0f, 0x74, + 0x8d, 0x9f, 0xa6, 0xb4, 0x6c, 0xe3, 0x68, 0xc8, 0x9b, 0xdd, 0x23, 0x66, + 0x1b, 0x5c, 0x36, 0x6e, 0x9a, 0x32, 0x26, 0xf7, 0x57, 0x4c, 0xc3, 0x6b, + 0xba, 0x66, 0x1b, 0x4e, 0x44, 0xde, 0x00, 0x00, 0x01, 0x03, 0x2b, 0xf9, + 0x95, 0x29, 0x4b, 0xf7, 0x2b, 0xe6, 0xae, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x28, 0xf3, 0x3c, 0x9b, 0xf8, 0xb5, 0xa5, 0x67, 0x4c, + 0xe3, 0x64, 0xd1, 0x75, 0x3c, 0xc3, 0x6b, 0x7a, 0x66, 0x1b, 0x4e, 0x44, + 0x3d, 0xd5, 0xd3, 0x30, 0xda, 0xe1, 0xb3, 0x74, 0xd1, 0x91, 0x37, 0xba, + 0x86, 0xcc, 0x36, 0xb4, 0x6c, 0xfd, 0x34, 0x64, 0x4d, 0xee, 0x9e, 0x99, + 0xc6, 0xd6, 0x8d, 0x9c, 0x6d, 0x38, 0xa9, 0xbd, 0xd2, 0x36, 0x71, 0xb5, + 0xbd, 0x33, 0x0d, 0xa3, 0x22, 0x6f, 0x75, 0x74, 0xcc, 0x36, 0xbb, 0xa6, + 0x6e, 0x9a, 0x31, 0x53, 0x7b, 0xab, 0xa6, 0x6e, 0x9a, 0xd1, 0xb3, 0x8d, + 0xa7, 0x15, 0x37, 0xba, 0xba, 0x66, 0x1b, 0x5a, 0x36, 0x6e, 0x9a, 0x06, + 0x43, 0xdd, 0x5d, 0x33, 0x0d, 0xae, 0x1b, 0x30, 0xda, 0x72, 0x26, 0xf7, + 0x57, 0x4c, 0xc3, 0x6b, 0x86, 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, 0x1b, + 0x37, 0x4d, 0x6f, 0x4c, 0xe3, 0x68, 0x19, 0x37, 0xba, 0x86, 0xcd, 0xd3, + 0x5a, 0x36, 0x61, 0xb4, 0xe4, 0x4d, 0xee, 0xae, 0x99, 0x86, 0xd7, 0x74, + 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, 0x1b, 0x30, 0xda, 0xde, 0x99, 0xfa, + 0x68, 0xc8, 0x9b, 0xdd, 0x5d, 0x33, 0x0d, 0xad, 0x1b, 0x3f, 0x4d, 0x23, + 0x26, 0xf7, 0x4f, 0x4c, 0xfd, 0x35, 0xbd, 0x33, 0x74, 0xd1, 0x91, 0x0f, + 0x75, 0x0d, 0x9f, 0xa6, 0xb7, 0xa6, 0x71, 0xb4, 0x64, 0x4d, 0xee, 0x9e, + 0x99, 0xba, 0x6b, 0x86, 0xcd, 0xd3, 0x4e, 0x44, 0xde, 0xea, 0x1b, 0x37, + 0x4d, 0x6f, 0x4c, 0xfd, 0x34, 0x64, 0x4d, 0xee, 0xa1, 0xb3, 0x0d, 0xad, + 0x1b, 0x38, 0xda, 0x32, 0x26, 0xf7, 0x4f, 0x4c, 0xe3, 0x6b, 0x46, 0xcd, + 0xd3, 0x4e, 0x44, 0xde, 0xea, 0xe9, 0x98, 0x6d, 0x70, 0xd9, 0xba, 0x68, + 0xc8, 0x87, 0xba, 0x86, 0xcd, 0xd3, 0x5d, 0xd3, 0x30, 0xda, 0x32, 0x26, + 0xf7, 0x57, 0x4c, 0xdd, 0x35, 0xa3, 0x67, 0x1b, 0x4e, 0x44, 0xde, 0xe9, + 0x1b, 0x38, 0xda, 0xde, 0x99, 0x86, 0xd1, 0x91, 0x37, 0xba, 0xba, 0x66, + 0x1b, 0x5c, 0x36, 0x6e, 0x9a, 0x32, 0x26, 0xf7, 0x57, 0x4c, 0xdd, 0x35, + 0xc3, 0x66, 0xe9, 0xa7, 0x22, 0x6f, 0x75, 0x74, 0xcc, 0x36, 0xb5, 0xe6, + 0xe9, 0xa3, 0x22, 0x1e, 0xea, 0xe9, 0x99, 0xeb, 0xba, 0x66, 0xe9, 0xa3, + 0x22, 0x6f, 0x75, 0x0d, 0x98, 0x6d, 0x70, 0xd9, 0x86, 0xd3, 0x91, 0x37, + 0xba, 0x86, 0xcd, 0xd3, 0x5a, 0x36, 0x61, 0xb4, 0x64, 0x4e, 0xea, 0xe9, + 0x9b, 0xa6, 0xbb, 0xa6, 0x6e, 0x9a, 0x32, 0x26, 0xf7, 0x53, 0xcc, 0x36, + 0xbb, 0xa6, 0x6e, 0x9a, 0x72, 0x26, 0xf7, 0x57, 0x4c, 0xc3, 0x6b, 0x46, + 0xcf, 0xd3, 0x46, 0x44, 0xde, 0xea, 0x1b, 0x37, 0x4d, 0x6f, 0x4c, 0xfd, + 0x34, 0x64, 0x43, 0xdd, 0x23, 0x67, 0xe9, 0xad, 0xe9, 0x98, 0x6d, 0x39, + 0x13, 0x7b, 0xab, 0xa6, 0x6e, 0x9a, 0xe1, 0xb3, 0x74, 0xd1, 0x91, 0x37, + 0xba, 0xba, 0x66, 0xe9, 0xad, 0x1b, 0x3f, 0x4d, 0x19, 0x13, 0x7b, 0xa4, + 0x6c, 0xfd, 0x35, 0xa3, 0x67, 0x1b, 0x4e, 0x44, 0xde, 0xe9, 0xe9, 0x9c, + 0x6d, 0x6f, 0x4c, 0xdd, 0x34, 0x64, 0x4d, 0xe0, 0x00, 0x00, 0x01, 0x04, + 0x2b, 0xf9, 0x95, 0x22, 0xf3, 0x74, 0xdf, 0xb8, 0xb5, 0xf3, 0x56, 0x91, + 0x7a, 0xce, 0x99, 0xc6, 0xc8, 0x64, 0x5d, 0x23, 0x67, 0xe9, 0xad, 0xe9, + 0x9b, 0xa6, 0x9c, 0x89, 0xdd, 0xe0, 0x19, 0xf4, 0x80, 0x54, 0x5f, 0x41, + 0x2c, 0x20, 0x61, 0x7f, 0x14, 0x4b, 0x25, 0xa5, 0x05, 0x27, 0x25, 0x25, + 0x20, 0x95, 0xd0, 0x58, 0xd2, 0xd1, 0xba, 0x33, 0x73, 0x4d, 0xb1, 0xe0, + 0x0c, 0x00, 0x60, 0x1a, 0x8d, 0xf2, 0x40, 0xaf, 0xc9, 0xcf, 0xbe, 0x01, + 0x88, 0x0e, 0xce, 0xe6, 0xe0, 0x0a, 0x6d, 0x1d, 0x32, 0x00, 0x36, 0xf8, + 0xa2, 0x62, 0x40, 0x74, 0xb0, 0x42, 0x08, 0x49, 0x29, 0x0f, 0xbe, 0x00, + 0xa3, 0xe1, 0xa1, 0x8c, 0xff, 0xff, 0x90, 0x8e, 0x06, 0x06, 0xba, 0xd2, + 0x57, 0xbe, 0x6c, 0x01, 0xa2, 0x51, 0xd0, 0x58, 0x67, 0xdb, 0xfd, 0xc9, + 0x79, 0xf6, 0xea, 0x3b, 0x91, 0x2f, 0x54, 0x03, 0x1c, 0x9d, 0x90, 0x5a, + 0x3f, 0xc6, 0x75, 0xde, 0xc0, 0x13, 0x10, 0xd2, 0x94, 0x16, 0x50, 0x6f, + 0x1a, 0x1a, 0x05, 0x0b, 0x49, 0x30, 0x37, 0xb3, 0x3f, 0x43, 0x74, 0x24, + 0x90, 0x84, 0x01, 0x72, 0xba, 0x0b, 0xe3, 0x30, 0xcd, 0xd2, 0xc6, 0x59, + 0x7a, 0x6d, 0x20, 0x21, 0xc0, 0x07, 0xc0, 0x19, 0xe4, 0xac, 0x60, 0x09, + 0xca, 0x18, 0x49, 0xed, 0x89, 0xa4, 0x27, 0xea, 0x43, 0x30, 0xd2, 0xc3, + 0x3f, 0x7e, 0xa5, 0x13, 0x31, 0xf0, 0x7b, 0xbc, 0x03, 0x10, 0x29, 0x88, + 0x40, 0x55, 0x39, 0x19, 0x90, 0x90, 0x94, 0x08, 0xe4, 0x0f, 0x72, 0x00, + 0xe8, 0x34, 0x0c, 0x86, 0x23, 0x9a, 0x77, 0xba, 0xba, 0x66, 0xe9, 0xad, + 0xe9, 0x9f, 0xa6, 0xec, 0xc8, 0xb9, 0xde, 0xe9, 0xe9, 0xb4, 0x13, 0x12, + 0x1a, 0x02, 0x14, 0x96, 0x80, 0x28, 0xdb, 0x06, 0x27, 0x20, 0xb2, 0x19, + 0x78, 0x68, 0x66, 0xfb, 0xf2, 0x59, 0x45, 0x6c, 0x94, 0x24, 0xb2, 0xd0, + 0xcc, 0x96, 0xf9, 0x08, 0xfd, 0x69, 0xe9, 0x47, 0x45, 0x79, 0x0d, 0x25, + 0xfc, 0x82, 0x83, 0x3e, 0xc1, 0xa4, 0xd2, 0xd0, 0x18, 0x57, 0x7d, 0xbe, + 0xe9, 0xeb, 0x2d, 0x19, 0x3b, 0xe4, 0xf0, 0xc2, 0x86, 0x86, 0x8c, 0x6e, + 0xf9, 0x1e, 0xf2, 0xc8, 0x41, 0x40, 0x3a, 0x0c, 0xdb, 0x29, 0x05, 0x63, + 0x7b, 0x87, 0xff, 0xd7, 0xff, 0x37, 0xf5, 0xfe, 0xbf, 0x78, 0x80, 0x0f, + 0x80, 0x34, 0x40, 0x6e, 0x03, 0x25, 0xa0, 0x6e, 0x76, 0x46, 0x1f, 0xf1, + 0xbc, 0x7e, 0xbd, 0x10, 0x10, 0x80, 0xef, 0x86, 0x32, 0x10, 0x7f, 0x27, + 0x55, 0x3d, 0x0c, 0x02, 0x02, 0x94, 0x35, 0x21, 0xa5, 0xf6, 0x67, 0xe5, + 0xa5, 0x0e, 0x02, 0x20, 0xce, 0x52, 0x1c, 0x5b, 0x55, 0x80, 0x1d, 0x13, + 0x07, 0x16, 0x09, 0x3f, 0x81, 0xa0, 0x9b, 0xfb, 0x64, 0x09, 0x4a, 0xdc, + 0x65, 0xeb, 0x50, 0x03, 0xb0, 0x1d, 0x97, 0x9c, 0x96, 0x56, 0x5a, 0x73, + 0x12, 0xbf, 0x5a, 0x3f, 0xdd, 0x60, 0x65, 0x6b, 0xc7, 0xaa, 0xf0, 0x53, + 0x8a, 0x2b, 0x3a, 0x7a, 0x36, 0x38, 0xe2, 0x05, 0xe9, 0xa0, 0x96, 0x5f, + 0x57, 0xe1, 0x75, 0x9f, 0x9d, 0x01, 0xb2, 0x05, 0x80, 0x46, 0x92, 0x0d, + 0x6f, 0x21, 0x15, 0xc0, 0x98, 0x24, 0xfe, 0x96, 0x00, 0x31, 0xbb, 0x24, + 0xb0, 0xd0, 0x26, 0x80, 0x08, 0xb8, 0x00, 0xc6, 0x80, 0x64, 0x37, 0x1a, + 0x8e, 0x1d, 0xf1, 0x17, 0xdb, 0x0d, 0x28, 0x7e, 0x0a, 0xb1, 0x18, 0x9b, + 0xce, 0x1a, 0x05, 0x06, 0xaf, 0x06, 0x96, 0x80, 0xa6, 0xaf, 0x46, 0x42, + 0x3e, 0x41, 0x7b, 0xef, 0xce, 0xe3, 0x52, 0xdc, 0x9e, 0x78, 0xab, 0x22, + 0x08, 0x60, 0x12, 0xe6, 0x17, 0xbb, 0xe2, 0x25, 0xc5, 0x2c, 0xac, 0x5a, + 0x3e, 0xeb, 0xde, 0xe6, 0xcc, 0x29, 0xae, 0x8e, 0xf8, 0xfb, 0xd2, 0x03, + 0x62, 0x85, 0x80, 0x46, 0x92, 0x0d, 0x00, 0x6c, 0x50, 0xee, 0x01, 0x1a, + 0x48, 0x37, 0xad, 0x1a, 0x43, 0x43, 0xa0, 0x97, 0x94, 0x3d, 0x0b, 0xf8, + 0xce, 0x49, 0x02, 0x34, 0x25, 0x93, 0x40, 0x25, 0x40, 0x04, 0x5c, 0x00, + 0x63, 0x69, 0xc6, 0x63, 0x70, 0x55, 0x8b, 0x26, 0xfc, 0x98, 0x0d, 0x8a, + 0x1d, 0xc0, 0x23, 0x49, 0x06, 0x80, 0x36, 0x28, 0x77, 0x00, 0x8d, 0x24, + 0x1b, 0xd7, 0x92, 0xc8, 0x60, 0x12, 0xa0, 0x02, 0x2e, 0x00, 0x31, 0x89, + 0x2c, 0x86, 0x01, 0x2a, 0x00, 0x22, 0x00, 0x17, 0xd4, 0xe5, 0x0f, 0xc1, + 0x54, 0x8c, 0x4d, 0xf9, 0x40, 0x0b, 0x0a, 0x30, 0x6e, 0x01, 0x1f, 0xe4, + 0x7f, 0xf5, 0xd9, 0x2c, 0x31, 0x25, 0x2f, 0x04, 0xe0, 0x08, 0x44, 0xa9, + 0x5c, 0x39, 0xab, 0xb1, 0x0c, 0x35, 0x07, 0xb2, 0x44, 0x2b, 0x91, 0xfd, + 0xe3, 0xd0, 0x9c, 0x5e, 0xdf, 0x1b, 0x0c, 0x8e, 0x3b, 0xd6, 0x12, 0x8a, + 0x9f, 0xdd, 0xe2, 0xf2, 0x76, 0xfb, 0xef, 0xbe, 0x7d, 0xf2, 0xf7, 0xdc, + 0xdd, 0xe2, 0xf5, 0x00, 0x1a, 0x86, 0x00, 0x1f, 0x00, 0xef, 0xa3, 0x12, + 0x03, 0x77, 0x0d, 0x28, 0x53, 0x7d, 0x8b, 0x46, 0x51, 0xdb, 0xfd, 0xf3, + 0xf0, 0x3e, 0xd9, 0x5a, 0xdc, 0xf7, 0xb6, 0x1a, 0x18, 0x06, 0x13, 0xf0, + 0x50, 0x8b, 0xc9, 0x28, 0xb2, 0x5a, 0x37, 0xba, 0x9e, 0x77, 0xad, 0x79, + 0x9e, 0xd1, 0xa8, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, 0xd4, + 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0x97, 0x9d, 0xeb, 0x5e, 0x77, + 0xa1, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0xbd, 0x2d, 0x17, 0x4b, 0xce, + 0xf5, 0xaf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x86, + 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0xb4, 0xee, 0x97, 0x9d, 0xeb, + 0x5e, 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, + 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, + 0xef, 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, + 0x99, 0xeb, 0x9e, 0x67, 0xa5, 0xa7, 0x75, 0x3c, 0xcf, 0x5c, 0xf3, 0x3d, + 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, 0x33, + 0xd7, 0x3c, 0xcf, 0x4b, 0x4e, 0xea, 0x79, 0x9e, 0xb9, 0xe6, 0x7a, 0x1a, + 0x2e, 0xa7, 0x99, 0xeb, 0x5e, 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xef, 0x5a, + 0xf3, 0x3d, 0x2d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe8, 0x69, 0xdd, + 0x4f, 0x33, 0xd7, 0x3c, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, + 0x7a, 0x5a, 0x77, 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd0, 0xd1, 0x75, 0x3c, + 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe9, + 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xef, 0x43, 0x4e, 0xe9, 0x79, 0xde, + 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, 0xce, 0xf5, 0xaf, 0x33, 0xd2, 0xd3, + 0xba, 0x9e, 0x67, 0xac, 0x8d, 0x2d, 0x16, 0xaf, 0x17, 0x8d, 0x29, 0x9c, + 0x00, 0x00, 0x01, 0x05, 0x3b, 0xf9, 0xd3, 0xce, 0xf5, 0xaf, 0x33, 0xdf, + 0xb5, 0x35, 0xf3, 0x97, 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd2, 0xd1, 0x75, + 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x0d, 0x3b, 0xa9, 0xe7, 0x28, 0xb0, 0x1b, + 0x00, 0xec, 0x31, 0x26, 0x06, 0xb9, 0x31, 0x21, 0x06, 0x9e, 0x57, 0xe9, + 0x1d, 0xbb, 0xa3, 0x61, 0xe1, 0xb5, 0x8f, 0x68, 0x26, 0xee, 0x59, 0x49, + 0xdb, 0xed, 0xc0, 0xf7, 0xf9, 0xc6, 0x27, 0x2d, 0x09, 0x61, 0xe8, 0xed, + 0xf0, 0xbd, 0x71, 0x01, 0x44, 0x81, 0x80, 0xd4, 0x24, 0xc3, 0xb8, 0x8f, + 0x6e, 0x1a, 0x82, 0x5a, 0x3e, 0xab, 0x78, 0x80, 0x3b, 0x47, 0x48, 0x67, + 0xf9, 0x7d, 0x01, 0x38, 0xc7, 0x63, 0x03, 0xf3, 0xe0, 0xfb, 0xab, 0xbe, + 0xfb, 0x7d, 0xdf, 0x73, 0x6f, 0x3a, 0x52, 0x49, 0x68, 0xdc, 0x7e, 0x5e, + 0x1d, 0xcd, 0xc1, 0xdd, 0x57, 0xae, 0xc0, 0x5b, 0xa4, 0xa0, 0x25, 0xc5, + 0x12, 0x3a, 0xd0, 0x1c, 0xca, 0x0e, 0xf7, 0x8a, 0x43, 0x43, 0xa3, 0x71, + 0xc4, 0x6b, 0xd2, 0x26, 0x76, 0x7d, 0x53, 0xe3, 0x79, 0x9e, 0xb9, 0xeb, + 0x12, 0x9c, 0x8d, 0xff, 0xfb, 0x6c, 0xbf, 0xfa, 0xb6, 0xc3, 0xfd, 0xef, + 0x64, 0x3d, 0xe4, 0xbd, 0x6d, 0xce, 0x2b, 0x3a, 0x5c, 0x09, 0x62, 0x7f, + 0x20, 0x62, 0x3c, 0x84, 0x34, 0xb3, 0x6c, 0x69, 0x1b, 0x00, 0xfa, 0xe6, + 0x86, 0x66, 0x46, 0x73, 0xfa, 0xf8, 0x07, 0xc7, 0x5e, 0xb5, 0x3c, 0x6a, + 0x53, 0xc7, 0x2b, 0x11, 0x40, 0x3e, 0xbe, 0x47, 0x04, 0x6b, 0xdd, 0x3f, + 0xd4, 0xfe, 0x02, 0xfa, 0x09, 0x33, 0xa8, 0xb0, 0x47, 0xfb, 0x21, 0x82, + 0x5f, 0xd9, 0x77, 0xcf, 0xaf, 0xa3, 0x06, 0x70, 0x1c, 0x82, 0x3f, 0xd9, + 0x0c, 0x12, 0xfe, 0xd3, 0x74, 0x80, 0x9c, 0x10, 0xc1, 0x81, 0x23, 0xf7, + 0x11, 0xbd, 0x00, 0x30, 0x24, 0xf1, 0x17, 0x8d, 0xee, 0x70, 0x60, 0x05, + 0xc0, 0x21, 0x26, 0x95, 0x83, 0x71, 0x41, 0x85, 0x15, 0xf3, 0x80, 0x52, + 0x5a, 0x50, 0x5b, 0x0f, 0x19, 0x83, 0x0b, 0x65, 0x2f, 0x64, 0xf4, 0x73, + 0x3e, 0xd7, 0x7c, 0x9a, 0x9d, 0x8a, 0x2d, 0xd7, 0x86, 0x77, 0x10, 0xd7, + 0x9b, 0x21, 0x80, 0x27, 0x2c, 0xa4, 0x96, 0x05, 0x39, 0x34, 0x07, 0x41, + 0x84, 0xde, 0x4d, 0x6c, 0x94, 0xe2, 0xf9, 0x41, 0x85, 0x37, 0x7c, 0x5e, + 0x35, 0x39, 0x38, 0x61, 0xed, 0xdd, 0xec, 0x00, 0x1e, 0x00, 0x60, 0x80, + 0x18, 0xe2, 0xfa, 0x51, 0xd2, 0x30, 0xcc, 0x47, 0xb9, 0x08, 0x64, 0xd2, + 0x53, 0x2d, 0xab, 0x4e, 0xe7, 0x37, 0x7c, 0x1d, 0xc7, 0x72, 0x27, 0xb8, + 0x84, 0x24, 0x9e, 0x81, 0xc7, 0x08, 0x1c, 0x1c, 0x40, 0xaf, 0x21, 0xfe, + 0x94, 0x10, 0x8b, 0x3b, 0xa7, 0x37, 0x0a, 0x7b, 0xa0, 0x19, 0xd8, 0x35, + 0x02, 0xdf, 0x13, 0x9c, 0x89, 0x57, 0x41, 0x29, 0x2c, 0x67, 0xb5, 0xeb, + 0xd5, 0x47, 0x28, 0x01, 0xe9, 0x4d, 0x83, 0x09, 0xb8, 0x0f, 0x24, 0x94, + 0x93, 0xc9, 0x05, 0xf6, 0xc1, 0x45, 0xb1, 0xe7, 0x6f, 0xee, 0xfb, 0xde, + 0x34, 0x03, 0x32, 0x80, 0xc8, 0x0c, 0x08, 0x68, 0xca, 0xe4, 0xa4, 0xb7, + 0xc7, 0xef, 0xb3, 0xef, 0x83, 0xef, 0x6f, 0xb2, 0x53, 0xb7, 0xc8, 0x4f, + 0xdf, 0x3e, 0x57, 0x36, 0xe9, 0x02, 0xa5, 0x38, 0xc4, 0xe1, 0x1c, 0x3a, + 0x60, 0x51, 0x2a, 0xfb, 0xda, 0x3c, 0xef, 0x02, 0x51, 0x60, 0x49, 0x21, + 0x5e, 0x7e, 0x50, 0x6a, 0xc0, 0xb1, 0x7d, 0x87, 0x21, 0x3f, 0x85, 0x6d, + 0xff, 0xbb, 0xc9, 0x49, 0x1d, 0xee, 0x21, 0xb8, 0x7e, 0xb8, 0xf7, 0x97, + 0x00, 0x70, 0xe0, 0x60, 0x9a, 0xee, 0x08, 0x7f, 0x6a, 0x70, 0x47, 0x04, + 0x28, 0xe1, 0x44, 0x57, 0xbd, 0x08, 0x60, 0x09, 0x80, 0x76, 0x43, 0x61, + 0x85, 0x72, 0xb7, 0x3d, 0x21, 0xbb, 0xa1, 0x81, 0x20, 0x0f, 0xb1, 0x7f, + 0x8a, 0x6d, 0x85, 0xeb, 0xc2, 0x10, 0x88, 0x48, 0x02, 0x81, 0x88, 0x0c, + 0x41, 0xd8, 0xd1, 0xff, 0xf7, 0xff, 0xb3, 0x67, 0xfd, 0x7e, 0xf3, 0x08, + 0x41, 0x9d, 0x0f, 0xfa, 0x9f, 0x33, 0x5e, 0x81, 0x0c, 0xbd, 0xf3, 0x65, + 0xdf, 0xa4, 0x00, 0x66, 0xa0, 0x03, 0xee, 0x00, 0x29, 0x4e, 0x13, 0xb9, + 0x49, 0xec, 0x17, 0xb7, 0x0e, 0xbd, 0x2b, 0xdc, 0xa0, 0x07, 0x64, 0xdc, + 0x1a, 0x43, 0x28, 0xac, 0xbc, 0x4c, 0xdd, 0x3c, 0xf2, 0x4a, 0x73, 0x61, + 0x44, 0x87, 0x71, 0x3a, 0xee, 0xbc, 0xc0, 0x77, 0x82, 0x76, 0x1c, 0x85, + 0xd8, 0x10, 0x94, 0x55, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf7, 0x6b, 0x5c, + 0xd7, 0x4b, 0xce, 0xf5, 0xaf, 0x3b, 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, + 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0xb4, 0x5d, + 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x43, 0x4e, 0xe9, 0x79, 0xde, 0xb5, 0xe7, + 0x7a, 0x1a, 0x77, 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd2, 0xd3, 0xba, 0x9e, + 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, + 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x67, 0xa5, 0xa2, 0xea, 0x79, 0x9e, + 0xb9, 0xe7, 0x7a, 0x1a, 0x77, 0x4b, 0xcc, 0xf5, 0xcf, 0x33, 0xd0, 0xd3, + 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x96, 0x9d, 0xd2, 0xf3, 0x3d, 0x73, + 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x9d, 0xeb, 0x5e, 0x67, 0xa1, 0xa7, 0x75, + 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x2d, 0x17, 0x4b, 0xce, 0xf5, 0xaf, 0x3b, + 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, + 0x3d, 0x73, 0xcc, 0xf4, 0xb4, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x77, 0xa1, + 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe7, 0x7a, + 0xd7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd7, 0x3c, 0xcf, 0x43, 0x45, + 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0x97, 0x9d, 0xeb, 0x5e, + 0x77, 0xa5, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, + 0xe6, 0x7a, 0xca, 0x52, 0xd3, 0xb5, 0x79, 0xa9, 0x49, 0x45, 0xca, 0x52, + 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x00, 0x00, 0x00, + 0x01, 0x06, 0x43, 0xf1, 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x7e, 0xc4, 0xd7, + 0xcf, 0x5d, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0xde, + 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xaf, 0x3b, 0xd2, 0xd3, + 0xad, 0xf9, 0x04, 0xce, 0xdf, 0xe0, 0xfc, 0x45, 0x27, 0xac, 0x3e, 0xb4, + 0x9b, 0x99, 0x3b, 0x8f, 0x57, 0x23, 0xe2, 0x2d, 0xd3, 0x71, 0x9f, 0x71, + 0xc4, 0x63, 0x48, 0x21, 0xd7, 0x94, 0x0d, 0x40, 0xd4, 0x7c, 0x69, 0x82, + 0x71, 0xd7, 0xcd, 0x60, 0x9c, 0x4e, 0xbd, 0x9e, 0xaf, 0x54, 0x7a, 0x97, + 0xa1, 0x48, 0x00, 0xb4, 0x9a, 0x18, 0x18, 0x3f, 0x8d, 0x4a, 0xd8, 0x20, + 0xa6, 0xfc, 0x4e, 0xfd, 0x91, 0xf0, 0xe4, 0xda, 0xd0, 0x58, 0xd4, 0x64, + 0xe7, 0x5e, 0xdd, 0x67, 0xae, 0xf3, 0xe0, 0x0f, 0x8a, 0x72, 0x59, 0x34, + 0x7f, 0x25, 0x21, 0x6a, 0x64, 0x3b, 0xfd, 0x8c, 0xfd, 0x2f, 0xb8, 0xf1, + 0x9a, 0xf4, 0x40, 0xa0, 0x60, 0x17, 0x58, 0x1e, 0x58, 0x7d, 0xc4, 0x4d, + 0x2d, 0x8a, 0x13, 0x7c, 0xa0, 0x13, 0x93, 0x40, 0x2c, 0x21, 0x16, 0x9c, + 0x7b, 0x32, 0x30, 0xf4, 0xf5, 0x6c, 0xb1, 0xfe, 0xf7, 0x28, 0xc4, 0xa0, + 0xcc, 0x83, 0xfe, 0xcb, 0xec, 0xcf, 0xcd, 0xec, 0x2f, 0xaf, 0xdc, 0x80, + 0x07, 0xa1, 0xa4, 0xb2, 0x68, 0x61, 0x33, 0x1c, 0x80, 0x14, 0xa5, 0x4e, + 0x29, 0x18, 0x52, 0xc2, 0x77, 0x01, 0xe5, 0xd8, 0xfe, 0xf3, 0xc0, 0xaf, + 0x59, 0x45, 0x00, 0x8f, 0x89, 0xf7, 0x41, 0x31, 0x0c, 0x57, 0xe3, 0xef, + 0xe6, 0x97, 0xd3, 0x11, 0xc0, 0x72, 0x08, 0xff, 0x84, 0xd0, 0x4b, 0xfc, + 0x4d, 0xf3, 0x4b, 0xea, 0x3b, 0xac, 0xb0, 0x47, 0xfc, 0x26, 0x82, 0x5f, + 0xe2, 0x68, 0x02, 0xab, 0x0c, 0x01, 0x1e, 0x23, 0x7b, 0x60, 0x1d, 0x12, + 0x78, 0x8b, 0xcf, 0xca, 0x01, 0xbe, 0x4a, 0x3b, 0xe4, 0x0c, 0x4f, 0x0d, + 0x4e, 0x01, 0x21, 0x5b, 0x71, 0xa9, 0x16, 0xd8, 0xec, 0xcb, 0xff, 0x6b, + 0xe9, 0xc0, 0x54, 0x01, 0xb9, 0x41, 0xa9, 0x0d, 0x0d, 0xc4, 0xc0, 0x2e, + 0x92, 0xd3, 0xf3, 0x99, 0xbf, 0xff, 0x28, 0xe7, 0x61, 0xd7, 0x89, 0x00, + 0x6e, 0x05, 0x4b, 0x00, 0xd0, 0x01, 0xd0, 0x40, 0x42, 0x40, 0x35, 0x4b, + 0x3e, 0x3c, 0x02, 0xc2, 0x97, 0x97, 0xbf, 0x24, 0xfe, 0xfb, 0x81, 0x24, + 0xdf, 0x6b, 0xbe, 0x60, 0x03, 0x04, 0x24, 0x06, 0x01, 0x8a, 0xff, 0x74, + 0xaf, 0xc1, 0x35, 0x8b, 0xeb, 0x32, 0xbc, 0xfc, 0xd6, 0x4c, 0xe0, 0x16, + 0x90, 0x80, 0x7a, 0x82, 0x21, 0x07, 0x5b, 0xca, 0x01, 0xca, 0x04, 0xf2, + 0x37, 0xbd, 0x80, 0x06, 0x45, 0xf0, 0x32, 0x30, 0x7e, 0x23, 0x5f, 0x1c, + 0x49, 0x4c, 0x1a, 0xe6, 0x33, 0x87, 0x9c, 0x46, 0xba, 0x13, 0x9d, 0x70, + 0x65, 0x63, 0x8d, 0x24, 0x8e, 0x23, 0xda, 0x03, 0x3e, 0xe5, 0x21, 0xbe, + 0x3b, 0x4c, 0x43, 0x48, 0xc0, 0xc2, 0xf8, 0xed, 0xef, 0xaa, 0x64, 0x6d, + 0x99, 0x6a, 0x1d, 0xfe, 0x22, 0xde, 0xab, 0xed, 0x96, 0xf6, 0xe8, 0x43, + 0xd5, 0x3f, 0x1c, 0x7c, 0x3f, 0x49, 0x7d, 0x98, 0x95, 0xb0, 0x1f, 0x0d, + 0x04, 0x9f, 0xb0, 0xd0, 0x01, 0x8d, 0xc2, 0x01, 0xd0, 0x05, 0xe5, 0x80, + 0xe9, 0x3b, 0x06, 0xfe, 0x59, 0x33, 0x9e, 0x9e, 0x49, 0x0c, 0x16, 0x2d, + 0x03, 0xcd, 0xc7, 0xae, 0xac, 0x92, 0x92, 0xad, 0xfe, 0xe2, 0xec, 0xd2, + 0x4c, 0xfb, 0x56, 0xfe, 0x09, 0x25, 0xa1, 0x07, 0xf6, 0xbd, 0x59, 0x60, + 0x55, 0xd4, 0x80, 0xbe, 0x03, 0x5b, 0xc9, 0xa5, 0x18, 0xbc, 0xde, 0xfa, + 0x71, 0x68, 0xe5, 0x76, 0xe1, 0xdc, 0x78, 0x0b, 0xe8, 0x76, 0xab, 0x35, + 0x53, 0xab, 0x29, 0xcd, 0xff, 0xbf, 0xfc, 0xff, 0xc7, 0xfa, 0x3d, 0x6b, + 0xcc, 0xf7, 0xab, 0xdf, 0xf7, 0xf7, 0x93, 0x75, 0x3c, 0xef, 0x5a, 0xf3, + 0x3c, 0x9a, 0x77, 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd0, 0xd1, 0x75, 0x3c, + 0xcf, 0x5c, 0xf3, 0x3d, 0x2d, 0x3b, 0xa5, 0xe7, 0x7a, 0xd7, 0x9d, 0xe8, + 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0xde, + 0xb1, 0xe7, 0x7a, 0x5a, 0x77, 0x53, 0xcc, 0xf5, 0xaf, 0x3b, 0xd0, 0xd3, + 0xba, 0x5e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x8b, 0xa9, 0xe7, 0x7a, 0xd7, + 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x43, 0x4e, 0xe9, + 0x79, 0xde, 0xb5, 0xe7, 0x7a, 0x1a, 0x77, 0x4b, 0xce, 0xf5, 0xaf, 0x33, + 0xd2, 0xd3, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x86, 0x9d, 0xd2, 0xf3, + 0xbd, 0x6b, 0xce, 0xf4, 0x34, 0x5d, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x4b, + 0x4e, 0xea, 0x79, 0xde, 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, 0xcc, 0xf5, + 0xcf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x96, 0x9d, + 0xd2, 0xf3, 0xbd, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x9e, + 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xcf, 0x5c, 0xf3, 0x3d, 0x2d, 0x17, 0x53, + 0xcc, 0xf5, 0xaf, 0x3b, 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, 0x78, 0xd2, + 0xd3, 0xba, 0xde, 0x67, 0x9e, 0x94, 0xce, 0xc4, 0x69, 0x49, 0x45, 0xca, + 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, + 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x40, 0x00, 0x00, + 0x01, 0x07, 0x4b, 0xf3, 0x2f, 0x33, 0xd6, 0xbc, 0xef, 0x7e, 0xb2, 0xd7, + 0x83, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0xbd, 0x0d, 0x3b, 0xa5, 0xe7, 0x7a, + 0xd7, 0x9d, 0xe9, 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, + 0xd3, 0xb7, 0xdf, 0x76, 0xfb, 0xab, 0xee, 0x77, 0xbc, 0x80, 0x06, 0x41, + 0xa0, 0x64, 0x85, 0xb7, 0x3c, 0xae, 0x4a, 0xef, 0xf7, 0xea, 0x3b, 0xf6, + 0x35, 0x28, 0x6e, 0x1f, 0x7b, 0x47, 0xb9, 0x40, 0x19, 0x06, 0x92, 0x88, + 0x63, 0x3f, 0x50, 0x6e, 0x6d, 0x80, 0x7a, 0x87, 0xea, 0x12, 0x87, 0x71, + 0x1a, 0xf3, 0x40, 0xa7, 0x6f, 0xf0, 0xf2, 0x3e, 0xbb, 0x48, 0x68, 0x74, + 0x38, 0xea, 0xe3, 0x7a, 0x70, 0xc4, 0xfd, 0x87, 0xbe, 0x0f, 0x03, 0xb7, + 0x83, 0xd8, 0x62, 0x36, 0x1d, 0xad, 0xa9, 0x28, 0x69, 0x68, 0x48, 0xe7, + 0x0f, 0xb4, 0x90, 0xd2, 0xc8, 0x17, 0xd5, 0x79, 0x3b, 0x5e, 0x8b, 0xbb, + 0x55, 0x3e, 0xc3, 0x86, 0x80, 0xdc, 0xb2, 0xc3, 0x40, 0x25, 0x4f, 0x65, + 0x01, 0xec, 0xac, 0x27, 0x75, 0xa7, 0x09, 0x8b, 0xd7, 0xab, 0x66, 0xca, + 0xd8, 0xc8, 0x1f, 0x7a, 0xff, 0x92, 0xcb, 0x71, 0xfe, 0xf2, 0x19, 0x90, + 0xc2, 0xaf, 0xe4, 0xf7, 0xde, 0x40, 0x4e, 0x43, 0x0d, 0x03, 0x21, 0xa1, + 0x85, 0x27, 0x14, 0x12, 0x5f, 0x4a, 0x59, 0xd2, 0xb0, 0xd2, 0xd4, 0x83, + 0x31, 0xc7, 0xf1, 0xea, 0xbe, 0x0f, 0x7d, 0x30, 0x10, 0x46, 0x8b, 0x48, + 0x15, 0x26, 0x96, 0x94, 0x95, 0x90, 0x1b, 0xdd, 0x5d, 0x3d, 0x2e, 0x37, + 0xf5, 0xee, 0x8d, 0xb2, 0xbb, 0x2b, 0x99, 0x7c, 0xb0, 0x09, 0x80, 0x2c, + 0xe1, 0xb8, 0x02, 0xd2, 0x46, 0xef, 0x7b, 0xa3, 0x7a, 0x32, 0x58, 0xfa, + 0xf3, 0xd6, 0xb0, 0x0d, 0xd9, 0x09, 0xe0, 0x2a, 0xe3, 0x80, 0xe5, 0xf4, + 0x94, 0xa3, 0xa3, 0xfd, 0xcc, 0x55, 0xf0, 0xa0, 0x03, 0xce, 0x3f, 0x5c, + 0x20, 0x03, 0xc2, 0x11, 0xfd, 0x0f, 0xc4, 0xf6, 0xbe, 0x5c, 0xbe, 0x51, + 0x9e, 0xd3, 0x57, 0xff, 0x58, 0x01, 0xd7, 0x25, 0x7c, 0x5f, 0x17, 0xaf, + 0x90, 0xa0, 0x87, 0x8b, 0x4f, 0xc7, 0x0e, 0x89, 0x34, 0x98, 0x35, 0x86, + 0x66, 0xd8, 0xca, 0xdc, 0x4c, 0x28, 0x31, 0x69, 0x5f, 0xc1, 0x5a, 0xcc, + 0x0b, 0x74, 0x0b, 0xad, 0x6f, 0x54, 0xa0, 0x03, 0x72, 0x68, 0xc2, 0x90, + 0x19, 0xba, 0xcb, 0x4e, 0x70, 0x15, 0x21, 0x7c, 0x2c, 0xe0, 0xfb, 0xdb, + 0xbc, 0xa4, 0x32, 0x1f, 0x01, 0xd9, 0x37, 0x93, 0x7b, 0xe7, 0x52, 0x37, + 0x03, 0xc8, 0x20, 0xf3, 0xf8, 0x76, 0x32, 0xd2, 0xf7, 0xe8, 0xe4, 0xdf, + 0xbf, 0x6c, 0x72, 0x15, 0x78, 0x41, 0x88, 0x19, 0xae, 0xa7, 0x99, 0xeb, + 0x9e, 0x67, 0xba, 0x5a, 0xc2, 0xb4, 0xf1, 0x00, 0x3a, 0x0d, 0x01, 0x27, + 0x01, 0x11, 0x00, 0x81, 0x79, 0xc2, 0x68, 0x60, 0x19, 0x43, 0x1a, 0x67, + 0x17, 0x7d, 0x75, 0xcd, 0xc7, 0xe5, 0xe1, 0x37, 0xc8, 0x09, 0x81, 0xb8, + 0x35, 0x0e, 0x1d, 0xc4, 0xde, 0x7d, 0xea, 0x27, 0x35, 0x55, 0xf5, 0xbe, + 0xf9, 0xf7, 0xdf, 0x7c, 0xaf, 0xb9, 0xfb, 0xe3, 0xf4, 0xef, 0x78, 0xd0, + 0x07, 0x20, 0x3a, 0x19, 0xc0, 0x2d, 0x46, 0x3d, 0xb1, 0x6d, 0x8d, 0x3b, + 0x24, 0x89, 0xc3, 0x85, 0xde, 0xe9, 0xed, 0xfa, 0x1a, 0xe1, 0x42, 0x46, + 0xeb, 0xa5, 0xe7, 0x7a, 0xd7, 0x9d, 0xe6, 0xd6, 0x77, 0x4b, 0xce, 0xf5, + 0xaf, 0x33, 0xd2, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, + 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0x97, 0x9d, 0xeb, 0x5e, + 0x77, 0xa5, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x17, 0x53, + 0xcc, 0xf5, 0xcf, 0x33, 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, 0x79, 0x9e, + 0x96, 0x9d, 0xd4, 0xf3, 0xbd, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x99, + 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x2d, + 0x3b, 0xa5, 0xe7, 0x7a, 0xd7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x3b, 0xd6, + 0xbc, 0xcf, 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x73, 0xcc, 0xf4, 0xb4, 0xee, + 0xa7, 0x99, 0xeb, 0x9e, 0x67, 0xa1, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, + 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, + 0x33, 0xd7, 0x3c, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, + 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xb1, 0xa5, 0xa2, 0xeb, 0x79, 0x9e, 0x6a, + 0x4a, 0x77, 0x29, 0x4a, 0x4a, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0x80, 0x00, 0x00, 0x01, 0x08, 0x53, 0xe4, 0x3c, 0xef, + 0x5a, 0xf3, 0x3d, 0xfa, 0x83, 0x5e, 0x25, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, + 0xf4, 0xb4, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x74, 0xbc, + 0xef, 0x69, 0x49, 0xc1, 0x41, 0xf9, 0xd7, 0x8c, 0x67, 0xca, 0x73, 0x2d, + 0x3c, 0x6a, 0x0b, 0x42, 0x3a, 0x73, 0xaf, 0xf0, 0x1e, 0xa0, 0xed, 0xc7, + 0x39, 0xc9, 0x53, 0x29, 0x2c, 0x65, 0xc4, 0x51, 0x30, 0x6a, 0x3f, 0x7c, + 0xa3, 0xed, 0x7a, 0x5b, 0xb5, 0xf3, 0xde, 0x61, 0xbd, 0x1f, 0x7c, 0x7e, + 0xfb, 0xe3, 0x17, 0x96, 0xb1, 0x43, 0xed, 0x43, 0x06, 0x97, 0xca, 0x61, + 0xa4, 0x95, 0x30, 0xd7, 0x00, 0xa3, 0xa4, 0xef, 0xbe, 0xed, 0xce, 0x54, + 0xe4, 0x81, 0x9b, 0xab, 0x8c, 0xef, 0x8e, 0x61, 0x9d, 0x95, 0x9b, 0x33, + 0xee, 0x3b, 0x65, 0x3e, 0xcb, 0xb9, 0x30, 0x14, 0xf9, 0x6e, 0xbd, 0xed, + 0x49, 0x6e, 0xaa, 0xf7, 0xe7, 0x00, 0x37, 0x28, 0x7e, 0xbd, 0x70, 0x6a, + 0x72, 0x52, 0xa5, 0xec, 0x47, 0xba, 0xe9, 0xcc, 0x93, 0x42, 0xb6, 0x23, + 0x4e, 0xc4, 0x63, 0xc3, 0x88, 0x26, 0x5e, 0x5b, 0x5d, 0x95, 0x55, 0x2f, + 0x11, 0xac, 0xbb, 0x5a, 0x12, 0x35, 0x1b, 0x8f, 0xdb, 0x85, 0xd4, 0x96, + 0x8e, 0x82, 0x5f, 0xe8, 0x6c, 0x4a, 0x42, 0x50, 0x96, 0xc8, 0x74, 0xed, + 0x94, 0xf8, 0xa0, 0x97, 0xcf, 0x97, 0xff, 0x37, 0x1d, 0x7a, 0xa1, 0x84, + 0xce, 0x97, 0x15, 0x71, 0x25, 0x2c, 0x1f, 0x5c, 0x7c, 0xd8, 0x02, 0xd0, + 0xc0, 0x27, 0x82, 0xf4, 0xa0, 0x37, 0x0c, 0x02, 0x78, 0x2f, 0x5f, 0x54, + 0xea, 0xce, 0x24, 0x89, 0x64, 0x4e, 0x6c, 0xe7, 0x2c, 0xdc, 0xc0, 0x6c, + 0x4d, 0xe5, 0x30, 0xab, 0xd0, 0x6a, 0xbf, 0xe6, 0xc0, 0x07, 0xa4, 0x20, + 0x45, 0xfc, 0x40, 0x5e, 0xad, 0xe0, 0x27, 0x21, 0x60, 0x12, 0xa3, 0x88, + 0xee, 0x46, 0xbe, 0x9b, 0xca, 0x01, 0x42, 0x3e, 0x4a, 0xfa, 0x0a, 0xe9, + 0x14, 0xfb, 0x2f, 0x61, 0xce, 0x78, 0x1b, 0xb5, 0xa7, 0xab, 0x88, 0xab, + 0xb2, 0x19, 0x44, 0xfa, 0x72, 0x37, 0xaf, 0xbe, 0x7b, 0xc0, 0xae, 0x33, + 0x7e, 0x4e, 0xbd, 0x8a, 0x53, 0xd2, 0x78, 0x55, 0x52, 0x76, 0xce, 0xc6, + 0x71, 0xd9, 0x41, 0x56, 0xaf, 0x82, 0x1d, 0x66, 0x99, 0x87, 0x8f, 0x3e, + 0xcd, 0x74, 0x55, 0x57, 0xbc, 0xcf, 0x56, 0x82, 0x93, 0xd0, 0x35, 0x08, + 0x25, 0x81, 0x76, 0xfb, 0xed, 0xce, 0xdb, 0xe3, 0xd1, 0xd8, 0xd5, 0xe6, + 0xe2, 0xd5, 0x5e, 0x19, 0x89, 0x68, 0x2d, 0xb3, 0xfd, 0xbb, 0x65, 0x25, + 0xf0, 0xd6, 0x01, 0xee, 0xea, 0x1e, 0xea, 0xff, 0xb2, 0xae, 0xc0, 0x0b, + 0x0e, 0x59, 0xf7, 0x38, 0xd3, 0xee, 0x3c, 0xcf, 0x63, 0x42, 0x30, 0x0d, + 0xc0, 0xa2, 0x3a, 0x18, 0x97, 0xf0, 0xd2, 0x94, 0xa6, 0x2f, 0x20, 0xe3, + 0xb6, 0xe8, 0x6e, 0x31, 0xa6, 0x26, 0x94, 0x18, 0x51, 0x48, 0x25, 0x7d, + 0xc0, 0xba, 0x50, 0xf9, 0x05, 0x21, 0x94, 0x48, 0xf8, 0xa5, 0x3a, 0x9f, + 0x7e, 0x22, 0x90, 0x0b, 0x0f, 0x15, 0x50, 0xd3, 0xaf, 0x63, 0xdc, 0x80, + 0x10, 0x00, 0x9c, 0x00, 0xf0, 0x35, 0xff, 0x02, 0xc9, 0x51, 0x62, 0x87, + 0x24, 0x38, 0xd1, 0x42, 0x2b, 0x80, 0xc0, 0x46, 0x74, 0x36, 0xe4, 0x20, + 0xc4, 0xe4, 0xb6, 0x40, 0x17, 0x25, 0x6e, 0x78, 0xc7, 0x4b, 0x9d, 0xdf, + 0xf3, 0xff, 0x32, 0x25, 0x13, 0x06, 0x90, 0x89, 0x79, 0x09, 0xfc, 0x6a, + 0x70, 0x40, 0x61, 0x49, 0x03, 0xc8, 0x35, 0x39, 0xf0, 0x75, 0x8e, 0x26, + 0x32, 0x4f, 0xaf, 0xfd, 0xf5, 0x77, 0xf3, 0x2e, 0x1a, 0x19, 0xf3, 0x0d, + 0xc1, 0x7a, 0xfa, 0xef, 0x6d, 0xfe, 0xdd, 0xf0, 0xfa, 0xb2, 0xb6, 0xfb, + 0xbf, 0x02, 0x4b, 0x6d, 0xcf, 0x71, 0xf6, 0x84, 0xa9, 0x3e, 0x8f, 0x95, + 0x5e, 0xaa, 0xae, 0x79, 0x9e, 0xb9, 0xe6, 0x7b, 0xb5, 0xae, 0x5b, 0xa5, + 0xe7, 0x7a, 0xd7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x3b, 0xd6, 0xbc, 0xcf, + 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb9, 0xe6, 0x7a, 0x5a, 0x2e, 0xa7, 0x99, + 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0x3d, 0x0d, + 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, + 0xbc, 0xef, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, 0x5a, 0x77, + 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, + 0x9e, 0x86, 0x8b, 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, + 0x3b, 0xd6, 0xbc, 0xef, 0x4b, 0x4e, 0xe9, 0x79, 0xde, 0xb5, 0xe6, 0x7a, + 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, + 0xad, 0x79, 0xde, 0x96, 0x9d, 0xd4, 0xf3, 0x3d, 0x64, 0x69, 0x68, 0xb4, + 0x79, 0xa3, 0x4a, 0x67, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, + 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, + 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, + 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, + 0x11, 0x72, 0x94, 0xa4, 0x44, 0x00, 0x00, 0x01, 0x09, 0x53, 0xa1, 0xe6, + 0x7a, 0xd7, 0x9d, 0xef, 0xd2, 0x1a, 0xf1, 0xee, 0x97, 0x9d, 0xeb, 0x5e, + 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, + 0xed, 0x08, 0x46, 0x41, 0xc1, 0x46, 0x6c, 0x3b, 0x0a, 0xa3, 0xcc, 0xf6, + 0x59, 0xf0, 0x7c, 0xed, 0xf1, 0x5b, 0x66, 0xca, 0x5b, 0x88, 0x1c, 0xc6, + 0x28, 0xea, 0xc4, 0x14, 0x84, 0x66, 0x3c, 0xf3, 0xf6, 0x34, 0xfc, 0xdb, + 0x6c, 0xb3, 0x54, 0xba, 0xa7, 0x9d, 0xec, 0x46, 0xa5, 0x99, 0xfa, 0xeb, + 0x6d, 0x76, 0xe2, 0x99, 0x23, 0x46, 0xff, 0xc4, 0x21, 0x62, 0x7e, 0x46, + 0xe4, 0xf8, 0xf2, 0xb7, 0xee, 0xfc, 0x42, 0x8d, 0x3d, 0x6c, 0xf8, 0x3b, + 0xea, 0xb7, 0x9d, 0xec, 0xc9, 0x69, 0x77, 0x7e, 0xe7, 0x4d, 0xb5, 0xd4, + 0xf6, 0xb2, 0x56, 0x0e, 0x66, 0x66, 0x43, 0x30, 0xe3, 0x9d, 0x85, 0x3d, + 0x5b, 0xcc, 0xf1, 0xbf, 0x41, 0xdd, 0x9e, 0x47, 0xd8, 0x65, 0xbd, 0x68, + 0xcc, 0xe3, 0xce, 0xcc, 0xa3, 0x0e, 0x5b, 0x3f, 0x61, 0x7e, 0x0f, 0x33, + 0xd1, 0x92, 0x9c, 0x6f, 0xa9, 0xb5, 0xbf, 0x2b, 0x2f, 0xaf, 0x7e, 0xa7, + 0x14, 0xb5, 0x08, 0x5f, 0xa9, 0x7a, 0xe7, 0x99, 0xe4, 0xff, 0x9f, 0xe2, + 0xd4, 0x3f, 0x81, 0x17, 0x62, 0x75, 0x6a, 0x50, 0xea, 0x75, 0x2d, 0x4c, + 0x8e, 0x79, 0xe7, 0xa3, 0xc1, 0xe6, 0x7b, 0x2c, 0x94, 0xe3, 0x66, 0xda, + 0xd7, 0x81, 0xb3, 0xbb, 0x89, 0x7d, 0xe2, 0x1f, 0xc4, 0x89, 0xac, 0x79, + 0x9e, 0x4f, 0xc7, 0x9d, 0x3b, 0x53, 0x18, 0x0e, 0x11, 0x9b, 0x4c, 0x1f, + 0xcf, 0x7e, 0x2f, 0xaf, 0xac, 0x3f, 0xce, 0xf3, 0x3d, 0x0f, 0xc7, 0xf9, + 0xdd, 0x7f, 0xf1, 0xe2, 0x8e, 0xc7, 0x0e, 0x76, 0x72, 0x7c, 0xcf, 0xce, + 0x75, 0x0e, 0x64, 0x75, 0x8f, 0x5e, 0xf5, 0x8f, 0x3b, 0xd0, 0xe9, 0x1f, + 0x36, 0xd6, 0x86, 0x35, 0x1e, 0xb5, 0xe6, 0x79, 0x2e, 0x2e, 0xa7, 0x99, + 0xeb, 0x9e, 0x67, 0x86, 0x8b, 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe9, 0x69, + 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xcf, 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x73, + 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x9e, 0x67, 0xa5, 0xa7, 0x75, + 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xd7, 0x99, + 0xe8, 0x69, 0xdd, 0x4f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, 0xea, 0x79, + 0x9e, 0xb9, 0xe6, 0x7a, 0x5a, 0x2e, 0xa7, 0x99, 0xeb, 0x5e, 0x77, 0xa1, + 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0xbd, 0x0d, 0x3b, 0xa5, 0xe7, 0x7a, + 0xd7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd7, 0x3c, 0x68, 0x69, 0xdd, + 0x6f, 0x33, 0xd6, 0x52, 0x99, 0xda, 0xbc, 0xd4, 0xa4, 0xa2, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, + 0x01, 0x0a, 0x53, 0xd0, 0xf3, 0xbd, 0x6b, 0xcc, 0xf7, 0xdf, 0xb5, 0xe5, + 0x5d, 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x43, 0x45, 0xd2, 0xf3, 0xbd, 0x6b, + 0xce, 0xf4, 0xb4, 0xee, 0x97, 0x9d, 0xeb, 0x5e, 0x67, 0xa1, 0xa7, 0x75, + 0x3c, 0xcf, 0x5c, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, + 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, 0xea, 0x79, + 0xde, 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd2, + 0xd1, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xad, 0xe6, 0x7a, + 0xd7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x33, 0xd7, 0x3c, 0xcf, 0x4b, 0x4e, + 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xaf, + 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x77, 0xad, 0x79, 0x9e, 0x96, 0x8b, 0xa9, + 0xe6, 0x7a, 0xe7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xef, + 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, 0x5a, 0x77, 0x4b, 0xce, + 0xf5, 0xaf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x86, + 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0xb4, 0xee, 0x97, 0x9d, 0xeb, + 0x5e, 0x77, 0xa1, 0xa2, 0xe9, 0x79, 0xde, 0xb5, 0xe6, 0x7a, 0x1a, 0x77, + 0x53, 0xcc, 0xf5, 0xcf, 0x1a, 0x5a, 0x77, 0x5b, 0xcc, 0xf3, 0xd2, 0x99, + 0xd8, 0x8d, 0x29, 0x28, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x20, 0x00, 0x00, 0x01, 0x0b, 0x53, 0xeb, 0xbc, 0xef, + 0x5a, 0xf3, 0xbd, 0xf6, 0xcd, 0x79, 0x97, 0x4b, 0xce, 0xf5, 0xaf, 0x33, + 0xd2, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, + 0x3d, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x67, 0xa5, + 0xa7, 0x75, 0x3c, 0xcf, 0x5c, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, + 0xd7, 0x9d, 0xe8, 0x68, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x96, 0x9d, + 0xd2, 0xf3, 0xbd, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x5e, + 0x77, 0xa1, 0xa7, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x2d, 0x3b, 0xa5, + 0xe7, 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, + 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x73, 0xcc, 0xf4, 0xb4, 0xee, 0xa7, 0x99, + 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, + 0x3b, 0xa9, 0xe7, 0x7a, 0xd7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, + 0xbc, 0xef, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, 0x1a, 0x77, + 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd2, 0xd3, 0xba, 0x9e, 0x77, 0xac, 0x9a, + 0x86, 0x8b, 0xad, 0xe6, 0x79, 0xa9, 0x29, 0xd8, 0x8d, 0x29, 0x28, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x88, 0x00, 0x00, + 0x01, 0x0c, 0x53, 0xf4, 0xef, 0x33, 0xd6, 0xbc, 0xef, 0x7d, 0x73, 0x5e, + 0x7d, 0xd2, 0xf3, 0xbd, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0x97, 0x99, 0xeb, + 0x9e, 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xef, 0x58, 0xf3, 0xbd, 0x2d, 0x3b, + 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe8, 0x68, 0xba, 0x5e, 0x77, 0xad, 0x79, + 0x9e, 0x86, 0x9d, 0xd4, 0xf3, 0xbd, 0x6b, 0xcc, 0xf4, 0xb4, 0xee, 0xa7, + 0x99, 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0xbd, + 0x0d, 0x3b, 0xa5, 0xe7, 0x7a, 0xd7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, + 0xd7, 0x3c, 0xcf, 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0x34, + 0xee, 0x97, 0x9d, 0xeb, 0x5e, 0x67, 0xa5, 0xa7, 0x75, 0x3c, 0xef, 0x5a, + 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe8, 0x69, 0xdd, + 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x4b, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe6, + 0x7a, 0x1a, 0x77, 0x53, 0xce, 0xf5, 0x91, 0xa5, 0xa2, 0xd1, 0xe6, 0x8d, + 0x29, 0x9d, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, + 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, + 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, + 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, + 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, + 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, + 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, + 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, + 0x29, 0x4a, 0x44, 0x40, 0x00, 0x00, 0x01, 0x0d, 0x53, 0xf6, 0xcf, 0x3b, + 0xd6, 0x3c, 0xef, 0x7d, 0x2b, 0x5c, 0x57, 0x53, 0xcc, 0xf5, 0xaf, 0x3b, + 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, 0x79, 0x9e, 0x96, 0x8b, 0xa9, 0xe7, + 0x7a, 0xd7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x43, + 0x4e, 0xe9, 0x79, 0xde, 0xb5, 0xe7, 0x7a, 0x5a, 0x77, 0x4b, 0xce, 0xf5, + 0xaf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, + 0xd4, 0xf3, 0x3d, 0x73, 0xcc, 0xf4, 0xb4, 0xee, 0xa7, 0x99, 0xeb, 0x5e, + 0x77, 0xa1, 0xa2, 0xe9, 0x79, 0xde, 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, + 0xcc, 0xf5, 0xcf, 0x33, 0xd2, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, + 0x86, 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x9d, + 0xeb, 0x29, 0x4c, 0xed, 0x5e, 0x31, 0xa4, 0xa2, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x88, 0x00, 0x00, 0x01, + 0x0e, 0x53, 0xfa, 0x2b, 0xce, 0xf5, 0xaf, 0x33, 0xde, 0xe3, 0x5c, 0xb7, + 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd2, 0xd1, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, + 0xbd, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, + 0x3b, 0xd6, 0xbc, 0xcf, 0x4b, 0x4e, 0xea, 0x79, 0x9e, 0xb9, 0xe6, 0x7a, + 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xaf, 0x3b, 0xd0, 0xd3, 0xba, 0x5e, 0x77, + 0xad, 0x79, 0x9e, 0x96, 0x8b, 0xa9, 0xe7, 0x7a, 0xd7, 0x99, 0xe8, 0x69, + 0xdd, 0x4f, 0x33, 0xd7, 0x3c, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, + 0xe6, 0xa5, 0xa7, 0x75, 0x3c, 0xef, 0x3d, 0x29, 0x9d, 0x88, 0xd2, 0x92, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x88, 0x00, 0x00, 0x01, 0x0f, 0x53, 0xfa, 0xab, 0xcc, 0xf5, 0xaf, 0x3b, + 0xde, 0xa3, 0x5c, 0xf7, 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd0, 0xd3, 0xba, + 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, + 0xf4, 0xb4, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x74, 0xbc, + 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe7, 0x7a, 0xd7, 0x99, 0xe9, + 0x68, 0xba, 0x9e, 0x67, 0xad, 0x78, 0xd0, 0xd3, 0xb4, 0x79, 0x9e, 0x6a, + 0x4a, 0x76, 0x23, 0x4a, 0x4a, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x97, 0xcf, + 0xca, 0x00, 0x58, 0x90, 0xd0, 0x1d, 0x00, 0x1f, 0xa0, 0x0a, 0x86, 0x01, + 0x81, 0x89, 0x4b, 0xb1, 0x6e, 0xc5, 0x3b, 0xa9, 0x9c, 0xcf, 0x6e, 0x58, + 0x6a, 0x03, 0x18, 0x6f, 0x67, 0xed, 0x84, 0xd3, 0xb7, 0xf7, 0xf4, 0x0a, + 0x5c, 0x22, 0x10, 0x0c, 0x0a, 0x21, 0x93, 0x40, 0x6c, 0x82, 0x1a, 0x1f, + 0xf4, 0x21, 0x1c, 0x61, 0x6c, 0x9e, 0x90, 0xe5, 0x31, 0xaf, 0x54, 0x01, + 0xa8, 0x06, 0x98, 0x03, 0x44, 0x92, 0xb9, 0x34, 0xa2, 0xcb, 0xcc, 0xe8, + 0x40, 0xd5, 0x38, 0xff, 0xf3, 0x07, 0x1e, 0xb9, 0x12, 0xd0, 0x34, 0x61, + 0xac, 0xec, 0xdc, 0xe8, 0x6e, 0x7d, 0xfa, 0x2a, 0x52, 0x96, 0x35, 0x5c, + 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0xf1, 0xa0, 0x18, + 0x00, 0x38, 0x48, 0x05, 0xe8, 0x4f, 0x28, 0x9a, 0x56, 0xe8, 0x61, 0xa8, + 0xf9, 0x2a, 0x77, 0x65, 0xb3, 0x73, 0xad, 0x8b, 0x2d, 0x01, 0x98, 0x6a, + 0x7b, 0x61, 0xf4, 0xec, 0xf7, 0xec, 0xa9, 0x72, 0x80, 0x62, 0x05, 0x10, + 0x03, 0xa2, 0xf9, 0x40, 0x67, 0x16, 0x94, 0x25, 0x28, 0x4b, 0x64, 0xe6, + 0xe8, 0xec, 0x6e, 0xce, 0x1d, 0x71, 0x09, 0x80, 0x0f, 0x31, 0x34, 0x07, + 0x5c, 0x07, 0x60, 0x15, 0x86, 0x20, 0xa4, 0x29, 0x23, 0x72, 0x5f, 0x25, + 0xb7, 0x18, 0x72, 0xfa, 0xcc, 0x3e, 0x7c, 0xb4, 0x6d, 0xbf, 0xea, 0xe7, + 0x2b, 0x9b, 0x26, 0xf7, 0xec, 0x29, 0x78, 0x00, 0x13, 0x00, 0x2e, 0x48, + 0x06, 0xa1, 0x9c, 0x34, 0x98, 0x43, 0x0c, 0xfb, 0x71, 0x9d, 0xf1, 0x79, + 0x6d, 0xb8, 0xe1, 0x76, 0xde, 0x00, 0xe9, 0xdb, 0x13, 0x00, 0x62, 0x03, + 0xa2, 0x6a, 0x7f, 0x24, 0x62, 0xdb, 0x9d, 0xfb, 0x84, 0xb6, 0xb5, 0x00, + 0xb4, 0x31, 0xfe, 0x1a, 0xbc, 0x7a, 0xba, 0x84, 0xaa, 0x43, 0x3d, 0xfb, + 0x2a, 0x5c, 0xd0, 0x1d, 0x80, 0xc0, 0x9a, 0x02, 0x62, 0xc9, 0x44, 0xd2, + 0xba, 0x39, 0x8e, 0x77, 0x5b, 0x3e, 0xe6, 0xeb, 0x20, 0x06, 0x80, 0x26, + 0x00, 0x37, 0x0d, 0xc4, 0xcc, 0x5a, 0x0a, 0x4f, 0x24, 0x6f, 0xba, 0xdb, + 0x7f, 0xd7, 0xb2, 0x9b, 0x63, 0x6c, 0x39, 0x68, 0xc8, 0x1a, 0xea, 0x67, + 0x73, 0xd5, 0x53, 0x2d, 0xaf, 0xbb, 0x4b, 0xe7, 0x25, 0x80, 0x2a, 0xe1, + 0x81, 0xa8, 0x0c, 0x48, 0x0c, 0x40, 0xa2, 0x0b, 0x4b, 0x0c, 0x47, 0x1a, + 0xfd, 0xfb, 0x66, 0x51, 0xbe, 0xf5, 0x60, 0x27, 0x00, 0xc8, 0x0a, 0x80, + 0x65, 0xd1, 0x89, 0xa8, 0x61, 0xbb, 0xfd, 0xf7, 0xdf, 0x76, 0x3d, 0x78, + 0xdd, 0x6c, 0x82, 0x18, 0x61, 0x7f, 0x6c, 0x9d, 0xdd, 0x78, 0x58, 0x81, + 0xf2, 0xe8, 0xfa, 0xfb, 0x74, 0xa5, 0xe4, 0x00, 0x60, 0x00, 0xd8, 0x0a, + 0x80, 0x9c, 0x0a, 0x16, 0x43, 0x43, 0x64, 0x23, 0x76, 0xdc, 0xf2, 0xc6, + 0x65, 0xec, 0xc1, 0x4b, 0xb1, 0x49, 0x68, 0x2b, 0x3a, 0x7b, 0x63, 0xaa, + 0xd9, 0xef, 0xd7, 0xd2, 0x94, 0xb2, 0x9d, 0xca, 0x52, 0x91, 0x17, 0x29, + 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, + 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xb8, + 0x40, 0x1a, 0x00, 0x98, 0x00, 0xdc, 0x37, 0x13, 0x31, 0x68, 0x29, 0x3c, + 0x91, 0xbe, 0xeb, 0x6d, 0xff, 0x5e, 0xca, 0x6d, 0x8d, 0xad, 0x29, 0xc8, + 0xdf, 0xf5, 0x6c, 0x7c, 0x8f, 0xbe, 0xed, 0x2f, 0x22, 0x00, 0xec, 0x03, + 0x04, 0x00, 0x5c, 0x90, 0xc2, 0x80, 0xcf, 0x2c, 0xb4, 0xa0, 0x6a, 0x73, + 0x36, 0xc6, 0x66, 0xe6, 0xbf, 0xdc, 0x2a, 0xf5, 0xd5, 0x14, 0x5e, 0x2d, + 0x19, 0x28, 0x5e, 0xe7, 0x4b, 0xe6, 0xb9, 0x4a, 0x52, 0xca, 0xab, 0x94, + 0xbe, 0x6a, 0x03, 0xa0, 0x05, 0x88, 0x26, 0x90, 0xb8, 0x0e, 0xd2, 0x05, + 0x70, 0x0d, 0x86, 0x25, 0x2e, 0x84, 0x24, 0x68, 0x16, 0xfd, 0xd4, 0x96, + 0x1d, 0xef, 0xa5, 0x5d, 0x25, 0x06, 0xa0, 0xbe, 0x9c, 0x8e, 0xe3, 0x3b, + 0x71, 0x23, 0xa8, 0xfb, 0xeb, 0x94, 0xa5, 0x2e, 0x6a, 0xee, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x88, + 0x00, 0x00, 0x01, 0x10, 0x53, 0xfb, 0x23, 0xcc, 0xf5, 0xcf, 0x33, 0xdd, + 0x6d, 0x65, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x2d, 0x3b, 0xa5, 0xe7, + 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, + 0x4e, 0xea, 0x79, 0x9e, 0xb6, 0x34, 0xb4, 0x5a, 0x3c, 0xd1, 0xa5, 0x33, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x7c, 0x24, 0x00, 0xb8, 0x37, + 0x06, 0x80, 0xe8, 0x9a, 0x4c, 0xf9, 0xc6, 0x25, 0xd6, 0xbc, 0x30, 0xbc, + 0xcc, 0xc0, 0x22, 0xee, 0x26, 0xfb, 0xb5, 0xf2, 0xb0, 0x02, 0xac, 0x90, + 0x03, 0xf2, 0x6f, 0x58, 0xc0, 0x30, 0xdc, 0x73, 0xf4, 0xe6, 0x71, 0x6c, + 0x35, 0x83, 0xef, 0x64, 0x03, 0x54, 0x38, 0x14, 0x2c, 0x53, 0x7c, 0x27, + 0x7b, 0x00, 0xce, 0xc9, 0xd5, 0xff, 0x41, 0x00, 0x84, 0x0c, 0x8e, 0x9e, + 0x49, 0xef, 0x91, 0x8d, 0xc3, 0xfd, 0xea, 0x4a, 0x29, 0x21, 0xa9, 0x6c, + 0x1f, 0xee, 0x70, 0x15, 0x4f, 0x48, 0x6a, 0x45, 0x76, 0x0a, 0xbe, 0x58, + 0x03, 0xad, 0x8a, 0x26, 0x72, 0xc7, 0xbf, 0xe1, 0xd6, 0xbf, 0x8c, 0xce, + 0x6f, 0xa2, 0xfd, 0xa0, 0x05, 0x48, 0xdf, 0x23, 0x96, 0x13, 0xcc, 0x65, + 0x0a, 0xbd, 0x29, 0x64, 0xce, 0x18, 0x96, 0x4f, 0x0f, 0xb9, 0x64, 0x2d, + 0xc3, 0x49, 0xbb, 0xf7, 0xeb, 0x7f, 0xc0, 0x3f, 0xbc, 0x88, 0x0e, 0xd8, + 0xb0, 0xd2, 0x8a, 0x0b, 0x0f, 0x6a, 0x9f, 0xe4, 0xec, 0xee, 0xa7, 0xe2, + 0xa2, 0xfd, 0x29, 0x60, 0x50, 0x94, 0x50, 0xde, 0x91, 0xcc, 0x2e, 0x72, + 0x60, 0x14, 0xc1, 0xa8, 0xe9, 0xfd, 0xb6, 0xbc, 0x58, 0x09, 0xfb, 0x92, + 0x8a, 0xc6, 0xf7, 0x20, 0x11, 0x6e, 0x71, 0x60, 0x28, 0x02, 0x8d, 0xbb, + 0xbe, 0x61, 0x2e, 0x7b, 0x13, 0xee, 0xd1, 0xbc, 0x66, 0x1f, 0xf9, 0x3a, + 0xcd, 0xaf, 0xec, 0xa1, 0x85, 0x01, 0x94, 0xb3, 0xb5, 0xe3, 0x80, 0x0f, + 0x00, 0xbb, 0x6e, 0xac, 0x7f, 0xe4, 0x6c, 0x1d, 0x56, 0x02, 0x62, 0x80, + 0xbf, 0xff, 0x12, 0x09, 0xdc, 0x07, 0xf7, 0x64, 0x0a, 0x86, 0xfe, 0x1b, + 0xdb, 0x92, 0x15, 0x88, 0xb7, 0x07, 0xff, 0xa5, 0xb7, 0x32, 0xd0, 0xca, + 0xb3, 0xd4, 0x00, 0xc4, 0x99, 0x80, 0xa1, 0x0c, 0xb0, 0xcc, 0x9c, 0x5e, + 0x47, 0x61, 0xb8, 0xd0, 0x3b, 0x8e, 0xe2, 0xef, 0x00, 0x00, 0x91, 0x3c, + 0x02, 0xb0, 0xd4, 0x3a, 0xb8, 0x63, 0xfc, 0x2f, 0x27, 0x31, 0x1f, 0xdf, + 0x53, 0xbe, 0x7e, 0x00, 0xb0, 0x34, 0x94, 0x02, 0x1c, 0x86, 0x01, 0xca, + 0x12, 0x81, 0x0d, 0x86, 0x80, 0x0c, 0xae, 0x50, 0x2b, 0x9c, 0x84, 0x5e, + 0x3c, 0x23, 0x93, 0xad, 0x10, 0x49, 0xd5, 0xc6, 0xf0, 0x1d, 0x94, 0x08, + 0x5f, 0x29, 0xd8, 0xdf, 0x82, 0xfa, 0xc8, 0xfe, 0xa4, 0x0a, 0xa7, 0x23, + 0x20, 0x99, 0x8e, 0x28, 0xfe, 0x3d, 0x26, 0x85, 0x1d, 0x78, 0x52, 0x1a, + 0x00, 0xc0, 0x69, 0x64, 0x3e, 0x3b, 0x0c, 0x01, 0x5f, 0xcc, 0x4e, 0x67, + 0xbe, 0xd8, 0x7f, 0xb4, 0x28, 0x27, 0x61, 0x5b, 0x05, 0xd3, 0xea, 0xdf, + 0x81, 0x00, 0x98, 0x10, 0x82, 0x40, 0x1d, 0x72, 0xc0, 0x70, 0x9e, 0x51, + 0x69, 0x70, 0x1e, 0xf7, 0xc8, 0xe1, 0xe1, 0x0d, 0x7e, 0x81, 0xbe, 0x00, + 0x00, 0xec, 0x10, 0x81, 0xc0, 0x30, 0x41, 0x30, 0xf7, 0x25, 0x06, 0x39, + 0xac, 0x8c, 0xee, 0xc0, 0x22, 0x27, 0xdf, 0x68, 0x01, 0x80, 0x14, 0xe0, + 0x3b, 0x26, 0x94, 0x19, 0x80, 0xff, 0x5f, 0x1d, 0x8d, 0xe1, 0x57, 0x50, + 0x0e, 0xbe, 0x18, 0x1a, 0x1b, 0xc0, 0xfa, 0x7f, 0x71, 0x5a, 0x84, 0x9f, + 0xb5, 0x7f, 0xf2, 0x32, 0x18, 0x49, 0x09, 0x1f, 0x9e, 0x11, 0xc2, 0xf5, + 0xe9, 0xc0, 0x40, 0x87, 0x2f, 0xf6, 0x3b, 0x13, 0xc0, 0x7f, 0x79, 0x80, + 0x13, 0xa7, 0x64, 0x64, 0x63, 0x56, 0x1d, 0xa0, 0x02, 0x0c, 0x10, 0x9e, + 0x3f, 0xb0, 0x50, 0x1b, 0xbd, 0x27, 0x01, 0x47, 0x16, 0x70, 0x7d, 0x91, + 0xb7, 0xeb, 0xc8, 0x63, 0x4b, 0x21, 0xfc, 0x85, 0xb3, 0x6c, 0x28, 0x08, + 0xde, 0x4c, 0x04, 0x29, 0xc3, 0x36, 0x35, 0xdc, 0x3a, 0x50, 0x28, 0x4d, + 0xe1, 0xa5, 0xf5, 0x6c, 0xaf, 0xd5, 0x96, 0x7e, 0x36, 0xfa, 0x81, 0x0d, + 0x03, 0x03, 0x53, 0x9f, 0xf1, 0x22, 0x2e, 0x3d, 0x82, 0x76, 0x38, 0x66, + 0xae, 0xfb, 0xbb, 0x3f, 0x1d, 0x7c, 0x9f, 0x86, 0x24, 0x30, 0x99, 0xf9, + 0x2d, 0x5c, 0xcc, 0x77, 0x7b, 0xea, 0x48, 0x40, 0xd4, 0xec, 0x3f, 0xb5, + 0xf1, 0x90, 0x13, 0xf5, 0x21, 0x1b, 0x0b, 0x11, 0xb6, 0xb8, 0x53, 0xd6, + 0x8d, 0x6b, 0xaf, 0xe8, 0x85, 0x13, 0x1d, 0x04, 0xb4, 0xf6, 0x45, 0x9c, + 0xb2, 0x16, 0x42, 0x31, 0x68, 0x48, 0xdd, 0x79, 0x50, 0x28, 0x5e, 0xd8, + 0x0c, 0x17, 0x8f, 0x39, 0xc5, 0x99, 0x7a, 0xa0, 0x2a, 0x18, 0x03, 0x6e, + 0xdb, 0xf6, 0xeb, 0xad, 0xf8, 0xd6, 0x0a, 0x1d, 0xf4, 0xaf, 0xd9, 0x6c, + 0xfd, 0xb5, 0xf3, 0xc0, 0x0a, 0x91, 0xbe, 0x41, 0x69, 0x64, 0xf1, 0x4a, + 0x0b, 0xbd, 0xda, 0x1d, 0xd4, 0x7e, 0x58, 0x55, 0xf0, 0xa0, 0x2c, 0x03, + 0x14, 0x0e, 0x4e, 0x0b, 0xc0, 0x72, 0x82, 0xfb, 0x08, 0x34, 0x54, 0xf5, + 0xfd, 0x40, 0x02, 0xc6, 0x2c, 0x95, 0xfe, 0xdc, 0xc3, 0xd7, 0x12, 0x19, + 0x33, 0x13, 0x06, 0x67, 0x7f, 0xcf, 0xb8, 0x60, 0x3a, 0x47, 0xe4, 0xd5, + 0xbb, 0xa3, 0x72, 0x05, 0x8c, 0x37, 0x00, 0xdc, 0x86, 0xcf, 0x9b, 0x36, + 0x03, 0x82, 0x5f, 0xda, 0x3f, 0x1a, 0x7a, 0xb6, 0x7f, 0x4d, 0xf6, 0x52, + 0x05, 0x32, 0x03, 0x31, 0x7d, 0x2b, 0xec, 0xa1, 0x20, 0x72, 0xe8, 0x94, + 0x4c, 0x74, 0x12, 0xd3, 0xd9, 0x17, 0xcf, 0x00, 0x4f, 0xff, 0x18, 0x8c, + 0x2f, 0xf0, 0xe0, 0x23, 0x74, 0x40, 0xa1, 0x7b, 0x60, 0x30, 0x5e, 0x3c, + 0xe7, 0x16, 0x65, 0xd1, 0x92, 0x91, 0x8e, 0x2c, 0x9d, 0x49, 0xb7, 0xea, + 0x8b, 0x21, 0x64, 0x23, 0x16, 0x84, 0x8d, 0xd7, 0x19, 0x00, 0x3a, 0xdc, + 0xa1, 0xbd, 0x2a, 0x5f, 0x56, 0x33, 0x5d, 0x90, 0x2a, 0x18, 0x03, 0x6f, + 0xb6, 0xfd, 0xba, 0xef, 0x98, 0x80, 0x4c, 0x03, 0xac, 0x23, 0x70, 0xff, + 0xdc, 0x0d, 0x8a, 0x8e, 0x9e, 0xd9, 0xb9, 0xfe, 0x2f, 0xe8, 0xc1, 0x80, + 0x50, 0x96, 0x7f, 0xdb, 0xb3, 0xec, 0x68, 0x1a, 0xbb, 0xc0, 0x36, 0x49, + 0x2f, 0xa3, 0x67, 0xf8, 0x07, 0xf7, 0xce, 0x00, 0x4d, 0x9f, 0x96, 0x5f, + 0x0b, 0x27, 0xfb, 0xc6, 0x06, 0x77, 0xc9, 0x2d, 0x41, 0x3f, 0xf7, 0x15, + 0x95, 0x9e, 0xd3, 0x97, 0xf7, 0xe6, 0x9c, 0x17, 0xe4, 0xd7, 0xf5, 0xa0, + 0x0a, 0xb0, 0xd2, 0x51, 0x2f, 0x0d, 0x30, 0x70, 0x7d, 0xaf, 0x10, 0x80, + 0xb9, 0x2b, 0xa7, 0x6e, 0xe4, 0xe6, 0x7b, 0x86, 0x18, 0x92, 0x8a, 0xef, + 0x9c, 0xb4, 0xfb, 0x92, 0x03, 0xa7, 0x2c, 0x34, 0xb4, 0xa4, 0xfc, 0xa0, + 0x0e, 0x2c, 0x3f, 0xfb, 0x7e, 0x28, 0x9f, 0x55, 0xfa, 0xa2, 0xc0, 0xa0, + 0xc4, 0xb6, 0x49, 0x8f, 0xff, 0x56, 0xcb, 0x69, 0x49, 0xa0, 0x50, 0x06, + 0xc8, 0xdd, 0xdc, 0xcf, 0x72, 0x12, 0x02, 0x90, 0x2a, 0x30, 0xc7, 0x58, + 0xb6, 0x01, 0xf5, 0x84, 0x06, 0x2e, 0x5a, 0x72, 0x31, 0x87, 0x89, 0xfd, + 0x96, 0x02, 0xf9, 0xef, 0xdd, 0x19, 0xcc, 0x7f, 0x2b, 0xf7, 0x01, 0x84, + 0x24, 0x86, 0xbf, 0x3b, 0x63, 0x7d, 0xe1, 0x48, 0x41, 0x85, 0x12, 0xdd, + 0x09, 0xfc, 0x63, 0xba, 0x83, 0xae, 0x78, 0x0e, 0xf8, 0x18, 0x0c, 0xc5, + 0x63, 0x54, 0x1d, 0xae, 0x68, 0x0e, 0x91, 0x8a, 0x2b, 0xf3, 0x4f, 0xe2, + 0x80, 0x82, 0xee, 0xd4, 0xa7, 0xf4, 0x7e, 0x3d, 0x5b, 0x50, 0xd7, 0xf4, + 0xd2, 0x1b, 0x86, 0x93, 0x7e, 0xcf, 0xd9, 0x41, 0x47, 0x93, 0xe4, 0x00, + 0xab, 0x23, 0xec, 0x94, 0xed, 0xf9, 0xdb, 0x2d, 0x62, 0xef, 0x0c, 0x03, + 0x04, 0x70, 0x92, 0xf9, 0xe9, 0x02, 0x37, 0xa4, 0xc9, 0x0c, 0x41, 0x7c, + 0xbf, 0xfa, 0x52, 0xe7, 0x63, 0x8d, 0x1e, 0x7d, 0xc6, 0xe6, 0x63, 0x35, + 0x77, 0xec, 0x0b, 0x26, 0x24, 0xa4, 0x8c, 0x3f, 0xf3, 0x9a, 0xf3, 0xdc, + 0x0a, 0x62, 0xb0, 0x17, 0x4b, 0x39, 0x99, 0x77, 0xcf, 0x88, 0x43, 0x4b, + 0x2d, 0x25, 0xa7, 0x37, 0xc1, 0x1b, 0x0e, 0x36, 0xfa, 0x2e, 0x04, 0x2f, + 0xa2, 0x6b, 0xf3, 0x9c, 0x9c, 0x66, 0x0f, 0xa4, 0x6f, 0xe1, 0xd9, 0x73, + 0xab, 0x7c, 0x28, 0x0e, 0xf0, 0x01, 0xb0, 0x03, 0xac, 0x18, 0x37, 0xbf, + 0xd9, 0x0e, 0xcc, 0x03, 0x9e, 0x35, 0x39, 0x8c, 0x53, 0xeb, 0xef, 0x77, + 0xc4, 0x40, 0x16, 0xf4, 0xa4, 0x06, 0xc1, 0x88, 0x5a, 0x8b, 0x2d, 0x28, + 0xc3, 0xb6, 0x52, 0x78, 0x70, 0xdb, 0xec, 0x25, 0x93, 0x03, 0x43, 0x18, + 0x6a, 0x3a, 0x1f, 0x6f, 0x9f, 0x1f, 0xbd, 0x90, 0x09, 0x80, 0x70, 0x37, + 0x9e, 0x5f, 0xe9, 0xa8, 0xb3, 0xc4, 0x5e, 0xda, 0x58, 0xf9, 0x30, 0x35, + 0x08, 0xc5, 0xf4, 0x27, 0x21, 0x27, 0x76, 0x57, 0x68, 0x00, 0xd8, 0x34, + 0x94, 0x4b, 0x19, 0xc6, 0x3b, 0x0d, 0x65, 0x3d, 0xe2, 0x3e, 0x1f, 0x74, + 0x5e, 0xda, 0x52, 0x94, 0x4e, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x20, 0x00, 0x00, 0x01, 0x11, 0x6b, 0xfb, 0x9b, 0xce, + 0xf5, 0xaf, 0x35, 0x4d, 0x4d, 0xd4, 0xf3, 0x3d, 0x64, 0x65, 0x16, 0xaf, + 0x18, 0xd2, 0x99, 0xdc, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, + 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, + 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, + 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x2f, 0x99, 0x00, + 0x64, 0x01, 0x98, 0x0e, 0x80, 0x34, 0xc5, 0xb2, 0x09, 0xa9, 0xe5, 0xfc, + 0xff, 0x8c, 0xc7, 0x1c, 0x68, 0xbf, 0x7d, 0x36, 0x97, 0x49, 0x65, 0x60, + 0xdc, 0xdc, 0xea, 0x37, 0x6b, 0xc4, 0x1b, 0xd1, 0xd2, 0xe8, 0xe9, 0xee, + 0x7f, 0x4b, 0x3b, 0x87, 0x7f, 0x79, 0x60, 0x13, 0x00, 0x5f, 0xc0, 0xa1, + 0x31, 0x24, 0x24, 0xa0, 0x96, 0x51, 0x6e, 0xeb, 0x6f, 0xb2, 0x73, 0x25, + 0x8c, 0x10, 0x2e, 0xf6, 0xd4, 0xa5, 0x25, 0x64, 0xe6, 0xeb, 0x9b, 0xb5, + 0xfc, 0xd4, 0x30, 0x01, 0xf9, 0x08, 0x04, 0xc8, 0x4e, 0x01, 0xd1, 0x31, + 0x2e, 0x93, 0xf9, 0x7d, 0xce, 0x38, 0xe1, 0xcb, 0xbd, 0x38, 0x14, 0x01, + 0x3a, 0x49, 0x80, 0x31, 0x40, 0x15, 0x2d, 0xcb, 0xef, 0xdc, 0x7f, 0x7d, + 0xf1, 0x15, 0x57, 0xa3, 0xa5, 0xae, 0x26, 0x23, 0x0d, 0x51, 0x25, 0x5f, + 0xf3, 0xf8, 0x7d, 0x2f, 0xc2, 0xaf, 0xc0, 0x80, 0x6a, 0x00, 0xff, 0x00, + 0xef, 0xf2, 0x50, 0x66, 0x43, 0xa0, 0x60, 0xde, 0x49, 0x18, 0xee, 0xc3, + 0xb2, 0xdc, 0x4e, 0xf7, 0x2c, 0x86, 0x00, 0xcc, 0x0c, 0x86, 0x21, 0x08, + 0x26, 0x64, 0x27, 0x2f, 0x73, 0xf1, 0x5c, 0xed, 0xd1, 0x87, 0xb7, 0xe7, + 0x9d, 0xef, 0xa9, 0x52, 0xd0, 0x02, 0xc2, 0x99, 0x19, 0x89, 0x07, 0x2f, + 0xc8, 0x6f, 0xbf, 0x9c, 0x80, 0x64, 0x00, 0xfc, 0xa0, 0x13, 0x06, 0x06, + 0xb9, 0x49, 0x48, 0x69, 0x7d, 0xc6, 0xb9, 0xea, 0x56, 0xe1, 0xee, 0x17, + 0x7d, 0x01, 0x29, 0xe5, 0xa7, 0xa5, 0x3d, 0xfa, 0x7f, 0xff, 0x8f, 0xfe, + 0xba, 0x96, 0x18, 0xa4, 0x23, 0xa5, 0x3b, 0x2d, 0x27, 0x87, 0x54, 0xf7, + 0x2e, 0x00, 0x06, 0x80, 0x57, 0x00, 0xc0, 0x0a, 0x94, 0x1a, 0x9d, 0xfe, + 0x40, 0xd5, 0x63, 0xd6, 0x8f, 0x88, 0xb7, 0x7e, 0x96, 0x5f, 0x64, 0xef, + 0xb9, 0xdd, 0x55, 0x1d, 0x7e, 0x18, 0x01, 0xf8, 0x06, 0x20, 0x06, 0xfc, + 0xac, 0x8d, 0xd0, 0x84, 0x63, 0xb8, 0x13, 0xe6, 0xa8, 0x51, 0xc1, 0xf7, + 0x7c, 0x06, 0x05, 0x86, 0x06, 0x06, 0xa0, 0xb2, 0xc6, 0x81, 0x64, 0xa1, + 0x3b, 0x23, 0x65, 0xb7, 0x75, 0xbf, 0xd7, 0x6a, 0x96, 0xa8, 0x29, 0x08, + 0x40, 0xcc, 0xad, 0x4b, 0xbf, 0x7b, 0xf9, 0x28, 0x0c, 0x40, 0x1f, 0x80, + 0x80, 0x04, 0xf9, 0x18, 0x34, 0x98, 0x4d, 0x08, 0xfd, 0xbe, 0xfb, 0x75, + 0x9f, 0x76, 0x4a, 0x00, 0x7e, 0x50, 0x0c, 0x32, 0x70, 0x0e, 0x88, 0x59, + 0x0e, 0xc0, 0x5c, 0xbe, 0xe9, 0x1e, 0xb3, 0x02, 0xaf, 0xa0, 0xd2, 0xe9, + 0xc0, 0x50, 0xad, 0x86, 0x2d, 0x87, 0x3f, 0x6a, 0x06, 0xfb, 0xe7, 0x16, + 0x4d, 0x49, 0x0c, 0xb1, 0xa4, 0xd4, 0xf4, 0x9c, 0xfc, 0xd6, 0x14, 0x65, + 0xf3, 0xa2, 0x10, 0x03, 0xf0, 0x28, 0x42, 0x01, 0xd7, 0x21, 0x21, 0x01, + 0xab, 0xc9, 0xc4, 0xb0, 0x8d, 0xdd, 0x2e, 0xb3, 0xcf, 0xc4, 0x4b, 0xea, + 0xd4, 0xb1, 0x48, 0x62, 0x0b, 0xdb, 0x73, 0xb2, 0xf6, 0xca, 0x3a, 0xad, + 0xfe, 0xbf, 0x2e, 0x02, 0x00, 0x13, 0x80, 0x1c, 0x10, 0xc9, 0x7b, 0x0d, + 0x47, 0x74, 0x7d, 0xcf, 0x19, 0xf9, 0x1d, 0x55, 0x60, 0x19, 0x81, 0x52, + 0x60, 0x0c, 0x43, 0x78, 0x69, 0x58, 0xb4, 0x27, 0xb3, 0xee, 0xcc, 0xac, + 0xcf, 0x9b, 0x88, 0xbd, 0x65, 0x28, 0x25, 0x20, 0x69, 0x2c, 0xd6, 0x60, + 0xe9, 0xba, 0x9a, 0xf8, 0x80, 0x20, 0x02, 0xa0, 0x07, 0x80, 0x54, 0xa0, + 0xd6, 0x71, 0x8e, 0xf8, 0x57, 0xdf, 0x11, 0x6f, 0x2e, 0x02, 0x10, 0x07, + 0x7c, 0x86, 0x90, 0x14, 0x20, 0x94, 0x76, 0xe9, 0xeb, 0x4a, 0x76, 0x58, + 0xce, 0x63, 0x3b, 0xa8, 0xd3, 0x6f, 0xa1, 0xd2, 0xae, 0x18, 0x8e, 0x9c, + 0x9f, 0x97, 0xb3, 0x49, 0xfb, 0x35, 0xfc, 0xcc, 0x34, 0x02, 0xf0, 0x28, + 0x02, 0x74, 0x20, 0x00, 0xfc, 0x98, 0x92, 0x5a, 0x00, 0xf9, 0x5f, 0x65, + 0x30, 0xb1, 0x66, 0x5e, 0x98, 0x03, 0x50, 0x0d, 0x4b, 0x00, 0xd5, 0x3b, + 0xa7, 0x64, 0x64, 0x12, 0x11, 0x8c, 0x73, 0xd6, 0x2e, 0xf4, 0xd4, 0xa9, + 0x04, 0xc4, 0x23, 0xef, 0xb1, 0xce, 0xc2, 0x9c, 0xf6, 0x8f, 0xff, 0xbf, + 0x36, 0x01, 0xa0, 0x08, 0x52, 0x01, 0xa1, 0x7d, 0x28, 0xd9, 0x3b, 0xe6, + 0xfb, 0x90, 0x4f, 0xd7, 0x97, 0x00, 0x13, 0x81, 0x42, 0x11, 0x65, 0x93, + 0x40, 0xa1, 0x31, 0x28, 0x2c, 0xbd, 0xf0, 0x4f, 0xc4, 0xac, 0xeb, 0x7d, + 0xb0, 0xbd, 0x95, 0x7d, 0x32, 0x92, 0x49, 0x31, 0x09, 0xef, 0xf1, 0xf9, + 0xba, 0xcf, 0xc2, 0xe1, 0xfd, 0xf8, 0x00, 0x0c, 0x00, 0x1f, 0x80, 0x1e, + 0x16, 0x4d, 0x48, 0x6e, 0xe5, 0x81, 0x96, 0xff, 0xe6, 0x77, 0xdf, 0x91, + 0xdf, 0xda, 0x80, 0x04, 0xe0, 0x85, 0xf3, 0x89, 0xb8, 0x98, 0x56, 0xd9, + 0x29, 0x2c, 0x62, 0xd6, 0x94, 0xe6, 0x7f, 0xcd, 0x6e, 0xbc, 0xa5, 0x5e, + 0xd6, 0x93, 0x01, 0xb1, 0x4f, 0xd2, 0xcb, 0x4e, 0x6e, 0x22, 0x06, 0xfb, + 0xf9, 0xe0, 0x08, 0x40, 0x1d, 0xf2, 0x1a, 0x40, 0x50, 0x82, 0x51, 0xdb, + 0xa7, 0xad, 0x29, 0xd9, 0x63, 0x39, 0x8c, 0xee, 0xa3, 0x4d, 0xb3, 0x06, + 0x80, 0x5e, 0x05, 0x00, 0x4e, 0x84, 0x00, 0x1f, 0x93, 0x12, 0x4b, 0x40, + 0x1f, 0x2b, 0xec, 0xa6, 0x16, 0x2c, 0xcb, 0xe9, 0x94, 0xac, 0x02, 0xc2, + 0xb6, 0xcd, 0x98, 0x61, 0xdf, 0x7e, 0xe1, 0x72, 0x1b, 0xeb, 0xff, 0x1a, + 0x58, 0x6a, 0x40, 0xa9, 0x68, 0xc7, 0x19, 0x83, 0xef, 0x97, 0x00, 0xc5, + 0x28, 0xc3, 0x32, 0x77, 0xfb, 0xbf, 0x51, 0xea, 0x03, 0x77, 0xdb, 0x8a, + 0x49, 0x29, 0x39, 0x2c, 0xec, 0x65, 0xcd, 0x26, 0x86, 0x80, 0xdd, 0x2e, + 0xcc, 0xa6, 0xbb, 0x3a, 0x17, 0x87, 0x67, 0xb3, 0x7a, 0xbb, 0x92, 0x1a, + 0x01, 0x7b, 0x13, 0x00, 0x2c, 0x02, 0x85, 0x01, 0x82, 0x99, 0xbb, 0xad, + 0x3d, 0xcf, 0x71, 0x2b, 0x0e, 0xaa, 0x01, 0x00, 0x05, 0xe0, 0x16, 0x06, + 0x24, 0x98, 0x1a, 0x8e, 0x59, 0x5f, 0x76, 0xd8, 0xfc, 0xe7, 0xe1, 0x1e, + 0xfa, 0x15, 0x2e, 0x20, 0x1b, 0x15, 0xf3, 0x31, 0xae, 0x2d, 0xc7, 0xb8, + 0xfb, 0x47, 0xf7, 0xe5, 0xb8, 0x15, 0x0c, 0x0c, 0xe9, 0x00, 0xb0, 0x33, + 0xa3, 0xf5, 0x64, 0xa5, 0x2e, 0x9d, 0xba, 0x3e, 0xec, 0xcd, 0xb7, 0x59, + 0xeb, 0xbc, 0xf0, 0x06, 0x80, 0x17, 0x86, 0x00, 0x80, 0x99, 0xd0, 0x4a, + 0x4a, 0x53, 0xff, 0xee, 0xad, 0x8e, 0x56, 0x73, 0xd8, 0x83, 0x7d, 0x2a, + 0x94, 0x24, 0x31, 0x09, 0xcd, 0xd6, 0x1e, 0x3a, 0x6f, 0xf5, 0xf9, 0x20, + 0x0c, 0x80, 0x4e, 0x00, 0x78, 0x43, 0xe5, 0xa7, 0x74, 0x8d, 0xdf, 0x71, + 0xeb, 0xfb, 0xba, 0xc5, 0xeb, 0x30, 0x0c, 0x40, 0x2f, 0x49, 0x34, 0xb7, + 0x2b, 0x01, 0x8c, 0x37, 0x25, 0x47, 0xa7, 0xa0, 0x91, 0xd2, 0x23, 0x6c, + 0xd8, 0xeb, 0xdb, 0x52, 0x40, 0x61, 0x03, 0x46, 0xb2, 0xd9, 0xa1, 0xfb, + 0x35, 0xfc, 0xbc, 0x07, 0x60, 0x0e, 0xca, 0x21, 0xe2, 0x81, 0x08, 0x1b, + 0x74, 0xa1, 0x6e, 0x95, 0xed, 0xd4, 0x76, 0x34, 0xe3, 0x6b, 0xc0, 0xa0, + 0x03, 0xdc, 0x4c, 0x02, 0x9c, 0x98, 0x90, 0xc2, 0x5f, 0xee, 0xb7, 0x24, + 0x8d, 0xfd, 0xc6, 0x9a, 0x72, 0xaf, 0xa4, 0xd2, 0xdc, 0x02, 0xc2, 0x86, + 0x0c, 0x65, 0x3b, 0x9f, 0xe9, 0x1b, 0xef, 0xe7, 0x00, 0x30, 0x00, 0x72, + 0x1a, 0x4c, 0x2d, 0x3c, 0xb0, 0x94, 0x24, 0xcc, 0xa1, 0x8a, 0x50, 0xc1, + 0x46, 0x35, 0xd6, 0xe0, 0x3b, 0x02, 0x85, 0x16, 0x4b, 0x21, 0x13, 0x3e, + 0xe7, 0xe2, 0x5f, 0x7c, 0x8e, 0xee, 0xad, 0x89, 0xcf, 0xef, 0x63, 0x4b, + 0x20, 0x30, 0x8d, 0xff, 0xd9, 0x9c, 0xf6, 0x36, 0xa7, 0xf7, 0xf3, 0xe0, + 0x0c, 0x00, 0x2f, 0x00, 0x3c, 0x25, 0xe2, 0xf2, 0x4b, 0xc9, 0xe3, 0x45, + 0x8d, 0x37, 0xac, 0x79, 0x12, 0xe8, 0xa0, 0x03, 0x5c, 0x51, 0x34, 0xa2, + 0xd0, 0x4c, 0x29, 0x08, 0x47, 0xfc, 0x95, 0x9d, 0x7f, 0xa1, 0x9f, 0xfe, + 0x7b, 0x1f, 0x87, 0x5e, 0xc2, 0x90, 0x06, 0x10, 0xff, 0xb1, 0x24, 0xf8, + 0x7f, 0x7f, 0x36, 0x01, 0xd8, 0x03, 0xb0, 0x32, 0x5e, 0xe5, 0x15, 0xb6, + 0xc3, 0x06, 0xb9, 0xe5, 0xa3, 0x77, 0x19, 0xc7, 0x67, 0x77, 0x1f, 0x6a, + 0x00, 0xcc, 0x01, 0xf8, 0x01, 0xd9, 0x45, 0x15, 0x90, 0x33, 0x66, 0x4f, + 0x01, 0x5b, 0x32, 0x85, 0x2e, 0xf7, 0xf4, 0xab, 0x13, 0x0a, 0xc3, 0x1b, + 0x12, 0x0e, 0x90, 0xdf, 0x7f, 0x36, 0x01, 0x88, 0x03, 0xb0, 0x0a, 0xc0, + 0xa6, 0x26, 0x62, 0xb1, 0x4e, 0x49, 0x1c, 0xdb, 0xbe, 0xc8, 0xa0, 0x00, + 0x84, 0x10, 0x82, 0x80, 0x2c, 0xc5, 0x6d, 0xc6, 0x12, 0x9c, 0xc1, 0x8c, + 0xdf, 0x89, 0xf7, 0xd2, 0x69, 0x30, 0x0b, 0x0a, 0xdb, 0x66, 0x3b, 0x8d, + 0x34, 0xf8, 0x1b, 0xee, 0x52, 0x94, 0xbc, 0xfb, 0x5b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0x80, 0x00, 0x00, 0x01, 0x12, + 0x73, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4b, 0x31, 0x5b, 0x25, 0x3b, 0x7f, 0xf3, 0x8e, 0xce, + 0x45, 0xd7, 0x9c, 0x0d, 0x01, 0x32, 0x03, 0x08, 0x60, 0x16, 0x01, 0x90, + 0x97, 0xd9, 0xfa, 0x19, 0x0b, 0xdb, 0x9f, 0xbf, 0xe6, 0x81, 0x2b, 0x5e, + 0x9d, 0xbb, 0x07, 0xd2, 0xd7, 0xeb, 0x29, 0x63, 0x0d, 0x0c, 0x49, 0x30, + 0xbe, 0x52, 0x73, 0x74, 0x76, 0xf6, 0xcb, 0x33, 0xe7, 0x72, 0x94, 0xa4, + 0xa7, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x5e, 0x5c, 0x06, 0x00, 0x3a, 0x28, + 0x9a, 0x51, 0x34, 0xb4, 0xa5, 0x38, 0x0c, 0x21, 0xdb, 0x6c, 0xb1, 0xcb, + 0xfc, 0x2d, 0x57, 0xa8, 0xb4, 0xdf, 0x7f, 0xf0, 0xea, 0x6e, 0x52, 0x94, + 0xb3, 0xaa, 0xe5, 0x29, 0x79, 0x70, 0x1d, 0x00, 0xe8, 0xa2, 0x69, 0x44, + 0xd2, 0xd2, 0x94, 0xe0, 0x30, 0x87, 0x6d, 0xb2, 0xc7, 0x2f, 0xf0, 0xbb, + 0x4f, 0xf2, 0x33, 0xfa, 0x4f, 0xbf, 0x51, 0x4a, 0x52, 0xce, 0xab, 0x94, + 0xa9, 0x4a, 0x3a, 0x0f, 0xcc, 0xa7, 0x0e, 0xf5, 0xd1, 0x17, 0x29, 0x4a, + 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, + 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x97, 0x9a, 0x00, + 0x3e, 0x40, 0x01, 0xe7, 0x21, 0x8d, 0xe4, 0x9c, 0x8d, 0x98, 0xfe, 0x69, + 0xbc, 0xc1, 0x16, 0xe0, 0x1b, 0x60, 0x0b, 0x30, 0x69, 0x2d, 0x0e, 0x34, + 0x61, 0x28, 0x0f, 0x89, 0xb6, 0x1b, 0x89, 0xec, 0x1f, 0x4b, 0x5f, 0x5e, + 0x94, 0xa5, 0x8d, 0x57, 0x29, 0x4b, 0x20, 0x61, 0x5c, 0x34, 0xb4, 0x23, + 0x01, 0xff, 0xb4, 0xe2, 0xf5, 0xd2, 0xf2, 0x21, 0x80, 0x26, 0x28, 0x9b, + 0xcb, 0x18, 0x1b, 0xd8, 0x33, 0x7f, 0xd2, 0x4a, 0xdb, 0x36, 0x1c, 0x05, + 0xdd, 0x66, 0x2a, 0xb0, 0x98, 0x03, 0xa0, 0xc2, 0x19, 0xc4, 0xdf, 0x82, + 0x50, 0x53, 0x72, 0x8d, 0x49, 0xc7, 0x27, 0x8e, 0xba, 0x09, 0x79, 0x62, + 0x85, 0x7a, 0x1a, 0xfd, 0x55, 0x2e, 0x50, 0x15, 0x26, 0x62, 0x10, 0x6a, + 0x4a, 0x1a, 0x37, 0xa3, 0xb7, 0xec, 0xcb, 0x14, 0xbf, 0x64, 0x26, 0x81, + 0x44, 0x13, 0x0a, 0x42, 0x49, 0x7d, 0xcb, 0xcf, 0x91, 0xd0, 0xdd, 0x9d, + 0x9f, 0x84, 0xf6, 0x39, 0x47, 0xd9, 0x8d, 0xca, 0x16, 0x74, 0xda, 0xfd, + 0x05, 0x29, 0x4b, 0x2a, 0xae, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x20, 0x00, + 0x00, 0x01, 0x13, 0x73, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xad, 0xfc, 0xc8, 0x0e, 0x90, 0x34, 0x9a, + 0x50, 0xae, 0xe1, 0x4b, 0xb8, 0x5c, 0xaf, 0xc8, 0x7c, 0xb4, 0x84, 0x20, + 0x6b, 0xbf, 0x22, 0x0f, 0xb7, 0x80, 0xeb, 0xa0, 0xa1, 0xa9, 0xff, 0x6e, + 0x33, 0xe4, 0x09, 0xbd, 0x87, 0xbd, 0x13, 0xd3, 0xc4, 0x35, 0x8d, 0x7f, + 0xf3, 0x07, 0x41, 0x35, 0x25, 0xa7, 0xec, 0xdb, 0x1f, 0xdc, 0x89, 0xee, + 0xea, 0x32, 0x0b, 0x3b, 0x36, 0x46, 0x74, 0xf3, 0x78, 0x1d, 0xb4, 0xe4, + 0xec, 0x4c, 0xf9, 0xd6, 0x94, 0x93, 0xb5, 0x90, 0x84, 0x5e, 0xd8, 0xb4, + 0x7e, 0x30, 0x6e, 0xcd, 0xc5, 0xeb, 0xa1, 0x98, 0x89, 0x45, 0xb8, 0xcc, + 0x4a, 0xfd, 0x38, 0x60, 0x12, 0x1e, 0x06, 0xaa, 0xba, 0x10, 0x8c, 0x8f, + 0xf0, 0xd4, 0x7c, 0x79, 0xaf, 0x76, 0xc2, 0x1f, 0x3f, 0x75, 0xaf, 0x90, + 0x2e, 0x08, 0xd4, 0x21, 0x05, 0xa1, 0xc6, 0xee, 0x71, 0x06, 0xca, 0xaa, + 0xdf, 0x6a, 0x0a, 0xc0, 0x60, 0x97, 0xb0, 0xd6, 0xfc, 0x97, 0xb9, 0x98, + 0xc0, 0x37, 0x7a, 0x42, 0xc6, 0x74, 0xf1, 0x9b, 0xec, 0x66, 0xf7, 0x88, + 0x28, 0xb0, 0xc2, 0x62, 0x72, 0x50, 0x97, 0x47, 0x40, 0x4a, 0x39, 0xee, + 0xc7, 0x01, 0xdb, 0xe8, 0x1e, 0xc3, 0x1f, 0xea, 0xaf, 0xfe, 0x7e, 0x8c, + 0xc4, 0x2f, 0xf8, 0x1f, 0xe4, 0xec, 0x38, 0x3e, 0xd6, 0x33, 0x24, 0x98, + 0x37, 0x6d, 0xb8, 0xd6, 0x53, 0xac, 0x2f, 0xd4, 0x27, 0xa1, 0x21, 0x85, + 0x23, 0x76, 0x18, 0x7b, 0x8b, 0x97, 0x06, 0xa3, 0x24, 0xac, 0x07, 0x90, + 0x4f, 0xe3, 0xda, 0xec, 0x59, 0x3e, 0xce, 0xf7, 0x96, 0x8c, 0x19, 0xc6, + 0x6c, 0x35, 0x26, 0x89, 0x1d, 0xae, 0x70, 0x68, 0x66, 0x03, 0x38, 0xa4, + 0x36, 0x52, 0xc8, 0xff, 0xda, 0xb7, 0x42, 0x03, 0x77, 0x1a, 0xeb, 0x3c, + 0x70, 0x59, 0xf7, 0x3f, 0x06, 0xa3, 0x13, 0x7f, 0x77, 0xdd, 0xdf, 0xb0, + 0xf9, 0xac, 0x9c, 0x4f, 0x8b, 0xf4, 0x19, 0x25, 0x6f, 0xf0, 0xc7, 0xc6, + 0x0f, 0x12, 0x7d, 0xe6, 0x83, 0x0a, 0xfc, 0x85, 0xc0, 0x7a, 0x95, 0x88, + 0xcb, 0x70, 0xb5, 0x5e, 0xcd, 0x39, 0x08, 0x3b, 0x8d, 0xbc, 0x40, 0x0d, + 0xf1, 0x40, 0x63, 0x65, 0xed, 0x82, 0x43, 0xcf, 0xa3, 0x8a, 0x01, 0xf4, + 0xef, 0xda, 0xa5, 0x25, 0x33, 0x21, 0x01, 0xff, 0xac, 0x81, 0x02, 0x66, + 0x1a, 0x5e, 0xc3, 0xfc, 0x4b, 0xc5, 0x23, 0x84, 0x71, 0xa2, 0xee, 0x58, + 0x14, 0x4e, 0x42, 0x7f, 0xfd, 0x95, 0xd8, 0x4d, 0xcc, 0x65, 0x77, 0xcd, + 0x04, 0x3f, 0x90, 0xde, 0x73, 0x1e, 0xe2, 0xb5, 0x68, 0x62, 0x10, 0x4d, + 0x4f, 0x46, 0xcd, 0x9b, 0xac, 0xd1, 0x35, 0xa1, 0x85, 0xa1, 0x1d, 0x38, + 0xc4, 0xa3, 0xa0, 0x69, 0x15, 0xa7, 0x42, 0x50, 0x18, 0x84, 0x9a, 0x94, + 0xa8, 0x46, 0xba, 0x96, 0x46, 0x6b, 0x3a, 0xc3, 0xda, 0xde, 0x19, 0x08, + 0x0c, 0x40, 0x0c, 0x30, 0x6a, 0x4a, 0x43, 0xf2, 0xdb, 0x12, 0x5d, 0x3b, + 0x61, 0xef, 0xf6, 0x16, 0x4e, 0xbd, 0xd5, 0xe4, 0xc0, 0x62, 0x1a, 0x84, + 0xa0, 0x84, 0x52, 0x3b, 0xe2, 0x8a, 0x58, 0x0a, 0xf9, 0xf7, 0x12, 0x5f, + 0xb6, 0x16, 0xbb, 0x5d, 0x5f, 0xe3, 0x19, 0xbe, 0xe5, 0xe4, 0x1f, 0xd9, + 0x08, 0x23, 0xd1, 0x28, 0x46, 0x25, 0x6e, 0xaf, 0xc9, 0x2c, 0x66, 0x20, + 0xd9, 0x72, 0x72, 0x49, 0x99, 0x03, 0x46, 0x74, 0x7e, 0x3d, 0x7b, 0x88, + 0xbb, 0xc8, 0xc5, 0x7e, 0xdf, 0xfc, 0xa5, 0x71, 0x63, 0xfd, 0xd6, 0x16, + 0xab, 0x3b, 0xca, 0x5e, 0xc5, 0x3a, 0x12, 0x86, 0xe6, 0x30, 0xe2, 0x2c, + 0x03, 0x72, 0x32, 0x0a, 0x53, 0x7f, 0x9f, 0x91, 0x0f, 0x90, 0xb2, 0xf7, + 0x41, 0x48, 0xeb, 0xf8, 0xd1, 0x43, 0xae, 0x68, 0x6a, 0x4a, 0xe5, 0x8d, + 0x1a, 0xe7, 0x32, 0x8e, 0x85, 0xa8, 0x3e, 0x2f, 0xa2, 0x43, 0x46, 0x28, + 0x66, 0x65, 0xb3, 0xfb, 0x0a, 0x50, 0x8c, 0x18, 0x57, 0x66, 0xfd, 0x64, + 0x03, 0x42, 0xed, 0xc4, 0x34, 0xa0, 0xb2, 0xdf, 0x77, 0x16, 0x2a, 0x62, + 0x6a, 0x50, 0x92, 0xf3, 0x70, 0x8c, 0xda, 0x79, 0xd4, 0x17, 0xe2, 0xf9, + 0xa1, 0xa8, 0x42, 0x32, 0x3b, 0x33, 0xa0, 0xe7, 0x34, 0x8f, 0x10, 0xd0, + 0xcd, 0xd2, 0x52, 0x11, 0x9b, 0xfd, 0x9c, 0x73, 0xdb, 0x8b, 0x2f, 0x16, + 0x4d, 0x70, 0x3d, 0x94, 0x1d, 0xae, 0x20, 0x19, 0x42, 0x08, 0x63, 0x5d, + 0xf2, 0xed, 0x16, 0x4e, 0xf4, 0xd6, 0x1b, 0x0a, 0x26, 0x06, 0x0d, 0x4f, + 0xe8, 0x62, 0x12, 0x1f, 0x72, 0xfb, 0x8d, 0x4a, 0x94, 0xc2, 0xdd, 0xba, + 0xb3, 0x5f, 0x42, 0xbc, 0xd1, 0x34, 0x94, 0x8f, 0xc0, 0x52, 0x03, 0x7e, + 0xb6, 0x3c, 0xd3, 0x7f, 0xe7, 0xf1, 0x43, 0xef, 0x47, 0x8f, 0x5d, 0xe7, + 0xa0, 0x7a, 0xed, 0xbd, 0x7f, 0xe1, 0xcb, 0xe3, 0x03, 0x03, 0x10, 0x02, + 0x3f, 0x69, 0x0d, 0xc8, 0x42, 0x0a, 0x6c, 0xdf, 0xe7, 0xe1, 0xe7, 0x59, + 0x88, 0x69, 0x29, 0x29, 0xcd, 0xdf, 0x2b, 0x08, 0xd3, 0x90, 0xd2, 0x84, + 0x96, 0xfb, 0xb9, 0x19, 0xae, 0xcc, 0x61, 0xe6, 0xfb, 0x2b, 0xb8, 0x9b, + 0xb6, 0xc5, 0x6d, 0xb6, 0xec, 0xfc, 0xfb, 0x9b, 0xc3, 0x10, 0xe2, 0x90, + 0x8c, 0x34, 0x48, 0xea, 0xe2, 0x1a, 0x51, 0xcb, 0x1b, 0xbb, 0x9c, 0xd8, + 0x74, 0xc4, 0xc2, 0xf0, 0xc2, 0xd1, 0xd2, 0x33, 0xa5, 0x97, 0x52, 0xdb, + 0x45, 0x69, 0xa5, 0x25, 0x23, 0x13, 0x3f, 0x2d, 0x19, 0x09, 0xff, 0x12, + 0xb6, 0x3c, 0x5b, 0x9e, 0xa6, 0x1f, 0x7b, 0x17, 0x6b, 0x9c, 0x58, 0xcc, + 0xad, 0xc6, 0x80, 0xf4, 0xe0, 0xec, 0x41, 0xba, 0x37, 0x9b, 0xad, 0xaf, + 0xc0, 0x93, 0x00, 0xb0, 0x0c, 0x08, 0x69, 0x02, 0x7f, 0x23, 0x11, 0x49, + 0xd7, 0xad, 0x26, 0x24, 0x61, 0x68, 0xe3, 0x93, 0xb8, 0x57, 0xbc, 0xa8, + 0x6f, 0x46, 0x02, 0x85, 0x14, 0x82, 0xd1, 0xfb, 0xf4, 0xa7, 0x0b, 0x22, + 0xde, 0xef, 0x2a, 0xe9, 0x42, 0xc6, 0x8b, 0xfc, 0x81, 0x47, 0x32, 0xb0, + 0xf0, 0x00, 0x3b, 0x26, 0x20, 0x00, 0xe4, 0x34, 0x6f, 0x08, 0xc0, 0x5d, + 0x22, 0x3e, 0xdd, 0x82, 0xfb, 0x5e, 0xc4, 0x9a, 0x1b, 0xc9, 0xa5, 0xf4, + 0xf3, 0x49, 0xc2, 0xaf, 0x1a, 0x01, 0x98, 0x15, 0x28, 0x37, 0xb1, 0x4d, + 0xc6, 0xe4, 0x72, 0xd2, 0x6f, 0xfb, 0x33, 0x08, 0x22, 0xdf, 0x42, 0x90, + 0x63, 0x8c, 0xdf, 0x8e, 0x69, 0xfb, 0x94, 0xa5, 0x2e, 0x0b, 0x5b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x88, 0x00, 0x00, 0x01, 0x14, 0x83, 0xfb, 0xfd, + 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0x00, 0x00, 0x01, 0x15, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x16, 0x83, + 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0x00, 0x00, 0x01, 0x17, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, + 0x18, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x19, 0x83, 0xfb, 0xfd, 0x29, + 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, + 0x00, 0x01, 0x1a, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x1b, 0x83, 0xfb, + 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0x00, 0x00, 0x01, 0x1c, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x1d, + 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x1e, 0x83, 0xfb, 0xfd, 0x29, 0x49, + 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, + 0x01, 0x1f, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x20, 0x83, 0xfb, 0xfd, + 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0x00, 0x00, 0x01, 0x21, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x22, 0x83, + 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0x00, 0x00, 0x01, 0x23, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, + 0x24, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0xb7, +}; diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/suspend.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/suspend.h new file mode 100644 index 0000000000..bea25ee510 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/suspend.h @@ -0,0 +1,36 @@ +/* + * $Id: suspend.h,v 1.2 2008/10/22 11:59:32 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_SUSPEND_H +#define VDR_STREAMDEV_SUSPEND_H + +#include + +class cSuspendLive: public cPlayer, public cThread { +protected: + virtual void Activate(bool On); + virtual void Action(void); + + void Stop(void); + +public: + cSuspendLive(void); + virtual ~cSuspendLive(); +}; + +class cSuspendCtl: public cControl { +private: + cSuspendLive *m_Suspend; + static bool m_Active; + +public: + cSuspendCtl(void); + virtual ~cSuspendCtl(); + virtual void Hide(void) {} + virtual eOSState ProcessKey(eKeys Key); + + static bool IsActive(void) { return m_Active; } +}; + +#endif // VDR_STREAMDEV_SUSPEND_H diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/tools.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/tools.c new file mode 100644 index 0000000000..1c7cc18cbb --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/tools.c @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "tools.h" + + +uint64_t ntohll(uint64_t a) +{ + return htonll(a); +} + +uint64_t htonll(uint64_t a) +{ +#if BYTE_ORDER == BIG_ENDIAN + return a; +#else + uint64_t b = 0; + + b = ((a << 56) & 0xFF00000000000000ULL) + | ((a << 40) & 0x00FF000000000000ULL) + | ((a << 24) & 0x0000FF0000000000ULL) + | ((a << 8) & 0x000000FF00000000ULL) + | ((a >> 8) & 0x00000000FF000000ULL) + | ((a >> 24) & 0x0000000000FF0000ULL) + | ((a >> 40) & 0x000000000000FF00ULL) + | ((a >> 56) & 0x00000000000000FFULL) ; + + return b; +#endif +} diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/tools.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/tools.h new file mode 100644 index 0000000000..fda1785f31 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/tools.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __VNSI_TOOLS_H +#define __VNSI_TOOLS_H + +#include + +#define TRANSPONDER(c1, c2) (c1->Transponder() == c2->Transponder()) + +uint64_t ntohll(uint64_t a); +uint64_t htonll(uint64_t a); + +#endif //__VNSI_TOOLS_H diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vdrcommand.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vdrcommand.h new file mode 100644 index 0000000000..4826158e08 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vdrcommand.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef VDRCOMMAND_H +#define VDRCOMMAND_H + +/** Current VNSI Protocol Version number */ +const static uint32_t VNSIProtocolVersion = 1; + +/** Packet types */ +const static uint32_t CHANNEL_REQUEST_RESPONSE = 1; +const static uint32_t CHANNEL_STREAM = 2; +const static uint32_t CHANNEL_KEEPALIVE = 3; +const static uint32_t CHANNEL_NETLOG = 4; +const static uint32_t CHANNEL_STATUS = 5; +const static uint32_t CHANNEL_SCAN = 6; + + +/** Response packets operation codes */ + +/* OPCODE 1 - 19: VNSI network functions for general purpose */ +const static uint32_t VDR_LOGIN = 1; +const static uint32_t VDR_GETTIME = 2; +const static uint32_t VDR_ENABLESTATUSINTERFACE = 3; +const static uint32_t VDR_ENABLEOSDINTERFACE = 4; + +/* OPCODE 20 - 39: VNSI network functions for live streaming */ +const static uint32_t VDR_CHANNELSTREAM_OPEN = 20; +const static uint32_t VDR_CHANNELSTREAM_CLOSE = 21; + +/* OPCODE 40 - 59: VNSI network functions for recording streaming */ +const static uint32_t VDR_RECSTREAM_OPEN = 40; +const static uint32_t VDR_RECSTREAM_CLOSE = 41; +const static uint32_t VDR_RECSTREAM_GETBLOCK = 42; +const static uint32_t VDR_RECSTREAM_POSTOFRAME = 43; +const static uint32_t VDR_RECSTREAM_FRAMETOPOS = 44; +const static uint32_t VDR_RECSTREAM_GETIFRAME = 45; + +/* OPCODE 60 - 79: VNSI network functions for channel access */ +const static uint32_t VDR_CHANNELS_GROUPSCOUNT = 60; +const static uint32_t VDR_CHANNELS_GETCOUNT = 61; +const static uint32_t VDR_CHANNELS_GETGROUPS = 62; +const static uint32_t VDR_CHANNELS_GETCHANNELS = 63; + +/* OPCODE 80 - 99: VNSI network functions for timer access */ +const static uint32_t VDR_TIMER_GETCOUNT = 80; +const static uint32_t VDR_TIMER_GET = 81; +const static uint32_t VDR_TIMER_GETLIST = 82; +const static uint32_t VDR_TIMER_ADD = 83; +const static uint32_t VDR_TIMER_DELETE = 84; +const static uint32_t VDR_TIMER_UPDATE = 85; + +/* OPCODE 100 - 119: VNSI network functions for recording access */ +const static uint32_t VDR_RECORDINGS_DISKSIZE = 100; +const static uint32_t VDR_RECORDINGS_GETCOUNT = 101; +const static uint32_t VDR_RECORDINGS_GETLIST = 102; +const static uint32_t VDR_RECORDINGS_GETINFO = 103; +const static uint32_t VDR_RECORDINGS_DELETE = 104; +const static uint32_t VDR_RECORDINGS_MOVE = 105; + +/* OPCODE 120 - 139: VNSI network functions for epg access and manipulating */ +const static uint32_t VDR_EPG_GETFORCHANNEL = 120; + +/* OPCODE 140 - 159: VNSI network functions for channel scanning */ +const static uint32_t VDR_SCAN_SUPPORTED = 140; +const static uint32_t VDR_SCAN_GETCOUNTRIES = 141; +const static uint32_t VDR_SCAN_GETSATELLITES = 142; +const static uint32_t VDR_SCAN_START = 143; +const static uint32_t VDR_SCAN_STOP = 144; + + +/** Stream packet types (server -> client) */ +const static uint32_t VDR_STREAM_CHANGE = 1; +const static uint32_t VDR_STREAM_STATUS = 2; +const static uint32_t VDR_STREAM_QUEUESTATUS = 3; +const static uint32_t VDR_STREAM_MUXPKT = 4; +const static uint32_t VDR_STREAM_SIGNALINFO = 5; +const static uint32_t VDR_STREAM_CONTENTINFO = 6; + +/** Scan packet types (server -> client) */ +const static uint32_t VDR_SCANNER_PERCENTAGE = 1; +const static uint32_t VDR_SCANNER_SIGNAL = 2; +const static uint32_t VDR_SCANNER_DEVICE = 3; +const static uint32_t VDR_SCANNER_TRANSPONDER = 4; +const static uint32_t VDR_SCANNER_NEWCHANNEL = 5; +const static uint32_t VDR_SCANNER_FINISHED = 6; +const static uint32_t VDR_SCANNER_STATUS = 7; + +/** Status packet types (server -> client) */ +const static uint32_t VDR_STATUS_TIMERCHANGE = 1; +const static uint32_t VDR_STATUS_RECORDING = 2; +const static uint32_t VDR_STATUS_MESSAGE = 3; + +/** Packet return codes */ +const static uint32_t VDR_RET_OK = 0; +const static uint32_t VDR_RET_RECRUNNING = 1; +const static uint32_t VDR_RET_NOTSUPPORTED = 995; +const static uint32_t VDR_RET_DATAUNKNOWN = 996; +const static uint32_t VDR_RET_DATALOCKED = 997; +const static uint32_t VDR_RET_DATAINVALID = 998; +const static uint32_t VDR_RET_ERROR = 999; + +#endif diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vnsiserver.c b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vnsiserver.c new file mode 100644 index 0000000000..2ee3522e26 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vnsiserver.c @@ -0,0 +1,138 @@ +/* + * vnsiserver.c: A plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id$ + */ + +#include +#include "server.h" + +static const char *VERSION = "0.0.1"; +static const char *DESCRIPTION = "VDR-Network-Streaming-Interface (VNSI) Server"; + +class cPluginVNSIServer : public cPlugin { +private: + cServer *Server; + +public: + cPluginVNSIServer(void); + virtual ~cPluginVNSIServer(); + virtual const char *Version(void) { return VERSION; } + virtual const char *Description(void) { return DESCRIPTION; } + virtual const char *CommandLineHelp(void); + virtual bool ProcessArgs(int argc, char *argv[]); + virtual bool Initialize(void); + virtual bool Start(void); + virtual void Stop(void); + virtual void Housekeeping(void); + virtual void MainThreadHook(void); + virtual cString Active(void); + virtual time_t WakeupTime(void); + virtual const char *MainMenuEntry(void) { return NULL; } + virtual cOsdObject *MainMenuAction(void) { return NULL; } + virtual cMenuSetupPage *SetupMenu(void); + virtual bool SetupParse(const char *Name, const char *Value); + virtual bool Service(const char *Id, void *Data = NULL); + virtual const char **SVDRPHelpPages(void); + virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode); + }; + +cPluginVNSIServer::cPluginVNSIServer(void) +{ + Server = NULL; +} + +cPluginVNSIServer::~cPluginVNSIServer() +{ + // Clean up after yourself! +} + +const char *cPluginVNSIServer::CommandLineHelp(void) +{ + // Return a string that describes all known command line options. + return NULL; +} + +bool cPluginVNSIServer::ProcessArgs(int argc, char *argv[]) +{ + // Implement command line argument processing here if applicable. + return true; +} + +bool cPluginVNSIServer::Initialize(void) +{ + // Initialize any background activities the plugin shall perform. + VNSIServerConfig.ConfigDirectory = ConfigDirectory(PLUGIN_NAME_I18N); + return true; +} + +bool cPluginVNSIServer::Start(void) +{ + VNSIServerConfig.readNoSignalStream(); + Server = new cServer(VNSIServerConfig.listen_port); + + return true; +} + +void cPluginVNSIServer::Stop(void) +{ + delete Server; + Server = NULL; +} + +void cPluginVNSIServer::Housekeeping(void) +{ + // Perform any cleanup or other regular tasks. +} + +void cPluginVNSIServer::MainThreadHook(void) +{ + // Perform actions in the context of the main program thread. + // WARNING: Use with great care - see PLUGINS.html! +} + +cString cPluginVNSIServer::Active(void) +{ + // Return a message string if shutdown should be postponed + return NULL; +} + +time_t cPluginVNSIServer::WakeupTime(void) +{ + // Return custom wakeup time for shutdown script + return 0; +} + +cMenuSetupPage *cPluginVNSIServer::SetupMenu(void) +{ + // Return a setup menu in case the plugin supports one. + return NULL; +} + +bool cPluginVNSIServer::SetupParse(const char *Name, const char *Value) +{ + // Parse your own setup parameters and store their values. + return false; +} + +bool cPluginVNSIServer::Service(const char *Id, void *Data) +{ + // Handle custom service requests from other plugins + return false; +} + +const char **cPluginVNSIServer::SVDRPHelpPages(void) +{ + // Return help text for SVDRP commands this plugin implements + return NULL; +} + +cString cPluginVNSIServer::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) +{ + // Process SVDRP commands this plugin implements + return NULL; +} + +VDRPLUGINCREATOR(cPluginVNSIServer); // Don't touch this! diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vnsiserver/allowed_hosts.conf b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vnsiserver/allowed_hosts.conf new file mode 100644 index 0000000000..b9fa1af1a8 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vnsiserver/allowed_hosts.conf @@ -0,0 +1,13 @@ +# +# allowed_hosts.conf This file describes a number of host addresses that +# are allowed to connect to the streamdev server running +# with the Video Disk Recorder (VDR) on this system. +# Syntax: +# +# IP-Address[/Netmask] +# + +127.0.0.1 # always accept localhost +#192.168.0.0/24 # any host on the local net +#204.152.189.113 # a specific host +#0.0.0.0/0 # any host on any net (USE THIS WITH CARE!) diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vnsiserver/noSignal.mpg b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/vnsiserver/noSignal.mpg new file mode 100644 index 0000000000000000000000000000000000000000..ebf1ff1140e3ae372cb3e9d34461cf43dc36ec2b GIT binary patch literal 25207 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 literal 0 HcmV?d00001 diff --git a/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/wirbelscanservice.h b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/wirbelscanservice.h new file mode 100644 index 0000000000..625e0d3d96 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdr-plugin-vnsiserver/wirbelscanservice.h @@ -0,0 +1,57 @@ +/* + * wirbelscan.c: A plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id$ + */ + +#ifndef __WIRBELSCAN_SERVICE_H +#define __WIRBELSCAN_SERVICE_H + +typedef enum scantype +{ + DVB_TERR = 0, + DVB_CABLE = 1, + DVB_SAT = 2, + PVRINPUT = 3, + PVRINPUT_FM = 4, + DVB_ATSC = 5, +} scantype_t; + +typedef void (*WirbelScanService_GetCountries_v1_0)(int index, const char *isoName, const char *longName); +typedef void (*WirbelScanService_GetSatellites_v1_0)(int index, const char *shortName, const char *longName); + +struct WirbelScanService_DoScan_v1_0 +{ + scantype_t type; + + bool scan_tv; + bool scan_radio; + bool scan_fta; + bool scan_scrambled; + bool scan_hd; + + int CountryIndex; + + int DVBC_Inversion; + int DVBC_Symbolrate; + int DVBC_QAM; + + int DVBT_Inversion; + + int SatIndex; + + int ATSC_Type; + + void (*SetPercentage)(int percent); + void (*SetSignalStrength)(int strenght, bool locked); + void (*SetDeviceInfo)(const char *Info); + void (*SetTransponder)(const char *Info); + void (*NewChannel)(const char *Name, bool isRadio, bool isEncrypted, bool isHD); + void (*IsFinished)(); + void (*SetStatus)(int status); +}; + +#endif //__WIRBELSCAN_SERVICE_H + diff --git a/xbmc/pvrclients/vdr-vnsi/vdrcommand.h b/xbmc/pvrclients/vdr-vnsi/vdrcommand.h new file mode 100644 index 0000000000..4532482ed2 --- /dev/null +++ b/xbmc/pvrclients/vdr-vnsi/vdrcommand.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2010 Alwin Esch (Team XBMC) + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef VDRCOMMAND_H +#define VDRCOMMAND_H + +/** Current VNSI Protocol Version number */ +const static uint32_t VNSIProtocolVersion = 1; + + +/** Packet types */ +const static uint32_t CHANNEL_REQUEST_RESPONSE = 1; +const static uint32_t CHANNEL_STREAM = 2; +const static uint32_t CHANNEL_KEEPALIVE = 3; +const static uint32_t CHANNEL_NETLOG = 4; +const static uint32_t CHANNEL_STATUS = 5; +const static uint32_t CHANNEL_SCAN = 6; + + +/** Response packets operation codes */ + +/* OPCODE 1 - 19: VNSI network functions for general purpose */ +const static uint32_t VDR_LOGIN = 1; +const static uint32_t VDR_GETTIME = 2; +const static uint32_t VDR_ENABLESTATUSINTERFACE = 3; +const static uint32_t VDR_ENABLEOSDINTERFACE = 4; + +/* OPCODE 20 - 39: VNSI network functions for live streaming */ +const static uint32_t VDR_CHANNELSTREAM_OPEN = 20; +const static uint32_t VDR_CHANNELSTREAM_CLOSE = 21; + +/* OPCODE 40 - 59: VNSI network functions for recording streaming */ +const static uint32_t VDR_RECSTREAM_OPEN = 40; +const static uint32_t VDR_RECSTREAM_CLOSE = 41; +const static uint32_t VDR_RECSTREAM_GETBLOCK = 42; +const static uint32_t VDR_RECSTREAM_POSTOFRAME = 43; +const static uint32_t VDR_RECSTREAM_FRAMETOPOS = 44; +const static uint32_t VDR_RECSTREAM_GETIFRAME = 45; + +/* OPCODE 60 - 79: VNSI network functions for channel access */ +const static uint32_t VDR_CHANNELS_GROUPSCOUNT = 60; +const static uint32_t VDR_CHANNELS_GETCOUNT = 61; +const static uint32_t VDR_CHANNELS_GETGROUPS = 62; +const static uint32_t VDR_CHANNELS_GETCHANNELS = 63; + +/* OPCODE 80 - 99: VNSI network functions for timer access */ +const static uint32_t VDR_TIMER_GETCOUNT = 80; +const static uint32_t VDR_TIMER_GET = 81; +const static uint32_t VDR_TIMER_GETLIST = 82; +const static uint32_t VDR_TIMER_ADD = 83; +const static uint32_t VDR_TIMER_DELETE = 84; +const static uint32_t VDR_TIMER_UPDATE = 85; + +/* OPCODE 100 - 119: VNSI network functions for recording access */ +const static uint32_t VDR_RECORDINGS_DISKSIZE = 100; +const static uint32_t VDR_RECORDINGS_GETCOUNT = 101; +const static uint32_t VDR_RECORDINGS_GETLIST = 102; +const static uint32_t VDR_RECORDINGS_GETINFO = 103; +const static uint32_t VDR_RECORDINGS_DELETE = 104; +const static uint32_t VDR_RECORDINGS_MOVE = 105; + +/* OPCODE 120 - 139: VNSI network functions for epg access and manipulating */ +const static uint32_t VDR_EPG_GETFORCHANNEL = 120; + +/* OPCODE 140 - 159: VNSI network functions for channel scanning */ +const static uint32_t VDR_SCAN_SUPPORTED = 140; +const static uint32_t VDR_SCAN_GETCOUNTRIES = 141; +const static uint32_t VDR_SCAN_GETSATELLITES = 142; +const static uint32_t VDR_SCAN_START = 143; +const static uint32_t VDR_SCAN_STOP = 144; + + +/** Stream packet types (server -> client) */ +const static uint32_t VDR_STREAM_CHANGE = 1; +const static uint32_t VDR_STREAM_STATUS = 2; +const static uint32_t VDR_STREAM_QUEUESTATUS = 3; +const static uint32_t VDR_STREAM_MUXPKT = 4; +const static uint32_t VDR_STREAM_SIGNALINFO = 5; +const static uint32_t VDR_STREAM_CONTENTINFO = 6; + +/** Scan packet types (server -> client) */ +const static uint32_t VDR_SCANNER_PERCENTAGE = 1; +const static uint32_t VDR_SCANNER_SIGNAL = 2; +const static uint32_t VDR_SCANNER_DEVICE = 3; +const static uint32_t VDR_SCANNER_TRANSPONDER = 4; +const static uint32_t VDR_SCANNER_NEWCHANNEL = 5; +const static uint32_t VDR_SCANNER_FINISHED = 6; +const static uint32_t VDR_SCANNER_STATUS = 7; + +/** Status packet types (server -> client) */ +const static uint32_t VDR_STATUS_TIMERCHANGE = 1; +const static uint32_t VDR_STATUS_RECORDING = 2; +const static uint32_t VDR_STATUS_MESSAGE = 3; + +/** Packet return codes */ +const static uint32_t VDR_RET_OK = 0; +const static uint32_t VDR_RET_RECRUNNING = 1; +const static uint32_t VDR_RET_NOTSUPPORTED = 995; +const static uint32_t VDR_RET_DATAUNKNOWN = 996; +const static uint32_t VDR_RET_DATALOCKED = 997; +const static uint32_t VDR_RET_DATAINVALID = 998; +const static uint32_t VDR_RET_ERROR = 999; + +#endif diff --git a/xbmc/settings/VideoSettings.h b/xbmc/settings/VideoSettings.h index 1699802e6d..aba5e32330 100644 --- a/xbmc/settings/VideoSettings.h +++ b/xbmc/settings/VideoSettings.h @@ -51,6 +51,7 @@ enum EINTERLACEMETHOD VS_INTERLACEMETHOD_VDPAU_TEMPORAL_HALF=13, VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL=14, VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL_HALF=15, + VS_INTERLACEMETHOD_AUTO_ION=16, }; enum ESCALINGMETHOD diff --git a/xbmc/utils/GUIInfoManager.cpp b/xbmc/utils/GUIInfoManager.cpp index 7b2f974c9b..dedc557cf6 100644 --- a/xbmc/utils/GUIInfoManager.cpp +++ b/xbmc/utils/GUIInfoManager.cpp @@ -29,6 +29,7 @@ #include "Util.h" #include "lib/libscrobbler/lastfmscrobbler.h" #include "Weather.h" +#include "PVRManager.h" #include "PartyModeManager.h" #include "addons/Visualisation.h" #include "ButtonTranslator.h" @@ -46,6 +47,8 @@ #include "LastFmManager.h" #include "PictureInfoTag.h" #include "MusicInfoTag.h" +#include "PVREpg.h" +#include "PVRChannels.h" #include "GUIDialogMusicScan.h" #include "GUIDialogVideoScan.h" #include "GUIWindowManager.h" @@ -243,6 +246,54 @@ int CGUIInfoManager::TranslateSingleString(const CStdString &strCondition) else if (strTest.Equals("weather.fanartcode")) ret = WEATHER_FANART_CODE; else if (strTest.Equals("weather.plugin")) ret = WEATHER_PLUGIN; } + else if (strCategory.Equals("pvr")) + { + if (strTest.Equals("pvr.isrecording")) ret = PVR_IS_RECORDING; + else if (strTest.Equals("pvr.hastimer")) ret = PVR_HAS_TIMER; + else if (strTest.Equals("pvr.nowrecordingtitle")) ret = PVR_NOW_RECORDING_TITLE; + else if (strTest.Equals("pvr.nowrecordingdatetime")) ret = PVR_NOW_RECORDING_DATETIME; + else if (strTest.Equals("pvr.nowrecordingchannel")) ret = PVR_NOW_RECORDING_CHANNEL; + else if (strTest.Equals("pvr.nextrecordingtitle")) ret = PVR_NEXT_RECORDING_TITLE; + else if (strTest.Equals("pvr.nextrecordingdatetime")) ret = PVR_NEXT_RECORDING_DATETIME; + else if (strTest.Equals("pvr.nextrecordingchannel")) ret = PVR_NEXT_RECORDING_CHANNEL; + else if (strTest.Equals("pvr.backendname")) ret = PVR_BACKEND_NAME; + else if (strTest.Equals("pvr.backendversion")) ret = PVR_BACKEND_VERSION; + else if (strTest.Equals("pvr.backendhost")) ret = PVR_BACKEND_HOST; + else if (strTest.Equals("pvr.backenddiskspace")) ret = PVR_BACKEND_DISKSPACE; + else if (strTest.Equals("pvr.backendchannels")) ret = PVR_BACKEND_CHANNELS; + else if (strTest.Equals("pvr.backendtimers")) ret = PVR_BACKEND_TIMERS; + else if (strTest.Equals("pvr.backendrecordings")) ret = PVR_BACKEND_RECORDINGS; + else if (strTest.Equals("pvr.backendnumber")) ret = PVR_BACKEND_NUMBER; + else if (strTest.Equals("pvr.hasepg")) ret = PVR_HAS_EPG; + else if (strTest.Equals("pvr.hastxt")) ret = PVR_HAS_TXT; + else if (strTest.Equals("pvr.hasdirector")) ret = PVR_HAS_DIRECTOR; + else if (strTest.Equals("pvr.totaldiscspace")) ret = PVR_TOTAL_DISKSPACE; + else if (strTest.Equals("pvr.nexttimer")) ret = PVR_NEXT_TIMER; + else if (strTest.Equals("pvr.isplayingtv")) ret = PVR_IS_PLAYING_TV; + else if (strTest.Equals("pvr.isplayingradio")) ret = PVR_IS_PLAYING_RADIO; + else if (strTest.Equals("pvr.isplayingrecording")) ret = PVR_IS_PLAYING_RECORDING; + else if (strTest.Equals("pvr.duration")) ret = PVR_PLAYING_DURATION; + else if (strTest.Equals("pvr.time")) ret = PVR_PLAYING_TIME; + else if (strTest.Equals("pvr.progress")) ret = PVR_PLAYING_PROGRESS; + else if (strTest.Equals("pvr.actstreamclient")) ret = PVR_ACTUAL_STREAM_CLIENT; + else if (strTest.Equals("pvr.actstreamdevice")) ret = PVR_ACTUAL_STREAM_DEVICE; + else if (strTest.Equals("pvr.actstreamstatus")) ret = PVR_ACTUAL_STREAM_STATUS; + else if (strTest.Equals("pvr.actstreamsignal")) ret = PVR_ACTUAL_STREAM_SIG; + else if (strTest.Equals("pvr.actstreamsnr")) ret = PVR_ACTUAL_STREAM_SNR; + else if (strTest.Equals("pvr.actstreamber")) ret = PVR_ACTUAL_STREAM_BER; + else if (strTest.Equals("pvr.actstreamunc")) ret = PVR_ACTUAL_STREAM_UNC; + else if (strTest.Equals("pvr.actstreamvideobitrate")) ret = PVR_ACTUAL_STREAM_VIDEO_BR; + else if (strTest.Equals("pvr.actstreamaudiobitrate")) ret = PVR_ACTUAL_STREAM_AUDIO_BR; + else if (strTest.Equals("pvr.actstreamdolbybitrate")) ret = PVR_ACTUAL_STREAM_DOLBY_BR; + else if (strTest.Equals("pvr.actstreamprogrsignal")) ret = PVR_ACTUAL_STREAM_SIG_PROGR; + else if (strTest.Equals("pvr.actstreamprogrsnr")) ret = PVR_ACTUAL_STREAM_SNR_PROGR; + else if (strTest.Equals("pvr.actstreamisencrypted")) ret = PVR_ACTUAL_STREAM_ENCRYPTED; + else if (strTest.Equals("pvr.actstreamencryptionname")) ret = PVR_ACTUAL_STREAM_CRYPTION; + } + else if (strCategory.Equals("addon")) + { + if (strTest.Equals("addon.rating")) ret = ADDON_STAR_RATING; + } else if (strCategory.Equals("bar")) { if (strTest.Equals("bar.gputemperature")) ret = SYSTEM_GPU_TEMPERATURE; @@ -566,6 +617,19 @@ int CGUIInfoManager::TranslateSingleString(const CStdString &strCondition) else if (strTest.Equals("videoplayer.audiocodec")) return VIDEOPLAYER_AUDIO_CODEC; else if (strTest.Equals("videoplayer.audiochannels")) return VIDEOPLAYER_AUDIO_CHANNELS; else if (strTest.Equals("videoplayer.hasteletext")) return VIDEOPLAYER_HASTELETEXT; + else if (strTest.Equals("videoplayer.starttime")) return VIDEOPLAYER_STARTTIME; + else if (strTest.Equals("videoplayer.endtime")) return VIDEOPLAYER_ENDTIME; + else if (strTest.Equals("videoplayer.nexttitle")) return VIDEOPLAYER_NEXT_TITLE; + else if (strTest.Equals("videoplayer.nextgenre")) return VIDEOPLAYER_NEXT_GENRE; + else if (strTest.Equals("videoplayer.nextplot")) return VIDEOPLAYER_NEXT_PLOT; + else if (strTest.Equals("videoplayer.nextplotoutline")) return VIDEOPLAYER_NEXT_PLOT_OUTLINE; + else if (strTest.Equals("videoplayer.nextstarttime")) return VIDEOPLAYER_NEXT_STARTTIME; + else if (strTest.Equals("videoplayer.nextendtime")) return VIDEOPLAYER_NEXT_ENDTIME; + else if (strTest.Equals("videoplayer.nextduration")) return VIDEOPLAYER_NEXT_DURATION; + else if (strTest.Equals("videoplayer.channelname")) return VIDEOPLAYER_CHANNEL_NAME; + else if (strTest.Equals("videoplayer.channelnumber")) return VIDEOPLAYER_CHANNEL_NUMBER; + else if (strTest.Equals("videoplayer.channelgroup")) return VIDEOPLAYER_CHANNEL_GROUP; + else if (strTest.Equals("videoplayer.parentalrating")) return VIDEOPLAYER_PARENTAL_RATING; } else if (strCategory.Equals("playlist")) { @@ -928,6 +992,25 @@ int CGUIInfoManager::TranslateListItem(const CStdString &info) else if (info.Equals("audiolanguage")) return LISTITEM_AUDIO_LANGUAGE; else if (info.Equals("subtitlelanguage")) return LISTITEM_SUBTITLE_LANGUAGE; else if (info.Equals("isfolder")) return LISTITEM_IS_FOLDER; + else if (info.Equals("starttime")) return LISTITEM_STARTTIME; + else if (info.Equals("endtime")) return LISTITEM_ENDTIME; + else if (info.Equals("startdate")) return LISTITEM_STARTDATE; + else if (info.Equals("enddate")) return LISTITEM_ENDDATE; + else if (info.Equals("nexttitle")) return LISTITEM_NEXT_TITLE; + else if (info.Equals("nextgenre")) return LISTITEM_NEXT_GENRE; + else if (info.Equals("nextplot")) return LISTITEM_NEXT_PLOT; + else if (info.Equals("nextplotoutline")) return LISTITEM_NEXT_PLOT_OUTLINE; + else if (info.Equals("nextstarttime")) return LISTITEM_NEXT_STARTTIME; + else if (info.Equals("nextendtime")) return LISTITEM_NEXT_ENDTIME; + else if (info.Equals("nextstartdate")) return LISTITEM_NEXT_STARTDATE; + else if (info.Equals("nextenddate")) return LISTITEM_NEXT_ENDDATE; + else if (info.Equals("channelname")) return LISTITEM_CHANNEL_NAME; + else if (info.Equals("channelnumber")) return LISTITEM_CHANNEL_NUMBER; + else if (info.Equals("channelgroup")) return LISTITEM_CHANNEL_GROUP; + else if (info.Equals("hastimer")) return LISTITEM_HASTIMER; + else if (info.Equals("isrecording")) return LISTITEM_ISRECORDING; + else if (info.Equals("isencrypted")) return LISTITEM_ISENCRYPTED; + else if (info.Equals("parentalrating")) return LISTITEM_PARENTALRATING; else if (info.Equals("originaltitle")) return LISTITEM_ORIGINALTITLE; else if (info.Left(9).Equals("property(")) return AddListItemProp(info.Mid(9, info.GetLength() - 10)); return 0; @@ -959,6 +1042,9 @@ int CGUIInfoManager::TranslateMusicPlayerString(const CStdString &info) const else if (info.Equals("exists")) return MUSICPLAYER_EXISTS; else if (info.Equals("hasprevious")) return MUSICPLAYER_HASPREVIOUS; else if (info.Equals("hasnext")) return MUSICPLAYER_HASNEXT; + else if (info.Equals("channelname")) return MUSICPLAYER_CHANNEL_NAME; + else if (info.Equals("channelnumber")) return MUSICPLAYER_CHANNEL_NUMBER; + else if (info.Equals("channelgroup")) return MUSICPLAYER_CHANNEL_GROUP; return 0; } @@ -1007,6 +1093,37 @@ CStdString CGUIInfoManager::GetLabel(int info, int contextWindow) switch (info) { + case PVR_NOW_RECORDING_CHANNEL: + case PVR_NOW_RECORDING_DATETIME: + case PVR_NOW_RECORDING_TITLE: + case PVR_NEXT_RECORDING_CHANNEL: + case PVR_NEXT_RECORDING_DATETIME: + case PVR_NEXT_RECORDING_TITLE: + case PVR_BACKEND_NAME: + case PVR_BACKEND_VERSION: + case PVR_BACKEND_HOST: + case PVR_BACKEND_DISKSPACE: + case PVR_BACKEND_CHANNELS: + case PVR_BACKEND_TIMERS: + case PVR_BACKEND_RECORDINGS: + case PVR_BACKEND_NUMBER: + case PVR_TOTAL_DISKSPACE: + case PVR_NEXT_TIMER: + case PVR_PLAYING_TIME: + case PVR_PLAYING_DURATION: + case PVR_ACTUAL_STREAM_CLIENT: + case PVR_ACTUAL_STREAM_DEVICE: + case PVR_ACTUAL_STREAM_STATUS: + case PVR_ACTUAL_STREAM_BER: + case PVR_ACTUAL_STREAM_UNC: + case PVR_ACTUAL_STREAM_VIDEO_BR: + case PVR_ACTUAL_STREAM_AUDIO_BR: + case PVR_ACTUAL_STREAM_DOLBY_BR: + case PVR_ACTUAL_STREAM_CRYPTION: + case PVR_ACTUAL_STREAM_SIG: + case PVR_ACTUAL_STREAM_SNR: + strLabel = g_PVRManager.TranslateCharInfo(info); + break; case WEATHER_CONDITIONS: strLabel = g_weatherManager.GetInfo(WEATHER_LABEL_CURRENT_COND); strLabel = strLabel.Trim(); @@ -1107,6 +1224,9 @@ CStdString CGUIInfoManager::GetLabel(int info, int contextWindow) case MUSICPLAYER_RATING: case MUSICPLAYER_COMMENT: case MUSICPLAYER_LYRICS: + case MUSICPLAYER_CHANNEL_NAME: + case MUSICPLAYER_CHANNEL_NUMBER: + case MUSICPLAYER_CHANNEL_GROUP: strLabel = GetMusicLabel(info); break; case VIDEOPLAYER_TITLE: @@ -1135,6 +1255,19 @@ CStdString CGUIInfoManager::GetLabel(int info, int contextWindow) case VIDEOPLAYER_WRITER: case VIDEOPLAYER_TAGLINE: case VIDEOPLAYER_TRAILER: + case VIDEOPLAYER_STARTTIME: + case VIDEOPLAYER_ENDTIME: + case VIDEOPLAYER_NEXT_TITLE: + case VIDEOPLAYER_NEXT_GENRE: + case VIDEOPLAYER_NEXT_PLOT: + case VIDEOPLAYER_NEXT_PLOT_OUTLINE: + case VIDEOPLAYER_NEXT_STARTTIME: + case VIDEOPLAYER_NEXT_ENDTIME: + case VIDEOPLAYER_NEXT_DURATION: + case VIDEOPLAYER_CHANNEL_NAME: + case VIDEOPLAYER_CHANNEL_NUMBER: + case VIDEOPLAYER_CHANNEL_GROUP: + case VIDEOPLAYER_PARENTAL_RATING: strLabel = GetVideoLabel(info); break; case VIDEOPLAYER_VIDEO_CODEC: @@ -1669,6 +1802,10 @@ int CGUIInfoManager::GetInt(int info, int contextWindow) const } case SYSTEM_CPU_USAGE: return g_cpuInfo.getUsedPercentage(); + case PVR_PLAYING_PROGRESS: + case PVR_ACTUAL_STREAM_SIG_PROGR: + case PVR_ACTUAL_STREAM_SNR_PROGR: + return g_PVRManager.TranslateIntInfo(info); } return 0; } @@ -1792,6 +1929,9 @@ bool CGUIInfoManager::GetBool(int condition1, int contextWindow, const CGUIListI bReturn = g_settings.UsingLoginScreen(); else if (condition == WEATHER_IS_FETCHED) bReturn = g_weatherManager.IsFetched(); + else if (condition >= PVR_IS_RECORDING && condition <= PVR_IS_RECORDING+20) + bReturn = g_PVRManager.TranslateBoolInfo(condition); + else if (condition == SYSTEM_INTERNET_STATE) { g_sysinfo.GetInfo(condition); @@ -1840,7 +1980,8 @@ bool CGUIInfoManager::GetBool(int condition1, int contextWindow, const CGUIListI bReturn = ((CGUIMediaWindow*)pWindow)->CurrentDirectory().HasThumbnail(); } else if (condition == VIDEOPLAYER_HAS_INFO) - bReturn = (m_currentFile->HasVideoInfoTag() && !m_currentFile->GetVideoInfoTag()->IsEmpty()); + bReturn = ((m_currentFile->HasVideoInfoTag() && !m_currentFile->GetVideoInfoTag()->IsEmpty()) || + (m_currentFile->HasPVRChannelInfoTag() && !m_currentFile->GetPVRChannelInfoTag()->IsEmpty())); else if (condition >= CONTAINER_SCROLL_PREVIOUS && condition <= CONTAINER_SCROLL_NEXT) { // no parameters, so we assume it's just requested for a media window. It therefore @@ -2354,6 +2495,8 @@ bool CGUIInfoManager::GetMultiInfoBool(const GUIInfo &info, int contextWindow, c strContent = "musicvideos"; if (m_currentFile->HasVideoInfoTag() && m_currentFile->GetVideoInfoTag()->m_strStatus == "livetv") strContent = "livetv"; + if (m_currentFile->HasPVRChannelInfoTag()) + strContent = "livetv"; bReturn = m_stringParameters[info.GetData1()].Equals(strContent); } break; @@ -3043,6 +3186,31 @@ CStdString CGUIInfoManager::GetMusicTagLabel(int info, const CFileItem *item) co return GetItemLabel(item, LISTITEM_COMMENT); case MUSICPLAYER_DURATION: return GetItemLabel(item, LISTITEM_DURATION); + case MUSICPLAYER_CHANNEL_NAME: + { + cPVRChannelInfoTag* channeltag = m_currentFile->GetPVRChannelInfoTag(); + if (channeltag) + return channeltag->Name(); + } + break; + case MUSICPLAYER_CHANNEL_NUMBER: + { + cPVRChannelInfoTag* channeltag = m_currentFile->GetPVRChannelInfoTag(); + if (channeltag) + { + CStdString strNumber; + strNumber.Format("%i", channeltag->Number()); + return strNumber; + } + } + break; + case MUSICPLAYER_CHANNEL_GROUP: + { + cPVRChannelInfoTag* channeltag = m_currentFile->GetPVRChannelInfoTag(); + if (channeltag && channeltag->IsRadio()) + return PVRChannelGroupsRadio.GetGroupName(g_PVRManager.GetPlayingGroup()); + } + break; } return ""; } @@ -3054,6 +3222,8 @@ CStdString CGUIInfoManager::GetVideoLabel(int item) if (item == VIDEOPLAYER_TITLE) { + if (m_currentFile->HasPVRChannelInfoTag()) + return m_currentFile->GetPVRChannelInfoTag()->NowTitle(); if (m_currentFile->HasVideoInfoTag() && !m_currentFile->GetVideoInfoTag()->m_strTitle.IsEmpty()) return m_currentFile->GetVideoInfoTag()->m_strTitle; // don't have the title, so use dvdplayer, label, or drop down to title from path @@ -3073,6 +3243,73 @@ CStdString CGUIInfoManager::GetVideoLabel(int item) if (g_playlistPlayer.GetCurrentPlaylist() == PLAYLIST_VIDEO) return GetPlaylistLabel(PLAYLIST_POSITION); } + else if (m_currentFile->HasPVRChannelInfoTag()) + { + cPVRChannelInfoTag* tag = m_currentFile->GetPVRChannelInfoTag(); + + switch (item) + { + /* Now playing infos */ + case VIDEOPLAYER_ORIGINALTITLE: + return tag->NowTitle(); + case VIDEOPLAYER_GENRE: + return tag->NowGenre(); + case VIDEOPLAYER_PLOT: + return tag->NowPlot(); + case VIDEOPLAYER_PLOT_OUTLINE: + return tag->NowPlotOutline(); + case VIDEOPLAYER_STARTTIME: + return tag->NowStartTime().GetAsLocalizedTime("", false); + case VIDEOPLAYER_ENDTIME: + return tag->NowEndTime().GetAsLocalizedTime("", false); + + /* Next playing infos */ + case VIDEOPLAYER_NEXT_TITLE: + return tag->NextTitle(); + case VIDEOPLAYER_NEXT_GENRE: + return tag->NextGenre(); + case VIDEOPLAYER_NEXT_PLOT: + return tag->NextPlot(); + case VIDEOPLAYER_NEXT_PLOT_OUTLINE: + return tag->NextPlotOutline(); + case VIDEOPLAYER_NEXT_STARTTIME: + return tag->NextStartTime().GetAsLocalizedTime("", false); + case VIDEOPLAYER_NEXT_ENDTIME: + return tag->NextEndTime().GetAsLocalizedTime("", false); + case VIDEOPLAYER_NEXT_DURATION: + { + CStdString duration; + if (tag->NextDuration() > 0) + duration = StringUtils::SecondsToTimeString(tag->NextDuration()); + + return duration; + } + + case VIDEOPLAYER_PARENTAL_RATING: + { + CStdString rating; + if (tag->NowParentalRating() > 0) + rating.Format("%i", tag->NowParentalRating()); + return rating; + } + break; + + /* General channel infos */ + case VIDEOPLAYER_CHANNEL_NAME: + return tag->Name(); + case VIDEOPLAYER_CHANNEL_NUMBER: + { + CStdString strNumber; + strNumber.Format("%i", tag->Number()); + return strNumber; + } + case VIDEOPLAYER_CHANNEL_GROUP: + { + if (tag && !tag->IsRadio()) + return PVRChannelGroupsTV.GetGroupName(g_PVRManager.GetPlayingGroup()); + } + } + } else if (m_currentFile->HasVideoInfoTag()) { switch (item) @@ -3291,7 +3528,7 @@ void CGUIInfoManager::SetCurrentMovie(CFileItem &item) CLog::Log(LOGDEBUG,"CGUIInfoManager::SetCurrentMovie(%s)",item.m_strPath.c_str()); *m_currentFile = item; - if (!m_currentFile->HasVideoInfoTag() || m_currentFile->GetVideoInfoTag()->IsEmpty()) + if (!m_currentFile->HasPVRChannelInfoTag() && (!m_currentFile->HasVideoInfoTag() || m_currentFile->GetVideoInfoTag()->IsEmpty())) { // attempt to get some information CVideoDatabase dbs; dbs.Open(); @@ -3686,6 +3923,14 @@ CStdString CGUIInfoManager::GetItemLabel(const CFileItem *item, int info) const case LISTITEM_LABEL2: return item->GetLabel2(); case LISTITEM_TITLE: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NowTitle(); + if (item->HasPVRRecordingInfoTag()) + return item->GetPVRRecordingInfoTag()->m_strTitle; + if (item->HasEPGInfoTag()) + return item->GetEPGInfoTag()->Title(); + if (item->HasPVRTimerInfoTag()) + return item->GetPVRTimerInfoTag()->Title(); if (item->HasVideoInfoTag()) return item->GetVideoInfoTag()->m_strTitle; if (item->HasMusicInfoTag()) @@ -3747,6 +3992,12 @@ CStdString CGUIInfoManager::GetItemLabel(const CFileItem *item, int info) const return item->GetVideoInfoTag()->m_strGenre; if (item->HasMusicInfoTag()) return item->GetMusicInfoTag()->GetGenre(); + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NowGenre(); + if (item->HasPVRRecordingInfoTag()) + return item->GetPVRRecordingInfoTag()->m_strGenre; + if (item->HasEPGInfoTag()) + return item->GetEPGInfoTag()->Genre(); break; case LISTITEM_FILENAME: if (item->IsMusicDb() && item->HasMusicInfoTag()) @@ -3755,6 +4006,14 @@ CStdString CGUIInfoManager::GetItemLabel(const CFileItem *item, int info) const return CUtil::GetFileName(item->GetVideoInfoTag()->m_strFileNameAndPath); return CUtil::GetFileName(item->m_strPath); case LISTITEM_DATE: + if (item->HasEPGInfoTag()) + return item->GetEPGInfoTag()->Start().GetAsLocalizedDateTime(false, false); + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NowStartTime().GetAsLocalizedDateTime(false, false); + if (item->HasPVRRecordingInfoTag()) + return item->GetPVRRecordingInfoTag()->RecordingTime().GetAsLocalizedDateTime(false, false); + if (item->HasPVRTimerInfoTag()) + return item->GetPVRTimerInfoTag()->Summary(); if (item->m_dateTime.IsValid()) return item->m_dateTime.GetAsLocalizedDate(); break; @@ -3795,14 +4054,30 @@ CStdString CGUIInfoManager::GetItemLabel(const CFileItem *item, int info) const case LISTITEM_DURATION: { CStdString duration; - if (item->HasVideoInfoTag()) + if (item->HasPVRChannelInfoTag()) + { + if (item->GetPVRChannelInfoTag()->NowDuration() > 0) + duration = StringUtils::SecondsToTimeString(item->GetPVRChannelInfoTag()->NowDuration()); + } + else if (item->HasPVRRecordingInfoTag()) + { + if (item->GetPVRRecordingInfoTag()->GetDuration() > 0) + duration = StringUtils::SecondsToTimeString(item->GetPVRRecordingInfoTag()->GetDuration()); + } + else if (item->HasEPGInfoTag()) { + if (item->GetEPGInfoTag()->GetDuration() > 0) + duration = StringUtils::SecondsToTimeString(item->GetEPGInfoTag()->GetDuration()); + } + else if (item->HasVideoInfoTag()) + { + duration = item->GetVideoInfoTag()->m_strRuntime; if (item->GetVideoInfoTag()->m_streamDetails.GetVideoDuration() > 0) duration.Format("%i", item->GetVideoInfoTag()->m_streamDetails.GetVideoDuration() / 60); else if (!item->GetVideoInfoTag()->m_strRuntime.IsEmpty()) duration = item->GetVideoInfoTag()->m_strRuntime; } - if (item->HasMusicInfoTag()) + else if (item->HasMusicInfoTag()) { if (item->GetMusicInfoTag()->GetDuration() > 0) duration = StringUtils::SecondsToTimeString(item->GetMusicInfoTag()->GetDuration()); @@ -3810,6 +4085,12 @@ CStdString CGUIInfoManager::GetItemLabel(const CFileItem *item, int info) const return duration; } case LISTITEM_PLOT: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NowPlot(); + if (item->HasEPGInfoTag()) + return item->GetEPGInfoTag()->Plot(); + if (item->HasPVRRecordingInfoTag()) + return item->GetPVRRecordingInfoTag()->Plot(); if (item->HasVideoInfoTag()) { if (!(!item->GetVideoInfoTag()->m_strShowTitle.IsEmpty() && item->GetVideoInfoTag()->m_iSeason == -1)) // dont apply to tvshows @@ -3820,6 +4101,12 @@ CStdString CGUIInfoManager::GetItemLabel(const CFileItem *item, int info) const } break; case LISTITEM_PLOT_OUTLINE: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NowPlotOutline(); + if (item->HasEPGInfoTag()) + return item->GetEPGInfoTag()->PlotOutline(); + if (item->HasPVRRecordingInfoTag()) + return item->GetPVRRecordingInfoTag()->PlotOutline(); if (item->HasVideoInfoTag()) return item->GetVideoInfoTag()->m_strPlotOutline; break; @@ -3850,6 +4137,8 @@ CStdString CGUIInfoManager::GetItemLabel(const CFileItem *item, int info) const return item->GetVideoInfoTag()->m_strShowTitle; break; case LISTITEM_COMMENT: + if (item->HasPVRTimerInfoTag()) + return item->GetPVRTimerInfoTag()->GetStatus(); if (item->HasMusicInfoTag()) return item->GetMusicInfoTag()->GetComment(); break; @@ -4006,6 +4295,116 @@ CStdString CGUIInfoManager::GetItemLabel(const CFileItem *item, int info) const if (item->HasVideoInfoTag()) return item->GetVideoInfoTag()->m_streamDetails.GetSubtitleLanguage(); break; + case LISTITEM_STARTTIME: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NowStartTime().GetAsLocalizedTime("", false); + if (item->HasEPGInfoTag()) + return item->GetEPGInfoTag()->Start().GetAsLocalizedTime("", false); + if (item->HasPVRTimerInfoTag()) + return item->GetPVRTimerInfoTag()->Start().GetAsLocalizedTime("", false); + if (item->HasPVRRecordingInfoTag()) + return item->GetPVRRecordingInfoTag()->RecordingTime().GetAsLocalizedTime("", false); + if (item->m_dateTime.IsValid()) + return item->m_dateTime.GetAsLocalizedTime("", false); + break; + case LISTITEM_ENDTIME: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NowEndTime().GetAsLocalizedTime("", false); + if (item->HasEPGInfoTag()) + return item->GetEPGInfoTag()->End().GetAsLocalizedTime("", false); + if (item->HasPVRTimerInfoTag()) + return item->GetPVRTimerInfoTag()->Stop().GetAsLocalizedTime("", false); + break; + case LISTITEM_STARTDATE: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NowStartTime().GetAsLocalizedDate(true); + if (item->HasEPGInfoTag()) + return item->GetEPGInfoTag()->Start().GetAsLocalizedDate(true); + if (item->HasPVRTimerInfoTag()) + return item->GetPVRTimerInfoTag()->Start().GetAsLocalizedDate(true); + if (item->HasPVRRecordingInfoTag()) + return item->GetPVRRecordingInfoTag()->RecordingTime().GetAsLocalizedDate(true); + if (item->m_dateTime.IsValid()) + return item->m_dateTime.GetAsLocalizedDate(true); + break; + case LISTITEM_ENDDATE: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NowEndTime().GetAsLocalizedDate(true); + if (item->HasEPGInfoTag()) + return item->GetEPGInfoTag()->End().GetAsLocalizedDate(true); + if (item->HasPVRTimerInfoTag()) + return item->GetPVRTimerInfoTag()->Stop().GetAsLocalizedDate(true); + break; + case LISTITEM_CHANNEL_NUMBER: + { + CStdString number; + if (item->HasPVRChannelInfoTag()) + number.Format("%i", item->GetPVRChannelInfoTag()->Number()); + if (item->HasEPGInfoTag()) + number.Format("%i", item->GetEPGInfoTag()->ChannelNumber()); + if (item->HasPVRTimerInfoTag()) + number.Format("%i", item->GetPVRTimerInfoTag()->ChannelNumber()); + + return number; + } + break; + case LISTITEM_CHANNEL_NAME: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->Name(); + if (item->HasEPGInfoTag()) + return item->GetEPGInfoTag()->ChannelName(); + if (item->HasPVRRecordingInfoTag()) + return item->GetPVRRecordingInfoTag()->ChannelName(); + if (item->HasPVRTimerInfoTag()) + return item->GetPVRTimerInfoTag()->ChannelName(); + break; + case LISTITEM_NEXT_STARTTIME: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NextStartTime().GetAsLocalizedTime("", false); + break; + case LISTITEM_NEXT_ENDTIME: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NextEndTime().GetAsLocalizedTime("", false); + break; + case LISTITEM_NEXT_STARTDATE: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NextStartTime().GetAsLocalizedDate(true); + break; + case LISTITEM_NEXT_ENDDATE: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NextEndTime().GetAsLocalizedDate(true); + break; + case LISTITEM_NEXT_PLOT: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NextPlot(); + break; + case LISTITEM_NEXT_PLOT_OUTLINE: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NextPlotOutline(); + break; + case LISTITEM_NEXT_DURATION: + { + CStdString duration; + if (item->HasPVRChannelInfoTag()) + { + if (item->GetPVRChannelInfoTag()->NextDuration() > 0) + duration = StringUtils::SecondsToTimeString(item->GetPVRChannelInfoTag()->NextDuration()); + } + return duration; + } + break; + case LISTITEM_NEXT_GENRE: + if (item->HasPVRChannelInfoTag()) + return item->GetPVRChannelInfoTag()->NextGenre(); + break; + case LISTITEM_PARENTALRATING: + { + CStdString rating; + if (item->HasEPGInfoTag() && item->GetEPGInfoTag()->ParentalRating() > 0) + rating.Format("%i", item->GetEPGInfoTag()->ParentalRating()); + return rating; + } + break; } return ""; } @@ -4038,6 +4437,14 @@ CStdString CGUIInfoManager::GetItemImage(const CFileItem *item, int info) const return rating; } break; + case ADDON_STAR_RATING: + { + CStdString rating; + //TODO need to check item is an addon + rating.Format("rating%d.png", item->GetPropertyInt("Addon.Rating")); + return rating; + } + break; } /* switch (info) */ return GetItemLabel(item, info); @@ -4068,6 +4475,59 @@ bool CGUIInfoManager::GetItemBool(const CGUIListItem *item, int condition) const return item->IsSelected(); else if (condition == LISTITEM_IS_FOLDER) return item->m_bIsFolder; + + if (item->IsFileItem()) + { + const CFileItem *pItem = (const CFileItem *)item; + if (condition == LISTITEM_ISRECORDING) + { + if (pItem->HasPVRChannelInfoTag()) + { + return pItem->GetPVRChannelInfoTag()->IsRecording(); + } + else if (pItem->HasEPGInfoTag()) + { + const cPVRTimerInfoTag *timer = pItem->GetEPGInfoTag()->Timer(); + if (timer) + { + CDateTime now = CDateTime::GetCurrentDateTime(); + if ((timer->Start() <= now) && (timer->Stop() >= now) && timer->Active()) + return true; + } + } + else if (pItem->HasPVRTimerInfoTag()) + { + const cPVRTimerInfoTag *timer = pItem->GetPVRTimerInfoTag(); + CDateTime now = CDateTime::GetCurrentDateTime(); + if ((timer->Start() <= now) && (timer->Stop() >= now) && timer->Active()) + return true; + } + } + else if (condition == LISTITEM_HASTIMER) + { + if (pItem->HasEPGInfoTag()) + { + const cPVRTimerInfoTag *timer = pItem->GetEPGInfoTag()->Timer(); + if (timer) + { + if (timer->Start() > CDateTime::GetCurrentDateTime() && timer->Active()) + return true; + } + } + } + else if (condition == LISTITEM_ISENCRYPTED) + { + if (pItem->HasPVRChannelInfoTag()) + { + return pItem->GetPVRChannelInfoTag()->IsEncrypted(); + } + else if (pItem->HasEPGInfoTag()) + { + return pItem->GetEPGInfoTag()->IsEncrypted(); + } + } + } + return false; } diff --git a/xbmc/utils/GUIInfoManager.h b/xbmc/utils/GUIInfoManager.h index e4ec858a0a..393b59b7af 100644 --- a/xbmc/utils/GUIInfoManager.h +++ b/xbmc/utils/GUIInfoManager.h @@ -210,7 +210,10 @@ class CDateTime; #define MUSICPLAYER_HASNEXT 223 #define MUSICPLAYER_EXISTS 224 #define MUSICPLAYER_PLAYLISTPLAYING 225 -#define MUSICPLAYER_ALBUM_ARTIST 226 +#define MUSICPLAYER_CHANNEL_NAME 226 +#define MUSICPLAYER_CHANNEL_NUMBER 227 +#define MUSICPLAYER_CHANNEL_GROUP 228 +#define MUSICPLAYER_ALBUM_ARTIST 229 #define VIDEOPLAYER_TITLE 250 #define VIDEOPLAYER_GENRE 251 @@ -250,16 +253,29 @@ class CDateTime; #define VIDEOPLAYER_AUDIO_CHANNELS 289 #define VIDEOPLAYER_VIDEO_ASPECT 290 #define VIDEOPLAYER_HASTELETEXT 291 -#define VIDEOPLAYER_COUNTRY 292 - -#define AUDIOSCROBBLER_ENABLED 300 -#define AUDIOSCROBBLER_CONN_STATE 301 -#define AUDIOSCROBBLER_SUBMIT_INT 302 -#define AUDIOSCROBBLER_FILES_CACHED 303 -#define AUDIOSCROBBLER_SUBMIT_STATE 304 -#define LASTFM_RADIOPLAYING 305 -#define LASTFM_CANLOVE 306 -#define LASTFM_CANBAN 307 +#define VIDEOPLAYER_STARTTIME 292 +#define VIDEOPLAYER_ENDTIME 293 +#define VIDEOPLAYER_NEXT_TITLE 294 +#define VIDEOPLAYER_NEXT_GENRE 295 +#define VIDEOPLAYER_NEXT_PLOT 296 +#define VIDEOPLAYER_NEXT_PLOT_OUTLINE 297 +#define VIDEOPLAYER_NEXT_STARTTIME 298 +#define VIDEOPLAYER_NEXT_ENDTIME 299 +#define VIDEOPLAYER_NEXT_DURATION 300 +#define VIDEOPLAYER_CHANNEL_NAME 301 +#define VIDEOPLAYER_CHANNEL_NUMBER 302 +#define VIDEOPLAYER_CHANNEL_GROUP 303 +#define VIDEOPLAYER_PARENTAL_RATING 304 +#define VIDEOPLAYER_COUNTRY 305 + +#define AUDIOSCROBBLER_ENABLED 320 +#define AUDIOSCROBBLER_CONN_STATE 321 +#define AUDIOSCROBBLER_SUBMIT_INT 322 +#define AUDIOSCROBBLER_FILES_CACHED 323 +#define AUDIOSCROBBLER_SUBMIT_STATE 324 +#define LASTFM_RADIOPLAYING 325 +#define LASTFM_CANLOVE 326 +#define LASTFM_CANBAN 327 #define CONTAINER_SCROLL_PREVIOUS 345 // NOTE: These 5 must be kept in this consecutive order #define CONTAINER_MOVE_PREVIOUS 346 @@ -413,6 +429,51 @@ class CDateTime; #define FANART_COLOR3 1002 #define FANART_IMAGE 1003 +#define PVR_IS_RECORDING 1101 +#define PVR_HAS_TIMER 1102 +#define PVR_HAS_EPG 1103 +#define PVR_HAS_TXT 1104 +#define PVR_HAS_DIRECTOR 1105 +#define PVR_IS_PLAYING_TV 1106 +#define PVR_IS_PLAYING_RADIO 1107 +#define PVR_IS_PLAYING_RECORDING 1108 +#define PVR_ACTUAL_STREAM_ENCRYPTED 1110 + +#define PVR_NEXT_RECORDING_CHANNEL 1120 +#define PVR_NEXT_RECORDING_DATETIME 1121 +#define PVR_NEXT_RECORDING_TITLE 1122 +#define PVR_NOW_RECORDING_CHANNEL 1123 +#define PVR_NOW_RECORDING_DATETIME 1124 +#define PVR_NOW_RECORDING_TITLE 1125 +#define PVR_BACKEND_NAME 1126 +#define PVR_BACKEND_VERSION 1127 +#define PVR_BACKEND_HOST 1128 +#define PVR_BACKEND_DISKSPACE 1129 +#define PVR_BACKEND_CHANNELS 1130 +#define PVR_BACKEND_TIMERS 1131 +#define PVR_BACKEND_RECORDINGS 1132 +#define PVR_BACKEND_NUMBER 1133 +#define PVR_TOTAL_DISKSPACE 1134 +#define PVR_NEXT_TIMER 1135 +#define PVR_PLAYING_DURATION 1136 +#define PVR_PLAYING_TIME 1137 +#define PVR_PLAYING_PROGRESS 1138 +#define PVR_ACTUAL_STREAM_CLIENT 1142 +#define PVR_ACTUAL_STREAM_DEVICE 1143 +#define PVR_ACTUAL_STREAM_STATUS 1144 +#define PVR_ACTUAL_STREAM_SIG 1145 +#define PVR_ACTUAL_STREAM_SNR 1146 +#define PVR_ACTUAL_STREAM_SIG_PROGR 1147 +#define PVR_ACTUAL_STREAM_SNR_PROGR 1148 +#define PVR_ACTUAL_STREAM_BER 1149 +#define PVR_ACTUAL_STREAM_UNC 1150 +#define PVR_ACTUAL_STREAM_VIDEO_BR 1151 +#define PVR_ACTUAL_STREAM_AUDIO_BR 1152 +#define PVR_ACTUAL_STREAM_DOLBY_BR 1153 +#define PVR_ACTUAL_STREAM_CRYPTION 1154 + +#define ADDON_STAR_RATING 1200 + #define WINDOW_PROPERTY 9993 #define WINDOW_IS_TOPMOST 9994 #define WINDOW_IS_VISIBLE 9995 @@ -490,8 +551,28 @@ class CDateTime; #define LISTITEM_AUDIO_LANGUAGE (LISTITEM_START + 51) #define LISTITEM_SUBTITLE_LANGUAGE (LISTITEM_START + 52) #define LISTITEM_IS_FOLDER (LISTITEM_START + 53) -#define LISTITEM_ORIGINALTITLE (LISTITEM_START + 54) -#define LISTITEM_COUNTRY (LISTITEM_START + 55) +#define LISTITEM_STARTTIME (LISTITEM_START + 54) +#define LISTITEM_ENDTIME (LISTITEM_START + 55) +#define LISTITEM_STARTDATE (LISTITEM_START + 56) +#define LISTITEM_ENDDATE (LISTITEM_START + 57) +#define LISTITEM_NEXT_TITLE (LISTITEM_START + 58) +#define LISTITEM_NEXT_GENRE (LISTITEM_START + 59) +#define LISTITEM_NEXT_PLOT (LISTITEM_START + 60) +#define LISTITEM_NEXT_PLOT_OUTLINE (LISTITEM_START + 61) +#define LISTITEM_NEXT_STARTTIME (LISTITEM_START + 62) +#define LISTITEM_NEXT_ENDTIME (LISTITEM_START + 63) +#define LISTITEM_NEXT_STARTDATE (LISTITEM_START + 64) +#define LISTITEM_NEXT_ENDDATE (LISTITEM_START + 65) +#define LISTITEM_NEXT_DURATION (LISTITEM_START + 66) +#define LISTITEM_CHANNEL_NAME (LISTITEM_START + 67) +#define LISTITEM_CHANNEL_NUMBER (LISTITEM_START + 68) +#define LISTITEM_CHANNEL_GROUP (LISTITEM_START + 69) +#define LISTITEM_HASTIMER (LISTITEM_START + 70) +#define LISTITEM_ISRECORDING (LISTITEM_START + 71) +#define LISTITEM_ISENCRYPTED (LISTITEM_START + 72) +#define LISTITEM_PARENTALRATING (LISTITEM_START + 73) +#define LISTITEM_ORIGINALTITLE (LISTITEM_START + 74) +#define LISTITEM_COUNTRY (LISTITEM_START + 75) #define LISTITEM_PROPERTY_START (LISTITEM_START + 200) #define LISTITEM_PROPERTY_END (LISTITEM_PROPERTY_START + 1000) diff --git a/xbmc/utils/Makefile b/xbmc/utils/Makefile index bd81e9041d..f026521c92 100644 --- a/xbmc/utils/Makefile +++ b/xbmc/utils/Makefile @@ -57,6 +57,10 @@ SRCS=AlarmClock.cpp \ DbusServer.cpp \ Atomics.cpp \ LockFree.cpp \ + PVREpg.cpp \ + PVRTimers.cpp \ + PVRChannels.cpp \ + PVRRecordings.cpp \ StreamDetails.cpp \ TimeUtils.cpp \ JobManager.cpp \ @@ -64,6 +68,7 @@ SRCS=AlarmClock.cpp \ fastmemcpy.c \ PasswordManager.cpp \ AliasShortcutUtils.cpp \ + TextSearch.cpp \ WebServer.cpp \ AnnouncementManager.cpp \ Semaphore.cpp \ diff --git a/xbmc/utils/PVRChannels.cpp b/xbmc/utils/PVRChannels.cpp new file mode 100644 index 0000000000..23848fba46 --- /dev/null +++ b/xbmc/utils/PVRChannels.cpp @@ -0,0 +1,1447 @@ +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * DESCRIPTION: + * + */ + +#include "FileItem.h" +#include "PVREpg.h" +#include "PVRChannels.h" +#include "GUISettings.h" +#include "TVDatabase.h" +#include "PVRManager.h" +#include "GUIWindowManager.h" +#include "GUIDialogYesNo.h" +#include "GUIDialogOK.h" +#include "LocalizeStrings.h" +#include "utils/log.h" +#include "Util.h" +#include "URL.h" +#include "FileSystem/File.h" +#include "MusicInfoTag.h" + +using namespace XFILE; +using namespace MUSIC_INFO; + + +// --- cPVRChannelInfoTag --------------------------------------------------------- + +bool cPVRChannelInfoTag::operator==(const cPVRChannelInfoTag& right) const +{ + if (this == &right) return true; + + return (m_iIdChannel == right.m_iIdChannel && + m_iIdUnique == right.m_iIdUnique && + m_iChannelNum == right.m_iChannelNum && + m_iClientNum == right.m_iClientNum && + m_strClientName == right.m_strClientName && + m_clientID == right.m_clientID && + m_iGroupID == right.m_iGroupID && + m_strChannel == right.m_strChannel && + m_IconPath == right.m_IconPath && + m_radio == right.m_radio && + m_hide == right.m_hide && + m_isRecording == right.m_isRecording && + m_encryptionSystem == right.m_encryptionSystem && + m_strStreamURL == right.m_strStreamURL && + m_strFileNameAndPath == right.m_strFileNameAndPath); +} + +bool cPVRChannelInfoTag::operator!=(const cPVRChannelInfoTag &right) const +{ + if (m_iIdChannel != right.m_iIdChannel) return true; + if (m_iIdUnique != right.m_iIdUnique) return true; + if (m_iChannelNum != right.m_iChannelNum) return true; + if (m_iClientNum != right.m_iClientNum) return true; + if (m_strClientName != right.m_strClientName) return true; + if (m_clientID != right.m_clientID) return true; + if (m_iGroupID != right.m_iGroupID) return true; + if (m_strChannel != right.m_strChannel) return true; + if (m_IconPath != right.m_IconPath) return true; + if (m_radio != right.m_radio) return true; + if (m_hide != right.m_hide) return true; + if (m_isRecording != right.m_isRecording) return true; + if (m_encryptionSystem != right.m_encryptionSystem) return true; + if (m_strStreamURL != right.m_strStreamURL) return true; + if (m_strFileNameAndPath != right.m_strFileNameAndPath) return true; + return false; +} + +void cPVRChannelInfoTag::Reset() +{ + m_iIdChannel = -1; + m_iChannelNum = -1; + m_iGroupID = 0; + m_strChannel = ""; + m_encryptionSystem = -1; + m_iIdUnique = -1; + m_radio = false; + m_hide = false; + m_isRecording = false; + m_grabEpg = true; + m_grabber = "client"; + m_bIsVirtual = false; + m_iPortalMasterChannel = -1; + + m_clientID = -1; + m_iClientNum = -1; + m_strClientName = ""; + + m_Epg = NULL; + m_epgNow = NULL; + m_epgNext = NULL; + + m_IconPath = ""; + m_strFileNameAndPath = ""; + m_strStreamURL = ""; +} + +void cPVRChannelInfoTag::ResetChannelEPGLinks() +{ + m_epgNow = NULL; + m_epgNext = NULL; +} + +void cPVRChannelInfoTag::UpdateRunningEvents() +{ + if (m_Epg == NULL) + return; + + if (m_epgNow == NULL || m_epgNow->End() < CDateTime::GetCurrentDateTime()) + { + m_epgNow = m_Epg->GetInfoTagNow(); + m_epgNext = m_Epg->GetInfoTagNext(); + } +} + +CStdString cPVRChannelInfoTag::EncryptionName() const +{ + // http://www.dvb.org/index.php?id=174 + // http://en.wikipedia.org/wiki/Conditional_access_system + CStdString strName; + + if (m_encryptionSystem == 0x0000) + strName = g_localizeStrings.Get(19013); /* Free To Air */ + else if (m_encryptionSystem < 0x0000) + strName = g_localizeStrings.Get(13205); /* Unknown */ + else if (m_encryptionSystem >= 0x0001 && m_encryptionSystem <= 0x009F) + strName.Format("%s (%X)", g_localizeStrings.Get(19014).c_str(), m_encryptionSystem); /* Fixed */ + else if (m_encryptionSystem >= 0x00A0 && m_encryptionSystem <= 0x00A1) + strName.Format("%s (%X)", g_localizeStrings.Get(338).c_str(), m_encryptionSystem); /* Analog */ + else if (m_encryptionSystem >= 0x00A2 && m_encryptionSystem <= 0x00FF) + strName.Format("%s (%X)", g_localizeStrings.Get(19014).c_str(), m_encryptionSystem); /* Fixed */ + else if (m_encryptionSystem >= 0x0100 && m_encryptionSystem <= 0x01FF) + strName.Format("%s (%X)", "SECA Mediaguard", m_encryptionSystem); // Canal Plus + else if (m_encryptionSystem == 0x0464) + strName.Format("%s (%X)", "EuroDec", m_encryptionSystem); // EuroDec + else if (m_encryptionSystem >= 0x0500 && m_encryptionSystem <= 0x05FF) + strName.Format("%s (%X)", "Viaccess", m_encryptionSystem); // France Telecom + else if (m_encryptionSystem >= 0x0600 && m_encryptionSystem <= 0x06FF) + strName.Format("%s (%X)", "Irdeto", m_encryptionSystem); // Irdeto + else if (m_encryptionSystem >= 0x0900 && m_encryptionSystem <= 0x09FF) + strName.Format("%s (%X)", "NDS Videoguard", m_encryptionSystem); // News Datacom + else if (m_encryptionSystem >= 0x0B00 && m_encryptionSystem <= 0x0BFF) + strName.Format("%s (%X)", "Conax", m_encryptionSystem); // Norwegian Telekom + else if (m_encryptionSystem >= 0x0D00 && m_encryptionSystem <= 0x0DFF) + strName.Format("%s (%X)", "CryptoWorks", m_encryptionSystem); // Philips + else if (m_encryptionSystem >= 0x0E00 && m_encryptionSystem <= 0x0EFF) + strName.Format("%s (%X)", "PowerVu", m_encryptionSystem); // Scientific Atlanta + else if (m_encryptionSystem == 0x1000) + strName.Format("%s (%X)", "RAS", m_encryptionSystem); // Tandberg Television + else if (m_encryptionSystem >= 0x1200 && m_encryptionSystem <= 0x12FF) + strName.Format("%s (%X)", "NagraVision", m_encryptionSystem); // BellVu Express + else if (m_encryptionSystem >= 0x1700 && m_encryptionSystem <= 0x17FF) + strName.Format("%s (%X)", "BetaCrypt", m_encryptionSystem); // BetaTechnik + else if (m_encryptionSystem >= 0x1800 && m_encryptionSystem <= 0x18FF) + strName.Format("%s (%X)", "NagraVision", m_encryptionSystem); // Kudelski SA + else if (m_encryptionSystem == 0x22F0) + strName.Format("%s (%X)", "Codicrypt", m_encryptionSystem); // Scopus Network Technologies + else if (m_encryptionSystem == 0x2600) + strName.Format("%s (%X)", "BISS", m_encryptionSystem); // European Broadcasting Union + else if (m_encryptionSystem == 0x4347) + strName.Format("%s (%X)", "CryptOn", m_encryptionSystem); // CryptOn + else if (m_encryptionSystem == 0x4800) + strName.Format("%s (%X)", "Accessgate", m_encryptionSystem); // Telemann + else if (m_encryptionSystem == 0x4900) + strName.Format("%s (%X)", "China Crypt", m_encryptionSystem); // CryptoWorks + else if (m_encryptionSystem == 0x4A10) + strName.Format("%s (%X)", "EasyCas", m_encryptionSystem); // EasyCas + else if (m_encryptionSystem == 0x4A20) + strName.Format("%s (%X)", "AlphaCrypt", m_encryptionSystem); // AlphaCrypt + else if (m_encryptionSystem == 0x4A70) + strName.Format("%s (%X)", "DreamCrypt", m_encryptionSystem); // Dream Multimedia + else if (m_encryptionSystem == 0x4A60) + strName.Format("%s (%X)", "SkyCrypt", m_encryptionSystem); // @Sky + else if (m_encryptionSystem == 0x4A61) + strName.Format("%s (%X)", "Neotioncrypt", m_encryptionSystem); // Neotion + else if (m_encryptionSystem == 0x4A62) + strName.Format("%s (%X)", "SkyCrypt", m_encryptionSystem); // @Sky + else if (m_encryptionSystem == 0x4A63) + strName.Format("%s (%X)", "Neotion SHL", m_encryptionSystem); // Neotion + else if (m_encryptionSystem >= 0x4A64 && m_encryptionSystem <= 0x4A6F) + strName.Format("%s (%X)", "SkyCrypt", m_encryptionSystem); // @Sky + else if (m_encryptionSystem == 0x4A80) + strName.Format("%s (%X)", "ThalesCrypt", m_encryptionSystem); // TPS + else if (m_encryptionSystem == 0x4AA1) + strName.Format("%s (%X)", "KeyFly", m_encryptionSystem); // SIDSA + else if (m_encryptionSystem == 0x4ABF) + strName.Format("%s (%X)", "DG-Crypt", m_encryptionSystem); // Beijing Compunicate Technology Inc. + else if (m_encryptionSystem >= 0x4AD0 && m_encryptionSystem <= 0x4AD1) + strName.Format("%s (%X)", "X-Crypt", m_encryptionSystem); // XCrypt Inc. + else if (m_encryptionSystem == 0x4AD4) + strName.Format("%s (%X)", "OmniCrypt", m_encryptionSystem); // Widevine Technologies, Inc. + else if (m_encryptionSystem == 0x4AE0) + strName.Format("%s (%X)", "RossCrypt", m_encryptionSystem); // Digi Raum Electronics Co. Ltd. + else if (m_encryptionSystem == 0x5500) + strName.Format("%s (%X)", "Z-Crypt", m_encryptionSystem); // Digi Raum Electronics Co. Ltd. + else if (m_encryptionSystem == 0x5501) + strName.Format("%s (%X)", "Griffin", m_encryptionSystem); // Griffin + else + strName.Format("%s (%X)", g_localizeStrings.Get(19499).c_str(), m_encryptionSystem); // Other/Unknown + + return strName; +} + +CStdString cPVRChannelInfoTag::NowTitle(void) const +{ + if (m_Epg == NULL) + { + m_Epg = PVREpgs.GetEPG(m_iIdChannel); + if (!m_Epg) + return g_localizeStrings.Get(19055); + } + + if (!m_Epg->IsUpdateRunning() && (m_epgNow == NULL || m_epgNow->End() < CDateTime::GetCurrentDateTime())) + { + m_epgNow = m_Epg->GetInfoTagNow(); + m_epgNext = m_Epg->GetInfoTagNext(); + } + + if (m_epgNow == NULL) + return g_localizeStrings.Get(19055); + + return m_epgNow->Title(); +} + +CStdString cPVRChannelInfoTag::NowPlotOutline(void) const +{ + if (m_epgNow == NULL) + return ""; + + return m_epgNow->PlotOutline(); +} + +CStdString cPVRChannelInfoTag::NowPlot(void) const +{ + if (m_epgNow == NULL) + return ""; + + return m_epgNow->Plot(); +} + +CDateTime cPVRChannelInfoTag::NowStartTime(void) const +{ + if (m_Epg == NULL || m_epgNow == NULL) + return CDateTime::GetCurrentDateTime(); + + if (!m_Epg->IsUpdateRunning() && m_epgNow->End() < CDateTime::GetCurrentDateTime()) + { + m_epgNow = m_Epg->GetInfoTagNow(); + m_epgNext = m_Epg->GetInfoTagNext(); + } + + return m_epgNow->Start(); +} + +CDateTime cPVRChannelInfoTag::NowEndTime(void) const +{ + if (m_epgNow == NULL) + return CDateTime::GetCurrentDateTime()+CDateTimeSpan(0,1,0,0); + + return m_epgNow->End(); +} + +int cPVRChannelInfoTag::NowDuration() const +{ + if (m_epgNow == NULL) + return 3600; /* One hour */ + + time_t start, end; + m_epgNow->Start().GetAsTime(start); + m_epgNow->End().GetAsTime(end); + return end - start; +} + +int cPVRChannelInfoTag::NowPlayTime() const +{ + if (m_epgNow == NULL) + return 0; + + CDateTimeSpan time = CDateTime::GetCurrentDateTime() - m_epgNow->Start(); + return time.GetDays() * 60 * 60 * 24 + + time.GetHours() * 60 * 60 + + time.GetMinutes() * 60 + + time.GetSeconds(); +} + +CStdString cPVRChannelInfoTag::NowGenre(void) const +{ + if (m_epgNow == NULL) + return ""; + + return m_epgNow->Genre(); +} + +int cPVRChannelInfoTag::NowParentalRating() const +{ + if (m_epgNow == NULL) + return 0; + + return m_epgNow->ParentalRating(); +} + +CStdString cPVRChannelInfoTag::NextTitle(void) const +{ + if (m_epgNext == NULL) + return g_localizeStrings.Get(19055); + + return m_epgNext->Title(); +} + +CStdString cPVRChannelInfoTag::NextPlotOutline(void) const +{ + if (m_epgNext == NULL) + return ""; + + return m_epgNext->PlotOutline(); +} + +CStdString cPVRChannelInfoTag::NextPlot(void) const +{ + if (m_epgNext == NULL) + return ""; + + return m_epgNext->Plot(); +} + +CDateTime cPVRChannelInfoTag::NextStartTime(void) const +{ + if (m_epgNext == NULL) + return CDateTime::GetCurrentDateTime()+CDateTimeSpan(0,1,0,0); + + return m_epgNext->Start(); +} + +CDateTime cPVRChannelInfoTag::NextEndTime(void) const +{ + if (m_epgNow == NULL || m_epgNext == NULL || m_Epg == NULL) + return CDateTime::GetCurrentDateTime()+CDateTimeSpan(0,2,0,0); + + if (!m_Epg->IsUpdateRunning() && m_epgNow->End() < CDateTime::GetCurrentDateTime()) + { + m_epgNow = m_Epg->GetInfoTagNow(); + m_epgNext = m_Epg->GetInfoTagNext(); + } + return m_epgNext->End(); +} + +int cPVRChannelInfoTag::NextDuration() const +{ + if (m_epgNext == NULL) + return 3600; /* One hour */ + + time_t start, end; + m_epgNext->Start().GetAsTime(start); + m_epgNext->End().GetAsTime(end); + return end - start; +} + +CStdString cPVRChannelInfoTag::NextGenre(void) const +{ + if (m_epgNext == NULL) + return ""; + + return m_epgNext->Genre(); +} + +int cPVRChannelInfoTag::NextParentalRating() const +{ + if (m_epgNow == NULL) + return 0; + + return m_epgNow->ParentalRating(); +} + +bool cPVRChannelInfoTag::IsEmpty() const +{ + return (NowTitle().IsEmpty() && + m_strFileNameAndPath.IsEmpty() && + m_strStreamURL.IsEmpty()); +} + +int cPVRChannelInfoTag::GetPortalChannels(CFileItemList* results) +{ + if (m_iPortalMasterChannel != 0) + return -1; + + for (unsigned int i = 0; i < m_PortalChannels.size(); i++) + { + CFileItemPtr channel(new CFileItem(cPVRChannels::GetByChannelIDFromAll(m_PortalChannels[i]))); + CStdString path; + path.Format("pvr://channels/tv/portal-%04i/%i.pvr", ChannelID(),i+1); + channel->m_strPath = path; + results->Add(channel); + } + return results->Size(); +} + +// --- cPVRChannels --------------------------------------------------------------- + +cPVRChannels PVRChannelsTV; +cPVRChannels PVRChannelsRadio; + +cPVRChannels::cPVRChannels(void) +{ + m_bRadio = false; + m_iHiddenChannels = 0; +} + +bool cPVRChannels::Load(bool radio) +{ + m_bRadio = radio; + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + CLIENTMAP *clients = g_PVRManager.Clients(); + + Clear(); + + database->Open(); + if (database->GetDBNumChannels(m_bRadio) > 0) + { + database->GetDBChannelList(*this, m_bRadio); + database->Close(); + Update(); + ReNumberAndCheck(); + } + else + { + CLog::Log(LOGNOTICE, "PVR: TV Database holds no %s channels, reading channels from clients", m_bRadio ? "Radio" : "TV"); + + CLIENTMAPITR itr = clients->begin(); + while (itr != clients->end()) + { + if ((*itr).second->ReadyToUse() && (*itr).second->GetNumChannels() > 0) + { + (*itr).second->GetChannelList(*this, m_bRadio); + } + itr++; + } + ReNumberAndCheck(); + SearchAndSetChannelIcons(); + for (unsigned int i = 0; i < size(); i++) + database->AddDBChannel(at(i), false, (i==0), (i >= size()-1)); + + clear(); + database->GetDBChannelList(*this, m_bRadio); + database->Compress(true); + database->Close(); + ReNumberAndCheck(); + } + return false; +} + +void cPVRChannels::Unload() +{ + Clear(); +} + +bool cPVRChannels::Update() +{ + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + CLIENTMAP *clients = g_PVRManager.Clients(); + cPVRChannels PVRChannels_tmp; + + database->Open(); + + CLIENTMAPITR itr = clients->begin(); + while (itr != clients->end()) + { + if ((*itr).second->ReadyToUse() && (*itr).second->GetNumChannels() > 0) + { + (*itr).second->GetChannelList(PVRChannels_tmp, m_bRadio); + } + itr++; + } + + PVRChannels_tmp.ReNumberAndCheck(); + + /* + * First whe look for moved channels on backend (other backend number) + * and delete no more present channels inside database. + * Problem: + * If a channel on client is renamed, it is deleted from Database + * and later added as new channel and loose his Group Information + */ + for (unsigned int i = 0; i < size(); i++) + { + bool found = false; + bool changed = false; + + if (!at(i).IsVirtual()) + { + for (unsigned int j = 0; j < PVRChannels_tmp.size(); j++) + { + if (at(i).UniqueID() == PVRChannels_tmp[j].UniqueID() && + at(i).ClientID() == PVRChannels_tmp[j].ClientID()) + { + if (at(i).ClientNumber() != PVRChannels_tmp[j].ClientNumber()) + { + at(i).SetClientNumber(PVRChannels_tmp[j].ClientNumber()); + changed = true; + } + + if (at(i).ClientName() != PVRChannels_tmp[j].ClientName()) + { + at(i).SetClientName(PVRChannels_tmp[j].ClientName()); + at(i).SetName(PVRChannels_tmp[j].ClientName()); + changed = true; + } + + found = true; + PVRChannels_tmp.erase(PVRChannels_tmp.begin()+j); + break; + } + } + + if (changed) + { + database->UpdateDBChannel(at(i)); + CLog::Log(LOGINFO,"PVR: Updated %s channel %s", m_bRadio?"Radio":"TV", at(i).Name().c_str()); + } + + if (!found) + { + CLog::Log(LOGINFO,"PVR: Removing %s channel %s (no more present)", m_bRadio?"Radio":"TV", at(i).Name().c_str()); + database->RemoveDBChannel(at(i)); + erase(begin()+i); + i--; + } + } + } + + /* + * Now whe add new channels to frontend + * All entries now present in the temp lists, are new entries + */ + for (unsigned int i = 0; i < PVRChannels_tmp.size(); i++) + { + PVRChannels_tmp[i].SetChannelID(database->AddDBChannel(PVRChannels_tmp[i])); + push_back(PVRChannels_tmp[i]); + CLog::Log(LOGINFO,"PVR: Added %s channel %s", m_bRadio?"Radio":"TV", PVRChannels_tmp[i].Name().c_str()); + } + + database->Close(); + + m_iHiddenChannels = 0; + for (unsigned int i = 0; i < size(); i++) + { + if (at(i).IsHidden()) + m_iHiddenChannels++; + + + + } + + return false; +} + +void cPVRChannels::SearchAndSetChannelIcons(bool writeDB) +{ + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + database->Open(); + + for (unsigned int i = 0; i < size(); i++) + { + CStdString iconpath; + + /* If the Icon is already set continue with next channel */ + if (at(i).Icon() != "") + continue; + + if (g_guiSettings.GetString("pvrmenu.iconpath") != "") + { + /* Search icon by channel name */ + iconpath = g_guiSettings.GetString("pvrmenu.iconpath") + at(i).ClientName(); + if (CFile::Exists(iconpath + ".tbn")) + { + at(i).SetIcon(iconpath + ".tbn"); + if (writeDB) database->UpdateDBChannel(at(i)); + continue; + } + else if (CFile::Exists(iconpath + ".jpg")) + { + at(i).SetIcon(iconpath + ".jpg"); + if (writeDB) database->UpdateDBChannel(at(i)); + continue; + } + else if (CFile::Exists(iconpath + ".png")) + { + at(i).SetIcon(iconpath + ".png"); + if (writeDB) database->UpdateDBChannel(at(i)); + continue; + } + + /* Search icon by channel name in lower case */ + iconpath = g_guiSettings.GetString("pvrmenu.iconpath") + at(i).ClientName().ToLower(); + if (CFile::Exists(iconpath + ".tbn")) + { + at(i).SetIcon(iconpath + ".tbn"); + if (writeDB) database->UpdateDBChannel(at(i)); + continue; + } + else if (CFile::Exists(iconpath + ".jpg")) + { + at(i).SetIcon(iconpath + ".jpg"); + if (writeDB) database->UpdateDBChannel(at(i)); + continue; + } + else if (CFile::Exists(iconpath + ".png")) + { + at(i).SetIcon(iconpath + ".png"); + if (writeDB) database->UpdateDBChannel(at(i)); + continue; + } + + /* Search Icon by Unique Id */ + iconpath.Format("%s/%08d",g_guiSettings.GetString("pvrmenu.iconpath"), at(i).UniqueID()); + if (CFile::Exists(iconpath + ".tbn")) + { + at(i).SetIcon(iconpath + ".tbn"); + if (writeDB) database->UpdateDBChannel(at(i)); + continue; + } + else if (CFile::Exists(iconpath + ".jpg")) + { + at(i).SetIcon(iconpath + ".jpg"); + if (writeDB) database->UpdateDBChannel(at(i)); + continue; + } + else if (CFile::Exists(iconpath + ".png")) + { + at(i).SetIcon(iconpath + ".png"); + if (writeDB) database->UpdateDBChannel(at(i)); + continue; + } + } + + /* Start channel icon scraper here if nothing was found*/ + /// TODO + + + CLog::Log(LOGNOTICE,"PVR: No channel icon found for %s, use '%s' or '%08li' with extension 'tbn', 'jpg' or 'png'", at(i).Name().c_str(), at(i).ClientName().c_str(), at(i).UniqueID()); + } + + database->Close(); +} + +void cPVRChannels::ReNumberAndCheck(void) +{ + int Number = 1; + m_iHiddenChannels = 0; + for (unsigned int i = 0; i < size(); i++) + { + if (at(i).ClientNumber() <= 0 && !at(i).IsVirtual()) + { + CLog::Log(LOGERROR, "PVR: Channel '%s' from client '%ld' is invalid, removing from list", at(i).Name().c_str(), at(i).ClientID()); + erase(begin()+i); + i--; + break; + } + + if (at(i).UniqueID() <= 0 && !at(i).IsVirtual()) + CLog::Log(LOGNOTICE, "PVR: Channel '%s' from client '%ld' have no unique ID. Contact PVR Client developer.", at(i).Name().c_str(), at(i).ClientID()); + + if (at(i).Name().IsEmpty()) + { + CStdString name; + CLog::Log(LOGERROR, "PVR: Client channel '%i' from client '%ld' have no channel name", at(i).ClientNumber(), at(i).ClientID()); + name.Format(g_localizeStrings.Get(19085), at(i).ClientNumber()); + at(i).SetName(name); + } + + if (at(i).IsHidden()) + m_iHiddenChannels++; + + at(i).SetNumber(Number); + + CStdString path; + if (!m_bRadio) + path.Format("pvr://channels/tv/all/%i.pvr", Number); + else + path.Format("pvr://channels/radio/all/%i.pvr", Number); + + at(i).SetPath(path); + Number++; + } +} + +int cPVRChannels::GetChannels(CFileItemList* results, int group_id) +{ + int cnt = 0; + + for (unsigned int i = 0; i < size(); i++) + { + if (at(i).IsHidden()) + continue; + + if ((group_id != -1) && (at(i).GroupID() != group_id)) + continue; + + CFileItemPtr channel(new CFileItem(at(i))); + + results->Add(channel); + cnt++; + } + return cnt; +} + +int cPVRChannels::GetHiddenChannels(CFileItemList* results) +{ + int cnt = 0; + + for (unsigned int i = 0; i < size(); i++) + { + if (!at(i).IsHidden()) + continue; + + CFileItemPtr channel(new CFileItem(at(i))); + results->Add(channel); + cnt++; + } + return cnt; +} + +void cPVRChannels::MoveChannel(unsigned int oldindex, unsigned int newindex) +{ + cPVRChannels m_channels_temp; + + if ((newindex == oldindex) || (newindex == 0)) + return; + + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + database->Open(); + + m_channels_temp.push_back(at(oldindex-1)); + erase(begin()+oldindex-1); + if (newindex < size()) + insert(begin()+newindex-1, m_channels_temp[0]); + else + push_back(m_channels_temp[0]); + + for (unsigned int i = 0; i < size(); i++) + { + if (at(i).Number() != (int) i+1) + { + CStdString path; + at(i).SetNumber(i+1); + + if (!m_bRadio) + path.Format("pvr://channels/tv/all/%i.pvr", at(i).Number()); + else + path.Format("pvr://channels/radio/all/%i.pvr", at(i).Number()); + + at(i).SetPath(path); + database->UpdateDBChannel(at(i)); + } + } + + CLog::Log(LOGNOTICE, "PVR: TV Channel %d moved to %d", oldindex, newindex); + database->Close(); + + /* Synchronize channel epg containers */ + PVREpgs.AssignChangedChannelTags(m_bRadio); + + /* Synchronize channel numbers inside timers */ + for (unsigned int i = 0; i < PVRTimers.size(); i++) + { + cPVRChannelInfoTag *tag = GetByClient(PVRTimers[i].ClientNumber(), PVRTimers[i].ClientID()); + if (tag) + PVRTimers[i].SetNumber(tag->Number()); + } + + return; +} + +void cPVRChannels::HideChannel(unsigned int number) +{ + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + + for (unsigned int i = 0; i < PVRTimers.size(); i++) + { + if ((PVRTimers[i].Number() == (int) number) && (PVRTimers[i].IsRadio() == m_bRadio)) + { + CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO); + if (!pDialog) + return; + + pDialog->SetHeading(19098); + pDialog->SetLine(0, 19099); + pDialog->SetLine(1, ""); + pDialog->SetLine(2, 19100); + pDialog->DoModal(); + + if (!pDialog->IsConfirmed()) + return; + + PVRTimers.DeleteTimer(PVRTimers[i], true); + } + } + + if ((g_PVRManager.IsPlayingTV() || g_PVRManager.IsPlayingRadio()) && (g_PVRManager.GetCurrentPlayingItem()->GetPVRChannelInfoTag()->Number() == (int) number)) + { + CGUIDialogOK::ShowAndGetInput(19098,19101,0,19102); + return; + } + + if (at(number-1).IsHidden()) + { + at(number-1).SetHidden(false); + database->Open(); + database->UpdateDBChannel(at(number-1)); + m_iHiddenChannels = database->GetNumHiddenChannels(); + database->Close(); + } + else + { + at(number-1).SetHidden(true); + PVREpgs.ClearChannel(at(number-1).ChannelID()); + database->Open(); + database->UpdateDBChannel(at(number-1)); + m_iHiddenChannels = database->GetNumHiddenChannels(); + database->Close(); + MoveChannel(number, size()); + } +} + +cPVRChannelInfoTag *cPVRChannels::GetByNumber(int Number) +{ + for (unsigned int i = 0; i < size(); i++) + { + if (at(i).Number() == Number) + return &at(i); + } + return NULL; +} + +cPVRChannelInfoTag *cPVRChannels::GetByClient(int Number, int ClientID) +{ + for (unsigned int i = 0; i < size(); i++) + { + if (at(i).ClientNumber() == Number && at(i).ClientID() == ClientID) + return &at(i); + } + return NULL; +} + +cPVRChannelInfoTag *cPVRChannels::GetByChannelID(long ChannelID) +{ + for (unsigned int i = 0; i < size(); i++) + { + if (at(i).ChannelID() == ChannelID) + return &at(i); + } + return NULL; +} + +cPVRChannelInfoTag *cPVRChannels::GetByUniqueID(long UniqueID) +{ + for (unsigned int i = 0; i < size(); i++) + { + if (at(i).UniqueID() != 0) + { + if (at(i).UniqueID() == UniqueID) + return &at(i); + } + else + { + if (at(i).ChannelID() == UniqueID) + return &at(i); + } + } + return NULL; +} + +CStdString cPVRChannels::GetNameForChannel(unsigned int Number) +{ + if ((Number <= size()+1) && (Number > 0)) + { + if (at(Number-1).Name() != NULL) + return at(Number-1).Name(); + else + return g_localizeStrings.Get(13205); + } + return ""; +} + +CStdString cPVRChannels::GetChannelIcon(unsigned int Number) +{ + if (Number > 0 && Number <= size()+1) + return ""; + + return at(Number-1).Icon(); +} + +void cPVRChannels::SetChannelIcon(unsigned int Number, CStdString Icon) +{ + if (Number > size()+1) + return; + + if (at(Number-1).Icon() != Icon) + { + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + database->Open(); + at(Number-1).SetIcon(Icon); + database->UpdateDBChannel(at(Number-1)); + database->Close(); + } +} + +void cPVRChannels::ResetChannelEPGLinks() +{ + for (unsigned int i = 0; i < size(); i++) + { + at(i).ResetChannelEPGLinks(); + } +} + +void cPVRChannels::Clear() +{ + /* Clear all current present Channels inside list */ + erase(begin(), end()); + return; +} + +int cPVRChannels::GetNumChannelsFromAll() +{ + return PVRChannelsTV.GetNumChannels()+PVRChannelsRadio.GetNumChannels(); +} + +void cPVRChannels::SearchMissingChannelIcons() +{ + CLog::Log(LOGINFO,"PVR: Manual Channel Icon search started..."); + PVRChannelsTV.SearchAndSetChannelIcons(true); + PVRChannelsRadio.SearchAndSetChannelIcons(true); + /// TODO: Add Process dialog here + CGUIDialogOK::ShowAndGetInput(19103,0,20177,0); +} + +cPVRChannelInfoTag *cPVRChannels::GetByClientFromAll(int Number, int ClientID) +{ + cPVRChannelInfoTag *channel; + + channel = PVRChannelsTV.GetByClient(Number, ClientID); + if (channel != NULL) + return channel; + + channel = PVRChannelsRadio.GetByClient(Number, ClientID); + if (channel != NULL) + return channel; + + return NULL; +} + +cPVRChannelInfoTag *cPVRChannels::GetByChannelIDFromAll(long ChannelID) +{ + cPVRChannelInfoTag *channel; + + channel = PVRChannelsTV.GetByChannelID(ChannelID); + if (channel != NULL) + return channel; + + channel = PVRChannelsRadio.GetByChannelID(ChannelID); + if (channel != NULL) + return channel; + + return NULL; +} + +cPVRChannelInfoTag *cPVRChannels::GetByUniqueIDFromAll(long UniqueID) +{ + cPVRChannelInfoTag *channel; + + channel = PVRChannelsTV.GetByUniqueID(UniqueID); + if (channel != NULL) + return channel; + + channel = PVRChannelsRadio.GetByUniqueID(UniqueID); + if (channel != NULL) + return channel; + + return NULL; +} + +bool cPVRChannels::GetDirectory(const CStdString& strPath, CFileItemList &items) +{ + CStdString base(strPath); + CUtil::RemoveSlashAtEnd(base); + + CURL url(strPath); + CStdString fileName = url.GetFileName(); + CUtil::RemoveSlashAtEnd(fileName); + + if (fileName == "channels") + { + CFileItemPtr item; + + item.reset(new CFileItem(base + "/tv/", true)); + item->SetLabel(g_localizeStrings.Get(19020)); + item->SetLabelPreformated(true); + items.Add(item); + + item.reset(new CFileItem(base + "/radio/", true)); + item->SetLabel(g_localizeStrings.Get(19021)); + item->SetLabelPreformated(true); + items.Add(item); + + return true; + } + else if (fileName == "channels/tv") + { + CFileItemPtr item; + + item.reset(new CFileItem(base + "/all/", true)); + item->SetLabel(g_localizeStrings.Get(593)); + item->SetLabelPreformated(true); + items.Add(item); + + if (PVRChannelsTV.GetNumHiddenChannels() > 0) + { + item.reset(new CFileItem(base + "/.hidden/", true)); + item->SetLabel(g_localizeStrings.Get(19022)); + item->SetLabelPreformated(true); + items.Add(item); + } + + for (unsigned int i = 0; i < PVRChannelGroupsTV.size(); i++) + { + base += "/" + PVRChannelGroupsTV[i].GroupName() + "/"; + item.reset(new CFileItem(base, true)); + item->SetLabel(PVRChannelGroupsTV[i].GroupName()); + item->SetLabelPreformated(true); + items.Add(item); + } + + return true; + } + else if (fileName == "channels/radio") + { + CFileItemPtr item; + + item.reset(new CFileItem(base + "/all/", true)); + item->SetLabel(g_localizeStrings.Get(593)); + item->SetLabelPreformated(true); + items.Add(item); + + if (PVRChannelsTV.GetNumHiddenChannels() > 0) + { + item.reset(new CFileItem(base + "/.hidden/", true)); + item->SetLabel(g_localizeStrings.Get(19022)); + item->SetLabelPreformated(true); + items.Add(item); + } + + for (unsigned int i = 0; i < PVRChannelGroupsRadio.size(); i++) + { + base += "/" + PVRChannelGroupsRadio[i].GroupName() + "/"; + item.reset(new CFileItem(base, true)); + item->SetLabel(PVRChannelGroupsRadio[i].GroupName()); + item->SetLabelPreformated(true); + items.Add(item); + } + + return true; + } + else if (fileName.Left(12) == "channels/tv/") + { + if (fileName.substr(12) == ".hidden") + { + for (unsigned int i = 0; i < PVRChannelsTV.size(); i++) + { + if (!PVRChannelsTV[i].IsHidden()) + continue; + + CFileItemPtr channel(new CFileItem(PVRChannelsTV[i])); + items.Add(channel); + } + } + else + { + int groupID = PVRChannelGroupsTV.GetGroupId(fileName.substr(12)); + + for (unsigned int i = 0; i < PVRChannelsTV.size(); i++) + { + if (PVRChannelsTV[i].IsHidden()) + continue; + + if ((groupID != -1) && (PVRChannelsTV[i].GroupID() != groupID)) + continue; + + CFileItemPtr channel(new CFileItem(PVRChannelsTV[i])); + items.Add(channel); + } + } + return true; + } + else if (fileName.Left(15) == "channels/radio/") + { + if (fileName.substr(15) == ".hidden") + { + for (unsigned int i = 0; i < PVRChannelsRadio.size(); i++) + { + if (!PVRChannelsRadio[i].IsHidden()) + continue; + + CFileItemPtr channel(new CFileItem(PVRChannelsRadio[i])); + items.Add(channel); + } + } + else + { + int groupID = PVRChannelGroupsRadio.GetGroupId(fileName.substr(15)); + + for (unsigned int i = 0; i < PVRChannelsRadio.size(); i++) + { + if (PVRChannelsRadio[i].IsHidden()) + continue; + + if ((groupID != -1) && (PVRChannelsRadio[i].GroupID() != groupID)) + continue; + + CFileItemPtr channel(new CFileItem(PVRChannelsRadio[i])); + CMusicInfoTag* musictag = channel->GetMusicInfoTag(); + if (musictag) + { + musictag->SetURL(PVRChannelsRadio[i].Path()); + musictag->SetTitle(PVRChannelsRadio[i].NowTitle()); + musictag->SetArtist(PVRChannelsRadio[i].Name()); + musictag->SetAlbumArtist(PVRChannelsRadio[i].Name()); + musictag->SetGenre(PVRChannelsRadio[i].NowGenre()); + musictag->SetDuration(PVRChannelsRadio[i].NowDuration()); + musictag->SetLoaded(true); + musictag->SetComment(""); + musictag->SetLyrics(""); + } + items.Add(channel); + } + } + return true; + } + + return false; +} + +cPVRChannelInfoTag *cPVRChannels::GetByPath(CStdString &path) +{ + CURL url(path); + CStdString fileName = url.GetFileName(); + CUtil::RemoveSlashAtEnd(fileName); + + if (fileName.Left(16) == "channels/tv/all/") + { + fileName.erase(0,16); + int channelNr = atoi(fileName.c_str()); + + for (unsigned int i = 0; i < PVRChannelsTV.size(); i++) + { + if (PVRChannelsTV[i].Number() == channelNr) + return &PVRChannelsTV[i]; + } + } + else if (fileName.Left(19) == "channels/radio/all/") + { + fileName.erase(0,19); + int channelNr = atoi(fileName.c_str()); + + for (unsigned int i = 0; i < PVRChannelsRadio.size(); i++) + { + if (PVRChannelsRadio[i].Number() == channelNr) + return &PVRChannelsRadio[i]; + } + } + + return NULL; +} + +// --- cPVRChannelGroup ----------------------------------------------------------- + +cPVRChannelGroup::cPVRChannelGroup(void) +{ + m_iGroupID = 0; + m_GroupName = ""; +} + +// --- cPVRChannelGroups ---------------------------------------------------------- + +cPVRChannelGroups PVRChannelGroupsTV; +cPVRChannelGroups PVRChannelGroupsRadio; + +cPVRChannelGroups::cPVRChannelGroups(void) +{ +} + +bool cPVRChannelGroups::Load(bool radio) +{ + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + database->Open(); + + m_bRadio = radio; + Clear(); + if (!m_bRadio) + database->GetChannelGroupList(*this); + else + database->GetRadioChannelGroupList(*this); + + database->Close(); + return true; +} + +void cPVRChannelGroups::Unload() +{ + Clear(); +} + +int cPVRChannelGroups::GetGroupList(CFileItemList* results) +{ + for (unsigned int i = 0; i < size(); i++) + { + CFileItemPtr group(new CFileItem(at(i).GroupName())); + group->m_strTitle = at(i).GroupName(); + group->m_strPath.Format("%i", at(i).GroupID()); + results->Add(group); + } + return size(); +} + +int cPVRChannelGroups::GetFirstChannelForGroupID(int GroupId) +{ + if (GroupId == -1) + return 1; + + cPVRChannels *channels; + if (!m_bRadio) + channels = &PVRChannelsTV; + else + channels = &PVRChannelsRadio; + + for (unsigned int i = 0; i < channels->size(); i++) + { + if (channels->at(i).GroupID() == GroupId) + return i+1; + } + return 1; +} + +int cPVRChannelGroups::GetPrevGroupID(int current_group_id) +{ + if (size() == 0) + return -1; + + if ((current_group_id == -1) || (current_group_id == 0)) + return at(size()-1).GroupID(); + + for (unsigned int i = 0; i < size(); i++) + { + if (current_group_id == at(i).GroupID()) + { + if (i != 0) + return at(i-1).GroupID(); + else + return -1; + } + } + return -1; +} + +int cPVRChannelGroups::GetNextGroupID(int current_group_id) +{ + unsigned int i = 0; + + if (size() == 0) + return -1; + + if ((current_group_id == 0) || (current_group_id == -1)) + return at(0).GroupID(); + + if (size() == 0) + return -1; + + for (; i < size(); i++) + { + if (current_group_id == at(i).GroupID()) + break; + } + + if (i >= size()-1) + return -1; + else + return at(i+1).GroupID(); +} + +void cPVRChannelGroups::AddGroup(const CStdString &name) +{ + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + database->Open(); + + Clear(); + if (!m_bRadio) + { + database->AddChannelGroup(name, -1); + database->GetChannelGroupList(*this); + } + else + { + database->AddRadioChannelGroup(name, -1); + database->GetRadioChannelGroupList(*this); + } + + database->Close(); +} + +bool cPVRChannelGroups::RenameGroup(unsigned int GroupId, const CStdString &newname) +{ + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + database->Open(); + + Clear(); + if (!m_bRadio) + { + database->SetChannelGroupName(GroupId, newname); + database->GetChannelGroupList(*this); + } + else + { + database->SetRadioChannelGroupName(GroupId, newname); + database->GetRadioChannelGroupList(*this); + } + + database->Close(); + return true; +} + +bool cPVRChannelGroups::DeleteGroup(unsigned int GroupId) +{ + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + database->Open(); + + Clear(); + + /* Set all channels with this group to undefined */ + if (!m_bRadio) + { + /* Delete the group inside Database */ + database->DeleteChannelGroup(GroupId); + + for (unsigned int i = 0; i < PVRChannelsTV.size(); i++) + { + if (PVRChannelsTV[i].GroupID() == GroupId) + { + PVRChannelsTV[i].SetGroupID(0); + database->UpdateDBChannel(PVRChannelsTV[i]); + } + } + + /* Reload the group list */ + database->GetChannelGroupList(*this); + } + else + { + /* Delete the group inside Database */ + database->DeleteRadioChannelGroup(GroupId); + + for (unsigned int i = 0; i < PVRChannelsRadio.size(); i++) + { + if (PVRChannelsRadio[i].GroupID() == GroupId) + { + PVRChannelsRadio[i].SetGroupID(0); + database->UpdateDBChannel(PVRChannelsRadio[i]); + } + } + + /* Reload the group list */ + database->GetRadioChannelGroupList(*this); + } + + database->Close(); + return true; +} + +CStdString cPVRChannelGroups::GetGroupName(int GroupId) +{ + if (GroupId != -1) + { + for (unsigned int i = 0; i < size(); i++) + { + if (GroupId == at(i).GroupID()) + return at(i).GroupName(); + } + } + + return g_localizeStrings.Get(593); +} + +int cPVRChannelGroups::GetGroupId(CStdString GroupName) +{ + if (GroupName.IsEmpty() || GroupName == g_localizeStrings.Get(593) || GroupName == "all") + return -1; + + for (unsigned int i = 0; i < size(); i++) + { + if (GroupName == at(i).GroupName()) + return at(i).GroupID(); + } + return -1; +} + +bool cPVRChannelGroups::ChannelToGroup(const cPVRChannelInfoTag &channel, unsigned int GroupId) +{ + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + database->Open(); + + cPVRChannels *channels; + if (!channel.IsRadio()) + channels = &PVRChannelsTV; + else + channels = &PVRChannelsRadio; + + channels->at(channel.Number()-1).SetGroupID(GroupId); + database->UpdateDBChannel(channels->at(channel.Number()-1)); + + database->Close(); + return true; +} + +void cPVRChannelGroups::Clear() +{ + /* Clear all current present Channel groups inside list */ + erase(begin(), end()); + return; +} diff --git a/xbmc/utils/PVRChannels.h b/xbmc/utils/PVRChannels.h new file mode 100644 index 0000000000..5123f300f5 --- /dev/null +++ b/xbmc/utils/PVRChannels.h @@ -0,0 +1,312 @@ +#pragma once +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * for DESCRIPTION see 'PVRChannels.cpp' + */ + +#include "VideoInfoTag.h" +#include "DateTime.h" +#include "FileItem.h" +#include "../addons/include/xbmc_pvr_types.h" + +class cPVREpg; + +class cPVRChannelInfoTag +{ + friend class cPVREpgs; + friend class CTVDatabase; + +private: + void UpdateRunningEvents(); + void DisplayError(PVR_ERROR err) const; + + mutable const cPVREpg *m_Epg; + mutable const cPVREPGInfoTag *m_epgNow; + mutable const cPVREPGInfoTag *m_epgNext; + + /* XBMC related channel data */ + int m_iIdChannel; /**< \brief Database number */ + int m_iChannelNum; /**< \brief Channel number for channels on XBMC */ + int m_iGroupID; /**< \brief Channel group identfier */ + int m_encryptionSystem; /**< \brief Encryption System, 0 for FreeToAir, -1 unknown */ + bool m_radio; /**< \brief Radio channel */ + bool m_hide; /**< \brief Channel is hide inside filelists */ + bool m_isRecording; /**< \brief True if channel is currently recording */ + bool m_grabEpg; /**< \brief Load EPG if set to true */ + CStdString m_grabber; /**< \brief The EPG grabber name (client for backend reading) */ + CStdString m_IconPath; /**< \brief Path to the logo image */ + CStdString m_strChannel; /**< \brief Channel name */ + int m_countWatched; /**< \brief The count how much this channel was selected */ + long m_secondsWatched; /**< \brief How many seconds this channel was watched */ + CDateTime m_lastTimeWatched; /**< \brief The Date where this channel was selected last time */ + bool m_bIsVirtual; /**< \brief Is a user defined virtual channel if true */ + + long m_iPortalMasterChannel; /**< \brief If it is a Portal Slave channel here is the master channel id or 0 for master, -1 for no portal */ + std::vector m_PortalChannels; /**< \brief Stores the slave portal channels if this is a master */ + + /* Client related channel data */ + long m_iIdUnique; /**< \brief Unique Id for this channel */ + int m_clientID; /**< \brief Id of client channel come from */ + int m_iClientNum; /**< \brief Channel number on client */ + CStdString m_strClientName; /**< \brief Channel name on client */ + + CStdString m_strInputFormat; /**< \brief The stream input type based upon ffmpeg/libavformat/allformats.c */ + CStdString m_strStreamURL; /**< \brief URL of the stream, if empty use Client to read stream */ + CStdString m_strFileNameAndPath; /**< \brief Filename for PVRManager to open and read stream */ + + std::vector m_linkedChannels; /**< \brief Channels linked to this channel */ + +public: + cPVRChannelInfoTag() { Reset(); }; + void Reset(); + ///< Set the tag to it's initial values. + void ResetChannelEPGLinks(); + ///< Clear the EPG links + bool IsEmpty() const; + ///< True if no required data is present inside the tag. + + bool operator ==(const cPVRChannelInfoTag &right) const; + bool operator !=(const cPVRChannelInfoTag &right) const; + + /* Channel information */ + CStdString Name(void) const { return m_strChannel; } + ///< Return the currently by XBMC used name for this channel. + void SetName(CStdString name) { m_strChannel = name; } + ///< Set the name, XBMC uses for this channel. + int Number(void) const { return m_iChannelNum; } + ///< Return the currently by XBMC used channel number for this channel. + void SetNumber(int Number) { m_iChannelNum = Number; } + ///< Change the XBMC number for this channel. + CStdString ClientName(void) const { return m_strClientName; } + ///< Return the name used by the client driver on the backend. + void SetClientName(CStdString name) { m_strClientName = name; } + ///< Set the name used by the client (is changed only in this tag, + ///< no client action to change name is performed ). + int ClientNumber(void) const { return m_iClientNum; } + ///< Return the channel number used by the client driver. + void SetClientNumber(int Number) { m_iClientNum = Number; } + ///< Change the client number for this channel (is changed only in this tag, + ///< no client action to change name is performed ). + long ClientID(void) const { return m_clientID; } + ///< The client ID this channel belongs to. + void SetClientID(int ClientId) { m_clientID = ClientId; } + ///< Set the client ID for this channel. + long ChannelID(void) const { return m_iIdChannel; } + ///< Return XBMC own channel ID for this channel which is used in the + ///< TV Database. + void SetChannelID(int ChannelID) { m_iIdChannel = ChannelID; } + ///< Change the channel ID for this channel (no Action to the Database + ///< are taken) + long UniqueID(void) const { return m_iIdUnique; } + ///< A UniqueID for this channel provider to identify same channels on different clients. + void SetUniqueID(long id) { m_iIdUnique = id; } + ///< Change the Unique ID for this channel. + long GroupID(void) const { return m_iGroupID; } + ///< The Group this channel belongs to, -1 for undefined. + void SetGroupID(long group) { m_iGroupID = group; } + ///< Set the group ID for this channel. + bool IsEncrypted(void) const { return m_encryptionSystem > 0; } + ///< Return true if this channel is encrypted. Does not inform if XBMC can play it, + ///< decryption is done by the client associated backend. + int EncryptionSystem(void) const { return m_encryptionSystem; } + ///< Return the encryption system ID for this channel, 0 for FTA. Is based + ///< upon: http://www.dvb.org/index.php?id=174. + CStdString EncryptionName() const; + ///< Return a human understandable name for the used encryption system. + void SetEncryptionSystem(int system) { m_encryptionSystem = system; } + ///< Set the encryption ID for this channel. + bool IsRadio(void) const { return m_radio; } + ///< Return true if this is a Radio channel. + void SetRadio(bool radio) { m_radio = radio; } + ///< Set the radio flag. + bool IsRecording(void) const { return m_isRecording; } + ///< True if this channel is currently recording. + void SetRecording(bool rec) { m_isRecording = rec; } + ///< Set the recording state. + CStdString StreamURL(void) const { return m_strStreamURL; } + ///< The Stream URL to access this channel, it can be all types of protocol and types + ///< are supported by XBMC or in case the client read the stream leave it empty + void SetStreamURL(CStdString stream) { m_strStreamURL = stream; } + ///< Set the stream URL + CStdString Path(void) const { return m_strFileNameAndPath; } + ///< Return the path in the XBMC virtual Filesystem. + void SetPath(CStdString path) { m_strFileNameAndPath = path; } + ///< Set the path in XBMC Virtual Filesystem. + CStdString Icon(void) const { return m_IconPath; } + ///< Return Path with Filename of the Icon for this channel. + void SetIcon(CStdString icon) { m_IconPath = icon; } + ///< Set the path and filename for this channel Icon + bool IsHidden(void) const { return m_hide; } + ///< If this channel is hidden from view it is true. + void SetHidden(bool hide) { m_hide = hide; } + ///< Mask this channel hidden. + bool GrabEpg() { return m_grabEpg; } + ///< If false ignore EPG for this channel. + void SetGrabEpg(bool grabEpg) { m_grabEpg = grabEpg; } + ///< Change the EPG grabbing flag + bool IsVirtual() { return m_bIsVirtual; } + ///< If true it is a virtual channel + void SetVirtual(bool virtualChannel) { m_bIsVirtual = virtualChannel; } + ///< Change the virtual flag + CStdString Grabber(void) const { return m_grabber; } + ///< Get the EPG scraper name + void SetGrabber(CStdString Grabber) { m_grabber = Grabber; } + ///< Set the EPG scraper name, use "client" for loading the EPG from Backend + bool IsPortalMaster() { return m_iPortalMasterChannel == 0; } + ///< Return true if this is a Portal Master channel + bool IsPortalSlave() { return m_iPortalMasterChannel > 0; } + ///< Return true if this is a Portal Slave channel + void SetPortalChannel(long channelID) { m_iPortalMasterChannel = channelID; } + ///< Set the portal channel ID, use -1 for no portal channel, NULL if this is a + ///< master channel or if it is a slave the master client ID + void ClearPortalChannels() { m_PortalChannels.erase(m_PortalChannels.begin(), m_PortalChannels.end()); } + ///< Remove all Portal channels + void AddPortalChannel(long channelID) { m_PortalChannels.push_back(channelID); } + ///< Add a channel ID to the Portal list + int GetPortalChannels(CFileItemList* results); + ///< Returns a File Item list with all portal channels + + /*! \brief Get the input format from the Backend + If it is empty ffmpeg scanning the stream to find the right input format. + See "xbmc/cores/dvdplayer/Codecs/ffmpeg/libavformat/allformats.c" for a + list of the input formats. + \return The name of the input format + */ + CStdString InputFormat(void) const { return m_strInputFormat; } + + /*! \brief Set the input format of the Backend this channel belongs to + \param format The name of the input format + */ + void SetInputFormat(CStdString format) { m_strInputFormat = format; } + + /*! \name EPG information for now playing event */ + CStdString NowTitle() const; + CStdString NowPlotOutline() const; + CStdString NowPlot() const; + CDateTime NowStartTime(void) const; + CDateTime NowEndTime(void) const; + int NowDuration() const; + int NowPlayTime() const; + CStdString NowGenre(void) const; + int NowParentalRating() const; + + /*! \name EPG information for next playing event */ + CStdString NextTitle() const; + CStdString NextPlotOutline() const; + CStdString NextPlot() const; + CDateTime NextStartTime(void) const; + CDateTime NextEndTime(void) const; + int NextDuration() const; + CStdString NextGenre(void) const; + int NextParentalRating() const; + + /*! \name Linked channel functions, used for portal mode */ + void ClearChannelLinkage() { m_linkedChannels.erase(m_linkedChannels.begin(), m_linkedChannels.end()); } + void AddChannelLinkage(long LinkedChannel) { m_linkedChannels.push_back(LinkedChannel); } +}; + +class cPVRChannels : public std::vector +{ +private: + bool m_bRadio; + int m_iHiddenChannels; + +public: + cPVRChannels(void); + bool Load(bool radio); + void Unload(); + bool Update(); + void ReNumberAndCheck(void); + void SearchAndSetChannelIcons(bool writeDB = false); + int GetNumChannels() const { return size(); } + int GetNumHiddenChannels() const { return m_iHiddenChannels; } + int GetChannels(CFileItemList* results, int group_id = -1); + int GetHiddenChannels(CFileItemList* results); + void MoveChannel(unsigned int oldindex, unsigned int newindex); + void HideChannel(unsigned int number); + cPVRChannelInfoTag *GetByNumber(int Number); + cPVRChannelInfoTag *GetByClient(int Number, int ClientID); + cPVRChannelInfoTag *GetByChannelID(long ChannelID); + cPVRChannelInfoTag *GetByUniqueID(long UniqueID); + CStdString GetNameForChannel(unsigned int Number); + CStdString GetChannelIcon(unsigned int Number); + void SetChannelIcon(unsigned int Number, CStdString Icon); + void ResetChannelEPGLinks(); + void Clear(); + + static int GetNumChannelsFromAll(); + static void SearchMissingChannelIcons(); + static cPVRChannelInfoTag *GetByClientFromAll(int Number, int ClientID); + static cPVRChannelInfoTag *GetByChannelIDFromAll(long ChannelID); + static cPVRChannelInfoTag *GetByUniqueIDFromAll(long UniqueID); + static cPVRChannelInfoTag *GetByPath(CStdString &path); + static bool GetDirectory(const CStdString& strPath, CFileItemList &items); +}; + +class cPVRChannelGroup +{ +private: + unsigned long m_iGroupID; + CStdString m_GroupName; + int m_iSortOrder; + +public: + cPVRChannelGroup(void); + + long GroupID(void) const { return m_iGroupID; } + void SetGroupID(long group) { m_iGroupID = group; } + CStdString GroupName(void) const { return m_GroupName; } + void SetGroupName(CStdString name) { m_GroupName = name; } + long SortOrder(void) const { return m_iSortOrder; } + void SetSortOrder(long sortorder) { m_iSortOrder = sortorder; } +}; + +class cPVRChannelGroups : public std::vector +{ +private: + bool m_bRadio; + +public: + cPVRChannelGroups(void); + + bool Load(bool radio = false); + void Unload(); + + int GetGroupList(CFileItemList* results); + int GetFirstChannelForGroupID(int GroupId); + int GetPrevGroupID(int current_group_id); + int GetNextGroupID(int current_group_id); + + void AddGroup(const CStdString &name); + bool RenameGroup(unsigned int GroupId, const CStdString &newname); + bool DeleteGroup(unsigned int GroupId); + bool ChannelToGroup(const cPVRChannelInfoTag &channel, unsigned int GroupId); + CStdString GetGroupName(int GroupId); + int GetGroupId(CStdString GroupName); + void Clear(); +}; + +extern cPVRChannels PVRChannelsTV; +extern cPVRChannels PVRChannelsRadio; +extern cPVRChannelGroups PVRChannelGroupsTV; +extern cPVRChannelGroups PVRChannelGroupsRadio; diff --git a/xbmc/utils/PVREpg.cpp b/xbmc/utils/PVREpg.cpp new file mode 100644 index 0000000000..6922f57c7b --- /dev/null +++ b/xbmc/utils/PVREpg.cpp @@ -0,0 +1,1452 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "FileItem.h" +#include "Application.h" +#include "PVREpg.h" +#include "PVRChannels.h" +#include "PVRManager.h" +#include "GUISettings.h" +#include "GUIDialogPVRUpdateProgressBar.h" +#include "GUIWindowManager.h" +#include "utils/log.h" +#include "utils/TimeUtils.h" +#include "utils/SingleLock.h" +#include "LocalizeStrings.h" +#include "TextSearch.h" + +using namespace std; + +/// ----- EPGSearchFilter ------------------------------------------------------------------------- + +void EPGSearchFilter::SetDefaults() +{ + m_SearchString = ""; + m_CaseSensitive = false; + m_SearchDescription = false; + m_GenreType = -1; + m_GenreSubType = -1; + m_minDuration = -1; + m_maxDuration = -1; + PVREpgs.GetFirstEPGDate().GetAsSystemTime(m_startDate); + m_startDate.wHour = 0; + m_startDate.wMinute = 0; + PVREpgs.GetLastEPGDate().GetAsSystemTime(m_endDate); + m_endDate.wHour = 23; + m_endDate.wMinute = 59; + m_startTime = m_startDate; + m_startTime.wHour = 0; + m_startTime.wMinute = 0; + m_endTime = m_endDate; + m_endTime.wHour = 23; + m_endTime.wMinute = 59; + m_ChannelNumber = -1; + m_FTAOnly = false; + m_IncUnknGenres = true; + m_Group = -1; + m_IgnPresentTimers = true; + m_IgnPresentRecords = true; + m_PreventRepeats = false; +} + +bool EPGSearchFilter::FilterEntry(const cPVREPGInfoTag &tag) const +{ + if (m_GenreType != -1) + { + if (tag.GenreType() != m_GenreType && + (!m_IncUnknGenres && + ((tag.GenreType() < EVCONTENTMASK_USERDEFINED || tag.GenreType() >= EVCONTENTMASK_MOVIEDRAMA)))) + { + return false; + } + } + if (m_minDuration != -1) + { + if (tag.GetDuration() < (m_minDuration*60)) + return false; + } + if (m_maxDuration != -1) + { + if (tag.GetDuration() > (m_maxDuration*60)) + return false; + } + if (m_ChannelNumber != -1) + { + if (m_ChannelNumber == -2) + { + if (tag.IsRadio()) + return false; + } + else if (m_ChannelNumber == -3) + { + if (!tag.IsRadio()) + return false; + } + else if (tag.ChannelNumber() != m_ChannelNumber) + return false; + } + if (m_FTAOnly && tag.IsEncrypted()) + { + return false; + } + if (m_Group != -1) + { + if (tag.GroupID() != m_Group) + return false; + } + + int timeTag = (tag.Start().GetHour()*60 + tag.Start().GetMinute()); + + if (timeTag < (m_startTime.wHour*60 + m_startTime.wMinute)) + return false; + + if (timeTag > (m_endTime.wHour*60 + m_endTime.wMinute)) + return false; + + if (tag.Start() < m_startDate) + return false; + + if (tag.Start() > m_endDate) + return false; + + if (m_SearchString != "") + { + cTextSearch search(tag.Title(), m_SearchString, m_CaseSensitive); + if (!search.DoSearch()) + { + if (m_SearchDescription) + { + search.SetText(tag.PlotOutline(), m_SearchString, m_CaseSensitive); + if (!search.DoSearch()) + { + search.SetText(tag.Plot(), m_SearchString, m_CaseSensitive); + if (!search.DoSearch()) + return false; + } + } + else + return false; + } + } + return true; +} + + +/// ----- cPVREPGInfoTag -------------------------------------------------------------------------- + +cPVREPGInfoTag::cPVREPGInfoTag(long uniqueBroadcastID) +{ + Reset(); + m_uniqueBroadcastID = uniqueBroadcastID; +} + +void cPVREPGInfoTag::Reset() +{ + m_strTitle = g_localizeStrings.Get(19055); + m_strGenre = ""; + m_strPlotOutline = ""; + m_strPlot = ""; + m_GenreType = 0; + m_GenreSubType = 0; + m_strFileNameAndPath = ""; + m_IconPath = ""; + m_isRecording = false; + m_Timer = NULL; + m_Epg = NULL; + m_parentalRating = 0; + m_starRating = 0; + m_notify = false; + m_seriesNum = ""; + m_episodeNum = ""; + m_episodePart = ""; + m_episodeName = ""; +} + +bool cPVREPGInfoTag::HasTimer(void) const +{ + for (unsigned int i = 0; i < PVRTimers.size(); ++i) + { + if (PVRTimers[i].Epg() == this) + return true; + } + return false; +} + +int cPVREPGInfoTag::GetDuration() const +{ + time_t start, end; + m_startTime.GetAsTime(start); + m_endTime.GetAsTime(end); + return end - start; +} + +void cPVREPGInfoTag::SetGenre(int ID, int subID) +{ + m_GenreType = ID; + m_GenreSubType = subID; + m_strGenre = ConvertGenreIdToString(ID, subID); +} + +CStdString cPVREPGInfoTag::ConvertGenreIdToString(int ID, int subID) const +{ + CStdString str = g_localizeStrings.Get(19499); + switch (ID) + { + case EVCONTENTMASK_MOVIEDRAMA: + if (subID <= 8) + str = g_localizeStrings.Get(19500 + subID); + else + str = g_localizeStrings.Get(19500) + " (undefined)"; + break; + case EVCONTENTMASK_NEWSCURRENTAFFAIRS: + if (subID <= 4) + str = g_localizeStrings.Get(19516 + subID); + else + str = g_localizeStrings.Get(19516) + " (undefined)"; + break; + case EVCONTENTMASK_SHOW: + if (subID <= 3) + str = g_localizeStrings.Get(19532 + subID); + else + str = g_localizeStrings.Get(19532) + " (undefined)"; + break; + case EVCONTENTMASK_SPORTS: + if (subID <= 0x0B) + str = g_localizeStrings.Get(19548 + subID); + else + str = g_localizeStrings.Get(19548) + " (undefined)"; + break; + case EVCONTENTMASK_CHILDRENYOUTH: + if (subID <= 5) + str = g_localizeStrings.Get(19564 + subID); + else + str = g_localizeStrings.Get(19564) + " (undefined)"; + break; + case EVCONTENTMASK_MUSICBALLETDANCE: + if (subID <= 6) + str = g_localizeStrings.Get(19580 + subID); + else + str = g_localizeStrings.Get(19580) + " (undefined)"; + break; + case EVCONTENTMASK_ARTSCULTURE: + if (subID <= 0x0B) + str = g_localizeStrings.Get(19596 + subID); + else + str = g_localizeStrings.Get(19596) + " (undefined)"; + break; + case EVCONTENTMASK_SOCIALPOLITICALECONOMICS: + if (subID <= 0x03) + str = g_localizeStrings.Get(19612 + subID); + else + str = g_localizeStrings.Get(19612) + " (undefined)"; + break; + case EVCONTENTMASK_EDUCATIONALSCIENCE: + if (subID <= 0x07) + str = g_localizeStrings.Get(19628 + subID); + else + str = g_localizeStrings.Get(19628) + " (undefined)"; + break; + case EVCONTENTMASK_LEISUREHOBBIES: + if (subID <= 0x07) + str = g_localizeStrings.Get(19644 + subID); + else + str = g_localizeStrings.Get(19644) + " (undefined)"; + break; + case EVCONTENTMASK_SPECIAL: + if (subID <= 0x03) + str = g_localizeStrings.Get(19660 + subID); + else + str = g_localizeStrings.Get(19660) + " (undefined)"; + break; + case EVCONTENTMASK_USERDEFINED: + if (subID <= 0x03) + str = g_localizeStrings.Get(19676 + subID); + else + str = g_localizeStrings.Get(19676) + " (undefined)"; + break; + default: + break; + } + return str; +} + + +/// ----- cPVREpg --------------------------------------------------------------------------------- + +struct sortEPGbyDate +{ + bool operator()(cPVREPGInfoTag* strItem1, cPVREPGInfoTag* strItem2) + { + if (!strItem1 || !strItem2) + return false; + + return strItem1->Start() < strItem2->Start(); + } +}; + +cPVREpg::cPVREpg(long ChannelID) +{ + m_channelID = ChannelID; + m_Channel = cPVRChannels::GetByChannelIDFromAll(ChannelID); + m_bUpdateRunning = false; + m_bValid = ChannelID == -1 ? false : true; +} + +bool cPVREpg::IsValid(void) const +{ + if (!m_bValid || m_tags.size() == 0) + return false; + + if (m_tags[m_tags.size()-1]->m_endTime < CDateTime::GetCurrentDateTime()) + return false; + + return true; +} + +cPVREPGInfoTag *cPVREpg::AddInfoTag(cPVREPGInfoTag *Tag) +{ + m_tags.push_back(Tag); + return Tag; +} + +void cPVREpg::DelInfoTag(cPVREPGInfoTag *tag) +{ + if (tag->m_Epg == this) + { + for (unsigned int i = 0; i < m_tags.size(); i++) + { + cPVREPGInfoTag *entry = m_tags[i]; + if (entry == tag) + { + delete entry; + m_tags.erase(m_tags.begin()+i); + return; + } + } + } +} + +void cPVREpg::Sort(void) +{ + sort(m_tags.begin(), m_tags.end(), sortEPGbyDate()); +} + +void cPVREpg::Cleanup(void) +{ + Cleanup(CDateTime::GetCurrentDateTime()); +} + +void cPVREpg::Cleanup(CDateTime Time) +{ + m_bUpdateRunning = true; + for (unsigned int i = 0; i < m_tags.size(); i++) + { + cPVREPGInfoTag *tag = m_tags[i]; + if (!tag->HasTimer() && (tag->End()+CDateTimeSpan(0, g_guiSettings.GetInt("pvrmenu.lingertime") / 60 + 1, g_guiSettings.GetInt("pvrmenu.lingertime") % 60, 0) < Time)) // adding one hour for safety + { + DelInfoTag(tag); + } + else + break; + } + m_bUpdateRunning = false; +} + +const cPVREPGInfoTag *cPVREpg::GetInfoTagNow(void) const +{ + CDateTime now = CDateTime::GetCurrentDateTime(); + + if (m_tags.size() == 0) + return NULL; + + for (unsigned int i = 0; i < m_tags.size(); i++) + { + if ((m_tags[i]->Start() <= now) && (m_tags[i]->End() > now)) + return m_tags[i]; + } + return NULL; +} + +const cPVREPGInfoTag *cPVREpg::GetInfoTagNext(void) const +{ + CDateTime now = CDateTime::GetCurrentDateTime(); + + if (m_tags.size() == 0) + return false; + + for (unsigned int i = 0; i < m_tags.size(); i++) + { + if ((m_tags[i]->Start() <= now) && (m_tags[i]->End() > now)) + { + CDateTime next = m_tags[i]->End(); + + for (unsigned int j = 0; j < m_tags.size(); j++) + { + if (m_tags[j]->Start() >= next) + { + return m_tags[j]; + } + } + } + } + + return NULL; +} + +const cPVREPGInfoTag *cPVREpg::GetInfoTag(long uniqueID, CDateTime StartTime) const +{ + if (uniqueID > 0) + { + for (unsigned int i = 0; i < m_tags.size(); i++) + { + if (m_tags[i]->GetUniqueBroadcastID() == uniqueID) + return m_tags[i]; + } + } + else + { + for (unsigned int i = 0; i < m_tags.size(); i++) + { + if (m_tags[i]->Start() == StartTime) + return m_tags[i]; + } + } + + return NULL; +} + +const cPVREPGInfoTag *cPVREpg::GetInfoTagAround(CDateTime Time) const +{ + if (m_tags.size() == 0) + return NULL; + + for (unsigned int i = 0; i < m_tags.size(); i++) + { + if ((m_tags[i]->Start() <= Time) && (m_tags[i]->End() >= Time)) + return m_tags[i]; + } + return NULL; +} + +CDateTime cPVREpg::GetLastEPGDate() +{ + CDateTime last = CDateTime::GetCurrentDateTime(); + for (unsigned int i = 0; i < m_tags.size(); i++) + { + if (m_tags[i]->End() >= last) + last = m_tags[i]->End(); + } + return last; +} + +bool cPVREpg::Add(const PVR_PROGINFO *data, cPVREpg *Epg) +{ + if (Epg && data) + { + cPVREPGInfoTag *InfoTag = NULL; + cPVREPGInfoTag *newInfoTag = NULL; + long uniqueBroadcastID = data->uid; + + InfoTag = (cPVREPGInfoTag *)Epg->GetInfoTag(uniqueBroadcastID, data->starttime); + if (!InfoTag) + InfoTag = newInfoTag = new cPVREPGInfoTag(uniqueBroadcastID); + + if (InfoTag) + { + CStdString path; + path.Format("pvr://guide/channel-%04i/%s.epg", Epg->m_Channel->Number(), InfoTag->Start().GetAsDBDateTime().c_str()); + InfoTag->SetPath(path); + InfoTag->SetStart((time_t)data->starttime); + InfoTag->SetEnd((time_t)data->endtime); + InfoTag->SetTitle(data->title); + InfoTag->SetPlotOutline(data->subtitle); + InfoTag->SetPlot(data->description); + InfoTag->SetGenre(data->genre_type, data->genre_sub_type); + InfoTag->SetParentalRating(data->parental_rating); + InfoTag->SetIcon(Epg->m_Channel->Icon()); + InfoTag->m_Epg = Epg; + + if (newInfoTag) + Epg->AddInfoTag(newInfoTag); + + return true; + } + } + return false; +} + +bool cPVREpg::AddDB(const PVR_PROGINFO *data, cPVREpg *Epg) +{ + if (Epg && data) + { + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + cPVREPGInfoTag InfoTag(data->uid); + + /// NOTE: Database is already opened by the EPG Update function + CStdString path; + path.Format("pvr://guide/channel-%04i/%s.epg", Epg->m_Channel->Number(), InfoTag.Start().GetAsDBDateTime().c_str()); + InfoTag.SetPath(path); + InfoTag.SetStart(CDateTime((time_t)data->starttime)); + InfoTag.SetEnd(CDateTime((time_t)data->endtime)); + InfoTag.SetTitle(data->title); + InfoTag.SetPlotOutline(data->subtitle); + InfoTag.SetPlot(data->description); + InfoTag.SetGenre(data->genre_type, data->genre_sub_type); + InfoTag.SetParentalRating(data->parental_rating); + InfoTag.SetIcon(Epg->m_Channel->Icon()); + InfoTag.m_Epg = Epg; + + return database->UpdateEPGEntry(InfoTag); + } + return false; +} + + +/// ----- cPVREpgs -------------------------------------------------------------------------------- + +cPVREpgs PVREpgs; + +cPVREpgs::cPVREpgs(void) +{ +} + +void cPVREpgs::Cleanup() +{ + CLog::Log(LOGINFO, "PVR: cleaning up epg data"); + + for (unsigned int i = 0; i < PVRChannelsTV.size(); i++) + { + cPVREpg *Epg = (cPVREpg *) GetEPG(&PVRChannelsTV[i], true); + Epg->Cleanup(CDateTime::GetCurrentDateTime()); + } + + for (unsigned int i = 0; i < PVRChannelsRadio.size(); i++) + { + cPVREpg *Epg = (cPVREpg *) GetEPG(&PVRChannelsRadio[i], true); + Epg->Cleanup(CDateTime::GetCurrentDateTime()); + } +} + +bool cPVREpgs::ClearAll(void) +{ + CSingleLock lock(m_critSection); + + for (unsigned int i = 0; i < PVRTimers.size(); ++i) + PVRTimers[i].SetEpg(NULL); + for (unsigned int i = 0; i < size(); i++) + at(i)->Cleanup(-1); + + return true; +} + +bool cPVREpgs::ClearChannel(long ChannelID) +{ + for (unsigned int i = 0; i < size(); i++) + { + if (ChannelID == at(i)->ChannelID()) + { + CSingleLock lock(m_critSection); + + at(i)->Cleanup(-1); + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + database->Open(); + database->EraseChannelEPG(ChannelID); + database->Close(); + return true; + } + } + + return false; +} + +void cPVREpgs::Load() +{ + long perfCnt = CTimeUtils::GetTimeMS(); + int channelcount = PVRChannelsTV.size() + PVRChannelsRadio.size(); + bool ignoreDB = g_guiSettings.GetBool("pvrepg.ignoredbforclient"); + int lingertime = g_guiSettings.GetInt("pvrmenu.lingertime")*60; + int daysToLoad = g_guiSettings.GetInt("pvrmenu.daystodisplay")*24*60*60; + CLIENTMAP *clients = g_PVRManager.Clients(); + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + bool completeNew = true; + m_bInihibitUpdate = true; + + CLog::Log(LOGINFO, "PVR: Starting EPG load for %i channels", channelcount); + + CGUIDialogPVRUpdateProgressBar *scanner = (CGUIDialogPVRUpdateProgressBar *)g_windowManager.GetWindow(WINDOW_DIALOG_EPG_SCAN); + scanner->Show(); + scanner->SetHeader(g_localizeStrings.Get(19004)); + + database->Open(); + + time_t start; + time_t end; + CDateTime::GetCurrentDateTime().GetAsTime(start); // NOTE: XBMC stores the EPG times as local time + CDateTime::GetCurrentDateTime().GetAsTime(end); + start -= lingertime; + end += daysToLoad; + + for (unsigned int i = 0; i < PVRChannelsTV.size(); i++) + { + CSingleLock lock(m_critSection); + + if (PVRChannelsTV[i].GrabEpg()) + { + scanner->SetProgress(i, channelcount); + scanner->SetTitle(PVRChannelsTV[i].Name()); + scanner->UpdateState(); + cPVREpg *p = AddEPG(PVRChannelsTV[i].ChannelID()); + if (p) + { + bool ret = database->GetEPGForChannel(PVRChannelsTV[i], p, start, end); + if (ret) + { + completeNew = false; + } + else + { + // If "ret" is false the database contains no EPG for this channel, load it now from client or over + // scrapers for only one day to speed up load, rest is done by Background Update + CDateTime::GetUTCDateTime().GetAsTime(start); // NOTE: Client EPG times are must be UTC based + CDateTime::GetUTCDateTime().GetAsTime(end); + start -= lingertime; + if (ignoreDB) + end += daysToLoad; + else + end += 24*60*60; // One day + + if (PVRChannelsTV[i].Grabber() == "client" && g_PVRManager.GetClientProps(PVRChannelsTV[i].ClientID())->SupportEPG && clients->find(PVRChannelsTV[i].ClientID())->second->ReadyToUse()) + { + ret = clients->find(PVRChannelsTV[i].ClientID())->second->GetEPGForChannel(PVRChannelsTV[i], p, start, end) == PVR_ERROR_NO_ERROR ? true : false; + if (ignoreDB) + ret = false; // This prevent the save of the loaded data inside the Database + else if (p->InfoTags()->size() > 0) + CLog::Log(LOGINFO, "PVR: TV Database contains no EPG data for TV Channel '%s', loaded from client '%li'", PVRChannelsTV[i].Name().c_str(), PVRChannelsTV[i].ClientID()); + else + CLog::Log(LOGDEBUG, "PVR: TV Channel '%s', on client '%li' contains no EPG data", PVRChannelsTV[i].Name().c_str(), PVRChannelsTV[i].ClientID()); + } + else + { + if (PVRChannelsTV[i].Grabber().IsEmpty()) + { + CLog::Log(LOGERROR, "PVR: No EPG grabber defined for TV Channel '%s'", PVRChannelsTV[i].Name().c_str()); + return; + } + CLog::Log(LOGINFO, "PVR: TV Database contains no EPG data for TV Channel '%s', loading with scraper '%s'", PVRChannelsTV[i].Name().c_str(), PVRChannelsTV[i].Grabber().c_str()); + /// TODO: Add Support for Web EPG Scrapers here + } + + // Store the loaded EPG entries inside Database + if (ret) + { + for (unsigned int i = 0; i < p->InfoTags()->size(); i++) + database->AddEPGEntry(*p->InfoTags()->at(i), false, (i==0), (i >= p->InfoTags()->size()-1)); + } + } + + // Initialize the channels' schedule pointers, so that the first WhatsOn menu will come up faster: + GetEPG(&PVRChannelsTV[i]); + } + } + } + + for (unsigned int i = 0; i < PVRChannelsRadio.size(); i++) + { + CSingleLock lock(m_critSection); + + if (PVRChannelsRadio[i].GrabEpg()) + { + scanner->SetProgress(PVRChannelsTV.size()+i, channelcount); + scanner->SetTitle(PVRChannelsRadio[i].Name()); + scanner->UpdateState(); + cPVREpg *p = AddEPG(PVRChannelsRadio[i].ChannelID()); + if (p) + { + bool ret = database->GetEPGForChannel(PVRChannelsRadio[i], p, start, end); + if (ret) + { + completeNew = false; + } + else + { + // If "ret" is false the database contains no EPG for this channel, load it now from client or over + // scrapers for only one day to speed up load + CDateTime::GetUTCDateTime().GetAsTime(start); // NOTE: Client EPG times are must be UTC based + CDateTime::GetUTCDateTime().GetAsTime(end); + start -= lingertime; + if (ignoreDB) + end += daysToLoad; + else + end += 24*60*60; // One day + + if (PVRChannelsRadio[i].Grabber() == "client" && g_PVRManager.GetClientProps(PVRChannelsRadio[i].ClientID())->SupportEPG && clients->find(PVRChannelsRadio[i].ClientID())->second->ReadyToUse()) + { + ret = clients->find(PVRChannelsRadio[i].ClientID())->second->GetEPGForChannel(PVRChannelsRadio[i], p, start, end) == PVR_ERROR_NO_ERROR ? true : false; + if (ignoreDB) + ret = false; // This prevent the save of the loaded data inside the Database + else if (p->InfoTags()->size() > 0) + CLog::Log(LOGINFO, "PVR: TV Database contains no EPG data for Radio Channel '%s', loaded from client '%li'", PVRChannelsRadio[i].Name().c_str(), PVRChannelsRadio[i].ClientID()); + else + CLog::Log(LOGDEBUG, "PVR: Radio Channel '%s', on client '%li' contains no EPG data", PVRChannelsRadio[i].Name().c_str(), PVRChannelsRadio[i].ClientID()); + } + else + { + if (PVRChannelsRadio[i].Grabber().IsEmpty()) + { + CLog::Log(LOGERROR, "PVR: No EPG grabber defined for Radio Channel '%s'", PVRChannelsRadio[i].Name().c_str()); + return; + } + CLog::Log(LOGINFO, "PVR: TV Database contains no EPG data for Radio Channel '%s', loading with scraper '%s'", PVRChannelsRadio[i].Name().c_str(), PVRChannelsRadio[i].Grabber().c_str()); + /// TODO: Add Support for Web EPG Scrapers here + } + + // Store the loaded EPG entries inside Database + if (ret) + { + for (unsigned int i = 0; i < p->InfoTags()->size(); i++) + database->AddEPGEntry(*p->InfoTags()->at(i), false, (i==0), (i >= p->InfoTags()->size()-1)); + } + } + + // Initialize the channels' schedule pointers, so that the first WhatsOn menu will come up faster: + GetEPG(&PVRChannelsRadio[i]); + } + } + } + + if (completeNew) + database->UpdateLastEPGScan(CDateTime::GetCurrentDateTime()); + + database->Close(); + scanner->Close(); + m_bInihibitUpdate = false; + + CLog::Log(LOGINFO, "PVR: EPG load finished after %li.%li seconds", (CTimeUtils::GetTimeMS()-perfCnt)/1000, (CTimeUtils::GetTimeMS()-perfCnt)%1000); + + return; +} + +void cPVREpgs::Unload() +{ + CSingleLock lock(m_critSection); + + CLog::Log(LOGINFO, "PVR: Unloading EPG information"); + + m_bInihibitUpdate = true; + + for (unsigned int i = 0; i < size(); i++) + { + cPVREpg *p = at(i); + if (p) + { + for (unsigned int j = 0; j < p->m_tags.size(); j++) + { + delete p->m_tags.at(j); + } + p->m_tags.clear(); + } + delete p; + erase(begin()+i); + } + + for (unsigned int i = 0; i < PVRTimers.size(); ++i) + PVRTimers[i].SetEpg(NULL); +} + +void cPVREpgs::Update(bool Scan) +{ + long perfCnt = CTimeUtils::GetTimeMS(); + int channelcount = PVRChannelsTV.size() + PVRChannelsRadio.size(); + bool ignoreDB = g_guiSettings.GetBool("pvrepg.ignoredbforclient"); + int lingertime = g_guiSettings.GetInt("pvrmenu.lingertime")*60; + int daysToLoad = g_guiSettings.GetInt("pvrmenu.daystodisplay")*24*60*60; + + if (m_bInihibitUpdate) + return; + + if (Scan) + CLog::Log(LOGNOTICE, "PVR: Starting EPG scan for %i channels", channelcount); + else + CLog::Log(LOGNOTICE, "PVR: Starting EPG update for %i channels", channelcount); + + time_t endLoad; + CDateTime::GetCurrentDateTime().GetAsTime(endLoad); + endLoad += daysToLoad; + + CLIENTMAP *clients = g_PVRManager.Clients(); + CTVDatabase *database = g_PVRManager.GetTVDatabase(); + + database->Open(); + database->EraseOldEPG(); + + for (unsigned int i = 0; i < PVRChannelsTV.size(); i++) + { + if (m_bInihibitUpdate) + return; + + CSingleLock lock(m_critSection); + + if (PVRChannelsTV[i].GrabEpg()) + { + cPVREpg *p = AddEPG(PVRChannelsTV[i].ChannelID()); + if (p) + { + bool ret = false; + + p->SetUpdate(true); + + if (Scan) + { + time_t start; + time_t end; + CDateTime::GetUTCDateTime().GetAsTime(start); // NOTE: Client EPG times must be UTC based + CDateTime::GetUTCDateTime().GetAsTime(end); + + start -= lingertime; + end += daysToLoad; + + if (PVRChannelsTV[i].Grabber() == "client" && g_PVRManager.GetClientProps(PVRChannelsTV[i].ClientID())->SupportEPG && clients->find(PVRChannelsTV[i].ClientID())->second->ReadyToUse()) + { + ret = clients->find(PVRChannelsTV[i].ClientID())->second->GetEPGForChannel(PVRChannelsTV[i], p, start, end) == PVR_ERROR_NO_ERROR ? true : false; + if (ignoreDB) + ret = false; // This prevent the save of the loaded data inside the Database + } + else + { + if (PVRChannelsTV[i].Grabber().IsEmpty()) + { + CLog::Log(LOGERROR, "PVR: No EPG grabber defined for TV Channel '%s'", PVRChannelsTV[i].Name().c_str()); + return; + } + /// TODO: Add Support for Web EPG Scrapers here + ret = false; + } + + if (ret) + { + for (unsigned int j = 0; j < p->InfoTags()->size(); j++) + database->UpdateEPGEntry(*p->InfoTags()->at(j), false, (j==0), (j >= p->InfoTags()->size()-1)); + + PVRChannelsTV[i].ResetChannelEPGLinks(); + } + if (p->InfoTags()->size() > 0) + CLog::Log(LOGINFO, "PVR: Refreshed '%i' EPG events for TV Channel '%s' during Scan", (int)p->InfoTags()->size(), PVRChannelsTV[i].Name().c_str()); + } + else + { + time_t DataLastTime; + unsigned int cntEntries = p->InfoTags()->size(); + + if (PVRChannelsTV[i].Grabber() == "client" && g_PVRManager.GetClientProps(PVRChannelsTV[i].ClientID())->SupportEPG && clients->find(PVRChannelsTV[i].ClientID())->second->ReadyToUse()) + { + if (ignoreDB) + { + p->GetLastEPGDate().GetAsTime(DataLastTime); + clients->find(PVRChannelsTV[i].ClientID())->second->GetEPGForChannel(PVRChannelsTV[i], p, DataLastTime, endLoad); + ret = false; // This prevent the save of the loaded data inside the Database + } + else + { + database->GetEPGDataEnd(PVRChannelsTV[i].ChannelID()).GetAsTime(DataLastTime); + ret = clients->find(PVRChannelsTV[i].ClientID())->second->GetEPGForChannel(PVRChannelsTV[i], p, DataLastTime, 0, true) == PVR_ERROR_NO_ERROR ? true : false; + } + } + else + { + if (PVRChannelsTV[i].Grabber().IsEmpty()) + { + CLog::Log(LOGERROR, "PVR: No EPG grabber defined for TV Channel '%s'", PVRChannelsTV[i].Name().c_str()); + return; + } + /// TODO: Add Support for Web EPG Scrapers here + ret = false; + } + + if (ret) + { + p->GetLastEPGDate().GetAsTime(DataLastTime); + database->GetEPGForChannel(PVRChannelsTV[i], p, DataLastTime, endLoad); + PVRChannelsTV[i].ResetChannelEPGLinks(); + } + if (p->InfoTags()->size()-cntEntries > 0) + CLog::Log(LOGINFO, "PVR: Added '%lu' EPG events for TV Channel '%s' during Update", p->InfoTags()->size()-cntEntries, PVRChannelsTV[i].Name().c_str()); + } + + /// This will check all programs in the list and + /// will remove any overlapping programs + /// An overlapping program is a tv program which overlaps with another tv program in time + /// for example. + /// program A on MTV runs from 20.00-21.00 on 1 november 2004 + /// program B on MTV runs from 20.55-22.00 on 1 november 2004 + /// this case, program B will be removed + p->Sort(); + CStdString previousName = ""; + CDateTime previousStart; + CDateTime previousEnd(1980, 1, 1, 0, 0, 0); + for (unsigned int j = 0; j < p->InfoTags()->size(); j++) + { + if (previousEnd > p->InfoTags()->at(j)->Start()) + { + //remove this program + CLog::Log(LOGNOTICE, "PVR: Removing Overlapped TV Event '%s' on channel '%s' at date '%s' to '%s'", + p->InfoTags()->at(j)->Title().c_str(), + p->InfoTags()->at(j)->ChannelName().c_str(), + p->InfoTags()->at(j)->Start().GetAsLocalizedDateTime(false, false).c_str(), + p->InfoTags()->at(j)->End().GetAsLocalizedDateTime(false, false).c_str()); + CLog::Log(LOGNOTICE, " Overlapps with '%s' at date '%s' to '%s'", + previousName.c_str(), + previousStart.GetAsLocalizedDateTime(false, false).c_str(), + previousEnd.GetAsLocalizedDateTime(false, false).c_str()); + database->RemoveEPGEntry(*p->InfoTags()->at(j)); + p->DelInfoTag(p->InfoTags()->at(j)); + } + else + { + previousName = p->InfoTags()->at(j)->Title(); + previousStart = p->InfoTags()->at(j)->Start(); + previousEnd = p->InfoTags()->at(j)->End(); + } + } + } + p->SetUpdate(false); + } + } + + for (unsigned int i = 0; i < PVRChannelsRadio.size(); i++) + { + if (m_bInihibitUpdate) + return; + + CSingleLock lock(m_critSection); + + if (PVRChannelsRadio[i].GrabEpg()) + { + cPVREpg *p = AddEPG(PVRChannelsRadio[i].ChannelID()); + if (p) + { + bool ret = false; + + p->SetUpdate(true); + + if (Scan) + { + time_t start; + time_t end; + CDateTime::GetUTCDateTime().GetAsTime(start); // NOTE: Client EPG times must be UTC based + CDateTime::GetUTCDateTime().GetAsTime(end); + start -= lingertime; + end += daysToLoad; + + if (PVRChannelsRadio[i].Grabber() == "client" && g_PVRManager.GetClientProps(PVRChannelsRadio[i].ClientID())->SupportEPG && clients->find(PVRChannelsRadio[i].ClientID())->second->ReadyToUse()) + { + ret = clients->find(PVRChannelsRadio[i].ClientID())->second->GetEPGForChannel(PVRChannelsRadio[i], p, start, end) == PVR_ERROR_NO_ERROR ? true : false; + if (ignoreDB) + ret = false; // This prevent the save of the loaded data inside the Database + } + else + { + if (PVRChannelsRadio[i].Grabber().IsEmpty()) + { + CLog::Log(LOGERROR, "PVR: No EPG grabber defined for Radio Channel '%s'", PVRChannelsRadio[i].Name().c_str()); + return; + } + /// TODO: Add Support for Web EPG Scrapers here + ret = false; + } + + if (ret) + { + for (unsigned int j = 0; j < p->InfoTags()->size(); j++) + database->UpdateEPGEntry(*p->InfoTags()->at(j), false, (j==0), (j >= p->InfoTags()->size()-1)); + + PVRChannelsRadio[i].ResetChannelEPGLinks(); + } + if (p->InfoTags()->size() > 0) + CLog::Log(LOGINFO, "PVR: Refreshed '%i' EPG events for Radio Channel '%s' during Scan", (int)p->InfoTags()->size(), PVRChannelsRadio[i].Name().c_str()); + } + else + { + time_t DataLastTime; + unsigned int cntEntries = p->InfoTags()->size(); + + if (PVRChannelsRadio[i].Grabber() == "client" && g_PVRManager.GetClientProps(PVRChannelsRadio[i].ClientID())->SupportEPG && clients->find(PVRChannelsRadio[i].ClientID())->second->ReadyToUse()) + { + if (ignoreDB) + { + p->GetLastEPGDate().GetAsTime(DataLastTime); + clients->find(PVRChannelsRadio[i].ClientID())->second->GetEPGForChannel(PVRChannelsRadio[i], p, DataLastTime, endLoad); + ret = false; // This prevent the save of the loaded data inside the Database + } + else + { + database->GetEPGDataEnd(PVRChannelsRadio[i].ChannelID()).GetAsTime(DataLastTime); + ret = clients->find(PVRChannelsRadio[i].ClientID())->second->GetEPGForChannel(PVRChannelsRadio[i], p, DataLastTime, 0, true) == PVR_ERROR_NO_ERROR ? true : false; + } + } + else + { + if (PVRChannelsRadio[i].Grabber().IsEmpty()) + { + CLog::Log(LOGERROR, "PVR: No EPG grabber defined for Radio Channel '%s'", PVRChannelsRadio[i].Name().c_str()); + return; + } + /// TODO: Add Support for Web EPG Scrapers here + ret = false; + } + + if (ret) + { + p->GetLastEPGDate().GetAsTime(DataLastTime); + database->GetEPGForChannel(PVRChannelsRadio[i], p, DataLastTime, endLoad); + PVRChannelsRadio[i].ResetChannelEPGLinks(); + } + + if (p->InfoTags()->size()-cntEntries > 0) + CLog::Log(LOGINFO, "PVR: Added '%lu' EPG events for Radio Channel '%s' during Update", p->InfoTags()->size()-cntEntries, PVRChannelsRadio[i].Name().c_str()); + } + + /// This will check all programs in the list and + /// will remove any overlapping programs + /// An overlapping program is a tv program which overlaps with another tv program in time + /// for example. + /// program A on MTV runs from 20.00-21.00 on 1 november 2004 + /// program B on MTV runs from 20.55-22.00 on 1 november 2004 + /// this case, program B will be removed + p->Sort(); + CStdString previousName = ""; + CDateTime previousStart; + CDateTime previousEnd(1980, 1, 1, 0, 0, 0); + for (unsigned int j = 0; j < p->InfoTags()->size(); j++) + { + if (previousEnd > p->InfoTags()->at(j)->Start()) + { + //remove this program + CLog::Log(LOGNOTICE, "PVR: Removing Overlapped Radio Event '%s' on channel '%s' at date '%s' to '%s'", + p->InfoTags()->at(j)->Title().c_str(), + p->InfoTags()->at(j)->ChannelName().c_str(), + p->InfoTags()->at(j)->Start().GetAsLocalizedDateTime(false, false).c_str(), + p->InfoTags()->at(j)->End().GetAsLocalizedDateTime(false, false).c_str()); + CLog::Log(LOGNOTICE, " Overlapps with '%s' at date '%s' to '%s'", + previousName.c_str(), + previousStart.GetAsLocalizedDateTime(false, false).c_str(), + previousEnd.GetAsLocalizedDateTime(false, false).c_str()); + database->RemoveEPGEntry(*p->InfoTags()->at(j)); + p->DelInfoTag(p->InfoTags()->at(j)); + } + else + { + previousName = p->InfoTags()->at(j)->Title(); + previousStart = p->InfoTags()->at(j)->Start(); + previousEnd = p->InfoTags()->at(j)->End(); + } + } + } + p->SetUpdate(false); + } + } + + if (Scan) + database->UpdateLastEPGScan(CDateTime::GetCurrentDateTime()); + database->Close(); + CLog::Log(LOGNOTICE, "PVR: EPG update finished after %li.%li seconds", (CTimeUtils::GetTimeMS()-perfCnt)/1000, (CTimeUtils::GetTimeMS()-perfCnt)%1000); + + return; +} + +int cPVREpgs::GetEPGAll(CFileItemList* results, bool radio) +{ + cPVRChannels *ch = !radio ? &PVRChannelsTV : &PVRChannelsRadio; + + for (unsigned int i = 0; i < ch->size(); i++) + { + if (ch->at(i).m_hide || !ch->at(i).m_grabEpg) + continue; + + const cPVREpg *Epg = GetEPG(&ch->at(i), false); + if (Epg && !Epg->IsUpdateRunning() && Epg->IsValid()) + { + const vector *ch_epg = Epg->InfoTags(); + for (unsigned int i = 0; i < ch_epg->size(); i++) + { + CFileItemPtr channel(new CFileItem(*ch_epg->at(i))); + results->Add(channel); + } + } + } + SetVariableData(results); + + return results->Size(); +} + +int cPVREpgs::GetEPGSearch(CFileItemList* results, const EPGSearchFilter &filter) +{ + for (unsigned int i = 0; i < PVRChannelsTV.size(); i++) + { + if (PVRChannelsTV[i].m_hide || !PVRChannelsTV[i].m_grabEpg) + continue; + + const cPVREpg *Epg = GetEPG(&PVRChannelsTV[i], false); + if (Epg && !Epg->IsUpdateRunning() && Epg->IsValid()) + { + const vector *ch_epg = Epg->InfoTags(); + + for (unsigned int i = 0; i < ch_epg->size(); i++) + { + if (filter.FilterEntry(*ch_epg->at(i))) + { + CFileItemPtr channel(new CFileItem(*ch_epg->at(i))); + results->Add(channel); + } + } + } + } + + for (unsigned int i = 0; i < PVRChannelsRadio.size(); i++) + { + if (PVRChannelsRadio[i].m_hide || !PVRChannelsRadio[i].m_grabEpg) + continue; + + const cPVREpg *Epg = GetEPG(&PVRChannelsRadio[i], false); + if (Epg && !Epg->IsUpdateRunning() && Epg->IsValid()) + { + const vector *ch_epg = Epg->InfoTags(); + + for (unsigned int i = 0; i < ch_epg->size(); i++) + { + if (filter.FilterEntry(*ch_epg->at(i))) + { + CFileItemPtr channel(new CFileItem(*ch_epg->at(i))); + results->Add(channel); + } + } + } + } + + if (filter.m_IgnPresentRecords && PVRRecordings.size() > 0) + { + for (unsigned int i = 0; i < PVRRecordings.size(); i++) + { + for (int j = 0; j < results->Size(); j++) + { + const cPVREPGInfoTag *epgentry = results->Get(j)->GetEPGInfoTag(); + if (epgentry) + { + if (epgentry->Title() != PVRRecordings[i].Title()) + continue; + if (epgentry->PlotOutline() != PVRRecordings[i].PlotOutline()) + continue; + if (epgentry->Plot() != PVRRecordings[i].Plot()) + continue; + + results->Remove(j); + j--; + } + } + } + } + + if (filter.m_IgnPresentTimers && PVRTimers.size() > 0) + { + for (unsigned int i = 0; i < PVRTimers.size(); i++) + { + for (int j = 0; j < results->Size(); j++) + { + const cPVREPGInfoTag *epgentry = results->Get(j)->GetEPGInfoTag(); + if (epgentry) + { + if (epgentry->ChannelNumber() != PVRTimers[i].Number()) + continue; + if (epgentry->Start() < PVRTimers[i].Start()) + continue; + if (epgentry->End() > PVRTimers[i].Stop()) + continue; + + results->Remove(j); + j--; + } + } + } + } + + if (filter.m_PreventRepeats) + { + unsigned int size = results->Size(); + for (unsigned int i = 0; i < size; i++) + { + const cPVREPGInfoTag *epgentry_1 = results->Get(i)->GetEPGInfoTag(); + for (unsigned int j = 0; j < size; j++) + { + const cPVREPGInfoTag *epgentry_2 = results->Get(j)->GetEPGInfoTag(); + if (i == j) + continue; + + if (epgentry_1->Title() != epgentry_2->Title()) + continue; + + if (epgentry_1->Plot() != epgentry_2->Plot()) + continue; + + if (epgentry_1->PlotOutline() != epgentry_2->PlotOutline()) + continue; + + results->Remove(j); + size = results->Size(); + i--; + j--; + } + } + } + + SetVariableData(results); + + return results->Size(); +} + +int cPVREpgs::GetEPGChannel(unsigned int number, CFileItemList* results, bool radio) +{ + cPVRChannels *ch = !radio ? &PVRChannelsTV : &PVRChannelsRadio; + + if (!ch->at(number-1).m_grabEpg) + return 0; + + const cPVREpg *Epg = GetEPG(&ch->at(number-1), true); + if (Epg && !Epg->IsUpdateRunning() && Epg->IsValid()) + { + const vector *ch_epg = Epg->InfoTags(); + for (unsigned int i = 0; i < ch_epg->size(); i++) + { + CFileItemPtr channel(new CFileItem(*ch_epg->at(i))); + channel->SetLabel2(ch_epg->at(i)->Start().GetAsLocalizedDateTime(false, false)); + results->Add(channel); + } + SetVariableData(results); + } + + return results->Size(); +} + +int cPVREpgs::GetEPGNow(CFileItemList* results, bool radio) +{ + cPVRChannels *ch = !radio ? &PVRChannelsTV : &PVRChannelsRadio; + + for (unsigned int i = 0; i < ch->size(); i++) + { + if (ch->at(i).m_hide || !ch->at(i).m_grabEpg) + continue; + + const cPVREpg *Epg = GetEPG(&ch->at(i), true); + if (!Epg || Epg->IsUpdateRunning() && Epg->IsValid()) + continue; + + const cPVREPGInfoTag *epgnow = Epg->GetInfoTagNow(); + if (!epgnow) + continue; + + CFileItemPtr entry(new CFileItem(*epgnow)); + entry->SetLabel2(epgnow->Start().GetAsLocalizedTime("", false)); + entry->m_strPath = ch->at(i).Name(); + entry->SetThumbnailImage(ch->at(i).Icon()); + results->Add(entry); + } + SetVariableData(results); + + return results->Size(); +} + +int cPVREpgs::GetEPGNext(CFileItemList* results, bool radio) +{ + cPVRChannels *ch = !radio ? &PVRChannelsTV : &PVRChannelsRadio; + + for (unsigned int i = 0; i < ch->size(); i++) + { + if (ch->at(i).m_hide || !ch->at(i).GrabEpg()) + continue; + + const cPVREpg *Epg = GetEPG(&ch->at(i), true); + if (!Epg || Epg->IsUpdateRunning() && Epg->IsValid()) + continue; + + const cPVREPGInfoTag *epgnext = Epg->GetInfoTagNext(); + if (!epgnext) + continue; + + CFileItemPtr entry(new CFileItem(*epgnext)); + entry->SetLabel2(epgnext->Start().GetAsLocalizedTime("", false)); + entry->m_strPath = ch->at(i).Name(); + entry->SetThumbnailImage(ch->at(i).Icon()); + results->Add(entry); + } + SetVariableData(results); + + return results->Size(); +} + +cPVREpg *cPVREpgs::AddEPG(long ChannelID) +{ + cPVREpg *p = (cPVREpg *)GetEPG(ChannelID); + if (!p) + { + p = new cPVREpg(ChannelID); + Add(p); + cPVRChannelInfoTag *channel = cPVRChannels::GetByChannelIDFromAll(ChannelID); + if (channel) + channel->m_Epg = p; + } + return p; +} + +const cPVREpg *cPVREpgs::GetEPG(long ChannelID) const +{ + for (unsigned int i = 0; i < size(); i++) + { + if (at(i)->ChannelID() == ChannelID) + return at(i); + } + return NULL; +} + +const cPVREpg *cPVREpgs::GetEPG(const cPVRChannelInfoTag *Channel, bool AddIfMissing) const +{ + // This is not very beautiful, but it dramatically speeds up the + // "What's on now/next?" menus. + static cPVREpg DummyEPG(-1); + + if (!Channel->m_Epg) + Channel->m_Epg = GetEPG(Channel->ChannelID()); + + if (!Channel->m_Epg) + Channel->m_Epg = &DummyEPG; + + if (Channel->m_Epg == &DummyEPG && AddIfMissing) + { + cPVREpg *Epg = new cPVREpg(Channel->ChannelID()); + ((cPVREpgs *)this)->Add(Epg); + Channel->m_Epg = Epg; + } + return Channel->m_Epg != &DummyEPG ? Channel->m_Epg: NULL; +} + +CDateTime cPVREpgs::GetFirstEPGDate(bool radio/* = false*/) +{ + CDateTime first = CDateTime::GetCurrentDateTime(); + cPVRChannels *ch = !radio ? &PVRChannelsTV : &PVRChannelsRadio; + + for (unsigned int i = 0; i < ch->size(); i++) + { + if (ch->at(i).m_hide || !ch->at(i).GrabEpg()) + continue; + + const cPVREpg *Epg = GetEPG(&ch->at(i), true); + if (Epg->IsUpdateRunning()) + continue; + + const vector *ch_epg = Epg->InfoTags(); + + for (unsigned int j = 0; j < ch_epg->size(); j++) + { + if (ch_epg->at(j)->Start() < first) + first = ch_epg->at(j)->Start(); + } + } + + return first; +} + +CDateTime cPVREpgs::GetLastEPGDate(bool radio/* = false*/) +{ + CDateTime last = CDateTime::GetCurrentDateTime(); + cPVRChannels *ch = !radio ? &PVRChannelsTV : &PVRChannelsRadio; + + for (unsigned int i = 0; i < ch->size(); i++) + { + if (ch->at(i).m_hide) + continue; + + const cPVREpg *Epg = GetEPG(&ch->at(i), true); + if (Epg->IsUpdateRunning()) + continue; + + const vector *ch_epg = Epg->InfoTags(); + + for (unsigned int j = 0; j < ch_epg->size(); j++) + { + if (ch_epg->at(j)->End() >= last) + last = ch_epg->at(j)->End(); + } + } + return last; +} + +void cPVREpgs::SetVariableData(CFileItemList* results) +{ + /* Reload Timers */ + PVRTimers.Update(); + + /* Clear first all Timers set inside the EPG tags */ + for (int j = 0; j < results->Size(); j++) + { + cPVREPGInfoTag *epg = results->Get(j)->GetEPGInfoTag(); + if (epg) + epg->SetTimer(NULL); + } + + /* Now go with the timers thru the EPG and set the Timer Tag for every matching item */ + for (unsigned int i = 0; i < PVRTimers.size(); i++) + { + for (int j = 0; j < results->Size(); j++) + { + cPVREPGInfoTag *epg = results->Get(j)->GetEPGInfoTag(); + if (epg) + { + if (!PVRTimers[i].Active()) + continue; + + if (epg->ChannelNumber() != PVRTimers[i].Number()) + continue; + + CDateTime timeAround = CDateTime(time_t((PVRTimers[i].StopTime() - PVRTimers[i].StartTime())/2 + PVRTimers[i].StartTime())); + if ((epg->Start() <= timeAround) && (epg->End() >= timeAround)) + { + epg->SetTimer(&PVRTimers[i]); + break; + } + } + } + } +} + +void cPVREpgs::AssignChangedChannelTags(bool radio/* = false*/) +{ + CSingleLock lock(m_critSection); + + cPVRChannels *ch = !radio ? &PVRChannelsTV : &PVRChannelsRadio; + for (unsigned int i = 0; i < ch->size(); i++) + { + cPVREpg *Epg = (cPVREpg *) GetEPG(&ch->at(i), true); + Epg->m_Channel = cPVRChannels::GetByChannelIDFromAll(Epg->ChannelID()); + } +} + +void cPVREpgs::Add(cPVREpg *entry) +{ + push_back(entry); +} + diff --git a/xbmc/utils/PVREpg.h b/xbmc/utils/PVREpg.h new file mode 100644 index 0000000000..f969b2f32a --- /dev/null +++ b/xbmc/utils/PVREpg.h @@ -0,0 +1,222 @@ +#pragma once +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "DateTime.h" +#include "utils/Thread.h" +#include "utils/PVRChannels.h" +#include "../addons/include/xbmc_pvr_types.h" + +class cPVREPGInfoTag; +class cPVRChannelInfoTag; + +/* Filter data to check with a EPGEntry */ +struct EPGSearchFilter +{ + void SetDefaults(); + bool FilterEntry(const cPVREPGInfoTag &tag) const; + + CStdString m_SearchString; + bool m_CaseSensitive; + bool m_SearchDescription; + int m_GenreType; + int m_GenreSubType; + int m_minDuration; + int m_maxDuration; + SYSTEMTIME m_startTime; + SYSTEMTIME m_endTime; + SYSTEMTIME m_startDate; + SYSTEMTIME m_endDate; + int m_ChannelNumber; + bool m_FTAOnly; + bool m_IncUnknGenres; + int m_Group; + bool m_IgnPresentTimers; + bool m_IgnPresentRecords; + bool m_PreventRepeats; +}; + + +class cPVREpg +{ + friend class cPVREpgs; + +private: + long m_channelID; + const cPVRChannelInfoTag *m_Channel; + std::vector m_tags; + bool m_bUpdateRunning; + bool m_bValid; + +public: + cPVREpg(long ChannelID); + long ChannelID(void) const { return m_channelID; } + bool IsValid(void) const; + const cPVRChannelInfoTag *ChannelTag(void) const { return m_Channel; } + cPVREPGInfoTag *AddInfoTag(cPVREPGInfoTag *Tag); + void DelInfoTag(cPVREPGInfoTag *tag); + void Cleanup(CDateTime Time); + void Cleanup(void); + void Sort(void); + const std::vector *InfoTags(void) const { return &m_tags; } + const cPVREPGInfoTag *GetInfoTagNow(void) const; + const cPVREPGInfoTag *GetInfoTagNext(void) const; + const cPVREPGInfoTag *GetInfoTag(long uniqueID, CDateTime StartTime) const; + const cPVREPGInfoTag *GetInfoTagAround(CDateTime Time) const; + CDateTime GetLastEPGDate(); + bool IsUpdateRunning() const { return m_bUpdateRunning; } + void SetUpdate(bool OnOff) { m_bUpdateRunning = OnOff; } + + static bool Add(const PVR_PROGINFO *data, cPVREpg *Epg); + static bool AddDB(const PVR_PROGINFO *data, cPVREpg *Epg); +}; + + +class cPVREPGInfoTag +{ + friend class cPVREpg; +private: + cPVREpg *m_Epg; // The Schedule this event belongs to + const cPVRTimerInfoTag *m_Timer; + + CStdString m_strTitle; + CStdString m_strPlotOutline; + CStdString m_strPlot; + CStdString m_strGenre; + CDateTime m_startTime; + CDateTime m_endTime; + CDateTimeSpan m_duration; + CStdString m_IconPath; + CStdString m_strFileNameAndPath; + int m_GenreType; + int m_GenreSubType; + bool m_isRecording; + CDateTime m_firstAired; + int m_parentalRating; + int m_starRating; + bool m_notify; + CStdString m_seriesNum; + CStdString m_episodeNum; + CStdString m_episodePart; + CStdString m_episodeName; + + long m_uniqueBroadcastID; // event's unique identifier for this tag + +public: + cPVREPGInfoTag(long uniqueBroadcastID); + cPVREPGInfoTag() { Reset(); }; + void Reset(); + + long GetUniqueBroadcastID(void) const { return m_uniqueBroadcastID; } + CDateTime Start(void) const { return m_startTime; } + void SetStart(CDateTime Start) { m_startTime = Start; } + CDateTime End(void) const { return m_endTime; } + void SetEnd(CDateTime Stop) { m_endTime = Stop; } + int GetDuration() const; + CStdString Title(void) const { return m_strTitle; } + void SetTitle(CStdString name) { m_strTitle = name; } + CStdString PlotOutline(void) const { return m_strPlotOutline; } + void SetPlotOutline(CStdString PlotOutline) { m_strPlotOutline = PlotOutline; } + CStdString Plot(void) const { return m_strPlot; } + void SetPlot(CStdString Plot) { m_strPlot = Plot; } + int GenreType(void) const { return m_GenreType; } + int GenreSubType(void) const { return m_GenreSubType; } + CStdString Genre(void) const { return m_strGenre; } + void SetGenre(int ID, int subID); + CDateTime FirstAired(void) const { return m_firstAired; } + void SetFirstAired(CDateTime FirstAired) { m_firstAired = FirstAired; } + int ParentalRating(void) const { return m_parentalRating; } + void SetParentalRating(int ParentalRating) { m_parentalRating = ParentalRating; } + int StarRating(void) const { return m_starRating; } + void SetStarRating(int StarRating) { m_starRating = StarRating; } + bool Notify(void) const { return m_notify; } + void SetNotify(bool Notify) { m_notify = Notify; } + CStdString SeriesNum(void) const { return m_seriesNum; } + void SetSeriesNum(CStdString SeriesNum) { m_seriesNum = SeriesNum; } + CStdString EpisodeNum(void) const { return m_episodeNum; } + void SetEpisodeNum(CStdString EpisodeNum) { m_episodeNum = EpisodeNum; } + CStdString EpisodePart(void) const { return m_episodePart; } + void SetEpisodePart(CStdString EpisodePart) { m_episodePart = EpisodePart; } + CStdString EpisodeName(void) const { return m_episodeName; } + void SetEpisodeName(CStdString EpisodeName) { m_episodeName = EpisodeName; } + CStdString Icon(void) const { return m_IconPath; } + void SetIcon(CStdString icon) { m_IconPath = icon; } + CStdString Path(void) const { return m_strFileNameAndPath; } + void SetPath(CStdString Path) { m_strFileNameAndPath = Path; } + bool HasTimer() const; + + /* Channel related Data */ + long ChannelID(void) const { return m_Epg->ChannelTag()->ChannelID(); } + long ChannelNumber(void) const { return m_Epg->ChannelTag()->Number(); } + CStdString ChannelName(void) const { return m_Epg->ChannelTag()->Name(); } + CStdString ChanneIcon(void) const { return m_Epg->ChannelTag()->Icon(); } + long GroupID(void) const { return m_Epg->ChannelTag()->GroupID(); } + bool IsEncrypted(void) const { return m_Epg->ChannelTag()->IsEncrypted(); } + bool IsRadio(void) const { return m_Epg->ChannelTag()->IsRadio(); } + bool ClientID(void) const { return m_Epg->ChannelTag()->ClientID(); } + + /*! \brief Get the cPVRChannelInfoTag class associated to this epg entry + \return the pointer to the info tag + */ + const cPVRChannelInfoTag *ChannelTag(void) const { return m_Epg->ChannelTag(); } + + /* Scheduled recording related Data */ + void SetTimer(const cPVRTimerInfoTag *Timer) { m_Timer = Timer; } + const cPVRTimerInfoTag *Timer(void) const { return m_Timer; } + + CStdString ConvertGenreIdToString(int ID, int subID) const; +}; + +class cPVREpgs : public std::vector +{ + friend class cPVREpg; + +private: + CCriticalSection m_critSection; + bool m_bInihibitUpdate; + +public: + cPVREpgs(void); + + cPVREpg *AddEPG(long ChannelID); + const cPVREpg *GetEPG(long ChannelID) const; + const cPVREpg *GetEPG(const cPVRChannelInfoTag *Channel, bool AddIfMissing = false) const; + void Add(cPVREpg *entry); + + void Cleanup(void); + bool ClearAll(void); + bool ClearChannel(long ChannelID); + void Load(); + void Unload(); + void Update(bool Scan = false); + void InihibitUpdate(bool yesNo) { m_bInihibitUpdate = yesNo; } + int GetEPGSearch(CFileItemList* results, const EPGSearchFilter &filter); + int GetEPGAll(CFileItemList* results, bool radio = false); + int GetEPGChannel(unsigned int number, CFileItemList* results, bool radio = false); + int GetEPGNow(CFileItemList* results, bool radio = false); + int GetEPGNext(CFileItemList* results, bool radio = false); + CDateTime GetFirstEPGDate(bool radio = false); + CDateTime GetLastEPGDate(bool radio = false); + void SetVariableData(CFileItemList* results); + void AssignChangedChannelTags(bool radio = false); +}; + +extern cPVREpgs PVREpgs; diff --git a/xbmc/utils/PVRRecordings.cpp b/xbmc/utils/PVRRecordings.cpp new file mode 100644 index 0000000000..d79637cb96 --- /dev/null +++ b/xbmc/utils/PVRRecordings.cpp @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * DESCRIPTION: + * + * cPVRRecordingInfoTag is part of the XBMC PVR system to support recording entrys, + * stored on a other Backend like VDR or MythTV. + * + * The recording information tag holds data about name, length, recording time + * and so on of recorded stream stored on the backend. + * + * The filename string is used to by the PVRManager and passed to DVDPlayer + * to stream data from the backend to XBMC. + * + * It is a also CVideoInfoTag and some of his variables must be set! + * + */ + +#include "FileItem.h" +#include "PVRRecordings.h" +#include "PVRManager.h" +#include "GUIDialogOK.h" +#include "GUIWindowManager.h" +#include "LocalizeStrings.h" +#include "Util.h" +#include "URL.h" +#include "utils/log.h" +#include "utils/SingleLock.h" + +/** + * Create a blank unmodified recording tag + */ +cPVRRecordingInfoTag::cPVRRecordingInfoTag() +{ + Reset(); +} + +bool cPVRRecordingInfoTag::operator ==(const cPVRRecordingInfoTag& right) const +{ + + if (this == &right) return true; + + return (m_clientIndex == right.m_clientIndex && + m_clientID == right.m_clientID && + m_strChannel == right.m_strChannel && + m_recordingTime == right.m_recordingTime && + m_duration == right.m_duration && + m_strPlotOutline == right.m_strPlotOutline && + m_strPlot == right.m_strPlot && + m_strStreamURL == right.m_strStreamURL && + m_Priority == right.m_Priority && + m_Lifetime == right.m_Lifetime && + m_strDirectory == right.m_strDirectory && + m_strFileNameAndPath == right.m_strFileNameAndPath && + m_strTitle == right.m_strTitle); +} + +bool cPVRRecordingInfoTag::operator !=(const cPVRRecordingInfoTag& right) const +{ + + if (this == &right) return false; + + if (m_clientIndex != right.m_clientIndex) return true; + if (m_clientID != right.m_clientID) return true; + if (m_strChannel != right.m_strChannel) return true; + if (m_recordingTime != right.m_recordingTime) return true; + if (m_duration != right.m_duration) return true; + if (m_strPlotOutline != right.m_strPlotOutline) return true; + if (m_strPlot != right.m_strPlot) return true; + if (m_strStreamURL != right.m_strStreamURL) return true; + if (m_Priority != right.m_Priority) return true; + if (m_Lifetime != right.m_Lifetime) return true; + if (m_strTitle != right.m_strTitle) return true; + if (m_strDirectory != right.m_strDirectory) return true; + if (m_strFileNameAndPath != right.m_strFileNameAndPath) return true; + + return false; +} + +/** + * Initialize blank cPVRRecordingInfoTag + */ +void cPVRRecordingInfoTag::Reset(void) +{ + m_clientIndex = -1; + m_clientID = g_PVRManager.GetFirstClientID(); // Temporary until we support multiple backends + m_strChannel = ""; + m_strDirectory = ""; + m_recordingTime = NULL; + m_strStreamURL = ""; + m_Priority = -1; + m_Lifetime = -1; + m_strFileNameAndPath = ""; + + CVideoInfoTag::Reset(); +} + +int cPVRRecordingInfoTag::GetDuration() const +{ + int duration; + duration = m_duration.GetDays()*60*60*24; + duration += m_duration.GetHours()*60*60; + duration += m_duration.GetMinutes()*60; + duration += m_duration.GetSeconds(); + duration /= 60; + return duration; +} + +bool cPVRRecordingInfoTag::Delete(void) const +{ + try + { + CLIENTMAP *clients = g_PVRManager.Clients(); + + /* and write it to the backend */ + PVR_ERROR err = clients->find(m_clientID)->second->DeleteRecording(*this); + + if (err != PVR_ERROR_NO_ERROR) + throw err; + + PVRRecordings.Update(); + return true; + } + catch (PVR_ERROR err) + { + DisplayError(err); + } + return false; +} + +bool cPVRRecordingInfoTag::Rename(CStdString &newName) const +{ + try + { + CLIENTMAP *clients = g_PVRManager.Clients(); + + /* and write it to the backend */ + PVR_ERROR err = clients->find(m_clientID)->second->RenameRecording(*this, newName); + + if (err != PVR_ERROR_NO_ERROR) + throw err; + + PVRRecordings.Update(); + return true; + } + catch (PVR_ERROR err) + { + DisplayError(err); + } + return false; +} + +void cPVRRecordingInfoTag::DisplayError(PVR_ERROR err) const +{ + if (err == PVR_ERROR_SERVER_ERROR) + CGUIDialogOK::ShowAndGetInput(19033,19111,19110,0); /* print info dialog "Server error!" */ + else if (err == PVR_ERROR_NOT_SYNC) + CGUIDialogOK::ShowAndGetInput(19033,19108,19110,0); /* print info dialog "Recordings not in sync!" */ + else if (err == PVR_ERROR_NOT_DELETED) + CGUIDialogOK::ShowAndGetInput(19033,19068,19110,0); /* print info dialog "Couldn't delete recording!" */ + else + CGUIDialogOK::ShowAndGetInput(19033,19147,19110,0); /* print info dialog "Unknown error!" */ + + return; +} + + +// --- cPVRRecordings --------------------------------------------------------------- + +cPVRRecordings PVRRecordings; + +cPVRRecordings::cPVRRecordings(void) +{ + +} + +void cPVRRecordings::Process() +{ + CSingleLock lock(m_critSection); + CLIENTMAP *clients = g_PVRManager.Clients(); + + Clear(); + + /* Go thru all clients and receive there Recordings */ + CLIENTMAPITR itr = clients->begin(); + int cnt = 0; + while (itr != clients->end()) + { + /* Load only if the client have Recordings */ + if ((*itr).second->GetNumRecordings() > 0) + { + (*itr).second->GetAllRecordings(this); + } + itr++; + cnt++; + } + + for (unsigned int i = 0; i < size(); ++i) + { + CFileItemPtr pFileItem(new CFileItem(at(i))); + + CStdString Path; + CStdString strTitle = at(i).Title(); + strTitle.Replace('/','-'); + + Path.Format("pvr://recordings/client_%04i/", at(i).ClientID()); + if (at(i).Directory() != "") + Path += at(i).Directory(); + + CUtil::AddSlashAtEnd(Path); + Path += strTitle + ".pvr"; + at(i).SetPath(Path); + } + return; +} + +void cPVRRecordings::Unload() +{ + CSingleLock lock(m_critSection); + Clear(); +} + +bool cPVRRecordings::Update(bool Wait) +{ + if (Wait) + { + Process(); + return true; + } + else + { + Create(); + SetName("PVR Recordings Update"); + SetPriority(-5); + } + return false; +} + +int cPVRRecordings::GetNumRecordings() +{ + CSingleLock lock(m_critSection); + return size(); +} + +int cPVRRecordings::GetRecordings(CFileItemList* results) +{ + CSingleLock lock(m_critSection); + for (unsigned int i = 0; i < size(); ++i) + { + CFileItemPtr pFileItem(new CFileItem(at(i))); + results->Add(pFileItem); + } + return size(); +} + +bool cPVRRecordings::DeleteRecording(const CFileItem &item) +{ + /* Check if a cPVRRecordingInfoTag is inside file item */ + if (!item.IsPVRRecording()) + { + CLog::Log(LOGERROR, "cPVRRecordings: DeleteRecording no RecordingInfoTag given!"); + return false; + } + + const cPVRRecordingInfoTag* tag = item.GetPVRRecordingInfoTag(); + return tag->Delete(); +} + +bool cPVRRecordings::RenameRecording(CFileItem &item, CStdString &newname) +{ + /* Check if a cPVRRecordingInfoTag is inside file item */ + if (!item.IsPVRRecording()) + { + CLog::Log(LOGERROR, "cPVRRecordings: RenameRecording no RecordingInfoTag given!"); + return false; + } + + cPVRRecordingInfoTag* tag = item.GetPVRRecordingInfoTag(); + return tag->Rename(newname); +} + +bool cPVRRecordings::RemoveRecording(const CFileItem &item) +{ + CSingleLock lock(m_critSection); + + if (!item.IsPVRRecording()) + { + CLog::Log(LOGERROR, "cPVRRecordings: RemoveRecording no RecordingInfoTag given!"); + return false; + } + + const cPVRRecordingInfoTag* tag = item.GetPVRRecordingInfoTag(); + + for (unsigned int i = 0; i < size(); ++i) + { + if (tag == &at(i)) + { + erase(begin()+i); + return true; + } + } + return false; +} + +bool cPVRRecordings::GetDirectory(const CStdString& strPath, CFileItemList &items) +{ + CSingleLock lock(m_critSection); + + CStdString base(strPath); + CUtil::RemoveSlashAtEnd(base); + + CURL url(strPath); + CStdString fileName = url.GetFileName(); + CUtil::RemoveSlashAtEnd(fileName); + + if (fileName == "recordings") + { +// if (g_PVRManager.Clients()->size() > 1) + { + CLIENTMAPITR itr = g_PVRManager.Clients()->begin(); + while (itr != g_PVRManager.Clients()->end()) + { + CFileItemPtr item; + CStdString dirName; + CStdString clientName; + + int clientID = g_PVRManager.Clients()->find((*itr).first)->second->GetID(); + clientName.Format(g_localizeStrings.Get(19016), clientID, g_PVRManager.Clients()->find((*itr).first)->second->GetBackendName()); + dirName.Format("%s/client_%04i/", base, clientID); + item.reset(new CFileItem(dirName, true)); + item->SetLabel(clientName); + item->SetLabelPreformated(true); + items.Add(item); + + itr++; + } + return true; + } + } + else if (fileName.Left(18) == "recordings/client_") + { + fileName.erase(0,18); + int clientID = atoi(fileName.c_str()); + CStdString curDir = url.GetFileName(); + CUtil::AddSlashAtEnd(curDir); + + CStdString strBuffer; + CStdString strSkip; + std::vector baseTokens; + if (!curDir.IsEmpty()) + CUtil::Tokenize(curDir, baseTokens, "/"); + + for (unsigned int i = 0; i < size(); ++i) + { + if (clientID != at(i).ClientID()) + continue; + + CStdString strEntryName; + CStdString strTitle = at(i).Title(); + strEntryName.Format("recordings/client_%04i/%s", clientID, at(i).Directory()); + CUtil::AddSlashAtEnd(strEntryName); + + strTitle.Replace('/','-'); + strEntryName += strTitle; + strEntryName.Replace('\\','/'); + CStdString strOrginalName = strEntryName; + + if (strEntryName == curDir) // skip the listed dir + continue; + + std::vector pathTokens; + CUtil::Tokenize(strEntryName, pathTokens, "/"); + if (pathTokens.size() < baseTokens.size()+1) + continue; + + bool bAdd = true; + strEntryName = ""; + for (unsigned int j = 0; j < baseTokens.size(); ++j) + { + if (pathTokens[j] != baseTokens[j]) + { + bAdd = false; + break; + } + strEntryName += pathTokens[j] + "/"; + } + if (!bAdd) + continue; + + strEntryName += pathTokens[baseTokens.size()]; + char c=strOrginalName[strEntryName.size()]; + if (c == '/' || c == '\\') + strEntryName += '/'; + + CFileItemPtr pFileItem; + bool bIsFolder = false; + if (strEntryName[strEntryName.size()-1] != '/') // this is a file + { + pFileItem.reset(new CFileItem(at(i))); + pFileItem->SetLabel(pathTokens[baseTokens.size()]); + pFileItem->SetLabel2(at(i).RecordingTime().GetAsLocalizedDateTime(true, false)); + pFileItem->m_dateTime = at(i).RecordingTime(); + pFileItem->m_strPath.Format("pvr://%s-%05i.pvr", strEntryName, at(i).ClientIndex()); + } + else + { // this is new folder. add if not already added + bIsFolder = true; + strBuffer = "pvr://" + strEntryName; + if (items.Contains(strBuffer)) // already added + continue; + + pFileItem.reset(new CFileItem(strBuffer, true)); + pFileItem->SetLabel(pathTokens[baseTokens.size()]); + } + pFileItem->SetLabelPreformated(true); + items.Add(pFileItem); + } + return true; + } + return false; +} + +cPVRRecordingInfoTag *cPVRRecordings::GetByPath(CStdString &path) +{ + CSingleLock lock(m_critSection); + + CURL url(path); + CStdString fileName = url.GetFileName(); + CUtil::RemoveSlashAtEnd(fileName); + + if (fileName.Left(18) == "recordings/client_") + { + fileName.erase(0,18); + int clientID = atoi(fileName.c_str()); + fileName.erase(0,5); + + if (fileName.IsEmpty()) + return NULL; + + CStdString title; + CStdString dir; + size_t found = fileName.find_last_of("/"); + if (found != CStdString::npos) + { + title = fileName.substr(found+1); + dir = fileName.substr(0, found); + } + else + { + title = fileName; + dir = ""; + } + CUtil::RemoveExtension(title); + unsigned int index = atoi(title.substr(title.size()-5).c_str()); + title.erase(title.size()-6); + + for (unsigned int i = 0; i < size(); ++i) + { + if (index > 0) + { + if (index == at(i).ClientIndex()) + return &at(i); + } + else + { + if ((title == at(i).Title()) && (dir == at(i).Directory()) && (clientID == at(i).ClientID())) + return &at(i); + } + } + } + + return NULL; +} + +void cPVRRecordings::Clear() +{ + /* Clear all current present Recordings inside list */ + erase(begin(), end()); + return; +} diff --git a/xbmc/utils/PVRRecordings.h b/xbmc/utils/PVRRecordings.h new file mode 100644 index 0000000000..169559c185 --- /dev/null +++ b/xbmc/utils/PVRRecordings.h @@ -0,0 +1,112 @@ +#pragma once +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * for DESCRIPTION see 'TVRecordInfoTag.cpp' + */ + +#include "VideoInfoTag.h" +#include "PVREpg.h" +#include "settings/VideoSettings.h" +#include "utils/Thread.h" +#include "DateTime.h" +#include "../addons/include/xbmc_pvr_types.h" + +class cPVRRecordingInfoTag : public CVideoInfoTag +{ +private: + int m_clientID; /// ID of the backend + int m_clientIndex; /// Index number of the reecording on the client, given by the backend, -1 for unknown + CStdString m_strChannel; /// Channel name where recording from + CDateTime m_recordingTime; /// Recording start time + CDateTimeSpan m_duration; /// Duration + int m_Priority; + int m_Lifetime; + CStdString m_strStreamURL; ///> Stream URL if empty use pvr client + CStdString m_strDirectory; ///> Directory of this recording on the client + CStdString m_strFileNameAndPath; ///> Filename for PVRManager to open and read stream + + void DisplayError(PVR_ERROR err) const; + +public: + cPVRRecordingInfoTag(); + bool operator ==(const cPVRRecordingInfoTag& right) const; + bool operator !=(const cPVRRecordingInfoTag& right) const; + void Reset(void); + + CStdString Title(void) const { return m_strTitle; } + void SetTitle(CStdString Title) { m_strTitle = Title; } + CStdString Directory(void) const { return m_strDirectory; } + void SetDirectory(CStdString path) { m_strDirectory = path; } + CStdString PlotOutline(void) const { return m_strPlotOutline; } + void SetPlotOutline(CStdString PlotOutline) { m_strPlotOutline = PlotOutline; } + CStdString Plot(void) const { return m_strPlot; } + void SetPlot(CStdString Plot) { m_strPlot = Plot; } + CStdString ChannelName(void) const { return m_strChannel; } + void SetChannelName(CStdString name) { m_strChannel = name; } + CDateTime RecordingTime(void) const { return m_recordingTime; } + void SetRecordingTime(CDateTime time) { m_recordingTime = time; } + int GetDuration() const; + void SetDuration(CDateTimeSpan duration) { m_duration = duration; } + int Lifetime(void) const { return m_Lifetime; } + void SetLifetime(int Lifetime) { m_Lifetime = Lifetime; } + int Priority(void) const { return m_Priority; } + void SetPriority(int Priority) { m_Priority = Priority; } + CStdString Path(void) const { return m_strFileNameAndPath; } + void SetPath(CStdString path) { m_strFileNameAndPath = path; } + + long ClientID(void) const { return m_clientID; } + void SetClientID(int ClientId) { m_clientID = ClientId; } + long ClientIndex(void) const { return m_clientIndex; } + void SetClientIndex(int ClientIndex) { m_clientIndex = ClientIndex; } + CStdString StreamURL(void) const { return m_strStreamURL; } + void SetStreamURL(CStdString stream) { m_strStreamURL = stream; } + + bool Delete(void) const; + bool Rename(CStdString &newName) const; + +}; + + +class cPVRRecordings : public std::vector + , private CThread +{ +private: + CCriticalSection m_critSection; + virtual void Process(); + +public: + cPVRRecordings(void); + bool Load() { return Update(true); } + void Unload(); + bool Update(bool Wait = false); + int GetNumRecordings(); + int GetRecordings(CFileItemList* results); + static bool DeleteRecording(const CFileItem &item); + static bool RenameRecording(CFileItem &item, CStdString &newname); + bool RemoveRecording(const CFileItem &item); + bool GetDirectory(const CStdString& strPath, CFileItemList &items); + cPVRRecordingInfoTag *GetByPath(CStdString &path); + void Clear(); +}; + +extern cPVRRecordings PVRRecordings; diff --git a/xbmc/utils/PVRTimers.cpp b/xbmc/utils/PVRTimers.cpp new file mode 100644 index 0000000000..b725098b0b --- /dev/null +++ b/xbmc/utils/PVRTimers.cpp @@ -0,0 +1,709 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * DESCRIPTION: + * + * cPVRTimerInfoTag is part of the PVRManager to support sheduled recordings. + * + * The timer information tag holds data about current programmed timers for + * the PVRManager. It is possible to create timers directly based upon + * a EPG entry by giving the EPG information tag or as instant timer + * on currently tuned channel, or give a blank tag to modify later. + * + * With exception of the blank one, the tag can easily and unmodified added + * by the PVRManager function "bool AddTimer(const CFileItem &item)" to + * the backend server. + * + * The filename inside the tag is for reference only and gives the index + * number of the tag reported by the PVR backend and can not be played! + * + * + * USED SETUP VARIABLES: + * + * ------------- Name -------------|---Type--|-default-|--Description----- + * pvrmanager.instantrecordtime = Integer = 180 = Length of a instant timer in minutes + * pvrmanager.defaultpriority = Integer = 50 = Default Priority + * pvrmanager.defaultlifetime = Integer = 99 = Liftime of the timer in days + * pvrmanager.marginstart = Integer = 2 = Minutes to start record earlier + * pvrmanager.marginstop = Integer = 10 = Minutes to stop record later + * + */ + +#include "FileItem.h" +#include "PVRTimers.h" +#include "PVREpg.h" +#include "GUISettings.h" +#include "PVRManager.h" +#include "Util.h" +#include "GUIDialogOK.h" +#include "GUIDialogYesNo.h" +#include "LocalizeStrings.h" +#include "utils/log.h" +#include "utils/SingleLock.h" +#include "URL.h" + +/** + * Create a blank unmodified timer tag + */ +cPVRTimerInfoTag::cPVRTimerInfoTag() +{ + Reset(); +} + +/** + * Creates a instant timer on current date and channel if "bool Init" + * is set one hour later as now otherwise a blank cPVRTimerInfoTag is + * given. + * \param bool Init = Initialize as instant timer if set + * + * Note: + * Check active flag "m_Active" is set, after creating the tag. If it + * is false something goes wrong during initialization! + * See Log for errors. + */ +cPVRTimerInfoTag::cPVRTimerInfoTag(bool Init) +{ + Reset(); + + /* Check if instant flag is set otherwise return */ + if (!Init) + { + CLog::Log(LOGERROR, "cPVRTimerInfoTag: Can't initialize tag, Init flag not set!"); + return; + } + + /* Get setup variables */ + int rectime = g_guiSettings.GetInt("pvrrecord.instantrecordtime"); + int defprio = g_guiSettings.GetInt("pvrrecord.defaultpriority"); + int deflifetime = g_guiSettings.GetInt("pvrrecord.defaultlifetime"); + + if (!rectime) + rectime = 180; /* Default 180 minutes */ + + if (!defprio) + defprio = 50; /* Default */ + + if (!deflifetime) + deflifetime = 99; /* Default 99 days */ + + const cPVRChannelInfoTag *channel = NULL; + + CFileItem *curPlayingChannel = g_PVRManager.GetCurrentPlayingItem(); + if (curPlayingChannel) + channel = curPlayingChannel->GetPVRChannelInfoTag(); + else + channel = PVRChannelsTV.GetByNumber(1); + + if (channel == NULL) + { + CLog::Log(LOGERROR, "cPVRTimerInfoTag: constructor can't get valid channel"); + return; + } + + /* Set default timer */ + m_clientIndex = -1; + m_Active = true; + m_strTitle = g_localizeStrings.Get(19056); + m_channelNum = channel->Number(); + m_clientNum = channel->ClientNumber(); + m_clientID = channel->ClientID(); + m_Radio = channel->IsRadio(); + + /* Calculate start/stop times */ + CDateTime time = CDateTime::GetCurrentDateTime(); + m_StartTime = time; + m_StopTime = time + CDateTimeSpan(0, rectime / 60, rectime % 60, 0); /* Add recording time */ + + /* Set priority and lifetime */ + m_Priority = defprio; + m_Lifetime = deflifetime; + + /* Generate summary string */ + m_Summary.Format("%s %s %s %s %s", m_StartTime.GetAsLocalizedDate() + , g_localizeStrings.Get(19159) + , m_StartTime.GetAsLocalizedTime("",false) + , g_localizeStrings.Get(19160) + , m_StopTime.GetAsLocalizedTime("",false)); + + m_strFileNameAndPath = "pvr://timers/new"; /* Unused only for reference */ + return; +} + +/** + * Create Timer based upon an TVEPGInfoTag + * \param const CFileItem& item = reference to cPVREPGInfoTag class + * + * Note: + * Check active flag "m_Active" is set, after creating the tag. If it + * is false something goes wrong during initialization! + * See Log for errors. + */ +cPVRTimerInfoTag::cPVRTimerInfoTag(const CFileItem& item) +{ + Reset(); + + const cPVREPGInfoTag* tag = item.GetEPGInfoTag(); + if (tag == NULL) + { + CLog::Log(LOGERROR, "cPVRTimerInfoTag: Can't initialize tag, no EPGInfoTag given!"); + return; + } + + const cPVRChannelInfoTag *channel = cPVRChannels::GetByChannelIDFromAll(tag->ChannelID()); + if (channel == NULL) + { + CLog::Log(LOGERROR, "cPVRTimerInfoTag: constructor is called with not present channel"); + return; + } + + /* Check epg end date is in the future */ + if (tag->End() < CDateTime::GetCurrentDateTime()) + { + CLog::Log(LOGERROR, "cPVRTimerInfoTag: Can't initialize tag, EPGInfoTag is in the past!"); + return; + } + + /* Get setup variables */ + int defprio = g_guiSettings.GetInt("pvrrecord.defaultpriority"); + int deflifetime = g_guiSettings.GetInt("pvrrecord.defaultlifetime"); + int marginstart = g_guiSettings.GetInt("pvrrecord.marginstart"); + int marginstop = g_guiSettings.GetInt("pvrrecord.marginstop"); + + if (!defprio) + defprio = 50; /* Default */ + + if (!deflifetime) + deflifetime = 99; /* Default 99 days */ + + if (!deflifetime) + marginstart = 2; /* Default start 2 minutes earlier */ + + if (!deflifetime) + marginstop = 10; /* Default stop 10 minutes later */ + + /* Set timer based on EPG entry */ + m_clientIndex = tag->GetUniqueBroadcastID(); + m_Active = true; + m_strTitle = tag->Title(); + m_channelNum = channel->Number(); + m_clientNum = channel->ClientNumber(); + m_clientID = channel->ClientID(); + m_Radio = channel->IsRadio(); + + if (m_strTitle.IsEmpty()) + m_strTitle = channel->Name(); + + /* Calculate start/stop times */ + m_StartTime = tag->Start() - CDateTimeSpan(0, marginstart / 60, marginstart % 60, 0); + m_StopTime = tag->End() + CDateTimeSpan(0, marginstop / 60, marginstop % 60, 0); + + /* Set priority and lifetime */ + m_Priority = defprio; + m_Lifetime = deflifetime; + + /* Generate summary string */ + m_Summary.Format("%s %s %s %s %s", m_StartTime.GetAsLocalizedDate() + , g_localizeStrings.Get(19159) + , m_StartTime.GetAsLocalizedTime("",false) + , g_localizeStrings.Get(19160) + , m_StopTime.GetAsLocalizedTime("",false)); + + m_strFileNameAndPath = "pvr://timers/new"; /* Unused only for reference */ + return; +} + +/** + * Compare equal for two cPVRTimerInfoTag + */ +bool cPVRTimerInfoTag::operator ==(const cPVRTimerInfoTag& right) const +{ + if (this == &right) return true; + + return (m_clientIndex == right.m_clientIndex && + m_Active == right.m_Active && + m_Summary == right.m_Summary && + m_channelNum == right.m_channelNum && + m_clientNum == right.m_clientNum && + m_Radio == right.m_Radio && + m_Repeat == right.m_Repeat && + m_StartTime == right.m_StartTime && + m_StopTime == right.m_StopTime && + m_FirstDay == right.m_FirstDay && + m_Weekdays == right.m_Weekdays && + m_recStatus == right.m_recStatus && + m_Priority == right.m_Priority && + m_Lifetime == right.m_Lifetime && + m_strFileNameAndPath == right.m_strFileNameAndPath && + m_strTitle == right.m_strTitle && + m_clientID == right.m_clientID); +} + +/** + * Compare not equal for two cPVRTimerInfoTag + */ +bool cPVRTimerInfoTag::operator !=(const cPVRTimerInfoTag& right) const +{ + if (this == &right) return false; + + return (m_clientIndex != right.m_clientIndex && + m_Active != right.m_Active && + m_Summary != right.m_Summary && + m_channelNum != right.m_channelNum && + m_clientNum != right.m_clientNum && + m_Radio != right.m_Radio && + m_Repeat != right.m_Repeat && + m_StartTime != right.m_StartTime && + m_StopTime != right.m_StopTime && + m_FirstDay != right.m_FirstDay && + m_Weekdays != right.m_Weekdays && + m_recStatus != right.m_recStatus && + m_Priority != right.m_Priority && + m_Lifetime != right.m_Lifetime && + m_strFileNameAndPath != right.m_strFileNameAndPath && + m_strTitle != right.m_strTitle && + m_clientID != right.m_clientID); +} + +time_t cPVRTimerInfoTag::StartTime(void) const +{ + time_t start; + m_StartTime.GetAsTime(start); + return start; +} + +time_t cPVRTimerInfoTag::StopTime(void) const +{ + time_t stop; + m_StopTime.GetAsTime(stop); + return stop; +} + +time_t cPVRTimerInfoTag::FirstDayTime(void) const +{ + time_t firstday; + m_FirstDay.GetAsTime(firstday); + return firstday; +} + +int cPVRTimerInfoTag::Compare(const cPVRTimerInfoTag &timer) const +{ + time_t timer1 = StartTime(); + time_t timer2 = timer.StartTime(); + int r = timer1 - timer2; + if (r == 0) + r = timer.m_Priority - m_Priority; + return r; +} + +/** + * Initialize blank cPVRTimerInfoTag + */ +void cPVRTimerInfoTag::Reset() +{ + m_strTitle = ""; + m_strDir = "/"; + m_Summary = ""; + + m_Active = false; + m_channelNum = -1; + m_clientID = g_PVRManager.GetFirstClientID(); + m_clientIndex = -1; + m_clientNum = -1; + m_Radio = false; + m_recStatus = false; + + m_StartTime = NULL; + m_StopTime = NULL; + + m_Repeat = false; + m_FirstDay = NULL; + m_Weekdays = 0; + + m_Priority = -1; + m_Lifetime = -1; + + m_EpgInfo = NULL; + + m_strFileNameAndPath = ""; +} + +/** + * Get the status string of this Timer, is used by the GUIInfoManager + */ +const CStdString cPVRTimerInfoTag::GetStatus() const +{ + if (m_strFileNameAndPath == "pvr://timers/add.timer") + return g_localizeStrings.Get(19026); + else if (!m_Active) + return g_localizeStrings.Get(13106); + else if (m_Active && (m_StartTime < CDateTime::GetCurrentDateTime() && m_StopTime > CDateTime::GetCurrentDateTime())) + return g_localizeStrings.Get(19162); + else + return g_localizeStrings.Get(305); +} + +bool cPVRTimerInfoTag::Add() const +{ + try + { + CLIENTMAP *clients = g_PVRManager.Clients(); + + /* and write it to the backend */ + PVR_ERROR err = clients->find(m_clientID)->second->AddTimer(*this); + if (err != PVR_ERROR_NO_ERROR) + throw err; + + if (m_StartTime < CDateTime::GetCurrentDateTime() && m_StopTime > CDateTime::GetCurrentDateTime()) + g_PVRManager.TriggerRecordingsUpdate(false); + + return true; + } + catch (PVR_ERROR err) + { + DisplayError(err); + } + return false; +} + +bool cPVRTimerInfoTag::Delete(bool force) const +{ + try + { + CLIENTMAP *clients = g_PVRManager.Clients(); + + /* and write it to the backend */ + PVR_ERROR err = clients->find(m_clientID)->second->DeleteTimer(*this, force); + + if (err == PVR_ERROR_RECORDING_RUNNING) + { + if (CGUIDialogYesNo::ShowAndGetInput(122,0,19122,0)) + err = clients->find(m_clientID)->second->DeleteTimer(*this, true); + } + + if (err != PVR_ERROR_NO_ERROR) + throw err; + + return true; + } + catch (PVR_ERROR err) + { + DisplayError(err); + } + return false; +} + +bool cPVRTimerInfoTag::Rename(CStdString &newname) const +{ + try + { + CLIENTMAP *clients = g_PVRManager.Clients(); + + /* and write it to the backend */ + PVR_ERROR err = clients->find(m_clientID)->second->RenameTimer(*this, newname); + + if (err == PVR_ERROR_NOT_IMPLEMENTED) + err = clients->find(m_clientID)->second->UpdateTimer(*this); + + if (err != PVR_ERROR_NO_ERROR) + throw err; + + return true; + } + catch (PVR_ERROR err) + { + DisplayError(err); + } + + return false; +} + +bool cPVRTimerInfoTag::Update() const +{ + try + { + CLIENTMAP *clients = g_PVRManager.Clients(); + + /* and write it to the backend */ + PVR_ERROR err = clients->find(m_clientID)->second->UpdateTimer(*this); + if (err != PVR_ERROR_NO_ERROR) + throw err; + + if (m_StartTime < CDateTime::GetCurrentDateTime() && m_StopTime > CDateTime::GetCurrentDateTime()) + g_PVRManager.TriggerRecordingsUpdate(false); + + return true; + } + catch (PVR_ERROR err) + { + DisplayError(err); + } + return false; +} + +void cPVRTimerInfoTag::DisplayError(PVR_ERROR err) const +{ + if (err == PVR_ERROR_SERVER_ERROR) + CGUIDialogOK::ShowAndGetInput(19033,19111,19110,0); /* print info dialog "Server error!" */ + else if (err == PVR_ERROR_NOT_SYNC) + CGUIDialogOK::ShowAndGetInput(19033,19112,19110,0); /* print info dialog "Timers not in sync!" */ + else if (err == PVR_ERROR_NOT_SAVED) + CGUIDialogOK::ShowAndGetInput(19033,19109,19110,0); /* print info dialog "Couldn't delete timer!" */ + else if (err == PVR_ERROR_ALREADY_PRESENT) + CGUIDialogOK::ShowAndGetInput(19033,19109,0,19067); /* print info dialog */ + else + CGUIDialogOK::ShowAndGetInput(19033,19147,19110,0); /* print info dialog "Unknown error!" */ + + return; +} + +void cPVRTimerInfoTag::SetEpg(const cPVREPGInfoTag *tag) +{ + if (m_EpgInfo != tag) + { + if (tag) + CLog::Log(LOGINFO, "cPVRTimerInfoTag: timer %s set to epg event %s", Title().c_str(), tag->Title().c_str()); + else + CLog::Log(LOGINFO, "cPVRTimerInfoTag: timer %s set to no epg event", Title().c_str()); + m_EpgInfo = tag; + } +} + +unsigned int cPVRTimerInfoTag::ChannelNumber() const +{ + cPVRChannelInfoTag *channeltag = cPVRChannels::GetByClientFromAll(m_clientNum, m_clientID); + if (channeltag) + return channeltag->Number(); + else + return 0; +} + +CStdString cPVRTimerInfoTag::ChannelName() const +{ + cPVRChannelInfoTag *channeltag = cPVRChannels::GetByClientFromAll(m_clientNum, m_clientID); + if (channeltag) + return channeltag->Name(); + else + return ""; +} + +// --- cPVRTimers --------------------------------------------------------------- + +cPVRTimers PVRTimers; + +cPVRTimers::cPVRTimers(void) +{ + +} + +void cPVRTimers::Unload() +{ + Clear(); +} + +bool cPVRTimers::Update() +{ + CSingleLock lock(m_critSection); + + CLIENTMAP *clients = g_PVRManager.Clients(); + + Clear(); + + CLIENTMAPITR itr = clients->begin(); + while (itr != clients->end()) + { + if (g_PVRManager.GetClientProps((*itr).second->GetID())->SupportTimers) + { + if ((*itr).second->GetNumTimers() > 0) + { + (*itr).second->GetAllTimers(this); + } + } + itr++; + } + + g_PVRManager.SyncInfo(); + return true; +} + +int cPVRTimers::GetNumTimers() +{ + return size(); +} + +int cPVRTimers::GetTimers(CFileItemList* results) +{ + Update(); + + for (unsigned int i = 0; i < size(); ++i) + { + CFileItemPtr timer(new CFileItem(at(i))); + results->Add(timer); + } + + return size(); +} + +cPVRTimerInfoTag *cPVRTimers::GetTimer(cPVRTimerInfoTag *Timer) +{ + CSingleLock lock(m_critSection); + for (unsigned int i = 0; i < size(); i++) + { + if (at(i).Number() == Timer->Number() && + ((at(i).Weekdays() && at(i).Weekdays() == Timer->Weekdays()) || (!at(i).Weekdays() && at(i).FirstDay() == Timer->FirstDay())) && + at(i).Start() == Timer->Start() && + at(i).Stop() == Timer->Stop()) + return &at(i); + } + return NULL; +} + +cPVRTimerInfoTag *cPVRTimers::GetMatch(CDateTime t) +{ + + return NULL; +} + +cPVRTimerInfoTag *cPVRTimers::GetMatch(time_t t) +{ + + return NULL; +} + +cPVRTimerInfoTag *cPVRTimers::GetMatch(const cPVREPGInfoTag *Epg, int *Match) +{ + + return NULL; +} + +cPVRTimerInfoTag *cPVRTimers::GetNextActiveTimer(void) +{ + CSingleLock lock(m_critSection); + cPVRTimerInfoTag *t0 = NULL; + for (unsigned int i = 0; i < size(); i++) + { + if ((at(i).Active()) && (!t0 || (at(i).Stop() > CDateTime::GetCurrentDateTime() && at(i).Compare(*t0) < 0))) + { + t0 = &at(i); + } + } + return t0; +} + +bool cPVRTimers::AddTimer(const CFileItem &item) +{ + /* Check if a cPVRTimerInfoTag is inside file item */ + if (!item.IsPVRTimer()) + { + CLog::Log(LOGERROR, "cPVRTimers: AddTimer no TimerInfoTag given!"); + return false; + } + + const cPVRTimerInfoTag* tag = item.GetPVRTimerInfoTag(); + if (!g_PVRManager.GetClientProps(tag->ClientID())->SupportTimers) + { + CGUIDialogOK::ShowAndGetInput(19033,0,19215,0); + return false; + } + return tag->Add(); +} + +bool cPVRTimers::DeleteTimer(const CFileItem &item, bool force) +{ + /* Check if a cPVRTimerInfoTag is inside file item */ + if (!item.IsPVRTimer()) + { + CLog::Log(LOGERROR, "cPVRTimers: DeleteTimer no TimerInfoTag given!"); + return false; + } + + const cPVRTimerInfoTag* tag = item.GetPVRTimerInfoTag(); + return tag->Delete(force); +} + +bool cPVRTimers::RenameTimer(CFileItem &item, CStdString &newname) +{ + /* Check if a cPVRTimerInfoTag is inside file item */ + if (!item.IsPVRTimer()) + { + CLog::Log(LOGERROR, "cPVRTimers: RenameTimer no TimerInfoTag given!"); + return false; + } + + cPVRTimerInfoTag* tag = item.GetPVRTimerInfoTag(); + if (tag->Rename(newname)) + { + tag->SetTitle(newname); + return true; + } + return false; +} + +bool cPVRTimers::UpdateTimer(const CFileItem &item) +{ + /* Check if a cPVRTimerInfoTag is inside file item */ + if (!item.IsPVRTimer()) + { + CLog::Log(LOGERROR, "cPVRTimers: UpdateTimer no TimerInfoTag given!"); + return false; + } + + const cPVRTimerInfoTag* tag = item.GetPVRTimerInfoTag(); + return tag->Update(); +} + +bool cPVRTimers::GetDirectory(const CStdString& strPath, CFileItemList &items) +{ + CStdString base(strPath); + CUtil::RemoveSlashAtEnd(base); + + CURL url(strPath); + CStdString fileName = url.GetFileName(); + CUtil::RemoveSlashAtEnd(fileName); + + if (fileName == "timers") + { + CFileItemPtr item; + + Update(); + + item.reset(new CFileItem(base + "/add.timer", false)); + item->SetLabel(g_localizeStrings.Get(19026)); + item->SetLabelPreformated(true); + items.Add(item); + + for (unsigned int i = 0; i < size(); ++i) + { + item.reset(new CFileItem(at(i))); + items.Add(item); + } + + return true; + } + return false; +} + +void cPVRTimers::Clear() +{ + /* Clear all current present Timers inside list */ + erase(begin(), end()); + return; +} diff --git a/xbmc/utils/PVRTimers.h b/xbmc/utils/PVRTimers.h new file mode 100644 index 0000000000..ee57e00eb7 --- /dev/null +++ b/xbmc/utils/PVRTimers.h @@ -0,0 +1,151 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +/* + * for DESCRIPTION see 'PVRTimers.cpp' + */ + +#include "DateTime.h" +#include "../addons/include/xbmc_pvr_types.h" + +class CFileItem; +class cPVREPGInfoTag; +class CGUIDialogPVRTimerSettings; + +class cPVRTimerInfoTag +{ +private: + friend class CGUIDialogPVRTimerSettings; + + CStdString m_strTitle; /// Name of this timer + CStdString m_strDir; /// Directory where the recording must be stored + CStdString m_Summary; /// Summary string with the time to show inside a GUI list + bool m_Active; /// Active flag, if it is false backend ignore the timer + int m_channelNum; /// Integer value of the channel number + int m_clientID; /// ID of the backend + int m_clientIndex; /// Index number of the tag, given by the backend, -1 for new + int m_clientNum; /// Integer value of the client number + bool m_Radio; /// Is Radio channel if set + bool m_recStatus; /// Is this timer recording? + int m_Priority; /// Priority of the timer + int m_Lifetime; /// Lifetime of the timer in days + bool m_Repeat; /// Repeating timer if true, use the m_FirstDay and repeat flags + CDateTime m_StartTime; /// Start time + CDateTime m_StopTime; /// Stop time + CDateTime m_FirstDay; /// If it is a repeating timer the first date it starts + int m_Weekdays; /// Bit based store of weekdays to repeat + CStdString m_strFileNameAndPath; /// Filename is only for reference + + const cPVREPGInfoTag *m_EpgInfo; + + void DisplayError(PVR_ERROR err) const; + +public: + cPVRTimerInfoTag(); + cPVRTimerInfoTag(const CFileItem& item); + cPVRTimerInfoTag(bool Init); + + bool operator ==(const cPVRTimerInfoTag& right) const; + bool operator !=(const cPVRTimerInfoTag& right) const; + + void Reset(); + const CStdString GetStatus() const; + int Compare(const cPVRTimerInfoTag &timer) const; + time_t StartTime(void) const; + time_t StopTime(void) const; + time_t FirstDayTime(void) const; + CDateTime Start(void) const { return m_StartTime; } + void SetStart(CDateTime Start) { m_StartTime = Start; } + CDateTime Stop(void) const { return m_StopTime; } + void SetStop(CDateTime Stop) { m_StopTime = Stop; } + CStdString Title(void) const { return m_strTitle; } + void SetTitle(CStdString name) { m_strTitle = name; } + CStdString Dir(void) const { return m_strDir; } + void SetDir(CStdString dir) { m_strDir = dir; } + int Number(void) const { return m_channelNum; } + void SetNumber(int Number) { m_channelNum = Number; } + bool Active(void) const { return m_Active; } + void SetActive(bool Active) { m_Active = Active; } + bool IsRadio(void) const { return m_Radio; } + void SetRadio(bool Radio) { m_Radio = Radio; } + int ClientNumber(void) const { return m_clientNum; } + void SetClientNumber(int Number) { m_clientNum = Number; } + long ClientIndex(void) const { return m_clientIndex; } + void SetClientIndex(int ClientIndex) { m_clientIndex = ClientIndex; } + long ClientID(void) const { return m_clientID; } + void SetClientID(int ClientId) { m_clientID = ClientId; } + bool IsRecording(void) const { return m_recStatus; } + void SetRecording(bool Recording) { m_recStatus = Recording; } + int Lifetime(void) const { return m_Lifetime; } + void SetLifetime(int Lifetime) { m_Lifetime = Lifetime; } + int Priority(void) const { return m_Priority; } + void SetPriority(int Priority) { m_Priority = Priority; } + bool IsRepeating(void) const { return m_Repeat; } + void SetRepeating(bool Repeating) { m_Repeat = Repeating; } + int Weekdays(void) const { return m_Weekdays; } + void SetWeekdays(int Weekdays) { m_Weekdays = Weekdays; } + CDateTime FirstDay(void) const { return m_FirstDay; } + void SetFirstDay(CDateTime FirstDay) { m_FirstDay = FirstDay; } + CStdString Summary(void) const { return m_Summary; } + void SetSummary(CStdString Summary) { m_Summary = Summary; } + CStdString Path(void) const { return m_strFileNameAndPath; } + void SetPath(CStdString path) { m_strFileNameAndPath = path; } + const cPVREPGInfoTag *Epg() const { return m_EpgInfo;} + void SetEpg(const cPVREPGInfoTag *tag); + + /* Channel related Info data */ + unsigned int ChannelNumber(void) const; + CStdString ChannelName(void) const; + + /* Client control functions */ + bool Add() const; + bool Delete(bool force = false) const; + bool Rename(CStdString &newname) const; + bool Update() const; +}; + +class cPVRTimers : public std::vector +{ +private: + CCriticalSection m_critSection; + +public: + cPVRTimers(void); + bool Load() { return Update(); } + void Unload(); + bool Update(); + int GetNumTimers(); + int GetTimers(CFileItemList* results); + cPVRTimerInfoTag *GetTimer(cPVRTimerInfoTag *Timer); + cPVRTimerInfoTag *GetMatch(CDateTime t); + cPVRTimerInfoTag *GetMatch(time_t t); + cPVRTimerInfoTag *GetMatch(const cPVREPGInfoTag *Epg, int *Match = NULL); + cPVRTimerInfoTag *GetNextActiveTimer(void); + bool GetDirectory(const CStdString& strPath, CFileItemList &items); + static bool AddTimer(const CFileItem &item); + static bool DeleteTimer(const CFileItem &item, bool force = false); + static bool RenameTimer(CFileItem &item, CStdString &newname); + static bool UpdateTimer(const CFileItem &item); + void Clear(); +}; + +extern cPVRTimers PVRTimers; diff --git a/xbmc/utils/TextSearch.cpp b/xbmc/utils/TextSearch.cpp new file mode 100644 index 0000000000..7b755913f1 --- /dev/null +++ b/xbmc/utils/TextSearch.cpp @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include +#include + +#include "TextSearch.h" + +using namespace std; + +cTextSearch::cTextSearch(void) +{ + m_text = ""; + m_searchText = ""; + m_CaseSensitive = false; +} + +cTextSearch::cTextSearch(CStdString text, CStdString searchText, bool caseSensitive) +{ + m_CaseSensitive = caseSensitive; + m_text = text; + m_searchText = searchText; +} + +cTextSearch::~cTextSearch(void) +{ + m_AND.clear(); + m_OR.clear(); + m_NOT.clear(); +} + +void cTextSearch::SetText(CStdString text, CStdString searchText, bool caseSensitive) +{ + m_CaseSensitive = caseSensitive; + m_text = text; + m_searchText = searchText; +} + +bool cTextSearch::DoSearch() +{ + m_AND.clear(); + m_OR.clear(); + m_NOT.clear(); + + CStdString text = m_text; + CStdString searchStr = m_searchText; + + if (text == "") + return false; + + if (searchStr == "") + return true; + + if (!m_CaseSensitive) + text.ToLower(); + + /* Only one word search */ + if (searchStr.Find(" ") == CStdString::npos) + { + if (searchStr[0] == '"') + searchStr.erase(0, 1); + if (searchStr[searchStr.size()-1] == '"') + searchStr.erase(searchStr.size()-1, 1); + + if (!m_CaseSensitive) + searchStr.ToLower(); + + if (text.Find(searchStr) == CStdString::npos) + return false; + else + return true; + } + else + { + while (searchStr.length() > 3) + { + /* Remove white spaces and begin of search line */ + if (searchStr[0] == ' ') + { + searchStr.erase(0, 1); + } + /* Search for AND */ + else if (searchStr.Find("AND") != CStdString::npos || searchStr.Find("+") != CStdString::npos) + { + size_t firstPos = searchStr.Find("AND"); + size_t lastPos = CStdString::npos; + + if (firstPos != CStdString::npos) + searchStr.erase(firstPos, 3); + else + { + firstPos = searchStr.Find("+"); + searchStr.erase(firstPos, 1); + } + + ClearBlankCharacter(&searchStr, firstPos); + + if (searchStr[firstPos] == '"') + { + firstPos++; // Remove the phrase + lastPos = searchStr.Find("\"", firstPos); + } + else + lastPos = searchStr.Find(" "); + + size_t strSize = CStdString::npos; + if (lastPos != CStdString::npos) + strSize = lastPos-firstPos; + + m_AND.push_back(searchStr.substr(firstPos, strSize)); + searchStr.erase(firstPos, lastPos-firstPos); + } + /* Search for OR */ + else if (searchStr.Find("OR") != CStdString::npos || searchStr.Find("|") != CStdString::npos) + { + size_t firstPos = searchStr.Find("OR"); + size_t lastPos = CStdString::npos; + + if (firstPos != CStdString::npos) + searchStr.erase(firstPos, 2); + else + { + firstPos = searchStr.Find("|"); + searchStr.erase(firstPos, 1); + } + + ClearBlankCharacter(&searchStr, firstPos); + + if (searchStr[firstPos] == '"') + { + firstPos++; // Remove the phrase + lastPos = searchStr.Find("\"", firstPos); + } + else + lastPos = searchStr.Find(" "); + + size_t strSize = CStdString::npos; + if (lastPos != CStdString::npos) + strSize = lastPos-firstPos; + + m_OR.push_back(searchStr.substr(firstPos, strSize)); + searchStr.erase(firstPos, lastPos-firstPos); + } + /* Search for OR */ + else if (searchStr.Find("NOT") != CStdString::npos || searchStr.Find("-") != CStdString::npos) + { + size_t firstPos = searchStr.Find("NOT"); + size_t lastPos = CStdString::npos; + + if (firstPos != CStdString::npos) + searchStr.erase(firstPos, 3); + else + { + firstPos = searchStr.Find("-"); + searchStr.erase(firstPos, 1); + } + + ClearBlankCharacter(&searchStr, firstPos); + + if (searchStr[firstPos] == '"') + { + firstPos++; // Remove the phrase + lastPos = searchStr.Find("\"", firstPos); + } + else + lastPos = searchStr.Find(" "); + + size_t strSize = CStdString::npos; + if (lastPos != CStdString::npos) + strSize = lastPos-firstPos; + + m_NOT.push_back(searchStr.substr(firstPos, strSize)); + searchStr.erase(firstPos, lastPos-firstPos); + } + else + { + size_t lastPos = CStdString::npos; + + if (searchStr[0] == '"') + { + searchStr.erase(0, 1); + lastPos = searchStr.Find("\""); + } + else + { + lastPos = searchStr.Find(" "); + } + + m_AND.push_back(searchStr.substr(0, lastPos)); + searchStr.erase(0, lastPos); + } + } + + /* If no search words are present everything is true */ + if (m_AND.size() == 0 && m_OR.size() == 0 && m_NOT.size() == 0) + return true; + + for (unsigned int i = 0; i < m_NOT.size(); i++) + { + CStdString word = m_NOT[i]; + if (!m_CaseSensitive) + word.ToLower(); + + if (text.Find(word) != CStdString::npos) + return false; + } + + for (unsigned int i = 0; i < m_OR.size(); i++) + { + CStdString word = m_OR[i]; + if (!m_CaseSensitive) + word.ToLower(); + + if (text.Find(word) != CStdString::npos) + return true; + } + + for (unsigned int i = 0; i < m_AND.size(); i++) + { + CStdString word = m_AND[i]; + if (!m_CaseSensitive) + word.ToLower(); + + if (text.Find(word) == CStdString::npos) + return false; + } + + return true; + } + return false; +} + +bool cTextSearch::SearchText(CStdString text, CStdString searchText, bool caseSensitive) +{ + cTextSearch search(text, searchText, caseSensitive); + return search.DoSearch(); +} + +void cTextSearch::ClearBlankCharacter(CStdString *text, int pos) +{ + if (text->at(pos) > ' ') + return; + while (text->length() > 0 && text->at(pos) == ' ') + text->erase(pos, 1); +} diff --git a/xbmc/utils/TextSearch.h b/xbmc/utils/TextSearch.h new file mode 100644 index 0000000000..90bc639f5b --- /dev/null +++ b/xbmc/utils/TextSearch.h @@ -0,0 +1,48 @@ +#pragma once +/* + * Copyright (C) 2005-2009 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include "StringUtils.h" + +class cTextSearch +{ +public: + cTextSearch(void); + cTextSearch(CStdString text, CStdString searchText, bool caseSensitive); + virtual ~cTextSearch(void); + + void SetText(CStdString text, CStdString searchText, bool caseSensitive); + bool DoSearch(); + + static bool SearchText(CStdString text, CStdString searchText, bool caseSensitive); + +private: + void ClearBlankCharacter(CStdString *text, int pos); + + CStdString m_text; + CStdString m_searchText; + std::vector m_AND; + std::vector m_OR; + std::vector m_NOT; + bool m_CaseSensitive; +}; + diff --git a/xbmc/win32/stdbool.h b/xbmc/win32/stdbool.h new file mode 100644 index 0000000000..87a55750ae --- /dev/null +++ b/xbmc/win32/stdbool.h @@ -0,0 +1,53 @@ +/* Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING. If not, write to +the Free Software Foundation, 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. */ + +/* As a special exception, if you include this header file into source + files compiled by GCC, this header file does not by itself cause + the resulting executable to be covered by the GNU General Public + License. This exception does not however invalidate any other + reasons why the executable file might be covered by the GNU General + Public License. */ + +/* +* ISO C Standard: 7.16 Boolean type and values +*/ + +#ifndef _STDBOOL_H +#define _STDBOOL_H + +#ifndef __cplusplus + +#define bool _Bool +#define true 1 +#define false 0 + +#else /* __cplusplus */ + +/* Supporting in C++ is a GCC extension. */ +#define _Bool bool +#define bool bool +#define false false +#define true true + +#endif /* __cplusplus */ + +/* Signal that all the definitions are present. */ +#define __bool_true_false_are_defined 1 + +#endif /* stdbool.h */ -- GitLab

6J!&{K1sUT^%HsP?0Q4Kgc)3LJ1KPwk>^ zk*SlI$alaI1)4myi{8;DhMRZ9^u)gYCg9~cI%3-X+i&09a!b$10KW0-{s7kw`8IQ7 zhkS1VEeF4Ick)ItcxL-ogH3z;+{?^kRxj5dvnsM2vmSb8@vO%8-2Sh`Z36%21pYe! Xn$@1Zv1O*_00000NkvXXu0mjf?Bnve literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/OSDChannelDownNF.png b/addons/skin.confluence/media/OSDChannelDownNF.png new file mode 100644 index 0000000000000000000000000000000000000000..6795c433283169ea9cf3e80ab9dab61e513b4010 GIT binary patch literal 3686 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 Ef_hxhf&c&j literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/OSDChannelListFO.png b/addons/skin.confluence/media/OSDChannelListFO.png new file mode 100644 index 0000000000000000000000000000000000000000..a08bc13a7dc8cbd3203feee565c21fbbd1ca0507 GIT binary patch literal 4766 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 literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/OSDChannelListNF.png b/addons/skin.confluence/media/OSDChannelListNF.png new file mode 100644 index 0000000000000000000000000000000000000000..8339fdced4751387a7f1376bd896a8af9f9cfbed GIT binary patch literal 3654 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 Y0GDGDna3T9-2eap07*qoM6N<$f~G;nY5)KL literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/OSDChannelUPFO.png b/addons/skin.confluence/media/OSDChannelUPFO.png new file mode 100644 index 0000000000000000000000000000000000000000..a3e6dbad5fa73da2f81bd67a7ed3077e8285df9c GIT binary patch literal 4643 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 QNdN!<07*qoM6N<$g8wbsy8r+H literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/OSDRecordNF2.png b/addons/skin.confluence/media/OSDRecordNF2.png new file mode 100644 index 0000000000000000000000000000000000000000..10060e73b3152f0a3a1c817c476670896ed422d1 GIT binary patch literal 1883 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 Y0QUh0@L^rE`v3p{07*qoM6N<$f*P8>rT_o{ literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/OSDepgFO.png b/addons/skin.confluence/media/OSDepgFO.png new file mode 100644 index 0000000000000000000000000000000000000000..141f7adfc789ff37e320ab9bb3078c7c876b690c GIT binary patch literal 4842 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 Q0ssI207*qoM6N<$g1KTRivR!s literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/OSDepgNF.png b/addons/skin.confluence/media/OSDepgNF.png new file mode 100644 index 0000000000000000000000000000000000000000..cf9a86b6566cc1f9d81537d5f88c757900dc1dca GIT binary patch literal 3648 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 diff --git a/addons/skin.confluence/media/genre-a-moviedrama.png b/addons/skin.confluence/media/genre-a-moviedrama.png new file mode 100644 index 0000000000000000000000000000000000000000..22c3f1aeadbe5bdbe5be626ce533cc0b85fc18c4 GIT binary patch literal 2849 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 literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/genre-c-show.png b/addons/skin.confluence/media/genre-c-show.png new file mode 100644 index 0000000000000000000000000000000000000000..bb944893aa217a04e96a4c7a69c954794ff44456 GIT binary patch literal 2849 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 literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/genre-d-sports.png b/addons/skin.confluence/media/genre-d-sports.png new file mode 100644 index 0000000000000000000000000000000000000000..59232f6db5c82fd2ebac20ae33bb99d9b21c0c51 GIT binary patch literal 2849 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 literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/genre-e-child.png b/addons/skin.confluence/media/genre-e-child.png new file mode 100644 index 0000000000000000000000000000000000000000..cbe5344ce3fc4cf2230881547e7f2f33f4f955b5 GIT binary patch literal 2849 zcmV++3*PjJP)KLZ*U+5Lu!Sk^o_Z5E4Meg@_7P6crJiNL9pw)e1;Xm069{HJUZAPk55R%$-RIA z6-eL&AQ0xu!e<4=008gy@A0LT~suv4>S3ILP<0Bm`DLLvaF4FK%)Nj?Pt*r}7;7Xa9z9H|HZjR63e zC`Tj$K)V27Re@400>HumpsYY5E(E}?0f1SyGDiY{y#)Yvj#!WnKwtoXnL;eg03bL5 z07D)V%>y7z1E4U{zu>7~aD})?0RX_umCct+(lZpemCzb@^6=o|A>zVpu|i=NDG+7} zl4`aK{0#b-!z=TL9Wt0BGO&T{GJWpjryhdijfaIQ&2!o}p04JRKYg3k&Tf zVxhe-O!X z{f;To;xw^bEES6JSc$k$B2CA6xl)ltA<32E66t?3@gJ7`36pmX0IY^jz)rRYwaaY4 ze(nJRiw;=Qb^t(r^DT@T3y}a2XEZW-_W%Hszxj_qD**t_m!#tW0KDiJT&R>6OvVTR z07RgHDzHHZ48atvzz&?j9lXF70$~P3Knx_nJP<+#`N z#-MZ2bTkiLfR>_b(HgWKJ%F~Nr_oF3b#wrIijHG|(J>BYjM-sajE6;FiC7vY#};Gd zST$CUHDeuEH+B^pz@B062qXfFfD`NpUW5?BY=V%GM_5c)L#QR}BeW8_2v-S%gfYS= zB9o|3v?Y2H`NVi)In3rTB8+ej^> zQ=~r95NVuDChL%G$=>7$vVg20myx%S50Foi`^m%Pw-h?Xh~i8Mq9jtJloCocWk2Nv zrJpiFnV_ms&8eQ$2&#xWpIS+6pmtC%Q-`S&GF4Q#^mhymh7E(qNMa}%YZ-ePrx>>xFPTiH1=E+A$W$=bG8>s^ zm=Bn5Rah$aDtr}@$`X}2l~$F0mFKEdRdZE8)p@E5RI61Ft6o-prbbn>P~)iy)E2AN zsU20jsWz_8Qg>31P|s0cqrPALg8E|(vWA65poU1JRAaZs8I2(p#xiB`SVGovRs-uS zYnV-9TeA7=Om+qP8+I>yOjAR1s%ETak!GFdam@h^# z)@rS0t$wXH+Irf)+G6c;?H29p+V6F6oj{!|o%K3xI`?%6x;DB|x`n#ibhIR?(H}Q3Gzd138Ei2)WAMz7W9Vy`X}HnwgyEn!VS)>mv$8&{hQn>w4zwy3R}t;BYlZQm5)6pty=DfLrs+A-|>>;~;Q z_F?uV_HFjh9n2gO9o9Q^JA86v({H5aB!kjoO6 zc9$1ZZKsN-Zl8L~mE{`ly3)1N^`o1+o7}D0ZPeY&J;i;i`%NyJ8_8Y6J?}yE@b_5a zam?eLr<8@mESk|3$_SkmS{wQ>%qC18))9_|&j{ZT zes8AvOzF(F2#DZEY>2oYX&IRp`F#{ADl)1r>QS^)ba8a|EY_^#S^HO&t^Rgqwv=MZThqqEWH8 zxJo>d=ABlR_Bh=;eM9Tw|Ih34~oTE|= zX_mAr*D$vzw@+p(E0Yc6dFE}(8oqt`+R{gE3x4zjX+Sb3_cYE^= zgB=w+-tUy`ytONMS8KgRef4hA?t0j zufM;t32jm~jUGrkaOInTZ`zyfns>EuS}G30LFK_G-==(f<51|K&cocp&EJ`SxAh3? zNO>#LI=^+SEu(FqJ)ynt=!~PC9bO$rzPJB=?=j6w@a-(u02P7 zaQ)#(uUl{HW%tYNS3ItC^iAtK(eKlL`f9+{bJzISE?u8_z3;~C8@FyI-5j_jy7l;W z_U#vU3hqqYU3!mrul&B+{ptt$59)uk{;_4iZQ%G|z+lhASr6|H35TBkl>gI*;nGLU zN7W-nBaM%pA0HbH8olyl&XeJ%vZoWz%6?Y=dFykl=imL}`%BMQ{Mhgd`HRoLu6e2R za__6DuR6yg#~-}Tc|Gx_{H@O0eebyMy5GmWADJlpK>kqk(fVV@r_fLLKIeS?{4e)} z^ZO;zpECde03c&XQcVB=dL;k=fP(-4`Tqa_faw4Lbua(`>RI+y?e7jKeZ#YO-C z08U9nK~#9!V*LOAKf^!*CWZk9EHH+|ox?NfY4Hmc+w?&Z0;3KXb-<_tMjbHffKdmG zI$+cRqYfB#z^DU89Wd$u#u48;1^@s6|NjF3?~n>RHKC-I00000NkvXXu0mjfI&^1( literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/genre-f-music.png b/addons/skin.confluence/media/genre-f-music.png new file mode 100644 index 0000000000000000000000000000000000000000..848d7723150e7ee8163d568cb231371103f1258f GIT binary patch literal 2849 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 literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/genre-g-arts.png b/addons/skin.confluence/media/genre-g-arts.png new file mode 100644 index 0000000000000000000000000000000000000000..d93345eaac80e585e79069cd6b75128a1d838951 GIT binary patch literal 2849 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 literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/genre-i-science.png b/addons/skin.confluence/media/genre-i-science.png new file mode 100644 index 0000000000000000000000000000000000000000..17b46a653e42c42864d495b2940dcaa054e66e0c GIT binary patch literal 2849 zcmV++3*PjJP)KLZ*U+5Lu!Sk^o_Z5E4Meg@_7P6crJiNL9pw)e1;Xm069{HJUZAPk55R%$-RIA z6-eL&AQ0xu!e<4=008gy@A0LT~suv4>S3ILP<0Bm`DLLvaF4FK%)Nj?Pt*r}7;7Xa9z9H|HZjR63e zC`Tj$K)V27Re@400>HumpsYY5E(E}?0f1SyGDiY{y#)Yvj#!WnKwtoXnL;eg03bL5 z07D)V%>y7z1E4U{zu>7~aD})?0RX_umCct+(lZpemCzb@^6=o|A>zVpu|i=NDG+7} zl4`aK{0#b-!z=TL9Wt0BGO&T{GJWpjryhdijfaIQ&2!o}p04JRKYg3k&Tf zVxhe-O!X z{f;To;xw^bEES6JSc$k$B2CA6xl)ltA<32E66t?3@gJ7`36pmX0IY^jz)rRYwaaY4 ze(nJRiw;=Qb^t(r^DT@T3y}a2XEZW-_W%Hszxj_qD**t_m!#tW0KDiJT&R>6OvVTR z07RgHDzHHZ48atvzz&?j9lXF70$~P3Knx_nJP<+#`N z#-MZ2bTkiLfR>_b(HgWKJ%F~Nr_oF3b#wrIijHG|(J>BYjM-sajE6;FiC7vY#};Gd zST$CUHDeuEH+B^pz@B062qXfFfD`NpUW5?BY=V%GM_5c)L#QR}BeW8_2v-S%gfYS= zB9o|3v?Y2H`NVi)In3rTB8+ej^> zQ=~r95NVuDChL%G$=>7$vVg20myx%S50Foi`^m%Pw-h?Xh~i8Mq9jtJloCocWk2Nv zrJpiFnV_ms&8eQ$2&#xWpIS+6pmtC%Q-`S&GF4Q#^mhymh7E(qNMa}%YZ-ePrx>>xFPTiH1=E+A$W$=bG8>s^ zm=Bn5Rah$aDtr}@$`X}2l~$F0mFKEdRdZE8)p@E5RI61Ft6o-prbbn>P~)iy)E2AN zsU20jsWz_8Qg>31P|s0cqrPALg8E|(vWA65poU1JRAaZs8I2(p#xiB`SVGovRs-uS zYnV-9TeA7=Om+qP8+I>yOjAR1s%ETak!GFdam@h^# z)@rS0t$wXH+Irf)+G6c;?H29p+V6F6oj{!|o%K3xI`?%6x;DB|x`n#ibhIR?(H}Q3Gzd138Ei2)WAMz7W9Vy`X}HnwgyEn!VS)>mv$8&{hQn>w4zwy3R}t;BYlZQm5)6pty=DfLrs+A-|>>;~;Q z_F?uV_HFjh9n2gO9o9Q^JA86v({H5aB!kjoO6 zc9$1ZZKsN-Zl8L~mE{`ly3)1N^`o1+o7}D0ZPeY&J;i;i`%NyJ8_8Y6J?}yE@b_5a zam?eLr<8@mESk|3$_SkmS{wQ>%qC18))9_|&j{ZT zes8AvOzF(F2#DZEY>2oYX&IRp`F#{ADl)1r>QS^)ba8a|EY_^#S^HO&t^Rgqwv=MZThqqEWH8 zxJo>d=ABlR_Bh=;eM9Tw|Ih34~oTE|= zX_mAr*D$vzw@+p(E0Yc6dFE}(8oqt`+R{gE3x4zjX+Sb3_cYE^= zgB=w+-tUy`ytONMS8KgRef4hA?t0j zufM;t32jm~jUGrkaOInTZ`zyfns>EuS}G30LFK_G-==(f<51|K&cocp&EJ`SxAh3? zNO>#LI=^+SEu(FqJ)ynt=!~PC9bO$rzPJB=?=j6w@a-(u02P7 zaQ)#(uUl{HW%tYNS3ItC^iAtK(eKlL`f9+{bJzISE?u8_z3;~C8@FyI-5j_jy7l;W z_U#vU3hqqYU3!mrul&B+{ptt$59)uk{;_4iZQ%G|z+lhASr6|H35TBkl>gI*;nGLU zN7W-nBaM%pA0HbH8olyl&XeJ%vZoWz%6?Y=dFykl=imL}`%BMQ{Mhgd`HRoLu6e2R za__6DuR6yg#~-}Tc|Gx_{H@O0eebyMy5GmWADJlpK>kqk(fVV@r_fLLKIeS?{4e)} z^ZO;zpECde03c&XQcVB=dL;k=fP(-4`Tqa_faw4Lbua(`>RI+y?e7jKeZ#YO-C z08U9nK~#9!V*LOAKf^!*CWZk9EHK7=#rZSoX>sEH#PmTC0;3KXb-<_tMjbHffKdmG zI$+cRqYfB#z^DU89Wd$u#u48;1^@s6|NjF3$q))VYeV&500000NkvXXu0mjfmda>N literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/genre-j-hobby.png b/addons/skin.confluence/media/genre-j-hobby.png new file mode 100644 index 0000000000000000000000000000000000000000..29be2db90b3d376a3f14c18d36637f73515aef3d GIT binary patch literal 2849 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 literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/genre-k-special.png b/addons/skin.confluence/media/genre-k-special.png new file mode 100644 index 0000000000000000000000000000000000000000..e18cd2b987a92dec33860f793b3d0186ac1d1a7b GIT binary patch literal 2849 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 literal 0 HcmV?d00001 diff --git a/addons/skin.confluence/media/genre-l-unknown.png b/addons/skin.confluence/media/genre-l-unknown.png new file mode 100644 index 0000000000000000000000000000000000000000..80c6192880b397208096a8ac2fe1c43cf93de162 GIT binary patch literal 2849 zcmV++3*PjJP)KLZ*U+5Lu!Sk^o_Z5E4Meg@_7P6crJiNL9pw)e1;Xm069{HJUZAPk55R%$-RIA z6-eL&AQ0xu!e<4=008gy@A0LT~suv4>S3ILP<0Bm`DLLvaF4FK%)Nj?Pt*r}7;7Xa9z9H|HZjR63e zC`Tj$K)V27Re@400>HumpsYY5E(E}?0f1SyGDiY{y#)Yvj#!WnKwtoXnL;eg03bL5 z07D)V%>y7z1E4U{zu>7~aD})?0RX_umCct+(lZpemCzb@^6=o|A>zVpu|i=NDG+7} zl4`aK{0#b-!z=TL9Wt0BGO&T{GJWpjryhdijfaIQ&2!o}p04JRKYg3k&Tf zVxhe-O!X z{f;To;xw^bEES6JSc$k$B2CA6xl)ltA<32E66t?3@gJ7`36pmX0IY^jz)rRYwaaY4 ze(nJRiw;=Qb^t(r^DT@T3y}a2XEZW-_W%Hszxj_qD**t_m!#tW0KDiJT&R>6OvVTR z07RgHDzHHZ48atvzz&?j9lXF70$~P3Knx_nJP<+#`N z#-MZ2bTkiLfR>_b(HgWKJ%F~Nr_oF3b#wrIijHG|(J>BYjM-sajE6;FiC7vY#};Gd zST$CUHDeuEH+B^pz@B062qXfFfD`NpUW5?BY=V%GM_5c)L#QR}BeW8_2v-S%gfYS= zB9o|3v?Y2H`NVi)In3rTB8+ej^> zQ=~r95NVuDChL%G$=>7$vVg20myx%S50Foi`^m%Pw-h?Xh~i8Mq9jtJloCocWk2Nv zrJpiFnV_ms&8eQ$2&#xWpIS+6pmtC%Q-`S&GF4Q#^mhymh7E(qNMa}%YZ-ePrx>>xFPTiH1=E+A$W$=bG8>s^ zm=Bn5Rah$aDtr}@$`X}2l~$F0mFKEdRdZE8)p@E5RI61Ft6o-prbbn>P~)iy)E2AN zsU20jsWz_8Qg>31P|s0cqrPALg8E|(vWA65poU1JRAaZs8I2(p#xiB`SVGovRs-uS zYnV-9TeA7=Om+qP8+I>yOjAR1s%ETak!GFdam@h^# z)@rS0t$wXH+Irf)+G6c;?H29p+V6F6oj{!|o%K3xI`?%6x;DB|x`n#ibhIR?(H}Q3Gzd138Ei2)WAMz7W9Vy`X}HnwgyEn!VS)>mv$8&{hQn>w4zwy3R}t;BYlZQm5)6pty=DfLrs+A-|>>;~;Q z_F?uV_HFjh9n2gO9o9Q^JA86v({H5aB!kjoO6 zc9$1ZZKsN-Zl8L~mE{`ly3)1N^`o1+o7}D0ZPeY&J;i;i`%NyJ8_8Y6J?}yE@b_5a zam?eLr<8@mESk|3$_SkmS{wQ>%qC18))9_|&j{ZT zes8AvOzF(F2#DZEY>2oYX&IRp`F#{ADl)1r>QS^)ba8a|EY_^#S^HO&t^Rgqwv=MZThqqEWH8 zxJo>d=ABlR_Bh=;eM9Tw|Ih34~oTE|= zX_mAr*D$vzw@+p(E0Yc6dFE}(8oqt`+R{gE3x4zjX+Sb3_cYE^= zgB=w+-tUy`ytONMS8KgRef4hA?t0j zufM;t32jm~jUGrkaOInTZ`zyfns>EuS}G30LFK_G-==(f<51|K&cocp&EJ`SxAh3? zNO>#LI=^+SEu(FqJ)ynt=!~PC9bO$rzPJB=?=j6w@a-(u02P7 zaQ)#(uUl{HW%tYNS3ItC^iAtK(eKlL`f9+{bJzISE?u8_z3;~C8@FyI-5j_jy7l;W z_U#vU3hqqYU3!mrul&B+{ptt$59)uk{;_4iZQ%G|z+lhASr6|H35TBkl>gI*;nGLU zN7W-nBaM%pA0HbH8olyl&XeJ%vZoWz%6?Y=dFykl=imL}`%BMQ{Mhgd`HRoLu6e2R za__6DuR6yg#~-}Tc|Gx_{H@O0eebyMy5GmWADJlpK>kqk(fVV@r_fLLKIeS?{4e)} z^ZO;zpECde03c&XQcVB=dL;k=fP(-4`Tqa_faw4Lbua(`>RI+y?e7jKeZ#YO-C z08U9nK~#9!V*LOAKf^!*CWZk9EHFkxQ`<~>THH`ql|Cp!VAKJl4j6U7r~^hFFzSF& z2aGyk)B&Ro7SetPageControl(pageControl); ((CGUIWrappingListContainer *)control)->SetRenderOffset(offset); } + else if (type == CGUIControl::GUICONTAINER_EPGGRID) + { + control = new CGUIEPGGridContainer(parentID, id, posX, posY, width, height, orientation, scrollTime, preloadItems, timeBlocks, rulerUnit); + ((CGUIEPGGridContainer *)control)->LoadLayout(pControlNode); +// ((CGUIEPGGridContainer *)control)->LoadContent(pControlNode); /// + } else if (type == CGUIControl::GUICONTAINER_FIXEDLIST) { control = new CGUIFixedListContainer(parentID, id, posX, posY, width, height, orientation, scrollTime, preloadItems, focusPosition, iMovementRange); diff --git a/guilib/GUIEPGGridContainer.cpp b/guilib/GUIEPGGridContainer.cpp new file mode 100644 index 0000000000..cb5a74caa3 --- /dev/null +++ b/guilib/GUIEPGGridContainer.cpp @@ -0,0 +1,1745 @@ +/* +* Copyright (C) 2005-2008 Team XBMC +* http://www.xbmc.org +* +* This Program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2, or (at your option) +* any later version. +* +* This Program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with XBMC; see the file COPYING. If not, write to +* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +* http://www.gnu.org/copyleft/gpl.html +* +*/ + +#include "Key.h" +#include "GUIEPGGridContainer.h" +#include "PVRManager.h" +#include "GUIControlFactory.h" +#include "GUIListItem.h" +#include "GUIFontManager.h" +#include "utils/log.h" +#include "utils/TimeUtils.h" +#include "utils/GUIInfoManager.h" +#include "LocalizeStrings.h" + +#define SHORTGAP 5 // how many blocks is considered a short-gap in nav logic +#define MINSPERBLOCK 5 /// would be nice to offer zooming of busy schedules /// performance cost to increase resolution 5 fold? +#define BLOCKJUMP 4 // how many blocks are jumped with each analogue scroll action + +CGUIEPGGridContainer::CGUIEPGGridContainer(int parentID, int controlID, float posX, float posY, float width, + float height, ORIENTATION orientation, int scrollTime, + int preloadItems, int timeBlocks, int rulerUnit) + : CGUIControl(parentID, controlID, posX, posY, width, height) +{ + ControlType = GUICONTAINER_EPGGRID; + m_blocksPerPage = timeBlocks; + m_rulerUnit = rulerUnit; + m_channelCursor = 0; + m_blockCursor = 0; + m_channelOffset = 0; + m_blockOffset = 0; + m_channelScrollOffset = 0; + m_channelScrollSpeed = 0; + m_channelScrollLastTime = 0; + m_programmeScrollOffset = 0; + m_programmeScrollSpeed = 0; + m_programmeScrollLastTime = 0; + m_scrollTime = scrollTime ? scrollTime : 1; + m_renderTime = 0; + m_item = NULL; + m_lastItem = NULL; + m_lastChannel = NULL; + m_channelWrapAround = true; /// get from settings? + m_orientation = orientation; + m_programmeLayout = NULL; + m_focusedProgrammeLayout= NULL; + m_channelLayout = NULL; + m_focusedChannelLayout = NULL; + m_rulerLayout = NULL; + m_rulerPosX = 0; + m_rulerPosY = 0; + m_rulerHeight = 0; + m_rulerWidth = 0; + m_channelPosX = 0; + m_channelPosY = 0; + m_channelHeight = 0; + m_channelWidth = 0; + m_gridPosX = 0; + m_gridPosY = 0; + m_gridWidth = 0; + m_gridHeight = 0; + m_blockSize = 0; + m_analogScrollCount = 0; + m_cacheChannelItems = preloadItems; + m_cacheRulerItems = preloadItems; + m_cacheProgrammeItems = preloadItems; + m_gridIndex = NULL; +} + +CGUIEPGGridContainer::~CGUIEPGGridContainer(void) +{ +} + +void CGUIEPGGridContainer::Render() +{ + ValidateOffset(); + + if (m_bInvalidated) + UpdateLayout(); + + if (!m_focusedChannelLayout || !m_channelLayout || !m_rulerLayout || !m_focusedProgrammeLayout || !m_programmeLayout) + return; + + UpdateScrollOffset(); + + int chanOffset = (int)floorf(m_channelScrollOffset / m_programmeLayout->Size(m_orientation)); + int blockOffset = (int)floorf(m_programmeScrollOffset / m_blockSize); + int rulerOffset = (int)floorf(m_programmeScrollOffset / m_blockSize); + + /// Render channel names + int cacheBeforeChannel, cacheAfterChannel; + GetChannelCacheOffsets(cacheBeforeChannel, cacheAfterChannel); + + // Free memory not used on screen + if ((int)m_channelItems.size() > m_channelsPerPage + cacheBeforeChannel + cacheAfterChannel) + FreeChannelMemory(CorrectOffset(chanOffset - cacheBeforeChannel, 0), CorrectOffset(chanOffset + m_channelsPerPage + 1 + cacheAfterChannel, 0)); + + if (m_orientation == VERTICAL) + g_graphicsContext.SetClipRegion(m_channelPosX, m_channelPosY, m_channelWidth, m_gridHeight); + else + g_graphicsContext.SetClipRegion(m_channelPosX, m_channelPosY, m_gridWidth, m_channelHeight); + + CPoint originChannel = CPoint(m_channelPosX, m_channelPosY) + m_renderOffset; + float pos = (m_orientation == VERTICAL) ? originChannel.y : originChannel.x; + float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width; + + // we offset our draw position to take into account scrolling and whether or not our focused + // item is offscreen "above" the list. + float drawOffset = (chanOffset - cacheBeforeChannel) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset; + if (m_channelOffset + m_channelCursor < chanOffset) + drawOffset += m_focusedChannelLayout->Size(m_orientation) - m_channelLayout->Size(m_orientation); + pos += drawOffset; + end += cacheAfterChannel * m_channelLayout->Size(m_orientation); + + float focusedPos = 0; + CGUIListItemPtr focusedItem; + int current = chanOffset;// - cacheBeforeChannel; + while (pos < end && (int)m_channelItems.size()) + { + int itemNo = CorrectOffset(current, 0); + if (itemNo >= (int)m_channelItems.size()) + break; + bool focused = (current == m_channelOffset + m_channelCursor); + if (itemNo >= 0) + { + CGUIListItemPtr item = m_channelItems[itemNo]; + // render our item + if (focused) + { + focusedPos = pos; + focusedItem = item; + } + else + { + if (m_orientation == VERTICAL) + RenderChannelItem(originChannel.x, pos, item.get(), false); + else + RenderChannelItem(pos, originChannel.y, item.get(), false); + } + } + // increment our position + pos += focused ? m_focusedChannelLayout->Size(m_orientation) : m_channelLayout->Size(m_orientation); + current++; + } + // render focused item last so it can overlap other items + if (focusedItem) + { + if (m_orientation == VERTICAL) + RenderChannelItem(originChannel.x, focusedPos, focusedItem.get(), true); + else + RenderChannelItem(focusedPos, originChannel.y, focusedItem.get(), true); + } + g_graphicsContext.RestoreClipRegion(); + + /// Render the ruler items + g_graphicsContext.SetClipRegion(m_posX, m_posY, m_width, m_height); + CGUIListItemPtr item = m_rulerItems[0]; + g_graphicsContext.SetOrigin(m_posX, m_posY); + item->SetLabel(m_rulerItems[rulerOffset/m_rulerUnit+1]->GetLabel2()); + if (!item->GetLayout()) + { + CGUIListItemLayout *layout = new CGUIListItemLayout(*m_rulerLayout); + if (m_orientation == VERTICAL) + layout->SetWidth(m_channelWidth); + else + layout->SetHeight(m_channelHeight); + item->SetLayout(layout); + } + if (item->GetLayout()) + item->GetLayout()->Render(item.get(), m_parentID, m_renderTime); + g_graphicsContext.RestoreOrigin(); + + int cacheBeforeRuler, cacheAfterRuler; + GetRulerCacheOffsets(cacheBeforeRuler, cacheAfterRuler); + + g_graphicsContext.RestoreClipRegion(); + + // Free memory not used on screen + if ((int)m_rulerItems.size() > m_blocksPerPage + cacheBeforeRuler + cacheAfterRuler) + FreeRulerMemory(CorrectOffset(rulerOffset - cacheBeforeRuler, 0), CorrectOffset(rulerOffset + m_blocksPerPage + 1 + cacheAfterRuler, 0)); + + if (m_orientation == VERTICAL) + g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_gridWidth, m_rulerHeight); + else + g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_rulerWidth, m_gridHeight); + + CPoint originRuler = CPoint(m_rulerPosX, m_rulerPosY) + m_renderOffset; + pos = (m_orientation == VERTICAL) ? originRuler.x : originRuler.y; + end = (m_orientation == VERTICAL) ? m_posX + m_width : m_posY + m_height; + drawOffset = (rulerOffset - cacheBeforeRuler) * m_blockSize - m_programmeScrollOffset; + pos += drawOffset; + end += cacheAfterRuler * m_rulerLayout->Size(m_orientation == VERTICAL ? HORIZONTAL : VERTICAL); + + if (rulerOffset % m_rulerUnit != 0) + { + /* first ruler marker starts before current view */ + int startBlock = rulerOffset - 1; + + while (startBlock % m_rulerUnit != 0) + startBlock--; + + int missingSection = rulerOffset - startBlock; + + pos -= missingSection * m_blockSize; + } + while (pos < end && m_rulerItems.size()) + { + item = m_rulerItems[rulerOffset/m_rulerUnit+1]; + if (m_orientation == VERTICAL) + { + g_graphicsContext.SetOrigin(pos, originRuler.y); + pos += m_rulerWidth; + } + else + { + g_graphicsContext.SetOrigin(originRuler.x, pos); + pos += m_rulerHeight; + } + if (!item->GetLayout()) + { + CGUIListItemLayout *layout = new CGUIListItemLayout(*m_rulerLayout); + if (m_orientation == VERTICAL) + layout->SetWidth(m_rulerWidth); + else + layout->SetHeight(m_rulerHeight); + + item->SetLayout(layout); + } + if (item->GetLayout()) + item->GetLayout()->Render(item.get(), m_parentID, m_renderTime); + g_graphicsContext.RestoreOrigin(); + + rulerOffset += m_rulerUnit; + } + g_graphicsContext.RestoreClipRegion(); + + /// Render programmes + int cacheBeforeProgramme, cacheAfterProgramme; + GetProgrammeCacheOffsets(cacheBeforeProgramme, cacheAfterProgramme); + + // Free memory not used on screen + if ((int)m_programmeItems.size() > m_ProgrammesPerPage + cacheBeforeProgramme + cacheAfterProgramme) + FreeProgrammeMemory(CorrectOffset(blockOffset - cacheBeforeProgramme, 0), CorrectOffset(blockOffset + m_ProgrammesPerPage + 1 + cacheAfterProgramme, 0)); + + g_graphicsContext.SetClipRegion(m_gridPosX, m_gridPosY, m_gridWidth, m_gridHeight); + CPoint originProgramme = CPoint(m_gridPosX, m_gridPosY) + m_renderOffset; + float posA = (m_orientation != VERTICAL) ? originProgramme.y : originProgramme.x; + float endA = (m_orientation != VERTICAL) ? m_posY + m_height : m_posX + m_width; + float posB = (m_orientation == VERTICAL) ? originProgramme.y : originProgramme.x; + float endB = (m_orientation == VERTICAL) ? m_gridPosY + m_gridHeight : m_posX + m_width; + endA += cacheAfterProgramme * m_blockSize; + + float DrawOffsetA = blockOffset * m_blockSize - m_programmeScrollOffset; + posA += DrawOffsetA; + float DrawOffsetB = (chanOffset - cacheBeforeProgramme) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset; + posB += DrawOffsetB; + + int channel = chanOffset; + + float focusedPosX = 0; + float focusedPosY = 0; + float focusedwidth = 0; + float focusedheight = 0; + while (posB < endB && m_channelItems.size()) + { + if (channel >= (int)m_channelItems.size()) + break; + + int block = blockOffset; + float posA2 = posA; + + CGUIListItemPtr item = m_gridIndex[channel][block].item; + if (item == m_gridIndex[channel][blockOffset-1].item && blockOffset != 0) + { + /* first program starts before current view */ + int startBlock = blockOffset - 1; + while (m_gridIndex[channel][startBlock].item == item) + startBlock--; + + block = startBlock + 1; + int missingSection = blockOffset - block; + posA2 -= missingSection * m_blockSize; + } + + while (posA2 < endA && m_programmeItems.size()) // FOR EACH ITEM /////////////// + { + item = m_gridIndex[channel][block].item; + if (!item || !item.get()->IsFileItem()) + break; + + bool focused = (channel == m_channelOffset + m_channelCursor) && (item == m_gridIndex[m_channelOffset + m_channelCursor][m_blockOffset + m_blockCursor].item); + + // render our item + if (focused) + { + if (m_orientation == VERTICAL) + { + focusedPosX = posA2; + focusedPosY = posB; + } + else + { + focusedPosX = posB; + focusedPosY = posA2; + } + focusedItem = item; + focusedwidth = m_gridIndex[channel][block].width; + focusedheight = m_gridIndex[channel][block].height; + } + else + { + if (m_orientation == VERTICAL) + RenderProgrammeItem(posA2, posB, m_gridIndex[channel][block].width, m_gridIndex[channel][block].height, item.get(), focused); + else + RenderProgrammeItem(posB, posA2, m_gridIndex[channel][block].width, m_gridIndex[channel][block].height, item.get(), focused); + } + + // increment our X position + if (m_orientation == VERTICAL) + { + posA2 += m_gridIndex[channel][block].width; // assumes focused & unfocused layouts have equal length + block += (int)(m_gridIndex[channel][block].width / m_blockSize); + } + else + { + posA2 += m_gridIndex[channel][block].height; // assumes focused & unfocused layouts have equal length + block += (int)(m_gridIndex[channel][block].height / m_blockSize); + } + } + + // increment our Y position + channel++; + posB += m_orientation == VERTICAL ? m_channelHeight : m_channelWidth; + } + + // and render the focused item last (for overlapping purposes) + if (focusedItem) + RenderProgrammeItem(focusedPosX, focusedPosY, focusedwidth, focusedheight, focusedItem.get(), true); + + g_graphicsContext.RestoreClipRegion(); + + CGUIControl::Render(); +} + +void CGUIEPGGridContainer::RenderChannelItem(float posX, float posY, CGUIListItem *item, bool focused) +{ + if (!m_focusedChannelLayout || !m_channelLayout) return; + + // set the origin + g_graphicsContext.SetOrigin(posX, posY); + + if (m_bInvalidated) + item->SetInvalid(); + if (focused) + { + if (!item->GetFocusedLayout()) + { + CGUIListItemLayout *layout = new CGUIListItemLayout(*m_focusedChannelLayout); + item->SetFocusedLayout(layout); + } + if (item->GetFocusedLayout()) + { + if (item != m_lastChannel || !HasFocus()) + { + item->GetFocusedLayout()->SetFocusedItem(0); + } + if (item != m_lastChannel && HasFocus()) + { + item->GetFocusedLayout()->ResetAnimation(ANIM_TYPE_UNFOCUS); + unsigned int subItem = 1; + if (m_lastChannel && m_lastChannel->GetFocusedLayout()) + subItem = m_lastChannel->GetFocusedLayout()->GetFocusedItem(); + item->GetFocusedLayout()->SetFocusedItem(subItem ? subItem : 1); + } + item->GetFocusedLayout()->Render(item, m_parentID, m_renderTime); + } + m_lastChannel = item; + } + else + { + if (item->GetFocusedLayout()) + item->GetFocusedLayout()->SetFocusedItem(0); // focus is not set + if (!item->GetLayout()) + { + CGUIListItemLayout *layout = new CGUIListItemLayout(*m_channelLayout); + item->SetLayout(layout); + } + if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS)) + item->GetFocusedLayout()->Render(item, m_parentID, m_renderTime); + else if (item->GetLayout()) + item->GetLayout()->Render(item, m_parentID, m_renderTime); + } + g_graphicsContext.RestoreOrigin(); +} + +void CGUIEPGGridContainer::RenderProgrammeItem(float posX, float posY, float width, float height, CGUIListItem *item, bool focused) +{ + if (!m_focusedProgrammeLayout || !m_programmeLayout) return; + + // set the origin + g_graphicsContext.SetOrigin(posX, posY); + + if (m_bInvalidated) + item->SetInvalid(); + if (focused) + { + if (!item->GetFocusedLayout()) + { + CGUIListItemLayout *layout = new CGUIListItemLayout(*m_focusedProgrammeLayout); + CFileItem *fileItem = item->IsFileItem() ? (CFileItem *)item : NULL; + if (fileItem) + { + const cPVREPGInfoTag* tag = fileItem->GetEPGInfoTag(); + if (m_orientation == VERTICAL) + layout->SetWidth(width); + else + layout->SetHeight(height); + + item->SetProperty("GenreType", tag->GenreType()); + } + item->SetFocusedLayout(layout); + } + if (item->GetFocusedLayout()) + { + if (item != m_lastItem || !HasFocus()) + { + item->GetFocusedLayout()->SetFocusedItem(0); + } + if (item != m_lastItem && HasFocus()) + { + item->GetFocusedLayout()->ResetAnimation(ANIM_TYPE_UNFOCUS); + unsigned int subItem = 1; + if (m_lastItem && m_lastItem->GetFocusedLayout()) + subItem = m_lastItem->GetFocusedLayout()->GetFocusedItem(); + item->GetFocusedLayout()->SetFocusedItem(subItem ? subItem : 1); + } + item->GetFocusedLayout()->Render(item, m_parentID, m_renderTime); + } + m_lastItem = item; + } + else + { + if (item->GetFocusedLayout()) + item->GetFocusedLayout()->SetFocusedItem(0); // focus is not set + if (!item->GetLayout()) + { + CGUIListItemLayout *layout = new CGUIListItemLayout(*m_programmeLayout); + CFileItem *fileItem = item->IsFileItem() ? (CFileItem *)item : NULL; + if (fileItem) + { + const cPVREPGInfoTag* tag = fileItem->GetEPGInfoTag(); + if (m_orientation == VERTICAL) + layout->SetWidth(width); + else + layout->SetHeight(height); + + item->SetProperty("GenreType", tag->GenreType()); + } + item->SetLayout(layout); + } + if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS)) + item->GetFocusedLayout()->Render(item, m_parentID, m_renderTime); + else if (item->GetLayout()) + item->GetLayout()->Render(item, m_parentID, m_renderTime); + } + g_graphicsContext.RestoreOrigin(); +} + +bool CGUIEPGGridContainer::OnAction(const CAction &action) +{ + switch (action.GetID()) + { + case ACTION_MOVE_LEFT: + case ACTION_MOVE_RIGHT: + case ACTION_MOVE_DOWN: + case ACTION_MOVE_UP: + { // use base class implementation + + return CGUIControl::OnAction(action); + } + + break; + case ACTION_PAGE_UP: + { + if (m_orientation == VERTICAL) + { + if (m_channelOffset == 0) + { // already on the first page, so move to the first item + SetChannel(0); + } + else + { // scroll up to the previous page + ChannelScroll(-m_channelsPerPage); + } + } + else + ProgrammesScroll(-m_blocksPerPage/4); + + return true; + } + + break; + case ACTION_PAGE_DOWN: + { + if (m_orientation == VERTICAL) + { + if (m_channelOffset == m_channels - m_channelsPerPage || m_channels < m_channelsPerPage) + { // already at the last page, so move to the last item. + SetChannel(m_channels - m_channelOffset - 1); + } + else + { // scroll down to the next page + ChannelScroll(m_channelsPerPage); + } + } + else + ProgrammesScroll(m_blocksPerPage/4); + + return true; + } + + break; + + // smooth scrolling (for analog controls) + case ACTION_TELETEXT_RED: + case ACTION_TELETEXT_GREEN: + case ACTION_SCROLL_UP: // left horizontal scrolling + { + int blocksToJump = action.GetID() == ACTION_TELETEXT_RED ? m_blocksPerPage/2 : m_blocksPerPage/4; + + m_analogScrollCount += action.GetAmount() * action.GetAmount(); + bool handled = false; + + while (m_analogScrollCount > 0.4) + { + handled = true; + m_analogScrollCount -= 0.4f; + + if (m_blockOffset > 0 && m_blockCursor <= m_blocksPerPage / 2) + { + ProgrammesScroll(-blocksToJump); + } + else if (m_blockCursor > blocksToJump) + { + SetBlock(m_blockCursor - blocksToJump); + } + } + + return handled; + } + + break; + + case ACTION_TELETEXT_BLUE: + case ACTION_TELETEXT_YELLOW: + case ACTION_SCROLL_DOWN: // right horizontal scrolling + { + int blocksToJump = action.GetID() == ACTION_TELETEXT_BLUE ? m_blocksPerPage/2 : m_blocksPerPage/4; + + m_analogScrollCount += action.GetAmount() * action.GetAmount(); + bool handled = false; + + while (m_analogScrollCount > 0.4) + { + handled = true; + m_analogScrollCount -= 0.4f; + + if (m_blockOffset + m_blocksPerPage < m_blocks && m_blockCursor >= m_blocksPerPage / 2) + { + ProgrammesScroll(blocksToJump); + } + else if (m_blockCursor < m_blocksPerPage - blocksToJump && m_blockOffset + m_blockCursor < m_blocks - blocksToJump) + { + SetBlock(m_blockCursor + blocksToJump); + } + } + + return handled; + } + + break; + + default: + + if (action.GetID()) + { + return OnClick(action.GetID()); + } + } + + return false; +} + +bool CGUIEPGGridContainer::OnMessage(CGUIMessage& message) +{ + if (message.GetControlId() == GetID()) + { + if (message.GetMessage() == GUI_MSG_ITEM_SELECTED) + { + message.SetParam1(GetSelectedItem()); + return true; + } + else if (message.GetMessage() == GUI_MSG_LABEL_BIND && message.GetPointer()) + { + Reset(); + CFileItemList *items = (CFileItemList *)message.GetPointer(); + + /* Create Channel items */ + int ChannelLast = -1; + ItemsPtr itemsPointer; + itemsPointer.start = 0; + for (int i = 0; i < items->Size(); ++i) + { + const cPVREPGInfoTag* tag = items->Get(i)->GetEPGInfoTag(); + int ChannelNow = tag->ChannelNumber(); + if (ChannelNow != ChannelLast) + { + if (i > 0) + { + itemsPointer.stop = i-1; + m_epgItemsPtr.push_back(itemsPointer); + itemsPointer.start = i; + } + ChannelLast = ChannelNow; + CGUIListItemPtr item(new CFileItem(*tag->ChannelTag())); + m_channelItems.push_back(item); + } + } + if (m_epgItemsPtr.size() > 0) + { + itemsPointer.stop = items->Size()-1; + m_epgItemsPtr.push_back(itemsPointer); + } + + /* Create programme items */ + for (int i = 0; i < items->Size(); i++) + m_programmeItems.push_back(items->Get(i)); + + m_gridIndex = (struct GridItemsPtr **) calloc(1,m_channelItems.size()*sizeof(struct GridItemsPtr)); + if (m_gridIndex != NULL) + { + for (unsigned int i = 0; i < m_channelItems.size(); i++) + { + m_gridIndex[i] = (struct GridItemsPtr*) calloc(1,MAXBLOCKS*sizeof(struct GridItemsPtr)); + } + } + + UpdateLayout(true); // true to refresh all items + + /* Create Ruler items */ + CDateTime ruler = m_gridStart; + CDateTimeSpan unit(0, 0, m_rulerUnit * MINSPERBLOCK, 0); + CGUIListItemPtr rulerItem(new CFileItem(ruler.GetAsLocalizedDate(true, true))); + rulerItem->SetProperty("DateLabel", true); + m_rulerItems.push_back(rulerItem); + + for (; ruler < m_gridEnd; ruler += unit) + { + CGUIListItemPtr rulerItem(new CFileItem(ruler.GetAsLocalizedTime("", false))); + rulerItem->SetLabel2(ruler.GetAsLocalizedDate(true, true)); + m_rulerItems.push_back(rulerItem); + } + + UpdateItems(); + //SelectItem(message.GetParam1()); + return true; + } + else if (message.GetMessage() == GUI_MSG_REFRESH_LIST) + { // update our list contents + for (unsigned int i = 0; i < m_channelItems.size(); ++i) + m_channelItems[i]->SetInvalid(); + for (unsigned int i = 0; i < m_programmeItems.size(); ++i) + m_programmeItems[i]->SetInvalid(); + for (unsigned int i = 0; i < m_rulerItems.size(); ++i) + m_rulerItems[i]->SetInvalid(); + } + } + + return CGUIControl::OnMessage(message); +} + +void CGUIEPGGridContainer::UpdateItems() +{ + CDateTimeSpan blockDuration, gridDuration; + + gridDuration = m_gridEnd - m_gridStart; + + m_blocks = (gridDuration.GetDays()*24*60 + gridDuration.GetHours()*60 + gridDuration.GetMinutes()) / MINSPERBLOCK; + if (m_blocks >= MAXBLOCKS) + m_blocks = MAXBLOCKS; + + /* if less than one page, can't display grid */ + if (m_blocks < m_blocksPerPage) + { + CLog::Log(LOGERROR, "(%s) - Less than one page of data available.", __FUNCTION__); + CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), GetParentID()); // message the window + SendWindowMessage(msg); + return; + } + + blockDuration.SetDateTimeSpan(0, 0, MINSPERBLOCK, 0); + + long tick(CTimeUtils::GetTimeMS()); + + for (unsigned int row = 0; row < m_channelItems.size(); ++row) + { + CDateTime gridCursor = m_gridStart; //reset cursor for new channel + unsigned long progIdx = m_epgItemsPtr[row].start; + unsigned long lastIdx = m_epgItemsPtr[row].stop; + unsigned int channelnum = ((CFileItem *)m_programmeItems[progIdx].get())->GetEPGInfoTag()->ChannelNumber(); + + /** FOR EACH BLOCK **********************************************************************/ + + for (int block = 0; block < m_blocks; block++) + { + while (progIdx <= lastIdx) + { + CGUIListItemPtr item = m_programmeItems[progIdx]; + if (((CFileItem *)item.get())->GetEPGInfoTag()->ChannelNumber() != channelnum) + break; + + const cPVREPGInfoTag* tag = ((CFileItem *)item.get())->GetEPGInfoTag(); + if (tag == NULL) + progIdx++; + + if (m_gridEnd <= tag->Start()) + { + break; + } + else if (gridCursor >= tag->End()) + { + progIdx++; + } + else if (gridCursor < tag->End()) + { + m_gridIndex[row][block].item = item; + break; + } + else + { + progIdx++; + } + } + + gridCursor += blockDuration; + } + + /** FOR EACH BLOCK **********************************************************************/ + int itemSize = 1; // size of the programme in blocks + int savedBlock = 0; + + for (int block = 0; block < m_blocks; block++) + { + if (m_gridIndex[row][block].item != m_gridIndex[row][block+1].item) + { + if (!m_gridIndex[row][block].item) + { + cPVREPGInfoTag broadcast(NULL); + CFileItemPtr unknown(new CFileItem(broadcast)); + for (int i = block ; i > block - itemSize; i--) + { + m_gridIndex[row][i].item = unknown; + } + } + + CGUIListItemPtr item = m_gridIndex[row][block].item; + CFileItem *fileItem = (CFileItem *)item.get(); + + m_gridIndex[row][savedBlock].item->SetProperty("GenreType", fileItem->GetEPGInfoTag()->GenreType()); + if (m_orientation == VERTICAL) + { + m_gridIndex[row][savedBlock].width = itemSize*m_blockSize; + m_gridIndex[row][savedBlock].height = m_channelHeight; + } + else + { + m_gridIndex[row][savedBlock].width = m_channelWidth; + m_gridIndex[row][savedBlock].height = itemSize*m_blockSize; + } + + itemSize = 1; + savedBlock = block+1; + } + else + { + itemSize++; + } + } + } + + /******************************************* END ******************************************/ + + CLog::Log(LOGDEBUG, "%s completed successfully in %u ms", __FUNCTION__, (unsigned int)(CTimeUtils::GetTimeMS()-tick)); + + m_channels = (int)m_epgItemsPtr.size(); + m_item = GetItem(m_channelCursor); + m_blockCursor = GetBlock(m_item->item, m_channelCursor); + + SetInvalid(); +} + +void CGUIEPGGridContainer::ChannelScroll(int amount) +{ + // increase or decrease the vertical offset + int offset = m_channelOffset + amount; + + if (offset > m_channels - m_channelsPerPage) + { + offset = m_channels - m_channelsPerPage; + } + + if (offset < 0) offset = 0; + + ScrollToChannelOffset(offset); +} + +void CGUIEPGGridContainer::ProgrammesScroll(int amount) +{ + // increase or decrease the horizontal offset + int offset = m_blockOffset + amount; + + if (offset > m_blocks - m_blocksPerPage) + { + offset = m_blocks - m_blocksPerPage; + } + + if (offset < 0) offset = 0; + + ScrollToBlockOffset(offset); +} + +bool CGUIEPGGridContainer::MoveChannel(bool direction) +{ + if (direction) + { + if (m_channelCursor > 0) + { + SetChannel(m_channelCursor - 1); + } + else if (m_channelCursor == 0 && m_channelOffset) + { + ScrollToChannelOffset(m_channelOffset - 1); + SetChannel(0); + } + else if (m_channelWrapAround) + { + int offset = m_channels - m_channelsPerPage; + + if (offset < 0) offset = 0; + + SetChannel(m_channels - offset - 1); + + ScrollToChannelOffset(offset); + } + else + return false; + } + else + { + if (m_channelOffset + m_channelCursor + 1 < m_channels) + { + if (m_channelCursor + 1 < m_channelsPerPage) + { + SetChannel(m_channelCursor + 1); + } + else + { + ScrollToChannelOffset(m_channelOffset + 1); + SetChannel(m_channelsPerPage - 1); + } + } + else if (m_channelWrapAround) + { + SetChannel(0); + ScrollToChannelOffset(0); + } + else + return false; + } + return true; +} + +bool CGUIEPGGridContainer::MoveProgrammes(bool direction) +{ + if (direction) + { + if (m_item->item != m_gridIndex[m_channelCursor + m_channelOffset][m_blockOffset].item) + { + // this is not first item on page + m_item = GetPrevItem(m_channelCursor); + m_blockCursor = GetBlock(m_item->item, m_channelCursor); + } + else if (m_blockCursor <= 0 && m_blockOffset) + { + // we're at the left edge and offset + int itemSize = GetItemSize(m_item); + int block = GetRealBlock(m_item->item, m_channelCursor); + + if (block < m_blockOffset) /* current item begins before current offset, keep selected */ + { + if (itemSize > m_blocksPerPage) /* current item is longer than one page, scroll one page left */ + { + m_blockOffset < m_blocksPerPage ? block = 0 : block = m_blockOffset - m_blocksPerPage; // number blocks left < m_blocksPerPAge + ScrollToBlockOffset(block); + SetBlock(0); + } + else /* current item is shorter than one page, scroll left to start of item */ + { + ScrollToBlockOffset(block); // -1? + SetBlock(0); // align cursor to left edge + } + } + else /* current item starts on this page's edge, select the previous item */ + { + m_item = GetPrevItem(m_channelCursor); + itemSize = GetItemSize(m_item); + + if (itemSize > m_blocksPerPage) // previous item is longer than one page, scroll left to last page of item */ + { + ScrollToBlockOffset(m_blockOffset - m_blocksPerPage); // left one whole page + //SetBlock(m_blocksPerPage -1 ); // helps navigation by setting cursor to far right edge + SetBlock(0); // align cursor to left edge + } + else /* previous item is shorter than one page, scroll left to start of item */ + { + ScrollToBlockOffset(m_blockOffset - itemSize); + SetBlock(0); //should be zero + } + } + } + else + return false; + } + else + { + if (m_item->item != m_gridIndex[m_channelCursor + m_channelOffset][m_blocksPerPage + m_blockOffset - 1].item) + { + // this is not last item on page + m_item = GetNextItem(m_channelCursor); + m_blockCursor = GetBlock(m_item->item, m_channelCursor); + } + else if ((m_blockOffset != m_blocks - m_blocksPerPage) && m_blocks > m_blocksPerPage) + { + // at right edge, more than one page and not at maximum offset + int itemSize = GetItemSize(m_item); + int block = GetRealBlock(m_item->item, m_channelCursor); + + if (itemSize > m_blocksPerPage - m_blockCursor) // current item extends into next page, keep selected + { + if (itemSize > m_blocksPerPage) // current item is longer than one page, scroll one page right + { + if (m_blockOffset && m_blockOffset + m_blocksPerPage > m_blocks) + block = m_blocks - m_blocksPerPage; + else + block = m_blockOffset + m_blocksPerPage; + + ScrollToBlockOffset(block); + + SetBlock(0); + } + else // current item is shorter than one page, scroll so end of item sits on end of grid + { + ScrollToBlockOffset(block + itemSize - m_blocksPerPage); + SetBlock(GetBlock(m_item->item, m_channelCursor)); /// change to middle block of item? + } + } + else // current item finishes on this page's edge, select the next item + { + m_item = GetNextItem(m_channelCursor); + itemSize = GetItemSize(m_item); + + if (itemSize > m_blocksPerPage) // next item is longer than one page, scroll to first page of this item + { + ScrollToBlockOffset(m_blockOffset + m_blocksPerPage); + SetBlock(0); + } + else // next item is shorter than one page, scroll so end of item sits on end of grid + { + ScrollToBlockOffset(m_blockOffset + itemSize); + SetBlock(m_blocksPerPage - itemSize); /// change to middle block of item? + } + } + } + else + return false; + } + return true; +} + +void CGUIEPGGridContainer::OnUp() +{ + if (m_orientation == VERTICAL) + { + if (!MoveChannel(true)) + CGUIControl::OnUp(); + } + else + { + if (!MoveProgrammes(true)) + CGUIControl::OnUp(); + } +} + +void CGUIEPGGridContainer::OnDown() +{ + if (m_orientation == VERTICAL) + { + if (!MoveChannel(false)) + CGUIControl::OnDown(); + } + else + { + if (!MoveProgrammes(false)) + CGUIControl::OnDown(); + } +} + +void CGUIEPGGridContainer::OnLeft() +{ + if (m_orientation == VERTICAL) + { + if (!MoveProgrammes(true)) + CGUIControl::OnLeft(); + } + else + { + if (!MoveChannel(true)) + CGUIControl::OnLeft(); + } +} + +void CGUIEPGGridContainer::OnRight() +{ + if (m_orientation == VERTICAL) + { + if (!MoveProgrammes(false)) + CGUIControl::OnRight(); + } + else + { + if (!MoveChannel(false)) + CGUIControl::OnRight(); + } +} + +void CGUIEPGGridContainer::SetChannel(int channel) +{ + if (m_blockCursor + m_blockOffset == 0 || m_blockOffset + m_blockCursor + GetItemSize(m_item) == m_blocks) + { + m_item = GetItem(channel); + m_blockCursor = GetBlock(m_item->item, channel); + m_channelCursor = channel; + return; + } + + /* basic checks failed, need to correctly identify nearest item */ + m_item = GetClosestItem(channel); + m_channelCursor = channel; + m_blockCursor = GetBlock(m_item->item, m_channelCursor); +} + +void CGUIEPGGridContainer::SetBlock(int block) +{ + m_blockCursor = block; + m_item = GetItem(m_channelCursor); +} + +CGUIListItemLayout *CGUIEPGGridContainer::GetFocusedLayout() const +{ + CGUIListItemPtr item = GetListItem(0); + + if (item.get()) return item->GetFocusedLayout(); + + return NULL; +} + +bool CGUIEPGGridContainer::SelectItemFromPoint(const CPoint &point) +{ + /* point has already had origin set to m_posX, m_posY */ + if (!m_focusedProgrammeLayout || !m_programmeLayout) + return false; + + int channel = (int)(point.y / m_channelHeight); + int block = (int)(point.x / m_blockSize); + + if (channel > m_channelsPerPage) channel = m_channelsPerPage - 1; + if (channel < 0) channel = 0; + if (block > m_blocksPerPage) block = m_blocksPerPage - 1; + if (block < 0) block = 0; + + SetChannel(channel); + SetBlock(block); + return true; +} + +bool CGUIEPGGridContainer::OnMouseOver(const CPoint &point) +{ + // select the item under the pointer + SelectItemFromPoint(point - CPoint(m_gridPosX, m_posY + m_rulerHeight)); + return CGUIControl::OnMouseOver(point); +} + +bool CGUIEPGGridContainer::OnMouseClick(int dwButton, const CPoint &point) +{ + if (SelectItemFromPoint(point - CPoint(m_gridPosX, m_posY + m_rulerHeight))) + { // send click message to window + OnClick(ACTION_MOUSE_LEFT_CLICK + dwButton); + return true; + } + + return false; +} + +bool CGUIEPGGridContainer::OnMouseDoubleClick(int dwButton, const CPoint &point) +{ + if (SelectItemFromPoint(point - CPoint(m_gridPosX, m_posY + m_rulerHeight))) + { // send double click message to window + OnClick(ACTION_MOUSE_DOUBLE_CLICK + dwButton); + return true; + } + + return false; +} + +bool CGUIEPGGridContainer::OnClick(int actionID) +{ + int subItem = 0; + + if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK) + { + // grab the currently focused subitem (if applicable) + CGUIListItemLayout *focusedLayout = GetFocusedLayout(); + + if (focusedLayout) + subItem = focusedLayout->GetFocusedItem(); + } + + // Don't know what to do, so send to our parent window. + CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID(), actionID, subItem); + return SendWindowMessage(msg); +} + +bool CGUIEPGGridContainer::OnMouseWheel(char wheel, const CPoint &point) +{ + ///doesn't work while an item is selected? + ProgrammesScroll(-wheel); + return true; +} + +int CGUIEPGGridContainer::GetSelectedItem() const +{ + CGUIListItemPtr currentItem = m_gridIndex[m_channelCursor + m_channelOffset][m_blockCursor + m_blockOffset].item; /// + for (int i = 0; i < (int)m_programmeItems.size(); i++) + { + if (currentItem == m_programmeItems[i]) + return i; + } + return 0; +} + +CGUIListItemPtr CGUIEPGGridContainer::GetListItem(int offset) const +{ + if (!m_epgItemsPtr.size()) + return CGUIListItemPtr(); + + return m_item->item; +} + +GridItemsPtr *CGUIEPGGridContainer::GetClosestItem(const int &channel) +{ + GridItemsPtr *closest = GetItem(channel); + int block = GetBlock(closest->item, channel); + int left; // num blocks to start of previous item + int right; // num blocks to start of next item + + if (block == m_blockCursor) + return closest; // item & m_item start together + + if (block + GetItemSize(closest) == m_blockCursor + GetItemSize(m_item)) + return closest; // closest item ends when current does + + if (block > m_blockCursor) // item starts after m_item + { + left = m_blockCursor - GetBlock(closest->item, channel); + right = block - m_blockCursor; + } + else + { + left = m_blockCursor - block; + right = GetBlock(GetNextItem(channel)->item, channel) - m_blockCursor; + } + + if (right <= SHORTGAP && right <= left && m_blockCursor + right < m_blocksPerPage) + return &m_gridIndex[channel + m_channelOffset][m_blockCursor + right + m_blockOffset]; + + return &m_gridIndex[channel + m_channelOffset][m_blockCursor - left + m_blockOffset]; +} + +int CGUIEPGGridContainer::GetItemSize(GridItemsPtr *item) +{ + if (!item) + return m_blockSize; /// stops it crashing + + return (m_orientation == VERTICAL ? item->width : item->height) / m_blockSize; +} + +int CGUIEPGGridContainer::GetBlock(const CGUIListItemPtr &item, const int &channel) +{ + return GetRealBlock(item, channel) - m_blockOffset; +} + +int CGUIEPGGridContainer::GetRealBlock(const CGUIListItemPtr &item, const int &channel) +{ + int block = 0; + + while (m_gridIndex[channel + m_channelOffset][block].item != item && block < m_blocks) + block++; + + return block; +} + +GridItemsPtr *CGUIEPGGridContainer::GetNextItem(const int &channel) +{ + int i = m_blockCursor; + + while (m_gridIndex[channel + m_channelOffset][i + m_blockOffset].item == m_gridIndex[channel + m_channelOffset][m_blockCursor + m_blockOffset].item && i < m_blocksPerPage) + i++; + + return &m_gridIndex[channel + m_channelOffset][i + m_blockOffset]; +} + +GridItemsPtr *CGUIEPGGridContainer::GetPrevItem(const int &channel) +{ + int i = m_blockCursor; + + while (m_gridIndex[channel + m_channelOffset][i + m_blockOffset].item == m_gridIndex[channel + m_channelOffset][m_blockCursor + m_blockOffset].item && i > 0) + i--; + + return &m_gridIndex[channel + m_channelOffset][i + m_blockOffset]; + +// return &m_gridIndex[channel + m_channelOffset][m_blockCursor + m_blockOffset - 1]; +} + +GridItemsPtr *CGUIEPGGridContainer::GetItem(const int &channel) +{ + return &m_gridIndex[channel + m_channelOffset][m_blockCursor + m_blockOffset]; +} + +void CGUIEPGGridContainer::SetFocus(bool bOnOff) +{ + if (bOnOff != HasFocus()) + { + SetInvalid(); + /*m_lastItem.reset(); + m_lastChannel.reset();*/ + } + + CGUIControl::SetFocus(bOnOff); +} + +void CGUIEPGGridContainer::DoRender(unsigned int currentTime) +{ + m_renderTime = currentTime; + CGUIControl::DoRender(currentTime); + m_wasReset = false; +} + +void CGUIEPGGridContainer::ScrollToChannelOffset(int offset) +{ + float size = m_programmeLayout->Size(VERTICAL); + int range = m_channelsPerPage / 4; + + if (range <= 0) range = 1; + + if (offset * size < m_channelScrollOffset && m_channelScrollOffset - offset * size > size * range) + { // scrolling up, and we're jumping more than 0.5 of a screen + m_channelScrollOffset = (offset + range) * size; + } + + if (offset * size > m_channelScrollOffset && offset * size - m_channelScrollOffset > size * range) + { // scrolling down, and we're jumping more than 0.5 of a screen + m_channelScrollOffset = (offset - range) * size; + } + + m_channelScrollSpeed = (offset * size - m_channelScrollOffset) / m_scrollTime; + + m_channelOffset = offset; +} + +void CGUIEPGGridContainer::ScrollToBlockOffset(int offset) +{ + float size = m_blockSize; + int range = m_blocksPerPage / 1; + + if (range <= 0) range = 1; + + if (offset * size < m_programmeScrollOffset && m_programmeScrollOffset - offset * size > size * range) + { // scrolling left, and we're jumping more than 0.5 of a screen + m_programmeScrollOffset = (offset + range) * size; + } + + if (offset * size > m_programmeScrollOffset && offset * size - m_programmeScrollOffset > size * range) + { // scrolling right, and we're jumping more than 0.5 of a screen + m_programmeScrollOffset = (offset - range) * size; + } + + m_programmeScrollSpeed = (offset * size - m_programmeScrollOffset) / m_scrollTime; + + m_blockOffset = offset; +} + +void CGUIEPGGridContainer::ValidateOffset() +{ + if (!m_programmeLayout) + return; + + if (m_channelOffset > m_channels - m_channelsPerPage) + { + m_channelOffset = m_channels - m_channelsPerPage; + m_channelScrollOffset = m_channelOffset * m_channelHeight; + } + + if (m_channelOffset < 0) + { + m_channelOffset = 0; + m_channelScrollOffset = 0; + } + + if (m_blockOffset > m_blocks - m_blocksPerPage) + { + m_blockOffset = m_blocks - m_blocksPerPage; + m_programmeScrollOffset = m_blockOffset * m_blockSize; + } + + if (m_blockOffset < 0) + { + m_blockOffset = 0; + m_programmeScrollOffset = 0; + } +} + +void CGUIEPGGridContainer::LoadLayout(TiXmlElement *layout) +{ + /* layouts for the channel column */ + TiXmlElement *itemElement = layout->FirstChildElement("channellayout"); + while (itemElement) + { // we have a new item layout + CGUIListItemLayout itemLayout; + itemLayout.LoadLayout(itemElement, false); + m_channelLayouts.push_back(itemLayout); + itemElement = itemElement->NextSiblingElement("channellayout"); + } + itemElement = layout->FirstChildElement("focusedchannellayout"); + while (itemElement) + { // we have a new item layout + CGUIListItemLayout itemLayout; + itemLayout.LoadLayout(itemElement, true); + m_focusedChannelLayouts.push_back(itemLayout); + itemElement = itemElement->NextSiblingElement("focusedchannellayout"); + } + + /* layouts for the grid items */ + itemElement = layout->FirstChildElement("focusedlayout"); + while (itemElement) + { + CGUIListItemLayout itemLayout; + itemLayout.LoadLayout(itemElement, true); + m_focusedProgrammeLayouts.push_back(itemLayout); + itemElement = itemElement->NextSiblingElement("focusedlayout"); + } + itemElement = layout->FirstChildElement("itemlayout"); + while (itemElement) + { + CGUIListItemLayout itemLayout; + itemLayout.LoadLayout(itemElement, false); + m_programmeLayouts.push_back(itemLayout); + itemElement = itemElement->NextSiblingElement("itemlayout"); + } + + /* layout for the timeline above the grid */ + itemElement = layout->FirstChildElement("rulerlayout"); + while (itemElement) + { + CGUIListItemLayout itemLayout; + itemLayout.LoadLayout(itemElement, false); + m_rulerLayouts.push_back(itemLayout); + itemElement = itemElement->NextSiblingElement("rulerlayout"); + } +} + +void CGUIEPGGridContainer::UpdateLayout(bool updateAllItems) +{ + // if container is invalid, either new data has arrived, or m_blockSize has changed + // need to run UpdateItems rather than CalculateLayout? + if (updateAllItems) + { // free memory of items + for (iItems it = m_channelItems.begin(); it != m_channelItems.end(); it++) + (*it)->FreeMemory(); + for (iItems it = m_rulerItems.begin(); it != m_rulerItems.end(); it++) + (*it)->FreeMemory(); + for (iItems it = m_programmeItems.begin(); it != m_programmeItems.end(); it++) + (*it)->FreeMemory(); + } + + // and recalculate the layout + CalculateLayout(); +} + +CStdString CGUIEPGGridContainer::GetDescription() const +{ + CStdString strLabel; + int item = GetSelectedItem(); + if (item >= 0 && item < (int)m_programmeItems.size()) + { + CGUIListItemPtr pItem = m_programmeItems[item]; + strLabel = pItem->GetLabel(); + } + return strLabel; +} + +void CGUIEPGGridContainer::Reset() +{ + for (unsigned int i = 0; i < m_channelItems.size(); i++) + free(m_gridIndex[i]); + free(m_gridIndex); + + m_wasReset = true; + m_channelItems.clear(); + m_programmeItems.clear(); + m_rulerItems.clear(); + m_epgItemsPtr.clear(); + + m_lastItem = NULL; + m_lastChannel = NULL; +} + +void CGUIEPGGridContainer::GoToBegin() +{ + ScrollToBlockOffset(0); +} + +void CGUIEPGGridContainer::GoToEnd() +{ + ScrollToBlockOffset(m_blocks - m_blocksPerPage); +} + +void CGUIEPGGridContainer::SetStartEnd(CDateTime start, CDateTime end) +{ + m_gridStart = start; + m_gridEnd = end; +} + +void CGUIEPGGridContainer::CalculateLayout() +{ + CGUIListItemLayout *oldFocusedChannelLayout = m_focusedChannelLayout; + CGUIListItemLayout *oldChannelLayout = m_channelLayout; + CGUIListItemLayout *oldFocusedProgrammeLayout = m_focusedProgrammeLayout; + CGUIListItemLayout *oldProgrammeLayout = m_programmeLayout; + CGUIListItemLayout *oldRulerLayout = m_rulerLayout; + GetCurrentLayouts(); + + if (!m_focusedProgrammeLayout || !m_programmeLayout || !m_focusedChannelLayout || !m_channelLayout || !m_rulerLayout) + return; + + if (oldChannelLayout == m_channelLayout && oldFocusedChannelLayout == m_focusedChannelLayout && + oldProgrammeLayout == m_programmeLayout && oldFocusedProgrammeLayout == m_focusedProgrammeLayout && + oldRulerLayout == m_rulerLayout) + return; // nothing has changed, so don't update stuff + + m_channelHeight = m_channelLayout->Size(VERTICAL); + m_channelWidth = m_channelLayout->Size(HORIZONTAL); + if (m_orientation == VERTICAL) + { + m_rulerHeight = m_rulerLayout->Size(VERTICAL); + m_gridPosX = m_posX + m_channelWidth; + m_gridPosY = m_posY + m_rulerHeight; + m_gridWidth = m_width - m_channelWidth; + m_gridHeight = m_height - m_rulerHeight; + m_blockSize = m_gridWidth / m_blocksPerPage; + m_rulerWidth = m_rulerUnit * m_blockSize; + m_channelPosX = m_posX; + m_channelPosY = m_posY + m_rulerHeight; + m_rulerPosX = m_posX + m_channelWidth; + m_rulerPosY = m_posY; + m_channelsPerPage = (int)(m_gridHeight / m_channelHeight); + m_ProgrammesPerPage = (int)(m_gridWidth / m_blockSize) + 1; + } + else + { + m_rulerWidth = m_rulerLayout->Size(HORIZONTAL); + m_gridPosX = m_posX + m_rulerWidth; + m_gridPosY = m_posY + m_channelHeight; + m_gridWidth = m_width - m_rulerWidth; + m_gridHeight = m_height - m_channelHeight; + m_blockSize = m_gridHeight / m_blocksPerPage; + m_rulerHeight = m_rulerUnit * m_blockSize; + m_channelPosX = m_posX + m_rulerWidth; + m_channelPosY = m_posY; + m_rulerPosX = m_posX; + m_rulerPosY = m_posY + m_channelHeight; + m_channelsPerPage = (int)(m_gridWidth / m_channelWidth); + m_ProgrammesPerPage = (int)(m_gridHeight / m_blockSize) + 1; + } + + // ensure that the scroll offsets are a multiple of our sizes + m_channelScrollOffset = m_channelOffset * m_programmeLayout->Size(m_orientation); + m_programmeScrollOffset = m_blockOffset * m_blockSize; +} + +void CGUIEPGGridContainer::UpdateScrollOffset() +{ + m_channelScrollOffset += m_channelScrollSpeed * (m_renderTime - m_channelScrollLastTime); + if ((m_channelScrollSpeed < 0 && m_channelScrollOffset < m_channelOffset * m_programmeLayout->Size(m_orientation)) || + (m_channelScrollSpeed > 0 && m_channelScrollOffset > m_channelOffset * m_programmeLayout->Size(m_orientation))) + { + m_channelScrollOffset = m_channelOffset * m_programmeLayout->Size(m_orientation); + m_channelScrollSpeed = 0; + } + m_channelScrollLastTime = m_renderTime; + + m_programmeScrollOffset += m_programmeScrollSpeed * (m_renderTime - m_programmeScrollLastTime); + if ((m_programmeScrollSpeed < 0 && m_programmeScrollOffset < m_blockOffset * m_blockSize) || + (m_programmeScrollSpeed > 0 && m_programmeScrollOffset > m_blockOffset * m_blockSize)) + { + m_programmeScrollOffset = m_blockOffset * m_blockSize; + m_programmeScrollSpeed = 0; + } + m_programmeScrollLastTime = m_renderTime; +} + +void CGUIEPGGridContainer::GetCurrentLayouts() +{ + m_channelLayout = NULL; + for (unsigned int i = 0; i < m_channelLayouts.size(); i++) + { + int condition = m_channelLayouts[i].GetCondition(); + if (!condition || g_infoManager.GetBool(condition, GetParentID())) + { + m_channelLayout = &m_channelLayouts[i]; + break; + } + } + if (!m_channelLayout && m_channelLayouts.size()) + m_channelLayout = &m_channelLayouts[0]; // failsafe + + m_focusedChannelLayout = NULL; + for (unsigned int i = 0; i < m_focusedChannelLayouts.size(); i++) + { + int condition = m_focusedChannelLayouts[i].GetCondition(); + if (!condition || g_infoManager.GetBool(condition, GetParentID())) + { + m_focusedChannelLayout = &m_focusedChannelLayouts[i]; + break; + } + } + if (!m_focusedChannelLayout && m_focusedChannelLayouts.size()) + m_focusedChannelLayout = &m_focusedChannelLayouts[0]; // failsafe + + m_programmeLayout = NULL; + for (unsigned int i = 0; i < m_programmeLayouts.size(); i++) + { + int condition = m_programmeLayouts[i].GetCondition(); + if (!condition || g_infoManager.GetBool(condition, GetParentID())) + { + m_programmeLayout = &m_programmeLayouts[i]; + break; + } + } + if (!m_programmeLayout && m_programmeLayouts.size()) + m_programmeLayout = &m_programmeLayouts[0]; // failsafe + + m_focusedProgrammeLayout = NULL; + for (unsigned int i = 0; i < m_focusedProgrammeLayouts.size(); i++) + { + int condition = m_focusedProgrammeLayouts[i].GetCondition(); + if (!condition || g_infoManager.GetBool(condition, GetParentID())) + { + m_focusedProgrammeLayout = &m_focusedProgrammeLayouts[i]; + break; + } + } + if (!m_focusedProgrammeLayout && m_focusedProgrammeLayouts.size()) + m_focusedProgrammeLayout = &m_focusedProgrammeLayouts[0]; // failsafe + + m_rulerLayout = NULL; + for (unsigned int i = 0; i < m_rulerLayouts.size(); i++) + { + int condition = m_rulerLayouts[i].GetCondition(); + if (!condition || g_infoManager.GetBool(condition, GetParentID())) + { + m_rulerLayout = &m_rulerLayouts[i]; + break; + } + } + if (!m_rulerLayout && m_rulerLayouts.size()) + m_rulerLayout = &m_rulerLayouts[0]; // failsafe +} + +int CGUIEPGGridContainer::CorrectOffset(int offset, int cursor) const +{ + return offset + cursor; +} + +void CGUIEPGGridContainer::SetRenderOffset(const CPoint &offset) +{ + m_renderOffset = offset; +} + +void CGUIEPGGridContainer::FreeChannelMemory(int keepStart, int keepEnd) +{ + if (keepStart < keepEnd) + { // remove before keepStart and after keepEnd + for (int i = 0; i < keepStart && i < (int)m_channelItems.size(); ++i) + m_channelItems[i]->FreeMemory(); + for (int i = keepEnd + 1; i < (int)m_channelItems.size(); ++i) + m_channelItems[i]->FreeMemory(); + } + else + { // wrapping + for (int i = keepEnd + 1; i < keepStart && i < (int)m_channelItems.size(); ++i) + m_channelItems[i]->FreeMemory(); + } +} + +void CGUIEPGGridContainer::FreeProgrammeMemory(int keepStart, int keepEnd) +{ + if (keepStart < keepEnd) + { // remove before keepStart and after keepEnd + for (unsigned int i = 0; i < m_epgItemsPtr.size(); i++) + { + unsigned long progIdx = m_epgItemsPtr[i].start; + unsigned long lastIdx = m_epgItemsPtr[i].stop; + + for (unsigned int j = progIdx; j < keepStart+progIdx && j < lastIdx; ++j) + m_programmeItems[j]->FreeMemory(); + for (unsigned int j = keepEnd+progIdx + 1; j < lastIdx; ++j) + m_programmeItems[j]->FreeMemory(); + } + } + else + { // wrapping + for (unsigned int i = 0; i < m_epgItemsPtr.size(); i++) + { + unsigned long progIdx = m_epgItemsPtr[i].start; + unsigned long lastIdx = m_epgItemsPtr[i].stop; + + for (unsigned int j = keepEnd+progIdx + 1; j < keepStart+progIdx && j < lastIdx; ++j) + m_programmeItems[j]->FreeMemory(); + } + } +} + +void CGUIEPGGridContainer::FreeRulerMemory(int keepStart, int keepEnd) +{ + if (keepStart < keepEnd) + { // remove before keepStart and after keepEnd + for (int i = 1; i < keepStart && i < (int)m_rulerItems.size(); ++i) + m_rulerItems[i]->FreeMemory(); + for (int i = keepEnd + 1; i < (int)m_rulerItems.size(); ++i) + m_rulerItems[i]->FreeMemory(); + } + else + { // wrapping + for (int i = keepEnd + 1; i < keepStart && i < (int)m_rulerItems.size(); ++i) + { + if (i == 0) + continue; + m_rulerItems[i]->FreeMemory(); + } + } +} + +void CGUIEPGGridContainer::GetChannelCacheOffsets(int &cacheBefore, int &cacheAfter) +{ + if (m_channelScrollSpeed > 0) + { + cacheBefore = 0; + cacheAfter = m_cacheChannelItems; + } + else if (m_channelScrollSpeed < 0) + { + cacheBefore = m_cacheChannelItems; + cacheAfter = 0; + } + else + { + cacheBefore = m_cacheChannelItems / 2; + cacheAfter = m_cacheChannelItems / 2; + } +} + +void CGUIEPGGridContainer::GetProgrammeCacheOffsets(int &cacheBefore, int &cacheAfter) +{ + if (m_programmeScrollSpeed > 0) + { + cacheBefore = 0; + cacheAfter = m_cacheProgrammeItems; + } + else if (m_programmeScrollSpeed < 0) + { + cacheBefore = m_cacheProgrammeItems; + cacheAfter = 0; + } + else + { + cacheBefore = m_cacheProgrammeItems / 2; + cacheAfter = m_cacheProgrammeItems / 2; + } +} + +void CGUIEPGGridContainer::GetRulerCacheOffsets(int &cacheBefore, int &cacheAfter) +{ + if (m_programmeScrollSpeed > 0) + { + cacheBefore = 0; + cacheAfter = m_cacheRulerItems; + } + else if (m_programmeScrollSpeed < 0) + { + cacheBefore = m_cacheRulerItems; + cacheAfter = 0; + } + else + { + cacheBefore = m_cacheRulerItems / 2; + cacheAfter = m_cacheRulerItems / 2; + } +} diff --git a/guilib/GUIEPGGridContainer.h b/guilib/GUIEPGGridContainer.h new file mode 100644 index 0000000000..e797d2b5f6 --- /dev/null +++ b/guilib/GUIEPGGridContainer.h @@ -0,0 +1,215 @@ +#pragma once + +/* + * Copyright (C) 2005-2008 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "DateTime.h" +#include "FileItem.h" +#include "GUIControl.h" +#include "GUIListItemLayout.h" + +#define MAXCHANNELS 20 +#define MAXBLOCKS 2304 //! !!_EIGHT_!! days of 5 minute blocks + +struct GridItemsPtr +{ + CGUIListItemPtr item; + float width; + float height; +}; + +class CGUIEPGGridContainer : public CGUIControl +{ +public: + CGUIEPGGridContainer(int parentID, int controlID, float posX, float posY, float width, float height, + ORIENTATION orientation, int scrollTime, int preloadItems, int minutesPerPage, + int rulerUnit); + virtual ~CGUIEPGGridContainer(void); + virtual CGUIEPGGridContainer *Clone() const { return new CGUIEPGGridContainer(*this); }; + + virtual bool OnAction(const CAction &action); + virtual void OnDown(); + virtual void OnUp(); + virtual void OnLeft(); + virtual void OnRight(); + virtual bool OnMouseOver(const CPoint &point); + virtual bool OnMouseClick(int dwButton, const CPoint &point); + virtual bool OnMouseDoubleClick(int dwButton, const CPoint &point); + virtual bool OnMouseWheel(char wheel, const CPoint &point); + virtual bool OnMessage(CGUIMessage& message); + virtual void SetFocus(bool bOnOff); + + virtual CStdString GetDescription() const; + const int GetNumChannels() { return m_channels; }; + virtual int GetSelectedItem() const; + const int GetSelectedChannel() { return m_channelCursor + m_channelOffset; } + + void DoRender(unsigned int currentTime); + void Render(); + void LoadLayout(TiXmlElement *layout); + void LoadContent(TiXmlElement *content); + + virtual bool IsContainer() const { return true; }; + CGUIListItemPtr GetListItem(int offset) const; + + virtual int CorrectOffset(int offset, int cursor) const; + + /*! \brief Set the offset of the first item in the container from the container's position + Useful for lists/panels where the focused item may be larger than the non-focused items and thus + normally cut off from the clipping window defined by the container's position + size. + \param offset CPoint holding the offset in skin coordinates. + */ + void SetRenderOffset(const CPoint &offset); + + void GoToBegin(); + void GoToEnd(); + void SetStartEnd(CDateTime start, CDateTime end); + +protected: + bool OnClick(int actionID); + bool SelectItemFromPoint(const CPoint &point); + + void UpdateItems(); + + void SetChannel(int channel); + void SetBlock(int block); + void ChannelScroll(int amount); + void ProgrammesScroll(int amount); + void ValidateOffset(); + void UpdateLayout(bool refreshAllItems = false); + void CalculateLayout(); + void Reset(); + + GridItemsPtr *GetItem(const int &channel); + GridItemsPtr *GetNextItem(const int &channel); + GridItemsPtr *GetPrevItem(const int &channel); + GridItemsPtr *GetClosestItem(const int &channel); + + int GetItemSize(GridItemsPtr *item); + int GetBlock(const CGUIListItemPtr &item, const int &channel); + int GetRealBlock(const CGUIListItemPtr &item, const int &channel); + void MoveToRow(int row); + bool MoveChannel(bool direction); + bool MoveProgrammes(bool direction); + + CGUIListItemLayout *GetFocusedLayout() const; + + void ScrollToBlockOffset(int offset); + void ScrollToChannelOffset(int offset); + void UpdateScrollOffset(); + void RenderChannelItem(float posX, float posY, CGUIListItem *item, bool focused); + void RenderProgrammeItem(float posX, float posY, float width, float height, CGUIListItem *item, bool focused); + void GetCurrentLayouts(); + + CPoint m_renderOffset; ///< \brief render offset of the first item in the list \sa SetRenderOffset + + ORIENTATION m_orientation; + + struct ItemsPtr + { + long start; + long stop; + }; + std::vector< ItemsPtr > m_epgItemsPtr; + std::vector< CGUIListItemPtr > m_channelItems; + std::vector< CGUIListItemPtr > m_rulerItems; + std::vector< CGUIListItemPtr > m_programmeItems; + typedef std::vector ::iterator iItems; + + std::vector m_channelLayouts; + std::vector m_focusedChannelLayouts; + std::vector m_focusedProgrammeLayouts; + std::vector m_programmeLayouts; + std::vector m_rulerLayouts; + + CGUIListItemLayout *m_channelLayout; + CGUIListItemLayout *m_focusedChannelLayout; + CGUIListItemLayout *m_programmeLayout; + CGUIListItemLayout *m_focusedProgrammeLayout; + CGUIListItemLayout *m_rulerLayout; + + bool m_wasReset; // true if we've received a Reset message until we've rendered once. Allows + // us to make sure we don't tell the infomanager that we've been moving when + // the "movement" was simply due to the list being repopulated (thus cursor position + // changing around) + + void FreeChannelMemory(int keepStart, int keepEnd); + void FreeProgrammeMemory(int keepStart, int keepEnd); + void FreeRulerMemory(int keepStart, int keepEnd); + + void GetChannelCacheOffsets(int &cacheBefore, int &cacheAfter); + void GetProgrammeCacheOffsets(int &cacheBefore, int &cacheAfter); + void GetRulerCacheOffsets(int &cacheBefore, int &cacheAfter); + +private: + int m_rulerUnit; //! number of blocks that makes up one element of the ruler + int m_channels; + int m_channelsPerPage; + int m_ProgrammesPerPage; + int m_channelCursor; + int m_channelOffset; + int m_blocks; + int m_blocksPerPage; + int m_blockCursor; + int m_blockOffset; + int m_cacheChannelItems; + int m_cacheProgrammeItems; + int m_cacheRulerItems; + + float m_rulerPosX; //! X position of first ruler item + float m_rulerPosY; //! Y position of first ruler item + float m_rulerHeight; //! height of the scrolling timeline above the ruler items + float m_rulerWidth; //! width of each element of the ruler + float m_channelPosX; //! Y position of first channel row + float m_channelPosY; //! Y position of first channel row + float m_channelHeight; //! height of each channel row (& every grid item) + float m_channelWidth; //! width of the channel item + float m_gridPosX; //! X position of first grid item + float m_gridPosY; //! Y position of first grid item + float m_gridWidth; + float m_gridHeight; + float m_blockSize; //! a block's width in pixels + float m_analogScrollCount; + + CDateTime m_gridStart; + CDateTime m_gridEnd; + + struct GridItemsPtr **m_gridIndex; + GridItemsPtr *m_item; + CGUIListItem *m_lastItem; + CGUIListItem *m_lastChannel; + + unsigned int m_renderTime; + + int m_scrollTime; + bool m_channelWrapAround; + bool m_gridWrapAround; //! only when no more data available should this be true + + int m_programmeScrollLastTime; + float m_programmeScrollSpeed; + float m_programmeScrollOffset; + + int m_channelScrollLastTime; + float m_channelScrollSpeed; + float m_channelScrollOffset; + + CStdString m_label; +}; diff --git a/guilib/GUIEditControl.cpp b/guilib/GUIEditControl.cpp index ded9e1fa17..a66cd86d23 100644 --- a/guilib/GUIEditControl.cpp +++ b/guilib/GUIEditControl.cpp @@ -255,6 +255,20 @@ void CGUIEditControl::OnClick() case INPUT_TYPE_SECONDS: textChanged = CGUIDialogNumeric::ShowAndGetSeconds(utf8, g_localizeStrings.Get(21420)); break; + case INPUT_TYPE_TIME: + { + CDateTime dateTime; + dateTime.SetFromDBTime(utf8); + SYSTEMTIME time; + dateTime.GetAsSystemTime(time); + if (CGUIDialogNumeric::ShowAndGetTime(time, heading > 0 ? heading : g_localizeStrings.Get(21420))) + { + dateTime = CDateTime(time); + utf8 = dateTime.GetAsLocalizedTime("", false); + textChanged = true; + } + break; + } case INPUT_TYPE_DATE: { CDateTime dateTime; @@ -263,7 +277,7 @@ void CGUIEditControl::OnClick() dateTime = CDateTime(2000, 1, 1, 0, 0, 0); SYSTEMTIME date; dateTime.GetAsSystemTime(date); - if (CGUIDialogNumeric::ShowAndGetDate(date, g_localizeStrings.Get(21420))) + if (CGUIDialogNumeric::ShowAndGetDate(date, heading > 0 ? heading : g_localizeStrings.Get(21420))) { dateTime = CDateTime(date); utf8 = dateTime.GetAsDBDate(); diff --git a/guilib/GUIEditControl.h b/guilib/GUIEditControl.h index ada7bd8f79..63e0c79ef8 100644 --- a/guilib/GUIEditControl.h +++ b/guilib/GUIEditControl.h @@ -44,6 +44,7 @@ public: INPUT_TYPE_TEXT = 0, INPUT_TYPE_NUMBER, INPUT_TYPE_SECONDS, + INPUT_TYPE_TIME, INPUT_TYPE_DATE, INPUT_TYPE_IPADDRESS, INPUT_TYPE_PASSWORD, diff --git a/guilib/GUILabelControl.cpp b/guilib/GUILabelControl.cpp index f7e53b3ce4..9ef7ec3a42 100644 --- a/guilib/GUILabelControl.cpp +++ b/guilib/GUILabelControl.cpp @@ -139,6 +139,13 @@ float CGUILabelControl::GetWidth() const return m_width; } +void CGUILabelControl::SetWidth(float width) +{ + m_width = width; + m_label.SetMaxRect(m_posX, m_posY, m_width, m_height); + CGUIControl::SetWidth(m_width); +} + bool CGUILabelControl::OnMessage(CGUIMessage& message) { if ( message.GetControlId() == GetID() ) diff --git a/guilib/GUILabelControl.h b/guilib/GUILabelControl.h index c82e89c891..11a4c1106b 100644 --- a/guilib/GUILabelControl.h +++ b/guilib/GUILabelControl.h @@ -50,6 +50,7 @@ public: virtual bool OnMessage(CGUIMessage& message); virtual CStdString GetDescription() const; virtual float GetWidth() const; + virtual void SetWidth(float width); const CLabelInfo& GetLabelInfo() const { return m_label.GetLabelInfo(); }; void SetLabel(const std::string &strLabel); diff --git a/guilib/GUIListGroup.cpp b/guilib/GUIListGroup.cpp index 215e2212f2..c27a6dbf62 100644 --- a/guilib/GUIListGroup.cpp +++ b/guilib/GUIListGroup.cpp @@ -111,6 +111,50 @@ void CGUIListGroup::UpdateInfo(const CGUIListItem *item) } } +void CGUIListGroup::EnlargeWidth(float difference) +{ + // Alters the width of the controls that have an ID of 1 + for (iControls it = m_children.begin(); it != m_children.end(); it++) + { + CGUIControl *child = *it; + if (child->GetID() >= 1 && child->GetID() <= 14) + { + if (child->GetID() == 1) // label + { + child->SetWidth(child->GetWidth() + difference - 10); + child->SetVisible(child->GetWidth() > 10); /// + } + else + { + child->SetWidth(child->GetWidth() + difference); + } + } + } + SetInvalid(); +} + +void CGUIListGroup::EnlargeHeight(float difference) +{ + // Alters the width of the controls that have an ID of 1 + for (iControls it = m_children.begin(); it != m_children.end(); it++) + { + CGUIControl *child = *it; + if (child->GetID() >= 1 && child->GetID() <= 14) + { + if (child->GetID() == 1) // label + { + child->SetHeight(child->GetHeight() + difference); + child->SetVisible(child->GetHeight() > 10); /// + } + else + { + child->SetHeight(child->GetHeight() + difference); + } + } + } + SetInvalid(); +} + void CGUIListGroup::SetFocusedItem(unsigned int focus) { for (iControls it = m_children.begin(); it != m_children.end(); it++) diff --git a/guilib/GUIListGroup.h b/guilib/GUIListGroup.h index 241ca229f4..c8ad9d5c58 100644 --- a/guilib/GUIListGroup.h +++ b/guilib/GUIListGroup.h @@ -47,6 +47,8 @@ public: virtual void UpdateVisibility(const CGUIListItem *item = NULL); virtual void UpdateInfo(const CGUIListItem *item); + void EnlargeWidth(float difference); + void EnlargeHeight(float difference); void SetFocusedItem(unsigned int subfocus); unsigned int GetFocusedItem() const; bool MoveLeft(); diff --git a/guilib/GUIListItemLayout.cpp b/guilib/GUIListItemLayout.cpp index 9ab923ec01..94fd14674b 100644 --- a/guilib/GUIListItemLayout.cpp +++ b/guilib/GUIListItemLayout.cpp @@ -104,6 +104,20 @@ unsigned int CGUIListItemLayout::GetFocusedItem() const return m_group.GetFocusedItem(); } +void CGUIListItemLayout::SetWidth(float width) +{ + m_group.EnlargeWidth(width - m_width); + m_width = width; + SetInvalid(); +} + +void CGUIListItemLayout::SetHeight(float height) +{ + m_group.EnlargeHeight(height - m_height); + m_height = height; + SetInvalid(); +} + void CGUIListItemLayout::SelectItemFromPoint(const CPoint &point) { m_group.SelectItemFromPoint(point); diff --git a/guilib/GUIListItemLayout.h b/guilib/GUIListItemLayout.h index e934841425..b941ba59d0 100644 --- a/guilib/GUIListItemLayout.h +++ b/guilib/GUIListItemLayout.h @@ -48,6 +48,8 @@ public: void CreateListControlLayouts(float width, float height, bool focused, const CLabelInfo &labelInfo, const CLabelInfo &labelInfo2, const CTextureInfo &texture, const CTextureInfo &textureFocus, float texHeight, float iconWidth, float iconHeight, int nofocusCondition, int focusCondition); //#endif + void SetWidth(float width); + void SetHeight(float height); void SelectItemFromPoint(const CPoint &point); bool MoveLeft(); bool MoveRight(); diff --git a/guilib/GUIListLabel.cpp b/guilib/GUIListLabel.cpp index 28439cf58e..42561e4cf2 100644 --- a/guilib/GUIListLabel.cpp +++ b/guilib/GUIListLabel.cpp @@ -89,6 +89,18 @@ void CGUIListLabel::SetInvalid() CGUIControl::SetInvalid(); } +void CGUIListLabel::SetWidth(float width) +{ + m_width = width; + if (m_label.GetLabelInfo().align & XBFONT_RIGHT) + m_label.SetMaxRect(m_posX - m_width, m_posY, m_width, m_height); + else if (m_label.GetLabelInfo().align & XBFONT_CENTER_X) + m_label.SetMaxRect(m_posX - m_width*0.5f, m_posY, m_width, m_height); + else + m_label.SetMaxRect(m_posX, m_posY, m_posX + m_width, m_posY + m_height); + CGUIControl::SetWidth(m_width); +} + void CGUIListLabel::SetLabel(const CStdString &label) { m_label.SetText(label); diff --git a/guilib/GUIListLabel.h b/guilib/GUIListLabel.h index 5c82fe196e..a39e35e203 100644 --- a/guilib/GUIListLabel.h +++ b/guilib/GUIListLabel.h @@ -46,6 +46,7 @@ public: virtual void UpdateInfo(const CGUIListItem *item = NULL); virtual void SetFocus(bool focus); virtual void SetInvalid(); + virtual void SetWidth(float width); void SetLabel(const CStdString &label); void SetSelected(bool selected); diff --git a/guilib/Key.h b/guilib/Key.h index 9d870e2382..f30e6b4bd0 100644 --- a/guilib/Key.h +++ b/guilib/Key.h @@ -259,6 +259,8 @@ #define ACTION_AUDIO_DELAY 161 #define ACTION_SUBTITLE_DELAY 162 +#define ACTION_RECORD 170 + #define ACTION_PASTE 180 #define ACTION_NEXT_CONTROL 181 #define ACTION_PREV_CONTROL 182 @@ -309,14 +311,14 @@ #define WINDOW_SETTINGS_MYVIDEOS 10017 #define WINDOW_SETTINGS_NETWORK 10018 #define WINDOW_SETTINGS_APPEARANCE 10019 - -#define WINDOW_SCRIPTS 10020 // virtual window for backward compatibility +#define WINDOW_SETTINGS_MYTV 10020 #define WINDOW_VIDEO_FILES 10024 #define WINDOW_VIDEO_NAV 10025 #define WINDOW_VIDEO_PLAYLIST 10028 #define WINDOW_LOGIN_SCREEN 10029 +#define WINDOW_SCRIPTS 10030 #define WINDOW_SETTINGS_PROFILES 10034 #define WINDOW_ADDON_BROWSER 10040 @@ -368,7 +370,22 @@ #define WINDOW_MUSIC_NAV 10502 #define WINDOW_MUSIC_PLAYLIST_EDITOR 10503 -#define WINDOW_DIALOG_OSD_TELETEXT 10600 +// PVR related Window and Dialog ID's +#define WINDOW_TV 10600 +#define WINDOW_DIALOG_PVR_GUIDE_INFO 10601 +#define WINDOW_DIALOG_PVR_RECORDING_INFO 10602 +#define WINDOW_DIALOG_PVR_TIMER_SETTING 10603 +#define WINDOW_DIALOG_PVR_GROUP_MANAGER 10604 +#define WINDOW_DIALOG_PVR_CHANNEL_MANAGER 10605 +#define WINDOW_DIALOG_PVR_GUIDE_SEARCH 10606 +#define WINDOW_DIALOG_PVR_CHANNEL_SCAN 10607 +#define WINDOW_DIALOG_PVR_UPDATE_PROGRESS 10608 +#define WINDOW_DIALOG_PVR_OSD_CHANNELS 10609 +#define WINDOW_DIALOG_PVR_OSD_GUIDE 10610 +#define WINDOW_DIALOG_PVR_OSD_DIRECTOR 10611 +#define WINDOW_DIALOG_PVR_OSD_CUTTER 10612 +#define WINDOW_DIALOG_OSD_TELETEXT 10613 +#define WINDOW_DIALOG_EPG_SCAN 10614 //#define WINDOW_VIRTUAL_KEYBOARD 11000 #define WINDOW_DIALOG_SELECT 12000 @@ -397,6 +414,11 @@ #define WINDOW_PYTHON_START 13000 #define WINDOW_PYTHON_END 13099 +// WINDOW_ID's from 14000 to 14099 reserved for Addons + +#define WINDOW_ADDON_START 14000 +#define WINDOW_ADDON_END 14099 + #define ICON_TYPE_NONE 101 #define ICON_TYPE_PROGRAMS 102 #define ICON_TYPE_MUSIC 103 diff --git a/guilib/Makefile.in b/guilib/Makefile.in index 7b346050c2..58b9cd7342 100644 --- a/guilib/Makefile.in +++ b/guilib/Makefile.in @@ -19,6 +19,7 @@ SRCS=AnimatedGif.cpp \ GUIControlGroupList.cpp \ GUIDialog.cpp \ GUIEditControl.cpp \ + GUIEPGGridContainer.cpp \ GUIFadeLabelControl.cpp \ GUIFixedListContainer.cpp \ GUIFont.cpp \ diff --git a/guilib/system.h b/guilib/system.h index 6bfdbfe476..ba6a0c9631 100644 --- a/guilib/system.h +++ b/guilib/system.h @@ -43,6 +43,7 @@ #define HAS_UPNP #define HAS_VIDEO_PLAYBACK #define HAS_VISUALISATION +#define HAS_PVRCLIENTS #ifdef HAVE_LIBMICROHTTPD #define HAS_WEB_SERVER diff --git a/language/Dutch/strings.xml b/language/Dutch/strings.xml index e9dab323a6..c6c377e878 100644 --- a/language/Dutch/strings.xml +++ b/language/Dutch/strings.xml @@ -1,10 +1,10 @@ - + - - - + + + - + Programma's Afbeeldingen @@ -1521,13 +1521,529 @@ Temporal (Half) Temporal/Spatial (Half) DXVA + Auto - ION geoptimaliseerd Video post-processing Uitgeschakeld - Ingeschakeld voor SD beelden + Ingeschakeld voor SD content Altijd ingeschakeld Uitschakeltijd beeldscherm + %i MB + %i uren + %i dagen Kanaal wijzigen - Geimporteerde muziek map + Scheid de zoekwoorden door middel van AND, OR en/of NOT om de zoekopdracht te specificeren. + Of gebruik zinnen om op een tekst te zoeken, bijv. "Toen was geluk heel gewoon". + Zoek vergelijkbaar programma + Gids van clients wordt geladen + PVR stream informatie + Ontvanger + Status ontvanger + Signaal kwaliteit + SNR + BER + UNC + Server + Ongecodeerd + Vast + Versleuteling + Server %i - %s + TV Opnames + Standaard map voor TV thumbnails + Kanalen + TV + Radio + Verborgen + TV kanalen + Radio kanalen + Toekomstige opnames + Voeg opname toe... + Geen zoekresultaten + Geen gids aanwezig + TV gids + Nu + Straks + Tijdslijn + Informatie + Opname is al gestart + Kanaal kan niet worden weergegeven + Opname kan niet worden weergegeven + Toon signaal kwaliteit + Op dit moment niet ondersteund! + Bevestig verbergen van het kanaal + Planning + Hernoem opname? + Hernoem geplande opname? + Opname + Controleer de instellingen van de server + Geen PVR clients beschikbaar + Nieuw kanaal + Programma info + Beheer groepen + Toon kanaal + Toon normale kanalen + Toon verborgen kanalen + Verplaats kanaal naar: + Opname informatie + Verberg kanaal + Geen informatie beschikbaar! + Nieuwe geplande opname + Pas geplande opname aan + Geplande opname aan/uit + Stop opname + Verwijder geplande opname + Voeg geplande opname toe + Sorteer op: Kanaal + Ga naar begin + Ga naar einde + Standaard gids + Laadt opname informatie van clients + Dit programma wordt al opgenomen + Kon de opname niet verwijderen + TV gids + TV gids scan timeout + TV gids update timeout + Negeer database voor PVR-Server gids + Kanaal timeout + Actief: + Naam: + Map: + Radio: + Kanaal: + Dag: + Begin: + Einde: + Prioriteit: + Levensduur (dagen): + Eerste dag: + Onbekend kanaal: %u + Ma-__-__-__-__-__-__ + __-Di-__-__-__-__-__ + __-__-Wo-__-__-__-__ + __-__-__-Do-__-__-__ + __-__-__-__-Vr-__-__ + __-__-__-__-__-Za-__ + __-__-__-__-__-__-Zo + Ma-Di-Wo-Do-Vr-__-__ + Ma-Di-Wo-Do-Vr-Za-__ + Ma-Di-Wo-Do-Vr-Za-Zo + __-__-__-__-__-Za-Zo + Naam van de opname + Waarschuwing + Opname aanwezig + Verwijder kanaal en opname? + Kanaal wordt afgespeeld + Schakel over naar een ander kanaal! + Zoek missende iconen + Geef naam van de opname map + Grootte: + Volgende opname op + om + Opnames niet gesynchroniseerd + Kon de opname niet opslaan! + Probeer nog eens... + Server fout! + Opnames niet synchroon! + Volgende + Versie + Adres + Schijf grootte + Zoek naar kanalen + Kan de instellingen van de TV niet aanpassen tijdens het zoeken! + Op welke server wilt u zoeken? + Client nummer + Vermijd herhalingen + Opname loopt nog. Wilt u deze verwijderen? + Alleen ongecodeerd + Negeer bij lopende opnames + Negeer bij opgenomen programma's + Starttijd + Eindtijd + Startdatum + Einddatum + Minimale lengte + Maximale lengte + Neem onbekende genres op + Zoekopdracht + Neem omschrijving op + Hoofdlettergevoelig + Kanaal niet beschikbaar + Geen groepen gedifinieerd + Maak er eerst een aan + Naam nieuwe groep + Alle kanalen + Groep + Zoek in gids + Beheer groepen + Geen groepen + Gegroepeerd + Groepen + PVR-Server is niet compatibel! + Kanaal + Ma + Di + Wo + Do + Vr + Za + Zo + van + Volgende opname + Wordt nu opgenoemen + van + tot + op + Wordt opgenomen + Opnames + Kan de opname niet starten + Wijzig kanaal + PVR informatie + Zoek missende iconen + Wijzig kanaal zonder op OK te drukken + Verberg video lengte informatie + Scan timeout voor kanaal afspelen + Start afspelen geminimaliseerd + Lengte directe opname + Standaard prioriteit + Standaard levensduur + Marge opname begin + Marge opname einde + Afspelen + Info bij wisselen kanaal + Timeout kanaal informatie + TV + Menu/OSD + Weergegeven dagen in de TV gids + TV gids wachttijd + Kanaal info tijd + Wis TV database + Alle gegevens in de TV database worden gewist + Wis TV gids en lees nieuwe in + TV gids wordt gewist + Hervat laatste kanaal bij opstarten + Geminimaliseerd + PVR service + Geen server ondersteunt het scannen naar kanalen + Kon het scannen naar kanalen niet starten + Hervatten? + Client acties + PVR client specifieke acties + Opname gestart om: %s + Opname beeindigd om: %s + Beheer kanalen + TV gids bron: + Naam kanaal: + Kanaal icoon: + Pas kanaal aan + Nieuw kanaal + Beheer groepen + Activeer TV gids: + Groep: + Voer de naam van het nieuwe kanaal in + XBMC intern virtueel kanaal + Client + Verwijder kanaal + Lijst bevat wijzigigen + Selecteer client voor het kanaal + Voer een geldige URL in voor het kanaal + De PVR server ondersteunt geen timers! + Anders/Onbekend + Film/Drama + Detective/Thriller + Avontuur/Western/Oorlog + Science Fiction/Fantasie/Horror + Komedie + Soap/Melodrama/Folklore + Romantisch + Serieus/Klassiek/Religie/Historische Film/Drama + Volwassenen Film/Drama + + + + + + + + + + + + + + + Nieuws/Actualiteiten + Nieuws/Weer + Nieuws magazine + Documentaire + Discussie/Interview/Debat + + + + + + + + + + + + + + + + + + + + + + + Show/Spelshow + Spelshow/Quiz/Wedstrijd + Variété + Praatprogramma + + + + + + + + + + + + + + + + + + + + + + + + + Sport + Speciaal + Sport Magazine + Voetbal + Tennis/Squash + Team Sport + Atletiek + Motorsport + Watersport + Wintersport + Ruitersport + Vechtsport + + + + + + + + + Kinderen/jeugdprogramma's + Peuter programma's + Entertainment programma's voor 6 tot 14 + Entertainment programma's voor 10 tot 16 + Informatie/Educatie/School Programma's + Cartoons/Marionetten + + + + + + + + + + + + + + + + + + + + + Muziek/Ballet/Dans + Rock/Pop + Serieus/Klassieke muziek + Folklore/Tradionele muziek + Muziek/Opera + Ballet + + + + + + + + + + + + + + + + + + + + + Kunst/Cultuur + Uitvoerende kunsten + Fijne kunsten + Religie + Populaire cultuur/Tradionele kunst + Literatuur + Film/Cinema + Experimentele film/Video + Uitzending/Pers + Nieuwe media + Kunst/Cultuur magazines + Mode + + + + + + + + + Sociaal/Politiek/Economisch + Magazines/Verslagen/Documentaires + Economisch/Maatschappelijk advies + Opmerkelijke personen + + + + + + + + + + + + + + + + + + + + + + + + + Educatie/Wetenschap/Feiten + Natuur/Dieren/Milieu + Technologie/Natuurwetenschappen + Geneeskunde/Physiologie/Psychologie + Buitenland/Expedities + Sociale/Spirituele wetenschap + Verder leren + Talen + + + + + + + + + + + + + + + + + Vrije tijd/Hobbies + Tourisme/Reizen + Handwerk + Autorijden + Fitness & Gezondheid + Koken + Reclame/Winkelen + Tuinieren + + + + + + + + + + + + + + + + + Bijzondere kenmerken + Oude talen + Zwart/Wit + Niet gepubliceerd + Live uitzending + + + + + + + + + + + + + + + + + + + + + + + Drama + Detective/Thriller + Avontuur/Western/Oorlog + Science Fiction/Fantasie/Horror + Comedy + Soap/Melodrama/Folklore + Romantisch + Serieus/Klassiek-Regionaal/Historisch + Volwassenen + + + + + + + + + + + + + + + CD-ripmap Andere DVD-speler gebruiken - Locatie van de DVD-speler Trainersmap @@ -1882,6 +2398,8 @@ Landen Aflevering Afleveringen + Luisteraar + Luisteraars Verborgen bestanden en mappen weergeven TuxBox-client WAARSCHUWING: TuxBox is aan het opnemen. @@ -2124,6 +2642,7 @@ Muziekvideoinformatie Albuminformatie Artiestinformatie + PVR clients Configureren Uitschakelen Inschakelen diff --git a/language/English/strings.xml b/language/English/strings.xml index ac5063a20a..d4e496eeb4 100644 --- a/language/English/strings.xml +++ b/language/English/strings.xml @@ -1,5 +1,5 @@ - + Programs Pictures @@ -1559,6 +1559,7 @@ Temporal (Half) Temporal/Spatial (Half) DXVA + Auto - ION Optimized Video post-processing Disabled @@ -1567,7 +1568,421 @@ Display sleep timeout + %i MByte + %i hours + %i days + + Switch to channel + Separate different search words using AND, OR and/or NOT to specify your search. + Or use Phrases to find a full text e.G.: "The wizard of Oz". + Find similar program + Loading EPG from clients + PVR Stream Information + Receiving Device + Receiving Status + Signal Quality + SNR + BER + UNC + Backend + Free To Air + Fixed + Encryption + Server %i - %s + TV Recordings + Default folder for TV-Thumbnails + Channels + TV + Radio + Hidden + TV Channels + Radio Channels + Upcoming Recordings + Add Timer... + No search results present + No EPG entries present + TV Guide + Now + Next + Timeline + Information + Recording already started + Channel can not be played + Recording can not be played + Show Signal Qualitiy + Not Supported at the moment! + Confirm Channel hide + Timer + Rename recording? + Rename timer? + Recording + Please check your Settings or the Backend + No PVR Clients available + New Channel + Programme info + Group Managment + Show Channel + Show normal Channels + Show hidden Channels + Move channel to: + Recording information + Hide Channel + No Information available! + New Timer + Edit Timer + On/Off Timer + Stop Recording + Delete Timer + Add Timer + Sort by: Channel + Go to begin + Go to end + Default Programm Guide + Loading Recording Information from clients + Timer already set for this event + Couldn't delete recording! + EPG + EPG scan timeout + EPG update timeout + Ignore Database for PVR-Server EPG + Channel entry timeout + Active: + Name: + Directory: + Radio: + Channel: + Day: + Begin: + End: + Priority: + Lifetime (days): + First day: + Unknown Channel %u + Mo-__-__-__-__-__-__ + __-Tu-__-__-__-__-__ + __-__-We-__-__-__-__ + __-__-__-Th-__-__-__ + __-__-__-__-Fr-__-__ + __-__-__-__-__-Sa-__ + __-__-__-__-__-__-Su + Mo-Tu-We-Th-Fr-__-__ + Mo-Tu-We-Th-Fr-Sa-__ + Mo-Tu-We-Th-Fr-Sa-Su + __-__-__-__-__-Sa-Su + Enter the recording name + Warning + Timer present + Delete Channel and Timer? + Channel is playing + Please switch to another Channel! + Rescan for missing icons + Enter the recording directory + Size: + Next timer on + at + Recordings not in sync! + Couldn't save timer! + Try again... + Server error! + Timers not in sync! + Next + Version + Address + Disksize + Search for Channels + TV specific action are during search not possible! + On which server you want to search? + Client number + Avoid Repeats + Timer still recording - really delete? + Free-To-Air only + Ignore for by present Timers + Ignore for by present Recordings + Start Time + Stop Time + Start Date + Stop Date + Minimal Duration + Maximal Duration + Inluding unknown Genres + Search string + Include Description + Case Sensitive + Channel Unavailable + No Groups defined + Please create first one + New Group name + All Channels + Group + Search Guide + Group Managment + No Groups + Grouped + Groups + PVR-Server is not compatible! + Channel + Mo + Tu + We + Th + Fr + Sa + Su + from + Next Recording + Now Recording + from + to + On + Recording running + Recordings + Can not start recording + Switch + PVR information + Rescan for missing icons + Switch channel without pressing OK + Hide Video-Length info box + Scan Timeout before channel playback + Start playback Minimized + Instant record time + Default priority + Default lifetime + Margin at start + Margin at stop + Replay + Info on channel switch + Timeout requested channel info + TV + Menu/OSD + Days to display in TV Guide + TV Guide linger time + Channel info time + Clear TV Database + All data in the Database is being erased + Delete EPG and read new + EPG is being erased + Continue last channel after startup + Minimized + PVR service + No Server support channel scanning + Channel scan can't be started + Continue? + Client actions + PVR client specific actions + Recording started on: %s + Recording finished on: %s + Channel manager + EPG source: + Channel name: + Channel icon: + Edit channel + New channel + Group Managment + Activate EPG: + Group: + Enter new channel name + XBMC internal virtual channel + Client + Delete Channel + List contains changes + Select channel client + Enter a valid URL for the new channel + The PVR-Server don't support Timers! + + Other/Unknown + Movie/Drama + Detective/Thriller + Adventure/Western/War + Science Fiction/Fantasy/Horror + Comedy + Soap/Melodrama/Folkloric + Romance + Serious/Classical/Religious/Historical Movie/Drama + Adult Movie/Drama + + + + + + + + News/Current Affairs + News/Weather Report + News Magazine + Documentary + Discussion/Inverview/Debate + + + + + + + + + + + + Show/Game Show + Game Show/Quiz/Contest + Variety Show + Talk Show + + + + + + + + + + + + + Sports + Special Event + Sport Magazine + Football + Tennis/Squash + Team Sports + Athletics + Motor Sport + Water Sport + Winter Sports + Equestrian + Martial Sports + + + + + Children's/Youth Programmes + Pre-school Children's Programmes + Entertainment Programmes for 6 to 14 + Entertainment Programmes for 10 to 16 + Informational/Educational/School Programme + Cartoons/Puppets + + + + + + + + + + + Music/Ballet/Dance + Rock/Pop + Serious/Classical Music + Folk/Tradional Music + Musical/Opera + Ballet + + + + + + + + + + + Arts/Culture + Performing Arts + Fine Arts + Religion + Popular Culture/Traditional Arts + Literature + Film/Cinema + Experimental Film/Video + Broadcasting/Press + New Media + Arts/Culture Magazines + Fashion + + + + + Social/Political/Economics + Magazines/Reports/Documentary + Economics/Social Advisory + Remarkable People + + + + + + + + + + + + + Education/Science/Factual + Nature/Animals/Environment + Technology/Natural Sciences + Medicine/Physiology/Psychology + Foreign Countries/Expeditions + Social/Spiritual Sciences + Further Education + Languages + + + + + + + + + Leisure/Hobbies + Tourism/Travel + Handicraft + Motoring + Fitness & Health + Cooking + Advertisement/Shopping + Gardening + + + + + + + + + Special Characteristics + Original Language + Black & White + Unpublished + Live Broadcast + + + + + + + + + + + + Drama + Detective/Thriller + Adventure/Western/War + Science Fiction/Fantasy/Horror + Comedy + Soap/Melodrama/Folkloric + Romance + Serious/ClassicalReligion/Historical + Adult + + + + + + + Saved music folder Use external DVD player @@ -2193,6 +2608,7 @@ Music video information Album information Artist information + PVR clients Configure Disable @@ -2223,7 +2639,7 @@ Would you like to enable this Add-on? Would you like to disable this Add-on? Add-on update available! - Enabled Add-ons + Installed Add-ons Auto update Add-on enabled Add-on updated @@ -2246,7 +2662,7 @@ - + Disabled System Add-on Add-on has been marked as broken in repository. Would you like to disable it on your system? diff --git a/language/German/strings.xml b/language/German/strings.xml index 79b6c606ab..645f410a3a 100644 --- a/language/German/strings.xml +++ b/language/German/strings.xml @@ -1561,7 +1561,417 @@ Bildschirm Sleep Timeout + Zum Kanal wechseln + Benutzen Sie AND, OR und NOT in Verbindung mit Ihren Suchbegriffen, um detaillierter zu suchen. + Oder benutzen Sie ganze Sätze in Ausrufezeichen z.B.: "Der Zauberer von Oz". + Finde ähnliche Sendung + Lade EPG von den PVR-Klienten + PVR Empfang-Informationen + Empfangsgerät + Empfangsstatus + Signal Qualität + SNR + BER + UNC + Backend + Frei empfangbar + Fest + Verschlüsselung + Server %i - %s + TV Aufnahmen + Standardordner für TV Thumbnails + Kanäle + TV + Radio + Versteckte + TV Kanäle + Radio Kanäle + Bevorstehende Aufnahmen + Timer hinzufügen... + Es liegen keine Suchergebnisse vor + Es sind keine EPG Einträge vorhanden + Programm + Jetzt + Nächstes + Zeitleiste + Information + Aufnahme läuft bereits + Kanal konnte nicht wiedergegeben werden + Aufnahme konnte nicht wiedergegeben werden + Signalqualität anzeigen + Wird derzeit nicht unterstützt! + Verstecken bestätigen + Timer + Aufnahme umbenennen? + Timer umbenennen? + Aufnahme + Bitte überprüfen Sie ihre Einstellungen oder den Server + Keine PVR Klienten verfügbar + Neuer Kanal + Titel-Informationen + Gruppenverwaltung + Zeige Kanal + Zeige normale Kanäle + Zeige versteckte Kanäle + Kanal verschieben nach: + Aufnahme-Informationen + Kanal verstecken + Keine Informationen verfügbar! + Neuer Timer + Timer bearbeiten + Timer aktivieren/deaktivieren + Aufnahme abbrechen + Timer löschen + Timer hinzufügen + Nach Kanal + Gehe zum Anfang + Gehe zum Ende + Standard Programmanzeige + Lade Aufnahmeinformationen von den PVR-Klienten + Es ist bereits ein Timer vorhanden + Konnte Aufnahme nicht löschen! + EPG + Zeit bis zur EPG-Suche + Zeit bis zur EPG-Aktualisierung + Ignoriere Datenbank für PVR-Server EPG + Zeitlimit für Kanaleingabe + Aktiv: + Name: + Ordner: + Radio: + Kanal: + Tag: + Anfang: + Ende: + Priorität: + Lebensdauer (Tage): + Erster Tag: + Unbekannter Kanal %u + Mo-__-__-__-__-__-__ + __-Di-__-__-__-__-__ + __-__-Mi-__-__-__-__ + __-__-__-Do-__-__-__ + __-__-__-__-Fr-__-__ + __-__-__-__-__-Sa-__ + __-__-__-__-__-__-So + Mo-Di-Mi-Do-Fr-__-__ + Mo-Di-Mi-Do-Fr-Sa-__ + Mo-Di-Mi-Do-Fr-Sa-So + __-__-__-__-__-Sa-So + Bitte geben Sie den Aufnahmenamen ein + Warnung + Timer vorhanden + Kanal und Timer löschen? + Kanalwiedergabe läuft + Bitte schalten Sie auf einen anderen Kanal! + Suche fehlende Kanallogos + Bitte geben Sie den Aufnahmeordner ein + Größe: + Nächster Timer am + um + Aufnahmen nicht synchron! + Konnte Timer nicht speichern! + Erneut versuchen... + Server Fehler! + Timers nicht synchron! + Danach + Version + Addresse + Festplattengröße + Kanäle suchen + TV ist während der Kanalsuche nicht möglich! + Auf welchem Server soll gesucht werden? + Klientnummer + Keine Wiederholungen + Timer zeichnet auf - trotzdem löschen? + Nur frei empfangbare Kanäle + Bei vorhandenem Timer ignorieren + Bei vorhandener Aufnahmen ignorieren + Start Uhrzeit + Stop Uhrzeit + Start Datum + Stop Datum + Minimale Dauer + Maximale Dauer + Inklusive unbekannter Genres + Suchbegriff + Inklusive Beschreibung + Groß-/Kleinschreibung + Kanal nicht verfügbar + Keine Gruppen definiert + Bitte erstellen Sie zuerst eine + Neuer Gruppenname + Alle Kanäle + Gruppe + EPG durchsuchen + Gruppenverwaltung + Ungruppiert + Keine Gruppen + Gruppen + PVR-Server ist nicht kompatibel! + Kanal + Mo + Di + Mi + Do + Fr + Sa + So + ab + Nächste Aufnahme + Jetzige Aufnahme + von + bis + Auf + Aufnahme läuft + Aufnahmen + Konnte Aufnahme nicht starten + Umschalten + PVR-Informationen + Suche fehlende Kanallogos + Beim umschalten auf OK verzichten + Infobox für Videolänge verbergen + Suchüberlauf vor Kanalwiedergabe + Wiedergabe minimiert starten + Dauer der Direktaufzeichnung + Standard-Priorität + Standard-Lebensdauer + Vorlauf zum Timer-Beginn + Nachlauf am Timer-Ende + Wiedergabe + Info beim Kanalwechsel + Angeforderte Kanalinfo schließen + TV + Menü/OSD + Tage für TV-Programm Anzeige + Alte EPG-Daten anzeigen + Anzeigedauer für Kanalinfo + TV Datenbank zurücksetzten + Alle Daten in der Datenbank werden gelöscht + EPG Daten löschen und neu einlesen + Alle EPG Daten werden gelöscht + Nach Start letzten Kanal wiedergeben + Minimiert + PVR-Service + Keiner der PVR-Server unterstützt die Kanalsuche + Kanalsuche konnte nicht durchgeführt werden + Fortfahren? + Klientaktionen + PVR Klient spezifische Aktionen + Aufnahme gestarted auf: %s + Aufnahme beendet auf: %s + Kanalverwaltung + EPG Quelle: + Kanalname: + Kanallogo: + Kanal bearbeiten + Neuer Kanal + Gruppenverwaltung + EPG Aktivieren: + Gruppe: + Bitte geben Sie den neuen Kanalnamen ein + XBMC interner virtueller Kanal + Klient + Kanal löschen + Liste enthält Änderungen + Wählen Sie die Kanalquelle + Bitte geben Sie eine URL für den Kanal ein + Der PVR-Server unterstützt keine Timer! + + Andere/Unbekannt + Movie/Drama + Detektivfilm/Thriller + Abenteuer/Western/Krieg + Science Fiction/Fantasy/Horror + Komödie + Seifenoper/Melodram/Folklore + Romanze + Serie/Klassik/Religion/Historienfilm + Erwachsenenfilm + + + + + + + + Nachrichten/Aktuelle Meldungen + Nachrichten/Wettervorhersage + Nachrichtenmagazin + Dokumentation + Diskussion/Interview/Debatte + + + + + + + + + + + + Show/Game Show + Game Show/Quiz/Contest + Varietevorführung + Talk Show + + + + + + + + + + + + + Sport + Sportveranstaltung + Sportmagazin + Fußball + Tennis/Squash + Team Sportarten + Athletik + Motorsport + Wassersport + Wintersport + Pferdesport + Kampfsport + + + + + Kinder- & Jugendprogramm + Unterhaltungsprogramm für Vorschulkinder + Unterhaltungsprogramm für 6 bis 14 Jährige + Unterhaltungsprogramm für 10 bis 16 Jährige + Information/Educational/Schulprogramm + Zeichentrick/Puppen + + + + + + + + + + + Musik/Ballet/Tanzen + Rock/Pop + Serious/Klassische Musik + Volksmusik/Tradionelle Musik + Musical/Oper + Ballet + + + + + + + + + + + Kunst/Kultur + Performing Arts + Bildende Kunst + Religion + Volkskultur/Traditionelle Kunst + Literatur + Film/Kino + Experimental Film/Video + Rundfunk/Presse + Neue Medien + Kunst/Kultur Magazin + Mode + + + + + Soziales/Politik/Wirtschaft + Magazin/Report/Dokumentation + Wirtschaft- & Sozialberatung + Bemerkenswerte Personen + + + + + + + + + + + + + Bildung/Wissenschaft/Fakten + Natur/Tiere/Umwelt + Technologie/Naturwissenschaften + Medizin/Physiologie/Psychologie + Ausland/Expeditionen + Sozial- & Geisteswissenschaften + Weiterbildung + Sprachen + + + + + + + + + Freizeit/Hobbys + Tourismus/Reisen + Handwerk + Auto + Fitness & Gesundheit + Kochen + Werbung/Einkaufen + Garten + + + + + + + + + Besondere Merkmale + Originalsprache + Schwarz/Weiß + Bisher unveröffentlicht + Livesendung + + + + + + + + + + + + Drama + Detektivfilm/Thriller + Abenteuer/Western/Krieg + Science Fiction/Fantasy/Horror + Komödie + Seifenoper/Melodram/Folklore + Romanze + Serie/Klassisch/Religion/Geschichte + Erwachsenenfilm + + + + + + + Ordner für CD-Kopien Externen DVD-Player verwenden diff --git a/lib/addons/library.xbmc.addon/Makefile.in b/lib/addons/library.xbmc.addon/Makefile.in new file mode 100644 index 0000000000..56afe6cbab --- /dev/null +++ b/lib/addons/library.xbmc.addon/Makefile.in @@ -0,0 +1,27 @@ +ARCH=@ARCH@ +INCLUDES=-I. -I../../../xbmc/addons/include -I../../../xbmc +DEFINES+= +CXXFLAGS=-fPIC +LIBNAME=libXBMC_addon +OBJS=$(LIBNAME).o + +LIB_SHARED=../../../addons/library.xbmc.addon/$(LIBNAME)-$(ARCH).so + +all: $(LIB_SHARED) + +$(LIB_SHARED): $(OBJS) +ifeq ($(findstring osx,$(ARCH)), osx) + @export MACOSX_DEPLOYMENT_TARGET=10.4 + $(CXX) -bundle -shared -flat_namespace -undefined suppress -o $(LIB_SHARED) $(OBJS) + ../../../tools/Mach5/wrapper.rb $@;mv output.so $@ +else + $(CXX) $(CFLAGS) $(LDFLAGS) -shared -g -o $(LIB_SHARED) $(OBJS) +endif + +CLEAN_FILES = \ + $(LIBNAME).so \ + +DISTCLEAN_FILES= \ + Makefile \ + +include ../../../Makefile.include diff --git a/lib/addons/library.xbmc.addon/libXBMC_addon.cpp b/lib/addons/library.xbmc.addon/libXBMC_addon.cpp new file mode 100644 index 0000000000..d14ec2657a --- /dev/null +++ b/lib/addons/library.xbmc.addon/libXBMC_addon.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include +#include +#include "../../../addons/library.xbmc.addon/libXBMC_addon.h" +#include "../../../xbmc/addons/AddonHelpers_local.h" + +#ifdef _WIN32 +#include +#define DLLEXPORT __declspec(dllexport) +#else +#define DLLEXPORT +#endif + + +using namespace std; + +AddonCB *m_Handle = NULL; +CB_AddOnLib *m_cb = NULL; + +extern "C" +{ + +DLLEXPORT int XBMC_register_me(void *hdl) +{ + if (!hdl) + fprintf(stderr, "libXBMC_addon-ERROR: XBMC_register_me is called with NULL handle !!!\n"); + else + { + m_Handle = (AddonCB*) hdl; + m_cb = m_Handle->AddOnLib_RegisterMe(m_Handle->addonData); + if (!m_cb) + fprintf(stderr, "libXBMC_addon-ERROR: XBMC_register_me can't get callback table from XBMC !!!\n"); + else + return 1; + } + return 0; +} + +DLLEXPORT void XBMC_unregister_me() +{ + if (m_Handle && m_cb) + m_Handle->AddOnLib_UnRegisterMe(m_Handle->addonData, m_cb); +} + +DLLEXPORT void XBMC_log(const addon_log_t loglevel, const char *format, ... ) +{ + if (m_cb == NULL) + return; + + char buffer[16384]; + va_list args; + va_start (args, format); + vsprintf (buffer, format, args); + va_end (args); + m_cb->Log(m_Handle->addonData, loglevel, buffer); +} + +DLLEXPORT bool XBMC_get_setting(string settingName, void *settingValue) +{ + if (m_cb == NULL) + return false; + + return m_cb->GetSetting(m_Handle->addonData, settingName.c_str(), settingValue); +} + +DLLEXPORT void XBMC_queue_notification(const queue_msg_t type, const char *format, ... ) +{ + if (m_cb == NULL) + return; + + char buffer[16384]; + va_list args; + va_start (args, format); + vsprintf (buffer, format, args); + va_end (args); + m_cb->QueueNotification(m_Handle->addonData, type, buffer); +} + +DLLEXPORT void XBMC_unknown_to_utf8(string &str) +{ + if (m_cb == NULL) + return; + + string buffer = m_cb->UnknownToUTF8(str.c_str()); + str = buffer; +} + +DLLEXPORT string XBMC_get_localized_string(int dwCode) +{ + if (m_cb == NULL) + return ""; + + string buffer = m_cb->GetLocalizedString(m_Handle->addonData, dwCode); + return buffer; +} + +DLLEXPORT string XBMC_get_dvd_menu_language() +{ + if (m_cb == NULL) + return ""; + + string buffer = m_cb->GetDVDMenuLanguage(m_Handle->addonData); + return buffer; +} + +}; diff --git a/lib/addons/library.xbmc.addon/project/VS2008Express/libXBMC_addon.sln b/lib/addons/library.xbmc.addon/project/VS2008Express/libXBMC_addon.sln new file mode 100644 index 0000000000..b282dec1ec --- /dev/null +++ b/lib/addons/library.xbmc.addon/project/VS2008Express/libXBMC_addon.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libXBMC_addon", "libXBMC_addon.vcproj", "{D450FE9A-CE56-4496-B4AB-379094E642F2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D450FE9A-CE56-4496-B4AB-379094E642F2}.Debug|Win32.ActiveCfg = Debug|Win32 + {D450FE9A-CE56-4496-B4AB-379094E642F2}.Debug|Win32.Build.0 = Debug|Win32 + {D450FE9A-CE56-4496-B4AB-379094E642F2}.Release|Win32.ActiveCfg = Release|Win32 + {D450FE9A-CE56-4496-B4AB-379094E642F2}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/lib/addons/library.xbmc.addon/project/VS2008Express/libXBMC_addon.vcproj b/lib/addons/library.xbmc.addon/project/VS2008Express/libXBMC_addon.vcproj new file mode 100644 index 0000000000..11330f0bfa --- /dev/null +++ b/lib/addons/library.xbmc.addon/project/VS2008Express/libXBMC_addon.vcproj @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/addons/library.xbmc.gui/Makefile.in b/lib/addons/library.xbmc.gui/Makefile.in new file mode 100644 index 0000000000..692106695f --- /dev/null +++ b/lib/addons/library.xbmc.gui/Makefile.in @@ -0,0 +1,27 @@ +ARCH=@ARCH@ +INCLUDES=-I. -I../../../xbmc/addons/include -I../../../xbmc -I../../../xbmc/cores/dvdplayer/DVDDemuxers +DEFINES+= +CXXFLAGS=-fPIC +LIBNAME=libXBMC_gui +OBJS=$(LIBNAME).o + +LIB_SHARED=../../../addons/library.xbmc.gui/$(LIBNAME)-$(ARCH).so + +all: $(LIB_SHARED) + +$(LIB_SHARED): $(OBJS) +ifeq ($(findstring osx,$(ARCH)), osx) + @export MACOSX_DEPLOYMENT_TARGET=10.4 + $(CXX) -bundle -shared -flat_namespace -undefined suppress -o $(LIB_SHARED) $(OBJS) + ../../../tools/Mach5/wrapper.rb $@;mv output.so $@ +else + $(CXX) $(CFLAGS) $(LDFLAGS) -shared -g -o $(LIB_SHARED) $(OBJS) +endif + +CLEAN_FILES = \ + $(LIB_SHARED) \ + +DISTCLEAN_FILES= \ + Makefile \ + +include ../../../Makefile.include diff --git a/lib/addons/library.xbmc.gui/libXBMC_gui.cpp b/lib/addons/library.xbmc.gui/libXBMC_gui.cpp new file mode 100644 index 0000000000..3c2898dee5 --- /dev/null +++ b/lib/addons/library.xbmc.gui/libXBMC_gui.cpp @@ -0,0 +1,555 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include +#include +#include +#include +#include "../../../addons/library.xbmc.gui/libXBMC_gui.h" +#include "addons/AddonHelpers_local.h" + +#ifdef _WIN32 +#include +#define DLLEXPORT __declspec(dllexport) +#else +#define DLLEXPORT +#endif + +using namespace std; + +AddonCB *m_Handle = NULL; +CB_GUILib *m_cb = NULL; + +extern "C" +{ + +DLLEXPORT int GUI_register_me(void *hdl) +{ + if (!hdl) + fprintf(stderr, "libXBMC_gui-ERROR: GUILib_register_me is called with NULL handle !!!\n"); + else + { + m_Handle = (AddonCB*) hdl; + m_cb = m_Handle->GUILib_RegisterMe(m_Handle->addonData); + if (!m_cb) + fprintf(stderr, "libXBMC_gui-ERROR: GUILib_register_me can't get callback table from XBMC !!!\n"); + else + return 1; + } + return 0; +} + +DLLEXPORT void GUI_unregister_me() +{ + if (m_Handle && m_cb) + m_Handle->GUILib_UnRegisterMe(m_Handle->addonData, m_cb); +} + +DLLEXPORT void GUI_lock() +{ + m_cb->Lock(); +} + +DLLEXPORT void GUI_unlock() +{ + m_cb->Unlock(); +} + +DLLEXPORT int GUI_get_screen_height() +{ + return m_cb->GetScreenHeight(); +} + +DLLEXPORT int GUI_get_screen_width() +{ + return m_cb->GetScreenWidth(); +} + +DLLEXPORT int GUI_get_video_resolution() +{ + return m_cb->GetVideoResolution(); +} + +DLLEXPORT cGUIWindow* GUI_Window_create(const char *xmlFilename, const char *defaultSkin, bool forceFallback, bool asDialog) +{ + return new cGUIWindow(xmlFilename, defaultSkin, forceFallback, asDialog); +} + +DLLEXPORT void GUI_Window_destroy(cGUIWindow* p) +{ + delete p; +} + +DLLEXPORT bool GUI_Window_OnClick(GUIHANDLE handle, int controlId) +{ + cGUIWindow *window = (cGUIWindow*) handle; + return window->OnClick(controlId); +} + +DLLEXPORT bool GUI_Window_OnFocus(GUIHANDLE handle, int controlId) +{ + cGUIWindow *window = (cGUIWindow*) handle; + return window->OnFocus(controlId); +} + +DLLEXPORT bool GUI_Window_OnInit(GUIHANDLE handle) +{ + cGUIWindow *window = (cGUIWindow*) handle; + return window->OnInit(); +} + +DLLEXPORT bool GUI_Window_OnAction(GUIHANDLE handle, int actionId) +{ + cGUIWindow *window = (cGUIWindow*) handle; + return window->OnAction(actionId); +} + + +cGUIWindow::cGUIWindow(const char *xmlFilename, const char *defaultSkin, bool forceFallback, bool asDialog) +{ + CBOnInit = NULL; + CBOnClick = NULL; + CBOnFocus = NULL; + if (m_Handle && m_cb) + { + m_WindowHandle = m_cb->Window_New(m_Handle->addonData, xmlFilename, defaultSkin, forceFallback, asDialog); + if (!m_WindowHandle) + fprintf(stderr, "libXBMC_gui-ERROR: cGUIWindow can't create window class from XBMC !!!\n"); + + m_cb->Window_SetCallbacks(m_Handle->addonData, m_WindowHandle, this, GUI_Window_OnInit, GUI_Window_OnClick, GUI_Window_OnFocus, GUI_Window_OnAction); + } +} + +cGUIWindow::~cGUIWindow() +{ + if (m_Handle && m_cb && m_WindowHandle) + { + m_cb->Window_Delete(m_Handle->addonData, m_WindowHandle); + m_WindowHandle = NULL; + } +} + +bool cGUIWindow::Show() +{ + return m_cb->Window_Show(m_Handle->addonData, m_WindowHandle); +} + +void cGUIWindow::Close() +{ + m_cb->Window_Close(m_Handle->addonData, m_WindowHandle); +} + +void cGUIWindow::DoModal() +{ + m_cb->Window_DoModal(m_Handle->addonData, m_WindowHandle); +} + +bool cGUIWindow::OnInit() +{ + if (!CBOnInit) + return false; + + return CBOnInit(m_cbhdl); +} + +bool cGUIWindow::OnClick(int controlId) +{ + if (!CBOnClick) + return false; + + return CBOnClick(m_cbhdl, controlId); +} + +bool cGUIWindow::OnFocus(int controlId) +{ + if (!CBOnFocus) + return false; + + return CBOnFocus(m_cbhdl, controlId); +} + +bool cGUIWindow::OnAction(int actionId) +{ + if (!CBOnAction) + return false; + + return CBOnAction(m_cbhdl, actionId); +} + +bool cGUIWindow::SetFocusId(int iControlId) +{ + return m_cb->Window_SetFocusId(m_Handle->addonData, m_WindowHandle, iControlId); +} + +int cGUIWindow::GetFocusId() +{ + return m_cb->Window_GetFocusId(m_Handle->addonData, m_WindowHandle); +} + +bool cGUIWindow::SetCoordinateResolution(int res) +{ + return m_cb->Window_SetCoordinateResolution(m_Handle->addonData, m_WindowHandle, res); +} + +void cGUIWindow::SetProperty(const char *key, const char *value) +{ + m_cb->Window_SetProperty(m_Handle->addonData, m_WindowHandle, key, value); +} + +void cGUIWindow::SetPropertyInt(const char *key, int value) +{ + m_cb->Window_SetPropertyInt(m_Handle->addonData, m_WindowHandle, key, value); +} + +void cGUIWindow::SetPropertyBool(const char *key, bool value) +{ + m_cb->Window_SetPropertyBool(m_Handle->addonData, m_WindowHandle, key, value); +} + +void cGUIWindow::SetPropertyDouble(const char *key, double value) +{ + m_cb->Window_SetPropertyDouble(m_Handle->addonData, m_WindowHandle, key, value); +} + +const char *cGUIWindow::GetProperty(const char *key) const +{ + return m_cb->Window_GetProperty(m_Handle->addonData, m_WindowHandle, key); +} + +int cGUIWindow::GetPropertyInt(const char *key) const +{ + return m_cb->Window_GetPropertyInt(m_Handle->addonData, m_WindowHandle, key); +} + +bool cGUIWindow::GetPropertyBool(const char *key) const +{ + return m_cb->Window_GetPropertyBool(m_Handle->addonData, m_WindowHandle, key); +} + +double cGUIWindow::GetPropertyDouble(const char *key) const +{ + return m_cb->Window_GetPropertyDouble(m_Handle->addonData, m_WindowHandle, key); +} + +void cGUIWindow::ClearProperties() +{ + m_cb->Window_ClearProperties(m_Handle->addonData, m_WindowHandle); +} + +int cGUIWindow::GetListSize() +{ + return m_cb->Window_GetListSize(m_Handle->addonData, m_WindowHandle); +} + +void cGUIWindow::ClearList() +{ + m_cb->Window_ClearList(m_Handle->addonData, m_WindowHandle); +} + +GUIHANDLE cGUIWindow::AddStringItem(const char *name, int itemPosition) +{ + return m_cb->Window_AddStringItem(m_Handle->addonData, m_WindowHandle, name, itemPosition); +} + +void cGUIWindow::AddItem(GUIHANDLE item, int itemPosition) +{ + m_cb->Window_AddItem(m_Handle->addonData, m_WindowHandle, item, itemPosition); +} + +void cGUIWindow::AddItem(cListItem *item, int itemPosition) +{ + m_cb->Window_AddItem(m_Handle->addonData, m_WindowHandle, item->m_ListItemHandle, itemPosition); +} + +void cGUIWindow::RemoveItem(int itemPosition) +{ + m_cb->Window_RemoveItem(m_Handle->addonData, m_WindowHandle, itemPosition); +} + +GUIHANDLE cGUIWindow::GetListItem(int listPos) +{ + return m_cb->Window_GetListItem(m_Handle->addonData, m_WindowHandle, listPos); +} + +void cGUIWindow::SetCurrentListPosition(int listPos) +{ + m_cb->Window_SetCurrentListPosition(m_Handle->addonData, m_WindowHandle, listPos); +} + +int cGUIWindow::GetCurrentListPosition() +{ + return m_cb->Window_GetCurrentListPosition(m_Handle->addonData, m_WindowHandle); +} + +void cGUIWindow::SetControlLabel(int controlId, const char *label) +{ + m_cb->Window_SetControlLabel(m_Handle->addonData, m_WindowHandle, controlId, label); +} + +///------------------------------------- +/// cGUISpinControl + +DLLEXPORT cGUISpinControl* GUI_control_get_spin(cGUIWindow *window, int controlId) +{ + return new cGUISpinControl(window, controlId); +} + +DLLEXPORT void GUI_control_release_spin(cGUISpinControl* p) +{ + delete p; +} + +cGUISpinControl::cGUISpinControl(cGUIWindow *window, int controlId) + : m_Window(window) + , m_ControlId(controlId) +{ + m_SpinHandle = m_cb->Window_GetControl_Spin(m_Handle->addonData, m_Window->m_WindowHandle, controlId); +} + +void cGUISpinControl::SetVisible(bool yesNo) +{ + if (m_SpinHandle) + m_cb->Control_Spin_SetVisible(m_Handle->addonData, m_SpinHandle, yesNo); +} + +void cGUISpinControl::SetText(const char *label) +{ + if (m_SpinHandle) + m_cb->Control_Spin_SetText(m_Handle->addonData, m_SpinHandle, label); +} + +void cGUISpinControl::Clear() +{ + if (m_SpinHandle) + m_cb->Control_Spin_Clear(m_Handle->addonData, m_SpinHandle); +} + +void cGUISpinControl::AddLabel(const char *label, int iValue) +{ + if (m_SpinHandle) + m_cb->Control_Spin_AddLabel(m_Handle->addonData, m_SpinHandle, label, iValue); +} + +int cGUISpinControl::GetValue() +{ + if (!m_SpinHandle) + return -1; + + return m_cb->Control_Spin_GetValue(m_Handle->addonData, m_SpinHandle); +} + +void cGUISpinControl::SetValue(int iValue) +{ + if (m_SpinHandle) + m_cb->Control_Spin_SetValue(m_Handle->addonData, m_SpinHandle, iValue); +} + +///------------------------------------- +/// cGUIRadioButton + +DLLEXPORT cGUIRadioButton* GUI_control_get_radiobutton(cGUIWindow *window, int controlId) +{ + return new cGUIRadioButton(window, controlId); +} + +DLLEXPORT void GUI_control_release_radiobutton(cGUIRadioButton* p) +{ + delete p; +} + +cGUIRadioButton::cGUIRadioButton(cGUIWindow *window, int controlId) + : m_Window(window) + , m_ControlId(controlId) +{ + m_ButtonHandle = m_cb->Window_GetControl_RadioButton(m_Handle->addonData, m_Window->m_WindowHandle, controlId); +} + +void cGUIRadioButton::SetVisible(bool yesNo) +{ + if (m_ButtonHandle) + m_cb->Control_RadioButton_SetVisible(m_Handle->addonData, m_ButtonHandle, yesNo); +} + +void cGUIRadioButton::SetText(const char *label) +{ + if (m_ButtonHandle) + m_cb->Control_RadioButton_SetText(m_Handle->addonData, m_ButtonHandle, label); +} + +void cGUIRadioButton::SetSelected(bool yesNo) +{ + if (m_ButtonHandle) + m_cb->Control_RadioButton_SetSelected(m_Handle->addonData, m_ButtonHandle, yesNo); +} + +bool cGUIRadioButton::IsSelected() +{ + if (!m_ButtonHandle) + return false; + + return m_cb->Control_RadioButton_IsSelected(m_Handle->addonData, m_ButtonHandle); +} + + +///------------------------------------- +/// cGUIProgressControl + +DLLEXPORT cGUIProgressControl* GUI_control_get_progress(cGUIWindow *window, int controlId) +{ + return new cGUIProgressControl(window, controlId); +} + +DLLEXPORT void GUI_control_release_progress(cGUIProgressControl* p) +{ + delete p; +} + +cGUIProgressControl::cGUIProgressControl(cGUIWindow *window, int controlId) + : m_Window(window) + , m_ControlId(controlId) +{ + m_ProgressHandle = m_cb->Window_GetControl_Progress(m_Handle->addonData, m_Window->m_WindowHandle, controlId); +} + +void cGUIProgressControl::SetPercentage(float fPercent) +{ + if (m_ProgressHandle) + m_cb->Control_Progress_SetPercentage(m_Handle->addonData, m_ProgressHandle, fPercent); +} + +float cGUIProgressControl::GetPercentage() const +{ + if (!m_ProgressHandle) + return 0.0; + + return m_cb->Control_Progress_GetPercentage(m_Handle->addonData, m_ProgressHandle); +} + +void cGUIProgressControl::SetInfo(int iInfo) +{ + if (m_ProgressHandle) + m_cb->Control_Progress_SetInfo(m_Handle->addonData, m_ProgressHandle, iInfo); +} + +int cGUIProgressControl::GetInfo() const +{ + if (!m_ProgressHandle) + return -1; + + return m_cb->Control_Progress_GetInfo(m_Handle->addonData, m_ProgressHandle); +} + +string cGUIProgressControl::GetDescription() const +{ + if (!m_ProgressHandle) + return ""; + + return m_cb->Control_Progress_GetDescription(m_Handle->addonData, m_ProgressHandle); +} + + +///------------------------------------- +/// cListItem + +DLLEXPORT cListItem* GUI_ListItem_create(const char *label, const char *label2, const char *iconImage, const char *thumbnailImage, const char *path) +{ + return new cListItem(label, label2, iconImage, thumbnailImage, path); +} + +DLLEXPORT void GUI_ListItem_destroy(cListItem* p) +{ + delete p; +} + + +cListItem::cListItem(const char *label, const char *label2, const char *iconImage, const char *thumbnailImage, const char *path) +{ + m_ListItemHandle = m_cb->ListItem_Create(m_Handle->addonData, label, label2, iconImage, thumbnailImage, path); +} + +const char *cListItem::GetLabel() +{ + if (!m_ListItemHandle) + return ""; + + return m_cb->ListItem_GetLabel(m_Handle->addonData, m_ListItemHandle); +} + +void cListItem::SetLabel(const char *label) +{ + if (m_ListItemHandle) + m_cb->ListItem_SetLabel(m_Handle->addonData, m_ListItemHandle, label); +} + +const char *cListItem::GetLabel2() +{ + if (!m_ListItemHandle) + return ""; + + return m_cb->ListItem_GetLabel2(m_Handle->addonData, m_ListItemHandle); +} + +void cListItem::SetLabel2(const char *label) +{ + if (m_ListItemHandle) + m_cb->ListItem_SetLabel2(m_Handle->addonData, m_ListItemHandle, label); +} + +void cListItem::SetIconImage(const char *image) +{ + if (m_ListItemHandle) + m_cb->ListItem_SetIconImage(m_Handle->addonData, m_ListItemHandle, image); +} + +void cListItem::SetThumbnailImage(const char *image) +{ + if (m_ListItemHandle) + m_cb->ListItem_SetThumbnailImage(m_Handle->addonData, m_ListItemHandle, image); +} + +void cListItem::SetInfo(const char *Info) +{ + if (m_ListItemHandle) + m_cb->ListItem_SetInfo(m_Handle->addonData, m_ListItemHandle, Info); +} + +void cListItem::SetProperty(const char *key, const char *value) +{ + if (m_ListItemHandle) + m_cb->ListItem_SetProperty(m_Handle->addonData, m_ListItemHandle, key, value); +} + +const char *cListItem::GetProperty(const char *key) const +{ + if (!m_ListItemHandle) + return ""; + + return m_cb->ListItem_GetProperty(m_Handle->addonData, m_ListItemHandle, key); +} + +void cListItem::SetPath(const char *Path) +{ + if (m_ListItemHandle) + m_cb->ListItem_SetPath(m_Handle->addonData, m_ListItemHandle, Path); +} + + +}; diff --git a/lib/addons/library.xbmc.gui/project/VS2008Express/libXBMC_gui.sln b/lib/addons/library.xbmc.gui/project/VS2008Express/libXBMC_gui.sln new file mode 100644 index 0000000000..7b05929c0c --- /dev/null +++ b/lib/addons/library.xbmc.gui/project/VS2008Express/libXBMC_gui.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libXBMC_gui", "libXBMC_gui.vcproj", "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Debug|Win32.ActiveCfg = Debug|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Debug|Win32.Build.0 = Debug|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Release|Win32.ActiveCfg = Release|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/lib/addons/library.xbmc.gui/project/VS2008Express/libXBMC_gui.vcproj b/lib/addons/library.xbmc.gui/project/VS2008Express/libXBMC_gui.vcproj new file mode 100644 index 0000000000..60b62b0afe --- /dev/null +++ b/lib/addons/library.xbmc.gui/project/VS2008Express/libXBMC_gui.vcproj @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/addons/library.xbmc.pvr/Makefile.in b/lib/addons/library.xbmc.pvr/Makefile.in new file mode 100644 index 0000000000..cbba1899bd --- /dev/null +++ b/lib/addons/library.xbmc.pvr/Makefile.in @@ -0,0 +1,27 @@ +ARCH=@ARCH@ +INCLUDES=-I. -I../../../xbmc/addons/include -I../../../xbmc -I../../../xbmc/cores/dvdplayer/DVDDemuxers +DEFINES+= +CXXFLAGS=-fPIC +LIBNAME=libXBMC_pvr +OBJS=$(LIBNAME).o + +LIB_SHARED=../../../addons/library.xbmc.pvr/$(LIBNAME)-$(ARCH).so + +all: $(LIB_SHARED) + +$(LIB_SHARED): $(OBJS) +ifeq ($(findstring osx,$(ARCH)), osx) + @export MACOSX_DEPLOYMENT_TARGET=10.4 + $(CXX) -bundle -shared -flat_namespace -undefined suppress -o $(LIB_SHARED) $(OBJS) + ../../../tools/Mach5/wrapper.rb $@;mv output.so $@ +else + $(CXX) $(CFLAGS) $(LDFLAGS) -shared -g -o $(LIB_SHARED) $(OBJS) +endif + +CLEAN_FILES = \ + $(LIB_SHARED) \ + +DISTCLEAN_FILES= \ + Makefile \ + +include ../../../Makefile.include diff --git a/lib/addons/library.xbmc.pvr/libXBMC_pvr.cpp b/lib/addons/library.xbmc.pvr/libXBMC_pvr.cpp new file mode 100644 index 0000000000..693b70487b --- /dev/null +++ b/lib/addons/library.xbmc.pvr/libXBMC_pvr.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2005-2010 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#define USE_DEMUX // enable including of the demuxer packet structure + +#include +#include +#include +#include +#include "../../../addons/library.xbmc.pvr/libXBMC_pvr.h" +#include "addons/AddonHelpers_local.h" +#include "cores/dvdplayer/DVDDemuxers/DVDDemuxPacket.h" + +#ifdef _WIN32 +#include +#define DLLEXPORT __declspec(dllexport) +#else +#define DLLEXPORT +#endif + +using namespace std; + +AddonCB *m_Handle = NULL; +CB_PVRLib *m_cb = NULL; + +extern "C" +{ + +DLLEXPORT int PVR_register_me(void *hdl) +{ + if (!hdl) + fprintf(stderr, "libXBMC_pvr-ERROR: PVRLib_register_me is called with NULL handle !!!\n"); + else + { + m_Handle = (AddonCB*) hdl; + m_cb = m_Handle->PVRLib_RegisterMe(m_Handle->addonData); + if (!m_cb) + fprintf(stderr, "libXBMC_pvr-ERROR: PVRLib_register_me can't get callback table from XBMC !!!\n"); + else + return 1; + } + return 0; +} + +DLLEXPORT void PVR_unregister_me() +{ + if (m_Handle && m_cb) + m_Handle->PVRLib_UnRegisterMe(m_Handle->addonData, m_cb); +} + +DLLEXPORT void PVR_transfer_epg_entry(const PVRHANDLE handle, const PVR_PROGINFO *epgentry) +{ + if (m_cb == NULL) + return; + + m_cb->TransferEpgEntry(m_Handle->addonData, handle, epgentry); +} + +DLLEXPORT void PVR_transfer_channel_entry(const PVRHANDLE handle, const PVR_CHANNEL *chan) +{ + if (m_cb == NULL) + return; + + m_cb->TransferChannelEntry(m_Handle->addonData, handle, chan); +} + +DLLEXPORT void PVR_transfer_timer_entry(const PVRHANDLE handle, const PVR_TIMERINFO *timer) +{ + if (m_cb == NULL) + return; + + m_cb->TransferTimerEntry(m_Handle->addonData, handle, timer); +} + +DLLEXPORT void PVR_transfer_recording_entry(const PVRHANDLE handle, const PVR_RECORDINGINFO *recording) +{ + if (m_cb == NULL) + return; + + m_cb->TransferRecordingEntry(m_Handle->addonData, handle, recording); +} + +DLLEXPORT void PVR_add_menu_hook(PVR_MENUHOOK *hook) +{ + if (m_cb == NULL) + return; + + m_cb->AddMenuHook(m_Handle->addonData, hook); +} + +DLLEXPORT void PVR_recording(const char *Name, const char *FileName, bool On) +{ + if (m_cb == NULL) + return; + + m_cb->Recording(m_Handle->addonData, Name, FileName, On); +} + +DLLEXPORT void PVR_trigger_timer_update() +{ + if (m_cb == NULL) + return; + + m_cb->TriggerTimerUpdate(m_Handle->addonData); +} + +DLLEXPORT void PVR_trigger_recording_update() +{ + if (m_cb == NULL) + return; + + m_cb->TriggerTimerUpdate(m_Handle->addonData); +} + +DLLEXPORT void PVR_free_demux_packet(DemuxPacket* pPacket) +{ + if (m_cb == NULL) + return; + + m_cb->FreeDemuxPacket(m_Handle->addonData, pPacket); +} + +DLLEXPORT DemuxPacket* PVR_allocate_demux_packet(int iDataSize) +{ + if (m_cb == NULL) + return NULL; + + return m_cb->AllocateDemuxPacket(m_Handle->addonData, iDataSize); +} + +}; diff --git a/lib/addons/library.xbmc.pvr/project/VS2008Express/libXBMC_pvr.sln b/lib/addons/library.xbmc.pvr/project/VS2008Express/libXBMC_pvr.sln new file mode 100644 index 0000000000..e69868a05c --- /dev/null +++ b/lib/addons/library.xbmc.pvr/project/VS2008Express/libXBMC_pvr.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libXBMC_pvr", "libXBMC_pvr.vcproj", "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Debug|Win32.ActiveCfg = Debug|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Debug|Win32.Build.0 = Debug|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Release|Win32.ActiveCfg = Release|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/lib/addons/library.xbmc.pvr/project/VS2008Express/libXBMC_pvr.vcproj b/lib/addons/library.xbmc.pvr/project/VS2008Express/libXBMC_pvr.vcproj new file mode 100644 index 0000000000..12c11f2881 --- /dev/null +++ b/lib/addons/library.xbmc.pvr/project/VS2008Express/libXBMC_pvr.vcproj @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/project/VS2008Express/XBMC for Windows.sln b/project/VS2008Express/XBMC for Windows.sln index f4b7b69c0b..449fc904aa 100644 --- a/project/VS2008Express/XBMC for Windows.sln +++ b/project/VS2008Express/XBMC for Windows.sln @@ -113,6 +113,21 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libwavpack_dll", "..\..\xbm EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libStSoundLibrary_dll", "..\..\xbmc\cores\paplayer\YMCodec\StSoundLibrary\StSoundLibrary.vcproj", "{D9885434-4B9D-41FB-B5FC-5E89D41AEFF0}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libXBMC_addon", "..\..\lib\addons\library.xbmc.addon\project\VS2008Express\libXBMC_addon.vcproj", "{2DCEA60B-4EBC-4DB7-9FBD-297C1EFD95D7}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libXBMC_gui", "..\..\lib\addons\library.xbmc.gui\project\VS2008Express\libXBMC_gui.vcproj", "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libXBMC_pvr", "..\..\lib\addons\library.xbmc.pvr\project\VS2008Express\libXBMC_pvr.vcproj", "{6D8C91F8-992F-4C83-9DE3-485D64EF8420}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pvrclient_vdr", "..\..\xbmc\pvrclients\vdr-streamdev\project\VS2008Express\XBMC_VDR.vcproj", "{838CDE27-AFDF-449F-9981-A78903C9C71E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pvrclient_mptv", "..\..\xbmc\pvrclients\MediaPortal\project\VS2008Express\XBMC_MPTV.vcproj", "{74C9642E-1988-48DC-8404-11717C355378}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pvrclient_hts", "..\..\xbmc\pvrclients\tvheadend\project\VS2008Express\XBMC_hts.vcproj", "{2F4A2296-3E00-4881-B6BC-1B7ADAC3CBF4}" + ProjectSection(ProjectDependencies) = postProject + {00700E12-A63B-4E54-B962-4011A90584BD} = {00700E12-A63B-4E54-B962-4011A90584BD} + EndProjectSection +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libdts_dll", "..\..\xbmc\cores\dvdplayer\Codecs\libdts\vc++\libdts.vcproj", "{F93133AB-EB51-4955-AEDF-639632891042}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libAC3Codec_dll", "..\..\xbmc\cores\paplayer\AC3Codec\vc++\AC3Codec.vcproj", "{3039DB92-836E-4563-9DE0-D0C2AB28F14F}" @@ -501,6 +516,54 @@ Global {D9885434-4B9D-41FB-B5FC-5E89D41AEFF0}.Release (DirectX)|Win32.Build.0 = Release|Win32 {D9885434-4B9D-41FB-B5FC-5E89D41AEFF0}.Release (OpenGL)|Win32.ActiveCfg = Release|Win32 {D9885434-4B9D-41FB-B5FC-5E89D41AEFF0}.Release (OpenGL)|Win32.Build.0 = Release|Win32 + {2DCEA60B-4EBC-4DB7-9FBD-297C1EFD95D7}.Debug (DirectX)|Win32.ActiveCfg = Debug|Win32 + {2DCEA60B-4EBC-4DB7-9FBD-297C1EFD95D7}.Debug (DirectX)|Win32.Build.0 = Debug|Win32 + {2DCEA60B-4EBC-4DB7-9FBD-297C1EFD95D7}.Debug (OpenGL)|Win32.ActiveCfg = Debug|Win32 + {2DCEA60B-4EBC-4DB7-9FBD-297C1EFD95D7}.Debug (OpenGL)|Win32.Build.0 = Debug|Win32 + {2DCEA60B-4EBC-4DB7-9FBD-297C1EFD95D7}.Release (DirectX)|Win32.ActiveCfg = Release|Win32 + {2DCEA60B-4EBC-4DB7-9FBD-297C1EFD95D7}.Release (DirectX)|Win32.Build.0 = Release|Win32 + {2DCEA60B-4EBC-4DB7-9FBD-297C1EFD95D7}.Release (OpenGL)|Win32.ActiveCfg = Release|Win32 + {2DCEA60B-4EBC-4DB7-9FBD-297C1EFD95D7}.Release (OpenGL)|Win32.Build.0 = Release|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Debug (DirectX)|Win32.ActiveCfg = Debug|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Debug (DirectX)|Win32.Build.0 = Debug|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Debug (OpenGL)|Win32.ActiveCfg = Debug|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Debug (OpenGL)|Win32.Build.0 = Debug|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Release (DirectX)|Win32.ActiveCfg = Release|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Release (DirectX)|Win32.Build.0 = Release|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Release (OpenGL)|Win32.ActiveCfg = Release|Win32 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}.Release (OpenGL)|Win32.Build.0 = Release|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Debug (DirectX)|Win32.ActiveCfg = Debug|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Debug (DirectX)|Win32.Build.0 = Debug|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Debug (OpenGL)|Win32.ActiveCfg = Debug|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Debug (OpenGL)|Win32.Build.0 = Debug|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Release (DirectX)|Win32.ActiveCfg = Release|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Release (DirectX)|Win32.Build.0 = Release|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Release (OpenGL)|Win32.ActiveCfg = Release|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Release (OpenGL)|Win32.Build.0 = Release|Win32 + {838CDE27-AFDF-449F-9981-A78903C9C71E}.Debug (DirectX)|Win32.ActiveCfg = Debug|Win32 + {838CDE27-AFDF-449F-9981-A78903C9C71E}.Debug (DirectX)|Win32.Build.0 = Debug|Win32 + {838CDE27-AFDF-449F-9981-A78903C9C71E}.Debug (OpenGL)|Win32.ActiveCfg = Debug|Win32 + {838CDE27-AFDF-449F-9981-A78903C9C71E}.Debug (OpenGL)|Win32.Build.0 = Debug|Win32 + {838CDE27-AFDF-449F-9981-A78903C9C71E}.Release (DirectX)|Win32.ActiveCfg = Release|Win32 + {838CDE27-AFDF-449F-9981-A78903C9C71E}.Release (DirectX)|Win32.Build.0 = Release|Win32 + {838CDE27-AFDF-449F-9981-A78903C9C71E}.Release (OpenGL)|Win32.ActiveCfg = Release|Win32 + {838CDE27-AFDF-449F-9981-A78903C9C71E}.Release (OpenGL)|Win32.Build.0 = Release|Win32 + {74C9642E-1988-48DC-8404-11717C355378}.Debug (DirectX)|Win32.ActiveCfg = Debug|Win32 + {74C9642E-1988-48DC-8404-11717C355378}.Debug (DirectX)|Win32.Build.0 = Debug|Win32 + {74C9642E-1988-48DC-8404-11717C355378}.Debug (OpenGL)|Win32.ActiveCfg = Debug|Win32 + {74C9642E-1988-48DC-8404-11717C355378}.Debug (OpenGL)|Win32.Build.0 = Debug|Win32 + {74C9642E-1988-48DC-8404-11717C355378}.Release (DirectX)|Win32.ActiveCfg = Release|Win32 + {74C9642E-1988-48DC-8404-11717C355378}.Release (DirectX)|Win32.Build.0 = Release|Win32 + {74C9642E-1988-48DC-8404-11717C355378}.Release (OpenGL)|Win32.ActiveCfg = Release|Win32 + {74C9642E-1988-48DC-8404-11717C355378}.Release (OpenGL)|Win32.Build.0 = Release|Win32 + {2F4A2296-3E00-4881-B6BC-1B7ADAC3CBF4}.Debug (DirectX)|Win32.ActiveCfg = Debug|Win32 + {2F4A2296-3E00-4881-B6BC-1B7ADAC3CBF4}.Debug (DirectX)|Win32.Build.0 = Debug|Win32 + {2F4A2296-3E00-4881-B6BC-1B7ADAC3CBF4}.Debug (OpenGL)|Win32.ActiveCfg = Debug|Win32 + {2F4A2296-3E00-4881-B6BC-1B7ADAC3CBF4}.Debug (OpenGL)|Win32.Build.0 = Debug|Win32 + {2F4A2296-3E00-4881-B6BC-1B7ADAC3CBF4}.Release (DirectX)|Win32.ActiveCfg = Release|Win32 + {2F4A2296-3E00-4881-B6BC-1B7ADAC3CBF4}.Release (DirectX)|Win32.Build.0 = Release|Win32 + {2F4A2296-3E00-4881-B6BC-1B7ADAC3CBF4}.Release (OpenGL)|Win32.ActiveCfg = Release|Win32 + {2F4A2296-3E00-4881-B6BC-1B7ADAC3CBF4}.Release (OpenGL)|Win32.Build.0 = Release|Win32 {F93133AB-EB51-4955-AEDF-639632891042}.Debug (DirectX)|Win32.ActiveCfg = Debug|Win32 {F93133AB-EB51-4955-AEDF-639632891042}.Debug (DirectX)|Win32.Build.0 = Debug|Win32 {F93133AB-EB51-4955-AEDF-639632891042}.Debug (OpenGL)|Win32.ActiveCfg = Debug|Win32 @@ -629,6 +692,14 @@ Global {FC7EBFE5-233C-4608-A4D9-5EF512BA96BF}.Release (DirectX)|Win32.Build.0 = Release|Win32 {FC7EBFE5-233C-4608-A4D9-5EF512BA96BF}.Release (OpenGL)|Win32.ActiveCfg = Release|Win32 {FC7EBFE5-233C-4608-A4D9-5EF512BA96BF}.Release (OpenGL)|Win32.Build.0 = Release|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Debug (DirectX)|Win32.ActiveCfg = Debug|Win32^ + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Debug (DirectX)|Win32.Build.0 = Debug|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Debug (OpenGL)|Win32.ActiveCfg = Debug|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Debug (OpenGL)|Win32.Build.0 = Debug|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Release (DirectX)|Win32.ActiveCfg = Release|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Release (DirectX)|Win32.Build.0 = Release|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Release (OpenGL)|Win32.ActiveCfg = Release|Win32 + {6D8C91F8-992F-4C83-9DE3-485D64EF8420}.Release (OpenGL)|Win32.Build.0 = Release|Win32 {680CDC79-9CCA-4282-9A8D-927CB0DB55B2}.Debug (DirectX)|Win32.ActiveCfg = Release|Win32 {680CDC79-9CCA-4282-9A8D-927CB0DB55B2}.Debug (DirectX)|Win32.Build.0 = Release|Win32 {680CDC79-9CCA-4282-9A8D-927CB0DB55B2}.Debug (OpenGL)|Win32.ActiveCfg = Release|Win32 @@ -677,14 +748,14 @@ Global {62C61EDD-FDC6-4F2C-9ED6-0FEC3C07D616}.Release (DirectX)|Win32.Build.0 = Release|Win32 {62C61EDD-FDC6-4F2C-9ED6-0FEC3C07D616}.Release (OpenGL)|Win32.ActiveCfg = ReleaseItanium|Win32 {62C61EDD-FDC6-4F2C-9ED6-0FEC3C07D616}.Release (OpenGL)|Win32.Build.0 = ReleaseItanium|Win32 - {BE0618A0-8043-433D-8F65-869267318CF7}.Debug (DirectX)|Win32.ActiveCfg = Release|Win32 - {BE0618A0-8043-433D-8F65-869267318CF7}.Debug (DirectX)|Win32.Build.0 = Release|Win32 - {BE0618A0-8043-433D-8F65-869267318CF7}.Debug (OpenGL)|Win32.ActiveCfg = Debug|Win32 - {BE0618A0-8043-433D-8F65-869267318CF7}.Debug (OpenGL)|Win32.Build.0 = Debug|Win32 - {BE0618A0-8043-433D-8F65-869267318CF7}.Release (DirectX)|Win32.ActiveCfg = Release|Win32 - {BE0618A0-8043-433D-8F65-869267318CF7}.Release (DirectX)|Win32.Build.0 = Release|Win32 - {BE0618A0-8043-433D-8F65-869267318CF7}.Release (OpenGL)|Win32.ActiveCfg = Release|Win32 - {BE0618A0-8043-433D-8F65-869267318CF7}.Release (OpenGL)|Win32.Build.0 = Release|Win32 + {1E2FB608-3DD2-4021-A598-90008FA6DE85}.Debug (DirectX)|Win32.ActiveCfg = Debug|Win32 + {1E2FB608-3DD2-4021-A598-90008FA6DE85}.Debug (DirectX)|Win32.Build.0 = Debug|Win32 + {1E2FB608-3DD2-4021-A598-90008FA6DE85}.Debug (OpenGL)|Win32.ActiveCfg = Debug|Win32 + {1E2FB608-3DD2-4021-A598-90008FA6DE85}.Debug (OpenGL)|Win32.Build.0 = Debug|Win32 + {1E2FB608-3DD2-4021-A598-90008FA6DE85}.Release (DirectX)|Win32.ActiveCfg = Release|Win32 + {1E2FB608-3DD2-4021-A598-90008FA6DE85}.Release (DirectX)|Win32.Build.0 = Release|Win32 + {1E2FB608-3DD2-4021-A598-90008FA6DE85}.Release (OpenGL)|Win32.ActiveCfg = Release|Win32 + {1E2FB608-3DD2-4021-A598-90008FA6DE85}.Release (OpenGL)|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/project/VS2008Express/XBMC.vcproj b/project/VS2008Express/XBMC.vcproj index aafac5d5b3..8b3083666a 100644 --- a/project/VS2008Express/XBMC.vcproj +++ b/project/VS2008Express/XBMC.vcproj @@ -464,6 +464,10 @@ RelativePath="..\..\xbmc\win32\PlatformInclude.h" > + + @@ -1086,6 +1090,18 @@ RelativePath="..\..\xbmc\cores\dvdplayer\DVDDemuxers\DVDDemuxHTSP.h" > + + + + + + @@ -1198,6 +1214,14 @@ RelativePath="..\..\xbmc\cores\dvdplayer\DVDInputStreams\DVDInputStreamNavigator.h" > + + + + @@ -2343,6 +2367,14 @@ RelativePath="..\..\xbmc\GUIViewStateVideo.h" > + + + + @@ -2603,6 +2635,14 @@ RelativePath="..\..\xbmc\OggTag.h" > + + + + @@ -3159,6 +3199,14 @@ RelativePath="..\..\xbmc\Temperature.cpp" > + + + + @@ -3339,6 +3387,14 @@ RelativePath="..\..\xbmc\ViewDatabase.h" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3651,6 +3795,14 @@ RelativePath="..\..\xbmc\GUIViewStatePrograms.h" > + + + + @@ -4211,6 +4363,22 @@ RelativePath="..\..\xbmc\FileSystem\PluginDirectory.h" > + + + + + + + + @@ -5331,6 +5499,38 @@ RelativePath="..\..\xbmc\addons\AddonDll.h" > + + + + + + + + + + + + + + + + @@ -5351,6 +5551,10 @@ RelativePath="..\..\xbmc\addons\DllAddon.h" > + + @@ -5419,6 +5623,66 @@ RelativePath="..\..\xbmc\addons\Visualisation.h" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -811,6 +815,10 @@ RelativePath="..\..\guilib\GUIEditControl.h" > + + diff --git a/system/keymaps/keyboard.xml b/system/keymaps/keyboard.xml index 75167874b5..428ad50370 100644 --- a/system/keymaps/keyboard.xml +++ b/system/keymaps/keyboard.xml @@ -139,6 +139,13 @@ Backspace + + + Delete + Move + Rename + + Highlight @@ -199,6 +206,7 @@ BigStepBack AudioDelay Fullscreen + Playlist XBMC.ActivateWindow(Teletext) @@ -246,6 +254,8 @@ CodecInfo LockPreset FullScreen + XBMC.ActivateWindow(PVROSDGuide) + XBMC.ActivateWindow(PVROSDChannels) @@ -561,6 +571,36 @@ Close + + + Close + Close + Close + + + + + Close + Close + + + + + Close + Close + + + + + Close + Close + + + + + PreviousMenu + + Close diff --git a/system/keymaps/remote.xml b/system/keymaps/remote.xml index cdb8135b5a..381f4b35cb 100644 --- a/system/keymaps/remote.xml +++ b/system/keymaps/remote.xml @@ -66,8 +66,8 @@ XBMC.ActivateWindow(MyVideos) XBMC.ActivateWindow(MyMusic) XBMC.ActivateWindow(MyPictures) - XBMC.ActivateWindow(VideoLibrary,TvShows) - XBMC.ActivateWindow(Home) + XBMC.ActivateWindow(MyTV) + XBMC.ActivateWindow(MyTV) XBMC.ActivateWindow(MyVideos) XBMC.ActivateWindow(MyMusic) XBMC.ActivateWindow(MyPictures) @@ -90,6 +90,11 @@ XBMC.ActivateWindow(Settings) + + + Delete + + Delete @@ -144,10 +149,13 @@ CodecInfo Info + XBMC.ActivateWindow(PVROSDGuide) XBMC.ActivateWindow(Teletext) ShowSubtitles ShowSubtitles AudioNextLanguage + Playlist + Language AudioNextLanguage @@ -181,6 +189,8 @@