<?


class Speedfan {
	private $fh_log = false;
	private $last_log_fname = false;
	private $window_caption; // по нему определяем, запущен ли speedfan или нет
	private $abs_path = ''; // абсолютный путь к папке
	private $time_changed_speedfansens_cfg = null; // время последнего обновления speedfansens.cfg
	private $end_line_parse = 70;
	private $max_interval_sens_record = 30; // сек. Максимальный возраст записи считающийся актуальным
	private $max_log_size; // (устанавливается в конструкторе) максимальный размер лога, после чего он обнуляется

	private $filetime_changed_log = 0;  // время изменения лога указанное в свойствах файла.

	// контроль за устареванием записей в логе
	private $last_entry_id = false;
	private $last_entry_time = 0;

	function __construct() {
		GLOBAL $GOP, $progDir;

		$this->window_caption = $GOP['speedfan_caption'];
		$this->abs_path = "$progDir\\SpeedFan";
		$this->max_log_size = 1024*1024;
	}

	// проверяем установлен ли speedfan. Проверка идет по ключам в реестре
	public function is_install() {
		$is_install = false;

		switch (true) {
			default:
				readRegKey(HKEY_CURRENT_USER, "Software/SpeedFan/TFm/FormVersion", $res);
				if($res === 0) {
					$is_install = true;
					break;
				}

				readRegKey(HKEY_LOCAL_MACHINE, "SOFTWARE/Wow6432Node/Microsoft/Windows/CurrentVersion/Uninstall/SpeedFan/DisplayName", $res);
				if(strlen($res) > 6) {
					$is_install = true;
					break;
				}

				readRegKey(HKEY_LOCAL_MACHINE, "SYSTEM/ControlSet001/services/speedfan/DisplayName", $res);
				if(strlen($res) > 6) {
					$is_install = true;
					break;
				}
		}

		return $is_install;
	}

	public function is_run() {
		// Windows_control::find хорош тем что он находит программу даже в трее
		if($handle = Windows_control::find($this->window_caption)) {
			
			/*
			// проверяем, чтобы программа была запущенна из нужной папки
			$window = WinFunc::GetListProcess($handle, 5);

			if($window[0]['path'] != $this->abs_path)
			*/
			return $handle;

		}
		else 
			return false;
	}

	public function replace_debug_nfo_simlink() {
		$debug_path = "{$this->abs_path}\\debug.nfo";
		err_no();
		unlink($debug_path); // удаляем, вдруг там файл
		err_yes();

		$s = 'mklink /j "'.$debug_path.'" "c:\debug.nfo"';
		$res = shell_exec($s); //на самом деле куда именно будет вести ссылка не важно. Эти данные все равно сразу в пустоту будут отправляться.

		//print $s;
	}

	public function run() {

		// отключили потому что нам нужно четко понимать для отладки, запустился ли он или нет
		//if($this->is_run())
		//	return true; // потому что снаружи мы если false, пишем что произошла ошибка

		/*
		debug.nfo мы заменяем символьной ссылкой чтобы не нагружал комп в некоторых случаях.
		 // удаляем debug.nfo чтобы проще было определять момент когда speedfan полностью загрузился
		$debug_path = "{$this->abs_path}\\debug.nfo";
		if(file_exists($debug_path))
			unlink($debug_path); 
		*/

		// сохраняем время изменения лога чтобы потом определить что программа загрузилась и начала работать
		$this->filetime_changed_log = $this->filetime_changed_log();

		// удаляем лог чтобы просто определить когда данные действительно пошли
		// в режиме разработчика speedfan не выключается и поэтому при инициализации лог занят и его не получается удалить. А после закрытия speedfan, он еще какое то время заблокирован
		//  is_writable не проверяет блокировку файла другими программами
		/*
		$log_path = $this->cur_log_name();
		if(is_writable($log_path)) 
			unlink($log_path);
		*/
		$path = "{$this->abs_path}\\speedfan.exe";
		if(file_exists($path)) {
			$res = Run($path);
		}
		else {
			print "Не найден $path\n";
			return false;
		}

		return true;
	}

	// проверка что запуск завершен
	// в debug.nfo будет строка 26.05.2016 13:00:28.256 - Activating timer...
	public function is_run_complete() {
		/*
		$path = "{$this->abs_path}\\debug.nfo";
		if(!file_exists($path)) return false;

		$debug_str = file_get_contents($path);
		if(stripos($debug_str, 'Activating timer'))
			 return true;
		else return false;
		*/

		if($this->filetime_changed_log != $this->filetime_changed_log())
			 return true;
		else return false;
	}

	public function close() {
		if($handle = $this->is_run()) {
			Windows_control::close($handle);
		}
	}

	public function install() {
		// надо проверять не запущен ли speedfan, иначе никакая установка естественно не прокатит
		//if($this->is_run())

		if($this->is_install())
			return true;
		
		// не нужно указывать полный путь. Если в нем будет пробел, то команда вообще не запустится
		$distr_speedfan = 'instspeedfan451.exe';
		if(file_exists($distr_speedfan)) {
			shell_exec("$distr_speedfan /S");
		}
		else {
			print "Не найден установщик speedfan\n";
			return false;
		}

		// удаляем ярлыки с рабочего стола и из меню
		// поскольку мы устанавливаем только если программа не установленна, можем не боятся что ярлыки будут не наши
		$this->del_speedfan_lnks();

		/*
		$_Path  = "$progDir\\instspeedfan451.exe /S";
		$WshShell = new COM("WScript.Shell");
		$oExec = $WshShell -> Run($_Path, 2, false);
		*/
	}

	// изменен ли файл speedfansens_cfg в за последние несколько секунд
	public function is_changed_speedfansens_cfg() {
		$new_time = $this->time_changed_speedfansens_cfg();

		if($this->time_changed_speedfansens_cfg !== NULL and 
		   $this->time_changed_speedfansens_cfg != $new_time) 
			return true;
		else return false;
	}

	

	// Активация сенсоров hdd чтобы они писали данные в лог
	// путем правки файла speedfansens.cfg
	public function edit_speedfansens_cfg() {
		# правим файл сенсеров
		$fsens = 'SpeedFan/speedfansens.cfg';
		$sens_str = file_get_contents($fsens);

		//print $sens_str;

/*
Пример записи:
xxx Temp 1 from WDC WD5000LPVX-22V0TT0WD-WXS1E64X5W0Z
name=HD0
active=true
wanted=40
warning=50
offset=0
intray=true
UsedPwms=0
logged=true
xxx end
*/
		 //ищем записи с именами HD1, HD2 и тд.
		 // ставим у них галочку logged
		$sens_str = preg_replace_callback(
			"#xxx Temp[^\n]+\nname=HD\d+.+?xxx end#s",
			function ($matches) {
				//print "нашли!";
				return str_replace("logged=false", "logged=true", $matches[0]);
			},
			$sens_str
		);

		file_put_contents($fsens, $sens_str);

		// заставляем speedfan принять новые настройки
		//$this->open_close_window_configure();
	}

	// показываем, сворачиваем окно Speedfan
	public function show_window($command = SW_SHOW) {// SW_SHOW, SW_HIDE
		$api = new TWinAPI();
		if($handle = $this->is_run()) {
			$api->ShowWindow($handle, $command); // вытаскивает окно из трея
		}
	}

	public function time_changed_speedfansens_cfg() {
		GLOBAL $log;

		clearstatcache();
		$path = "$this->abs_path\\speedfansens.cfg";
		if(file_exists($path))
			return filemtime($path);
		else return 0;
	}

	public function filetime_changed_log() {
		GLOBAL $log;

		clearstatcache();
		$path = $this->cur_log_name();
		if(file_exists($path))
			 $res = filemtime($path);
		else $res = 0;

		//$log->line("$res $path");

		return $res;
	}

	// Открывает и закрывает окно конфигурации speedfan
	// это действие нужно чтобы speedfan создал файл с сенсорами
	public function open_close_window_configure() {
		GLOBAL $log;

		// сохраняем время последнего обновления конфига чтобы потом определить что файл обновлен
		$this->time_changed_speedfansens_cfg = $this->time_changed_speedfansens_cfg();

		// разворачиваем окно из трея, иначе не получится по кнопкам клацать
		$this->show_window(SW_SHOW);

		//sleep(2);

		for($i=0; true; $i++) {
			if($i >= 50) {
				$log->line("Окно {$this->window_caption} не найдено");
				return false;
			}

			if($handle = Windows_control::find($this->window_caption)) {
				//$Prog = array(0=>array('handle'=>$handle));
				// есть вероятность что здесь може выбить пустой массив
				$Component = WinFunc::GetListProcess($handle, 5);
				if(!empty($Component))
					break;
				
			}
			/*
			$Prog = WinFunc::GetProcess(null, 'SpeedFan 4.51', 'TFm');
			if(!empty($Prog))
				break;
			*/
			usleep(1000*100);
		}


		// копаем до кнопки configure
		/*
		$Component = WinFunc::GetListProcess($handle, 5);
		if(empty($Component)) {// есть вероятность что здесь може выбить пустой массив
			$log->line("WinFunc::GetListProcess окна не сработал");
			return false;
		}*/

		$PageControl = WinFunc::GetProcess(null, null, 'TPageControl', $Component);
		$Component = WinFunc::GetListProcess($PageControl[0]['handle'], 5);
		$TTabSheet = WinFunc::GetProcess(null, null, 'TTabSheet', $Component);
		$Component = WinFunc::GetListProcess($TTabSheet[0]['handle'], 5);

		//$Configure = WinFunc::GetProcess(null, 'Configure', 'TJvXPButton', $Component);
		// кнопка конфигрурация идет первой в списке, ее имя не нужно
		$Configure = WinFunc::GetProcess(null, null, 'TJvXPButton', $Component);

		// нажимаем
		WinFunc::PostMessageA($Configure[0]['handle'], 513, 0, 0);
		WinFunc::PostMessageA($Configure[0]['handle'], 514, 0, 0);

		//sleep(2);

		// ловим окно конфигурации
		// может потребоваться несколько попыток
		for($i=0; true; $i++) {
			if($i >= 50) {
				$log->line("окно configure не найдено");
				return false;
			}

			//$Windows_mas = WinFunc::GetProcess(null, 'Configure', 'TDlgConfig');
			$Windows_mas = WinFunc::GetProcess(null, null, 'TDlgConfig');
			if(!empty($Windows_mas)) {
				// проходим по всем открым окнам конфигурации в поисках нужно (ищем по внутреннему устройству)
				foreach($Windows_mas as $window) {
					$Component = WinFunc::GetListProcess($window['handle'], 5);
					$TPanel = WinFunc::GetProcess(null, null, 'TPanel', $Component);
					if(empty($TPanel)) continue; // не то окно, выходим

					$Component = WinFunc::GetListProcess($TPanel[0]['handle'], 5);
					$TPanel = WinFunc::GetProcess(null, null, 'TPanel', $Component);
					if(empty($TPanel)) continue; // // не то окно, выходим
					
					$Component = WinFunc::GetListProcess($TPanel[0]['handle'], 5);
					//$Ok = WinFunc::GetProcess(null, 'OK', 'TBitBtn', $Component);
					// берем все кнопки. Их там всего 2.
					$buttons = WinFunc::GetProcess(null, null, 'TBitBtn', $Component);
					if(sizeof($buttons) == 2) {
						// если нашли 2 кнопки, то скорее всего это speedfan
						// выходим из всех циклов и продолжаем работу
						break 2;
					}
				}
			}

			usleep(1000*100);
		}

		//sleep(3);

		// ОК там вторая кнопка
		WinFunc::PostMessageA($buttons[1]['handle'], 513, 0, 0);
		WinFunc::PostMessageA($buttons[1]['handle'], 514, 0, 0);

		// сворачиваем окно
		$this->show_window(SW_HIDE);

		return true;
	}


	// удаление ярлыков speedfan
	private function del_speedfan_lnks() {
		// рабочий стол
		$lnk= "{$GLOBALS['_ENV']['USERPROFILE']}\\Desktop\\SpeedFan.lnk";
		if(is_writable($lnk))
			unlink($lnk);

		// главное меню
		$xp_path = "{$GLOBALS['_ENV']['USERPROFILE']}\\Start Menu"; // win xp
		$seven_path = "{$GLOBALS['_ENV']['APPDATA']}\\Microsoft\\Windows\\Start Menu"; // win 7-10
		if(file_exists($xp_path))
			$start_menu_path = $xp_path;
		else $start_menu_path = $seven_path;

		$speedfan_path = "$start_menu_path\\Programs\\SpeedFan";
		if(is_writable($speedfan_path)) {
			del_folder($speedfan_path);
		}
	}

	// достаем последнюю запись из лога
	private function get_end_line() {
		$fsize = filesize($this->log_fname);
		if($fsize <= $this->end_line_parse)
			 $offset = $fsize;
		else $offset = $this->end_line_parse;

		fseek($this->fh_log, -1*$offset, SEEK_END);
		$str = fread($this->fh_log, $offset+1);

		if(!preg_match("#.+\n([^\n\r]+)#", trim($str), $res))
			return false;

		return $res[1];
	}

	function log_control_size() {
		if(!($this->log_fname && file_exists($this->log_fname)))
			return false;

		$size = filesize($this->log_fname);
		if($size > $this->max_log_size) {
			$str = file_get_contents($this->log_fname);
			$str =substr($str, -70); // оставляем в логе только последние записи. Так надо чтобы пока speedfan не записал новые данные, $this->get_temp() возвращал последнюю температуру а не false
			file_put_contents($this->log_fname, $str);
		}
	}

	private function cur_log_name() {
		$date = date("Ymd");
		return "$this->abs_path\\SFLog$date.csv";
	}

	 function get_temp() {
		GLOBAL $log;

		$this->log_fname = $this->cur_log_name();

		// если имя лога изменилось или метод запускается впервые
		if($this->log_fname != $this->last_log_fname) {
			if($this->fh_log) {
				// при переходе на следующий день, speedfan переименует файл текущего лога
				// поэтому fclose выполнится с ошибкой. Поэтому блокируем ошибки.
				err_no();
				fclose($this->fh_log);
				err_yes();
			}

			if(!file_exists($this->log_fname)) {
				//print "файл не найден\n";
				return -1;
			}

			$this->fh_log = fopen($this->log_fname, 'r');
			$this->last_log_fname = $this->log_fname;
		}

		if(!$end_line = $this->get_end_line())
			return -1;
		
		// очищаем лог если он слишком большой
		$this->log_control_size();

		$temp_mas = explode("\t", $end_line);

		 // время записи. Мы его используем просто как id строки
		// speedfan в зависимости от компа по разному указывает время, поэтому его нельзя тупую использовать для определения насколько данные устарели
		$entry_id = $temp_mas[0];
		unset($temp_mas[0]);

		// если новая, сохраняем метку когда были получены данные
		if($this->last_entry_id != $entry_id) {

			// чтобы когда первый раз читаем лог, последняя запись не считалась актуальной (вдруг она была сделанна хрен знает когда). Это условие позволит дождаться новой записи.
			if($this->last_entry_id !== false) 
				$this->last_entry_time = time();

			$this->last_entry_id = $entry_id;
		}

		// не является ли запись слишком старой
		if(time() - $this->last_entry_time > $this->max_interval_sens_record)
			return -1;

		if(!sizeof($temp_mas))
			return -1;
		
		// берем самую большую температуру
		rsort($temp_mas);
		return $temp_mas[0];
	}

}


?>