mirror of
https://github.com/wavelog/wavelog.git
synced 2026-03-22 10:24:14 +00:00
Merge pull request #2896 from HB9HIL/dxclustercache_fix
Dxclustercache fix since we made various caching drivers available
This commit is contained in:
@@ -9,17 +9,27 @@ class Dxcluster extends CI_Controller {
|
||||
function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->load->model('user_model');
|
||||
$this->load->is_loaded('user_model') ?: $this->load->model('user_model');
|
||||
if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); }
|
||||
$this->load->model('dxcluster_model');
|
||||
$this->load->is_loaded('dxcluster_model') ?: $this->load->model('dxcluster_model');
|
||||
}
|
||||
|
||||
|
||||
function spots($band, $age = '', $de = '', $mode = 'All') {
|
||||
public function spots($band, $age = '', $de = '', $mode = 'All') {
|
||||
// Sanitize inputs
|
||||
$band = $this->security->xss_clean($band);
|
||||
$mode = $this->security->xss_clean($mode);
|
||||
|
||||
|
||||
// Only load cache driver if caching is enabled
|
||||
if (($this->config->item('enable_dxcluster_file_cache_band') ?? false) || ($this->config->item('enable_dxcluster_file_cache_worked') ?? false)) {
|
||||
$this->load->driver('cache', [
|
||||
'adapter' => $this->config->item('cache_adapter') ?? 'file',
|
||||
'backup' => $this->config->item('cache_backup') ?? 'file',
|
||||
'key_prefix' => $this->config->item('cache_key_prefix') ?? ''
|
||||
]);
|
||||
}
|
||||
|
||||
if ($age == '') {
|
||||
$age = $this->optionslib->get_option('dxcluster_maxage') ?? 60;
|
||||
} else {
|
||||
@@ -35,33 +45,42 @@ class Dxcluster extends CI_Controller {
|
||||
|
||||
header('Content-Type: application/json');
|
||||
if ($calls_found && !empty($calls_found)) {
|
||||
echo json_encode($calls_found);
|
||||
http_response_code(200);
|
||||
echo json_encode($calls_found, JSON_PRETTY_PRINT);
|
||||
} else {
|
||||
echo json_encode(['error' => 'not found']);
|
||||
$this->_return_not_found();
|
||||
}
|
||||
}
|
||||
|
||||
function qrg_lookup($qrg) {
|
||||
$call_found=$this->dxcluster_model->dxc_qrg_lookup($this->security->xss_clean($qrg));
|
||||
header('Content-Type: application/json');
|
||||
public function qrg_lookup($qrg) {
|
||||
$call_found = $this->dxcluster_model->dxc_qrg_lookup($this->security->xss_clean($qrg));
|
||||
header('Content-Type: application/json');
|
||||
if ($call_found) {
|
||||
http_response_code(200);
|
||||
echo json_encode($call_found, JSON_PRETTY_PRINT);
|
||||
} else {
|
||||
echo '{ "error": "not found" }';
|
||||
$this->_return_not_found();
|
||||
}
|
||||
}
|
||||
|
||||
function call($call) {
|
||||
public function call($call) {
|
||||
$date = date('Y-m-d', time());
|
||||
$dxccobj = new Dxcc($date);
|
||||
|
||||
$dxcc = $dxccobj->dxcc_lookup($call, $date);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
if ($dxcc) {
|
||||
header('Content-Type: application/json');
|
||||
http_response_code(200);
|
||||
echo json_encode($dxcc, JSON_PRETTY_PRINT);
|
||||
} else {
|
||||
echo '{ "error": "not found" }';
|
||||
$this->_return_not_found();
|
||||
}
|
||||
}
|
||||
|
||||
private function _return_not_found() {
|
||||
header('Content-Type: application/json');
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'not found'], JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,14 +24,14 @@ class DxclusterCache {
|
||||
/**
|
||||
* Generate RAW spot cache key (instance-wide, shared by all users)
|
||||
*/
|
||||
public function getRawCacheKey($maxage, $band) {
|
||||
public function get_raw_cache_key($maxage, $band) {
|
||||
return "dxcluster_raw_{$maxage}_{$band}_Any_All";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate logbook IDs key component (user-specific)
|
||||
*/
|
||||
public function getLogbookKey($user_id, $logbook_ids, $confirmation_prefs) {
|
||||
public function get_logbook_key($user_id, $logbook_ids, $confirmation_prefs) {
|
||||
$logbook_ids_str = implode('_', $logbook_ids);
|
||||
$confirmation_hash = md5($confirmation_prefs);
|
||||
return "{$user_id}_{$logbook_ids_str}_{$confirmation_hash}";
|
||||
@@ -40,21 +40,21 @@ class DxclusterCache {
|
||||
/**
|
||||
* Generate WORKED callsign cache key
|
||||
*/
|
||||
public function getWorkedCallKey($logbook_key, $callsign) {
|
||||
public function get_worked_call_key($logbook_key, $callsign) {
|
||||
return "dxcluster_worked_call_{$logbook_key}_{$callsign}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate WORKED DXCC cache key
|
||||
*/
|
||||
public function getWorkedDxccKey($logbook_key, $dxcc) {
|
||||
public function get_worked_dxcc_key($logbook_key, $dxcc) {
|
||||
return "dxcluster_worked_dxcc_{$logbook_key}_{$dxcc}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate WORKED continent cache key
|
||||
*/
|
||||
public function getWorkedContKey($logbook_key, $cont) {
|
||||
public function get_worked_cont_key($logbook_key, $cont) {
|
||||
return "dxcluster_worked_cont_{$logbook_key}_{$cont}";
|
||||
}
|
||||
|
||||
@@ -66,50 +66,35 @@ class DxclusterCache {
|
||||
* Invalidate cache after QSO add/edit/delete for current user
|
||||
* @param string $callsign - The worked callsign
|
||||
*/
|
||||
public function invalidateForCallsign($callsign) {
|
||||
public function invalidate_for_callsign($callsign) {
|
||||
// Skip if worked cache is disabled
|
||||
if ($this->CI->config->item('enable_dxcluster_file_cache_worked') !== true) return;
|
||||
|
||||
if (empty($callsign)) return;
|
||||
|
||||
// Get current user's logbook key
|
||||
$logbook_key = $this->getCurrentUserLogbookKey();
|
||||
$logbook_key = $this->_get_current_user_logbook_key();
|
||||
if (empty($logbook_key)) return;
|
||||
|
||||
// Delete callsign cache
|
||||
$this->deleteFile($this->getWorkedCallKey($logbook_key, $callsign));
|
||||
$this->_delete_from_cache($this->get_worked_call_key($logbook_key, $callsign));
|
||||
|
||||
// Look up DXCC and continent from callsign
|
||||
$dxccobj = new Dxcc(null);
|
||||
$dxcc_info = $dxccobj->dxcc_lookup($callsign, date('Y-m-d'));
|
||||
|
||||
if (!empty($dxcc_info['adif'])) {
|
||||
$this->deleteFile($this->getWorkedDxccKey($logbook_key, $dxcc_info['adif']));
|
||||
$this->_delete_from_cache($this->get_worked_dxcc_key($logbook_key, $dxcc_info['adif']));
|
||||
}
|
||||
if (!empty($dxcc_info['cont'])) {
|
||||
$this->deleteFile($this->getWorkedContKey($logbook_key, $dxcc_info['cont']));
|
||||
$this->_delete_from_cache($this->get_worked_cont_key($logbook_key, $dxcc_info['cont']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate all worked cache for current user (bulk operations)
|
||||
*/
|
||||
public function invalidateAllWorkedForCurrentUser() {
|
||||
// Skip if worked cache is disabled
|
||||
if ($this->CI->config->item('enable_dxcluster_file_cache_worked') !== true) return;
|
||||
|
||||
$logbook_key = $this->getCurrentUserLogbookKey();
|
||||
if (empty($logbook_key)) return;
|
||||
|
||||
$this->invalidateByPrefix("dxcluster_worked_call_{$logbook_key}_");
|
||||
$this->invalidateByPrefix("dxcluster_worked_dxcc_{$logbook_key}_");
|
||||
$this->invalidateByPrefix("dxcluster_worked_cont_{$logbook_key}_");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user's logbook key from session
|
||||
*/
|
||||
protected function getCurrentUserLogbookKey() {
|
||||
private function _get_current_user_logbook_key() {
|
||||
$user_id = $this->CI->session->userdata('user_id');
|
||||
$active_logbook = $this->CI->session->userdata('active_station_logbook');
|
||||
|
||||
@@ -122,104 +107,19 @@ class DxclusterCache {
|
||||
|
||||
if (empty($logbook_ids)) return null;
|
||||
|
||||
return $this->getLogbookKey($user_id, $logbook_ids, $confirmation_prefs);
|
||||
return $this->get_logbook_key($user_id, $logbook_ids, $confirmation_prefs);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// INTERNAL HELPERS
|
||||
// =========================================================================
|
||||
|
||||
protected function deleteFile($cache_key) {
|
||||
$cache_path = $this->getCachePath();
|
||||
if (!$cache_path) return;
|
||||
@unlink($cache_path . $cache_key);
|
||||
}
|
||||
|
||||
protected function invalidateByPrefix($prefix) {
|
||||
$cache_path = $this->getCachePath();
|
||||
if (!$cache_path) return;
|
||||
|
||||
$handle = @opendir($cache_path);
|
||||
if (!$handle) return;
|
||||
|
||||
while (($filename = readdir($handle)) !== false) {
|
||||
if (strpos($filename, $prefix) === 0) {
|
||||
@unlink($cache_path . $filename);
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
}
|
||||
|
||||
protected function getCachePath() {
|
||||
$cache_path = $this->CI->config->item('cache_path');
|
||||
$cache_path = ($cache_path === '' || $cache_path === false) ? APPPATH . 'cache/' : $cache_path;
|
||||
$cache_path = rtrim($cache_path, '/\\') . DIRECTORY_SEPARATOR;
|
||||
return (is_dir($cache_path) && is_writable($cache_path)) ? $cache_path : false;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// GARBAGE COLLECTION
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Run garbage collection with probability check (1% chance)
|
||||
* Call this on each request when worked cache is enabled
|
||||
*/
|
||||
public function maybeRunGc() {
|
||||
if (mt_rand(1, 100) === 1) {
|
||||
$this->cleanExpiredCache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean expired dxcluster cache files
|
||||
* Uses file mtime for fast pre-filtering before reading file contents
|
||||
*/
|
||||
public function cleanExpiredCache() {
|
||||
$cache_path = $this->getCachePath();
|
||||
if (!$cache_path || !is_readable($cache_path)) return;
|
||||
|
||||
$handle = @opendir($cache_path);
|
||||
if (!$handle) return;
|
||||
|
||||
$now = time();
|
||||
$deleted = 0;
|
||||
|
||||
// Max TTL for dxcluster files: raw=59s, worked=900s - use 900s + buffer
|
||||
$max_ttl = 1000;
|
||||
|
||||
while (($filename = readdir($handle)) !== false) {
|
||||
// Only process dxcluster cache files
|
||||
if (strpos($filename, 'dxcluster_') !== 0) continue;
|
||||
|
||||
$file = $cache_path . $filename;
|
||||
if (!is_file($file)) continue;
|
||||
|
||||
// Fast pre-filter: skip files modified recently (can't be expired yet)
|
||||
$mtime = @filemtime($file);
|
||||
if ($mtime !== false && ($now - $mtime) < $max_ttl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// File is old enough to potentially be expired - read and verify
|
||||
$data = @unserialize(@file_get_contents($file));
|
||||
if (!is_array($data) || !isset($data['time'], $data['ttl'])) {
|
||||
@unlink($file);
|
||||
$deleted++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if expired
|
||||
if ($data['ttl'] > 0 && $now > $data['time'] + $data['ttl']) {
|
||||
@unlink($file);
|
||||
$deleted++;
|
||||
}
|
||||
}
|
||||
|
||||
closedir($handle);
|
||||
|
||||
if ($deleted > 0) {
|
||||
log_message('debug', "DXCluster cache GC: deleted {$deleted} expired files");
|
||||
}
|
||||
private function _delete_from_cache($cache_key) {
|
||||
$this->CI->load->driver('cache', [
|
||||
'adapter' => $this->CI->config->item('cache_adapter') ?? 'file',
|
||||
'backup' => $this->CI->config->item('cache_backup') ?? 'file',
|
||||
'key_prefix' => $this->CI->config->item('cache_key_prefix') ?? ''
|
||||
]);
|
||||
$this->CI->cache->delete($cache_key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,11 @@ class Dxcluster_model extends CI_Model {
|
||||
'ISCAT', 'JT6M', 'FST4', 'FST4W', 'FREEDV', 'VARA'
|
||||
];
|
||||
|
||||
protected $cache_band_enabled;
|
||||
|
||||
public function __construct() {
|
||||
$this->load->Model('Modes');
|
||||
$this->load->is_loaded('Modes') ?: $this->load->model('Modes');
|
||||
|
||||
$this->db->where('bandedges.userid', $this->session->userdata('user_id'));
|
||||
$query = $this->db->get('bandedges');
|
||||
$result = $query->result_array();
|
||||
@@ -41,31 +44,12 @@ class Dxcluster_model extends CI_Model {
|
||||
$query = $this->db->get('bandedges');
|
||||
$this->bandedges = $query->result_array();
|
||||
}
|
||||
|
||||
$this->cache_band_enabled = ($this->config->item('enable_dxcluster_file_cache_band') ?? false);
|
||||
}
|
||||
|
||||
// Main function to get spot list from DXCache and process it
|
||||
public function dxc_spotlist($band = '20m', $maxage = 60, $de = '', $mode = 'All') {
|
||||
$this->load->helper(array('psr4_autoloader'));
|
||||
|
||||
// Check if file caching is enabled in config
|
||||
$cache_band_enabled = $this->config->item('enable_dxcluster_file_cache_band') === true;
|
||||
$cache_worked_enabled = $this->config->item('enable_dxcluster_file_cache_worked') === true;
|
||||
|
||||
// Only load cache driver if caching is enabled
|
||||
if ($cache_band_enabled || $cache_worked_enabled) {
|
||||
$this->load->driver('cache', [
|
||||
'adapter' => $this->config->item('cache_adapter') ?? 'file',
|
||||
'backup' => $this->config->item('cache_backup') ?? 'file',
|
||||
'key_prefix' => $this->config->item('cache_key_prefix') ?? ''
|
||||
]);
|
||||
|
||||
// Garbage collection: 1% chance to clean expired cache files
|
||||
// Only needed when worked cache is enabled (creates many per-callsign files)
|
||||
if ($cache_worked_enabled) {
|
||||
$this->load->library('DxclusterCache');
|
||||
$this->dxclustercache->maybeRunGc();
|
||||
}
|
||||
}
|
||||
|
||||
if($this->session->userdata('user_date_format')) {
|
||||
$custom_date_format = $this->session->userdata('user_date_format');
|
||||
@@ -81,17 +65,17 @@ class Dxcluster_model extends CI_Model {
|
||||
$dxcache_url = $dxcache_url . '/spots/'.$band;
|
||||
}
|
||||
|
||||
$this->load->model('logbook_model');
|
||||
$this->load->is_loaded('logbook_model') ?: $this->load->model('logbook_model');
|
||||
$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));
|
||||
|
||||
// Cache key for RAW cluster response (instance-wide, no worked status)
|
||||
// Use DxclusterCache library for centralized key generation
|
||||
$this->load->library('DxclusterCache');
|
||||
$raw_cache_key = $this->dxclustercache->getRawCacheKey($maxage, $band);
|
||||
|
||||
$raw_cache_key = $this->dxclustercache->get_raw_cache_key($maxage, $band);
|
||||
|
||||
// Check cache for raw processed spots (without worked status)
|
||||
$spotsout = null;
|
||||
if ($cache_band_enabled) {
|
||||
if ($this->cache_band_enabled) {
|
||||
$spotsout = $this->cache->get($raw_cache_key);
|
||||
}
|
||||
|
||||
@@ -149,7 +133,7 @@ class Dxcluster_model extends CI_Model {
|
||||
log_message('debug', 'DXCluster: Empty array received from ' . $dxcache_url . ' (no spots available)');
|
||||
return [];
|
||||
}
|
||||
$date = date('Ymd', time());
|
||||
$date = date('Y-m-d', time());
|
||||
|
||||
$dxccObj = new DXCC($date);
|
||||
|
||||
@@ -171,7 +155,7 @@ class Dxcluster_model extends CI_Model {
|
||||
$singlespot->frequency = floatval($singlespot->frequency);
|
||||
|
||||
// Validate against amateur band allocations (skip non-amateur frequencies)
|
||||
if (!$this->isFrequencyInAmateurBand($singlespot->frequency)) {
|
||||
if (!$this->_is_frequency_in_amateurband($singlespot->frequency)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -184,7 +168,7 @@ class Dxcluster_model extends CI_Model {
|
||||
|
||||
// Only determine mode if not provided by cluster
|
||||
if (!isset($singlespot->mode) || empty($singlespot->mode)) {
|
||||
$singlespot->mode = $this->get_mode($singlespot);
|
||||
$singlespot->mode = $this->_get_mode($singlespot);
|
||||
} else {
|
||||
// Normalize cluster-provided mode to lowercase
|
||||
$singlespot->mode = strtolower($singlespot->mode);
|
||||
@@ -192,7 +176,7 @@ class Dxcluster_model extends CI_Model {
|
||||
|
||||
// Only determine submode if not provided by cluster
|
||||
if (!isset($singlespot->submode) || empty($singlespot->submode)) {
|
||||
$singlespot->submode = $this->get_submode($singlespot);
|
||||
$singlespot->submode = $this->_get_submode($singlespot);
|
||||
} else {
|
||||
// Normalize cluster-provided submode to uppercase
|
||||
$singlespot->submode = strtoupper($singlespot->submode);
|
||||
@@ -248,14 +232,14 @@ class Dxcluster_model extends CI_Model {
|
||||
}
|
||||
|
||||
// Extract park references from message
|
||||
$singlespot = $this->enrich_spot_metadata($singlespot);
|
||||
$singlespot = $this->_enrich_spot_metadata($singlespot);
|
||||
|
||||
// Collect spots for batch processing
|
||||
$spotsout[] = $singlespot;
|
||||
}
|
||||
|
||||
// Cache the RAW processed spots (WITHOUT worked status) - instance-wide
|
||||
if ($cache_band_enabled && !empty($spotsout)) {
|
||||
if ($this->cache_band_enabled && !empty($spotsout)) {
|
||||
$this->cache->save($raw_cache_key, $spotsout, 59);
|
||||
}
|
||||
}
|
||||
@@ -265,7 +249,7 @@ class Dxcluster_model extends CI_Model {
|
||||
$de_lower = strtolower($de);
|
||||
$filter_continent = ($de != '' && $de != 'Any');
|
||||
$spotsout = array_filter($spotsout, function($spot) use ($mode, $de_lower, $filter_continent) {
|
||||
if ($mode != 'All' && !$this->modefilter($spot, $mode)) return false;
|
||||
if ($mode != 'All' && !$this->_modefilter($spot, $mode)) return false;
|
||||
if ($filter_continent && ($de_lower != strtolower($spot->dxcc_spotter->cont ?? ''))) return false;
|
||||
return true;
|
||||
});
|
||||
@@ -357,13 +341,14 @@ class Dxcluster_model extends CI_Model {
|
||||
|
||||
return $spotsout;
|
||||
} // Determine mode with priority: POTA/SOTA mode > message keywords > frequency-based
|
||||
function get_mode($spot) {
|
||||
|
||||
private function _get_mode($spot) {
|
||||
// Priority 0: If spot already has a valid mode from cluster, use it
|
||||
if (isset($spot->mode) && !empty($spot->mode)) {
|
||||
$existingMode = strtolower($spot->mode);
|
||||
// Validate it's a known mode category
|
||||
if (in_array($existingMode, ['cw', 'phone', 'digi', 'ssb'])) {
|
||||
return $this->mapToModeCategory($existingMode);
|
||||
return $this->_map_to_mode_category($existingMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,10 +357,10 @@ class Dxcluster_model extends CI_Model {
|
||||
$sotaMode = $spot->sota_mode ?? $spot->dxcc_spotted->sota_mode ?? null;
|
||||
|
||||
if (!empty($potaMode)) {
|
||||
return $this->mapToModeCategory($potaMode);
|
||||
return $this->_map_to_mode_category($potaMode);
|
||||
}
|
||||
if (!empty($sotaMode)) {
|
||||
return $this->mapToModeCategory($sotaMode);
|
||||
return $this->_map_to_mode_category($sotaMode);
|
||||
}
|
||||
|
||||
// Priority 2: Message keywords (explicit mode in message text)
|
||||
@@ -397,7 +382,7 @@ class Dxcluster_model extends CI_Model {
|
||||
|
||||
// Priority 3: Frequency-based mode (from bandedges table)
|
||||
// If frequency falls within a defined band edge, use that mode
|
||||
$frequencyMode = $this->Frequency2Mode($spot->frequency);
|
||||
$frequencyMode = $this->_frequency_to_mode($spot->frequency);
|
||||
if ($frequencyMode != '') {
|
||||
return $frequencyMode;
|
||||
}
|
||||
@@ -407,7 +392,7 @@ class Dxcluster_model extends CI_Model {
|
||||
}
|
||||
|
||||
// Map specific mode names to mode categories (phone/cw/digi)
|
||||
function mapToModeCategory($mode) {
|
||||
private function _map_to_mode_category($mode) {
|
||||
$modeUpper = strtoupper($mode);
|
||||
|
||||
// CW modes
|
||||
@@ -432,7 +417,7 @@ class Dxcluster_model extends CI_Model {
|
||||
}
|
||||
|
||||
// Determine submode for more specific mode classification
|
||||
function get_submode($spot) {
|
||||
private function _get_submode($spot) {
|
||||
// Priority 0: If spot already has a valid submode from cluster, use it
|
||||
if (isset($spot->submode) && !empty($spot->submode)) {
|
||||
return strtoupper($spot->submode);
|
||||
@@ -482,16 +467,16 @@ class Dxcluster_model extends CI_Model {
|
||||
return strtoupper($mode);
|
||||
}
|
||||
|
||||
function modefilter($spot, $mode) {
|
||||
private function _modefilter($spot, $mode) {
|
||||
$mode = strtolower($mode); // Normalize case
|
||||
$spotMode = strtolower($spot->mode ?? ''); // Get already-determined mode
|
||||
|
||||
// Since get_mode() already determined the mode using priority logic
|
||||
// Since _get_mode() already determined the mode using priority logic
|
||||
// (frequency > POTA/SOTA > message), we can directly compare
|
||||
return $spotMode === $mode;
|
||||
}
|
||||
|
||||
public function Frequency2Mode($frequency) {
|
||||
private function _frequency_to_mode($frequency) {
|
||||
// Ensure frequency is in Hz if input is in kHz
|
||||
if ($frequency < 1_000_000) {
|
||||
$frequency *= 1000;
|
||||
@@ -505,29 +490,12 @@ class Dxcluster_model extends CI_Model {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function isFrequencyInMode($frequency, $mode) {
|
||||
// Ensure frequency is in Hz if input is in kHz
|
||||
if ($frequency < 1_000_000) {
|
||||
$frequency *= 1000;
|
||||
}
|
||||
|
||||
foreach ($this->bandedges as $band) {
|
||||
if (strtolower($band['mode']) === strtolower($mode)) {
|
||||
if ($frequency >= $band['frequencyfrom'] && $frequency < $band['frequencyto']) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if frequency falls within amateur band allocations
|
||||
* @param float $frequency Frequency in Hz
|
||||
* @return bool True if frequency is in amateur band
|
||||
*/
|
||||
public function isFrequencyInAmateurBand($frequency) {
|
||||
private function _is_frequency_in_amateurband($frequency) {
|
||||
// Ensure frequency is in Hz if input is in kHz
|
||||
if ($frequency < 1_000_000) {
|
||||
$frequency *= 1000;
|
||||
@@ -543,7 +511,7 @@ class Dxcluster_model extends CI_Model {
|
||||
}
|
||||
|
||||
public function dxc_qrg_lookup($qrg, $maxage = 120) {
|
||||
$this->load->helper(array('psr4_autoloader'));
|
||||
|
||||
if (is_numeric($qrg)) {
|
||||
|
||||
$dxcache_url = ($this->optionslib->get_option('dxcache_url') == '' ? 'https://dxc.wavelog.org/dxcache' : $this->optionslib->get_option('dxcache_url'));
|
||||
@@ -585,98 +553,6 @@ class Dxcluster_model extends CI_Model {
|
||||
}
|
||||
}
|
||||
|
||||
function check_if_continent_worked_in_logbook($cont, $StationLocationsArray = null, $band = null, $mode = null) {
|
||||
|
||||
if ($StationLocationsArray == null) {
|
||||
$this->load->model('logbooks_model');
|
||||
$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));
|
||||
} else {
|
||||
$logbooks_locations_array = $StationLocationsArray;
|
||||
}
|
||||
|
||||
$this->db->select('COL_CONT');
|
||||
$this->db->where_in('station_id', $logbooks_locations_array);
|
||||
$this->db->where('COL_CONT', $cont);
|
||||
|
||||
if (isset($mode)) {
|
||||
$this->db->where(" COL_MODE in ".$this->Modes->get_modes_from_qrgmode($mode,true));
|
||||
}
|
||||
|
||||
$band = ($band == 'All') ? null : $band;
|
||||
if ($band != null && $band != 'SAT') {
|
||||
$this->db->where('COL_BAND', $band);
|
||||
} else if ($band == 'SAT') {
|
||||
// Where col_sat_name is not empty
|
||||
$this->db->where('COL_SAT_NAME !=', '');
|
||||
}
|
||||
$this->db->limit('2');
|
||||
|
||||
$query = $this->db->get($this->config->item('table_name'));
|
||||
return $query->num_rows();
|
||||
}
|
||||
|
||||
function check_if_continent_cnfmd_in_logbook($cont, $StationLocationsArray = null, $band = null, $mode = null) {
|
||||
|
||||
if ($StationLocationsArray == null) {
|
||||
$this->load->model('logbooks_model');
|
||||
$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));
|
||||
} else {
|
||||
$logbooks_locations_array = $StationLocationsArray;
|
||||
}
|
||||
|
||||
$user_default_confirmation = $this->session->userdata('user_default_confirmation');
|
||||
$extrawhere = '';
|
||||
if (isset($user_default_confirmation) && strpos($user_default_confirmation, 'Q') !== false) {
|
||||
$extrawhere = "COL_QSL_RCVD='Y'";
|
||||
}
|
||||
if (isset($user_default_confirmation) && strpos($user_default_confirmation, 'L') !== false) {
|
||||
if ($extrawhere != '') {
|
||||
$extrawhere .= " OR";
|
||||
}
|
||||
$extrawhere .= " COL_LOTW_QSL_RCVD='Y'";
|
||||
}
|
||||
if (isset($user_default_confirmation) && strpos($user_default_confirmation, 'E') !== false) {
|
||||
if ($extrawhere != '') {
|
||||
$extrawhere .= " OR";
|
||||
}
|
||||
$extrawhere .= " COL_EQSL_QSL_RCVD='Y'";
|
||||
}
|
||||
|
||||
if (isset($user_default_confirmation) && strpos($user_default_confirmation, 'Z') !== false) {
|
||||
if ($extrawhere != '') {
|
||||
$extrawhere .= " OR";
|
||||
}
|
||||
$extrawhere .= " COL_QRZCOM_QSO_DOWNLOAD_STATUS='Y'";
|
||||
}
|
||||
|
||||
|
||||
$this->db->select('COL_CONT');
|
||||
$this->db->where_in('station_id', $logbooks_locations_array);
|
||||
$this->db->where('COL_CONT', $cont);
|
||||
|
||||
if (isset($mode)) {
|
||||
$this->db->where(" COL_MODE in ".$this->Modes->get_modes_from_qrgmode($mode,true));
|
||||
}
|
||||
|
||||
$band = ($band == 'All') ? null : $band;
|
||||
if ($band != null && $band != 'SAT') {
|
||||
$this->db->where('COL_BAND', $band);
|
||||
} else if ($band == 'SAT') {
|
||||
// Where col_sat_name is not empty
|
||||
$this->db->where('COL_SAT_NAME !=', '');
|
||||
}
|
||||
if ($extrawhere != '') {
|
||||
$this->db->where('(' . $extrawhere . ')');
|
||||
} else {
|
||||
$this->db->where("1=0");
|
||||
}
|
||||
$this->db->limit('2');
|
||||
|
||||
$query = $this->db->get($this->config->item('table_name'));
|
||||
|
||||
return $query->num_rows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrich spot metadata with park references and contest detection
|
||||
* Extracts SOTA/POTA/IOTA/WWFF references and detects contest spots
|
||||
@@ -684,7 +560,7 @@ class Dxcluster_model extends CI_Model {
|
||||
* @param object $spot - Spot object with message and dxcc_spotted properties
|
||||
* @return object - Spot object with enriched dxcc_spotted containing references and isContest flag
|
||||
*/
|
||||
function enrich_spot_metadata($spot) {
|
||||
private function _enrich_spot_metadata($spot) {
|
||||
// Ensure dxcc_spotted object exists
|
||||
if (!property_exists($spot, 'dxcc_spotted') || !is_object($spot->dxcc_spotted)) {
|
||||
$spot->dxcc_spotted = (object)[];
|
||||
|
||||
@@ -899,7 +899,7 @@ class Logbook_model extends CI_Model {
|
||||
}
|
||||
|
||||
// Invalidate DXCluster cache for this callsign
|
||||
$this->dxclustercache->invalidateForCallsign($data['COL_CALL']);
|
||||
$this->dxclustercache->invalidate_for_callsign($data['COL_CALL']);
|
||||
|
||||
// Return QSO ID for ADIF generation
|
||||
return $last_id;
|
||||
@@ -1674,7 +1674,7 @@ class Logbook_model extends CI_Model {
|
||||
$retvals['success']=true;
|
||||
|
||||
// Invalidate DXCluster cache for this callsign
|
||||
$this->dxclustercache->invalidateForCallsign($data['COL_CALL']);
|
||||
$this->dxclustercache->invalidate_for_callsign($data['COL_CALL']);
|
||||
} catch (Exception $e) {
|
||||
$retvals['success']=false;
|
||||
$retvals['detail']=$e;
|
||||
@@ -2715,13 +2715,16 @@ class Logbook_model extends CI_Model {
|
||||
|
||||
// Load cache driver for file caching
|
||||
$cache_enabled = $this->config->item('enable_dxcluster_file_cache_worked') === true;
|
||||
if ($cache_enabled && !isset($this->cache)) {
|
||||
$this->load->driver('cache', [
|
||||
'adapter' => $this->config->item('cache_adapter') ?? 'file',
|
||||
'backup' => $this->config->item('cache_backup') ?? 'file',
|
||||
'key_prefix' => $this->config->item('cache_key_prefix') ?? ''
|
||||
]);
|
||||
}
|
||||
|
||||
// Gets already loaded in dxcluster controller
|
||||
//
|
||||
// if ($cache_enabled && !isset($this->cache)) {
|
||||
// $this->load->driver('cache', [
|
||||
// 'adapter' => $this->config->item('cache_adapter') ?? 'file',
|
||||
// 'backup' => $this->config->item('cache_backup') ?? 'file',
|
||||
// 'key_prefix' => $this->config->item('cache_key_prefix') ?? ''
|
||||
// ]);
|
||||
// }
|
||||
|
||||
// Cache TTL in seconds (15 minutes = 900 seconds)
|
||||
$cache_ttl = 900;
|
||||
@@ -2743,7 +2746,7 @@ class Logbook_model extends CI_Model {
|
||||
|
||||
// Build cache key with user_id, logbook_ids, and confirmation preference
|
||||
$user_id = $this->session->userdata('user_id');
|
||||
$logbook_ids_key = $this->dxclustercache->getLogbookKey($user_id, $logbooks_locations_array, $user_default_confirmation);
|
||||
$logbook_ids_key = $this->dxclustercache->get_logbook_key($user_id, $logbooks_locations_array, $user_default_confirmation);
|
||||
$spots_by_callsign = []; // Group spots by callsign for processing
|
||||
|
||||
foreach ($spots as $spot) {
|
||||
@@ -2781,7 +2784,7 @@ class Logbook_model extends CI_Model {
|
||||
if (!isset($this->spot_status_cache[$cache_key])) {
|
||||
// Check file cache
|
||||
if ($cache_enabled) {
|
||||
$file_cache_key = $this->dxclustercache->getWorkedCallKey($logbook_ids_key, $callsign);
|
||||
$file_cache_key = $this->dxclustercache->get_worked_call_key($logbook_ids_key, $callsign);
|
||||
$cached_data = $this->cache->get($file_cache_key);
|
||||
if ($cached_data !== false) {
|
||||
// Load from file cache into in-memory cache
|
||||
@@ -2799,7 +2802,7 @@ class Logbook_model extends CI_Model {
|
||||
|
||||
if (!isset($this->spot_status_cache[$cache_key])) {
|
||||
if ($cache_enabled) {
|
||||
$file_cache_key = $this->dxclustercache->getWorkedDxccKey($logbook_ids_key, $dxcc);
|
||||
$file_cache_key = $this->dxclustercache->get_worked_dxcc_key($logbook_ids_key, $dxcc);
|
||||
$cached_data = $this->cache->get($file_cache_key);
|
||||
if ($cached_data !== false) {
|
||||
$this->spot_status_cache[$cache_key] = $cached_data;
|
||||
@@ -2815,7 +2818,7 @@ class Logbook_model extends CI_Model {
|
||||
|
||||
if (!isset($this->spot_status_cache[$cache_key])) {
|
||||
if ($cache_enabled) {
|
||||
$file_cache_key = $this->dxclustercache->getWorkedContKey($logbook_ids_key, $cont);
|
||||
$file_cache_key = $this->dxclustercache->get_worked_cont_key($logbook_ids_key, $cont);
|
||||
$cached_data = $this->cache->get($file_cache_key);
|
||||
if ($cached_data !== false) {
|
||||
$this->spot_status_cache[$cache_key] = $cached_data;
|
||||
@@ -3058,7 +3061,7 @@ class Logbook_model extends CI_Model {
|
||||
|
||||
// Save to file cache for 15 minutes
|
||||
if ($cache_enabled) {
|
||||
$file_cache_key = $this->dxclustercache->getWorkedCallKey($logbook_ids_key, $callsign);
|
||||
$file_cache_key = $this->dxclustercache->get_worked_call_key($logbook_ids_key, $callsign);
|
||||
$this->cache->save($file_cache_key, $data, $cache_ttl);
|
||||
}
|
||||
}
|
||||
@@ -3067,7 +3070,7 @@ class Logbook_model extends CI_Model {
|
||||
$this->spot_status_cache[$cache_key] = $data;
|
||||
|
||||
if ($cache_enabled) {
|
||||
$file_cache_key = $this->dxclustercache->getWorkedDxccKey($logbook_ids_key, $dxcc);
|
||||
$file_cache_key = $this->dxclustercache->get_worked_dxcc_key($logbook_ids_key, $dxcc);
|
||||
$this->cache->save($file_cache_key, $data, $cache_ttl);
|
||||
}
|
||||
}
|
||||
@@ -3076,7 +3079,7 @@ class Logbook_model extends CI_Model {
|
||||
$this->spot_status_cache[$cache_key] = $data;
|
||||
|
||||
if ($cache_enabled) {
|
||||
$file_cache_key = $this->dxclustercache->getWorkedContKey($logbook_ids_key, $cont);
|
||||
$file_cache_key = $this->dxclustercache->get_worked_cont_key($logbook_ids_key, $cont);
|
||||
$this->cache->save($file_cache_key, $data, $cache_ttl);
|
||||
}
|
||||
} // Cache NOT WORKED items (negative results) - store empty arrays
|
||||
@@ -3087,7 +3090,7 @@ class Logbook_model extends CI_Model {
|
||||
$this->spot_status_cache[$cache_key] = []; // Empty = not worked
|
||||
|
||||
if ($cache_enabled) {
|
||||
$file_cache_key = $this->dxclustercache->getWorkedCallKey($logbook_ids_key, $callsign);
|
||||
$file_cache_key = $this->dxclustercache->get_worked_call_key($logbook_ids_key, $callsign);
|
||||
$this->cache->save($file_cache_key, [], $cache_ttl);
|
||||
}
|
||||
}
|
||||
@@ -3098,7 +3101,7 @@ class Logbook_model extends CI_Model {
|
||||
$this->spot_status_cache[$cache_key] = [];
|
||||
|
||||
if ($cache_enabled) {
|
||||
$file_cache_key = $this->dxclustercache->getWorkedDxccKey($logbook_ids_key, $dxcc);
|
||||
$file_cache_key = $this->dxclustercache->get_worked_dxcc_key($logbook_ids_key, $dxcc);
|
||||
$this->cache->save($file_cache_key, [], $cache_ttl);
|
||||
}
|
||||
}
|
||||
@@ -3109,7 +3112,7 @@ class Logbook_model extends CI_Model {
|
||||
$this->spot_status_cache[$cache_key] = [];
|
||||
|
||||
if ($cache_enabled) {
|
||||
$file_cache_key = $this->dxclustercache->getWorkedContKey($logbook_ids_key, $cont);
|
||||
$file_cache_key = $this->dxclustercache->get_worked_cont_key($logbook_ids_key, $cont);
|
||||
$this->cache->save($file_cache_key, [], $cache_ttl);
|
||||
}
|
||||
}
|
||||
@@ -4351,7 +4354,7 @@ class Logbook_model extends CI_Model {
|
||||
$this->db->delete("oqrs");
|
||||
|
||||
// Invalidate DXCluster cache for this callsign
|
||||
$this->dxclustercache->invalidateForCallsign($callsign);
|
||||
$this->dxclustercache->invalidate_for_callsign($callsign);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ class Dxcc {
|
||||
*/
|
||||
function read_data($date = NULL) {
|
||||
$CI = &get_instance();
|
||||
$CI->load->driver('cache', [
|
||||
$CI->load->is_loaded('cache') ?: $CI->load->driver('cache', [
|
||||
'adapter' => $CI->config->item('cache_adapter') ?? 'file',
|
||||
'backup' => $CI->config->item('cache_backup') ?? 'file',
|
||||
'key_prefix' => $CI->config->item('cache_key_prefix') ?? ''
|
||||
|
||||
Reference in New Issue
Block a user