Completed
Push — master ( 387b2c...31214c )
by Angus
03:11
created

Tracker_Admin_Model   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 382
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 1.44%

Importance

Changes 0
Metric Value
dl 0
loc 382
ccs 3
cts 208
cp 0.0144
rs 8.3673
c 0
b 0
f 0
wmc 45
lcom 1
cbo 5

8 Methods

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