Completed
Push — master ( d4c057...8e4aae )
by Angus
04:35
created

Tracker_Admin_Model   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 386
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 1.44%

Importance

Changes 0
Metric Value
dl 0
loc 386
ccs 3
cts 208
cp 0.0144
rs 8.3999
c 0
b 0
f 0
wmc 46
lcom 1
cbo 5

9 Methods

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