KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > team > internal > ccvs > core > resources > EclipseSynchronizer


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  * Matt McCutchen <hashproduct+eclipse@gmail.com> - Bug 181546 [Sync Info] Eclipse writes Entries-less metadata in recreated pruned dir
11  *******************************************************************************/

12 package org.eclipse.team.internal.ccvs.core.resources;
13
14 import java.util.ArrayList JavaDoc;
15 import java.util.Arrays JavaDoc;
16 import java.util.HashMap JavaDoc;
17 import java.util.HashSet JavaDoc;
18 import java.util.Iterator JavaDoc;
19 import java.util.List JavaDoc;
20 import java.util.Map JavaDoc;
21 import java.util.Set JavaDoc;
22
23 import org.eclipse.core.resources.*;
24 import org.eclipse.core.runtime.*;
25 import org.eclipse.core.runtime.jobs.*;
26 import org.eclipse.osgi.util.NLS;
27 import org.eclipse.team.core.TeamException;
28 import org.eclipse.team.internal.ccvs.core.*;
29 import org.eclipse.team.internal.ccvs.core.syncinfo.*;
30 import org.eclipse.team.internal.ccvs.core.syncinfo.ReentrantLock.CVSThreadInfo;
31 import org.eclipse.team.internal.ccvs.core.util.*;
32 import org.eclipse.team.internal.core.subscribers.BatchingLock.IFlushOperation;
33 import org.eclipse.team.internal.core.subscribers.BatchingLock.ThreadInfo;
34 import org.osgi.framework.Bundle;
35
36 /**
37  * A synchronizer is responsible for managing synchronization information for local
38  * CVS resources.
39  *
40  * This class is thread safe but only allows one thread to modify the cache at a time. It
41  * doesn't support fine grain locking on a resource basis. Lock ordering between the workspace
42  * lock and the synchronizer lock is guaranteed to be deterministic. That is, the workspace
43  * lock is *always* acquired before the synchronizer lock. This protects against possible
44  * deadlock cases where the synchronizer lock is acquired before a workspace lock.
45  *
46  * Special processing has been added for linked folders and their childen so
47  * that their CVS meta files are never read or written.
48  *
49  * IMPORTANT NOTICE: It is the responsibility of the clients of EclipseSynchronizer
50  * to ensure that they have wrapped operations that may modify the workspace in
51  * an IWorkspaceRunnable. If this is not done, deltas may fore at inopertune times
52  * and corrupt the sync info. The wrapping could be done within the synchronizer
53  * itself but would require the creation of an inner class for each case that requires
54  * it.
55  *
56  * @see ResourceSyncInfo
57  * @see FolderSyncInfo
58  */

59 public class EclipseSynchronizer implements IFlushOperation {
60     private static final String JavaDoc IS_DIRTY_INDICATOR = SyncInfoCache.IS_DIRTY_INDICATOR;
61     private static final String JavaDoc NOT_DIRTY_INDICATOR = SyncInfoCache.NOT_DIRTY_INDICATOR;
62     private static final String JavaDoc RECOMPUTE_INDICATOR = SyncInfoCache.RECOMPUTE_INDICATOR;
63         
64     // the cvs eclipse synchronizer is a singleton
65
private static EclipseSynchronizer instance;
66     
67     // track resources that have changed in a given operation
68
private ILock lock = Job.getJobManager().newLock();
69     private ReentrantLock resourceLock = new ReentrantLock();
70     
71     private SynchronizerSyncInfoCache synchronizerCache = new SynchronizerSyncInfoCache();
72     private SessionPropertySyncInfoCache sessionPropertyCache = new SessionPropertySyncInfoCache(synchronizerCache);
73     
74     /*
75      * Package private constructor to allow specialized subclass for handling folder deletions
76      */

77     EclipseSynchronizer() {
78     }
79     
80     /**
81      * Returns the singleton instance of the synchronizer.
82      */

83     public static EclipseSynchronizer getInstance() {
84         if(instance==null) {
85             instance = new EclipseSynchronizer();
86         }
87         return instance;
88     }
89     
90     public SyncInfoCache getSyncInfoCacheFor(IResource resource) {
91         if (resource.exists() && resource.isLocal(IResource.DEPTH_ZERO)) {
92             return sessionPropertyCache;
93         } else {
94             return synchronizerCache;
95         }
96     }
97
98     private boolean isValid(IResource resource) {
99         return resource.exists() || synchronizerCache.isPhantom(resource);
100     }
101     
102     /**
103      * Sets the folder sync info for the specified folder.
104      * The folder must exist and must not be the workspace root.
105      *
106      * @param folder the folder
107      * @param info the folder sync info, must not be null
108      * @see #getFolderSync, #deleteFolderSync
109      */

110     public void setFolderSync(IContainer folder, FolderSyncInfo info) throws CVSException {
111         Assert.isNotNull(info); // enforce the use of deleteFolderSync
112
// ignore folder sync on the root (i.e. CVSROOT/config/TopLevelAdmin=yes but we just ignore it)
113
if (folder.getType() == IResource.ROOT) return;
114         if (!isValid(folder)) {
115             // This means that the folder doesn't exist and is not a phantom
116
// Allow the set if the parent is a CVS folder since
117
// this can occur when creating phantom folders
118
if (getFolderSync(folder.getParent()) == null) {
119                 IStatus status = new CVSStatus(IStatus.ERROR, TeamException.UNABLE,
120                         NLS.bind(CVSMessages.EclipseSynchronizer_ErrorSettingFolderSync, new String JavaDoc[] { folder.getFullPath().toString() }),folder);
121                 throw new CVSException(status);
122             }
123         }
124         ISchedulingRule rule = null;
125         try {
126             rule = beginBatching(folder, null);
127             try {
128                 beginOperation();
129                 // get the old info
130
FolderSyncInfo oldInfo = getFolderSync(folder);
131                 // set folder sync and notify
132
getSyncInfoCacheFor(folder).setCachedFolderSync(folder, info, true);
133                 // if the sync info changed from null, we may need to adjust the ancestors
134
if (oldInfo == null) {
135                     adjustDirtyStateRecursively(folder, RECOMPUTE_INDICATOR);
136                 }
137                 folderChanged(folder);
138             } finally {
139                 endOperation();
140             }
141         } finally {
142             if (rule != null) endBatching(rule, null);
143         }
144     }
145     
146     /**
147      * Gets the folder sync info for the specified folder.
148      *
149      * @param folder the folder
150      * @return the folder sync info associated with the folder, or null if none.
151      * @see #setFolderSync, #deleteFolderSync
152      */

153     public FolderSyncInfo getFolderSync(IContainer folder) throws CVSException {
154         if (folder.getType() == IResource.ROOT || !isValid(folder)) return null;
155         // Do a check outside the lock for any folder sync info
156
FolderSyncInfo info = getSyncInfoCacheFor(folder).getCachedFolderSync(folder, false /* not thread safe */);
157         if (info != null)
158             return info;
159         try {
160             beginOperation();
161             cacheFolderSync(folder);
162             return getSyncInfoCacheFor(folder).getCachedFolderSync(folder, true /* thread safe */);
163         } finally {
164             endOperation();
165         }
166     }
167
168     /**
169      * Deletes the folder sync for the specified folder and the resource sync
170      * for all of its children. Does not recurse.
171      *
172      * @param folder the folder
173      * @see #getFolderSync, #setFolderSync
174      */

175     public void deleteFolderSync(IContainer folder) throws CVSException {
176         if (folder.getType() == IResource.ROOT || !isValid(folder)) return;
177         ISchedulingRule rule = null;
178         try {
179             rule = beginBatching(folder, null);
180             try {
181                 beginOperation();
182                 // iterate over all children with sync info and prepare notifications
183
// this is done first since deleting the folder sync may remove a phantom
184
cacheResourceSyncForChildren(folder, true /* can modify workspace */);
185                 IResource[] children = folder.members(true);
186                 for (int i = 0; i < children.length; i++) {
187                     IResource resource = children[i];
188                     resourceChanged(resource);
189                     // delete resource sync for all children
190
getSyncInfoCacheFor(resource).setCachedSyncBytes(resource, null, true);
191                 }
192                 // delete folder sync
193
getSyncInfoCacheFor(folder).setCachedFolderSync(folder, null, true);
194                 folderChanged(folder);
195             } catch (CoreException e) {
196                 throw CVSException.wrapException(e);
197             } finally {
198                 endOperation();
199             }
200         } finally {
201             if (rule != null) endBatching(rule, null);
202         }
203     }
204
205     private void folderChanged(IContainer folder) {
206         resourceLock.folderChanged(folder);
207     }
208
209     private void resourceChanged(IResource resource) {
210         resourceLock.resourceChanged(resource);
211     }
212
213     /**
214      * Sets the resource sync info for the specified resource.
215      * The parent folder must exist and must not be the workspace root.
216      *
217      * @param resource the resource
218      * @param info the resource sync info, must not be null
219      * @see #getResourceSync, #deleteResourceSync
220      */

221     public void setResourceSync(IResource resource, ResourceSyncInfo info) throws CVSException {
222         Assert.isNotNull(info); // enforce the use of deleteResourceSync
223
IContainer parent = resource.getParent();
224         if (parent == null || parent.getType() == IResource.ROOT || !isValid(parent)) {
225             IStatus status = new CVSStatus(IStatus.ERROR, TeamException.UNABLE,
226                 NLS.bind(CVSMessages.EclipseSynchronizer_ErrorSettingResourceSync, new String JavaDoc[] { resource.getFullPath().toString() }), resource);
227             throw new CVSException(status);
228         }
229         ISchedulingRule rule = null;
230         try {
231             rule = beginBatching(resource, null);
232             try {
233                 beginOperation();
234                 // cache resource sync for siblings, set for self, then notify
235
cacheResourceSyncForChildren(parent, true /* can modify workspace */);
236                 setCachedResourceSync(resource, info);
237                 resourceChanged(resource);
238             } finally {
239                 endOperation();
240             }
241         } finally {
242             if (rule != null) endBatching(rule, null);
243         }
244     }
245     
246     /**
247      * Gets the resource sync info for the specified folder.
248      *
249      * @param resource the resource
250      * @return the resource sync info associated with the resource, or null if none.
251      * @see #setResourceSync, #deleteResourceSync
252      */

253     public ResourceSyncInfo getResourceSync(IResource resource) throws CVSException {
254         byte[] info = getSyncBytes(resource);
255         if (info == null) return null;
256         return new ResourceSyncInfo(info);
257     }
258
259     /**
260      * Gets the resource sync info for the specified folder.
261      *
262      * @param resource the resource
263      * @return the resource sync info associated with the resource, or null if none.
264      * @see #setResourceSync, #deleteResourceSync
265      */

266     public byte[] getSyncBytes(IResource resource) throws CVSException {
267         IContainer parent = resource.getParent();
268         if (parent == null || parent.getType() == IResource.ROOT || !isValid(parent)) return null;
269         // Do a quick check outside the lock to see if there are sync butes for the resource.
270
byte[] info = getSyncInfoCacheFor(resource).getCachedSyncBytes(resource, false /* not thread safe */);
271         if (info != null)
272             return info;
273         try {
274             beginOperation();
275             // cache resource sync for siblings, then return for self
276
try {
277                 cacheResourceSyncForChildren(parent, false /* cannot modify workspace */);
278             } catch (CVSException e) {
279                 if (isCannotModifySynchronizer(e) || isResourceNotFound(e)) {
280                     // We will resort to loading the sync info for the requested resource from disk
281
byte[] bytes = getSyncBytesFromDisk(resource);
282                     if (!resource.exists() && bytes != null && !ResourceSyncInfo.isDeletion(bytes)) {
283                         bytes = ResourceSyncInfo.convertToDeletion(bytes);
284                     }
285                     return bytes;
286                 } else {
287                     throw e;
288                 }
289             }
290             return getCachedSyncBytes(resource);
291         } finally {
292             endOperation();
293         }
294     }
295
296     /**
297      * Sets the resource sync info for the specified resource.
298      * The parent folder must exist and must not be the workspace root.
299      *
300      * @param resource the resource
301      * @param info the resource sync info, must not be null
302      * @see #getResourceSync, #deleteResourceSync
303      */

304     public void setSyncBytes(IResource resource, byte[] syncBytes) throws CVSException {
305         Assert.isNotNull(syncBytes); // enforce the use of deleteResourceSync
306
IContainer parent = resource.getParent();
307         if (parent == null || parent.getType() == IResource.ROOT || !isValid(parent)) {
308             IStatus status = new CVSStatus(IStatus.ERROR, TeamException.UNABLE,
309                 NLS.bind(CVSMessages.EclipseSynchronizer_ErrorSettingResourceSync, new String JavaDoc[] { resource.getFullPath().toString() }),resource);
310             throw new CVSException(status);
311         }
312         ISchedulingRule rule = null;
313         try {
314             rule = beginBatching(resource, null);
315             try {
316                 beginOperation();
317                 // cache resource sync for siblings, set for self, then notify
318
cacheResourceSyncForChildren(parent, true /* can modify workspace */);
319                 setCachedSyncBytes(resource, syncBytes);
320                 resourceChanged(resource);
321             } finally {
322                 endOperation();
323             }
324         } finally {
325             if (rule != null) endBatching(rule, null);
326         }
327     }
328         
329     /**
330      * Deletes the resource sync info for the specified resource, if it exists.
331      *
332      * @param resource the resource
333      * @see #getResourceSync, #setResourceSync
334      */

335     public void deleteResourceSync(IResource resource) throws CVSException {
336         IContainer parent = resource.getParent();
337         if (parent == null || parent.getType() == IResource.ROOT || !isValid(parent)) return;
338         ISchedulingRule rule = null;
339         try {
340             rule = beginBatching(resource, null);
341             try {
342                 beginOperation();
343                 // cache resource sync for siblings, delete for self, then notify
344
cacheResourceSyncForChildren(parent, true /* can modify workspace */);
345                 if (getCachedSyncBytes(resource) != null) { // avoid redundant notifications
346
setCachedSyncBytes(resource, null);
347                     clearDirtyIndicator(resource);
348                     resourceChanged(resource);
349                 }
350             } finally {
351                 endOperation();
352             }
353         } finally {
354             if (rule != null) endBatching(rule, null);
355         }
356     }
357
358     /**
359      * @param resource
360      */

361     private void clearDirtyIndicator(IResource resource) throws CVSException {
362         getSyncInfoCacheFor(resource).flushDirtyCache(resource);
363         adjustDirtyStateRecursively(resource.getParent(), RECOMPUTE_INDICATOR);
364     }
365
366     /**
367      * Gets the array of ignore patterns for the specified folder.
368      *
369      * @param folder the folder
370      * @return the patterns, or an empty array if none
371      * @see #addIgnored
372      */

373     public boolean isIgnored(IResource resource) throws CVSException {
374         if (resource.getType() == IResource.ROOT ||
375             resource.getType() == IResource.PROJECT ||
376             ! resource.exists()) {
377             return false;
378         }
379         IContainer parent = resource.getParent();
380         FileNameMatcher matcher = sessionPropertyCache.getFolderIgnores(parent, false /* not thread safe */);
381         if (matcher == null) {
382             try {
383                 beginOperation();
384                 matcher = cacheFolderIgnores(parent);
385             } finally {
386                 endOperation();
387             }
388         }
389         return matcher.match(resource.getName());
390     }
391     
392     /**
393      * Adds a pattern to the set of ignores for the specified folder.
394      *
395      * @param folder the folder
396      * @param pattern the pattern
397      */

398     public void addIgnored(IContainer folder, String JavaDoc pattern) throws CVSException {
399         if (folder.getType() == IResource.ROOT || ! folder.exists()) {
400             IStatus status = new CVSStatus(IStatus.ERROR, TeamException.UNABLE,
401                 NLS.bind(CVSMessages.EclipseSynchronizer_ErrorSettingIgnorePattern, new String JavaDoc[] { folder.getFullPath().toString() }),folder);
402             throw new CVSException(status);
403         }
404         ISchedulingRule rule = null;
405         try {
406             rule = beginBatching(folder.getFile(new Path(SyncFileWriter.IGNORE_FILE)), null);
407             try {
408                 beginOperation();
409                 String JavaDoc[] ignores = SyncFileWriter.readCVSIgnoreEntries(folder);
410                 if (ignores != null) {
411                     // verify that the pattern has not already been added
412
for (int i = 0; i < ignores.length; i++) {
413                         if (ignores[i].equals(pattern)) return;
414                     }
415                     // add the pattern
416
String JavaDoc[] oldIgnores = ignores;
417                     ignores = new String JavaDoc[oldIgnores.length + 1];
418                     System.arraycopy(oldIgnores, 0, ignores, 0, oldIgnores.length);
419                     ignores[oldIgnores.length] = pattern;
420                 } else {
421                     ignores = new String JavaDoc[] { pattern };
422                 }
423                 setCachedFolderIgnores(folder, ignores);
424                 SyncFileWriter.writeCVSIgnoreEntries(folder, ignores);
425                 // broadcast changes to unmanaged children - they are the only candidates for being ignored
426
List JavaDoc possibleIgnores = new ArrayList JavaDoc();
427                 accumulateNonManagedChildren(folder, possibleIgnores);
428                 ResourceStateChangeListeners.getListener().resourceSyncInfoChanged((IResource[])possibleIgnores.toArray(new IResource[possibleIgnores.size()]));
429             } finally {
430                 endOperation();
431             }
432         } finally {
433             if (rule != null) endBatching(rule, null);
434         }
435     }
436     
437     /**
438      * Returns the members of this folder including deleted resources with sync info,
439      * but excluding special resources such as CVS subdirectories.
440      *
441      * @param folder the container to list
442      * @return the array of members
443      */

444     public IResource[] members(IContainer folder) throws CVSException {
445         if (! isValid(folder)) return new IResource[0];
446         try {
447             beginOperation();
448             if (folder.getType() != IResource.ROOT) {
449                 // ensure that the sync info is cached so any required phantoms are created
450
cacheResourceSyncForChildren(folder, false);
451             }
452         } catch (CVSException e) {
453             if (!isCannotModifySynchronizer(e) && !isResourceNotFound(e)) {
454                 throw e;
455             }
456         } finally {
457             endOperation();
458         }
459         try {
460             
461             return synchronizerCache.members(folder);
462             
463         } catch (CoreException e) {
464             throw CVSException.wrapException(e);
465         }
466     }
467     
468     private boolean isCannotModifySynchronizer(CVSException e) {
469         // IResourceStatus.WORKSPACE_LOCKED can occur if the resource sync is loaded
470
// during the POST_CHANGE delta phase.
471
// CVSStatus.FAILED_TO_CACHE_SYNC_INFO can occur if the resource sync is loaded
472
// when no scheduling rule is held.
473
return (e.getStatus().getCode() == IResourceStatus.WORKSPACE_LOCKED
474                 || e.getStatus().getCode() == CVSStatus.FAILED_TO_CACHE_SYNC_INFO);
475     }
476     
477     private boolean isResourceNotFound(CVSException e) {
478         return e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND;
479     }
480     
481     /**
482      * Begins a batch of operations in order to optimize sync file writing.
483      * The provided scheduling rule indicates the resources
484      * that the resources affected by the operation while the returned scheduling rule
485      * is the rule obtained by the lock. It may differ from the provided rule as it must
486      * encompass any sync files that may change as a result of the operation.
487      */

488     public ISchedulingRule beginBatching(ISchedulingRule resourceRule, IProgressMonitor monitor) {
489         return resourceLock.acquire(resourceRule, this /* IFlushOperation */, monitor);
490     }
491     
492     /**
493      * Ends a batch of operations. The provided rule must be the one that was returned
494      * by the corresponding call to beginBatching.
495      * <p>
496      * Progress cancellation is ignored while writting the cache to disk. This
497      * is to ensure cache to disk consistency.
498      * </p>
499      *
500      * @param monitor the progress monitor, may be null
501      * @exception CVSException with a status with code <code>COMMITTING_SYNC_INFO_FAILED</code>
502      * if all the CVS sync information could not be written to disk.
503      */

504     public void endBatching(ISchedulingRule rule, IProgressMonitor monitor) throws CVSException {
505         try {
506             resourceLock.release(rule, monitor);
507         } catch (TeamException e) {
508             throw CVSException.wrapException(e);
509         }
510     }
511     
512     /* (non-Javadoc)
513      *
514      * Callback which is invoked when the batching resource lock is released
515      * or when a flush is requested (see beginBatching(IResource)).
516      *
517      * @see org.eclipse.team.internal.ccvs.core.syncinfo.ReentrantLock.IRunnableOnExit#run(org.eclipse.team.internal.ccvs.core.syncinfo.ReentrantLock.ThreadInfo, org.eclipse.core.runtime.IProgressMonitor)
518      */

519     public void flush(final ThreadInfo info, IProgressMonitor monitor) throws CVSException {
520         if (info != null && !info.isEmpty()) {
521             try {
522                 ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
523                     public void run(IProgressMonitor pm) throws CoreException {
524                         IStatus status = commitCache(info, pm);
525                         if (!status.isOK()) {
526                             throw new CVSException(status);
527                         }
528                     }
529                 }, null, 0 /* no flags */, monitor);
530             } catch (CoreException e) {
531                 throw CVSException.wrapException(e);
532             }
533         }
534     }
535     
536     /*
537      * Begin an access to the internal data structures of the synchronizer
538      */

539     private void beginOperation() {
540         try {
541             // Do not try to acquire the lock if the resources tree is locked
542
// The reason for this is that during the resource delta phase (i.e. when the tree is locked)
543
// the workspace lock is held. If we obtain our lock, there is
544
// a chance of dealock. It is OK if we don't as we are still protected
545
// by scheduling rules and the workspace lock.
546
if (ResourcesPlugin.getWorkspace().isTreeLocked()) return;
547         } catch (RuntimeException JavaDoc e) {
548             // If we are not active, throw a cancel. Otherwise, propogate it.
549
// (see bug 78303)
550
if (Platform.getBundle(CVSProviderPlugin.ID).getState() == Bundle.ACTIVE) {
551                 throw e;
552             } else {
553                 throw new OperationCanceledException();
554             }
555         }
556         lock.acquire();
557     }
558     
559     /*
560      * End an access to the internal data structures of the synchronizer
561      */

562     private void endOperation() {
563         try {
564             // See beginOperation() for a description of why the lock is not obtained when the tree is locked
565
if (ResourcesPlugin.getWorkspace().isTreeLocked()) return;
566         } catch (RuntimeException JavaDoc e) {
567             // If we are not active, throw a cancel. Otherwise, propogate it.
568
// (see bug 78303)
569
if (Platform.getBundle(CVSProviderPlugin.ID).getState() == Bundle.ACTIVE) {
570                 throw e;
571             } else {
572                 throw new OperationCanceledException();
573             }
574         }
575         lock.release();
576     }
577     
578     /**
579      * Flush the sync information from the in-memory cache to disk and purge
580      * the entries from the cache.
581      * <p>
582      * Recursively flushes the sync information for all resources
583      * below the root to disk and purges the entries from memory
584      * so that the next time it is accessed it will be retrieved from disk.
585      * May flush more sync information than strictly needed, but never less.
586      * </p>
587      *
588      * @param root the root of the subtree to purge
589      * @param deep purge sync from child folders
590      * @param monitor the progress monitor, may be null
591      */

592     public void flush(IContainer root, boolean deep, IProgressMonitor monitor) throws CVSException {
593         monitor = Policy.monitorFor(monitor);
594         monitor.beginTask(null, 10);
595         ISchedulingRule rule = null;
596         try {
597             rule = beginBatching(root, Policy.subMonitorFor(monitor, 1));
598             try {
599                 beginOperation();
600                 try {
601                     // Flush changes to disk
602
resourceLock.flush(Policy.subMonitorFor(monitor, 8));
603                 } catch (TeamException e) {
604                     throw CVSException.wrapException(e);
605                 } finally {
606                     // Purge the in-memory cache
607
sessionPropertyCache.purgeCache(root, deep);
608                 }
609             } finally {
610                 endOperation();
611             }
612         } finally {
613             if (rule != null) endBatching(rule, Policy.subMonitorFor(monitor, 1));
614             monitor.done();
615         }
616     }
617
618     public void deconfigure(final IProject project, IProgressMonitor monitor) throws CVSException {
619         monitor = Policy.monitorFor(monitor);
620         monitor.beginTask(null, 100);
621         ISchedulingRule rule = null;
622         try {
623             rule = beginBatching(project, Policy.subMonitorFor(monitor, 10));
624             // Flush the sync info
625
flush(project, true /* deep */, Policy.subMonitorFor(monitor, 80));
626             
627             purgeDirtyCache(project, Policy.subMonitorFor(monitor, 5));
628                 
629             // forget about pruned folders however the top level pruned folder will have resource sync (e.g.
630
// a line in the Entry file). As a result the folder is managed but is not a CVS folder.
631
synchronizerCache.purgeCache(project, true);
632         } finally {
633             if (rule != null) endBatching(rule, Policy.subMonitorFor(monitor, 5));
634             monitor.done();
635         }
636     }
637
638     /**
639      * Called to notify the synchronizer that meta files have changed on disk, outside
640      * of the workbench. The cache will be flushed for this folder and it's immediate
641      * children and appropriate state change events are broadcasts to state change
642      * listeners.
643      */

644     public void ignoreFilesChanged(IContainer[] roots) throws CVSException {
645         for (int i = 0; i < roots.length; i++) {
646             IContainer container = roots[i];
647             if (container.exists()) {
648                 ISchedulingRule rule = null;
649                 try {
650                     Set JavaDoc changed = new HashSet JavaDoc();
651                     rule = beginBatching(container, null);
652                     try {
653                         beginOperation();
654                         
655                         // Record the previous ignore pattterns
656
FileNameMatcher oldIgnores = null;
657                         if (sessionPropertyCache.isFolderSyncInfoCached(container)) {
658                             oldIgnores = cacheFolderIgnores(container);
659                         }
660                         
661                         // Purge the cached state for direct children of the container
662
changed.addAll(Arrays.asList(
663                             sessionPropertyCache.purgeCache(container, oldIgnores == null /*flush deeply if the old patterns are not known*/)));
664                         
665                         // Purge the state for any children of previously ignored containers
666
if (oldIgnores != null) {
667                             FileNameMatcher newIgnores = cacheFolderIgnores(container);
668                             try {
669                                 IResource[] members = container.members();
670                                 for (int j = 0; j < members.length; j++) {
671                                     IResource resource = members[j];
672                                     if (resource.getType() == IResource.FOLDER) {
673                                         String JavaDoc name = resource.getName();
674                                         if (oldIgnores.match(name) && !newIgnores.match(name)) {
675                                             changed.addAll(Arrays.asList(
676                                                     sessionPropertyCache.purgeCache((IContainer)resource, true /*flush deeply*/)));
677                                         }
678                                     }
679                                 }
680                             } catch (CoreException e) {
681                                 // Just log and continue
682
CVSProviderPlugin.log(e);
683                             }
684                         }
685                     } finally {
686                         endOperation();
687                     }
688                     if (!changed.isEmpty()) {
689                         ResourceStateChangeListeners.getListener().resourceSyncInfoChanged(
690                             (IResource[]) changed.toArray(new IResource[changed.size()]));
691                     }
692                 } finally {
693                     if (rule != null) endBatching(rule, null);
694                 }
695             }
696         }
697     }
698     
699     public void syncFilesChangedExternally(IContainer[] changedMetaFiles, IFile[] externalDeletions) throws CVSException {
700         List JavaDoc changed = new ArrayList JavaDoc();
701         for (int i = 0; i < changedMetaFiles.length; i++) {
702             IContainer container = changedMetaFiles[i];
703             if (!isWithinActiveOperationScope(container)) {
704                 changed.addAll(Arrays.asList(
705                     sessionPropertyCache.purgeCache(container, false /*don't flush children*/)));
706             }
707         }
708         for (int i = 0; i < externalDeletions.length; i++) {
709             IFile file = externalDeletions[i];
710             if (!isWithinActiveOperationScope(file)) {
711                 sessionPropertyCache.purgeCache(file.getParent(), false /*don't flush children*/);
712                 changed.add(file);
713             }
714         }
715         if (!changed.isEmpty()) {
716             ResourceStateChangeListeners.getListener().externalSyncInfoChange(
717                     (IResource[]) changed.toArray(new IResource[changed.size()]));
718         }
719     }
720     
721     /*
722      * The resource is about to be deleted by the move delete hook.
723      * In all cases (except when the resource doesn't exist), this method
724      * will indicate that the dirty state of the parent needs to be recomputed.
725      * For managed resources, it will move the cached sync info from the session
726      * property cache into the sycnrhonizer cache, purging the session cache.
727      * @param resource the resource about to be deleted.
728      * <p>
729      * Note taht this method is not recursive. Hence, for managed resources
730      *
731      * @returns whether children need to be prepared
732      * @throws CVSException
733      */

734     /* private */ boolean prepareForDeletion(IResource resource) throws CVSException {
735         if (!resource.exists()) return false;
736         ISchedulingRule rule = null;
737         try {
738             rule = beginBatching(resource, null);
739             try {
740                 beginOperation();
741                 // Flush the dirty info for the resource and it's ancestors.
742
// Although we could be smarter, we need to do this because the
743
// deletion may fail.
744
adjustDirtyStateRecursively(resource, RECOMPUTE_INDICATOR);
745                 if (resource.getType() == IResource.FILE) {
746                     byte[] syncBytes = getSyncBytes(resource);
747                     if (syncBytes != null) {
748                         if (ResourceSyncInfo.isAddition(syncBytes)) {
749                             deleteResourceSync(resource);
750                         } else {
751                             syncBytes = convertToDeletion(syncBytes);
752                             synchronizerCache.setCachedSyncBytes(resource, syncBytes, true);
753                         }
754                         sessionPropertyCache.purgeResourceSyncCache(resource);
755                         resourceChanged(resource);
756                     }
757                     return false;
758                 } else {
759                     IContainer container = (IContainer)resource;
760                     if (container.getType() == IResource.PROJECT) {
761                         synchronizerCache.flush((IProject)container);
762                         return false;
763                     } else {
764                         // Move the folder sync info into phantom space
765
FolderSyncInfo info = getFolderSync(container);
766                         if (info == null) return false;
767                         synchronizerCache.setCachedFolderSync(container, info, true);
768                         folderChanged(container);
769                         // move the resource sync as well
770
byte[] syncBytes = getSyncBytes(resource);
771                         synchronizerCache.setCachedSyncBytes(resource, syncBytes, true);
772                         sessionPropertyCache.purgeResourceSyncCache(container);
773                         sessionPropertyCache.purgeCache(container, false);
774                         return true;
775                     }
776                 }
777             } finally {
778                 endOperation();
779             }
780         } finally {
781             if (rule != null) endBatching(rule, null);
782         }
783     }
784     
785     /**
786      * The resource has been deleted. Make sure any cached state is cleared.
787      * This is needed because the move/delete hook is not invoked in all situations
788      * (e.g. external deletion).
789      *
790      * @param resource
791      * @throws CVSException
792      */

793     protected void handleDeleted(IResource resource) throws CVSException {
794         if (resource.exists()) return;
795         try {
796             beginOperation();
797             adjustDirtyStateRecursively(resource, RECOMPUTE_INDICATOR);
798         } finally {
799             endOperation();
800         }
801     }
802     
803     /**
804      * Prepare for the deletion of the target resource from within
805      * the move/delete hook. The method is invoked by both the
806      * deleteFile/Folder methods and for the source resource
807      * of moveFile/Folder. This method will move the cached sync info
808      * into the phantom (ISynchronizer) cache so that outgoing deletions
809      * and known remote folders are preserved.
810      *
811      * @param resource
812      * @param monitor
813      * @throws CVSException
814      */

815     public void prepareForDeletion(IResource resource, IProgressMonitor monitor) throws CVSException {
816         // Move sync info to phantom space for the resource and all it's children
817
monitor = Policy.monitorFor(monitor);
818         try {
819             beginOperation();
820             monitor.beginTask(null, 100);
821             try {
822                 resource.accept(new IResourceVisitor() {
823                     public boolean visit(IResource innerResource) throws CoreException {
824                         try {
825                             return prepareForDeletion(innerResource);
826                         } catch (CVSException e) {
827                             CVSProviderPlugin.log(e);
828                             throw new CoreException(e.getStatus());
829                         }
830                     }
831                 });
832             } catch (CoreException e) {
833                 throw CVSException.wrapException(e);
834             }
835         } finally {
836             endOperation();
837             monitor.done();
838         }
839     }
840     
841     /**
842      * If not already cached, loads and caches the resource sync for the children of the container.
843      * Folder must exist and must not be the workspace root.
844      *
845      * @param container the container
846      */

847     private void cacheResourceSyncForChildren(IContainer container, boolean canModifyWorkspace) throws CVSException {
848         // don't try to load if the information is already cached
849
if (! getSyncInfoCacheFor(container).isResourceSyncInfoCached(container)) {
850             // load the sync info from disk
851
byte[][] infos;
852             // do not load the sync info for resources that are linked
853
if (isLinkedResource(container)) {
854                 infos = null;
855             } else {
856                 infos = SyncFileWriter.readAllResourceSync(container);
857             }
858             try {
859                 if (infos != null) {
860                     for (int i = 0; i < infos.length; i++) {
861                         byte[] syncBytes = infos[i];
862                         IPath name = new Path(null, getName(syncBytes));
863                         IResource resource;
864                         if (isFolder(syncBytes)) {
865                             resource = container.getFolder(name);
866                         } else {
867                             resource = container.getFile(name);
868                         }
869                         getSyncInfoCacheFor(resource).setCachedSyncBytes(resource, syncBytes, canModifyWorkspace);
870                     }
871                 }
872                 getSyncInfoCacheFor(container).setResourceSyncInfoCached(container);
873             } catch (CVSException e) {
874                 if (Policy.DEBUG_METAFILE_CHANGES) {
875                     System.err.println("Failed to cache Entries for folder " + container.getFullPath()); //$NON-NLS-1$
876
}
877                 throw e;
878             }
879         }
880     }
881     
882     /**
883      * If not already cached, loads and caches the folder sync for the
884      * container. Folder must exist and must not be the workspace root.
885      *
886      * @param container the container
887      */

888     private void cacheFolderSync(IContainer container) throws CVSException {
889         // don't try to load if the information is already cached
890
if (! getSyncInfoCacheFor(container).isFolderSyncInfoCached(container)) {
891             // load the sync info from disk
892
FolderSyncInfo info;
893             // do not load the sync info for resources that are linked
894
if (isLinkedResource(container)) {
895                 info = null;
896             } else {
897                 info = SyncFileWriter.readFolderSync(container);
898             }
899             getSyncInfoCacheFor(container).setCachedFolderSync(container, info, false);
900         }
901     }
902     
903     private boolean isLinkedResource(IResource resource) {
904         return CVSWorkspaceRoot.isLinkedResource(resource);
905     }
906
907     /**
908      * Load the sync info for the given resource from disk
909      * @param resource
910      * @return byte[]
911      */

912     private byte[] getSyncBytesFromDisk(IResource resource) throws CVSException {
913         byte[][] infos = SyncFileWriter.readAllResourceSync(resource.getParent());
914         if (infos == null) return null;
915         for (int i = 0; i < infos.length; i++) {
916             byte[] syncBytes = infos[i];
917             if (resource.getName().equals(getName(syncBytes))) {
918                 return syncBytes;
919             }
920         }
921         return null;
922     }
923     
924     /**
925      * Commits the cache after a series of operations.
926      *
927      * Will return STATUS_OK unless there were problems writting sync
928      * information to disk. If an error occurs a multistatus is returned
929      * with the list of reasons for the failures. Failures are recovered,
930      * and all changed resources are given a chance to be written to disk.
931      *
932      * @param monitor the progress monitor, may be null
933      */

934     /* internal use only */ IStatus commitCache(ThreadInfo threadInfo, IProgressMonitor monitor) {
935         if (threadInfo.isEmpty()) {
936             return SyncInfoCache.STATUS_OK;
937         }
938         List JavaDoc errors = new ArrayList JavaDoc();
939         try {
940             /*** prepare operation ***/
941             // find parents of changed resources
942
IResource[] changedResources = threadInfo.getChangedResources();
943             IContainer[] changedFolders;
944             if (threadInfo instanceof CVSThreadInfo) {
945                 changedFolders = ((CVSThreadInfo)threadInfo).getChangedFolders();
946             } else {
947                 changedFolders = new IContainer[0];
948             }
949             Set JavaDoc dirtyParents = new HashSet JavaDoc();
950             for (int i = 0; i < changedResources.length; i++) {
951                 IResource resource = changedResources[i];
952                 IContainer folder = resource.getParent();
953                 dirtyParents.add(folder);
954             }
955             
956             monitor = Policy.monitorFor(monitor);
957             int numDirty = dirtyParents.size();
958             int numResources = changedFolders.length + numDirty;
959             monitor.beginTask(null, numResources);
960             if(monitor.isCanceled()) {
961                 monitor.subTask(CVSMessages.EclipseSynchronizer_UpdatingSyncEndOperationCancelled);
962             } else {
963                 monitor.subTask(CVSMessages.EclipseSynchronizer_UpdatingSyncEndOperation);
964             }
965             
966             /*** write sync info to disk ***/
967             // folder sync info changes
968
for (int i = 0; i < changedFolders.length; i++) {
969                 IContainer folder = changedFolders[i];
970                 if (folder.exists() && folder.getType() != IResource.ROOT) {
971                     try {
972                         beginOperation();
973                         FolderSyncInfo info = sessionPropertyCache.getCachedFolderSync(folder, true);
974                         // Do not write the folder sync for linked resources
975
if (info == null) {
976                             // deleted folder sync info since we loaded it
977
// (but don't overwrite the sync info for linked folders
978
if (!isLinkedResource(folder))
979                                 SyncFileWriter.deleteFolderSync(folder);
980                             dirtyParents.remove(folder);
981                         } else {
982                             // modified or created new folder sync info since we loaded it
983
SyncFileWriter.writeFolderSync(folder, info);
984                         }
985                     } catch(CVSException e) {
986                         try {
987                             sessionPropertyCache.purgeCache(folder, true /* deep */);
988                         } catch(CVSException pe) {
989                             errors.add(pe.getStatus());
990                         }
991                         errors.add(e.getStatus());
992                     } finally {
993                         endOperation();
994                     }
995                 }
996                 monitor.worked(1);
997             }
998
999             // update progress for parents we will skip because they were deleted
1000
monitor.worked(numDirty - dirtyParents.size());
1001
1002            // resource sync info changes
1003
for (Iterator JavaDoc it = dirtyParents.iterator(); it.hasNext();) {
1004                IContainer folder = (IContainer) it.next();
1005                if (folder.exists() && folder.getType() != IResource.ROOT) {
1006                    // write sync info for all children in one go
1007
try {
1008                        beginOperation();
1009                        List JavaDoc infos = new ArrayList JavaDoc();
1010                        IResource[] children = folder.members(true);
1011                        for (int i = 0; i < children.length; i++) {
1012                            IResource resource = children[i];
1013                            byte[] syncBytes = getSyncBytes(resource);
1014                            if (syncBytes != null) {
1015                                infos.add(syncBytes);
1016                            }
1017                        }
1018                        // do not overwrite the sync info for linked resources
1019
if (infos.size() > 0 || !isLinkedResource(folder))
1020                            SyncFileWriter.writeAllResourceSync(folder,
1021                                (byte[][]) infos.toArray(new byte[infos.size()][]));
1022                    } catch(CVSException e) {
1023                        try {
1024                            sessionPropertyCache.purgeCache(folder, false /* depth 1 */);
1025                        } catch(CVSException pe) {
1026                            errors.add(pe.getStatus());
1027                        }
1028                        errors.add(e.getStatus());
1029                    } catch (CoreException e) {
1030                        try {
1031                            sessionPropertyCache.purgeCache(folder, false /* depth 1 */);
1032                        } catch(CVSException pe) {
1033                            errors.add(pe.getStatus());
1034                        }
1035                        errors.add(e.getStatus());
1036                    } finally {
1037                        endOperation();
1038                    }
1039                }
1040                monitor.worked(1);
1041            }
1042            
1043            /*** broadcast events ***/
1044            monitor.subTask(CVSMessages.EclipseSynchronizer_NotifyingListeners);
1045            Set JavaDoc allChanges = new HashSet JavaDoc();
1046            allChanges.addAll(Arrays.asList(changedResources));
1047            allChanges.addAll(Arrays.asList(changedFolders));
1048            allChanges.addAll(dirtyParents);
1049            IResource[] resources = (IResource[]) allChanges.toArray(
1050                new IResource[allChanges.size()]);
1051            broadcastResourceStateChanges(resources);
1052            if ( ! errors.isEmpty()) {
1053                MultiStatus status = new MultiStatus(CVSProviderPlugin.ID,
1054                                            CVSStatus.COMMITTING_SYNC_INFO_FAILED,
1055                                            CVSMessages.EclipseSynchronizer_ErrorCommitting,
1056                                            null);
1057                for (int i = 0; i < errors.size(); i++) {
1058                    status.merge((IStatus)errors.get(i));
1059                }
1060                return status;
1061            }
1062            return SyncInfoCache.STATUS_OK;
1063        } finally {
1064            monitor.done();
1065        }
1066    }
1067
1068    /**
1069     * Broadcasts the resource state changes for the given resources to CVS Provider Plugin
1070     */

1071    void broadcastResourceStateChanges(IResource[] resources) {
1072        if (resources.length > 0) {
1073            ResourceStateChangeListeners.getListener().resourceSyncInfoChanged(resources);
1074        }
1075    }
1076
1077    /**
1078     * Returns the resource sync info for the resource; null if none.
1079     * Parent must exist and must not be the workspace root.
1080     * The resource sync info for the children of the parent container MUST ALREADY BE CACHED.
1081     *
1082     * @param resource the resource
1083     * @return the resource sync info for the resource, or null
1084     * @see #cacheResourceSyncForChildren
1085     */

1086    private byte[] getCachedSyncBytes(IResource resource) throws CVSException {
1087        return getSyncInfoCacheFor(resource).getCachedSyncBytes(resource, true);
1088    }
1089
1090    /**
1091     * Returns the resource sync info for the resource; null if none.
1092     * Parent must exist and must not be the workspace root.
1093     * The resource sync info for the children of the parent container MUST ALREADY BE CACHED.
1094     *
1095     * @param resource the resource
1096     * @return the resource sync info for the resource, or null
1097     * @see #cacheResourceSyncForChildren
1098     */

1099    private void setCachedSyncBytes(IResource resource, byte[] syncBytes) throws CVSException {
1100        getSyncInfoCacheFor(resource).setCachedSyncBytes(resource, syncBytes, true);
1101        resourceChanged(resource);
1102    }
1103        
1104    /**
1105     * Sets the resource sync info for the resource; if null, deletes it. Parent
1106     * must exist and must not be the workspace root. The resource sync info for
1107     * the children of the parent container MUST ALREADY BE CACHED.
1108     *
1109     * @param resource the resource
1110     * @param info the new resource sync info
1111     * @see #cacheResourceSyncForChildren
1112     */

1113    private void setCachedResourceSync(IResource resource, ResourceSyncInfo info) throws CVSException {
1114        //todo
1115
byte[] syncBytes = null;
1116        if (info != null) syncBytes = info.getBytes();
1117        getSyncInfoCacheFor(resource).setCachedSyncBytes(resource, syncBytes, true);
1118    }
1119    
1120    /**
1121     * If not already cached, loads and caches the folder ignores sync for the container.
1122     * Folder must exist and must not be the workspace root.
1123     *
1124     * @param container the container
1125     * @return the folder ignore patterns, or an empty array if none
1126     */

1127    private FileNameMatcher cacheFolderIgnores(IContainer container) throws CVSException {
1128        return sessionPropertyCache.getFolderIgnores(container, true);
1129    }
1130    
1131    /**
1132     * Sets the array of folder ignore patterns for the container, must not be null.
1133     * Folder must exist and must not be the workspace root.
1134     *
1135     * @param container the container
1136     * @param ignores the array of ignore patterns
1137     */

1138    private void setCachedFolderIgnores(IContainer container, String JavaDoc[] ignores) throws CVSException {
1139        sessionPropertyCache.setCachedFolderIgnores(container, ignores);
1140    }
1141    
1142    /*
1143     * Recursively adds to the possibleIgnores list all children of the given
1144     * folder that can be ignored. This method may only be invoked when a
1145     * schedling rule for the given foldr is held and when the CVs sync lock is
1146     * held.
1147     *
1148     * @param folder the folder to be searched
1149     * @param possibleIgnores the list of IResources that can be ignored
1150     */

1151    private void accumulateNonManagedChildren(IContainer folder, List JavaDoc possibleIgnores) throws CVSException {
1152        try {
1153            cacheResourceSyncForChildren(folder, true /* can modify workspace */);
1154            IResource[] children = folder.members();
1155            List JavaDoc folders = new ArrayList JavaDoc();
1156            // deal with all files first and then folders to be otimized for caching scheme
1157
for (int i = 0; i < children.length; i++) {
1158                IResource child = children[i];
1159                if(getCachedSyncBytes(child)==null) {
1160                    possibleIgnores.add(child);
1161                }
1162                if(child.getType()!=IResource.FILE) {
1163                    folders.add(child);
1164                }
1165            }
1166            for (Iterator JavaDoc iter = folders.iterator(); iter.hasNext();) {
1167                IContainer child = (IContainer) iter.next();
1168                accumulateNonManagedChildren(child, possibleIgnores);
1169            }
1170        } catch(CoreException e) {
1171            throw CVSException.wrapException(e);
1172        }
1173    }
1174    
1175    /**
1176     * Add the entry to the CVS/Notify file. We are not initially concerned with efficiency
1177     * since edit/unedit are typically issued on a small set of files.
1178     *
1179     * XXX If there was a previous notify entry for the resource, it is replaced. This is
1180     * probably not the proper behavior (see EclipseFile).
1181     *
1182     * A value of null for info indicates that any entry for the given
1183     * resource is to be removed from the Notify file.
1184     *
1185     * @param resource
1186     * @param info
1187     */

1188    public void setNotifyInfo(IResource resource, NotifyInfo info) throws CVSException {
1189        NotifyInfo[] infos = SyncFileWriter.readAllNotifyInfo(resource.getParent());
1190        if (infos == null) {
1191            // if the file is empty and we are removing an entry, just return;
1192
if (info == null) return;
1193            infos = new NotifyInfo[] { info };
1194        } else {
1195            Map JavaDoc infoMap = new HashMap JavaDoc();
1196            for (int i = 0; i < infos.length; i++) {
1197                NotifyInfo notifyInfo = infos[i];
1198                infoMap.put(notifyInfo.getName(), notifyInfo);
1199            }
1200            if (info == null) {
1201                // if the info is null, remove the entry
1202
infoMap.remove(resource.getName());
1203            } else {
1204                // add the new entry to the list
1205
infoMap.put(info.getName(), info);
1206            }
1207            
1208            NotifyInfo[] newInfos = new NotifyInfo[infoMap.size()];
1209            int i = 0;
1210            for (Iterator JavaDoc iter = infoMap.values().iterator(); iter.hasNext();) {
1211                newInfos[i++] = (NotifyInfo) iter.next();
1212            }
1213            infos = newInfos;
1214        }
1215        SyncFileWriter.writeAllNotifyInfo(resource.getParent(), infos);
1216    }
1217
1218    /**
1219     * Method getNotifyInfo.
1220     * @param resource
1221     * @return NotifyInfo
1222     */

1223    public NotifyInfo getNotifyInfo(IResource resource) throws CVSException {
1224        NotifyInfo[] infos = SyncFileWriter.readAllNotifyInfo(resource.getParent());
1225        if (infos == null) return null;
1226        for (int i = 0; i < infos.length; i++) {
1227            NotifyInfo notifyInfo = infos[i];
1228            if (notifyInfo.getName().equals(resource.getName())) {
1229                return notifyInfo;
1230            }
1231        }
1232        return null;
1233    }
1234
1235    /**
1236     * Method deleteNotifyInfo.
1237     * @param resource
1238     */

1239    public void deleteNotifyInfo(IResource resource) throws CVSException {
1240        NotifyInfo[] infos = SyncFileWriter.readAllNotifyInfo(resource.getParent());
1241        if (infos == null) return;
1242        Map JavaDoc infoMap = new HashMap JavaDoc();
1243        for (int i = 0; i < infos.length; i++) {
1244            NotifyInfo notifyInfo = infos[i];
1245            infoMap.put(notifyInfo.getName(), notifyInfo);
1246        }
1247        infoMap.remove(resource.getName());
1248        NotifyInfo[] newInfos = new NotifyInfo[infoMap.size()];
1249        int i = 0;
1250        for (Iterator JavaDoc iter = infoMap.values().iterator(); iter.hasNext();) {
1251            newInfos[i++] = (NotifyInfo) iter.next();
1252        }
1253        SyncFileWriter.writeAllNotifyInfo(resource.getParent(), newInfos);
1254    }
1255    
1256    /**
1257     * Add the entry to the CVS/Baserev file. We are not initially concerned
1258     * with efficiency since edit/unedit are typically issued on a small set of
1259     * files.
1260     *
1261     * XXX If there was a previous notify entry for the resource, it is replaced. This is
1262     * probably not the proper behavior (see EclipseFile).
1263     *
1264     * @param resource
1265     * @param info
1266     */

1267    public void setBaserevInfo(IResource resource, BaserevInfo info) throws CVSException {
1268        BaserevInfo[] infos = SyncFileWriter.readAllBaserevInfo(resource.getParent());
1269        if (infos == null) {
1270            infos = new BaserevInfo[] { info };
1271        } else {
1272            Map JavaDoc infoMap = new HashMap JavaDoc();
1273            for (int i = 0; i < infos.length; i++) {
1274                infoMap.put(infos[i].getName(), infos[i]);
1275            }
1276            infoMap.put(info.getName(), info);
1277            BaserevInfo[] newInfos = new BaserevInfo[infoMap.size()];
1278            int i = 0;
1279            for (Iterator JavaDoc iter = infoMap.values().iterator(); iter.hasNext();) {
1280                newInfos[i++] = (BaserevInfo) iter.next();
1281            }
1282            infos = newInfos;
1283        }
1284        SyncFileWriter.writeAllBaserevInfo(resource.getParent(), infos);
1285    }
1286
1287    /**
1288     * Method getBaserevInfo.
1289     * @param resource
1290     * @return BaserevInfo
1291     */

1292    public BaserevInfo getBaserevInfo(IResource resource) throws CVSException {
1293        BaserevInfo[] infos = SyncFileWriter.readAllBaserevInfo(resource.getParent());
1294        if (infos == null) return null;
1295        for (int i = 0; i < infos.length; i++) {
1296            BaserevInfo info = infos[i];
1297            if (info.getName().equals(resource.getName())) {
1298                return info;
1299            }
1300        }
1301        return null;
1302    }
1303            
1304    /**
1305     * Method deleteNotifyInfo.
1306     * @param resource
1307     */

1308    public void deleteBaserevInfo(IResource resource) throws CVSException {
1309        BaserevInfo[] infos = SyncFileWriter.readAllBaserevInfo(resource.getParent());
1310        if (infos == null) return;
1311        Map JavaDoc infoMap = new HashMap JavaDoc();
1312        for (int i = 0; i < infos.length; i++) {
1313            infoMap.put(infos[i].getName(), infos[i]);
1314        }
1315        infoMap.remove(resource.getName());
1316        BaserevInfo[] newInfos = new BaserevInfo[infoMap.size()];
1317        int i = 0;
1318        for (Iterator JavaDoc iter = infoMap.values().iterator(); iter.hasNext();) {
1319            newInfos[i++] = (BaserevInfo) iter.next();
1320        }
1321        SyncFileWriter.writeAllBaserevInfo(resource.getParent(), newInfos);
1322    }
1323
1324    public void copyFileToBaseDirectory(final IFile file, IProgressMonitor monitor) throws CVSException {
1325        monitor = Policy.monitorFor(monitor);
1326        monitor.beginTask(null, 100);
1327        ISchedulingRule rule = null;
1328        try {
1329            rule = beginBatching(file, Policy.subMonitorFor(monitor, 10));
1330            ResourceSyncInfo info = getResourceSync(file);
1331            // The file must exist remotely and locally
1332
if (info == null || info.isAdded() || info.isDeleted())
1333                return;
1334            SyncFileWriter.writeFileToBaseDirectory(file, Policy.subMonitorFor(monitor, 80));
1335            resourceChanged(file);
1336        } finally {
1337            if (rule != null) endBatching(rule, Policy.subMonitorFor(monitor, 10));
1338            monitor.done();
1339        }
1340    }
1341    
1342    public void restoreFileFromBaseDirectory(final IFile file, IProgressMonitor monitor) throws CVSException {
1343        monitor = Policy.monitorFor(monitor);
1344        monitor.beginTask(null, 100);
1345        ISchedulingRule rule = null;
1346        try {
1347            rule = beginBatching(file, Policy.subMonitorFor(monitor, 10));
1348            ResourceSyncInfo info = getResourceSync(file);
1349            // The file must exist remotely
1350
if (info == null || info.isAdded())
1351                return;
1352            SyncFileWriter.restoreFileFromBaseDirectory(file, Policy.subMonitorFor(monitor, 80));
1353            resourceChanged(file);
1354        } finally {
1355            if (rule != null) endBatching(rule, Policy.subMonitorFor(monitor, 10));
1356            monitor.done();
1357        }
1358    }
1359    
1360    public void deleteFileFromBaseDirectory(final IFile file, IProgressMonitor monitor) throws CVSException {
1361        ResourceSyncInfo info = getResourceSync(file);
1362        // The file must exist remotely
1363
if (info == null || info.isAdded())
1364            return;
1365        SyncFileWriter.deleteFileFromBaseDirectory(file, monitor);
1366    }
1367    
1368    /**
1369     * Method isSyncInfoLoaded returns true if all the sync info for the
1370     * provided resources is loaded into the internal cache.
1371     *
1372     * @param resources
1373     * @param i
1374     * @return boolean
1375     */

1376    public boolean isSyncInfoLoaded(IResource[] resources, int depth) throws CVSException {
1377        // get the folders involved
1378
IContainer[] folders = getParentFolders(resources, depth);
1379        // for all folders that have a CVS folder, ensure the sync info is cached
1380
for (int i = 0; i < folders.length; i++) {
1381            IContainer parent = folders[i];
1382            if (!getSyncInfoCacheFor(parent).isSyncInfoLoaded(parent)) {
1383                return false;
1384            }
1385        }
1386        return true;
1387    }
1388
1389    /**
1390     * Method ensureSyncInfoLoaded loads all the relevent sync info into the cache.
1391     * This method can only be invoked when the workspace is open for modification.
1392     * in other words it cannot be invoked from inside a POST_CHANGE delta listener.
1393     * @param resources
1394     * @param i
1395     * @return Object
1396     */

1397    public void ensureSyncInfoLoaded(IResource[] resources, int depth) throws CVSException {
1398        // get the folders involved
1399
IContainer[] folders = getParentFolders(resources, depth);
1400        // Cache the sync info for all the folders
1401
for (int i = 0; i < folders.length; i++) {
1402            IContainer parent = folders[i];
1403            ISchedulingRule rule = null;
1404            try {
1405                rule = beginBatching(parent, null);
1406                try {
1407                    beginOperation();
1408                    cacheResourceSyncForChildren(parent, true /* can modify workspace */);
1409                    cacheFolderSync(parent);
1410                    cacheFolderIgnores(parent);
1411                } finally {
1412                    endOperation();
1413                }
1414            } finally {
1415                if (rule != null) endBatching(rule, null);
1416            }
1417        }
1418    }
1419
1420    /*
1421     * Collect the projects and parent folders of the resources since
1422     * thats were the sync info is kept.
1423     */

1424    private IContainer[] getParentFolders(IResource[] resources, int depth) throws CVSException {
1425        final Set JavaDoc folders = new HashSet JavaDoc();
1426        for (int i = 0; i < resources.length; i++) {
1427            IResource resource = resources[i];
1428            folders.add(resource.getProject());
1429            if (resource.getType() != IResource.PROJECT) {
1430                folders.add(resource.getParent());
1431            }
1432            // use the depth to gather child folders when appropriate
1433
if (depth != IResource.DEPTH_ZERO) {
1434                try {
1435                    resource.accept(new IResourceVisitor() {
1436                        public boolean visit(IResource innerResource) throws CoreException {
1437                            if (innerResource.getType() == IResource.FOLDER)
1438                                folders.add(innerResource);
1439                            // let the depth determine who we visit
1440
return true;
1441                        }
1442                    }, depth, false);
1443                } catch (CoreException e) {
1444                    throw CVSException.wrapException(e);
1445                }
1446            }
1447        }
1448        return (IContainer[]) folders.toArray(new IContainer[folders.size()]);
1449    }
1450    
1451    /**
1452     * Perform sync info batching within the context of the given resource
1453     * scheduling rule while running the given ICVSRunnable.
1454     * @param runnable
1455     * @param monitor
1456     * @throws CVSException
1457     */

1458    public void run(ISchedulingRule resourceRule, ICVSRunnable runnable, IProgressMonitor monitor) throws CVSException {
1459        monitor = Policy.monitorFor(monitor);
1460        monitor.beginTask(null, 100);
1461        ISchedulingRule rule = beginBatching(resourceRule, Policy.subMonitorFor(monitor, 1));
1462        try {
1463            runnable.run(Policy.subMonitorFor(monitor, 98));
1464        } finally {
1465            if (rule != null) endBatching(rule, Policy.subMonitorFor(monitor, 1));
1466            monitor.done();
1467        }
1468    }
1469    
1470    /**
1471     * Method isEdited returns true if a "cvs edit" was performed on the given
1472     * file and no commit or unedit has yet been performed.
1473     * @param iResource
1474     * @return boolean
1475     */

1476    public boolean isEdited(IFile resource) {
1477        return SyncFileWriter.isEdited(resource);
1478    }
1479    
1480    /* package */ void adjustDirtyStateRecursively(IResource resource, String JavaDoc indicator) throws CVSException {
1481        if (resource.getType() == IResource.ROOT) return;
1482        try {
1483            beginOperation();
1484            
1485            if (getSyncInfoCacheFor(resource).cachesDirtyState()) {
1486                if (indicator == getDirtyIndicator(resource)) {
1487                    return;
1488                }
1489                getSyncInfoCacheFor(resource).setDirtyIndicator(resource, indicator);
1490            }
1491            
1492            if (Policy.DEBUG_DIRTY_CACHING) {
1493                debug(resource, indicator, "adjusting dirty state"); //$NON-NLS-1$
1494
}
1495
1496            IContainer parent = resource.getParent();
1497            if(indicator == NOT_DIRTY_INDICATOR) {
1498                adjustDirtyStateRecursively(parent, RECOMPUTE_INDICATOR);
1499            }
1500            
1501            if(indicator == RECOMPUTE_INDICATOR) {
1502                adjustDirtyStateRecursively(parent, RECOMPUTE_INDICATOR);
1503            }
1504            
1505            if(indicator == IS_DIRTY_INDICATOR) {
1506                adjustDirtyStateRecursively(parent, indicator);
1507            }
1508        } finally {
1509            endOperation();
1510        }
1511    }
1512
1513    protected String JavaDoc getDirtyIndicator(IResource resource) throws CVSException {
1514        // Do a check outside the lock for the dirty indicator
1515
String JavaDoc indicator = getSyncInfoCacheFor(resource).getDirtyIndicator(resource, false /* not thread safe */);
1516        if (indicator != null)
1517            return indicator;
1518        try {
1519            beginOperation();
1520            return getSyncInfoCacheFor(resource).getDirtyIndicator(resource, true);
1521        } finally {
1522            endOperation();
1523        }
1524    }
1525    
1526    /*
1527     * Mark the given resource as either modified or clean using a persistant
1528     * property. Do nothing if the modified state is already what we want.
1529     * Return true if the modification state was changed.
1530     */

1531    protected void setDirtyIndicator(IResource resource, boolean modified) throws CVSException {
1532        String JavaDoc indicator = modified ? IS_DIRTY_INDICATOR : NOT_DIRTY_INDICATOR;
1533        // set the dirty indicator and adjust the parent accordingly
1534
adjustDirtyStateRecursively(resource, indicator);
1535    }
1536
1537    /**
1538     * Method getName.
1539     * @param syncBytes
1540     */

1541    private String JavaDoc getName(byte[] syncBytes) throws CVSException {
1542        return ResourceSyncInfo.getName(syncBytes);
1543    }
1544    
1545    /**
1546     * Method isFolder.
1547     * @param syncBytes
1548     * @return boolean
1549     */

1550    private boolean isFolder(byte[] syncBytes) {
1551        return ResourceSyncInfo.isFolder(syncBytes);
1552    }
1553        
1554    /**
1555     * Method convertToDeletion.
1556     * @param syncBytes
1557     * @return byte[]
1558     */

1559    private byte[] convertToDeletion(byte[] syncBytes) throws CVSException {
1560        return ResourceSyncInfo.convertToDeletion(syncBytes);
1561    }
1562
1563    static public void debug(IResource resource, String JavaDoc indicator, String JavaDoc string) {
1564        String JavaDoc di = EclipseSynchronizer.IS_DIRTY_INDICATOR;
1565        if(indicator == EclipseSynchronizer.IS_DIRTY_INDICATOR) {
1566            di = "dirty"; //$NON-NLS-1$
1567
} else if(indicator == EclipseSynchronizer.NOT_DIRTY_INDICATOR) {
1568            di = "clean"; //$NON-NLS-1$
1569
} else {
1570            di = "needs recomputing"; //$NON-NLS-1$
1571
}
1572        System.out.println("["+string + ":" + di + "] " + resource.getFullPath()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
1573
}
1574    
1575    /**
1576     * @param file
1577     * @return int
1578     */

1579    public int getModificationState(IResource resource) throws CVSException {
1580        String JavaDoc indicator = getDirtyIndicator(resource);
1581        if (Policy.DEBUG_DIRTY_CACHING) {
1582            debug(resource, indicator, "getModificationState"); //$NON-NLS-1$
1583
}
1584        if (indicator == null || indicator == RECOMPUTE_INDICATOR) {
1585            return ICVSFile.UNKNOWN;
1586        } else if (indicator == IS_DIRTY_INDICATOR) {
1587            return ICVSFile.DIRTY;
1588        } else if (indicator == NOT_DIRTY_INDICATOR) {
1589            return ICVSFile.CLEAN;
1590        } else {
1591            return ICVSFile.UNKNOWN;
1592        }
1593    }
1594
1595    /**
1596     * Return whether the resource is within the scope of a currently active
1597     * CVS operation.
1598     * @param resource
1599     * @return
1600     */

1601    public boolean isWithinActiveOperationScope(IResource resource) {
1602        return resourceLock.isWithinActiveOperationScope(resource);
1603    }
1604    
1605    /**
1606     * Set the timestamp of the given file and set it to be CLEAN. It is
1607     * assumed that this method is only invoked to reset the file timestamp
1608     * to the timestamp that is in the CVS/Entries file.
1609     * @param file
1610     * @param time
1611     * @throws CVSException
1612     */

1613    public void setTimeStamp(EclipseFile cvsFile, long time) throws CVSException {
1614        ISchedulingRule rule = null;
1615        IFile file = (IFile)cvsFile.getIResource();
1616        try {
1617            rule = beginBatching(file, null);
1618            try {
1619                beginOperation();
1620                try {
1621                    file.setLocalTimeStamp(time);
1622                    setModified(cvsFile, ICVSFile.CLEAN);
1623                } catch (CoreException e) {
1624                    throw CVSException.wrapException(e);
1625                }
1626                resourceChanged(file);
1627            } finally {
1628                endOperation();
1629            }
1630        } finally {
1631            if (rule != null) endBatching(rule, null);
1632        }
1633    }
1634
1635    /**
1636     * React to a resource that was just moved by the move/delete hook.
1637     * @param resource the resource that was moved (at its new location)
1638     */

1639    public void postMove(IResource resource) throws CVSException {
1640        try {
1641            beginOperation();
1642            if (resource.getType() == IResource.FILE) {
1643                // Purge any copied sync info so true sync info will
1644
// be obtained from the synchronizer cache
1645
sessionPropertyCache.purgeResourceSyncCache(resource);
1646            } else {
1647                IContainer container = (IContainer)resource;
1648                // Purge any copied sync info
1649
sessionPropertyCache.purgeCache(container, true /* deep */);
1650                // Dirty all resources so old sync info will be rewritten to disk
1651
try {
1652                    container.accept(new IResourceVisitor() {
1653                        public boolean visit(IResource resource) throws CoreException {
1654                            if (getSyncBytes(resource) != null) {
1655                                resourceChanged(resource);
1656                            }
1657                            if (resource.getType() != IResource.FILE) {
1658                                if (getFolderSync((IContainer)resource) != null) {
1659                                    folderChanged((IContainer)resource);
1660                                    return true;
1661                                }
1662                            }
1663                            return false;
1664                        }
1665                    });
1666                } catch (CoreException e) {
1667                    throw CVSException.wrapException(e);
1668                }
1669                // Flush the sync info to disk
1670
flush(container, true /* deep */, null);
1671            }
1672        } finally {
1673            endOperation();
1674        }
1675    }
1676    
1677    /**
1678     * This method is to be invoked only from the move/delete hook. It's purpose
1679     * is to obtain the sync look in order to prevent other threads from accessing
1680     * sync info while the move/delete is taking place.
1681     * @param runnable
1682     * @param monitor
1683     * @throws CVSException
1684     */

1685    public void performMoveDelete(ICVSRunnable runnable, IProgressMonitor monitor) throws CVSException {
1686        ISchedulingRule rule = null;
1687        try {
1688            monitor.beginTask(null, 100);
1689            rule = beginBatching(null, null);
1690            try {
1691                beginOperation();
1692                runnable.run(Policy.subMonitorFor(monitor, 95));
1693            } finally {
1694                endOperation();
1695            }
1696        } finally {
1697            if (rule != null) endBatching(rule, Policy.subMonitorFor(monitor, 5));
1698            monitor.done();
1699        }
1700    }
1701    
1702    /**
1703     * Compute the modification state for the given file. If the modificationState is
1704     * ICVSFile.UNKNOWN, it is computed. However, if it is CLEAN or DIRTY,
1705     * it is set accordingly. CLEAN or DIRTY can only be used if the caller is protected
1706     * from resource modifications (either by a scheduling rule or inside a delta handler).
1707     * @param file
1708     * @param modificationState
1709     * @return true if the file is dirty
1710     */

1711    public boolean setModified(EclipseFile cvsFile, int modificationState) throws CVSException {
1712        try {
1713            beginOperation();
1714            boolean dirty;
1715            if (modificationState == ICVSFile.UNKNOWN) {
1716                dirty = cvsFile.isDirty();
1717            } else {
1718                dirty = modificationState == ICVSFile.DIRTY;
1719            }
1720            setDirtyIndicator(cvsFile.getIResource(), dirty);
1721            return dirty;
1722        } finally {
1723            endOperation();
1724        }
1725
1726    }
1727
1728    /**
1729     * Set the modified state of the folder. This method can be called when no resource locks are
1730     * held. It will check the cached modification state of all the folder's children before setting.
1731     * If the states of the children do not match, the state for the folder is not cached.
1732     * @param folder
1733     * @param modified
1734     */

1735    public void setModified(ICVSFolder cvsFolder, boolean modified) throws CVSException {
1736        try {
1737            beginOperation();
1738            IContainer folder = (IContainer)cvsFolder.getIResource();
1739            // The drop out condition for clean or dirty are the opposite.
1740
// (i.e. if modified and a dirty is found we can set the indicator
1741
// and if not modified and a dirty or unknown is found we cannot set the indicator)
1742
boolean okToSet = !modified;
1743            // Obtain the children while we're locked to ensure some were not added or changed
1744
ICVSResource[] children = cvsFolder.members(ICVSFolder.ALL_UNIGNORED_MEMBERS);
1745            for (int i = 0; i < children.length; i++) {
1746                IResource resource = children[i].getIResource();
1747                if (modified) {
1748                    if (getDirtyIndicator(resource) == IS_DIRTY_INDICATOR) {
1749                        okToSet = true;
1750                        break;
1751                    }
1752                } else {
1753                    if (getDirtyIndicator(resource) != NOT_DIRTY_INDICATOR) {
1754                        okToSet = false;
1755                        break;
1756                    }
1757                }
1758            }
1759            if (okToSet) {
1760                setDirtyIndicator(folder, modified);
1761            }
1762        } finally {
1763            endOperation();
1764        }
1765    }
1766    
1767    public boolean wasPhantom(IResource resource) {
1768        if (resource.exists()) {
1769            try {
1770                return (synchronizerCache.getCachedSyncBytes(resource, true) != null
1771                    || (resource.getType() == IResource.FOLDER
1772                            && synchronizerCache.hasCachedFolderSync((IContainer)resource)));
1773            } catch (CVSException e) {
1774                // Log and assume resource is not a phantom
1775
CVSProviderPlugin.log(e);
1776            }
1777        }
1778        return false;
1779    }
1780    
1781    /**
1782     * Method called from background handler when resources that are mapped to CVS are recreated
1783     * @param resources
1784     * @param monitor
1785     * @throws CVSException
1786     */

1787    public void resourcesRecreated(IResource[] resources, IProgressMonitor monitor) throws CVSException {
1788        if (resources.length == 0) return;
1789        ISchedulingRule rule = null;
1790        ISchedulingRule projectsRule = getProjectRule(resources);
1791        try {
1792            monitor = Policy.monitorFor(monitor);
1793            monitor.beginTask(null, 100);
1794            rule = beginBatching(projectsRule, monitor);
1795            for (int i = 0; i < resources.length; i++) {
1796                IResource resource = resources[i];
1797                try {
1798                    created(resource);
1799                } catch (CVSException e) {
1800                    CVSProviderPlugin.log(e);
1801                }
1802            }
1803        } finally {
1804            if (rule != null) endBatching(rule, Policy.subMonitorFor(monitor, 5));
1805            monitor.done();
1806        }
1807    }
1808    
1809    private ISchedulingRule getProjectRule(IResource[] resources) {
1810        HashSet JavaDoc set = new HashSet JavaDoc();
1811        for (int i = 0; i < resources.length; i++) {
1812            IResource resource = resources[i];
1813            set.add(resource.getProject());
1814        }
1815        IProject[] projects = (IProject[]) set.toArray(new IProject[set.size()]);
1816        if (projects.length == 1) {
1817            return projects[0];
1818        }
1819        return new MultiRule(projects);
1820    }
1821
1822    protected void created(IResource resource) throws CVSException {
1823        try {
1824            beginOperation();
1825            if (resource.exists()) {
1826                restoreResourceSync(resource);
1827                if (resource.getType() == IResource.FOLDER) {
1828                    restoreFolderSync((IFolder)resource);
1829                }
1830            }
1831        } finally {
1832            endOperation();
1833        }
1834    }
1835    
1836    /*
1837     * Restore the folder sync info for the given folder
1838     */

1839    private void restoreFolderSync(IFolder folder) throws CVSException {
1840        try {
1841            // set the dirty count using what was cached in the phantom it
1842
beginOperation();
1843            FolderSyncInfo folderInfo = synchronizerCache.getCachedFolderSync(folder, true);
1844            if (folderInfo != null) {
1845                // There is folder sync info to restore
1846
if (folder.getFolder(SyncFileWriter.CVS_DIRNAME).exists()) {
1847                    // There is already a CVS subdirectory which indicates that
1848
// either the folder was recreated by an external tool or that
1849
// a folder with CVS information was copied from another location.
1850
// To know the difference, we need to compare the folder sync info.
1851
// If they are mapped to the same root and repository then just
1852
// purge the phantom info. Otherwise, keep the original sync info.
1853

1854                    // Get the new folder sync info
1855
FolderSyncInfo newFolderInfo = getFolderSync(folder);
1856                    if (newFolderInfo.getRoot().equals(folderInfo.getRoot())
1857                            && newFolderInfo.getRepository().equals(folderInfo.getRepository())) {
1858                        // The folder is the same so use what is on disk.
1859
// Fall through to ensure that the Root and Repository files exist
1860
} else {
1861                        // The folder is mapped to a different location.
1862
// Purge new resource sync before restoring from phantom
1863
ICVSFolder cvsFolder = CVSWorkspaceRoot.getCVSFolderFor(folder);
1864                        ICVSResource[] children = cvsFolder.members(ICVSFolder.MANAGED_MEMBERS);
1865                        for (int i = 0; i < children.length; i++) {
1866                            ICVSResource resource = children[i];
1867                            deleteResourceSync(resource.getIResource());
1868                        }
1869                    }
1870                }
1871
1872                // set the sync info using what was cached in the phantom
1873
setFolderSync(folder, folderInfo);
1874                // purge the dirty cache so any old persisted dirty state is purged
1875
sessionPropertyCache.purgeDirtyCache(folder);
1876                // Indicate that a member has changed so the entries file gets written (see bug 181546)
1877
IResource[] members = members(folder);
1878                IResource changedResource = null;
1879                for (int i = 0; i < members.length; i++) {
1880                    IResource resource = members[i];
1881                    if (getSyncBytes(resource) != null) {
1882                        changedResource = resource;
1883                        break;
1884                    }
1885                }
1886                if (changedResource == null) {
1887                    changedResource = folder.getFile("dummy"); //$NON-NLS-1$
1888
}
1889                resourceChanged(changedResource);
1890            }
1891        } finally {
1892            try {
1893                endOperation();
1894            } finally {
1895                synchronizerCache.flush(folder);
1896            }
1897        }
1898    }
1899
1900    /*
1901     * Restore the resource sync info for the given resource.
1902     */

1903    private void restoreResourceSync(IResource resource) throws CVSException {
1904        try {
1905            beginOperation();
1906            byte[] syncBytes = synchronizerCache.getCachedSyncBytes(resource, true);
1907            if (syncBytes != null) {
1908                if (!ResourceSyncInfo.isFolder(syncBytes)) {
1909                    syncBytes = ResourceSyncInfo.convertFromDeletion(syncBytes);
1910                }
1911                byte[] newBytes = getSyncBytes(resource);
1912                if (newBytes != null && !ResourceSyncInfo.isFolder(newBytes)) {
1913                    newBytes = ResourceSyncInfo.convertFromDeletion(newBytes);
1914                }
1915                if (newBytes == null || Util.equals(syncBytes, newBytes)) {
1916                    // only move the sync info if there is no new sync info
1917
setSyncBytes(resource, syncBytes);
1918                }
1919            }
1920        } finally {
1921            try {
1922                endOperation();
1923            } finally {
1924                synchronizerCache.setCachedSyncBytes(resource, null, true);
1925            }
1926        }
1927    }
1928    
1929    private void purgeDirtyCache(IProject project, IProgressMonitor monitor) throws CVSException {
1930        sessionPropertyCache.purgeDirtyCache(project);
1931    }
1932}
1933
Popular Tags