1 //////////////////////////////////////////////////////////////////////////
2 //
3 // pgAdmin III - PostgreSQL Tools
4 //
5 // Copyright (C) 2002 - 2016, The pgAdmin Development Team
6 // This software is released under the PostgreSQL Licence
7 //
8 // pgaJob.h - PostgreSQL Agent Job
9 //
10 //////////////////////////////////////////////////////////////////////////
11 
12 // wxWindows headers
13 #include <wx/wx.h>
14 
15 // App headers
16 #include "pgAdmin3.h"
17 #include "utils/misc.h"
18 #include "frm/frmMain.h"
19 #include "schema/pgObject.h"
20 #include "schema/pgCollection.h"
21 #include "schema/pgDatabase.h"
22 #include "agent/pgaJob.h"
23 #include "agent/pgaStep.h"
24 #include "agent/pgaSchedule.h"
25 
pgaJob(const wxString & newName)26 pgaJob::pgaJob(const wxString &newName)
27 	: pgServerObject(jobFactory, newName)
28 {
29 }
30 
GetTranslatedMessage(int kindOfMessage) const31 wxString pgaJob::GetTranslatedMessage(int kindOfMessage) const
32 {
33 	wxString message = wxEmptyString;
34 
35 	switch (kindOfMessage)
36 	{
37 		case RETRIEVINGDETAILS:
38 			message = _("Retrieving details on pgAgent job");
39 			break;
40 		case REFRESHINGDETAILS:
41 			message = _("Refreshing pgAgent job");
42 			break;
43 		case PROPERTIESREPORT:
44 			message = _("pgAgent job properties report");
45 			break;
46 		case PROPERTIES:
47 			message = _("pgAgent job properties");
48 			break;
49 		case DDLREPORT:
50 			message = _("pgAgent job DDL report");
51 			break;
52 		case DEPENDENCIESREPORT:
53 			message = _("pgAgent job dependencies report");
54 			break;
55 		case DEPENDENCIES:
56 			message = _("pgAgent job dependencies");
57 			break;
58 		case DEPENDENTSREPORT:
59 			message = _("pgAgent job dependents report");
60 			break;
61 		case DEPENDENTS:
62 			message = _("pgAgent job dependents");
63 			break;
64 		case DROPEXCLUDINGDEPS:
65 			message = wxString::Format(_("Are you sure you wish to drop job \"%s\"?"),
66 			                           GetFullIdentifier().c_str());
67 			break;
68 		case DROPTITLE:
69 			message = _("Drop job?");
70 			break;
71 	}
72 
73 	if (!message.IsEmpty() && !(kindOfMessage == DROPEXCLUDINGDEPS || kindOfMessage == DROPTITLE))
74 		message += wxT(" - ") + GetName();
75 
76 	return message;
77 }
78 
GetIconId()79 int pgaJob::GetIconId()
80 {
81 	if (GetEnabled())
82 		return jobFactory.GetIconId();
83 	else
84 		return jobFactory.GetDisabledId();
85 }
86 
87 
GetNewMenu()88 wxMenu *pgaJob::GetNewMenu()
89 {
90 	wxMenu *menu = pgObject::GetNewMenu();
91 	if (1) // check priv.
92 	{
93 		stepFactory.AppendMenu(menu);
94 		scheduleFactory.AppendMenu(menu);
95 	}
96 	return menu;
97 }
98 
99 
DropObject(wxFrame * frame,ctlTree * browser,bool cascaded)100 bool pgaJob::DropObject(wxFrame *frame, ctlTree *browser, bool cascaded)
101 {
102 	return GetConnection()->ExecuteVoid(wxT("DELETE FROM pgagent.pga_job WHERE jobid=") + NumToStr(GetRecId()));
103 }
104 
105 
ShowTreeDetail(ctlTree * browser,frmMain * form,ctlListView * properties,ctlSQLBox * sqlPane)106 void pgaJob::ShowTreeDetail(ctlTree *browser, frmMain *form, ctlListView *properties, ctlSQLBox *sqlPane)
107 {
108 	if (!expandedKids)
109 	{
110 		expandedKids = true;
111 
112 		browser->RemoveDummyChild(this);
113 
114 		// Log
115 		wxLogInfo(wxT("Adding child objects to Job."));
116 
117 		browser->AppendCollection(this, scheduleFactory);
118 		browser->AppendCollection(this, stepFactory);
119 	}
120 
121 	if (properties)
122 	{
123 		CreateListColumns(properties);
124 
125 		properties->AppendItem(_("Name"), GetName());
126 		properties->AppendItem(_("ID"), GetRecId());
127 		properties->AppendYesNoItem(_("Enabled"), GetEnabled());
128 		properties->AppendItem(_("Host agent"), GetHostAgent());
129 		properties->AppendItem(_("Job class"), GetJobclass());
130 		properties->AppendItem(_("Created"), GetCreated());
131 		properties->AppendItem(_("Changed"), GetChanged());
132 		properties->AppendItem(_("Next run"), GetNextrun());
133 		properties->AppendItem(_("Last run"), GetLastrun());
134 		properties->AppendItem(_("Last result"), GetLastresult());
135 		if (!GetCurrentAgent().IsEmpty())
136 			properties->AppendItem(_("Running at"), GetCurrentAgent());
137 		else
138 			properties->AppendItem(_("Running at"), _("Not currently running"));
139 
140 		properties->AppendItem(_("Comment"), firstLineOnly(GetComment()));
141 	}
142 }
143 
144 
145 
Refresh(ctlTree * browser,const wxTreeItemId item)146 pgObject *pgaJob::Refresh(ctlTree *browser, const wxTreeItemId item)
147 {
148 	pgObject *job = 0;
149 
150 	pgObject *obj = browser->GetObject(browser->GetItemParent(item));
151 	if (obj && obj->IsCollection())
152 		job = jobFactory.CreateObjects((pgCollection *)obj, 0, wxT("\n   WHERE j.jobid=") + NumToStr(GetRecId()));
153 
154 	return job;
155 }
156 
157 
158 
CreateObjects(pgCollection * collection,ctlTree * browser,const wxString & restriction)159 pgObject *pgaJobFactory::CreateObjects(pgCollection *collection, ctlTree *browser, const wxString &restriction)
160 {
161 	pgaJob *job = 0;
162 
163 	pgSet *jobs = collection->GetConnection()->ExecuteSet(
164 	                  wxT("SELECT j.*, cl.*, ag.*, sub.jlgstatus AS joblastresult ")
165 	                  wxT("  FROM pgagent.pga_job j JOIN")
166 	                  wxT("  pgagent.pga_jobclass cl ON cl.jclid=jobjclid LEFT OUTER JOIN")
167 	                  wxT("  pgagent.pga_jobagent ag ON ag.jagpid=jobagentid LEFT OUTER JOIN")
168 	                  wxT("  (SELECT DISTINCT ON (jlgjobid) jlgstatus, jlgjobid")
169 	                  wxT("   FROM pgagent.pga_joblog")
170 	                  wxT("  ORDER BY jlgjobid, jlgid desc) sub ON sub.jlgjobid = j.jobid ")
171 	                  + restriction +
172 	                  wxT("ORDER BY jobname;"));
173 
174 	if (jobs)
175 	{
176 		while (!jobs->Eof())
177 		{
178 			wxString status;
179 			if (jobs->GetVal(wxT("joblastresult")) == wxT("r"))
180 				status = _("Running");
181 			else if (jobs->GetVal(wxT("joblastresult")) == wxT("s"))
182 				status = _("Successful");
183 			else if (jobs->GetVal(wxT("joblastresult")) == wxT("f"))
184 				status = _("Failed");
185 			else if (jobs->GetVal(wxT("joblastresult")) == wxT("d"))
186 				status = _("Aborted");
187 			else if (jobs->GetVal(wxT("joblastresult")) == wxT("i"))
188 				status = _("No steps");
189 			else
190 				status = _("Unknown");
191 
192 			job = new pgaJob(jobs->GetVal(wxT("jobname")));
193 			job->iSetServer(collection->GetServer());
194 			job->iSetRecId(jobs->GetLong(wxT("jobid")));
195 			job->iSetComment(jobs->GetVal(wxT("jobdesc")));
196 
197 			job->iSetEnabled(jobs->GetBool(wxT("jobenabled")));
198 			job->iSetJobclass(jobs->GetVal(wxT("jclname")));
199 			job->iSetHostAgent(jobs->GetVal(wxT("jobhostagent")));
200 			job->iSetCreated(jobs->GetDateTime(wxT("jobcreated")));
201 			job->iSetChanged(jobs->GetDateTime(wxT("jobchanged")));
202 			job->iSetNextrun(jobs->GetDateTime(wxT("jobnextrun")));
203 			job->iSetLastrun(jobs->GetDateTime(wxT("joblastrun")));
204 			job->iSetLastresult(status);
205 			job->iSetCurrentAgent(jobs->GetVal(wxT("jagstation")));
206 
207 			if (browser)
208 			{
209 				browser->AppendObject(collection, job);
210 				jobs->MoveNext();
211 			}
212 			else
213 				break;
214 		}
215 
216 		delete jobs;
217 	}
218 	return job;
219 }
220 
ShowStatistics(frmMain * form,ctlListView * statistics)221 void pgaJob::ShowStatistics(frmMain *form, ctlListView *statistics)
222 {
223 	wxString sql =
224 	    wxT("SELECT jlgid")
225 	    wxT(", jlgstatus")
226 	    wxT(", jlgstart")
227 	    wxT(", jlgduration")
228 	    wxT(", (jlgstart + jlgduration) AS endtime")
229 	    wxT("  FROM pgagent.pga_joblog\n")
230 	    wxT(" WHERE jlgjobid = ") + NumToStr(GetRecId()) +
231 	    wxT(" ORDER BY jlgstart DESC") +
232 	    wxT(" LIMIT ") + NumToStr(settings->GetMaxRows());
233 
234 	if (statistics)
235 	{
236 		wxLogInfo(wxT("Displaying statistics for job %s"), GetFullIdentifier().c_str());
237 
238 		// Add the statistics view columns
239 		statistics->ClearAll();
240 		statistics->AddColumn(_("Run"), 50);
241 		statistics->AddColumn(_("Status"), 60);
242 		statistics->AddColumn(_("Start time"), 95);
243 		statistics->AddColumn(_("End time"), 95);
244 		statistics->AddColumn(_("Duration"), 70);
245 
246 		pgSet *stats = GetConnection()->ExecuteSet(sql);
247 		wxString status;
248 		wxDateTime startTime;
249 		wxDateTime endTime;
250 
251 		if (stats)
252 		{
253 			while (!stats->Eof())
254 			{
255 				if (stats->GetVal(1) == wxT("r"))
256 					status = _("Running");
257 				else if (stats->GetVal(1) == wxT("s"))
258 					status = _("Successful");
259 				else if (stats->GetVal(1) == wxT("f"))
260 					status = _("Failed");
261 				else if (stats->GetVal(1) == wxT("d"))
262 					status = _("Aborted");
263 				else if (stats->GetVal(1) == wxT("i"))
264 					status = _("No steps");
265 				else
266 					status = _("Unknown");
267 
268 				startTime.ParseDateTime(stats->GetVal(2));
269 				endTime.ParseDateTime(stats->GetVal(4));
270 
271 				long pos = statistics->AppendItem(stats->GetVal(0), status, startTime.Format());
272 				if (stats->GetVal(4).Length() > 0)
273 					statistics->SetItem(pos, 3, endTime.Format());
274 				statistics->SetItem(pos, 4, stats->GetVal(3));
275 
276 				stats->MoveNext();
277 			}
278 			delete stats;
279 		}
280 	}
281 }
282 
RunNow()283 bool pgaJob::RunNow()
284 {
285 	if (!GetConnection()->ExecuteVoid(wxT("UPDATE pgagent.pga_job SET jobnextrun = now() WHERE jobid=") + NumToStr(GetRecId())))
286 		return false;
287 
288 	return true;
289 }
290 
291 
pgaJobCollection(pgaFactory * factory,pgServer * sv)292 pgaJobCollection::pgaJobCollection(pgaFactory *factory, pgServer *sv)
293 	: pgServerObjCollection(factory, sv)
294 {
295 }
296 
297 
GetTranslatedMessage(int kindOfMessage) const298 wxString pgaJobCollection::GetTranslatedMessage(int kindOfMessage) const
299 {
300 	wxString message = wxEmptyString;
301 
302 	switch (kindOfMessage)
303 	{
304 		case RETRIEVINGDETAILS:
305 			message = _("Retrieving details on pgAgent jobs");
306 			break;
307 		case REFRESHINGDETAILS:
308 			message = _("Refreshing pgAgent jobs");
309 			break;
310 		case OBJECTSLISTREPORT:
311 			message = _("pgAgent jobs list report");
312 			break;
313 	}
314 
315 	return message;
316 }
317 
318 
pgaJobObject(pgaJob * _job,pgaFactory & factory,const wxString & newName)319 pgaJobObject::pgaJobObject(pgaJob *_job, pgaFactory &factory, const wxString &newName)
320 	: pgServerObject(factory, newName)
321 {
322 	job = _job;
323 	server = job->GetServer();
324 }
325 
326 
pgaJobObjCollection(pgaFactory * factory,pgaJob * _job)327 pgaJobObjCollection::pgaJobObjCollection(pgaFactory *factory, pgaJob *_job)
328 	: pgServerObjCollection(factory, _job->GetServer())
329 {
330 	job = _job;
331 }
332 
333 
CanCreate()334 bool pgaJobObjCollection::CanCreate()
335 {
336 	return job->CanCreate();
337 }
338 
339 
CreateCollection(pgObject * obj)340 pgCollection *pgaJobObjFactory::CreateCollection(pgObject *obj)
341 {
342 	return new pgaJobObjCollection(GetCollectionFactory(), (pgaJob *)obj);
343 }
344 
345 
346 /////////////////////////////
347 
348 
349 #include "images/job.pngc"
350 #include "images/jobs.pngc"
351 #include "images/jobdisabled.pngc"
352 
pgaJobFactory()353 pgaJobFactory::pgaJobFactory()
354 	: pgServerObjFactory(__("pgAgent Job"), __("New Job"), __("Create a new Job."), job_png_img)
355 {
356 	metaType = PGM_JOB;
357 	disabledId = addIcon(jobdisabled_png_img);
358 }
359 
360 
CreateCollection(pgObject * obj)361 pgCollection *pgaJobFactory::CreateCollection(pgObject *obj)
362 {
363 	return new pgaJobCollection(GetCollectionFactory(), (pgServer *)obj);
364 }
365 
366 
367 pgaJobFactory jobFactory;
368 static pgaCollectionFactory cf(&jobFactory, __("Jobs"), jobs_png_img);
369 
runNowFactory(menuFactoryList * list,wxMenu * mnu,ctlMenuToolbar * toolbar)370 runNowFactory::runNowFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list)
371 {
372 	mnu->Append(id, _("&Run now"), _("Reschedule the job to run now."));
373 }
374 
375 
StartDialog(frmMain * form,pgObject * obj)376 wxWindow *runNowFactory::StartDialog(frmMain *form, pgObject *obj)
377 {
378 	if (!((pgaJob *)(obj))->RunNow())
379 	{
380 		wxLogError(_("Failed to reschedule the job."));
381 	}
382 
383 	form->Refresh(obj);
384 
385 	return 0;
386 }
387 
388 
CheckEnable(pgObject * obj)389 bool runNowFactory::CheckEnable(pgObject *obj)
390 {
391 	if (obj)
392 	{
393 		if (obj->GetMetaType() == PGM_JOB && !obj->IsCollection())
394 			return true;
395 	}
396 	return false;
397 }
398