Completed
Pull Request — master (#488)
by Helpful
1295:51 queued 1292:33
created

DNDataTransfer::LogLink()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/**
4
 * Class representing a single data transfer in a project,
5
 * which can include a database export, an archive of all assets, or both.
6
 *
7
 * It can be one of two directions:
8
 * - Backup: Package up data on an environment and store it in a local file
9
 * - Restore: Transfer data from a local file into an environment, extract assets and/or restore a database
10
 *
11
 * The choice of database and/or assets is represented in the "Mode".
12
 * There's always one file archive involved (stored as the has_one "ArchiveFile") on the local Deploynaut environment.
13
 *
14
 * Each transfer is executed by a Resque job, so the model also contains
15
 * a reference to a Resque token (which might still be in progress).
16
 *
17
 * The "Environment" points to the source or target involved.
18
 *
19
 * @property string $ResqueToken
20
 * @property string $Status
21
 * @property string $Direction
22
 * @property string $Mode
23
 * @property string $Origin
24
 *
25
 * @method DNEnvironment Environment()
26
 * @property int EnvironmentID
27
 * @method Member Author()
28
 * @property int AuthorID
29
 * @method DNDataArchive DataArchive()
30
 * @property int DataArchiveID
31
 * @method DNDataTransfer BackupDataTransfer()
32
 * @property int BackupDataTransferID
33
 */
34
class DNDataTransfer extends DataObject {
35
36
	private static $db = array(
37
		"ResqueToken" => "Varchar(255)",
38
		// Observe that this is not the same as Resque status, since ResqueStatus is not persistent.
39
		"Status" => "Enum('Queued, Started, Finished, Failed, n/a', 'n/a')",
40
		"Direction" => "Enum('get, push', 'get')",
41
		"Mode" => "Enum('all, assets, db', '')",
42
		"Origin" => "Enum('EnvironmentTransfer,ManualUpload', 'EnvironmentTransfer')",
43
	);
44
45
	private static $has_one = array(
46
		"Environment" => "DNEnvironment",
47
		"Author" => "Member",
48
		"DataArchive" => "DNDataArchive",
49
		"BackupDataTransfer" => "DNDataTransfer" // denotes an automated backup done for a push of this data transfer
50
	);
51
52
	private static $singular_name = 'Data Transfer';
53
54
	private static $plural_name = 'Data Transfers';
55
56
	private static $summary_fields = array(
57
		'Created' => 'Created',
58
		'Author.Title' => 'Author',
59
		'Environment.Project.Name' => 'Project',
60
		'Environment.Name' => 'Environment',
61
		'Status' => 'Status',
62
		'Origin' => 'Origin',
63
	);
64
65
	private static $searchable_fields = array(
66
		'Environment.Project.Name' => array(
67
			'title' => 'Project',
68
		),
69
		'Environment.Name' => array(
70
			'title' => 'Environment',
71
		),
72
		'Status' => array(
73
			'title' => 'Status',
74
		),
75
		'Origin' => array(
76
			'title' => 'Origin',
77
		),
78
		'Mode' => array(
79
			'title' => 'Mode',
80
		),
81
		'Direction' => array(
82
			'title' => 'Direction',
83
		),
84
	);
85
86
	/**
87
	 * When running the transfer, should a backup be performed before pushing the data?
88
	 * @var bool
89
	 */
90
	protected $backupBeforePush = true;
91
92
	/**
93
	 * @param int $int
94
	 * @return string
95
	 */
96 View Code Duplication
	public static function map_resque_status($int) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
97
		$remap = array(
98
			Resque_Job_Status::STATUS_WAITING => "Queued",
99
			Resque_Job_Status::STATUS_RUNNING => "Running",
100
			Resque_Job_Status::STATUS_FAILED => "Failed",
101
			Resque_Job_Status::STATUS_COMPLETE => "Complete",
102
			false => "Invalid",
103
		);
104
		return $remap[$int];
105
	}
106
107
	/**
108
	 * @param boolean $value
109
	 */
110
	public function setBackupBeforePush($value) {
111
		$this->backupBeforePush = $value;
112
	}
113
114
	public function getTitle() {
115
		return $this->dbObject('Created')->Nice() . " (Status: {$this->Status})";
116
	}
117
118
	public function Link() {
119
		return Controller::join_links($this->Environment()->Project()->Link(), 'transfer', $this->ID);
120
	}
121
122
	public function LogLink() {
123
		return Controller::join_links($this->Link(), 'log');
124
	}
125
126
	public function getDefaultSearchContext() {
127
		$context = parent::getDefaultSearchContext();
128
		$context->getFields()->dataFieldByName('Status')->setHasEmptyDefault(true);
129
		$context->getFields()->dataFieldByName('Origin')->setHasEmptyDefault(true);
130
131
		return $context;
132
	}
133
134
	public function getCMSFields() {
135
		$fields = parent::getCMSFields();
136
		$fields->removeByName('EnvironmentID');
137
		$fields->removeByName('ArchiveFile');
138
		$fields->addFieldsToTab(
139
			'Root.Main',
140
			array(
141
				new ReadonlyField('ProjectName', 'Project', $this->Environment()->Project()->Name),
142
				new ReadonlyField('EnvironmentName', 'Environment', $this->Environment()->Name),
143
				new ReadonlyField(
144
					'DataArchive',
145
					'Archive File',
146
					sprintf(
147
						'<a href="%s">%s</a>',
148
						$this->DataArchive()->ArchiveFile()->AbsoluteURL,
149
						$this->DataArchive()->ArchiveFile()->Filename
150
					)
151
				),
152
			)
153
		);
154
		$fields = $fields->makeReadonly();
155
156
		return $fields;
157
	}
158
159
	/**
160
	 * Queue a transfer job
161
	 */
162
	public function start() {
163
		$env = $this->Environment();
164
		$log = $this->log();
165
166
		$args = array(
167
			'dataTransferID' => $this->ID,
168
			'logfile' => $this->logfile(),
169
			'backupBeforePush' => $this->backupBeforePush
170
		);
171
172
		if(!$this->AuthorID) {
173
			$this->AuthorID = Member::currentUserID();
174
		}
175
176 View Code Duplication
		if($this->AuthorID) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
177
			$author = $this->Author();
178
			$message = sprintf(
179
				'Data transfer on %s (%s, %s) initiated by %s (%s), with IP address %s',
180
				$env->getFullName(),
181
				$this->Direction,
182
				$this->Mode,
183
				$author->getName(),
184
				$author->Email,
185
				Controller::curr()->getRequest()->getIP()
186
			);
187
			$log->write($message);
188
		}
189
190
		$token = Resque::enqueue('git', 'DataTransferJob', $args, true);
191
		$this->ResqueToken = $token;
192
		$this->write();
193
194
		$message = sprintf('Data transfer queued as job %s', $token);
195
		$log->write($message);
196
	}
197
198
	/**
199
	 * @param Member|null $member
200
	 * @return bool
201
	 */
202
	public function canView($member = null) {
203
		return $this->Environment()->canView($member);
204
	}
205
206
	/**
207
	 * Return a path to the log file.
208
	 * @return string
209
	 */
210
	protected function logfile() {
211
		return sprintf(
212
			'%s.datatransfer.%s.log',
213
			$this->Environment()->getFullName('.'),
214
			$this->ID
215
		);
216
	}
217
218
	/**
219
	 * @return \DeploynautLogFile
220
	 */
221
	public function log() {
222
		return new DeploynautLogFile($this->logfile());
223
	}
224
225
	/**
226
	 * @return string
227
	 */
228
	public function LogContent() {
229
		return $this->log()->content();
230
	}
231
232
	public function getDescription() {
233
		$envName = $this->Environment()->getFullName();
234
		if($this->Direction == 'get') {
235
			if($this->Origin == 'ManualUpload') {
236
				$description = 'Manual upload of ' . $this->getModeNice() . ' to ' . $envName;
237
			} elseif($this->IsBackupDataTransfer()) {
238
				$description = 'Automated backup of ' . $this->getModeNice() . ' from ' . $envName;
239
			} else {
240
				$description = 'Backup of ' . $this->getModeNice() . ' to ' . $envName;
241
			}
242
		} else {
243
			$description = 'Restore ' . $this->getModeNice() . ' to ' . $envName;
244
		}
245
246
		return $description;
247
	}
248
249
	public function getModeNice() {
250
		if($this->Mode == 'all') {
251
			return 'database and assets';
252
		} else {
253
			return $this->Mode;
254
		}
255
	}
256
257
	/**
258
	 * Is this transfer an automated backup of a push transfer?
259
	 * @return boolean
260
	 */
261
	public function IsBackupDataTransfer() {
262
		return DB::query(sprintf(
263
			'SELECT COUNT("ID") FROM "DNDataTransfer" WHERE "BackupDataTransferID" = %d',
264
			$this->ID
265
		))->value();
266
	}
267
268
	/**
269
	 * Returns the status of the resque job
270
	 *
271
	 * @return string
272
	 */
273 View Code Duplication
	public function ResqueStatus() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
274
		$status = new Resque_Job_Status($this->ResqueToken);
0 ignored issues
show
Security Bug introduced by
It seems like $this->ResqueToken can also be of type false; however, Resque_Job_Status::__construct() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
275
		$statusCode = $status->get();
276
		// The Resque job can no longer be found, fallback to the DNDataTransfer.Status
277
		if($statusCode === false) {
278
			// Translate from the DNDataTransfer.Status to the Resque job status for UI purposes
279
			switch($this->Status) {
280
				case 'Finished':
281
					return 'Complete';
282
				case 'Started':
283
					return 'Running';
284
				default:
285
					return $this->Status;
286
			}
287
		}
288
		return self::map_resque_status($statusCode);
289
	}
290
291
}
292