Completed
Push — master ( 23153f...eb881d )
by Angus
03:14
created

Tracker_Admin_Model   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 346
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 1.62%

Importance

Changes 0
Metric Value
dl 0
loc 346
rs 8.3157
c 0
b 0
f 0
ccs 3
cts 185
cp 0.0162
wmc 43
lcom 1
cbo 5

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
B updateLatestChapters() 0 83 6
C updateAllTitlesBySite() 0 71 7
D updateCustom() 0 46 10
B refollowCustom() 0 33 5
C updateTitles() 0 51 8
B getNextUpdateTime() 0 33 6

How to fix   Complexity   

Complex Class

Complex classes like Tracker_Admin_Model often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Tracker_Admin_Model, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1); defined('BASEPATH') OR exit('No direct script access allowed');
2
3
class Tracker_Admin_Model extends Tracker_Base_Model {
4 119
	public function __construct() {
5 119
		parent::__construct();
6 119
	}
7
8
	/**
9
	 * Checks for any series that haven't updated in 16 hours and updates them.
10
	 * This is ran every 4 hours via a cron job.
11
	 */
12
	public function updateLatestChapters() {
13
		// @formatter:off
14
		$query = $this->db
15
			->select('
16
				tracker_titles.id as title_id,
17
				tracker_titles.title,
18
				tracker_titles.title_url,
19
				tracker_titles.status,
20
				tracker_sites.site,
21
				tracker_sites.site_class,
22
				tracker_sites.status,
23
				tracker_titles.latest_chapter,
24
				tracker_titles.last_updated,
25
				from_unixtime(MAX(auth_users.last_login)) AS timestamp
26
			')
27
			->from('tracker_titles')
28
			->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left')
29
			->join('tracker_chapters', 'tracker_titles.id = tracker_chapters.title_id', 'left')
30
			->join('auth_users', 'tracker_chapters.user_id = auth_users.id', 'left')
31
			->where('tracker_sites.status', 'enabled')
32
			->group_start()
33
				//Check if title is marked as on-going...
34
				->where('tracker_titles.status', 0)
35
				//AND matches one of where queries below
36
				->group_start()
37
					//Then check if it's NULL (only occurs for new series)
38
					->where('latest_chapter', NULL)
39
					//OR if it hasn't updated within the past 12 hours AND isn't a custom update site
40
					->or_group_start()
41
						->where('tracker_sites.use_custom', 'N')
42
						->where('last_checked < DATE_SUB(NOW(), INTERVAL 12 HOUR)')
43
					->group_end()
44
					//OR it is a custom update site and hasn't updated within the past 72 hours (3 days)
45
					->or_where('last_checked < DATE_SUB(NOW(), INTERVAL 72 HOUR)')
46
				->group_end()
47
			->group_end()
48
			->or_group_start()
49
				//Check if title is marked as complete...
50
				->where('tracker_titles.status', 1)
51
				//Then check if it hasn't updated within the past week
52
				->where('last_checked < DATE_SUB(NOW(), INTERVAL 1 WEEK)')
53
			->group_end()
54
			//Status 2 (One-shot) & 255 (Ignore) are both not updated intentionally.
55
			->group_by('tracker_titles.id, tracker_chapters.active')
56
			//Check if the series is actually being tracked by someone
57
			->having('timestamp IS NOT NULL')
58
			//AND if it's currently marked as active by the user
59
			->having('tracker_chapters.active', 'Y')
60
			//AND if they have been active in the last 120 hours (5 days)
61
			->having('timestamp > DATE_SUB(NOW(), INTERVAL 120 HOUR)')
62
			->order_by('tracker_titles.title', 'ASC')
63
			->get();
64
		// @formatter:on
65
66
		if($query->num_rows() > 0) {
67
			foreach ($query->result() as $row) {
68
				print "> {$row->title} <{$row->site_class}> | <{$row->title_id}>"; //Print this prior to doing anything so we can more easily find out if something went wrong
69
				$titleData = $this->sites->{$row->site_class}->getTitleData($row->title_url);
70
				if(is_array($titleData) && !is_null($titleData['latest_chapter'])) {
71
					//FIXME: "At the moment" we don't seem to be doing anything with TitleData['last_updated'].
72
					//       Should we even use this? Y/N
73
					if($this->Tracker->title->updateByID((int) $row->title_id, $titleData['latest_chapter'])) {
74
						//Make sure last_checked is always updated on successful run.
75
						//CHECK: Is there a reason we aren't just doing this in updateByID?
76
						$this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE)
77
						         ->where('id', $row->title_id)
78
						         ->update('tracker_titles');
79
80
						print " - ({$titleData['latest_chapter']})\n";
81
					} else {
82
						log_message('error', "{$row->title} failed to update successfully");
83
84
						print " - Something went wrong?\n";
85
					}
86
				} else {
87
					log_message('error', "{$row->title} failed to update successfully");
88
					$this->Tracker->title->updateFailedChecksByID((int) $row->title_id);
89
90
					print " - FAILED TO PARSE\n";
91
				}
92
			}
93
		}
94
	}
95
96
	/**
97
	 * Intended to be only used as a quick way to update all series on a site after a bug.
98
	 *
99
	 * @param string      $site
100
	 * @param null|string $last_checked
101
	 */
102
	public function updateAllTitlesBySite(string $site, ?string $last_checked = NULL) {
103
		// @formatter:off
104
		$query = $this->db
105
			->select('
106
				tracker_titles.id as title_id,
107
				tracker_titles.title,
108
				tracker_titles.title_url,
109
				tracker_titles.status,
110
				tracker_sites.site,
111
				tracker_sites.site_class,
112
				tracker_sites.status,
113
				tracker_titles.latest_chapter,
114
				tracker_titles.last_updated,
115
				from_unixtime(MAX(auth_users.last_login)) AS timestamp
116
			')
117
			->from('tracker_titles')
118
			->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left')
119
			->join('tracker_chapters', 'tracker_titles.id = tracker_chapters.title_id', 'left')
120
			->join('auth_users', 'tracker_chapters.user_id = auth_users.id', 'left')
121
			->where('tracker_sites.status', 'enabled')
122
			->where('tracker_sites.site_class', $site)
123
			->group_start()
124
				//Check if title is marked as on-going...
125
				->where('tracker_titles.status', 0)
126
				//Check if title is marked as complete...
127
				->or_where('tracker_titles.status', 1)
128
			->group_end()
129
			//Status 2 (One-shot) & 255 (Ignore) are both not updated intentionally.
130
			->group_by('tracker_titles.id, tracker_chapters.active')
131
			//Check if the series is actually being tracked by someone
132
			->having('timestamp IS NOT NULL')
133
			//AND if it's currently marked as active by the user
134
			->having('tracker_chapters.active', 'Y')
135
			//AND if they have been active in the last 120 hours (5 days)
136
			->having('timestamp > DATE_SUB(NOW(), INTERVAL 120 HOUR)')
137
			->order_by('tracker_titles.last_checked', 'ASC');
138
		// @formatter:on
139
		if(!is_null($last_checked)) {
140
			$query = $query->where('tracker_titles.last_checked >', $last_checked);
141
		}
142
		$query = $query->get();
143
144
		if($query->num_rows() > 0) {
145
			foreach ($query->result() as $row) {
146
				print "> {$row->title} <{$row->site_class}> | <{$row->title_id}>"; //Print this prior to doing anything so we can more easily find out if something went wrong
147
				$titleData = $this->sites->{$row->site_class}->getTitleData($row->title_url);
148
				if(is_array($titleData) && !is_null($titleData['latest_chapter'])) {
149
					//FIXME: "At the moment" we don't seem to be doing anything with TitleData['last_updated'].
150
					//       Should we even use this? Y/N
151
					if($this->Tracker->title->updateByID((int) $row->title_id, $titleData['latest_chapter'])) {
152
						//Make sure last_checked is always updated on successful run.
153
						//CHECK: Is there a reason we aren't just doing this in updateByID?
154
						$this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE)
155
						         ->where('id', $row->title_id)
156
						         ->update('tracker_titles');
157
158
						print " - ({$titleData['latest_chapter']})\n";
159
					} else {
160
						log_message('error', "{$row->title} failed to update successfully");
161
162
						print " - Something went wrong?\n";
163
					}
164
				} else {
165
					log_message('error', "{$row->title} failed to update successfully");
166
					$this->Tracker->title->updateFailedChecksByID((int) $row->title_id);
167
168
					print " - FAILED TO PARSE\n";
169
				}
170
			}
171
		}
172
	}
173
174
	/**
175
	 * Checks for any sites which support custom updating (usually via following lists) and updates them.
176
	 * This is run hourly.
177
	 */
178
	public function updateCustom() {
179
		$query = $this->db->select('*')
180
		                  ->from('tracker_sites')
181
		                  ->where('status', 'enabled')
182
		                  ->where('tracker_sites.use_custom', 'Y')
183
		                  ->get();
184
185
		$sites = $query->result_array();
186
		foreach ($sites as $site) {
187
			$siteClass = $this->sites->{$site['site_class']};
188
			if($titleDataList = $siteClass->doCustomUpdate()) {
189
				foreach ($titleDataList as $titleURL => $titleData) {
190
					print "> {$titleData['title']} <{$site['site_class']}>"; //Print this prior to doing anything so we can more easily find out if something went wrong
191
					if(is_array($titleData) && !is_null($titleData['latest_chapter'])) {
192
						if($dbTitleData = $this->Tracker->title->getID($titleURL, (int) $site['id'], FALSE, TRUE)) {
193
							if($this->sites->{$site['site_class']}->doCustomCheck($dbTitleData['latest_chapter'], $titleData['latest_chapter'])) {
194
								$titleID = $dbTitleData['id'];
195
								if($this->Tracker->title->updateByID((int) $titleID, $titleData['latest_chapter'])) {
196
									//Make sure last_checked is always updated on successful run.
197
									//CHECK: Is there a reason we aren't just doing this in updateByID?
198
									$this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE)
199
									         ->where('id', $titleID)
200
									         ->update('tracker_titles');
201
202
									print " - ({$titleData['latest_chapter']})\n";
203
								} else {
204
									print " - Title doesn't exist? ($titleID)\n";
205
								}
206
							} else {
207
								print " - Failed Check (DB: '{$dbTitleData['latest_chapter']}' || UPDATE: '{$titleData['latest_chapter']}')\n";
208
							}
209
						} else {
210
							//We only need to log if following page is missing title, not latest releases
211
							if($siteClass->customType === 1) {
212
								log_message('error', "CUSTOM: {$titleData['title']} - {$site['site_class']} || Title does not exist in DB??");
213
								print " - Title doesn't currently exist in DB? Maybe different language or title stub change? ($titleURL)\n";
214
							}
215
						}
216
					} else {
217
						log_message('error', "CUSTOM: {$titleData['title']} - {$site['site_class']} failed to custom update successfully");
218
						print " - FAILED TO PARSE\n";
219
					}
220
				}
221
			}
222
		}
223
	}
224
225
	public function refollowCustom() {
226
		$query = $this->db->select('tracker_titles.id, tracker_titles.title_url, tracker_sites.site_class')
227
		                  ->from('tracker_titles')
228
		                  ->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left')
229
		                  ->where('tracker_titles.followed','N')
230
		                  ->where('tracker_titles !=', '255')
231
		                  ->where('tracker_sites.status', 'enabled')
232
		                  ->where('tracker_sites.use_custom', 'Y')
233
		                  ->get();
234
235
		if($query->num_rows() > 0) {
236
			foreach($query->result() as $row) {
237
				$titleData = $this->Tracker->sites->{$row->site_class}->getTitleData($row->title_url, TRUE);
238
239
				if($titleData) {
240
					$titleData = array_intersect_key($titleData, array_flip(['followed']));
241
242
					if(!empty($titleData)) {
243
						$this->db->set($titleData)
244
						         ->where('id', $row->id)
245
						         ->update('tracker_titles');
246
247
						print "> {$row->site_class}:{$row->id}:{$row->title_url} FOLLOWED\n";
248
					} else {
249
						print "> {$row->site_class}:{$row->id}:{$row->title_url} FAILED (NO FOLLOWED)\n";
250
					}
251
				} else {
252
					log_message('error', "getTitleData failed for: {$row->site_class} | {$row->title_url}");
253
					print "> {$row->site_class}:{$row->id}:{$row->title_url} FAILED (NO TITLEDATA)\n";
254
				}
255
			}
256
		}
257
	}
258
259
	/**
260
	 * Checks every series to see if title has changed, and update if so.
261
	 * This is ran once a month via a cron job
262
	 */
263
	public function updateTitles() {
264
		// @formatter:off
265
		$query = $this->db
266
			->select('
267
				tracker_titles.id,
268
				tracker_titles.title,
269
				tracker_titles.title_url,
270
				tracker_titles.status,
271
				tracker_sites.site,
272
				tracker_sites.site_class,
273
				tracker_sites.status,
274
				tracker_titles.latest_chapter,
275
				tracker_titles.last_updated
276
			')
277
			->from('tracker_titles')
278
			->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left')
279
			->where('tracker_sites.status', 'enabled')
280
281
			->group_by('tracker_titles.id')
282
			->order_by('tracker_titles.title', 'ASC')
283
			->get();
284
		// @formatter:on
285
286
		if($query->num_rows() > 0) {
287
			foreach ($query->result() as $row) {
288
				print "> {$row->title} <{$row->site_class}>"; //Print this prior to doing anything so we can more easily find out if something went wrong
289
				$titleData = $this->sites->{$row->site_class}->getTitleData($row->title_url);
290
				if($titleData['title'] && is_array($titleData) && !is_null($titleData['latest_chapter'])) {
291
					if($titleData['title'] !== $row->title) {
292
						$this->db->set('title', $titleData['title'])
293
						         ->where('id', $row->id)
294
						         ->update('tracker_titles');
295
						//TODO: Add to history somehow?
296
						print " - NEW TITLE ({$titleData['title']})\n";
297
					} else {
298
						print " - TITLE NOT CHANGED\n";
299
					}
300
301
					//We might as well try to update as well.
302
					if($this->Tracker->title->updateByID((int) $row->id, $titleData['latest_chapter'])) {
303
						$this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE)
304
						         ->where('id', $row->id)
305
						         ->update('tracker_titles');
306
					}
307
				} else {
308
					log_message('error', "{$row->title} failed to update title successfully");
309
					print " - FAILED TO PARSE\n";
310
				}
311
			}
312
		}
313
	}
314
315
	public function getNextUpdateTime(string $format = "%H:%I:%S") : string {
316
		$temp_now = new DateTime();
317
		$temp_now->setTimezone(new DateTimeZone('America/New_York'));
318
		$temp_now_formatted = $temp_now->format('Y-m-d H:i:s');
319
320
		//NOTE: PHP Bug: DateTime:diff doesn't play nice with setTimezone, so we need to create another DT object
321
		$now         = new DateTime($temp_now_formatted);
322
		$future_date = new DateTime($temp_now_formatted);
323
		$now_hour    = (int) $now->format('H');
324
		if($now_hour < 4) {
325
			//Time until 4am
326
			$future_date->setTime(4, 00);
327
		} elseif($now_hour < 8) {
328
			//Time until 8am
329
			$future_date->setTime(8, 00);
330
		} elseif($now_hour < 12) {
331
			//Time until 12pm
332
			$future_date->setTime(12, 00);
333
		} elseif($now_hour < 16) {
334
			//Time until 4pm
335
			$future_date->setTime(16, 00);
336
		} elseif($now_hour < 20) {
337
			//Time until 8pm
338
			$future_date->setTime(20, 00);
339
		} else {
340
			//Time until 12am
341
			$future_date->setTime(00, 00);
342
			$future_date->add(new DateInterval('P1D'));
343
		}
344
345
		$interval = $future_date->diff($now);
346
		return $interval->format($format);
347
	}
348
}
349