001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.versioning.system.cvss.ui.actions.update;
043:
044: import org.netbeans.modules.versioning.system.cvss.*;
045: import org.netbeans.modules.versioning.util.VersioningOutputManager;
046: import org.netbeans.lib.cvsclient.command.update.UpdateCommand;
047: import org.netbeans.lib.cvsclient.command.GlobalOptions;
048: import org.netbeans.lib.cvsclient.command.DefaultFileInfoContainer;
049: import org.netbeans.lib.cvsclient.command.Command;
050: import org.netbeans.lib.cvsclient.event.*;
051: import org.openide.filesystems.*;
052: import org.openide.ErrorManager;
053: import org.openide.DialogDisplayer;
054: import org.openide.NotifyDescriptor;
055: import org.openide.util.NbBundle;
056:
057: import javax.swing.*;
058: import java.io.File;
059: import java.io.IOException;
060: import java.util.*;
061: import java.util.logging.Logger;
062: import java.util.logging.Level;
063:
064: /**
065: * Executes a given 'update' command and refreshes file statuses.
066: *
067: * @author Maros Sandor
068: */
069: public class UpdateExecutor extends ExecutorSupport {
070:
071: /**
072: * Contains all files that should NOT be set as up-to-date after Update finishes.
073: */
074: private Set<File> refreshedFiles = Collections
075: .synchronizedSet(new HashSet<File>());
076: private boolean rwUpdate;
077: private boolean mergeUpdate;
078:
079: /**
080: * Display name of the context on which update operates, eg "3 Projects".
081: */
082: private final String contextDisplayName;
083:
084: /**
085: * Files modified afterwards will not be cosidered up-to-date even if server says so.
086: */
087: private long updateStartTimestamp;
088:
089: /**
090: * Splits the original command into more commands if the original
091: * command would execute on incompatible files.
092: * See {@link #prepareBasicCommand(org.netbeans.lib.cvsclient.command.BasicCommand)}
093: * for more information.
094: *
095: * @param cmd command to execute
096: * @param cvs CVS engine to use
097: * @param options global option for the command
098: * @param contextDisplayName context name for the Update Results output tab (eg. "3 Projects"). If null, the output tab
099: * will not open
100: * @return array of executors that will execute the command (or array of splitted commands)
101: */
102: public static UpdateExecutor[] splitCommand(UpdateCommand cmd,
103: CvsVersioningSystem cvs, GlobalOptions options,
104: String contextDisplayName) {
105: Command[] cmds = new org.netbeans.lib.cvsclient.command.Command[0];
106: if (cmd.getDisplayName() == null)
107: cmd.setDisplayName(NbBundle.getMessage(
108: UpdateExecutor.class,
109: "MSG_UpdateExecutor_CmdDisplayName"));
110: try {
111: cmds = prepareBasicCommand(cmd);
112: } catch (IOException e) {
113: ErrorManager.getDefault().notify(e);
114: return null;
115: }
116: UpdateExecutor[] executors = new UpdateExecutor[cmds.length];
117: for (int i = 0; i < cmds.length; i++) {
118: Command command = cmds[i];
119: executors[i] = new UpdateExecutor(cvs,
120: (UpdateCommand) command, options,
121: contextDisplayName);
122: }
123: return executors;
124: }
125:
126: private UpdateExecutor(CvsVersioningSystem cvs, UpdateCommand cmd,
127: GlobalOptions options, String contextDisplayName) {
128: super (cvs, cmd, options);
129: this .contextDisplayName = contextDisplayName;
130: rwUpdate = options == null || !options.isDoNoChanges();
131: mergeUpdate = cmd.getMergeRevision1() != null;
132: }
133:
134: protected void setup() {
135: super .setup();
136: updateStartTimestamp = System.currentTimeMillis();
137: }
138:
139: public void fileInfoGenerated(FileInfoEvent e) {
140: super .fileInfoGenerated(e);
141: }
142:
143: /**
144: * Refreshes statuse of relevant files after this command terminates.
145: */
146: protected void commandFinished(ClientRuntime.Result result) {
147:
148: UpdateCommand ucmd = (UpdateCommand) cmd;
149:
150: // Sometimes the commandFinished() may be called before command.execute() is called. In this case, global options
151: // are not set yet and also this postprocessing does not make sense, return here to prevent NPE later
152: // See ExecutorSupport.commandTerminated and CommandRunnable.run, there is no guarantee that client.execute() precedes commandFinished()
153: if (ucmd.getGlobalOptions() == null) {
154: if (!cmd.hasFailed()) {
155: // this is somewhat unexpected, print a warning
156: Logger
157: .getLogger(
158: "org.netbeans.modules.versioning.system.cvss")
159: .log(Level.INFO,
160: "Warning: Update command did not fail but global options are null.");
161: }
162: return;
163: }
164:
165: cvs.setParameter(
166: CvsVersioningSystem.PARAM_BATCH_REFRESH_RUNNING,
167: Boolean.TRUE);
168:
169: File[] files = ucmd.getFiles();
170:
171: for (int i = 0; i < files.length; i++) {
172: cache.clearVirtualDirectoryContents(files[i], ucmd
173: .isRecursive(), ucmd.getGlobalOptions()
174: .getExclusions());
175: }
176:
177: if (rwUpdate && contextDisplayName != null) {
178: openOutputResults();
179: }
180:
181: Set<FileSystem> filesystems = new HashSet<FileSystem>(2);
182: boolean hasConflict = false;
183: for (Iterator i = toRefresh.iterator(); i.hasNext();) {
184: DefaultFileInfoContainer info = (DefaultFileInfoContainer) i
185: .next();
186: File file = info.getFile();
187: if (refreshedFiles.contains(file))
188: continue;
189: int c = info.getType().charAt(0);
190: if (c == 'P')
191: c = 'U';
192: if (rwUpdate) {
193: if (c == 'U') {
194: if (mergeUpdate) {
195: c = FileStatusCache.REPOSITORY_STATUS_MODIFIED;
196: } else {
197: c = FileStatusCache.REPOSITORY_STATUS_UPTODATE;
198: }
199: }
200: if (c == 'G')
201: c = FileStatusCache.REPOSITORY_STATUS_MODIFIED;
202: if (c == 'C')
203: hasConflict = true;
204: }
205: cache.refreshNow(file, c, true);
206: refreshedFiles.add(file);
207: }
208:
209: // refresh all command roots
210: // assuming that command roots and updated files all belong to the same filesystem
211: for (int i = 0; i < files.length; i++) {
212: if (ucmd.isRecursive()) {
213: refreshRecursively(files[i]);
214: } else {
215: refreshFlat(files[i]);
216: }
217: addFileSystem(filesystems, files[i]);
218: if (files[i].isFile()) {
219: cache.refreshCached(files[i].getParentFile(),
220: FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
221: }
222: }
223:
224: cvs.setParameter(
225: CvsVersioningSystem.PARAM_BATCH_REFRESH_RUNNING, null);
226: if (hasConflict) {
227: NotifyDescriptor nd = new NotifyDescriptor.Message(NbBundle
228: .getMessage(UpdateExecutor.class,
229: "MSG_UpdateGeneratedConflicts_Prompt"),
230: NotifyDescriptor.WARNING_MESSAGE);
231: DialogDisplayer.getDefault().notify(nd);
232: }
233:
234: for (Iterator i = filesystems.iterator(); i.hasNext();) {
235: FileSystem fileSystem = (FileSystem) i.next();
236: try {
237: CvsVersioningSystem.ignoreFilesystemEvents(true);
238: fileSystem.refresh(true); // fires fileChanged
239: } finally {
240: CvsVersioningSystem.ignoreFilesystemEvents(false);
241: }
242: }
243:
244: // special case: switching to a branch/tag changes textual annotations on nodes that are NOT changed during this operation, typically folders
245: if (ucmd.getUpdateByRevision() != null
246: || ucmd.isResetStickyOnes()) {
247: CvsVersioningSystem.getInstance().refreshAllAnnotations();
248: }
249: }
250:
251: private void openOutputResults() {
252: SwingUtilities.invokeLater(new Runnable() {
253: public void run() {
254: UpdateResults results = new UpdateResults(toRefresh,
255: cmd.getGlobalOptions().getCVSRoot(),
256: contextDisplayName);
257: VersioningOutputManager vom = VersioningOutputManager
258: .getInstance();
259: vom.addComponent(cmd.getGlobalOptions().getCVSRoot()
260: + "-UpdateExecutor", results); // NOI18N
261: }
262: });
263: }
264:
265: private void addFileSystem(Set<FileSystem> filesystems, File file) {
266: FileObject fo;
267: for (;;) {
268: fo = FileUtil.toFileObject(file);
269: if (fo != null)
270: break;
271: file = file.getParentFile();
272: if (file == null)
273: return;
274: }
275: try {
276: filesystems.add(fo.getFileSystem());
277: } catch (FileStateInvalidException e) {
278: // ignore invalid filesystems
279: }
280: }
281:
282: private void refreshRecursively(File file) {
283: try {
284: if (cvs.isIgnoredFilename(file))
285: return;
286: if (cmd.getGlobalOptions().isExcluded(file))
287: return;
288: if (file.isDirectory()) {
289: if (cache.getStatus(file).getStatus() == FileInformation.STATUS_NOTVERSIONED_EXCLUDED)
290: return;
291: File[] files = file.listFiles();
292: for (int i = 0; i < files.length; i++) {
293: refreshRecursively(files[i]);
294: }
295: if (!refreshedFiles.contains(file))
296: cache.refreshCached(file,
297: FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
298: } else {
299: if (!refreshedFiles.contains(file))
300: refreshFile(file);
301: }
302: } catch (Throwable e) {
303: // we catch exceptions here because we want to refresh statuses of all files regardless of any errors below
304: ErrorManager.getDefault().notify(e);
305: }
306: }
307:
308: private void refreshFlat(File file) {
309: if (cvs.isIgnoredFilename(file))
310: return;
311: if (cmd.getGlobalOptions().isExcluded(file))
312: return;
313: if (refreshedFiles.contains(file))
314: return;
315: if (file.isDirectory()) {
316: File[] files = file.listFiles();
317: for (int i = 0; i < files.length; i++) {
318: if (cvs.isIgnoredFilename(files[i]))
319: return;
320: if (refreshedFiles.contains(files[i]))
321: return;
322: if (files[i].isDirectory())
323: continue;
324: refreshFile(files[i]);
325: }
326: cache.refreshCached(file,
327: FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
328: } else {
329: refreshFile(file);
330: }
331: }
332:
333: private void refreshFile(File file) {
334: long lastModified = file.lastModified();
335: if (!cmd.hasFailed()
336: && cache.getStatus(file.getParentFile()).getStatus() == FileInformation.STATUS_VERSIONED_UPTODATE
337: && lastModified > 0
338: && lastModified < updateStartTimestamp) {
339: cache.refreshCached(file,
340: FileStatusCache.REPOSITORY_STATUS_UPTODATE);
341: } else {
342: cache.refreshCached(file,
343: FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
344: }
345: }
346: }
|