diff --git a/cronjobs/notifications.php b/cronjobs/notifications.php new file mode 100755 index 00000000..45bda82a --- /dev/null +++ b/cronjobs/notifications.php @@ -0,0 +1,61 @@ +#!/usr/bin/php +getAllIdleWorkers(); +if (empty($aWorkers)) { + verbose("No idle workers found\n"); +} else { + foreach ($aWorkers as $aWorker) { + $aData = $aWorker; + $aData['username'] = $user->getUserName($aWorker['account_id']); + $aData['subject'] = 'IDLE Worker : ' . $aWorker['username']; + $aData['email'] = $user->getUserEmail($aData['username']); + if ( $notification->isNotified($aData) ) { + verbose("Worker already notified\n"); + continue; + } + if ($notification->addNotification('idle_worker', $aData) && $notification->sendMail($aData['email'], 'idle_worker', $aData)) { + verbose ("Notified " . $aData['email'] . " for IDLE worker " . $aWorker['username'] . "\n"); + } else { + verbose("Unable to send notification: " . $notification->getError() . "\n"); + } + } +} + +// We notified, lets check which recovered +$aNotifications = $notification->getAllActive(); +foreach ($aNotifications as $aNotification) { + $aData = json_decode($aNotification['data'], true); + $aWorker = $worker->getWorker($aData['id']); + if ($aWorker['active'] == 1) { + if ($notification->setInactive($aNotification['id'])) { + verbose("Marked notification " . $aNotification['id'] . " as inactive\n"); + } else { + verbose("Failed to set notification inactive for " . $aWorker['username'] . "\n"); + } + } +} + +?> diff --git a/cronjobs/run-crons.sh b/cronjobs/run-crons.sh index 730a9063..5f9179dc 100755 --- a/cronjobs/run-crons.sh +++ b/cronjobs/run-crons.sh @@ -16,7 +16,7 @@ PIDFILE='/tmp/mmcfe-ng-cron.pid' CRONHOME='.' # List of cruns to execute -CRONS="findblock.php proportional_payout.php blockupdate.php auto_payout.php tickerupdate.php" +CRONS="findblock.php proportional_payout.php blockupdate.php auto_payout.php tickerupdate.php notifications.php" # Additional arguments to pass to cronjobs CRONARGS="-v" diff --git a/public/include/autoloader.inc.php b/public/include/autoloader.inc.php index ea16564e..a78d4a62 100644 --- a/public/include/autoloader.inc.php +++ b/public/include/autoloader.inc.php @@ -14,3 +14,5 @@ require_once(CLASS_DIR . '/worker.class.php'); require_once(CLASS_DIR . '/statistics.class.php'); require_once(CLASS_DIR . '/transaction.class.php'); require_once(CLASS_DIR . '/setting.class.php'); +require_once(CLASS_DIR . '/mail.class.php'); +require_once(CLASS_DIR . '/notification.class.php'); diff --git a/public/include/classes/mail.class.php b/public/include/classes/mail.class.php new file mode 100644 index 00000000..2800500c --- /dev/null +++ b/public/include/classes/mail.class.php @@ -0,0 +1,65 @@ +debug = $debug; + } + public function setMysql($mysqli) { + $this->mysqli = $mysqli; + } + public function setSmarty($smarty) { + $this->smarty = $smarty; + } + public function setConfig($config) { + $this->config = $config; + } + public function setErrorMessage($msg) { + $this->sError = $msg; + } + public function getError() { + return $this->sError; + } + function checkStmt($bState) { + $this->debug->append("STA " . __METHOD__, 4); + if ($bState ===! true) { + $this->debug->append("Failed to prepare statement: " . $this->mysqli->error); + $this->setErrorMessage('Internal application Error'); + return false; + } + return true; + } + + public function sendMail($email, $template, $aData) { + $this->smarty->assign('WEBSITENAME', $this->config['website']['name']); + $this->smarty->assign('SUBJECT', $aData['subject']); + $this->smarty->assign('DATA', $aData); + $headers = 'From: Website Administration <' . $this->config['website']['email'] . ">\n"; + $headers .= "MIME-Version: 1.0\n"; + $headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n"; + if (mail($email, + $this->smarty->fetch(BASEPATH . 'templates/mail/subject.tpl'), + $this->smarty->fetch(BASEPATH . 'templates/mail/' . $template . '.tpl'), + $headers)) { + return true; + } else { + $this->setErrorMessage("Unable to send mail"); + return false; + } + return false; + } +} + +// Make our class available automatically +$mail = new Mail (); +$mail->setDebug($debug); +$mail->setMysql($mysqli); +$mail->setSmarty($smarty); +$mail->setConfig($config); + +?> diff --git a/public/include/classes/notification.class.php b/public/include/classes/notification.class.php new file mode 100644 index 00000000..983db437 --- /dev/null +++ b/public/include/classes/notification.class.php @@ -0,0 +1,80 @@ + 'active', + 'type' => 'i', + 'value' => 0 + ); + return $this->updateSingle($id, $field); + } + + /** + * Update a single row in a table + * @param userID int Account ID + * @param field string Field to update + * @return bool + **/ + private function updateSingle($id, $field) { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare("UPDATE $this->table SET " . $field['name'] . " = ? WHERE id = ? LIMIT 1"); + if ($this->checkStmt($stmt) && $stmt->bind_param($field['type'].'i', $field['value'], $id) && $stmt->execute()) + return true; + $this->debug->append("Unable to update " . $field['name'] . " with " . $field['value'] . " for ID $id"); + return false; + } + /** + * We check our notification table for existing data + * so we can avoid duplicate entries + **/ + public function isNotified($aData) { + $data = json_encode($aData); + $stmt = $this->mysqli->prepare("SELECT id FROM $this->table WHERE data = ? AND active = 1 LIMIT 1"); + if ($stmt && $stmt->bind_param('s', $data) && $stmt->execute() && $stmt->store_result() && $stmt->num_rows == 1) + return true; + // Catchall + // Does not seem to have a notification set + $this->setErrorMessage("Unable to run query: " . $this->mysqli->error); + return false; + } + + /** + * Get all active notifications + **/ + public function getAllActive() { + $stmt =$this->mysqli->prepare("SELECT id, data FROM $this->table WHERE active = 1 LIMIT 1"); + if ($stmt && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_all(MYSQLI_ASSOC); + // Catchall + return false; + } + + /** + * Add a new notification to the table + * @param type string Type of the notification + * @return bool + **/ + public function addNotification($type, $data) { + // Store notification data as json + $data = json_encode($data); + $stmt = $this->mysqli->prepare("INSERT INTO $this->table (type, data, active) VALUES (?,?,1)"); + if ($stmt && $stmt->bind_param('ss', $type, $data) && $stmt->execute()) + return true; + $this->debug->append("Failed to add notification for $type with $data: " . $this->mysqli->error); + $this->setErrorMessage("Unable to add new notification"); + return false; + } +} + +$notification = new Notification(); +$notification->setDebug($debug); +$notification->setMysql($mysqli); +$notification->setSmarty($smarty); +$notification->setConfig($config); diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index 8db17a1e..b4627729 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -486,6 +486,7 @@ class User { } $smarty->assign('TOKEN', $token); $smarty->assign('USERNAME', $username); + $smarty->assign('SUBJECT', 'Password Reset Request'); $smarty->assign('WEBSITENAME', $this->config['website']['name']); $headers = 'From: Website Administration <' . $this->config['website']['email'] . ">\n"; $headers .= "MIME-Version: 1.0\n"; diff --git a/public/include/classes/worker.class.php b/public/include/classes/worker.class.php index 5a5c19c4..6a9f6524 100644 --- a/public/include/classes/worker.class.php +++ b/public/include/classes/worker.class.php @@ -43,17 +43,60 @@ class Worker { public function updateWorkers($account_id, $data) { $this->debug->append("STA " . __METHOD__, 4); $username = $this->user->getUserName($account_id); + $iFailed = 0; foreach ($data as $key => $value) { // Prefix the WebUser to Worker name $value['username'] = "$username." . $value['username']; - $stmt = $this->mysqli->prepare("UPDATE $this->table SET password = ?, username = ? WHERE account_id = ? AND id = ?"); - if ($this->checkStmt($stmt)) { - if (!$stmt->bind_param('ssii', $value['password'], $value['username'], $account_id, $key)) return false; - if (!$stmt->execute()) return false; - $stmt->close(); - } + $stmt = $this->mysqli->prepare("UPDATE $this->table SET password = ?, username = ?, monitor = ? WHERE account_id = ? AND id = ?"); + if ( ! ( $this->checkStmt($stmt) && $stmt->bind_param('ssiii', $value['password'], $value['username'], $value['monitor'], $account_id, $key) && $stmt->execute()) ) + $iFailed++; } - return true; + if ($iFailed == 0) + return true; + // Catchall + $this->setErrorMessage('Failed to update ' . $iFailed . ' worker.'); + return false; + } + + /** + * Fetch all IDLE workers that have monitoring enabled + * @param none + * @return data array Workers in IDLE state and monitoring enabled + **/ + public function getAllIdleWorkers() { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare(" + SELECT account_id, id, username + FROM " . $this->table . " + WHERE monitor = 1 AND ( SELECT SIGN(COUNT(id)) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) = 0"); + + if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_all(MYSQLI_ASSOC); + // Catchall + $this->setErrorMessage("Unable to fetch IDLE, monitored workers"); + echo $this->mysqli->error; + return false; + } + + /** + * Fetch a specific worker and its status + * @param id int Worker ID + * @return mixed array Worker details + **/ + public function getWorker($id) { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare(" + SELECT id, username, password, monitor, + ( SELECT SIGN(COUNT(id)) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) AS active, + ( SELECT ROUND(COUNT(id) * POW(2, " . $this->config['difficulty'] . ")/600/1000) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) AS hashrate + FROM $this->table + WHERE id = ? + "); + if ($this->checkStmt($stmt) && $stmt->bind_param('i', $id) && $stmt->execute() && $result = $stmt->get_result()) + return $result->fetch_assoc(); + // Catchall + echo $this->mysqli->error; + return false; } /** @@ -64,7 +107,7 @@ class Worker { public function getWorkers($account_id) { $this->debug->append("STA " . __METHOD__, 4); $stmt = $this->mysqli->prepare(" - SELECT id, username, password, + SELECT id, username, password, monitor, ( SELECT SIGN(COUNT(id)) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) AS active, ( SELECT ROUND(COUNT(id) * POW(2, " . $this->config['difficulty'] . ")/600/1000) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) AS hashrate FROM $this->table diff --git a/public/include/smarty.inc.php b/public/include/smarty.inc.php index b2e67f37..5668e1d3 100644 --- a/public/include/smarty.inc.php +++ b/public/include/smarty.inc.php @@ -16,10 +16,10 @@ $smarty = new Smarty; // Assign our local paths $debug->append('Define Smarty Paths', 3); -$smarty->template_dir = 'templates/' . THEME . '/'; -$smarty->compile_dir = 'templates/compile/'; +$smarty->template_dir = BASEPATH . 'templates/' . THEME . '/'; +$smarty->compile_dir = BASEPATH . 'templates/compile/'; // Optional smarty caching, check Smarty documentation for details $smarty->caching = $config['cache']; -$smarty->cache_dir = "templates/cache"; +$smarty->cache_dir = BASEPATH . "templates/cache"; ?> diff --git a/public/templates/mail/idle_worker.tpl b/public/templates/mail/idle_worker.tpl new file mode 100644 index 00000000..6d1c282c --- /dev/null +++ b/public/templates/mail/idle_worker.tpl @@ -0,0 +1,9 @@ + + +

One of your workers is currently IDLE: {$DATA.username}

+

Since monitoring is enabled for this worker, this notification was sent.

+

Please check your workers!

+
+
+ + diff --git a/public/templates/mail/subject.tpl b/public/templates/mail/subject.tpl index 665e26f5..94fd6a28 100644 --- a/public/templates/mail/subject.tpl +++ b/public/templates/mail/subject.tpl @@ -1 +1 @@ -[ {$WEBSITENAME} ] Password Reset Request +[ {$WEBSITENAME} ] {$SUBJECT} diff --git a/public/templates/mmcFE/account/workers/default.tpl b/public/templates/mmcFE/account/workers/default.tpl index f1b6e875..612bd227 100644 --- a/public/templates/mmcFE/account/workers/default.tpl +++ b/public/templates/mmcFE/account/workers/default.tpl @@ -10,6 +10,7 @@ Worker Name Password Active + Monitor Khash/s     @@ -20,6 +21,7 @@ {$username.0|escape}. + {$WORKERS[worker].hashrate|number_format} diff --git a/sql/mmcfe_ng_structure.sql b/sql/mmcfe_ng_structure.sql index c3c626bd..039b9a04 100644 --- a/sql/mmcfe_ng_structure.sql +++ b/sql/mmcfe_ng_structure.sql @@ -3,7 +3,7 @@ -- http://www.phpmyadmin.net -- -- Host: localhost --- Generation Time: Jun 06, 2013 at 09:01 PM +-- Generation Time: Jun 07, 2013 at 03:39 PM -- Server version: 5.5.31-0ubuntu0.13.04.1 -- PHP Version: 5.4.9-4ubuntu2 @@ -65,7 +65,24 @@ CREATE TABLE IF NOT EXISTS `blocks` ( PRIMARY KEY (`id`), UNIQUE KEY `height` (`height`,`blockhash`), KEY `time` (`time`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Discovered blocks persisted from Litecoin Service'; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Discovered blocks persisted from Litecoin Service'; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `notifications` +-- + +CREATE TABLE IF NOT EXISTS `notifications` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `type` varchar(25) NOT NULL, + `data` varchar(255) NOT NULL, + `active` tinyint(1) NOT NULL DEFAULT '1', + `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `active` (`active`), + KEY `data` (`data`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -------------------------------------------------------- @@ -173,7 +190,7 @@ CREATE TABLE IF NOT EXISTS `transactions` ( KEY `block_id` (`block_id`), KEY `account_id` (`account_id`), KEY `type` (`type`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;