2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
23 #include "threads/SystemClock.h"
25 #include "SystemInfo.h"
29 #include <sys/utsname.h>
31 #include "GUIInfoManager.h"
32 #include "filesystem/CurlFile.h"
33 #include "network/Network.h"
34 #include "Application.h"
35 #include "windowing/WindowingFactory.h"
36 #include "guilib/LocalizeStrings.h"
38 #include "utils/TimeUtils.h"
39 #include "utils/log.h"
43 #if defined(TARGET_DARWIN)
44 #include "osx/DarwinUtils.h"
45 #include "osx/CocoaInterface.h"
47 #include "powermanagement/PowerManager.h"
48 #include "utils/StringUtils.h"
49 #include "utils/XMLUtils.h"
51 /* Target identification */
52 #if defined(TARGET_DARWIN)
53 #include <Availability.h>
54 #elif defined(TARGET_ANDROID)
55 #include <android/api-level.h>
56 #elif defined(TARGET_FREEBSD)
57 #include <sys/param.h>
58 #elif defined(TARGET_LINUX)
59 #include <linux/version.h>
64 CSysInfoJob::CSysInfoJob()
68 bool CSysInfoJob::DoWork()
70 m_info.systemUptime = GetSystemUpTime(false);
71 m_info.systemTotalUptime = GetSystemUpTime(true);
72 m_info.internetState = GetInternetState();
73 m_info.videoEncoder = GetVideoEncoder();
74 m_info.cpuFrequency = GetCPUFreqInfo();
75 m_info.kernelVersion = CSysInfo::GetKernelVersion();
76 m_info.macAddress = GetMACAddress();
77 m_info.batteryLevel = GetBatteryLevel();
81 const CSysData &CSysInfoJob::GetData() const
86 CStdString CSysInfoJob::GetCPUFreqInfo()
88 CStdString strCPUFreq;
89 double CPUFreq = GetCPUFrequency();
90 strCPUFreq.Format("%4.2fMHz", CPUFreq);
94 CSysData::INTERNET_STATE CSysInfoJob::GetInternetState()
96 // Internet connection state!
97 XFILE::CCurlFile http;
98 if (http.IsInternet())
99 return CSysData::CONNECTED;
100 if (http.IsInternet(false))
101 return CSysData::NO_DNS;
102 return CSysData::DISCONNECTED;
105 CStdString CSysInfoJob::GetMACAddress()
107 #if defined(HAS_LINUX_NETWORK) || defined(HAS_WIN32_NETWORK)
108 CNetworkInterface* iface = g_application.getNetwork().GetFirstConnectedInterface();
110 return iface->GetMacAddress();
115 CStdString CSysInfoJob::GetVideoEncoder()
117 return "GPU: " + g_Windowing.GetRenderRenderer();
120 CStdString CSysInfoJob::GetBatteryLevel()
123 strVal.Format("%d%%", g_powerManager.BatteryLevel());
127 double CSysInfoJob::GetCPUFrequency()
129 #if defined (TARGET_POSIX) || defined(TARGET_WINDOWS)
130 return double (g_cpuInfo.getCPUFrequency());
136 bool CSysInfoJob::SystemUpTime(int iInputMinutes, int &iMinutes, int &iHours, int &iDays)
138 iMinutes=0;iHours=0;iDays=0;
139 iMinutes = iInputMinutes;
140 if (iMinutes >= 60) // Hour's
142 iHours = iMinutes / 60;
143 iMinutes = iMinutes - (iHours *60);
145 if (iHours >= 24) // Days
148 iHours = iHours - (iDays * 24);
153 CStdString CSysInfoJob::GetSystemUpTime(bool bTotalUptime)
155 CStdString strSystemUptime;
156 int iInputMinutes, iMinutes,iHours,iDays;
161 iInputMinutes = g_sysinfo.GetTotalUptime() + ((int)(XbmcThreads::SystemClockMillis() / 60000));
166 iInputMinutes = (int)(XbmcThreads::SystemClockMillis() / 60000);
169 SystemUpTime(iInputMinutes,iMinutes, iHours, iDays);
172 strSystemUptime.Format("%i %s, %i %s, %i %s",
173 iDays,g_localizeStrings.Get(12393),
174 iHours,g_localizeStrings.Get(12392),
175 iMinutes, g_localizeStrings.Get(12391));
177 else if (iDays == 0 && iHours >= 1 )
179 strSystemUptime.Format("%i %s, %i %s",
180 iHours,g_localizeStrings.Get(12392),
181 iMinutes, g_localizeStrings.Get(12391));
183 else if (iDays == 0 && iHours == 0 && iMinutes >= 0)
185 strSystemUptime.Format("%i %s",
186 iMinutes, g_localizeStrings.Get(12391));
188 return strSystemUptime;
191 CStdString CSysInfo::TranslateInfo(int info) const
195 case SYSTEM_VIDEO_ENCODER_INFO:
196 return m_info.videoEncoder;
197 case NETWORK_MAC_ADDRESS:
198 return m_info.macAddress;
199 case SYSTEM_KERNEL_VERSION:
200 return m_info.kernelVersion;
201 case SYSTEM_CPUFREQUENCY:
202 return m_info.cpuFrequency;
204 return m_info.systemUptime;
205 case SYSTEM_TOTALUPTIME:
206 return m_info.systemTotalUptime;
207 case SYSTEM_INTERNET_STATE:
208 if (m_info.internetState == CSysData::CONNECTED)
209 return g_localizeStrings.Get(13296);
210 else if (m_info.internetState == CSysData::NO_DNS)
211 return g_localizeStrings.Get(13274);
213 return g_localizeStrings.Get(13297);
214 case SYSTEM_BATTERY_LEVEL:
215 return m_info.batteryLevel;
221 void CSysInfo::Reset()
226 CSysInfo::CSysInfo(void) : CInfoLoader(15 * 1000)
228 memset(MD5_Sign, 0, sizeof(MD5_Sign));
229 m_iSystemTimeTotalUp = 0;
232 CSysInfo::~CSysInfo()
236 bool CSysInfo::Load(const TiXmlNode *settings)
238 if (settings == NULL)
241 const TiXmlElement *pElement = settings->FirstChildElement("general");
243 XMLUtils::GetInt(pElement, "systemtotaluptime", m_iSystemTimeTotalUp, 0, INT_MAX);
248 bool CSysInfo::Save(TiXmlNode *settings) const
250 if (settings == NULL)
253 TiXmlNode *generalNode = settings->FirstChild("general");
254 if (generalNode == NULL)
256 TiXmlElement generalNodeNew("general");
257 generalNode = settings->InsertEndChild(generalNodeNew);
258 if (generalNode == NULL)
261 XMLUtils::SetInt(generalNode, "systemtotaluptime", m_iSystemTimeTotalUp);
266 bool CSysInfo::GetDiskSpace(const CStdString drive,int& iTotal, int& iTotalFree, int& iTotalUsed, int& iPercentFree, int& iPercentUsed)
269 ULARGE_INTEGER ULTotal= { { 0 } };
270 ULARGE_INTEGER ULTotalFree= { { 0 } };
272 if( !drive.IsEmpty() && !drive.Equals("*") )
274 #ifdef TARGET_WINDOWS
275 UINT uidriveType = GetDriveType(( drive + ":\\" ));
276 if(uidriveType != DRIVE_UNKNOWN && uidriveType != DRIVE_NO_ROOT_DIR)
278 bRet= ( 0 != GetDiskFreeSpaceEx( ( drive + ":\\" ), NULL, &ULTotal, &ULTotalFree) );
282 ULARGE_INTEGER ULTotalTmp= { { 0 } };
283 ULARGE_INTEGER ULTotalFreeTmp= { { 0 } };
284 #ifdef TARGET_WINDOWS
285 char* pcBuffer= NULL;
286 DWORD dwStrLength= GetLogicalDriveStrings( 0, pcBuffer );
287 if( dwStrLength != 0 )
290 pcBuffer= new char [dwStrLength];
291 GetLogicalDriveStrings( dwStrLength, pcBuffer );
294 if( DRIVE_FIXED == GetDriveType( pcBuffer + iPos ) &&
295 GetDiskFreeSpaceEx( ( pcBuffer + iPos ), NULL, &ULTotal, &ULTotalFree ) )
297 ULTotalTmp.QuadPart+= ULTotal.QuadPart;
298 ULTotalFreeTmp.QuadPart+= ULTotalFree.QuadPart;
300 iPos += (strlen( pcBuffer + iPos) + 1 );
301 }while( strlen( pcBuffer + iPos ) > 0 );
304 #else // for linux and osx
305 static const char *drv_letter[] = { "C:\\", "E:\\", "F:\\", "G:\\", "X:\\", "Y:\\", "Z:\\", NULL };
306 for( int i = 0; drv_letter[i]; i++)
308 if( GetDiskFreeSpaceEx( drv_letter[i], NULL, &ULTotal, &ULTotalFree ) )
310 ULTotalTmp.QuadPart+= ULTotal.QuadPart;
311 ULTotalFreeTmp.QuadPart+= ULTotalFree.QuadPart;
315 if( ULTotalTmp.QuadPart || ULTotalFreeTmp.QuadPart )
317 ULTotal.QuadPart= ULTotalTmp.QuadPart;
318 ULTotalFree.QuadPart= ULTotalFreeTmp.QuadPart;
325 iTotal = (int)( ULTotal.QuadPart / MB );
326 iTotalFree = (int)( ULTotalFree.QuadPart / MB );
327 iTotalUsed = iTotal - iTotalFree;
328 if( ULTotal.QuadPart > 0 )
330 iPercentUsed = (int)( 100.0f * ( ULTotal.QuadPart - ULTotalFree.QuadPart ) / ULTotal.QuadPart + 0.5f );
336 iPercentFree = 100 - iPercentUsed;
342 CStdString CSysInfo::GetCPUModel()
344 return "CPU: " + g_cpuInfo.getCPUModel();
347 CStdString CSysInfo::GetCPUBogoMips()
349 return "BogoMips: " + g_cpuInfo.getCPUBogoMips();
352 CStdString CSysInfo::GetCPUHardware()
354 return "Hardware: " + g_cpuInfo.getCPUHardware();
357 CStdString CSysInfo::GetCPURevision()
359 return "Revision: " + g_cpuInfo.getCPURevision();
362 CStdString CSysInfo::GetCPUSerial()
364 return "Serial: " + g_cpuInfo.getCPUSerial();
367 bool CSysInfo::IsAeroDisabled()
369 #ifdef TARGET_WINDOWS
370 BOOL aeroEnabled = FALSE;
371 HRESULT res = DwmIsCompositionEnabled(&aeroEnabled);
378 CSysInfo::WindowsVersion CSysInfo::m_WinVer = WindowsVersionUnknown;
380 bool CSysInfo::IsWindowsVersion(WindowsVersion ver)
382 if (ver == WindowsVersionUnknown)
384 return GetWindowsVersion() == ver;
387 bool CSysInfo::IsWindowsVersionAtLeast(WindowsVersion ver)
389 if (ver == WindowsVersionUnknown)
391 return GetWindowsVersion() >= ver;
394 CSysInfo::WindowsVersion CSysInfo::GetWindowsVersion()
396 #ifdef TARGET_WINDOWS
397 if (m_WinVer == WindowsVersionUnknown)
399 OSVERSIONINFOEX osvi;
400 ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
401 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
402 if (GetVersionEx((OSVERSIONINFO *)&osvi))
404 if (osvi.dwMajorVersion == 5 && (osvi.dwMinorVersion == 1 || osvi.dwMinorVersion == 2 ))
405 m_WinVer = WindowsVersionWinXP;
406 else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0)
407 m_WinVer = WindowsVersionVista;
408 else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1)
409 m_WinVer = WindowsVersionWin7;
410 else if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2)
411 m_WinVer = WindowsVersionWin8;
412 /* Insert checks for new Windows versions here */
413 else if ( (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion > 2) || osvi.dwMajorVersion > 6)
414 m_WinVer = WindowsVersionFuture;
417 #endif // TARGET_WINDOWS
421 bool CSysInfo::IsOS64bit()
423 #ifdef TARGET_WINDOWS
426 if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
429 BOOL (WINAPI *ptrIsWow64) (HANDLE, PBOOL);
430 HMODULE hKernel32 = GetModuleHandleA("kernel32");
431 if (hKernel32 == NULL)
432 return false; // Can't detect OS
433 ptrIsWow64 = (BOOL (WINAPI *) (HANDLE, PBOOL)) GetProcAddress(hKernel32, "IsWow64Process");
434 BOOL wow64proc = FALSE;
435 if (ptrIsWow64 == NULL || ptrIsWow64(GetCurrentProcess(), &wow64proc) == FALSE)
436 return false; // Can't detect OS
437 return wow64proc != FALSE;
438 #else // TARGET_WINDOWS
439 // TODO: Implement Linux, FreeBSD, Android, OSX
441 #endif // TARGET_WINDOWS
444 CStdString CSysInfo::GetKernelVersion()
446 #if defined(TARGET_DARWIN)
447 return g_sysinfo.GetUnameVersion();
448 #elif defined (TARGET_POSIX)
452 CStdString strKernel;
453 strKernel.Format("%s %s %s %s", un.sysname, un.release, un.version, un.machine);
459 OSVERSIONINFOEX osvi;
460 ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
461 osvi.dwOSVersionInfoSize = sizeof(osvi);
463 std::string strKernel = "Windows";
464 if (GetVersionEx((OSVERSIONINFO *)&osvi))
466 switch (GetWindowsVersion())
468 case WindowsVersionWinXP:
469 if (GetSystemMetrics(SM_SERVERR2))
470 strKernel.append(" Server 2003 R2");
471 else if (osvi.wSuiteMask & VER_SUITE_STORAGE_SERVER)
472 strKernel.append(" Storage Server 2003");
473 else if (osvi.wSuiteMask & VER_SUITE_WH_SERVER)
474 strKernel.append(" Home Server");
475 else if (osvi.wProductType == VER_NT_WORKSTATION && IsOS64bit())
476 strKernel.append(" XP Professional");
477 else if (osvi.wProductType != VER_NT_WORKSTATION)
478 strKernel.append(" Server 2003");
479 else if (osvi.wSuiteMask & VER_SUITE_PERSONAL)
480 strKernel.append("XP Home Edition" );
482 strKernel.append("XP Professional" );
484 case WindowsVersionVista:
485 if (osvi.wProductType == VER_NT_WORKSTATION)
486 strKernel.append(" Vista");
488 strKernel.append(" Server 2008");
490 case WindowsVersionWin7:
491 if (osvi.wProductType == VER_NT_WORKSTATION)
492 strKernel.append(" 7");
494 strKernel.append(" Server 2008 R2");
496 case WindowsVersionWin8:
497 if (osvi.wProductType == VER_NT_WORKSTATION)
498 strKernel.append(" 8");
500 strKernel.append(" Server 2012");
502 case WindowsVersionFuture:
503 strKernel.append(" Unknown Future Version");
506 strKernel.append(" Unknown version");
510 // Append Service Pack version if any
511 if (osvi.wServicePackMajor > 0)
513 strKernel.append(StringUtils::Format(" SP%d", osvi.wServicePackMajor));
514 if (osvi.wServicePackMinor > 0)
516 strKernel.append(StringUtils::Format(".%d", osvi.wServicePackMinor));
521 strKernel.append(" 64-bit");
523 strKernel.append(" 32-bit");
525 strKernel.append(StringUtils::Format(", build %d", osvi.dwBuildNumber));
529 strKernel.append(" unknown");
531 strKernel.append(" 64-bit");
533 strKernel.append(" 32-bit");
540 bool CSysInfo::HasInternet()
542 if (m_info.internetState != CSysData::UNKNOWN)
543 return m_info.internetState == CSysData::CONNECTED;
544 return (m_info.internetState = CSysInfoJob::GetInternetState()) == CSysData::CONNECTED;
547 CStdString CSysInfo::GetHddSpaceInfo(int drive, bool shortText)
550 return GetHddSpaceInfo( percent, drive, shortText);
553 CStdString CSysInfo::GetHddSpaceInfo(int& percent, int drive, bool shortText)
555 int total, totalFree, totalUsed, percentFree, percentused;
558 if (g_sysinfo.GetDiskSpace("", total, totalFree, totalUsed, percentFree, percentused))
564 case SYSTEM_FREE_SPACE:
565 percent = percentFree;
567 case SYSTEM_USED_SPACE:
568 percent = percentused;
576 case SYSTEM_FREE_SPACE:
577 strRet.Format("%i MB %s", totalFree, g_localizeStrings.Get(160));
579 case SYSTEM_USED_SPACE:
580 strRet.Format("%i MB %s", totalUsed, g_localizeStrings.Get(20162));
582 case SYSTEM_TOTAL_SPACE:
583 strRet.Format("%i MB %s", total, g_localizeStrings.Get(20161));
585 case SYSTEM_FREE_SPACE_PERCENT:
586 strRet.Format("%i %% %s", percentFree, g_localizeStrings.Get(160));
588 case SYSTEM_USED_SPACE_PERCENT:
589 strRet.Format("%i %% %s", percentused, g_localizeStrings.Get(20162));
599 strRet = g_localizeStrings.Get(161);
604 #if defined(TARGET_LINUX)
605 CStdString CSysInfo::GetLinuxDistro()
607 #if defined(TARGET_ANDROID)
610 static const char* release_file[] = { "/etc/debian_version",
612 "/etc/mandrake-release",
613 "/etc/fedora-release",
614 "/etc/redhat-release",
615 "/etc/gentoo-release",
616 "/etc/slackware-version",
618 "/etc/buildroot-release",
620 CStdString result("");
621 char buffer[256] = {'\0'};
623 /* Try reading PRETTY_NAME from /etc/os-release first.
624 * If this fails, fall back to lsb_release or distro-specific release-file. */
626 FILE *os_release = fopen("/etc/os-release", "r");
633 while (fgets(buffer, sizeof(buffer), os_release))
638 if (strcmp(key, "PRETTY_NAME") == 0)
640 char *pretty_name = val;
642 // remove newline and enclosing quotes
643 if (pretty_name[strlen(pretty_name) - 1] == '\n')
644 pretty_name[strlen(pretty_name) - 1] = '\0';
646 if (pretty_name[0] == '\'' || pretty_name[0] == '\"')
649 pretty_name[strlen(pretty_name) - 1] = '\0';
652 result = pretty_name;
659 if (!result.IsEmpty())
663 FILE* pipe = popen("unset PYTHONHOME; unset PYTHONPATH; lsb_release -d 2>/dev/null | cut -f2", "r");
666 if (fread(buffer, sizeof(char), sizeof(buffer), pipe) > 0 && !ferror(pipe))
669 if (!result.IsEmpty())
670 return result.Trim();
674 for (int i = 0; result.IsEmpty() && release_file[i]; i++)
676 file = fopen(release_file[i], "r");
679 if (fgets(buffer, sizeof(buffer), file))
682 if (!result.IsEmpty())
683 return result.Trim();
689 CLog::Log(LOGWARNING, "Unable to determine Linux distribution");
695 CStdString CSysInfo::GetUnameVersion()
697 CStdString result = "";
699 #if defined(TARGET_ANDROID)
701 if (uname(&name) == -1)
703 result += name.release;
705 result += name.machine;
706 #elif defined(TARGET_DARWIN_IOS)
707 result = GetDarwinOSReleaseString();
709 result += GetDarwinVersionString();
711 FILE* pipe = popen("uname -rm", "r");
715 if (fgets(buffer, sizeof(buffer), pipe))
718 #if defined(TARGET_DARWIN)
721 result += GetDarwinVersionString();
725 CLog::Log(LOGWARNING, "Unable to determine Uname version");
728 #endif//else !TARGET_ANDROID
730 return result.Trim();
734 #if defined(TARGET_WINDOWS)
735 CStdString CSysInfo::GetUAWindowsVersion()
737 OSVERSIONINFOEX osvi = {};
739 osvi.dwOSVersionInfoSize = sizeof(osvi);
740 CStdString strVersion = "Windows NT";
742 if (GetVersionEx((OSVERSIONINFO *)&osvi))
744 strVersion.AppendFormat(" %d.%d", osvi.dwMajorVersion, osvi.dwMinorVersion);
751 if (IsWow64Process(GetCurrentProcess(), &bIsWow))
755 strVersion.append(";WOW64");
756 GetNativeSystemInfo(&si); // different function to read the info under Wow
760 if (si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64)
761 strVersion.append(";Win64;x64");
762 else if (si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_IA64)
763 strVersion.append(";Win64;IA64");
770 CStdString CSysInfo::GetUserAgent()
773 result = "XBMC/" + g_infoManager.GetLabel(SYSTEM_BUILD_VERSION) + " (";
774 #if defined(TARGET_WINDOWS)
775 result += GetUAWindowsVersion();
776 #elif defined(TARGET_DARWIN)
777 #if defined(TARGET_DARWIN_IOS)
780 result += "Mac OS X; ";
782 result += GetUnameVersion();
783 #elif defined(TARGET_FREEBSD)
784 result += "FreeBSD; ";
785 result += GetUnameVersion();
786 #elif defined(TARGET_POSIX)
788 result += GetLinuxDistro();
790 result += GetUnameVersion();
792 result += "; http://xbmc.org)";
797 bool CSysInfo::IsAppleTV2()
799 #if defined(TARGET_DARWIN)
800 return DarwinIsAppleTV2();
806 bool CSysInfo::HasVideoToolBoxDecoder()
810 #if defined(HAVE_VIDEOTOOLBOXDECODER)
811 result = DarwinHasVideoToolboxDecoder();
816 std::string CSysInfo::GetBuildTargetPlatformName(void)
818 #if defined(TARGET_DARWIN_OSX)
820 #elif defined(TARGET_DARWIN_IOS_ATV2)
821 return "Darwin iOS ATV2";
822 #elif defined(TARGET_DARWIN_IOS)
824 #elif defined(TARGET_FREEBSD)
826 #elif defined(TARGET_ANDROID)
828 #elif defined(TARGET_LINUX)
830 #elif defined(TARGET_WINDOWS)
833 return "unknown platform";
837 std::string CSysInfo::GetBuildTargetPlatformVersion(void)
839 /* Expand macro before stringify */
840 #define STR_MACRO(x) #x
841 #define XSTR_MACRO(x) STR_MACRO(x)
843 #if defined(TARGET_DARWIN_OSX)
844 return "version " XSTR_MACRO(__MAC_OS_X_VERSION_MIN_REQUIRED);
845 #elif defined(TARGET_DARWIN_IOS)
846 return "version " XSTR_MACRO(__IPHONE_OS_VERSION_MIN_REQUIRED);
847 #elif defined(TARGET_FREEBSD)
848 return "version " XSTR_MACRO(__FreeBSD_version);
849 #elif defined(TARGET_ANDROID)
850 return "API level " XSTR_MACRO(__ANDROID_API__);
851 #elif defined(TARGET_LINUX)
852 std::string ver = StringUtils::Format("%i.%i.%i", LINUX_VERSION_CODE >> 16, (LINUX_VERSION_CODE >> 8) & 0xff, LINUX_VERSION_CODE & 0xff);
854 #elif defined(TARGET_WINDOWS)
855 return "version " XSTR_MACRO(NTDDI_VERSION);
857 return "(unknown platform)";
862 CJob *CSysInfo::GetJob() const
864 return new CSysInfoJob();
867 void CSysInfo::OnJobComplete(unsigned int jobID, bool success, CJob *job)
869 m_info = ((CSysInfoJob *)job)->GetData();
870 CInfoLoader::OnJobComplete(jobID, success, job);