diff --git a/VERSION b/VERSION index 7c1777d22..bdc7d50a4 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ PRODUCT_ID=Airtime -PRODUCT_RELEASE=2.3.0 +PRODUCT_RELEASE=2.3.1 diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 47f3959fe..2833cffb1 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -698,7 +698,6 @@ class ApiController extends Zend_Controller_Action "platform"=>Application_Model_Systemstatus::GetPlatformInfo(), "airtime_version"=>Application_Model_Preference::GetAirtimeVersion(), "services"=>array( - "rabbitmq"=>Application_Model_Systemstatus::GetRabbitMqStatus(), "pypo"=>Application_Model_Systemstatus::GetPypoStatus(), "liquidsoap"=>Application_Model_Systemstatus::GetLiquidsoapStatus(), "media_monitor"=>Application_Model_Systemstatus::GetMediaMonitorStatus() @@ -996,7 +995,6 @@ class ApiController extends Zend_Controller_Action $request = $this->getRequest(); $data = $request->getParam("data"); $media_id = $request->getParam("media_id"); - $data_arr = json_decode($data); if (!is_null($media_id)) { diff --git a/airtime_mvc/application/controllers/SystemstatusController.php b/airtime_mvc/application/controllers/SystemstatusController.php index c84a23f9e..496dae625 100644 --- a/airtime_mvc/application/controllers/SystemstatusController.php +++ b/airtime_mvc/application/controllers/SystemstatusController.php @@ -17,7 +17,6 @@ class SystemstatusController extends Zend_Controller_Action "pypo"=>Application_Model_Systemstatus::GetPypoStatus(), "liquidsoap"=>Application_Model_Systemstatus::GetLiquidsoapStatus(), "media-monitor"=>Application_Model_Systemstatus::GetMediaMonitorStatus(), - "rabbitmq-server"=>Application_Model_Systemstatus::GetRabbitMqStatus() ); $partitions = Application_Model_Systemstatus::GetDiskInfo(); diff --git a/airtime_mvc/public/js/airtime/preferences/streamsetting.js b/airtime_mvc/public/js/airtime/preferences/streamsetting.js index 5399ed2d0..01d4717b3 100644 --- a/airtime_mvc/public/js/airtime/preferences/streamsetting.js +++ b/airtime_mvc/public/js/airtime/preferences/streamsetting.js @@ -71,7 +71,7 @@ function showForIcecast(ele){ div.find("#outputMountpoint-element").show() div.find("#outputUser-label").show() div.find("#outputUser-element").show() - div.find("select[id$=data-type]").find("option[value='ogg']").attr("disabled",""); + div.find("select[id$=data-type]").find("option[value='ogg']").removeAttr("disabled"); } function checkLiquidsoapStatus(){ diff --git a/install_minimal/airtime-install b/install_minimal/airtime-install index 0bd704900..68005f54b 100755 --- a/install_minimal/airtime-install +++ b/install_minimal/airtime-install @@ -208,14 +208,12 @@ if [ "$mediamonitor" = "t" -o "$pypo" = "t" ]; then fi -/usr/lib/airtime/utils/rabbitmq-update-pid.sh > /dev/null - touch /usr/share/airtime/public/index.php if [ "$python_service" -eq "0" ]; then #only run airtime-check-system if all components were installed echo -e "\n*** Verifying your system environment, running airtime-check-system ***" - sleep 15 + sleep 10 set +e airtime-check-system --no-color diff --git a/install_minimal/include/airtime-constants.php b/install_minimal/include/airtime-constants.php index cd5514f6d..10c620da9 100644 --- a/install_minimal/include/airtime-constants.php +++ b/install_minimal/include/airtime-constants.php @@ -1,3 +1,3 @@ /dev/null 2>&1 monit unmonitor airtime-liquidsoap >/dev/null 2>&1 monit unmonitor airtime-playout >/dev/null 2>&1 -monit unmonitor rabbitmq-server set -e #virtualenv_bin="/usr/lib/airtime/airtime_virtualenv/bin/" diff --git a/install_minimal/include/airtime-upgrade.php b/install_minimal/include/airtime-upgrade.php index 37772579a..cfa2093dc 100644 --- a/install_minimal/include/airtime-upgrade.php +++ b/install_minimal/include/airtime-upgrade.php @@ -108,4 +108,8 @@ if (strcmp($version, "2.3.0") < 0) { passthru("php --php-ini $SCRIPTPATH/../airtime-php.ini $SCRIPTPATH/../upgrades/airtime-2.3.0/airtime-upgrade.php"); pause(); } +if (strcmp($version, "2.3.1") < 0) { + passthru("php --php-ini $SCRIPTPATH/../airtime-php.ini $SCRIPTPATH/../upgrades/airtime-2.3.1/airtime-upgrade.php"); + pause(); +} echo "******************************* Upgrade Complete *******************************".PHP_EOL; diff --git a/install_minimal/upgrades/airtime-2.3.1/DbUpgrade.php b/install_minimal/upgrades/airtime-2.3.1/DbUpgrade.php new file mode 100644 index 000000000..363b5776a --- /dev/null +++ b/install_minimal/upgrades/airtime-2.3.1/DbUpgrade.php @@ -0,0 +1,25 @@ +&1 | grep -v \"will create implicit index\""); + passthru("export PGPASSWORD=$password && psql -h $host -U $username -q -f $dir/data/upgrade.sql $database 2>&1 | grep -v \"will create implicit index\""); + } +} diff --git a/install_minimal/upgrades/airtime-2.3.1/airtime-upgrade.php b/install_minimal/upgrades/airtime-2.3.1/airtime-upgrade.php new file mode 100644 index 000000000..53470a0df --- /dev/null +++ b/install_minimal/upgrades/airtime-2.3.1/airtime-upgrade.php @@ -0,0 +1,15 @@ +fetchColumn(); + + date_default_timezone_set($timezone); + } + + public static function connectToDatabase($p_exitOnError = true) + { + try { + $con = Propel::getConnection(); + } catch (Exception $e) { + echo $e->getMessage().PHP_EOL; + echo "Database connection problem.".PHP_EOL; + echo "Check if database exists with corresponding permissions.".PHP_EOL; + if ($p_exitOnError) { + exit(1); + } + return false; + } + return true; + } + + + public static function DbTableExists($p_name) + { + $con = Propel::getConnection(); + try { + $sql = "SELECT * FROM ".$p_name." LIMIT 1"; + $con->query($sql); + } catch (PDOException $e){ + return false; + } + return true; + } + + private static function GetAirtimeSrcDir() + { + return __DIR__."/../../../../airtime_mvc"; + } + + public static function MigrateTablesToVersion($dir, $version) + { + echo "Upgrading database, may take several minutes, please wait".PHP_EOL; + + $appDir = self::GetAirtimeSrcDir(); + $command = "php --php-ini $dir/../../airtime-php.ini ". + "$appDir/library/doctrine/migrations/doctrine-migrations.phar ". + "--configuration=$dir/common/migrations.xml ". + "--db-configuration=$appDir/library/doctrine/migrations/migrations-db.php ". + "--no-interaction migrations:migrate $version"; + system($command); + } + + public static function BypassMigrations($dir, $version) + { + $appDir = self::GetAirtimeSrcDir(); + $command = "php --php-ini $dir/../../airtime-php.ini ". + "$appDir/library/doctrine/migrations/doctrine-migrations.phar ". + "--configuration=$dir/common/migrations.xml ". + "--db-configuration=$appDir/library/doctrine/migrations/migrations-db.php ". + "--no-interaction --add migrations:version $version"; + system($command); + } + + public static function upgradeConfigFiles(){ + + $configFiles = array(UpgradeCommon::CONF_FILE_AIRTIME, + UpgradeCommon::CONF_FILE_PYPO, + //this is not necessary because liquidsoap configs + //are automatically generated + //UpgradeCommon::CONF_FILE_LIQUIDSOAP, + UpgradeCommon::CONF_FILE_MEDIAMONITOR, + UpgradeCommon::CONF_FILE_API_CLIENT); + + // Backup the config files + $suffix = date("Ymdhis")."-".UpgradeCommon::VERSION_NUMBER; + foreach ($configFiles as $conf) { + // do not back up monit cfg -- ok?? not being done anyway + if (file_exists($conf)) { + echo "Backing up $conf to $conf$suffix.bak".PHP_EOL; + //copy($conf, $conf.$suffix.".bak"); + exec("cp -p $conf $conf$suffix.bak"); //use cli version to preserve file attributes + } + } + + self::CreateIniFiles(UpgradeCommon::CONF_BACKUP_SUFFIX); + self::MergeConfigFiles($configFiles, $suffix); + } + + /** + * This function creates the /etc/airtime configuration folder + * and copies the default config files to it. + */ + public static function CreateIniFiles($suffix) + { + if (!file_exists("/etc/airtime/")){ + if (!mkdir("/etc/airtime/", 0755, true)){ + echo "Could not create /etc/airtime/ directory. Exiting."; + exit(1); + } + } + + $config_copy = array( + "../etc/airtime.conf" => self::CONF_FILE_AIRTIME, + "../etc/pypo.cfg" => self::CONF_FILE_PYPO, + "../etc/media-monitor.cfg" => self::CONF_FILE_MEDIAMONITOR, + "../etc/api_client.cfg" => self::CONF_FILE_API_CLIENT + ); + + echo "Copying configs:\n"; + foreach ($config_copy as $path_part => $destination) { + $full_path = OsPath::normpath(OsPath::join(__DIR__, + "$path_part.$suffix")); + echo "'$full_path' --> '$destination'\n"; + if(!copy($full_path, $destination)) { + echo "Failed on the copying operation above\n"; + exit(1); + } + } + } + + private static function MergeConfigFiles(array $configFiles, $suffix) { + foreach ($configFiles as $conf) { + if (file_exists("$conf$suffix.bak")) { + + if($conf === self::CONF_FILE_AIRTIME) { + // Parse with sections + $newSettings = parse_ini_file($conf, true); + $oldSettings = parse_ini_file("$conf$suffix.bak", true); + } + else { + $newSettings = self::ReadPythonConfig($conf); + $oldSettings = self::ReadPythonConfig("$conf$suffix.bak"); + } + + $settings = array_keys($newSettings); + + foreach($settings as $section) { + if(isset($oldSettings[$section])) { + if(is_array($oldSettings[$section])) { + $sectionKeys = array_keys($newSettings[$section]); + foreach($sectionKeys as $sectionKey) { + + if(isset($oldSettings[$section][$sectionKey])) { + self::UpdateIniValue($conf, $sectionKey, + $oldSettings[$section][$sectionKey]); + } + } + } else { + self::UpdateIniValue($conf, $section, + $oldSettings[$section]); + } + } + } + } + } + } + + private static function ReadPythonConfig($p_filename) + { + $values = array(); + + $fh = fopen($p_filename, 'r'); + + while(!feof($fh)){ + $line = fgets($fh); + if(substr(trim($line), 0, 1) == '#' || trim($line) == ""){ + continue; + }else{ + $info = explode('=', $line, 2); + $values[trim($info[0])] = trim($info[1]); + } + } + + return $values; + } + + /** + * This function updates an INI style config file. + * + * A property and the value the property should be changed to are + * supplied. If the property is not found, then no changes are made. + * + * @param string $p_filename + * The path the to the file. + * @param string $p_property + * The property to look for in order to change its value. + * @param string $p_value + * The value the property should be changed to. + * + */ + private static function UpdateIniValue($p_filename, $p_property, $p_value) + { + $lines = file($p_filename); + $n = count($lines); + foreach ($lines as &$line) { + if ($line[0] != "#"){ + $key_value = explode("=", $line); + $key = trim($key_value[0]); + + if ($key == $p_property){ + $line = "$p_property = $p_value".PHP_EOL; + } + } + } + + $fp=fopen($p_filename, 'w'); + for($i=0; $i<$n; $i++){ + fwrite($fp, $lines[$i]); + } + fclose($fp); + } + + public static function queryDb($p_sql){ + $con = Propel::getConnection(); + + try { + $result = $con->query($p_sql); + } catch (Exception $e) { + echo "Error executing $p_sql. Exiting."; + exit(1); + } + + return $result; + } +} + +class OsPath { + // this function is from http://stackoverflow.com/questions/2670299/is-there-a-php-equivalent-function-to-the-python-os-path-normpath + public static function normpath($path) + { + if (empty($path)) + return '.'; + + if (strpos($path, '/') === 0) + $initial_slashes = true; + else + $initial_slashes = false; + if ( + ($initial_slashes) && + (strpos($path, '//') === 0) && + (strpos($path, '///') === false) + ) + $initial_slashes = 2; + $initial_slashes = (int) $initial_slashes; + + $comps = explode('/', $path); + $new_comps = array(); + foreach ($comps as $comp) + { + if (in_array($comp, array('', '.'))) + continue; + if ( + ($comp != '..') || + (!$initial_slashes && !$new_comps) || + ($new_comps && (end($new_comps) == '..')) + ) + array_push($new_comps, $comp); + elseif ($new_comps) + array_pop($new_comps); + } + $comps = $new_comps; + $path = implode('/', $comps); + if ($initial_slashes) + $path = str_repeat('/', $initial_slashes) . $path; + if ($path) + return $path; + else + return '.'; + } + + /* Similar to the os.path.join python method + * http://stackoverflow.com/a/1782990/276949 */ + public static function join() { + $args = func_get_args(); + $paths = array(); + + foreach($args as $arg) { + $paths = array_merge($paths, (array)$arg); + } + + foreach($paths as &$path) { + $path = trim($path, DIRECTORY_SEPARATOR); + } + + if (substr($args[0], 0, 1) == DIRECTORY_SEPARATOR) { + $paths[0] = DIRECTORY_SEPARATOR . $paths[0]; + } + + return join(DIRECTORY_SEPARATOR, $paths); + } +} diff --git a/install_minimal/upgrades/airtime-2.3.1/data/upgrade.sql b/install_minimal/upgrades/airtime-2.3.1/data/upgrade.sql new file mode 100644 index 000000000..6b3bb0a42 --- /dev/null +++ b/install_minimal/upgrades/airtime-2.3.1/data/upgrade.sql @@ -0,0 +1,2 @@ +DELETE FROM cc_pref WHERE keystr = 'system_version'; +INSERT INTO cc_pref (keystr, valstr) VALUES ('system_version', '2.3.1'); diff --git a/python_apps/api_clients/api_client.py b/python_apps/api_clients/api_client.py index b0be86323..be80eb0ca 100644 --- a/python_apps/api_clients/api_client.py +++ b/python_apps/api_clients/api_client.py @@ -15,7 +15,7 @@ import json import base64 from configobj import ConfigObj -AIRTIME_VERSION = "2.3.0" +AIRTIME_VERSION = "2.3.1" # TODO : Place these functions in some common module. Right now, media diff --git a/python_apps/media-monitor/install/media-monitor-uninitialize.py b/python_apps/media-monitor/install/media-monitor-uninitialize.py index 84c8fb139..c73801b46 100644 --- a/python_apps/media-monitor/install/media-monitor-uninitialize.py +++ b/python_apps/media-monitor/install/media-monitor-uninitialize.py @@ -13,5 +13,7 @@ try: print "OK" else: print "Wasn't running" + + subprocess.call("update-rc.d -f airtime-media-monitor remove".split(" ")) except Exception, e: print e diff --git a/python_apps/pypo/airtime-liquidsoap-init-d b/python_apps/pypo/airtime-liquidsoap-init-d index 106006fab..21323e850 100755 --- a/python_apps/pypo/airtime-liquidsoap-init-d +++ b/python_apps/pypo/airtime-liquidsoap-init-d @@ -17,15 +17,7 @@ DAEMON=/usr/lib/airtime/pypo/bin/airtime-liquidsoap PIDFILE=/var/run/airtime-liquidsoap.pid start () { - chown pypo:pypo /var/log/airtime/pypo - chown pypo:pypo /var/log/airtime/pypo-liquidsoap - - touch /var/run/airtime-liquidsoap.pid - chown pypo:pypo /var/run/airtime-liquidsoap.pid - - start-stop-daemon --start --quiet --chuid $USERID:$GROUPID \ - --pidfile /var/run/airtime-liquidsoap.pid --nicelevel -15 --startas $DAEMON - + start_no_monit monit monitor airtime-liquidsoap >/dev/null 2>&1 } @@ -39,7 +31,14 @@ stop () { } start_no_monit() { - start-stop-daemon --start --background --quiet --chuid $USERID:$USERID --make-pidfile --pidfile $PIDFILE --startas $DAEMON + chown pypo:pypo /var/log/airtime/pypo + chown pypo:pypo /var/log/airtime/pypo-liquidsoap + + touch /var/run/airtime-liquidsoap.pid + chown pypo:pypo /var/run/airtime-liquidsoap.pid + + start-stop-daemon --start --quiet --chuid $USERID:$GROUPID \ + --pidfile /var/run/airtime-liquidsoap.pid --nicelevel -15 --startas $DAEMON } diff --git a/python_apps/pypo/install/pypo-uninitialize.py b/python_apps/pypo/install/pypo-uninitialize.py index 8765e5a18..65ac91991 100644 --- a/python_apps/pypo/install/pypo-uninitialize.py +++ b/python_apps/pypo/install/pypo-uninitialize.py @@ -6,7 +6,7 @@ if os.geteuid() != 0: print "Please run this as root." sys.exit(1) -try: +try: #stop pypo and liquidsoap processes print "Waiting for Pypo process to stop...", try: @@ -18,12 +18,16 @@ try: print "OK" else: print "Wasn't running" - + print "Waiting for Liquidsoap process to stop...", if (os.path.exists('/etc/init.d/airtime-liquidsoap')): subprocess.call("invoke-rc.d airtime-liquidsoap stop", shell=True) print "OK" else: print "Wasn't running" + + subprocess.call("update-rc.d -f airtime-playout remove".split(" ")) + subprocess.call("update-rc.d -f airtime-liquidsoap remove".split(" ")) + except Exception, e: print e diff --git a/python_apps/pypo/install/pypo-uninstall.py b/python_apps/pypo/install/pypo-uninstall.py deleted file mode 100644 index 7bc01ca4b..000000000 --- a/python_apps/pypo/install/pypo-uninstall.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- - -import os -import sys -from configobj import ConfigObj - -if os.geteuid() != 0: - print "Please run this as root." - sys.exit(1) - -PATH_INI_FILE = '/etc/airtime/pypo.cfg' - -def remove_path(path): - os.system('rm -rf "%s"' % path) - -def get_current_script_dir(): - current_script_dir = os.path.realpath(__file__) - index = current_script_dir.rindex('/') - return current_script_dir[0:index] - -def remove_monit_file(): - os.system("rm -f /etc/monit/conf.d/monit-airtime-playout.cfg") - os.system("rm -f /etc/monit/conf.d/monit-airtime-liquidsoap.cfg") - -try: - # load config file - try: - config = ConfigObj(PATH_INI_FILE) - except Exception, e: - print 'Error loading config file: ', e - sys.exit(1) - - os.system("invoke-rc.d airtime-playout stop") - os.system("invoke-rc.d airtime-liquidsoap stop") - - os.system("rm -f /etc/init.d/airtime-playout") - os.system("rm -f /etc/init.d/airtime-liquidsoap") - - os.system("update-rc.d -f airtime-playout remove >/dev/null 2>&1") - os.system("update-rc.d -f airtime-liquidsoap remove >/dev/null 2>&1") - - #remove logrotate script - os.system("rm -f /etc/logrotate.d/airtime-liquidsoap") - - print "Removing monit file" - remove_monit_file() - - print "Removing cache directories" - remove_path(config["cache_base_dir"]) - - print "Removing symlinks" - os.system("rm -f /usr/bin/airtime-liquidsoap") - - print "Removing pypo files" - remove_path(config["bin_dir"]) - - print "Pypo uninstall complete." -except Exception, e: - print "exception:" + str(e) diff --git a/python_apps/pypo/liquidsoap_scripts/ls_lib.liq b/python_apps/pypo/liquidsoap_scripts/ls_lib.liq index 3761f91c1..5aa77cac8 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_lib.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_lib.liq @@ -1,6 +1,6 @@ def notify(m) #current_media_id := string_of(m['schedule_table_id']) - command = "/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh --data='#{!pypo_data}' --media-id=#{m['schedule_table_id']} &" + command = "/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh --media-id=#{m['schedule_table_id']} &" log(command) system(command) end @@ -78,14 +78,16 @@ def output_to(output_type, type, bitrate, host, port, pass, mount_point, url, de source = ref s def on_error(msg) connected := "false" - system("/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh --error='#{msg}' --stream-id=#{stream} --time=#{!time} &") - log("/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh --error='#{msg}' --stream-id=#{stream} --time=#{!time} &") + command = "/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh --error='#{msg}' --stream-id=#{stream} --time=#{!time} &" + system(command) + log(command) 5. end def on_connect() connected := "true" - system("/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh --connect --stream-id=#{stream} --time=#{!time} &") - log("/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh --connect --stream-id=#{stream} --time=#{!time} &") + command = "/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh --connect --stream-id=#{stream} --time=#{!time} &" + system(command) + log(command) end stereo = (channels == "stereo") diff --git a/python_apps/pypo/liquidsoap_scripts/ls_script.liq b/python_apps/pypo/liquidsoap_scripts/ls_script.liq index c7df65a22..566df0d9e 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_script.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_script.liq @@ -154,8 +154,9 @@ def make_scheduled_play_unavailable() end def update_source_status(sourcename, status) = - system("/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh --source-name=#{sourcename} --source-status=#{status} &") - log("/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh --source-name=#{sourcename} --source-status=#{status} &") + command = "/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh --source-name=#{sourcename} --source-status=#{status} &" + system(command) + log(command) end def live_dj_connect(header) = diff --git a/python_apps/pypo/monit-airtime-liquidsoap.cfg b/python_apps/pypo/monit-airtime-liquidsoap.cfg index 297854faa..3b674aec2 100644 --- a/python_apps/pypo/monit-airtime-liquidsoap.cfg +++ b/python_apps/pypo/monit-airtime-liquidsoap.cfg @@ -12,5 +12,5 @@ if failed host localhost port 1234 send "version\r\nexit\r\n" expect "Liquidsoap" - retry 3 + with timeout 2 seconds retry 3 for 2 cycles then restart diff --git a/utils/airtime-check-system.php b/utils/airtime-check-system.php index a780b1243..86f13b5b7 100644 --- a/utils/airtime-check-system.php +++ b/utils/airtime-check-system.php @@ -205,17 +205,6 @@ class AirtimeCheck { $log = "/var/log/airtime/media-monitor/media-monitor.log"; self::show_log_file($log); } - if (isset($services->rabbitmq)) { - self::output_status("RABBITMQ_PROCESS_ID", $data->services->rabbitmq->process_id); - self::output_status("RABBITMQ_RUNNING_SECONDS", $data->services->rabbitmq->uptime_seconds); - self::output_status("RABBITMQ_MEM_PERC", $data->services->rabbitmq->memory_perc); - self::output_status("RABBITMQ_CPU_PERC", $data->services->rabbitmq->cpu_perc); - } else { - self::output_status("RABBITMQ_PROCESS_ID", "FAILED"); - self::output_status("RABBITMQ_RUNNING_SECONDS", "0"); - self::output_status("RABBITMQ_MEM_PERC", "0%"); - self::output_status("RABBITMQ_CPU_PERC", "0%"); - } } if (self::$AIRTIME_STATUS_OK){