2 * Copyright 2000-2010 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package git4idea.history;
18 import com.intellij.lifecycle.AtomicSectionsAware;
19 import com.intellij.openapi.application.PathManager;
20 import com.intellij.openapi.components.ServiceManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.util.Pair;
24 import com.intellij.openapi.util.Ref;
25 import com.intellij.openapi.util.UpdatedReference;
26 import com.intellij.openapi.util.text.StringUtil;
27 import com.intellij.openapi.vcs.ProjectLevelVcsManager;
28 import com.intellij.openapi.vcs.VcsException;
29 import com.intellij.openapi.vcs.VcsListener;
30 import com.intellij.openapi.vcs.changes.ControlledCycle;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.util.Consumer;
33 import com.intellij.util.io.DataExternalizer;
34 import com.intellij.util.io.EnumeratorStringDescriptor;
35 import com.intellij.util.io.PersistentHashMap;
36 import com.intellij.util.io.storage.HeavyProcessLatch;
37 import git4idea.GitVcs;
38 import git4idea.history.browser.ChangesFilter;
39 import git4idea.history.browser.GitCommit;
40 import git4idea.history.browser.LowLevelAccess;
41 import git4idea.history.browser.LowLevelAccessImpl;
42 import org.jetbrains.annotations.Nullable;
44 import java.io.DataInput;
45 import java.io.DataOutput;
47 import java.io.IOException;
50 public class GitUsersComponent {
51 private static final int ourPackSize = 50;
52 private static final int ourBackInterval = 60 * 1000;
53 private static final int ourForwardInterval = 100 * 60 * 1000;
55 private static final Logger LOG = Logger.getInstance("#git4idea.history.GitUsersComponent");
57 private final Object myLock;
58 private PersistentHashMap<String, UsersData> myState;
59 private final Map<VirtualFile, Pair<String, LowLevelAccess>> myAccessMap;
61 // activate-deactivate
62 private volatile boolean myIsActive;
63 private final ControlledCycle myControlledCycle;
64 private final VcsListener myVcsListener;
65 private final GitVcs myVcs;
66 private final ProjectLevelVcsManager myManager;
67 private final File myFile;
69 public GitUsersComponent(final ProjectLevelVcsManager manager) {
70 myVcs = (GitVcs) manager.findVcsByName(GitVcs.getKey().getName());
72 myLock = new Object();
74 final File vcsFile = new File(PathManager.getSystemPath(), "vcs");
75 File file = new File(vcsFile, "git_users");
77 myFile = new File(file, myVcs.getProject().getLocationHash());
79 myAccessMap = new HashMap<VirtualFile, Pair<String, LowLevelAccess>>();
81 // every 10 seconds is ok to check
82 myControlledCycle = new ControlledCycle(myVcs.getProject(), new MyRefresher(), "Git users loader");
83 myVcsListener = new VcsListener() {
84 public void directoryMappingChanged() {
85 final VirtualFile[] currentRoots = myManager.getRootsUnderVcs(myVcs);
86 synchronized (myLock) {
88 for (VirtualFile currentRoot : currentRoots) {
89 myAccessMap.put(currentRoot, new Pair<String, LowLevelAccess>(currentRoot.getPath(),
90 new LowLevelAccessImpl(myVcs.getProject(), currentRoot)));
97 public static GitUsersComponent getInstance(final Project project) {
98 return ServiceManager.getService(project, GitUsersComponent.class);
102 public List<String> getUsersList(final VirtualFile root) {
103 final Pair<String, LowLevelAccess> pair = myAccessMap.get(root);
104 if (pair == null) return null;
106 return new ArrayList<String>(myState.get(pair.getFirst()).getUsers());
108 catch (IOException e) {
114 // singleton update process, only roots can change outside
115 private class MyRefresher implements ControlledCycle.MyCallback {
116 public boolean call(final AtomicSectionsAware atomicSectionsAware) {
117 atomicSectionsAware.checkShouldExit();
120 final HashMap<VirtualFile, Pair<String, LowLevelAccess>> copy;
121 synchronized (myLock) {
122 copy = new HashMap<VirtualFile, Pair<String, LowLevelAccess>>(myAccessMap);
125 final Map<String, UsersData> toUpdate = new HashMap<String, UsersData>();
126 for (Pair<String, LowLevelAccess> pair : copy.values()) {
127 atomicSectionsAware.checkShouldExit();
129 final String key = pair.getFirst();
130 UsersData data = myState.get(key);
132 data = new UsersData();
135 if (data.load(pair.getSecond(), atomicSectionsAware)) {
136 toUpdate.put(key, data);
140 for (String s : toUpdate.keySet()) {
141 myState.put(s, toUpdate.get(s));
143 if (! toUpdate.isEmpty()) {
147 catch (IOException e) {
155 public void activate() {
157 myState = new PersistentHashMap<String, UsersData>(myFile, new EnumeratorStringDescriptor(), createExternalizer());
159 catch (IOException e) {
164 myManager.addVcsListener(myVcsListener);
165 myControlledCycle.start();
168 public void deactivate() {
169 if (myState != null) try {
172 catch (IOException e) {
177 synchronized (myLock) {
180 myManager.removeVcsListener(myVcsListener);
184 private static DataExternalizer<UsersData> createExternalizer() {
185 return new UsersDataExternalizer();
188 private static class UsersData {
189 private UpdatedReference<Long> myCloserDate;
190 private UpdatedReference<Long> myEarlierDate;
191 private final List<String> myUsers;
192 private boolean myForceUpdate;
193 private boolean myStartReached;
195 private UsersData() {
196 myUsers = new LinkedList<String>();
197 final long now = System.currentTimeMillis();
198 myCloserDate = new UpdatedReference<Long>(now);
199 myEarlierDate = new UpdatedReference<Long>(now + 1);
202 public UpdatedReference<Long> getCloserDate() {
206 public void setCloserDate(UpdatedReference<Long> closerDate) {
207 myCloserDate = closerDate;
210 public UpdatedReference<Long> getEarlierDate() {
211 return myEarlierDate;
214 public void setEarlierDate(UpdatedReference<Long> earlierDate) {
215 myEarlierDate = earlierDate;
218 public void forceUpdate() {
219 myForceUpdate = true;
222 public List<String> getUsers() {
226 public boolean isStartReached() {
227 return myStartReached;
230 public void setStartReached(boolean startReached) {
231 myStartReached = startReached;
234 public void addUsers(final Collection<String> users) {
235 myUsers.addAll(users);
238 public boolean load(final LowLevelAccess lowLevelAccess, final AtomicSectionsAware atomicSectionsAware) {
239 if (HeavyProcessLatch.INSTANCE.isRunning()) return false;
241 HeavyProcessLatch.INSTANCE.processStarted();
243 final Set<String> newData = new HashSet<String>();
244 boolean result = false;
245 if (!myStartReached && (myForceUpdate || myEarlierDate.isTimeToUpdate(ourBackInterval))) {
247 lookBack(lowLevelAccess, newData, atomicSectionsAware);
249 if (myForceUpdate || myCloserDate.isTimeToUpdate(ourForwardInterval)) {
251 lookForward(lowLevelAccess, newData, atomicSectionsAware);
253 myForceUpdate = false;
255 result |= ! newData.isEmpty();
259 HeavyProcessLatch.INSTANCE.processFinished();
263 private void putNewData(Set<String> newData) {
264 newData.addAll(myUsers);
266 myUsers.addAll(newData);
267 Collections.sort(myUsers);
270 private void lookForward(LowLevelAccess lowLevelAccess, Set<String> newData, AtomicSectionsAware atomicSectionsAware) {
271 myCloserDate.updateTs();
272 loadImpl(lowLevelAccess, newData, null, new Date(myCloserDate.getT()), atomicSectionsAware);
275 private void lookBack(LowLevelAccess lowLevelAccess, Set<String> newData, AtomicSectionsAware atomicSectionsAware) {
276 myEarlierDate.updateTs();
277 loadImpl(lowLevelAccess, newData, new Date(myEarlierDate.getT()), null, atomicSectionsAware);
280 private void loadImpl(LowLevelAccess lowLevelAccess,
281 final Set<String> newData,
282 @Nullable final Date before,
283 @Nullable final Date after,
284 final AtomicSectionsAware atomicSectionsAware) {
286 final Ref<Long> beforeTick = new Ref<Long>(Long.MAX_VALUE); // min
287 final Ref<Long> afterTick = new Ref<Long>(-1L); // max
288 lowLevelAccess.loadCommits(Collections.<String>emptyList(), before, after, Collections.<ChangesFilter.Filter>emptyList(),
289 new Consumer<GitCommit>() {
290 public void consume(GitCommit gitCommit) {
291 atomicSectionsAware.checkShouldExit();
293 final long time = gitCommit.getDate().getTime();
294 beforeTick.set(Math.min(beforeTick.get(), time));
295 afterTick.set(Math.max(afterTick.get(), time));
297 if (! StringUtil.isEmptyOrSpaces(gitCommit.getAuthor())) {
298 newData.add(gitCommit.getAuthor());
300 if (! StringUtil.isEmptyOrSpaces(gitCommit.getCommitter())) {
301 newData.add(gitCommit.getCommitter());
303 if (gitCommit.getParentsHashes().isEmpty()) {
304 myStartReached = true;
307 }, ourPackSize, Collections.<String>emptyList());
308 if (myCloserDate.getT() < afterTick.get()) {
309 myCloserDate.updateT(afterTick.get());
311 if (myEarlierDate.getT() > beforeTick.get()) {
312 myEarlierDate.updateT(beforeTick.get());
315 catch (VcsException e) {
321 private static class UsersDataExternalizer implements DataExternalizer<UsersData> {
322 public void save(DataOutput out, UsersData value) throws IOException {
323 final UpdatedReference<Long> closer = value.getCloserDate();
324 out.writeLong(closer.getT());
325 out.writeLong(closer.getTime());
327 final UpdatedReference<Long> earlier = value.getEarlierDate();
328 out.writeLong(earlier.getT());
329 out.writeLong(earlier.getTime());
331 final List<String> users = value.getUsers();
332 out.writeInt(users.size());
333 for (String user : users) {
337 out.writeBoolean(value.isStartReached());
340 public UsersData read(DataInput in) throws IOException {
341 final UsersData data = new UsersData();
342 final long closerDate = in.readLong();
343 final long closerUpdate = in.readLong();
344 data.setCloserDate(new UpdatedReference<Long>(closerDate, closerUpdate));
345 final long earlierDate = in.readLong();
346 final long earlierUpdate = in.readLong();
347 data.setEarlierDate(new UpdatedReference<Long>(earlierDate, earlierUpdate));
349 final List<String> users = new LinkedList<String>();
350 final int size = in.readInt();
351 for (int i = 0; i < size; i++) {
352 users.add(in.readUTF());
354 data.addUsers(users);
355 data.setStartReached(in.readBoolean());