1 /*******************************************************************************
2  * Copyright (c) 2008, 2017 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Ericsson AB - (Pascal Rapicault)
14  *     Ericsson AB   (Hamdan Msheik) - Bug 398833
15  *     Red Hat Inc. - Bug 460967
16  *     Mikael Barbero (Eclipse Foundation) - Bug 498116
17  *******************************************************************************/
18 package org.eclipse.equinox.internal.p2.ui.sdk.scheduler;
19 
20 import java.util.Date;
21 import java.util.Random;
22 import org.eclipse.core.runtime.IStatus;
23 import org.eclipse.core.runtime.Status;
24 import org.eclipse.core.runtime.jobs.Job;
25 import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper;
26 import org.eclipse.equinox.internal.p2.garbagecollector.GarbageCollector;
27 import org.eclipse.equinox.internal.p2.ui.sdk.scheduler.migration.MigrationSupport;
28 import org.eclipse.equinox.internal.provisional.p2.updatechecker.*;
29 import org.eclipse.equinox.p2.core.IProvisioningAgent;
30 import org.eclipse.equinox.p2.engine.IProfile;
31 import org.eclipse.equinox.p2.engine.IProfileRegistry;
32 import org.eclipse.equinox.p2.engine.query.IUProfilePropertyQuery;
33 import org.eclipse.equinox.p2.metadata.IInstallableUnit;
34 import org.eclipse.equinox.p2.query.IQuery;
35 import org.eclipse.jface.preference.IPreferenceStore;
36 import org.eclipse.ui.IStartup;
37 import org.eclipse.ui.statushandlers.StatusManager;
38 
39 /**
40  * This plug-in is loaded on startup to register with the update checker.
41  *
42  * @since 3.5
43  */
44 public class AutomaticUpdateScheduler implements IStartup {
45 	public static final String MIGRATION_DIALOG_SHOWN = "migrationDialogShown"; //$NON-NLS-1$
46 
47 	public static final String P_FUZZY_RECURRENCE = "fuzzy_recurrence"; //$NON-NLS-1$
48 
49 	public static final String[] FUZZY_RECURRENCE = { AutomaticUpdateMessages.SchedulerStartup_OnceADay,
50 			AutomaticUpdateMessages.SchedulerStartup_OnceAWeek, AutomaticUpdateMessages.SchedulerStartup_OnceAMonth };
51 
52 	private static final int ONE_HOUR_IN_MS = 60 * 60 * 1000;
53 	private static final int ONE_DAY_IN_MS = 24 * ONE_HOUR_IN_MS;
54 
55 	private IUpdateListener listener = null;
56 	private IUpdateChecker checker = null;
57 
58 	private IProvisioningAgent agent;
59 
60 	@Override
earlyStartup()61 	public void earlyStartup() {
62 		AutomaticUpdatePlugin.getDefault().setScheduler(this);
63 
64 		Job updateJob = Job.create("Update Job", e -> { //$NON-NLS-1$
65 			agent = ServiceHelper.getService(AutomaticUpdatePlugin.getContext(), IProvisioningAgent.class);
66 			IProfileRegistry registry = agent.getService(IProfileRegistry.class);
67 			IProfile currentProfile = registry.getProfile(IProfileRegistry.SELF);
68 			if (currentProfile != null && new MigrationSupport().performMigration(agent, registry, currentProfile)) {
69 				return;
70 			}
71 
72 			removeUnusedPlugins(registry);
73 			scheduleUpdate();
74 		});
75 		updateJob.setSystem(true);
76 		// allow the system to settle
77 		updateJob.schedule(20000);
78 
79 	}
80 
81 	/**
82 	 * Invokes the garbage collector to discard unused plugins, if specified by a
83 	 * corresponding preference.
84 	 *
85 	 */
removeUnusedPlugins(IProfileRegistry registry)86 	private void removeUnusedPlugins(IProfileRegistry registry) {
87 		// check if gc is enabled
88 		IPreferenceStore pref = AutomaticUpdatePlugin.getDefault().getPreferenceStore();
89 		if (!pref.getBoolean(PreferenceConstants.PREF_GC_ON_STARTUP)) {
90 			return;
91 		}
92 		GarbageCollector collector = agent.getService(GarbageCollector.class);
93 		if (collector == null || registry == null) {
94 			return;
95 		}
96 		IProfile profile = registry.getProfile(IProfileRegistry.SELF);
97 		if (profile == null)
98 			return;
99 		collector.runGC(profile);
100 	}
101 
shutdown()102 	public void shutdown() {
103 		removeUpdateListener();
104 	}
105 
rescheduleUpdate()106 	public void rescheduleUpdate() {
107 		removeUpdateListener();
108 		scheduleUpdate();
109 	}
110 
scheduleUpdate()111 	private void scheduleUpdate() {
112 		IPreferenceStore pref = AutomaticUpdatePlugin.getDefault().getPreferenceStore();
113 		// See if automatic search is enabled at all
114 		if (!pref.getBoolean(PreferenceConstants.PREF_AUTO_UPDATE_ENABLED)) {
115 			return;
116 		}
117 		String schedule = pref.getString(PreferenceConstants.PREF_AUTO_UPDATE_SCHEDULE);
118 		long delay = IUpdateChecker.ONE_TIME_CHECK;
119 		long poll = IUpdateChecker.ONE_TIME_CHECK;
120 		if (!schedule.equals(PreferenceConstants.PREF_UPDATE_ON_STARTUP)) {
121 			if (schedule.equals(PreferenceConstants.PREF_UPDATE_ON_FUZZY_SCHEDULE)) {
122 				delay = computeFuzzyDelay(pref);
123 				poll = computeFuzzyPoll(pref);
124 			}
125 		}
126 		// We do not access the AutomaticUpdater directly when we register
127 		// the listener. This prevents the UI classes from being started up
128 		// too soon.
129 		// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=227582
130 		listener = new IUpdateListener() {
131 			@Override
132 			public void updatesAvailable(UpdateEvent event) {
133 				AutomaticUpdatePlugin.getDefault().getAutomaticUpdater().updatesAvailable(event);
134 			}
135 
136 			@Override
137 			public void checkingForUpdates() {
138 				AutomaticUpdatePlugin.getDefault().getAutomaticUpdater().checkingForUpdates();
139 			}
140 		};
141 
142 		checker = agent.getService(IUpdateChecker.class);
143 		if (checker == null) {
144 			// Something did not initialize properly
145 			IStatus status = new Status(IStatus.ERROR, AutomaticUpdatePlugin.PLUGIN_ID,
146 					AutomaticUpdateMessages.AutomaticUpdateScheduler_UpdateNotInitialized);
147 			StatusManager.getManager().handle(status, StatusManager.LOG);
148 			return;
149 		}
150 		checker.addUpdateCheck(IProfileRegistry.SELF, getProfileQuery(), delay, poll, listener);
151 
152 	}
153 
getProfileQuery()154 	private IQuery<IInstallableUnit> getProfileQuery() {
155 		// We specifically avoid using the default policy's root property so that we
156 		// don't load all the
157 		// p2 UI classes in doing so.
158 		return new IUProfilePropertyQuery(IProfile.PROP_PROFILE_ROOT_IU, Boolean.TRUE.toString());
159 	}
160 
computeFuzzyDelay(IPreferenceStore pref)161 	private static long computeFuzzyDelay(IPreferenceStore pref) {
162 		Date nowDate = java.util.Calendar.getInstance().getTime();
163 		long now = nowDate.getTime();
164 		long lastCheckForUpdateSinceEpoch = new LastAutoCheckForUpdateMemo(
165 				AutomaticUpdatePlugin.getDefault().getAgentLocation()).readAndStoreIfAbsent(nowDate).getTime();
166 		long poll = computeFuzzyPoll(pref);
167 		if (now - lastCheckForUpdateSinceEpoch >= poll + getMaxDelay(pref)) {
168 			// Last check for update has exceeded the max delay we allow,
169 			// let's do it sometime in the next hour.
170 			return new Random().nextInt(ONE_HOUR_IN_MS);
171 		}
172 		long delay = now - lastCheckForUpdateSinceEpoch;
173 		// We do delay the next check sometime in the 8 hours after the computed
174 		// schedule
175 		return poll - delay + new Random().nextInt(8 * ONE_HOUR_IN_MS);
176 	}
177 
getMaxDelay(IPreferenceStore pref)178 	private static long getMaxDelay(IPreferenceStore pref) {
179 		String recurrence = pref.getString(P_FUZZY_RECURRENCE);
180 		if (AutomaticUpdateMessages.SchedulerStartup_OnceADay.equals(recurrence)) {
181 			return 6 * ONE_HOUR_IN_MS;
182 		} else if (AutomaticUpdateMessages.SchedulerStartup_OnceAWeek.equals(recurrence)) {
183 			return 2 * ONE_DAY_IN_MS;
184 		} else { // Once a month
185 			return 6 * ONE_DAY_IN_MS;
186 		}
187 	}
188 
computeFuzzyPoll(IPreferenceStore pref)189 	private static long computeFuzzyPoll(IPreferenceStore pref) {
190 		String recurrence = pref.getString(P_FUZZY_RECURRENCE);
191 		if (AutomaticUpdateMessages.SchedulerStartup_OnceADay.equals(recurrence)) {
192 			return ONE_DAY_IN_MS;
193 		} else if (AutomaticUpdateMessages.SchedulerStartup_OnceAWeek.equals(recurrence)) {
194 			return 7 * ONE_DAY_IN_MS;
195 		} else { // Once a month
196 			// It's not rocket science we're doing here,
197 			// let's approximate that a month is always 30 days long
198 			return 30 * ONE_DAY_IN_MS;
199 		}
200 	}
201 
removeUpdateListener()202 	private void removeUpdateListener() {
203 		// Remove the current listener if there is one
204 		if (listener != null && checker != null) {
205 			checker.removeUpdateCheck(listener);
206 			listener = null;
207 		}
208 	}
209 }
210