|
Lines 12-32
Link Here
|
| 12 |
|
12 |
|
| 13 |
import java.io.File; |
13 |
import java.io.File; |
| 14 |
import java.io.InputStream; |
14 |
import java.io.InputStream; |
| 15 |
import java.util.*; |
15 |
import java.util.ArrayList; |
| 16 |
import org.eclipse.core.internal.indexing.*; |
16 |
import java.util.Date; |
|
|
17 |
import java.util.HashSet; |
| 18 |
import java.util.Iterator; |
| 19 |
import java.util.LinkedList; |
| 20 |
import java.util.List; |
| 21 |
import java.util.Set; |
| 22 |
|
| 23 |
import org.eclipse.core.internal.indexing.IndexCursor; |
| 24 |
import org.eclipse.core.internal.indexing.IndexedStoreException; |
| 25 |
import org.eclipse.core.internal.indexing.ObjectID; |
| 17 |
import org.eclipse.core.internal.properties.IndexedStoreWrapper; |
26 |
import org.eclipse.core.internal.properties.IndexedStoreWrapper; |
| 18 |
import org.eclipse.core.internal.resources.*; |
27 |
import org.eclipse.core.internal.resources.FileState; |
| 19 |
import org.eclipse.core.internal.utils.*; |
28 |
import org.eclipse.core.internal.resources.ICoreConstants; |
| 20 |
import org.eclipse.core.resources.*; |
29 |
import org.eclipse.core.internal.resources.ResourceException; |
| 21 |
import org.eclipse.core.runtime.*; |
30 |
import org.eclipse.core.internal.resources.ResourceStatus; |
|
|
31 |
import org.eclipse.core.internal.resources.Workspace; |
| 32 |
import org.eclipse.core.internal.resources.WorkspaceDescription; |
| 33 |
import org.eclipse.core.internal.utils.Convert; |
| 34 |
import org.eclipse.core.internal.utils.Policy; |
| 35 |
import org.eclipse.core.internal.utils.UniversalUniqueIdentifier; |
| 36 |
import org.eclipse.core.resources.Checkpoint; |
| 37 |
import org.eclipse.core.resources.IContainer; |
| 38 |
import org.eclipse.core.resources.IFile; |
| 39 |
import org.eclipse.core.resources.IFileState; |
| 40 |
import org.eclipse.core.resources.IResource; |
| 41 |
import org.eclipse.core.resources.IResourceStatus; |
| 42 |
import org.eclipse.core.resources.IResourceVisitor; |
| 43 |
import org.eclipse.core.resources.IWorkspaceDescription; |
| 44 |
import org.eclipse.core.resources.ResourcesPlugin; |
| 45 |
import org.eclipse.core.runtime.CoreException; |
| 46 |
import org.eclipse.core.runtime.IPath; |
| 47 |
import org.eclipse.core.runtime.IProgressMonitor; |
| 48 |
import org.eclipse.core.runtime.Path; |
| 22 |
|
49 |
|
|
|
50 |
/* |
| 51 |
* HistoryStore |
| 52 |
* |
| 53 |
* Maintain Local History for the workspace. |
| 54 |
* <p> |
| 55 |
* Checkpoint notes: |
| 56 |
* </p><p> |
| 57 |
* Checkpoint garbage collection strategy is to remove all checkoints |
| 58 |
* that would be invalidated by the removal of a local history entry, |
| 59 |
* except in the case where a Project is deleted. Local History is |
| 60 |
* deleted whenever a project is deleted, which could easily cause us |
| 61 |
* to lose the entire set of checkpoints for other projects in the |
| 62 |
* workspace. That would be too limiting. Normal garbage collection of |
| 63 |
* local history entries due to expiration or limit counts will cause |
| 64 |
* all affected checkpoints to be removed. |
| 65 |
* </p> |
| 66 |
*/ |
| 23 |
public class HistoryStore { |
67 |
public class HistoryStore { |
| 24 |
protected Workspace workspace; |
68 |
protected Workspace workspace; |
| 25 |
protected IPath location; |
69 |
protected IPath location; |
| 26 |
protected BlobStore blobStore; |
70 |
protected BlobStore blobStore; |
| 27 |
private IndexedStoreWrapper store; |
71 |
private IndexedStoreWrapper store; |
| 28 |
private final static String INDEX_FILE = ".index"; //$NON-NLS-1$ |
72 |
private final static String INDEX_FILE = ".index"; //$NON-NLS-1$ |
| 29 |
|
73 |
private final static String CHECKPOINT_FILE = ".checkpoints.txt"; //$NON-NLS-1$ |
|
|
74 |
private CheckpointFileStore fCheckpointStore; |
| 30 |
//flag used inside stateAlreadyExists to prevent creating an array |
75 |
//flag used inside stateAlreadyExists to prevent creating an array |
| 31 |
protected boolean stateAlreadyExists; |
76 |
protected boolean stateAlreadyExists; |
| 32 |
|
77 |
|
|
Lines 34-39
Link Here
|
| 34 |
this.workspace = workspace; |
79 |
this.workspace = workspace; |
| 35 |
blobStore = new BlobStore(location, limit); |
80 |
blobStore = new BlobStore(location, limit); |
| 36 |
store = new IndexedStoreWrapper(location.append(INDEX_FILE)); |
81 |
store = new IndexedStoreWrapper(location.append(INDEX_FILE)); |
|
|
82 |
fCheckpointStore = new CheckpointFileStore(location.append(CHECKPOINT_FILE)); |
| 83 |
initCheckpointStore(); |
| 84 |
} |
| 85 |
|
| 86 |
private void initCheckpointStore() { |
| 87 |
long time = System.currentTimeMillis(); |
| 88 |
Date date = new Date(time); |
| 89 |
fCheckpointStore.addCheckpoint("session startup "+date.toString()); |
| 37 |
} |
90 |
} |
| 38 |
/** |
91 |
/** |
| 39 |
* Searches indexed store for key, and invokes visitor's defined behaviour on key matches. |
92 |
* Searches indexed store for key, and invokes visitor's defined behaviour on key matches. |
|
Lines 181-187
Link Here
|
| 181 |
HistoryStoreEntry entry = HistoryStoreEntry.create(store, cursor); |
234 |
HistoryStoreEntry entry = HistoryStoreEntry.create(store, cursor); |
| 182 |
// is it old? |
235 |
// is it old? |
| 183 |
if (entry.getLastModified() < minimumTimestamp) { |
236 |
if (entry.getLastModified() < minimumTimestamp) { |
| 184 |
remove(entry); |
237 |
remove(entry, true); |
| 185 |
continue; |
238 |
continue; |
| 186 |
} |
239 |
} |
| 187 |
if (!entry.getPath().equals(current)) { |
240 |
if (!entry.getPath().equals(current)) { |
|
Lines 197-202
Link Here
|
| 197 |
cursor.close(); |
250 |
cursor.close(); |
| 198 |
store.commit(); |
251 |
store.commit(); |
| 199 |
removeGarbage(blobs); |
252 |
removeGarbage(blobs); |
|
|
253 |
// clean up the Checkpoint store as well. |
| 254 |
fCheckpointStore.clean(minimumTimestamp); |
| 200 |
} catch (Exception e) { |
255 |
} catch (Exception e) { |
| 201 |
String message = Policy.bind("history.problemsCleaning"); //$NON-NLS-1$ |
256 |
String message = Policy.bind("history.problemsCleaning"); //$NON-NLS-1$ |
| 202 |
ResourceStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, null, message, e); |
257 |
ResourceStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, null, message, e); |
|
Lines 378-392
Link Here
|
| 378 |
", max: " + description.getMaxFileStateSize()); //$NON-NLS-1$ |
433 |
", max: " + description.getMaxFileStateSize()); //$NON-NLS-1$ |
| 379 |
return result; |
434 |
return result; |
| 380 |
} |
435 |
} |
| 381 |
protected void remove(HistoryStoreEntry entry) throws IndexedStoreException { |
436 |
protected void remove(HistoryStoreEntry entry, boolean cleanCheckpoints) throws IndexedStoreException { |
| 382 |
// Do not remove the blob yet. It may be referenced by another |
437 |
// Do not remove the blob yet. It may be referenced by another |
| 383 |
// history store entry. |
438 |
// history store entry. |
| 384 |
entry.remove(); |
439 |
entry.remove(); |
|
|
440 |
// collect any corresponding checkpoints that would be invalidated by |
| 441 |
// the removal of this entry |
| 442 |
if (cleanCheckpoints) { |
| 443 |
fCheckpointStore.clean(entry.getLastModified()); |
| 444 |
} |
| 385 |
} |
445 |
} |
| 386 |
/** |
446 |
/** |
| 387 |
* Removes all file states from this store. |
447 |
* Removes all file states from this store. |
| 388 |
*/ |
448 |
*/ |
| 389 |
public void removeAll() { |
449 |
public void removeAll() { |
|
|
450 |
// GC the checkpoints en mass so that calls to clean() are fast. |
| 451 |
fCheckpointStore.cleanAll(); |
| 390 |
// XXX: should implement a method with a better performance |
452 |
// XXX: should implement a method with a better performance |
| 391 |
removeAll(workspace.getRoot()); |
453 |
removeAll(workspace.getRoot()); |
| 392 |
} |
454 |
} |
|
Lines 397-403
Link Here
|
| 397 |
cursor.find(key); |
459 |
cursor.find(key); |
| 398 |
while (cursor.keyMatches(key)) { |
460 |
while (cursor.keyMatches(key)) { |
| 399 |
HistoryStoreEntry entry = HistoryStoreEntry.create(store, cursor); |
461 |
HistoryStoreEntry entry = HistoryStoreEntry.create(store, cursor); |
| 400 |
remove(entry); |
462 |
remove(entry, false); |
| 401 |
} |
463 |
} |
| 402 |
cursor.close(); |
464 |
cursor.close(); |
| 403 |
store.commit(); |
465 |
store.commit(); |
|
Lines 439-446
Link Here
|
| 439 |
if (entries.size() <= maxEntries) |
501 |
if (entries.size() <= maxEntries) |
| 440 |
return; |
502 |
return; |
| 441 |
int limit = entries.size() - maxEntries; |
503 |
int limit = entries.size() - maxEntries; |
| 442 |
for (int i = 0; i < limit; i++) |
504 |
for (int i = 0; i < limit; i++) { |
| 443 |
remove((HistoryStoreEntry) entries.get(i)); |
505 |
HistoryStoreEntry entry = (HistoryStoreEntry) entries.get(i); |
|
|
506 |
remove(entry, true); |
| 507 |
} |
| 444 |
} |
508 |
} |
| 445 |
public void shutdown(IProgressMonitor monitor) { |
509 |
public void shutdown(IProgressMonitor monitor) { |
| 446 |
if (store == null) |
510 |
if (store == null) |
|
Lines 451-456
Link Here
|
| 451 |
} |
515 |
} |
| 452 |
protected void resetIndexedStore() { |
516 |
protected void resetIndexedStore() { |
| 453 |
store.reset(); |
517 |
store.reset(); |
|
|
518 |
fCheckpointStore.reset(); |
| 454 |
IPath location = workspace.getMetaArea().getHistoryStoreLocation(); |
519 |
IPath location = workspace.getMetaArea().getHistoryStoreLocation(); |
| 455 |
java.io.File target = location.toFile(); |
520 |
java.io.File target = location.toFile(); |
| 456 |
Workspace.clear(target); |
521 |
Workspace.clear(target); |
|
Lines 501-505
Link Here
|
| 501 |
accept(path, new PathCollector(), true); |
566 |
accept(path, new PathCollector(), true); |
| 502 |
return allFiles; |
567 |
return allFiles; |
| 503 |
} |
568 |
} |
|
|
569 |
/** |
| 570 |
* Add a new Checkpoint to the local history |
| 571 |
*/ |
| 572 |
public void addCheckpoint(Checkpoint checkpoint) { |
| 573 |
fCheckpointStore.addCheckpoint(checkpoint); |
| 574 |
} |
| 575 |
|
| 576 |
/** |
| 577 |
* Get the complete list of valid Checkpoints. |
| 578 |
* Checkpoints for which there are no Local History States are not returned. |
| 579 |
* The returned list is a deep copy, so any changes you make to the list or |
| 580 |
* to the elements have no effect on the Local History or Checkpoint Store. |
| 581 |
* @return List of <code>Checkpoint</code> objects |
| 582 |
*/ |
| 583 |
public List getCheckpoints() { |
| 584 |
return fCheckpointStore.getCheckpointsClone(); |
| 585 |
} |
| 586 |
/** |
| 587 |
* Return the resources needed to rollback the workspace to the specified time. |
| 588 |
* @param rollbackTime time to roll the workspace back to. |
| 589 |
* @return Set[0] is a set of type <code>IFileState</code> that should be restored. |
| 590 |
* Set[1] is a set of type <code>IFile</code> that should be deleted. |
| 591 |
*/ |
| 592 |
public Set[] getRestoreAndDeleteFallbackStates (long rollbackTime) { |
| 593 |
Set sets[] = {null, null}; |
| 594 |
Set oldFileStates = getRollbackFileStates(rollbackTime); |
| 595 |
Set modifiedSinceFiles = getFilesModifiedSince(rollbackTime); |
| 596 |
Set restoreStates = new HashSet(); |
| 597 |
Set deleteStates = new HashSet(); |
| 598 |
|
| 599 |
// restore files are those files modified just previous to and |
| 600 |
// not after the rollback time. |
| 601 |
Iterator states = oldFileStates.iterator(); |
| 602 |
while (states.hasNext()) { |
| 603 |
IFileState state = (IFileState) states.next(); |
| 604 |
if (containsState(state, modifiedSinceFiles)) { |
| 605 |
restoreStates.add(state); |
| 606 |
} |
| 607 |
} |
| 608 |
// deleted files are those files modified since the rollback time, but |
| 609 |
// not appearing in local history. They must have been created since |
| 610 |
// the rollback time. |
| 611 |
Iterator files = modifiedSinceFiles.iterator(); |
| 612 |
while (files.hasNext()) { |
| 613 |
IFile file = (IFile) files.next(); |
| 614 |
if (!containsFile(file, oldFileStates)) { |
| 615 |
deleteStates.add(file); |
| 616 |
} |
| 617 |
} |
| 618 |
sets[0] = restoreStates; |
| 619 |
sets[1] = deleteStates; |
| 620 |
return sets; |
| 621 |
} |
| 622 |
|
| 623 |
private boolean containsFile(IFile file, Set stateSet) { |
| 624 |
String filePath = file.getFullPath().toString(); |
| 625 |
|
| 626 |
Iterator iter = stateSet.iterator(); |
| 627 |
while (iter.hasNext()) { |
| 628 |
IFileState state = (IFileState) iter.next(); |
| 629 |
if (filePath.equals(state.getFullPath().toString())) { |
| 630 |
return true; |
| 631 |
} |
| 632 |
} |
| 633 |
return false; |
| 634 |
} |
| 635 |
|
| 636 |
private boolean containsState(IFileState state, Set fileSet) { |
| 637 |
String statePath = state.getFullPath().toString(); |
| 638 |
|
| 639 |
Iterator iter = fileSet.iterator(); |
| 640 |
while (iter.hasNext()) { |
| 641 |
IFile file = (IFile) iter.next(); |
| 642 |
if (statePath.equals(file.getFullPath().toString())) { |
| 643 |
return true; |
| 644 |
} |
| 645 |
} |
| 646 |
return false; |
| 647 |
} |
| 648 |
|
| 649 |
/** |
| 650 |
* Return the files from the workspace that are new since the last |
| 651 |
* modification time. |
| 652 |
* |
| 653 |
* @param lastModified return files newer than this date |
| 654 |
* @return the set of newer files from the workspace. Element |
| 655 |
* type is <code>IFile</code>. |
| 656 |
*/ |
| 657 |
private Set getFilesModifiedSince(final long lastModified) { |
| 658 |
final Set set = new HashSet(); |
| 659 |
IResource root = workspace.getRoot(); |
| 660 |
IResourceVisitor visitor = new IResourceVisitor() { |
| 661 |
public boolean visit(IResource resource) { |
| 662 |
// boolean isLinked = resource.isLinked(); |
| 663 |
if (resource.getType() == IResource.FILE) { |
| 664 |
IFile file = (IFile) resource; |
| 665 |
if (file.getLocalTimeStamp() >= lastModified) { |
| 666 |
set.add(file); |
| 667 |
} |
| 668 |
} |
| 669 |
return true; |
| 670 |
}}; |
| 671 |
try { |
| 672 |
root.accept(visitor, IResource.DEPTH_INFINITE, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS); |
| 673 |
} catch (CoreException e) { |
| 674 |
// No exception should be thrown. |
| 675 |
} |
| 676 |
return set; |
| 677 |
} |
| 504 |
|
678 |
|
|
|
679 |
/** |
| 680 |
* Examine the last modification times of all file |
| 681 |
* states stored in the local history and return the set of file states that are |
| 682 |
* present which have modifications nearest and previous to the rollback time. |
| 683 |
* |
| 684 |
* @param rollbackTime |
| 685 |
* @return Set the set of all file states that should be restored for this target rollback time. |
| 686 |
* (element type is IFileState). |
| 687 |
*/ |
| 688 |
private Set getRollbackFileStates(long rollbackTime) { |
| 689 |
// collect all files in the space most proximal to, and younger than <code>lastModified</code> |
| 690 |
Set rollbackStates = new HashSet(); |
| 691 |
|
| 692 |
Set set = allFiles(new Path("/"), IResource.DEPTH_INFINITE); //$NON-NLS-1$ |
| 693 |
Iterator paths = set.iterator(); |
| 694 |
|
| 695 |
// for all file paths, add the one state needed for rollback |
| 696 |
while (paths.hasNext()) { |
| 697 |
IPath path = (IPath) paths.next(); |
| 698 |
IFileState[] states = getStates(path); |
| 699 |
boolean stateFound = false; |
| 700 |
|
| 701 |
// select the state that occurs just before the rollbackTime, maybe none, by |
| 702 |
// looking through all stored states for this path and selecting the one that has |
| 703 |
// a lastmodification time just before the rollback time. If there is none, then |
| 704 |
// this file is not needed for rollback. |
| 705 |
// |
| 706 |
// states must be sorted from youngest to oldest for this implementation, |
| 707 |
// which currently is true using getStates(). |
| 708 |
|
| 709 |
for (int i=0; i<states.length; i++) { |
| 710 |
IFileState state = states[i]; |
| 711 |
if (!stateFound && state.getModificationTime() < rollbackTime) { |
| 712 |
stateFound = true; |
| 713 |
rollbackStates.add(state); |
| 714 |
continue; |
| 715 |
} |
| 716 |
} |
| 717 |
} |
| 718 |
return rollbackStates; |
| 719 |
} |
| 505 |
} |
720 |
} |