diff --git a/.idea/fileTemplates/includes/File Header.java b/.idea/fileTemplates/includes/File Header.java index 8d4bd925..e1971c44 100644 --- a/.idea/fileTemplates/includes/File Header.java +++ b/.idea/fileTemplates/includes/File Header.java @@ -1,13 +1,13 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/.idea/fileTemplates/internal/AnnotationType.java b/.idea/fileTemplates/internal/AnnotationType.java index 955855ec..82b9a3fb 100644 --- a/.idea/fileTemplates/internal/AnnotationType.java +++ b/.idea/fileTemplates/internal/AnnotationType.java @@ -1,6 +1,6 @@ -#parse("File Header.java") - -#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end - -public @interface ${NAME} { -} +#parse("File Header.java") + +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end + +public @interface ${NAME} { +} diff --git a/.idea/fileTemplates/internal/Class.java b/.idea/fileTemplates/internal/Class.java index 151bb0a3..3b155ef5 100644 --- a/.idea/fileTemplates/internal/Class.java +++ b/.idea/fileTemplates/internal/Class.java @@ -1,6 +1,6 @@ -#parse("File Header.java") - -#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end - -public class ${NAME} { -} +#parse("File Header.java") + +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end + +public class ${NAME} { +} diff --git a/.idea/fileTemplates/internal/Enum.java b/.idea/fileTemplates/internal/Enum.java index 65929d41..d9173074 100644 --- a/.idea/fileTemplates/internal/Enum.java +++ b/.idea/fileTemplates/internal/Enum.java @@ -1,6 +1,6 @@ -#parse("File Header.java") - -#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end - -public enum ${NAME} { -} +#parse("File Header.java") + +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end + +public enum ${NAME} { +} diff --git a/.idea/fileTemplates/internal/Interface.java b/.idea/fileTemplates/internal/Interface.java index f2ea5005..74b9d657 100644 --- a/.idea/fileTemplates/internal/Interface.java +++ b/.idea/fileTemplates/internal/Interface.java @@ -1,6 +1,6 @@ -#parse("File Header.java") - -#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end - -public interface ${NAME} { -} +#parse("File Header.java") + +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end + +public interface ${NAME} { +} diff --git a/.idea/fileTemplates/internal/module-info.java b/.idea/fileTemplates/internal/module-info.java index be19fd74..57a05c6f 100644 --- a/.idea/fileTemplates/internal/module-info.java +++ b/.idea/fileTemplates/internal/module-info.java @@ -1,4 +1,4 @@ -#parse("File Header.java") - -module #[[$MODULE_NAME$]]# { +#parse("File Header.java") + +module #[[$MODULE_NAME$]]# { } \ No newline at end of file diff --git a/.idea/fileTemplates/internal/package-info.java b/.idea/fileTemplates/internal/package-info.java index fec26d0d..e0371453 100644 --- a/.idea/fileTemplates/internal/package-info.java +++ b/.idea/fileTemplates/internal/package-info.java @@ -1,3 +1,3 @@ -#parse("File Header.java") - +#parse("File Header.java") + #if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end \ No newline at end of file diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/ClientServerRef.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/ClientServerRef.java index 3a26add1..90b97492 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/ClientServerRef.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/ClientServerRef.java @@ -1,90 +1,90 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4.server.api; - - -import com.intellij.openapi.util.Comparing; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * This class is used as a reference to a connection object. - * It is mostly used by the caches, so that it can persist to which - * connection it applies. - *

- * It can directly be displayed to the user with a {@link #toString()} - * call. - */ -public final class ClientServerRef { - private final P4ServerName serverName; - private final String clientName; - - - // Like P4ServerName, this class could potentially be cached, like String.intern, but that - // might lead to additional memory problems beyond what we already have. - - public ClientServerRef(@NotNull final P4ServerName serverName, @Nullable final String clientName) { - this.serverName = serverName; - this.clientName = clientName; - } - - - @NotNull - public P4ServerName getServerName() { - return serverName; - } - - - @NotNull - public String getServerDisplayId() { - return getServerName().getDisplayName(); - } - - - @Nullable - public String getClientName() { - return clientName; - } - - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o == null) { - return false; - } - if (o.getClass().equals(getClass())) { - ClientServerRef that = (ClientServerRef) o; - return that.serverName.equals(serverName) && - Comparing.equal(that.clientName, clientName); - } - return false; - } - - - @Override - public int hashCode() { - return (serverName.hashCode() << 3) + - (clientName == null ? 0 : clientName.hashCode()); - } - - - @Override - public String toString() { - return clientName + "@" + serverName.getDisplayName(); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4.server.api; + + +import com.intellij.openapi.util.Comparing; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This class is used as a reference to a connection object. + * It is mostly used by the caches, so that it can persist to which + * connection it applies. + *

+ * It can directly be displayed to the user with a {@link #toString()} + * call. + */ +public final class ClientServerRef { + private final P4ServerName serverName; + private final String clientName; + + + // Like P4ServerName, this class could potentially be cached, like String.intern, but that + // might lead to additional memory problems beyond what we already have. + + public ClientServerRef(@NotNull final P4ServerName serverName, @Nullable final String clientName) { + this.serverName = serverName; + this.clientName = clientName; + } + + + @NotNull + public P4ServerName getServerName() { + return serverName; + } + + + @NotNull + public String getServerDisplayId() { + return getServerName().getDisplayName(); + } + + + @Nullable + public String getClientName() { + return clientName; + } + + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o == null) { + return false; + } + if (o.getClass().equals(getClass())) { + ClientServerRef that = (ClientServerRef) o; + return that.serverName.equals(serverName) && + Comparing.equal(that.clientName, clientName); + } + return false; + } + + + @Override + public int hashCode() { + return (serverName.hashCode() << 3) + + (clientName == null ? 0 : clientName.hashCode()); + } + + + @Override + public String toString() { + return clientName + "@" + serverName.getDisplayName(); + } +} diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/P4PluginVersion.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/P4PluginVersion.java index 9707024c..caee38ef 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/P4PluginVersion.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/P4PluginVersion.java @@ -1,85 +1,85 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4.server.api; - -import com.intellij.openapi.diagnostic.Logger; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.InputStream; - -/** - * Loads the plugin version from the plugin.xml file. - */ -public class P4PluginVersion { - private static final Logger LOG = Logger.getInstance(P4PluginVersion.class); - - private static volatile String version; - - public static String getPluginVersion() { - if (version == null) { - synchronized (P4PluginVersion.class) { - version = loadPluginVersion(); - } - if (LOG.isDebugEnabled()) { - LOG.debug("Running plugin version " + version); - } - } - return version; - } - - - @NotNull - private static String loadPluginVersion() { - ClassLoader cl = getClassLoader(); - if (cl == null) { - // Can't find the version. - return "1"; - } - try { - final InputStream res = cl.getResourceAsStream("p4ic-version.txt"); - if (res == null) { - return "3"; - } - try { - StringBuilder sb = new StringBuilder(); - byte[] buff = new byte[4096]; - int len; - while ((len = res.read(buff)) > 0) { - // TODO encoding - sb.append(new String(buff, 0, len)); - } - return sb.toString().trim(); - } finally { - res.close(); - } - } catch (Exception e) { - LOG.info("Cannot read p4ic-version.txt", e); - return "2"; - } - } - - @Nullable - private static ClassLoader getClassLoader() { - // Do not fetch the class loader from the thread context; we want - // the plugin's class loader, not whatever context this is running - // in. - final ClassLoader ret = P4PluginVersion.class.getClassLoader(); - if (ret != null) { - return ret; - } - return null; - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4.server.api; + +import com.intellij.openapi.diagnostic.Logger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.InputStream; + +/** + * Loads the plugin version from the plugin.xml file. + */ +public class P4PluginVersion { + private static final Logger LOG = Logger.getInstance(P4PluginVersion.class); + + private static volatile String version; + + public static String getPluginVersion() { + if (version == null) { + synchronized (P4PluginVersion.class) { + version = loadPluginVersion(); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Running plugin version " + version); + } + } + return version; + } + + + @NotNull + private static String loadPluginVersion() { + ClassLoader cl = getClassLoader(); + if (cl == null) { + // Can't find the version. + return "1"; + } + try { + final InputStream res = cl.getResourceAsStream("p4ic-version.txt"); + if (res == null) { + return "3"; + } + try { + StringBuilder sb = new StringBuilder(); + byte[] buff = new byte[4096]; + int len; + while ((len = res.read(buff)) > 0) { + // TODO encoding + sb.append(new String(buff, 0, len)); + } + return sb.toString().trim(); + } finally { + res.close(); + } + } catch (Exception e) { + LOG.info("Cannot read p4ic-version.txt", e); + return "2"; + } + } + + @Nullable + private static ClassLoader getClassLoader() { + // Do not fetch the class loader from the thread context; we want + // the plugin's class loader, not whatever context this is running + // in. + final ClassLoader ret = P4PluginVersion.class.getClassLoader(); + if (ret != null) { + return ret; + } + return null; + } +} diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/commands/file/AddEditAction.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/commands/file/AddEditAction.java index ff215467..0502ba29 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/commands/file/AddEditAction.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/commands/file/AddEditAction.java @@ -1,106 +1,106 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4.server.api.commands.file; - -import com.intellij.openapi.vcs.FilePath; -import net.groboclown.p4.server.api.P4CommandRunner; -import net.groboclown.p4.server.api.commands.AbstractAction; -import net.groboclown.p4.server.api.values.P4ChangelistId; -import net.groboclown.p4.server.api.values.P4FileType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.List; - -public class AddEditAction extends AbstractAction implements P4CommandRunner.ClientAction { - private final String actionId; - private final FilePath file; - private final P4FileType type; - private final P4ChangelistId changelistId; - private final String charset; - - public AddEditAction(@NotNull FilePath file, @Nullable P4FileType type, - @NotNull P4ChangelistId changelistId, @Nullable String charset) { - this(createActionId(AddEditAction.class), file, type, changelistId, charset); - } - - public AddEditAction(@NotNull String actionId, @NotNull FilePath file, @Nullable P4FileType type, - P4ChangelistId changelistId, String charset) { - this.actionId = actionId; - this.file = file; - this.type = type; - this.changelistId = changelistId; - this.charset = charset; - } - - public AddEditAction(@NotNull FilePath file, @Nullable P4FileType type, - @Nullable P4ChangelistId changelistId, @Nullable Charset charset) { - this(file, type, changelistId, charset == null ? null : charset.name()); - } - - @NotNull - @Override - public Class getResultType() { - return AddEditResult.class; - } - - @Override - public P4CommandRunner.ClientActionCmd getCmd() { - return P4CommandRunner.ClientActionCmd.ADD_EDIT_FILE; - } - - @NotNull - @Override - public String getActionId() { - return actionId; - } - - @NotNull - public FilePath getFile() { - return file; - } - - @Nullable - public P4FileType getFileType() { - return type; - } - - @Nullable - public P4ChangelistId getChangelistId() { - return changelistId; - } - - @Nullable - public String getCharset() { - return charset; - } - - @NotNull - @Override - public String[] getDisplayParameters() { - if (changelistId != null) { - return new String[] { changeId(changelistId) }; - } - return EMPTY; - } - - @NotNull - @Override - public List getAffectedFiles() { - return Collections.singletonList(file); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4.server.api.commands.file; + +import com.intellij.openapi.vcs.FilePath; +import net.groboclown.p4.server.api.P4CommandRunner; +import net.groboclown.p4.server.api.commands.AbstractAction; +import net.groboclown.p4.server.api.values.P4ChangelistId; +import net.groboclown.p4.server.api.values.P4FileType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.List; + +public class AddEditAction extends AbstractAction implements P4CommandRunner.ClientAction { + private final String actionId; + private final FilePath file; + private final P4FileType type; + private final P4ChangelistId changelistId; + private final String charset; + + public AddEditAction(@NotNull FilePath file, @Nullable P4FileType type, + @NotNull P4ChangelistId changelistId, @Nullable String charset) { + this(createActionId(AddEditAction.class), file, type, changelistId, charset); + } + + public AddEditAction(@NotNull String actionId, @NotNull FilePath file, @Nullable P4FileType type, + P4ChangelistId changelistId, String charset) { + this.actionId = actionId; + this.file = file; + this.type = type; + this.changelistId = changelistId; + this.charset = charset; + } + + public AddEditAction(@NotNull FilePath file, @Nullable P4FileType type, + @Nullable P4ChangelistId changelistId, @Nullable Charset charset) { + this(file, type, changelistId, charset == null ? null : charset.name()); + } + + @NotNull + @Override + public Class getResultType() { + return AddEditResult.class; + } + + @Override + public P4CommandRunner.ClientActionCmd getCmd() { + return P4CommandRunner.ClientActionCmd.ADD_EDIT_FILE; + } + + @NotNull + @Override + public String getActionId() { + return actionId; + } + + @NotNull + public FilePath getFile() { + return file; + } + + @Nullable + public P4FileType getFileType() { + return type; + } + + @Nullable + public P4ChangelistId getChangelistId() { + return changelistId; + } + + @Nullable + public String getCharset() { + return charset; + } + + @NotNull + @Override + public String[] getDisplayParameters() { + if (changelistId != null) { + return new String[] { changeId(changelistId) }; + } + return EMPTY; + } + + @NotNull + @Override + public List getAffectedFiles() { + return Collections.singletonList(file); + } +} diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/config/ClientConfig.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/config/ClientConfig.java index 71019579..3f857eaf 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/config/ClientConfig.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/config/ClientConfig.java @@ -1,189 +1,189 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4.server.api.config; - -import net.groboclown.p4.server.api.ClientServerRef; -import net.groboclown.p4.server.api.config.part.ConfigPart; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.annotation.concurrent.Immutable; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.apache.commons.lang3.StringUtils.isBlank; - -/** - * Stores information regarding a server configuration and the specific client/workspace in that - * server. - *

- * This is used for server commands that require a valid client workspace. - *

- * This class MUST be immutable. - */ -@Immutable -public final class ClientConfig { - // Just some character that won't appear in any real text, but - // is still viewable in a debugger. - static final char SEP = (char) 0x263a; - - private static final AtomicInteger COUNT = new AtomicInteger(0); - - private final int configVersion; - private final ServerConfig serverConfig; - private final String clientName; - private final String clientHostName; - private final String defaultCharSet; - private final String ignoreFileName; - - private final ClientServerRef clientServerRef; - private final String clientServerUniqueId; - - @NotNull - public static ClientConfig createFrom(@NotNull ServerConfig serverConfig, @NotNull ConfigPart data) { - if (!isValidClientConfig(serverConfig, data)) { - throw new IllegalArgumentException("did not validate data"); - } - return new ClientConfig(serverConfig, data); - } - - - public static boolean isValidClientConfig(@Nullable ServerConfig serverConfig, @Nullable ConfigPart part) { - if (serverConfig == null || part == null || part.hasError()) { - return false; - } - if (isBlank(part.getClientname())) { - return false; - } - if (isBlank(serverConfig.getUsername())) { - return false; - } - return true; - } - - - private ClientConfig(@NotNull ServerConfig serverConfig, @NotNull ConfigPart data) { - // Not needed anymore, because the calling class (P4ProjectConfigStack) does this check, and we don't - // want a misleading double exception in the logs. - /* - if (! serverConfig.isSameServerConnection(data)) { - LOG.error("Server config " + serverConfig + - " does not match data config " + ConfigPropertiesUtil.toProperties(data)); - } - */ - this.configVersion = COUNT.incrementAndGet(); - - this.serverConfig = serverConfig; - this.clientName = - data.hasClientnameSet() - ? data.getClientname() - : null; - this.clientHostName = - data.hasClientHostnameSet() - ? data.getClientHostname() - : null; - this.defaultCharSet = - data.hasDefaultCharsetSet() - ? data.getDefaultCharset() - : null; - this.ignoreFileName = - data.hasIgnoreFileNameSet() - ? data.getIgnoreFileName() - : null; - - this.clientServerUniqueId = serverConfig.getServerId() + SEP + - this.clientName + SEP + - this.clientHostName + SEP + - this.ignoreFileName + SEP + - this.defaultCharSet + SEP; - // root directories are not listed, because all client configs - // for the same client and server should be a shared object. - this.clientServerRef = new ClientServerRef(serverConfig.getServerName(), clientName); - } - - public int getConfigVersion() { - return this.configVersion; - } - - /** - * - * @return unique ID for this client, which is shared for all clients with the - * same setup. - */ - @NotNull - public String getClientServerUniqueId() { - return clientServerUniqueId; - } - - @NotNull - public ServerConfig getServerConfig() { - return serverConfig; - } - - @Nullable - public String getClientname() { - return clientName; - } - - @Nullable - public String getClientHostName() { - return clientHostName; - } - - @Nullable - public String getIgnoreFileName() { - return ignoreFileName; - } - - @Nullable - public String getDefaultCharSet() { - return defaultCharSet; - } - - @NotNull - public ClientServerRef getClientServerRef() { - return clientServerRef; - } - - public boolean isIn(@NotNull ServerConfig config) { - return getServerConfig().getServerId().equals(config.getServerId()); - } - - @Override - public String toString() { - if (clientName != null) { - return serverConfig.getServerName().getDisplayName() + "@" + clientName; - } - return serverConfig.getServerName().getDisplayName(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (obj == this) { - return true; - } - if (! (obj instanceof ClientConfig)) { - return false; - } - ClientConfig that = (ClientConfig) obj; - return getClientServerUniqueId().equals(that.getClientServerUniqueId()); - } - - @Override - public int hashCode() { - return clientServerUniqueId.hashCode(); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4.server.api.config; + +import net.groboclown.p4.server.api.ClientServerRef; +import net.groboclown.p4.server.api.config.part.ConfigPart; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.concurrent.Immutable; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +/** + * Stores information regarding a server configuration and the specific client/workspace in that + * server. + *

+ * This is used for server commands that require a valid client workspace. + *

+ * This class MUST be immutable. + */ +@Immutable +public final class ClientConfig { + // Just some character that won't appear in any real text, but + // is still viewable in a debugger. + static final char SEP = (char) 0x263a; + + private static final AtomicInteger COUNT = new AtomicInteger(0); + + private final int configVersion; + private final ServerConfig serverConfig; + private final String clientName; + private final String clientHostName; + private final String defaultCharSet; + private final String ignoreFileName; + + private final ClientServerRef clientServerRef; + private final String clientServerUniqueId; + + @NotNull + public static ClientConfig createFrom(@NotNull ServerConfig serverConfig, @NotNull ConfigPart data) { + if (!isValidClientConfig(serverConfig, data)) { + throw new IllegalArgumentException("did not validate data"); + } + return new ClientConfig(serverConfig, data); + } + + + public static boolean isValidClientConfig(@Nullable ServerConfig serverConfig, @Nullable ConfigPart part) { + if (serverConfig == null || part == null || part.hasError()) { + return false; + } + if (isBlank(part.getClientname())) { + return false; + } + if (isBlank(serverConfig.getUsername())) { + return false; + } + return true; + } + + + private ClientConfig(@NotNull ServerConfig serverConfig, @NotNull ConfigPart data) { + // Not needed anymore, because the calling class (P4ProjectConfigStack) does this check, and we don't + // want a misleading double exception in the logs. + /* + if (! serverConfig.isSameServerConnection(data)) { + LOG.error("Server config " + serverConfig + + " does not match data config " + ConfigPropertiesUtil.toProperties(data)); + } + */ + this.configVersion = COUNT.incrementAndGet(); + + this.serverConfig = serverConfig; + this.clientName = + data.hasClientnameSet() + ? data.getClientname() + : null; + this.clientHostName = + data.hasClientHostnameSet() + ? data.getClientHostname() + : null; + this.defaultCharSet = + data.hasDefaultCharsetSet() + ? data.getDefaultCharset() + : null; + this.ignoreFileName = + data.hasIgnoreFileNameSet() + ? data.getIgnoreFileName() + : null; + + this.clientServerUniqueId = serverConfig.getServerId() + SEP + + this.clientName + SEP + + this.clientHostName + SEP + + this.ignoreFileName + SEP + + this.defaultCharSet + SEP; + // root directories are not listed, because all client configs + // for the same client and server should be a shared object. + this.clientServerRef = new ClientServerRef(serverConfig.getServerName(), clientName); + } + + public int getConfigVersion() { + return this.configVersion; + } + + /** + * + * @return unique ID for this client, which is shared for all clients with the + * same setup. + */ + @NotNull + public String getClientServerUniqueId() { + return clientServerUniqueId; + } + + @NotNull + public ServerConfig getServerConfig() { + return serverConfig; + } + + @Nullable + public String getClientname() { + return clientName; + } + + @Nullable + public String getClientHostName() { + return clientHostName; + } + + @Nullable + public String getIgnoreFileName() { + return ignoreFileName; + } + + @Nullable + public String getDefaultCharSet() { + return defaultCharSet; + } + + @NotNull + public ClientServerRef getClientServerRef() { + return clientServerRef; + } + + public boolean isIn(@NotNull ServerConfig config) { + return getServerConfig().getServerId().equals(config.getServerId()); + } + + @Override + public String toString() { + if (clientName != null) { + return serverConfig.getServerName().getDisplayName() + "@" + clientName; + } + return serverConfig.getServerName().getDisplayName(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (! (obj instanceof ClientConfig)) { + return false; + } + ClientConfig that = (ClientConfig) obj; + return getClientServerUniqueId().equals(that.getClientServerUniqueId()); + } + + @Override + public int hashCode() { + return clientServerUniqueId.hashCode(); + } +} diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/config/ServerConfig.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/config/ServerConfig.java index 4a0ad5e2..f6e51856 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/config/ServerConfig.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/config/ServerConfig.java @@ -1,364 +1,364 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4.server.api.config; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.io.FileUtil; -import net.groboclown.p4.server.api.ApplicationPasswordRegistry; -import net.groboclown.p4.server.api.P4ServerName; -import net.groboclown.p4.server.api.config.part.ConfigPart; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; - -import javax.annotation.concurrent.Immutable; -import java.io.File; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -import static net.groboclown.p4.server.api.util.EqualUtil.isEqual; -import static org.apache.commons.lang3.StringUtils.isBlank; - - -/** - * Stores the connection information related to a specific Perforce - * server. It only keeps track of information required to identify the - * server, which means it needs the username, password, auth ticket file, - * trust ticket file, and server fingerprint. - *

- * Implementations must specify {@link #equals(Object)} and - * {@link #hashCode()}, to indicate whether the server connection - * properties are the same; it should not match on online mode. - *

- * If the configuration specified a password, then it is stored externally - * in the {@link ApplicationPasswordRegistry}. This also allows for keeping - * in one place the password in case the user enters it manually, which would - * break immutability. - *

- * This is used for running server commands that do not require a client, - * but do require a username. There are limited commands that no not require a - * username, and for those, the {@link P4ServerName} is sufficient for connectivity. - *

- * This class MUST be immutable. - */ -@Immutable -public final class ServerConfig { - private static final Logger LOG = Logger.getInstance(ServerConfig.class); - - // Just some character that won't appear in any real text, but - // is still viewable in a debugger. - static final char SEP = (char) 0x263b; - - private static final AtomicInteger COUNT = new AtomicInteger(0); - - private final int configVersion; - private final P4ServerName serverName; - private final String username; - private final File authTicket; - private final File trustTicket; - private final String serverFingerprint; - private final String loginSso; - - private final String serverId; - - private final boolean usesPassword; - - @NotNull - static String getServerIdForDataPart(@NotNull ConfigPart part) { - StringBuilder sb = new StringBuilder(); - if (part.hasServerNameSet() && part.getServerName() != null) { - sb.append(part.getServerName().getFullPort()); - } else { - sb.append((String) null); - } - // Note: does not include password information. - sb.append(SEP) - .append(part.hasUsernameSet() ? part.getUsername() : null) - .append(SEP) - .append(part.hasAuthTicketFileSet() ? part.getAuthTicketFile() : null) - .append(SEP) - .append(part.hasTrustTicketFileSet() ? part.getTrustTicketFile() : null) - .append(SEP) - .append(part.hasServerFingerprintSet() ? part.getServerFingerprint() : null); - // These may be common enough that we want to save memory by interning the strings. - return sb.toString().intern(); - } - - - - @NotNull - public static ServerConfig createFrom(@NotNull ConfigPart part) { - return new ServerConfig(part); - } - - public static boolean isValidServerConfig(@Nullable ConfigPart part) { - if (part == null || part.hasError()) { - return false; - } - - // This should be included with the above config problems. - if (part.getServerName() == null || isBlank(part.getServerName().getFullPort())) { - return false; - } - - if (isBlank(part.getUsername())) { - return false; - } - - return true; - } - - private ServerConfig(@NotNull ConfigPart part) { - if (! isValidServerConfig(part)) { - throw new IllegalArgumentException("Did not check validity before creating"); - } - this.configVersion = COUNT.incrementAndGet(); - - assert part.hasServerNameSet(); - this.serverName = part.getServerName(); - assert part.hasUsernameSet(); - this.username = part.getUsername(); - this.authTicket = - part.hasAuthTicketFileSet() - ? part.getAuthTicketFile() - : null; - this.trustTicket = - part.hasTrustTicketFileSet() - ? part.getTrustTicketFile() - : null; - this.serverFingerprint = - part.hasServerFingerprintSet() - ? part.getServerFingerprint() - : null; - this.loginSso = - part.hasLoginSsoSet() - ? part.getLoginSso() - : null; - this.usesPassword = part.requiresUserEnteredPassword() || part.hasPasswordSet(); - - this.serverId = getServerIdForDataPart(part); - - // Must be done at the very end. - // This logic is carefully constructed to only store the password if it's supplied by the - // user, and the user does not require that it is manually entered into the UI. - // This class never stores the password in itself. - if (!part.requiresUserEnteredPassword() && part.hasPasswordSet() && part.getPlaintextPassword() != null) { - LOG.info("Storing user password in password store"); - // DEBUG - // ApplicationPasswordRegistry.getInstance().store(this, part.getPlaintextPassword().toCharArray(), false); - ApplicationPasswordRegistry instance = ApplicationPasswordRegistry.getInstance(); - String passwd = part.getPlaintextPassword(); - char[] passwdChars = passwd.toCharArray(); - instance.store(this, passwdChars, false); - } - } - - - public int getConfigVersion() { - return this.configVersion; - } - - /** - * - * @return true if the user supplies a password, either through plaintext, or through the UI asking for it. - */ - public boolean usesStoredPassword() { - return usesPassword; - } - - @NotNull - public P4ServerName getServerName() { - return serverName; - } - - @NotNull - public String getUsername() { - return username; - } - - @Nullable - public File getAuthTicket() { - return authTicket; - } - - @Nullable - public File getTrustTicket() { - return trustTicket; - } - - @Nullable - public String getServerFingerprint() { - return serverFingerprint; - } - - @Nullable - public String getLoginSso() { - return loginSso; - } - - public boolean hasServerFingerprint() { - return getServerFingerprint() != null && getServerFingerprint().length() > 0; - } - - public boolean hasAuthTicket() { - return getAuthTicket() != null; - } - - public boolean hasTrustTicket() { - return getTrustTicket() != null; - } - - public boolean hasLoginSso() { - return getLoginSso() != null; - } - - /** - * - * @return unique identifier for the server connection settings. - */ - @NotNull - public String getServerId() { - return serverId; - } - - /** - * Checks if the {@literal part} connects to the same server - * in the same way as this server configuration. For identifying - * if the server itself is the same, then compare the - * {@link P4ServerName} values. - * - * @param part configuration to compare - * @return true if the {@literal part} and this config connect to - * the same server the same way. - */ - boolean isSameServerConnection(@Nullable ConfigPart part) { - if (part == null) { - LOG.debug("isSameServerConnection: input is null"); - return false; - } - - if (! isEqual(getServerName(), part.getServerName())) { - if (LOG.isDebugEnabled()) { - if (part.getServerName() == null) { - LOG.debug("isSameServerConnection: input server name is null"); - } else { - LOG.debug("isSameServerConnection: server doesn't match: " - + getServerName().getServerPort() + "::" + getServerName().getServerProtocol() + " <> " - + part.getServerName().getServerPort() + "::" + part.getServerName().getServerProtocol()); - } - } - return false; - } - - if (! filesEqual(hasAuthTicket(), getAuthTicket(), part.hasAuthTicketFileSet(), part.getAuthTicketFile())) { - if (LOG.isDebugEnabled()) { - LOG.debug("isSameServerConnection: auth ticket doesn't match: " - + getAuthTicket() + " <> " + part.getAuthTicketFile()); - } - return false; - } - - if (! filesEqual(hasTrustTicket(), getTrustTicket(), part.hasTrustTicketFileSet(), part.getTrustTicketFile())) { - if (LOG.isDebugEnabled()) { - LOG.debug("isSameServerConnection: trust ticket doesn't match: " - + getTrustTicket() + " <> " + part.getTrustTicketFile()); - } - return false; - } - - if (hasServerFingerprint() != part.hasServerFingerprintSet()) { - if (LOG.isDebugEnabled()) { - LOG.debug("isSameServerConnection: has server fingerprint mismatch: " - + hasServerFingerprint() + " <> " + part.hasServerFingerprintSet()); - } - return false; - } - if (hasServerFingerprint() && ! isEqual(getServerFingerprint(), part.getServerFingerprint())) { - if (LOG.isDebugEnabled()) { - LOG.debug("isSameServerConnection: server fingerprint mismatch: " - + getServerFingerprint() + " <> " + part.getServerFingerprint()); - } - return false; - } - - if (! isEqual(getUsername(), part.getUsername())) { - if (LOG.isDebugEnabled()) { - LOG.debug("isSameServerConnection: username mismatch: " - + getUsername() + " <> " + part.getUsername()); - } - return false; - } - - // password usage is a bit more complex. - if ( - (usesStoredPassword() && (!part.requiresUserEnteredPassword() && !part.hasPasswordSet())) - || (!usesStoredPassword() && (part.requiresUserEnteredPassword() || part.hasPasswordSet())) - ) { - return false; - } - - return true; - } - - @NotNull - private Map toProperties() { - return ConfigPropertiesUtil.toProperties(this, "(unset)", "(empty)", "(set)"); - } - - @TestOnly - @Override - public String toString() { - return toProperties().toString(); - } - - - // equals only cares about the information that connects - // to the server, not the individual server setup. Note that - // this might have the potential to lose information. - @Override - public boolean equals(Object other) { - if (other == null) { - return false; - } - if (this == other) { - return true; - } - if (!(other instanceof ServerConfig)) { - return false; - } - ServerConfig sc = (ServerConfig) other; - return sc.getServerId().equals(getServerId()); - } - - @Override - public int hashCode() { - return getServerId().hashCode(); - } - - - private static boolean filesEqual(boolean aSet, @Nullable File aFile, boolean bSet, @Nullable File bFile) { - return FileUtil.filesEqual(scrubFile(aSet, aFile), scrubFile(bSet, bFile)); - } - - @Nullable - private static File scrubFile(boolean isSet, @Nullable File file) { - if (! isSet || file == null) { - return null; - } - if (file.exists() && file.isFile()) { - return file; - } - return null; - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4.server.api.config; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.io.FileUtil; +import net.groboclown.p4.server.api.ApplicationPasswordRegistry; +import net.groboclown.p4.server.api.P4ServerName; +import net.groboclown.p4.server.api.config.part.ConfigPart; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import javax.annotation.concurrent.Immutable; +import java.io.File; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import static net.groboclown.p4.server.api.util.EqualUtil.isEqual; +import static org.apache.commons.lang3.StringUtils.isBlank; + + +/** + * Stores the connection information related to a specific Perforce + * server. It only keeps track of information required to identify the + * server, which means it needs the username, password, auth ticket file, + * trust ticket file, and server fingerprint. + *

+ * Implementations must specify {@link #equals(Object)} and + * {@link #hashCode()}, to indicate whether the server connection + * properties are the same; it should not match on online mode. + *

+ * If the configuration specified a password, then it is stored externally + * in the {@link ApplicationPasswordRegistry}. This also allows for keeping + * in one place the password in case the user enters it manually, which would + * break immutability. + *

+ * This is used for running server commands that do not require a client, + * but do require a username. There are limited commands that no not require a + * username, and for those, the {@link P4ServerName} is sufficient for connectivity. + *

+ * This class MUST be immutable. + */ +@Immutable +public final class ServerConfig { + private static final Logger LOG = Logger.getInstance(ServerConfig.class); + + // Just some character that won't appear in any real text, but + // is still viewable in a debugger. + static final char SEP = (char) 0x263b; + + private static final AtomicInteger COUNT = new AtomicInteger(0); + + private final int configVersion; + private final P4ServerName serverName; + private final String username; + private final File authTicket; + private final File trustTicket; + private final String serverFingerprint; + private final String loginSso; + + private final String serverId; + + private final boolean usesPassword; + + @NotNull + static String getServerIdForDataPart(@NotNull ConfigPart part) { + StringBuilder sb = new StringBuilder(); + if (part.hasServerNameSet() && part.getServerName() != null) { + sb.append(part.getServerName().getFullPort()); + } else { + sb.append((String) null); + } + // Note: does not include password information. + sb.append(SEP) + .append(part.hasUsernameSet() ? part.getUsername() : null) + .append(SEP) + .append(part.hasAuthTicketFileSet() ? part.getAuthTicketFile() : null) + .append(SEP) + .append(part.hasTrustTicketFileSet() ? part.getTrustTicketFile() : null) + .append(SEP) + .append(part.hasServerFingerprintSet() ? part.getServerFingerprint() : null); + // These may be common enough that we want to save memory by interning the strings. + return sb.toString().intern(); + } + + + + @NotNull + public static ServerConfig createFrom(@NotNull ConfigPart part) { + return new ServerConfig(part); + } + + public static boolean isValidServerConfig(@Nullable ConfigPart part) { + if (part == null || part.hasError()) { + return false; + } + + // This should be included with the above config problems. + if (part.getServerName() == null || isBlank(part.getServerName().getFullPort())) { + return false; + } + + if (isBlank(part.getUsername())) { + return false; + } + + return true; + } + + private ServerConfig(@NotNull ConfigPart part) { + if (! isValidServerConfig(part)) { + throw new IllegalArgumentException("Did not check validity before creating"); + } + this.configVersion = COUNT.incrementAndGet(); + + assert part.hasServerNameSet(); + this.serverName = part.getServerName(); + assert part.hasUsernameSet(); + this.username = part.getUsername(); + this.authTicket = + part.hasAuthTicketFileSet() + ? part.getAuthTicketFile() + : null; + this.trustTicket = + part.hasTrustTicketFileSet() + ? part.getTrustTicketFile() + : null; + this.serverFingerprint = + part.hasServerFingerprintSet() + ? part.getServerFingerprint() + : null; + this.loginSso = + part.hasLoginSsoSet() + ? part.getLoginSso() + : null; + this.usesPassword = part.requiresUserEnteredPassword() || part.hasPasswordSet(); + + this.serverId = getServerIdForDataPart(part); + + // Must be done at the very end. + // This logic is carefully constructed to only store the password if it's supplied by the + // user, and the user does not require that it is manually entered into the UI. + // This class never stores the password in itself. + if (!part.requiresUserEnteredPassword() && part.hasPasswordSet() && part.getPlaintextPassword() != null) { + LOG.info("Storing user password in password store"); + // DEBUG + // ApplicationPasswordRegistry.getInstance().store(this, part.getPlaintextPassword().toCharArray(), false); + ApplicationPasswordRegistry instance = ApplicationPasswordRegistry.getInstance(); + String passwd = part.getPlaintextPassword(); + char[] passwdChars = passwd.toCharArray(); + instance.store(this, passwdChars, false); + } + } + + + public int getConfigVersion() { + return this.configVersion; + } + + /** + * + * @return true if the user supplies a password, either through plaintext, or through the UI asking for it. + */ + public boolean usesStoredPassword() { + return usesPassword; + } + + @NotNull + public P4ServerName getServerName() { + return serverName; + } + + @NotNull + public String getUsername() { + return username; + } + + @Nullable + public File getAuthTicket() { + return authTicket; + } + + @Nullable + public File getTrustTicket() { + return trustTicket; + } + + @Nullable + public String getServerFingerprint() { + return serverFingerprint; + } + + @Nullable + public String getLoginSso() { + return loginSso; + } + + public boolean hasServerFingerprint() { + return getServerFingerprint() != null && getServerFingerprint().length() > 0; + } + + public boolean hasAuthTicket() { + return getAuthTicket() != null; + } + + public boolean hasTrustTicket() { + return getTrustTicket() != null; + } + + public boolean hasLoginSso() { + return getLoginSso() != null; + } + + /** + * + * @return unique identifier for the server connection settings. + */ + @NotNull + public String getServerId() { + return serverId; + } + + /** + * Checks if the {@literal part} connects to the same server + * in the same way as this server configuration. For identifying + * if the server itself is the same, then compare the + * {@link P4ServerName} values. + * + * @param part configuration to compare + * @return true if the {@literal part} and this config connect to + * the same server the same way. + */ + boolean isSameServerConnection(@Nullable ConfigPart part) { + if (part == null) { + LOG.debug("isSameServerConnection: input is null"); + return false; + } + + if (! isEqual(getServerName(), part.getServerName())) { + if (LOG.isDebugEnabled()) { + if (part.getServerName() == null) { + LOG.debug("isSameServerConnection: input server name is null"); + } else { + LOG.debug("isSameServerConnection: server doesn't match: " + + getServerName().getServerPort() + "::" + getServerName().getServerProtocol() + " <> " + + part.getServerName().getServerPort() + "::" + part.getServerName().getServerProtocol()); + } + } + return false; + } + + if (! filesEqual(hasAuthTicket(), getAuthTicket(), part.hasAuthTicketFileSet(), part.getAuthTicketFile())) { + if (LOG.isDebugEnabled()) { + LOG.debug("isSameServerConnection: auth ticket doesn't match: " + + getAuthTicket() + " <> " + part.getAuthTicketFile()); + } + return false; + } + + if (! filesEqual(hasTrustTicket(), getTrustTicket(), part.hasTrustTicketFileSet(), part.getTrustTicketFile())) { + if (LOG.isDebugEnabled()) { + LOG.debug("isSameServerConnection: trust ticket doesn't match: " + + getTrustTicket() + " <> " + part.getTrustTicketFile()); + } + return false; + } + + if (hasServerFingerprint() != part.hasServerFingerprintSet()) { + if (LOG.isDebugEnabled()) { + LOG.debug("isSameServerConnection: has server fingerprint mismatch: " + + hasServerFingerprint() + " <> " + part.hasServerFingerprintSet()); + } + return false; + } + if (hasServerFingerprint() && ! isEqual(getServerFingerprint(), part.getServerFingerprint())) { + if (LOG.isDebugEnabled()) { + LOG.debug("isSameServerConnection: server fingerprint mismatch: " + + getServerFingerprint() + " <> " + part.getServerFingerprint()); + } + return false; + } + + if (! isEqual(getUsername(), part.getUsername())) { + if (LOG.isDebugEnabled()) { + LOG.debug("isSameServerConnection: username mismatch: " + + getUsername() + " <> " + part.getUsername()); + } + return false; + } + + // password usage is a bit more complex. + if ( + (usesStoredPassword() && (!part.requiresUserEnteredPassword() && !part.hasPasswordSet())) + || (!usesStoredPassword() && (part.requiresUserEnteredPassword() || part.hasPasswordSet())) + ) { + return false; + } + + return true; + } + + @NotNull + private Map toProperties() { + return ConfigPropertiesUtil.toProperties(this, "(unset)", "(empty)", "(set)"); + } + + @TestOnly + @Override + public String toString() { + return toProperties().toString(); + } + + + // equals only cares about the information that connects + // to the server, not the individual server setup. Note that + // this might have the potential to lose information. + @Override + public boolean equals(Object other) { + if (other == null) { + return false; + } + if (this == other) { + return true; + } + if (!(other instanceof ServerConfig)) { + return false; + } + ServerConfig sc = (ServerConfig) other; + return sc.getServerId().equals(getServerId()); + } + + @Override + public int hashCode() { + return getServerId().hashCode(); + } + + + private static boolean filesEqual(boolean aSet, @Nullable File aFile, boolean bSet, @Nullable File bFile) { + return FileUtil.filesEqual(scrubFile(aSet, aFile), scrubFile(bSet, bFile)); + } + + @Nullable + private static File scrubFile(boolean isSet, @Nullable File file) { + if (! isSet || file == null) { + return null; + } + if (file.exists() && file.isFile()) { + return file; + } + return null; + } +} diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4ApiException.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4ApiException.java index 13c4f2de..33b81e7c 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4ApiException.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4ApiException.java @@ -1,32 +1,32 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4.server.api.exceptions; - -import com.perforce.p4java.exception.P4JavaError; -import com.perforce.p4java.exception.P4JavaException; -import org.jetbrains.annotations.NotNull; - -/** - * An underlying problem happened with the p4java extension that we don't know how to handle. - * It indicates an error in the plugin code. - */ -public class P4ApiException extends P4ServerException { - public P4ApiException(@NotNull P4JavaError e) { - super(e, false); - } - - public P4ApiException(@NotNull P4JavaException e) { - super(e, false); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4.server.api.exceptions; + +import com.perforce.p4java.exception.P4JavaError; +import com.perforce.p4java.exception.P4JavaException; +import org.jetbrains.annotations.NotNull; + +/** + * An underlying problem happened with the p4java extension that we don't know how to handle. + * It indicates an error in the plugin code. + */ +public class P4ApiException extends P4ServerException { + public P4ApiException(@NotNull P4JavaError e) { + super(e, false); + } + + public P4ApiException(@NotNull P4JavaException e) { + super(e, false); + } +} diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4FileException.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4FileException.java index cd702862..f76b8b01 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4FileException.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4FileException.java @@ -1,37 +1,37 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4.server.api.exceptions; - -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vfs.VirtualFile; -import com.perforce.p4java.core.file.IFileSpec; -import org.jetbrains.annotations.NotNull; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public abstract class P4FileException extends P4ServerException { - private final List affectedVirtualFiles; - private final List affectedFilePaths; - private final List affectedServerFiles; - - public P4FileException(@NotNull Throwable cause, boolean isWarning, VirtualFile... affected) { - super(cause, isWarning); - affectedVirtualFiles = Collections.unmodifiableList(Arrays.asList(affected)); - affectedFilePaths = Collections.emptyList(); - affectedServerFiles = Collections.emptyList(); - } - -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4.server.api.exceptions; + +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vfs.VirtualFile; +import com.perforce.p4java.core.file.IFileSpec; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public abstract class P4FileException extends P4ServerException { + private final List affectedVirtualFiles; + private final List affectedFilePaths; + private final List affectedServerFiles; + + public P4FileException(@NotNull Throwable cause, boolean isWarning, VirtualFile... affected) { + super(cause, isWarning); + affectedVirtualFiles = Collections.unmodifiableList(Arrays.asList(affected)); + affectedFilePaths = Collections.emptyList(); + affectedServerFiles = Collections.emptyList(); + } + +} diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4ServerException.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4ServerException.java index 78afd3be..6c376eb8 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4ServerException.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4ServerException.java @@ -1,24 +1,24 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4.server.api.exceptions; - -import com.intellij.openapi.vcs.VcsException; -import org.jetbrains.annotations.NotNull; - -public abstract class P4ServerException - extends VcsException { - public P4ServerException(@NotNull Throwable t, boolean isWarning) { - super(t, isWarning); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4.server.api.exceptions; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NotNull; + +public abstract class P4ServerException + extends VcsException { + public P4ServerException(@NotNull Throwable t, boolean isWarning) { + super(t, isWarning); + } +} diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4VcsConnectionException.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4VcsConnectionException.java index 68a3cc02..680cb542 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4VcsConnectionException.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/P4VcsConnectionException.java @@ -1,52 +1,52 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4.server.api.exceptions; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.VcsConnectionProblem; -import net.groboclown.p4.server.api.ClientServerRef; -import net.groboclown.p4.server.api.messagebus.ReconnectRequestMessage; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * A problem occurred because the user was disconnected from the server. - * This is not related to the connection request being wrong. - */ -public class P4VcsConnectionException extends VcsConnectionProblem { - @Nullable - private final Project project; - - @NotNull - private final ClientServerRef ref; - - public P4VcsConnectionException(@Nullable Project project, @NotNull ClientServerRef ref) { - super("Connection to server " + ref.getServerDisplayId() + " failed"); - this.project = project; - this.ref = ref; - } - - public P4VcsConnectionException(@Nullable Project project, @NotNull ClientServerRef ref, @NotNull Throwable cause) { - this(project, ref); - initCause(cause); - } - - @Override - public boolean attemptQuickFix(boolean mayDisplayDialogs) { - if (project != null) { - ReconnectRequestMessage.requestReconnectToClient(project, ref, mayDisplayDialogs); - } - return false; - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4.server.api.exceptions; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.VcsConnectionProblem; +import net.groboclown.p4.server.api.ClientServerRef; +import net.groboclown.p4.server.api.messagebus.ReconnectRequestMessage; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A problem occurred because the user was disconnected from the server. + * This is not related to the connection request being wrong. + */ +public class P4VcsConnectionException extends VcsConnectionProblem { + @Nullable + private final Project project; + + @NotNull + private final ClientServerRef ref; + + public P4VcsConnectionException(@Nullable Project project, @NotNull ClientServerRef ref) { + super("Connection to server " + ref.getServerDisplayId() + " failed"); + this.project = project; + this.ref = ref; + } + + public P4VcsConnectionException(@Nullable Project project, @NotNull ClientServerRef ref, @NotNull Throwable cause) { + this(project, ref); + initCause(cause); + } + + @Override + public boolean attemptQuickFix(boolean mayDisplayDialogs) { + if (project != null) { + ReconnectRequestMessage.requestReconnectToClient(project, ref, mayDisplayDialogs); + } + return false; + } +} diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/VcsInterruptedException.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/VcsInterruptedException.java index 26b23716..a98ea6f7 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/VcsInterruptedException.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/exceptions/VcsInterruptedException.java @@ -1,35 +1,35 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4.server.api.exceptions; - -import com.intellij.openapi.vcs.VcsException; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.CancellationException; - -public class VcsInterruptedException extends VcsException { - public VcsInterruptedException(@NotNull InterruptedException ex) { - super(ex); - } - public VcsInterruptedException( - @NotNull @Nls(capitalization = Nls.Capitalization.Sentence) String operation, - @NotNull InterruptedException ex) { - super(operation, ex); - } - public VcsInterruptedException(@NotNull CancellationException ex) { - super(ex); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4.server.api.exceptions; + +import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.CancellationException; + +public class VcsInterruptedException extends VcsException { + public VcsInterruptedException(@NotNull InterruptedException ex) { + super(ex); + } + public VcsInterruptedException( + @NotNull @Nls(capitalization = Nls.Capitalization.Sentence) String operation, + @NotNull InterruptedException ex) { + super(operation, ex); + } + public VcsInterruptedException(@NotNull CancellationException ex) { + super(ex); + } +} diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/values/JobStatusNames.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/values/JobStatusNames.java index 7c550eca..2769a8e5 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/values/JobStatusNames.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/values/JobStatusNames.java @@ -1,33 +1,33 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4.server.api.values; - -import org.jetbrains.annotations.NotNull; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; -import java.util.Set; - -/** - * The valid job statuses that can be set for a job. - */ -@Immutable -public interface JobStatusNames { - @NotNull - Set getJobStatusNames(); - - @Nullable - JobStatus toJobStatus(String name); -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4.server.api.values; + +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import java.util.Set; + +/** + * The valid job statuses that can be set for a job. + */ +@Immutable +public interface JobStatusNames { + @NotNull + Set getJobStatusNames(); + + @Nullable + JobStatus toJobStatus(String name); +} diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/values/P4ChangelistId.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/values/P4ChangelistId.java index 60d3dd65..a4cf241c 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/values/P4ChangelistId.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/values/P4ChangelistId.java @@ -1,75 +1,75 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4.server.api.values; - -import com.intellij.openapi.vcs.history.VcsRevisionNumber; -import net.groboclown.p4.server.api.ClientServerRef; -import net.groboclown.p4.server.api.P4ServerName; -import net.groboclown.p4.server.api.config.ServerConfig; -import org.jetbrains.annotations.NotNull; - -import javax.annotation.concurrent.Immutable; - -/** - * Perforce changelists must be identified by their ID and their origin - * server. - *

- * For a changelist that is pending creation, this object will not change. Instead, when the - * creation finishes, the cached version should be refreshed to reflect the new changelist. - */ -@Immutable -public interface P4ChangelistId extends VcsRevisionNumber { - enum State { - /** The changelist is a formally created changelist on the server, and not a default changelist. */ - NUMBERED, - /** The changelist is the unnumbered, "default" changelist for the client, stored on the server. */ - DEFAULT, - /** The changelist is pending creation on the server. */ - PENDING_CREATION - } - - int getChangelistId(); - - @NotNull - P4ServerName getServerName(); - - /** - * The client that created the changelist. - * - * @return the name of the client that created the changelist. - */ - @NotNull - String getClientname(); - - @NotNull - ClientServerRef getClientServerRef(); - - @NotNull - State getState(); - - /** - * - * @return true if this is the default changelist for the client. - */ - boolean isDefaultChangelist(); - - /** - * Is this changelist in the given client? Matches on the client name - * and the server config id. - * - * @param serverConfig server to check - * @return true if the given client matches the server config and client name. - */ - boolean isIn(@NotNull ServerConfig serverConfig); -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4.server.api.values; + +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import net.groboclown.p4.server.api.ClientServerRef; +import net.groboclown.p4.server.api.P4ServerName; +import net.groboclown.p4.server.api.config.ServerConfig; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.concurrent.Immutable; + +/** + * Perforce changelists must be identified by their ID and their origin + * server. + *

+ * For a changelist that is pending creation, this object will not change. Instead, when the + * creation finishes, the cached version should be refreshed to reflect the new changelist. + */ +@Immutable +public interface P4ChangelistId extends VcsRevisionNumber { + enum State { + /** The changelist is a formally created changelist on the server, and not a default changelist. */ + NUMBERED, + /** The changelist is the unnumbered, "default" changelist for the client, stored on the server. */ + DEFAULT, + /** The changelist is pending creation on the server. */ + PENDING_CREATION + } + + int getChangelistId(); + + @NotNull + P4ServerName getServerName(); + + /** + * The client that created the changelist. + * + * @return the name of the client that created the changelist. + */ + @NotNull + String getClientname(); + + @NotNull + ClientServerRef getClientServerRef(); + + @NotNull + State getState(); + + /** + * + * @return true if this is the default changelist for the client. + */ + boolean isDefaultChangelist(); + + /** + * Is this changelist in the given client? Matches on the client name + * and the server config id. + * + * @param serverConfig server to check + * @return true if the given client matches the server config and client name. + */ + boolean isIn(@NotNull ServerConfig serverConfig); +} diff --git a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/values/P4Job.java b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/values/P4Job.java index b334a8c1..2ea4c87b 100644 --- a/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/values/P4Job.java +++ b/idea-p4server/api/src/main/java/net/groboclown/p4/server/api/values/P4Job.java @@ -1,45 +1,45 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4.server.api.values; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.annotation.concurrent.Immutable; -import java.util.Map; - -/** - * An immutable view of a Perforce Job. The details might contain additional - * whitespace that the user did not originally include when creating the job. - * To display to the end user, the {@link P4JobSpec} should be used. - * - * @see P4Job - */ -@Immutable -public interface P4Job { - @NotNull - String getJobId(); - - @NotNull - Map getRawDetails(); - - /** - * - * @return the description as stored on the server. It might have extra whitespace - * that the user did not intentionally add. - */ - @Nullable - String getDescription(); -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4.server.api.values; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.concurrent.Immutable; +import java.util.Map; + +/** + * An immutable view of a Perforce Job. The details might contain additional + * whitespace that the user did not originally include when creating the job. + * To display to the end user, the {@link P4JobSpec} should be used. + * + * @see P4Job + */ +@Immutable +public interface P4Job { + @NotNull + String getJobId(); + + @NotNull + Map getRawDetails(); + + /** + * + * @return the description as stored on the server. It might have extra whitespace + * that the user did not intentionally add. + */ + @Nullable + String getDescription(); +} diff --git a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/config/win/PreferencesWinRegistry.java b/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/config/win/PreferencesWinRegistry.java index f3d11a35..d72b6d5a 100644 --- a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/config/win/PreferencesWinRegistry.java +++ b/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/config/win/PreferencesWinRegistry.java @@ -1,279 +1,279 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package net.groboclown.p4.server.impl.config.win; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.prefs.Preferences; - - -/** - * Taken from - * http://svn.apache.org/repos/asf/incubator/npanday/trunk/components/dotnet-registry/src/main/java/npanday/registry/impl/WinRegistry.java - * It's been modified to remove the external dependencies and not allow writing to the - * registry. - * - * It takes advantage of the Preferences class' ability to inspect the Windows registry. - * Note that this may not be compatible with all Java versions. - * Indeed, this doesn't work for JDK 11. - */ -public class PreferencesWinRegistry { - public static final int HKEY_CURRENT_USER = 0x80000001; - - public static final int HKEY_LOCAL_MACHINE = 0x80000002; - - public static final int REG_SUCCESS = 0; - - private static final int KEY_ALL_ACCESS = 0xf003f; - - private static final int KEY_READ = 0x20019; - - private static Preferences userRoot = Preferences.userRoot(); - - private static Preferences systemRoot = Preferences.systemRoot(); - - private static Class userClass = userRoot.getClass(); - - private static Method regOpenKey = null; - - private static Method regCloseKey = null; - - private static Method regQueryValueEx = null; - - private static Method regEnumValue = null; - - private static Method regQueryInfoKey = null; - - private static Method regEnumKeyEx = null; - - - static { - try { - regOpenKey = userClass.getDeclaredMethod( - "WindowsRegOpenKey", - int.class, byte[].class, int.class); - regOpenKey.setAccessible(true); - regCloseKey = userClass.getDeclaredMethod("WindowsRegCloseKey", - int.class); - regCloseKey.setAccessible(true); - regQueryValueEx = userClass.getDeclaredMethod( - "WindowsRegQueryValueEx", - int.class, byte[].class); - regQueryValueEx.setAccessible(true); - regEnumValue = userClass.getDeclaredMethod( - "WindowsRegEnumValue", - int.class, int.class, int.class); - regEnumValue.setAccessible(true); - regQueryInfoKey = userClass.getDeclaredMethod("WindowsRegQueryInfoKey1", - int.class); - regQueryInfoKey.setAccessible(true); - regEnumKeyEx = userClass.getDeclaredMethod( - "WindowsRegEnumKeyEx", - int.class, int.class, int.class); - regEnumKeyEx.setAccessible(true); - } catch (NoSuchMethodException e) { - // we are not on windows, then! - } catch (Exception e) { - e.printStackTrace(); - } - } - - - public static boolean isAvailable() { - return regOpenKey != null - && regCloseKey != null - && regQueryValueEx != null - && regEnumValue != null - && regQueryInfoKey != null - && regEnumKeyEx != null; - } - - - /** - * Read a value from key and value name - * - * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE - * @param key - * @param valueName - * @return the value - * @throws IllegalArgumentException - * @throws IllegalAccessException - * @throws InvocationTargetException - */ - public static String readString(int hkey, String key, String valueName) throws - IllegalArgumentException, - IllegalAccessException, - InvocationTargetException { - if (hkey == HKEY_LOCAL_MACHINE) { - return readString(systemRoot, hkey, key, valueName); - } else if (hkey == HKEY_CURRENT_USER) { - return readString(userRoot, hkey, key, valueName); - } else { - throw new IllegalArgumentException("hkey=" + hkey); - } - } - - /** - * Read value(s) and value name(s) form given key - * - * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE - * @param key - * @return the value name(s) plus the value(s) - * @throws IllegalArgumentException - * @throws IllegalAccessException - * @throws InvocationTargetException - */ - public static Map readStringValues(int hkey, String key) throws - IllegalArgumentException, - IllegalAccessException, - InvocationTargetException { - if (hkey == HKEY_LOCAL_MACHINE) { - return readStringValues(systemRoot, hkey, key); - } else if (hkey == HKEY_CURRENT_USER) { - return readStringValues(userRoot, hkey, key); - } else { - throw new IllegalArgumentException("hkey=" + hkey); - } - } - - /** - * Read the value name(s) from a given key - * - * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE - * @param key - * @return the value name(s) - * @throws IllegalArgumentException - * @throws IllegalAccessException - * @throws InvocationTargetException - */ - public static List readStringSubKeys(int hkey, String key) throws - IllegalArgumentException, - IllegalAccessException, - InvocationTargetException { - if (hkey == HKEY_LOCAL_MACHINE) { - return readStringSubKeys(systemRoot, hkey, key); - } else if (hkey == HKEY_CURRENT_USER) { - return readStringSubKeys(userRoot, hkey, key); - } else { - throw new IllegalArgumentException("hkey=" + hkey); - } - } - - - // ===================== - - - private static String readString(Preferences root, int hkey, String key, String value) throws - IllegalArgumentException, - IllegalAccessException, - InvocationTargetException { - int[] handles = (int[]) regOpenKey.invoke( - root, new Object[]{ - hkey, toCstr(key), KEY_READ - } - ); - if (handles[1] != REG_SUCCESS) { - return null; - } - byte[] valb = (byte[]) regQueryValueEx.invoke( - root, new Object[]{ handles[0], toCstr(value)} - ); - regCloseKey.invoke(root, handles[0]); - return (valb != null ? new String(valb, Charset.defaultCharset()).trim() : null); - } - - private static Map readStringValues(Preferences root, int hkey, String key) throws - IllegalArgumentException, - IllegalAccessException, - InvocationTargetException { - HashMap results = new HashMap(); - int[] handles = (int[]) regOpenKey.invoke( - root, new Object[]{ - hkey, toCstr(key), KEY_READ - } - ); - if (handles[1] != REG_SUCCESS) { - return null; - } - int[] info = (int[]) regQueryInfoKey.invoke(root, new Object[]{ handles[0] }); - - int count = info[2]; // count - int maxlen = info[3]; // value length max - for (int index = 0; index < count; index++) { - byte[] name = (byte[]) regEnumValue.invoke( - root, new Object[] { - handles[0], index, maxlen + 1 - } - ); - if (name != null) { - String nameText = new String(name, Charset.defaultCharset()); - final String value = readString(hkey, key, nameText); - results.put(nameText.trim(), value); - } - } - regCloseKey.invoke(root, handles[0]); - return results; - } - - private static List readStringSubKeys(Preferences root, int hkey, String key) throws - IllegalArgumentException, - IllegalAccessException, - InvocationTargetException { - List results = new ArrayList(); - int[] handles = (int[]) regOpenKey.invoke( - root, new Object[]{ - hkey, toCstr(key), KEY_READ - } - ); - if (handles[1] != REG_SUCCESS) { - return null; - } - int[] info = (int[]) regQueryInfoKey.invoke(root, new Object[]{ handles[0] }); - - int count = info[0]; // count - int maxlen = info[3]; // value length max - for (int index = 0; index < count; index++) { - byte[] name = (byte[]) regEnumKeyEx.invoke( - root, new Object[]{ - handles[0], index, maxlen + 1 - } - ); - results.add(new String(name, Charset.defaultCharset()).trim()); - } - regCloseKey.invoke(root, handles[0]); - return results; - } - - - // utility - private static byte[] toCstr(String str) { - byte[] result = new byte[str.length() + 1]; - - for (int i = 0; i < str.length(); i++) { - result[i] = (byte) str.charAt(i); - } - result[str.length()] = 0; - return result; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package net.groboclown.p4.server.impl.config.win; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.prefs.Preferences; + + +/** + * Taken from + * http://svn.apache.org/repos/asf/incubator/npanday/trunk/components/dotnet-registry/src/main/java/npanday/registry/impl/WinRegistry.java + * It's been modified to remove the external dependencies and not allow writing to the + * registry. + * + * It takes advantage of the Preferences class' ability to inspect the Windows registry. + * Note that this may not be compatible with all Java versions. + * Indeed, this doesn't work for JDK 11. + */ +public class PreferencesWinRegistry { + public static final int HKEY_CURRENT_USER = 0x80000001; + + public static final int HKEY_LOCAL_MACHINE = 0x80000002; + + public static final int REG_SUCCESS = 0; + + private static final int KEY_ALL_ACCESS = 0xf003f; + + private static final int KEY_READ = 0x20019; + + private static Preferences userRoot = Preferences.userRoot(); + + private static Preferences systemRoot = Preferences.systemRoot(); + + private static Class userClass = userRoot.getClass(); + + private static Method regOpenKey = null; + + private static Method regCloseKey = null; + + private static Method regQueryValueEx = null; + + private static Method regEnumValue = null; + + private static Method regQueryInfoKey = null; + + private static Method regEnumKeyEx = null; + + + static { + try { + regOpenKey = userClass.getDeclaredMethod( + "WindowsRegOpenKey", + int.class, byte[].class, int.class); + regOpenKey.setAccessible(true); + regCloseKey = userClass.getDeclaredMethod("WindowsRegCloseKey", + int.class); + regCloseKey.setAccessible(true); + regQueryValueEx = userClass.getDeclaredMethod( + "WindowsRegQueryValueEx", + int.class, byte[].class); + regQueryValueEx.setAccessible(true); + regEnumValue = userClass.getDeclaredMethod( + "WindowsRegEnumValue", + int.class, int.class, int.class); + regEnumValue.setAccessible(true); + regQueryInfoKey = userClass.getDeclaredMethod("WindowsRegQueryInfoKey1", + int.class); + regQueryInfoKey.setAccessible(true); + regEnumKeyEx = userClass.getDeclaredMethod( + "WindowsRegEnumKeyEx", + int.class, int.class, int.class); + regEnumKeyEx.setAccessible(true); + } catch (NoSuchMethodException e) { + // we are not on windows, then! + } catch (Exception e) { + e.printStackTrace(); + } + } + + + public static boolean isAvailable() { + return regOpenKey != null + && regCloseKey != null + && regQueryValueEx != null + && regEnumValue != null + && regQueryInfoKey != null + && regEnumKeyEx != null; + } + + + /** + * Read a value from key and value name + * + * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE + * @param key + * @param valueName + * @return the value + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public static String readString(int hkey, String key, String valueName) throws + IllegalArgumentException, + IllegalAccessException, + InvocationTargetException { + if (hkey == HKEY_LOCAL_MACHINE) { + return readString(systemRoot, hkey, key, valueName); + } else if (hkey == HKEY_CURRENT_USER) { + return readString(userRoot, hkey, key, valueName); + } else { + throw new IllegalArgumentException("hkey=" + hkey); + } + } + + /** + * Read value(s) and value name(s) form given key + * + * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE + * @param key + * @return the value name(s) plus the value(s) + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public static Map readStringValues(int hkey, String key) throws + IllegalArgumentException, + IllegalAccessException, + InvocationTargetException { + if (hkey == HKEY_LOCAL_MACHINE) { + return readStringValues(systemRoot, hkey, key); + } else if (hkey == HKEY_CURRENT_USER) { + return readStringValues(userRoot, hkey, key); + } else { + throw new IllegalArgumentException("hkey=" + hkey); + } + } + + /** + * Read the value name(s) from a given key + * + * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE + * @param key + * @return the value name(s) + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public static List readStringSubKeys(int hkey, String key) throws + IllegalArgumentException, + IllegalAccessException, + InvocationTargetException { + if (hkey == HKEY_LOCAL_MACHINE) { + return readStringSubKeys(systemRoot, hkey, key); + } else if (hkey == HKEY_CURRENT_USER) { + return readStringSubKeys(userRoot, hkey, key); + } else { + throw new IllegalArgumentException("hkey=" + hkey); + } + } + + + // ===================== + + + private static String readString(Preferences root, int hkey, String key, String value) throws + IllegalArgumentException, + IllegalAccessException, + InvocationTargetException { + int[] handles = (int[]) regOpenKey.invoke( + root, new Object[]{ + hkey, toCstr(key), KEY_READ + } + ); + if (handles[1] != REG_SUCCESS) { + return null; + } + byte[] valb = (byte[]) regQueryValueEx.invoke( + root, new Object[]{ handles[0], toCstr(value)} + ); + regCloseKey.invoke(root, handles[0]); + return (valb != null ? new String(valb, Charset.defaultCharset()).trim() : null); + } + + private static Map readStringValues(Preferences root, int hkey, String key) throws + IllegalArgumentException, + IllegalAccessException, + InvocationTargetException { + HashMap results = new HashMap(); + int[] handles = (int[]) regOpenKey.invoke( + root, new Object[]{ + hkey, toCstr(key), KEY_READ + } + ); + if (handles[1] != REG_SUCCESS) { + return null; + } + int[] info = (int[]) regQueryInfoKey.invoke(root, new Object[]{ handles[0] }); + + int count = info[2]; // count + int maxlen = info[3]; // value length max + for (int index = 0; index < count; index++) { + byte[] name = (byte[]) regEnumValue.invoke( + root, new Object[] { + handles[0], index, maxlen + 1 + } + ); + if (name != null) { + String nameText = new String(name, Charset.defaultCharset()); + final String value = readString(hkey, key, nameText); + results.put(nameText.trim(), value); + } + } + regCloseKey.invoke(root, handles[0]); + return results; + } + + private static List readStringSubKeys(Preferences root, int hkey, String key) throws + IllegalArgumentException, + IllegalAccessException, + InvocationTargetException { + List results = new ArrayList(); + int[] handles = (int[]) regOpenKey.invoke( + root, new Object[]{ + hkey, toCstr(key), KEY_READ + } + ); + if (handles[1] != REG_SUCCESS) { + return null; + } + int[] info = (int[]) regQueryInfoKey.invoke(root, new Object[]{ handles[0] }); + + int count = info[0]; // count + int maxlen = info[3]; // value length max + for (int index = 0; index < count; index++) { + byte[] name = (byte[]) regEnumKeyEx.invoke( + root, new Object[]{ + handles[0], index, maxlen + 1 + } + ); + results.add(new String(name, Charset.defaultCharset()).trim()); + } + regCloseKey.invoke(root, handles[0]); + return results; + } + + + // utility + private static byte[] toCstr(String str) { + byte[] result = new byte[str.length() + 1]; + + for (int i = 0; i < str.length(); i++) { + result[i] = (byte) str.charAt(i); + } + result[str.length()] = 0; + return result; + } +} diff --git a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/ignore/IgnoreFilePattern.java b/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/ignore/IgnoreFilePattern.java index ba5fb5a9..0737b53a 100644 --- a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/ignore/IgnoreFilePattern.java +++ b/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/ignore/IgnoreFilePattern.java @@ -1,621 +1,621 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4.server.impl.ignore; - -import com.intellij.openapi.util.SystemInfo; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.Reader; -import java.util.ArrayList; -import java.util.List; - -/** - * Represents a single line from an ignore file. - */ -public class IgnoreFilePattern { - private static final String[] EMPTY = new String[0]; - - private final String basePattern; - private final PathPart rootPart; - private final boolean isNegative; - - - // the parse file is contained in this class to keep the parse logic for the file in one - // place. - static List parseFile(@NotNull Reader reader) throws IOException { - BufferedReader lineReader = - reader instanceof BufferedReader - ? (BufferedReader) reader - : new BufferedReader(reader); - // Note: there is now a "p4 ignores" command, and "p4 ignore" is now - // also built into the API for newer servers. For caching and UI display, we still need this. - - // File format: - // (from https://www.perforce.com/perforce/r15.2/manuals/cmdref/P4IGNORE.html) - // (Clarifications at http://forums.perforce.com/index.php?/topic/4492-new-p4-ignore-functionality/page__gopid__18940#entry18940) - // The syntax for ignore rules is not the same as Perforce syntax. Instead, it is similar to that used - // by other versioning systems: - // - // * Rules are specified using local filepath syntax. Unix style paths will work on Windows for cross - // platform file support. - // - // * A # character at the beginning of a line denotes a comment - // - // * A ! character at the beginning of a line line excludes the file specification.These exclusions override rules - // defined above it in the P4IGNORE file, but may be overridden by later rules. - // - // * A / (or \ on Windows) character at the beginning of a line causes the file specification to be - // considered relative to the P4IGNORE file. This is useful when the rule must apply to files at - // particular depots of the directory tree. - // - // * A / (or \ on Windows) character at the end of a line causes the file specification to only match - // directories, and not files of the same name. - // - // * The * wildcard matches substrings. Like the Perforce wildcard equivalent, it does not match path - // separators; however, if it is not used as part of a path, the directory scanning nature of the rule may - // make it appear to perform like the Perforce "..." wildcard. - // - // * The ** wildcard matches substrings including path separators. It is equivalent to the Perforce "..." - // wildcard, which is not permitted. (Note: the "**" must match at least 1 wild card) - // - // For example: - // - // # Ignore.p4ignore files - // .p4ignore - // - // # Ignore object files, shared libraries, executables - // *.dll - // *.so - // *.exe - // *.o - // - // # Ignore all HTML files except the readme file - // *.html - // !readme.html - // - // # Ignore the bin directory - // bin/ - // - // # Ignore the build.properties file in this directory - // /build.properties - // - // # Ignore all text files in test directories - // test/**.txt - // - - - // This description unfortunately does not describe these circumstances: - // - if a "!" is found after the matcher, is it still a match? - // (does the first match force a result?) - // TODO It looks like last match? - // - if a path is specified ("temp/*.tmp"), is it relative to the - // ignore file? Assuming not; as per the .gitignore format - // (if a file matcher starts with a '/', then it is relative to - // the ignore files). - - List ret = new ArrayList<>(); - - String line; - while ((line = lineReader.readLine()) != null) { - IgnoreFilePattern ifp = parseLinePattern(line); - if (ifp != null) { - ret.add(ifp); - } - } - return ret; - } - - @Nullable - static IgnoreFilePattern parseLinePattern(final String line) { - String pattern = line.trim(); - if (pattern.length() <= 0 || pattern.charAt(0) == '#') { - return null; - } - boolean isNegative = false; - if (pattern.charAt(0) == '!' && pattern.length() > 1) { - isNegative = true; - pattern = pattern.substring(1).trim(); - } - if (pattern.length() > 0) { - return new IgnoreFilePattern(line, createPattern(pattern), isNegative); - } - return null; - } - - - IgnoreFilePattern(@NotNull PathPart rootPart, boolean isNegative) { - this(null, rootPart, isNegative); - } - - - IgnoreFilePattern(@Nullable String original, @NotNull PathPart rootPart, boolean isNegative) { - this.basePattern = original; - this.rootPart = rootPart; - this.isNegative = isNegative; - } - - - public boolean isIgnoreMatchType() { - return ! isNegative; - } - - @Override - public String toString() { - return basePattern; - } - - - /** - * - * @param pathParts the path of the file to check, relative to the source ignore file. Each - * path element is its own item in the list. - * @return true if the file matches this pattern; it does not mean that it's ignored. - */ - public boolean matches(final List pathParts) { - PathPart next = rootPart; - for (String pathPart: pathParts) { - if (next == null) { - return false; - } - PathNameMatchResult result = next.match(pathPart); - if (result.isLastElementMatch) { - return true; - } - if (!result.isMatch) { - return false; - } - next = result.next; - } - return false; - } - - private enum ParseState { - END_PATH, - STAR_PATH, - JUST_FOUND_STAR_AT_END, - JUST_FOUND_STAR_IN_MIDDLE, - JUST_FOUND_STAR_STAR_AT_END, - JUST_FOUND_STAR_STAR_IN_MIDDLE, - TEXT_PATH - } - - - @NotNull - static PathPart createPattern(@NotNull final String pattern) { - if (pattern.length() <= 0) { - throw new IllegalArgumentException("pattern cannot be empty"); - } - - // Split the pattern based on the path separator. From there, - // parse each part based on the '*' inclusion. - - // In order to make the splitting easier for us, just change separators to a single format. - char[] pat = pattern.replace('\\', '/').replace(File.separatorChar, '/').toCharArray(); - int lastPos = pat.length - 1; - - // Special case: if the pattern starts with a '/', then start it with a StarStar matcher. - boolean isAbsolute = false; - int startPos = 0; - while (startPos <= lastPos && pat[startPos] == '/' ) { - isAbsolute = true; - startPos++; - } - - // work backwards through the pattern to assemble the parts with the next wiring correctly. - PathPart ret = null; - - ParseState state = ParseState.END_PATH; - // Special case: trailing slash means an implicit directory. - { - boolean isDir = false; - while (lastPos > startPos && pat[lastPos] == '/') { - isDir = true; - lastPos--; - } - if (isDir) { - ret = new StrictStarStarPart(null); - } - } - - List pieces = new ArrayList<>(); - int lastPartPos = lastPos; - while (lastPos >= startPos) { - char c = pat[lastPos]; - switch (state) { - case END_PATH: - if (c == '/') { - // double slashes - count as just one slash. - // adjust the positions to count this as the new end. - // don't need to adjust the pieces or state. - lastPartPos = lastPos; - } else if (c == '*') { - pieces.add(0, "*"); - lastPartPos = lastPos; - state = ParseState.JUST_FOUND_STAR_AT_END; - } else { - state = ParseState.TEXT_PATH; - } - break; - case JUST_FOUND_STAR_AT_END: - if (c == '*') { - // double star at end. - ret = new StrictStarStarPart(ret); - lastPartPos = lastPos; - pieces.clear(); - state = ParseState.JUST_FOUND_STAR_STAR_AT_END; - } else if (c == '/') { - ret = new SingleAnyMatchPart(ret); - pieces.clear(); - lastPartPos = lastPos; - state = ParseState.END_PATH; - } else { - state = ParseState.STAR_PATH; - } - break; - case JUST_FOUND_STAR_IN_MIDDLE: - if (c == '*') { - // found a star star, with stuff after it. - // keep the stuff after it, marking it as a *a type. - ret = new StarMatchPart(ret, pieces.toArray(EMPTY), isFileSystemCaseSensitive()); - lastPartPos = lastPos; - pieces.clear(); - ret = new StrictStarStarPart(ret); - state = ParseState.JUST_FOUND_STAR_STAR_IN_MIDDLE; - } else if (c == '/') { - ret = new StarMatchPart(ret, pieces.toArray(EMPTY), isFileSystemCaseSensitive()); - lastPartPos = lastPos; - pieces.clear(); - state = ParseState.END_PATH; - } else { - state = ParseState.STAR_PATH; - } - break; - case JUST_FOUND_STAR_STAR_AT_END: - case JUST_FOUND_STAR_STAR_IN_MIDDLE: - if (c == '*') { - // ***, which is weird. But okay. - pieces.add(0, "*"); - lastPartPos = lastPos; - state = ParseState.JUST_FOUND_STAR_AT_END; - } else if (c == '/') { - // only star star in path. - lastPartPos = lastPos; - pieces.clear(); - state = ParseState.END_PATH; - } else { - // counts as a* format. - lastPartPos = lastPos; - pieces.add(0, "*"); - state = ParseState.STAR_PATH; - } - break; - case STAR_PATH: - if (c == '*') { - pieces.add(0, new String(pat, lastPos + 1, lastPartPos - lastPos - 1)); - pieces.add(0, "*"); - lastPartPos = lastPos; - state = ParseState.JUST_FOUND_STAR_IN_MIDDLE; - } else if (c == '/') { - pieces.add(0, new String(pat, lastPos, lastPartPos - lastPos - 1)); - ret = new StarMatchPart(ret, pieces.toArray(EMPTY), isFileSystemCaseSensitive()); - lastPartPos = lastPos; - pieces.clear(); - state = ParseState.END_PATH; - } - // else just keep going. - break; - case TEXT_PATH: - if (c == '*') { - pieces.add(0, new String(pat, lastPos + 1, lastPartPos - lastPos)); - pieces.add(0, "*"); - lastPartPos = lastPos; - state = ParseState.JUST_FOUND_STAR_IN_MIDDLE; - } else if (c == '/') { - ret = new ExactMatchPart(ret, - new String(pat, lastPos + 1, lastPartPos - lastPos), - isFileSystemCaseSensitive()); - lastPartPos = lastPos; - pieces.clear(); - state = ParseState.END_PATH; - } - // else just keep going - break; - } - lastPos--; - } - switch (state) { - case JUST_FOUND_STAR_AT_END: - ret = new SingleAnyMatchPart(ret); - break; - case JUST_FOUND_STAR_IN_MIDDLE: - ret = new StarMatchPart(ret, pieces.toArray(EMPTY), isFileSystemCaseSensitive()); - break; - case STAR_PATH: - pieces.add(0, new String(pat, startPos, lastPartPos - startPos)); - ret = new StarMatchPart(ret, pieces.toArray(EMPTY), isFileSystemCaseSensitive()); - break; - case TEXT_PATH: - ret = new ExactMatchPart(ret, new String(pat, startPos, lastPartPos - startPos), - isFileSystemCaseSensitive()); - break; - case JUST_FOUND_STAR_STAR_AT_END: - case JUST_FOUND_STAR_STAR_IN_MIDDLE: - case END_PATH: - // do nothing - break; - } - if ((!isAbsolute && !(ret instanceof StrictStarStarPart)) || (isAbsolute && ret == null)) { - ret = new StrictStarStarPart(ret); - } - if (ret == null) { - // Weird state. Allow it, though. - ret = new ExactMatchPart(null, "", isFileSystemCaseSensitive()); - } - return ret; - } - - - private static boolean isFileSystemCaseSensitive() { - // This isn't 100% true - you can configure windows file system to be case sensitive. - return !SystemInfo.isWindows; - } - - static class PathNameMatchResult { - final boolean isMatch; - final boolean requiresMore; - final boolean isLastElementMatch; - final PathPart next; - - PathNameMatchResult(boolean isMatch, @Nullable PathPart next) { - this(isMatch, next != null, next); - } - - PathNameMatchResult(boolean isMatch, boolean requiresMore, @Nullable PathPart next) { - this.isMatch = isMatch; - this.requiresMore = requiresMore; - this.next = next; - this.isLastElementMatch = isMatch && (next == null || !requiresMore); - } - } - - private static final PathNameMatchResult NOT_MATCH = new PathNameMatchResult(false, false, null); - - abstract static class PathPart { - final PathPart next; - - protected PathPart(@Nullable PathPart next) { - this.next = next; - } - - @NotNull - abstract PathNameMatchResult match(@NotNull String name); - } - - - // Matching patterns: - // if a pattern ends with a '/', then use StrictStarStarPart with no next. - // /a/ -> ExactMatchPart - // /*/ -> SingleAnyMatchPart - // /**/ -> StrictStarStarPart - // /a**b/ -> /a*/**/*b/ -> StarMatchPart, StrictStarStarPart, StarMatchPart - // /a**/ -> /a*/**/ -> StarMatchPart, StrictStarStarPart - // /**b/ -> /**/*b/ -> StrictStarStarPart, StarMatchPart - // /a*/ -> StarMatchPart - // /*b/ -> StarMatchPart - // /a*b/ -> StarMatchPart - // /*a*/ -> StarMatchPart - - - static class SingleAnyMatchPart extends PathPart { - protected SingleAnyMatchPart(@Nullable PathPart next) { - super(next); - } - - @NotNull - @Override - PathNameMatchResult match(@NotNull String name) { - return new PathNameMatchResult(true, next); - } - } - - - static class ExactMatchPart extends PathPart { - private final String match; - private final boolean caseInsensitive; - - protected ExactMatchPart(@Nullable PathPart next, @NotNull String match, boolean caseInsensitive) { - super(next); - this.match = match; - this.caseInsensitive = caseInsensitive; - } - - @NotNull - @Override - PathNameMatchResult match(@NotNull String name) { - boolean isMatch = caseInsensitive ? name.equalsIgnoreCase(match) : name.equals(match); - return isMatch ? new PathNameMatchResult(true, next) : NOT_MATCH; - } - } - - // If P4 ignore ever learns character sets ([ab] meaning a or b), then this will need to take something - // other than String for the matches. - static class StarMatchPart extends PathPart { - final String[] matches; - private final boolean caseInsensitive; - - // matches implies a '*' at the end, but it isn't there. - StarMatchPart(@Nullable PathPart next, @NotNull String[] matches, boolean caseInsensitive) { - super(next); - if (matches.length < 2) { - throw new IllegalArgumentException("matches must have at least one star and one non-star"); - } - this.matches = matches; - this.caseInsensitive = caseInsensitive; - if (caseInsensitive) { - for (int i = 0; i < matches.length; i++) { - matches[i] = matches[i].toLowerCase(); - } - } - } - - @NotNull - @Override - PathNameMatchResult match(@NotNull String name) { - String remaining = caseInsensitive ? name.toLowerCase() : name; - return isRecursiveMatches(0, matches.length - 1, remaining) - ? new PathNameMatchResult(true, next) - : NOT_MATCH; - } - - - boolean isRecursiveMatches(int startMatchPos, int endMatchPos, String remaining) { - assert startMatchPos >= 0; - assert endMatchPos < matches.length; - assert startMatchPos <= endMatchPos; - - // peel away until we get to a non-star. - // - If match is [a, *], the hasStartStar loop will first not increment startMatchPos. - // It will see that remaining starts with the 'a' (if not, quit), so increment to the - // '*'. This equals the endMatchPos, but we'll keep looping. - // The second loop will match a star, and keep looking. Now the startMatchPos > end, - // so it will quit with a yes. - // - If match is [*, a], the hasStartStar will just stop with a hasStartStar = true. - // Then, the hasEndStar will find a non-star at the end, find that remaining - // matches the end string (if not, quit), so decrement the end by one (start == end now), and loop again. - // The second hasEndStar loop will find a star, decrement the end (start > end now), and quit with a yes. - // - If the match is [*, a, *], the hasStartStar will end with startMatchPos = 1, and hasStartStar = true. - // The hasEndStar will end with endMatchPos = 1, and hasEndStar = true. - // - If the match is [a, *, b, *] or [*, b, *, a], then it's essentially just like the [*, a, *] pattern. - // - If the match is [*, a, *, b, *], or something more complicated, now we're in trouble. The - // "complicated" section enters with start pointing at "a" and end pointing at "b". - // Now we need to recurse for each "a" and "b" position within the remaining string, where the - // "a" position < "b" position. - - - boolean hasStartStar = false; - while (!hasStartStar) { - while (startMatchPos <= endMatchPos && "*".equals(matches[startMatchPos])) { - startMatchPos++; - hasStartStar = true; - } - if (startMatchPos > endMatchPos) { - // It's just stars - return true; - } - if (!hasStartStar) { - if (!remaining.startsWith(matches[startMatchPos])) { - return false; - } - remaining = remaining.substring(matches[startMatchPos].length()); - startMatchPos++; - } - } - - // it's possible at this point to have start pos == end pos. - - boolean hasEndStar = false; - while (!hasEndStar) { - while (endMatchPos >= startMatchPos && "*".equals(matches[endMatchPos])) { - endMatchPos--; - hasEndStar = true; - } - if (startMatchPos > endMatchPos) { - // It's just stars - return true; - } - if (!hasEndStar) { - if (!remaining.endsWith(matches[endMatchPos])) { - return false; - } - remaining = remaining.substring(0, remaining.length() - matches[endMatchPos].length()); - endMatchPos--; - } - } - - // Easy out. Stars surrounding a single string. - if (startMatchPos == endMatchPos) { - // Stars surrounding a single text bit. - return remaining.contains(matches[startMatchPos]); - } - - // Complicated. - // Find all the indexes of the startMatchPos within remaining, and find all the indexes of the - // endMatchPos within remaining. Then, run a recurse match on each combination where - // start index < end index. - - List startMatchIndicies = matchPositions(remaining, matches[startMatchPos]); - List endMatchIndicies = matchPositions(remaining, matches[endMatchPos]); - for (int si = 0; si < startMatchIndicies.size(); si++) { - // Point the starting string index to where the match completes. - int sip = startMatchIndicies.get(si) + matches[startMatchPos].length(); - for (int ei = 0; ei < endMatchIndicies.size(); ei++) { - // Point the ending string index to where the match starts. - int eip = endMatchIndicies.get(ei); - if (sip <= eip) { - if (isRecursiveMatches(startMatchPos + 1, endMatchPos - 1, - remaining.substring(sip, eip))) { - return true; - } - } - } - } - return false; - } - - - List matchPositions(String str, String match) { - int len = str.length(); - List ret = new ArrayList<>(len); - int p = 0; - while (p < len) { - int mp = str.indexOf(match, p); - if (mp >= p) { - ret.add(mp); - p = mp + 1; - } else { - break; - } - } - return ret; - } - } - - /** slash '**' slash. Note that anything that has something '**' something can be split into this and others. */ - static class StrictStarStarPart extends PathPart { - StrictStarStarPart(@Nullable PathPart next) { - super(next); - } - - @NotNull - @Override - PathNameMatchResult match(@NotNull String name) { - if (next == null) { - // Any child will do - return new PathNameMatchResult(true, false, this); - } - // Totally non-greedy match. - PathNameMatchResult nextMatch = next.match(name); - if (nextMatch.isMatch) { - return nextMatch; - } - // Because we have a next-match, this requires another path element. - return new PathNameMatchResult(true, true, this); - } - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4.server.impl.ignore; + +import com.intellij.openapi.util.SystemInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a single line from an ignore file. + */ +public class IgnoreFilePattern { + private static final String[] EMPTY = new String[0]; + + private final String basePattern; + private final PathPart rootPart; + private final boolean isNegative; + + + // the parse file is contained in this class to keep the parse logic for the file in one + // place. + static List parseFile(@NotNull Reader reader) throws IOException { + BufferedReader lineReader = + reader instanceof BufferedReader + ? (BufferedReader) reader + : new BufferedReader(reader); + // Note: there is now a "p4 ignores" command, and "p4 ignore" is now + // also built into the API for newer servers. For caching and UI display, we still need this. + + // File format: + // (from https://www.perforce.com/perforce/r15.2/manuals/cmdref/P4IGNORE.html) + // (Clarifications at http://forums.perforce.com/index.php?/topic/4492-new-p4-ignore-functionality/page__gopid__18940#entry18940) + // The syntax for ignore rules is not the same as Perforce syntax. Instead, it is similar to that used + // by other versioning systems: + // + // * Rules are specified using local filepath syntax. Unix style paths will work on Windows for cross + // platform file support. + // + // * A # character at the beginning of a line denotes a comment + // + // * A ! character at the beginning of a line line excludes the file specification.These exclusions override rules + // defined above it in the P4IGNORE file, but may be overridden by later rules. + // + // * A / (or \ on Windows) character at the beginning of a line causes the file specification to be + // considered relative to the P4IGNORE file. This is useful when the rule must apply to files at + // particular depots of the directory tree. + // + // * A / (or \ on Windows) character at the end of a line causes the file specification to only match + // directories, and not files of the same name. + // + // * The * wildcard matches substrings. Like the Perforce wildcard equivalent, it does not match path + // separators; however, if it is not used as part of a path, the directory scanning nature of the rule may + // make it appear to perform like the Perforce "..." wildcard. + // + // * The ** wildcard matches substrings including path separators. It is equivalent to the Perforce "..." + // wildcard, which is not permitted. (Note: the "**" must match at least 1 wild card) + // + // For example: + // + // # Ignore.p4ignore files + // .p4ignore + // + // # Ignore object files, shared libraries, executables + // *.dll + // *.so + // *.exe + // *.o + // + // # Ignore all HTML files except the readme file + // *.html + // !readme.html + // + // # Ignore the bin directory + // bin/ + // + // # Ignore the build.properties file in this directory + // /build.properties + // + // # Ignore all text files in test directories + // test/**.txt + // + + + // This description unfortunately does not describe these circumstances: + // - if a "!" is found after the matcher, is it still a match? + // (does the first match force a result?) + // TODO It looks like last match? + // - if a path is specified ("temp/*.tmp"), is it relative to the + // ignore file? Assuming not; as per the .gitignore format + // (if a file matcher starts with a '/', then it is relative to + // the ignore files). + + List ret = new ArrayList<>(); + + String line; + while ((line = lineReader.readLine()) != null) { + IgnoreFilePattern ifp = parseLinePattern(line); + if (ifp != null) { + ret.add(ifp); + } + } + return ret; + } + + @Nullable + static IgnoreFilePattern parseLinePattern(final String line) { + String pattern = line.trim(); + if (pattern.length() <= 0 || pattern.charAt(0) == '#') { + return null; + } + boolean isNegative = false; + if (pattern.charAt(0) == '!' && pattern.length() > 1) { + isNegative = true; + pattern = pattern.substring(1).trim(); + } + if (pattern.length() > 0) { + return new IgnoreFilePattern(line, createPattern(pattern), isNegative); + } + return null; + } + + + IgnoreFilePattern(@NotNull PathPart rootPart, boolean isNegative) { + this(null, rootPart, isNegative); + } + + + IgnoreFilePattern(@Nullable String original, @NotNull PathPart rootPart, boolean isNegative) { + this.basePattern = original; + this.rootPart = rootPart; + this.isNegative = isNegative; + } + + + public boolean isIgnoreMatchType() { + return ! isNegative; + } + + @Override + public String toString() { + return basePattern; + } + + + /** + * + * @param pathParts the path of the file to check, relative to the source ignore file. Each + * path element is its own item in the list. + * @return true if the file matches this pattern; it does not mean that it's ignored. + */ + public boolean matches(final List pathParts) { + PathPart next = rootPart; + for (String pathPart: pathParts) { + if (next == null) { + return false; + } + PathNameMatchResult result = next.match(pathPart); + if (result.isLastElementMatch) { + return true; + } + if (!result.isMatch) { + return false; + } + next = result.next; + } + return false; + } + + private enum ParseState { + END_PATH, + STAR_PATH, + JUST_FOUND_STAR_AT_END, + JUST_FOUND_STAR_IN_MIDDLE, + JUST_FOUND_STAR_STAR_AT_END, + JUST_FOUND_STAR_STAR_IN_MIDDLE, + TEXT_PATH + } + + + @NotNull + static PathPart createPattern(@NotNull final String pattern) { + if (pattern.length() <= 0) { + throw new IllegalArgumentException("pattern cannot be empty"); + } + + // Split the pattern based on the path separator. From there, + // parse each part based on the '*' inclusion. + + // In order to make the splitting easier for us, just change separators to a single format. + char[] pat = pattern.replace('\\', '/').replace(File.separatorChar, '/').toCharArray(); + int lastPos = pat.length - 1; + + // Special case: if the pattern starts with a '/', then start it with a StarStar matcher. + boolean isAbsolute = false; + int startPos = 0; + while (startPos <= lastPos && pat[startPos] == '/' ) { + isAbsolute = true; + startPos++; + } + + // work backwards through the pattern to assemble the parts with the next wiring correctly. + PathPart ret = null; + + ParseState state = ParseState.END_PATH; + // Special case: trailing slash means an implicit directory. + { + boolean isDir = false; + while (lastPos > startPos && pat[lastPos] == '/') { + isDir = true; + lastPos--; + } + if (isDir) { + ret = new StrictStarStarPart(null); + } + } + + List pieces = new ArrayList<>(); + int lastPartPos = lastPos; + while (lastPos >= startPos) { + char c = pat[lastPos]; + switch (state) { + case END_PATH: + if (c == '/') { + // double slashes - count as just one slash. + // adjust the positions to count this as the new end. + // don't need to adjust the pieces or state. + lastPartPos = lastPos; + } else if (c == '*') { + pieces.add(0, "*"); + lastPartPos = lastPos; + state = ParseState.JUST_FOUND_STAR_AT_END; + } else { + state = ParseState.TEXT_PATH; + } + break; + case JUST_FOUND_STAR_AT_END: + if (c == '*') { + // double star at end. + ret = new StrictStarStarPart(ret); + lastPartPos = lastPos; + pieces.clear(); + state = ParseState.JUST_FOUND_STAR_STAR_AT_END; + } else if (c == '/') { + ret = new SingleAnyMatchPart(ret); + pieces.clear(); + lastPartPos = lastPos; + state = ParseState.END_PATH; + } else { + state = ParseState.STAR_PATH; + } + break; + case JUST_FOUND_STAR_IN_MIDDLE: + if (c == '*') { + // found a star star, with stuff after it. + // keep the stuff after it, marking it as a *a type. + ret = new StarMatchPart(ret, pieces.toArray(EMPTY), isFileSystemCaseSensitive()); + lastPartPos = lastPos; + pieces.clear(); + ret = new StrictStarStarPart(ret); + state = ParseState.JUST_FOUND_STAR_STAR_IN_MIDDLE; + } else if (c == '/') { + ret = new StarMatchPart(ret, pieces.toArray(EMPTY), isFileSystemCaseSensitive()); + lastPartPos = lastPos; + pieces.clear(); + state = ParseState.END_PATH; + } else { + state = ParseState.STAR_PATH; + } + break; + case JUST_FOUND_STAR_STAR_AT_END: + case JUST_FOUND_STAR_STAR_IN_MIDDLE: + if (c == '*') { + // ***, which is weird. But okay. + pieces.add(0, "*"); + lastPartPos = lastPos; + state = ParseState.JUST_FOUND_STAR_AT_END; + } else if (c == '/') { + // only star star in path. + lastPartPos = lastPos; + pieces.clear(); + state = ParseState.END_PATH; + } else { + // counts as a* format. + lastPartPos = lastPos; + pieces.add(0, "*"); + state = ParseState.STAR_PATH; + } + break; + case STAR_PATH: + if (c == '*') { + pieces.add(0, new String(pat, lastPos + 1, lastPartPos - lastPos - 1)); + pieces.add(0, "*"); + lastPartPos = lastPos; + state = ParseState.JUST_FOUND_STAR_IN_MIDDLE; + } else if (c == '/') { + pieces.add(0, new String(pat, lastPos, lastPartPos - lastPos - 1)); + ret = new StarMatchPart(ret, pieces.toArray(EMPTY), isFileSystemCaseSensitive()); + lastPartPos = lastPos; + pieces.clear(); + state = ParseState.END_PATH; + } + // else just keep going. + break; + case TEXT_PATH: + if (c == '*') { + pieces.add(0, new String(pat, lastPos + 1, lastPartPos - lastPos)); + pieces.add(0, "*"); + lastPartPos = lastPos; + state = ParseState.JUST_FOUND_STAR_IN_MIDDLE; + } else if (c == '/') { + ret = new ExactMatchPart(ret, + new String(pat, lastPos + 1, lastPartPos - lastPos), + isFileSystemCaseSensitive()); + lastPartPos = lastPos; + pieces.clear(); + state = ParseState.END_PATH; + } + // else just keep going + break; + } + lastPos--; + } + switch (state) { + case JUST_FOUND_STAR_AT_END: + ret = new SingleAnyMatchPart(ret); + break; + case JUST_FOUND_STAR_IN_MIDDLE: + ret = new StarMatchPart(ret, pieces.toArray(EMPTY), isFileSystemCaseSensitive()); + break; + case STAR_PATH: + pieces.add(0, new String(pat, startPos, lastPartPos - startPos)); + ret = new StarMatchPart(ret, pieces.toArray(EMPTY), isFileSystemCaseSensitive()); + break; + case TEXT_PATH: + ret = new ExactMatchPart(ret, new String(pat, startPos, lastPartPos - startPos), + isFileSystemCaseSensitive()); + break; + case JUST_FOUND_STAR_STAR_AT_END: + case JUST_FOUND_STAR_STAR_IN_MIDDLE: + case END_PATH: + // do nothing + break; + } + if ((!isAbsolute && !(ret instanceof StrictStarStarPart)) || (isAbsolute && ret == null)) { + ret = new StrictStarStarPart(ret); + } + if (ret == null) { + // Weird state. Allow it, though. + ret = new ExactMatchPart(null, "", isFileSystemCaseSensitive()); + } + return ret; + } + + + private static boolean isFileSystemCaseSensitive() { + // This isn't 100% true - you can configure windows file system to be case sensitive. + return !SystemInfo.isWindows; + } + + static class PathNameMatchResult { + final boolean isMatch; + final boolean requiresMore; + final boolean isLastElementMatch; + final PathPart next; + + PathNameMatchResult(boolean isMatch, @Nullable PathPart next) { + this(isMatch, next != null, next); + } + + PathNameMatchResult(boolean isMatch, boolean requiresMore, @Nullable PathPart next) { + this.isMatch = isMatch; + this.requiresMore = requiresMore; + this.next = next; + this.isLastElementMatch = isMatch && (next == null || !requiresMore); + } + } + + private static final PathNameMatchResult NOT_MATCH = new PathNameMatchResult(false, false, null); + + abstract static class PathPart { + final PathPart next; + + protected PathPart(@Nullable PathPart next) { + this.next = next; + } + + @NotNull + abstract PathNameMatchResult match(@NotNull String name); + } + + + // Matching patterns: + // if a pattern ends with a '/', then use StrictStarStarPart with no next. + // /a/ -> ExactMatchPart + // /*/ -> SingleAnyMatchPart + // /**/ -> StrictStarStarPart + // /a**b/ -> /a*/**/*b/ -> StarMatchPart, StrictStarStarPart, StarMatchPart + // /a**/ -> /a*/**/ -> StarMatchPart, StrictStarStarPart + // /**b/ -> /**/*b/ -> StrictStarStarPart, StarMatchPart + // /a*/ -> StarMatchPart + // /*b/ -> StarMatchPart + // /a*b/ -> StarMatchPart + // /*a*/ -> StarMatchPart + + + static class SingleAnyMatchPart extends PathPart { + protected SingleAnyMatchPart(@Nullable PathPart next) { + super(next); + } + + @NotNull + @Override + PathNameMatchResult match(@NotNull String name) { + return new PathNameMatchResult(true, next); + } + } + + + static class ExactMatchPart extends PathPart { + private final String match; + private final boolean caseInsensitive; + + protected ExactMatchPart(@Nullable PathPart next, @NotNull String match, boolean caseInsensitive) { + super(next); + this.match = match; + this.caseInsensitive = caseInsensitive; + } + + @NotNull + @Override + PathNameMatchResult match(@NotNull String name) { + boolean isMatch = caseInsensitive ? name.equalsIgnoreCase(match) : name.equals(match); + return isMatch ? new PathNameMatchResult(true, next) : NOT_MATCH; + } + } + + // If P4 ignore ever learns character sets ([ab] meaning a or b), then this will need to take something + // other than String for the matches. + static class StarMatchPart extends PathPart { + final String[] matches; + private final boolean caseInsensitive; + + // matches implies a '*' at the end, but it isn't there. + StarMatchPart(@Nullable PathPart next, @NotNull String[] matches, boolean caseInsensitive) { + super(next); + if (matches.length < 2) { + throw new IllegalArgumentException("matches must have at least one star and one non-star"); + } + this.matches = matches; + this.caseInsensitive = caseInsensitive; + if (caseInsensitive) { + for (int i = 0; i < matches.length; i++) { + matches[i] = matches[i].toLowerCase(); + } + } + } + + @NotNull + @Override + PathNameMatchResult match(@NotNull String name) { + String remaining = caseInsensitive ? name.toLowerCase() : name; + return isRecursiveMatches(0, matches.length - 1, remaining) + ? new PathNameMatchResult(true, next) + : NOT_MATCH; + } + + + boolean isRecursiveMatches(int startMatchPos, int endMatchPos, String remaining) { + assert startMatchPos >= 0; + assert endMatchPos < matches.length; + assert startMatchPos <= endMatchPos; + + // peel away until we get to a non-star. + // - If match is [a, *], the hasStartStar loop will first not increment startMatchPos. + // It will see that remaining starts with the 'a' (if not, quit), so increment to the + // '*'. This equals the endMatchPos, but we'll keep looping. + // The second loop will match a star, and keep looking. Now the startMatchPos > end, + // so it will quit with a yes. + // - If match is [*, a], the hasStartStar will just stop with a hasStartStar = true. + // Then, the hasEndStar will find a non-star at the end, find that remaining + // matches the end string (if not, quit), so decrement the end by one (start == end now), and loop again. + // The second hasEndStar loop will find a star, decrement the end (start > end now), and quit with a yes. + // - If the match is [*, a, *], the hasStartStar will end with startMatchPos = 1, and hasStartStar = true. + // The hasEndStar will end with endMatchPos = 1, and hasEndStar = true. + // - If the match is [a, *, b, *] or [*, b, *, a], then it's essentially just like the [*, a, *] pattern. + // - If the match is [*, a, *, b, *], or something more complicated, now we're in trouble. The + // "complicated" section enters with start pointing at "a" and end pointing at "b". + // Now we need to recurse for each "a" and "b" position within the remaining string, where the + // "a" position < "b" position. + + + boolean hasStartStar = false; + while (!hasStartStar) { + while (startMatchPos <= endMatchPos && "*".equals(matches[startMatchPos])) { + startMatchPos++; + hasStartStar = true; + } + if (startMatchPos > endMatchPos) { + // It's just stars + return true; + } + if (!hasStartStar) { + if (!remaining.startsWith(matches[startMatchPos])) { + return false; + } + remaining = remaining.substring(matches[startMatchPos].length()); + startMatchPos++; + } + } + + // it's possible at this point to have start pos == end pos. + + boolean hasEndStar = false; + while (!hasEndStar) { + while (endMatchPos >= startMatchPos && "*".equals(matches[endMatchPos])) { + endMatchPos--; + hasEndStar = true; + } + if (startMatchPos > endMatchPos) { + // It's just stars + return true; + } + if (!hasEndStar) { + if (!remaining.endsWith(matches[endMatchPos])) { + return false; + } + remaining = remaining.substring(0, remaining.length() - matches[endMatchPos].length()); + endMatchPos--; + } + } + + // Easy out. Stars surrounding a single string. + if (startMatchPos == endMatchPos) { + // Stars surrounding a single text bit. + return remaining.contains(matches[startMatchPos]); + } + + // Complicated. + // Find all the indexes of the startMatchPos within remaining, and find all the indexes of the + // endMatchPos within remaining. Then, run a recurse match on each combination where + // start index < end index. + + List startMatchIndicies = matchPositions(remaining, matches[startMatchPos]); + List endMatchIndicies = matchPositions(remaining, matches[endMatchPos]); + for (int si = 0; si < startMatchIndicies.size(); si++) { + // Point the starting string index to where the match completes. + int sip = startMatchIndicies.get(si) + matches[startMatchPos].length(); + for (int ei = 0; ei < endMatchIndicies.size(); ei++) { + // Point the ending string index to where the match starts. + int eip = endMatchIndicies.get(ei); + if (sip <= eip) { + if (isRecursiveMatches(startMatchPos + 1, endMatchPos - 1, + remaining.substring(sip, eip))) { + return true; + } + } + } + } + return false; + } + + + List matchPositions(String str, String match) { + int len = str.length(); + List ret = new ArrayList<>(len); + int p = 0; + while (p < len) { + int mp = str.indexOf(match, p); + if (mp >= p) { + ret.add(mp); + p = mp + 1; + } else { + break; + } + } + return ret; + } + } + + /** slash '**' slash. Note that anything that has something '**' something can be split into this and others. */ + static class StrictStarStarPart extends PathPart { + StrictStarStarPart(@Nullable PathPart next) { + super(next); + } + + @NotNull + @Override + PathNameMatchResult match(@NotNull String name) { + if (next == null) { + // Any child will do + return new PathNameMatchResult(true, false, this); + } + // Totally non-greedy match. + PathNameMatchResult nextMatch = next.match(name); + if (nextMatch.isMatch) { + return nextMatch; + } + // Because we have a next-match, this requires another path element. + return new PathNameMatchResult(true, true, this); + } + } +} diff --git a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/ignore/IgnoreFiles.java b/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/ignore/IgnoreFiles.java index f93015d9..0689a6a4 100644 --- a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/ignore/IgnoreFiles.java +++ b/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/ignore/IgnoreFiles.java @@ -1,94 +1,94 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4.server.impl.ignore; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; - -/** - * A hybrid local cached file. The ignore file is stored entirely on the client (it can be in Perforce, - * but the storage will still be considered local), but we'll treat it like the local cache. - */ -public class IgnoreFiles { - private static final Logger LOG = Logger.getInstance(IgnoreFiles.class); - - private final String ignoreFileName; - - public IgnoreFiles(@Nullable final String ignoreFileName) { - this.ignoreFileName = ignoreFileName; - } - - @Nullable - public String getIgnoreFileName() { - return ignoreFileName; - } - - @Nullable - public VirtualFile getIgnoreFileForPath(@Nullable final VirtualFile path) { - if (path == null) { - return null; - } - String ignoreFileName = getIgnoreFileName(); - if (ignoreFileName != null) { - VirtualFile prevDir = path; - VirtualFile f = prevDir.getParent(); - while (f != null && f.isDirectory() && !f.equals(prevDir)) { - VirtualFile ignoreFile = f.findChild(ignoreFileName); - if (ignoreFile != null && ignoreFile.exists() && !ignoreFile.isDirectory()) { - return ignoreFile; - } - prevDir = f; - f = f.getParent(); - } - } - return null; - } - - public boolean isFileIgnored(@Nullable final FilePath file) { - if (file == null || file.getVirtualFile() == null) { - return true; - } - final VirtualFile ignoreFile = getIgnoreFileForPath(file.getVirtualFile()); - return isMatch(file, ignoreFile); - } - - private boolean isMatch(@NotNull final FilePath file, @Nullable final VirtualFile ignoreFile) { - LOG.debug("Checking ignore status on " + file + " against ignore file " + ignoreFile); - if (ignoreFile == null || ignoreFile.isDirectory()) { - return false; - } - VirtualFile vf = file.getVirtualFile(); - if (vf == null) { - return false; - } - - // TODO look at caching these ignore file results. - // It would mean needing to be aware of file reload events, though. - - try { - final IgnoreFileSet patterns = IgnoreFileSet.create(ignoreFile); - return patterns.isCoveredByIgnoreFile(vf) && patterns.isIgnored(vf); - } catch (IOException e) { - // problem reading; assume it's not ignored - LOG.info(e); - return false; - } - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4.server.impl.ignore; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; + +/** + * A hybrid local cached file. The ignore file is stored entirely on the client (it can be in Perforce, + * but the storage will still be considered local), but we'll treat it like the local cache. + */ +public class IgnoreFiles { + private static final Logger LOG = Logger.getInstance(IgnoreFiles.class); + + private final String ignoreFileName; + + public IgnoreFiles(@Nullable final String ignoreFileName) { + this.ignoreFileName = ignoreFileName; + } + + @Nullable + public String getIgnoreFileName() { + return ignoreFileName; + } + + @Nullable + public VirtualFile getIgnoreFileForPath(@Nullable final VirtualFile path) { + if (path == null) { + return null; + } + String ignoreFileName = getIgnoreFileName(); + if (ignoreFileName != null) { + VirtualFile prevDir = path; + VirtualFile f = prevDir.getParent(); + while (f != null && f.isDirectory() && !f.equals(prevDir)) { + VirtualFile ignoreFile = f.findChild(ignoreFileName); + if (ignoreFile != null && ignoreFile.exists() && !ignoreFile.isDirectory()) { + return ignoreFile; + } + prevDir = f; + f = f.getParent(); + } + } + return null; + } + + public boolean isFileIgnored(@Nullable final FilePath file) { + if (file == null || file.getVirtualFile() == null) { + return true; + } + final VirtualFile ignoreFile = getIgnoreFileForPath(file.getVirtualFile()); + return isMatch(file, ignoreFile); + } + + private boolean isMatch(@NotNull final FilePath file, @Nullable final VirtualFile ignoreFile) { + LOG.debug("Checking ignore status on " + file + " against ignore file " + ignoreFile); + if (ignoreFile == null || ignoreFile.isDirectory()) { + return false; + } + VirtualFile vf = file.getVirtualFile(); + if (vf == null) { + return false; + } + + // TODO look at caching these ignore file results. + // It would mean needing to be aware of file reload events, though. + + try { + final IgnoreFileSet patterns = IgnoreFileSet.create(ignoreFile); + return patterns.isCoveredByIgnoreFile(vf) && patterns.isIgnored(vf); + } catch (IOException e) { + // problem reading; assume it's not ignored + LOG.info(e); + return false; + } + } +} diff --git a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/tasks/TempFileWatchDog.java b/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/tasks/TempFileWatchDog.java index 11a7cc7d..eb1047c0 100644 --- a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/tasks/TempFileWatchDog.java +++ b/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/tasks/TempFileWatchDog.java @@ -1,140 +1,140 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4.server.impl.tasks; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.util.ConcurrencyUtil; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.FilenameFilter; -import java.io.IOException; -import java.nio.file.Files; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -/** - * A background task that monitors the temporary files created by - * the P4 java API. - */ -public class TempFileWatchDog { - private static final Logger LOG = Logger.getInstance(TempFileWatchDog.class); - private static final ScheduledExecutorService SCHEDULER = - ConcurrencyUtil.newSingleScheduledThreadExecutor("P4 Temp File Cleanup"); - private static final FilenameFilter TEMP_FILE_FILTER = new TempFileFilter(); - private static final long INTERVAL_SECONDS = 10L; - private static final String PREFIX = "p4j"; - private static final String SUFFIX = ".tmp"; - - private final File tempDir; - private ScheduledFuture cleanup; - - - private static File getDefaultTempDir() { - try { - return Files.createTempDirectory("tmp").toFile(); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - - - public TempFileWatchDog() { - this(getDefaultTempDir()); - } - - - public TempFileWatchDog(@NotNull File tempDir) { - if (! tempDir.isDirectory() || ! tempDir.canRead()) { - throw new IllegalArgumentException(tempDir.getAbsolutePath()); - } - this.tempDir = tempDir; - } - - - @NotNull - public File getTempDir() { - return tempDir; - } - - - public synchronized void start() { - if (cleanup == null) { - cleanup = SCHEDULER.scheduleWithFixedDelay(new Watcher(), INTERVAL_SECONDS, INTERVAL_SECONDS, TimeUnit.SECONDS); - } - } - - - public synchronized void stop() { - if (cleanup != null) { - cleanup.cancel(false); - cleanup = null; - } - } - - - private void cleanUpFiles() { - File[] contents = tempDir.listFiles(TEMP_FILE_FILTER); - if (contents != null) { - for (File file : contents) { - if (!file.delete()) { - LOG.info("Could not delete temporary Perforce file " + file); - } - } - } - } - - - public void cleanUpTempDir() { - File[] contents = tempDir.listFiles(); - if (contents != null) { - for (File file: contents) { - // Ignore result. We do the final check by trying to - // delete the directory, which will fail if it's not - // empty. - file.delete(); - } - } - if (! tempDir.delete()) { - LOG.info("Could not delete temporary directory " + tempDir); - } - } - - - - private class Watcher implements Runnable { - @Override - public void run() { - try { - cleanUpFiles(); - } catch (Exception ex) { - LOG.warn(ex); - } - } - } - - - private static class TempFileFilter implements FilenameFilter { - - @Override - public boolean accept(File dir, String name) { - if (name == null) { - return false; - } - String lName = name.toLowerCase(); - return lName.startsWith(PREFIX) && lName.endsWith(SUFFIX); - } - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4.server.impl.tasks; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.util.ConcurrencyUtil; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.nio.file.Files; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * A background task that monitors the temporary files created by + * the P4 java API. + */ +public class TempFileWatchDog { + private static final Logger LOG = Logger.getInstance(TempFileWatchDog.class); + private static final ScheduledExecutorService SCHEDULER = + ConcurrencyUtil.newSingleScheduledThreadExecutor("P4 Temp File Cleanup"); + private static final FilenameFilter TEMP_FILE_FILTER = new TempFileFilter(); + private static final long INTERVAL_SECONDS = 10L; + private static final String PREFIX = "p4j"; + private static final String SUFFIX = ".tmp"; + + private final File tempDir; + private ScheduledFuture cleanup; + + + private static File getDefaultTempDir() { + try { + return Files.createTempDirectory("tmp").toFile(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + + public TempFileWatchDog() { + this(getDefaultTempDir()); + } + + + public TempFileWatchDog(@NotNull File tempDir) { + if (! tempDir.isDirectory() || ! tempDir.canRead()) { + throw new IllegalArgumentException(tempDir.getAbsolutePath()); + } + this.tempDir = tempDir; + } + + + @NotNull + public File getTempDir() { + return tempDir; + } + + + public synchronized void start() { + if (cleanup == null) { + cleanup = SCHEDULER.scheduleWithFixedDelay(new Watcher(), INTERVAL_SECONDS, INTERVAL_SECONDS, TimeUnit.SECONDS); + } + } + + + public synchronized void stop() { + if (cleanup != null) { + cleanup.cancel(false); + cleanup = null; + } + } + + + private void cleanUpFiles() { + File[] contents = tempDir.listFiles(TEMP_FILE_FILTER); + if (contents != null) { + for (File file : contents) { + if (!file.delete()) { + LOG.info("Could not delete temporary Perforce file " + file); + } + } + } + } + + + public void cleanUpTempDir() { + File[] contents = tempDir.listFiles(); + if (contents != null) { + for (File file: contents) { + // Ignore result. We do the final check by trying to + // delete the directory, which will fail if it's not + // empty. + file.delete(); + } + } + if (! tempDir.delete()) { + LOG.info("Could not delete temporary directory " + tempDir); + } + } + + + + private class Watcher implements Runnable { + @Override + public void run() { + try { + cleanUpFiles(); + } catch (Exception ex) { + LOG.warn(ex); + } + } + } + + + private static class TempFileFilter implements FilenameFilter { + + @Override + public boolean accept(File dir, String name) { + if (name == null) { + return false; + } + String lName = name.toLowerCase(); + return lName.startsWith(PREFIX) && lName.endsWith(SUFFIX); + } + } +} diff --git a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/values/P4AnnotatedLineImpl.java b/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/values/P4AnnotatedLineImpl.java index 57a51ad6..3cd5f3b3 100644 --- a/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/values/P4AnnotatedLineImpl.java +++ b/idea-p4server/impl/src/main/java/net/groboclown/p4/server/impl/values/P4AnnotatedLineImpl.java @@ -1,100 +1,100 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4.server.impl.values; - -import com.intellij.openapi.vcs.FilePath; -import com.perforce.p4java.core.file.IFileAnnotation; -import com.perforce.p4java.core.file.IFileRevisionData; -import net.groboclown.p4.server.api.ClientServerRef; -import net.groboclown.p4.server.api.values.P4AnnotatedLine; -import net.groboclown.p4.server.api.values.P4ChangelistId; -import net.groboclown.p4.server.api.values.P4FileRevision; -import net.groboclown.p4.server.api.values.P4RemoteFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Date; - -public class P4AnnotatedLineImpl implements P4AnnotatedLine { - private final FilePath baseFile; - private final P4RemoteFile depotPath; - private final IFileAnnotation ann; - private final int lineNumber; - private final IFileRevisionData revisionData; - private final P4ChangelistId changelistId; - private final P4FileRevision rev; - - public P4AnnotatedLineImpl(@NotNull ClientServerRef ref, @NotNull FilePath baseFile, int lineNumber, - @NotNull IFileAnnotation ann, - @NotNull IFileRevisionData data) { - this.baseFile = baseFile; - this.depotPath = new P4RemoteFileImpl(ann); - this.ann = ann; - this.revisionData = data; - this.lineNumber = lineNumber; - this.changelistId = new P4ChangelistIdImpl(revisionData.getChangelistId(), ref); - this.rev = new P4FileRevisionImpl(ref, data); - } - - @Override - @NotNull - public P4FileRevision getRev() { - return rev; - } - - @Override - public P4ChangelistId getChangelist() { - return changelistId; - } - - @Override - @Nullable - public String getAuthor() { - return revisionData.getUserName(); - } - - @Override - @Nullable - public Date getDate() { - return revisionData.getDate(); - } - - @Override - @Nullable - public String getComment() { - return revisionData.getDescription(); - } - - @Override - @NotNull - public IFileRevisionData getRevisionData() { - return revisionData; - } - - @Override - @NotNull - public P4RemoteFile getDepotPath() { - return depotPath; - } - - @Override - public int getLineNumber() { - return lineNumber; - } - - @Override - public int getRevNumber() { - return revisionData.getRevision(); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4.server.impl.values; + +import com.intellij.openapi.vcs.FilePath; +import com.perforce.p4java.core.file.IFileAnnotation; +import com.perforce.p4java.core.file.IFileRevisionData; +import net.groboclown.p4.server.api.ClientServerRef; +import net.groboclown.p4.server.api.values.P4AnnotatedLine; +import net.groboclown.p4.server.api.values.P4ChangelistId; +import net.groboclown.p4.server.api.values.P4FileRevision; +import net.groboclown.p4.server.api.values.P4RemoteFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Date; + +public class P4AnnotatedLineImpl implements P4AnnotatedLine { + private final FilePath baseFile; + private final P4RemoteFile depotPath; + private final IFileAnnotation ann; + private final int lineNumber; + private final IFileRevisionData revisionData; + private final P4ChangelistId changelistId; + private final P4FileRevision rev; + + public P4AnnotatedLineImpl(@NotNull ClientServerRef ref, @NotNull FilePath baseFile, int lineNumber, + @NotNull IFileAnnotation ann, + @NotNull IFileRevisionData data) { + this.baseFile = baseFile; + this.depotPath = new P4RemoteFileImpl(ann); + this.ann = ann; + this.revisionData = data; + this.lineNumber = lineNumber; + this.changelistId = new P4ChangelistIdImpl(revisionData.getChangelistId(), ref); + this.rev = new P4FileRevisionImpl(ref, data); + } + + @Override + @NotNull + public P4FileRevision getRev() { + return rev; + } + + @Override + public P4ChangelistId getChangelist() { + return changelistId; + } + + @Override + @Nullable + public String getAuthor() { + return revisionData.getUserName(); + } + + @Override + @Nullable + public Date getDate() { + return revisionData.getDate(); + } + + @Override + @Nullable + public String getComment() { + return revisionData.getDescription(); + } + + @Override + @NotNull + public IFileRevisionData getRevisionData() { + return revisionData; + } + + @Override + @NotNull + public P4RemoteFile getDepotPath() { + return depotPath; + } + + @Override + public int getLineNumber() { + return lineNumber; + } + + @Override + public int getRevNumber() { + return revisionData.getRevision(); + } +} diff --git a/idea-test-core/src/main/java/net/groboclown/idea/altmock/MockFilePath.java b/idea-test-core/src/main/java/net/groboclown/idea/altmock/MockFilePath.java index 60a37e05..a2572997 100644 --- a/idea-test-core/src/main/java/net/groboclown/idea/altmock/MockFilePath.java +++ b/idea-test-core/src/main/java/net/groboclown/idea/altmock/MockFilePath.java @@ -1,150 +1,150 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.idea.altmock; - -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.nio.charset.Charset; - -public class MockFilePath implements FilePath { - private final File f; - private final VirtualFile vf; - - public MockFilePath(final File f) { - this.f = f; - this.vf = new MockVirtualFile(f); - } - - @Nullable - @Override - public VirtualFile getVirtualFile() { - return vf; - } - - @Nullable - @Override - public VirtualFile getVirtualFileParent() { - return vf.getParent(); - } - - @NotNull - @Override - public File getIOFile() { - return f; - } - - @NotNull - @Override - public String getName() { - return vf.getName(); - } - - @NotNull - @Override - public String getPresentableUrl() { - return f.toURI().toASCIIString(); - } - - @Deprecated - @Nullable - @Override - public Document getDocument() { - return null; - } - - @NotNull - @Override - public Charset getCharset() { - return Charset.defaultCharset(); - } - - @NotNull - @Override - public Charset getCharset(@Nullable final Project project) { - return Charset.defaultCharset(); - } - - @NotNull - @Override - public FileType getFileType() { - return MockFileType.MOCK; - } - - @Deprecated - @Override - public void refresh() { - - } - - @Deprecated - @Override - public void hardRefresh() { - - } - - @NotNull - @Override - public String getPath() { - return vf.getPath(); - } - - @Override - public boolean isDirectory() { - return vf.isDirectory(); - } - - @Override - public boolean isUnder(@NotNull final FilePath filePath, final boolean b) { - // not implemented here - return false; - } - - @Nullable - @Override - public FilePath getParentPath() { - File parent = f.getParentFile(); - if (parent == null) { - return null; - } - return new MockFilePath(parent); - } - - @Override - public boolean isNonLocal() { - return false; - } - - @Override - public int hashCode() { - return vf.getPath().hashCode() + vf.getName().hashCode(); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof FilePath && vf.equals(((FilePath) obj).getVirtualFile()); - } - - @Override - public String toString() { - return f.toString(); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.idea.altmock; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.nio.charset.Charset; + +public class MockFilePath implements FilePath { + private final File f; + private final VirtualFile vf; + + public MockFilePath(final File f) { + this.f = f; + this.vf = new MockVirtualFile(f); + } + + @Nullable + @Override + public VirtualFile getVirtualFile() { + return vf; + } + + @Nullable + @Override + public VirtualFile getVirtualFileParent() { + return vf.getParent(); + } + + @NotNull + @Override + public File getIOFile() { + return f; + } + + @NotNull + @Override + public String getName() { + return vf.getName(); + } + + @NotNull + @Override + public String getPresentableUrl() { + return f.toURI().toASCIIString(); + } + + @Deprecated + @Nullable + @Override + public Document getDocument() { + return null; + } + + @NotNull + @Override + public Charset getCharset() { + return Charset.defaultCharset(); + } + + @NotNull + @Override + public Charset getCharset(@Nullable final Project project) { + return Charset.defaultCharset(); + } + + @NotNull + @Override + public FileType getFileType() { + return MockFileType.MOCK; + } + + @Deprecated + @Override + public void refresh() { + + } + + @Deprecated + @Override + public void hardRefresh() { + + } + + @NotNull + @Override + public String getPath() { + return vf.getPath(); + } + + @Override + public boolean isDirectory() { + return vf.isDirectory(); + } + + @Override + public boolean isUnder(@NotNull final FilePath filePath, final boolean b) { + // not implemented here + return false; + } + + @Nullable + @Override + public FilePath getParentPath() { + File parent = f.getParentFile(); + if (parent == null) { + return null; + } + return new MockFilePath(parent); + } + + @Override + public boolean isNonLocal() { + return false; + } + + @Override + public int hashCode() { + return vf.getPath().hashCode() + vf.getName().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof FilePath && vf.equals(((FilePath) obj).getVirtualFile()); + } + + @Override + public String toString() { + return f.toString(); + } +} diff --git a/idea-test-core/src/main/java/net/groboclown/idea/altmock/MockFileType.java b/idea-test-core/src/main/java/net/groboclown/idea/altmock/MockFileType.java index 781ae3bf..7eca5208 100644 --- a/idea-test-core/src/main/java/net/groboclown/idea/altmock/MockFileType.java +++ b/idea-test-core/src/main/java/net/groboclown/idea/altmock/MockFileType.java @@ -1,66 +1,66 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.idea.altmock; - -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class MockFileType implements FileType { - public static final FileType MOCK = new MockFileType(); - - @NotNull - @Override - public String getName() { - return "mock"; - } - - @NotNull - @Override - public String getDescription() { - return "mock"; - } - - @NotNull - @Override - public String getDefaultExtension() { - return ".mock"; - } - - @Nullable - @Override - public Icon getIcon() { - return null; - } - - @Override - public boolean isBinary() { - return false; - } - - @Override - public boolean isReadOnly() { - return false; - } - - @Nullable - @Override - public String getCharset(@NotNull VirtualFile file, @NotNull byte[] content) { - return null; - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.idea.altmock; + +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class MockFileType implements FileType { + public static final FileType MOCK = new MockFileType(); + + @NotNull + @Override + public String getName() { + return "mock"; + } + + @NotNull + @Override + public String getDescription() { + return "mock"; + } + + @NotNull + @Override + public String getDefaultExtension() { + return ".mock"; + } + + @Nullable + @Override + public Icon getIcon() { + return null; + } + + @Override + public boolean isBinary() { + return false; + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Nullable + @Override + public String getCharset(@NotNull VirtualFile file, @NotNull byte[] content) { + return null; + } +} diff --git a/idea-test-core/src/main/java/net/groboclown/idea/altmock/MockVirtualFile.java b/idea-test-core/src/main/java/net/groboclown/idea/altmock/MockVirtualFile.java index fd93a248..1f26784b 100644 --- a/idea-test-core/src/main/java/net/groboclown/idea/altmock/MockVirtualFile.java +++ b/idea-test-core/src/main/java/net/groboclown/idea/altmock/MockVirtualFile.java @@ -1,144 +1,144 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.idea.altmock; - -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileSystem; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; - -public class MockVirtualFile extends VirtualFile { - private static final VirtualFile[] EMPTY = new VirtualFile[0]; - private final File f; - - public MockVirtualFile(@NotNull File f) { - this.f = f; - } - - @NotNull - @Override - public String getName() { - return f.getName(); - } - - @NotNull - @Override - public VirtualFileSystem getFileSystem() { - throw new IllegalStateException("not implemented"); - } - - @NotNull - @Override - public String getPath() { - return f.getPath(); - } - - @Override - public boolean isWritable() { - return f.canWrite(); - } - - @Override - public boolean isDirectory() { - return f.isDirectory(); - } - - @Override - public boolean isValid() { - return true; - } - - @Override - public VirtualFile getParent() { - return new MockVirtualFile(f.getParentFile()); - } - - @Override - public VirtualFile[] getChildren() { - if (! f.isDirectory()) { - return EMPTY; - } - File[] files = f.listFiles(); - if (files == null) { - return EMPTY; - } - List ret = new ArrayList<>(); - for (File f : files) { - ret.add(new MockVirtualFile(f)); - } - return ret.toArray(EMPTY); - } - - @NotNull - @Override - public OutputStream getOutputStream(Object requestor, long newModificationStamp, long newTimeStamp) - throws IOException { - return new FileOutputStream(f); - } - - @NotNull - @Override - public byte[] contentsToByteArray() - throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try (InputStream inp = getInputStream()) { - byte[] buff = new byte[4096]; - int len; - while ((len = inp.read(buff, 0, 4096)) > 0) { - out.write(buff, 0, len); - } - } - return out.toByteArray(); - } - - @Override - public long getTimeStamp() { - if (f.exists()) { - return f.lastModified(); - } - return 0; - } - - @Override - public long getLength() { - if (f.isFile()) { - return f.length(); - } - return 0; - } - - @Override - public void refresh(boolean asynchronous, boolean recursive, @Nullable Runnable postRunnable) { - if (postRunnable != null) { - postRunnable.run(); - } - } - - @Override - public InputStream getInputStream() - throws IOException { - return new FileInputStream(f); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.idea.altmock; + +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileSystem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +public class MockVirtualFile extends VirtualFile { + private static final VirtualFile[] EMPTY = new VirtualFile[0]; + private final File f; + + public MockVirtualFile(@NotNull File f) { + this.f = f; + } + + @NotNull + @Override + public String getName() { + return f.getName(); + } + + @NotNull + @Override + public VirtualFileSystem getFileSystem() { + throw new IllegalStateException("not implemented"); + } + + @NotNull + @Override + public String getPath() { + return f.getPath(); + } + + @Override + public boolean isWritable() { + return f.canWrite(); + } + + @Override + public boolean isDirectory() { + return f.isDirectory(); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public VirtualFile getParent() { + return new MockVirtualFile(f.getParentFile()); + } + + @Override + public VirtualFile[] getChildren() { + if (! f.isDirectory()) { + return EMPTY; + } + File[] files = f.listFiles(); + if (files == null) { + return EMPTY; + } + List ret = new ArrayList<>(); + for (File f : files) { + ret.add(new MockVirtualFile(f)); + } + return ret.toArray(EMPTY); + } + + @NotNull + @Override + public OutputStream getOutputStream(Object requestor, long newModificationStamp, long newTimeStamp) + throws IOException { + return new FileOutputStream(f); + } + + @NotNull + @Override + public byte[] contentsToByteArray() + throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (InputStream inp = getInputStream()) { + byte[] buff = new byte[4096]; + int len; + while ((len = inp.read(buff, 0, 4096)) > 0) { + out.write(buff, 0, len); + } + } + return out.toByteArray(); + } + + @Override + public long getTimeStamp() { + if (f.exists()) { + return f.lastModified(); + } + return 0; + } + + @Override + public long getLength() { + if (f.isFile()) { + return f.length(); + } + return 0; + } + + @Override + public void refresh(boolean asynchronous, boolean recursive, @Nullable Runnable postRunnable) { + if (postRunnable != null) { + postRunnable.run(); + } + } + + @Override + public InputStream getInputStream() + throws IOException { + return new FileInputStream(f); + } +} diff --git a/idea-test-core/src/main/java/net/groboclown/idea/mock/IOFilePath.java b/idea-test-core/src/main/java/net/groboclown/idea/mock/IOFilePath.java index 6ca4cfb5..a1e06081 100644 --- a/idea-test-core/src/main/java/net/groboclown/idea/mock/IOFilePath.java +++ b/idea-test-core/src/main/java/net/groboclown/idea/mock/IOFilePath.java @@ -1,150 +1,150 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.idea.mock; - -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.nio.charset.Charset; - -public class IOFilePath - implements FilePath { - private final File f; - private final VirtualFile vf; - private final boolean isDirectory; - - public IOFilePath(final File f) { - this.f = f; - this.vf = new IOVirtualFile(f); - this.isDirectory = f.isDirectory(); - } - - public IOFilePath(final File f, boolean isDirectory) { - this.f = f; - this.vf = new IOVirtualFile(f); - this.isDirectory = isDirectory; - } - - @Nullable - @Override - public VirtualFile getVirtualFile() { - return vf; - } - - @Nullable - @Override - public VirtualFile getVirtualFileParent() { - return vf.getParent(); - } - - @NotNull - @Override - public File getIOFile() { - return f; - } - - @NotNull - @Override - public String getName() { - return vf.getName(); - } - - @NotNull - @Override - public String getPresentableUrl() { - return f.toURI().toASCIIString(); - } - - @Deprecated - @Nullable - @Override - public Document getDocument() { - return null; - } - - @NotNull - @Override - public Charset getCharset() { - return Charset.defaultCharset(); - } - - @NotNull - @Override - public Charset getCharset(@Nullable final Project project) { - return Charset.defaultCharset(); - } - - @NotNull - @Override - public FileType getFileType() { - return MockFileType.MOCK; - } - - @Deprecated - @Override - public void refresh() { - - } - - @Deprecated - @Override - public void hardRefresh() { - - } - - @NotNull - @Override - public String getPath() { - return vf.getPath(); - } - - @Override - public boolean isDirectory() { - return isDirectory; - } - - @Override - public boolean isUnder(@NotNull final FilePath filePath, final boolean b) { - // not implemented here - return false; - } - - @Nullable - @Override - public FilePath getParentPath() { - return new IOFilePath(f.getParentFile()); - } - - @Override - public boolean isNonLocal() { - return false; - } - - @Override - public int hashCode() { - return vf.getPath().hashCode() + vf.getName().hashCode(); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof FilePath && vf.equals(((FilePath) obj).getVirtualFile()); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.idea.mock; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.nio.charset.Charset; + +public class IOFilePath + implements FilePath { + private final File f; + private final VirtualFile vf; + private final boolean isDirectory; + + public IOFilePath(final File f) { + this.f = f; + this.vf = new IOVirtualFile(f); + this.isDirectory = f.isDirectory(); + } + + public IOFilePath(final File f, boolean isDirectory) { + this.f = f; + this.vf = new IOVirtualFile(f); + this.isDirectory = isDirectory; + } + + @Nullable + @Override + public VirtualFile getVirtualFile() { + return vf; + } + + @Nullable + @Override + public VirtualFile getVirtualFileParent() { + return vf.getParent(); + } + + @NotNull + @Override + public File getIOFile() { + return f; + } + + @NotNull + @Override + public String getName() { + return vf.getName(); + } + + @NotNull + @Override + public String getPresentableUrl() { + return f.toURI().toASCIIString(); + } + + @Deprecated + @Nullable + @Override + public Document getDocument() { + return null; + } + + @NotNull + @Override + public Charset getCharset() { + return Charset.defaultCharset(); + } + + @NotNull + @Override + public Charset getCharset(@Nullable final Project project) { + return Charset.defaultCharset(); + } + + @NotNull + @Override + public FileType getFileType() { + return MockFileType.MOCK; + } + + @Deprecated + @Override + public void refresh() { + + } + + @Deprecated + @Override + public void hardRefresh() { + + } + + @NotNull + @Override + public String getPath() { + return vf.getPath(); + } + + @Override + public boolean isDirectory() { + return isDirectory; + } + + @Override + public boolean isUnder(@NotNull final FilePath filePath, final boolean b) { + // not implemented here + return false; + } + + @Nullable + @Override + public FilePath getParentPath() { + return new IOFilePath(f.getParentFile()); + } + + @Override + public boolean isNonLocal() { + return false; + } + + @Override + public int hashCode() { + return vf.getPath().hashCode() + vf.getName().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof FilePath && vf.equals(((FilePath) obj).getVirtualFile()); + } +} diff --git a/idea-test-core/src/main/java/net/groboclown/idea/mock/P4Request.java b/idea-test-core/src/main/java/net/groboclown/idea/mock/P4Request.java index 49712ee4..e754208d 100644 --- a/idea-test-core/src/main/java/net/groboclown/idea/mock/P4Request.java +++ b/idea-test-core/src/main/java/net/groboclown/idea/mock/P4Request.java @@ -1,116 +1,116 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.idea.mock; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Arrays; -import java.util.Map; - -public final class P4Request { - private final String cmd; - private final String[] args; - private final Map request; - private final String inputData; - - public P4Request(@NotNull String cmd, @NotNull String[] args, @Nullable Map request, - @Nullable String inputData) { - this.cmd = cmd; - this.args = args; - this.request = request; - this.inputData = inputData; - } - - public P4Request(@NotNull String cmd, @NotNull String... args) { - this.cmd = cmd; - this.args = args; - this.request = null; - this.inputData = null; - } - - @NotNull - public String getCmd() { - return cmd; - } - - @NotNull - public String[] getArgs() { - return args; - } - - @Nullable - public Map getRequest() { - return request; - } - - @Nullable - public String getInputData() { - return inputData; - } - - @Override - public int hashCode() { - int ret = cmd.hashCode() + Arrays.hashCode(args); - if (request != null && ! request.isEmpty()) { - ret += request.hashCode(); - } - if (inputData != null && inputData.length() > 0) { - ret += inputData.hashCode(); - } - return ret; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null) { - return false; - } - if (! (o.getClass().equals(P4Request.class))) { - return false; - } - P4Request that = (P4Request) o; - if (request == null || request.isEmpty()) { - if (that.request != null && ! that.request.isEmpty()) { - return false; - } - } - if (inputData == null || inputData.length() <= 0) { - if (that.inputData != null && that.inputData.length() > 0) { - return false; - } - } - return cmd.equals(that.cmd) && - Arrays.equals(args, that.args); - } - - @Override - public String toString() { - StringBuilder ret = new StringBuilder(cmd); - for (String arg : args) { - ret.append(' ').append(arg); - } - if (request != null) { - ret.append(' ').append(request); - } - if (inputData != null) { - ret.append(" [").append(inputData).append(']'); - } - return ret.toString(); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.idea.mock; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Map; + +public final class P4Request { + private final String cmd; + private final String[] args; + private final Map request; + private final String inputData; + + public P4Request(@NotNull String cmd, @NotNull String[] args, @Nullable Map request, + @Nullable String inputData) { + this.cmd = cmd; + this.args = args; + this.request = request; + this.inputData = inputData; + } + + public P4Request(@NotNull String cmd, @NotNull String... args) { + this.cmd = cmd; + this.args = args; + this.request = null; + this.inputData = null; + } + + @NotNull + public String getCmd() { + return cmd; + } + + @NotNull + public String[] getArgs() { + return args; + } + + @Nullable + public Map getRequest() { + return request; + } + + @Nullable + public String getInputData() { + return inputData; + } + + @Override + public int hashCode() { + int ret = cmd.hashCode() + Arrays.hashCode(args); + if (request != null && ! request.isEmpty()) { + ret += request.hashCode(); + } + if (inputData != null && inputData.length() > 0) { + ret += inputData.hashCode(); + } + return ret; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + if (! (o.getClass().equals(P4Request.class))) { + return false; + } + P4Request that = (P4Request) o; + if (request == null || request.isEmpty()) { + if (that.request != null && ! that.request.isEmpty()) { + return false; + } + } + if (inputData == null || inputData.length() <= 0) { + if (that.inputData != null && that.inputData.length() > 0) { + return false; + } + } + return cmd.equals(that.cmd) && + Arrays.equals(args, that.args); + } + + @Override + public String toString() { + StringBuilder ret = new StringBuilder(cmd); + for (String arg : args) { + ret.append(' ').append(arg); + } + if (request != null) { + ret.append(' ').append(request); + } + if (inputData != null) { + ret.append(" [").append(inputData).append(']'); + } + return ret.toString(); + } +} diff --git a/idea-test-core/src/main/java/net/groboclown/idea/mock/VFFilePath.java b/idea-test-core/src/main/java/net/groboclown/idea/mock/VFFilePath.java index 840233be..96dfa60a 100644 --- a/idea-test-core/src/main/java/net/groboclown/idea/mock/VFFilePath.java +++ b/idea-test-core/src/main/java/net/groboclown/idea/mock/VFFilePath.java @@ -1,154 +1,154 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.idea.mock; - -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.nio.charset.Charset; - -/** - * Simulation of a FilePath, for when the API causes an error with using the correct - * API. Mostly, this is used for lightweight unit tests that don't want to mock the - * world. - */ -public class VFFilePath - implements FilePath { - private final VirtualFile vf; - - public VFFilePath(@NotNull final VirtualFile vf) { - this.vf = vf; - } - - @Nullable - @Override - public VirtualFile getVirtualFile() { - return vf; - } - - @Nullable - @Override - public VirtualFile getVirtualFileParent() { - return vf.getParent(); - } - - @NotNull - @Override - public File getIOFile() { - return new File(vf.getPath()); - } - - @NotNull - @Override - public String getName() { - return vf.getName(); - } - - @NotNull - @Override - public String getPresentableUrl() { - return getIOFile().toURI().toASCIIString(); - } - - @Deprecated - @Nullable - @Override - public Document getDocument() { - return null; - } - - @NotNull - @Override - public Charset getCharset() { - return Charset.defaultCharset(); - } - - @NotNull - @Override - public Charset getCharset(@Nullable final Project project) { - return Charset.defaultCharset(); - } - - @NotNull - @Override - public FileType getFileType() { - return MockFileType.MOCK; - } - - @Deprecated - @Override - public void refresh() { - - } - - @Deprecated - @Override - public void hardRefresh() { - - } - - @NotNull - @Override - public String getPath() { - return vf.getPath(); - } - - @Override - public boolean isDirectory() { - return vf.isDirectory(); - } - - @Override - public boolean isUnder(@NotNull final FilePath filePath, final boolean b) { - // not implemented here - return false; - } - - @Nullable - @Override - public FilePath getParentPath() { - if (vf.getParent() == null) { - return null; - } - return new VFFilePath(vf.getParent()); - } - - @Override - public boolean isNonLocal() { - return false; - } - - @Override - public int hashCode() { - return vf.getPath().hashCode() + vf.getName().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (obj == this) { - return true; - } - return obj instanceof FilePath && vf.equals(((FilePath) obj).getVirtualFile()); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.idea.mock; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.nio.charset.Charset; + +/** + * Simulation of a FilePath, for when the API causes an error with using the correct + * API. Mostly, this is used for lightweight unit tests that don't want to mock the + * world. + */ +public class VFFilePath + implements FilePath { + private final VirtualFile vf; + + public VFFilePath(@NotNull final VirtualFile vf) { + this.vf = vf; + } + + @Nullable + @Override + public VirtualFile getVirtualFile() { + return vf; + } + + @Nullable + @Override + public VirtualFile getVirtualFileParent() { + return vf.getParent(); + } + + @NotNull + @Override + public File getIOFile() { + return new File(vf.getPath()); + } + + @NotNull + @Override + public String getName() { + return vf.getName(); + } + + @NotNull + @Override + public String getPresentableUrl() { + return getIOFile().toURI().toASCIIString(); + } + + @Deprecated + @Nullable + @Override + public Document getDocument() { + return null; + } + + @NotNull + @Override + public Charset getCharset() { + return Charset.defaultCharset(); + } + + @NotNull + @Override + public Charset getCharset(@Nullable final Project project) { + return Charset.defaultCharset(); + } + + @NotNull + @Override + public FileType getFileType() { + return MockFileType.MOCK; + } + + @Deprecated + @Override + public void refresh() { + + } + + @Deprecated + @Override + public void hardRefresh() { + + } + + @NotNull + @Override + public String getPath() { + return vf.getPath(); + } + + @Override + public boolean isDirectory() { + return vf.isDirectory(); + } + + @Override + public boolean isUnder(@NotNull final FilePath filePath, final boolean b) { + // not implemented here + return false; + } + + @Nullable + @Override + public FilePath getParentPath() { + if (vf.getParent() == null) { + return null; + } + return new VFFilePath(vf.getParent()); + } + + @Override + public boolean isNonLocal() { + return false; + } + + @Override + public int hashCode() { + return vf.getPath().hashCode() + vf.getName().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + return obj instanceof FilePath && vf.equals(((FilePath) obj).getVirtualFile()); + } +} diff --git a/p4java/r14-1/src/main/java/com/jcraft/jzlib/ZStreamException.java b/p4java/r14-1/src/main/java/com/jcraft/jzlib/ZStreamException.java index 424b74b7..308bb8a1 100644 --- a/p4java/r14-1/src/main/java/com/jcraft/jzlib/ZStreamException.java +++ b/p4java/r14-1/src/main/java/com/jcraft/jzlib/ZStreamException.java @@ -1,44 +1,44 @@ -/* -*-mode:java; c-basic-offset:2; -*- */ -/* -Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. - - 3. The names of the authors may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, -INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/* - * This program is based on zlib-1.1.3, so all credit should go authors - * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) - * and contributors of zlib. - */ - -package com.jcraft.jzlib; - -public class ZStreamException extends java.io.IOException { - public ZStreamException() { - super(); - } - public ZStreamException(String s) { - super(s); - } -} +/* -*-mode:java; c-basic-offset:2; -*- */ +/* +Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * This program is based on zlib-1.1.3, so all credit should go authors + * Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu) + * and contributors of zlib. + */ + +package com.jcraft.jzlib; + +public class ZStreamException extends java.io.IOException { + public ZStreamException() { + super(); + } + public ZStreamException(String s) { + super(s); + } +} diff --git a/p4java/r14-1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java b/p4java/r14-1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java index 955735f4..75e9be6e 100644 --- a/p4java/r14-1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java +++ b/p4java/r14-1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java @@ -1,118 +1,118 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.impl.mapbased.rpc.sys.helper; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; -import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFile; -import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFileType; -import com.perforce.p4java.io.apple.AppleFileData; -import com.perforce.p4java.io.apple.AppleFileDecoder; - -/** - * Helper class for handling Apple files. - */ -public class AppleFileHelper { - - /** - * Extract the data fork and the resource fork from the Apple file. - * - * @param file the Apple file - */ - public static void extractFile(RpcPerforceFile file) { - if (file.getFileType() == RpcPerforceFileType.FST_APPLEFILE) { - FileOutputStream fosData = null; - FileOutputStream fosResource = null; - try { - byte[] data = AppleFileHelper.getBytesFromFile(file); - AppleFileData fileData = new AppleFileData(data); - AppleFileDecoder appleFile = new AppleFileDecoder(fileData); - appleFile.extract(); - fosData = new FileOutputStream(file); - AppleFileData forkData = appleFile.getDataFork(); - if (forkData != AppleFileData.EMPTY_FILE_DATA) { - fosData.write(forkData.getBytes()); - } - String resourceFilePath = file.getParent() + File.separator + "%" + file.getName(); - RpcPerforceFile targetResourceFile = new RpcPerforceFile(resourceFilePath, file.getFileType()); - fosResource = new FileOutputStream(targetResourceFile); - AppleFileData forkResource = appleFile.getResourceFork(); - if (forkResource != AppleFileData.EMPTY_FILE_DATA) { - fosResource.write(forkResource.getBytes()); - } - } catch (IOException e) { - Log.error("Problem handling the Apple file: " + file.getName()); - } catch (FileDecoderException e) { - Log.error("Problem decoding the Apple file: " + file.getName()); - } finally { - if (fosData != null) { - try { - fosData.close(); - } catch (Exception e) { - // Do nothing - } - } - if (fosResource != null) { - try { - fosResource.close(); - } catch (Exception e) { - // Do nothing - } - } - } - } - } - - /** - * Gets the bytes from file. - * - * @param file - * the file - * @return the bytes from file - * @throws IOException - * Signals that an I/O exception has occurred. - */ - public static byte[] getBytesFromFile(File file) throws IOException { - InputStream is = new FileInputStream(file); - - long length = file.length(); - if (length > Integer.MAX_VALUE) { - // File is too large - throw new IOException("Apple file too large for decoding."); - } - - byte[] bytes = new byte[(int) length]; - int offset = 0; - int numRead = 0; - - try { - while (offset < bytes.length - && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { - offset += numRead; - } - // Ensure all the bytes have been read in - if (offset < bytes.length) { - throw new IOException( - "Could not completely read the Apple file " - + file.getName()); - } - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - // Do nothing - } - } - } - - return bytes; - } -} +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.impl.mapbased.rpc.sys.helper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; +import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFile; +import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFileType; +import com.perforce.p4java.io.apple.AppleFileData; +import com.perforce.p4java.io.apple.AppleFileDecoder; + +/** + * Helper class for handling Apple files. + */ +public class AppleFileHelper { + + /** + * Extract the data fork and the resource fork from the Apple file. + * + * @param file the Apple file + */ + public static void extractFile(RpcPerforceFile file) { + if (file.getFileType() == RpcPerforceFileType.FST_APPLEFILE) { + FileOutputStream fosData = null; + FileOutputStream fosResource = null; + try { + byte[] data = AppleFileHelper.getBytesFromFile(file); + AppleFileData fileData = new AppleFileData(data); + AppleFileDecoder appleFile = new AppleFileDecoder(fileData); + appleFile.extract(); + fosData = new FileOutputStream(file); + AppleFileData forkData = appleFile.getDataFork(); + if (forkData != AppleFileData.EMPTY_FILE_DATA) { + fosData.write(forkData.getBytes()); + } + String resourceFilePath = file.getParent() + File.separator + "%" + file.getName(); + RpcPerforceFile targetResourceFile = new RpcPerforceFile(resourceFilePath, file.getFileType()); + fosResource = new FileOutputStream(targetResourceFile); + AppleFileData forkResource = appleFile.getResourceFork(); + if (forkResource != AppleFileData.EMPTY_FILE_DATA) { + fosResource.write(forkResource.getBytes()); + } + } catch (IOException e) { + Log.error("Problem handling the Apple file: " + file.getName()); + } catch (FileDecoderException e) { + Log.error("Problem decoding the Apple file: " + file.getName()); + } finally { + if (fosData != null) { + try { + fosData.close(); + } catch (Exception e) { + // Do nothing + } + } + if (fosResource != null) { + try { + fosResource.close(); + } catch (Exception e) { + // Do nothing + } + } + } + } + } + + /** + * Gets the bytes from file. + * + * @param file + * the file + * @return the bytes from file + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public static byte[] getBytesFromFile(File file) throws IOException { + InputStream is = new FileInputStream(file); + + long length = file.length(); + if (length > Integer.MAX_VALUE) { + // File is too large + throw new IOException("Apple file too large for decoding."); + } + + byte[] bytes = new byte[(int) length]; + int offset = 0; + int numRead = 0; + + try { + while (offset < bytes.length + && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { + offset += numRead; + } + // Ensure all the bytes have been read in + if (offset < bytes.length) { + throw new IOException( + "Could not completely read the Apple file " + + file.getName()); + } + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + // Do nothing + } + } + } + + return bytes; + } +} diff --git a/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFile.java b/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFile.java index 5b89f33a..615bfcc9 100644 --- a/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFile.java +++ b/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFile.java @@ -1,735 +1,735 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; - -/** - * This abstract class handles AppleSingle/Double files. It contains a common - * method to verify the Apple file, and figure out if it is an AppleSingle or - * AppleDouble formatted file. - *

- * - * The AppleSingle format is a representation of Macintosh files as one - * consecutive stream of bytes. AppleSingle combines the data fork, resource - * fork and the related Finder meta-file information into a single file. - *

- * - * The AppleDouble format stores the data fork, resource fork as two separate - * files. AppleDouble leaves the data fork in its original format, and the - * resource fork and Finder information were combined into a second file. - *

- * - * Apple defined the magic number for the AppleSingle format as 0x00051600, and - * the magic number for the AppleDouble format as 0x00051607. - * - *

- * AppleSingle file header: 
- * 
- * Field Length
- * ----- ------
- * Magic number ------- 4 bytes
- * Version number ------ 4 bytes
- * Filler ------------- 16 bytes
- * Number of entries ----- 2 bytes
- * 
- * Entry descriptor for each entry:
- * Entry ID ------ 4 bytes
- * Offset -------- 4 bytes
- * Length -------- 4 bytes
- * 
- * Apple reserved entry IDs:
- * 
- * Data Fork -------- 1 Data fork
- * Resource Fork ----- 2 Resource fork
- * Real Name -------- 3 File's name as created on home file system
- * Comment --------- 4 Standard Macintosh comment
- * Icon, B&W -------- 5 Standard Macintosh black and white icon
- * Icon, Color -------- 6 Macintosh color icon
- * File Dates Info ------8 File creation date, modification date, and so on
- * Finder Info -------- 9 Standard Macintosh Finder information
- * Macintosh File Info ---10 Macintosh file information, attributes, and so on
- * ProDOS File Info -----11 ProDOS file information, attributes, and so on
- * MS-DOS File Info ----12 MS-DOS file information, attributes, and so on
- * Short Name --------13 AFP short name
- * AFP File Info ------- 14 AFP file information, attributes, and so on
- * Directory ID --------15 AFP directory ID
- * 
- * - * See RFC 1740 for reference: http://tools.ietf.org/html/rfc1740 - */ -public abstract class AppleFile { - - /** The Apple file format: AppleSingle, AppleDouble, default to unknown. */ - protected FileFormat format = FileFormat.UNKNOWN; - - /** The raw Apple file. */ - protected AppleFileData fileData = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 1: Data fork. */ - protected AppleFileData dataFork = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 2: Resource fork. */ - protected AppleFileData resourceFork = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 3: File's name as created on home file system. */ - protected AppleFileData realName = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 4: Standard Macintosh comment. */ - protected AppleFileData comment = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 5: Standard Macintosh black and white icon. */ - protected AppleFileData iconBW = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 6: Macintosh color icon. */ - protected AppleFileData iconColor = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 8: File creation date, modification date, and so on. */ - protected AppleFileData fileDatesInfo = AppleFileData.EMPTY_FILE_DATA; - - /** The file dates info entry. */ - protected FileDatesInfoEntry fileDatesInfoEntry = null; - - /** Entry 9: Standard Macintosh Finder information. */ - protected AppleFileData finderInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 10: Macintosh file information, attributes, and so on. */ - protected AppleFileData macintoshInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 11: ProDOS file information, attributes, and so on. */ - protected AppleFileData proDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 12: MS-DOS file information, attributes, and so on. */ - protected AppleFileData msDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 13: AFP short name. */ - protected AppleFileData shortName = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 14: AFP file information, attributes, and so on. */ - protected AppleFileData afpFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 15: AFP directory ID. */ - protected AppleFileData directoryID = AppleFileData.EMPTY_FILE_DATA; - - /** The num entries. */ - protected int numEntries = 0; - - /** - * The Apple file format. - */ - public enum FileFormat { - - APPLE_SINGLE, - APPLE_DOUBLE, - UNKNOWN; - - /** - * Return a suitable Apple file format as inferred from the passed-in - * string. Otherwise return the UNKNOWN file format. - * - * @param fileFormat - * the file format - * @return the FileFormat - */ - public static FileFormat fromString(String fileFormat) { - if (fileFormat == null) { - return null; - } - - try { - return FileFormat.valueOf(fileFormat.toUpperCase()); - } catch (IllegalArgumentException iae) { - Log.error("Bad conversion attempt in FileFormat.fromString; string: " - + fileFormat + "; message: " + iae.getMessage()); - Log.exception(iae); - return UNKNOWN; - } - } - }; - - /** - * This class represents the file dates. - */ - public class FileDatesInfoEntry { - - /** The create time. */ - private int createTime = Integer.MIN_VALUE; - - /** The modify time. */ - private int modifyTime = Integer.MIN_VALUE; - - /** The backup time. */ - private int backupTime = Integer.MIN_VALUE; - - /** The access time. */ - private int accessTime = Integer.MIN_VALUE; - - /** - * Instantiates a new file dates info entry. - */ - public FileDatesInfoEntry() { - - } - - /** - * Gets the creates the time. - * - * @return the creates the time - */ - public int getCreateTime() { - return createTime; - } - - /** - * Sets the creates the time. - * - * @param createTime - * the new creates the time - */ - public void setCreateTime(int createTime) { - this.createTime = createTime; - } - - /** - * Gets the modify time. - * - * @return the modify time - */ - public int getModifyTime() { - return modifyTime; - } - - /** - * Sets the modify time. - * - * @param modifyTime - * the new modify time - */ - public void setModifyTime(int modifyTime) { - this.modifyTime = modifyTime; - } - - /** - * Gets the backup time. - * - * @return the backup time - */ - public int getBackupTime() { - return backupTime; - } - - /** - * Sets the backup time. - * - * @param backupTime - * the new backup time - */ - public void setBackupTime(int backupTime) { - this.backupTime = backupTime; - } - - /** - * Gets the access time. - * - * @return the access time - */ - public int getAccessTime() { - return accessTime; - } - - /** - * Sets the access time. - * - * @param accessTime - * the new access time - */ - public void setAccessTime(int accessTime) { - this.accessTime = accessTime; - } - } - - /** - * Sets the num entries. - * - * @param numEntries - * the new num entries - */ - public void setNumEntries(int numEntries) { - this.numEntries = numEntries; - } - - /** - * Verify the validity of the Apple file. - * - * @throws FileDecoderException - * the file decoder exception - */ - @SuppressWarnings("unused") - protected void verify() throws FileDecoderException { - byte[] data = this.fileData.getData(); - int offset = this.fileData.getOffset(); - int length = this.fileData.getLength(); - int position = offset; - if (length < 26) { - throw new FileDecoderException("File is too short"); - } - - /* Magic number */ - int magic = 0; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - - /* Check Apple file format: AppleSingle or AppleDobule */ - if (magic == 0x00051600) { - this.format = FileFormat.APPLE_SINGLE; - } else if (magic == 0x00051607) { - this.format = FileFormat.APPLE_DOUBLE; - } else { - throw new FileDecoderException("Invalid Apple file magic number."); - } - - /* Version number */ - int version = 0; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - if (version != 0x00020000) { - throw new FileDecoderException("Unknown Apple file version"); - } - - /* Filler */ - position += 16; - - /* Number of entries */ - this.numEntries = 0; - this.numEntries |= data[(position++)] & 0xFF; - this.numEntries <<= 8; - this.numEntries |= data[(position++)] & 0xFF; - if (length < 26 + 12 * this.numEntries) { - throw new FileDecoderException("Corrupt Apple file data."); - } - - /* Check entries */ - int entryId = 0; - int entryOffset = 0; - int entryLength = 0; - int contentPosition = 26 + 12 * this.numEntries; - for (int i = 0; i < this.numEntries; i++) { - position = 26 + i * 12; - /* Entry ID */ - entryId = 0; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - - /* Entry offset */ - entryOffset = 0; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset &= 0x7FFFFFFF; - - /* Entry length */ - entryLength = 0; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength &= 0x7FFFFFFF; - if ((entryOffset < contentPosition) - || (length < entryOffset + entryLength)) { - throw new FileDecoderException("Corrupt Apple file data."); - } - } - } - - /** - * Extract file dates. - * - * @param data the data - * @param offset the offset - * @param length the length - */ - protected void extractFileDates(byte[] data, int offset, int length) { - if ((0 > offset) || (offset > data.length)) - throw new IndexOutOfBoundsException(); - if ((0 > length) || (length > data.length - offset)) - throw new IndexOutOfBoundsException(); - - int position = offset; - - int createTime = 0; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - int modifyTime = 0; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - int backupTime = 0; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - int accessTime = 0; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - - this.fileDatesInfoEntry = new FileDatesInfoEntry(); - fileDatesInfoEntry.setCreateTime(createTime); - fileDatesInfoEntry.setModifyTime(modifyTime); - fileDatesInfoEntry.setBackupTime(backupTime); - fileDatesInfoEntry.setAccessTime(accessTime); - } - - /** - * Gets the format. - * - * @return the format - */ - public FileFormat getFormat() { - return format; - } - - /** - * Sets the format. - * - * @param format - * the new format - */ - public void setFormat(FileFormat format) { - this.format = format; - } - - /** - * Gets the file data. - * - * @return the file data - */ - public AppleFileData getFileData() { - return fileData; - } - - /** - * Sets the file data. - * - * @param fileData - * the new file data - */ - public void setFileData(AppleFileData fileData) { - this.fileData = fileData; - } - - /** - * Gets the data fork. - * - * @return the data fork - */ - public AppleFileData getDataFork() { - return dataFork; - } - - /** - * Sets the data fork. - * - * @param dataFork - * the new data fork - */ - public void setDataFork(AppleFileData dataFork) { - this.dataFork = dataFork; - } - - /** - * Gets the resource fork. - * - * @return the resource fork - */ - public AppleFileData getResourceFork() { - return resourceFork; - } - - /** - * Sets the resource fork. - * - * @param resourceFork - * the new resource fork - */ - public void setResourceFork(AppleFileData resourceFork) { - this.resourceFork = resourceFork; - } - - /** - * Gets the real name. - * - * @return the real name - */ - public AppleFileData getRealName() { - return realName; - } - - /** - * Sets the real name. - * - * @param realName - * the new real name - */ - public void setRealName(AppleFileData realName) { - this.realName = realName; - } - - /** - * Gets the comment. - * - * @return the comment - */ - public AppleFileData getComment() { - return comment; - } - - /** - * Sets the comment. - * - * @param comment - * the new comment - */ - public void setComment(AppleFileData comment) { - this.comment = comment; - } - - /** - * Gets the icon bw. - * - * @return the icon bw - */ - public AppleFileData getIconBW() { - return iconBW; - } - - /** - * Sets the icon bw. - * - * @param iconBW - * the new icon bw - */ - public void setIconBW(AppleFileData iconBW) { - this.iconBW = iconBW; - } - - /** - * Gets the icon color. - * - * @return the icon color - */ - public AppleFileData getIconColor() { - return iconColor; - } - - /** - * Sets the icon color. - * - * @param iconColor - * the new icon color - */ - public void setIconColor(AppleFileData iconColor) { - this.iconColor = iconColor; - } - - /** - * Gets the file dates info. - * - * @return the file dates info - */ - public AppleFileData getFileDatesInfo() { - return fileDatesInfo; - } - - /** - * Sets the file dates info. - * - * @param fileDatesInfo - * the new file dates info - */ - public void setFileDatesInfo(AppleFileData fileDatesInfo) { - this.fileDatesInfo = fileDatesInfo; - } - - /** - * Gets the finder info. - * - * @return the finder info - */ - public AppleFileData getFinderInfo() { - return finderInfo; - } - - /** - * Sets the finder info. - * - * @param finderInfo - * the new finder info - */ - public void setFinderInfo(AppleFileData finderInfo) { - this.finderInfo = finderInfo; - } - - /** - * Gets the macintosh info. - * - * @return the macintosh info - */ - public AppleFileData getMacintoshInfo() { - return macintoshInfo; - } - - /** - * Sets the macintosh info. - * - * @param macintoshInfo - * the new macintosh info - */ - public void setMacintoshInfo(AppleFileData macintoshInfo) { - this.macintoshInfo = macintoshInfo; - } - - /** - * Gets the pro dos file info. - * - * @return the pro dos file info - */ - public AppleFileData getProDOSFileInfo() { - return proDOSFileInfo; - } - - /** - * Sets the pro dos file info. - * - * @param proDOSFileInfo - * the new pro dos file info - */ - public void setProDOSFileInfo(AppleFileData proDOSFileInfo) { - this.proDOSFileInfo = proDOSFileInfo; - } - - /** - * Gets the ms dos file info. - * - * @return the ms dos file info - */ - public AppleFileData getMsDOSFileInfo() { - return msDOSFileInfo; - } - - /** - * Sets the ms dos file info. - * - * @param msDOSFileInfo - * the new ms dos file info - */ - public void setMsDOSFileInfo(AppleFileData msDOSFileInfo) { - this.msDOSFileInfo = msDOSFileInfo; - } - - /** - * Gets the short name. - * - * @return the short name - */ - public AppleFileData getShortName() { - return shortName; - } - - /** - * Sets the short name. - * - * @param shortName - * the new short name - */ - public void setShortName(AppleFileData shortName) { - this.shortName = shortName; - } - - /** - * Gets the afp file info. - * - * @return the afp file info - */ - public AppleFileData getAfpFileInfo() { - return afpFileInfo; - } - - /** - * Sets the afp file info. - * - * @param afpFileInfo - * the new afp file info - */ - public void setAfpFileInfo(AppleFileData afpFileInfo) { - this.afpFileInfo = afpFileInfo; - } - - /** - * Gets the directory id. - * - * @return the directory id - */ - public AppleFileData getDirectoryID() { - return directoryID; - } - - /** - * Sets the directory id. - * - * @param directoryID - * the new directory id - */ - public void setDirectoryID(AppleFileData directoryID) { - this.directoryID = directoryID; - } - - /** - * Gets the num entries. - * - * @return the num entries - */ - public int getNumEntries() { - return numEntries; - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; + +/** + * This abstract class handles AppleSingle/Double files. It contains a common + * method to verify the Apple file, and figure out if it is an AppleSingle or + * AppleDouble formatted file. + *

+ * + * The AppleSingle format is a representation of Macintosh files as one + * consecutive stream of bytes. AppleSingle combines the data fork, resource + * fork and the related Finder meta-file information into a single file. + *

+ * + * The AppleDouble format stores the data fork, resource fork as two separate + * files. AppleDouble leaves the data fork in its original format, and the + * resource fork and Finder information were combined into a second file. + *

+ * + * Apple defined the magic number for the AppleSingle format as 0x00051600, and + * the magic number for the AppleDouble format as 0x00051607. + * + *

+ * AppleSingle file header: 
+ * 
+ * Field Length
+ * ----- ------
+ * Magic number ------- 4 bytes
+ * Version number ------ 4 bytes
+ * Filler ------------- 16 bytes
+ * Number of entries ----- 2 bytes
+ * 
+ * Entry descriptor for each entry:
+ * Entry ID ------ 4 bytes
+ * Offset -------- 4 bytes
+ * Length -------- 4 bytes
+ * 
+ * Apple reserved entry IDs:
+ * 
+ * Data Fork -------- 1 Data fork
+ * Resource Fork ----- 2 Resource fork
+ * Real Name -------- 3 File's name as created on home file system
+ * Comment --------- 4 Standard Macintosh comment
+ * Icon, B&W -------- 5 Standard Macintosh black and white icon
+ * Icon, Color -------- 6 Macintosh color icon
+ * File Dates Info ------8 File creation date, modification date, and so on
+ * Finder Info -------- 9 Standard Macintosh Finder information
+ * Macintosh File Info ---10 Macintosh file information, attributes, and so on
+ * ProDOS File Info -----11 ProDOS file information, attributes, and so on
+ * MS-DOS File Info ----12 MS-DOS file information, attributes, and so on
+ * Short Name --------13 AFP short name
+ * AFP File Info ------- 14 AFP file information, attributes, and so on
+ * Directory ID --------15 AFP directory ID
+ * 
+ * + * See RFC 1740 for reference: http://tools.ietf.org/html/rfc1740 + */ +public abstract class AppleFile { + + /** The Apple file format: AppleSingle, AppleDouble, default to unknown. */ + protected FileFormat format = FileFormat.UNKNOWN; + + /** The raw Apple file. */ + protected AppleFileData fileData = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 1: Data fork. */ + protected AppleFileData dataFork = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 2: Resource fork. */ + protected AppleFileData resourceFork = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 3: File's name as created on home file system. */ + protected AppleFileData realName = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 4: Standard Macintosh comment. */ + protected AppleFileData comment = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 5: Standard Macintosh black and white icon. */ + protected AppleFileData iconBW = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 6: Macintosh color icon. */ + protected AppleFileData iconColor = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 8: File creation date, modification date, and so on. */ + protected AppleFileData fileDatesInfo = AppleFileData.EMPTY_FILE_DATA; + + /** The file dates info entry. */ + protected FileDatesInfoEntry fileDatesInfoEntry = null; + + /** Entry 9: Standard Macintosh Finder information. */ + protected AppleFileData finderInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 10: Macintosh file information, attributes, and so on. */ + protected AppleFileData macintoshInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 11: ProDOS file information, attributes, and so on. */ + protected AppleFileData proDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 12: MS-DOS file information, attributes, and so on. */ + protected AppleFileData msDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 13: AFP short name. */ + protected AppleFileData shortName = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 14: AFP file information, attributes, and so on. */ + protected AppleFileData afpFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 15: AFP directory ID. */ + protected AppleFileData directoryID = AppleFileData.EMPTY_FILE_DATA; + + /** The num entries. */ + protected int numEntries = 0; + + /** + * The Apple file format. + */ + public enum FileFormat { + + APPLE_SINGLE, + APPLE_DOUBLE, + UNKNOWN; + + /** + * Return a suitable Apple file format as inferred from the passed-in + * string. Otherwise return the UNKNOWN file format. + * + * @param fileFormat + * the file format + * @return the FileFormat + */ + public static FileFormat fromString(String fileFormat) { + if (fileFormat == null) { + return null; + } + + try { + return FileFormat.valueOf(fileFormat.toUpperCase()); + } catch (IllegalArgumentException iae) { + Log.error("Bad conversion attempt in FileFormat.fromString; string: " + + fileFormat + "; message: " + iae.getMessage()); + Log.exception(iae); + return UNKNOWN; + } + } + }; + + /** + * This class represents the file dates. + */ + public class FileDatesInfoEntry { + + /** The create time. */ + private int createTime = Integer.MIN_VALUE; + + /** The modify time. */ + private int modifyTime = Integer.MIN_VALUE; + + /** The backup time. */ + private int backupTime = Integer.MIN_VALUE; + + /** The access time. */ + private int accessTime = Integer.MIN_VALUE; + + /** + * Instantiates a new file dates info entry. + */ + public FileDatesInfoEntry() { + + } + + /** + * Gets the creates the time. + * + * @return the creates the time + */ + public int getCreateTime() { + return createTime; + } + + /** + * Sets the creates the time. + * + * @param createTime + * the new creates the time + */ + public void setCreateTime(int createTime) { + this.createTime = createTime; + } + + /** + * Gets the modify time. + * + * @return the modify time + */ + public int getModifyTime() { + return modifyTime; + } + + /** + * Sets the modify time. + * + * @param modifyTime + * the new modify time + */ + public void setModifyTime(int modifyTime) { + this.modifyTime = modifyTime; + } + + /** + * Gets the backup time. + * + * @return the backup time + */ + public int getBackupTime() { + return backupTime; + } + + /** + * Sets the backup time. + * + * @param backupTime + * the new backup time + */ + public void setBackupTime(int backupTime) { + this.backupTime = backupTime; + } + + /** + * Gets the access time. + * + * @return the access time + */ + public int getAccessTime() { + return accessTime; + } + + /** + * Sets the access time. + * + * @param accessTime + * the new access time + */ + public void setAccessTime(int accessTime) { + this.accessTime = accessTime; + } + } + + /** + * Sets the num entries. + * + * @param numEntries + * the new num entries + */ + public void setNumEntries(int numEntries) { + this.numEntries = numEntries; + } + + /** + * Verify the validity of the Apple file. + * + * @throws FileDecoderException + * the file decoder exception + */ + @SuppressWarnings("unused") + protected void verify() throws FileDecoderException { + byte[] data = this.fileData.getData(); + int offset = this.fileData.getOffset(); + int length = this.fileData.getLength(); + int position = offset; + if (length < 26) { + throw new FileDecoderException("File is too short"); + } + + /* Magic number */ + int magic = 0; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + + /* Check Apple file format: AppleSingle or AppleDobule */ + if (magic == 0x00051600) { + this.format = FileFormat.APPLE_SINGLE; + } else if (magic == 0x00051607) { + this.format = FileFormat.APPLE_DOUBLE; + } else { + throw new FileDecoderException("Invalid Apple file magic number."); + } + + /* Version number */ + int version = 0; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + if (version != 0x00020000) { + throw new FileDecoderException("Unknown Apple file version"); + } + + /* Filler */ + position += 16; + + /* Number of entries */ + this.numEntries = 0; + this.numEntries |= data[(position++)] & 0xFF; + this.numEntries <<= 8; + this.numEntries |= data[(position++)] & 0xFF; + if (length < 26 + 12 * this.numEntries) { + throw new FileDecoderException("Corrupt Apple file data."); + } + + /* Check entries */ + int entryId = 0; + int entryOffset = 0; + int entryLength = 0; + int contentPosition = 26 + 12 * this.numEntries; + for (int i = 0; i < this.numEntries; i++) { + position = 26 + i * 12; + /* Entry ID */ + entryId = 0; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + + /* Entry offset */ + entryOffset = 0; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset &= 0x7FFFFFFF; + + /* Entry length */ + entryLength = 0; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength &= 0x7FFFFFFF; + if ((entryOffset < contentPosition) + || (length < entryOffset + entryLength)) { + throw new FileDecoderException("Corrupt Apple file data."); + } + } + } + + /** + * Extract file dates. + * + * @param data the data + * @param offset the offset + * @param length the length + */ + protected void extractFileDates(byte[] data, int offset, int length) { + if ((0 > offset) || (offset > data.length)) + throw new IndexOutOfBoundsException(); + if ((0 > length) || (length > data.length - offset)) + throw new IndexOutOfBoundsException(); + + int position = offset; + + int createTime = 0; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + int modifyTime = 0; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + int backupTime = 0; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + int accessTime = 0; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + + this.fileDatesInfoEntry = new FileDatesInfoEntry(); + fileDatesInfoEntry.setCreateTime(createTime); + fileDatesInfoEntry.setModifyTime(modifyTime); + fileDatesInfoEntry.setBackupTime(backupTime); + fileDatesInfoEntry.setAccessTime(accessTime); + } + + /** + * Gets the format. + * + * @return the format + */ + public FileFormat getFormat() { + return format; + } + + /** + * Sets the format. + * + * @param format + * the new format + */ + public void setFormat(FileFormat format) { + this.format = format; + } + + /** + * Gets the file data. + * + * @return the file data + */ + public AppleFileData getFileData() { + return fileData; + } + + /** + * Sets the file data. + * + * @param fileData + * the new file data + */ + public void setFileData(AppleFileData fileData) { + this.fileData = fileData; + } + + /** + * Gets the data fork. + * + * @return the data fork + */ + public AppleFileData getDataFork() { + return dataFork; + } + + /** + * Sets the data fork. + * + * @param dataFork + * the new data fork + */ + public void setDataFork(AppleFileData dataFork) { + this.dataFork = dataFork; + } + + /** + * Gets the resource fork. + * + * @return the resource fork + */ + public AppleFileData getResourceFork() { + return resourceFork; + } + + /** + * Sets the resource fork. + * + * @param resourceFork + * the new resource fork + */ + public void setResourceFork(AppleFileData resourceFork) { + this.resourceFork = resourceFork; + } + + /** + * Gets the real name. + * + * @return the real name + */ + public AppleFileData getRealName() { + return realName; + } + + /** + * Sets the real name. + * + * @param realName + * the new real name + */ + public void setRealName(AppleFileData realName) { + this.realName = realName; + } + + /** + * Gets the comment. + * + * @return the comment + */ + public AppleFileData getComment() { + return comment; + } + + /** + * Sets the comment. + * + * @param comment + * the new comment + */ + public void setComment(AppleFileData comment) { + this.comment = comment; + } + + /** + * Gets the icon bw. + * + * @return the icon bw + */ + public AppleFileData getIconBW() { + return iconBW; + } + + /** + * Sets the icon bw. + * + * @param iconBW + * the new icon bw + */ + public void setIconBW(AppleFileData iconBW) { + this.iconBW = iconBW; + } + + /** + * Gets the icon color. + * + * @return the icon color + */ + public AppleFileData getIconColor() { + return iconColor; + } + + /** + * Sets the icon color. + * + * @param iconColor + * the new icon color + */ + public void setIconColor(AppleFileData iconColor) { + this.iconColor = iconColor; + } + + /** + * Gets the file dates info. + * + * @return the file dates info + */ + public AppleFileData getFileDatesInfo() { + return fileDatesInfo; + } + + /** + * Sets the file dates info. + * + * @param fileDatesInfo + * the new file dates info + */ + public void setFileDatesInfo(AppleFileData fileDatesInfo) { + this.fileDatesInfo = fileDatesInfo; + } + + /** + * Gets the finder info. + * + * @return the finder info + */ + public AppleFileData getFinderInfo() { + return finderInfo; + } + + /** + * Sets the finder info. + * + * @param finderInfo + * the new finder info + */ + public void setFinderInfo(AppleFileData finderInfo) { + this.finderInfo = finderInfo; + } + + /** + * Gets the macintosh info. + * + * @return the macintosh info + */ + public AppleFileData getMacintoshInfo() { + return macintoshInfo; + } + + /** + * Sets the macintosh info. + * + * @param macintoshInfo + * the new macintosh info + */ + public void setMacintoshInfo(AppleFileData macintoshInfo) { + this.macintoshInfo = macintoshInfo; + } + + /** + * Gets the pro dos file info. + * + * @return the pro dos file info + */ + public AppleFileData getProDOSFileInfo() { + return proDOSFileInfo; + } + + /** + * Sets the pro dos file info. + * + * @param proDOSFileInfo + * the new pro dos file info + */ + public void setProDOSFileInfo(AppleFileData proDOSFileInfo) { + this.proDOSFileInfo = proDOSFileInfo; + } + + /** + * Gets the ms dos file info. + * + * @return the ms dos file info + */ + public AppleFileData getMsDOSFileInfo() { + return msDOSFileInfo; + } + + /** + * Sets the ms dos file info. + * + * @param msDOSFileInfo + * the new ms dos file info + */ + public void setMsDOSFileInfo(AppleFileData msDOSFileInfo) { + this.msDOSFileInfo = msDOSFileInfo; + } + + /** + * Gets the short name. + * + * @return the short name + */ + public AppleFileData getShortName() { + return shortName; + } + + /** + * Sets the short name. + * + * @param shortName + * the new short name + */ + public void setShortName(AppleFileData shortName) { + this.shortName = shortName; + } + + /** + * Gets the afp file info. + * + * @return the afp file info + */ + public AppleFileData getAfpFileInfo() { + return afpFileInfo; + } + + /** + * Sets the afp file info. + * + * @param afpFileInfo + * the new afp file info + */ + public void setAfpFileInfo(AppleFileData afpFileInfo) { + this.afpFileInfo = afpFileInfo; + } + + /** + * Gets the directory id. + * + * @return the directory id + */ + public AppleFileData getDirectoryID() { + return directoryID; + } + + /** + * Sets the directory id. + * + * @param directoryID + * the new directory id + */ + public void setDirectoryID(AppleFileData directoryID) { + this.directoryID = directoryID; + } + + /** + * Gets the num entries. + * + * @return the num entries + */ + public int getNumEntries() { + return numEntries; + } } \ No newline at end of file diff --git a/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java b/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java index f28c979a..d89471e2 100644 --- a/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java +++ b/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java @@ -1,91 +1,91 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -/** - * This class is for representing the AppleSingle/Double file or its file forks - * (data fork and resource fork) and the related Finder meta-file information. - */ -public final class AppleFileData { - - public static final AppleFileData EMPTY_FILE_DATA = new AppleFileData(); - private byte[] data; - private int offset; - private int length; - - /** - * Instantiates a new apple file data. - */ - public AppleFileData() { - this.data = new byte[0]; - this.offset = 0; - this.length = 0; - } - - /** - * Instantiates a new apple file data. - * - * @param data the data - */ - public AppleFileData(byte[] data) { - this.data = data; - this.offset = 0; - this.length = data.length; - } - - /** - * Instantiates a new apple file data. - * - * @param data the data - * @param offset the offset - * @param length the length - */ - public AppleFileData(byte[] data, int offset, int length) { - if ((0 > offset) || (offset > data.length)) - throw new IndexOutOfBoundsException(); - if ((0 > length) || (length > data.length - offset)) - throw new IndexOutOfBoundsException(); - this.data = data; - this.offset = offset; - this.length = length; - } - - /** - * Gets the bytes. - * - * @return the bytes - */ - public byte[] getBytes() { - byte[] data = new byte[this.length]; - System.arraycopy(this.data, this.offset, data, 0, this.length); - return data; - } - - /** - * Gets the data. - * - * @return the data - */ - public byte[] getData() { - return this.data; - } - - /** - * Gets the offset. - * - * @return the offset - */ - public int getOffset() { - return this.offset; - } - - /** - * Gets the length. - * - * @return the length - */ - public int getLength() { - return this.length; - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +/** + * This class is for representing the AppleSingle/Double file or its file forks + * (data fork and resource fork) and the related Finder meta-file information. + */ +public final class AppleFileData { + + public static final AppleFileData EMPTY_FILE_DATA = new AppleFileData(); + private byte[] data; + private int offset; + private int length; + + /** + * Instantiates a new apple file data. + */ + public AppleFileData() { + this.data = new byte[0]; + this.offset = 0; + this.length = 0; + } + + /** + * Instantiates a new apple file data. + * + * @param data the data + */ + public AppleFileData(byte[] data) { + this.data = data; + this.offset = 0; + this.length = data.length; + } + + /** + * Instantiates a new apple file data. + * + * @param data the data + * @param offset the offset + * @param length the length + */ + public AppleFileData(byte[] data, int offset, int length) { + if ((0 > offset) || (offset > data.length)) + throw new IndexOutOfBoundsException(); + if ((0 > length) || (length > data.length - offset)) + throw new IndexOutOfBoundsException(); + this.data = data; + this.offset = offset; + this.length = length; + } + + /** + * Gets the bytes. + * + * @return the bytes + */ + public byte[] getBytes() { + byte[] data = new byte[this.length]; + System.arraycopy(this.data, this.offset, data, 0, this.length); + return data; + } + + /** + * Gets the data. + * + * @return the data + */ + public byte[] getData() { + return this.data; + } + + /** + * Gets the offset. + * + * @return the offset + */ + public int getOffset() { + return this.offset; + } + + /** + * Gets the length. + * + * @return the length + */ + public int getLength() { + return this.length; + } } \ No newline at end of file diff --git a/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java b/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java index 3248f85d..aa7b0363 100644 --- a/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java +++ b/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java @@ -1,143 +1,143 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; - -/** - * This class handles the extraction of the data fork, resource fork and other - * entries from an AppleSingle/Double file. The Perforce 'apple' file type is a - * compressed AppleSingle (Mac resource + data) file. The Perforce 'resource' - * file type is a compressed AppleDouble (Mac resource fork) file. - */ -public class AppleFileDecoder extends AppleFile { - - /** - * Instantiates a new apple file decoder. - * - * @param fileData - * the file data - */ - public AppleFileDecoder(AppleFileData fileData) { - if (fileData != null) { - this.fileData = fileData; - } - } - - /** - * Extract the data fork, resource fork and other entries from the Apple - * file. - * - * @throws FileDecoderException - * the file decoder exception - */ - @SuppressWarnings("unused") - public void extract() throws FileDecoderException { - // Verify the validity of the Apple file - verify(); - - byte[] data = this.fileData.getData(); - int offset = this.fileData.getOffset(); - int length = this.fileData.getLength(); - int contentPosition = 0; - int entryId = 0; - int entryOffset = 0; - int entryLength = 0; - for (int i = 0; i < this.numEntries; i++) { - contentPosition = offset + 26 + i * 12; - /* Entry ID */ - entryId = 0; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - - /* Entry offset */ - entryOffset = 0; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset &= 0x7FFFFFFF; - - /* Entry length */ - entryLength = 0; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength &= 0x7FFFFFFF; - - switch (entryId) { - case 1: - this.dataFork = new AppleFileData(data, offset + entryOffset, - entryLength); - break; - case 2: - this.resourceFork = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 3: - this.realName = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 4: - this.comment = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 5: - this.iconBW = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 6: - this.iconColor = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 8: - this.fileDatesInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - extractFileDates(data, offset + entryOffset, entryLength); - break; - case 9: - this.finderInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 10: - this.macintoshInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 11: - this.proDOSFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 12: - this.msDOSFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 13: - this.shortName = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 14: - this.afpFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 15: - this.directoryID = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - default: - Log.warn("Apple file entry ID: " + entryId + " is not handled."); - - } - } - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; + +/** + * This class handles the extraction of the data fork, resource fork and other + * entries from an AppleSingle/Double file. The Perforce 'apple' file type is a + * compressed AppleSingle (Mac resource + data) file. The Perforce 'resource' + * file type is a compressed AppleDouble (Mac resource fork) file. + */ +public class AppleFileDecoder extends AppleFile { + + /** + * Instantiates a new apple file decoder. + * + * @param fileData + * the file data + */ + public AppleFileDecoder(AppleFileData fileData) { + if (fileData != null) { + this.fileData = fileData; + } + } + + /** + * Extract the data fork, resource fork and other entries from the Apple + * file. + * + * @throws FileDecoderException + * the file decoder exception + */ + @SuppressWarnings("unused") + public void extract() throws FileDecoderException { + // Verify the validity of the Apple file + verify(); + + byte[] data = this.fileData.getData(); + int offset = this.fileData.getOffset(); + int length = this.fileData.getLength(); + int contentPosition = 0; + int entryId = 0; + int entryOffset = 0; + int entryLength = 0; + for (int i = 0; i < this.numEntries; i++) { + contentPosition = offset + 26 + i * 12; + /* Entry ID */ + entryId = 0; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + + /* Entry offset */ + entryOffset = 0; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset &= 0x7FFFFFFF; + + /* Entry length */ + entryLength = 0; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength &= 0x7FFFFFFF; + + switch (entryId) { + case 1: + this.dataFork = new AppleFileData(data, offset + entryOffset, + entryLength); + break; + case 2: + this.resourceFork = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 3: + this.realName = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 4: + this.comment = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 5: + this.iconBW = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 6: + this.iconColor = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 8: + this.fileDatesInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + extractFileDates(data, offset + entryOffset, entryLength); + break; + case 9: + this.finderInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 10: + this.macintoshInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 11: + this.proDOSFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 12: + this.msDOSFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 13: + this.shortName = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 14: + this.afpFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 15: + this.directoryID = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + default: + Log.warn("Apple file entry ID: " + entryId + " is not handled."); + + } + } + } } \ No newline at end of file diff --git a/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java b/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java index 7c5d0a04..abdfed59 100644 --- a/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java +++ b/p4java/r14-1/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java @@ -1,300 +1,300 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.exception.FileEncoderException; - -/** - * This class handles the combination of the data fork, resource fork and other - * entries into an AppleSingle/Double file. - *

- * - * Note that if it is an AppleDouble, the data fork is a separate file external - * to this file. - */ -public class AppleFileEncoder extends AppleFile { - - /** - * Instantiates a new apple file decoder. - * - * @throws FileEncoderException - */ - public AppleFileEncoder(FileFormat fileFormat) throws FileEncoderException { - if (fileFormat == null) { - throw new FileEncoderException("Null file format passed to the AppleFileEncoder constructor."); - } - if (fileFormat == FileFormat.UNKNOWN) { - throw new FileEncoderException("Unknown file format passed to the AppleFileEncoder constructor."); - } - } - - /** - * Combine the data fork, resource fork and other entries into an - * AppleSingle/Double file. - * - * @throws FileEncoderException - * the file encoder exception - */ - @SuppressWarnings("unused") - public void combine() throws FileEncoderException { - - boolean isAppleSingle = (this.format == FileFormat.APPLE_SINGLE); - boolean isAppleDouble = (this.format == FileFormat.APPLE_DOUBLE); - - boolean hasDataFork = (this.dataFork != AppleFileData.EMPTY_FILE_DATA); - boolean hasResourceFork = (this.resourceFork != AppleFileData.EMPTY_FILE_DATA); - boolean hasRealName = (this.realName != AppleFileData.EMPTY_FILE_DATA); - boolean hasComment = (this.comment != AppleFileData.EMPTY_FILE_DATA); - boolean hasIconBW = (this.iconBW != AppleFileData.EMPTY_FILE_DATA); - boolean hasIconColor = (this.iconColor != AppleFileData.EMPTY_FILE_DATA); - boolean hasFileDatesInfo = (this.fileDatesInfoEntry != null); - boolean hasFinderInfo = (this.finderInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasMacintoshInfo = (this.macintoshInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasProDOSFileInfo = (this.proDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasMsDOSFileInfo = (this.msDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasShortName = (this.shortName != AppleFileData.EMPTY_FILE_DATA); - boolean hasAfpFileInfo = (this.afpFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasDirectoryID = (this.directoryID != AppleFileData.EMPTY_FILE_DATA); - - this.fileData = AppleFileData.EMPTY_FILE_DATA; - - int length = 90 + this.realName.getLength() - + this.resourceFork.getLength(); - - /* AppleSingle includes the data fork */ - if (isAppleSingle) { - length += this.dataFork.getLength(); - } - - byte[] data = new byte[length]; - int position = 0; - - /* Magic number for AppleSingle or AppleDouble */ - if (isAppleDouble) { - data[(position++)] = 0; - data[(position++)] = 5; - data[(position++)] = 22; - data[(position++)] = 0; - } else { - data[(position++)] = 0; - data[(position++)] = 5; - data[(position++)] = 22; - data[(position++)] = 7; - } - - /* Version number */ - data[(position++)] = 0; - data[(position++)] = 2; - data[(position++)] = 0; - data[(position++)] = 0; - - /* Filler */ - - for (int k = 0; k < 16; k++) { - data[(position++)] = 0; - } - - /* Number of entries */ - this.numEntries = 0; - if (hasRealName) { - this.numEntries += 1; - } - if (hasFileDatesInfo) { - this.numEntries += 1; - } - if (hasResourceFork) { - this.numEntries += 1; - } - if ((hasDataFork) && (isAppleSingle)) { - this.numEntries += 1; - } - data[(position++)] = ((byte) (this.numEntries >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.numEntries & 0xFF)); - - /* Header information for the entries */ - - /* Real name entry header */ - int realNamePosition = 0; - if (hasRealName) { - int realNameEntryId = 3; - int realNameEntryOffset = 0; - int realNameEntryLength = this.realName.getLength(); - data[(position++)] = ((byte) (realNameEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 0 & 0xFF)); - realNamePosition = position; - data[(position++)] = ((byte) (realNameEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 0 & 0xFF)); - } - - /* File dates info entry header */ - int fileDatesInfoPosition = 0; - if (hasFileDatesInfo) { - int fileDatesInfoEntryId = 8; - int fileDatesInfoEntryOffset = 0; - int fileDatesInfoEntryLength = 16; - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 0 & 0xFF)); - fileDatesInfoPosition = position; - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 0 & 0xFF)); - } - - /* Resource fork entry header */ - int resourceForkPosition = 0; - if (hasResourceFork) { - int resourceForkEntryId = 2; - int resourceForkEntryOffset = 0; - int resourceFokrEntryLength = this.resourceFork.getLength(); - data[(position++)] = ((byte) (resourceForkEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 0 & 0xFF)); - resourceForkPosition = position; - data[(position++)] = ((byte) (resourceForkEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 0 & 0xFF)); - } - - /* Data fork entry header */ - int dataForkPosition = 0; - if ((hasDataFork) && (isAppleSingle)) { - int dataForkEntryId = 1; - int dataForkEntryOffset = 0; - int dataForkEntryLength = this.dataFork.getLength(); - data[(position++)] = ((byte) (dataForkEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 0 & 0xFF)); - dataForkPosition = position; - data[(position++)] = ((byte) (dataForkEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 0 & 0xFF)); - } - - /* Content for the entries */ - - /* Real name content */ - if (hasRealName) { - int realNamePositionCurrent = position; - position = realNamePosition; - data[(position++)] = ((byte) (realNamePositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 0 & 0xFF)); - position = realNamePositionCurrent; - byte[] realNameData = this.realName.getData(); - int realNameOffset = this.realName.getOffset(); - int realNameLength = this.realName.getLength(); - System.arraycopy(realNameData, realNameOffset, data, position, - realNameLength); - position += realNameLength; - } - - /* File dates info content */ - if (hasFileDatesInfo) { - int fileDatesInfoPositionCurrent = position; - position = fileDatesInfoPosition; - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 0 & 0xFF)); - position = fileDatesInfoPositionCurrent; - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 0 & 0xFF)); - } - - /* Resource fork content */ - if (hasResourceFork) { - int resourceForkPositionCurrent = position; - position = resourceForkPosition; - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 0 & 0xFF)); - position = resourceForkPositionCurrent; - byte[] resourceForkData = this.resourceFork.getData(); - int resourceForkOffset = this.resourceFork.getOffset(); - int resourceForkLength = this.resourceFork.getLength(); - System.arraycopy(resourceForkData, resourceForkOffset, data, - position, resourceForkLength); - position += resourceForkLength; - } - - /* Data fork content */ - if ((hasDataFork) && (isAppleSingle)) { - int dataForkPosition2 = position; - position = dataForkPosition; - data[(position++)] = ((byte) (dataForkPosition2 >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 0 & 0xFF)); - position = dataForkPosition2; - byte[] dataForkData = this.dataFork.getData(); - int dataForkOffset = this.dataFork.getOffset(); - int dataForkLength = this.dataFork.getLength(); - System.arraycopy(dataForkData, dataForkOffset, data, position, - dataForkLength); - position += dataForkLength; - } - - /* Create the Apple file data */ - this.fileData = new AppleFileData(data, 0, position); - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.exception.FileEncoderException; + +/** + * This class handles the combination of the data fork, resource fork and other + * entries into an AppleSingle/Double file. + *

+ * + * Note that if it is an AppleDouble, the data fork is a separate file external + * to this file. + */ +public class AppleFileEncoder extends AppleFile { + + /** + * Instantiates a new apple file decoder. + * + * @throws FileEncoderException + */ + public AppleFileEncoder(FileFormat fileFormat) throws FileEncoderException { + if (fileFormat == null) { + throw new FileEncoderException("Null file format passed to the AppleFileEncoder constructor."); + } + if (fileFormat == FileFormat.UNKNOWN) { + throw new FileEncoderException("Unknown file format passed to the AppleFileEncoder constructor."); + } + } + + /** + * Combine the data fork, resource fork and other entries into an + * AppleSingle/Double file. + * + * @throws FileEncoderException + * the file encoder exception + */ + @SuppressWarnings("unused") + public void combine() throws FileEncoderException { + + boolean isAppleSingle = (this.format == FileFormat.APPLE_SINGLE); + boolean isAppleDouble = (this.format == FileFormat.APPLE_DOUBLE); + + boolean hasDataFork = (this.dataFork != AppleFileData.EMPTY_FILE_DATA); + boolean hasResourceFork = (this.resourceFork != AppleFileData.EMPTY_FILE_DATA); + boolean hasRealName = (this.realName != AppleFileData.EMPTY_FILE_DATA); + boolean hasComment = (this.comment != AppleFileData.EMPTY_FILE_DATA); + boolean hasIconBW = (this.iconBW != AppleFileData.EMPTY_FILE_DATA); + boolean hasIconColor = (this.iconColor != AppleFileData.EMPTY_FILE_DATA); + boolean hasFileDatesInfo = (this.fileDatesInfoEntry != null); + boolean hasFinderInfo = (this.finderInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasMacintoshInfo = (this.macintoshInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasProDOSFileInfo = (this.proDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasMsDOSFileInfo = (this.msDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasShortName = (this.shortName != AppleFileData.EMPTY_FILE_DATA); + boolean hasAfpFileInfo = (this.afpFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasDirectoryID = (this.directoryID != AppleFileData.EMPTY_FILE_DATA); + + this.fileData = AppleFileData.EMPTY_FILE_DATA; + + int length = 90 + this.realName.getLength() + + this.resourceFork.getLength(); + + /* AppleSingle includes the data fork */ + if (isAppleSingle) { + length += this.dataFork.getLength(); + } + + byte[] data = new byte[length]; + int position = 0; + + /* Magic number for AppleSingle or AppleDouble */ + if (isAppleDouble) { + data[(position++)] = 0; + data[(position++)] = 5; + data[(position++)] = 22; + data[(position++)] = 0; + } else { + data[(position++)] = 0; + data[(position++)] = 5; + data[(position++)] = 22; + data[(position++)] = 7; + } + + /* Version number */ + data[(position++)] = 0; + data[(position++)] = 2; + data[(position++)] = 0; + data[(position++)] = 0; + + /* Filler */ + + for (int k = 0; k < 16; k++) { + data[(position++)] = 0; + } + + /* Number of entries */ + this.numEntries = 0; + if (hasRealName) { + this.numEntries += 1; + } + if (hasFileDatesInfo) { + this.numEntries += 1; + } + if (hasResourceFork) { + this.numEntries += 1; + } + if ((hasDataFork) && (isAppleSingle)) { + this.numEntries += 1; + } + data[(position++)] = ((byte) (this.numEntries >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.numEntries & 0xFF)); + + /* Header information for the entries */ + + /* Real name entry header */ + int realNamePosition = 0; + if (hasRealName) { + int realNameEntryId = 3; + int realNameEntryOffset = 0; + int realNameEntryLength = this.realName.getLength(); + data[(position++)] = ((byte) (realNameEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 0 & 0xFF)); + realNamePosition = position; + data[(position++)] = ((byte) (realNameEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 0 & 0xFF)); + } + + /* File dates info entry header */ + int fileDatesInfoPosition = 0; + if (hasFileDatesInfo) { + int fileDatesInfoEntryId = 8; + int fileDatesInfoEntryOffset = 0; + int fileDatesInfoEntryLength = 16; + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 0 & 0xFF)); + fileDatesInfoPosition = position; + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 0 & 0xFF)); + } + + /* Resource fork entry header */ + int resourceForkPosition = 0; + if (hasResourceFork) { + int resourceForkEntryId = 2; + int resourceForkEntryOffset = 0; + int resourceFokrEntryLength = this.resourceFork.getLength(); + data[(position++)] = ((byte) (resourceForkEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 0 & 0xFF)); + resourceForkPosition = position; + data[(position++)] = ((byte) (resourceForkEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 0 & 0xFF)); + } + + /* Data fork entry header */ + int dataForkPosition = 0; + if ((hasDataFork) && (isAppleSingle)) { + int dataForkEntryId = 1; + int dataForkEntryOffset = 0; + int dataForkEntryLength = this.dataFork.getLength(); + data[(position++)] = ((byte) (dataForkEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 0 & 0xFF)); + dataForkPosition = position; + data[(position++)] = ((byte) (dataForkEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 0 & 0xFF)); + } + + /* Content for the entries */ + + /* Real name content */ + if (hasRealName) { + int realNamePositionCurrent = position; + position = realNamePosition; + data[(position++)] = ((byte) (realNamePositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 0 & 0xFF)); + position = realNamePositionCurrent; + byte[] realNameData = this.realName.getData(); + int realNameOffset = this.realName.getOffset(); + int realNameLength = this.realName.getLength(); + System.arraycopy(realNameData, realNameOffset, data, position, + realNameLength); + position += realNameLength; + } + + /* File dates info content */ + if (hasFileDatesInfo) { + int fileDatesInfoPositionCurrent = position; + position = fileDatesInfoPosition; + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 0 & 0xFF)); + position = fileDatesInfoPositionCurrent; + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 0 & 0xFF)); + } + + /* Resource fork content */ + if (hasResourceFork) { + int resourceForkPositionCurrent = position; + position = resourceForkPosition; + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 0 & 0xFF)); + position = resourceForkPositionCurrent; + byte[] resourceForkData = this.resourceFork.getData(); + int resourceForkOffset = this.resourceFork.getOffset(); + int resourceForkLength = this.resourceFork.getLength(); + System.arraycopy(resourceForkData, resourceForkOffset, data, + position, resourceForkLength); + position += resourceForkLength; + } + + /* Data fork content */ + if ((hasDataFork) && (isAppleSingle)) { + int dataForkPosition2 = position; + position = dataForkPosition; + data[(position++)] = ((byte) (dataForkPosition2 >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 0 & 0xFF)); + position = dataForkPosition2; + byte[] dataForkData = this.dataFork.getData(); + int dataForkOffset = this.dataFork.getOffset(); + int dataForkLength = this.dataFork.getLength(); + System.arraycopy(dataForkData, dataForkOffset, data, position, + dataForkLength); + position += dataForkLength; + } + + /* Create the Apple file data */ + this.fileData = new AppleFileData(data, 0, position); + } } \ No newline at end of file diff --git a/p4java/r17-2/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java b/p4java/r17-2/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java index 955735f4..75e9be6e 100644 --- a/p4java/r17-2/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java +++ b/p4java/r17-2/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java @@ -1,118 +1,118 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.impl.mapbased.rpc.sys.helper; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; -import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFile; -import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFileType; -import com.perforce.p4java.io.apple.AppleFileData; -import com.perforce.p4java.io.apple.AppleFileDecoder; - -/** - * Helper class for handling Apple files. - */ -public class AppleFileHelper { - - /** - * Extract the data fork and the resource fork from the Apple file. - * - * @param file the Apple file - */ - public static void extractFile(RpcPerforceFile file) { - if (file.getFileType() == RpcPerforceFileType.FST_APPLEFILE) { - FileOutputStream fosData = null; - FileOutputStream fosResource = null; - try { - byte[] data = AppleFileHelper.getBytesFromFile(file); - AppleFileData fileData = new AppleFileData(data); - AppleFileDecoder appleFile = new AppleFileDecoder(fileData); - appleFile.extract(); - fosData = new FileOutputStream(file); - AppleFileData forkData = appleFile.getDataFork(); - if (forkData != AppleFileData.EMPTY_FILE_DATA) { - fosData.write(forkData.getBytes()); - } - String resourceFilePath = file.getParent() + File.separator + "%" + file.getName(); - RpcPerforceFile targetResourceFile = new RpcPerforceFile(resourceFilePath, file.getFileType()); - fosResource = new FileOutputStream(targetResourceFile); - AppleFileData forkResource = appleFile.getResourceFork(); - if (forkResource != AppleFileData.EMPTY_FILE_DATA) { - fosResource.write(forkResource.getBytes()); - } - } catch (IOException e) { - Log.error("Problem handling the Apple file: " + file.getName()); - } catch (FileDecoderException e) { - Log.error("Problem decoding the Apple file: " + file.getName()); - } finally { - if (fosData != null) { - try { - fosData.close(); - } catch (Exception e) { - // Do nothing - } - } - if (fosResource != null) { - try { - fosResource.close(); - } catch (Exception e) { - // Do nothing - } - } - } - } - } - - /** - * Gets the bytes from file. - * - * @param file - * the file - * @return the bytes from file - * @throws IOException - * Signals that an I/O exception has occurred. - */ - public static byte[] getBytesFromFile(File file) throws IOException { - InputStream is = new FileInputStream(file); - - long length = file.length(); - if (length > Integer.MAX_VALUE) { - // File is too large - throw new IOException("Apple file too large for decoding."); - } - - byte[] bytes = new byte[(int) length]; - int offset = 0; - int numRead = 0; - - try { - while (offset < bytes.length - && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { - offset += numRead; - } - // Ensure all the bytes have been read in - if (offset < bytes.length) { - throw new IOException( - "Could not completely read the Apple file " - + file.getName()); - } - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - // Do nothing - } - } - } - - return bytes; - } -} +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.impl.mapbased.rpc.sys.helper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; +import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFile; +import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFileType; +import com.perforce.p4java.io.apple.AppleFileData; +import com.perforce.p4java.io.apple.AppleFileDecoder; + +/** + * Helper class for handling Apple files. + */ +public class AppleFileHelper { + + /** + * Extract the data fork and the resource fork from the Apple file. + * + * @param file the Apple file + */ + public static void extractFile(RpcPerforceFile file) { + if (file.getFileType() == RpcPerforceFileType.FST_APPLEFILE) { + FileOutputStream fosData = null; + FileOutputStream fosResource = null; + try { + byte[] data = AppleFileHelper.getBytesFromFile(file); + AppleFileData fileData = new AppleFileData(data); + AppleFileDecoder appleFile = new AppleFileDecoder(fileData); + appleFile.extract(); + fosData = new FileOutputStream(file); + AppleFileData forkData = appleFile.getDataFork(); + if (forkData != AppleFileData.EMPTY_FILE_DATA) { + fosData.write(forkData.getBytes()); + } + String resourceFilePath = file.getParent() + File.separator + "%" + file.getName(); + RpcPerforceFile targetResourceFile = new RpcPerforceFile(resourceFilePath, file.getFileType()); + fosResource = new FileOutputStream(targetResourceFile); + AppleFileData forkResource = appleFile.getResourceFork(); + if (forkResource != AppleFileData.EMPTY_FILE_DATA) { + fosResource.write(forkResource.getBytes()); + } + } catch (IOException e) { + Log.error("Problem handling the Apple file: " + file.getName()); + } catch (FileDecoderException e) { + Log.error("Problem decoding the Apple file: " + file.getName()); + } finally { + if (fosData != null) { + try { + fosData.close(); + } catch (Exception e) { + // Do nothing + } + } + if (fosResource != null) { + try { + fosResource.close(); + } catch (Exception e) { + // Do nothing + } + } + } + } + } + + /** + * Gets the bytes from file. + * + * @param file + * the file + * @return the bytes from file + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public static byte[] getBytesFromFile(File file) throws IOException { + InputStream is = new FileInputStream(file); + + long length = file.length(); + if (length > Integer.MAX_VALUE) { + // File is too large + throw new IOException("Apple file too large for decoding."); + } + + byte[] bytes = new byte[(int) length]; + int offset = 0; + int numRead = 0; + + try { + while (offset < bytes.length + && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { + offset += numRead; + } + // Ensure all the bytes have been read in + if (offset < bytes.length) { + throw new IOException( + "Could not completely read the Apple file " + + file.getName()); + } + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + // Do nothing + } + } + } + + return bytes; + } +} diff --git a/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFile.java b/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFile.java index 5b89f33a..615bfcc9 100644 --- a/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFile.java +++ b/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFile.java @@ -1,735 +1,735 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; - -/** - * This abstract class handles AppleSingle/Double files. It contains a common - * method to verify the Apple file, and figure out if it is an AppleSingle or - * AppleDouble formatted file. - *

- * - * The AppleSingle format is a representation of Macintosh files as one - * consecutive stream of bytes. AppleSingle combines the data fork, resource - * fork and the related Finder meta-file information into a single file. - *

- * - * The AppleDouble format stores the data fork, resource fork as two separate - * files. AppleDouble leaves the data fork in its original format, and the - * resource fork and Finder information were combined into a second file. - *

- * - * Apple defined the magic number for the AppleSingle format as 0x00051600, and - * the magic number for the AppleDouble format as 0x00051607. - * - *

- * AppleSingle file header: 
- * 
- * Field Length
- * ----- ------
- * Magic number ------- 4 bytes
- * Version number ------ 4 bytes
- * Filler ------------- 16 bytes
- * Number of entries ----- 2 bytes
- * 
- * Entry descriptor for each entry:
- * Entry ID ------ 4 bytes
- * Offset -------- 4 bytes
- * Length -------- 4 bytes
- * 
- * Apple reserved entry IDs:
- * 
- * Data Fork -------- 1 Data fork
- * Resource Fork ----- 2 Resource fork
- * Real Name -------- 3 File's name as created on home file system
- * Comment --------- 4 Standard Macintosh comment
- * Icon, B&W -------- 5 Standard Macintosh black and white icon
- * Icon, Color -------- 6 Macintosh color icon
- * File Dates Info ------8 File creation date, modification date, and so on
- * Finder Info -------- 9 Standard Macintosh Finder information
- * Macintosh File Info ---10 Macintosh file information, attributes, and so on
- * ProDOS File Info -----11 ProDOS file information, attributes, and so on
- * MS-DOS File Info ----12 MS-DOS file information, attributes, and so on
- * Short Name --------13 AFP short name
- * AFP File Info ------- 14 AFP file information, attributes, and so on
- * Directory ID --------15 AFP directory ID
- * 
- * - * See RFC 1740 for reference: http://tools.ietf.org/html/rfc1740 - */ -public abstract class AppleFile { - - /** The Apple file format: AppleSingle, AppleDouble, default to unknown. */ - protected FileFormat format = FileFormat.UNKNOWN; - - /** The raw Apple file. */ - protected AppleFileData fileData = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 1: Data fork. */ - protected AppleFileData dataFork = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 2: Resource fork. */ - protected AppleFileData resourceFork = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 3: File's name as created on home file system. */ - protected AppleFileData realName = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 4: Standard Macintosh comment. */ - protected AppleFileData comment = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 5: Standard Macintosh black and white icon. */ - protected AppleFileData iconBW = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 6: Macintosh color icon. */ - protected AppleFileData iconColor = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 8: File creation date, modification date, and so on. */ - protected AppleFileData fileDatesInfo = AppleFileData.EMPTY_FILE_DATA; - - /** The file dates info entry. */ - protected FileDatesInfoEntry fileDatesInfoEntry = null; - - /** Entry 9: Standard Macintosh Finder information. */ - protected AppleFileData finderInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 10: Macintosh file information, attributes, and so on. */ - protected AppleFileData macintoshInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 11: ProDOS file information, attributes, and so on. */ - protected AppleFileData proDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 12: MS-DOS file information, attributes, and so on. */ - protected AppleFileData msDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 13: AFP short name. */ - protected AppleFileData shortName = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 14: AFP file information, attributes, and so on. */ - protected AppleFileData afpFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 15: AFP directory ID. */ - protected AppleFileData directoryID = AppleFileData.EMPTY_FILE_DATA; - - /** The num entries. */ - protected int numEntries = 0; - - /** - * The Apple file format. - */ - public enum FileFormat { - - APPLE_SINGLE, - APPLE_DOUBLE, - UNKNOWN; - - /** - * Return a suitable Apple file format as inferred from the passed-in - * string. Otherwise return the UNKNOWN file format. - * - * @param fileFormat - * the file format - * @return the FileFormat - */ - public static FileFormat fromString(String fileFormat) { - if (fileFormat == null) { - return null; - } - - try { - return FileFormat.valueOf(fileFormat.toUpperCase()); - } catch (IllegalArgumentException iae) { - Log.error("Bad conversion attempt in FileFormat.fromString; string: " - + fileFormat + "; message: " + iae.getMessage()); - Log.exception(iae); - return UNKNOWN; - } - } - }; - - /** - * This class represents the file dates. - */ - public class FileDatesInfoEntry { - - /** The create time. */ - private int createTime = Integer.MIN_VALUE; - - /** The modify time. */ - private int modifyTime = Integer.MIN_VALUE; - - /** The backup time. */ - private int backupTime = Integer.MIN_VALUE; - - /** The access time. */ - private int accessTime = Integer.MIN_VALUE; - - /** - * Instantiates a new file dates info entry. - */ - public FileDatesInfoEntry() { - - } - - /** - * Gets the creates the time. - * - * @return the creates the time - */ - public int getCreateTime() { - return createTime; - } - - /** - * Sets the creates the time. - * - * @param createTime - * the new creates the time - */ - public void setCreateTime(int createTime) { - this.createTime = createTime; - } - - /** - * Gets the modify time. - * - * @return the modify time - */ - public int getModifyTime() { - return modifyTime; - } - - /** - * Sets the modify time. - * - * @param modifyTime - * the new modify time - */ - public void setModifyTime(int modifyTime) { - this.modifyTime = modifyTime; - } - - /** - * Gets the backup time. - * - * @return the backup time - */ - public int getBackupTime() { - return backupTime; - } - - /** - * Sets the backup time. - * - * @param backupTime - * the new backup time - */ - public void setBackupTime(int backupTime) { - this.backupTime = backupTime; - } - - /** - * Gets the access time. - * - * @return the access time - */ - public int getAccessTime() { - return accessTime; - } - - /** - * Sets the access time. - * - * @param accessTime - * the new access time - */ - public void setAccessTime(int accessTime) { - this.accessTime = accessTime; - } - } - - /** - * Sets the num entries. - * - * @param numEntries - * the new num entries - */ - public void setNumEntries(int numEntries) { - this.numEntries = numEntries; - } - - /** - * Verify the validity of the Apple file. - * - * @throws FileDecoderException - * the file decoder exception - */ - @SuppressWarnings("unused") - protected void verify() throws FileDecoderException { - byte[] data = this.fileData.getData(); - int offset = this.fileData.getOffset(); - int length = this.fileData.getLength(); - int position = offset; - if (length < 26) { - throw new FileDecoderException("File is too short"); - } - - /* Magic number */ - int magic = 0; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - - /* Check Apple file format: AppleSingle or AppleDobule */ - if (magic == 0x00051600) { - this.format = FileFormat.APPLE_SINGLE; - } else if (magic == 0x00051607) { - this.format = FileFormat.APPLE_DOUBLE; - } else { - throw new FileDecoderException("Invalid Apple file magic number."); - } - - /* Version number */ - int version = 0; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - if (version != 0x00020000) { - throw new FileDecoderException("Unknown Apple file version"); - } - - /* Filler */ - position += 16; - - /* Number of entries */ - this.numEntries = 0; - this.numEntries |= data[(position++)] & 0xFF; - this.numEntries <<= 8; - this.numEntries |= data[(position++)] & 0xFF; - if (length < 26 + 12 * this.numEntries) { - throw new FileDecoderException("Corrupt Apple file data."); - } - - /* Check entries */ - int entryId = 0; - int entryOffset = 0; - int entryLength = 0; - int contentPosition = 26 + 12 * this.numEntries; - for (int i = 0; i < this.numEntries; i++) { - position = 26 + i * 12; - /* Entry ID */ - entryId = 0; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - - /* Entry offset */ - entryOffset = 0; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset &= 0x7FFFFFFF; - - /* Entry length */ - entryLength = 0; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength &= 0x7FFFFFFF; - if ((entryOffset < contentPosition) - || (length < entryOffset + entryLength)) { - throw new FileDecoderException("Corrupt Apple file data."); - } - } - } - - /** - * Extract file dates. - * - * @param data the data - * @param offset the offset - * @param length the length - */ - protected void extractFileDates(byte[] data, int offset, int length) { - if ((0 > offset) || (offset > data.length)) - throw new IndexOutOfBoundsException(); - if ((0 > length) || (length > data.length - offset)) - throw new IndexOutOfBoundsException(); - - int position = offset; - - int createTime = 0; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - int modifyTime = 0; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - int backupTime = 0; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - int accessTime = 0; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - - this.fileDatesInfoEntry = new FileDatesInfoEntry(); - fileDatesInfoEntry.setCreateTime(createTime); - fileDatesInfoEntry.setModifyTime(modifyTime); - fileDatesInfoEntry.setBackupTime(backupTime); - fileDatesInfoEntry.setAccessTime(accessTime); - } - - /** - * Gets the format. - * - * @return the format - */ - public FileFormat getFormat() { - return format; - } - - /** - * Sets the format. - * - * @param format - * the new format - */ - public void setFormat(FileFormat format) { - this.format = format; - } - - /** - * Gets the file data. - * - * @return the file data - */ - public AppleFileData getFileData() { - return fileData; - } - - /** - * Sets the file data. - * - * @param fileData - * the new file data - */ - public void setFileData(AppleFileData fileData) { - this.fileData = fileData; - } - - /** - * Gets the data fork. - * - * @return the data fork - */ - public AppleFileData getDataFork() { - return dataFork; - } - - /** - * Sets the data fork. - * - * @param dataFork - * the new data fork - */ - public void setDataFork(AppleFileData dataFork) { - this.dataFork = dataFork; - } - - /** - * Gets the resource fork. - * - * @return the resource fork - */ - public AppleFileData getResourceFork() { - return resourceFork; - } - - /** - * Sets the resource fork. - * - * @param resourceFork - * the new resource fork - */ - public void setResourceFork(AppleFileData resourceFork) { - this.resourceFork = resourceFork; - } - - /** - * Gets the real name. - * - * @return the real name - */ - public AppleFileData getRealName() { - return realName; - } - - /** - * Sets the real name. - * - * @param realName - * the new real name - */ - public void setRealName(AppleFileData realName) { - this.realName = realName; - } - - /** - * Gets the comment. - * - * @return the comment - */ - public AppleFileData getComment() { - return comment; - } - - /** - * Sets the comment. - * - * @param comment - * the new comment - */ - public void setComment(AppleFileData comment) { - this.comment = comment; - } - - /** - * Gets the icon bw. - * - * @return the icon bw - */ - public AppleFileData getIconBW() { - return iconBW; - } - - /** - * Sets the icon bw. - * - * @param iconBW - * the new icon bw - */ - public void setIconBW(AppleFileData iconBW) { - this.iconBW = iconBW; - } - - /** - * Gets the icon color. - * - * @return the icon color - */ - public AppleFileData getIconColor() { - return iconColor; - } - - /** - * Sets the icon color. - * - * @param iconColor - * the new icon color - */ - public void setIconColor(AppleFileData iconColor) { - this.iconColor = iconColor; - } - - /** - * Gets the file dates info. - * - * @return the file dates info - */ - public AppleFileData getFileDatesInfo() { - return fileDatesInfo; - } - - /** - * Sets the file dates info. - * - * @param fileDatesInfo - * the new file dates info - */ - public void setFileDatesInfo(AppleFileData fileDatesInfo) { - this.fileDatesInfo = fileDatesInfo; - } - - /** - * Gets the finder info. - * - * @return the finder info - */ - public AppleFileData getFinderInfo() { - return finderInfo; - } - - /** - * Sets the finder info. - * - * @param finderInfo - * the new finder info - */ - public void setFinderInfo(AppleFileData finderInfo) { - this.finderInfo = finderInfo; - } - - /** - * Gets the macintosh info. - * - * @return the macintosh info - */ - public AppleFileData getMacintoshInfo() { - return macintoshInfo; - } - - /** - * Sets the macintosh info. - * - * @param macintoshInfo - * the new macintosh info - */ - public void setMacintoshInfo(AppleFileData macintoshInfo) { - this.macintoshInfo = macintoshInfo; - } - - /** - * Gets the pro dos file info. - * - * @return the pro dos file info - */ - public AppleFileData getProDOSFileInfo() { - return proDOSFileInfo; - } - - /** - * Sets the pro dos file info. - * - * @param proDOSFileInfo - * the new pro dos file info - */ - public void setProDOSFileInfo(AppleFileData proDOSFileInfo) { - this.proDOSFileInfo = proDOSFileInfo; - } - - /** - * Gets the ms dos file info. - * - * @return the ms dos file info - */ - public AppleFileData getMsDOSFileInfo() { - return msDOSFileInfo; - } - - /** - * Sets the ms dos file info. - * - * @param msDOSFileInfo - * the new ms dos file info - */ - public void setMsDOSFileInfo(AppleFileData msDOSFileInfo) { - this.msDOSFileInfo = msDOSFileInfo; - } - - /** - * Gets the short name. - * - * @return the short name - */ - public AppleFileData getShortName() { - return shortName; - } - - /** - * Sets the short name. - * - * @param shortName - * the new short name - */ - public void setShortName(AppleFileData shortName) { - this.shortName = shortName; - } - - /** - * Gets the afp file info. - * - * @return the afp file info - */ - public AppleFileData getAfpFileInfo() { - return afpFileInfo; - } - - /** - * Sets the afp file info. - * - * @param afpFileInfo - * the new afp file info - */ - public void setAfpFileInfo(AppleFileData afpFileInfo) { - this.afpFileInfo = afpFileInfo; - } - - /** - * Gets the directory id. - * - * @return the directory id - */ - public AppleFileData getDirectoryID() { - return directoryID; - } - - /** - * Sets the directory id. - * - * @param directoryID - * the new directory id - */ - public void setDirectoryID(AppleFileData directoryID) { - this.directoryID = directoryID; - } - - /** - * Gets the num entries. - * - * @return the num entries - */ - public int getNumEntries() { - return numEntries; - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; + +/** + * This abstract class handles AppleSingle/Double files. It contains a common + * method to verify the Apple file, and figure out if it is an AppleSingle or + * AppleDouble formatted file. + *

+ * + * The AppleSingle format is a representation of Macintosh files as one + * consecutive stream of bytes. AppleSingle combines the data fork, resource + * fork and the related Finder meta-file information into a single file. + *

+ * + * The AppleDouble format stores the data fork, resource fork as two separate + * files. AppleDouble leaves the data fork in its original format, and the + * resource fork and Finder information were combined into a second file. + *

+ * + * Apple defined the magic number for the AppleSingle format as 0x00051600, and + * the magic number for the AppleDouble format as 0x00051607. + * + *

+ * AppleSingle file header: 
+ * 
+ * Field Length
+ * ----- ------
+ * Magic number ------- 4 bytes
+ * Version number ------ 4 bytes
+ * Filler ------------- 16 bytes
+ * Number of entries ----- 2 bytes
+ * 
+ * Entry descriptor for each entry:
+ * Entry ID ------ 4 bytes
+ * Offset -------- 4 bytes
+ * Length -------- 4 bytes
+ * 
+ * Apple reserved entry IDs:
+ * 
+ * Data Fork -------- 1 Data fork
+ * Resource Fork ----- 2 Resource fork
+ * Real Name -------- 3 File's name as created on home file system
+ * Comment --------- 4 Standard Macintosh comment
+ * Icon, B&W -------- 5 Standard Macintosh black and white icon
+ * Icon, Color -------- 6 Macintosh color icon
+ * File Dates Info ------8 File creation date, modification date, and so on
+ * Finder Info -------- 9 Standard Macintosh Finder information
+ * Macintosh File Info ---10 Macintosh file information, attributes, and so on
+ * ProDOS File Info -----11 ProDOS file information, attributes, and so on
+ * MS-DOS File Info ----12 MS-DOS file information, attributes, and so on
+ * Short Name --------13 AFP short name
+ * AFP File Info ------- 14 AFP file information, attributes, and so on
+ * Directory ID --------15 AFP directory ID
+ * 
+ * + * See RFC 1740 for reference: http://tools.ietf.org/html/rfc1740 + */ +public abstract class AppleFile { + + /** The Apple file format: AppleSingle, AppleDouble, default to unknown. */ + protected FileFormat format = FileFormat.UNKNOWN; + + /** The raw Apple file. */ + protected AppleFileData fileData = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 1: Data fork. */ + protected AppleFileData dataFork = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 2: Resource fork. */ + protected AppleFileData resourceFork = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 3: File's name as created on home file system. */ + protected AppleFileData realName = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 4: Standard Macintosh comment. */ + protected AppleFileData comment = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 5: Standard Macintosh black and white icon. */ + protected AppleFileData iconBW = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 6: Macintosh color icon. */ + protected AppleFileData iconColor = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 8: File creation date, modification date, and so on. */ + protected AppleFileData fileDatesInfo = AppleFileData.EMPTY_FILE_DATA; + + /** The file dates info entry. */ + protected FileDatesInfoEntry fileDatesInfoEntry = null; + + /** Entry 9: Standard Macintosh Finder information. */ + protected AppleFileData finderInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 10: Macintosh file information, attributes, and so on. */ + protected AppleFileData macintoshInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 11: ProDOS file information, attributes, and so on. */ + protected AppleFileData proDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 12: MS-DOS file information, attributes, and so on. */ + protected AppleFileData msDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 13: AFP short name. */ + protected AppleFileData shortName = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 14: AFP file information, attributes, and so on. */ + protected AppleFileData afpFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 15: AFP directory ID. */ + protected AppleFileData directoryID = AppleFileData.EMPTY_FILE_DATA; + + /** The num entries. */ + protected int numEntries = 0; + + /** + * The Apple file format. + */ + public enum FileFormat { + + APPLE_SINGLE, + APPLE_DOUBLE, + UNKNOWN; + + /** + * Return a suitable Apple file format as inferred from the passed-in + * string. Otherwise return the UNKNOWN file format. + * + * @param fileFormat + * the file format + * @return the FileFormat + */ + public static FileFormat fromString(String fileFormat) { + if (fileFormat == null) { + return null; + } + + try { + return FileFormat.valueOf(fileFormat.toUpperCase()); + } catch (IllegalArgumentException iae) { + Log.error("Bad conversion attempt in FileFormat.fromString; string: " + + fileFormat + "; message: " + iae.getMessage()); + Log.exception(iae); + return UNKNOWN; + } + } + }; + + /** + * This class represents the file dates. + */ + public class FileDatesInfoEntry { + + /** The create time. */ + private int createTime = Integer.MIN_VALUE; + + /** The modify time. */ + private int modifyTime = Integer.MIN_VALUE; + + /** The backup time. */ + private int backupTime = Integer.MIN_VALUE; + + /** The access time. */ + private int accessTime = Integer.MIN_VALUE; + + /** + * Instantiates a new file dates info entry. + */ + public FileDatesInfoEntry() { + + } + + /** + * Gets the creates the time. + * + * @return the creates the time + */ + public int getCreateTime() { + return createTime; + } + + /** + * Sets the creates the time. + * + * @param createTime + * the new creates the time + */ + public void setCreateTime(int createTime) { + this.createTime = createTime; + } + + /** + * Gets the modify time. + * + * @return the modify time + */ + public int getModifyTime() { + return modifyTime; + } + + /** + * Sets the modify time. + * + * @param modifyTime + * the new modify time + */ + public void setModifyTime(int modifyTime) { + this.modifyTime = modifyTime; + } + + /** + * Gets the backup time. + * + * @return the backup time + */ + public int getBackupTime() { + return backupTime; + } + + /** + * Sets the backup time. + * + * @param backupTime + * the new backup time + */ + public void setBackupTime(int backupTime) { + this.backupTime = backupTime; + } + + /** + * Gets the access time. + * + * @return the access time + */ + public int getAccessTime() { + return accessTime; + } + + /** + * Sets the access time. + * + * @param accessTime + * the new access time + */ + public void setAccessTime(int accessTime) { + this.accessTime = accessTime; + } + } + + /** + * Sets the num entries. + * + * @param numEntries + * the new num entries + */ + public void setNumEntries(int numEntries) { + this.numEntries = numEntries; + } + + /** + * Verify the validity of the Apple file. + * + * @throws FileDecoderException + * the file decoder exception + */ + @SuppressWarnings("unused") + protected void verify() throws FileDecoderException { + byte[] data = this.fileData.getData(); + int offset = this.fileData.getOffset(); + int length = this.fileData.getLength(); + int position = offset; + if (length < 26) { + throw new FileDecoderException("File is too short"); + } + + /* Magic number */ + int magic = 0; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + + /* Check Apple file format: AppleSingle or AppleDobule */ + if (magic == 0x00051600) { + this.format = FileFormat.APPLE_SINGLE; + } else if (magic == 0x00051607) { + this.format = FileFormat.APPLE_DOUBLE; + } else { + throw new FileDecoderException("Invalid Apple file magic number."); + } + + /* Version number */ + int version = 0; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + if (version != 0x00020000) { + throw new FileDecoderException("Unknown Apple file version"); + } + + /* Filler */ + position += 16; + + /* Number of entries */ + this.numEntries = 0; + this.numEntries |= data[(position++)] & 0xFF; + this.numEntries <<= 8; + this.numEntries |= data[(position++)] & 0xFF; + if (length < 26 + 12 * this.numEntries) { + throw new FileDecoderException("Corrupt Apple file data."); + } + + /* Check entries */ + int entryId = 0; + int entryOffset = 0; + int entryLength = 0; + int contentPosition = 26 + 12 * this.numEntries; + for (int i = 0; i < this.numEntries; i++) { + position = 26 + i * 12; + /* Entry ID */ + entryId = 0; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + + /* Entry offset */ + entryOffset = 0; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset &= 0x7FFFFFFF; + + /* Entry length */ + entryLength = 0; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength &= 0x7FFFFFFF; + if ((entryOffset < contentPosition) + || (length < entryOffset + entryLength)) { + throw new FileDecoderException("Corrupt Apple file data."); + } + } + } + + /** + * Extract file dates. + * + * @param data the data + * @param offset the offset + * @param length the length + */ + protected void extractFileDates(byte[] data, int offset, int length) { + if ((0 > offset) || (offset > data.length)) + throw new IndexOutOfBoundsException(); + if ((0 > length) || (length > data.length - offset)) + throw new IndexOutOfBoundsException(); + + int position = offset; + + int createTime = 0; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + int modifyTime = 0; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + int backupTime = 0; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + int accessTime = 0; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + + this.fileDatesInfoEntry = new FileDatesInfoEntry(); + fileDatesInfoEntry.setCreateTime(createTime); + fileDatesInfoEntry.setModifyTime(modifyTime); + fileDatesInfoEntry.setBackupTime(backupTime); + fileDatesInfoEntry.setAccessTime(accessTime); + } + + /** + * Gets the format. + * + * @return the format + */ + public FileFormat getFormat() { + return format; + } + + /** + * Sets the format. + * + * @param format + * the new format + */ + public void setFormat(FileFormat format) { + this.format = format; + } + + /** + * Gets the file data. + * + * @return the file data + */ + public AppleFileData getFileData() { + return fileData; + } + + /** + * Sets the file data. + * + * @param fileData + * the new file data + */ + public void setFileData(AppleFileData fileData) { + this.fileData = fileData; + } + + /** + * Gets the data fork. + * + * @return the data fork + */ + public AppleFileData getDataFork() { + return dataFork; + } + + /** + * Sets the data fork. + * + * @param dataFork + * the new data fork + */ + public void setDataFork(AppleFileData dataFork) { + this.dataFork = dataFork; + } + + /** + * Gets the resource fork. + * + * @return the resource fork + */ + public AppleFileData getResourceFork() { + return resourceFork; + } + + /** + * Sets the resource fork. + * + * @param resourceFork + * the new resource fork + */ + public void setResourceFork(AppleFileData resourceFork) { + this.resourceFork = resourceFork; + } + + /** + * Gets the real name. + * + * @return the real name + */ + public AppleFileData getRealName() { + return realName; + } + + /** + * Sets the real name. + * + * @param realName + * the new real name + */ + public void setRealName(AppleFileData realName) { + this.realName = realName; + } + + /** + * Gets the comment. + * + * @return the comment + */ + public AppleFileData getComment() { + return comment; + } + + /** + * Sets the comment. + * + * @param comment + * the new comment + */ + public void setComment(AppleFileData comment) { + this.comment = comment; + } + + /** + * Gets the icon bw. + * + * @return the icon bw + */ + public AppleFileData getIconBW() { + return iconBW; + } + + /** + * Sets the icon bw. + * + * @param iconBW + * the new icon bw + */ + public void setIconBW(AppleFileData iconBW) { + this.iconBW = iconBW; + } + + /** + * Gets the icon color. + * + * @return the icon color + */ + public AppleFileData getIconColor() { + return iconColor; + } + + /** + * Sets the icon color. + * + * @param iconColor + * the new icon color + */ + public void setIconColor(AppleFileData iconColor) { + this.iconColor = iconColor; + } + + /** + * Gets the file dates info. + * + * @return the file dates info + */ + public AppleFileData getFileDatesInfo() { + return fileDatesInfo; + } + + /** + * Sets the file dates info. + * + * @param fileDatesInfo + * the new file dates info + */ + public void setFileDatesInfo(AppleFileData fileDatesInfo) { + this.fileDatesInfo = fileDatesInfo; + } + + /** + * Gets the finder info. + * + * @return the finder info + */ + public AppleFileData getFinderInfo() { + return finderInfo; + } + + /** + * Sets the finder info. + * + * @param finderInfo + * the new finder info + */ + public void setFinderInfo(AppleFileData finderInfo) { + this.finderInfo = finderInfo; + } + + /** + * Gets the macintosh info. + * + * @return the macintosh info + */ + public AppleFileData getMacintoshInfo() { + return macintoshInfo; + } + + /** + * Sets the macintosh info. + * + * @param macintoshInfo + * the new macintosh info + */ + public void setMacintoshInfo(AppleFileData macintoshInfo) { + this.macintoshInfo = macintoshInfo; + } + + /** + * Gets the pro dos file info. + * + * @return the pro dos file info + */ + public AppleFileData getProDOSFileInfo() { + return proDOSFileInfo; + } + + /** + * Sets the pro dos file info. + * + * @param proDOSFileInfo + * the new pro dos file info + */ + public void setProDOSFileInfo(AppleFileData proDOSFileInfo) { + this.proDOSFileInfo = proDOSFileInfo; + } + + /** + * Gets the ms dos file info. + * + * @return the ms dos file info + */ + public AppleFileData getMsDOSFileInfo() { + return msDOSFileInfo; + } + + /** + * Sets the ms dos file info. + * + * @param msDOSFileInfo + * the new ms dos file info + */ + public void setMsDOSFileInfo(AppleFileData msDOSFileInfo) { + this.msDOSFileInfo = msDOSFileInfo; + } + + /** + * Gets the short name. + * + * @return the short name + */ + public AppleFileData getShortName() { + return shortName; + } + + /** + * Sets the short name. + * + * @param shortName + * the new short name + */ + public void setShortName(AppleFileData shortName) { + this.shortName = shortName; + } + + /** + * Gets the afp file info. + * + * @return the afp file info + */ + public AppleFileData getAfpFileInfo() { + return afpFileInfo; + } + + /** + * Sets the afp file info. + * + * @param afpFileInfo + * the new afp file info + */ + public void setAfpFileInfo(AppleFileData afpFileInfo) { + this.afpFileInfo = afpFileInfo; + } + + /** + * Gets the directory id. + * + * @return the directory id + */ + public AppleFileData getDirectoryID() { + return directoryID; + } + + /** + * Sets the directory id. + * + * @param directoryID + * the new directory id + */ + public void setDirectoryID(AppleFileData directoryID) { + this.directoryID = directoryID; + } + + /** + * Gets the num entries. + * + * @return the num entries + */ + public int getNumEntries() { + return numEntries; + } } \ No newline at end of file diff --git a/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java b/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java index f28c979a..d89471e2 100644 --- a/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java +++ b/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java @@ -1,91 +1,91 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -/** - * This class is for representing the AppleSingle/Double file or its file forks - * (data fork and resource fork) and the related Finder meta-file information. - */ -public final class AppleFileData { - - public static final AppleFileData EMPTY_FILE_DATA = new AppleFileData(); - private byte[] data; - private int offset; - private int length; - - /** - * Instantiates a new apple file data. - */ - public AppleFileData() { - this.data = new byte[0]; - this.offset = 0; - this.length = 0; - } - - /** - * Instantiates a new apple file data. - * - * @param data the data - */ - public AppleFileData(byte[] data) { - this.data = data; - this.offset = 0; - this.length = data.length; - } - - /** - * Instantiates a new apple file data. - * - * @param data the data - * @param offset the offset - * @param length the length - */ - public AppleFileData(byte[] data, int offset, int length) { - if ((0 > offset) || (offset > data.length)) - throw new IndexOutOfBoundsException(); - if ((0 > length) || (length > data.length - offset)) - throw new IndexOutOfBoundsException(); - this.data = data; - this.offset = offset; - this.length = length; - } - - /** - * Gets the bytes. - * - * @return the bytes - */ - public byte[] getBytes() { - byte[] data = new byte[this.length]; - System.arraycopy(this.data, this.offset, data, 0, this.length); - return data; - } - - /** - * Gets the data. - * - * @return the data - */ - public byte[] getData() { - return this.data; - } - - /** - * Gets the offset. - * - * @return the offset - */ - public int getOffset() { - return this.offset; - } - - /** - * Gets the length. - * - * @return the length - */ - public int getLength() { - return this.length; - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +/** + * This class is for representing the AppleSingle/Double file or its file forks + * (data fork and resource fork) and the related Finder meta-file information. + */ +public final class AppleFileData { + + public static final AppleFileData EMPTY_FILE_DATA = new AppleFileData(); + private byte[] data; + private int offset; + private int length; + + /** + * Instantiates a new apple file data. + */ + public AppleFileData() { + this.data = new byte[0]; + this.offset = 0; + this.length = 0; + } + + /** + * Instantiates a new apple file data. + * + * @param data the data + */ + public AppleFileData(byte[] data) { + this.data = data; + this.offset = 0; + this.length = data.length; + } + + /** + * Instantiates a new apple file data. + * + * @param data the data + * @param offset the offset + * @param length the length + */ + public AppleFileData(byte[] data, int offset, int length) { + if ((0 > offset) || (offset > data.length)) + throw new IndexOutOfBoundsException(); + if ((0 > length) || (length > data.length - offset)) + throw new IndexOutOfBoundsException(); + this.data = data; + this.offset = offset; + this.length = length; + } + + /** + * Gets the bytes. + * + * @return the bytes + */ + public byte[] getBytes() { + byte[] data = new byte[this.length]; + System.arraycopy(this.data, this.offset, data, 0, this.length); + return data; + } + + /** + * Gets the data. + * + * @return the data + */ + public byte[] getData() { + return this.data; + } + + /** + * Gets the offset. + * + * @return the offset + */ + public int getOffset() { + return this.offset; + } + + /** + * Gets the length. + * + * @return the length + */ + public int getLength() { + return this.length; + } } \ No newline at end of file diff --git a/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java b/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java index 3248f85d..aa7b0363 100644 --- a/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java +++ b/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java @@ -1,143 +1,143 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; - -/** - * This class handles the extraction of the data fork, resource fork and other - * entries from an AppleSingle/Double file. The Perforce 'apple' file type is a - * compressed AppleSingle (Mac resource + data) file. The Perforce 'resource' - * file type is a compressed AppleDouble (Mac resource fork) file. - */ -public class AppleFileDecoder extends AppleFile { - - /** - * Instantiates a new apple file decoder. - * - * @param fileData - * the file data - */ - public AppleFileDecoder(AppleFileData fileData) { - if (fileData != null) { - this.fileData = fileData; - } - } - - /** - * Extract the data fork, resource fork and other entries from the Apple - * file. - * - * @throws FileDecoderException - * the file decoder exception - */ - @SuppressWarnings("unused") - public void extract() throws FileDecoderException { - // Verify the validity of the Apple file - verify(); - - byte[] data = this.fileData.getData(); - int offset = this.fileData.getOffset(); - int length = this.fileData.getLength(); - int contentPosition = 0; - int entryId = 0; - int entryOffset = 0; - int entryLength = 0; - for (int i = 0; i < this.numEntries; i++) { - contentPosition = offset + 26 + i * 12; - /* Entry ID */ - entryId = 0; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - - /* Entry offset */ - entryOffset = 0; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset &= 0x7FFFFFFF; - - /* Entry length */ - entryLength = 0; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength &= 0x7FFFFFFF; - - switch (entryId) { - case 1: - this.dataFork = new AppleFileData(data, offset + entryOffset, - entryLength); - break; - case 2: - this.resourceFork = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 3: - this.realName = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 4: - this.comment = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 5: - this.iconBW = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 6: - this.iconColor = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 8: - this.fileDatesInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - extractFileDates(data, offset + entryOffset, entryLength); - break; - case 9: - this.finderInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 10: - this.macintoshInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 11: - this.proDOSFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 12: - this.msDOSFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 13: - this.shortName = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 14: - this.afpFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 15: - this.directoryID = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - default: - Log.warn("Apple file entry ID: " + entryId + " is not handled."); - - } - } - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; + +/** + * This class handles the extraction of the data fork, resource fork and other + * entries from an AppleSingle/Double file. The Perforce 'apple' file type is a + * compressed AppleSingle (Mac resource + data) file. The Perforce 'resource' + * file type is a compressed AppleDouble (Mac resource fork) file. + */ +public class AppleFileDecoder extends AppleFile { + + /** + * Instantiates a new apple file decoder. + * + * @param fileData + * the file data + */ + public AppleFileDecoder(AppleFileData fileData) { + if (fileData != null) { + this.fileData = fileData; + } + } + + /** + * Extract the data fork, resource fork and other entries from the Apple + * file. + * + * @throws FileDecoderException + * the file decoder exception + */ + @SuppressWarnings("unused") + public void extract() throws FileDecoderException { + // Verify the validity of the Apple file + verify(); + + byte[] data = this.fileData.getData(); + int offset = this.fileData.getOffset(); + int length = this.fileData.getLength(); + int contentPosition = 0; + int entryId = 0; + int entryOffset = 0; + int entryLength = 0; + for (int i = 0; i < this.numEntries; i++) { + contentPosition = offset + 26 + i * 12; + /* Entry ID */ + entryId = 0; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + + /* Entry offset */ + entryOffset = 0; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset &= 0x7FFFFFFF; + + /* Entry length */ + entryLength = 0; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength &= 0x7FFFFFFF; + + switch (entryId) { + case 1: + this.dataFork = new AppleFileData(data, offset + entryOffset, + entryLength); + break; + case 2: + this.resourceFork = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 3: + this.realName = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 4: + this.comment = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 5: + this.iconBW = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 6: + this.iconColor = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 8: + this.fileDatesInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + extractFileDates(data, offset + entryOffset, entryLength); + break; + case 9: + this.finderInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 10: + this.macintoshInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 11: + this.proDOSFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 12: + this.msDOSFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 13: + this.shortName = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 14: + this.afpFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 15: + this.directoryID = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + default: + Log.warn("Apple file entry ID: " + entryId + " is not handled."); + + } + } + } } \ No newline at end of file diff --git a/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java b/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java index 7c5d0a04..abdfed59 100644 --- a/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java +++ b/p4java/r17-2/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java @@ -1,300 +1,300 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.exception.FileEncoderException; - -/** - * This class handles the combination of the data fork, resource fork and other - * entries into an AppleSingle/Double file. - *

- * - * Note that if it is an AppleDouble, the data fork is a separate file external - * to this file. - */ -public class AppleFileEncoder extends AppleFile { - - /** - * Instantiates a new apple file decoder. - * - * @throws FileEncoderException - */ - public AppleFileEncoder(FileFormat fileFormat) throws FileEncoderException { - if (fileFormat == null) { - throw new FileEncoderException("Null file format passed to the AppleFileEncoder constructor."); - } - if (fileFormat == FileFormat.UNKNOWN) { - throw new FileEncoderException("Unknown file format passed to the AppleFileEncoder constructor."); - } - } - - /** - * Combine the data fork, resource fork and other entries into an - * AppleSingle/Double file. - * - * @throws FileEncoderException - * the file encoder exception - */ - @SuppressWarnings("unused") - public void combine() throws FileEncoderException { - - boolean isAppleSingle = (this.format == FileFormat.APPLE_SINGLE); - boolean isAppleDouble = (this.format == FileFormat.APPLE_DOUBLE); - - boolean hasDataFork = (this.dataFork != AppleFileData.EMPTY_FILE_DATA); - boolean hasResourceFork = (this.resourceFork != AppleFileData.EMPTY_FILE_DATA); - boolean hasRealName = (this.realName != AppleFileData.EMPTY_FILE_DATA); - boolean hasComment = (this.comment != AppleFileData.EMPTY_FILE_DATA); - boolean hasIconBW = (this.iconBW != AppleFileData.EMPTY_FILE_DATA); - boolean hasIconColor = (this.iconColor != AppleFileData.EMPTY_FILE_DATA); - boolean hasFileDatesInfo = (this.fileDatesInfoEntry != null); - boolean hasFinderInfo = (this.finderInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasMacintoshInfo = (this.macintoshInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasProDOSFileInfo = (this.proDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasMsDOSFileInfo = (this.msDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasShortName = (this.shortName != AppleFileData.EMPTY_FILE_DATA); - boolean hasAfpFileInfo = (this.afpFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasDirectoryID = (this.directoryID != AppleFileData.EMPTY_FILE_DATA); - - this.fileData = AppleFileData.EMPTY_FILE_DATA; - - int length = 90 + this.realName.getLength() - + this.resourceFork.getLength(); - - /* AppleSingle includes the data fork */ - if (isAppleSingle) { - length += this.dataFork.getLength(); - } - - byte[] data = new byte[length]; - int position = 0; - - /* Magic number for AppleSingle or AppleDouble */ - if (isAppleDouble) { - data[(position++)] = 0; - data[(position++)] = 5; - data[(position++)] = 22; - data[(position++)] = 0; - } else { - data[(position++)] = 0; - data[(position++)] = 5; - data[(position++)] = 22; - data[(position++)] = 7; - } - - /* Version number */ - data[(position++)] = 0; - data[(position++)] = 2; - data[(position++)] = 0; - data[(position++)] = 0; - - /* Filler */ - - for (int k = 0; k < 16; k++) { - data[(position++)] = 0; - } - - /* Number of entries */ - this.numEntries = 0; - if (hasRealName) { - this.numEntries += 1; - } - if (hasFileDatesInfo) { - this.numEntries += 1; - } - if (hasResourceFork) { - this.numEntries += 1; - } - if ((hasDataFork) && (isAppleSingle)) { - this.numEntries += 1; - } - data[(position++)] = ((byte) (this.numEntries >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.numEntries & 0xFF)); - - /* Header information for the entries */ - - /* Real name entry header */ - int realNamePosition = 0; - if (hasRealName) { - int realNameEntryId = 3; - int realNameEntryOffset = 0; - int realNameEntryLength = this.realName.getLength(); - data[(position++)] = ((byte) (realNameEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 0 & 0xFF)); - realNamePosition = position; - data[(position++)] = ((byte) (realNameEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 0 & 0xFF)); - } - - /* File dates info entry header */ - int fileDatesInfoPosition = 0; - if (hasFileDatesInfo) { - int fileDatesInfoEntryId = 8; - int fileDatesInfoEntryOffset = 0; - int fileDatesInfoEntryLength = 16; - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 0 & 0xFF)); - fileDatesInfoPosition = position; - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 0 & 0xFF)); - } - - /* Resource fork entry header */ - int resourceForkPosition = 0; - if (hasResourceFork) { - int resourceForkEntryId = 2; - int resourceForkEntryOffset = 0; - int resourceFokrEntryLength = this.resourceFork.getLength(); - data[(position++)] = ((byte) (resourceForkEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 0 & 0xFF)); - resourceForkPosition = position; - data[(position++)] = ((byte) (resourceForkEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 0 & 0xFF)); - } - - /* Data fork entry header */ - int dataForkPosition = 0; - if ((hasDataFork) && (isAppleSingle)) { - int dataForkEntryId = 1; - int dataForkEntryOffset = 0; - int dataForkEntryLength = this.dataFork.getLength(); - data[(position++)] = ((byte) (dataForkEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 0 & 0xFF)); - dataForkPosition = position; - data[(position++)] = ((byte) (dataForkEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 0 & 0xFF)); - } - - /* Content for the entries */ - - /* Real name content */ - if (hasRealName) { - int realNamePositionCurrent = position; - position = realNamePosition; - data[(position++)] = ((byte) (realNamePositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 0 & 0xFF)); - position = realNamePositionCurrent; - byte[] realNameData = this.realName.getData(); - int realNameOffset = this.realName.getOffset(); - int realNameLength = this.realName.getLength(); - System.arraycopy(realNameData, realNameOffset, data, position, - realNameLength); - position += realNameLength; - } - - /* File dates info content */ - if (hasFileDatesInfo) { - int fileDatesInfoPositionCurrent = position; - position = fileDatesInfoPosition; - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 0 & 0xFF)); - position = fileDatesInfoPositionCurrent; - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 0 & 0xFF)); - } - - /* Resource fork content */ - if (hasResourceFork) { - int resourceForkPositionCurrent = position; - position = resourceForkPosition; - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 0 & 0xFF)); - position = resourceForkPositionCurrent; - byte[] resourceForkData = this.resourceFork.getData(); - int resourceForkOffset = this.resourceFork.getOffset(); - int resourceForkLength = this.resourceFork.getLength(); - System.arraycopy(resourceForkData, resourceForkOffset, data, - position, resourceForkLength); - position += resourceForkLength; - } - - /* Data fork content */ - if ((hasDataFork) && (isAppleSingle)) { - int dataForkPosition2 = position; - position = dataForkPosition; - data[(position++)] = ((byte) (dataForkPosition2 >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 0 & 0xFF)); - position = dataForkPosition2; - byte[] dataForkData = this.dataFork.getData(); - int dataForkOffset = this.dataFork.getOffset(); - int dataForkLength = this.dataFork.getLength(); - System.arraycopy(dataForkData, dataForkOffset, data, position, - dataForkLength); - position += dataForkLength; - } - - /* Create the Apple file data */ - this.fileData = new AppleFileData(data, 0, position); - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.exception.FileEncoderException; + +/** + * This class handles the combination of the data fork, resource fork and other + * entries into an AppleSingle/Double file. + *

+ * + * Note that if it is an AppleDouble, the data fork is a separate file external + * to this file. + */ +public class AppleFileEncoder extends AppleFile { + + /** + * Instantiates a new apple file decoder. + * + * @throws FileEncoderException + */ + public AppleFileEncoder(FileFormat fileFormat) throws FileEncoderException { + if (fileFormat == null) { + throw new FileEncoderException("Null file format passed to the AppleFileEncoder constructor."); + } + if (fileFormat == FileFormat.UNKNOWN) { + throw new FileEncoderException("Unknown file format passed to the AppleFileEncoder constructor."); + } + } + + /** + * Combine the data fork, resource fork and other entries into an + * AppleSingle/Double file. + * + * @throws FileEncoderException + * the file encoder exception + */ + @SuppressWarnings("unused") + public void combine() throws FileEncoderException { + + boolean isAppleSingle = (this.format == FileFormat.APPLE_SINGLE); + boolean isAppleDouble = (this.format == FileFormat.APPLE_DOUBLE); + + boolean hasDataFork = (this.dataFork != AppleFileData.EMPTY_FILE_DATA); + boolean hasResourceFork = (this.resourceFork != AppleFileData.EMPTY_FILE_DATA); + boolean hasRealName = (this.realName != AppleFileData.EMPTY_FILE_DATA); + boolean hasComment = (this.comment != AppleFileData.EMPTY_FILE_DATA); + boolean hasIconBW = (this.iconBW != AppleFileData.EMPTY_FILE_DATA); + boolean hasIconColor = (this.iconColor != AppleFileData.EMPTY_FILE_DATA); + boolean hasFileDatesInfo = (this.fileDatesInfoEntry != null); + boolean hasFinderInfo = (this.finderInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasMacintoshInfo = (this.macintoshInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasProDOSFileInfo = (this.proDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasMsDOSFileInfo = (this.msDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasShortName = (this.shortName != AppleFileData.EMPTY_FILE_DATA); + boolean hasAfpFileInfo = (this.afpFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasDirectoryID = (this.directoryID != AppleFileData.EMPTY_FILE_DATA); + + this.fileData = AppleFileData.EMPTY_FILE_DATA; + + int length = 90 + this.realName.getLength() + + this.resourceFork.getLength(); + + /* AppleSingle includes the data fork */ + if (isAppleSingle) { + length += this.dataFork.getLength(); + } + + byte[] data = new byte[length]; + int position = 0; + + /* Magic number for AppleSingle or AppleDouble */ + if (isAppleDouble) { + data[(position++)] = 0; + data[(position++)] = 5; + data[(position++)] = 22; + data[(position++)] = 0; + } else { + data[(position++)] = 0; + data[(position++)] = 5; + data[(position++)] = 22; + data[(position++)] = 7; + } + + /* Version number */ + data[(position++)] = 0; + data[(position++)] = 2; + data[(position++)] = 0; + data[(position++)] = 0; + + /* Filler */ + + for (int k = 0; k < 16; k++) { + data[(position++)] = 0; + } + + /* Number of entries */ + this.numEntries = 0; + if (hasRealName) { + this.numEntries += 1; + } + if (hasFileDatesInfo) { + this.numEntries += 1; + } + if (hasResourceFork) { + this.numEntries += 1; + } + if ((hasDataFork) && (isAppleSingle)) { + this.numEntries += 1; + } + data[(position++)] = ((byte) (this.numEntries >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.numEntries & 0xFF)); + + /* Header information for the entries */ + + /* Real name entry header */ + int realNamePosition = 0; + if (hasRealName) { + int realNameEntryId = 3; + int realNameEntryOffset = 0; + int realNameEntryLength = this.realName.getLength(); + data[(position++)] = ((byte) (realNameEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 0 & 0xFF)); + realNamePosition = position; + data[(position++)] = ((byte) (realNameEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 0 & 0xFF)); + } + + /* File dates info entry header */ + int fileDatesInfoPosition = 0; + if (hasFileDatesInfo) { + int fileDatesInfoEntryId = 8; + int fileDatesInfoEntryOffset = 0; + int fileDatesInfoEntryLength = 16; + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 0 & 0xFF)); + fileDatesInfoPosition = position; + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 0 & 0xFF)); + } + + /* Resource fork entry header */ + int resourceForkPosition = 0; + if (hasResourceFork) { + int resourceForkEntryId = 2; + int resourceForkEntryOffset = 0; + int resourceFokrEntryLength = this.resourceFork.getLength(); + data[(position++)] = ((byte) (resourceForkEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 0 & 0xFF)); + resourceForkPosition = position; + data[(position++)] = ((byte) (resourceForkEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 0 & 0xFF)); + } + + /* Data fork entry header */ + int dataForkPosition = 0; + if ((hasDataFork) && (isAppleSingle)) { + int dataForkEntryId = 1; + int dataForkEntryOffset = 0; + int dataForkEntryLength = this.dataFork.getLength(); + data[(position++)] = ((byte) (dataForkEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 0 & 0xFF)); + dataForkPosition = position; + data[(position++)] = ((byte) (dataForkEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 0 & 0xFF)); + } + + /* Content for the entries */ + + /* Real name content */ + if (hasRealName) { + int realNamePositionCurrent = position; + position = realNamePosition; + data[(position++)] = ((byte) (realNamePositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 0 & 0xFF)); + position = realNamePositionCurrent; + byte[] realNameData = this.realName.getData(); + int realNameOffset = this.realName.getOffset(); + int realNameLength = this.realName.getLength(); + System.arraycopy(realNameData, realNameOffset, data, position, + realNameLength); + position += realNameLength; + } + + /* File dates info content */ + if (hasFileDatesInfo) { + int fileDatesInfoPositionCurrent = position; + position = fileDatesInfoPosition; + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 0 & 0xFF)); + position = fileDatesInfoPositionCurrent; + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 0 & 0xFF)); + } + + /* Resource fork content */ + if (hasResourceFork) { + int resourceForkPositionCurrent = position; + position = resourceForkPosition; + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 0 & 0xFF)); + position = resourceForkPositionCurrent; + byte[] resourceForkData = this.resourceFork.getData(); + int resourceForkOffset = this.resourceFork.getOffset(); + int resourceForkLength = this.resourceFork.getLength(); + System.arraycopy(resourceForkData, resourceForkOffset, data, + position, resourceForkLength); + position += resourceForkLength; + } + + /* Data fork content */ + if ((hasDataFork) && (isAppleSingle)) { + int dataForkPosition2 = position; + position = dataForkPosition; + data[(position++)] = ((byte) (dataForkPosition2 >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 0 & 0xFF)); + position = dataForkPosition2; + byte[] dataForkData = this.dataFork.getData(); + int dataForkOffset = this.dataFork.getOffset(); + int dataForkLength = this.dataFork.getLength(); + System.arraycopy(dataForkData, dataForkOffset, data, position, + dataForkLength); + position += dataForkLength; + } + + /* Create the Apple file data */ + this.fileData = new AppleFileData(data, 0, position); + } } \ No newline at end of file diff --git a/p4java/r17-2/src/test/java/com/perforce/p4java/tests/dev/unit/bug/r132/CreateSymbolicLinkTest.java b/p4java/r17-2/src/test/java/com/perforce/p4java/tests/dev/unit/bug/r132/CreateSymbolicLinkTest.java index 59a9a133..1c224364 100644 --- a/p4java/r17-2/src/test/java/com/perforce/p4java/tests/dev/unit/bug/r132/CreateSymbolicLinkTest.java +++ b/p4java/r17-2/src/test/java/com/perforce/p4java/tests/dev/unit/bug/r132/CreateSymbolicLinkTest.java @@ -1,54 +1,54 @@ -package com.perforce.p4java.tests.dev.unit.bug.r132; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; - -import org.junit.jupiter.api.Test; -import org.junit.platform.runner.JUnitPlatform; -import org.junit.runner.RunWith; - -import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SymbolicLinkHelper; -import com.perforce.p4java.tests.dev.unit.P4JavaTestCase; - -/** - * Test create symbolic link with non-existing target. - */ -@RunWith(JUnitPlatform.class) -public class CreateSymbolicLinkTest extends P4JavaTestCase { - /** - * Test create symbolic link with non-existing target. - */ - @Test - public void testCreateSymbolicLink() throws IOException { - // Check if symlink capable (JDK 7 or above) - if (SymbolicLinkHelper.isSymbolicLinkCapable()) { - String target = File.createTempFile("link-to-me", ".tmp").getAbsolutePath(); - String link = System.getProperty("java.io.tmpdir") + "/p4java-bin-" + getRandomInt(); - - // Create symbolic link - debugPrint("Creating link " + link + " to " + target); - String path = SymbolicLinkHelper.createSymbolicLink(link, target); - assertNotNull("Failed to create a symbolic link from " + link + " to " + target); - assertTrue(path + " is not a symbolic link", SymbolicLinkHelper.isSymbolicLink(path)); - - debugPrint("Creating a new file object for " + path); - File file = new File(path); - assertTrue("A new File object to " + path + " is not reported as a symbolic link.", - Files.isSymbolicLink(file.toPath())); - assertTrue("A new File object to " + path + " is not reported as not existing.", - file.exists()); - - debugPrint("Checking the parent path " + path); - String parentPath = file.getParent(); - assertNotNull(file + " has not parent path.", parentPath); - - debugPrint("Deleting " + file); - assertTrue("Failed to delete " + file.toPath(), Files.deleteIfExists(file.toPath())); - } - } - -} +package com.perforce.p4java.tests.dev.unit.bug.r132; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SymbolicLinkHelper; +import com.perforce.p4java.tests.dev.unit.P4JavaTestCase; + +/** + * Test create symbolic link with non-existing target. + */ +@RunWith(JUnitPlatform.class) +public class CreateSymbolicLinkTest extends P4JavaTestCase { + /** + * Test create symbolic link with non-existing target. + */ + @Test + public void testCreateSymbolicLink() throws IOException { + // Check if symlink capable (JDK 7 or above) + if (SymbolicLinkHelper.isSymbolicLinkCapable()) { + String target = File.createTempFile("link-to-me", ".tmp").getAbsolutePath(); + String link = System.getProperty("java.io.tmpdir") + "/p4java-bin-" + getRandomInt(); + + // Create symbolic link + debugPrint("Creating link " + link + " to " + target); + String path = SymbolicLinkHelper.createSymbolicLink(link, target); + assertNotNull("Failed to create a symbolic link from " + link + " to " + target); + assertTrue(path + " is not a symbolic link", SymbolicLinkHelper.isSymbolicLink(path)); + + debugPrint("Creating a new file object for " + path); + File file = new File(path); + assertTrue("A new File object to " + path + " is not reported as a symbolic link.", + Files.isSymbolicLink(file.toPath())); + assertTrue("A new File object to " + path + " is not reported as not existing.", + file.exists()); + + debugPrint("Checking the parent path " + path); + String parentPath = file.getParent(); + assertNotNull(file + " has not parent path.", parentPath); + + debugPrint("Deleting " + file); + assertTrue("Failed to delete " + file.toPath(), Files.deleteIfExists(file.toPath())); + } + } + +} diff --git a/p4java/r17-2/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java b/p4java/r17-2/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java index bab7e806..978db638 100644 --- a/p4java/r17-2/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java +++ b/p4java/r17-2/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java @@ -1,83 +1,83 @@ -/** - * Copyright (c) 2012 Perforce Software. All rights reserved. - */ -package com.perforce.p4java.tests.dev.unit.features123; - -import static org.junit.Assert.assertTrue; - -import java.io.File; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SymbolicLinkHelper; -import com.perforce.p4java.tests.dev.unit.P4JavaTestCase; - -/** - * Test symbolic link helper (JDK 7 or above) - */ -public class SymbolicLinkHelperTest extends P4JavaTestCase { - - /** - * @BeforeClass annotation to a method to be run before all the tests in a - * class. - */ - @BeforeClass - public static void oneTimeSetUp() { - // one-time initialization code (before all the tests). - } - - /** - * @AfterClass annotation to a method to be run after all the tests in a - * class. - */ - @AfterClass - public static void oneTimeTearDown() { - // one-time cleanup code (after all the tests). - } - - /** - * @Before annotation to a method to be run before each test in a class. - */ - @Before - public void setUp() { - // initialization code (before each test). - } - - /** - * @After annotation to a method to be run after each test in a class. - */ - @After - public void tearDown() { - // cleanup code (after each test). - } - - /** - * Test symbolic link - */ - @Test - public void tesSymbolicLink() { - // Check if symlink capable - if (SymbolicLinkHelper.isSymbolicLinkCapable()) { - - String target = "/usr/bin"; - String link = "/tmp/user-bin-" + getRandomInt(); - - // Create symbolic link - Object path = SymbolicLinkHelper.createSymbolicLink(link, target); - - boolean isSymlink = SymbolicLinkHelper.isSymbolicLink(path - .toString()); - assertTrue(isSymlink); - - File file = new File(path.toString()); - if (file.exists()) { - file.delete(); - } - } - } - -} +/** + * Copyright (c) 2012 Perforce Software. All rights reserved. + */ +package com.perforce.p4java.tests.dev.unit.features123; + +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SymbolicLinkHelper; +import com.perforce.p4java.tests.dev.unit.P4JavaTestCase; + +/** + * Test symbolic link helper (JDK 7 or above) + */ +public class SymbolicLinkHelperTest extends P4JavaTestCase { + + /** + * @BeforeClass annotation to a method to be run before all the tests in a + * class. + */ + @BeforeClass + public static void oneTimeSetUp() { + // one-time initialization code (before all the tests). + } + + /** + * @AfterClass annotation to a method to be run after all the tests in a + * class. + */ + @AfterClass + public static void oneTimeTearDown() { + // one-time cleanup code (after all the tests). + } + + /** + * @Before annotation to a method to be run before each test in a class. + */ + @Before + public void setUp() { + // initialization code (before each test). + } + + /** + * @After annotation to a method to be run after each test in a class. + */ + @After + public void tearDown() { + // cleanup code (after each test). + } + + /** + * Test symbolic link + */ + @Test + public void tesSymbolicLink() { + // Check if symlink capable + if (SymbolicLinkHelper.isSymbolicLinkCapable()) { + + String target = "/usr/bin"; + String link = "/tmp/user-bin-" + getRandomInt(); + + // Create symbolic link + Object path = SymbolicLinkHelper.createSymbolicLink(link, target); + + boolean isSymlink = SymbolicLinkHelper.isSymbolicLink(path + .toString()); + assertTrue(isSymlink); + + File file = new File(path.toString()); + if (file.exists()) { + file.delete(); + } + } + } + +} diff --git a/p4java/r18-1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java b/p4java/r18-1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java index 955735f4..75e9be6e 100644 --- a/p4java/r18-1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java +++ b/p4java/r18-1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java @@ -1,118 +1,118 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.impl.mapbased.rpc.sys.helper; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; -import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFile; -import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFileType; -import com.perforce.p4java.io.apple.AppleFileData; -import com.perforce.p4java.io.apple.AppleFileDecoder; - -/** - * Helper class for handling Apple files. - */ -public class AppleFileHelper { - - /** - * Extract the data fork and the resource fork from the Apple file. - * - * @param file the Apple file - */ - public static void extractFile(RpcPerforceFile file) { - if (file.getFileType() == RpcPerforceFileType.FST_APPLEFILE) { - FileOutputStream fosData = null; - FileOutputStream fosResource = null; - try { - byte[] data = AppleFileHelper.getBytesFromFile(file); - AppleFileData fileData = new AppleFileData(data); - AppleFileDecoder appleFile = new AppleFileDecoder(fileData); - appleFile.extract(); - fosData = new FileOutputStream(file); - AppleFileData forkData = appleFile.getDataFork(); - if (forkData != AppleFileData.EMPTY_FILE_DATA) { - fosData.write(forkData.getBytes()); - } - String resourceFilePath = file.getParent() + File.separator + "%" + file.getName(); - RpcPerforceFile targetResourceFile = new RpcPerforceFile(resourceFilePath, file.getFileType()); - fosResource = new FileOutputStream(targetResourceFile); - AppleFileData forkResource = appleFile.getResourceFork(); - if (forkResource != AppleFileData.EMPTY_FILE_DATA) { - fosResource.write(forkResource.getBytes()); - } - } catch (IOException e) { - Log.error("Problem handling the Apple file: " + file.getName()); - } catch (FileDecoderException e) { - Log.error("Problem decoding the Apple file: " + file.getName()); - } finally { - if (fosData != null) { - try { - fosData.close(); - } catch (Exception e) { - // Do nothing - } - } - if (fosResource != null) { - try { - fosResource.close(); - } catch (Exception e) { - // Do nothing - } - } - } - } - } - - /** - * Gets the bytes from file. - * - * @param file - * the file - * @return the bytes from file - * @throws IOException - * Signals that an I/O exception has occurred. - */ - public static byte[] getBytesFromFile(File file) throws IOException { - InputStream is = new FileInputStream(file); - - long length = file.length(); - if (length > Integer.MAX_VALUE) { - // File is too large - throw new IOException("Apple file too large for decoding."); - } - - byte[] bytes = new byte[(int) length]; - int offset = 0; - int numRead = 0; - - try { - while (offset < bytes.length - && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { - offset += numRead; - } - // Ensure all the bytes have been read in - if (offset < bytes.length) { - throw new IOException( - "Could not completely read the Apple file " - + file.getName()); - } - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - // Do nothing - } - } - } - - return bytes; - } -} +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.impl.mapbased.rpc.sys.helper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; +import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFile; +import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFileType; +import com.perforce.p4java.io.apple.AppleFileData; +import com.perforce.p4java.io.apple.AppleFileDecoder; + +/** + * Helper class for handling Apple files. + */ +public class AppleFileHelper { + + /** + * Extract the data fork and the resource fork from the Apple file. + * + * @param file the Apple file + */ + public static void extractFile(RpcPerforceFile file) { + if (file.getFileType() == RpcPerforceFileType.FST_APPLEFILE) { + FileOutputStream fosData = null; + FileOutputStream fosResource = null; + try { + byte[] data = AppleFileHelper.getBytesFromFile(file); + AppleFileData fileData = new AppleFileData(data); + AppleFileDecoder appleFile = new AppleFileDecoder(fileData); + appleFile.extract(); + fosData = new FileOutputStream(file); + AppleFileData forkData = appleFile.getDataFork(); + if (forkData != AppleFileData.EMPTY_FILE_DATA) { + fosData.write(forkData.getBytes()); + } + String resourceFilePath = file.getParent() + File.separator + "%" + file.getName(); + RpcPerforceFile targetResourceFile = new RpcPerforceFile(resourceFilePath, file.getFileType()); + fosResource = new FileOutputStream(targetResourceFile); + AppleFileData forkResource = appleFile.getResourceFork(); + if (forkResource != AppleFileData.EMPTY_FILE_DATA) { + fosResource.write(forkResource.getBytes()); + } + } catch (IOException e) { + Log.error("Problem handling the Apple file: " + file.getName()); + } catch (FileDecoderException e) { + Log.error("Problem decoding the Apple file: " + file.getName()); + } finally { + if (fosData != null) { + try { + fosData.close(); + } catch (Exception e) { + // Do nothing + } + } + if (fosResource != null) { + try { + fosResource.close(); + } catch (Exception e) { + // Do nothing + } + } + } + } + } + + /** + * Gets the bytes from file. + * + * @param file + * the file + * @return the bytes from file + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public static byte[] getBytesFromFile(File file) throws IOException { + InputStream is = new FileInputStream(file); + + long length = file.length(); + if (length > Integer.MAX_VALUE) { + // File is too large + throw new IOException("Apple file too large for decoding."); + } + + byte[] bytes = new byte[(int) length]; + int offset = 0; + int numRead = 0; + + try { + while (offset < bytes.length + && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { + offset += numRead; + } + // Ensure all the bytes have been read in + if (offset < bytes.length) { + throw new IOException( + "Could not completely read the Apple file " + + file.getName()); + } + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + // Do nothing + } + } + } + + return bytes; + } +} diff --git a/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFile.java b/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFile.java index 5b89f33a..615bfcc9 100644 --- a/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFile.java +++ b/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFile.java @@ -1,735 +1,735 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; - -/** - * This abstract class handles AppleSingle/Double files. It contains a common - * method to verify the Apple file, and figure out if it is an AppleSingle or - * AppleDouble formatted file. - *

- * - * The AppleSingle format is a representation of Macintosh files as one - * consecutive stream of bytes. AppleSingle combines the data fork, resource - * fork and the related Finder meta-file information into a single file. - *

- * - * The AppleDouble format stores the data fork, resource fork as two separate - * files. AppleDouble leaves the data fork in its original format, and the - * resource fork and Finder information were combined into a second file. - *

- * - * Apple defined the magic number for the AppleSingle format as 0x00051600, and - * the magic number for the AppleDouble format as 0x00051607. - * - *

- * AppleSingle file header: 
- * 
- * Field Length
- * ----- ------
- * Magic number ------- 4 bytes
- * Version number ------ 4 bytes
- * Filler ------------- 16 bytes
- * Number of entries ----- 2 bytes
- * 
- * Entry descriptor for each entry:
- * Entry ID ------ 4 bytes
- * Offset -------- 4 bytes
- * Length -------- 4 bytes
- * 
- * Apple reserved entry IDs:
- * 
- * Data Fork -------- 1 Data fork
- * Resource Fork ----- 2 Resource fork
- * Real Name -------- 3 File's name as created on home file system
- * Comment --------- 4 Standard Macintosh comment
- * Icon, B&W -------- 5 Standard Macintosh black and white icon
- * Icon, Color -------- 6 Macintosh color icon
- * File Dates Info ------8 File creation date, modification date, and so on
- * Finder Info -------- 9 Standard Macintosh Finder information
- * Macintosh File Info ---10 Macintosh file information, attributes, and so on
- * ProDOS File Info -----11 ProDOS file information, attributes, and so on
- * MS-DOS File Info ----12 MS-DOS file information, attributes, and so on
- * Short Name --------13 AFP short name
- * AFP File Info ------- 14 AFP file information, attributes, and so on
- * Directory ID --------15 AFP directory ID
- * 
- * - * See RFC 1740 for reference: http://tools.ietf.org/html/rfc1740 - */ -public abstract class AppleFile { - - /** The Apple file format: AppleSingle, AppleDouble, default to unknown. */ - protected FileFormat format = FileFormat.UNKNOWN; - - /** The raw Apple file. */ - protected AppleFileData fileData = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 1: Data fork. */ - protected AppleFileData dataFork = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 2: Resource fork. */ - protected AppleFileData resourceFork = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 3: File's name as created on home file system. */ - protected AppleFileData realName = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 4: Standard Macintosh comment. */ - protected AppleFileData comment = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 5: Standard Macintosh black and white icon. */ - protected AppleFileData iconBW = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 6: Macintosh color icon. */ - protected AppleFileData iconColor = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 8: File creation date, modification date, and so on. */ - protected AppleFileData fileDatesInfo = AppleFileData.EMPTY_FILE_DATA; - - /** The file dates info entry. */ - protected FileDatesInfoEntry fileDatesInfoEntry = null; - - /** Entry 9: Standard Macintosh Finder information. */ - protected AppleFileData finderInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 10: Macintosh file information, attributes, and so on. */ - protected AppleFileData macintoshInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 11: ProDOS file information, attributes, and so on. */ - protected AppleFileData proDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 12: MS-DOS file information, attributes, and so on. */ - protected AppleFileData msDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 13: AFP short name. */ - protected AppleFileData shortName = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 14: AFP file information, attributes, and so on. */ - protected AppleFileData afpFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 15: AFP directory ID. */ - protected AppleFileData directoryID = AppleFileData.EMPTY_FILE_DATA; - - /** The num entries. */ - protected int numEntries = 0; - - /** - * The Apple file format. - */ - public enum FileFormat { - - APPLE_SINGLE, - APPLE_DOUBLE, - UNKNOWN; - - /** - * Return a suitable Apple file format as inferred from the passed-in - * string. Otherwise return the UNKNOWN file format. - * - * @param fileFormat - * the file format - * @return the FileFormat - */ - public static FileFormat fromString(String fileFormat) { - if (fileFormat == null) { - return null; - } - - try { - return FileFormat.valueOf(fileFormat.toUpperCase()); - } catch (IllegalArgumentException iae) { - Log.error("Bad conversion attempt in FileFormat.fromString; string: " - + fileFormat + "; message: " + iae.getMessage()); - Log.exception(iae); - return UNKNOWN; - } - } - }; - - /** - * This class represents the file dates. - */ - public class FileDatesInfoEntry { - - /** The create time. */ - private int createTime = Integer.MIN_VALUE; - - /** The modify time. */ - private int modifyTime = Integer.MIN_VALUE; - - /** The backup time. */ - private int backupTime = Integer.MIN_VALUE; - - /** The access time. */ - private int accessTime = Integer.MIN_VALUE; - - /** - * Instantiates a new file dates info entry. - */ - public FileDatesInfoEntry() { - - } - - /** - * Gets the creates the time. - * - * @return the creates the time - */ - public int getCreateTime() { - return createTime; - } - - /** - * Sets the creates the time. - * - * @param createTime - * the new creates the time - */ - public void setCreateTime(int createTime) { - this.createTime = createTime; - } - - /** - * Gets the modify time. - * - * @return the modify time - */ - public int getModifyTime() { - return modifyTime; - } - - /** - * Sets the modify time. - * - * @param modifyTime - * the new modify time - */ - public void setModifyTime(int modifyTime) { - this.modifyTime = modifyTime; - } - - /** - * Gets the backup time. - * - * @return the backup time - */ - public int getBackupTime() { - return backupTime; - } - - /** - * Sets the backup time. - * - * @param backupTime - * the new backup time - */ - public void setBackupTime(int backupTime) { - this.backupTime = backupTime; - } - - /** - * Gets the access time. - * - * @return the access time - */ - public int getAccessTime() { - return accessTime; - } - - /** - * Sets the access time. - * - * @param accessTime - * the new access time - */ - public void setAccessTime(int accessTime) { - this.accessTime = accessTime; - } - } - - /** - * Sets the num entries. - * - * @param numEntries - * the new num entries - */ - public void setNumEntries(int numEntries) { - this.numEntries = numEntries; - } - - /** - * Verify the validity of the Apple file. - * - * @throws FileDecoderException - * the file decoder exception - */ - @SuppressWarnings("unused") - protected void verify() throws FileDecoderException { - byte[] data = this.fileData.getData(); - int offset = this.fileData.getOffset(); - int length = this.fileData.getLength(); - int position = offset; - if (length < 26) { - throw new FileDecoderException("File is too short"); - } - - /* Magic number */ - int magic = 0; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - - /* Check Apple file format: AppleSingle or AppleDobule */ - if (magic == 0x00051600) { - this.format = FileFormat.APPLE_SINGLE; - } else if (magic == 0x00051607) { - this.format = FileFormat.APPLE_DOUBLE; - } else { - throw new FileDecoderException("Invalid Apple file magic number."); - } - - /* Version number */ - int version = 0; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - if (version != 0x00020000) { - throw new FileDecoderException("Unknown Apple file version"); - } - - /* Filler */ - position += 16; - - /* Number of entries */ - this.numEntries = 0; - this.numEntries |= data[(position++)] & 0xFF; - this.numEntries <<= 8; - this.numEntries |= data[(position++)] & 0xFF; - if (length < 26 + 12 * this.numEntries) { - throw new FileDecoderException("Corrupt Apple file data."); - } - - /* Check entries */ - int entryId = 0; - int entryOffset = 0; - int entryLength = 0; - int contentPosition = 26 + 12 * this.numEntries; - for (int i = 0; i < this.numEntries; i++) { - position = 26 + i * 12; - /* Entry ID */ - entryId = 0; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - - /* Entry offset */ - entryOffset = 0; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset &= 0x7FFFFFFF; - - /* Entry length */ - entryLength = 0; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength &= 0x7FFFFFFF; - if ((entryOffset < contentPosition) - || (length < entryOffset + entryLength)) { - throw new FileDecoderException("Corrupt Apple file data."); - } - } - } - - /** - * Extract file dates. - * - * @param data the data - * @param offset the offset - * @param length the length - */ - protected void extractFileDates(byte[] data, int offset, int length) { - if ((0 > offset) || (offset > data.length)) - throw new IndexOutOfBoundsException(); - if ((0 > length) || (length > data.length - offset)) - throw new IndexOutOfBoundsException(); - - int position = offset; - - int createTime = 0; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - int modifyTime = 0; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - int backupTime = 0; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - int accessTime = 0; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - - this.fileDatesInfoEntry = new FileDatesInfoEntry(); - fileDatesInfoEntry.setCreateTime(createTime); - fileDatesInfoEntry.setModifyTime(modifyTime); - fileDatesInfoEntry.setBackupTime(backupTime); - fileDatesInfoEntry.setAccessTime(accessTime); - } - - /** - * Gets the format. - * - * @return the format - */ - public FileFormat getFormat() { - return format; - } - - /** - * Sets the format. - * - * @param format - * the new format - */ - public void setFormat(FileFormat format) { - this.format = format; - } - - /** - * Gets the file data. - * - * @return the file data - */ - public AppleFileData getFileData() { - return fileData; - } - - /** - * Sets the file data. - * - * @param fileData - * the new file data - */ - public void setFileData(AppleFileData fileData) { - this.fileData = fileData; - } - - /** - * Gets the data fork. - * - * @return the data fork - */ - public AppleFileData getDataFork() { - return dataFork; - } - - /** - * Sets the data fork. - * - * @param dataFork - * the new data fork - */ - public void setDataFork(AppleFileData dataFork) { - this.dataFork = dataFork; - } - - /** - * Gets the resource fork. - * - * @return the resource fork - */ - public AppleFileData getResourceFork() { - return resourceFork; - } - - /** - * Sets the resource fork. - * - * @param resourceFork - * the new resource fork - */ - public void setResourceFork(AppleFileData resourceFork) { - this.resourceFork = resourceFork; - } - - /** - * Gets the real name. - * - * @return the real name - */ - public AppleFileData getRealName() { - return realName; - } - - /** - * Sets the real name. - * - * @param realName - * the new real name - */ - public void setRealName(AppleFileData realName) { - this.realName = realName; - } - - /** - * Gets the comment. - * - * @return the comment - */ - public AppleFileData getComment() { - return comment; - } - - /** - * Sets the comment. - * - * @param comment - * the new comment - */ - public void setComment(AppleFileData comment) { - this.comment = comment; - } - - /** - * Gets the icon bw. - * - * @return the icon bw - */ - public AppleFileData getIconBW() { - return iconBW; - } - - /** - * Sets the icon bw. - * - * @param iconBW - * the new icon bw - */ - public void setIconBW(AppleFileData iconBW) { - this.iconBW = iconBW; - } - - /** - * Gets the icon color. - * - * @return the icon color - */ - public AppleFileData getIconColor() { - return iconColor; - } - - /** - * Sets the icon color. - * - * @param iconColor - * the new icon color - */ - public void setIconColor(AppleFileData iconColor) { - this.iconColor = iconColor; - } - - /** - * Gets the file dates info. - * - * @return the file dates info - */ - public AppleFileData getFileDatesInfo() { - return fileDatesInfo; - } - - /** - * Sets the file dates info. - * - * @param fileDatesInfo - * the new file dates info - */ - public void setFileDatesInfo(AppleFileData fileDatesInfo) { - this.fileDatesInfo = fileDatesInfo; - } - - /** - * Gets the finder info. - * - * @return the finder info - */ - public AppleFileData getFinderInfo() { - return finderInfo; - } - - /** - * Sets the finder info. - * - * @param finderInfo - * the new finder info - */ - public void setFinderInfo(AppleFileData finderInfo) { - this.finderInfo = finderInfo; - } - - /** - * Gets the macintosh info. - * - * @return the macintosh info - */ - public AppleFileData getMacintoshInfo() { - return macintoshInfo; - } - - /** - * Sets the macintosh info. - * - * @param macintoshInfo - * the new macintosh info - */ - public void setMacintoshInfo(AppleFileData macintoshInfo) { - this.macintoshInfo = macintoshInfo; - } - - /** - * Gets the pro dos file info. - * - * @return the pro dos file info - */ - public AppleFileData getProDOSFileInfo() { - return proDOSFileInfo; - } - - /** - * Sets the pro dos file info. - * - * @param proDOSFileInfo - * the new pro dos file info - */ - public void setProDOSFileInfo(AppleFileData proDOSFileInfo) { - this.proDOSFileInfo = proDOSFileInfo; - } - - /** - * Gets the ms dos file info. - * - * @return the ms dos file info - */ - public AppleFileData getMsDOSFileInfo() { - return msDOSFileInfo; - } - - /** - * Sets the ms dos file info. - * - * @param msDOSFileInfo - * the new ms dos file info - */ - public void setMsDOSFileInfo(AppleFileData msDOSFileInfo) { - this.msDOSFileInfo = msDOSFileInfo; - } - - /** - * Gets the short name. - * - * @return the short name - */ - public AppleFileData getShortName() { - return shortName; - } - - /** - * Sets the short name. - * - * @param shortName - * the new short name - */ - public void setShortName(AppleFileData shortName) { - this.shortName = shortName; - } - - /** - * Gets the afp file info. - * - * @return the afp file info - */ - public AppleFileData getAfpFileInfo() { - return afpFileInfo; - } - - /** - * Sets the afp file info. - * - * @param afpFileInfo - * the new afp file info - */ - public void setAfpFileInfo(AppleFileData afpFileInfo) { - this.afpFileInfo = afpFileInfo; - } - - /** - * Gets the directory id. - * - * @return the directory id - */ - public AppleFileData getDirectoryID() { - return directoryID; - } - - /** - * Sets the directory id. - * - * @param directoryID - * the new directory id - */ - public void setDirectoryID(AppleFileData directoryID) { - this.directoryID = directoryID; - } - - /** - * Gets the num entries. - * - * @return the num entries - */ - public int getNumEntries() { - return numEntries; - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; + +/** + * This abstract class handles AppleSingle/Double files. It contains a common + * method to verify the Apple file, and figure out if it is an AppleSingle or + * AppleDouble formatted file. + *

+ * + * The AppleSingle format is a representation of Macintosh files as one + * consecutive stream of bytes. AppleSingle combines the data fork, resource + * fork and the related Finder meta-file information into a single file. + *

+ * + * The AppleDouble format stores the data fork, resource fork as two separate + * files. AppleDouble leaves the data fork in its original format, and the + * resource fork and Finder information were combined into a second file. + *

+ * + * Apple defined the magic number for the AppleSingle format as 0x00051600, and + * the magic number for the AppleDouble format as 0x00051607. + * + *

+ * AppleSingle file header: 
+ * 
+ * Field Length
+ * ----- ------
+ * Magic number ------- 4 bytes
+ * Version number ------ 4 bytes
+ * Filler ------------- 16 bytes
+ * Number of entries ----- 2 bytes
+ * 
+ * Entry descriptor for each entry:
+ * Entry ID ------ 4 bytes
+ * Offset -------- 4 bytes
+ * Length -------- 4 bytes
+ * 
+ * Apple reserved entry IDs:
+ * 
+ * Data Fork -------- 1 Data fork
+ * Resource Fork ----- 2 Resource fork
+ * Real Name -------- 3 File's name as created on home file system
+ * Comment --------- 4 Standard Macintosh comment
+ * Icon, B&W -------- 5 Standard Macintosh black and white icon
+ * Icon, Color -------- 6 Macintosh color icon
+ * File Dates Info ------8 File creation date, modification date, and so on
+ * Finder Info -------- 9 Standard Macintosh Finder information
+ * Macintosh File Info ---10 Macintosh file information, attributes, and so on
+ * ProDOS File Info -----11 ProDOS file information, attributes, and so on
+ * MS-DOS File Info ----12 MS-DOS file information, attributes, and so on
+ * Short Name --------13 AFP short name
+ * AFP File Info ------- 14 AFP file information, attributes, and so on
+ * Directory ID --------15 AFP directory ID
+ * 
+ * + * See RFC 1740 for reference: http://tools.ietf.org/html/rfc1740 + */ +public abstract class AppleFile { + + /** The Apple file format: AppleSingle, AppleDouble, default to unknown. */ + protected FileFormat format = FileFormat.UNKNOWN; + + /** The raw Apple file. */ + protected AppleFileData fileData = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 1: Data fork. */ + protected AppleFileData dataFork = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 2: Resource fork. */ + protected AppleFileData resourceFork = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 3: File's name as created on home file system. */ + protected AppleFileData realName = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 4: Standard Macintosh comment. */ + protected AppleFileData comment = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 5: Standard Macintosh black and white icon. */ + protected AppleFileData iconBW = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 6: Macintosh color icon. */ + protected AppleFileData iconColor = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 8: File creation date, modification date, and so on. */ + protected AppleFileData fileDatesInfo = AppleFileData.EMPTY_FILE_DATA; + + /** The file dates info entry. */ + protected FileDatesInfoEntry fileDatesInfoEntry = null; + + /** Entry 9: Standard Macintosh Finder information. */ + protected AppleFileData finderInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 10: Macintosh file information, attributes, and so on. */ + protected AppleFileData macintoshInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 11: ProDOS file information, attributes, and so on. */ + protected AppleFileData proDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 12: MS-DOS file information, attributes, and so on. */ + protected AppleFileData msDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 13: AFP short name. */ + protected AppleFileData shortName = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 14: AFP file information, attributes, and so on. */ + protected AppleFileData afpFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 15: AFP directory ID. */ + protected AppleFileData directoryID = AppleFileData.EMPTY_FILE_DATA; + + /** The num entries. */ + protected int numEntries = 0; + + /** + * The Apple file format. + */ + public enum FileFormat { + + APPLE_SINGLE, + APPLE_DOUBLE, + UNKNOWN; + + /** + * Return a suitable Apple file format as inferred from the passed-in + * string. Otherwise return the UNKNOWN file format. + * + * @param fileFormat + * the file format + * @return the FileFormat + */ + public static FileFormat fromString(String fileFormat) { + if (fileFormat == null) { + return null; + } + + try { + return FileFormat.valueOf(fileFormat.toUpperCase()); + } catch (IllegalArgumentException iae) { + Log.error("Bad conversion attempt in FileFormat.fromString; string: " + + fileFormat + "; message: " + iae.getMessage()); + Log.exception(iae); + return UNKNOWN; + } + } + }; + + /** + * This class represents the file dates. + */ + public class FileDatesInfoEntry { + + /** The create time. */ + private int createTime = Integer.MIN_VALUE; + + /** The modify time. */ + private int modifyTime = Integer.MIN_VALUE; + + /** The backup time. */ + private int backupTime = Integer.MIN_VALUE; + + /** The access time. */ + private int accessTime = Integer.MIN_VALUE; + + /** + * Instantiates a new file dates info entry. + */ + public FileDatesInfoEntry() { + + } + + /** + * Gets the creates the time. + * + * @return the creates the time + */ + public int getCreateTime() { + return createTime; + } + + /** + * Sets the creates the time. + * + * @param createTime + * the new creates the time + */ + public void setCreateTime(int createTime) { + this.createTime = createTime; + } + + /** + * Gets the modify time. + * + * @return the modify time + */ + public int getModifyTime() { + return modifyTime; + } + + /** + * Sets the modify time. + * + * @param modifyTime + * the new modify time + */ + public void setModifyTime(int modifyTime) { + this.modifyTime = modifyTime; + } + + /** + * Gets the backup time. + * + * @return the backup time + */ + public int getBackupTime() { + return backupTime; + } + + /** + * Sets the backup time. + * + * @param backupTime + * the new backup time + */ + public void setBackupTime(int backupTime) { + this.backupTime = backupTime; + } + + /** + * Gets the access time. + * + * @return the access time + */ + public int getAccessTime() { + return accessTime; + } + + /** + * Sets the access time. + * + * @param accessTime + * the new access time + */ + public void setAccessTime(int accessTime) { + this.accessTime = accessTime; + } + } + + /** + * Sets the num entries. + * + * @param numEntries + * the new num entries + */ + public void setNumEntries(int numEntries) { + this.numEntries = numEntries; + } + + /** + * Verify the validity of the Apple file. + * + * @throws FileDecoderException + * the file decoder exception + */ + @SuppressWarnings("unused") + protected void verify() throws FileDecoderException { + byte[] data = this.fileData.getData(); + int offset = this.fileData.getOffset(); + int length = this.fileData.getLength(); + int position = offset; + if (length < 26) { + throw new FileDecoderException("File is too short"); + } + + /* Magic number */ + int magic = 0; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + + /* Check Apple file format: AppleSingle or AppleDobule */ + if (magic == 0x00051600) { + this.format = FileFormat.APPLE_SINGLE; + } else if (magic == 0x00051607) { + this.format = FileFormat.APPLE_DOUBLE; + } else { + throw new FileDecoderException("Invalid Apple file magic number."); + } + + /* Version number */ + int version = 0; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + if (version != 0x00020000) { + throw new FileDecoderException("Unknown Apple file version"); + } + + /* Filler */ + position += 16; + + /* Number of entries */ + this.numEntries = 0; + this.numEntries |= data[(position++)] & 0xFF; + this.numEntries <<= 8; + this.numEntries |= data[(position++)] & 0xFF; + if (length < 26 + 12 * this.numEntries) { + throw new FileDecoderException("Corrupt Apple file data."); + } + + /* Check entries */ + int entryId = 0; + int entryOffset = 0; + int entryLength = 0; + int contentPosition = 26 + 12 * this.numEntries; + for (int i = 0; i < this.numEntries; i++) { + position = 26 + i * 12; + /* Entry ID */ + entryId = 0; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + + /* Entry offset */ + entryOffset = 0; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset &= 0x7FFFFFFF; + + /* Entry length */ + entryLength = 0; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength &= 0x7FFFFFFF; + if ((entryOffset < contentPosition) + || (length < entryOffset + entryLength)) { + throw new FileDecoderException("Corrupt Apple file data."); + } + } + } + + /** + * Extract file dates. + * + * @param data the data + * @param offset the offset + * @param length the length + */ + protected void extractFileDates(byte[] data, int offset, int length) { + if ((0 > offset) || (offset > data.length)) + throw new IndexOutOfBoundsException(); + if ((0 > length) || (length > data.length - offset)) + throw new IndexOutOfBoundsException(); + + int position = offset; + + int createTime = 0; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + int modifyTime = 0; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + int backupTime = 0; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + int accessTime = 0; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + + this.fileDatesInfoEntry = new FileDatesInfoEntry(); + fileDatesInfoEntry.setCreateTime(createTime); + fileDatesInfoEntry.setModifyTime(modifyTime); + fileDatesInfoEntry.setBackupTime(backupTime); + fileDatesInfoEntry.setAccessTime(accessTime); + } + + /** + * Gets the format. + * + * @return the format + */ + public FileFormat getFormat() { + return format; + } + + /** + * Sets the format. + * + * @param format + * the new format + */ + public void setFormat(FileFormat format) { + this.format = format; + } + + /** + * Gets the file data. + * + * @return the file data + */ + public AppleFileData getFileData() { + return fileData; + } + + /** + * Sets the file data. + * + * @param fileData + * the new file data + */ + public void setFileData(AppleFileData fileData) { + this.fileData = fileData; + } + + /** + * Gets the data fork. + * + * @return the data fork + */ + public AppleFileData getDataFork() { + return dataFork; + } + + /** + * Sets the data fork. + * + * @param dataFork + * the new data fork + */ + public void setDataFork(AppleFileData dataFork) { + this.dataFork = dataFork; + } + + /** + * Gets the resource fork. + * + * @return the resource fork + */ + public AppleFileData getResourceFork() { + return resourceFork; + } + + /** + * Sets the resource fork. + * + * @param resourceFork + * the new resource fork + */ + public void setResourceFork(AppleFileData resourceFork) { + this.resourceFork = resourceFork; + } + + /** + * Gets the real name. + * + * @return the real name + */ + public AppleFileData getRealName() { + return realName; + } + + /** + * Sets the real name. + * + * @param realName + * the new real name + */ + public void setRealName(AppleFileData realName) { + this.realName = realName; + } + + /** + * Gets the comment. + * + * @return the comment + */ + public AppleFileData getComment() { + return comment; + } + + /** + * Sets the comment. + * + * @param comment + * the new comment + */ + public void setComment(AppleFileData comment) { + this.comment = comment; + } + + /** + * Gets the icon bw. + * + * @return the icon bw + */ + public AppleFileData getIconBW() { + return iconBW; + } + + /** + * Sets the icon bw. + * + * @param iconBW + * the new icon bw + */ + public void setIconBW(AppleFileData iconBW) { + this.iconBW = iconBW; + } + + /** + * Gets the icon color. + * + * @return the icon color + */ + public AppleFileData getIconColor() { + return iconColor; + } + + /** + * Sets the icon color. + * + * @param iconColor + * the new icon color + */ + public void setIconColor(AppleFileData iconColor) { + this.iconColor = iconColor; + } + + /** + * Gets the file dates info. + * + * @return the file dates info + */ + public AppleFileData getFileDatesInfo() { + return fileDatesInfo; + } + + /** + * Sets the file dates info. + * + * @param fileDatesInfo + * the new file dates info + */ + public void setFileDatesInfo(AppleFileData fileDatesInfo) { + this.fileDatesInfo = fileDatesInfo; + } + + /** + * Gets the finder info. + * + * @return the finder info + */ + public AppleFileData getFinderInfo() { + return finderInfo; + } + + /** + * Sets the finder info. + * + * @param finderInfo + * the new finder info + */ + public void setFinderInfo(AppleFileData finderInfo) { + this.finderInfo = finderInfo; + } + + /** + * Gets the macintosh info. + * + * @return the macintosh info + */ + public AppleFileData getMacintoshInfo() { + return macintoshInfo; + } + + /** + * Sets the macintosh info. + * + * @param macintoshInfo + * the new macintosh info + */ + public void setMacintoshInfo(AppleFileData macintoshInfo) { + this.macintoshInfo = macintoshInfo; + } + + /** + * Gets the pro dos file info. + * + * @return the pro dos file info + */ + public AppleFileData getProDOSFileInfo() { + return proDOSFileInfo; + } + + /** + * Sets the pro dos file info. + * + * @param proDOSFileInfo + * the new pro dos file info + */ + public void setProDOSFileInfo(AppleFileData proDOSFileInfo) { + this.proDOSFileInfo = proDOSFileInfo; + } + + /** + * Gets the ms dos file info. + * + * @return the ms dos file info + */ + public AppleFileData getMsDOSFileInfo() { + return msDOSFileInfo; + } + + /** + * Sets the ms dos file info. + * + * @param msDOSFileInfo + * the new ms dos file info + */ + public void setMsDOSFileInfo(AppleFileData msDOSFileInfo) { + this.msDOSFileInfo = msDOSFileInfo; + } + + /** + * Gets the short name. + * + * @return the short name + */ + public AppleFileData getShortName() { + return shortName; + } + + /** + * Sets the short name. + * + * @param shortName + * the new short name + */ + public void setShortName(AppleFileData shortName) { + this.shortName = shortName; + } + + /** + * Gets the afp file info. + * + * @return the afp file info + */ + public AppleFileData getAfpFileInfo() { + return afpFileInfo; + } + + /** + * Sets the afp file info. + * + * @param afpFileInfo + * the new afp file info + */ + public void setAfpFileInfo(AppleFileData afpFileInfo) { + this.afpFileInfo = afpFileInfo; + } + + /** + * Gets the directory id. + * + * @return the directory id + */ + public AppleFileData getDirectoryID() { + return directoryID; + } + + /** + * Sets the directory id. + * + * @param directoryID + * the new directory id + */ + public void setDirectoryID(AppleFileData directoryID) { + this.directoryID = directoryID; + } + + /** + * Gets the num entries. + * + * @return the num entries + */ + public int getNumEntries() { + return numEntries; + } } \ No newline at end of file diff --git a/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java b/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java index f28c979a..d89471e2 100644 --- a/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java +++ b/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java @@ -1,91 +1,91 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -/** - * This class is for representing the AppleSingle/Double file or its file forks - * (data fork and resource fork) and the related Finder meta-file information. - */ -public final class AppleFileData { - - public static final AppleFileData EMPTY_FILE_DATA = new AppleFileData(); - private byte[] data; - private int offset; - private int length; - - /** - * Instantiates a new apple file data. - */ - public AppleFileData() { - this.data = new byte[0]; - this.offset = 0; - this.length = 0; - } - - /** - * Instantiates a new apple file data. - * - * @param data the data - */ - public AppleFileData(byte[] data) { - this.data = data; - this.offset = 0; - this.length = data.length; - } - - /** - * Instantiates a new apple file data. - * - * @param data the data - * @param offset the offset - * @param length the length - */ - public AppleFileData(byte[] data, int offset, int length) { - if ((0 > offset) || (offset > data.length)) - throw new IndexOutOfBoundsException(); - if ((0 > length) || (length > data.length - offset)) - throw new IndexOutOfBoundsException(); - this.data = data; - this.offset = offset; - this.length = length; - } - - /** - * Gets the bytes. - * - * @return the bytes - */ - public byte[] getBytes() { - byte[] data = new byte[this.length]; - System.arraycopy(this.data, this.offset, data, 0, this.length); - return data; - } - - /** - * Gets the data. - * - * @return the data - */ - public byte[] getData() { - return this.data; - } - - /** - * Gets the offset. - * - * @return the offset - */ - public int getOffset() { - return this.offset; - } - - /** - * Gets the length. - * - * @return the length - */ - public int getLength() { - return this.length; - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +/** + * This class is for representing the AppleSingle/Double file or its file forks + * (data fork and resource fork) and the related Finder meta-file information. + */ +public final class AppleFileData { + + public static final AppleFileData EMPTY_FILE_DATA = new AppleFileData(); + private byte[] data; + private int offset; + private int length; + + /** + * Instantiates a new apple file data. + */ + public AppleFileData() { + this.data = new byte[0]; + this.offset = 0; + this.length = 0; + } + + /** + * Instantiates a new apple file data. + * + * @param data the data + */ + public AppleFileData(byte[] data) { + this.data = data; + this.offset = 0; + this.length = data.length; + } + + /** + * Instantiates a new apple file data. + * + * @param data the data + * @param offset the offset + * @param length the length + */ + public AppleFileData(byte[] data, int offset, int length) { + if ((0 > offset) || (offset > data.length)) + throw new IndexOutOfBoundsException(); + if ((0 > length) || (length > data.length - offset)) + throw new IndexOutOfBoundsException(); + this.data = data; + this.offset = offset; + this.length = length; + } + + /** + * Gets the bytes. + * + * @return the bytes + */ + public byte[] getBytes() { + byte[] data = new byte[this.length]; + System.arraycopy(this.data, this.offset, data, 0, this.length); + return data; + } + + /** + * Gets the data. + * + * @return the data + */ + public byte[] getData() { + return this.data; + } + + /** + * Gets the offset. + * + * @return the offset + */ + public int getOffset() { + return this.offset; + } + + /** + * Gets the length. + * + * @return the length + */ + public int getLength() { + return this.length; + } } \ No newline at end of file diff --git a/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java b/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java index 3248f85d..aa7b0363 100644 --- a/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java +++ b/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java @@ -1,143 +1,143 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; - -/** - * This class handles the extraction of the data fork, resource fork and other - * entries from an AppleSingle/Double file. The Perforce 'apple' file type is a - * compressed AppleSingle (Mac resource + data) file. The Perforce 'resource' - * file type is a compressed AppleDouble (Mac resource fork) file. - */ -public class AppleFileDecoder extends AppleFile { - - /** - * Instantiates a new apple file decoder. - * - * @param fileData - * the file data - */ - public AppleFileDecoder(AppleFileData fileData) { - if (fileData != null) { - this.fileData = fileData; - } - } - - /** - * Extract the data fork, resource fork and other entries from the Apple - * file. - * - * @throws FileDecoderException - * the file decoder exception - */ - @SuppressWarnings("unused") - public void extract() throws FileDecoderException { - // Verify the validity of the Apple file - verify(); - - byte[] data = this.fileData.getData(); - int offset = this.fileData.getOffset(); - int length = this.fileData.getLength(); - int contentPosition = 0; - int entryId = 0; - int entryOffset = 0; - int entryLength = 0; - for (int i = 0; i < this.numEntries; i++) { - contentPosition = offset + 26 + i * 12; - /* Entry ID */ - entryId = 0; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - - /* Entry offset */ - entryOffset = 0; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset &= 0x7FFFFFFF; - - /* Entry length */ - entryLength = 0; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength &= 0x7FFFFFFF; - - switch (entryId) { - case 1: - this.dataFork = new AppleFileData(data, offset + entryOffset, - entryLength); - break; - case 2: - this.resourceFork = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 3: - this.realName = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 4: - this.comment = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 5: - this.iconBW = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 6: - this.iconColor = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 8: - this.fileDatesInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - extractFileDates(data, offset + entryOffset, entryLength); - break; - case 9: - this.finderInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 10: - this.macintoshInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 11: - this.proDOSFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 12: - this.msDOSFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 13: - this.shortName = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 14: - this.afpFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 15: - this.directoryID = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - default: - Log.warn("Apple file entry ID: " + entryId + " is not handled."); - - } - } - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; + +/** + * This class handles the extraction of the data fork, resource fork and other + * entries from an AppleSingle/Double file. The Perforce 'apple' file type is a + * compressed AppleSingle (Mac resource + data) file. The Perforce 'resource' + * file type is a compressed AppleDouble (Mac resource fork) file. + */ +public class AppleFileDecoder extends AppleFile { + + /** + * Instantiates a new apple file decoder. + * + * @param fileData + * the file data + */ + public AppleFileDecoder(AppleFileData fileData) { + if (fileData != null) { + this.fileData = fileData; + } + } + + /** + * Extract the data fork, resource fork and other entries from the Apple + * file. + * + * @throws FileDecoderException + * the file decoder exception + */ + @SuppressWarnings("unused") + public void extract() throws FileDecoderException { + // Verify the validity of the Apple file + verify(); + + byte[] data = this.fileData.getData(); + int offset = this.fileData.getOffset(); + int length = this.fileData.getLength(); + int contentPosition = 0; + int entryId = 0; + int entryOffset = 0; + int entryLength = 0; + for (int i = 0; i < this.numEntries; i++) { + contentPosition = offset + 26 + i * 12; + /* Entry ID */ + entryId = 0; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + + /* Entry offset */ + entryOffset = 0; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset &= 0x7FFFFFFF; + + /* Entry length */ + entryLength = 0; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength &= 0x7FFFFFFF; + + switch (entryId) { + case 1: + this.dataFork = new AppleFileData(data, offset + entryOffset, + entryLength); + break; + case 2: + this.resourceFork = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 3: + this.realName = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 4: + this.comment = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 5: + this.iconBW = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 6: + this.iconColor = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 8: + this.fileDatesInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + extractFileDates(data, offset + entryOffset, entryLength); + break; + case 9: + this.finderInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 10: + this.macintoshInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 11: + this.proDOSFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 12: + this.msDOSFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 13: + this.shortName = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 14: + this.afpFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 15: + this.directoryID = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + default: + Log.warn("Apple file entry ID: " + entryId + " is not handled."); + + } + } + } } \ No newline at end of file diff --git a/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java b/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java index 7c5d0a04..abdfed59 100644 --- a/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java +++ b/p4java/r18-1/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java @@ -1,300 +1,300 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.exception.FileEncoderException; - -/** - * This class handles the combination of the data fork, resource fork and other - * entries into an AppleSingle/Double file. - *

- * - * Note that if it is an AppleDouble, the data fork is a separate file external - * to this file. - */ -public class AppleFileEncoder extends AppleFile { - - /** - * Instantiates a new apple file decoder. - * - * @throws FileEncoderException - */ - public AppleFileEncoder(FileFormat fileFormat) throws FileEncoderException { - if (fileFormat == null) { - throw new FileEncoderException("Null file format passed to the AppleFileEncoder constructor."); - } - if (fileFormat == FileFormat.UNKNOWN) { - throw new FileEncoderException("Unknown file format passed to the AppleFileEncoder constructor."); - } - } - - /** - * Combine the data fork, resource fork and other entries into an - * AppleSingle/Double file. - * - * @throws FileEncoderException - * the file encoder exception - */ - @SuppressWarnings("unused") - public void combine() throws FileEncoderException { - - boolean isAppleSingle = (this.format == FileFormat.APPLE_SINGLE); - boolean isAppleDouble = (this.format == FileFormat.APPLE_DOUBLE); - - boolean hasDataFork = (this.dataFork != AppleFileData.EMPTY_FILE_DATA); - boolean hasResourceFork = (this.resourceFork != AppleFileData.EMPTY_FILE_DATA); - boolean hasRealName = (this.realName != AppleFileData.EMPTY_FILE_DATA); - boolean hasComment = (this.comment != AppleFileData.EMPTY_FILE_DATA); - boolean hasIconBW = (this.iconBW != AppleFileData.EMPTY_FILE_DATA); - boolean hasIconColor = (this.iconColor != AppleFileData.EMPTY_FILE_DATA); - boolean hasFileDatesInfo = (this.fileDatesInfoEntry != null); - boolean hasFinderInfo = (this.finderInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasMacintoshInfo = (this.macintoshInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasProDOSFileInfo = (this.proDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasMsDOSFileInfo = (this.msDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasShortName = (this.shortName != AppleFileData.EMPTY_FILE_DATA); - boolean hasAfpFileInfo = (this.afpFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasDirectoryID = (this.directoryID != AppleFileData.EMPTY_FILE_DATA); - - this.fileData = AppleFileData.EMPTY_FILE_DATA; - - int length = 90 + this.realName.getLength() - + this.resourceFork.getLength(); - - /* AppleSingle includes the data fork */ - if (isAppleSingle) { - length += this.dataFork.getLength(); - } - - byte[] data = new byte[length]; - int position = 0; - - /* Magic number for AppleSingle or AppleDouble */ - if (isAppleDouble) { - data[(position++)] = 0; - data[(position++)] = 5; - data[(position++)] = 22; - data[(position++)] = 0; - } else { - data[(position++)] = 0; - data[(position++)] = 5; - data[(position++)] = 22; - data[(position++)] = 7; - } - - /* Version number */ - data[(position++)] = 0; - data[(position++)] = 2; - data[(position++)] = 0; - data[(position++)] = 0; - - /* Filler */ - - for (int k = 0; k < 16; k++) { - data[(position++)] = 0; - } - - /* Number of entries */ - this.numEntries = 0; - if (hasRealName) { - this.numEntries += 1; - } - if (hasFileDatesInfo) { - this.numEntries += 1; - } - if (hasResourceFork) { - this.numEntries += 1; - } - if ((hasDataFork) && (isAppleSingle)) { - this.numEntries += 1; - } - data[(position++)] = ((byte) (this.numEntries >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.numEntries & 0xFF)); - - /* Header information for the entries */ - - /* Real name entry header */ - int realNamePosition = 0; - if (hasRealName) { - int realNameEntryId = 3; - int realNameEntryOffset = 0; - int realNameEntryLength = this.realName.getLength(); - data[(position++)] = ((byte) (realNameEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 0 & 0xFF)); - realNamePosition = position; - data[(position++)] = ((byte) (realNameEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 0 & 0xFF)); - } - - /* File dates info entry header */ - int fileDatesInfoPosition = 0; - if (hasFileDatesInfo) { - int fileDatesInfoEntryId = 8; - int fileDatesInfoEntryOffset = 0; - int fileDatesInfoEntryLength = 16; - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 0 & 0xFF)); - fileDatesInfoPosition = position; - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 0 & 0xFF)); - } - - /* Resource fork entry header */ - int resourceForkPosition = 0; - if (hasResourceFork) { - int resourceForkEntryId = 2; - int resourceForkEntryOffset = 0; - int resourceFokrEntryLength = this.resourceFork.getLength(); - data[(position++)] = ((byte) (resourceForkEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 0 & 0xFF)); - resourceForkPosition = position; - data[(position++)] = ((byte) (resourceForkEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 0 & 0xFF)); - } - - /* Data fork entry header */ - int dataForkPosition = 0; - if ((hasDataFork) && (isAppleSingle)) { - int dataForkEntryId = 1; - int dataForkEntryOffset = 0; - int dataForkEntryLength = this.dataFork.getLength(); - data[(position++)] = ((byte) (dataForkEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 0 & 0xFF)); - dataForkPosition = position; - data[(position++)] = ((byte) (dataForkEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 0 & 0xFF)); - } - - /* Content for the entries */ - - /* Real name content */ - if (hasRealName) { - int realNamePositionCurrent = position; - position = realNamePosition; - data[(position++)] = ((byte) (realNamePositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 0 & 0xFF)); - position = realNamePositionCurrent; - byte[] realNameData = this.realName.getData(); - int realNameOffset = this.realName.getOffset(); - int realNameLength = this.realName.getLength(); - System.arraycopy(realNameData, realNameOffset, data, position, - realNameLength); - position += realNameLength; - } - - /* File dates info content */ - if (hasFileDatesInfo) { - int fileDatesInfoPositionCurrent = position; - position = fileDatesInfoPosition; - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 0 & 0xFF)); - position = fileDatesInfoPositionCurrent; - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 0 & 0xFF)); - } - - /* Resource fork content */ - if (hasResourceFork) { - int resourceForkPositionCurrent = position; - position = resourceForkPosition; - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 0 & 0xFF)); - position = resourceForkPositionCurrent; - byte[] resourceForkData = this.resourceFork.getData(); - int resourceForkOffset = this.resourceFork.getOffset(); - int resourceForkLength = this.resourceFork.getLength(); - System.arraycopy(resourceForkData, resourceForkOffset, data, - position, resourceForkLength); - position += resourceForkLength; - } - - /* Data fork content */ - if ((hasDataFork) && (isAppleSingle)) { - int dataForkPosition2 = position; - position = dataForkPosition; - data[(position++)] = ((byte) (dataForkPosition2 >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 0 & 0xFF)); - position = dataForkPosition2; - byte[] dataForkData = this.dataFork.getData(); - int dataForkOffset = this.dataFork.getOffset(); - int dataForkLength = this.dataFork.getLength(); - System.arraycopy(dataForkData, dataForkOffset, data, position, - dataForkLength); - position += dataForkLength; - } - - /* Create the Apple file data */ - this.fileData = new AppleFileData(data, 0, position); - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.exception.FileEncoderException; + +/** + * This class handles the combination of the data fork, resource fork and other + * entries into an AppleSingle/Double file. + *

+ * + * Note that if it is an AppleDouble, the data fork is a separate file external + * to this file. + */ +public class AppleFileEncoder extends AppleFile { + + /** + * Instantiates a new apple file decoder. + * + * @throws FileEncoderException + */ + public AppleFileEncoder(FileFormat fileFormat) throws FileEncoderException { + if (fileFormat == null) { + throw new FileEncoderException("Null file format passed to the AppleFileEncoder constructor."); + } + if (fileFormat == FileFormat.UNKNOWN) { + throw new FileEncoderException("Unknown file format passed to the AppleFileEncoder constructor."); + } + } + + /** + * Combine the data fork, resource fork and other entries into an + * AppleSingle/Double file. + * + * @throws FileEncoderException + * the file encoder exception + */ + @SuppressWarnings("unused") + public void combine() throws FileEncoderException { + + boolean isAppleSingle = (this.format == FileFormat.APPLE_SINGLE); + boolean isAppleDouble = (this.format == FileFormat.APPLE_DOUBLE); + + boolean hasDataFork = (this.dataFork != AppleFileData.EMPTY_FILE_DATA); + boolean hasResourceFork = (this.resourceFork != AppleFileData.EMPTY_FILE_DATA); + boolean hasRealName = (this.realName != AppleFileData.EMPTY_FILE_DATA); + boolean hasComment = (this.comment != AppleFileData.EMPTY_FILE_DATA); + boolean hasIconBW = (this.iconBW != AppleFileData.EMPTY_FILE_DATA); + boolean hasIconColor = (this.iconColor != AppleFileData.EMPTY_FILE_DATA); + boolean hasFileDatesInfo = (this.fileDatesInfoEntry != null); + boolean hasFinderInfo = (this.finderInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasMacintoshInfo = (this.macintoshInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasProDOSFileInfo = (this.proDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasMsDOSFileInfo = (this.msDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasShortName = (this.shortName != AppleFileData.EMPTY_FILE_DATA); + boolean hasAfpFileInfo = (this.afpFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasDirectoryID = (this.directoryID != AppleFileData.EMPTY_FILE_DATA); + + this.fileData = AppleFileData.EMPTY_FILE_DATA; + + int length = 90 + this.realName.getLength() + + this.resourceFork.getLength(); + + /* AppleSingle includes the data fork */ + if (isAppleSingle) { + length += this.dataFork.getLength(); + } + + byte[] data = new byte[length]; + int position = 0; + + /* Magic number for AppleSingle or AppleDouble */ + if (isAppleDouble) { + data[(position++)] = 0; + data[(position++)] = 5; + data[(position++)] = 22; + data[(position++)] = 0; + } else { + data[(position++)] = 0; + data[(position++)] = 5; + data[(position++)] = 22; + data[(position++)] = 7; + } + + /* Version number */ + data[(position++)] = 0; + data[(position++)] = 2; + data[(position++)] = 0; + data[(position++)] = 0; + + /* Filler */ + + for (int k = 0; k < 16; k++) { + data[(position++)] = 0; + } + + /* Number of entries */ + this.numEntries = 0; + if (hasRealName) { + this.numEntries += 1; + } + if (hasFileDatesInfo) { + this.numEntries += 1; + } + if (hasResourceFork) { + this.numEntries += 1; + } + if ((hasDataFork) && (isAppleSingle)) { + this.numEntries += 1; + } + data[(position++)] = ((byte) (this.numEntries >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.numEntries & 0xFF)); + + /* Header information for the entries */ + + /* Real name entry header */ + int realNamePosition = 0; + if (hasRealName) { + int realNameEntryId = 3; + int realNameEntryOffset = 0; + int realNameEntryLength = this.realName.getLength(); + data[(position++)] = ((byte) (realNameEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 0 & 0xFF)); + realNamePosition = position; + data[(position++)] = ((byte) (realNameEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 0 & 0xFF)); + } + + /* File dates info entry header */ + int fileDatesInfoPosition = 0; + if (hasFileDatesInfo) { + int fileDatesInfoEntryId = 8; + int fileDatesInfoEntryOffset = 0; + int fileDatesInfoEntryLength = 16; + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 0 & 0xFF)); + fileDatesInfoPosition = position; + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 0 & 0xFF)); + } + + /* Resource fork entry header */ + int resourceForkPosition = 0; + if (hasResourceFork) { + int resourceForkEntryId = 2; + int resourceForkEntryOffset = 0; + int resourceFokrEntryLength = this.resourceFork.getLength(); + data[(position++)] = ((byte) (resourceForkEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 0 & 0xFF)); + resourceForkPosition = position; + data[(position++)] = ((byte) (resourceForkEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 0 & 0xFF)); + } + + /* Data fork entry header */ + int dataForkPosition = 0; + if ((hasDataFork) && (isAppleSingle)) { + int dataForkEntryId = 1; + int dataForkEntryOffset = 0; + int dataForkEntryLength = this.dataFork.getLength(); + data[(position++)] = ((byte) (dataForkEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 0 & 0xFF)); + dataForkPosition = position; + data[(position++)] = ((byte) (dataForkEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 0 & 0xFF)); + } + + /* Content for the entries */ + + /* Real name content */ + if (hasRealName) { + int realNamePositionCurrent = position; + position = realNamePosition; + data[(position++)] = ((byte) (realNamePositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 0 & 0xFF)); + position = realNamePositionCurrent; + byte[] realNameData = this.realName.getData(); + int realNameOffset = this.realName.getOffset(); + int realNameLength = this.realName.getLength(); + System.arraycopy(realNameData, realNameOffset, data, position, + realNameLength); + position += realNameLength; + } + + /* File dates info content */ + if (hasFileDatesInfo) { + int fileDatesInfoPositionCurrent = position; + position = fileDatesInfoPosition; + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 0 & 0xFF)); + position = fileDatesInfoPositionCurrent; + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 0 & 0xFF)); + } + + /* Resource fork content */ + if (hasResourceFork) { + int resourceForkPositionCurrent = position; + position = resourceForkPosition; + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 0 & 0xFF)); + position = resourceForkPositionCurrent; + byte[] resourceForkData = this.resourceFork.getData(); + int resourceForkOffset = this.resourceFork.getOffset(); + int resourceForkLength = this.resourceFork.getLength(); + System.arraycopy(resourceForkData, resourceForkOffset, data, + position, resourceForkLength); + position += resourceForkLength; + } + + /* Data fork content */ + if ((hasDataFork) && (isAppleSingle)) { + int dataForkPosition2 = position; + position = dataForkPosition; + data[(position++)] = ((byte) (dataForkPosition2 >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 0 & 0xFF)); + position = dataForkPosition2; + byte[] dataForkData = this.dataFork.getData(); + int dataForkOffset = this.dataFork.getOffset(); + int dataForkLength = this.dataFork.getLength(); + System.arraycopy(dataForkData, dataForkOffset, data, position, + dataForkLength); + position += dataForkLength; + } + + /* Create the Apple file data */ + this.fileData = new AppleFileData(data, 0, position); + } } \ No newline at end of file diff --git a/p4java/r18-1/src/test/java/com/perforce/p4java/tests/dev/unit/bug/r132/CreateSymbolicLinkTest.java b/p4java/r18-1/src/test/java/com/perforce/p4java/tests/dev/unit/bug/r132/CreateSymbolicLinkTest.java index 59a9a133..1c224364 100644 --- a/p4java/r18-1/src/test/java/com/perforce/p4java/tests/dev/unit/bug/r132/CreateSymbolicLinkTest.java +++ b/p4java/r18-1/src/test/java/com/perforce/p4java/tests/dev/unit/bug/r132/CreateSymbolicLinkTest.java @@ -1,54 +1,54 @@ -package com.perforce.p4java.tests.dev.unit.bug.r132; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; - -import org.junit.jupiter.api.Test; -import org.junit.platform.runner.JUnitPlatform; -import org.junit.runner.RunWith; - -import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SymbolicLinkHelper; -import com.perforce.p4java.tests.dev.unit.P4JavaTestCase; - -/** - * Test create symbolic link with non-existing target. - */ -@RunWith(JUnitPlatform.class) -public class CreateSymbolicLinkTest extends P4JavaTestCase { - /** - * Test create symbolic link with non-existing target. - */ - @Test - public void testCreateSymbolicLink() throws IOException { - // Check if symlink capable (JDK 7 or above) - if (SymbolicLinkHelper.isSymbolicLinkCapable()) { - String target = File.createTempFile("link-to-me", ".tmp").getAbsolutePath(); - String link = System.getProperty("java.io.tmpdir") + "/p4java-bin-" + getRandomInt(); - - // Create symbolic link - debugPrint("Creating link " + link + " to " + target); - String path = SymbolicLinkHelper.createSymbolicLink(link, target); - assertNotNull("Failed to create a symbolic link from " + link + " to " + target); - assertTrue(path + " is not a symbolic link", SymbolicLinkHelper.isSymbolicLink(path)); - - debugPrint("Creating a new file object for " + path); - File file = new File(path); - assertTrue("A new File object to " + path + " is not reported as a symbolic link.", - Files.isSymbolicLink(file.toPath())); - assertTrue("A new File object to " + path + " is not reported as not existing.", - file.exists()); - - debugPrint("Checking the parent path " + path); - String parentPath = file.getParent(); - assertNotNull(file + " has not parent path.", parentPath); - - debugPrint("Deleting " + file); - assertTrue("Failed to delete " + file.toPath(), Files.deleteIfExists(file.toPath())); - } - } - -} +package com.perforce.p4java.tests.dev.unit.bug.r132; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; + +import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SymbolicLinkHelper; +import com.perforce.p4java.tests.dev.unit.P4JavaTestCase; + +/** + * Test create symbolic link with non-existing target. + */ +@RunWith(JUnitPlatform.class) +public class CreateSymbolicLinkTest extends P4JavaTestCase { + /** + * Test create symbolic link with non-existing target. + */ + @Test + public void testCreateSymbolicLink() throws IOException { + // Check if symlink capable (JDK 7 or above) + if (SymbolicLinkHelper.isSymbolicLinkCapable()) { + String target = File.createTempFile("link-to-me", ".tmp").getAbsolutePath(); + String link = System.getProperty("java.io.tmpdir") + "/p4java-bin-" + getRandomInt(); + + // Create symbolic link + debugPrint("Creating link " + link + " to " + target); + String path = SymbolicLinkHelper.createSymbolicLink(link, target); + assertNotNull("Failed to create a symbolic link from " + link + " to " + target); + assertTrue(path + " is not a symbolic link", SymbolicLinkHelper.isSymbolicLink(path)); + + debugPrint("Creating a new file object for " + path); + File file = new File(path); + assertTrue("A new File object to " + path + " is not reported as a symbolic link.", + Files.isSymbolicLink(file.toPath())); + assertTrue("A new File object to " + path + " is not reported as not existing.", + file.exists()); + + debugPrint("Checking the parent path " + path); + String parentPath = file.getParent(); + assertNotNull(file + " has not parent path.", parentPath); + + debugPrint("Deleting " + file); + assertTrue("Failed to delete " + file.toPath(), Files.deleteIfExists(file.toPath())); + } + } + +} diff --git a/p4java/r18-1/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java b/p4java/r18-1/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java index bab7e806..978db638 100644 --- a/p4java/r18-1/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java +++ b/p4java/r18-1/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java @@ -1,83 +1,83 @@ -/** - * Copyright (c) 2012 Perforce Software. All rights reserved. - */ -package com.perforce.p4java.tests.dev.unit.features123; - -import static org.junit.Assert.assertTrue; - -import java.io.File; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SymbolicLinkHelper; -import com.perforce.p4java.tests.dev.unit.P4JavaTestCase; - -/** - * Test symbolic link helper (JDK 7 or above) - */ -public class SymbolicLinkHelperTest extends P4JavaTestCase { - - /** - * @BeforeClass annotation to a method to be run before all the tests in a - * class. - */ - @BeforeClass - public static void oneTimeSetUp() { - // one-time initialization code (before all the tests). - } - - /** - * @AfterClass annotation to a method to be run after all the tests in a - * class. - */ - @AfterClass - public static void oneTimeTearDown() { - // one-time cleanup code (after all the tests). - } - - /** - * @Before annotation to a method to be run before each test in a class. - */ - @Before - public void setUp() { - // initialization code (before each test). - } - - /** - * @After annotation to a method to be run after each test in a class. - */ - @After - public void tearDown() { - // cleanup code (after each test). - } - - /** - * Test symbolic link - */ - @Test - public void tesSymbolicLink() { - // Check if symlink capable - if (SymbolicLinkHelper.isSymbolicLinkCapable()) { - - String target = "/usr/bin"; - String link = "/tmp/user-bin-" + getRandomInt(); - - // Create symbolic link - Object path = SymbolicLinkHelper.createSymbolicLink(link, target); - - boolean isSymlink = SymbolicLinkHelper.isSymbolicLink(path - .toString()); - assertTrue(isSymlink); - - File file = new File(path.toString()); - if (file.exists()) { - file.delete(); - } - } - } - -} +/** + * Copyright (c) 2012 Perforce Software. All rights reserved. + */ +package com.perforce.p4java.tests.dev.unit.features123; + +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SymbolicLinkHelper; +import com.perforce.p4java.tests.dev.unit.P4JavaTestCase; + +/** + * Test symbolic link helper (JDK 7 or above) + */ +public class SymbolicLinkHelperTest extends P4JavaTestCase { + + /** + * @BeforeClass annotation to a method to be run before all the tests in a + * class. + */ + @BeforeClass + public static void oneTimeSetUp() { + // one-time initialization code (before all the tests). + } + + /** + * @AfterClass annotation to a method to be run after all the tests in a + * class. + */ + @AfterClass + public static void oneTimeTearDown() { + // one-time cleanup code (after all the tests). + } + + /** + * @Before annotation to a method to be run before each test in a class. + */ + @Before + public void setUp() { + // initialization code (before each test). + } + + /** + * @After annotation to a method to be run after each test in a class. + */ + @After + public void tearDown() { + // cleanup code (after each test). + } + + /** + * Test symbolic link + */ + @Test + public void tesSymbolicLink() { + // Check if symlink capable + if (SymbolicLinkHelper.isSymbolicLinkCapable()) { + + String target = "/usr/bin"; + String link = "/tmp/user-bin-" + getRandomInt(); + + // Create symbolic link + Object path = SymbolicLinkHelper.createSymbolicLink(link, target); + + boolean isSymlink = SymbolicLinkHelper.isSymbolicLink(path + .toString()); + assertTrue(isSymlink); + + File file = new File(path.toString()); + if (file.exists()) { + file.delete(); + } + } + } + +} diff --git a/p4java/r19-1/LICENSE.txt b/p4java/r19-1/LICENSE.txt index 5309eefb..d67531fa 100644 --- a/p4java/r19-1/LICENSE.txt +++ b/p4java/r19-1/LICENSE.txt @@ -1,282 +1,282 @@ -Copyright (c) 2009-2016, Perforce Software, Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -License of included (com.jcraft) jzlib library: ------------------------------------------------------------------------------- - -Copyright (c) 2000-2016 ymnk, JCraft,Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution. - - 3. The names of the authors may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, -INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -License of included (org.apache.commons) commons-lang3: -License of included (commons-io) commons-io: ------------------------------------------------------------------------------- - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -License of included (com.google.code.findbugs) jsr305 ------------------------------------------------------------------------------- - -The JSR-305 reference implementation (lib/jsr305.jar) is -distributed under the terms of the New BSD license: - - http://www.opensource.org/licenses/bsd-license.php - -See the JSR-305 home page for more information: - - http://code.google.com/p/jsr-305/ - - -License of included (com.google.code.findbugs) jsr305 ------------------------------------------------------------------------------- - -Copyright (c) 2005 Brian Goetz -Released under the Creative Commons Attribution License - (http://creativecommons.org/licenses/by/2.5) -Official home: http://www.jcip.net - +Copyright (c) 2009-2016, Perforce Software, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +License of included (com.jcraft) jzlib library: +------------------------------------------------------------------------------ + +Copyright (c) 2000-2016 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +License of included (org.apache.commons) commons-lang3: +License of included (commons-io) commons-io: +------------------------------------------------------------------------------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +License of included (com.google.code.findbugs) jsr305 +------------------------------------------------------------------------------ + +The JSR-305 reference implementation (lib/jsr305.jar) is +distributed under the terms of the New BSD license: + + http://www.opensource.org/licenses/bsd-license.php + +See the JSR-305 home page for more information: + + http://code.google.com/p/jsr-305/ + + +License of included (com.google.code.findbugs) jsr305 +------------------------------------------------------------------------------ + +Copyright (c) 2005 Brian Goetz +Released under the Creative Commons Attribution License + (http://creativecommons.org/licenses/by/2.5) +Official home: http://www.jcip.net + diff --git a/p4java/r19-1/pom.xml b/p4java/r19-1/pom.xml index ef41cf03..86ca474b 100644 --- a/p4java/r19-1/pom.xml +++ b/p4java/r19-1/pom.xml @@ -1,126 +1,126 @@ - - 4.0.0 - com.perforce - p4java - jar - 2019.1.1939255 - Perforce Java API - http://www.perforce.com - - - Perforce Software - http://www.perforce.com - - - - P4Java, the Perforce Java API is a Java-native API for accessing Perforce SCM services from - within Java applications, servlets, plug-ins, and other Java contexts. - - - - - Perforce Software - LICENSE.txt - repo - - - - - scm:perforce:workshop.perforce.com:1666//guest/perforce_software/p4java - - - scm:perforce:workshop.perforce.com:1666//guest/perforce_software/p4java - - https://swarm.workshop.perforce.com/files/guest/perforce_software/p4java - - - - - p4java - Perforce Software - support+java@perforce.com - - - - - 1.7 - 1.7 - UTF-8 - 0 - releases - 1.6.5 - 4.12 - 5.0.0-M2 - 4.12.0-M2 - 1.0.0-M2 - - - - - com.jcraft - jzlib - 1.1.3 - - - org.apache.commons - commons-lang3 - 3.4 - - - org.apache.commons - commons-exec - 1.3 - - - commons-codec - commons-codec - 1.13 - - - commons-io - commons-io - 2.5 - - - com.google.code.findbugs - jsr305 - 3.0.1 - - - org.slf4j - slf4j-api - 1.7.24 - - - org.slf4j - slf4j-simple - 1.7.24 - - - org.apache.commons - commons-compress - 1.9 - test - - - junit - junit - ${junit.version} - test - - - org.mockito - mockito-core - 2.7.12 - test - - - com.googlecode.java-diff-utils - diffutils - 1.3.0 - test - - - + + 4.0.0 + com.perforce + p4java + jar + 2019.1.1939255 + Perforce Java API + http://www.perforce.com + + + Perforce Software + http://www.perforce.com + + + + P4Java, the Perforce Java API is a Java-native API for accessing Perforce SCM services from + within Java applications, servlets, plug-ins, and other Java contexts. + + + + + Perforce Software + LICENSE.txt + repo + + + + + scm:perforce:workshop.perforce.com:1666//guest/perforce_software/p4java + + + scm:perforce:workshop.perforce.com:1666//guest/perforce_software/p4java + + https://swarm.workshop.perforce.com/files/guest/perforce_software/p4java + + + + + p4java + Perforce Software + support+java@perforce.com + + + + + 1.7 + 1.7 + UTF-8 + 0 + releases + 1.6.5 + 4.12 + 5.0.0-M2 + 4.12.0-M2 + 1.0.0-M2 + + + + + com.jcraft + jzlib + 1.1.3 + + + org.apache.commons + commons-lang3 + 3.4 + + + org.apache.commons + commons-exec + 1.3 + + + commons-codec + commons-codec + 1.13 + + + commons-io + commons-io + 2.5 + + + com.google.code.findbugs + jsr305 + 3.0.1 + + + org.slf4j + slf4j-api + 1.7.24 + + + org.slf4j + slf4j-simple + 1.7.24 + + + org.apache.commons + commons-compress + 1.9 + test + + + junit + junit + ${junit.version} + test + + + org.mockito + mockito-core + 2.7.12 + test + + + com.googlecode.java-diff-utils + diffutils + 1.3.0 + test + + + diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/.project b/p4java/r19-1/src/main/java/com/perforce/p4java/.project index c34e3af2..145b7c98 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/.project +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/.project @@ -1,11 +1,11 @@ - - - p4java - - - - - - - - + + + p4java + + + + + + + + diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/client/package.html b/p4java/r19-1/src/main/java/com/perforce/p4java/client/package.html index f505bba5..83a680a3 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/client/package.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/client/package.html @@ -1,12 +1,12 @@ - - - - - -Provides interfaces for accessing and manipulating Perforce client workspaces and associated objects. -

-The com.perforce.p4j.client package publicly defines Perforce client workspaces and the various -associated interfaces and operations. Perforce client workspaces (a.k.a. "clients" where -this usage isn't ambiguous) are defined in detail in the main Perforce documentation. - + + + + + +Provides interfaces for accessing and manipulating Perforce client workspaces and associated objects. +

+The com.perforce.p4j.client package publicly defines Perforce client workspaces and the various +associated interfaces and operations. Perforce client workspaces (a.k.a. "clients" where +this usage isn't ambiguous) are defined in detail in the main Perforce documentation. + \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/core/file/package.html b/p4java/r19-1/src/main/java/com/perforce/p4java/core/file/package.html index 51f991b8..4085f9e7 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/core/file/package.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/core/file/package.html @@ -1,8 +1,8 @@ - - - - - -Provides interfaces and classes for defining and accessing Perforce depot and workspace files. - + + + + + +Provides interfaces and classes for defining and accessing Perforce depot and workspace files. + \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/core/package.html b/p4java/r19-1/src/main/java/com/perforce/p4java/core/package.html index 507047c0..f209af70 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/core/package.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/core/package.html @@ -1,10 +1,10 @@ - - - - - -Provides interfaces and classes for defining and accessing Perforce objects -such as jobs, changelists, etc., and, through the file sub-package, Perforce -files. - + + + + + +Provides interfaces and classes for defining and accessing Perforce objects +such as jobs, changelists, etc., and, through the file sub-package, Perforce +files. + \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/exception/package.html b/p4java/r19-1/src/main/java/com/perforce/p4java/exception/package.html index f6c27bad..5f833c07 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/exception/package.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/exception/package.html @@ -1,19 +1,19 @@ - - - - - -Provides exception and error classes for signaling and handling user, internal, and Perforce server errors. -

-P4Java uses a fairly standard set of extensions to java.lang.Exception to signal recoverable errors, -but it also uses extensions to java.lang.Error to signal unrecoverable errors that it's detected itself -(such as null pointers or out-of-range values). Since they're defined as extensions to Error, they're -not typically declared in method "throws" clauses, which means users are free to ignore them in the same -way that they can ignore non-P4Java Error throwables. -

-However, if you wish to keep control in the face of all possible errors, -it's probably a good idea to keep an outer catch block for P4JError catching as well as other exceptions -and throwables — P4Java does not throw such errors lightly, only for conditions that -show a serious programming error (either internally or by the user). - + + + + + +Provides exception and error classes for signaling and handling user, internal, and Perforce server errors. +

+P4Java uses a fairly standard set of extensions to java.lang.Exception to signal recoverable errors, +but it also uses extensions to java.lang.Error to signal unrecoverable errors that it's detected itself +(such as null pointers or out-of-range values). Since they're defined as extensions to Error, they're +not typically declared in method "throws" clauses, which means users are free to ignore them in the same +way that they can ignore non-P4Java Error throwables. +

+However, if you wish to keep control in the face of all possible errors, +it's probably a good idea to keep an outer catch block for P4JError catching as well as other exceptions +and throwables — P4Java does not throw such errors lightly, only for conditions that +show a serious programming error (either internally or by the user). + \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/client/package.html b/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/client/package.html index a656eba1..1e9876a1 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/client/package.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/client/package.html @@ -1,12 +1,12 @@ - - - - - -Provides standard implementation classes for the com.perforce.p4java.client interfaces. -Users are free to use implementations defined here for their own purposes, but such use -is not mandatory, and nor should users rely on these implementations always being -returned by the associated interfaces (P4Java reserves the right to change or extend -implementations under the covers). - + + + + + +Provides standard implementation classes for the com.perforce.p4java.client interfaces. +Users are free to use implementations defined here for their own purposes, but such use +is not mandatory, and nor should users rely on these implementations always being +returned by the associated interfaces (P4Java reserves the right to change or extend +implementations under the covers). + \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/core/file/package.html b/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/core/file/package.html index 707a7478..00ee6844 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/core/file/package.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/core/file/package.html @@ -1,12 +1,12 @@ - - - - - -Provides standard implementation classes for the com.perforce.p4java.core.file interfaces. -Users are free to use implementations defined here for their own purposes, but such use -is not mandatory, and nor should users rely on these implementations always being -returned by the associated interfaces (P4Java reserves the right to change or extend -implementations under the covers). - + + + + + +Provides standard implementation classes for the com.perforce.p4java.core.file interfaces. +Users are free to use implementations defined here for their own purposes, but such use +is not mandatory, and nor should users rely on these implementations always being +returned by the associated interfaces (P4Java reserves the right to change or extend +implementations under the covers). + \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/core/package.html b/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/core/package.html index ad16efc8..99e0b3d8 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/core/package.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/core/package.html @@ -1,12 +1,12 @@ - - - - - -Provides standard implementation classes for the com.perforce.p4java.core interfaces. -Users are free to use implementations defined here for their own purposes, but such use -is not mandatory, and nor should users rely on these implementations always being -returned by the associated interfaces (P4Java reserves the right to change or extend -implementations under the covers). - + + + + + +Provides standard implementation classes for the com.perforce.p4java.core interfaces. +Users are free to use implementations defined here for their own purposes, but such use +is not mandatory, and nor should users rely on these implementations always being +returned by the associated interfaces (P4Java reserves the right to change or extend +implementations under the covers). + \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/package.html b/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/package.html index 5d4d58cd..c3b1ca6b 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/package.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/package.html @@ -1,14 +1,14 @@ - - - - - -Provides generic and / or canonical implementation classes for many of the standard -P4Java client, server, and core interfaces. -

-Users are free to use implementations defined here for their own purposes, but such use -is not mandatory, and nor should users rely on these implementations always being -returned by the associated interfaces (P4Java reserves the right to change or extend -implementations under the covers). - + + + + + +Provides generic and / or canonical implementation classes for many of the standard +P4Java client, server, and core interfaces. +

+Users are free to use implementations defined here for their own purposes, but such use +is not mandatory, and nor should users rely on these implementations always being +returned by the associated interfaces (P4Java reserves the right to change or extend +implementations under the covers). + \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/sys/package.html b/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/sys/package.html index cf50e533..1e754a94 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/sys/package.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/impl/generic/sys/package.html @@ -1,10 +1,10 @@ - - - - - -Provides one or more "helper" and other classes to assist P4Java with -typical operating system operations that can not always be done efficiently (or at all) -in Java. - + + + + + +Provides one or more "helper" and other classes to assist P4Java with +typical operating system operations that can not always be done efficiently (or at all) +in Java. + \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java b/p4java/r19-1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java index 955735f4..75e9be6e 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java @@ -1,118 +1,118 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.impl.mapbased.rpc.sys.helper; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; -import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFile; -import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFileType; -import com.perforce.p4java.io.apple.AppleFileData; -import com.perforce.p4java.io.apple.AppleFileDecoder; - -/** - * Helper class for handling Apple files. - */ -public class AppleFileHelper { - - /** - * Extract the data fork and the resource fork from the Apple file. - * - * @param file the Apple file - */ - public static void extractFile(RpcPerforceFile file) { - if (file.getFileType() == RpcPerforceFileType.FST_APPLEFILE) { - FileOutputStream fosData = null; - FileOutputStream fosResource = null; - try { - byte[] data = AppleFileHelper.getBytesFromFile(file); - AppleFileData fileData = new AppleFileData(data); - AppleFileDecoder appleFile = new AppleFileDecoder(fileData); - appleFile.extract(); - fosData = new FileOutputStream(file); - AppleFileData forkData = appleFile.getDataFork(); - if (forkData != AppleFileData.EMPTY_FILE_DATA) { - fosData.write(forkData.getBytes()); - } - String resourceFilePath = file.getParent() + File.separator + "%" + file.getName(); - RpcPerforceFile targetResourceFile = new RpcPerforceFile(resourceFilePath, file.getFileType()); - fosResource = new FileOutputStream(targetResourceFile); - AppleFileData forkResource = appleFile.getResourceFork(); - if (forkResource != AppleFileData.EMPTY_FILE_DATA) { - fosResource.write(forkResource.getBytes()); - } - } catch (IOException e) { - Log.error("Problem handling the Apple file: " + file.getName()); - } catch (FileDecoderException e) { - Log.error("Problem decoding the Apple file: " + file.getName()); - } finally { - if (fosData != null) { - try { - fosData.close(); - } catch (Exception e) { - // Do nothing - } - } - if (fosResource != null) { - try { - fosResource.close(); - } catch (Exception e) { - // Do nothing - } - } - } - } - } - - /** - * Gets the bytes from file. - * - * @param file - * the file - * @return the bytes from file - * @throws IOException - * Signals that an I/O exception has occurred. - */ - public static byte[] getBytesFromFile(File file) throws IOException { - InputStream is = new FileInputStream(file); - - long length = file.length(); - if (length > Integer.MAX_VALUE) { - // File is too large - throw new IOException("Apple file too large for decoding."); - } - - byte[] bytes = new byte[(int) length]; - int offset = 0; - int numRead = 0; - - try { - while (offset < bytes.length - && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { - offset += numRead; - } - // Ensure all the bytes have been read in - if (offset < bytes.length) { - throw new IOException( - "Could not completely read the Apple file " - + file.getName()); - } - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - // Do nothing - } - } - } - - return bytes; - } -} +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.impl.mapbased.rpc.sys.helper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; +import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFile; +import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFileType; +import com.perforce.p4java.io.apple.AppleFileData; +import com.perforce.p4java.io.apple.AppleFileDecoder; + +/** + * Helper class for handling Apple files. + */ +public class AppleFileHelper { + + /** + * Extract the data fork and the resource fork from the Apple file. + * + * @param file the Apple file + */ + public static void extractFile(RpcPerforceFile file) { + if (file.getFileType() == RpcPerforceFileType.FST_APPLEFILE) { + FileOutputStream fosData = null; + FileOutputStream fosResource = null; + try { + byte[] data = AppleFileHelper.getBytesFromFile(file); + AppleFileData fileData = new AppleFileData(data); + AppleFileDecoder appleFile = new AppleFileDecoder(fileData); + appleFile.extract(); + fosData = new FileOutputStream(file); + AppleFileData forkData = appleFile.getDataFork(); + if (forkData != AppleFileData.EMPTY_FILE_DATA) { + fosData.write(forkData.getBytes()); + } + String resourceFilePath = file.getParent() + File.separator + "%" + file.getName(); + RpcPerforceFile targetResourceFile = new RpcPerforceFile(resourceFilePath, file.getFileType()); + fosResource = new FileOutputStream(targetResourceFile); + AppleFileData forkResource = appleFile.getResourceFork(); + if (forkResource != AppleFileData.EMPTY_FILE_DATA) { + fosResource.write(forkResource.getBytes()); + } + } catch (IOException e) { + Log.error("Problem handling the Apple file: " + file.getName()); + } catch (FileDecoderException e) { + Log.error("Problem decoding the Apple file: " + file.getName()); + } finally { + if (fosData != null) { + try { + fosData.close(); + } catch (Exception e) { + // Do nothing + } + } + if (fosResource != null) { + try { + fosResource.close(); + } catch (Exception e) { + // Do nothing + } + } + } + } + } + + /** + * Gets the bytes from file. + * + * @param file + * the file + * @return the bytes from file + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public static byte[] getBytesFromFile(File file) throws IOException { + InputStream is = new FileInputStream(file); + + long length = file.length(); + if (length > Integer.MAX_VALUE) { + // File is too large + throw new IOException("Apple file too large for decoding."); + } + + byte[] bytes = new byte[(int) length]; + int offset = 0; + int numRead = 0; + + try { + while (offset < bytes.length + && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { + offset += numRead; + } + // Ensure all the bytes have been read in + if (offset < bytes.length) { + throw new IOException( + "Could not completely read the Apple file " + + file.getName()); + } + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + // Do nothing + } + } + } + + return bytes; + } +} diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFile.java b/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFile.java index 5b89f33a..615bfcc9 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFile.java +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFile.java @@ -1,735 +1,735 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; - -/** - * This abstract class handles AppleSingle/Double files. It contains a common - * method to verify the Apple file, and figure out if it is an AppleSingle or - * AppleDouble formatted file. - *

- * - * The AppleSingle format is a representation of Macintosh files as one - * consecutive stream of bytes. AppleSingle combines the data fork, resource - * fork and the related Finder meta-file information into a single file. - *

- * - * The AppleDouble format stores the data fork, resource fork as two separate - * files. AppleDouble leaves the data fork in its original format, and the - * resource fork and Finder information were combined into a second file. - *

- * - * Apple defined the magic number for the AppleSingle format as 0x00051600, and - * the magic number for the AppleDouble format as 0x00051607. - * - *

- * AppleSingle file header: 
- * 
- * Field Length
- * ----- ------
- * Magic number ------- 4 bytes
- * Version number ------ 4 bytes
- * Filler ------------- 16 bytes
- * Number of entries ----- 2 bytes
- * 
- * Entry descriptor for each entry:
- * Entry ID ------ 4 bytes
- * Offset -------- 4 bytes
- * Length -------- 4 bytes
- * 
- * Apple reserved entry IDs:
- * 
- * Data Fork -------- 1 Data fork
- * Resource Fork ----- 2 Resource fork
- * Real Name -------- 3 File's name as created on home file system
- * Comment --------- 4 Standard Macintosh comment
- * Icon, B&W -------- 5 Standard Macintosh black and white icon
- * Icon, Color -------- 6 Macintosh color icon
- * File Dates Info ------8 File creation date, modification date, and so on
- * Finder Info -------- 9 Standard Macintosh Finder information
- * Macintosh File Info ---10 Macintosh file information, attributes, and so on
- * ProDOS File Info -----11 ProDOS file information, attributes, and so on
- * MS-DOS File Info ----12 MS-DOS file information, attributes, and so on
- * Short Name --------13 AFP short name
- * AFP File Info ------- 14 AFP file information, attributes, and so on
- * Directory ID --------15 AFP directory ID
- * 
- * - * See RFC 1740 for reference: http://tools.ietf.org/html/rfc1740 - */ -public abstract class AppleFile { - - /** The Apple file format: AppleSingle, AppleDouble, default to unknown. */ - protected FileFormat format = FileFormat.UNKNOWN; - - /** The raw Apple file. */ - protected AppleFileData fileData = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 1: Data fork. */ - protected AppleFileData dataFork = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 2: Resource fork. */ - protected AppleFileData resourceFork = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 3: File's name as created on home file system. */ - protected AppleFileData realName = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 4: Standard Macintosh comment. */ - protected AppleFileData comment = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 5: Standard Macintosh black and white icon. */ - protected AppleFileData iconBW = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 6: Macintosh color icon. */ - protected AppleFileData iconColor = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 8: File creation date, modification date, and so on. */ - protected AppleFileData fileDatesInfo = AppleFileData.EMPTY_FILE_DATA; - - /** The file dates info entry. */ - protected FileDatesInfoEntry fileDatesInfoEntry = null; - - /** Entry 9: Standard Macintosh Finder information. */ - protected AppleFileData finderInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 10: Macintosh file information, attributes, and so on. */ - protected AppleFileData macintoshInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 11: ProDOS file information, attributes, and so on. */ - protected AppleFileData proDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 12: MS-DOS file information, attributes, and so on. */ - protected AppleFileData msDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 13: AFP short name. */ - protected AppleFileData shortName = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 14: AFP file information, attributes, and so on. */ - protected AppleFileData afpFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 15: AFP directory ID. */ - protected AppleFileData directoryID = AppleFileData.EMPTY_FILE_DATA; - - /** The num entries. */ - protected int numEntries = 0; - - /** - * The Apple file format. - */ - public enum FileFormat { - - APPLE_SINGLE, - APPLE_DOUBLE, - UNKNOWN; - - /** - * Return a suitable Apple file format as inferred from the passed-in - * string. Otherwise return the UNKNOWN file format. - * - * @param fileFormat - * the file format - * @return the FileFormat - */ - public static FileFormat fromString(String fileFormat) { - if (fileFormat == null) { - return null; - } - - try { - return FileFormat.valueOf(fileFormat.toUpperCase()); - } catch (IllegalArgumentException iae) { - Log.error("Bad conversion attempt in FileFormat.fromString; string: " - + fileFormat + "; message: " + iae.getMessage()); - Log.exception(iae); - return UNKNOWN; - } - } - }; - - /** - * This class represents the file dates. - */ - public class FileDatesInfoEntry { - - /** The create time. */ - private int createTime = Integer.MIN_VALUE; - - /** The modify time. */ - private int modifyTime = Integer.MIN_VALUE; - - /** The backup time. */ - private int backupTime = Integer.MIN_VALUE; - - /** The access time. */ - private int accessTime = Integer.MIN_VALUE; - - /** - * Instantiates a new file dates info entry. - */ - public FileDatesInfoEntry() { - - } - - /** - * Gets the creates the time. - * - * @return the creates the time - */ - public int getCreateTime() { - return createTime; - } - - /** - * Sets the creates the time. - * - * @param createTime - * the new creates the time - */ - public void setCreateTime(int createTime) { - this.createTime = createTime; - } - - /** - * Gets the modify time. - * - * @return the modify time - */ - public int getModifyTime() { - return modifyTime; - } - - /** - * Sets the modify time. - * - * @param modifyTime - * the new modify time - */ - public void setModifyTime(int modifyTime) { - this.modifyTime = modifyTime; - } - - /** - * Gets the backup time. - * - * @return the backup time - */ - public int getBackupTime() { - return backupTime; - } - - /** - * Sets the backup time. - * - * @param backupTime - * the new backup time - */ - public void setBackupTime(int backupTime) { - this.backupTime = backupTime; - } - - /** - * Gets the access time. - * - * @return the access time - */ - public int getAccessTime() { - return accessTime; - } - - /** - * Sets the access time. - * - * @param accessTime - * the new access time - */ - public void setAccessTime(int accessTime) { - this.accessTime = accessTime; - } - } - - /** - * Sets the num entries. - * - * @param numEntries - * the new num entries - */ - public void setNumEntries(int numEntries) { - this.numEntries = numEntries; - } - - /** - * Verify the validity of the Apple file. - * - * @throws FileDecoderException - * the file decoder exception - */ - @SuppressWarnings("unused") - protected void verify() throws FileDecoderException { - byte[] data = this.fileData.getData(); - int offset = this.fileData.getOffset(); - int length = this.fileData.getLength(); - int position = offset; - if (length < 26) { - throw new FileDecoderException("File is too short"); - } - - /* Magic number */ - int magic = 0; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - - /* Check Apple file format: AppleSingle or AppleDobule */ - if (magic == 0x00051600) { - this.format = FileFormat.APPLE_SINGLE; - } else if (magic == 0x00051607) { - this.format = FileFormat.APPLE_DOUBLE; - } else { - throw new FileDecoderException("Invalid Apple file magic number."); - } - - /* Version number */ - int version = 0; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - if (version != 0x00020000) { - throw new FileDecoderException("Unknown Apple file version"); - } - - /* Filler */ - position += 16; - - /* Number of entries */ - this.numEntries = 0; - this.numEntries |= data[(position++)] & 0xFF; - this.numEntries <<= 8; - this.numEntries |= data[(position++)] & 0xFF; - if (length < 26 + 12 * this.numEntries) { - throw new FileDecoderException("Corrupt Apple file data."); - } - - /* Check entries */ - int entryId = 0; - int entryOffset = 0; - int entryLength = 0; - int contentPosition = 26 + 12 * this.numEntries; - for (int i = 0; i < this.numEntries; i++) { - position = 26 + i * 12; - /* Entry ID */ - entryId = 0; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - - /* Entry offset */ - entryOffset = 0; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset &= 0x7FFFFFFF; - - /* Entry length */ - entryLength = 0; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength &= 0x7FFFFFFF; - if ((entryOffset < contentPosition) - || (length < entryOffset + entryLength)) { - throw new FileDecoderException("Corrupt Apple file data."); - } - } - } - - /** - * Extract file dates. - * - * @param data the data - * @param offset the offset - * @param length the length - */ - protected void extractFileDates(byte[] data, int offset, int length) { - if ((0 > offset) || (offset > data.length)) - throw new IndexOutOfBoundsException(); - if ((0 > length) || (length > data.length - offset)) - throw new IndexOutOfBoundsException(); - - int position = offset; - - int createTime = 0; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - int modifyTime = 0; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - int backupTime = 0; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - int accessTime = 0; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - - this.fileDatesInfoEntry = new FileDatesInfoEntry(); - fileDatesInfoEntry.setCreateTime(createTime); - fileDatesInfoEntry.setModifyTime(modifyTime); - fileDatesInfoEntry.setBackupTime(backupTime); - fileDatesInfoEntry.setAccessTime(accessTime); - } - - /** - * Gets the format. - * - * @return the format - */ - public FileFormat getFormat() { - return format; - } - - /** - * Sets the format. - * - * @param format - * the new format - */ - public void setFormat(FileFormat format) { - this.format = format; - } - - /** - * Gets the file data. - * - * @return the file data - */ - public AppleFileData getFileData() { - return fileData; - } - - /** - * Sets the file data. - * - * @param fileData - * the new file data - */ - public void setFileData(AppleFileData fileData) { - this.fileData = fileData; - } - - /** - * Gets the data fork. - * - * @return the data fork - */ - public AppleFileData getDataFork() { - return dataFork; - } - - /** - * Sets the data fork. - * - * @param dataFork - * the new data fork - */ - public void setDataFork(AppleFileData dataFork) { - this.dataFork = dataFork; - } - - /** - * Gets the resource fork. - * - * @return the resource fork - */ - public AppleFileData getResourceFork() { - return resourceFork; - } - - /** - * Sets the resource fork. - * - * @param resourceFork - * the new resource fork - */ - public void setResourceFork(AppleFileData resourceFork) { - this.resourceFork = resourceFork; - } - - /** - * Gets the real name. - * - * @return the real name - */ - public AppleFileData getRealName() { - return realName; - } - - /** - * Sets the real name. - * - * @param realName - * the new real name - */ - public void setRealName(AppleFileData realName) { - this.realName = realName; - } - - /** - * Gets the comment. - * - * @return the comment - */ - public AppleFileData getComment() { - return comment; - } - - /** - * Sets the comment. - * - * @param comment - * the new comment - */ - public void setComment(AppleFileData comment) { - this.comment = comment; - } - - /** - * Gets the icon bw. - * - * @return the icon bw - */ - public AppleFileData getIconBW() { - return iconBW; - } - - /** - * Sets the icon bw. - * - * @param iconBW - * the new icon bw - */ - public void setIconBW(AppleFileData iconBW) { - this.iconBW = iconBW; - } - - /** - * Gets the icon color. - * - * @return the icon color - */ - public AppleFileData getIconColor() { - return iconColor; - } - - /** - * Sets the icon color. - * - * @param iconColor - * the new icon color - */ - public void setIconColor(AppleFileData iconColor) { - this.iconColor = iconColor; - } - - /** - * Gets the file dates info. - * - * @return the file dates info - */ - public AppleFileData getFileDatesInfo() { - return fileDatesInfo; - } - - /** - * Sets the file dates info. - * - * @param fileDatesInfo - * the new file dates info - */ - public void setFileDatesInfo(AppleFileData fileDatesInfo) { - this.fileDatesInfo = fileDatesInfo; - } - - /** - * Gets the finder info. - * - * @return the finder info - */ - public AppleFileData getFinderInfo() { - return finderInfo; - } - - /** - * Sets the finder info. - * - * @param finderInfo - * the new finder info - */ - public void setFinderInfo(AppleFileData finderInfo) { - this.finderInfo = finderInfo; - } - - /** - * Gets the macintosh info. - * - * @return the macintosh info - */ - public AppleFileData getMacintoshInfo() { - return macintoshInfo; - } - - /** - * Sets the macintosh info. - * - * @param macintoshInfo - * the new macintosh info - */ - public void setMacintoshInfo(AppleFileData macintoshInfo) { - this.macintoshInfo = macintoshInfo; - } - - /** - * Gets the pro dos file info. - * - * @return the pro dos file info - */ - public AppleFileData getProDOSFileInfo() { - return proDOSFileInfo; - } - - /** - * Sets the pro dos file info. - * - * @param proDOSFileInfo - * the new pro dos file info - */ - public void setProDOSFileInfo(AppleFileData proDOSFileInfo) { - this.proDOSFileInfo = proDOSFileInfo; - } - - /** - * Gets the ms dos file info. - * - * @return the ms dos file info - */ - public AppleFileData getMsDOSFileInfo() { - return msDOSFileInfo; - } - - /** - * Sets the ms dos file info. - * - * @param msDOSFileInfo - * the new ms dos file info - */ - public void setMsDOSFileInfo(AppleFileData msDOSFileInfo) { - this.msDOSFileInfo = msDOSFileInfo; - } - - /** - * Gets the short name. - * - * @return the short name - */ - public AppleFileData getShortName() { - return shortName; - } - - /** - * Sets the short name. - * - * @param shortName - * the new short name - */ - public void setShortName(AppleFileData shortName) { - this.shortName = shortName; - } - - /** - * Gets the afp file info. - * - * @return the afp file info - */ - public AppleFileData getAfpFileInfo() { - return afpFileInfo; - } - - /** - * Sets the afp file info. - * - * @param afpFileInfo - * the new afp file info - */ - public void setAfpFileInfo(AppleFileData afpFileInfo) { - this.afpFileInfo = afpFileInfo; - } - - /** - * Gets the directory id. - * - * @return the directory id - */ - public AppleFileData getDirectoryID() { - return directoryID; - } - - /** - * Sets the directory id. - * - * @param directoryID - * the new directory id - */ - public void setDirectoryID(AppleFileData directoryID) { - this.directoryID = directoryID; - } - - /** - * Gets the num entries. - * - * @return the num entries - */ - public int getNumEntries() { - return numEntries; - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; + +/** + * This abstract class handles AppleSingle/Double files. It contains a common + * method to verify the Apple file, and figure out if it is an AppleSingle or + * AppleDouble formatted file. + *

+ * + * The AppleSingle format is a representation of Macintosh files as one + * consecutive stream of bytes. AppleSingle combines the data fork, resource + * fork and the related Finder meta-file information into a single file. + *

+ * + * The AppleDouble format stores the data fork, resource fork as two separate + * files. AppleDouble leaves the data fork in its original format, and the + * resource fork and Finder information were combined into a second file. + *

+ * + * Apple defined the magic number for the AppleSingle format as 0x00051600, and + * the magic number for the AppleDouble format as 0x00051607. + * + *

+ * AppleSingle file header: 
+ * 
+ * Field Length
+ * ----- ------
+ * Magic number ------- 4 bytes
+ * Version number ------ 4 bytes
+ * Filler ------------- 16 bytes
+ * Number of entries ----- 2 bytes
+ * 
+ * Entry descriptor for each entry:
+ * Entry ID ------ 4 bytes
+ * Offset -------- 4 bytes
+ * Length -------- 4 bytes
+ * 
+ * Apple reserved entry IDs:
+ * 
+ * Data Fork -------- 1 Data fork
+ * Resource Fork ----- 2 Resource fork
+ * Real Name -------- 3 File's name as created on home file system
+ * Comment --------- 4 Standard Macintosh comment
+ * Icon, B&W -------- 5 Standard Macintosh black and white icon
+ * Icon, Color -------- 6 Macintosh color icon
+ * File Dates Info ------8 File creation date, modification date, and so on
+ * Finder Info -------- 9 Standard Macintosh Finder information
+ * Macintosh File Info ---10 Macintosh file information, attributes, and so on
+ * ProDOS File Info -----11 ProDOS file information, attributes, and so on
+ * MS-DOS File Info ----12 MS-DOS file information, attributes, and so on
+ * Short Name --------13 AFP short name
+ * AFP File Info ------- 14 AFP file information, attributes, and so on
+ * Directory ID --------15 AFP directory ID
+ * 
+ * + * See RFC 1740 for reference: http://tools.ietf.org/html/rfc1740 + */ +public abstract class AppleFile { + + /** The Apple file format: AppleSingle, AppleDouble, default to unknown. */ + protected FileFormat format = FileFormat.UNKNOWN; + + /** The raw Apple file. */ + protected AppleFileData fileData = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 1: Data fork. */ + protected AppleFileData dataFork = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 2: Resource fork. */ + protected AppleFileData resourceFork = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 3: File's name as created on home file system. */ + protected AppleFileData realName = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 4: Standard Macintosh comment. */ + protected AppleFileData comment = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 5: Standard Macintosh black and white icon. */ + protected AppleFileData iconBW = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 6: Macintosh color icon. */ + protected AppleFileData iconColor = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 8: File creation date, modification date, and so on. */ + protected AppleFileData fileDatesInfo = AppleFileData.EMPTY_FILE_DATA; + + /** The file dates info entry. */ + protected FileDatesInfoEntry fileDatesInfoEntry = null; + + /** Entry 9: Standard Macintosh Finder information. */ + protected AppleFileData finderInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 10: Macintosh file information, attributes, and so on. */ + protected AppleFileData macintoshInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 11: ProDOS file information, attributes, and so on. */ + protected AppleFileData proDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 12: MS-DOS file information, attributes, and so on. */ + protected AppleFileData msDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 13: AFP short name. */ + protected AppleFileData shortName = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 14: AFP file information, attributes, and so on. */ + protected AppleFileData afpFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 15: AFP directory ID. */ + protected AppleFileData directoryID = AppleFileData.EMPTY_FILE_DATA; + + /** The num entries. */ + protected int numEntries = 0; + + /** + * The Apple file format. + */ + public enum FileFormat { + + APPLE_SINGLE, + APPLE_DOUBLE, + UNKNOWN; + + /** + * Return a suitable Apple file format as inferred from the passed-in + * string. Otherwise return the UNKNOWN file format. + * + * @param fileFormat + * the file format + * @return the FileFormat + */ + public static FileFormat fromString(String fileFormat) { + if (fileFormat == null) { + return null; + } + + try { + return FileFormat.valueOf(fileFormat.toUpperCase()); + } catch (IllegalArgumentException iae) { + Log.error("Bad conversion attempt in FileFormat.fromString; string: " + + fileFormat + "; message: " + iae.getMessage()); + Log.exception(iae); + return UNKNOWN; + } + } + }; + + /** + * This class represents the file dates. + */ + public class FileDatesInfoEntry { + + /** The create time. */ + private int createTime = Integer.MIN_VALUE; + + /** The modify time. */ + private int modifyTime = Integer.MIN_VALUE; + + /** The backup time. */ + private int backupTime = Integer.MIN_VALUE; + + /** The access time. */ + private int accessTime = Integer.MIN_VALUE; + + /** + * Instantiates a new file dates info entry. + */ + public FileDatesInfoEntry() { + + } + + /** + * Gets the creates the time. + * + * @return the creates the time + */ + public int getCreateTime() { + return createTime; + } + + /** + * Sets the creates the time. + * + * @param createTime + * the new creates the time + */ + public void setCreateTime(int createTime) { + this.createTime = createTime; + } + + /** + * Gets the modify time. + * + * @return the modify time + */ + public int getModifyTime() { + return modifyTime; + } + + /** + * Sets the modify time. + * + * @param modifyTime + * the new modify time + */ + public void setModifyTime(int modifyTime) { + this.modifyTime = modifyTime; + } + + /** + * Gets the backup time. + * + * @return the backup time + */ + public int getBackupTime() { + return backupTime; + } + + /** + * Sets the backup time. + * + * @param backupTime + * the new backup time + */ + public void setBackupTime(int backupTime) { + this.backupTime = backupTime; + } + + /** + * Gets the access time. + * + * @return the access time + */ + public int getAccessTime() { + return accessTime; + } + + /** + * Sets the access time. + * + * @param accessTime + * the new access time + */ + public void setAccessTime(int accessTime) { + this.accessTime = accessTime; + } + } + + /** + * Sets the num entries. + * + * @param numEntries + * the new num entries + */ + public void setNumEntries(int numEntries) { + this.numEntries = numEntries; + } + + /** + * Verify the validity of the Apple file. + * + * @throws FileDecoderException + * the file decoder exception + */ + @SuppressWarnings("unused") + protected void verify() throws FileDecoderException { + byte[] data = this.fileData.getData(); + int offset = this.fileData.getOffset(); + int length = this.fileData.getLength(); + int position = offset; + if (length < 26) { + throw new FileDecoderException("File is too short"); + } + + /* Magic number */ + int magic = 0; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + + /* Check Apple file format: AppleSingle or AppleDobule */ + if (magic == 0x00051600) { + this.format = FileFormat.APPLE_SINGLE; + } else if (magic == 0x00051607) { + this.format = FileFormat.APPLE_DOUBLE; + } else { + throw new FileDecoderException("Invalid Apple file magic number."); + } + + /* Version number */ + int version = 0; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + if (version != 0x00020000) { + throw new FileDecoderException("Unknown Apple file version"); + } + + /* Filler */ + position += 16; + + /* Number of entries */ + this.numEntries = 0; + this.numEntries |= data[(position++)] & 0xFF; + this.numEntries <<= 8; + this.numEntries |= data[(position++)] & 0xFF; + if (length < 26 + 12 * this.numEntries) { + throw new FileDecoderException("Corrupt Apple file data."); + } + + /* Check entries */ + int entryId = 0; + int entryOffset = 0; + int entryLength = 0; + int contentPosition = 26 + 12 * this.numEntries; + for (int i = 0; i < this.numEntries; i++) { + position = 26 + i * 12; + /* Entry ID */ + entryId = 0; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + + /* Entry offset */ + entryOffset = 0; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset &= 0x7FFFFFFF; + + /* Entry length */ + entryLength = 0; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength &= 0x7FFFFFFF; + if ((entryOffset < contentPosition) + || (length < entryOffset + entryLength)) { + throw new FileDecoderException("Corrupt Apple file data."); + } + } + } + + /** + * Extract file dates. + * + * @param data the data + * @param offset the offset + * @param length the length + */ + protected void extractFileDates(byte[] data, int offset, int length) { + if ((0 > offset) || (offset > data.length)) + throw new IndexOutOfBoundsException(); + if ((0 > length) || (length > data.length - offset)) + throw new IndexOutOfBoundsException(); + + int position = offset; + + int createTime = 0; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + int modifyTime = 0; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + int backupTime = 0; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + int accessTime = 0; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + + this.fileDatesInfoEntry = new FileDatesInfoEntry(); + fileDatesInfoEntry.setCreateTime(createTime); + fileDatesInfoEntry.setModifyTime(modifyTime); + fileDatesInfoEntry.setBackupTime(backupTime); + fileDatesInfoEntry.setAccessTime(accessTime); + } + + /** + * Gets the format. + * + * @return the format + */ + public FileFormat getFormat() { + return format; + } + + /** + * Sets the format. + * + * @param format + * the new format + */ + public void setFormat(FileFormat format) { + this.format = format; + } + + /** + * Gets the file data. + * + * @return the file data + */ + public AppleFileData getFileData() { + return fileData; + } + + /** + * Sets the file data. + * + * @param fileData + * the new file data + */ + public void setFileData(AppleFileData fileData) { + this.fileData = fileData; + } + + /** + * Gets the data fork. + * + * @return the data fork + */ + public AppleFileData getDataFork() { + return dataFork; + } + + /** + * Sets the data fork. + * + * @param dataFork + * the new data fork + */ + public void setDataFork(AppleFileData dataFork) { + this.dataFork = dataFork; + } + + /** + * Gets the resource fork. + * + * @return the resource fork + */ + public AppleFileData getResourceFork() { + return resourceFork; + } + + /** + * Sets the resource fork. + * + * @param resourceFork + * the new resource fork + */ + public void setResourceFork(AppleFileData resourceFork) { + this.resourceFork = resourceFork; + } + + /** + * Gets the real name. + * + * @return the real name + */ + public AppleFileData getRealName() { + return realName; + } + + /** + * Sets the real name. + * + * @param realName + * the new real name + */ + public void setRealName(AppleFileData realName) { + this.realName = realName; + } + + /** + * Gets the comment. + * + * @return the comment + */ + public AppleFileData getComment() { + return comment; + } + + /** + * Sets the comment. + * + * @param comment + * the new comment + */ + public void setComment(AppleFileData comment) { + this.comment = comment; + } + + /** + * Gets the icon bw. + * + * @return the icon bw + */ + public AppleFileData getIconBW() { + return iconBW; + } + + /** + * Sets the icon bw. + * + * @param iconBW + * the new icon bw + */ + public void setIconBW(AppleFileData iconBW) { + this.iconBW = iconBW; + } + + /** + * Gets the icon color. + * + * @return the icon color + */ + public AppleFileData getIconColor() { + return iconColor; + } + + /** + * Sets the icon color. + * + * @param iconColor + * the new icon color + */ + public void setIconColor(AppleFileData iconColor) { + this.iconColor = iconColor; + } + + /** + * Gets the file dates info. + * + * @return the file dates info + */ + public AppleFileData getFileDatesInfo() { + return fileDatesInfo; + } + + /** + * Sets the file dates info. + * + * @param fileDatesInfo + * the new file dates info + */ + public void setFileDatesInfo(AppleFileData fileDatesInfo) { + this.fileDatesInfo = fileDatesInfo; + } + + /** + * Gets the finder info. + * + * @return the finder info + */ + public AppleFileData getFinderInfo() { + return finderInfo; + } + + /** + * Sets the finder info. + * + * @param finderInfo + * the new finder info + */ + public void setFinderInfo(AppleFileData finderInfo) { + this.finderInfo = finderInfo; + } + + /** + * Gets the macintosh info. + * + * @return the macintosh info + */ + public AppleFileData getMacintoshInfo() { + return macintoshInfo; + } + + /** + * Sets the macintosh info. + * + * @param macintoshInfo + * the new macintosh info + */ + public void setMacintoshInfo(AppleFileData macintoshInfo) { + this.macintoshInfo = macintoshInfo; + } + + /** + * Gets the pro dos file info. + * + * @return the pro dos file info + */ + public AppleFileData getProDOSFileInfo() { + return proDOSFileInfo; + } + + /** + * Sets the pro dos file info. + * + * @param proDOSFileInfo + * the new pro dos file info + */ + public void setProDOSFileInfo(AppleFileData proDOSFileInfo) { + this.proDOSFileInfo = proDOSFileInfo; + } + + /** + * Gets the ms dos file info. + * + * @return the ms dos file info + */ + public AppleFileData getMsDOSFileInfo() { + return msDOSFileInfo; + } + + /** + * Sets the ms dos file info. + * + * @param msDOSFileInfo + * the new ms dos file info + */ + public void setMsDOSFileInfo(AppleFileData msDOSFileInfo) { + this.msDOSFileInfo = msDOSFileInfo; + } + + /** + * Gets the short name. + * + * @return the short name + */ + public AppleFileData getShortName() { + return shortName; + } + + /** + * Sets the short name. + * + * @param shortName + * the new short name + */ + public void setShortName(AppleFileData shortName) { + this.shortName = shortName; + } + + /** + * Gets the afp file info. + * + * @return the afp file info + */ + public AppleFileData getAfpFileInfo() { + return afpFileInfo; + } + + /** + * Sets the afp file info. + * + * @param afpFileInfo + * the new afp file info + */ + public void setAfpFileInfo(AppleFileData afpFileInfo) { + this.afpFileInfo = afpFileInfo; + } + + /** + * Gets the directory id. + * + * @return the directory id + */ + public AppleFileData getDirectoryID() { + return directoryID; + } + + /** + * Sets the directory id. + * + * @param directoryID + * the new directory id + */ + public void setDirectoryID(AppleFileData directoryID) { + this.directoryID = directoryID; + } + + /** + * Gets the num entries. + * + * @return the num entries + */ + public int getNumEntries() { + return numEntries; + } } \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java b/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java index f28c979a..d89471e2 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java @@ -1,91 +1,91 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -/** - * This class is for representing the AppleSingle/Double file or its file forks - * (data fork and resource fork) and the related Finder meta-file information. - */ -public final class AppleFileData { - - public static final AppleFileData EMPTY_FILE_DATA = new AppleFileData(); - private byte[] data; - private int offset; - private int length; - - /** - * Instantiates a new apple file data. - */ - public AppleFileData() { - this.data = new byte[0]; - this.offset = 0; - this.length = 0; - } - - /** - * Instantiates a new apple file data. - * - * @param data the data - */ - public AppleFileData(byte[] data) { - this.data = data; - this.offset = 0; - this.length = data.length; - } - - /** - * Instantiates a new apple file data. - * - * @param data the data - * @param offset the offset - * @param length the length - */ - public AppleFileData(byte[] data, int offset, int length) { - if ((0 > offset) || (offset > data.length)) - throw new IndexOutOfBoundsException(); - if ((0 > length) || (length > data.length - offset)) - throw new IndexOutOfBoundsException(); - this.data = data; - this.offset = offset; - this.length = length; - } - - /** - * Gets the bytes. - * - * @return the bytes - */ - public byte[] getBytes() { - byte[] data = new byte[this.length]; - System.arraycopy(this.data, this.offset, data, 0, this.length); - return data; - } - - /** - * Gets the data. - * - * @return the data - */ - public byte[] getData() { - return this.data; - } - - /** - * Gets the offset. - * - * @return the offset - */ - public int getOffset() { - return this.offset; - } - - /** - * Gets the length. - * - * @return the length - */ - public int getLength() { - return this.length; - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +/** + * This class is for representing the AppleSingle/Double file or its file forks + * (data fork and resource fork) and the related Finder meta-file information. + */ +public final class AppleFileData { + + public static final AppleFileData EMPTY_FILE_DATA = new AppleFileData(); + private byte[] data; + private int offset; + private int length; + + /** + * Instantiates a new apple file data. + */ + public AppleFileData() { + this.data = new byte[0]; + this.offset = 0; + this.length = 0; + } + + /** + * Instantiates a new apple file data. + * + * @param data the data + */ + public AppleFileData(byte[] data) { + this.data = data; + this.offset = 0; + this.length = data.length; + } + + /** + * Instantiates a new apple file data. + * + * @param data the data + * @param offset the offset + * @param length the length + */ + public AppleFileData(byte[] data, int offset, int length) { + if ((0 > offset) || (offset > data.length)) + throw new IndexOutOfBoundsException(); + if ((0 > length) || (length > data.length - offset)) + throw new IndexOutOfBoundsException(); + this.data = data; + this.offset = offset; + this.length = length; + } + + /** + * Gets the bytes. + * + * @return the bytes + */ + public byte[] getBytes() { + byte[] data = new byte[this.length]; + System.arraycopy(this.data, this.offset, data, 0, this.length); + return data; + } + + /** + * Gets the data. + * + * @return the data + */ + public byte[] getData() { + return this.data; + } + + /** + * Gets the offset. + * + * @return the offset + */ + public int getOffset() { + return this.offset; + } + + /** + * Gets the length. + * + * @return the length + */ + public int getLength() { + return this.length; + } } \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java b/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java index 3248f85d..aa7b0363 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java @@ -1,143 +1,143 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; - -/** - * This class handles the extraction of the data fork, resource fork and other - * entries from an AppleSingle/Double file. The Perforce 'apple' file type is a - * compressed AppleSingle (Mac resource + data) file. The Perforce 'resource' - * file type is a compressed AppleDouble (Mac resource fork) file. - */ -public class AppleFileDecoder extends AppleFile { - - /** - * Instantiates a new apple file decoder. - * - * @param fileData - * the file data - */ - public AppleFileDecoder(AppleFileData fileData) { - if (fileData != null) { - this.fileData = fileData; - } - } - - /** - * Extract the data fork, resource fork and other entries from the Apple - * file. - * - * @throws FileDecoderException - * the file decoder exception - */ - @SuppressWarnings("unused") - public void extract() throws FileDecoderException { - // Verify the validity of the Apple file - verify(); - - byte[] data = this.fileData.getData(); - int offset = this.fileData.getOffset(); - int length = this.fileData.getLength(); - int contentPosition = 0; - int entryId = 0; - int entryOffset = 0; - int entryLength = 0; - for (int i = 0; i < this.numEntries; i++) { - contentPosition = offset + 26 + i * 12; - /* Entry ID */ - entryId = 0; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - - /* Entry offset */ - entryOffset = 0; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset &= 0x7FFFFFFF; - - /* Entry length */ - entryLength = 0; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength &= 0x7FFFFFFF; - - switch (entryId) { - case 1: - this.dataFork = new AppleFileData(data, offset + entryOffset, - entryLength); - break; - case 2: - this.resourceFork = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 3: - this.realName = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 4: - this.comment = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 5: - this.iconBW = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 6: - this.iconColor = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 8: - this.fileDatesInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - extractFileDates(data, offset + entryOffset, entryLength); - break; - case 9: - this.finderInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 10: - this.macintoshInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 11: - this.proDOSFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 12: - this.msDOSFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 13: - this.shortName = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 14: - this.afpFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 15: - this.directoryID = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - default: - Log.warn("Apple file entry ID: " + entryId + " is not handled."); - - } - } - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; + +/** + * This class handles the extraction of the data fork, resource fork and other + * entries from an AppleSingle/Double file. The Perforce 'apple' file type is a + * compressed AppleSingle (Mac resource + data) file. The Perforce 'resource' + * file type is a compressed AppleDouble (Mac resource fork) file. + */ +public class AppleFileDecoder extends AppleFile { + + /** + * Instantiates a new apple file decoder. + * + * @param fileData + * the file data + */ + public AppleFileDecoder(AppleFileData fileData) { + if (fileData != null) { + this.fileData = fileData; + } + } + + /** + * Extract the data fork, resource fork and other entries from the Apple + * file. + * + * @throws FileDecoderException + * the file decoder exception + */ + @SuppressWarnings("unused") + public void extract() throws FileDecoderException { + // Verify the validity of the Apple file + verify(); + + byte[] data = this.fileData.getData(); + int offset = this.fileData.getOffset(); + int length = this.fileData.getLength(); + int contentPosition = 0; + int entryId = 0; + int entryOffset = 0; + int entryLength = 0; + for (int i = 0; i < this.numEntries; i++) { + contentPosition = offset + 26 + i * 12; + /* Entry ID */ + entryId = 0; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + + /* Entry offset */ + entryOffset = 0; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset &= 0x7FFFFFFF; + + /* Entry length */ + entryLength = 0; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength &= 0x7FFFFFFF; + + switch (entryId) { + case 1: + this.dataFork = new AppleFileData(data, offset + entryOffset, + entryLength); + break; + case 2: + this.resourceFork = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 3: + this.realName = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 4: + this.comment = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 5: + this.iconBW = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 6: + this.iconColor = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 8: + this.fileDatesInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + extractFileDates(data, offset + entryOffset, entryLength); + break; + case 9: + this.finderInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 10: + this.macintoshInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 11: + this.proDOSFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 12: + this.msDOSFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 13: + this.shortName = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 14: + this.afpFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 15: + this.directoryID = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + default: + Log.warn("Apple file entry ID: " + entryId + " is not handled."); + + } + } + } } \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java b/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java index 7c5d0a04..abdfed59 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java @@ -1,300 +1,300 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.exception.FileEncoderException; - -/** - * This class handles the combination of the data fork, resource fork and other - * entries into an AppleSingle/Double file. - *

- * - * Note that if it is an AppleDouble, the data fork is a separate file external - * to this file. - */ -public class AppleFileEncoder extends AppleFile { - - /** - * Instantiates a new apple file decoder. - * - * @throws FileEncoderException - */ - public AppleFileEncoder(FileFormat fileFormat) throws FileEncoderException { - if (fileFormat == null) { - throw new FileEncoderException("Null file format passed to the AppleFileEncoder constructor."); - } - if (fileFormat == FileFormat.UNKNOWN) { - throw new FileEncoderException("Unknown file format passed to the AppleFileEncoder constructor."); - } - } - - /** - * Combine the data fork, resource fork and other entries into an - * AppleSingle/Double file. - * - * @throws FileEncoderException - * the file encoder exception - */ - @SuppressWarnings("unused") - public void combine() throws FileEncoderException { - - boolean isAppleSingle = (this.format == FileFormat.APPLE_SINGLE); - boolean isAppleDouble = (this.format == FileFormat.APPLE_DOUBLE); - - boolean hasDataFork = (this.dataFork != AppleFileData.EMPTY_FILE_DATA); - boolean hasResourceFork = (this.resourceFork != AppleFileData.EMPTY_FILE_DATA); - boolean hasRealName = (this.realName != AppleFileData.EMPTY_FILE_DATA); - boolean hasComment = (this.comment != AppleFileData.EMPTY_FILE_DATA); - boolean hasIconBW = (this.iconBW != AppleFileData.EMPTY_FILE_DATA); - boolean hasIconColor = (this.iconColor != AppleFileData.EMPTY_FILE_DATA); - boolean hasFileDatesInfo = (this.fileDatesInfoEntry != null); - boolean hasFinderInfo = (this.finderInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasMacintoshInfo = (this.macintoshInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasProDOSFileInfo = (this.proDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasMsDOSFileInfo = (this.msDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasShortName = (this.shortName != AppleFileData.EMPTY_FILE_DATA); - boolean hasAfpFileInfo = (this.afpFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasDirectoryID = (this.directoryID != AppleFileData.EMPTY_FILE_DATA); - - this.fileData = AppleFileData.EMPTY_FILE_DATA; - - int length = 90 + this.realName.getLength() - + this.resourceFork.getLength(); - - /* AppleSingle includes the data fork */ - if (isAppleSingle) { - length += this.dataFork.getLength(); - } - - byte[] data = new byte[length]; - int position = 0; - - /* Magic number for AppleSingle or AppleDouble */ - if (isAppleDouble) { - data[(position++)] = 0; - data[(position++)] = 5; - data[(position++)] = 22; - data[(position++)] = 0; - } else { - data[(position++)] = 0; - data[(position++)] = 5; - data[(position++)] = 22; - data[(position++)] = 7; - } - - /* Version number */ - data[(position++)] = 0; - data[(position++)] = 2; - data[(position++)] = 0; - data[(position++)] = 0; - - /* Filler */ - - for (int k = 0; k < 16; k++) { - data[(position++)] = 0; - } - - /* Number of entries */ - this.numEntries = 0; - if (hasRealName) { - this.numEntries += 1; - } - if (hasFileDatesInfo) { - this.numEntries += 1; - } - if (hasResourceFork) { - this.numEntries += 1; - } - if ((hasDataFork) && (isAppleSingle)) { - this.numEntries += 1; - } - data[(position++)] = ((byte) (this.numEntries >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.numEntries & 0xFF)); - - /* Header information for the entries */ - - /* Real name entry header */ - int realNamePosition = 0; - if (hasRealName) { - int realNameEntryId = 3; - int realNameEntryOffset = 0; - int realNameEntryLength = this.realName.getLength(); - data[(position++)] = ((byte) (realNameEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 0 & 0xFF)); - realNamePosition = position; - data[(position++)] = ((byte) (realNameEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 0 & 0xFF)); - } - - /* File dates info entry header */ - int fileDatesInfoPosition = 0; - if (hasFileDatesInfo) { - int fileDatesInfoEntryId = 8; - int fileDatesInfoEntryOffset = 0; - int fileDatesInfoEntryLength = 16; - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 0 & 0xFF)); - fileDatesInfoPosition = position; - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 0 & 0xFF)); - } - - /* Resource fork entry header */ - int resourceForkPosition = 0; - if (hasResourceFork) { - int resourceForkEntryId = 2; - int resourceForkEntryOffset = 0; - int resourceFokrEntryLength = this.resourceFork.getLength(); - data[(position++)] = ((byte) (resourceForkEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 0 & 0xFF)); - resourceForkPosition = position; - data[(position++)] = ((byte) (resourceForkEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 0 & 0xFF)); - } - - /* Data fork entry header */ - int dataForkPosition = 0; - if ((hasDataFork) && (isAppleSingle)) { - int dataForkEntryId = 1; - int dataForkEntryOffset = 0; - int dataForkEntryLength = this.dataFork.getLength(); - data[(position++)] = ((byte) (dataForkEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 0 & 0xFF)); - dataForkPosition = position; - data[(position++)] = ((byte) (dataForkEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 0 & 0xFF)); - } - - /* Content for the entries */ - - /* Real name content */ - if (hasRealName) { - int realNamePositionCurrent = position; - position = realNamePosition; - data[(position++)] = ((byte) (realNamePositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 0 & 0xFF)); - position = realNamePositionCurrent; - byte[] realNameData = this.realName.getData(); - int realNameOffset = this.realName.getOffset(); - int realNameLength = this.realName.getLength(); - System.arraycopy(realNameData, realNameOffset, data, position, - realNameLength); - position += realNameLength; - } - - /* File dates info content */ - if (hasFileDatesInfo) { - int fileDatesInfoPositionCurrent = position; - position = fileDatesInfoPosition; - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 0 & 0xFF)); - position = fileDatesInfoPositionCurrent; - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 0 & 0xFF)); - } - - /* Resource fork content */ - if (hasResourceFork) { - int resourceForkPositionCurrent = position; - position = resourceForkPosition; - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 0 & 0xFF)); - position = resourceForkPositionCurrent; - byte[] resourceForkData = this.resourceFork.getData(); - int resourceForkOffset = this.resourceFork.getOffset(); - int resourceForkLength = this.resourceFork.getLength(); - System.arraycopy(resourceForkData, resourceForkOffset, data, - position, resourceForkLength); - position += resourceForkLength; - } - - /* Data fork content */ - if ((hasDataFork) && (isAppleSingle)) { - int dataForkPosition2 = position; - position = dataForkPosition; - data[(position++)] = ((byte) (dataForkPosition2 >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 0 & 0xFF)); - position = dataForkPosition2; - byte[] dataForkData = this.dataFork.getData(); - int dataForkOffset = this.dataFork.getOffset(); - int dataForkLength = this.dataFork.getLength(); - System.arraycopy(dataForkData, dataForkOffset, data, position, - dataForkLength); - position += dataForkLength; - } - - /* Create the Apple file data */ - this.fileData = new AppleFileData(data, 0, position); - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.exception.FileEncoderException; + +/** + * This class handles the combination of the data fork, resource fork and other + * entries into an AppleSingle/Double file. + *

+ * + * Note that if it is an AppleDouble, the data fork is a separate file external + * to this file. + */ +public class AppleFileEncoder extends AppleFile { + + /** + * Instantiates a new apple file decoder. + * + * @throws FileEncoderException + */ + public AppleFileEncoder(FileFormat fileFormat) throws FileEncoderException { + if (fileFormat == null) { + throw new FileEncoderException("Null file format passed to the AppleFileEncoder constructor."); + } + if (fileFormat == FileFormat.UNKNOWN) { + throw new FileEncoderException("Unknown file format passed to the AppleFileEncoder constructor."); + } + } + + /** + * Combine the data fork, resource fork and other entries into an + * AppleSingle/Double file. + * + * @throws FileEncoderException + * the file encoder exception + */ + @SuppressWarnings("unused") + public void combine() throws FileEncoderException { + + boolean isAppleSingle = (this.format == FileFormat.APPLE_SINGLE); + boolean isAppleDouble = (this.format == FileFormat.APPLE_DOUBLE); + + boolean hasDataFork = (this.dataFork != AppleFileData.EMPTY_FILE_DATA); + boolean hasResourceFork = (this.resourceFork != AppleFileData.EMPTY_FILE_DATA); + boolean hasRealName = (this.realName != AppleFileData.EMPTY_FILE_DATA); + boolean hasComment = (this.comment != AppleFileData.EMPTY_FILE_DATA); + boolean hasIconBW = (this.iconBW != AppleFileData.EMPTY_FILE_DATA); + boolean hasIconColor = (this.iconColor != AppleFileData.EMPTY_FILE_DATA); + boolean hasFileDatesInfo = (this.fileDatesInfoEntry != null); + boolean hasFinderInfo = (this.finderInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasMacintoshInfo = (this.macintoshInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasProDOSFileInfo = (this.proDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasMsDOSFileInfo = (this.msDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasShortName = (this.shortName != AppleFileData.EMPTY_FILE_DATA); + boolean hasAfpFileInfo = (this.afpFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasDirectoryID = (this.directoryID != AppleFileData.EMPTY_FILE_DATA); + + this.fileData = AppleFileData.EMPTY_FILE_DATA; + + int length = 90 + this.realName.getLength() + + this.resourceFork.getLength(); + + /* AppleSingle includes the data fork */ + if (isAppleSingle) { + length += this.dataFork.getLength(); + } + + byte[] data = new byte[length]; + int position = 0; + + /* Magic number for AppleSingle or AppleDouble */ + if (isAppleDouble) { + data[(position++)] = 0; + data[(position++)] = 5; + data[(position++)] = 22; + data[(position++)] = 0; + } else { + data[(position++)] = 0; + data[(position++)] = 5; + data[(position++)] = 22; + data[(position++)] = 7; + } + + /* Version number */ + data[(position++)] = 0; + data[(position++)] = 2; + data[(position++)] = 0; + data[(position++)] = 0; + + /* Filler */ + + for (int k = 0; k < 16; k++) { + data[(position++)] = 0; + } + + /* Number of entries */ + this.numEntries = 0; + if (hasRealName) { + this.numEntries += 1; + } + if (hasFileDatesInfo) { + this.numEntries += 1; + } + if (hasResourceFork) { + this.numEntries += 1; + } + if ((hasDataFork) && (isAppleSingle)) { + this.numEntries += 1; + } + data[(position++)] = ((byte) (this.numEntries >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.numEntries & 0xFF)); + + /* Header information for the entries */ + + /* Real name entry header */ + int realNamePosition = 0; + if (hasRealName) { + int realNameEntryId = 3; + int realNameEntryOffset = 0; + int realNameEntryLength = this.realName.getLength(); + data[(position++)] = ((byte) (realNameEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 0 & 0xFF)); + realNamePosition = position; + data[(position++)] = ((byte) (realNameEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 0 & 0xFF)); + } + + /* File dates info entry header */ + int fileDatesInfoPosition = 0; + if (hasFileDatesInfo) { + int fileDatesInfoEntryId = 8; + int fileDatesInfoEntryOffset = 0; + int fileDatesInfoEntryLength = 16; + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 0 & 0xFF)); + fileDatesInfoPosition = position; + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 0 & 0xFF)); + } + + /* Resource fork entry header */ + int resourceForkPosition = 0; + if (hasResourceFork) { + int resourceForkEntryId = 2; + int resourceForkEntryOffset = 0; + int resourceFokrEntryLength = this.resourceFork.getLength(); + data[(position++)] = ((byte) (resourceForkEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 0 & 0xFF)); + resourceForkPosition = position; + data[(position++)] = ((byte) (resourceForkEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 0 & 0xFF)); + } + + /* Data fork entry header */ + int dataForkPosition = 0; + if ((hasDataFork) && (isAppleSingle)) { + int dataForkEntryId = 1; + int dataForkEntryOffset = 0; + int dataForkEntryLength = this.dataFork.getLength(); + data[(position++)] = ((byte) (dataForkEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 0 & 0xFF)); + dataForkPosition = position; + data[(position++)] = ((byte) (dataForkEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 0 & 0xFF)); + } + + /* Content for the entries */ + + /* Real name content */ + if (hasRealName) { + int realNamePositionCurrent = position; + position = realNamePosition; + data[(position++)] = ((byte) (realNamePositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 0 & 0xFF)); + position = realNamePositionCurrent; + byte[] realNameData = this.realName.getData(); + int realNameOffset = this.realName.getOffset(); + int realNameLength = this.realName.getLength(); + System.arraycopy(realNameData, realNameOffset, data, position, + realNameLength); + position += realNameLength; + } + + /* File dates info content */ + if (hasFileDatesInfo) { + int fileDatesInfoPositionCurrent = position; + position = fileDatesInfoPosition; + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 0 & 0xFF)); + position = fileDatesInfoPositionCurrent; + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 0 & 0xFF)); + } + + /* Resource fork content */ + if (hasResourceFork) { + int resourceForkPositionCurrent = position; + position = resourceForkPosition; + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 0 & 0xFF)); + position = resourceForkPositionCurrent; + byte[] resourceForkData = this.resourceFork.getData(); + int resourceForkOffset = this.resourceFork.getOffset(); + int resourceForkLength = this.resourceFork.getLength(); + System.arraycopy(resourceForkData, resourceForkOffset, data, + position, resourceForkLength); + position += resourceForkLength; + } + + /* Data fork content */ + if ((hasDataFork) && (isAppleSingle)) { + int dataForkPosition2 = position; + position = dataForkPosition; + data[(position++)] = ((byte) (dataForkPosition2 >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 0 & 0xFF)); + position = dataForkPosition2; + byte[] dataForkData = this.dataFork.getData(); + int dataForkOffset = this.dataFork.getOffset(); + int dataForkLength = this.dataFork.getLength(); + System.arraycopy(dataForkData, dataForkOffset, data, position, + dataForkLength); + position += dataForkLength; + } + + /* Create the Apple file data */ + this.fileData = new AppleFileData(data, 0, position); + } } \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/option/package.html b/p4java/r19-1/src/main/java/com/perforce/p4java/option/package.html index c886337e..420c78dc 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/option/package.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/option/package.html @@ -1,14 +1,14 @@ - - - - - -Provides a series of classes that implement P4Java-wide server, client, and other class method -options definitions and processing. -

-Package com.perforce.p4java.options and its changelist, client, and server sub-packages were introduced -with the 2010.1 release to make it easier for users and developers to ensure forwards and backwards -binary compatibility. Please see the Perforce knowledge base article linked to here (TBD) for the -full semantics and usage details. - + + + + + +Provides a series of classes that implement P4Java-wide server, client, and other class method +options definitions and processing. +

+Package com.perforce.p4java.options and its changelist, client, and server sub-packages were introduced +with the 2010.1 release to make it easier for users and developers to ensure forwards and backwards +binary compatibility. Please see the Perforce knowledge base article linked to here (TBD) for the +full semantics and usage details. + \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/overview.html b/p4java/r19-1/src/main/java/com/perforce/p4java/overview.html index 55c0eefd..c687ca12 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/overview.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/overview.html @@ -1,34 +1,34 @@ - - - - - -Perforce's P4Java is a Java-native API for accessing Perforce's SCM services in a Java-natural -and Java-native way. -

-P4Java presents Perforce services and Perforce-managed resources and files as first-class Java interfaces, -classes, methods, and objects, rather than as simple strings or command-line-like functions. This approach -reduces the impedance mismatch between Java and Perforce, and makes it easier to integrate the API into -Java applications and tools; it is particularly useful for integrating into model-view-controller (MVC) -contexts and workflows. -

-P4Java is aimed mostly at the following types of Java development: -

-P4Java is aimed at JDK 6 and later environments, but will work with some limitations against a JDK 5 installation -(see the related P4Java User Guide for details).. -

Related Documentation

- - + + + + + +Perforce's P4Java is a Java-native API for accessing Perforce's SCM services in a Java-natural +and Java-native way. +

+P4Java presents Perforce services and Perforce-managed resources and files as first-class Java interfaces, +classes, methods, and objects, rather than as simple strings or command-line-like functions. This approach +reduces the impedance mismatch between Java and Perforce, and makes it easier to integrate the API into +Java applications and tools; it is particularly useful for integrating into model-view-controller (MVC) +contexts and workflows. +

+P4Java is aimed mostly at the following types of Java development: +

+P4Java is aimed at JDK 6 and later environments, but will work with some limitations against a JDK 5 installation +(see the related P4Java User Guide for details).. +

Related Documentation

+ + \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/package.html b/p4java/r19-1/src/main/java/com/perforce/p4java/package.html index 337416d7..24fdb080 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/package.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/package.html @@ -1,9 +1,9 @@ - - - - - -Defines the root of the Perforce P4Java API product package hierarchy and a small handful of useful product-wide -definitions and services such as loggers, tracers, and product metadata. - + + + + + +Defines the root of the Perforce P4Java API product package hierarchy and a small handful of useful product-wide +definitions and services such as loggers, tracers, and product metadata. + \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/server/callback/package.html b/p4java/r19-1/src/main/java/com/perforce/p4java/server/callback/package.html index 2d55d59e..460501e5 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/server/callback/package.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/server/callback/package.html @@ -1,9 +1,9 @@ - - - - - -Provides interfaces and classes for defining and accessing Perforce server callbacks -for things like logging, command progress, single sign on (SSO) authentication, etc. - + + + + + +Provides interfaces and classes for defining and accessing Perforce server callbacks +for things like logging, command progress, single sign on (SSO) authentication, etc. + \ No newline at end of file diff --git a/p4java/r19-1/src/main/java/com/perforce/p4java/server/package.html b/p4java/r19-1/src/main/java/com/perforce/p4java/server/package.html index b52372a1..ca74f9e5 100644 --- a/p4java/r19-1/src/main/java/com/perforce/p4java/server/package.html +++ b/p4java/r19-1/src/main/java/com/perforce/p4java/server/package.html @@ -1,8 +1,8 @@ - - - - - -Provides interfaces and classes for accessing Perforce SCM servers and associated server-level services. - + + + + + +Provides interfaces and classes for accessing Perforce SCM servers and associated server-level services. + \ No newline at end of file diff --git a/p4java/r19-1/src/main/resources/META-INF/MANIFEST.MF b/p4java/r19-1/src/main/resources/META-INF/MANIFEST.MF index 9e8ed36b..49552550 100644 --- a/p4java/r19-1/src/main/resources/META-INF/MANIFEST.MF +++ b/p4java/r19-1/src/main/resources/META-INF/MANIFEST.MF @@ -1,39 +1,39 @@ -Manifest-Version: 1.0 -Bundle-Name: Perforce Java API -Bundle-Description: Perforce Java API -Bundle-ManifestVersion: 2 -Bundle-SymbolicName: com.perforce.p4java -Bundle-Vendor: Perforce Software -Bundle-Version: 1.0.0 -Export-Package: com.perforce.p4java, - com.perforce.p4java.admin, - com.perforce.p4java.client, - com.perforce.p4java.core, - com.perforce.p4java.core.file, - com.perforce.p4java.exception, - com.perforce.p4java.impl.generic.admin, - com.perforce.p4java.impl.generic.client, - com.perforce.p4java.impl.generic.core, - com.perforce.p4java.impl.generic.core.file, - com.perforce.p4java.impl.generic.sys, - com.perforce.p4java.impl.mapbased, - com.perforce.p4java.impl.mapbased.client, - com.perforce.p4java.impl.mapbased.rpc, - com.perforce.p4java.impl.mapbased.rpc.connection, - com.perforce.p4java.impl.mapbased.rpc.func, - com.perforce.p4java.impl.mapbased.rpc.func.client, - com.perforce.p4java.impl.mapbased.rpc.func.helper, - com.perforce.p4java.impl.mapbased.rpc.func.proto, - com.perforce.p4java.impl.mapbased.rpc.func.user, - com.perforce.p4java.impl.mapbased.rpc.msg, - com.perforce.p4java.impl.mapbased.rpc.packet, - com.perforce.p4java.impl.mapbased.rpc.stream, - com.perforce.p4java.impl.mapbased.rpc.sys, - com.perforce.p4java.impl.mapbased.rpc.sys.helper, - com.perforce.p4java.impl.mapbased.server, - com.perforce.p4java.option, - com.perforce.p4java.option.changelist, - com.perforce.p4java.option.client, - com.perforce.p4java.option.server, - com.perforce.p4java.server, - com.perforce.p4java.server.callback +Manifest-Version: 1.0 +Bundle-Name: Perforce Java API +Bundle-Description: Perforce Java API +Bundle-ManifestVersion: 2 +Bundle-SymbolicName: com.perforce.p4java +Bundle-Vendor: Perforce Software +Bundle-Version: 1.0.0 +Export-Package: com.perforce.p4java, + com.perforce.p4java.admin, + com.perforce.p4java.client, + com.perforce.p4java.core, + com.perforce.p4java.core.file, + com.perforce.p4java.exception, + com.perforce.p4java.impl.generic.admin, + com.perforce.p4java.impl.generic.client, + com.perforce.p4java.impl.generic.core, + com.perforce.p4java.impl.generic.core.file, + com.perforce.p4java.impl.generic.sys, + com.perforce.p4java.impl.mapbased, + com.perforce.p4java.impl.mapbased.client, + com.perforce.p4java.impl.mapbased.rpc, + com.perforce.p4java.impl.mapbased.rpc.connection, + com.perforce.p4java.impl.mapbased.rpc.func, + com.perforce.p4java.impl.mapbased.rpc.func.client, + com.perforce.p4java.impl.mapbased.rpc.func.helper, + com.perforce.p4java.impl.mapbased.rpc.func.proto, + com.perforce.p4java.impl.mapbased.rpc.func.user, + com.perforce.p4java.impl.mapbased.rpc.msg, + com.perforce.p4java.impl.mapbased.rpc.packet, + com.perforce.p4java.impl.mapbased.rpc.stream, + com.perforce.p4java.impl.mapbased.rpc.sys, + com.perforce.p4java.impl.mapbased.rpc.sys.helper, + com.perforce.p4java.impl.mapbased.server, + com.perforce.p4java.option, + com.perforce.p4java.option.changelist, + com.perforce.p4java.option.client, + com.perforce.p4java.option.server, + com.perforce.p4java.server, + com.perforce.p4java.server.callback diff --git a/p4java/r19-1/src/main/resources/META-INF/services/java.nio.charset.spi.CharsetProvider b/p4java/r19-1/src/main/resources/META-INF/services/java.nio.charset.spi.CharsetProvider index d0653449..8f766f98 100644 --- a/p4java/r19-1/src/main/resources/META-INF/services/java.nio.charset.spi.CharsetProvider +++ b/p4java/r19-1/src/main/resources/META-INF/services/java.nio.charset.spi.CharsetProvider @@ -1 +1 @@ -com.perforce.p4java.charset.PerforceCharsetProvider +com.perforce.p4java.charset.PerforceCharsetProvider diff --git a/p4java/r19-1/src/main/resources/com/perforce/p4java/messages/ClientTrustMessages.properties b/p4java/r19-1/src/main/resources/com/perforce/p4java/messages/ClientTrustMessages.properties index 73f62511..37620d7c 100644 --- a/p4java/r19-1/src/main/resources/com/perforce/p4java/messages/ClientTrustMessages.properties +++ b/p4java/r19-1/src/main/resources/com/perforce/p4java/messages/ClientTrustMessages.properties @@ -1,56 +1,56 @@ -# not established warning message (no fingerprint in file when running normal commands 'p4 info', etc.) -client.trust.warning.notestablished=\ -The authenticity of ''{0}'' can''t be established,\n \ -this may be your first attempt to connect to this Perforce server.\n \ -The fingerprint for the public key sent to your client is\n \ -{1}\n - -# new connection warning message (no fingerprint in file when running 'p4 trust' command) -client.trust.warning.newconnection=\ -The fingerprint of the server of your Perforce setting\n \ -''{0}'' is not known.\n \ -That fingerprint is {1}\n - -# new key warning message (wrong fingerpint when running commands) -client.trust.warning.newkey=\ -***** WARNING PERFORCE SERVER IDENTIFICATION HAS CHANGED! *****\n \ -It is possible that someone is intercepting your connection to the Perforce server ''{0}''\n \ -If this is not a scheduled key change, then you should contact your Perforce administrator.\n \ -The fingerprint for the mismatched key sent to your client is\n \ -{1}\n - -# command exception: new connection -client.trust.exception.newconnection=\ -To allow connection use the 'addTrust' method with the 'autoAccept' option. - -# command exception: new key -client.trust.exception.newkey=\ -Can't trust mismatched Perforce server key without both 'force' and 'autoAccept' options. - -# add trust exception: new connection -client.trust.add.exception.newconnection=\ -To trust the Perforce server key use the 'autoAccept' option. - -# add trust exception: new key -client.trust.add.exception.newkey=\ -Can't trust mismatched Perforce server key without both 'force' and 'autoAccept' options. - -# trust added info -client.trust.added=\ -Added trust for Perforce server ''{0}'' ({1}) - -# trust deleted info -client.trust.removed=\ -Removed trust for Perforce server ''{0}'' ({1}) - -# trust already established info -client.trust.alreadyestablished=\ -Trust already established. - -# install fingerprint exception -client.trust.install.exception=\ -Error occurred while installing fingerprint {0} for Perforce server ''{1}'' ({2}) to trust file. - -# uninstall fingerprint exception -client.trust.uninstall.exception=\ -Error occurred while uninstalling fingerprint for Perforce server ''{0}'' ({1}) from trust file. +# not established warning message (no fingerprint in file when running normal commands 'p4 info', etc.) +client.trust.warning.notestablished=\ +The authenticity of ''{0}'' can''t be established,\n \ +this may be your first attempt to connect to this Perforce server.\n \ +The fingerprint for the public key sent to your client is\n \ +{1}\n + +# new connection warning message (no fingerprint in file when running 'p4 trust' command) +client.trust.warning.newconnection=\ +The fingerprint of the server of your Perforce setting\n \ +''{0}'' is not known.\n \ +That fingerprint is {1}\n + +# new key warning message (wrong fingerpint when running commands) +client.trust.warning.newkey=\ +***** WARNING PERFORCE SERVER IDENTIFICATION HAS CHANGED! *****\n \ +It is possible that someone is intercepting your connection to the Perforce server ''{0}''\n \ +If this is not a scheduled key change, then you should contact your Perforce administrator.\n \ +The fingerprint for the mismatched key sent to your client is\n \ +{1}\n + +# command exception: new connection +client.trust.exception.newconnection=\ +To allow connection use the 'addTrust' method with the 'autoAccept' option. + +# command exception: new key +client.trust.exception.newkey=\ +Can't trust mismatched Perforce server key without both 'force' and 'autoAccept' options. + +# add trust exception: new connection +client.trust.add.exception.newconnection=\ +To trust the Perforce server key use the 'autoAccept' option. + +# add trust exception: new key +client.trust.add.exception.newkey=\ +Can't trust mismatched Perforce server key without both 'force' and 'autoAccept' options. + +# trust added info +client.trust.added=\ +Added trust for Perforce server ''{0}'' ({1}) + +# trust deleted info +client.trust.removed=\ +Removed trust for Perforce server ''{0}'' ({1}) + +# trust already established info +client.trust.alreadyestablished=\ +Trust already established. + +# install fingerprint exception +client.trust.install.exception=\ +Error occurred while installing fingerprint {0} for Perforce server ''{1}'' ({2}) to trust file. + +# uninstall fingerprint exception +client.trust.uninstall.exception=\ +Error occurred while uninstalling fingerprint for Perforce server ''{0}'' ({1}) from trust file. diff --git a/p4java/r19-1/src/main/resources/com/perforce/p4java/messages/PerforceMessages.properties b/p4java/r19-1/src/main/resources/com/perforce/p4java/messages/PerforceMessages.properties index cf5ad33d..42b785ab 100644 --- a/p4java/r19-1/src/main/resources/com/perforce/p4java/messages/PerforceMessages.properties +++ b/p4java/r19-1/src/main/resources/com/perforce/p4java/messages/PerforceMessages.properties @@ -1 +1 @@ -# General P4Java Perforce messages +# General P4Java Perforce messages diff --git a/p4java/r19-1/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java b/p4java/r19-1/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java index bab7e806..978db638 100644 --- a/p4java/r19-1/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java +++ b/p4java/r19-1/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java @@ -1,83 +1,83 @@ -/** - * Copyright (c) 2012 Perforce Software. All rights reserved. - */ -package com.perforce.p4java.tests.dev.unit.features123; - -import static org.junit.Assert.assertTrue; - -import java.io.File; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SymbolicLinkHelper; -import com.perforce.p4java.tests.dev.unit.P4JavaTestCase; - -/** - * Test symbolic link helper (JDK 7 or above) - */ -public class SymbolicLinkHelperTest extends P4JavaTestCase { - - /** - * @BeforeClass annotation to a method to be run before all the tests in a - * class. - */ - @BeforeClass - public static void oneTimeSetUp() { - // one-time initialization code (before all the tests). - } - - /** - * @AfterClass annotation to a method to be run after all the tests in a - * class. - */ - @AfterClass - public static void oneTimeTearDown() { - // one-time cleanup code (after all the tests). - } - - /** - * @Before annotation to a method to be run before each test in a class. - */ - @Before - public void setUp() { - // initialization code (before each test). - } - - /** - * @After annotation to a method to be run after each test in a class. - */ - @After - public void tearDown() { - // cleanup code (after each test). - } - - /** - * Test symbolic link - */ - @Test - public void tesSymbolicLink() { - // Check if symlink capable - if (SymbolicLinkHelper.isSymbolicLinkCapable()) { - - String target = "/usr/bin"; - String link = "/tmp/user-bin-" + getRandomInt(); - - // Create symbolic link - Object path = SymbolicLinkHelper.createSymbolicLink(link, target); - - boolean isSymlink = SymbolicLinkHelper.isSymbolicLink(path - .toString()); - assertTrue(isSymlink); - - File file = new File(path.toString()); - if (file.exists()) { - file.delete(); - } - } - } - -} +/** + * Copyright (c) 2012 Perforce Software. All rights reserved. + */ +package com.perforce.p4java.tests.dev.unit.features123; + +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SymbolicLinkHelper; +import com.perforce.p4java.tests.dev.unit.P4JavaTestCase; + +/** + * Test symbolic link helper (JDK 7 or above) + */ +public class SymbolicLinkHelperTest extends P4JavaTestCase { + + /** + * @BeforeClass annotation to a method to be run before all the tests in a + * class. + */ + @BeforeClass + public static void oneTimeSetUp() { + // one-time initialization code (before all the tests). + } + + /** + * @AfterClass annotation to a method to be run after all the tests in a + * class. + */ + @AfterClass + public static void oneTimeTearDown() { + // one-time cleanup code (after all the tests). + } + + /** + * @Before annotation to a method to be run before each test in a class. + */ + @Before + public void setUp() { + // initialization code (before each test). + } + + /** + * @After annotation to a method to be run after each test in a class. + */ + @After + public void tearDown() { + // cleanup code (after each test). + } + + /** + * Test symbolic link + */ + @Test + public void tesSymbolicLink() { + // Check if symlink capable + if (SymbolicLinkHelper.isSymbolicLinkCapable()) { + + String target = "/usr/bin"; + String link = "/tmp/user-bin-" + getRandomInt(); + + // Create symbolic link + Object path = SymbolicLinkHelper.createSymbolicLink(link, target); + + boolean isSymlink = SymbolicLinkHelper.isSymbolicLink(path + .toString()); + assertTrue(isSymlink); + + File file = new File(path.toString()); + if (file.exists()) { + file.delete(); + } + } + } + +} diff --git a/p4java/r19-1/src/test/resources/com/perforce/p4java/common/io/utf8_unix_linux_osx_line_endings_without_bom.txt b/p4java/r19-1/src/test/resources/com/perforce/p4java/common/io/utf8_unix_linux_osx_line_endings_without_bom.txt index 422c2b7a..c30dea8a 100644 --- a/p4java/r19-1/src/test/resources/com/perforce/p4java/common/io/utf8_unix_linux_osx_line_endings_without_bom.txt +++ b/p4java/r19-1/src/test/resources/com/perforce/p4java/common/io/utf8_unix_linux_osx_line_endings_without_bom.txt @@ -1,2 +1,2 @@ -a -b +a +b diff --git a/p4java/r19-1/src/test/resources/com/perforce/p4java/common/io/utf8_with_bom_unix_lineending.txt b/p4java/r19-1/src/test/resources/com/perforce/p4java/common/io/utf8_with_bom_unix_lineending.txt index 668bab23..29857843 100644 --- a/p4java/r19-1/src/test/resources/com/perforce/p4java/common/io/utf8_with_bom_unix_lineending.txt +++ b/p4java/r19-1/src/test/resources/com/perforce/p4java/common/io/utf8_with_bom_unix_lineending.txt @@ -1,2 +1,2 @@ -a +a b \ No newline at end of file diff --git a/p4java/r19-1/src/test/resources/com/perforce/p4java/common/io/utf_8-jp_without_bom.txt b/p4java/r19-1/src/test/resources/com/perforce/p4java/common/io/utf_8-jp_without_bom.txt index cd4b3123..9e2dfcda 100644 --- a/p4java/r19-1/src/test/resources/com/perforce/p4java/common/io/utf_8-jp_without_bom.txt +++ b/p4java/r19-1/src/test/resources/com/perforce/p4java/common/io/utf_8-jp_without_bom.txt @@ -1,9 +1,9 @@ -UTF-8(ユーティーエフã¯ã¡ã€ãƒ¦ãƒ¼ãƒ†ã‚£ãƒ¼ã‚¨ãƒ•ã‚¨ã‚¤ãƒˆï¼‰ã¯ISO/IEC 10646 (UCS) ã¨Unicodeã§ä½¿ãˆã‚‹8ビット符å·å˜ä½ã®æ–‡å­—符å·åŒ–å½¢å¼åŠã³æ–‡å­—符å·åŒ–スキーム。 - -æ­£å¼å称ã¯ã€ISO/IEC 10646ã§ã¯ “UCS Transformation Format 8â€ã€Unicodeã§ã¯ “Unicode Transformation Format-8†ã¨ã„ã†ã€‚両者ã¯ISO/IEC 10646ã¨Unicodeã®ã‚³ãƒ¼ãƒ‰é‡è¤‡ç¯„囲ã§äº’æ›æ€§ãŒã‚る。RFCã«ã‚‚仕様ãŒã‚ã‚‹[1]。 - -2ãƒã‚¤ãƒˆç›®ä»¥é™ã«ã€Œ/ã€ãªã©ã®ASCII文字ãŒç¾ã‚Œãªã„よã†ã«å·¥å¤«ã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‹ã‚‰ã€UTF-FSS (File System Safe) ã¨ã‚‚ã„ã‚れる。旧å称ã¯UTF-2。 - -データ交æ›æ–¹å¼ã€ãƒ•ã‚¡ã‚¤ãƒ«å½¢å¼ã¨ã—ã¦ã€ä¸€èˆ¬çš„ã«UTF-8ã¯ä½¿ã‚れる傾å‘ã«ã‚る。 - -当åˆã¯ã€ãƒ™ãƒ«ç ”究所ã«ãŠã„ã¦Plan 9ã§ç”¨ã„るエンコードã¨ã—ã¦ã€ãƒ­ãƒ–・パイクã«ã‚ˆã‚‹è¨­è¨ˆæŒ‡é‡ã®ã‚‚ã¨ã€ã‚±ãƒ³ãƒ»ãƒˆãƒ³ãƒ—ソンã«ã‚ˆã£ã¦è€ƒæ¡ˆã•ã‚ŒãŸ +UTF-8(ユーティーエフã¯ã¡ã€ãƒ¦ãƒ¼ãƒ†ã‚£ãƒ¼ã‚¨ãƒ•ã‚¨ã‚¤ãƒˆï¼‰ã¯ISO/IEC 10646 (UCS) ã¨Unicodeã§ä½¿ãˆã‚‹8ビット符å·å˜ä½ã®æ–‡å­—符å·åŒ–å½¢å¼åŠã³æ–‡å­—符å·åŒ–スキーム。 + +æ­£å¼å称ã¯ã€ISO/IEC 10646ã§ã¯ “UCS Transformation Format 8â€ã€Unicodeã§ã¯ “Unicode Transformation Format-8†ã¨ã„ã†ã€‚両者ã¯ISO/IEC 10646ã¨Unicodeã®ã‚³ãƒ¼ãƒ‰é‡è¤‡ç¯„囲ã§äº’æ›æ€§ãŒã‚る。RFCã«ã‚‚仕様ãŒã‚ã‚‹[1]。 + +2ãƒã‚¤ãƒˆç›®ä»¥é™ã«ã€Œ/ã€ãªã©ã®ASCII文字ãŒç¾ã‚Œãªã„よã†ã«å·¥å¤«ã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‹ã‚‰ã€UTF-FSS (File System Safe) ã¨ã‚‚ã„ã‚れる。旧å称ã¯UTF-2。 + +データ交æ›æ–¹å¼ã€ãƒ•ã‚¡ã‚¤ãƒ«å½¢å¼ã¨ã—ã¦ã€ä¸€èˆ¬çš„ã«UTF-8ã¯ä½¿ã‚れる傾å‘ã«ã‚る。 + +当åˆã¯ã€ãƒ™ãƒ«ç ”究所ã«ãŠã„ã¦Plan 9ã§ç”¨ã„るエンコードã¨ã—ã¦ã€ãƒ­ãƒ–・パイクã«ã‚ˆã‚‹è¨­è¨ˆæŒ‡é‡ã®ã‚‚ã¨ã€ã‚±ãƒ³ãƒ»ãƒˆãƒ³ãƒ—ソンã«ã‚ˆã£ã¦è€ƒæ¡ˆã•ã‚ŒãŸ diff --git a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/func/helper/md5_digest_test.txt b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/func/helper/md5_digest_test.txt index f33b424e..ed583b7c 100644 --- a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/func/helper/md5_digest_test.txt +++ b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/func/helper/md5_digest_test.txt @@ -1,31568 +1,31568 @@ - - - - - - - Edit Custom Tool - - - 38 - - - 161 - - - - Menu item: - - - 51 - - - - &Name: - - - 62 - - - - Place&ment: - - - 70 - - - - N&ew Folder... - - - 87 - - - - &Help - - - 98 - - - - Cancel - - - 103 - - - - OK - - - 108 - - - - Add Custom Tool - - - 168 - - - - Error - - - 228 - - - 244 - - - 258 - - - - Menu item must have a name - - - 229 - - - - More than one replaceable file argument of type %X is not allowed - -%1 - - - 242 - - - - Only %f may be used as a replacable parameter in the Initial Directory field. -It will be substituted with the path to the directory containing the requested file(s). - - - 258 - - - - - - New Folder - - - 316 - - - - &Name: - - - 331 - - - - Include folder in conte&xt menus - - - 341 - - - - Cancel - - - 345 - - - - OK - - - 350 - - - - - - - - - - <h2>The Perforce Administration Tool</h2>Copyright &#169; 2002-%1 <A href>Perforce Software</A>.<br>All rights reserved.<hr width=10000>%2 <br> - - - 21 - - - - Rev. %1 - - - 36 - - - - - - - - - - The path %1 - does not appear to be a depot path. - - - 95 - - - - The path %1 - could not be found. - - - 156 - - - - - - - - - - Sort By - - - 736 - - - - Show Files As - - - 742 - - - - - - - - - - The P4Admin application cannot be found. - - - 78 - - - - P4V - - - 83 - - - 89 - - - - Do you want to locate it? - - - 87 - - - - Locate P4Admin - - - 94 - - - - - - - - - - Change Password - - - 25 - - - - &Enter new password: - - - 36 - - - - &Confirm new password: - - - 45 - - - - OK - - - 59 - - - - Cancel - - - 64 - - - - Change Password: %1 - - - 80 - - - - Passwords must be at least eight characters and contain upper and -lower case letters, or letters with at least one symbol or number. - - - 85 - - - - The password entries do not match. -Please re-enter the passwords. - - - 113 - - - - - - - - - - - - - 11 - - - - User/Group - - - 12 - - - - Access Level - - - 13 - - - - Granted To - - - 14 - - - - - - - - - - Cre&ate %1... - - - 367 - - - - - - You do not have permission to modify group '%1'. - - - 596 - - - - - - The group must contain at least one user or -one subgroup. - - - - 461 - - - - - - - - - - Depot Tree - - - 83 - - - - Access Level - - - 84 - - - - Granted To - - - 85 - - - - - - - - - - Home - - - 359 - - - 436 - - - - Users && Groups - - - 363 - - - 443 - - - - Permissions - - - 366 - - - 449 - - - - &New... - - - 630 - - - - Create a new depot - - - 659 - - - - &User... - - - 527 - - - - Depots - - - 369 - - - 461 - - - - Dashboard - - - 1223 - - - - Create a new user - - - 641 - - - - &Group... - - - 530 - - - - Create a new group - - - 650 - - - - &Depot... - - - 533 - - - - Change Password... - - - 858 - - - - Load new license file - - - 870 - - - - &Table Format - - - 883 - - - - &Plain Text Format - - - 892 - - - - &Insert Line - - - 571 - - - - Connections - - - 242 - - - - Log - - - 275 - - - - &Log Pane - - - 554 - - - - &Change Password... - - - 557 - - - - &Load License File... - - - 560 - - - - &Delete Line - - - 574 - - - - Move Line &Up - - - 577 - - - - M&ove Line Down - - - 580 - - - - &Save Edits - - - 583 - - - - &Revert Edits - - - 586 - - - - Process &Monitor... - - - 589 - - - - Co&nfigure Monitoring... - - - 592 - - - - P&assword Security Level... - - - 595 - - - - Choose Character &Encoding... - - - 604 - - - - &File - - - 629 - - - - Close Window - - - 679 - - - - &Administration - - - 853 - - - - &Permissions Table - - - 876 - - - - View process monitor - - - 946 - - - - Main Tabs: - - - 1994 - - - - P4Admin - - - 2695 - - - - Perforce P4Admin - - - 2698 - - - - - Perforce P4Admin - - - 2700 - - - window title, suffix - - - - Permission editing not supported against 2016.1 or greater servers - - - 2893 - - - - If you proceed, you will become the sole user with superuser access. You will need to specifically grant other administrators superuser access in order for them to use this tool.<br><br>Do you want to proceed? - - - 3141 - - - - &Update Spec Depot... - - - 598 - - - - Update spec depot - - - 960 - - - - &Obliterate... - - - 601 - - - - Obliterate files or folders - - - 966 - - - - &Edit - - - 718 - - - - &Find - - - 536 - - - - &View - - - 785 - - - - &Administration Home - - - 539 - - - - View administration home - - - 793 - - - - &Users && Groups - - - 542 - - - - View users and groups - - - 802 - - - - &Permissions - - - 545 - - - - View permissions - - - 809 - - - - &Depots - - - 548 - - - - View depots - - - 817 - - - - &Toolbar - - - 551 - - - - &Connections - - - 970 - - - - Open &Recent - - - 978 - - - - &Favorite Connections - - - 984 - - - - &Tools - - - 1000 - - - - &Diff Against... - - - 1003 - - - - &Window - - - 1035 - - - - Zoom - - - 1046 - - - - &Help - - - 1059 - - - - Toolbar - - - 1160 - - - - The group '%1' does not contain any subgroups or users and will be deleted.<br><br>Do you want to remove all permissions assigned to this group from the Perforce database? - - - 1554 - - - - Select License File - - - 1943 - - - - Loading license file - - - 1971 - - - - Alerts: - - - 1984 - - - - None found. - - - 1988 - - - 1998 - - - - The server is currently unlicensed and must be shutdown to add licenses - - - 2048 - - - - The IP address listed in the new license file does not match the IP address listed in the current license file - - - 2053 - - - - Do you want to save changes made to the permissions table? - - - 2651 - - - - - - - - - - Edit File Type - - - 120 - - - - Add File Type - - - 125 - - - - &Extension: - - - 140 - - - - &Application: - - - 183 - - - - &Browse... - - - 190 - - - - Arg&uments: - - - 192 - - - - Application - - - 202 - - - - In Folder - - - 202 - - - - Arguments - - - 202 - - - - Cancel - - - 224 - - - - &Save - - - 225 - - - - The arguments field must contain the %1 and %2 arguments. - - - 259 - - - %1 and %2 are literals, not placeholders. - - - - The arguments field must contain the %1, %2 and %r arguments. - - - 287 - - - %1 and %2 are literals, not placeholders. - - - - Choose an application - - - 317 - - - - - - - - - - A&dd... - - - 51 - - - - &Edit... - - - 54 - - - - &Remove - - - 58 - - - - Extension - - - 66 - - - - Application - - - 66 - - - - In Folder - - - 66 - - - - - - - - - - Manage Bookmarks - - - 37 - - - - Bookmark... - - - 27 - - - - Type : - - - 76 - - - - Location : - - - 80 - - - - Do you want to delete all of the bookmarks? - - - 306 - - - - Do you want to delete the folder '%1' and all of the bookmarks it contains? - - - 308 - - - - Edit Bookmark - - - 149 - - - - Add Bookmark - - - 160 - - - - Depot file - - - 257 - - - - Depot folder - - - 258 - - - - Workspace file - - - 259 - - - - Workspace folder - - - 260 - - - - Are you sure you want to delete the bookmark '%1'? - - - 294 - - - - - - - - - - Add Bookmark - - - 48 - - - - Menu item: - - - 60 - - - - &Name: - - - 62 - - - - Place&ment: - - - 68 - - - - N&ew Folder... - - - 73 - - - - bookmark - - - 186 - - - - Folder or file: - - - 82 - - - - Shortc&ut: - - - 78 - - - - &Location: - - - 86 - - - - &Browse... - - - 91 - - - - Not a valid depot path. - - - 320 - - - - Depot path not found. - - - 330 - - - - Not a valid path. - - - 372 - - - - Path not found. - - - 382 - - - - - - - - - - Branch - - - 15 - - - - &Branch - - - 16 - - - - - - - - - - Checking permissions table for central settings file - - - 155 - - - - yes (line %1 of permissions table) - - - 169 - - - - no path found in permissions table - - - 170 - - - - yes: default central settings file specified - - - 184 - - - - Central settings file specified for user: %1 - - - 189 - - - - Warning: central settings file not found at %1 - - - 255 - - - - Loaded central settings file at %1 - - - 259 - - - - Parse error at line %1, column %2: %3 - - - 288 - - - - Central settings file redirect to: %1 - - - 309 - - - - Perforce applets accepted for this server: %1 - - - 376 - - - - yes - - - 376 - - - - no - - - 376 - - - - Central settings redirect: %1 - - - 416 - - - - Applets are disabled for public.perforce.com - - - 454 - - - - Applets disabled in Preferences for "%1" - - - 462 - - - - Perforce applets accepted: no - - - 488 - - - 506 - - - - %1, line %2 reading central settings file - - - 557 - - - - Alerts: - - - 615 - - - - Main tabs: - - - 623 - - - 651 - - - - Submit dialog: - - - 638 - - - - Files: - - - 676 - - - - Folders: - - - 677 - - - - Revisions: - - - 678 - - - - Submitted changelists: - - - 679 - - - - Jobs: - - - 680 - - - - Pending changelists: - - - 681 - - - - Branch mappings: - - - 682 - - - - Labels: - - - 683 - - - - Workspaces: - - - 684 - - - - Details panes: - - - 695 - - - - Bottom tabs: - - - 706 - - - - Applets disabled in Preferences for all servers - - - 785 - - - - - - - - - - Change Ownership - - - 33 - - - - Change the user and/or workspace for Pending Changelist " - - - 42 - - - - &User - - - 47 - - - - &Workspace - - - 48 - - - - &Browse... - - - 59 - - - - Br&owse... - - - 60 - - - - Cancel - - - 62 - - - - OK - - - 63 - - - - Cannot Change Ownership - - - 173 - - - 179 - - - - User '%1' does not exist - - - 175 - - - 234 - - - - Workspace '%1' does not exist - - - 181 - - - - - - - - - - Create or Update Workspace View - - - 50 - - - - Create a new workspace or update an existing workspace using the view and options of the template workspace: - - - 53 - - - - &Create workspace - - - 63 - - - - &Workspace Name: - - - 68 - - - - &Update existing workspace - - - 76 - - - - Workspace &Name: - - - 80 - - - - &Browse... - - - 88 - - - - &OK - - - 99 - - - - Cancel - - - 103 - - - - '%1' is a reserved Perforce workspace name, and considered invalid by Perforce. - - - 154 - - - - Unable to update a workspace named '%1'. -Workspace '%1' does not exist. - - - 173 - - - - A workspace that is associated with a stream cannot be updated from a template. - - - 184 - - - - - - - - - - Filter Files - - - 80 - - - - &Filter - - - 117 - - - - &Save... - - - 118 - - - - Set as D&efault - - - 119 - - - - &Close - - - 120 - - - - File Name - - - 376 - - - - and - - - 572 - - - - or - - - 575 - - - 600 - - - - "%1" - - - 682 - - - - - - - - - - Changelist %1 contains shelved files and cannot be %2. - -Do you want to delete the shelved files? - - - 36 - - - - - - - - - - Manage Favorite Connections - - - 25 - - - - Connection... - - - 22 - - - - Edit Favorite Connection - - - 76 - - - - Add Favorite Connection - - - 88 - - - - Are you sure you want to delete the favorite connection '%1'? - - - 175 - - - - Do you want to delete all the favorite connections? - - - 187 - - - - Do you want to delete folder '%1' and all the connections it contains? - - - 189 - - - - - - - - - - Add Favorite Connection - - - 47 - - - - Menu item: - - - 66 - - - - &Name: - - - 68 - - - - Place&ment: - - - 73 - - - - N&ew Folder... - - - 77 - - - - Alt+E - - - 78 - - - - Connection : - - - 91 - - - - &Server: - - - 97 - - - - Short&cut: - - - 86 - - - - (server name or host:port) - - - 99 - - - - &User: - - - 102 - - - - &Browse... - - - 105 - - - - Alt+B - - - 106 - - - - &Workspace: - - - 113 - - - - B&rowse... - - - 116 - - - - Alt+R - - - 117 - - - - Cancel - - - 123 - - - - OK - - - 127 - - - - favorite connection - - - 265 - - - - - - - - - - Refresh - - - 227 - - - - Refresh Connections - - - 239 - - - - - - - - - - &New - - - 66 - - - - Alt+N - - - 67 - - - - &Edit... - - - 70 - - - - Alt+E - - - 71 - - - - Dele&te - - - 74 - - - - Alt+T - - - 75 - - - - Move &Up - - - 78 - - - - Alt+U - - - 79 - - - - Move &Down - - - 82 - - - - Alt+D - - - 83 - - - - Folder... - - - 110 - - - - Separator - - - 111 - - - - Edit Folder - - - 504 - - - - Enter a new name for the folder: - - - 505 - - - - Delete Folder - - - 555 - - - - &Delete All - - - 560 - - - - Delete &Folder Only - - - 562 - - - - - - - - - - Copy - - - 15 - - - - &Copy - - - 16 - - - - - - - - - - Manage Custom Tools - - - 58 - - - - Custo&m tools menu: - - - 72 - - - - &New - - - 94 - - - - &Edit... - - - 100 - - - - Dele&te - - - 106 - - - - Move &Up - - - 112 - - - - Move &Down - - - 118 - - - - &Import Tools... - - - 129 - - - 131 - - - - E&xport Tools... - - - 137 - - - 139 - - - - Cancel - - - 144 - - - - OK - - - 151 - - - - Tool... - - - 163 - - - - Folder... - - - 164 - - - - Separator - - - 165 - - - - Edit Folder - - - 336 - - - - Delete Folder - - - 404 - - - - Do you want to delete all the tools? - - - 407 - - - - Do you want to delete folder '%1' and all the tools it contains? - - - 409 - - - - &Delete All - - - 412 - - - - Delete &Folder Only - - - 416 - - - - You have made changes to the Custom Tools that have not been saved. Do you want to save these changes? - - - 581 - - - - xml files (*.xml);;All Files (*.*) - - - 713 - - - 833 - - - - xml Files (*.xml);;All Files(*) - - - 715 - - - 835 - - - - Read Custom Tools from a File - - - 720 - - - - Error - - - 742 - - - 883 - - - - No CustomTool Definitions found in this file. - - - 743 - - - - Save Custom Tools to a File - - - 842 - - - 847 - - - - %1 already exists. Overwrite? - - - 866 - - - - Overwrite? - - - 868 - - - - Unable to write %1 - - - 884 - - - - Application: - - - 920 - - - - Arguments: - - - 930 - - - - - - - - - - Edit settings - - - 57 - - - - - - - - - - Delete Group: - - - 25 - - - - &Remove all permissions assigned to this group from the -Perforce database - - - 28 - - - - &Delete Group - - - 39 - - - - Cancel - - - 40 - - - - %1 (%2) - - - 64 - - - %1=group name, %2=server port - - - - - - - - - - Delete User: - - - 73 - - - - %1 (%2) - - - 98 - - - %1=user name. %2=server port - - - - The following changelists will be deleted, and any files they contain will be reverted. -Note: Reverting will not change the content of the files on the user's local disk. - - - 105 - - - - The following workspace specifications will be deleted from the Perforce database. - - - 123 - - - - Delete &shelved files in user's pending changelists - - - 141 - - - - &Remove all permissions assigned to this user from the Perforce database - - - 147 - - - - &Delete User - - - 157 - - - - Cancel - - - 159 - - - - User '%1' cannot be deleted because the user's pending changelists contain shelved files. - - - 271 - - - - User '%1' cannot be deleted because the user's shelved files could not be automatically discarded. - - - 314 - - - - The user was not deleted. Some changelists could not be removed. - - - 495 - - - - - - - - - - New Depot... - - - 163 - - - - Local and spec depots must be empty before deleting. -Obliterate all files to empty the depot. - - - 322 - - - - The depot contains files that are marked for add or edit. -These files must be reverted before deleting the depot. - - - 325 - - - - Delete %1 Form - - - 345 - - - - You are about to delete %1 from the server. -Are you sure you want to continue? - - - - 346 - - - - - - - - - - There is no selected directory. - - - 934 - - - - Cannot create query from an unknown object - - - 957 - - - - Missing branch specification. - - - 1251 - - - 1428 - - - - Cannot build the workspace mapping from the workspace view. - - - - 1340 - - - - Stopped diff. - - - 2001 - - - 2528 - - - - Cannot diff local folders using depot syntax. - - - 2165 - - - - Cannot diff these two workspace folders. - - - 2172 - - - - Loading file: %1 - - - 3686 - - - 3848 - - - - Loading file: %1@=%2 - - - 3690 - - - 3852 - - - - Loading file: %1#%2 - - - 3693 - - - 3855 - - - - - - - - - - The number of files in the changelist exceeds the preference for the maximum number of files displayed per changelist. - - - 1303 - - - - Are you sure you want run %1 diff comparison(s) in the diff application? - - - 1362 - - - - &Diff - - - 1370 - - - - - - Diff Against Have Revision - - - 162 - - - - Diff Against Previous Revision - - - 173 - - - - Diff Against Source Revision - - - 187 - - - - Diff Files Against Have Revisions - - - 196 - - - - Diff Files Against Previous Revisions - - - 203 - - - - Diff Selected - - - 231 - - - - Diff Using Mapping '%1' - - - 241 - - - - - - - - - - has been unloaded. Can't diff until it is reloaded. - - - 365 - - - - The workspace view changed. To continue using Folder Diff you need to restart it. -Do you want to automatically restart Folder Diff now? - -Restart Folder Diff? - - - 444 - - - - Folder Diff - - - 448 - - - - &Restart - - - 455 - - - 462 - - - - &Do Not Restart - - - 464 - - - - Starting Diff ... - - - 732 - - - - There are no files that need to be reconciled. - - - - 1061 - - - - Encountered an error running the server's diff command. - - - 1652 - - - - A folder diff cannot be performed on the root directory. - - - - 1655 - - - - Limit the scope to a specific depot or directory. - - - 1656 - - - - The scope of the diff is unsupported. - - - 1659 - - - - Invalid file revision. - - - 1662 - - - - Cannot start the diff application. - - - 1665 - - - - Cannot diff a deleted file or folder. - - - 1668 - - - - The file is synced, but not on the local disk. - - - 1671 - - - - The server does not recognize the path. - - - 1674 - - - - The path does not exist on the local disk. - - - 1677 - - - - The path is recognized as both a file and folder. - - - 1680 - - - - The files are identical. - - - 1686 - - - - The following two files are identical: - -%1 -%2 - - - 1693 - - - - Cannot diff a folder against a file. - - - 1699 - - - - The format of both paths must match (either depot or workspace syntax). - - - 1702 - - - - Current workspace view does not map path between server and local disk. - - - 1705 - - - - The diff was cancelled. - - - 1708 - - - - There was a problem creating the diff query. - - - 1715 - - - - There are not enough valid queries to diff. - - - 1718 - - - - A verification on the query must be run first. - - - 1721 - - - - The workspace must be valid to run this diff. - - - 1724 - - - - The file is open for add or branch. - - - 1727 - - - - No such changelist. - - - 1730 - - - - Encountered an unknown error. - - - 1734 - - - - %1 Diff - - - 1780 - - - - - - - - - - file.txt - - - 2798 - - - - Red text indicates files that were modified offline: - - - 2799 - - - - *If the file is read only or if the visual cue for modified files has been turned off in preferences, the icon will not appear blue. - - - 2864 - - - - - - Workspace location: - - - 1763 - - - - Depot location: - - - 1766 - - - - Revision: - - - 1769 - - - - Date modified: - - - 1772 - - - - File size: - - - 1775 - - - - Type: - - - 1778 - - - - Perforce filetype: - - - 1781 - - - - Reconcile options: - - - 1784 - - - - #%1 of %2 - - - 1856 - - - - The file was created locally and has not been marked for add - - - 2079 - - - - Mark for add - - - 2080 - - - 2086 - - - 2105 - - - - if you intend to add the file to the depot - - - 2080 - - - - Delete local file - - - 2081 - - - 2087 - - - - to remove the file from the workspace - - - 2081 - - - 2087 - - - 2106 - - - - The file has the same name as a deleted depot file - - - 2085 - - - - if you intend to re-add the file to the depot - - - 2086 - - - 2105 - - - - The file was modified without having been checked out - - - 2091 - - - - Check out - - - 2092 - - - 2122 - - - - if you intend to submit the changes to the depot - - - 2092 - - - 2122 - - - - Get revision - - - 2093 - - - 2099 - - - - (using force option) to replace the file with a copy from the depot - - - 2093 - - - - The depot file is missing from the workspace - - - 2097 - - - - Mark for delete - - - 2098 - - - - if you intend to delete the file from the depot - - - 2098 - - - - (using force option) to restore the depot file in the workspace - - - 2099 - - - - Remove from workspace - - - 2100 - - - - to notify the Perforce Server that you have deleted the file from the workspace - - - 2100 - - - - The file has been deleted from the depot - - - 2104 - - - - Get latest revision - - - 2106 - - - - The file was marked for add and is no longer in the workspace - - - 2110 - - - - Revert - - - 2111 - - - 2116 - - - 2123 - - - 2128 - - - - the file to notify the Perforce Server that you no longer plan to add it to the depot - - - 2111 - - - - The file was checked out and is no longer in the workspace - - - 2115 - - - - the file to restore the file in the workspace. After the file is restored you can then check the file out, mark it for delete or remove it from the workspace - - - 2116 - - - - The file was modified after having been marked for branch or integrate - - - 2121 - - - - the file to discard the modifications and undo the pending branch or integration action - - - 2123 - - - - The file was marked for delete, but still exists in your workspace - - - 2127 - - - - the file to discard any modifications and undo the pending delete action - - - 2128 - - - - - - No preview available because the file is empty or the size is unavailable from the server - - - 2458 - - - - No preview available because the file size is greater than the limit specified in the preferences. - - - 2463 - - - - No preview available for this item. - - - 2612 - - - - Revision: - - - 2654 - - - - File: - - - 2659 - - - - workspace version - - - 2700 - - - 2710 - - - - - - Revision: - - - 1074 - - - - Date submitted: - - - 1077 - - - - Changelist: - - - 1080 - - - - Submitted by: - - - 1083 - - - - Description: - - - 1086 - - - - - - Workspace versions of files - - - 1373 - - - - Labeled revisions - - - 1376 - - - - Revisions in changelist - - - 1378 - - - - No Files Found - - - 1463 - - - - Files - - - 1543 - - - 1673 - - - - form - - - 1694 - - - - - - Access type: - - - 3011 - - - - Edit - - - 2951 - - - - - - Revision: - - - 297 - - - - Date submitted: - - - 300 - - - - Changelist: - - - 303 - - - - Submitted by: - - - 306 - - - - Perforce filetype: - - - 309 - - - - Action: - - - 312 - - - - File size: - - - 315 - - - - Workspace: - - - 318 - - - - Description: - - - 321 - - - - - - Revision: - - - 749 - - - - This revision not used in any integrations - - - 763 - - - - Sources (contributing content to this revision) - - - 764 - - - - Targets (receiving content from this revision) - - - 765 - - - - - - Revision: - - - 891 - - - - - - restricted - - - 3186 - - - - public - - - 3186 - - - - Changelist: - - - 3070 - - - - Workspace: - - - 3073 - - - - Date submitted: - - - 3076 - - - - Submitted by: - - - 3079 - - - - Access type: - - - 3082 - - - - Description: - - - 3085 - - - - - - - - - - Stopped diff. - - - 441 - - - - - - - - - - Change %1: File Diffs - - - 1062 - - - - Expand All - - - 1095 - - - - Collapse All - - - 1131 - - - - E&xpand All - - - 1141 - - - - - - Changelist: - - - 157 - - - - Workspace: - - - 166 - - - - Date submitted: - - - 148 - - - - Submitted by: - - - 175 - - - - R&estrict access to changelist - - - 182 - - - - <a href>(more info)</a> - - - 186 - - - - Access type: - - - 193 - - - - Description: - - - 217 - - - - Files: - - - 235 - - - - File Name - - - 249 - - - - Show File Diffs - - - 287 - - - - J&ob: - - - 383 - - - - &Add - - - 391 - - - - B&rowse... - - - 397 - - - - &Status for newly added jobs: - - - 413 - - - - form - - - 530 - - - - %1 does not exist. - - - 660 - - - - restricted - - - 915 - - - - public - - - 915 - - - - - - - - - - File Path Builder - - - 47 - - - - File: - - - 67 - - - - &Folder: - - - 60 - - - - &Browse... - - - 62 - - - - &Include subfolders - - - 64 - - - - &All files - - - 76 - - - - File &name - - - 78 - - - - Revision range: - - - 93 - - - - Path preview: - - - 108 - - - - Cancel - - - 116 - - - - OK - - - 119 - - - - starts with - - - 178 - - - - ends with - - - 184 - - - - contains - - - 190 - - - - is - - - 196 - - - - - - All r&evisions - - - 438 - - - - Re&visions starting at: - - - 443 - - - - Revisions &up to: - - - 447 - - - - Revisions fro&m/to: - - - 451 - - - - B&rowse... - - - 455 - - - - Br&owse... - - - 458 - - - - Bro&wse... - - - 461 - - - - Brow&se... - - - 464 - - - - %1,%2 - - - 573 - - - - %1 and %2 - - - 610 - - - - revision %1 - - - 667 - - - - change number %1 - - - 669 - - - - workspace %1's have revision - - - 672 - - - - or on %1 - - - 674 - - - - label %1 - - - 676 - - - - - - - - - - match any of the following file paths: - - - 35 - - - 1407 - - - 1767 - - - - are - - - 36 - - - - File paths - - - 37 - - - - Name - - - 1764 - - - 2164 - - - - is not - - - 2005 - - - - does not start with - - - 2012 - - - - starts with - - - 2014 - - - - does not end with - - - 2019 - - - - ends with - - - 2021 - - - - contains - - - 2028 - - - - word starting with - - - 2041 - - - - word ending with - - - 2043 - - - - Parent - - - 2167 - - - - is - - - 2007 - - - - Any Field - - - 911 - - - - does not contain - - - 2032 - - - - the exact word - - - 2039 - - - - Directory Name - - - 2161 - - - - - - and - - - 455 - - - - Match case for %1 name - - - 456 - - - %1 is object display name - - - - - - - - - - &Add... - - - 132 - - - - &Remove - - - 133 - - - - &Specify revision using: - - - 136 - - - - No Files. - - - 190 - - - - Add Files/Folders - - - 481 - - - - - - - - - - Loading... - - - 550 - - - - Setting up... - - - 703 - - - - &Previous - - - 785 - - - - &Next - - - 789 - - - - Diff: - - - 779 - - - - Folder Diff Toolbar - - - 599 - - - - Show files in a tree hierarchy - - - 794 - - - - Show files in a flat list - - - 796 - - - - Help - - - 803 - - - - Unique files: - - - 815 - - - - File differences: - - - 821 - - - - Move files: - - - 827 - - - - Show identical file pairs - - - 907 - - - - Show files without counterparts (unique) - - - 916 - - - - Show file pairs with content differences - - - 926 - - - - Show moved or renamed files - - - 936 - - - - Print List of Diffs - - - 972 - - - - Show only offline work - - - 1068 - - - - <font color="grey">none applied</font> - - - 1145 - - - - Filter - - - 1147 - - - - Filter... - - - 1269 - - - - Apply filter - - - 1270 - - - - Manage Filters... - - - 1273 - - - - Organize the saved Filters - - - 1274 - - - - Diff: %1 of %2 - - - 1344 - - - - Unique files: %1 - - - 1345 - - - - File differences: %1 - - - 1346 - - - - Moved files: %1 - - - 1347 - - - - Folder Diff - - - 1397 - - - - Mapping %1 - - - 1422 - - - - (@shelved changelist %1) - - - 1528 - - - - (rev %1) - - - 1503 - - - - (@label %1) - - - 1508 - - - - (@changelist %1) - - - 1513 - - - - (@date %1) - - - 1518 - - - - (@workspace %1) - - - 1523 - - - - (workspace rev) - - - 1487 - - - - Clear Filter - - - 953 - - - - Folder Diff %2 (%1) - - - 1425 - - - Folder Diff window title. %1=config serialized string. %2=branch mapping name (usually empty/blank) - - - - (have rev) - - - 1495 - - - - (latest rev) - - - 1498 - - - - - - Apply filter - - - 1002 - - - - - - - - - - Revision (Changelist) - - - 352 - - - - - - Folder History - - - 583 - - - - - - Tear off this revision history - - - 829 - - - - File: - - - 937 - - - - Show files in a list or as thumbnails - - - 809 - - - - Show branch history - - - 845 - - - - Disabled - - - 854 - - - - Follow Branch Actions - - - 855 - - - - Follow Both Branch and Copy Actions - - - 856 - - - - Folder: - - - 921 - - - - %1 (%2) - - - 1027 - - - - %1 - %2 (%3) - - - 1041 - - - - Show Files As - - - 1436 - - - - - - - - - - Save Diff to Text File - - - 1940 - - - - unknown fstat error, perhaps caused by a toad or a small dwarf living in the fstat agent. - - - 3016 - - - - - - Path: - - - 5776 - - - - Unique files: - - - 5779 - - - - - - - - - - Back out - - - 1419 - - - 1422 - - - - Do you want to create a new pending changelist?<br><br>If you do not create a new changelist, the files with backed out changes will be placed in the default changelist. - - - 1479 - - - - Do you want to create a new pending changelist?<br><br>If you do not create a new changelist, the file with the backed out revision will be placed in the default changelist. - - - 1483 - - - - Back out changelist %1 - - - 1529 - - - - Back out revision %1 from %2 - - - 1534 - - - - Back out operation could not be performed. One or more files are not mapped to your workspace view - - - 1569 - - - - - - Are you sure you want to permanently delete the selected local files? - - - 1320 - - - - Are you sure you want to permanently delete file '%1'? - - - 1322 - - - - - - You are about to unlock a file locked by '%1'. Are you sure you want to continue? - - - 152 - - - - You are about to unlock one or more files locked on another workspace. Are you sure you want to continue? - - - 157 - - - - Do you want to unlock only your locked files, or all locked files in this folder? - - - 183 - - - - &My Files - - - 192 - - - - &All Files - - - 194 - - - - - - &Move All Files to Another Changelist... - - - 266 - - - - Cannot check out "exclusive open" file(s). - -One or more of the selected files are checked out by another user. -Files that have been assigned the "exclusive open" attribute cannot -be checked out by multiple users simultaneously. - - - - 733 - - - - Do you want to add the folder '%1' as a symlink file, or do you want to add the contents of the folder to which this symlink points? - - - 907 - - - - Add &Symlink - - - 914 - - - - Add &Contents - - - 917 - - - - Cancel - - - 920 - - - - - - Your workspace is currently set to changelist %1. - - - 1168 - - - - Do you want to get revisions at this changelist or get the latest revisions of all stream files? - - - 1171 - - - - Get Revisions at Change - - - 1174 - - - - Get Latest Revisions - - - 1176 - - - - - - - - - - You must remove all checked out files from the changelist before changing its ownership. - - - 91 - - - - The ownership of Pending Changelist '%1' cannot be changed because the changelist is currently being edited. - - - 113 - - - - - - - - - - Choose the location in the depot where the folder "%1" will be added: - - - 39 - - - - Choose the location in the depot where the file "%1" will be added: - - - 40 - - - - - - Don't prompt me to get latest revision - - - 98 - - - - You do not have the latest revision for all the files selected. - -Do you want to get the latest revision of those files before checking them out? - - - 102 - - - - You do not have the latest revision of the file. - -Do you want to get the latest revision before checking it out? - - - 105 - - - - &Get Latest - - - 130 - - - - Cancel - - - 132 - - - - &Don't Get Latest - - - 134 - - - - - - check out - - - 197 - - - - Check out - - - 198 - - - - add - - - 201 - - - - Add - - - 202 - - - - delete - - - 206 - - - - Delete - - - 207 - - - - reopen - - - 211 - - - - Reopen - - - 212 - - - - At least one of the files you are attempting to %1 is imported -and cannot be submitted from your current workspace. - -Do you want to proceed with the %1? Or only %1 the non-imported files? - - - 231 - - - - Do not show again - - - 236 - - - - %1 all files - - - 242 - - - - %1 non-imported files - - - 243 - - - - Cancel - - - 244 - - - - - - - - - - Connection Error - - - 107 - - - 186 - - - - User '%1' does not exist. - - - 149 - - - - Workspace '%1' does not exist - - - 152 - - - - The security level of server '%1' requires the password for user '%2' to be reset. - - - 179 - - - - The security level of server '%1' requires that a password be set for user '%2'. - - - 183 - - - - - - - - - - Open With - - - 427 - - - - Show In - - - 545 - - - - Remove From Group - - - 1062 - - - 1347 - - - - Change Password... - - - 1095 - - - - Show Permissions - - - 1105 - - - 1381 - - - - - - - - - - updating... - - - 234 - - - - list - - - 905 - - - - - - - - - - Delete %1 Form - - - 203 - - - - You are about to delete %1 '%2' from the server. -Are you sure you want to continue? - - - - 204 - - - - Delete %1 Forms - - - 228 - - - - You are about to delete selected %1s from the server. -Are you sure you want to continue? - - - - 229 - - - - deleted - - - 82 - - - - Only empty Pending Changelists will be deleted from the server. -Are you sure you want to continue? - - - - 184 - - - - There was a problem deleting the shelve, please be sure you have permission to delete the shelve. - - - 140 - - - - Delete Pending Changelist Form - - - 183 - - - - - - - - - - The folder %1 is not located within the filtered view of the tree. - - - 854 - - - - The selected items are not located within the filtered view of the tree. - - - 861 - - - - - - - - - - Perforce command run: - - - 507 - - - - &Ignore additional errors for this command - - - 518 - - - - - - -Password not valid, please enter the correct password: - - - 83 - - - - -Please enter the password: - - - 85 - - - - Unable to connect to the server %1 as user '%2' - -Try reconnecting to the server? - - - 185 - - - - &Reconnect - - - 193 - - - - &Close P4V - - - 194 - - - - -'%1' validation failed: %2 - - - - 72 - - - - A password is required for user '%1' on server '%2'. -%3 - - - 77 - - - - - - Perforce Password Required - - - 363 - - - - OK - - - 385 - - - - Cancel - - - 386 - - - - - - Perforce Fingerprint Required - - - 285 - - - - ***WARNING: It is possible that someone is intercepting your connection.*** - - - 305 - - - - The fingerprint send by the server ( %1 ) does not match the fingerprint you previously trusted. - - - 309 - - - - The fingerprint of the public key sent by the server is -%1 - - - 312 - - - 325 - - - - &Trust this fingerprint for future connections - - - 315 - - - - The authenticity of the server ( %1 ) can't be established. - - - 321 - - - - &Trust this fingerprint - - - 328 - - - - Connect - - - 332 - - - - Cancel - - - 333 - - - - - - - - - - Form - - - 107 - - - - Workspace Root: - - - 132 - - - - Browse... - - - 141 - - - - Select root directory - - - 336 - - - - - - - - - - Submitting files to the stream restricted to stream owner - - - 486 - - - - Locked (only the stream owner can edit stream settings) - - - 487 - - - - Allow change propagation to parent - - - 488 - - - - Allow change propagation from parent - - - 489 - - - - Unable to create a %1 named "%2", because "%3" is already in use as a name for a %4. - - - 1265 - - - - Branch Mapping - - - 1478 - - - - %1: Invalid ID - - - 1480 - - - - The %1 name may not be empty. - - - 1485 - - - - The name may not begin with a '-' character. - - - 1493 - - - - Illegal, non-alphanumeric characters detected in the %1 name. - - - - 1508 - - - - The %1 name must contain at least one non-numeric character. - - - 1518 - - - - Whitespace is not allowed in the %1 name. -Do you want the spaces fixed automatically? - - - - 1527 - - - - - - - - - - Change Password - - - 40 - - - 46 - - - 54 - - - - The security level of this server requires the password to be reset. - - - 41 - - - - Set Password - - - 50 - - - - The security level of this server requires that a password be set. - - - 51 - - - - Your password has expired, please change your password. - - - 55 - - - - Enter &old password: - - - 100 - - - - &Enter new password: - - - 117 - - - - &Confirm new password: - - - 128 - - - - - - - - - - Identical Files - - - 230 - - - - - No differences found for the following %n file comparison(s): - No differences found for the following %n file comparisons: - - - No differences found for the following %n file comparison(s): - No differences found for the following %n file comparisons: - - - 234 - - - - File 1 - - - 249 - - - - File 2 - - - 250 - - - - - - Reverting will overwrite your changes to the following files: - - - 308 - - - - Pending Action - - - 327 - - - - - - - - - - User name: %1 - - - 37 - - - - Workspace name: %1 - - - 38 - - - - Workspace host: %1 - - - 39 - - - - Workspace root: %1 - - - 45 - - - - Current directory: %1 - - - 51 - - - - Workspace address: %1 - - - 57 - - - - Unicode: %1 - - - 58 - - - - Charset: %1 - - - 59 - - - - Security: %1 - - - 60 - - - - Server Name: %1 - - - 61 - - - - Server description: %1 - - - 62 - - - - Server address: %1 - - - 63 - - - - Server root: %1 - - - 64 - - - - Server date: %1 - - - 65 - - - - Server uptime: %1 - - - 66 - - - - Server version: %1 - - - 67 - - - - Authorization server: %1 - - - 68 - - - - Changelist server: %1 - - - 69 - - - - Server license: %1 - - - 70 - - - - Server license IP: %1 - - - 71 - - - - Password: %1 - - - 72 - - - - Monitor: %1 - - - 73 - - - - Proxy Version: %1 - - - 74 - - - - Broker Address: %1 - - - 75 - - - - Broker Version: %1 - - - 76 - - - - Server case handling: %1 - - - 77 - - - - Minimum Client Level: %1 - - - 78 - - - - Message text for clients that are too old: %1 - - - 79 - - - - Workspace unknown.%1 - - - 122 - - - - - - - - - - Depot Tree - - - 62 - - - - Client Expression - - - 67 - - - - - - - - - - New Depot - - - 36 - - - - Enter a name for the new depot: - - - 39 - - - - Name already in use as a depot. - - - 128 - - - - Unable to create a depot named "%1", because it is already in use as a name for a %2. - - - 161 - - - - Unable to create a depot named '%1'. - - - - 167 - - - - Depot '%1' already exists. - - - 172 - - - - - - - - - - %1 New Folder - - - 127 - - - %1="P4V" or other application name - - - - OK - - - 133 - - - - Cancel - - - 134 - - - - Enter a name for the new folder - - - - 137 - - - - Could not create new folder. - - - 212 - - - - The folder "%1" already exists. - - - 217 - - - - The file "%1" already exists. - - - 222 - - - - - - - - - - The following file is already checked out - - - 53 - - - - The following files are already checked out - - - 55 - - - - %1 operation cannot be performed - - - 60 - - - - Warning: The following file is checked out on another workspace - - - 74 - - - - Warning: The following files are checked out on another workspace - - - 76 - - - - Warning: The following %1 files are a subset of those checked out on another workspace - - - 78 - - - - Do you want to proceed with the %1 operation? - - - 84 - - - - The changelist you are trying to %1 won't be checked for files that are currently checked out because it contains too many files. - -Proceed with the '%1' anyway? - - - 269 - - - - Back out - - - 275 - - - - Back Out - - - 276 - - - - %1 by %2@%3 - - - 370 - - - %1=file path. %2=user name. %3=workspace name - - - - - - - - - - The new password and confirm new password fields do not match. -Please re-enter your new password. - - - 41 - - - - Passwords must be at least eight characters long and must contain upper and lower case letters, or letters plus one or more symbols or numbers. - - - 60 - - - - - - - - - - Select Pending Changelist - - - 50 - - - 63 - - - - &Move selected files to pending changelist - - - 49 - - - - Files in the default changelist cannot be shelved without -first moving them to a new numbered changelist. - -New changelist description: - - - 55 - - - - &Add files to pending changelist: - - - 62 - - - - &Changelist description - - - 80 - - - - &Don't show this dialog again (always use default changelist) - - - 91 - - - - &Move - - - 103 - - - - - - - - - - Shelved Files in - - - 72 - - - - - - - - - - Delete or Replace Writable Files - - - 67 - - - - The following files are writable and may contain edits. The requested operation will delete or replace these files. - - - 71 - - - - Select the files you want to update: - - - 76 - - - - C&ontinue - - - 132 - - - - Unchec&k all - - - 135 - - - 298 - - - - Chec&k all - - - 293 - - - - - - - - - - Are you sure you want to delete all shelved files in pending changelist '%1'? - - - 436 - - - - Are you sure you want to delete shelved file '%1'? - - - 467 - - - - Are you sure you want to delete the selected shelved files? - - - 471 - - - - Unable to delete the following shelved files: - - - 529 - - - - No files found that can be shelved. - - - 573 - - - - - - - - - - No Items Available - - - 127 - - - - There are %1 revisions in this label. - - - 130 - - - %1=number of revisions - - - - No files available from server. The changelist contains no files (obliterated) or you are not allowed to view them (protections). - - - 136 - - - - There are %1 files in this changelist. - - - 140 - - - %1=number of files - - - - %1 (Not In Depot) - - - 145 - - - %1=file path - - - - Sort Files By - - - 666 - - - - - - - - - - Special Edit of View Map - - - 31 - - - - Workspace Path - - - 36 - - - - Depot Path - - - 43 - - - - Enter an expression - - - 62 - - - - Enter a specific filename (for example, "name.doc") - - - 69 - - - - Enter an extension (for example, "html") - - - 121 - - - - //depot/files - - - 191 - - - - Files '*' - - - 192 - - - - Tree '...' - - - 193 - - - - File - - - 194 - - - - File Extension - - - 195 - - - - Expression - - - 196 - - - - Cancel - - - 197 - - - - Save - - - 198 - - - - Include - - - 199 - - - - Exclude - - - 200 - - - - Error - - - 298 - - - 303 - - - 310 - - - - Please enter a valid text string - - - 298 - - - - No wildcards are allowed! - - - 303 - - - - Both fields must contain text - - - 310 - - - - - - - - - - Info - - - 874 - - - - $APP could not add 1 item. - - - 1031 - - - - $APP could not add %n items. - - - 1032 - - - - $APP can only accept files and folders that are under the workspace root: $WORKSPACE_ROOT - - - 1033 - - - - ...and %1 other items. - - - 1038 - - - - Unable to unshelve the following files: - - - 1877 - - - - Unable to shelve files: - - - 2025 - - - - - - - - - - none found - - - 102 - - - - Name: - - - 150 - - - - Host: - - - 151 - - - - Port: - - - 152 - - - - IP address: - - - 153 - - - - Version: - - - 154 - - - - Root: - - - 155 - - - - Monitoring: - - - 156 - - - - Unicode support: - - - 157 - - - - Journal number: - - - 158 - - - - Uptime: - - - 159 - - - - Authentication server: - - - 160 - - - - Changelist server: - - - 161 - - - - Create new user - - - 183 - - - - Create new group - - - 184 - - - - Load new license file - - - 185 - - - - Display permissions for users and groups - - - 186 - - - - Alerts displayed on the Administration Home page: - - - 256 - - - - P4 process has been running for: - - - 259 - - - - minutes - - - 281 - - - - Support will expire in: - - - 285 - - - - days - - - 293 - - - - Unused user licenses remaining: - - - 297 - - - - Cancel - - - 313 - - - - Save - - - 314 - - - - <b><font size="+0">Alerts!</font></b> - - - 328 - - - 814 - - - - <b><font size="+0">Server Info</font></b> - - - 382 - - - 1750 - - - - Files - - - 494 - - - - Sizes - - - 495 - - - - Location - - - 496 - - - - <b><font size="+0">Disk Space Usage</font></b> - - - 512 - - - - <i><font size="+0">Security is determined by the authentication trigger(s) in use.<br><br>See the enabled triggers in the Triggers table below.</font></i> - - - 530 - - - - <b><font size="+0">Security Level</font></b> - - - 537 - - - 795 - - - - <b><font size="+0">Account Management Quicklinks</font></b> - - - 575 - - - - Licenses in use: - - - 600 - - - - Remaining licenses: - - - 613 - - - - License total: - - - 624 - - - - <b><font size="+0">User Licenses</font></b> - - - 660 - - - - Users who have not accessed their account in more than: - - - 670 - - - - 2 years - - - 673 - - - - 1 year - - - 674 - - - - 6 months - - - 675 - - - - 2 months - - - 676 - - - - 1 month - - - 677 - - - - 2 weeks - - - 678 - - - - 1 week - - - 679 - - - - <b><font size="+0">Inactive Users</font></b> - - - 711 - - - 840 - - - - <b><font size="+0">Triggers</font></b> - - - 726 - - - 860 - - - - <b><font size="+0">Security Level</font></b>&nbsp;&nbsp;(authentication trigger(s) in use) - - - 802 - - - - <b><font size="+0">Security Level</font></b>&nbsp;&nbsp;(level - - - 806 - - - - ) - - - 806 - - - - <b><font size="+0">Triggers</font></b>&nbsp;&nbsp; - - - 867 - - - - - (%n triggers in use) - - - - (%n triggers in use) - - - - 868 - - - - %1 day, %2 hrs - - - 1798 - - - - %1 days, %2 hrs - - - 1799 - - - - Only %1 unused user licenses remain - - - 2071 - - - - (no license file) - - - 1845 - - - - (support renewal date: - - - 1859 - - - - (support expired: - - - 1861 - - - - (expiration date: - - - 1863 - - - - (see authentication server) - - - 1867 - - - - (Error reading license information) - - - 1872 - - - - Your support has expired - - - 1896 - - - - <b><font size="+0">User Licenses</font></b>&nbsp;&nbsp; - - - 1905 - - - - enabled - - - 1267 - - - - Server Disk* - - - 406 - - - - Disk in use: - - - 424 - - - - Remaining space: - - - 438 - - - - Total disk size: - - - 451 - - - - * Disk where server root is located - - - 463 - - - - database tables - - - 473 - - - - journal - - - 474 - - - - log - - - 475 - - - - audit log - - - 476 - - - - (1 alert) - - - 822 - - - - - (%n alerts) - (%n alerts) - - - (%n alerts) - (%n alerts) - - - 823 - - - - <b><font size="+0">Alerts!</font></b>&nbsp;&nbsp; - - - 825 - - - - <b><font size="+0">Inactive Users</font></b>&nbsp;&nbsp; - - - 853 - - - 2102 - - - - - (%n inactive users) - (%n inactive users) - - - (%n inactive users) - (%n inactive users) - - - 854 - - - 2103 - - - - disabled - - - 1269 - - - 2178 - - - 2197 - - - - Administration Home last updated: %1 - - - 1752 - - - - - Your support will expire in %n day(s) - Your support will expire in %n days - - - Your support will expire in %n day(s) - Your support will expire in %n days - - - 1894 - - - - - Process ID %1 has been running for over %n minute(s) - Process ID %1 has been running for over %n minutes - - - Process ID %1 has been running for over %n minute(s) - Process ID %1 has been running for over %n minutes - - - 1983 - - - - The current permission settings allow new users to create their own accounts. (To disable automatic user creation, see the System Administrator's Guide) - - - 2265 - - - - The current permission settings do not allow new users to create their own accounts. - - - 2271 - - - - No unused user licenses remain - - - 2069 - - - - unknown - - - 2162 - - - 2185 - - - - enabled (excluding idle processes) - - - 2180 - - - - enabled (including idle processes) - - - 2182 - - - - 0 - - - 2194 - - - - - - - - - - none - - - 103 - - - 113 - - - - - - - - - - does not exist - - - 1351 - - - 1362 - - - - - - <nobr><b>Source:</b> %1</nobr> - - - 553 - - - - Cannot rename %1: A file with the name you specified already exists. Specify a different name. - - - 623 - - - - - - - - - - Source - - - 605 - - - - Target - - - 606 - - - - - - Base path: - - - 160 - - - 434 - - - - You must enter a depot path. - - - 229 - - - - The following directories are local only and will not be integrated:<br> - - - 369 - - - - - - - - - - (To reverse the direction of integration, click the arrow) - - - 63 - - - - Branch ma&pping: - - - 43 - - - - &Browse... - - - 51 - - - - &New... - - - 55 - - - - &Edit View - - - 67 - - - - Diff Vie&w - - - 72 - - - - - - - - - - No Items Found - - - 78 - - - - - - - - - - Integrate - - - 92 - - - - &Options: - - - 122 - - - - (Current settings: - - - 125 - - - - Filter - - - 175 - - - - Set Defaults... - - - 186 - - - - Cancel - - - 188 - - - - Description cannot be set on the "default" changelist - - - 436 - - - - Merging %1 to %2 - - - 460 - - - - Copying %1 to %2 - - - 463 - - - - Branching %1 to %2 - - - 467 - - - - Integrating %1 to %2 - - - 471 - - - - Merging using %1 - - - 479 - - - - Copying using %1 - - - 482 - - - - Branching using %1 - - - 486 - - - - Integrating using %1 - - - 490 - - - - Merging - -%1 - - - 503 - - - - - -to %1 - - - 505 - - - 510 - - - 516 - - - 522 - - - - Copying - -%1 - - - 508 - - - - Branching - -%1 - - - 514 - - - - Integrating - -%1 - - - 520 - - - - The source and target are identical, nothing will be branched. Change the target path. - - - 1086 - - - - merging - - - 1283 - - - - copying - - - 1286 - - - - branching - - - 1289 - - - - integrating - - - 1292 - - - - Cannot use %1 method "stream to stream" when %2 a 'task' stream to/from a stream other than its own parent. - - - 1296 - - - - The operation cannot be completed because there is no common source path. We recommend you create a branch mapping for this operation. - - - 1340 - - - - The operation cannot be completed because you cannot limit to and from a label. - - - 1419 - - - - You are about to merge in both directions of your branch mapping. This is because the path you are filtering with is broader than your branch mapping. - -Do you want to continue? - - - 1440 - - - - &Continue - - - 1441 - - - - C&ancel - - - 1442 - - - - You need to use the Merge/Integrate dialog instead of Branch to branch files to an existing location. - - - 1556 - - - - Integration errors: - - - 1581 - - - - Files are checked out in the workspace. Check in, revert, or use another workspace to copy files to target. - - - 1644 - - - - There are a total of %1 errors. - - - - 1664 - - - 1712 - - - - Errors: - - - - 1735 - - - - Branched files: - - - - 1742 - - - - Integrate preview - - - 1815 - - - - Submit of changelist #%1 failed - - - 1981 - - - - because the files are opened in another changelist. - - - 1985 - - - - . %1 - - - 1987 - - - - Preview change&lists - - - 2039 - - - - Resolve and Submit - - - 170 - - - - Submit - - - 172 - - - - Advanced - - - 178 - - - - &Preview - - - 193 - - - - Not all files were resolved. The merged files are in changelist #%1. - - - 1905 - - - - Files will not be submitted. - - - 1908 - - - - - - - - - - &Remove - - - 66 - - - - Branch only the following files/folders: - - - 100 - - - - Merge only the following files/folders: - - - 104 - - - - Copy only the following files/folders: - - - 108 - - - - Source files/folders: - - - 53 - - - 112 - - - - &Add... - - - 65 - - - - - - - - - - Filter method: - - - 38 - - - - Revision range and &files/folders - - - 42 - - - - so&urce - - - 79 - - - - tar&get - - - 80 - - - - Use files as copy - - - 160 - - - - Use files as branch - - - 164 - - - - Se&lected changelists - - - 221 - - - - Se&lected range of changelists - - - 223 - - - - Use files as merge - - - 155 - - - - - - - - - - &From: - - - 71 - - - - &To: - - - 76 - - - - Latest - - - 105 - - - - Specify revision using - - - 106 - - - - Rev&isions to branch: - - - 107 - - - - All revisions - - - 111 - - - - Revisions up to - - - 112 - - - - Rev&isions to merge: - - - 116 - - - - Revisions equal to - - - 117 - - - 127 - - - - Revisions from/to - - - 118 - - - - Rev&isions to copy: - - - 122 - - - - Rev&isions to integrate: - - - 126 - - - - Revisions including - - - 128 - - - - %1 and %2 - - - 245 - - - - revision %1 - - - 303 - - - - change number %1 - - - 305 - - - - workspace %1's have revision - - - 308 - - - - or on %1 - - - 310 - - - - label %1 - - - 312 - - - - - - - - - - Stream to stream - - - 17 - - - - Specify source and target files - - - 19 - - - - Use branch mapping - - - 21 - - - - Merge metho&d: - - - 88 - - - - Copy metho&d: - - - 92 - - - - Branch metho&d: - - - 96 - - - - - - - - - - &Do not copy newly branched target files to workspace (-v) - - - 32 - - - - &Enable baseless merges (-i) - - - 34 - - - - E&nable integrations around deleted revisions (-d) - - - 37 - - - - &Integrate over deleted targets (-Dt) - - - 40 - - - - Delete t&arget file when source is deleted (-Ds) - - - 42 - - - - &Try to integrate changes when source deleted and re-added (-Di) - - - 44 - - - - &Force integration on all revisions, disregarding integration history (-f) - - - 47 - - - - Explicitly create revisions for all files, even those without integration history (-f) - - - 49 - - - - Do not get latest re&vision of selected files (-h) - - - 51 - - - - Propagate source filet&ypes to target files (-t) - - - 55 - - - - S&chedule 'branch resolves' instead of branching new target files (-Rb) - - - 57 - - - - Sc&hedule 'delete resolves' instead of deleting target files (-Rd) - - - 59 - - - - S&kip previously merged cherry-picked revisions to improve merge results (-Rs) - - - 61 - - - - Dis&regard indirect integration history (-1) - - - 53 - - - - Check for opened files and &warn prior to merging (might affect server performance) - - - 63 - - - - - - - - - - Step %1 of %2: - - - 80 - - - - Validating %1 parameters - - - 85 - - - - Branching files - - - 89 - - - - Performing %1 - - - 91 - - - - Resolving files - - - 94 - - - - Submitting files - - - 97 - - - - merge - - - 113 - - - - copy - - - 115 - - - - branch - - - 117 - - - - integrate - - - 119 - - - - - - - - - - auto resolve, - - - 60 - - - - resolve later, - - - 65 - - - - all revisions - - - 44 - - - - filtered by files - - - 47 - - - - filtered by revisions - - - 50 - - - - filtered by revisions and files - - - 53 - - - - auto submit, - - - 73 - - - - submit later, - - - 78 - - - - , advanced options - - - 90 - - - - , force merge - - - 95 - - - - 1 entry falls outside the bounds of the branch view and will not be integrated - - - 111 - - - - - %n entries fall outside the bounds of the branch view and will not be integrated - %n entries fall outside the bounds of the branch view and will not be integrated - - - %n entries fall outside the bounds of the branch view and will not be integrated - %n entries fall outside the bounds of the branch view and will not be integrated - - - 115 - - - - Error: Source stream does not exist. Choose an existing source stream. - - - 125 - - - - Error: Branch mapping does not exist. Choose an existing branch mapping or create a new one. - - - 141 - - - - A source and target path must be selected. - - - 157 - - - - Error: At least one changelist must be selected. - - - 173 - - - - - - - - - - So&urce stream: - - - 42 - - - - Target stream: - - - 43 - - - - &Browse - - - 50 - - - - - - - - - - &Browse... - - - 45 - - - - Generate branch mapping from above paths for use in future integrations: - - - 51 - - - - Enter a name for the new branch mapping: - - - 152 - - - - &Save... - - - 49 - - - - Choose tar&get files/folders: - - - 40 - - - - Can't create a branch named %1, it's already a %2 - - - 205 - - - - A branch with that name already exists. - - - 245 - - - - - - - - - - Integration Preview - - - 104 - - - 283 - - - - %n branch - - - 196 - - - - %n branches - - - 202 - - - - %n integration - - - 208 - - - - %n integrations - - - 214 - - - - %n error - - - 220 - - - - %n errors - - - 226 - - - - Show or Hide Normal Integrations - - - 288 - - - - Show or Hide Branched Files - - - 294 - - - - Show or Hide Integration Errors - - - 300 - - - - Previous - - - 306 - - - - Next - - - 307 - - - - Previous Error - - - 310 - - - - Next Error - - - 311 - - - - Show files in a tree hierarchy - - - 314 - - - - Show files in a flat list - - - 315 - - - - - - - - - - &Font: - - - 25 - - - - Font: - - - 25 - - - - &Size: - - - 29 - - - - Size: - - - 29 - - - - Sample: - - - 48 - - - - abcdefghijklmnopqrstuvwxyz -ABCDEFGHIJKLMNOPQRSTUVWXYZ - - - 53 - - - - &Reset - - - 60 - - - - Select Font - - - 18 - - - - - - - - - - 1 file %1 - - - 814 - - - e.g. 1 file locked - - - - %1 files %2 - - - 816 - - - e.g. 2 files locked - - - - no files %1 - - - 818 - - - - - - %1 - moved from %2 - - - 737 - - - - no files moved - - - 753 - - - - 1 file moved - - - 756 - - - - %1 files moved - - - 760 - - - - - - {%1 more items} - - - 359 - - - - %1 warnings reported - - - 427 - - - - 1 warning reported - - - 429 - - - - %1 errors reported - - - 436 - - - - 1 error reported - - - 438 - - - - - - 1 file opened for %1 - - - 693 - - - - %1 files opened for %2 - - - 698 - - - - no files opened - - - 704 - - - - - - 1 file reverted - - - 626 - - - - %1 files reverted - - - 628 - - - - no files reverted (all files were either changed or opened for add, delete or branch) - - - 630 - - - - no files reverted - - - 632 - - - - - - Shelf promoted - - - 1116 - - - - 1 file shelved - - - 1121 - - - - %1 files shelved - - - 1123 - - - - 1 file unchanged, not shelved - - - 1126 - - - - %1 files unchanged, not shelved - - - 1128 - - - - no files shelved - - - 1119 - - - - - - Change - - - 846 - - - - created with %1 open file(s). - - - 848 - - - - -Locking %1 file(s)... - - - 849 - - - - renamed - - - 866 - - - - and submitted - - - 866 - - - - Submitted change - - - 868 - - - - 1 file added - - - 872 - - - - %1 files added - - - 874 - - - - 1 file edited - - - 877 - - - - %1 files edited - - - 879 - - - - 1 file branched - - - 882 - - - - %1 files branched - - - 884 - - - - 1 file integrated - - - 887 - - - - %1 files integrated - - - 889 - - - - 1 file deleted - - - 892 - - - - %1 files deleted - - - 894 - - - - no files submitted - - - 901 - - - - - - 1 file updated - - - 569 - - - - %1 files updated - - - 571 - - - - 1 file added - - - 574 - - - - %1 files added - - - 576 - - - - 1 files removed - - - 579 - - - - %1 files removed - - - 581 - - - - no files updated - - - 584 - - - - - - 1 file updated - - - 972 - - - - %1 files updated - - - 974 - - - - 1 files added - - - 977 - - - - %1 files added - - - 979 - - - - 1 files removed - - - 982 - - - - %1 files removed - - - 984 - - - - no files updated, added or removed - - - 987 - - - - - - %1 unshelved - - - 1194 - - - 1207 - - - - 1 file unshelved - - - 1214 - - - - %1 files unshelved - - - 1216 - - - e.g. 2 files unshelved - - - - no files unshelved - - - 1218 - - - - - - - - - - Show Folder "%1" in Depot Tree - - - 471 - - - - Show Folder "%1" in Workspace Tree - - - 477 - - - - Show File "%1" in Depot Tree - - - 485 - - - - Show File "%1" in Workspace Tree - - - 491 - - - - Go to Changelist "%1" - - - 520 - - - - Go to Workspace "%1" - - - 527 - - - - Go to Job "%1" - - - 534 - - - - Go to User "%1" - - - 541 - - - - Copy - - - 774 - - - - Select All - - - 775 - - - - - - - - - - &Open Connection... - - - 162 - - - - Open a new connection by choosing the P4 settings - - - 163 - - - - &Add Favorite Connection... - - - 164 - - - - Add the current connection to the Connection list. - - - 165 - - - - &Manage Favorites... - - - 166 - - - - Open the Connection Editor. - - - 167 - - - - &Set Up Connection... - - - 169 - - - - Launches the Connection Wizard. - - - 170 - - - - En&vironment Settings... - - - 172 - - - - Launches Environment Dialog. - - - 173 - - - - Manage &Custom Tools... - - - 175 - - - - Customize tools for use - - - 176 - - - - &Go To... - - - 178 - - - - Open a specific form by name - - - 179 - - - - Unloaded content - - - 182 - - - - Display unloaded form - - - 183 - - - - &Manage Bookmarks... - - - 204 - - - - Manager bookmarks for this client - - - 205 - - - - &Administration - - - 207 - - - - Display Administration Tool - - - 208 - - - - &Close Connection - - - 210 - - - - Close the connection window and associated windows - - - 211 - - - - &Log Off - - - 212 - - - - Log out of session and close the connection window - - - 213 - - - - Reconnec&t - - - 214 - - - - Reconnect when disconnected - - - 215 - - - - # Integrate Preview - - - 218 - - - - Show Integrate Preview window - - - 219 - - - - Choose Character &Encoding... - - - 222 - - - - Choose character encoding - - - 223 - - - - &Branch Mappings - - - 299 - - - - View branch mappings - - - 300 - - - - &Streams - - - 326 - - - - View streams - - - 327 - - - - Ch&ange Password... - - - 224 - - - - Change the current server password - - - 225 - - - - &Tear Off - - - 227 - - - - Tear off pane into separate window - - - 228 - - - - &Address Bar - - - 231 - - - - Show/Hide the Address Bar - - - 232 - - - - Details &Pane - - - 233 - - - - Show/Hide the Details Pane - - - 234 - - - - Show/Hide the Tooltips - - - 238 - - - - &System Info - - - 241 - - - - Display system info in a window - - - 242 - - - - &What's New in P4V - - - 243 - - - - Show What's New in a tab - - - 244 - - - - &History - - - 295 - - - - Tab &Bars - - - 235 - - - - Show/Hide the Tab Bars - - - 236 - - - - T&ooltips - - - 237 - - - - &Dock Tab - - - 246 - - - - Dock Tab - - - 247 - - - - &Undock Tab - - - 248 - - - - Undock Tab - - - 249 - - - - Clo&se Tab - - - 250 - - - - Close Tab - - - 251 - - - - &Upload Files To Server... - - - 253 - - - - Launches the File Uploading Wizard. - - - 254 - - - - &Depot Tree - - - 282 - - - - View depot tree - - - 283 - - - - &Workspace Tree - - - 285 - - - - View workspace tree - - - 286 - - - - F&ind File... - - - 289 - - - - Search for file(s) - - - 290 - - - - Files i&n Folder - - - 292 - - - - View files in folder - - - 293 - - - - View the file revision history - - - 296 - - - - Workspa&ces - - - 302 - - - - View workspaces - - - 303 - - - - &Groups - - - 305 - - - - View groups - - - 306 - - - - &Jobs - - - 308 - - - - View jobs - - - 309 - - - - &Labels - - - 311 - - - - View labels - - - 312 - - - - &Pending Changelists - - - 314 - - - - View pending changelists - - - 315 - - - - &Submitted Changelists - - - 317 - - - - View submitted changelists - - - 318 - - - - Lo&g - - - 320 - - - - View Log Pane - - - 321 - - - - D&ashboard - - - 323 - - - - View Dashboard Pane - - - 324 - - - - - - - - - - Merge/Integrate - - - 15 - - - - &Merge - - - 16 - - - - - - - - - - Merge Warnings - - - 31 - - - - To avoid overwriting your edits, shelve or submit these files before merging - - - 41 - - - - No Items Found - - - 54 - - - - Don't warn me again about open files - - - 64 - - - - Cancel - - - 68 - - - - Continue with merge - - - 69 - - - - - - - - - - Process ID - - - 97 - - - - Client Program - - - 99 - - - - IP Address - - - 100 - - - - Status - - - 102 - - - - Owner - - - 103 - - - - Workspace - - - 105 - - - - Elapsed Time - - - 107 - - - - P4 Command - - - 108 - - - - Arguments - - - 109 - - - - &Show idle processes - - - 121 - - - - &Terminate Process - - - 127 - - - - Locked database tables - - - 137 - - - - Table - - - 142 - - - - Operation - - - 143 - - - - Write - - - 397 - - - - Read - - - 403 - - - - You are about to terminate process %1.<br>Are you sure you want to continue? - - - 460 - - - - - - Process Monitor (%1) - - - 43 - - - - - - - - - - Rename/Move operation could not be performed - - - 112 - - - 178 - - - 214 - - - - Rename/Move operation warning - - - 125 - - - - The new name chosen contains invalid characters - - - 179 - - - - The new location chosen contains invalid characters - - - 215 - - - - - - - - - - Rename/Move operation could not be performed - - - 539 - - - 553 - - - - The files/folders selected must be located within the same folder. - - - 540 - - - - None of the files are in the depot - - - 554 - - - - Rename/Move - - - 567 - - - 632 - - - - - - Rename/Move - - - 41 - - - - &Rename/move the file back to its original name and/or location - - - 52 - - - - Rename: - - - 59 - - - - Name: - - - 68 - - - - &New name: - - - 79 - - - - Move to: - - - 87 - - - - &Add files to pending changelist: - - - 118 - - - - The following files are deleted from the depot: - - - 271 - - - - Location: - - - 96 - - - - N&ew location: - - - 106 - - - - &Browse... - - - 110 - - - - Sub&mit... - - - 135 - - - - Cancel - - - 138 - - - - &Save to Changelist - - - 140 - - - - - - - - - - &Apply - - - 124 - - - - &Help - - - 125 - - - - Stream &name: - - - 142 - - - - &Stream type: - - - 155 - - - - mainline - serves as the base or trunk of a stream system - - - 166 - - - - development - used for long term projects and major new features - - - 167 - - - - release - used for fixing bugs, testing and release distribution - - - 168 - - - - virtual - used to narrow the scope and submit directly to parent - - - 171 - - - - task - creates lightweight branch, used for bug fixes and new features - - - 175 - - - - Change propagation: - - - 187 - - - 203 - - - 221 - - - 240 - - - 258 - - - - The mainline is the recipient of most changes made in development or release streams. - - - 188 - - - - &To parent: allow copies to the parent stream - - - 204 - - - 259 - - - - &From parent: allow merges from the parent stream - - - 208 - - - 263 - - - - &To parent: allow merges to the parent stream - - - 222 - - - - &From parent: allow copies from the parent stream - - - 226 - - - - Changes pass through the virtual stream to and from the real parent stream. - - - 241 - - - - &Depot: - - - 292 - - - - &Browse... - - - 302 - - - 337 - - - - Please choose one of the values from the list - - - 307 - - - 342 - - - 1319 - - - - &Location: - - - 351 - - - - D&escription: - - - 380 - - - - &Create a workspace to use with this stream - - - 395 - - - - Basic Settings - - - 407 - - - - &Owner: - - - 413 - - - - &Locked (only the stream owner can edit stream settings) - - - 425 - - - - &Paths (assign access levels to stream paths): - - - 433 - - - - Re&mapped (change the default mapping of depot files to the workspace): - - - 446 - - - - &Ignored (specify file or folder patterns to exclude from stream): - - - 459 - - - - Additional Fields: - - - 507 - - - - Advanced - - - 513 - - - - New - - - 582 - - - - Edit - - - 586 - - - - Stream: %1 (%2) - - - 606 - - - - Created by %1. - - - 706 - - - 1546 - - - - Br&anch files from parent on stream creation - - - 2148 - - - - Are you sure you want to convert this 'task' stream to another type? - -This action cannot be undone. Streams cannot be converted back to 'task'. - - - 1330 - - - - Would you like to populate the stream now? - - - 1379 - - - - Would you like to populate the stream now - - - 1381 - - - - If you choose to populate now, a workspace -will be created so that you can submit the -files to the depot. - - - 1383 - - - - &Populate - - - 1402 - - - - &Don't Populate - - - 1404 - - - - Are you sure you want to convert this 'task' stream to another type? -This action cannot be undone. Streams cannot be converted back to 'task'. - - - 1432 - - - - Failed to create stream "'%1'" - - - 1679 - - - - Failed to edit stream "'%1'" - - - 1704 - - - - Submitting files to the stream restricted to stream owner - - - 2144 - - - - Popul&ate the mainline stream after it is created - - - 2150 - - - - The &parent stream from which this stream will be sourced: - - - 2171 - - - - Branch files from parent on stream creation - - - 402 - - - 2182 - - - - (path must be %1 levels deep - e.g. %2) - - - 1081 - - - 1213 - - - - Specify a stream location. - - - 1269 - - - - &Parent stream: - - - 2142 - - - - Submit restrictions need to be made in source stream - - - 2183 - - - - The root folder you have chosen is already in use by another stream. - - - 1354 - - - - - - - - - - Potential Conflicts - - - 29 - - - - - %n file(s) have potential conflicts with file(s) currently opened in the workspace. - %n files have potential conflicts with files currently opened in the workspace. - - - %n file(s) have potential conflicts with file(s) currently opened in the workspace. - %n files have potential conflicts with files currently opened in the workspace. - - - 147 - - - - - - - - - - checked out - - - 61 - - - - marked for add - - - 62 - - - - marked for delete - - - 63 - - - - marked for branch - - - 64 - - - - marked for integrate - - - 68 - - - - marked for import - - - 69 - - - - marked for move/add - - - 70 - - - - marked for move/delete - - - 71 - - - - purged - - - 72 - - - - archived - - - 73 - - - - status unknown, fetching... - - - 95 - - - - not synced in your workspace - - - 99 - - - 416 - - - - not in depot - - - 102 - - - - not mapped to workspace view - - - 103 - - - - not under workspace root - - - 104 - - - - modified - - - 105 - - - - default changelist - - - 115 - - - - changelist %1 - - - 116 - - - - - %n users: - %n users: - - - %n users: - %n users: - - - 145 - - - - %1 in %2 - - - 314 - - - $ACTION in $CHANGELIST - - - - moved from: - - - 319 - - - - moved to: - - - 321 - - - - %1 by %2 - - - 378 - - - $ACTION by $USER(s) - - - - link/alias/shortcut - - - 386 - - - - deleted and moved to new location - - - 393 - - - - deleted at head revision - - - 397 - - - - (#%1 of %2) - - - 407 - - - - not latest revision of file - - - 415 - - - - needs resolve - - - 422 - - - - latest revision of file - - - 428 - - - - locked - - - 434 - - - - locked by %1 - - - 441 - - - - locked by other user - - - 446 - - - - exclusive checkout - - - 455 - - - 465 - - - - exclusive checkout by %1 - - - 460 - - - - imported from another depot location - - - 472 - - - - %1-bit - - - 509 - - - - - - - - - - #%1 %2 @%3 by %4 - - - 19 - - - #%1=revision_number %2=action @%3=changelist_number by %4=user - - - - #%1 %2 @%3 - - - 25 - - - #%1=revision_number %2=action @%3=changelist_number - - - - - - - - - - %1: %2 - - - 75 - - - %1=type, such as "Submitted Changelist" or "Branch". %2=name - - - - - - - - - - &Cancel Operation - - - 409 - - - - Refres&h All - - - 413 - - - - &Refresh - - - 417 - - - - &Print... - - - 422 - - - - P&rint Preview... - - - 426 - - - - Save as PDF... - - - 429 - - - - &Add Bookmark... - - - 446 - - - - &Bookmark... - - - 448 - - - - &Get Latest Revision - - - 452 - - - - Get Revisio&n... - - - 456 - - - - Remove from &Workspace - - - 459 - - - - Dele&te Local File - - - 461 - - - - Show in Finder - - - 464 - - - - Show &in Explorer - - - 468 - - - - Show &in File Browser - - - 471 - - - - Open Command Window &Here - - - 477 - - - - Open Terminal Window &Here - - - 479 - - - - &Depot Tree - - - 486 - - - - &Workspace Tree - - - 490 - - - - &Pending Changelist - - - 494 - - - - Files i&n Folder - - - 511 - - - - Find File... - - - 514 - - - - &Check Out - - - 529 - - - - Check Out and O&pen - - - 533 - - - - Mark for &Add - - - 535 - - - - Mark for &Delete - - - 538 - - - - &Submit... - - - 541 - - - - &Move to Another Changelist... - - - 546 - - - - Revert If Unc&hanged - - - 550 - - - - Re&vert - - - 554 - - - - &Open - - - 566 - - - - &Choose Application... - - - 568 - - - - &Lock - - - 575 - - - - &Unlock - - - 578 - - - - La&bel... - - - 581 - - - - Lab&el... - - - 616 - - - - &Integrate... - - - 584 - - - - &Rename/Move... - - - 587 - - - - Rollbac&k... - - - 589 - - - - &New... - - - 595 - - - - Cre&ate... - - - 599 - - - - &Pending Changelist... - - - 608 - - - - &Job... - - - 612 - - - - &New Workspace... - - - 622 - - - - New Folder... - - - 628 - - - 632 - - - - F&older... - - - 630 - - - - &Edit - - - 634 - - - - Edit Current &Workspace... - - - 639 - - - - Edit Current &User... - - - 643 - - - - &Delete - - - 645 - - - 783 - - - - &Switch to - - - 650 - - - - Remove &All Jobs - - - 658 - - - - Remove from Changelist - - - 661 - - - - View Changelist - - - 667 - - - - View - - - 669 - - - - &Diff Against... - - - 673 - - - - Diff - - - 677 - - - - Diff Against - - - 681 - - - - Diff Against Workspace File - - - 685 - - - - &Revision Graph - - - 700 - - - - &Time-lapse View - - - 703 - - - - Folder History - - - 499 - - - - &Save Filter... - - - 432 - - - - &Save %1 Filter... - - - 433 - - - - &Manage Filters... - - - 436 - - - - &Manage %1 Filters... - - - 437 - - - - &Clear Filter - - - 440 - - - - &Clear %1 Filter - - - 441 - - - - &Refresh Filter - - - 443 - - - - &Refresh %1 Filter - - - 444 - - - - Show in Depot Tree - - - 488 - - - - Show in Workspace Tree - - - 492 - - - - Show in Pending Changelist - - - 496 - - - - &History - - - 506 - - - - &Save Search... - - - 518 - - - - &Manage Searches... - - - 520 - - - - S&tream Graph - - - 522 - - - - Stream Graph... - - - 526 - - - - &Resolve... - - - 558 - - - - Resolve Files... - - - 560 - - - - R&e-resolve Previously Resolved Files... - - - 562 - - - - Re-resolve Previously Resolved Files... - - - 564 - - - - Switch to Wor&kspace... - - - 570 - - - - Switch to Workspace, Work in this Stream - - - 571 - - - - Change Filet&ype... - - - 573 - - - - &Branch Mapping... - - - 604 - - - - New Branch Mapping... - - - 606 - - - - New Pending Changelist... - - - 610 - - - - New Job... - - - 614 - - - - New Label... - - - 618 - - - - New Stream... - - - 626 - - - - Edit Spec - - - 637 - - - - Delete Spec - - - 648 - - - - Switch to Workspace - - - 652 - - - - Remove All Jobs from Changelist - - - 659 - - - - Remove Job from Changelist - - - 662 - - - - Get This Revision - - - 664 - - - - Diff Against Have, Previous or Selected Revision - - - 679 - - - - Diff Against Head Revision - - - 687 - - - - Diff Using Branch Mapping - - - 689 - - - - Reconcile O&ffline Work... - - - 694 - - - - Advanced Reconcile Offline Work... - - - 696 - - - - File Propeties... - - - 698 - - - - Refresh Folder History - - - 721 - - - - C&opy... - - - 737 - - - - C&opy Using %1... - - - 738 - - - - C&opy to '%1'... - - - 739 - - - - &Branch File... - - - 741 - - - - &Branch Files... - - - 742 - - - - Create New Stream... - - - 744 - - - - Create New Stream from '%1'... - - - 745 - - - - Create New Stream from... - - - 746 - - - - Include - - - 748 - - - - Exclude - - - 750 - - - - Include Tree - - - 752 - - - - Exclude Tree - - - 754 - - - - Include Files - - - 756 - - - - Exclude Files - - - 758 - - - - Include Special... - - - 760 - - - - Exclude Special... - - - 762 - - - - Clear - - - 764 - - - - Clear Log - - - 766 - - - - View Timestamp - - - 768 - - - - View Server - - - 770 - - - - Font... - - - 772 - - - - Set Log Font - - - 773 - - - - &Make Shelf Global - - - 787 - - - - Request New Swarm Review... - - - 802 - - - - Open Review in Swarm - - - 805 - - - - Open Review '%1' in Swarm - - - 806 - - - - Update a Swarm Review... - - - 809 - - - 812 - - - - Update Swarm Review '%1'... - - - 810 - - - - Add to a Swarm Review... - - - 811 - - - - &Show Hidden Files - - - 832 - - - - Sort by Descending Order - - - 840 - - - - Show File Filter Tree - - - 910 - - - - Show/Hide Filter Tree - - - 912 - - - - C&lear Filter - - - 916 - - - - &Show Only Files with Highlights - - - 921 - - - - Zoom &Out - - - 925 - - - - Zoom &In - - - 931 - - - - &Ancestors of Selection - - - 937 - - - - Highlight Ancestors of Selection - - - 938 - - - - &Descendants of Selection - - - 942 - - - - Highlight Descendants of Selection - - - 943 - - - - A&ncestors and Descendants of Selection - - - 947 - - - - Highlight Ancestors and Descendants of Selection - - - 948 - - - - &File Containing Selection - - - 952 - - - - Highlight File Containing Selection - - - 954 - - - - &Revisions with Edits - - - 958 - - - - Highlight Revisions with Edits - - - 959 - - - - &Clear Highlight - - - 963 - - - - &File Renames Collapsed - - - 967 - - - - &Compressed Integration History - - - 971 - - - - Hid&e File - - - 975 - - - - Move File &Up - - - 979 - - - - Move File &Down - - - 984 - - - - Fil&ter Options... - - - 989 - - - - &Go To Initial Selection - - - 993 - - - - &Branch History - - - 997 - - - - Sort by Branch Hstory - - - 998 - - - - &File Path - - - 1002 - - - - Sort by File Path - - - 1003 - - - - &Highlight Includes Ignore Actions - - - 1007 - - - - Save Graph &As... - - - 1011 - - - - Files by Na&me... - - - 1015 - - - - Highlight Files by Name - - - 1016 - - - - Select Row - - - 1022 - - - FolderDiff - - - - Diff Row - - - 1027 - - - FolderDiff - - - - Checkout then Open - - - 1032 - - - FolderDiff - - - - Previous Diff - - - 1037 - - - FolderDiff - - - - Next Diff - - - 1043 - - - FolderDiff - - - - Show Files in Tree Hierarchy - - - 1049 - - - FolderDiff - - - - Show Files in Flat List - - - 1054 - - - FolderDiff - - - - Show Identical File Pairs - - - 1059 - - - FolderDiff - - - - Show Files without Counterparts - - - 1065 - - - FolderDiff - - - - Show Files with Content Differences - - - 1071 - - - FolderDiff - - - - Show Moved or Renamed Files - - - 1077 - - - FolderDiff - - - - Manage Folder Diff Filters - - - 1083 - - - FolderDiff - - - - Print the List of Diffs - - - 1089 - - - FolderDiff - - - - Show Only Offline Work - - - 1095 - - - FolderDiff - - - - Go To Line Number - - - 1101 - - - Timelapse - - - - Go to line number - - - 1105 - - - - Next Diff - - - 1108 - - - Timelapse - - - - Next diff - - - 1112 - - - - Previous Diff - - - 1115 - - - Timelapse - - - - Previous diff - - - 1119 - - - - Show User Information - - - 1122 - - - Timelapse - - - - Show user information - - - 1125 - - - - Show Aging of Text - - - 1128 - - - Timelapse - - - - Show aging of text - - - 1131 - - - - Show Line Numbers - - - 1134 - - - Timelapse - - - - Show line numbers - - - 1138 - - - - Show Lifetimes - - - 1141 - - - Timelapse - - - - Show lifetimes - - - 1144 - - - - No Branch History - - - 1147 - - - Timelapse - - - - Show direct history - - - 1148 - - - - Show Branch History - - - 1153 - - - Timelapse - - - - Show branch history - - - 1154 - - - - Show Originating Changelist - - - 1159 - - - Timelapse - - - - Show originating changelist information - - - 1160 - - - - Ignore Line Ending and All White Space Differences - - - 1165 - - - Timelapse - - - - Ignore Line Ending and White Space Length Differences - - - 1169 - - - Timelapse - - - - Ignore Line Ending Differences - - - 1173 - - - Timelapse - - - - Recognize Line Ending and White Space Differences - - - 1177 - - - Timelapse - - - - Single Revision Mode - - - 1181 - - - Timelapse - - - - Multiple Revision Mode - - - 1185 - - - Timelapse - - - - Incremental Differences Mode - - - 1189 - - - Timelapse - - - - Scale By Date - - - 1193 - - - Timelapse - - - - Scale By Changelist - - - 1197 - - - Timelapse - - - - Scale By Revision - - - 1201 - - - Timelapse - - - - Snap To Revision - - - 1205 - - - Timelapse - - - - Enable/Disable tweening effects - - - 1206 - - - - &Shelve... - - - 775 - - - - Refresh All Branch Mappings - - - 709 - - - - Refresh All Submitted Changelists - - - 712 - - - - Refresh All Groups - - - 714 - - - - Refresh All Jobs - - - 716 - - - - Refresh All Labels - - - 718 - - - - Refresh All Pending Changelists - - - 724 - - - - Refresh All Users - - - 726 - - - - Refresh All Workspaces - - - 729 - - - - Refresh All Streams - - - 731 - - - - &Unshelve... - - - 780 - - - - &Show Deleted Depot Files - - - 826 - - - - &Hide Deleted Depot Files - - - 827 - - - - &No Folder Filter - - - 828 - - - - &Hide Files Not in Depot - - - 830 - - - - Do Not Show Hi&dden Files - - - 833 - - - - &Entire Computer - - - 834 - - - - &Workspace Root - - - 835 - - - - &Files in Workspace/Depot Tree - - - 836 - - - - &Ascending Order - - - 837 - - - - Sort by Ascending Order - - - 838 - - - - D&escending Order - - - 839 - - - - &Refresh %1 - - - 418 - - - - &Refresh Selected Item - - - 419 - - - - &Refresh Selected Items - - - 420 - - - - &Print %1... - - - 423 - - - - P&rint Preview %1... - - - 427 - - - - Save %1 as PDF... - - - 430 - - - - &Export to... - - - 450 - - - - Show in &Depot Tree - - - 487 - - - - Show in &Workspace Tree - - - 491 - - - - Show in &Pending Changelist - - - 495 - - - - &Labels - - - 503 - - - - %1 &History - - - 507 - - - - &Submit %1... - - - 542 - - - - Revert Unc&hanged Files - - - 551 - - - - Re&vert Files - - - 555 - - - - La&bel Files... - - - 582 - - - - &Integrate Using %1... - - - 585 - - - - Back Out Revision - - - 591 - - - - Back Out %1 - - - 592 - - - - &New %1... - - - 596 - - - - Cre&ate %1 '%2'... - - - 600 - - - - Cre&ate %1 from '%2'... - - - 601 - - - - &Stream... - - - 624 - - - - &Edit %1 - - - 635 - - - - &Edit Selected Items - - - 636 - - - - Map to Workspace View... - - - 641 - - - - &Delete %1 - - - 646 - - - - &Delete Selected Items - - - 647 - - - - &Switch to %1 - - - 651 - - - - Change Ownership... - - - 655 - - - - View %1 - - - 670 - - - - View Selected Items - - - 671 - - - - Diff Against Parent - - - 691 - - - - Diff Against %1 - - - 692 - - - - Merge/&Integrate... - - - 733 - - - - Merge/&Integrate Using %1... - - - 734 - - - - Merge/&Integrate to '%1'... - - - 735 - - - - &Shelve Files... - - - 776 - - - - Submit Shelved Files... - - - 778 - - - - &Unshelve Files... - - - 781 - - - - &Delete Shelved Files - - - 784 - - - - Delete Shelved File - - - 785 - - - - Populate Stream - - - 790 - - - - &Toolbar - - - 791 - - - - Show/Hide the Toolbar - - - 792 - - - - Unload - - - 794 - - - - Unload %1 - - - 795 - - - - Unload Selected Items - - - 796 - - - - Reload - - - 798 - - - - Reload %1 - - - 799 - - - - Reload Selected Items - - - 800 - - - - Show Files &Not in Depot - - - 829 - - - - Show &Only Files Not in Depot - - - 831 - - - - - - &List - - - 847 - - - 863 - - - - La&rge Thumbnail (160x160) - - - 848 - - - - &Medium Thumbnail (120x120) - - - 849 - - - - &Small Thumbnail (80x80) - - - 850 - - - - Show Files as List - - - 853 - - - - Show Files as Large Thumbnail (160x160) - - - 854 - - - - Show Files as Medium Thumbnail (120x120) - - - 855 - - - - Show Files as Small Thumbnail (80x80) - - - 856 - - - - &Entire Depot Tree - - - 858 - - - - Tree Restricted to &Workspace View - - - 859 - - - - &Graph - - - 862 - - - - &Tree - - - 864 - - - - Show Streams as Graph - - - 867 - - - - Show Streams as List - - - 868 - - - - Show Streams as Tree - - - 869 - - - - Cre&ate from Template... - - - 1275 - - - - - - - - - - Obliterate - - - 87 - - - - Obliterate the following files/folders from "%1": - - - 88 - - - - obliterate - - - 90 - - - - lev - - - 90 - - - - No Files. - - - 122 - - - - &Add... - - - 154 - - - - &Remove - - - 155 - - - - &Preview - - - 184 - - - - &Obliterate - - - 185 - - - - Cancel - - - 186 - - - - Add Files/Folders - - - 256 - - - - Files in remote depots cannot be obliterated - - - 280 - - - - The specified range is not valid. - - - 324 - - - - No summary text. - - - 421 - - - - - - - - - - Obliterate Preview - - - 15 - - - - Obliterate Results - - - 17 - - - - OK - - - 30 - - - - - - - - - - <b>Warning!</b> Obliterating alters the Perforce server database to permanently remove all references to the selected files. If you obliterate files, it might not be possible to restore the server to its previous state. For further information on the obliterate operation, see the Perforce System Administrator's Guide. - - - 21 - - - - Proceed with obliterating files - - - 27 - - - - OK - - - 30 - - - - - - - - - - Content not found: - - - 71 - - - - Content access denied: - - - 73 - - - - Connection refused: - - - 75 - - - - Path not found: - - - 78 - - - - Protocol Failure. Either malformed p4 URI or no access to file: - - - 84 - - - - An error has occurred with file: - - - 87 - - - - - - Authentication Required - - - 111 - - - - The server %1:%2 at %3 requires a username and password. - - - 114 - - - - User Name: - - - 121 - - - - Password: - - - 124 - - - - - - - - - - More than one replaceable file argument of type %X is not allowed - -%1 - - - 748 - - - - Error - - - 750 - - - 793 - - - 880 - - - 923 - - - 1283 - - - 1370 - - - 1514 - - - 1573 - - - - No opened files selected in active pane -Cannot run %1 - - - 778 - - - - No files selected in active pane -Cannot run %1 - - - 779 - - - 1282 - - - - Unable to determine local workspace path for all selected files -Cannot run %1 - - - 788 - - - - No object selected in active pane -Cannot run %1 - - - 875 - - - - Unable to determine selected object -Cannot run %1 - - - 877 - - - - Tools > %1 requires that only one file be selected - - - 921 - - - - Error: The current selection cannot be used to run %1 - - - 1227 - - - - The 'Start In' directory '%1' does not exist. - - - 1371 - - - - Error: Unable to run %1 - - - 1496 - - - 1563 - - - - Error: %1 crashed - - - 1498 - - - - Error: %1 timed out - - - 1500 - - - - Error: %1 failed to write - - - 1502 - - - - Error: %1 failed to read - - - 1504 - - - - - - - - - - Refresh - - - 40 - - - - - - - - - - Initializing... - - - 118 - - - - Depot Tree - - - 170 - - - 432 - - - - Access level filter: - - - 177 - - - - Host IP filter: - - - 189 - - - - Show files - - - 198 - - - - Note: Some access levels for users and groups may have been assigned through the authentication server's permission table. - - - 211 - - - - Select user or group to view permissions: - - - 305 - - - 373 - - - - Permissions for user: - - - 329 - - - - Permissions for group: - - - 395 - - - - Select folder or file to view permissions: - - - 426 - - - - Permissions for file/folder: - - - 478 - - - - &nbsp;&nbsp;Permissions (unsaved) for user: <b>%1</b> - - - 527 - - - - &nbsp;&nbsp;Permissions (unsaved) for group: <b>%1</b> - - - 529 - - - - &nbsp;&nbsp;Permissions for user: <b>%1</b> - - - 534 - - - - &nbsp;&nbsp;Permissions for group: <b>%1</b> - - - 536 - - - - &nbsp;&nbsp;Permissions (unsaved) for file: <b>%1</b> - - - 564 - - - - &nbsp;&nbsp;Permissions (unsaved) for folder: <b>%1</b> - - - 566 - - - - &nbsp;&nbsp;Permissions for file: <b>%1</b> - - - 571 - - - - &nbsp;&nbsp;Permissions for folder: <b>%1</b> - - - 573 - - - - Processing %1/%2 - - - 1562 - - - progress %1 out of %2 - - - - Discard the changes made to the permissions table? - - - 695 - - - - Error: unable to load Protect Table - - - 850 - - - - Saving protect table... - - - 898 - - - - Table saved - - - 915 - - - - Error: Table not saved - - - 917 - - - - Loading Permissions Viewer data... - - - 1262 - - - - all users - - - 1507 - - - - all groups - - - 1508 - - - - no access - - - 1596 - - - 1653 - - - - - - - - - - none - - - 1311 - - - - - - - - - - Stream: - - - 164 - - - - <a href="%1">Refresh merge/copy information</a> - - - 214 - - - - Work in this Stream - - - 227 - - - 237 - - - - Currently working in this Stream - - - 234 - - - - - - - - - - Preview of %1 - - - 108 - - - - Richtext Editor - - - 140 - - - - &Hide fields when empty - - - 161 - - - - Fields: - - - 195 - - - - &Set As Default - - - 200 - - - - Page Setup - - - 484 - - - - Next page - - - 516 - - - - Previous page - - - 517 - - - - First page - - - 518 - - - - Last page - - - 519 - - - - Fit width - - - 528 - - - - Fit page - - - 529 - - - - Zoom in - - - 540 - - - - Zoom out - - - 541 - - - - Portrait - - - 547 - - - - Landscape - - - 548 - - - - Show single page - - - 558 - - - - Show facing pages - - - 559 - - - - Show overview of all pages - - - 560 - - - - Print - - - 575 - - - - Page setup - - - 576 - - - - close - - - 577 - - - - - - - - - - Cancel - - - 34 - - - - - - - - - - Edit permissions using the table format - - - 102 - - - - Edit permissions using the plain text format - - - 108 - - - - Undo - - - 116 - - - - Redo - - - 124 - - - - Insert line - - - 134 - - - - Delete line - - - 140 - - - - Move line up - - - 146 - - - - Move line down - - - 152 - - - - &Find - - - 161 - - - - Filter table by the selected user, group or folder - - - 170 - - - - Revert Edits - - - 188 - - - - Save Edits - - - 192 - - - - &nbsp;&nbsp;Table filtered by user: <b>%1</b> - - - 282 - - - - &nbsp;&nbsp;Table filtered by group: <b>%1</b> - - - 284 - - - - &nbsp;&nbsp;Table filtered by path: <b>%1</b> - - - 295 - - - - * - - - 751 - - - - Access Level - - - 762 - - - - User/Group - - - 766 - - - - Name - - - 770 - - - - Host - - - 774 - - - - Folder/File - - - 779 - - - - Comment - - - 783 - - - - - - - - - - <integration preview> - - - 380 - - - - - - - - - - already opened - - - 554 - - - 556 - - - 559 - - - 560 - - - 561 - - - - local depot - - - 555 - - - - requires -d - - - 557 - - - - requires -i - - - 558 - - - - existing - - - 562 - - - - deleted - - - 563 - - - - requires sync - - - 564 - - - - error - - - 565 - - - 566 - - - 574 - - - - sync - - - 567 - - - 568 - - - 569 - - - - bad flag - - - 570 - - - - requires -v - - - 571 - - - - incompatible type - - - 572 - - - - - - - - - - Binary File - - - 327 - - - - You are about to run Timelapse view on a binary file, would you like to continue? - - - 328 - - - - - - Time-lapse View - %1 (%2, %3) - - - 439 - - - Window title, %1=file, %2=server_port, %3=user - - - - Invalid file path: %1 - - - 212 - - - 223 - - - - Invalid file revision path: %1 - - - 270 - - - 280 - - - - - - - - - - Browser Preview - - - 39 - - - - Source Text - - - 41 - - - - - - File: - - - 207 - - - - No preview available - - - 281 - - - - - - Refresh - - - 117 - - - - - - - - - - none applied - - - 137 - - - 1479 - - - 2358 - - - 1785 - - - - - - Show only workspaces available for use on this computer - - - 1913 - - - - <font color="gray">Workspace</font> can be used by this computer - - - 2036 - - - - - - <font color="gray">Query is</font> "%1" - - - 2331 - - - - (including integrated revisions) - - - 2335 - - - - and File paths are - - - 2351 - - - - - - Show only pending changelists with shelved files - - - 2820 - - - - Change the sort order of files in pending changelists - - - 2885 - - - - <font color="gray">Pending Changelist</font> has shelved files - - - 2938 - - - - - - Match case when filtering by name - - - 1092 - - - - Show unloaded %1 - - - 1116 - - - - "%1" - - - 1587 - - - 1591 - - - - and - - - 1616 - - - - or - - - 1618 - - - - - - - - - - of the following conditions: - - - 149 - - - 150 - - - 924 - - - 156 - - - - is not empty - - - 160 - - - 167 - - - 932 - - - 167 - - - - is empty - - - 166 - - - 172 - - - 937 - - - 173 - - - - - - File Name - - - 63 - - - 79 - - - - Folder Path - - - 64 - - - 84 - - - - Date Last Submitted - - - 332 - - - - Submitted - - - 333 - - - - is - - - 334 - - - 336 - - - - - - - - - - There are no files that need to be reconciled. - - - - 776 - - - - Reconcile Offline Work - - - 825 - - - - Modified files (Select files to check out) - - - 865 - - - - Local files not in depot (Select files to mark for add) - - - 873 - - - - Depot files missing from workspace (Select files to mark for delete) - - - 881 - - - - &Add files to pending changelist: - - - 892 - - - - A&dvanced Reconcile... - - - 904 - - - - &Reconcile - - - 905 - - - - Cancel - - - 906 - - - - Reconciled offline work - - - 1132 - - - - There are files that need to be reconciled that don't - - - 1309 - - - - fall under one of the three basic categories: - - - 1310 - - - - Modified files that are not checked out - - - 1313 - - - - Local files not in depot - - - 1314 - - - - Depot files missing from workspace - - - 1315 - - - - Use Advanced Reconcile option? - - - 1316 - - - - &Advanced - - - 1325 - - - 1330 - - - - Save Filter... - - - 1534 - - - - Set As Default - - - 1537 - - - - Manage Filters... - - - 1540 - - - - "%1" - - - 1712 - - - 1716 - - - - and - - - 1741 - - - - or - - - 1743 - - - - - - - - - - Resolve (%1) - - - 205 - - - - Files to resolve: - - - 246 - - - - list - - - 873 - - - - Recommended action: - - - 440 - - - - Resolve options: - - - 527 - - - 580 - - - 659 - - - 709 - - - 732 - - - 754 - - - 778 - - - - Common base - - - 474 - - - - Merged result - - - 512 - - - - Accept &Merged - - - 831 - - - - &Diff - - - 807 - - - - &File History - - - 808 - - - - Time-lapse &View - - - 809 - - - - Click the "Accept Merged" button below. - - - 987 - - - - Only %1 file uniquely differs from the common base file. - - - 1013 - - - - Only %1 file differs from the common base file. - - - 1015 - - - - Click the "Run Merge Tool" button below. - - - 989 - - - - Symbolic links cannot be merged - - - 1019 - - - 2785 - - - - In order to do a merge of binary files click the "Run Merge Tool" button - - - 1021 - - - - Files cannot be merged because their file types are incompatible. - - - 1023 - - - - Perforce could not resolve this file. - - - 993 - - - - Target - - - 83 - - - - Source - - - 87 - - - - Re-resolve (%1) - - - 207 - - - - Resolve method: - - - 212 - - - - &Auto resolve multiple files - - - 230 - - - - &Interactively resolve files one at a time - - - 233 - - - - Previously resolved files: - - - 248 - - - - S&elect - - - 251 - - - - Merge &binary files as text when resolving content - - - 256 - - - - Resolve (target) - - - 285 - - - - Resolve With (source) - - - 287 - - - - Resolve Type - - - 290 - - - - Auto resolve options: - - - 334 - - - - <strong>Sa&fe automatic resolve (no merging)</strong><br>Accept source if only the source file has changed. Accept target if only the target file has changed. Don't resolve file if both source and target have changed. - - - 357 - - - - <strong>A&utomatic resolve (allow merging)</strong><br>Accept source if only the source file has changed. Accept target if only the target file has changed. Merge changes if both source and target have changed and there are no conflicts. - - - 358 - - - - <strong>Accept &source</strong><br>All changes made to the source file are replicated in the target file. - - - 359 - - - - <strong>Accept &target</strong><br>Leave target file unchanged. - - - 360 - - - - <strong>Aut&omatic resolve (allow merging with conflicts)</strong><br>Accept source if only the source file has changed. Accept target if only the target file has changed. Merge changes if both source and target have changed, even if there are conflicts. - - - 361 - - - - Set As Auto &Default - - - 416 - - - - Auto &Resolve - - - 418 - - - - Source file - - - 492 - - - - Target file - - - 495 - - - - Replace target file with a copy of source file - - - 533 - - - - Leave target file unchanged - - - 534 - - - - Replace target file with the merged result of source and target - - - 535 - - - - Manually merge source and target before accepting - - - 536 - - - - Common base: - - - 558 - - - 638 - - - - Match the filetype of the source file: - - - 582 - - - - Keep the filetype of the target file: - - - 585 - - - - Combine the filetypes of both the source and target: - - - 590 - - - - Rename the file to match the source file: - - - 661 - - - - Keep the current name of the target file: - - - 664 - - - - Combine the name changes made to both the source and target: - - - 669 - - - - Branch target file from source - - - 711 - - - - Do not branch from source file (ignore) - - - 712 - - - - Delete the target file - - - 733 - - - - Do not delete the target file (ignore) - - - 734 - - - - Replace target file's attributes with those from source - - - 755 - - - - Leave target file's attributes unchanged - - - 756 - - - - Add source's propagating attributes to target file - - - 757 - - - - A&dditional Actions - - - 797 - - - - &Open File - - - 806 - - - - Revision &Graph - - - 810 - - - - Accept &Source - - - 829 - - - - Accept &Target - - - 830 - - - - &Run Merge Tool - - - 832 - - - - All - - - 965 - - - - Binary Files - - - 967 - - - - Text Files - - - 969 - - - - Content Resolves - - - 971 - - - - Move Resolves - - - 973 - - - - Filetype Resolves - - - 975 - - - - Branch Resolves - - - 977 - - - - Delete Resolves - - - 979 - - - - Attribute Resolves - - - 981 - - - - Click the "Accept Target" button below. - - - 983 - - - - Click the "Accept Source" button below. - - - 985 - - - - No recommended action. - - - 991 - - - - The target file does not currently exist in the depot. - - - 995 - - - - The source file has been deleted but the corresponding target file still exists. - - - 997 - - - - Both the source and target files have been renamed or moved. - - - 999 - - - - The %1 file has been renamed or moved. - - - 1001 - - - - The filetypes of both the source and target files have changed. - - - 1003 - - - - The attributes of the files have been changed. - - - 1005 - - - - is an unknown type of resolve. Upgrade to the newest P4V for more detailed information. - - - 1007 - - - - Target file and source file are identical - - - 1009 - - - - Both files differ from common base file, but with no conflicts. - - - 1011 - - - - Conflicts exist between target file and source file. - - - 1017 - - - - Performing Auto resolve - - - 1334 - - - - There are still conflicts and conflict markers in the merged file. - Do you accept this file? - - - 1511 - - - - Cannot Find P4Merge - - - 1600 - - - - P4Merge not found - - - 1600 - - - - Merge file used for resolve - - - 1655 - - - - %1 of the files could not be resolved using the Auto resolve option chosen. - - - 2090 - - - - Please use a different Auto resolve method. - - - 2093 - - - - Please use a different Auto resolve method or the Interactive resolve option on the remaining files. - - - 2095 - - - - %1 of %2 items selected - - - 2379 - - - - Differences from base: %1 %2 - - - 2577 - - - 2581 - - - - unique - - - 2578 - - - 2582 - - - - There are conflicts remaining - - - 2594 - - - - No conflicts remaining - - - 2602 - - - - Conflicts: %1 - - - 2609 - - - - +%1 common to both - - - 2620 - - - - target - - - 2675 - - - 2677 - - - 2755 - - - - source - - - 2684 - - - 2686 - - - 2753 - - - - Accept Merged - - - 2936 - - - - Replace '%1' with the merged result file? - - - 2937 - - - - You can no longer resolve '%1'. - - - 2986 - - - - %1 vs %2 - - - 3078 - - - - Base vs %1 - - - 3079 - - - 3080 - - - - Base vs Merged Result - - - 3089 - - - - %1 vs Merged Result - - - 3090 - - - 3091 - - - - A merge tool is open. You must exit the merge tool. - - - 3226 - - - - %1 - - - 3034 - - - 3035 - - - 3047 - - - 3051 - - - 3055 - - - 3056 - - - 3113 - - - 3117 - - - 3121 - - - 3122 - - - 3145 - - - 3149 - - - 3153 - - - 3154 - - - 3177 - - - 3181 - - - 3185 - - - 3186 - - - - Merged Result - - - 3041 - - - - - - - - - - Re-resolve... - - - 96 - - - - There are no files that need resolving. - - - 219 - - - - There are no files to re-resolve. - - - 224 - - - - The file can not be re-resolved. - - - 229 - - - - - - - - - - Details - - - 81 - - - 86 - - - 91 - - - 96 - - - 116 - - - - Integrations - - - 101 - - - - Files - - - 111 - - - - Checked Out By - - - 121 - - - - Preview - - - 126 - - - - Legend - - - 131 - - - - - - - - - - &Do not limit operation - - - 29 - - - - &Operate on all revisions up to: - - - 33 - - - - Operate on only revisions bet&ween: - - - 36 - - - - %1 all revisions - - - 107 - - - - %1 revisions up to: - - - 111 - - - - %1 revisions between: - - - 115 - - - - obliterate - - - 119 - - - - - - - - - - Rollback to Revision... - - - 105 - - - - Rollback - - - 144 - - - - - - Rollback Preview - - - 938 - - - - None - - - 942 - - - - Files that will be rolled back to an earlier revision: - - - 948 - - - - Files that will be re-added: - - - 962 - - - - Files that will be deleted: - - - 976 - - - - - - The file has only one revision and cannot be rolled back - - - 574 - - - - The file cannot be rolled back. The revision you requested is the same as the latest revision in the depot. - - - 741 - - - - Pending &changelist: - - - 286 - - - - Rollback File - - - 291 - - - - Rollback the following file: - - - 294 - - - - Rollback Folder - - - 299 - - - - Rollback the following folder: - - - 303 - - - - Bro&wse... - - - 305 - - - - &Rollback folder to: - - - 360 - - - - &Delete files not in label - - - 363 - - - - R&ollback file to: - - - 390 - - - - &Rollback to previous revision (%1 of %2) - - - 425 - - - - &Preview - - - 437 - - - - &Save to Changelist - - - 438 - - - - Sub&mit... - - - 439 - - - - Cancel - - - 440 - - - - Please enter a revision specifier. - - - 564 - - - - revision %1 - - - 638 - - - - changelist %1 - - - 641 - - - - label %1 - - - 644 - - - - Rollback %1 to %2 - - - 655 - - - - Rollback operation could not be performed. The folder is not mapped to your workspace view - - - 670 - - - - Rollback operation could not be performed. The file is not mapped to your workspace view - - - 673 - - - - Rollback Preview - - - 693 - - - - There are no files to roll back. The revisions you requested are the same as the latest revisions in the depot. - - - 745 - - - - Choose Folder - - - 803 - - - - Rollback - - - 827 - - - - - - - - - - 3 - - - 30 - - - - highest - - - 31 - - - - (ticket-based authentication required) - - - 32 - - - - 2 - - - 37 - - - - high - - - 38 - - - - (strong passwords required) - - - 39 - - - - 1 - - - 44 - - - - medium - - - 45 - - - - (passwords required) - - - 46 - - - - 0 - - - 51 - - - - low - - - 52 - - - - (passwords not required) - - - 53 - - - - - - - - - - Close Window - - - 343 - - - - &Close - - - 346 - - - - CTRL+W - - - 349 - - - - &Minimize - - - 353 - - - - CTRL+M - - - 354 - - - - Maximize - - - 356 - - - - Zoom - - - 357 - - - - Bring All to Front - - - 360 - - - - &Find... - - - 363 - - - - &Copy - - - 368 - - - - CTRL+C - - - 369 - - - - Clear - - - 372 - - - - Copy Depot Path - - - 375 - - - - CTRL+SHIFT+C - - - 376 - - - Copy Depot Path - - - - &Select All - - - 378 - - - - CTRL+A - - - 379 - - - - CTRL+H - - - 387 - - - - CTRL+ALT+H - - - 392 - - - - - - - - - - No bookmarks - - - 89 - - - - - - - - - - Choose Character Encoding - - - 21 - - - - &Choose character encoding to use with the current connection: - - - 36 - - - - Warning: Changing the character encoding can make existing workspace files unreadable - - - 47 - - - - &Use this encoding for all connections to Unicode servers -(will not affect connections for which the encoding was previously set) - - - 54 - - - - - - - - - - &Don't show this dialog again - - - 33 - - - - OK - - - 39 - - - - No - - - 52 - - - - Yes - - - 53 - - - - - - - - - - Filter Depot Tree - - - 292 - - - - Filter Workspace Tree - - - 298 - - - - Sort By - - - 303 - - - - - - - - - - Environment Settings - - - 36 - - - - Windows environment variables for the Perforce connection. - - - 43 - - - - Type in settings or choose from menu: - - - 47 - - - - &Connections - - - 50 - - - - No favorite connections - - - 67 - - - - (host:port) - - - 73 - - - - &Server (P4PORT): - - - 75 - - - - &User (P4USER): - - - 84 - - - - (optional) - - - 92 - - - - &Workspace (P4CLIENT): - - - 94 - - - - C&harset (P4CHARSET): - - - 104 - - - - Us&e current connection for environment settings - - - 110 - - - - - - - - - - Loading revision data... - - - 91 - - - - - - Toolbar - - - 250 - - - - &File - - - 275 - - - - &Edit - - - 304 - - - - &View - - - 316 - - - - &Submitted Changelist - - - 324 - - - - H&ighlight - - - 331 - - - - &Tools - - - 335 - - - - Manage &Custom Tools... - - - 344 - - - - &Window - - - 355 - - - - &Help - - - 374 - - - - Revision Graph &Help - - - 380 - - - - There were too many revisions in this path to graph. - - - - 428 - - - - Try graphing a smaller set of files. - - - 429 - - - - Processing revision data... - - - 444 - - - - Cancel - - - 445 - - - - Processing integration data... - - - 514 - - - - Loading %1 - - - 717 - - - - No workspace selected. - - - 1206 - - - - Revision Graph - %1 (%2) - - - 1288 - - - - - - - - - - Address Bar - - - 219 - - - - &Log Off - - - 1402 - - - - Toolbar - - - 858 - - - - %1 is still running server commands. Do you want to close the connection %2? -Closing the connection may result in partial completion of commands, but will not corrupt your data. - - - 1104 - - - - No update available. - -This version of P4V is up to date! - -Installed version: %1 - - - 2552 - - - 2581 - - - - &Copy - - - 2265 - - - 2270 - - - - &Do Not Copy - - - 2272 - - - - &Get Latest - - - 2288 - - - 2293 - - - - &Don't Get Latest - - - 2295 - - - - &Update - - - 2333 - - - 2338 - - - - &Do Not Update - - - 2340 - - - - &Window - - - 2992 - - - - Open &Recent - - - 2998 - - - - &Favorite Connections - - - 3004 - - - - &Dock Window - - - 3010 - - - - &Bookmarks - - - 3016 - - - - Filter Dep&ot - - - 3022 - - - - Filter Wor&kspace - - - 3028 - - - - &Tools - - - 3034 - - - 3438 - - - - &File - - - 3067 - - - - &New - - - 3072 - - - - &Show In - - - 3105 - - - - &Edit - - - 3132 - - - - &View - - - 3250 - - - - Sort B&y - - - 2861 - - - 3305 - - - - Could not connect to Swarm. '%1' - - - 429 - - - - Connected to Swarm Version '%1' - - - 434 - - - - Would you like to automatically check for new versions of P4V? - - - 507 - - - - Refresh - - - 878 - - - - Get Latest - - - 889 - - - - Submit - - - 899 - - - - Checkout - - - 911 - - - - Add - - - 921 - - - - Delete - - - 931 - - - - Revert - - - 942 - - - - Diff - - - 954 - - - - Timelapse - - - 964 - - - - Revgraph - - - 975 - - - - Cancel - - - 987 - - - - Copy latest version of all mapped files to new root directory? - - - 2254 - - - - If your workspace view includes a large number of files, this may require considerable disk space. - - - 2256 - - - - Get latest revisions of all files added to your workspace view? - - - 2282 - - - - Update workspace files to match the workspace view changes? - -Updating will only remove files that are no longer in the view and move files that have been mapped to a different location. - - - 2325 - - - - %1%4Updates available for P4V.%5%3%3Please go to <a href="http://www.perforce.com/downloads/Perforce-Software-Version-Management/complete_list/Customer%20Downloads">Download Installer</a> to get the new version.%2 - - - 2602 - - - - %2Updates available for P4V.%3%4%4There's an update available, but your administrator has set a limit on allowed versions of P4V.%4%4Please ask your administrator which version to download.%4%4Installed version: %1%5 - - - 2615 - - - 2644 - - - - Installed version of P4V: %1<hr> - - - 2658 - - - - Release Notes - - - 2663 - - - 2696 - - - - Download Installer - - - 2668 - - - 2701 - - - - %1A new release of P4V is available.%2%3Newest version: %4%3%5 | %6 - - - 2669 - - - - %1There's a patch available for your version of P4V.%2%3Latest patch: %4%3%5 | %6 - - - 2702 - - - - Show &Files As - - - 2865 - - - 3308 - - - - Show &Streams As - - - 2871 - - - 3312 - - - - Show/&Hide - - - 2879 - - - - &Search - - - 3154 - - - - Fil&e Searches - - - 3192 - - - - Pen&ding Changelist Filters - - - 3196 - - - - S&ubmitted Changelist Filters - - - 3200 - - - - Bra&nch Mapping Filters - - - 3206 - - - - &Label Filters - - - 3213 - - - - &Workspace Filters - - - 3220 - - - - &Job Filters - - - 3227 - - - - Stre&am Filters - - - 3234 - - - - Stream &Graph Filters - - - 3238 - - - - Show/H&ide - - - 3319 - - - - &Actions - - - 3334 - - - - Res&olving - - - 3353 - - - - Sh&elving - - - 3362 - - - - &Connection - - - 3397 - - - - &Help - - - 3506 - - - - - - - - - - Directory does not exist - - - 96 - - - 104 - - - - %1 could not be found - - - 96 - - - 104 - - - - - - - - - - Open File With - - - 33 - - - - %1 %2 - - - 339 - - - - Launch error - - - 409 - - - 414 - - - - Could not start "%1" - - - 410 - - - 415 - - - - - - - - - - Open &With - - - 37 - - - - - - - - - - Richtext Editor - - - 92 - - - - - - - - - - Select folder to export to - - - 90 - - - - - - - - - - Status - - - 177 - - - - - - - - - - Locked - - - 78 - - - - Stream Root - - - 82 - - - - Parent Root - - - 83 - - - - Submitted By - - - 98 - - - - Date Submitted - - - 99 - - - - Access Type - - - 100 - - - 104 - - - - Date Modified - - - 109 - - - - Last Accessed - - - 110 - - - - Full Name - - - 111 - - - - Password Changed - - - 112 - - - - Max Results - - - 117 - - - - Max Scan Rows - - - 118 - - - - Max Lock Time (msec) - - - 119 - - - - Login Timeout - - - 120 - - - - Password Expiration - - - 121 - - - - Changelist - - - 128 - - - - Unable to shelve files: - - - 301 - - - - Unable to unshelve the following files: - - - 356 - - - - &Show In Depot Tree - - - 443 - - - - - - Parent - - - 887 - - - - Stream - - - 889 - - - - Submit Access - - - 891 - - - - - - Review Id - - - 691 - - - - Review State - - - 694 - - - - Get These Revisions - - - 781 - - - - - - Review Id - - - 923 - - - - Review State - - - 926 - - - - - - - - - - P4Admin - - - 82 - - - - Perforce P4Admin - - - 82 - - - - - - - 115 - - - - - - - - - - %1 is still running server commands. Do you want to stop those commands? -Stopping may result in partial completion of commands, but will not corrupt your data. - - - 288 - - - - Yes - - - 293 - - - - No - - - 292 - - - - Switch - - - 302 - - - - Don't Switch - - - 301 - - - - %1 is still running server commands. Do you want to stop those commands? -Switching workspaces may result in partial completion of commands, but will not corrupt your data. - - - 297 - - - - This workspace cannot be used on this computer either because the host field does not match your computer name or the workspace root cannot be used on this computer. - - - 401 - - - - This workspace cannot be used because it has been unloaded. It must be reloaded to be used (p4 reload -c). - - - 405 - - - - The stream associated with workspace "%1" has been unloaded. Stream must be reloaded in order to use this workspace. - - - 408 - - - - This workspace no longer exists on the server. - - - 411 - - - - Switching to workspace "%1" failed. - - - 416 - - - - Workspace switch error - - - 421 - - - - %1 - - - 488 - - - all window captions [add C,P,U] - - - - - Perforce P4V - - - 700 - - - - - - - - - - Checked Out - - - 234 - - - - Shelved* - - - 170 - - - - &Revert checked out files before unshelving - - - 316 - - - - &Overwrite workspace files even if they are writeable - - - 319 - - - - &Delete shelved files after they are unshelved - - - 539 - - - - Map unshelved files - - - 254 - - - - using %1 - - - 266 - - - - Browse... - - - 279 - - - 281 - - - - &Preview - - - 304 - - - - &Don't shelve unchanged files - - - 323 - - - - &Make shelf globally accessible - - - 326 - - - - File Name - - - 351 - - - - Open Review in Swarm - - - 383 - - - - &Unshelve - - - 492 - - - - &Shelve - - - 497 - - - - Update Files in Review - - - 501 - - - - Update Files - - - 502 - - - - Request New Swarm Review - - - 506 - - - - Request Review - - - 507 - - - - Update a Swarm Review - - - 511 - - - - Update Review - - - 512 - - - - Add to a Swarm Review - - - 516 - - - - Add to Review - - - 517 - - - - &Ok - - - 521 - - - - Cancel - - - 527 - - - - The following files will be shelved in pending changelist '%1' to update in review: - - - 646 - - - - The following files will be shelved for review: - - - 650 - - - - The following shelved files will be part of the review: - - - 654 - - - 676 - - - - The following files will be sent for review: - - - 658 - - - - Update Review: - - - 689 - - - - View Review Description - - - 692 - - - - Review Description: - - - 704 - - - 720 - - - - Reviewers: - - - 741 - - - - unshelve - - - 966 - - - - shelve - - - 966 - - - - You must select files to %1. - - - 969 - - - - %1 (not mapped to branch mapping source) - - - 1079 - - - 2166 - - - - Unable to unshelve the following file(s): - - - 1083 - - - 1559 - - - 2170 - - - - Some of the files you are trying to unshelve are checked out. - - - 1219 - - - - Select the "Resolve Files" button if you want to merge content from the shelved files into the checked out files. Otherwise, revert the checked out files before unshelving. - - - 1220 - - - - &Resolve Files - - - 1227 - - - - Unshelved from pending changelist '%1': - - - 1242 - - - - Unable to shelve files: - - - 1466 - - - - %1 unshelved, needs resolve - - - 1589 - - - - %1 unshelved - - - 1594 - - - - Preview: - - - 1597 - - - - You do not have permission to move the following files: - - - 1626 - - - - Please specify a stream root. - - - 1831 - - - - Please specify a branch mapping. - - - 1844 - - - - Using Stream - - - 1904 - - - - Please specify a valid branch mapping. - - - 2182 - - - - Required - - - 2255 - - - - Us&er: - - - 2298 - - - - A&dd - - - 2305 - - - - Br&owse... - - - 2316 - - - - Remo&ve - - - 2323 - - - - Select Users - - - 2346 - - - - User '%1' does not exist. - - - - 2404 - - - - Changelist could not be added to review. - - - - 2549 - - - - Changelist '%1' added to review '%2' - - - 2561 - - - - Review could not be created. - - - - 2627 - - - - Review '%1' created - - - 2638 - - - - Review ID not found - - - 2681 - - - - Swarm connection closed. - - - 2704 - - - - &Add to pending changelist: - - - 563 - - - - Options: - - - 572 - - - 760 - - - - &Revert checked out files after they are shelved - - - 632 - - - - Select files in the default pending changelist to shelve: - - - 640 - - - - *A shelved version of the file already exists in the changelist and will be replaced if selected - - - 672 - - - - Some of the files selected are checked out and cannot be unshelved unless they are reverted first. - - - 1204 - - - - Unshelve - - - 491 - - - - Select shelved files in pending changelist '%1' to unshelve: - - - 556 - - - - Select files in pending changelist '%1' to shelve: - - - 642 - - - - Shelve - - - 496 - - - - - - - - - - Cre&ate/Update %1 from '%2'... - - - 134 - - - - - - - - - - Stream '%1' has been created but an error occurred while trying to create a temporary workspace to branch the files from the parent stream. - - - 514 - - - - Stream '%1' has been created but an error occurred while trying to create the workspace. - - - 519 - - - - You will need to create a workspace for the stream. - - - 522 - - - - If you wish to branch files from the parent stream, use the branch dialog to branch those files. - - - 523 - - - - Populating files from parent stream... - - - 710 - - - - Branching files from parent stream... - - - 715 - - - - Stop - - - 716 - - - - Initial branch of files from %1 (%2) to %3 (%4) - - - 754 - - - 1031 - - - %1=parentName, %2=parent stream root, %3=new stream name, %4=new stream root - - - - Add Files Wizard - - - 855 - - - - Add Files Assistant - - - 857 - - - - Stream '%1' has been created but files have not been branched to it because an error occurred while trying to branch the files to the stream. - - - 957 - - - - Submitting files to stream... - - - 1006 - - - - Stream '%1' has been created but files have not been branched to it because an error occurred while trying to submit the files to the stream. You may use the workspace '%2' to try to submit the files in the changelist specified below. - - - 1067 - - - - Stream '%1' has been created. Files have been branched and submitted from the parent. An error occurred while trying to delete the temporary workspace '%2' used to branch and submit the files. You will need to delete the workspace manually. - - - 1150 - - - - Reverting files... - - - 1302 - - - - Stream '%1' has been created but files have not been branched to it because you stopped the Branch operation. An error occurred while trying to revert the branched files. - - - 1352 - - - - - - - - - - <a href="%1">Merge</a> changes - - - 982 - - - - Nothing to merge - - - 985 - - - - <i>Not fetched</i> - - - 988 - - - 1041 - - - - Copies pending, merge prior to copying - - - 1029 - - - - <a href="%1">Copy</a> changes - - - 1033 - - - - Nothing to copy - - - 1037 - - - - - - - - - - Could not parse the URI '%1' - - - 497 - - - - Cannot select objects on different servers - - - 499 - - - - p4v-js parse error - - - 1212 - - - - Missing quote somewhere - - - 1212 - - - 1213 - - - - P4JsApi global args error - - - 1266 - - - 1271 - - - 1276 - - - 1297 - - - 1302 - - - 1309 - - - 1325 - - - 1332 - - - 1341 - - - 1363 - - - 1370 - - - 1377 - - - 1382 - - - - -G not supported in P4JsApi - - - 1266 - - - 1267 - - - - -s not supported in P4JsApi - - - 1271 - - - 1272 - - - - -V not supported in P4JsApi - - - 1276 - - - 1277 - - - - PerforceJsApi global args error - - - 1290 - - - - -c not enough arguments - - - 1290 - - - 1291 - - - - -d not supported in P4JsApi - - - 1297 - - - 1298 - - - - Command not allowed by P4JsApi. - - - 1397 - - - 1398 - - - - P4JsApi broker error - - - 1409 - - - 1419 - - - - 'broker' must be followed by a command - - - 1409 - - - 1410 - - - - known commands are not allowed - - - 1419 - - - 1420 - - - - -H not supported in P4JsApi - - - 1302 - - - 1303 - - - - -p not enough arguments - - - 1309 - - - 1310 - - - 1332 - - - 1333 - - - - -P not supported in P4JsApi - - - 1325 - - - 1326 - - - - -x not enough arguments - - - 1341 - - - 1342 - - - - P4JsApi -x error - - - 1347 - - - - File not found. Requires absolute path - - - 1347 - - - 1348 - - - - -C not enough arguments - - - 1363 - - - 1364 - - - - -Q not supported in P4JsApi - - - 1370 - - - 1371 - - - - -L not supported in P4JsApi - - - 1377 - - - 1378 - - - - Invalid global option. - - - 1382 - - - 1383 - - - - P4JsApi error - - - 1397 - - - - p4 command error - - - 1480 - - - 1513 - - - 1521 - - - 1529 - - - - p4 command error: empty form - - - 1480 - - - 1481 - - - 1513 - - - 1514 - - - - p4 command error: unrecognized datatype in form - - - 1521 - - - 1522 - - - - p4 command error: last argument found was a '-i' - - - 1529 - - - 1530 - - - - - - - - - - No opened files found. - - - 258 - - - - You cannot submit an existing submitted changelist - - - 293 - - - - You do not have permission to submit this changelist - - - 298 - - - - Submit Changelist: %1 (%2) - - - 338 - - - 360 - - - - Changelist %1 contains shelved files and cannot be submitted. - - - 380 - - - - There was a problem deleting the shelve, please be sure you have permission to delete the shelve. - - - 396 - - - - - - There are no shelved files. - - - 497 - - - - Shelved change must be owned by user submitting the change. - - - 505 - - - - Submitting shelved files from a task stream is not supported. - - - 514 - - - - Changelist %1 contains non-shelved files and therefore the shelved files cannot be submitted. - - - 522 - - - - Move to Another Changelist... - - - 539 - - - - Revert Files - - - 540 - - - - Create changelist failed - - - 596 - - - - You do not have permission to move the following files: - - - 610 - - - - Files not reverted - - - 631 - - - - - - - - - - Write a changelist description: - - - 272 - - - - Choose files to submit: - - - 273 - - - - Choose additional options: - - - 274 - - - - Link jobs to changelist (optional): - - - 275 - - - - Out of date: Get latest revision to set up a resolve (this will not overwrite your copy of the file). - - - 319 - - - - Locked: File must be unlocked or submitted by user who locked it before you can submit. - - - 333 - - - - Moved: Edits to the file cannot be submitted until you resolve the moved file. - - - 340 - - - - Deleted: Edits to the file cannot be submitted without re-adding the file. - - - 347 - - - - Files to submit: - - - 457 - - - - Do not s&ubmit if resolved files have been modified - - - 561 - - - - On su&bmit: - - - 568 - - - - Submit all selected files - - - 573 - - - - Submit all files - - - 574 - - - - Don't submit unchanged files - - - 576 - - - - Revert unchanged files - - - 578 - - - - &Check out submitted files after submit - - - 580 - - - - There are %1 other files in this changelist that will not be submitted. - - - 1842 - - - - Unresolved: Resolve file(s) before submitting. - - - 326 - - - - form - - - 356 - - - - R&estrict access to changelist - - - 584 - - - - <a href>(more info)</a> - - - 586 - - - - Sa&ve - - - 416 - - - - J&ob: - - - 694 - - - - &Add - - - 703 - - - - B&rowse... - - - 712 - - - - &Job status upon submit: - - - 734 - - - - %1 does not exist. - - - 1406 - - - - This pending change has updated. Do you want to reset the file list? - - - 1784 - - - - - - - - - - <%1> - - - 1324 - - - - Unmapped (%1) - - - 1332 - - - - No unmapped files (%1) - - - 1334 - - - - Excluded files (%1) - - - 1338 - - - - - - - - - - Folder Diff Filter Error - - - 1453 - - - 1469 - - - - Skipping filter #%1 is not using a valid regular expression. - - - 1454 - - - 1470 - - - - - - - - - - Copying files to workspace - - - 51 - - - - &Stop - - - 52 - - - - files - - - 90 - - - - file - - - 92 - - - - (%1 of %2 selected) - - - 94 - - - - Copying %1 %2 to workspace %3 - - - 97 - - - - Get Revision Progress - - - 42 - - - - - - - - - - updating... - - - 85 - - - - - - - - - - Filter Options - - - 31 - - - - Apply the following settings to the file filter tree - - - 47 - - - - Show files that are specified in the view below: - - - 49 - - - - &Clear View - - - 65 - - - - &View of Current Graph - - - 68 - - - - Hide &deleted files - - - 79 - - - - Hide files with no &edits - - - 82 - - - - Hide files not mapped in &workspace view - - - 85 - - - - Hide files not &modified - - - 92 - - - - since date - - - 100 - - - - since changelist - - - 101 - - - - in the last week - - - 102 - - - - in the last month - - - 103 - - - - in the last six months - - - 104 - - - - in the last year - - - 105 - - - - &Set As Default - - - 153 - - - - &Filter - - - 158 - - - - Cancel - - - 159 - - - - Select Date - - - 206 - - - - Do you want the current settings to be -remembered as the default filter? - -The default filter will be applied to -Revision Graph on startup. - - - 229 - - - - - - - - - - Building filter tree... - - - 368 - - - - Cancel - - - 369 - - - - - - - - - - File Filter Tree - - - 46 - - - - Filter Options... - - - 69 - - - - - - - - - - Find File - - - 38 - - - - All or part of the &file name: - - - 19 - - - - &Previous - - - 41 - - - - &Next - - - 43 - - - - Highlight Files by Name - - - 50 - - - - &OK - - - 52 - - - - &Cancel - - - 53 - - - - - - - - - - Could not load OpenGL libraries. -Double check that OpenGL libraries are installed on your -system and that all appropriate drivers are updated. -See your system administrator if you require assistance. - - - - 887 - - - - Could not create an OpenGL rendering context. -Double check that your windowing system supports OpenGL -graphics and that all appropriate options are enabled. -See your system administrator if you require assistance. - - - - 894 - - - - This build does not support OpenGL. - - - - 901 - - - - - - - - - - Generating graph... - - - 68 - - - - Cancel - - - 69 - - - - You are in a twisty maze of passages, all alike. - - - 674 - - - - Too much junk! - - - 721 - - - - More than 32 edits selected. Remaining lines omitted. - - - 722 - - - - Remote depot - - - 1014 - - - - Spec depot - - - 1017 - - - - Unsubmitted changelist - - - 1020 - - - - Changelist %1 - - - 1023 - - - - remote - - - 1246 - - - - spec - - - 1250 - - - - pending - - - 1254 - - - - Times - - - 1317 - - - font for revision graph ellipsis - - - - No revisions to graph. - - - 1381 - - - - Save Graph As - - - 1557 - - - - Portable Document Format (*.pdf);;PostScript (*.ps) - - - 1558 - - - - ;;PNG Image (*.png);;JPEG Image (*.jpg) - - - 1559 - - - - ;;SVG Image (*.svg) - - - 1561 - - - - Unable to save file. - - - 1610 - - - 1632 - - - 1649 - - - - The graph is too large to save as a raster image. - - - 1625 - - - - Unknown file format. - - - 1655 - - - - %1 Error - - - 1661 - - - - - - - - - - Find changes made by &user: - - - 1304 - - - - &Search only in differences - - - 1305 - - - - - - Loading Time-lapse View data... - - - 61 - - - - Mode: - - - 272 - - - - Content range: - - - 273 - - - - Scale: - - - 274 - - - - Refresh - - - 282 - - - - Whitespace option - - - 319 - - - - Single revision - - - 325 - - - - Incremental diffs - - - 326 - - - - Multiple revisions - - - 327 - - - - About - - - 416 - - - - About Time-lapse View - - - 420 - - - - Help - - - 408 - - - - Original - - - 95 - - - - Current - - - 96 - - - - Unable to refresh Time-lapse View. - - - 518 - - - - Unable to open Time-lapse View. - - - 519 - - - - Slider revision: - - - 982 - - - - Starting revision: - - - 983 - - - - Ending revision: - - - 984 - - - - Selection added: - - - 985 - - - - Selection deleted: - - - 986 - - - - - - - - - - to - - - 22 - - - - - - - - - - Least recent - - - 566 - - - - Most recent - - - 567 - - - - 0 to 0 - - - 639 - - - - %1 to %2 - - - 641 - - - - - - - - - - Add - - - 44 - - - - Edit - - - 58 - - - - Delete - - - 72 - - - - Branch - - - 100 - - - - Add (branch w/ edit) - - - 128 - - - - Copy - - - 156 - - - - Merge - - - 193 - - - - Merge w/ Edit - - - 228 - - - - Ignore - - - 263 - - - - - - - - - - The first revision of an independently added (not branched) file. - - - 63 - - - - The first revision of a file that was directly branched from another file. - - - 65 - - - - A resolve in which the source completely overwrote the target. - - - 67 - - - - The last revision of a deleted file. - - - 69 - - - - A branch whose contents were edited before submit. - - - 71 - - - - An edit to an existing file. - - - 73 - - - - A resolve in which no changes were propagated from source to target. - - - 75 - - - - A resolve that required manual intervention, or whose results were edited before submit. - - - 77 - - - - A resolve completed via a purely automatic merge. - - - 79 - - - - - - - - - - Navigator - - - 223 - - - - Legend - - - 228 - - - - Clear filters (show all files) - - - 340 - - - - Diff selected revisions - - - 312 - - - - Diff Against Previous Revision - - - 423 - - - - Toolbar - - - 446 - - - - Sort &By - - - 606 - - - - &File Filter Tree - - - 627 - - - - Sho&w/Hide - - - 632 - - - - &Toolbar - - - 638 - - - - Loading workspace view... - - - 958 - - - - Cancel - - - 959 - - - - - - - - - - Select an argument to use with the application: - - - 20 - - - - Uppercase '%' specifiers send values to the application as a group -Lowercase '%' specifiers run the application once for each value - - - 30 - - - - OK - - - 32 - - - - Cancel - - - 33 - - - - Select Argument - - - 46 - - - - Argument - - - 53 - - - - Description - - - 54 - - - - %a - - - 57 - - - - Selected label - - - 58 - - - - %b - - - 61 - - - - Selected branch - - - 62 - - - - %C - - - 65 - - - - Selected changelists - - - 66 - - - 70 - - - - %c - - - 69 - - - - %D - - - 73 - - - - Selected files or folders (depot syntax used for depot files) - - - 74 - - - 78 - - - - %d - - - 77 - - - - %F - - - 81 - - - - Selected files (workspace syntax used for depot files) - - - 82 - - - 86 - - - - %f - - - 85 - - - - %i - - - 89 - - - - Selected workspace - - - 90 - - - - %J - - - 93 - - - - Selected jobs - - - 94 - - - 98 - - - - %j - - - 97 - - - - %O - - - - 101 - - - - Selected checked out files (when left pane has focus) -All checked out files (when right pane has focus) - - - 102 - - - 106 - - - - %o - - - - 105 - - - - %P - - - 109 - - - - Selected pending changelists - - - 110 - - - 114 - - - - %p - - - 113 - - - - %s - - - 117 - - - - Selected submitted changelists - - - 118 - - - 122 - - - - %S - - - 121 - - - - %t - - - 125 - - - - Selected stream - - - 126 - - - - %u - - - 129 - - - - Selected user - - - 130 - - - - $c - - - 133 - - - - Current workspace - - - 134 - - - - $p - - - 137 - - - - Current port - - - 138 - - - - $r - - - 141 - - - - Current workspace root - - - 142 - - - - $u - - - 145 - - - - Current user - - - 146 - - - - $D - - - 149 - - - - Arguments from prompt dialog - - - 150 - - - - $% - - - 153 - - - - Literal '%' character - - - 154 - - - - $$ - - - 157 - - - - Literal '$' character - - - 158 - - - - - - - - - - ToolDetail - - - 32 - - - - Associated application: - - - 61 - - - - Include folder in context menus - - - 170 - - - - Include folder in conte&xt menus - - - 171 - - - - Add to applicable context menus - - - 174 - - - - Add to applicable conte&xt menus - - - 175 - - - - Application: - - - 180 - - - - &Application: - - - 180 - - - - &Browse... - - - 182 - - - - Arguments: - - - 185 - - - - Ar&guments: - - - 185 - - - - Selec&t... - - - 187 - - - - Start In: - - - 190 - - - - &Start In: - - - 190 - - - - Bro&wse... - - - 192 - - - - Run tool in terminal window - - - 195 - - - - Run t&ool in terminal window - - - 195 - - - - Close window upon completion - - - 198 - - - - Close window &upon completion - - - 199 - - - - Prompt user for arguments - - - 202 - - - - &Prompt user for arguments - - - 203 - - - - Add file browser to prompt dialog - - - 206 - - - - Add &file browser to prompt dialog - - - 207 - - - - Refresh %1 upon completion - - - 210 - - - - &Refresh %1 upon completion - - - 211 - - - - &Ignore P4CONFIG files - - - 213 - - - - Description: - - - 216 - - - - &Description: - - - 216 - - - - Executable files (*.exe *.com *.bat);;All Files (*.*) - - - 421 - - - - All Files (*) - - - 423 - - - - Choose Application - - - 427 - - - - Select Start location - - - 456 - - - - - - - - - - %1 Console - - - 21 - - - - &Reply - - - 63 - - - - - - - - - - Import Preview - - - 30 - - - - Export Preview - - - 32 - - - - &Select the tools to import: - - - 39 - - - - &Select the tools to export: - - - 41 - - - - Select &All - - - 65 - - - - Select &None - - - 73 - - - - &Import - - - 82 - - - - &Export... - - - 84 - - - - Cancel - - - 87 - - - - Application: - - - 147 - - - - Arguments: - - - 156 - - - - - - - - - - Arguments: - - - 20 - - - - &Browse - - - 42 - - - - OK - - - 46 - - - - Cancel - - - 48 - - - - All files (*.*) - - - 104 - - - - All files (*) - - - 106 - - - - - - - - - - Invalid file path: %1 - - - 177 - - - 187 - - - - Invalid file revision path: %1 - - - 217 - - - 224 - - - - Invalid folder path: %1 - - - 255 - - - - Files in selected &folder - - - 264 - - - - Files in selected folder and its &subfolders (recursive) - - - 265 - - - - Choose which files to display in the graph: - - - 270 - - - - - - - - - - About %1 - - - 24 - - - - <h2>The %1</h2>Copyright &#169; 2002-%2 <A href>Perforce Software</A>.<br>All rights reserved.<hr width=10000>%3 <br> - - - 33 - - - - Rev. %1 - - - 57 - - - - - - - - - - Add Files to Workspace - - - 30 - - - - Do you want to copy files from the server to your workspace? - - - 39 - - - - &Yes, I would like to copy files to my workspace - - - 51 - - - - N&o, I will copy files to my workspace later - - - 52 - - - - Yes, I would like to copy files to my workspace - - - 56 - - - - No, I will copy files to my workspace later - - - 57 - - - - Check the folders and files to copy: - - - 60 - - - - Number of files to copy: - - - 80 - - - - - - - - - - Add Files Wizard - - - 77 - - - - Add Files Assistant - - - 81 - - - - Are you sure you want to cancel the %1? - - - 397 - - - - assistant - - - 398 - - - - wizard - - - 398 - - - - - - - - - - The path %1 - could not be found. - - - 180 - - - - - - - - - - Depot: - - - 112 - - - - &Owner: - - - 131 - - - - &Description: - - - 140 - - - - Depot type: - - - 151 - - - 159 - - - - Date modified: - - - 121 - - - - &local (a depot that resides on the current Perforce server) - - - 163 - - - - s&tream (a local depot that stores stream files) - - - 173 - - - - &spec (a local depot that stores changes to specifications) - - - 179 - - - - &remote (read-only access to files on a different Perforce server) - - - 183 - - - - archi&ve (a depot used to store obsolete revisions) - - - 188 - - - - &unload (where database records can be unloaded) - - - 196 - - - - (host:port) - - - 221 - - - - R&emote Server: - - - 223 - - - - Map: - - - 237 - - - - S&pecification mapping: - - - 251 - - - - File e&xtension: - - - 266 - - - - (appended to versioned specifications) - - - 268 - - - - <b>Note:</b> Remote depots are best used for integrating code drops from a remote depot to a local depot. For best performance, do not use remote depots for routine, daily work. - - - 282 - - - - <b>Note:</b> By default, the storage location is relative to the server root. Use an absolute path to store files in a location that is not under the server root. - - - 294 - - - - Storage lo&cation for versioned files: - - - 423 - - - - local (a depot that resides on the current Perforce server) - - - 439 - - - - stream (a local depot that stores stream files) - - - 444 - - - - archive (a depot used to store obsolete revisions) - - - 449 - - - - spec (a local depot that stores changes to specifications) - - - 454 - - - - Storage lo&cation for versioned specifications: - - - 456 - - - - remote (read-only access to files on a different Perforce server) - - - 468 - - - - &Mapped portion of remote server: - - - 470 - - - - unload (where database records can be unloaded) - - - 481 - - - - You must manually copy all versioned files to the new location on the server machine prior to changing the location in the depot specification. - -Proceed with changing the depot location? - - - - 590 - - - - The changes were not saved. Check for invalid form values. - - - - 686 - - - - Do you want to populate the spec depot? - -The current versions of all branch, depot, group, job, label, user and workspace specifications will be copied to the depot. - - - 709 - - - - - - - - - - Member - - - 379 - - - - Gr&oups: - - - 402 - - - 417 - - - - General - - - 481 - - - 809 - - - - Owner - - - 381 - - - - Type: - - - 173 - - - - &standard - - - 185 - - - - opera&tor (restricted to server management) - - - 192 - - - - servi&ce (used only for server-to-server process authentication) - - - 197 - - - - Note: Owners can edit the group's settings - - - 422 - - - - Form - - - 446 - - - - Reviews - - - 458 - - - - form - - - 566 - - - - &Group: - - - 647 - - - - A&dd - - - 654 - - - - &Browse... - - - 662 - - - - &Remove - - - 669 - - - - You do not have permission to add users to group '%1'. - - - 856 - - - - The group '%1' does not exist. - -Do you want to create a group with this name? - - - - 879 - - - - Select Groups - - - 915 - - - - You do not have permission to remove users from group '%1'. - - - 977 - - - - User '%1' already exists. - - - - 1079 - - - - Your password entries do not match. -Please re-enter your password. - - - 1100 - - - - Password changed: - - - 1530 - - - - &User: - - - 119 - - - 1507 - - - - &Full name: - - - 145 - - - 1512 - - - - &Job view: - - - 213 - - - 1516 - - - - &Password: - - - 156 - - - 1518 - - - - &Email: - - - 132 - - - 1524 - - - - Re&views: - - - 225 - - - 1526 - - - - &Type: - - - 1532 - - - - Last accessed: - - - 1510 - - - - Date modified: - - - 1514 - - - - (repeat password to confirm) - - - 168 - - - 1528 - - - - - - - - - - Select User - - - 40 - - - - User '%1' does not have permission to browse users. - - - 152 - - - - - - - - - - the Explorer - - - 305 - - - - any open file browser window - - - 307 - - - - the Finder - - - 309 - - - - Add Files to Server - - - 327 - - - - The Perforce server you selected is empty. - - - 335 - - - - Click the "Browse" button to browse the local file system for folders you want to add. -You can also drag files from %1. - - - 336 - - - - Bro&wse - - - 337 - - - - &Remove - - - 338 - - - - Ren&ame - - - 339 - - - - N&ew Folder - - - 340 - - - - Number of files to copy: - - - 341 - - - - Size of files to copy:: - - - 342 - - - - Folder %1 already contains a folder called %2. - Would you like to replace the existing folder? - - - 426 - - - - Folder %1 already contains a file called %2. - Would you like to replace the existing file? - - - 431 - - - - Choose Folder to Add - - - 558 - - - - New Folder - - - 595 - - - - mainline "%1" - - - 664 - - - - Choose Files to Add - - - 665 - - - - server - - - 682 - - - - The Perforce %1 you selected is empty. - - - 689 - - - - - - - - - - Choose Stream - - - 120 - - - - Do you want to use a stream for your workspace? - - - 128 - - - - No, I do not want to use a stream - - - 129 - - - - Yes, I know which stream I want to use - - - 130 - - - - Select the stream to link to your workspace: - - - 131 - - - - Check the stream folders and files to copy to your workspace: - - - 132 - - - - Number of files to copy: - - - 133 - - - - A stream is a predefined collection of files on a Perforce server. <a href="moreInfo">More Info</a> - - - 139 - - - - Streams are groups of related files, like branches and codelines, with additional properties. Most notably, streams are defined hierarchically using the mainline model, and Perforce generates the views for workspaces that are associated with a stream. Among the advantages of this approach are (1) change is propagated in a controlled way through the hierarchy that you define, and (2) you can compose the contents of a stream by defining its view, thereby providing all users who work in the stream with a consistent view of its contents. - - - 237 - - - - For further information, see the - - - 247 - - - - %1 online help - - - 249 - - - - OK - - - 259 - - - - - Number of files to copy: %n - Number of files to copy: %n - - - Number of files to copy: %n - Number of files to copy: %n - - - 330 - - - - - - - - - - Select folder to view - - - 60 - - - - - - All Depots - - - 304 - - - - Entire Workspace - - - 310 - - - - Select folder to search in - - - 314 - - - - - - - - - - Enter revision number - - - 467 - - - - &Browse... - - - 58 - - - - Changelist - - - 520 - - - - Workspace - - - 563 - - - - Date/Time - - - 594 - - - - Label - - - 636 - - - - Have - - - 653 - - - - Shelved - - - 709 - - - - - - - - - - Open Connection - - - 144 - - - - Type in connection settings or choose a recent or favorite connection: - - - 158 - - - - &Server: - - - 161 - - - - server:port - - - 164 - - - - &Browse... - - - 167 - - - - Locate DVCS root folder to use. - - - 168 - - - - &Connections - - - 170 - - - - Type in connection settings: - - - 190 - - - - No favorite connections - - - 195 - - - - &User: - - - 199 - - - - Br&owse... - - - 204 - - - - &New... - - - 206 - - - - Create a new user - - - 207 - - - - &Workspace: - - - 210 - - - - (optional) - - - 214 - - - - B&rowse... - - - 217 - - - - N&ew... - - - 219 - - - - Create a new workspace - - - 220 - - - - Select DVCS root folder - - - 801 - - - - Show &dialog at startup - - - 225 - - - - &Help - - - 237 - - - - Version: %1 - - - 249 - - - - Trying to connect to the server on '%1'... - - - 451 - - - - '%1' is a reserved %2 workspace name, and considered invalid by %3. - - - 480 - - - - '%1' is a service user and cannot be used to log into %2. - - - 556 - - - - Unable to connect to the server %1 as user '%2' - -Would you like to run %3 without a connection? - - - 652 - - - - Charset must be selected for Unicode server. - - - 607 - - - - %1 - no such user. - - - 610 - - - - A password is required for this user. -Missing or invalid password. - - - 613 - - - - The security level of this server requires the password to be reset. - - - 630 - - - - %1 - no such client. - - - 636 - - - - There isn't a valid DVCS repo at the location specified. - - - 671 - - - - A character encoding must be selected to connect to this server. - - - 788 - - - - - - - - - - Are you sure you want to cancel the assistant? - - - 126 - - - - Are you sure you want to cancel the wizard? - - - 127 - - - - - - - - - - System Info - - - 29 - - - - Jobs column - - - 107 - - - - Disabled features: - - - 110 - - - - Server refresh interval: %1 minute(s) %2 - - - 114 - - - - Maximum files displayed per changelists: %1 %2 - - - 118 - - - - Maximum file preview size: %1 k-byte(s) %2 - - - 121 - - - - Entries fetched at a time: %1 %2 - - - 125 - - - - Connection: - - - 143 - - - - (pushed from the server) - - - 165 - - - - Application: - - - 144 - - - - Perforce Applet Configuration: - - - 145 - - - - Swarm Configuration: - - - 146 - - - - Computer: - - - 147 - - - - Integration - - - 172 - - - - Labeling - - - 174 - - - - Revision Graph - - - 180 - - - - Time-lapse View - - - 182 - - - - Custom Tools - - - 184 - - - - Administration - - - 186 - - - - Connection Wizard - - - 188 - - - - Dashboard - - - 190 - - - - - - - - - - Diff - - - 192 - - - - 1st Path: - - - 203 - - - - 1 - - - 204 - - - - &Browse... - - - 205 - - - - &Workspace version on local disk - - - 206 - - - - &Latest revision - - - 207 - - - - &Have revision - - - 208 - - - - &Specify revision - - - 209 - - - - Br&owse... - - - 210 - - - - 2nd Path: - - - 212 - - - - 2 - - - 213 - - - - B&rowse... - - - 214 - - - - Wor&kspace version on local disk - - - 215 - - - - La&test revision - - - 216 - - - - Ha&ve revision - - - 217 - - - - Spe&cify revision - - - 218 - - - - Brows&e... - - - 219 - - - - &Diff - - - 243 - - - - Cancel - - - 244 - - - - - - %1 (last revision retrieved from depot) - - - 904 - - - 1086 - - - 1172 - - - 1303 - - - 1443 - - - - %1 (#%2 - last revision retrieved from depot) - - - 1098 - - - - %1 deleted at (#%2) - - - 1105 - - - 1147 - - - - %1 (#%2) - - - 1111 - - - 1155 - - - 1310 - - - - Have revision (last revision retrieved from depot) - - - 1139 - - - - - - - - - - Select a Folder or File - - - 147 - - - - &Depot - - - 162 - - - - &Workspace - - - 178 - - - - Cancel - - - 182 - - - - OK - - - 186 - - - - - - Browse for Folder - - - 282 - - - - Browse for Files/Folder - - - 284 - - - - Cancel - - - 291 - - - - OK - - - 295 - - - - - - - - - - No Files Found - - - 168 - - - - Find Files - %1 (%2) - - - 281 - - - - Save Search... - - - 505 - - - - Manage Searches... - - - 508 - - - - and - - - 592 - - - - or - - - 595 - - - 637 - - - 639 - - - - and - - - 617 - - - 619 - - - 649 - - - 651 - - - - "%1" - - - 708 - - - - Changelist - - - 816 - - - - Date - - - 821 - - - - &Clear Search - - - 992 - - - - &Save Search... - - - 998 - - - - &Manage Searches... - - - 1004 - - - - &Refresh Search - - - 1010 - - - - - - - - - - Close - - - 36 - - - - File Information - - - 69 - - - - - - - - - - Change Filetype - - - 67 - - - - C&hange base filetype and/or file attributes - - - 114 - - - - &Add additional file attributes - - - 115 - - - - Workspace related attributes: - - - 117 - - - - +&m Preserve local modification times - - - 118 - - - - +&w Always writable in workspace - - - 119 - - - - +&x Exec bit set in workspace - - - 120 - - - - +&k RCS keyword expansion - - - 121 - - - - Expansion of Id, Header, Author, Date, DateTime, Change, File, Revision - - - 122 - - - - +ko &RCS keyword expansion of $Id$, $Header$ only - - - 123 - - - - +C Server stores compressed file per re&vision (binary default) - - - 127 - - - - +&l Exclusive checkout (don't allow concurrent checkouts) - - - 124 - - - - Server storage method: - - - 126 - - - - +&D Server stores deltas in RCS format (text default) - - - 128 - - - - +&F Server stores full file per revision - - - 129 - - - - +&S Server limits the number of revisions stored to: - - - 131 - - - - &Base Filetype: - - - 154 - - - - Add files to &pending changelist: - - - 224 - - - - S&erver stores revisions using default method for base file type - - - 387 - - - - Do not chang&e how server stores file contents - - - 394 - - - - Change filetype - - - 407 - - - - - - - - - - Show files in a list or as thumbnails - - - 487 - - - - Tear off this folder view - - - 512 - - - - Folder: - - - 550 - - - - %1 (%2) - - - 606 - - - - %1 - %2 (%3) - - - 614 - - - - Sort By - - - 1261 - - - - Show Files As - - - 1266 - - - - - - - - - - %1 will now copy any server files you chose to your workspace and bring up a connection window with the following settings: - - - 30 - - - - %1 will now bring up a connection window with the following settings: - - - 36 - - - - - - - - - - Dock window - - - 31 - - - - - - - - - - form - - - 122 - - - - - - - - - - Cancel - - - 90 - - - 272 - - - - OK - - - 91 - - - 273 - - - - Go To Spec - - - 218 - - - - Changelist - - - 223 - - - 429 - - - - Enter Number - - - 268 - - - - Enter Name - - - 269 - - - - Exact Match - - - 270 - - - - Enter Stream Name - - - 271 - - - - %1 cannot be accessed because it has been unloaded. - - - 455 - - - - %1 does not exist. - - - 433 - - - - %1: '%2' - - - 434 - - - - - - - - - - &Group: - - - 185 - - - - &Subgroups: - - - 224 - - - - &Users: - - - 195 - - - - &Maximum number of results returned per server request: - - - 268 - - - - Ma&ximum number of database rows scanned per server request: - - - 288 - - - - Maximum loc&k time of database tables per server request (msec): - - - 308 - - - - form - - - 497 - - - - days - - - 518 - - - 538 - - - - Su&bgroup: - - - 641 - - - - &Add - - - 648 - - - - Bro&wse... - - - 658 - - - - &Remove - - - 665 - - - - Member - - - 702 - - - - Note: Owners can edit the group's settings - - - 205 - - - - Duration be&fore login session times out: - - - 322 - - - - Duration before &password expires: - - - 363 - - - - Appl&y - - - 410 - - - - Owner - - - 704 - - - - Us&er: - - - 759 - - - - A&dd - - - 766 - - - - Br&owse... - - - 776 - - - - Remo&ve - - - 783 - - - - Duration be&fore login session times out (days - hh:mm:ss): - - - 879 - - - - Duration before &password expires (days - hh:mm:ss): - - - 886 - - - - The group '%1' cannot be added as a subgroup -because it would cause a recursive relationship. - - - - 1056 - - - - 1 day, - - - 928 - - - 944 - - - - %1 days, - - - 928 - - - 944 - - - - hrs - - - 929 - - - 945 - - - - The changes were not saved. Check for invalid form values. - - - - 981 - - - - Select Subgroups - - - 1086 - - - - Select Users - - - 1142 - - - - The group must contain at least one user -or one subgroup. - - - 1398 - - - - Group '%1' already exists. - - - - 1411 - - - - Invalid ID - - - 1441 - - - - A valid Group name is required to save the form. - - - 1442 - - - - Error in group specification. Invalid MaxResults: -'%1' - - - - 1518 - - - - Error in group specification. Invalid MaxScanRows: -'%1' - - - - 1534 - - - - Error in group specification. Invalid MaxLockTime: -'%1' - - - - 1550 - - - - - - - - - - &Changelist: - - - 314 - - - - &Add - - - 320 - - - - B&rowse... - - - 328 - - - - form - - - 350 - - - - - - - - - - UILabelSyncPreviewWindow - - - 102 - - - - Files - - - 103 - - - - Cancel - - - 104 - - - 133 - - - - Label Checked Files - - - 105 - - - - Label: - - - 130 - - - - No Files. - - - 145 - - - - Label in sync - - - 202 - - - - - - - - - - Label '%1' is an automatic label. -Files cannot be associated with this type of label. - - - 57 - - - - Label Files - - - 83 - - - 323 - - - - View: - - - 146 - - - - Cancel - - - 149 - - - - Lab&el: - - - 92 - - - - Br&owse... - - - 97 - - - - A&t latest revision - - - 122 - - - - Action: - - - 142 - - - - Appl&y label to file revisions - - - 143 - - - - Re&move label from file revisions - - - 144 - - - - E&xclude deleted revisions - - - 145 - - - - Files/Folders: - - - 147 - - - - &Preview - - - 148 - - - - &Label - - - 150 - - - - This label doesn't exist. Create it? - - - 263 - - - - Invalid Label - - - 410 - - - - You must be the owner of the label to use it. - - - 411 - - - - <No view defined for this label.> - - - 512 - - - - - - - - - - %1: - - - 64 - - - - %1 does not exist. - - - 179 - - - - - - - - - - Log - - - 45 - - - - - - - - - - New Workspace - - - 20 - - - - &Save - - - 34 - - - - &Help - - - 35 - - - - - - - - - - Move - - - 35 - - - - Files/Folders: - - - 45 - - - - Move to: - - - 55 - - - - Location: - - - 59 - - - - N&ew location: - - - 70 - - - - &Browse... - - - 74 - - - - &Add files to pending changelist: - - - 83 - - - - Sub&mit... - - - 98 - - - - Cancel - - - 101 - - - - &Save to Changelist - - - 103 - - - - The following files are deleted from the depot: - - - 179 - - - - - - - - - - A password is not required. - - - 28 - - - - This server does not permit new users to create their own accounts. - - - 34 - - - - User '%1' already exists. - - - 40 - - - - &User name: - - - 74 - - - - Name used for log in (no spaces allowed) - - - 78 - - - - &Full name: - - - 87 - - - - First and last name - - - 90 - - - - &Password*: - - - - 105 - - - - Enter password twice - - - 108 - - - - &Email: - - - 122 - - - - &Save - - - 132 - - - - &Help - - - 139 - - - - New User (%1) - - - 203 - - - - - - - - - - R&estrict access to changelist - - - 162 - - - - <a href>(more info)</a> - - - 164 - - - - Access type: - - - 134 - - - - &Description: - - - 188 - - - - &Files - - - 197 - - - - &Shelved Files - - - 214 - - - - &Unshelve - - - 219 - - - - Changelist: - - - 237 - - - - Date: - - - 238 - - - - Workspace: - - - 241 - - - - User: - - - 242 - - - - A&pply - - - 331 - - - - &Jobs - - - 616 - - - - Jo&b: - - - 696 - - - - &Add - - - 703 - - - - B&rowse... - - - 712 - - - - %1 does not exist. - - - 923 - - - - restricted - - - 1249 - - - - public - - - 1249 - - - - - - - - - - &Apply - - - 83 - - - - &Help - - - 85 - - - - Preferences - - - 46 - - - - - - - - - - Welcome to the Connection Setup Wizard - - - 34 - - - - Welcome to the Connection Setup Assistant - - - 36 - - - - This wizard helps you set up a connection with a Perforce Server. - - - 51 - - - - This assistant helps you set up a connection with a Perforce Server. - - - 56 - - - - Type in the server connection settings: - - - 60 - - - 84 - - - - &Type in the server connection settings: - - - 77 - - - - &Host: - - - 78 - - - - &Port number: - - - 79 - - - - Host: - - - 85 - - - - Port number: - - - 86 - - - - The server stores files managed by Perforce. - - - 107 - - - - More Info - - - 109 - - - - A Perforce server stores files so that multiple users can access them. You can copy files from the server to a workspace on your computer. You can also send files from your workspace to the server, where they can be retreived by other users. - - - - 218 - - - - For further information, see the - - - 223 - - - - %1 online help - - - 225 - - - - OK - - - 235 - - - - - - - - - - None - - - 180 - - - 198 - - - - (in use) - - - 182 - - - 202 - - - - The shortcut is already in use for the %1 "%2". - -Reassign %3 shortcut key? - - - 217 - - - - - - - - - - Showing only workspaces available for use on this computer. - - - 208 - - - - Choose a stream from the list below to merge to the target stream: - - - 231 - - - - <a href>Display all streams</a> if you need to bypass the configured flow of change and merge from another stream. - - - 235 - - - - &New %1... - - - 621 - - - - Switch - - - 663 - - - - Merge - - - 665 - - - - Copy - - - 667 - - - - Your current workspace is not associated with the following stream. Switch to another workspace to complete the %1. - - - 670 - - - - %1 to stream: - - - 672 - - - - - - - - - - Cancel - - - 205 - - - - OK - - - 206 - - - - New Workspace - - - 226 - - - - Automatically get the latest revisions after switching your workspace - - - 221 - - - - Select Submitted Changelist - - - 461 - - - - Select Pending Changelist - - - 463 - - - - Select User - - - 465 - - - - Select Workspace - - - 467 - - - - Select Stream - - - 469 - - - - Select Mapping - - - 471 - - - - Select Label - - - 473 - - - - Select Jobs - - - 475 - - - - Select Group - - - 477 - - - - Select Depot - - - 479 - - - - Select Pending Shelved Changelist - - - 481 - - - - Generic Spec Picker - - - 483 - - - - - - - - - - &Print... - - - 213 - - - - &Back Out - - - 219 - - - - &Edit - - - 228 - - - - Close - - - 252 - - - - &Apply - - - 321 - - - - Save as &Numbered Changelist - - - 329 - - - - The - - - 642 - - - - , ' - - - 644 - - - - ' has been unloaded. - - - 646 - - - - %1 is unable to perform the requested operation without a server connection. - - - 731 - - - - Do you want to save changes to %1? - - - 851 - - - - &Submit - - - 312 - - - - &Save - - - 862 - - - - You are about to edit a locked %1 owned by another user. -Are you sure you want to continue? - - - 80 - - - - New - - - 835 - - - - Do you want to save your new change as a numbered change? - - - 846 - - - - Do you want to save your default change as a numbered change? - - - 848 - - - - &Don't Save - - - 863 - - - - - - - - - - Tear off this view - - - 280 - - - - Show/hide filtering - - - 120 - - - - Show/hide navigator - - - 128 - - - - Unloaded... - - - 147 - - - - Show streams in a graph, list or tree - - - 255 - - - - none - - - 361 - - - - Depot - - - 362 - - - - is - - - 362 - - - - %1 (%2) - - - 494 - - - - %1 - %2 (%3) - - - 502 - - - - - - - - - - Options: - - - 94 - - - - Get Revision - - - 71 - - - - Get or replace the following files/folders: - - - 111 - - - - &Force Operation (replace file even if you already have the revision specified) - - - 120 - - - - Only get revisions for files listed in c&hangelist - - - 122 - - - - Re&move files from workspace if they are not in label - - - 123 - - - - Get &latest revision - - - 99 - - - - Preview: - - - 115 - - - - Safe &Update: Don't overwrite files that were changed without being checked out. - - - 121 - - - - &Preview - - - 210 - - - - &Hide Preview - - - 211 - - - - &Get Revision - - - 212 - - - - Cancel - - - 213 - - - - Please enter a revision specifier. - - - 314 - - - - The revision specified does not exist - - - 329 - - - - %1#%2 - deleted as %3 - - - 393 - - - %1#%2=file#revision_number, %3=file - - - - %1#%2 - added as %3 - - - 402 - - - %1#%2=file#revision_number, %3=file - - - - %1#%2 - - - 411 - - - %1#%2=file#revision_number - - - - %1 - not updated (marked for delete) - - - 426 - - - - %1 - not updated (checked out) - - - 429 - - - - %1 - not updated (safe sync used and modified) - - - 432 - - - - Files up-to-date - - - 450 - - - - - - - - - - Pending - - - 29 - - - - Submitted - - - 36 - - - - Shelved - - - 41 - - - - Branches - - - 45 - - - - History - - - 55 - - - - Depot - - - 59 - - - - Files - - - 63 - - - - Files in Folder - - - 65 - - - - Find File - - - 68 - - - - Perforce Applet - - - 70 - - - - Log - - - 72 - - - - Dashboard - - - 74 - - - - %1 (%2) - - - 292 - - - - - - - - - - Change the sort order of files - - - 62 - - - - Depot - - - 95 - - - - Filter depot - - - 99 - - - - Filter workspace - - - 104 - - - - - - - - - - No Items Found - - - 292 - - - - Tasks: - - - 335 - - - - No tasks available - - - 361 - - - - Files in my workspace are in pending changelists - - - 492 - - - - Files in my workspace need to be resolved - - - 500 - - - - Jobs on the server are in my Job View - - - 507 - - - - In order to use this feature, you must be connected to a 2009.2 or later server. - - - 640 - - - - - More than <a href="jobs">%n job(s)</a> are in your <a href="jobview">job view</a> - - - - More than <a href="jobs">%n job(s)</a> are in your <a href="jobview">job view</a> - - - - 1004 - - - - - <a href="jobs">%n job(s)</a> are in your <a href="jobview">job view</a> - - - - <a href="jobs">%n job(s)</a> are in your <a href="jobview">job view</a> - - - - 1006 - - - - is having trouble translating the location "%1" to a valid workspace location. To filter using this location, drag the folder from the workspace tree to the workspace folder field or include the depot location in your workspace view. - - - 1377 - - - - Edit settings - - - 1961 - - - - - Choose a folder with fewer files in order to see current tasks and status. The selected Workspace folder has more than %n depot files. - Choose a folder with fewer files in order to see current tasks and status. The selected Workspace folder has more than %n depot files. - - - Choose a folder with fewer files in order to see current tasks and status. The selected Workspace folder has more than %n depot files. - Choose a folder with fewer files in order to see current tasks and status. The selected Workspace folder has more than %n depot files. - - - 1617 - - - - - <a href="test">Get latest revisions (%n file(s))</a> - <a href="test">Get latest revisions (%n files)</a> - - - <a href="test">Get latest revisions (%n file(s))</a> - <a href="test">Get latest revisions (%n files)</a> - - - 989 - - - - - Merge from %1 (%n change(s)) - Merge from %1 (%n changes) - - - Merge from %1 (%n change(s)) - Merge from %1 (%n changes) - - - 1031 - - - - - Merge to %1 (%n change(s)) - Merge to %1 (%n changes) - - - Merge to %1 (%n change(s)) - Merge to %1 (%n changes) - - - 1033 - - - - - Copy to %1 (%n change(s)) - Copy to %1 (%n changes) - - - Copy to %1 (%n change(s)) - Copy to %1 (%n changes) - - - 1064 - - - - - Copy from %1 (%n change(s)) - Copy from %1 (%n changes) - - - Copy from %1 (%n change(s)) - Copy from %1 (%n changes) - - - 1066 - - - - <br><a href="merge">merge</a> required prior to copy - - - 1074 - - - - - <a href>View %n pending changelist(s) - <a href>View %n pending changelists - - - <a href>View %n pending changelist(s) - <a href>View %n pending changelists - - - 1147 - - - - - (%n file(s))</a> - (%n files)</a> - - - (%n file(s))</a> - (%n files)</a> - - - 1148 - - - - <a href>Select workspace</a> - - - 456 - - - - Workspace folder: - - - 260 - - - - Dashboard last updated: - - - 320 - - - - <a href>Update now</a> - - - 326 - - - - Folder status: - - - 379 - - - 586 - - - - Display a task when: - - - 417 - - - - Check for updates: - - - 419 - - - - Manual user refresh - - - 421 - - - - Every - - - 422 - - - - minutes - - - 433 - - - - No workspace is selected - - - 455 - - - - My stream or its parent may receive merges - - - 463 - - - - My stream or its parent may be copied - - - 471 - - - - Files in my workspace are not at the latest revisions - - - 484 - - - - Show changelists when the folder is out of date - - - 588 - - - - Display the number of files with potential conflicts - - - 591 - - - - - <a href>Resolve conflicts (%n file(s))</a> - <a href>Resolve conflicts (%n files)</a> - - - <a href>Resolve conflicts (%n file(s))</a> - <a href>Resolve conflicts (%n files)</a> - - - 1187 - - - - Changelists from remote depots cannot be shown - - - 1468 - - - - All files are at the latest revision. - - - 1474 - - - - Not updated with the following changelists (%1) - - - 1478 - - - - - - - - - - OK - - - 22 - - - - UITextWindow - - - 30 - - - - - - - - - - Ready to Add Files - - - 21 - - - - &Start - - - 109 - - - - Files will be added to the root of the depot named depot - - - 116 - - - - Files will be added to the depot: - - - 127 - - - - Files will be in the stream: - - - 129 - - - - To begin copying files to the workspace and add them to the Perforce Server, click "Start". - - - 133 - - - - Number of files to copy: - - - 134 - - - - Size of files to copy: - - - 135 - - - - - - - - - - &Pause - - - 108 - - - - &Continue - - - 109 - - - - Updating Workspace and Server - - - 170 - - - - Copying files to workspace... - - - 258 - - - 392 - - - - Ready to upload files to server... - - - 333 - - - - Error copying "%1" from "%2": (%3) - - - 365 - - - - Copying files paused... - - - 399 - - - - Uploading files to server... - - - 449 - - - - Error adding file to server: File is locked - - - 479 - - - - Populated server with files - - - 538 - - - - Connection Setup - - - 599 - - - - Add Files - - - 601 - - - - Wizard - - - 604 - - - - Assistant - - - 606 - - - - Completing the %1 %2 - - - 608 - - - - N&o - - - 819 - - - - - - - - 625 - - - 631 - - - - Error adding files to server: - - - 627 - - - - - - - - - 631 - - - - Are you sure you want to stop copying files to the workspace? - - - 805 - - - - &Yes - - - 818 - - - - - - - - - - Connect to the server with an existing or new user account. - - - 60 - - - - Name used to log in (no spaces allowed) - - - 76 - - - - First and last name - - - 83 - - - - Enter password twice - - - 90 - - - - &Create a new user account: - - - 102 - - - 112 - - - - B&rowse... - - - 104 - - - - &Full name: - - - 106 - - - - &Password*: - - - 107 - - - - &Email: - - - 108 - - - - Browse... - - - 114 - - - - &Log in to server "%1" with an existing user account: - - - 253 - - - - Full name: - - - 80 - - - 116 - - - - Log In - - - 39 - - - - &User: - - - 62 - - - 103 - - - - User: - - - 72 - - - 113 - - - 115 - - - - Password: - - - 84 - - - - Email: - - - 91 - - - - U&ser: - - - 105 - - - - Password*: - - - 117 - - - - Email address: - - - 118 - - - - User name not found - - - 352 - - - - User '%1' not found. - - - 353 - - - - - - - - - - &Back - - - 67 - - - - &Next - - - 68 - - - - Connection Setup Wizard - - - 69 - - - - Connection Setup Assistant - - - 76 - - - - To close this wizard, click Finish. - - - 172 - - - - - - - - - - Choose Workspace - - - 96 - - - 390 - - - - Choose an existing workspace or create a new one. - - - 118 - - - 391 - - - - No spaces allowed in name - - - 123 - - - - &Select a workspace that has already been set up for your computer: - - - 136 - - - - &Create a new workspace: - - - 137 - - - - &Workspace name: - - - 138 - - - - &Location: - - - 139 - - - - B&rowse... - - - 140 - - - - Select a workspace that has already been set up for your computer: - - - 144 - - - - Create a new workspace: - - - 145 - - - - Workspace name: - - - 146 - - - - Location: - - - 147 - - - - Browse... - - - 148 - - - - The workspace is where you keep local copies of the files managed by Perforce. - - - 155 - - - - More Info - - - 157 - - - - Location - - - 326 - - - - Create a New Workspace - - - 378 - - - - A folder named "%1" already exists at the location "%2". - -Do you want to choose a different name or location for the workspace? - - - 583 - - - - For further information, see the - - - 992 - - - - %1 online help - - - 994 - - - - Could not create a directory - - - 550 - - - - Stream - - - 331 - - - - Cannot create a directory named '%1' - - - 599 - - - - Select the location for the top-level workspace folder: - - - 940 - - - - Select Workspace Location - - - 941 - - - - A workspace is a folder on your computer for keeping files you're working on. You can copy files from the Perforce server to your workspace for editing or viewing. When you're done working on a file, you can copy your version of the file back to the server, thereby making it available to other Perforce users. - -If you connect to more than one Perforce server, create a different workspace for each server. - - - - - 983 - - - - OK - - - 1004 - - - - - - - - - - (no workspace selected) - - - 58 - - - - Switch to Workspace... - - - 63 - - - - New Workspace... - - - 68 - - - - - - - - - - Workspace - - - 156 - - - - Depot - - - 157 - - - - - - - - - - Update Spec Depot - - - 20 - - - - Select the specifications that you want to add to the depot: - - - 28 - - - - &Branch Mapping - - - 36 - - - - &Depot - - - 38 - - - - &Group - - - 40 - - - - &Job - - - 42 - - - - &Label - - - 44 - - - - U&ser - - - 46 - - - - &Workspace - - - 48 - - - - &Update - - - 55 - - - - - - - - - - Advanced Settings - - - 25 - - - - &Save cookies from applets - - - 32 - - - - &Remove Cookies - - - 34 - - - - All&ow applets to store data locally - - - 43 - - - - &Manually configure web proxy used by applets for internet access: - - - 47 - - - - A&ddress: - - - 55 - - - - &Port: - - - 60 - - - - Are you sure you want to remove all cookies? - - - 159 - - - - &Remove - - - 162 - - - - - - - - - - Perforce Applet - - - 37 - - - - - - - - - - Browse... - - - 78 - - - - %1(%2): %3 - - - 166 - - - 383 - - - - Select HTML File - - - 330 - - - - %1(%2): %3<br> - - - 382 - - - - Save File - - - 395 - - - - - - - - - - What's New - - - 43 - - - - <html><body><h1>Failed to load the What's New page</h1><br><i>If your machine is behind a firewall, you might need to adjust your proxy settings in Preferences/Applets/Configure Proxy</i></body></html> - - - 103 - - - - http://www.perforce.com/applications/p4v/latestP4V_en.html - - - 112 - - - - - - - - - - Open in preferred application - - - 8 - - - - Get latest revision - - - 9 - - - - Diff against have revision - - - 10 - - - - Diff against previous revision - - - 11 - - - - Diff against source revision - - - 12 - - - - Diff against latest revision - - - 13 - - - - Diff against workspace file - - - 14 - - - - Revert file - - - 15 - - - - Revert file if unchanged - - - 16 - - - - Submit file - - - 17 - - - - Submit pending changelist - - - 18 - - - - Shelve file - - - 19 - - - - Unshelve file - - - 20 - - - - Check out file - - - 21 - - - - Check out and open file - - - 22 - - - - Get this revision - - - 23 - - - - Unshelve files - - - 24 - - - - Delete shelved files - - - 25 - - - - Show file history - - - 26 - - - - Merge/Integrate using the revision - - - 27 - - - - View pending changelist - - - 28 - - - - Edit pending changelist - - - 29 - - - - View submitted changelist - - - 30 - - - - Edit submitted changelist - - - 31 - - - - Get revisions at submitted changelist - - - 32 - - - - Merge/Integrate using the changelist - - - 33 - - - - View branch mapping - - - 34 - - - - Edit branch mapping - - - 35 - - - - Merge/Integrate using the branch mapping - - - 36 - - - - View label - - - 37 - - - - Edit label - - - 38 - - - - Get revisions at label - - - 39 - - - - Label files - - - 40 - - - - View workspace - - - 41 - - - - Edit workspace - - - 42 - - - - Switch to workspace - - - 43 - - - - View job - - - 44 - - - - Edit job - - - 45 - - - - View stream - - - 46 - - - - Edit stream - - - 47 - - - - Merge/Integrate to stream - - - 48 - - - - Copy to stream - - - 49 - - - - Expand/Collapse folder - - - 50 - - - - Check out folder - - - 51 - - - - Show folder history - - - 52 - - - - - - - - - - updating... - - - 228 - - - - list - - - 863 - - - - - - - - - - The following files were not marked for add, since they are 'ignored': - - - 37 - - - - Do not warn me about ignored files. - - - 51 - - - - - - - - - - workspace - - - 36 - - - - - - - - - - Submit Changelist Progress - - - 21 - - - - &Stop - - - 33 - - - - File: %1 %2% complete - - - 107 - - - - Submitting file %1 of %2 to depot - - - 111 - - - - - - - - - - Reverting will undo the following file changes: - - - 109 - - - - File - - - 170 - - - - Do you want to proceed with the revert of the selected files? - - - 217 - - - - Don't &warn me about file changes - - - 225 - - - - &Revert - - - 236 - - - - &Don't Revert - - - 240 - - - - Reverting will undo the following changed files: - - - 253 - - - - Do you want to proceed with the revert of these files? - - - 254 - - - - - - - - - - The following file revisions in your workspace have been purged from the depot: - - - 43 - - - - If you revert these files, the workspace file will be replaced with the latest revision - - - 69 - - - - &Revert - - - 84 - - - - &Don't Revert - - - 87 - - - - - - - - - - === Date: %1 - - - 175 - - - - === Config: %1 - - - 200 - - - - - - - - - - &Dates: - - - 23 - - - - OS format - - - 32 - - - - Perforce standard (yyyy/mm/dd hh:mm:ss) - - - 33 - - - - - - - - - - &No default, prompt for selection - - - 32 - - - - &Don't prompt again. Remember my choice as the default. - - - 38 - - - - &Last - - - 41 - - - - All re&visions - - - 42 - - - - You must select at least 2 revisions - - - 46 - - - - S&pecify revision using: - - - 51 - - - - revisions - - - 105 - - - - &From - - - 115 - - - - &To - - - 116 - - - - - - Time-lapse Image Range - - - 252 - - - - How many revisions of the file would you like Time-lapse to retrieve? - - - 255 - - - - <i>Your upper bound must be greater or equal to the lower bound.</i> - - - 310 - - - - - - - - - - Job Query Builder - - - 76 - - - - Query preview: - - - 77 - - - - - - - - - - Construct a search query - - - 34 - - - - - - - - - - Browse... - - - 299 - - - - - - - - - - Construct a file path - - - 41 - - - - - - - - - - Current User - - - 188 - - - - Current workspace - - - 197 - - - 241 - - - 245 - - - - Current depot or workspace tree selection - - - 205 - - - - - - - - - - Name - - - 19 - - - - Stream Root - - - 20 - - - - Stream w&idth: - - - 22 - - - - Display stream: - - - 30 - - - - - - - - - - Show date and ti&me as: - - - 23 - - - - Server time - - - 32 - - - - Local time - - - 33 - - - - - - - - - - The %1 cannot be accessed because it has been unloaded. - -To use it, it must first be reloaded. - - - 332 - - - - Unloaded %1s (%2) - - - 418 - - - - - - - - - - Submitted - - - 141 - - - 368 - - - - is - - - 328 - - - 330 - - - - and - - - 334 - - - - Changelist - - - 372 - - - - - - is between - - - 157 - - - - - - - - - - File name - - - 141 - - - - Folder path - - - 144 - - - - - - - - - - &Browse... - - - 72 - - - - and - - - 371 - - - 470 - - - - Changelist - - - 390 - - - - Date - - - 484 - - - - - - - - - - Filter: - - - 78 - - - - Clear filter - - - 106 - - - - Apply saved filter - - - 124 - - - - Unloaded... - - - 138 - - - - Refresh filter - - - 152 - - - - Save Filter... - - - 306 - - - - Save as a named filter - - - 307 - - - - Manage Filters... - - - 310 - - - - Manage named filters - - - 311 - - - - no matches - - - 417 - - - zero rows retrieved - - - - - %n match(es) - %n matches - - - %n match(es) - %n matches - - - 420 - - - All rows retrieved - - - - - %n+ match(es) - %n+ matches - - - %n+ match(es) - %n+ matches - - - 421 - - - More rows to be retrieved - - - - - - Keywords or search query: - - - 632 - - - - Files match any of the following file paths: - - - 636 - - - - - - Search in: - - - 685 - - - - Find - - - 689 - - - - Apply saved search - - - 695 - - - - Clear search - - - 706 - - - - Path - - - 728 - - - - Raw Query - - - 729 - - - - Name matches any of the following: - - - 737 - - - - Submission date or changelist: - - - 749 - - - - Include deleted depot files - - - 755 - - - - Filter - - - 762 - - - - - - - - - - Configure Monitoring - - - 21 - - - - Monitoring &disabled - - - 25 - - - - Monitor &active processes only - - - 29 - - - - Monitor &both active and inactive processes - - - 33 - - - - - - - - - - Password Security Level - - - 22 - - - - Select security level for all passwords: - - - 26 - - - - &3 (ticket-based authentication required) - - - 30 - - - - &2 (strong passwords required) - - - 34 - - - - &1 (passwords required) - - - 38 - - - - &0 (passwords not required) - - - 42 - - - - - - - - - - Setup is now complete. - - - 104 - - - - Server: - - - 105 - - - - User: - - - 106 - - - - Workspace: - - - 107 - - - - Stream: - - - 108 - - - - - - - - - - Choose a Depot Type - - - 49 - - - - Use a streams depot - - - 63 - - - - Use a classic depot - - - 65 - - - - Streams depots enable you to build change propagation rules and workspace definitions into codelines. <a href="moreInfo">More Info</a> - - - 77 - - - - Depot: - - - 84 - - - - Files will be added to the mainline stream. - - - 95 - - - - Mainline name: - - - 100 - - - - Files will be added to the depot named depot. - - - 116 - - - - Streams provide a robust branching model layered on Perforce's core behaviors. If your organization is new to Perforce, streams can provide you with an effective branching strategy without having to design it from scratch. Streams give you straightforward tools for generating codelines and guiding users in the most effective management of their work. If your organization already uses Perforce, you may already have a branching model that works for you, with the tools and processes in place to support it. In that case, "classic" (non-streams) depots may be preferable. Whether you start with stream depots or classic depots, you can always add depots of either type at any time; they can be used side-by-side. - - - 257 - - - - For further information, see - - - 272 - - - - <a href> About Streams</a>. - - - 276 - - - - OK - - - 289 - - - - - - - - - - File Searches - - - 58 - - - 109 - - - 152 - - - - Folder Diff - - - 60 - - - 145 - - - - Reconcile - - - 62 - - - 147 - - - - Stream Graph - - - 64 - - - 149 - - - - %1 Filters - - - 67 - - - 111 - - - 154 - - - - Add File Searches - - - 85 - - - - Add Filter Files - - - 87 - - - - Add Reconcile Filter - - - 89 - - - - Add Stream Graph Filter - - - 91 - - - - Add %1 Filter - - - 93 - - - - Edit Filter - - - 141 - - - - Menu Item: - - - 164 - - - - &Name: - - - 166 - - - - Place&ment: - - - 173 - - - - N&ew Folder... - - - 178 - - - - Filter: - - - 182 - - - - Show only workspaces available for use on this computer - - - 334 - - - - Match case when filtering by name - - - 353 - - - - Changelist - - - 643 - - - - Date - - - 648 - - - - - - [Current User] - - - 576 - - - 289 - - - - [Current Workspace] - - - 577 - - - 290 - - - - [Current Host] - - - 578 - - - 291 - - - - [Current P4Port] - - - 579 - - - 292 - - - - [Current Charset] - - - 580 - - - 293 - - - - - - - - - - Filter... - - - 50 - - - - Manage Filters - - - 58 - - - - File Searches - - - 132 - - - 160 - - - - Folder Diff Filters - - - 134 - - - - Reconcile Filters - - - 136 - - - - Stream Graph Filters - - - 138 - - - - %1 Filters - - - 140 - - - 169 - - - - Folder Diff - - - 162 - - - - Reconcile - - - 164 - - - - Stream Graph - - - 166 - - - - Add File Searches - - - 211 - - - - Add Filter Files - - - 213 - - - - Add Reconcile Filter - - - 215 - - - - Add Stream Graph Filter - - - 217 - - - - Add %1 Filter - - - 219 - - - - Are you sure you want to delete the query '%1'? - - - 319 - - - - Do you want to delete all of the queries? - - - 331 - - - - Do you want to delete the folder '%1' and all of the queries it contains? - - - 333 - - - - - - - - - - Add Filter - - - 76 - - - - %1 Filters - - - 79 - - - - Named Filter: - - - 90 - - - - &Name: - - - 92 - - - - Place&ment: - - - 99 - - - - N&ew Folder... - - - 104 - - - - - - - - - - Reconcile - - - 120 - - - - Folder Diff - - - 122 - - - - Stream Graph - - - 124 - - - - %1 Filters - - - 125 - - - - - - - - - - Merge - - - 37 - - - 121 - - - - Cancel - - - 38 - - - 98 - - - 122 - - - 146 - - - 168 - - - 189 - - - - Copy - - - 39 - - - 99 - - - - Your stream is not configured to receive merges in this direction. - -Are you sure you want to merge instead of copy? - - - 83 - - - - Force the merge, I am working outside the established stream configuration - - - 88 - - - 158 - - - - Continue with Merge - - - 90 - - - 160 - - - - Force the preview, I am working outside the established stream configuration - - - 94 - - - 164 - - - 184 - - - - Continue with Preview - - - 96 - - - 118 - - - 143 - - - 166 - - - 186 - - - - Your stream is not configured to receive copies in this direction. - -Are you sure you want to copy instead of merge? - - - 104 - - - - Force the copy, I understand that I am working outside the established stream configuration - - - 109 - - - - Continue with Copy - - - 111 - - - 137 - - - 179 - - - - Force the preview, I understand that I am working outside the established stream configuration - - - 116 - - - - There are outstanding changes in the target which will be overwritten if you proceed with copying. It is recommended that you perform a merge prior to copying. - - - - 129 - - - - Force the copy, I understand that the changes will be overwritten. - - - 135 - - - - Force the preview, I understand that the changes will be overwritten. - - - 141 - - - - Merge Outstanding Changes - - - 145 - - - - Your stream is not configured to receive merges. - - - 154 - - - - Your stream is not configured to receive copies. - - - 173 - - - - Force the copy, I am working outside the established stream configuration - - - 177 - - - - - - - - - - &Add files to pending changelist - - - 39 - - - - Automat&ically resolve files after merging - - - 57 - - - - Resol&ve option: - - - 60 - - - - P&ending changelist: - - - 62 - - - - Chan&gelist description: - - - 84 - - - - Add previously linked &job(s) to the new changelist - - - 87 - - - - Jobs cannot be linked to the "default" changelist - - - 92 - - - - Automatica&lly submit after resolving - - - 140 - - - - Automatica&lly submit copied files - - - 148 - - - - Automatica&lly submit after branching files - - - 152 - - - - - - - - - - Created by - - - 139 - - - - - - - - - - Connections - - - 48 - - - - Streams - - - 55 - - - - Server Data - - - 62 - - - - Behavior - - - 69 - - - - Integrate Flags - - - 83 - - - - Double Click - - - 104 - - - - Shortcuts - - - 111 - - - - Logging - - - 118 - - - - Display - - - 125 - - - - Files and History - - - 132 - - - - Application Font - - - 139 - - - - Features - - - 146 - - - - Tools - - - 153 - - - - Image Timelapse - - - 160 - - - - File Editors - - - 167 - - - - Diff - - - 174 - - - - Merge - - - 181 - - - - Applets - - - 188 - - - - Merge-Integrate - - - 217 - - - - Copy - - - 223 - - - - Branch - - - 229 - - - - - - - - - - Allo&w Perforce applets to run in %1 - - - 39 - - - - Always accept Perforce applets from the following servers: - - - 43 - - - - &Server: - - - 45 - - - - (server name or host:port) - - - 49 - - - 89 - - - - A&dd - - - 52 - - - 92 - - - - &Remove - - - 57 - - - - Never accept Perforce applets from the following servers: - - - 82 - - - - S&erver: - - - 85 - - - - Re&move - - - 98 - - - - Note: To run Perforce applets after adding a server to the "always accept" list, close and reopen any connections to that server. - - - 121 - - - - Ad&vanced... - - - 127 - - - - - - - - - - Prompts: - - - 30 - - - - &Warn before reverting files - - - 34 - - - - Warn when &ignored files are not marked for add - - - 36 - - - - Prompt for changelist when checking out, adding, or deleting &files - - - 38 - - - - Prompt to &get latest revision when checking out files that are out of date - - - 40 - - - - When dropping file(s): - - - 63 - - - - on &a file, do a diff comparison - - - 67 - - - - anywhere within a changelist, &move open files to new changelist - - - 72 - - - - on a file, do &nothing - - - 77 - - - - &Disable refresh all for the reconcile offline work dialog. - - - 84 - - - - Drag and drop: - - - 51 - - - - &Enable integration on directory-to-directory drag and drop - - - 56 - - - - - - - - - - When the application launches: - - - 34 - - - - &Show the Perforce Connection dialog - - - 43 - - - - &Restore all previously opened connections - - - 45 - - - 82 - - - - &Open the connection specified by your Perforce environment settings - - - 49 - - - - &Change Settings - - - 64 - - - - Opening and closing connections: - - - 91 - - - - Use IP-specific tickets when lo&gging in - - - 95 - - - - Au&tomatically log off when closing a connection - - - 98 - - - - &Don't expand Workspace and Depot trees to their previous state when opening connections - - - 103 - - - - A&utomatically check for P4V updates. - - - 112 - - - - Contri&bute your anonymous usage data to help us improve our products. - - - 116 - - - - - - - - - - Default diff application: - - - 30 - - - - &P4Merge - - - 32 - - - - &Other application - - - 34 - - - - &Location: - - - 42 - - - - &Browse... - - - 48 - - - - Arg&uments: - - - 52 - - - - Specify diff application by extension (overrides default): - - - 57 - - - - Please specify the location of the application. - - - 102 - - - - Choose an application - - - 178 - - - - - - - - - - Application: - - - 28 - - - - Show item count on tab bar of detai&ls pane - - - 34 - - - - Show the &What's New in P4V page after upgrading - - - 38 - - - - (requires refresh to see changes) - - - 54 - - - - Localization: - - - 58 - - - - Set &encoding for all connections to: - - - 68 - - - - (will not affect connections for which the encoding was previously set) - - - 75 - - - - Use the s&ystem character encoding in P4Merge when connected to non-unicode servers - - - 88 - - - - - - - - - - Object - - - 166 - - - - Double Click Behavior - - - 166 - - - - &Restore Defaults - - - 174 - - - - File in Depot tree - - - 183 - - - - File in Workspace tree - - - 186 - - - - Folder in Depot tree - - - 189 - - - - Folder in Workspace tree - - - 192 - - - - File in Pending changelist - - - 195 - - - - Shelved File - - - 198 - - - - Revision - - - 201 - - - - Pending Changelist - - - 204 - - - - Shelved Files Folder - - - 207 - - - - Submitted Changelist - - - 210 - - - - Branch Mapping - - - 213 - - - - Label - - - 216 - - - - Workspace - - - 219 - - - - Job - - - 222 - - - - Stream - - - 225 - - - - - - - - - - User specified editor associations: - - - 50 - - - - Extension - - - 58 - - - - Default - - - 58 - - - - Application - - - 58 - - - - A&dd... - - - 64 - - - - &Edit... - - - 66 - - - - &Remove - - - 68 - - - - Application Association - - - 155 - - - - true - - - 167 - - - 248 - - - - false - - - 248 - - - - - - - - - - Enable these features: - - - 38 - - - - &Labels - - - 46 - - - - &Jobs - - - 50 - - - - &Streams - - - 54 - - - - &Unload/Reload - - - 58 - - - - Enable these menu options: - - - 62 - - - - &Merge, Copy and Branch Dialogs - - - 66 - - - - Set Up Connection Assistant - - - 71 - - - - Set Up Connection Wi&zard - - - 73 - - - - &Revision Graph - - - 78 - - - - C&ustom Tools - - - 82 - - - - &Time-lapse - - - 86 - - - - A&dministration Tool - - - 90 - - - - Changes will take effect the next time you start P4V. - - - 97 - - - - Warning: Your administrator may turn off some features, overriding your preference selections made here. - - - 100 - - - - - - - - - - Default revisions to retrieve for Image Time-lapse View: - - - 21 - - - - Image Time-lapse View Filetype Associations: - - - 28 - - - - Do not open files as images in Time-lapse View - - - 32 - - - - Open selected filetypes as images in Time-lapse View - - - 34 - - - - - - - - - - Specify source and target files - - - 18 - - - - Branch mapping - - - 19 - - - - Remember my last choice - - - 20 - - - - Resolve and Submit - - - 21 - - - - Submit - - - 22 - - - - Filter - - - 23 - - - - Advanced - - - 24 - - - - - - Restore Defaults - - - 92 - - - - When the - - - 93 - - - - dialog opens: - - - 93 - - - - Use this default - - - 94 - - - - method: - - - 94 - - - - From a &non-stream file or folder: - - - 95 - - - - From a submitted &changelist: - - - 98 - - - - When filtering a branch mapping by files, by default use the files as the - - - 101 - - - - : - - - 101 - - - - &Source - - - 102 - - - - &Target - - - 103 - - - - &Remember my last choice - - - 104 - - - - Default &options tab: - - - 105 - - - - &Add files to pending changelist - - - 108 - - - - Automatically resolve f&iles after merging - - - 110 - - - - Resol&ve method: - - - 111 - - - - P&ending changelist: - - - 114 - - - - Add previously linked &job(s) to the new changelist - - - 117 - - - - Check for opened files and &warn prior to merging (might affect server performance) - - - 118 - - - - &Do not copy newly branched files to workspace (-v) - - - 119 - - - - Specify source and target files - - - 130 - - - - Branch mapping - - - 131 - - - - Remember my last choice - - - 132 - - - - Resolve and Submit - - - 143 - - - - Submit - - - 145 - - - - Filter - - - 146 - - - - Advanced - - - 147 - - - - Automatica&lly submit after resolving - - - 164 - - - - Automatica&lly submit copied files - - - 168 - - - - Automatically submit branched files - - - 175 - - - - - - The integrate flags are applied when the integrate command is run by the<br>Merge/Integrate and Branch dialogs. <a href>More Info</a> - - - 437 - - - - &Do not copy newly branched target files to workspace (-v) - - - 441 - - - - &Try to integrate changes when source deleted and re-added (-Di) - - - 443 - - - - &Force integration on all revisions, disregarding integration history (-f) - - - 445 - - - - Do not get latest re&vision of selected files (-h) - - - 447 - - - - Dis&regard indirect integration history (-1) - - - 449 - - - - Propagate source filet&ypes to target files (-t) - - - 451 - - - - S&chedule 'branch resolves' instead of branching new target files (-Rb) - - - 453 - - - - Sc&hedule 'delete resolves' instead of deleting target files (-Rd) - - - 455 - - - - S&kip previously merged cherry-picked revisions to improve merge results (-Rs) - - - 457 - - - - - - - - - - Log pane options: - - - 30 - - - - Show p4 reporting &commands (dirs, filelog, fstat, etc.) - - - 36 - - - - Show &p4 command output for file operations - - - 43 - - - - Logging to a file: - - - 49 - - - - Enable l&ogging to file - - - 55 - - - - &Name: - - - 62 - - - - Selec&t... - - - 64 - - - - &Size: - - - 77 - - - - KB - - - 78 - - - - A restart will be requred to start or stop logging to a file - - - 86 - - - - Enter a log file name: - - - 178 - - - - Text (*.txt) - - - 180 - - - - - - - - - - Default merge application: - - - 29 - - - - &P4Merge - - - 31 - - - - &Other application - - - 33 - - - - &Location: - - - 42 - - - - &Browse... - - - 46 - - - - Arg&uments: - - - 52 - - - - Specify merge application by extension (overrides default): - - - 57 - - - - The arguments field must contain the -%1, %2 and %r arguments. - - - - 102 - - - - Please specify the location of the application. - - - 104 - - - - Choose an application - - - 180 - - - - - - - - - - Server data: - - - 26 - - - - Check server for &updates every: - - - 38 - - - - minutes - - - 40 - - - - Ma&ximum number of files displayed per changelist*: - - - 50 - - - - &Maximum size of files to preview (excludes audio and video files): - - - 66 - - - - KB - - - 67 - - - - &Number of changelists, jobs, branch mappings or labels to fetch at a time: - - - 77 - - - - (0 = all) - - - 84 - - - - Parallel commands: - - - 97 - - - - &Enable parallel sync - - - 101 - - - - Enable &parallel submit** - - - 105 - - - - * After limit is reached, changelist and resolve dialogs show plain text list of files - - - 116 - - - - ** The progress indicator is unavailable with parallel submit - - - 119 - - - - - - - - - - &Shortcuts for: - - - 53 - - - - &Restore Defaults - - - 78 - - - - Click the "Shortcut" column for a command, and then press the keyboard shortcut to assign. Shortcuts must include either the command key, the control key or a function key. - - - 89 - - - - Click the "Shortcut" column for a command, and then press the keyboard shortcut to assign. Shortcuts must include either the control key or a function key. - - - 90 - - - - &Undo - - - 103 - - - - &Clear - - - 112 - - - - Use &Default - - - 121 - - - - Command - - - 138 - - - - Menu - - - 138 - - - - Shortcut - - - 138 - - - - This key combination is reserved for %1 shortcuts - - - 194 - - - - bookmark - - - 221 - - - - favorite connection - - - 223 - - - - pre-defined - - - 241 - - - - The shortcut is already in use for the %1 command "%2" - -Reassign the %3 shortcut key? - - - 258 - - - - Are you sure you want to reset the key assignments? -All custom shortcuts assigned to commands will be removed. - - - 392 - - - - - - - - - - When performing stream operations: - - - 22 - - - - Stream workspaces: - - - 26 - - - - Stream graph: - - - 30 - - - - When clicking 'Work in this Stream'*: - - - 34 - - - - use different workspace - - - 37 - - - 45 - - - - reuse current workspace - - - 38 - - - 46 - - - - When dragging workspace icon to a new stream*: - - - 42 - - - - Don't &warn me when switching workspaces - - - 50 - - - - A&utomatically update the workspace with all stream files when switching between streams - - - 54 - - - - When &branching streams, include file deletion actions (Server 12.1 or later) - - - 56 - - - - Don't allow streams to be re&parented with drag and drop in the stream graph. - - - 58 - - - - Sh&ow pending stream-to-stream merge and copy hints* - - - 62 - - - - * When disabled, pending stream-to-parent merge and copy hints can be displayed on individual stream refresh - - - 100 - - - - Do not warn when chec&king out, adding or deleting imported files, always - - - 65 - - - - act on imported files - - - 68 - - - - ignore action for imported files - - - 69 - - - - - - - - - - Revision Graph: - - - 26 - - - - Limit Revision &Graph to ancestors and descendants - - - 60 - - - - Time-lapse View: - - - 64 - - - - By default Time-lapse View should show - - - 67 - - - - Direct history - - - 68 - - - - Branch history - - - 69 - - - - Originating changelist history - - - 70 - - - - - - - - - - content - - - 91 - - - - - - - - - - Task stream - - - 129 - - - - %1 '%2' could not be reloaded because you are not its owner. - - - 132 - - - - Unloaded - - - 160 - - - - Task Streams - - - 175 - - - - Labels - - - 178 - - - - Workspaces - - - 181 - - - - - - - - - - Task stream - - - 156 - - - - %1 '%2' could not be unloaded because you are not its owner. - - - 159 - - - - - - - - - - New - - - 105 - - - 2423 - - - - Switch to workspace &immediately - - - 127 - - - - A&utomatically get all revisions - - - 130 - - - - &Basic - - - 136 - - - - &Advanced - - - 137 - - - - &Workspace name: - - - 303 - - - - Wor&kspace root: - - - 309 - - - - &Stream: - - - 314 - - - - Str&eam at change: - - - 319 - - - - B&rowse... - - - 323 - - - - Brows&e... - - - 325 - - - - Workspace &Mappings: - - - 333 - - - - To edit these mappings, you must edit the stream's path configuration - - - 341 - - - - Include selected in workspace mapping - - - 392 - - - - Exclude selected from workspace mapping - - - 396 - - - - Clear selected from workspace mapping - - - 400 - - - - View workspace mapping as tree - - - 405 - - - - View workspace mapping as text - - - 410 - - - - Ow&ner: - - - 491 - - - - Loc&ked: only the owner can edit workspace settings - - - 495 - - - - &Description: - - - 499 - - - - Ho&st: - - - 504 - - - - Al&t roots: - - - 509 - - - - C&hange View: - - - 522 - - - - Restrict to Ser&ver ID: - - - 531 - - - - File Options: - - - 537 - - - - All&write: leave all workspace files writeable when getting revisions - - - 545 - - - - C&lobber: overwrite writeable workspace files when getting revisions - - - 546 - - - - Com&press: speed up slow connections by compressing files when submitting or getting revisions - - - 547 - - - - Modtim&e: set file modification times to what they were in the submitter's workspace - - - 548 - - - - &Rmdir: delete workspace directories when empty - - - 549 - - - - Line ending characters for te&xt files: - - - 562 - - - - Local: defaults to the current operating system - - - 567 - - - - Unix: UNIX style linefeed - - - 568 - - - - Mac: Macintosh style carriage return - - - 569 - - - - Win: Windows style carriage return linefeed - - - 570 - - - - Shared: writes UNIX style and reads local style - - - 571 - - - - On s&ubmit: - - - 579 - - - - Check out submitted &files after submit - - - 584 - - - - Submit all selected files - - - 587 - - - - Don't submit unchanged files - - - 588 - - - - Revert unchanged files - - - 589 - - - - Additional Fields: - - - 684 - - - - Created by %1. - - - 864 - - - - Switch to new workspace &immediately - - - 894 - - - - has been unloaded. Reload to get workspace view. - - - 1608 - - - - Select Root Folder - - - 1690 - - - - You can't have a recursive path for a workspace root. - - - 1812 - - - - A folder named "%1" already exists. -Do you want to choose a different location for the workspace? - - - 1822 - - - %1=workspace location - - - - '%1' is a reserved Perforce workspace name, and considered invalid by Perforce. - - - 1843 - - - - Do you want to save changes to %1? - - - 2425 - - - - &Save - - - 2438 - - - - &Don't Save - - - 2439 - - - - Unable to create a workspace named "%1", because"%2" is already in use as a name for a %3. - - - 2470 - - - - Unable to create a workspace named '%1'. - - - - 2476 - - - - Workspace '%1' already exists. - - - 2481 - - - - %1/Perforce - - - 2499 - - - %1=home - - - - Error at line %1 of field 'View' in client specification. -Wrong number of words for field 'View'. - - - 2556 - - - - This user does not exist: - - - - 2604 - - - - - -Continue with saving workspace? - - - 2606 - - - - &Save Workspace - - - 2621 - - - - &Cancel - - - 2622 - - - - - - Select Stream - - - 2669 - - - - - - - - - - Browse... - - - 82 - - - - Graph View Options - - - 53 - - - - Depot: - - - 77 - - - - Apply saved filter - - - 103 - - - - Select Streams - - - 114 - - - - None - - - 117 - - - - All - - - 118 - - - - Current stream - - - 119 - - - - Streams with my workspaces - - - 120 - - - - More stable than main - - - 121 - - - - Less stable than main - - - 122 - - - - Apply - - - 155 - - - - Save Filter... - - - 766 - - - - Save as a named filter - - - 767 - - - - Manage Filters... - - - 770 - - - - Manage named filters - - - 771 - - - - - - - - - - Graph Navigator - - - 114 - - - - No streams selected. - - - 320 - - - - Refresh Streams - - - 400 - - - - Currently un-doing a stream reparenting, wait until the previous one is finished before un-doing another. - - - 590 - - - - Reparent Stream - - - 1039 - - - - - - - - - - You are about to switch your workspace to a stream not associated with your current mainline. - -Are you sure you want to continue? - - - 112 - - - - The following file is checked out and cannot be submitted until you switch back. - - - 182 - - - - In order to get revisions of this file after switching, you must first shelve or submit this file before switching. - - - - - 183 - - - - The following files are checked out and cannot be submitted until you switch back. - - - 188 - - - - In order to get revisions of these files after switching, you must first shelve or submit these files before switching. - - - - - 189 - - - - Do you want to continue with switching your workspace? - - - 193 - - - - Switch - - - 208 - - - - Do Not Switch - - - 210 - - - - - - - - - - The stream this client is attached to has been unloaded. -Reload the stream to be able to use the workspace. - - - 114 - - - - - - - - - - The stream "%1" is empty. - - - 56 - - - %1=stream name - - - - Choose one of the options below to populate the stream: - - - 75 - - - - Would you like to populate the stream? - - - 77 - - - - &Copy files from the local file system - - - 90 - - - - &Branch files from the server's depots - - - 91 - - - - &Populate - - - 105 - - - - Cancel - - - 106 - - - - - - - - - - &Show In Depot Tree - - - 110 - - - - - - - - - - Time-lapse View Tools - - - 24 - - - - Revisions - - - 34 - - - - Changelists - - - 35 - - - - Dates - - - 36 - - - - - - - - - - Retrieving file revision list - - - 230 - - - - Retrieving image content: 0% - - - 430 - - - - Ready - - - 514 - - - - Retrieving image content: %1% - - - 550 - - - - Current selection: - - - 619 - - - - Scale: - - - 620 - - - - Refresh - - - 654 - - - - Retrieved range: - - - 669 - - - - Autoplay: - - - 676 - - - - rotate images every - - - 678 - - - - seconds - - - 680 - - - - Help - - - 690 - - - - About - - - 698 - - - - About Time-lapse View - - - 702 - - - - - - - - - - Work in this Stream... - - - 135 - - - - - - - - - - OK - - - 28 - - - - - - - - - - Add These Files with Wildcards - - - 48 - - - - The following files contain wildcards in their names. - - - 52 - - - - Select the files you want to add: - - - 56 - - - - C&ontinue - - - 112 - - - - Unchec&k all - - - 115 - - - 219 - - - - Chec&k all - - - 214 - - - - - - - - - - Contribute your anonymous usage data to help us improve our products? - - - 436 - - - - By choosing to send Perforce usage data, you help us improve all our products. This data measures things like which product versions are in use and which features are most popular. All data collection is anonymous and no ip address information is logged or stored. It is examined only on an aggregate basis, and maintained in accordance with Perforce's privacy policies. - - - 439 - - - - &Yes - - - 446 - - - - &No - - - 447 - - - - &More - - - 451 - - - - - - - - - - SSL Error # '%1' - - - 309 - - - - Error count: '%1' - - - 315 - - - - - - Certificate Error - - - 646 - - - - &Trust - - - 671 - - - - Alt+T - - - 672 - - - - &Don't Trust - - - 673 - - - - Alt+D - - - 674 - - - - OK - - - 676 - - - - The root certificate used for the Swarm integration is self-signed and untrusted. - - - 778 - - - - The root certificate used for the Swarm integration has been rejected. All Swarm functionality will be unavailable - - - 780 - - - - Trust this certificate? - - - 810 - - - - Please contact your system administrator. - - - 812 - - - - The root certificate used for the Swarm integration has been rejected. All Swarm functionality will be unavailable. Please contact your system administrator. - - - 824 - - - - - - - - - - Connection timed out - - - 680 - - - - - - - - - - There was a problem reaching the update service. - - - 87 - - - 127 - - - - There was a problem parsing the response from the update server. -Please contact Perforce support at support@perforce.com. - - - 94 - - - - There was a problem reading the current P4V version, please contact support. - - - 142 - - - - - - - - - - Unable to connect to the server %1 using workspace '%2' - - - - 106 - - - - No such workspace. - - - 107 - - - - Unable to connect to the server %1 with user '%2' - - - - 112 - - - - No such user. - - - 113 - - - - - - - - - - Files: - - - 35 - - - - Use a distinct file icon for &modified files - - - 40 - - - - Show Perforce f&iletype for files in the Workspace and Depot tree - - - 45 - - - - Show Perforce f&iletype for files in the Depot tree - - - 47 - - - - Show re&vision information for files in the Workspace and Depot tree - - - 52 - - - - Show re&vision information for files in the Depot tree - - - 54 - - - - Render th&umbnails for Maya files - - - 61 - - - - File Revision History: - - - 69 - - - - Hide files/revisions from 'task' streams (when following branch, copy actions) - - - 72 - - - - - - - - - - Files: - - - 39 - - - - Application &font: - - - 43 - - - - F&ont style: - - - 45 - - - - &Size: - - - 46 - - - - &Restore Defaults - - - 47 - - - - Sample: - - - 85 - - - - abcdefghijklmnopqrstuvwxyz -ABCDEFGHIJKLMNOPQRSTUVWXYZ - - - 91 - - - - Show fi&xed sized fonts only - - - 95 - - - - - - - - - - This revision has been deleted and/or contains no data. - - - 357 - - - - - - - - - - OK - - - 48 - - - - Cancel - - - 49 - - - - - - - - - - Auto-generated repro script for this graph - - - 16 - - - - - - - - - - There is a new version available for P4V - - - 35 - - - - - - + + + + + + + Edit Custom Tool + + + 38 + + + 161 + + + + Menu item: + + + 51 + + + + &Name: + + + 62 + + + + Place&ment: + + + 70 + + + + N&ew Folder... + + + 87 + + + + &Help + + + 98 + + + + Cancel + + + 103 + + + + OK + + + 108 + + + + Add Custom Tool + + + 168 + + + + Error + + + 228 + + + 244 + + + 258 + + + + Menu item must have a name + + + 229 + + + + More than one replaceable file argument of type %X is not allowed + +%1 + + + 242 + + + + Only %f may be used as a replacable parameter in the Initial Directory field. +It will be substituted with the path to the directory containing the requested file(s). + + + 258 + + + + + + New Folder + + + 316 + + + + &Name: + + + 331 + + + + Include folder in conte&xt menus + + + 341 + + + + Cancel + + + 345 + + + + OK + + + 350 + + + + + + + + + + <h2>The Perforce Administration Tool</h2>Copyright &#169; 2002-%1 <A href>Perforce Software</A>.<br>All rights reserved.<hr width=10000>%2 <br> + + + 21 + + + + Rev. %1 + + + 36 + + + + + + + + + + The path %1 + does not appear to be a depot path. + + + 95 + + + + The path %1 + could not be found. + + + 156 + + + + + + + + + + Sort By + + + 736 + + + + Show Files As + + + 742 + + + + + + + + + + The P4Admin application cannot be found. + + + 78 + + + + P4V + + + 83 + + + 89 + + + + Do you want to locate it? + + + 87 + + + + Locate P4Admin + + + 94 + + + + + + + + + + Change Password + + + 25 + + + + &Enter new password: + + + 36 + + + + &Confirm new password: + + + 45 + + + + OK + + + 59 + + + + Cancel + + + 64 + + + + Change Password: %1 + + + 80 + + + + Passwords must be at least eight characters and contain upper and +lower case letters, or letters with at least one symbol or number. + + + 85 + + + + The password entries do not match. +Please re-enter the passwords. + + + 113 + + + + + + + + + + + + + 11 + + + + User/Group + + + 12 + + + + Access Level + + + 13 + + + + Granted To + + + 14 + + + + + + + + + + Cre&ate %1... + + + 367 + + + + + + You do not have permission to modify group '%1'. + + + 596 + + + + + + The group must contain at least one user or +one subgroup. + + + + 461 + + + + + + + + + + Depot Tree + + + 83 + + + + Access Level + + + 84 + + + + Granted To + + + 85 + + + + + + + + + + Home + + + 359 + + + 436 + + + + Users && Groups + + + 363 + + + 443 + + + + Permissions + + + 366 + + + 449 + + + + &New... + + + 630 + + + + Create a new depot + + + 659 + + + + &User... + + + 527 + + + + Depots + + + 369 + + + 461 + + + + Dashboard + + + 1223 + + + + Create a new user + + + 641 + + + + &Group... + + + 530 + + + + Create a new group + + + 650 + + + + &Depot... + + + 533 + + + + Change Password... + + + 858 + + + + Load new license file + + + 870 + + + + &Table Format + + + 883 + + + + &Plain Text Format + + + 892 + + + + &Insert Line + + + 571 + + + + Connections + + + 242 + + + + Log + + + 275 + + + + &Log Pane + + + 554 + + + + &Change Password... + + + 557 + + + + &Load License File... + + + 560 + + + + &Delete Line + + + 574 + + + + Move Line &Up + + + 577 + + + + M&ove Line Down + + + 580 + + + + &Save Edits + + + 583 + + + + &Revert Edits + + + 586 + + + + Process &Monitor... + + + 589 + + + + Co&nfigure Monitoring... + + + 592 + + + + P&assword Security Level... + + + 595 + + + + Choose Character &Encoding... + + + 604 + + + + &File + + + 629 + + + + Close Window + + + 679 + + + + &Administration + + + 853 + + + + &Permissions Table + + + 876 + + + + View process monitor + + + 946 + + + + Main Tabs: + + + 1994 + + + + P4Admin + + + 2695 + + + + Perforce P4Admin + + + 2698 + + + + - Perforce P4Admin + + + 2700 + + + window title, suffix + + + + Permission editing not supported against 2016.1 or greater servers + + + 2893 + + + + If you proceed, you will become the sole user with superuser access. You will need to specifically grant other administrators superuser access in order for them to use this tool.<br><br>Do you want to proceed? + + + 3141 + + + + &Update Spec Depot... + + + 598 + + + + Update spec depot + + + 960 + + + + &Obliterate... + + + 601 + + + + Obliterate files or folders + + + 966 + + + + &Edit + + + 718 + + + + &Find + + + 536 + + + + &View + + + 785 + + + + &Administration Home + + + 539 + + + + View administration home + + + 793 + + + + &Users && Groups + + + 542 + + + + View users and groups + + + 802 + + + + &Permissions + + + 545 + + + + View permissions + + + 809 + + + + &Depots + + + 548 + + + + View depots + + + 817 + + + + &Toolbar + + + 551 + + + + &Connections + + + 970 + + + + Open &Recent + + + 978 + + + + &Favorite Connections + + + 984 + + + + &Tools + + + 1000 + + + + &Diff Against... + + + 1003 + + + + &Window + + + 1035 + + + + Zoom + + + 1046 + + + + &Help + + + 1059 + + + + Toolbar + + + 1160 + + + + The group '%1' does not contain any subgroups or users and will be deleted.<br><br>Do you want to remove all permissions assigned to this group from the Perforce database? + + + 1554 + + + + Select License File + + + 1943 + + + + Loading license file + + + 1971 + + + + Alerts: + + + 1984 + + + + None found. + + + 1988 + + + 1998 + + + + The server is currently unlicensed and must be shutdown to add licenses + + + 2048 + + + + The IP address listed in the new license file does not match the IP address listed in the current license file + + + 2053 + + + + Do you want to save changes made to the permissions table? + + + 2651 + + + + + + + + + + Edit File Type + + + 120 + + + + Add File Type + + + 125 + + + + &Extension: + + + 140 + + + + &Application: + + + 183 + + + + &Browse... + + + 190 + + + + Arg&uments: + + + 192 + + + + Application + + + 202 + + + + In Folder + + + 202 + + + + Arguments + + + 202 + + + + Cancel + + + 224 + + + + &Save + + + 225 + + + + The arguments field must contain the %1 and %2 arguments. + + + 259 + + + %1 and %2 are literals, not placeholders. + + + + The arguments field must contain the %1, %2 and %r arguments. + + + 287 + + + %1 and %2 are literals, not placeholders. + + + + Choose an application + + + 317 + + + + + + + + + + A&dd... + + + 51 + + + + &Edit... + + + 54 + + + + &Remove + + + 58 + + + + Extension + + + 66 + + + + Application + + + 66 + + + + In Folder + + + 66 + + + + + + + + + + Manage Bookmarks + + + 37 + + + + Bookmark... + + + 27 + + + + Type : + + + 76 + + + + Location : + + + 80 + + + + Do you want to delete all of the bookmarks? + + + 306 + + + + Do you want to delete the folder '%1' and all of the bookmarks it contains? + + + 308 + + + + Edit Bookmark + + + 149 + + + + Add Bookmark + + + 160 + + + + Depot file + + + 257 + + + + Depot folder + + + 258 + + + + Workspace file + + + 259 + + + + Workspace folder + + + 260 + + + + Are you sure you want to delete the bookmark '%1'? + + + 294 + + + + + + + + + + Add Bookmark + + + 48 + + + + Menu item: + + + 60 + + + + &Name: + + + 62 + + + + Place&ment: + + + 68 + + + + N&ew Folder... + + + 73 + + + + bookmark + + + 186 + + + + Folder or file: + + + 82 + + + + Shortc&ut: + + + 78 + + + + &Location: + + + 86 + + + + &Browse... + + + 91 + + + + Not a valid depot path. + + + 320 + + + + Depot path not found. + + + 330 + + + + Not a valid path. + + + 372 + + + + Path not found. + + + 382 + + + + + + + + + + Branch + + + 15 + + + + &Branch + + + 16 + + + + + + + + + + Checking permissions table for central settings file + + + 155 + + + + yes (line %1 of permissions table) + + + 169 + + + + no path found in permissions table + + + 170 + + + + yes: default central settings file specified + + + 184 + + + + Central settings file specified for user: %1 + + + 189 + + + + Warning: central settings file not found at %1 + + + 255 + + + + Loaded central settings file at %1 + + + 259 + + + + Parse error at line %1, column %2: %3 + + + 288 + + + + Central settings file redirect to: %1 + + + 309 + + + + Perforce applets accepted for this server: %1 + + + 376 + + + + yes + + + 376 + + + + no + + + 376 + + + + Central settings redirect: %1 + + + 416 + + + + Applets are disabled for public.perforce.com + + + 454 + + + + Applets disabled in Preferences for "%1" + + + 462 + + + + Perforce applets accepted: no + + + 488 + + + 506 + + + + %1, line %2 reading central settings file + + + 557 + + + + Alerts: + + + 615 + + + + Main tabs: + + + 623 + + + 651 + + + + Submit dialog: + + + 638 + + + + Files: + + + 676 + + + + Folders: + + + 677 + + + + Revisions: + + + 678 + + + + Submitted changelists: + + + 679 + + + + Jobs: + + + 680 + + + + Pending changelists: + + + 681 + + + + Branch mappings: + + + 682 + + + + Labels: + + + 683 + + + + Workspaces: + + + 684 + + + + Details panes: + + + 695 + + + + Bottom tabs: + + + 706 + + + + Applets disabled in Preferences for all servers + + + 785 + + + + + + + + + + Change Ownership + + + 33 + + + + Change the user and/or workspace for Pending Changelist " + + + 42 + + + + &User + + + 47 + + + + &Workspace + + + 48 + + + + &Browse... + + + 59 + + + + Br&owse... + + + 60 + + + + Cancel + + + 62 + + + + OK + + + 63 + + + + Cannot Change Ownership + + + 173 + + + 179 + + + + User '%1' does not exist + + + 175 + + + 234 + + + + Workspace '%1' does not exist + + + 181 + + + + + + + + + + Create or Update Workspace View + + + 50 + + + + Create a new workspace or update an existing workspace using the view and options of the template workspace: + + + 53 + + + + &Create workspace + + + 63 + + + + &Workspace Name: + + + 68 + + + + &Update existing workspace + + + 76 + + + + Workspace &Name: + + + 80 + + + + &Browse... + + + 88 + + + + &OK + + + 99 + + + + Cancel + + + 103 + + + + '%1' is a reserved Perforce workspace name, and considered invalid by Perforce. + + + 154 + + + + Unable to update a workspace named '%1'. +Workspace '%1' does not exist. + + + 173 + + + + A workspace that is associated with a stream cannot be updated from a template. + + + 184 + + + + + + + + + + Filter Files + + + 80 + + + + &Filter + + + 117 + + + + &Save... + + + 118 + + + + Set as D&efault + + + 119 + + + + &Close + + + 120 + + + + File Name + + + 376 + + + + and + + + 572 + + + + or + + + 575 + + + 600 + + + + "%1" + + + 682 + + + + + + + + + + Changelist %1 contains shelved files and cannot be %2. + +Do you want to delete the shelved files? + + + 36 + + + + + + + + + + Manage Favorite Connections + + + 25 + + + + Connection... + + + 22 + + + + Edit Favorite Connection + + + 76 + + + + Add Favorite Connection + + + 88 + + + + Are you sure you want to delete the favorite connection '%1'? + + + 175 + + + + Do you want to delete all the favorite connections? + + + 187 + + + + Do you want to delete folder '%1' and all the connections it contains? + + + 189 + + + + + + + + + + Add Favorite Connection + + + 47 + + + + Menu item: + + + 66 + + + + &Name: + + + 68 + + + + Place&ment: + + + 73 + + + + N&ew Folder... + + + 77 + + + + Alt+E + + + 78 + + + + Connection : + + + 91 + + + + &Server: + + + 97 + + + + Short&cut: + + + 86 + + + + (server name or host:port) + + + 99 + + + + &User: + + + 102 + + + + &Browse... + + + 105 + + + + Alt+B + + + 106 + + + + &Workspace: + + + 113 + + + + B&rowse... + + + 116 + + + + Alt+R + + + 117 + + + + Cancel + + + 123 + + + + OK + + + 127 + + + + favorite connection + + + 265 + + + + + + + + + + Refresh + + + 227 + + + + Refresh Connections + + + 239 + + + + + + + + + + &New + + + 66 + + + + Alt+N + + + 67 + + + + &Edit... + + + 70 + + + + Alt+E + + + 71 + + + + Dele&te + + + 74 + + + + Alt+T + + + 75 + + + + Move &Up + + + 78 + + + + Alt+U + + + 79 + + + + Move &Down + + + 82 + + + + Alt+D + + + 83 + + + + Folder... + + + 110 + + + + Separator + + + 111 + + + + Edit Folder + + + 504 + + + + Enter a new name for the folder: + + + 505 + + + + Delete Folder + + + 555 + + + + &Delete All + + + 560 + + + + Delete &Folder Only + + + 562 + + + + + + + + + + Copy + + + 15 + + + + &Copy + + + 16 + + + + + + + + + + Manage Custom Tools + + + 58 + + + + Custo&m tools menu: + + + 72 + + + + &New + + + 94 + + + + &Edit... + + + 100 + + + + Dele&te + + + 106 + + + + Move &Up + + + 112 + + + + Move &Down + + + 118 + + + + &Import Tools... + + + 129 + + + 131 + + + + E&xport Tools... + + + 137 + + + 139 + + + + Cancel + + + 144 + + + + OK + + + 151 + + + + Tool... + + + 163 + + + + Folder... + + + 164 + + + + Separator + + + 165 + + + + Edit Folder + + + 336 + + + + Delete Folder + + + 404 + + + + Do you want to delete all the tools? + + + 407 + + + + Do you want to delete folder '%1' and all the tools it contains? + + + 409 + + + + &Delete All + + + 412 + + + + Delete &Folder Only + + + 416 + + + + You have made changes to the Custom Tools that have not been saved. Do you want to save these changes? + + + 581 + + + + xml files (*.xml);;All Files (*.*) + + + 713 + + + 833 + + + + xml Files (*.xml);;All Files(*) + + + 715 + + + 835 + + + + Read Custom Tools from a File + + + 720 + + + + Error + + + 742 + + + 883 + + + + No CustomTool Definitions found in this file. + + + 743 + + + + Save Custom Tools to a File + + + 842 + + + 847 + + + + %1 already exists. Overwrite? + + + 866 + + + + Overwrite? + + + 868 + + + + Unable to write %1 + + + 884 + + + + Application: + + + 920 + + + + Arguments: + + + 930 + + + + + + + + + + Edit settings + + + 57 + + + + + + + + + + Delete Group: + + + 25 + + + + &Remove all permissions assigned to this group from the +Perforce database + + + 28 + + + + &Delete Group + + + 39 + + + + Cancel + + + 40 + + + + %1 (%2) + + + 64 + + + %1=group name, %2=server port + + + + + + + + + + Delete User: + + + 73 + + + + %1 (%2) + + + 98 + + + %1=user name. %2=server port + + + + The following changelists will be deleted, and any files they contain will be reverted. +Note: Reverting will not change the content of the files on the user's local disk. + + + 105 + + + + The following workspace specifications will be deleted from the Perforce database. + + + 123 + + + + Delete &shelved files in user's pending changelists + + + 141 + + + + &Remove all permissions assigned to this user from the Perforce database + + + 147 + + + + &Delete User + + + 157 + + + + Cancel + + + 159 + + + + User '%1' cannot be deleted because the user's pending changelists contain shelved files. + + + 271 + + + + User '%1' cannot be deleted because the user's shelved files could not be automatically discarded. + + + 314 + + + + The user was not deleted. Some changelists could not be removed. + + + 495 + + + + + + + + + + New Depot... + + + 163 + + + + Local and spec depots must be empty before deleting. +Obliterate all files to empty the depot. + + + 322 + + + + The depot contains files that are marked for add or edit. +These files must be reverted before deleting the depot. + + + 325 + + + + Delete %1 Form + + + 345 + + + + You are about to delete %1 from the server. +Are you sure you want to continue? + + + + 346 + + + + + + + + + + There is no selected directory. + + + 934 + + + + Cannot create query from an unknown object + + + 957 + + + + Missing branch specification. + + + 1251 + + + 1428 + + + + Cannot build the workspace mapping from the workspace view. + + + + 1340 + + + + Stopped diff. + + + 2001 + + + 2528 + + + + Cannot diff local folders using depot syntax. + + + 2165 + + + + Cannot diff these two workspace folders. + + + 2172 + + + + Loading file: %1 + + + 3686 + + + 3848 + + + + Loading file: %1@=%2 + + + 3690 + + + 3852 + + + + Loading file: %1#%2 + + + 3693 + + + 3855 + + + + + + + + + + The number of files in the changelist exceeds the preference for the maximum number of files displayed per changelist. + + + 1303 + + + + Are you sure you want run %1 diff comparison(s) in the diff application? + + + 1362 + + + + &Diff + + + 1370 + + + + + + Diff Against Have Revision + + + 162 + + + + Diff Against Previous Revision + + + 173 + + + + Diff Against Source Revision + + + 187 + + + + Diff Files Against Have Revisions + + + 196 + + + + Diff Files Against Previous Revisions + + + 203 + + + + Diff Selected + + + 231 + + + + Diff Using Mapping '%1' + + + 241 + + + + + + + + + + has been unloaded. Can't diff until it is reloaded. + + + 365 + + + + The workspace view changed. To continue using Folder Diff you need to restart it. +Do you want to automatically restart Folder Diff now? + +Restart Folder Diff? + + + 444 + + + + Folder Diff + + + 448 + + + + &Restart + + + 455 + + + 462 + + + + &Do Not Restart + + + 464 + + + + Starting Diff ... + + + 732 + + + + There are no files that need to be reconciled. + + + + 1061 + + + + Encountered an error running the server's diff command. + + + 1652 + + + + A folder diff cannot be performed on the root directory. + + + + 1655 + + + + Limit the scope to a specific depot or directory. + + + 1656 + + + + The scope of the diff is unsupported. + + + 1659 + + + + Invalid file revision. + + + 1662 + + + + Cannot start the diff application. + + + 1665 + + + + Cannot diff a deleted file or folder. + + + 1668 + + + + The file is synced, but not on the local disk. + + + 1671 + + + + The server does not recognize the path. + + + 1674 + + + + The path does not exist on the local disk. + + + 1677 + + + + The path is recognized as both a file and folder. + + + 1680 + + + + The files are identical. + + + 1686 + + + + The following two files are identical: + +%1 +%2 + + + 1693 + + + + Cannot diff a folder against a file. + + + 1699 + + + + The format of both paths must match (either depot or workspace syntax). + + + 1702 + + + + Current workspace view does not map path between server and local disk. + + + 1705 + + + + The diff was cancelled. + + + 1708 + + + + There was a problem creating the diff query. + + + 1715 + + + + There are not enough valid queries to diff. + + + 1718 + + + + A verification on the query must be run first. + + + 1721 + + + + The workspace must be valid to run this diff. + + + 1724 + + + + The file is open for add or branch. + + + 1727 + + + + No such changelist. + + + 1730 + + + + Encountered an unknown error. + + + 1734 + + + + %1 Diff + + + 1780 + + + + + + + + + + file.txt + + + 2798 + + + + Red text indicates files that were modified offline: + + + 2799 + + + + *If the file is read only or if the visual cue for modified files has been turned off in preferences, the icon will not appear blue. + + + 2864 + + + + + + Workspace location: + + + 1763 + + + + Depot location: + + + 1766 + + + + Revision: + + + 1769 + + + + Date modified: + + + 1772 + + + + File size: + + + 1775 + + + + Type: + + + 1778 + + + + Perforce filetype: + + + 1781 + + + + Reconcile options: + + + 1784 + + + + #%1 of %2 + + + 1856 + + + + The file was created locally and has not been marked for add + + + 2079 + + + + Mark for add + + + 2080 + + + 2086 + + + 2105 + + + + if you intend to add the file to the depot + + + 2080 + + + + Delete local file + + + 2081 + + + 2087 + + + + to remove the file from the workspace + + + 2081 + + + 2087 + + + 2106 + + + + The file has the same name as a deleted depot file + + + 2085 + + + + if you intend to re-add the file to the depot + + + 2086 + + + 2105 + + + + The file was modified without having been checked out + + + 2091 + + + + Check out + + + 2092 + + + 2122 + + + + if you intend to submit the changes to the depot + + + 2092 + + + 2122 + + + + Get revision + + + 2093 + + + 2099 + + + + (using force option) to replace the file with a copy from the depot + + + 2093 + + + + The depot file is missing from the workspace + + + 2097 + + + + Mark for delete + + + 2098 + + + + if you intend to delete the file from the depot + + + 2098 + + + + (using force option) to restore the depot file in the workspace + + + 2099 + + + + Remove from workspace + + + 2100 + + + + to notify the Perforce Server that you have deleted the file from the workspace + + + 2100 + + + + The file has been deleted from the depot + + + 2104 + + + + Get latest revision + + + 2106 + + + + The file was marked for add and is no longer in the workspace + + + 2110 + + + + Revert + + + 2111 + + + 2116 + + + 2123 + + + 2128 + + + + the file to notify the Perforce Server that you no longer plan to add it to the depot + + + 2111 + + + + The file was checked out and is no longer in the workspace + + + 2115 + + + + the file to restore the file in the workspace. After the file is restored you can then check the file out, mark it for delete or remove it from the workspace + + + 2116 + + + + The file was modified after having been marked for branch or integrate + + + 2121 + + + + the file to discard the modifications and undo the pending branch or integration action + + + 2123 + + + + The file was marked for delete, but still exists in your workspace + + + 2127 + + + + the file to discard any modifications and undo the pending delete action + + + 2128 + + + + + + No preview available because the file is empty or the size is unavailable from the server + + + 2458 + + + + No preview available because the file size is greater than the limit specified in the preferences. + + + 2463 + + + + No preview available for this item. + + + 2612 + + + + Revision: + + + 2654 + + + + File: + + + 2659 + + + + workspace version + + + 2700 + + + 2710 + + + + + + Revision: + + + 1074 + + + + Date submitted: + + + 1077 + + + + Changelist: + + + 1080 + + + + Submitted by: + + + 1083 + + + + Description: + + + 1086 + + + + + + Workspace versions of files + + + 1373 + + + + Labeled revisions + + + 1376 + + + + Revisions in changelist + + + 1378 + + + + No Files Found + + + 1463 + + + + Files + + + 1543 + + + 1673 + + + + form + + + 1694 + + + + + + Access type: + + + 3011 + + + + Edit + + + 2951 + + + + + + Revision: + + + 297 + + + + Date submitted: + + + 300 + + + + Changelist: + + + 303 + + + + Submitted by: + + + 306 + + + + Perforce filetype: + + + 309 + + + + Action: + + + 312 + + + + File size: + + + 315 + + + + Workspace: + + + 318 + + + + Description: + + + 321 + + + + + + Revision: + + + 749 + + + + This revision not used in any integrations + + + 763 + + + + Sources (contributing content to this revision) + + + 764 + + + + Targets (receiving content from this revision) + + + 765 + + + + + + Revision: + + + 891 + + + + + + restricted + + + 3186 + + + + public + + + 3186 + + + + Changelist: + + + 3070 + + + + Workspace: + + + 3073 + + + + Date submitted: + + + 3076 + + + + Submitted by: + + + 3079 + + + + Access type: + + + 3082 + + + + Description: + + + 3085 + + + + + + + + + + Stopped diff. + + + 441 + + + + + + + + + + Change %1: File Diffs + + + 1062 + + + + Expand All + + + 1095 + + + + Collapse All + + + 1131 + + + + E&xpand All + + + 1141 + + + + + + Changelist: + + + 157 + + + + Workspace: + + + 166 + + + + Date submitted: + + + 148 + + + + Submitted by: + + + 175 + + + + R&estrict access to changelist + + + 182 + + + + <a href>(more info)</a> + + + 186 + + + + Access type: + + + 193 + + + + Description: + + + 217 + + + + Files: + + + 235 + + + + File Name + + + 249 + + + + Show File Diffs + + + 287 + + + + J&ob: + + + 383 + + + + &Add + + + 391 + + + + B&rowse... + + + 397 + + + + &Status for newly added jobs: + + + 413 + + + + form + + + 530 + + + + %1 does not exist. + + + 660 + + + + restricted + + + 915 + + + + public + + + 915 + + + + + + + + + + File Path Builder + + + 47 + + + + File: + + + 67 + + + + &Folder: + + + 60 + + + + &Browse... + + + 62 + + + + &Include subfolders + + + 64 + + + + &All files + + + 76 + + + + File &name + + + 78 + + + + Revision range: + + + 93 + + + + Path preview: + + + 108 + + + + Cancel + + + 116 + + + + OK + + + 119 + + + + starts with + + + 178 + + + + ends with + + + 184 + + + + contains + + + 190 + + + + is + + + 196 + + + + + + All r&evisions + + + 438 + + + + Re&visions starting at: + + + 443 + + + + Revisions &up to: + + + 447 + + + + Revisions fro&m/to: + + + 451 + + + + B&rowse... + + + 455 + + + + Br&owse... + + + 458 + + + + Bro&wse... + + + 461 + + + + Brow&se... + + + 464 + + + + %1,%2 + + + 573 + + + + %1 and %2 + + + 610 + + + + revision %1 + + + 667 + + + + change number %1 + + + 669 + + + + workspace %1's have revision + + + 672 + + + + or on %1 + + + 674 + + + + label %1 + + + 676 + + + + + + + + + + match any of the following file paths: + + + 35 + + + 1407 + + + 1767 + + + + are + + + 36 + + + + File paths + + + 37 + + + + Name + + + 1764 + + + 2164 + + + + is not + + + 2005 + + + + does not start with + + + 2012 + + + + starts with + + + 2014 + + + + does not end with + + + 2019 + + + + ends with + + + 2021 + + + + contains + + + 2028 + + + + word starting with + + + 2041 + + + + word ending with + + + 2043 + + + + Parent + + + 2167 + + + + is + + + 2007 + + + + Any Field + + + 911 + + + + does not contain + + + 2032 + + + + the exact word + + + 2039 + + + + Directory Name + + + 2161 + + + + + + and + + + 455 + + + + Match case for %1 name + + + 456 + + + %1 is object display name + + + + + + + + + + &Add... + + + 132 + + + + &Remove + + + 133 + + + + &Specify revision using: + + + 136 + + + + No Files. + + + 190 + + + + Add Files/Folders + + + 481 + + + + + + + + + + Loading... + + + 550 + + + + Setting up... + + + 703 + + + + &Previous + + + 785 + + + + &Next + + + 789 + + + + Diff: + + + 779 + + + + Folder Diff Toolbar + + + 599 + + + + Show files in a tree hierarchy + + + 794 + + + + Show files in a flat list + + + 796 + + + + Help + + + 803 + + + + Unique files: + + + 815 + + + + File differences: + + + 821 + + + + Move files: + + + 827 + + + + Show identical file pairs + + + 907 + + + + Show files without counterparts (unique) + + + 916 + + + + Show file pairs with content differences + + + 926 + + + + Show moved or renamed files + + + 936 + + + + Print List of Diffs + + + 972 + + + + Show only offline work + + + 1068 + + + + <font color="grey">none applied</font> + + + 1145 + + + + Filter + + + 1147 + + + + Filter... + + + 1269 + + + + Apply filter + + + 1270 + + + + Manage Filters... + + + 1273 + + + + Organize the saved Filters + + + 1274 + + + + Diff: %1 of %2 + + + 1344 + + + + Unique files: %1 + + + 1345 + + + + File differences: %1 + + + 1346 + + + + Moved files: %1 + + + 1347 + + + + Folder Diff + + + 1397 + + + + Mapping %1 + + + 1422 + + + + (@shelved changelist %1) + + + 1528 + + + + (rev %1) + + + 1503 + + + + (@label %1) + + + 1508 + + + + (@changelist %1) + + + 1513 + + + + (@date %1) + + + 1518 + + + + (@workspace %1) + + + 1523 + + + + (workspace rev) + + + 1487 + + + + Clear Filter + + + 953 + + + + Folder Diff %2 (%1) + + + 1425 + + + Folder Diff window title. %1=config serialized string. %2=branch mapping name (usually empty/blank) + + + + (have rev) + + + 1495 + + + + (latest rev) + + + 1498 + + + + + + Apply filter + + + 1002 + + + + + + + + + + Revision (Changelist) + + + 352 + + + + + + Folder History + + + 583 + + + + + + Tear off this revision history + + + 829 + + + + File: + + + 937 + + + + Show files in a list or as thumbnails + + + 809 + + + + Show branch history + + + 845 + + + + Disabled + + + 854 + + + + Follow Branch Actions + + + 855 + + + + Follow Both Branch and Copy Actions + + + 856 + + + + Folder: + + + 921 + + + + %1 (%2) + + + 1027 + + + + %1 - %2 (%3) + + + 1041 + + + + Show Files As + + + 1436 + + + + + + + + + + Save Diff to Text File + + + 1940 + + + + unknown fstat error, perhaps caused by a toad or a small dwarf living in the fstat agent. + + + 3016 + + + + + + Path: + + + 5776 + + + + Unique files: + + + 5779 + + + + + + + + + + Back out + + + 1419 + + + 1422 + + + + Do you want to create a new pending changelist?<br><br>If you do not create a new changelist, the files with backed out changes will be placed in the default changelist. + + + 1479 + + + + Do you want to create a new pending changelist?<br><br>If you do not create a new changelist, the file with the backed out revision will be placed in the default changelist. + + + 1483 + + + + Back out changelist %1 + + + 1529 + + + + Back out revision %1 from %2 + + + 1534 + + + + Back out operation could not be performed. One or more files are not mapped to your workspace view + + + 1569 + + + + + + Are you sure you want to permanently delete the selected local files? + + + 1320 + + + + Are you sure you want to permanently delete file '%1'? + + + 1322 + + + + + + You are about to unlock a file locked by '%1'. Are you sure you want to continue? + + + 152 + + + + You are about to unlock one or more files locked on another workspace. Are you sure you want to continue? + + + 157 + + + + Do you want to unlock only your locked files, or all locked files in this folder? + + + 183 + + + + &My Files + + + 192 + + + + &All Files + + + 194 + + + + + + &Move All Files to Another Changelist... + + + 266 + + + + Cannot check out "exclusive open" file(s). + +One or more of the selected files are checked out by another user. +Files that have been assigned the "exclusive open" attribute cannot +be checked out by multiple users simultaneously. + + + + 733 + + + + Do you want to add the folder '%1' as a symlink file, or do you want to add the contents of the folder to which this symlink points? + + + 907 + + + + Add &Symlink + + + 914 + + + + Add &Contents + + + 917 + + + + Cancel + + + 920 + + + + + + Your workspace is currently set to changelist %1. + + + 1168 + + + + Do you want to get revisions at this changelist or get the latest revisions of all stream files? + + + 1171 + + + + Get Revisions at Change + + + 1174 + + + + Get Latest Revisions + + + 1176 + + + + + + + + + + You must remove all checked out files from the changelist before changing its ownership. + + + 91 + + + + The ownership of Pending Changelist '%1' cannot be changed because the changelist is currently being edited. + + + 113 + + + + + + + + + + Choose the location in the depot where the folder "%1" will be added: + + + 39 + + + + Choose the location in the depot where the file "%1" will be added: + + + 40 + + + + + + Don't prompt me to get latest revision + + + 98 + + + + You do not have the latest revision for all the files selected. + +Do you want to get the latest revision of those files before checking them out? + + + 102 + + + + You do not have the latest revision of the file. + +Do you want to get the latest revision before checking it out? + + + 105 + + + + &Get Latest + + + 130 + + + + Cancel + + + 132 + + + + &Don't Get Latest + + + 134 + + + + + + check out + + + 197 + + + + Check out + + + 198 + + + + add + + + 201 + + + + Add + + + 202 + + + + delete + + + 206 + + + + Delete + + + 207 + + + + reopen + + + 211 + + + + Reopen + + + 212 + + + + At least one of the files you are attempting to %1 is imported +and cannot be submitted from your current workspace. + +Do you want to proceed with the %1? Or only %1 the non-imported files? + + + 231 + + + + Do not show again + + + 236 + + + + %1 all files + + + 242 + + + + %1 non-imported files + + + 243 + + + + Cancel + + + 244 + + + + + + + + + + Connection Error + + + 107 + + + 186 + + + + User '%1' does not exist. + + + 149 + + + + Workspace '%1' does not exist + + + 152 + + + + The security level of server '%1' requires the password for user '%2' to be reset. + + + 179 + + + + The security level of server '%1' requires that a password be set for user '%2'. + + + 183 + + + + + + + + + + Open With + + + 427 + + + + Show In + + + 545 + + + + Remove From Group + + + 1062 + + + 1347 + + + + Change Password... + + + 1095 + + + + Show Permissions + + + 1105 + + + 1381 + + + + + + + + + + updating... + + + 234 + + + + list + + + 905 + + + + + + + + + + Delete %1 Form + + + 203 + + + + You are about to delete %1 '%2' from the server. +Are you sure you want to continue? + + + + 204 + + + + Delete %1 Forms + + + 228 + + + + You are about to delete selected %1s from the server. +Are you sure you want to continue? + + + + 229 + + + + deleted + + + 82 + + + + Only empty Pending Changelists will be deleted from the server. +Are you sure you want to continue? + + + + 184 + + + + There was a problem deleting the shelve, please be sure you have permission to delete the shelve. + + + 140 + + + + Delete Pending Changelist Form + + + 183 + + + + + + + + + + The folder %1 is not located within the filtered view of the tree. + + + 854 + + + + The selected items are not located within the filtered view of the tree. + + + 861 + + + + + + + + + + Perforce command run: + + + 507 + + + + &Ignore additional errors for this command + + + 518 + + + + + + +Password not valid, please enter the correct password: + + + 83 + + + + +Please enter the password: + + + 85 + + + + Unable to connect to the server %1 as user '%2' + +Try reconnecting to the server? + + + 185 + + + + &Reconnect + + + 193 + + + + &Close P4V + + + 194 + + + + +'%1' validation failed: %2 + + + + 72 + + + + A password is required for user '%1' on server '%2'. +%3 + + + 77 + + + + + + Perforce Password Required + + + 363 + + + + OK + + + 385 + + + + Cancel + + + 386 + + + + + + Perforce Fingerprint Required + + + 285 + + + + ***WARNING: It is possible that someone is intercepting your connection.*** + + + 305 + + + + The fingerprint send by the server ( %1 ) does not match the fingerprint you previously trusted. + + + 309 + + + + The fingerprint of the public key sent by the server is +%1 + + + 312 + + + 325 + + + + &Trust this fingerprint for future connections + + + 315 + + + + The authenticity of the server ( %1 ) can't be established. + + + 321 + + + + &Trust this fingerprint + + + 328 + + + + Connect + + + 332 + + + + Cancel + + + 333 + + + + + + + + + + Form + + + 107 + + + + Workspace Root: + + + 132 + + + + Browse... + + + 141 + + + + Select root directory + + + 336 + + + + + + + + + + Submitting files to the stream restricted to stream owner + + + 486 + + + + Locked (only the stream owner can edit stream settings) + + + 487 + + + + Allow change propagation to parent + + + 488 + + + + Allow change propagation from parent + + + 489 + + + + Unable to create a %1 named "%2", because "%3" is already in use as a name for a %4. + + + 1265 + + + + Branch Mapping + + + 1478 + + + + %1: Invalid ID + + + 1480 + + + + The %1 name may not be empty. + + + 1485 + + + + The name may not begin with a '-' character. + + + 1493 + + + + Illegal, non-alphanumeric characters detected in the %1 name. + + + + 1508 + + + + The %1 name must contain at least one non-numeric character. + + + 1518 + + + + Whitespace is not allowed in the %1 name. +Do you want the spaces fixed automatically? + + + + 1527 + + + + + + + + + + Change Password + + + 40 + + + 46 + + + 54 + + + + The security level of this server requires the password to be reset. + + + 41 + + + + Set Password + + + 50 + + + + The security level of this server requires that a password be set. + + + 51 + + + + Your password has expired, please change your password. + + + 55 + + + + Enter &old password: + + + 100 + + + + &Enter new password: + + + 117 + + + + &Confirm new password: + + + 128 + + + + + + + + + + Identical Files + + + 230 + + + + + No differences found for the following %n file comparison(s): + No differences found for the following %n file comparisons: + + + No differences found for the following %n file comparison(s): + No differences found for the following %n file comparisons: + + + 234 + + + + File 1 + + + 249 + + + + File 2 + + + 250 + + + + + + Reverting will overwrite your changes to the following files: + + + 308 + + + + Pending Action + + + 327 + + + + + + + + + + User name: %1 + + + 37 + + + + Workspace name: %1 + + + 38 + + + + Workspace host: %1 + + + 39 + + + + Workspace root: %1 + + + 45 + + + + Current directory: %1 + + + 51 + + + + Workspace address: %1 + + + 57 + + + + Unicode: %1 + + + 58 + + + + Charset: %1 + + + 59 + + + + Security: %1 + + + 60 + + + + Server Name: %1 + + + 61 + + + + Server description: %1 + + + 62 + + + + Server address: %1 + + + 63 + + + + Server root: %1 + + + 64 + + + + Server date: %1 + + + 65 + + + + Server uptime: %1 + + + 66 + + + + Server version: %1 + + + 67 + + + + Authorization server: %1 + + + 68 + + + + Changelist server: %1 + + + 69 + + + + Server license: %1 + + + 70 + + + + Server license IP: %1 + + + 71 + + + + Password: %1 + + + 72 + + + + Monitor: %1 + + + 73 + + + + Proxy Version: %1 + + + 74 + + + + Broker Address: %1 + + + 75 + + + + Broker Version: %1 + + + 76 + + + + Server case handling: %1 + + + 77 + + + + Minimum Client Level: %1 + + + 78 + + + + Message text for clients that are too old: %1 + + + 79 + + + + Workspace unknown.%1 + + + 122 + + + + + + + + + + Depot Tree + + + 62 + + + + Client Expression + + + 67 + + + + + + + + + + New Depot + + + 36 + + + + Enter a name for the new depot: + + + 39 + + + + Name already in use as a depot. + + + 128 + + + + Unable to create a depot named "%1", because it is already in use as a name for a %2. + + + 161 + + + + Unable to create a depot named '%1'. + + + + 167 + + + + Depot '%1' already exists. + + + 172 + + + + + + + + + + %1 New Folder + + + 127 + + + %1="P4V" or other application name + + + + OK + + + 133 + + + + Cancel + + + 134 + + + + Enter a name for the new folder + + + + 137 + + + + Could not create new folder. + + + 212 + + + + The folder "%1" already exists. + + + 217 + + + + The file "%1" already exists. + + + 222 + + + + + + + + + + The following file is already checked out + + + 53 + + + + The following files are already checked out + + + 55 + + + + %1 operation cannot be performed + + + 60 + + + + Warning: The following file is checked out on another workspace + + + 74 + + + + Warning: The following files are checked out on another workspace + + + 76 + + + + Warning: The following %1 files are a subset of those checked out on another workspace + + + 78 + + + + Do you want to proceed with the %1 operation? + + + 84 + + + + The changelist you are trying to %1 won't be checked for files that are currently checked out because it contains too many files. + +Proceed with the '%1' anyway? + + + 269 + + + + Back out + + + 275 + + + + Back Out + + + 276 + + + + %1 by %2@%3 + + + 370 + + + %1=file path. %2=user name. %3=workspace name + + + + + + + + + + The new password and confirm new password fields do not match. +Please re-enter your new password. + + + 41 + + + + Passwords must be at least eight characters long and must contain upper and lower case letters, or letters plus one or more symbols or numbers. + + + 60 + + + + + + + + + + Select Pending Changelist + + + 50 + + + 63 + + + + &Move selected files to pending changelist + + + 49 + + + + Files in the default changelist cannot be shelved without +first moving them to a new numbered changelist. + +New changelist description: + + + 55 + + + + &Add files to pending changelist: + + + 62 + + + + &Changelist description + + + 80 + + + + &Don't show this dialog again (always use default changelist) + + + 91 + + + + &Move + + + 103 + + + + + + + + + + Shelved Files in + + + 72 + + + + + + + + + + Delete or Replace Writable Files + + + 67 + + + + The following files are writable and may contain edits. The requested operation will delete or replace these files. + + + 71 + + + + Select the files you want to update: + + + 76 + + + + C&ontinue + + + 132 + + + + Unchec&k all + + + 135 + + + 298 + + + + Chec&k all + + + 293 + + + + + + + + + + Are you sure you want to delete all shelved files in pending changelist '%1'? + + + 436 + + + + Are you sure you want to delete shelved file '%1'? + + + 467 + + + + Are you sure you want to delete the selected shelved files? + + + 471 + + + + Unable to delete the following shelved files: + + + 529 + + + + No files found that can be shelved. + + + 573 + + + + + + + + + + No Items Available + + + 127 + + + + There are %1 revisions in this label. + + + 130 + + + %1=number of revisions + + + + No files available from server. The changelist contains no files (obliterated) or you are not allowed to view them (protections). + + + 136 + + + + There are %1 files in this changelist. + + + 140 + + + %1=number of files + + + + %1 (Not In Depot) + + + 145 + + + %1=file path + + + + Sort Files By + + + 666 + + + + + + + + + + Special Edit of View Map + + + 31 + + + + Workspace Path + + + 36 + + + + Depot Path + + + 43 + + + + Enter an expression + + + 62 + + + + Enter a specific filename (for example, "name.doc") + + + 69 + + + + Enter an extension (for example, "html") + + + 121 + + + + //depot/files + + + 191 + + + + Files '*' + + + 192 + + + + Tree '...' + + + 193 + + + + File + + + 194 + + + + File Extension + + + 195 + + + + Expression + + + 196 + + + + Cancel + + + 197 + + + + Save + + + 198 + + + + Include + + + 199 + + + + Exclude + + + 200 + + + + Error + + + 298 + + + 303 + + + 310 + + + + Please enter a valid text string + + + 298 + + + + No wildcards are allowed! + + + 303 + + + + Both fields must contain text + + + 310 + + + + + + + + + + Info + + + 874 + + + + $APP could not add 1 item. + + + 1031 + + + + $APP could not add %n items. + + + 1032 + + + + $APP can only accept files and folders that are under the workspace root: $WORKSPACE_ROOT + + + 1033 + + + + ...and %1 other items. + + + 1038 + + + + Unable to unshelve the following files: + + + 1877 + + + + Unable to shelve files: + + + 2025 + + + + + + + + + + none found + + + 102 + + + + Name: + + + 150 + + + + Host: + + + 151 + + + + Port: + + + 152 + + + + IP address: + + + 153 + + + + Version: + + + 154 + + + + Root: + + + 155 + + + + Monitoring: + + + 156 + + + + Unicode support: + + + 157 + + + + Journal number: + + + 158 + + + + Uptime: + + + 159 + + + + Authentication server: + + + 160 + + + + Changelist server: + + + 161 + + + + Create new user + + + 183 + + + + Create new group + + + 184 + + + + Load new license file + + + 185 + + + + Display permissions for users and groups + + + 186 + + + + Alerts displayed on the Administration Home page: + + + 256 + + + + P4 process has been running for: + + + 259 + + + + minutes + + + 281 + + + + Support will expire in: + + + 285 + + + + days + + + 293 + + + + Unused user licenses remaining: + + + 297 + + + + Cancel + + + 313 + + + + Save + + + 314 + + + + <b><font size="+0">Alerts!</font></b> + + + 328 + + + 814 + + + + <b><font size="+0">Server Info</font></b> + + + 382 + + + 1750 + + + + Files + + + 494 + + + + Sizes + + + 495 + + + + Location + + + 496 + + + + <b><font size="+0">Disk Space Usage</font></b> + + + 512 + + + + <i><font size="+0">Security is determined by the authentication trigger(s) in use.<br><br>See the enabled triggers in the Triggers table below.</font></i> + + + 530 + + + + <b><font size="+0">Security Level</font></b> + + + 537 + + + 795 + + + + <b><font size="+0">Account Management Quicklinks</font></b> + + + 575 + + + + Licenses in use: + + + 600 + + + + Remaining licenses: + + + 613 + + + + License total: + + + 624 + + + + <b><font size="+0">User Licenses</font></b> + + + 660 + + + + Users who have not accessed their account in more than: + + + 670 + + + + 2 years + + + 673 + + + + 1 year + + + 674 + + + + 6 months + + + 675 + + + + 2 months + + + 676 + + + + 1 month + + + 677 + + + + 2 weeks + + + 678 + + + + 1 week + + + 679 + + + + <b><font size="+0">Inactive Users</font></b> + + + 711 + + + 840 + + + + <b><font size="+0">Triggers</font></b> + + + 726 + + + 860 + + + + <b><font size="+0">Security Level</font></b>&nbsp;&nbsp;(authentication trigger(s) in use) + + + 802 + + + + <b><font size="+0">Security Level</font></b>&nbsp;&nbsp;(level + + + 806 + + + + ) + + + 806 + + + + <b><font size="+0">Triggers</font></b>&nbsp;&nbsp; + + + 867 + + + + + (%n triggers in use) + + + + (%n triggers in use) + + + + 868 + + + + %1 day, %2 hrs + + + 1798 + + + + %1 days, %2 hrs + + + 1799 + + + + Only %1 unused user licenses remain + + + 2071 + + + + (no license file) + + + 1845 + + + + (support renewal date: + + + 1859 + + + + (support expired: + + + 1861 + + + + (expiration date: + + + 1863 + + + + (see authentication server) + + + 1867 + + + + (Error reading license information) + + + 1872 + + + + Your support has expired + + + 1896 + + + + <b><font size="+0">User Licenses</font></b>&nbsp;&nbsp; + + + 1905 + + + + enabled + + + 1267 + + + + Server Disk* + + + 406 + + + + Disk in use: + + + 424 + + + + Remaining space: + + + 438 + + + + Total disk size: + + + 451 + + + + * Disk where server root is located + + + 463 + + + + database tables + + + 473 + + + + journal + + + 474 + + + + log + + + 475 + + + + audit log + + + 476 + + + + (1 alert) + + + 822 + + + + + (%n alerts) + (%n alerts) + + + (%n alerts) + (%n alerts) + + + 823 + + + + <b><font size="+0">Alerts!</font></b>&nbsp;&nbsp; + + + 825 + + + + <b><font size="+0">Inactive Users</font></b>&nbsp;&nbsp; + + + 853 + + + 2102 + + + + + (%n inactive users) + (%n inactive users) + + + (%n inactive users) + (%n inactive users) + + + 854 + + + 2103 + + + + disabled + + + 1269 + + + 2178 + + + 2197 + + + + Administration Home last updated: %1 + + + 1752 + + + + + Your support will expire in %n day(s) + Your support will expire in %n days + + + Your support will expire in %n day(s) + Your support will expire in %n days + + + 1894 + + + + + Process ID %1 has been running for over %n minute(s) + Process ID %1 has been running for over %n minutes + + + Process ID %1 has been running for over %n minute(s) + Process ID %1 has been running for over %n minutes + + + 1983 + + + + The current permission settings allow new users to create their own accounts. (To disable automatic user creation, see the System Administrator's Guide) + + + 2265 + + + + The current permission settings do not allow new users to create their own accounts. + + + 2271 + + + + No unused user licenses remain + + + 2069 + + + + unknown + + + 2162 + + + 2185 + + + + enabled (excluding idle processes) + + + 2180 + + + + enabled (including idle processes) + + + 2182 + + + + 0 + + + 2194 + + + + + + + + + + none + + + 103 + + + 113 + + + + + + + + + + does not exist + + + 1351 + + + 1362 + + + + + + <nobr><b>Source:</b> %1</nobr> + + + 553 + + + + Cannot rename %1: A file with the name you specified already exists. Specify a different name. + + + 623 + + + + + + + + + + Source + + + 605 + + + + Target + + + 606 + + + + + + Base path: + + + 160 + + + 434 + + + + You must enter a depot path. + + + 229 + + + + The following directories are local only and will not be integrated:<br> + + + 369 + + + + + + + + + + (To reverse the direction of integration, click the arrow) + + + 63 + + + + Branch ma&pping: + + + 43 + + + + &Browse... + + + 51 + + + + &New... + + + 55 + + + + &Edit View + + + 67 + + + + Diff Vie&w + + + 72 + + + + + + + + + + No Items Found + + + 78 + + + + + + + + + + Integrate + + + 92 + + + + &Options: + + + 122 + + + + (Current settings: + + + 125 + + + + Filter + + + 175 + + + + Set Defaults... + + + 186 + + + + Cancel + + + 188 + + + + Description cannot be set on the "default" changelist + + + 436 + + + + Merging %1 to %2 + + + 460 + + + + Copying %1 to %2 + + + 463 + + + + Branching %1 to %2 + + + 467 + + + + Integrating %1 to %2 + + + 471 + + + + Merging using %1 + + + 479 + + + + Copying using %1 + + + 482 + + + + Branching using %1 + + + 486 + + + + Integrating using %1 + + + 490 + + + + Merging + +%1 + + + 503 + + + + + +to %1 + + + 505 + + + 510 + + + 516 + + + 522 + + + + Copying + +%1 + + + 508 + + + + Branching + +%1 + + + 514 + + + + Integrating + +%1 + + + 520 + + + + The source and target are identical, nothing will be branched. Change the target path. + + + 1086 + + + + merging + + + 1283 + + + + copying + + + 1286 + + + + branching + + + 1289 + + + + integrating + + + 1292 + + + + Cannot use %1 method "stream to stream" when %2 a 'task' stream to/from a stream other than its own parent. + + + 1296 + + + + The operation cannot be completed because there is no common source path. We recommend you create a branch mapping for this operation. + + + 1340 + + + + The operation cannot be completed because you cannot limit to and from a label. + + + 1419 + + + + You are about to merge in both directions of your branch mapping. This is because the path you are filtering with is broader than your branch mapping. + +Do you want to continue? + + + 1440 + + + + &Continue + + + 1441 + + + + C&ancel + + + 1442 + + + + You need to use the Merge/Integrate dialog instead of Branch to branch files to an existing location. + + + 1556 + + + + Integration errors: + + + 1581 + + + + Files are checked out in the workspace. Check in, revert, or use another workspace to copy files to target. + + + 1644 + + + + There are a total of %1 errors. + + + + 1664 + + + 1712 + + + + Errors: + + + + 1735 + + + + Branched files: + + + + 1742 + + + + Integrate preview + + + 1815 + + + + Submit of changelist #%1 failed + + + 1981 + + + + because the files are opened in another changelist. + + + 1985 + + + + . %1 + + + 1987 + + + + Preview change&lists + + + 2039 + + + + Resolve and Submit + + + 170 + + + + Submit + + + 172 + + + + Advanced + + + 178 + + + + &Preview + + + 193 + + + + Not all files were resolved. The merged files are in changelist #%1. + + + 1905 + + + + Files will not be submitted. + + + 1908 + + + + + + + + + + &Remove + + + 66 + + + + Branch only the following files/folders: + + + 100 + + + + Merge only the following files/folders: + + + 104 + + + + Copy only the following files/folders: + + + 108 + + + + Source files/folders: + + + 53 + + + 112 + + + + &Add... + + + 65 + + + + + + + + + + Filter method: + + + 38 + + + + Revision range and &files/folders + + + 42 + + + + so&urce + + + 79 + + + + tar&get + + + 80 + + + + Use files as copy + + + 160 + + + + Use files as branch + + + 164 + + + + Se&lected changelists + + + 221 + + + + Se&lected range of changelists + + + 223 + + + + Use files as merge + + + 155 + + + + + + + + + + &From: + + + 71 + + + + &To: + + + 76 + + + + Latest + + + 105 + + + + Specify revision using + + + 106 + + + + Rev&isions to branch: + + + 107 + + + + All revisions + + + 111 + + + + Revisions up to + + + 112 + + + + Rev&isions to merge: + + + 116 + + + + Revisions equal to + + + 117 + + + 127 + + + + Revisions from/to + + + 118 + + + + Rev&isions to copy: + + + 122 + + + + Rev&isions to integrate: + + + 126 + + + + Revisions including + + + 128 + + + + %1 and %2 + + + 245 + + + + revision %1 + + + 303 + + + + change number %1 + + + 305 + + + + workspace %1's have revision + + + 308 + + + + or on %1 + + + 310 + + + + label %1 + + + 312 + + + + + + + + + + Stream to stream + + + 17 + + + + Specify source and target files + + + 19 + + + + Use branch mapping + + + 21 + + + + Merge metho&d: + + + 88 + + + + Copy metho&d: + + + 92 + + + + Branch metho&d: + + + 96 + + + + + + + + + + &Do not copy newly branched target files to workspace (-v) + + + 32 + + + + &Enable baseless merges (-i) + + + 34 + + + + E&nable integrations around deleted revisions (-d) + + + 37 + + + + &Integrate over deleted targets (-Dt) + + + 40 + + + + Delete t&arget file when source is deleted (-Ds) + + + 42 + + + + &Try to integrate changes when source deleted and re-added (-Di) + + + 44 + + + + &Force integration on all revisions, disregarding integration history (-f) + + + 47 + + + + Explicitly create revisions for all files, even those without integration history (-f) + + + 49 + + + + Do not get latest re&vision of selected files (-h) + + + 51 + + + + Propagate source filet&ypes to target files (-t) + + + 55 + + + + S&chedule 'branch resolves' instead of branching new target files (-Rb) + + + 57 + + + + Sc&hedule 'delete resolves' instead of deleting target files (-Rd) + + + 59 + + + + S&kip previously merged cherry-picked revisions to improve merge results (-Rs) + + + 61 + + + + Dis&regard indirect integration history (-1) + + + 53 + + + + Check for opened files and &warn prior to merging (might affect server performance) + + + 63 + + + + + + + + + + Step %1 of %2: + + + 80 + + + + Validating %1 parameters + + + 85 + + + + Branching files + + + 89 + + + + Performing %1 + + + 91 + + + + Resolving files + + + 94 + + + + Submitting files + + + 97 + + + + merge + + + 113 + + + + copy + + + 115 + + + + branch + + + 117 + + + + integrate + + + 119 + + + + + + + + + + auto resolve, + + + 60 + + + + resolve later, + + + 65 + + + + all revisions + + + 44 + + + + filtered by files + + + 47 + + + + filtered by revisions + + + 50 + + + + filtered by revisions and files + + + 53 + + + + auto submit, + + + 73 + + + + submit later, + + + 78 + + + + , advanced options + + + 90 + + + + , force merge + + + 95 + + + + 1 entry falls outside the bounds of the branch view and will not be integrated + + + 111 + + + + + %n entries fall outside the bounds of the branch view and will not be integrated + %n entries fall outside the bounds of the branch view and will not be integrated + + + %n entries fall outside the bounds of the branch view and will not be integrated + %n entries fall outside the bounds of the branch view and will not be integrated + + + 115 + + + + Error: Source stream does not exist. Choose an existing source stream. + + + 125 + + + + Error: Branch mapping does not exist. Choose an existing branch mapping or create a new one. + + + 141 + + + + A source and target path must be selected. + + + 157 + + + + Error: At least one changelist must be selected. + + + 173 + + + + + + + + + + So&urce stream: + + + 42 + + + + Target stream: + + + 43 + + + + &Browse + + + 50 + + + + + + + + + + &Browse... + + + 45 + + + + Generate branch mapping from above paths for use in future integrations: + + + 51 + + + + Enter a name for the new branch mapping: + + + 152 + + + + &Save... + + + 49 + + + + Choose tar&get files/folders: + + + 40 + + + + Can't create a branch named %1, it's already a %2 + + + 205 + + + + A branch with that name already exists. + + + 245 + + + + + + + + + + Integration Preview + + + 104 + + + 283 + + + + %n branch + + + 196 + + + + %n branches + + + 202 + + + + %n integration + + + 208 + + + + %n integrations + + + 214 + + + + %n error + + + 220 + + + + %n errors + + + 226 + + + + Show or Hide Normal Integrations + + + 288 + + + + Show or Hide Branched Files + + + 294 + + + + Show or Hide Integration Errors + + + 300 + + + + Previous + + + 306 + + + + Next + + + 307 + + + + Previous Error + + + 310 + + + + Next Error + + + 311 + + + + Show files in a tree hierarchy + + + 314 + + + + Show files in a flat list + + + 315 + + + + + + + + + + &Font: + + + 25 + + + + Font: + + + 25 + + + + &Size: + + + 29 + + + + Size: + + + 29 + + + + Sample: + + + 48 + + + + abcdefghijklmnopqrstuvwxyz +ABCDEFGHIJKLMNOPQRSTUVWXYZ + + + 53 + + + + &Reset + + + 60 + + + + Select Font + + + 18 + + + + + + + + + + 1 file %1 + + + 814 + + + e.g. 1 file locked + + + + %1 files %2 + + + 816 + + + e.g. 2 files locked + + + + no files %1 + + + 818 + + + + + + %1 - moved from %2 + + + 737 + + + + no files moved + + + 753 + + + + 1 file moved + + + 756 + + + + %1 files moved + + + 760 + + + + + + {%1 more items} + + + 359 + + + + %1 warnings reported + + + 427 + + + + 1 warning reported + + + 429 + + + + %1 errors reported + + + 436 + + + + 1 error reported + + + 438 + + + + + + 1 file opened for %1 + + + 693 + + + + %1 files opened for %2 + + + 698 + + + + no files opened + + + 704 + + + + + + 1 file reverted + + + 626 + + + + %1 files reverted + + + 628 + + + + no files reverted (all files were either changed or opened for add, delete or branch) + + + 630 + + + + no files reverted + + + 632 + + + + + + Shelf promoted + + + 1116 + + + + 1 file shelved + + + 1121 + + + + %1 files shelved + + + 1123 + + + + 1 file unchanged, not shelved + + + 1126 + + + + %1 files unchanged, not shelved + + + 1128 + + + + no files shelved + + + 1119 + + + + + + Change + + + 846 + + + + created with %1 open file(s). + + + 848 + + + + +Locking %1 file(s)... + + + 849 + + + + renamed + + + 866 + + + + and submitted + + + 866 + + + + Submitted change + + + 868 + + + + 1 file added + + + 872 + + + + %1 files added + + + 874 + + + + 1 file edited + + + 877 + + + + %1 files edited + + + 879 + + + + 1 file branched + + + 882 + + + + %1 files branched + + + 884 + + + + 1 file integrated + + + 887 + + + + %1 files integrated + + + 889 + + + + 1 file deleted + + + 892 + + + + %1 files deleted + + + 894 + + + + no files submitted + + + 901 + + + + + + 1 file updated + + + 569 + + + + %1 files updated + + + 571 + + + + 1 file added + + + 574 + + + + %1 files added + + + 576 + + + + 1 files removed + + + 579 + + + + %1 files removed + + + 581 + + + + no files updated + + + 584 + + + + + + 1 file updated + + + 972 + + + + %1 files updated + + + 974 + + + + 1 files added + + + 977 + + + + %1 files added + + + 979 + + + + 1 files removed + + + 982 + + + + %1 files removed + + + 984 + + + + no files updated, added or removed + + + 987 + + + + + + %1 unshelved + + + 1194 + + + 1207 + + + + 1 file unshelved + + + 1214 + + + + %1 files unshelved + + + 1216 + + + e.g. 2 files unshelved + + + + no files unshelved + + + 1218 + + + + + + + + + + Show Folder "%1" in Depot Tree + + + 471 + + + + Show Folder "%1" in Workspace Tree + + + 477 + + + + Show File "%1" in Depot Tree + + + 485 + + + + Show File "%1" in Workspace Tree + + + 491 + + + + Go to Changelist "%1" + + + 520 + + + + Go to Workspace "%1" + + + 527 + + + + Go to Job "%1" + + + 534 + + + + Go to User "%1" + + + 541 + + + + Copy + + + 774 + + + + Select All + + + 775 + + + + + + + + + + &Open Connection... + + + 162 + + + + Open a new connection by choosing the P4 settings + + + 163 + + + + &Add Favorite Connection... + + + 164 + + + + Add the current connection to the Connection list. + + + 165 + + + + &Manage Favorites... + + + 166 + + + + Open the Connection Editor. + + + 167 + + + + &Set Up Connection... + + + 169 + + + + Launches the Connection Wizard. + + + 170 + + + + En&vironment Settings... + + + 172 + + + + Launches Environment Dialog. + + + 173 + + + + Manage &Custom Tools... + + + 175 + + + + Customize tools for use + + + 176 + + + + &Go To... + + + 178 + + + + Open a specific form by name + + + 179 + + + + Unloaded content + + + 182 + + + + Display unloaded form + + + 183 + + + + &Manage Bookmarks... + + + 204 + + + + Manager bookmarks for this client + + + 205 + + + + &Administration + + + 207 + + + + Display Administration Tool + + + 208 + + + + &Close Connection + + + 210 + + + + Close the connection window and associated windows + + + 211 + + + + &Log Off + + + 212 + + + + Log out of session and close the connection window + + + 213 + + + + Reconnec&t + + + 214 + + + + Reconnect when disconnected + + + 215 + + + + # Integrate Preview + + + 218 + + + + Show Integrate Preview window + + + 219 + + + + Choose Character &Encoding... + + + 222 + + + + Choose character encoding + + + 223 + + + + &Branch Mappings + + + 299 + + + + View branch mappings + + + 300 + + + + &Streams + + + 326 + + + + View streams + + + 327 + + + + Ch&ange Password... + + + 224 + + + + Change the current server password + + + 225 + + + + &Tear Off + + + 227 + + + + Tear off pane into separate window + + + 228 + + + + &Address Bar + + + 231 + + + + Show/Hide the Address Bar + + + 232 + + + + Details &Pane + + + 233 + + + + Show/Hide the Details Pane + + + 234 + + + + Show/Hide the Tooltips + + + 238 + + + + &System Info + + + 241 + + + + Display system info in a window + + + 242 + + + + &What's New in P4V + + + 243 + + + + Show What's New in a tab + + + 244 + + + + &History + + + 295 + + + + Tab &Bars + + + 235 + + + + Show/Hide the Tab Bars + + + 236 + + + + T&ooltips + + + 237 + + + + &Dock Tab + + + 246 + + + + Dock Tab + + + 247 + + + + &Undock Tab + + + 248 + + + + Undock Tab + + + 249 + + + + Clo&se Tab + + + 250 + + + + Close Tab + + + 251 + + + + &Upload Files To Server... + + + 253 + + + + Launches the File Uploading Wizard. + + + 254 + + + + &Depot Tree + + + 282 + + + + View depot tree + + + 283 + + + + &Workspace Tree + + + 285 + + + + View workspace tree + + + 286 + + + + F&ind File... + + + 289 + + + + Search for file(s) + + + 290 + + + + Files i&n Folder + + + 292 + + + + View files in folder + + + 293 + + + + View the file revision history + + + 296 + + + + Workspa&ces + + + 302 + + + + View workspaces + + + 303 + + + + &Groups + + + 305 + + + + View groups + + + 306 + + + + &Jobs + + + 308 + + + + View jobs + + + 309 + + + + &Labels + + + 311 + + + + View labels + + + 312 + + + + &Pending Changelists + + + 314 + + + + View pending changelists + + + 315 + + + + &Submitted Changelists + + + 317 + + + + View submitted changelists + + + 318 + + + + Lo&g + + + 320 + + + + View Log Pane + + + 321 + + + + D&ashboard + + + 323 + + + + View Dashboard Pane + + + 324 + + + + + + + + + + Merge/Integrate + + + 15 + + + + &Merge + + + 16 + + + + + + + + + + Merge Warnings + + + 31 + + + + To avoid overwriting your edits, shelve or submit these files before merging + + + 41 + + + + No Items Found + + + 54 + + + + Don't warn me again about open files + + + 64 + + + + Cancel + + + 68 + + + + Continue with merge + + + 69 + + + + + + + + + + Process ID + + + 97 + + + + Client Program + + + 99 + + + + IP Address + + + 100 + + + + Status + + + 102 + + + + Owner + + + 103 + + + + Workspace + + + 105 + + + + Elapsed Time + + + 107 + + + + P4 Command + + + 108 + + + + Arguments + + + 109 + + + + &Show idle processes + + + 121 + + + + &Terminate Process + + + 127 + + + + Locked database tables + + + 137 + + + + Table + + + 142 + + + + Operation + + + 143 + + + + Write + + + 397 + + + + Read + + + 403 + + + + You are about to terminate process %1.<br>Are you sure you want to continue? + + + 460 + + + + + + Process Monitor (%1) + + + 43 + + + + + + + + + + Rename/Move operation could not be performed + + + 112 + + + 178 + + + 214 + + + + Rename/Move operation warning + + + 125 + + + + The new name chosen contains invalid characters + + + 179 + + + + The new location chosen contains invalid characters + + + 215 + + + + + + + + + + Rename/Move operation could not be performed + + + 539 + + + 553 + + + + The files/folders selected must be located within the same folder. + + + 540 + + + + None of the files are in the depot + + + 554 + + + + Rename/Move + + + 567 + + + 632 + + + + + + Rename/Move + + + 41 + + + + &Rename/move the file back to its original name and/or location + + + 52 + + + + Rename: + + + 59 + + + + Name: + + + 68 + + + + &New name: + + + 79 + + + + Move to: + + + 87 + + + + &Add files to pending changelist: + + + 118 + + + + The following files are deleted from the depot: + + + 271 + + + + Location: + + + 96 + + + + N&ew location: + + + 106 + + + + &Browse... + + + 110 + + + + Sub&mit... + + + 135 + + + + Cancel + + + 138 + + + + &Save to Changelist + + + 140 + + + + + + + + + + &Apply + + + 124 + + + + &Help + + + 125 + + + + Stream &name: + + + 142 + + + + &Stream type: + + + 155 + + + + mainline - serves as the base or trunk of a stream system + + + 166 + + + + development - used for long term projects and major new features + + + 167 + + + + release - used for fixing bugs, testing and release distribution + + + 168 + + + + virtual - used to narrow the scope and submit directly to parent + + + 171 + + + + task - creates lightweight branch, used for bug fixes and new features + + + 175 + + + + Change propagation: + + + 187 + + + 203 + + + 221 + + + 240 + + + 258 + + + + The mainline is the recipient of most changes made in development or release streams. + + + 188 + + + + &To parent: allow copies to the parent stream + + + 204 + + + 259 + + + + &From parent: allow merges from the parent stream + + + 208 + + + 263 + + + + &To parent: allow merges to the parent stream + + + 222 + + + + &From parent: allow copies from the parent stream + + + 226 + + + + Changes pass through the virtual stream to and from the real parent stream. + + + 241 + + + + &Depot: + + + 292 + + + + &Browse... + + + 302 + + + 337 + + + + Please choose one of the values from the list + + + 307 + + + 342 + + + 1319 + + + + &Location: + + + 351 + + + + D&escription: + + + 380 + + + + &Create a workspace to use with this stream + + + 395 + + + + Basic Settings + + + 407 + + + + &Owner: + + + 413 + + + + &Locked (only the stream owner can edit stream settings) + + + 425 + + + + &Paths (assign access levels to stream paths): + + + 433 + + + + Re&mapped (change the default mapping of depot files to the workspace): + + + 446 + + + + &Ignored (specify file or folder patterns to exclude from stream): + + + 459 + + + + Additional Fields: + + + 507 + + + + Advanced + + + 513 + + + + New + + + 582 + + + + Edit + + + 586 + + + + Stream: %1 (%2) + + + 606 + + + + Created by %1. + + + 706 + + + 1546 + + + + Br&anch files from parent on stream creation + + + 2148 + + + + Are you sure you want to convert this 'task' stream to another type? + +This action cannot be undone. Streams cannot be converted back to 'task'. + + + 1330 + + + + Would you like to populate the stream now? + + + 1379 + + + + Would you like to populate the stream now + + + 1381 + + + + If you choose to populate now, a workspace +will be created so that you can submit the +files to the depot. + + + 1383 + + + + &Populate + + + 1402 + + + + &Don't Populate + + + 1404 + + + + Are you sure you want to convert this 'task' stream to another type? +This action cannot be undone. Streams cannot be converted back to 'task'. + + + 1432 + + + + Failed to create stream "'%1'" + + + 1679 + + + + Failed to edit stream "'%1'" + + + 1704 + + + + Submitting files to the stream restricted to stream owner + + + 2144 + + + + Popul&ate the mainline stream after it is created + + + 2150 + + + + The &parent stream from which this stream will be sourced: + + + 2171 + + + + Branch files from parent on stream creation + + + 402 + + + 2182 + + + + (path must be %1 levels deep - e.g. %2) + + + 1081 + + + 1213 + + + + Specify a stream location. + + + 1269 + + + + &Parent stream: + + + 2142 + + + + Submit restrictions need to be made in source stream + + + 2183 + + + + The root folder you have chosen is already in use by another stream. + + + 1354 + + + + + + + + + + Potential Conflicts + + + 29 + + + + + %n file(s) have potential conflicts with file(s) currently opened in the workspace. + %n files have potential conflicts with files currently opened in the workspace. + + + %n file(s) have potential conflicts with file(s) currently opened in the workspace. + %n files have potential conflicts with files currently opened in the workspace. + + + 147 + + + + + + + + + + checked out + + + 61 + + + + marked for add + + + 62 + + + + marked for delete + + + 63 + + + + marked for branch + + + 64 + + + + marked for integrate + + + 68 + + + + marked for import + + + 69 + + + + marked for move/add + + + 70 + + + + marked for move/delete + + + 71 + + + + purged + + + 72 + + + + archived + + + 73 + + + + status unknown, fetching... + + + 95 + + + + not synced in your workspace + + + 99 + + + 416 + + + + not in depot + + + 102 + + + + not mapped to workspace view + + + 103 + + + + not under workspace root + + + 104 + + + + modified + + + 105 + + + + default changelist + + + 115 + + + + changelist %1 + + + 116 + + + + + %n users: + %n users: + + + %n users: + %n users: + + + 145 + + + + %1 in %2 + + + 314 + + + $ACTION in $CHANGELIST + + + + moved from: + + + 319 + + + + moved to: + + + 321 + + + + %1 by %2 + + + 378 + + + $ACTION by $USER(s) + + + + link/alias/shortcut + + + 386 + + + + deleted and moved to new location + + + 393 + + + + deleted at head revision + + + 397 + + + + (#%1 of %2) + + + 407 + + + + not latest revision of file + + + 415 + + + + needs resolve + + + 422 + + + + latest revision of file + + + 428 + + + + locked + + + 434 + + + + locked by %1 + + + 441 + + + + locked by other user + + + 446 + + + + exclusive checkout + + + 455 + + + 465 + + + + exclusive checkout by %1 + + + 460 + + + + imported from another depot location + + + 472 + + + + %1-bit + + + 509 + + + + + + + + + + #%1 %2 @%3 by %4 + + + 19 + + + #%1=revision_number %2=action @%3=changelist_number by %4=user + + + + #%1 %2 @%3 + + + 25 + + + #%1=revision_number %2=action @%3=changelist_number + + + + + + + + + + %1: %2 + + + 75 + + + %1=type, such as "Submitted Changelist" or "Branch". %2=name + + + + + + + + + + &Cancel Operation + + + 409 + + + + Refres&h All + + + 413 + + + + &Refresh + + + 417 + + + + &Print... + + + 422 + + + + P&rint Preview... + + + 426 + + + + Save as PDF... + + + 429 + + + + &Add Bookmark... + + + 446 + + + + &Bookmark... + + + 448 + + + + &Get Latest Revision + + + 452 + + + + Get Revisio&n... + + + 456 + + + + Remove from &Workspace + + + 459 + + + + Dele&te Local File + + + 461 + + + + Show in Finder + + + 464 + + + + Show &in Explorer + + + 468 + + + + Show &in File Browser + + + 471 + + + + Open Command Window &Here + + + 477 + + + + Open Terminal Window &Here + + + 479 + + + + &Depot Tree + + + 486 + + + + &Workspace Tree + + + 490 + + + + &Pending Changelist + + + 494 + + + + Files i&n Folder + + + 511 + + + + Find File... + + + 514 + + + + &Check Out + + + 529 + + + + Check Out and O&pen + + + 533 + + + + Mark for &Add + + + 535 + + + + Mark for &Delete + + + 538 + + + + &Submit... + + + 541 + + + + &Move to Another Changelist... + + + 546 + + + + Revert If Unc&hanged + + + 550 + + + + Re&vert + + + 554 + + + + &Open + + + 566 + + + + &Choose Application... + + + 568 + + + + &Lock + + + 575 + + + + &Unlock + + + 578 + + + + La&bel... + + + 581 + + + + Lab&el... + + + 616 + + + + &Integrate... + + + 584 + + + + &Rename/Move... + + + 587 + + + + Rollbac&k... + + + 589 + + + + &New... + + + 595 + + + + Cre&ate... + + + 599 + + + + &Pending Changelist... + + + 608 + + + + &Job... + + + 612 + + + + &New Workspace... + + + 622 + + + + New Folder... + + + 628 + + + 632 + + + + F&older... + + + 630 + + + + &Edit + + + 634 + + + + Edit Current &Workspace... + + + 639 + + + + Edit Current &User... + + + 643 + + + + &Delete + + + 645 + + + 783 + + + + &Switch to + + + 650 + + + + Remove &All Jobs + + + 658 + + + + Remove from Changelist + + + 661 + + + + View Changelist + + + 667 + + + + View + + + 669 + + + + &Diff Against... + + + 673 + + + + Diff + + + 677 + + + + Diff Against + + + 681 + + + + Diff Against Workspace File + + + 685 + + + + &Revision Graph + + + 700 + + + + &Time-lapse View + + + 703 + + + + Folder History + + + 499 + + + + &Save Filter... + + + 432 + + + + &Save %1 Filter... + + + 433 + + + + &Manage Filters... + + + 436 + + + + &Manage %1 Filters... + + + 437 + + + + &Clear Filter + + + 440 + + + + &Clear %1 Filter + + + 441 + + + + &Refresh Filter + + + 443 + + + + &Refresh %1 Filter + + + 444 + + + + Show in Depot Tree + + + 488 + + + + Show in Workspace Tree + + + 492 + + + + Show in Pending Changelist + + + 496 + + + + &History + + + 506 + + + + &Save Search... + + + 518 + + + + &Manage Searches... + + + 520 + + + + S&tream Graph + + + 522 + + + + Stream Graph... + + + 526 + + + + &Resolve... + + + 558 + + + + Resolve Files... + + + 560 + + + + R&e-resolve Previously Resolved Files... + + + 562 + + + + Re-resolve Previously Resolved Files... + + + 564 + + + + Switch to Wor&kspace... + + + 570 + + + + Switch to Workspace, Work in this Stream + + + 571 + + + + Change Filet&ype... + + + 573 + + + + &Branch Mapping... + + + 604 + + + + New Branch Mapping... + + + 606 + + + + New Pending Changelist... + + + 610 + + + + New Job... + + + 614 + + + + New Label... + + + 618 + + + + New Stream... + + + 626 + + + + Edit Spec + + + 637 + + + + Delete Spec + + + 648 + + + + Switch to Workspace + + + 652 + + + + Remove All Jobs from Changelist + + + 659 + + + + Remove Job from Changelist + + + 662 + + + + Get This Revision + + + 664 + + + + Diff Against Have, Previous or Selected Revision + + + 679 + + + + Diff Against Head Revision + + + 687 + + + + Diff Using Branch Mapping + + + 689 + + + + Reconcile O&ffline Work... + + + 694 + + + + Advanced Reconcile Offline Work... + + + 696 + + + + File Propeties... + + + 698 + + + + Refresh Folder History + + + 721 + + + + C&opy... + + + 737 + + + + C&opy Using %1... + + + 738 + + + + C&opy to '%1'... + + + 739 + + + + &Branch File... + + + 741 + + + + &Branch Files... + + + 742 + + + + Create New Stream... + + + 744 + + + + Create New Stream from '%1'... + + + 745 + + + + Create New Stream from... + + + 746 + + + + Include + + + 748 + + + + Exclude + + + 750 + + + + Include Tree + + + 752 + + + + Exclude Tree + + + 754 + + + + Include Files + + + 756 + + + + Exclude Files + + + 758 + + + + Include Special... + + + 760 + + + + Exclude Special... + + + 762 + + + + Clear + + + 764 + + + + Clear Log + + + 766 + + + + View Timestamp + + + 768 + + + + View Server + + + 770 + + + + Font... + + + 772 + + + + Set Log Font + + + 773 + + + + &Make Shelf Global + + + 787 + + + + Request New Swarm Review... + + + 802 + + + + Open Review in Swarm + + + 805 + + + + Open Review '%1' in Swarm + + + 806 + + + + Update a Swarm Review... + + + 809 + + + 812 + + + + Update Swarm Review '%1'... + + + 810 + + + + Add to a Swarm Review... + + + 811 + + + + &Show Hidden Files + + + 832 + + + + Sort by Descending Order + + + 840 + + + + Show File Filter Tree + + + 910 + + + + Show/Hide Filter Tree + + + 912 + + + + C&lear Filter + + + 916 + + + + &Show Only Files with Highlights + + + 921 + + + + Zoom &Out + + + 925 + + + + Zoom &In + + + 931 + + + + &Ancestors of Selection + + + 937 + + + + Highlight Ancestors of Selection + + + 938 + + + + &Descendants of Selection + + + 942 + + + + Highlight Descendants of Selection + + + 943 + + + + A&ncestors and Descendants of Selection + + + 947 + + + + Highlight Ancestors and Descendants of Selection + + + 948 + + + + &File Containing Selection + + + 952 + + + + Highlight File Containing Selection + + + 954 + + + + &Revisions with Edits + + + 958 + + + + Highlight Revisions with Edits + + + 959 + + + + &Clear Highlight + + + 963 + + + + &File Renames Collapsed + + + 967 + + + + &Compressed Integration History + + + 971 + + + + Hid&e File + + + 975 + + + + Move File &Up + + + 979 + + + + Move File &Down + + + 984 + + + + Fil&ter Options... + + + 989 + + + + &Go To Initial Selection + + + 993 + + + + &Branch History + + + 997 + + + + Sort by Branch Hstory + + + 998 + + + + &File Path + + + 1002 + + + + Sort by File Path + + + 1003 + + + + &Highlight Includes Ignore Actions + + + 1007 + + + + Save Graph &As... + + + 1011 + + + + Files by Na&me... + + + 1015 + + + + Highlight Files by Name + + + 1016 + + + + Select Row + + + 1022 + + + FolderDiff + + + + Diff Row + + + 1027 + + + FolderDiff + + + + Checkout then Open + + + 1032 + + + FolderDiff + + + + Previous Diff + + + 1037 + + + FolderDiff + + + + Next Diff + + + 1043 + + + FolderDiff + + + + Show Files in Tree Hierarchy + + + 1049 + + + FolderDiff + + + + Show Files in Flat List + + + 1054 + + + FolderDiff + + + + Show Identical File Pairs + + + 1059 + + + FolderDiff + + + + Show Files without Counterparts + + + 1065 + + + FolderDiff + + + + Show Files with Content Differences + + + 1071 + + + FolderDiff + + + + Show Moved or Renamed Files + + + 1077 + + + FolderDiff + + + + Manage Folder Diff Filters + + + 1083 + + + FolderDiff + + + + Print the List of Diffs + + + 1089 + + + FolderDiff + + + + Show Only Offline Work + + + 1095 + + + FolderDiff + + + + Go To Line Number + + + 1101 + + + Timelapse + + + + Go to line number + + + 1105 + + + + Next Diff + + + 1108 + + + Timelapse + + + + Next diff + + + 1112 + + + + Previous Diff + + + 1115 + + + Timelapse + + + + Previous diff + + + 1119 + + + + Show User Information + + + 1122 + + + Timelapse + + + + Show user information + + + 1125 + + + + Show Aging of Text + + + 1128 + + + Timelapse + + + + Show aging of text + + + 1131 + + + + Show Line Numbers + + + 1134 + + + Timelapse + + + + Show line numbers + + + 1138 + + + + Show Lifetimes + + + 1141 + + + Timelapse + + + + Show lifetimes + + + 1144 + + + + No Branch History + + + 1147 + + + Timelapse + + + + Show direct history + + + 1148 + + + + Show Branch History + + + 1153 + + + Timelapse + + + + Show branch history + + + 1154 + + + + Show Originating Changelist + + + 1159 + + + Timelapse + + + + Show originating changelist information + + + 1160 + + + + Ignore Line Ending and All White Space Differences + + + 1165 + + + Timelapse + + + + Ignore Line Ending and White Space Length Differences + + + 1169 + + + Timelapse + + + + Ignore Line Ending Differences + + + 1173 + + + Timelapse + + + + Recognize Line Ending and White Space Differences + + + 1177 + + + Timelapse + + + + Single Revision Mode + + + 1181 + + + Timelapse + + + + Multiple Revision Mode + + + 1185 + + + Timelapse + + + + Incremental Differences Mode + + + 1189 + + + Timelapse + + + + Scale By Date + + + 1193 + + + Timelapse + + + + Scale By Changelist + + + 1197 + + + Timelapse + + + + Scale By Revision + + + 1201 + + + Timelapse + + + + Snap To Revision + + + 1205 + + + Timelapse + + + + Enable/Disable tweening effects + + + 1206 + + + + &Shelve... + + + 775 + + + + Refresh All Branch Mappings + + + 709 + + + + Refresh All Submitted Changelists + + + 712 + + + + Refresh All Groups + + + 714 + + + + Refresh All Jobs + + + 716 + + + + Refresh All Labels + + + 718 + + + + Refresh All Pending Changelists + + + 724 + + + + Refresh All Users + + + 726 + + + + Refresh All Workspaces + + + 729 + + + + Refresh All Streams + + + 731 + + + + &Unshelve... + + + 780 + + + + &Show Deleted Depot Files + + + 826 + + + + &Hide Deleted Depot Files + + + 827 + + + + &No Folder Filter + + + 828 + + + + &Hide Files Not in Depot + + + 830 + + + + Do Not Show Hi&dden Files + + + 833 + + + + &Entire Computer + + + 834 + + + + &Workspace Root + + + 835 + + + + &Files in Workspace/Depot Tree + + + 836 + + + + &Ascending Order + + + 837 + + + + Sort by Ascending Order + + + 838 + + + + D&escending Order + + + 839 + + + + &Refresh %1 + + + 418 + + + + &Refresh Selected Item + + + 419 + + + + &Refresh Selected Items + + + 420 + + + + &Print %1... + + + 423 + + + + P&rint Preview %1... + + + 427 + + + + Save %1 as PDF... + + + 430 + + + + &Export to... + + + 450 + + + + Show in &Depot Tree + + + 487 + + + + Show in &Workspace Tree + + + 491 + + + + Show in &Pending Changelist + + + 495 + + + + &Labels + + + 503 + + + + %1 &History + + + 507 + + + + &Submit %1... + + + 542 + + + + Revert Unc&hanged Files + + + 551 + + + + Re&vert Files + + + 555 + + + + La&bel Files... + + + 582 + + + + &Integrate Using %1... + + + 585 + + + + Back Out Revision + + + 591 + + + + Back Out %1 + + + 592 + + + + &New %1... + + + 596 + + + + Cre&ate %1 '%2'... + + + 600 + + + + Cre&ate %1 from '%2'... + + + 601 + + + + &Stream... + + + 624 + + + + &Edit %1 + + + 635 + + + + &Edit Selected Items + + + 636 + + + + Map to Workspace View... + + + 641 + + + + &Delete %1 + + + 646 + + + + &Delete Selected Items + + + 647 + + + + &Switch to %1 + + + 651 + + + + Change Ownership... + + + 655 + + + + View %1 + + + 670 + + + + View Selected Items + + + 671 + + + + Diff Against Parent + + + 691 + + + + Diff Against %1 + + + 692 + + + + Merge/&Integrate... + + + 733 + + + + Merge/&Integrate Using %1... + + + 734 + + + + Merge/&Integrate to '%1'... + + + 735 + + + + &Shelve Files... + + + 776 + + + + Submit Shelved Files... + + + 778 + + + + &Unshelve Files... + + + 781 + + + + &Delete Shelved Files + + + 784 + + + + Delete Shelved File + + + 785 + + + + Populate Stream + + + 790 + + + + &Toolbar + + + 791 + + + + Show/Hide the Toolbar + + + 792 + + + + Unload + + + 794 + + + + Unload %1 + + + 795 + + + + Unload Selected Items + + + 796 + + + + Reload + + + 798 + + + + Reload %1 + + + 799 + + + + Reload Selected Items + + + 800 + + + + Show Files &Not in Depot + + + 829 + + + + Show &Only Files Not in Depot + + + 831 + + + + + + &List + + + 847 + + + 863 + + + + La&rge Thumbnail (160x160) + + + 848 + + + + &Medium Thumbnail (120x120) + + + 849 + + + + &Small Thumbnail (80x80) + + + 850 + + + + Show Files as List + + + 853 + + + + Show Files as Large Thumbnail (160x160) + + + 854 + + + + Show Files as Medium Thumbnail (120x120) + + + 855 + + + + Show Files as Small Thumbnail (80x80) + + + 856 + + + + &Entire Depot Tree + + + 858 + + + + Tree Restricted to &Workspace View + + + 859 + + + + &Graph + + + 862 + + + + &Tree + + + 864 + + + + Show Streams as Graph + + + 867 + + + + Show Streams as List + + + 868 + + + + Show Streams as Tree + + + 869 + + + + Cre&ate from Template... + + + 1275 + + + + + + + + + + Obliterate + + + 87 + + + + Obliterate the following files/folders from "%1": + + + 88 + + + + obliterate + + + 90 + + + + lev + + + 90 + + + + No Files. + + + 122 + + + + &Add... + + + 154 + + + + &Remove + + + 155 + + + + &Preview + + + 184 + + + + &Obliterate + + + 185 + + + + Cancel + + + 186 + + + + Add Files/Folders + + + 256 + + + + Files in remote depots cannot be obliterated + + + 280 + + + + The specified range is not valid. + + + 324 + + + + No summary text. + + + 421 + + + + + + + + + + Obliterate Preview + + + 15 + + + + Obliterate Results + + + 17 + + + + OK + + + 30 + + + + + + + + + + <b>Warning!</b> Obliterating alters the Perforce server database to permanently remove all references to the selected files. If you obliterate files, it might not be possible to restore the server to its previous state. For further information on the obliterate operation, see the Perforce System Administrator's Guide. + + + 21 + + + + Proceed with obliterating files + + + 27 + + + + OK + + + 30 + + + + + + + + + + Content not found: + + + 71 + + + + Content access denied: + + + 73 + + + + Connection refused: + + + 75 + + + + Path not found: + + + 78 + + + + Protocol Failure. Either malformed p4 URI or no access to file: + + + 84 + + + + An error has occurred with file: + + + 87 + + + + + + Authentication Required + + + 111 + + + + The server %1:%2 at %3 requires a username and password. + + + 114 + + + + User Name: + + + 121 + + + + Password: + + + 124 + + + + + + + + + + More than one replaceable file argument of type %X is not allowed + +%1 + + + 748 + + + + Error + + + 750 + + + 793 + + + 880 + + + 923 + + + 1283 + + + 1370 + + + 1514 + + + 1573 + + + + No opened files selected in active pane +Cannot run %1 + + + 778 + + + + No files selected in active pane +Cannot run %1 + + + 779 + + + 1282 + + + + Unable to determine local workspace path for all selected files +Cannot run %1 + + + 788 + + + + No object selected in active pane +Cannot run %1 + + + 875 + + + + Unable to determine selected object +Cannot run %1 + + + 877 + + + + Tools > %1 requires that only one file be selected + + + 921 + + + + Error: The current selection cannot be used to run %1 + + + 1227 + + + + The 'Start In' directory '%1' does not exist. + + + 1371 + + + + Error: Unable to run %1 + + + 1496 + + + 1563 + + + + Error: %1 crashed + + + 1498 + + + + Error: %1 timed out + + + 1500 + + + + Error: %1 failed to write + + + 1502 + + + + Error: %1 failed to read + + + 1504 + + + + + + + + + + Refresh + + + 40 + + + + + + + + + + Initializing... + + + 118 + + + + Depot Tree + + + 170 + + + 432 + + + + Access level filter: + + + 177 + + + + Host IP filter: + + + 189 + + + + Show files + + + 198 + + + + Note: Some access levels for users and groups may have been assigned through the authentication server's permission table. + + + 211 + + + + Select user or group to view permissions: + + + 305 + + + 373 + + + + Permissions for user: + + + 329 + + + + Permissions for group: + + + 395 + + + + Select folder or file to view permissions: + + + 426 + + + + Permissions for file/folder: + + + 478 + + + + &nbsp;&nbsp;Permissions (unsaved) for user: <b>%1</b> + + + 527 + + + + &nbsp;&nbsp;Permissions (unsaved) for group: <b>%1</b> + + + 529 + + + + &nbsp;&nbsp;Permissions for user: <b>%1</b> + + + 534 + + + + &nbsp;&nbsp;Permissions for group: <b>%1</b> + + + 536 + + + + &nbsp;&nbsp;Permissions (unsaved) for file: <b>%1</b> + + + 564 + + + + &nbsp;&nbsp;Permissions (unsaved) for folder: <b>%1</b> + + + 566 + + + + &nbsp;&nbsp;Permissions for file: <b>%1</b> + + + 571 + + + + &nbsp;&nbsp;Permissions for folder: <b>%1</b> + + + 573 + + + + Processing %1/%2 + + + 1562 + + + progress %1 out of %2 + + + + Discard the changes made to the permissions table? + + + 695 + + + + Error: unable to load Protect Table + + + 850 + + + + Saving protect table... + + + 898 + + + + Table saved + + + 915 + + + + Error: Table not saved + + + 917 + + + + Loading Permissions Viewer data... + + + 1262 + + + + all users + + + 1507 + + + + all groups + + + 1508 + + + + no access + + + 1596 + + + 1653 + + + + + + + + + + none + + + 1311 + + + + + + + + + + Stream: + + + 164 + + + + <a href="%1">Refresh merge/copy information</a> + + + 214 + + + + Work in this Stream + + + 227 + + + 237 + + + + Currently working in this Stream + + + 234 + + + + + + + + + + Preview of %1 + + + 108 + + + + Richtext Editor + + + 140 + + + + &Hide fields when empty + + + 161 + + + + Fields: + + + 195 + + + + &Set As Default + + + 200 + + + + Page Setup + + + 484 + + + + Next page + + + 516 + + + + Previous page + + + 517 + + + + First page + + + 518 + + + + Last page + + + 519 + + + + Fit width + + + 528 + + + + Fit page + + + 529 + + + + Zoom in + + + 540 + + + + Zoom out + + + 541 + + + + Portrait + + + 547 + + + + Landscape + + + 548 + + + + Show single page + + + 558 + + + + Show facing pages + + + 559 + + + + Show overview of all pages + + + 560 + + + + Print + + + 575 + + + + Page setup + + + 576 + + + + close + + + 577 + + + + + + + + + + Cancel + + + 34 + + + + + + + + + + Edit permissions using the table format + + + 102 + + + + Edit permissions using the plain text format + + + 108 + + + + Undo + + + 116 + + + + Redo + + + 124 + + + + Insert line + + + 134 + + + + Delete line + + + 140 + + + + Move line up + + + 146 + + + + Move line down + + + 152 + + + + &Find + + + 161 + + + + Filter table by the selected user, group or folder + + + 170 + + + + Revert Edits + + + 188 + + + + Save Edits + + + 192 + + + + &nbsp;&nbsp;Table filtered by user: <b>%1</b> + + + 282 + + + + &nbsp;&nbsp;Table filtered by group: <b>%1</b> + + + 284 + + + + &nbsp;&nbsp;Table filtered by path: <b>%1</b> + + + 295 + + + + * + + + 751 + + + + Access Level + + + 762 + + + + User/Group + + + 766 + + + + Name + + + 770 + + + + Host + + + 774 + + + + Folder/File + + + 779 + + + + Comment + + + 783 + + + + + + + + + + <integration preview> + + + 380 + + + + + + + + + + already opened + + + 554 + + + 556 + + + 559 + + + 560 + + + 561 + + + + local depot + + + 555 + + + + requires -d + + + 557 + + + + requires -i + + + 558 + + + + existing + + + 562 + + + + deleted + + + 563 + + + + requires sync + + + 564 + + + + error + + + 565 + + + 566 + + + 574 + + + + sync + + + 567 + + + 568 + + + 569 + + + + bad flag + + + 570 + + + + requires -v + + + 571 + + + + incompatible type + + + 572 + + + + + + + + + + Binary File + + + 327 + + + + You are about to run Timelapse view on a binary file, would you like to continue? + + + 328 + + + + + + Time-lapse View - %1 (%2, %3) + + + 439 + + + Window title, %1=file, %2=server_port, %3=user + + + + Invalid file path: %1 + + + 212 + + + 223 + + + + Invalid file revision path: %1 + + + 270 + + + 280 + + + + + + + + + + Browser Preview + + + 39 + + + + Source Text + + + 41 + + + + + + File: + + + 207 + + + + No preview available + + + 281 + + + + + + Refresh + + + 117 + + + + + + + + + + none applied + + + 137 + + + 1479 + + + 2358 + + + 1785 + + + + + + Show only workspaces available for use on this computer + + + 1913 + + + + <font color="gray">Workspace</font> can be used by this computer + + + 2036 + + + + + + <font color="gray">Query is</font> "%1" + + + 2331 + + + + (including integrated revisions) + + + 2335 + + + + and File paths are + + + 2351 + + + + + + Show only pending changelists with shelved files + + + 2820 + + + + Change the sort order of files in pending changelists + + + 2885 + + + + <font color="gray">Pending Changelist</font> has shelved files + + + 2938 + + + + + + Match case when filtering by name + + + 1092 + + + + Show unloaded %1 + + + 1116 + + + + "%1" + + + 1587 + + + 1591 + + + + and + + + 1616 + + + + or + + + 1618 + + + + + + + + + + of the following conditions: + + + 149 + + + 150 + + + 924 + + + 156 + + + + is not empty + + + 160 + + + 167 + + + 932 + + + 167 + + + + is empty + + + 166 + + + 172 + + + 937 + + + 173 + + + + + + File Name + + + 63 + + + 79 + + + + Folder Path + + + 64 + + + 84 + + + + Date Last Submitted + + + 332 + + + + Submitted + + + 333 + + + + is + + + 334 + + + 336 + + + + + + + + + + There are no files that need to be reconciled. + + + + 776 + + + + Reconcile Offline Work + + + 825 + + + + Modified files (Select files to check out) + + + 865 + + + + Local files not in depot (Select files to mark for add) + + + 873 + + + + Depot files missing from workspace (Select files to mark for delete) + + + 881 + + + + &Add files to pending changelist: + + + 892 + + + + A&dvanced Reconcile... + + + 904 + + + + &Reconcile + + + 905 + + + + Cancel + + + 906 + + + + Reconciled offline work + + + 1132 + + + + There are files that need to be reconciled that don't + + + 1309 + + + + fall under one of the three basic categories: + + + 1310 + + + + Modified files that are not checked out + + + 1313 + + + + Local files not in depot + + + 1314 + + + + Depot files missing from workspace + + + 1315 + + + + Use Advanced Reconcile option? + + + 1316 + + + + &Advanced + + + 1325 + + + 1330 + + + + Save Filter... + + + 1534 + + + + Set As Default + + + 1537 + + + + Manage Filters... + + + 1540 + + + + "%1" + + + 1712 + + + 1716 + + + + and + + + 1741 + + + + or + + + 1743 + + + + + + + + + + Resolve (%1) + + + 205 + + + + Files to resolve: + + + 246 + + + + list + + + 873 + + + + Recommended action: + + + 440 + + + + Resolve options: + + + 527 + + + 580 + + + 659 + + + 709 + + + 732 + + + 754 + + + 778 + + + + Common base + + + 474 + + + + Merged result + + + 512 + + + + Accept &Merged + + + 831 + + + + &Diff + + + 807 + + + + &File History + + + 808 + + + + Time-lapse &View + + + 809 + + + + Click the "Accept Merged" button below. + + + 987 + + + + Only %1 file uniquely differs from the common base file. + + + 1013 + + + + Only %1 file differs from the common base file. + + + 1015 + + + + Click the "Run Merge Tool" button below. + + + 989 + + + + Symbolic links cannot be merged + + + 1019 + + + 2785 + + + + In order to do a merge of binary files click the "Run Merge Tool" button + + + 1021 + + + + Files cannot be merged because their file types are incompatible. + + + 1023 + + + + Perforce could not resolve this file. + + + 993 + + + + Target + + + 83 + + + + Source + + + 87 + + + + Re-resolve (%1) + + + 207 + + + + Resolve method: + + + 212 + + + + &Auto resolve multiple files + + + 230 + + + + &Interactively resolve files one at a time + + + 233 + + + + Previously resolved files: + + + 248 + + + + S&elect + + + 251 + + + + Merge &binary files as text when resolving content + + + 256 + + + + Resolve (target) + + + 285 + + + + Resolve With (source) + + + 287 + + + + Resolve Type + + + 290 + + + + Auto resolve options: + + + 334 + + + + <strong>Sa&fe automatic resolve (no merging)</strong><br>Accept source if only the source file has changed. Accept target if only the target file has changed. Don't resolve file if both source and target have changed. + + + 357 + + + + <strong>A&utomatic resolve (allow merging)</strong><br>Accept source if only the source file has changed. Accept target if only the target file has changed. Merge changes if both source and target have changed and there are no conflicts. + + + 358 + + + + <strong>Accept &source</strong><br>All changes made to the source file are replicated in the target file. + + + 359 + + + + <strong>Accept &target</strong><br>Leave target file unchanged. + + + 360 + + + + <strong>Aut&omatic resolve (allow merging with conflicts)</strong><br>Accept source if only the source file has changed. Accept target if only the target file has changed. Merge changes if both source and target have changed, even if there are conflicts. + + + 361 + + + + Set As Auto &Default + + + 416 + + + + Auto &Resolve + + + 418 + + + + Source file + + + 492 + + + + Target file + + + 495 + + + + Replace target file with a copy of source file + + + 533 + + + + Leave target file unchanged + + + 534 + + + + Replace target file with the merged result of source and target + + + 535 + + + + Manually merge source and target before accepting + + + 536 + + + + Common base: + + + 558 + + + 638 + + + + Match the filetype of the source file: + + + 582 + + + + Keep the filetype of the target file: + + + 585 + + + + Combine the filetypes of both the source and target: + + + 590 + + + + Rename the file to match the source file: + + + 661 + + + + Keep the current name of the target file: + + + 664 + + + + Combine the name changes made to both the source and target: + + + 669 + + + + Branch target file from source + + + 711 + + + + Do not branch from source file (ignore) + + + 712 + + + + Delete the target file + + + 733 + + + + Do not delete the target file (ignore) + + + 734 + + + + Replace target file's attributes with those from source + + + 755 + + + + Leave target file's attributes unchanged + + + 756 + + + + Add source's propagating attributes to target file + + + 757 + + + + A&dditional Actions + + + 797 + + + + &Open File + + + 806 + + + + Revision &Graph + + + 810 + + + + Accept &Source + + + 829 + + + + Accept &Target + + + 830 + + + + &Run Merge Tool + + + 832 + + + + All + + + 965 + + + + Binary Files + + + 967 + + + + Text Files + + + 969 + + + + Content Resolves + + + 971 + + + + Move Resolves + + + 973 + + + + Filetype Resolves + + + 975 + + + + Branch Resolves + + + 977 + + + + Delete Resolves + + + 979 + + + + Attribute Resolves + + + 981 + + + + Click the "Accept Target" button below. + + + 983 + + + + Click the "Accept Source" button below. + + + 985 + + + + No recommended action. + + + 991 + + + + The target file does not currently exist in the depot. + + + 995 + + + + The source file has been deleted but the corresponding target file still exists. + + + 997 + + + + Both the source and target files have been renamed or moved. + + + 999 + + + + The %1 file has been renamed or moved. + + + 1001 + + + + The filetypes of both the source and target files have changed. + + + 1003 + + + + The attributes of the files have been changed. + + + 1005 + + + + is an unknown type of resolve. Upgrade to the newest P4V for more detailed information. + + + 1007 + + + + Target file and source file are identical + + + 1009 + + + + Both files differ from common base file, but with no conflicts. + + + 1011 + + + + Conflicts exist between target file and source file. + + + 1017 + + + + Performing Auto resolve + + + 1334 + + + + There are still conflicts and conflict markers in the merged file. + Do you accept this file? + + + 1511 + + + + Cannot Find P4Merge + + + 1600 + + + + P4Merge not found + + + 1600 + + + + Merge file used for resolve + + + 1655 + + + + %1 of the files could not be resolved using the Auto resolve option chosen. + + + 2090 + + + + Please use a different Auto resolve method. + + + 2093 + + + + Please use a different Auto resolve method or the Interactive resolve option on the remaining files. + + + 2095 + + + + %1 of %2 items selected + + + 2379 + + + + Differences from base: %1 %2 + + + 2577 + + + 2581 + + + + unique + + + 2578 + + + 2582 + + + + There are conflicts remaining + + + 2594 + + + + No conflicts remaining + + + 2602 + + + + Conflicts: %1 + + + 2609 + + + + +%1 common to both + + + 2620 + + + + target + + + 2675 + + + 2677 + + + 2755 + + + + source + + + 2684 + + + 2686 + + + 2753 + + + + Accept Merged + + + 2936 + + + + Replace '%1' with the merged result file? + + + 2937 + + + + You can no longer resolve '%1'. + + + 2986 + + + + %1 vs %2 + + + 3078 + + + + Base vs %1 + + + 3079 + + + 3080 + + + + Base vs Merged Result + + + 3089 + + + + %1 vs Merged Result + + + 3090 + + + 3091 + + + + A merge tool is open. You must exit the merge tool. + + + 3226 + + + + %1 + + + 3034 + + + 3035 + + + 3047 + + + 3051 + + + 3055 + + + 3056 + + + 3113 + + + 3117 + + + 3121 + + + 3122 + + + 3145 + + + 3149 + + + 3153 + + + 3154 + + + 3177 + + + 3181 + + + 3185 + + + 3186 + + + + Merged Result + + + 3041 + + + + + + + + + + Re-resolve... + + + 96 + + + + There are no files that need resolving. + + + 219 + + + + There are no files to re-resolve. + + + 224 + + + + The file can not be re-resolved. + + + 229 + + + + + + + + + + Details + + + 81 + + + 86 + + + 91 + + + 96 + + + 116 + + + + Integrations + + + 101 + + + + Files + + + 111 + + + + Checked Out By + + + 121 + + + + Preview + + + 126 + + + + Legend + + + 131 + + + + + + + + + + &Do not limit operation + + + 29 + + + + &Operate on all revisions up to: + + + 33 + + + + Operate on only revisions bet&ween: + + + 36 + + + + %1 all revisions + + + 107 + + + + %1 revisions up to: + + + 111 + + + + %1 revisions between: + + + 115 + + + + obliterate + + + 119 + + + + + + + + + + Rollback to Revision... + + + 105 + + + + Rollback + + + 144 + + + + + + Rollback Preview + + + 938 + + + + None + + + 942 + + + + Files that will be rolled back to an earlier revision: + + + 948 + + + + Files that will be re-added: + + + 962 + + + + Files that will be deleted: + + + 976 + + + + + + The file has only one revision and cannot be rolled back + + + 574 + + + + The file cannot be rolled back. The revision you requested is the same as the latest revision in the depot. + + + 741 + + + + Pending &changelist: + + + 286 + + + + Rollback File + + + 291 + + + + Rollback the following file: + + + 294 + + + + Rollback Folder + + + 299 + + + + Rollback the following folder: + + + 303 + + + + Bro&wse... + + + 305 + + + + &Rollback folder to: + + + 360 + + + + &Delete files not in label + + + 363 + + + + R&ollback file to: + + + 390 + + + + &Rollback to previous revision (%1 of %2) + + + 425 + + + + &Preview + + + 437 + + + + &Save to Changelist + + + 438 + + + + Sub&mit... + + + 439 + + + + Cancel + + + 440 + + + + Please enter a revision specifier. + + + 564 + + + + revision %1 + + + 638 + + + + changelist %1 + + + 641 + + + + label %1 + + + 644 + + + + Rollback %1 to %2 + + + 655 + + + + Rollback operation could not be performed. The folder is not mapped to your workspace view + + + 670 + + + + Rollback operation could not be performed. The file is not mapped to your workspace view + + + 673 + + + + Rollback Preview + + + 693 + + + + There are no files to roll back. The revisions you requested are the same as the latest revisions in the depot. + + + 745 + + + + Choose Folder + + + 803 + + + + Rollback + + + 827 + + + + + + + + + + 3 + + + 30 + + + + highest + + + 31 + + + + (ticket-based authentication required) + + + 32 + + + + 2 + + + 37 + + + + high + + + 38 + + + + (strong passwords required) + + + 39 + + + + 1 + + + 44 + + + + medium + + + 45 + + + + (passwords required) + + + 46 + + + + 0 + + + 51 + + + + low + + + 52 + + + + (passwords not required) + + + 53 + + + + + + + + + + Close Window + + + 343 + + + + &Close + + + 346 + + + + CTRL+W + + + 349 + + + + &Minimize + + + 353 + + + + CTRL+M + + + 354 + + + + Maximize + + + 356 + + + + Zoom + + + 357 + + + + Bring All to Front + + + 360 + + + + &Find... + + + 363 + + + + &Copy + + + 368 + + + + CTRL+C + + + 369 + + + + Clear + + + 372 + + + + Copy Depot Path + + + 375 + + + + CTRL+SHIFT+C + + + 376 + + + Copy Depot Path + + + + &Select All + + + 378 + + + + CTRL+A + + + 379 + + + + CTRL+H + + + 387 + + + + CTRL+ALT+H + + + 392 + + + + + + + + + + No bookmarks + + + 89 + + + + + + + + + + Choose Character Encoding + + + 21 + + + + &Choose character encoding to use with the current connection: + + + 36 + + + + Warning: Changing the character encoding can make existing workspace files unreadable + + + 47 + + + + &Use this encoding for all connections to Unicode servers +(will not affect connections for which the encoding was previously set) + + + 54 + + + + + + + + + + &Don't show this dialog again + + + 33 + + + + OK + + + 39 + + + + No + + + 52 + + + + Yes + + + 53 + + + + + + + + + + Filter Depot Tree + + + 292 + + + + Filter Workspace Tree + + + 298 + + + + Sort By + + + 303 + + + + + + + + + + Environment Settings + + + 36 + + + + Windows environment variables for the Perforce connection. + + + 43 + + + + Type in settings or choose from menu: + + + 47 + + + + &Connections + + + 50 + + + + No favorite connections + + + 67 + + + + (host:port) + + + 73 + + + + &Server (P4PORT): + + + 75 + + + + &User (P4USER): + + + 84 + + + + (optional) + + + 92 + + + + &Workspace (P4CLIENT): + + + 94 + + + + C&harset (P4CHARSET): + + + 104 + + + + Us&e current connection for environment settings + + + 110 + + + + + + + + + + Loading revision data... + + + 91 + + + + + + Toolbar + + + 250 + + + + &File + + + 275 + + + + &Edit + + + 304 + + + + &View + + + 316 + + + + &Submitted Changelist + + + 324 + + + + H&ighlight + + + 331 + + + + &Tools + + + 335 + + + + Manage &Custom Tools... + + + 344 + + + + &Window + + + 355 + + + + &Help + + + 374 + + + + Revision Graph &Help + + + 380 + + + + There were too many revisions in this path to graph. + + + + 428 + + + + Try graphing a smaller set of files. + + + 429 + + + + Processing revision data... + + + 444 + + + + Cancel + + + 445 + + + + Processing integration data... + + + 514 + + + + Loading %1 + + + 717 + + + + No workspace selected. + + + 1206 + + + + Revision Graph - %1 (%2) + + + 1288 + + + + + + + + + + Address Bar + + + 219 + + + + &Log Off + + + 1402 + + + + Toolbar + + + 858 + + + + %1 is still running server commands. Do you want to close the connection %2? +Closing the connection may result in partial completion of commands, but will not corrupt your data. + + + 1104 + + + + No update available. + +This version of P4V is up to date! + +Installed version: %1 + + + 2552 + + + 2581 + + + + &Copy + + + 2265 + + + 2270 + + + + &Do Not Copy + + + 2272 + + + + &Get Latest + + + 2288 + + + 2293 + + + + &Don't Get Latest + + + 2295 + + + + &Update + + + 2333 + + + 2338 + + + + &Do Not Update + + + 2340 + + + + &Window + + + 2992 + + + + Open &Recent + + + 2998 + + + + &Favorite Connections + + + 3004 + + + + &Dock Window + + + 3010 + + + + &Bookmarks + + + 3016 + + + + Filter Dep&ot + + + 3022 + + + + Filter Wor&kspace + + + 3028 + + + + &Tools + + + 3034 + + + 3438 + + + + &File + + + 3067 + + + + &New + + + 3072 + + + + &Show In + + + 3105 + + + + &Edit + + + 3132 + + + + &View + + + 3250 + + + + Sort B&y + + + 2861 + + + 3305 + + + + Could not connect to Swarm. '%1' + + + 429 + + + + Connected to Swarm Version '%1' + + + 434 + + + + Would you like to automatically check for new versions of P4V? + + + 507 + + + + Refresh + + + 878 + + + + Get Latest + + + 889 + + + + Submit + + + 899 + + + + Checkout + + + 911 + + + + Add + + + 921 + + + + Delete + + + 931 + + + + Revert + + + 942 + + + + Diff + + + 954 + + + + Timelapse + + + 964 + + + + Revgraph + + + 975 + + + + Cancel + + + 987 + + + + Copy latest version of all mapped files to new root directory? + + + 2254 + + + + If your workspace view includes a large number of files, this may require considerable disk space. + + + 2256 + + + + Get latest revisions of all files added to your workspace view? + + + 2282 + + + + Update workspace files to match the workspace view changes? + +Updating will only remove files that are no longer in the view and move files that have been mapped to a different location. + + + 2325 + + + + %1%4Updates available for P4V.%5%3%3Please go to <a href="http://www.perforce.com/downloads/Perforce-Software-Version-Management/complete_list/Customer%20Downloads">Download Installer</a> to get the new version.%2 + + + 2602 + + + + %2Updates available for P4V.%3%4%4There's an update available, but your administrator has set a limit on allowed versions of P4V.%4%4Please ask your administrator which version to download.%4%4Installed version: %1%5 + + + 2615 + + + 2644 + + + + Installed version of P4V: %1<hr> + + + 2658 + + + + Release Notes + + + 2663 + + + 2696 + + + + Download Installer + + + 2668 + + + 2701 + + + + %1A new release of P4V is available.%2%3Newest version: %4%3%5 | %6 + + + 2669 + + + + %1There's a patch available for your version of P4V.%2%3Latest patch: %4%3%5 | %6 + + + 2702 + + + + Show &Files As + + + 2865 + + + 3308 + + + + Show &Streams As + + + 2871 + + + 3312 + + + + Show/&Hide + + + 2879 + + + + &Search + + + 3154 + + + + Fil&e Searches + + + 3192 + + + + Pen&ding Changelist Filters + + + 3196 + + + + S&ubmitted Changelist Filters + + + 3200 + + + + Bra&nch Mapping Filters + + + 3206 + + + + &Label Filters + + + 3213 + + + + &Workspace Filters + + + 3220 + + + + &Job Filters + + + 3227 + + + + Stre&am Filters + + + 3234 + + + + Stream &Graph Filters + + + 3238 + + + + Show/H&ide + + + 3319 + + + + &Actions + + + 3334 + + + + Res&olving + + + 3353 + + + + Sh&elving + + + 3362 + + + + &Connection + + + 3397 + + + + &Help + + + 3506 + + + + + + + + + + Directory does not exist + + + 96 + + + 104 + + + + %1 could not be found + + + 96 + + + 104 + + + + + + + + + + Open File With + + + 33 + + + + %1 %2 + + + 339 + + + + Launch error + + + 409 + + + 414 + + + + Could not start "%1" + + + 410 + + + 415 + + + + + + + + + + Open &With + + + 37 + + + + + + + + + + Richtext Editor + + + 92 + + + + + + + + + + Select folder to export to + + + 90 + + + + + + + + + + Status + + + 177 + + + + + + + + + + Locked + + + 78 + + + + Stream Root + + + 82 + + + + Parent Root + + + 83 + + + + Submitted By + + + 98 + + + + Date Submitted + + + 99 + + + + Access Type + + + 100 + + + 104 + + + + Date Modified + + + 109 + + + + Last Accessed + + + 110 + + + + Full Name + + + 111 + + + + Password Changed + + + 112 + + + + Max Results + + + 117 + + + + Max Scan Rows + + + 118 + + + + Max Lock Time (msec) + + + 119 + + + + Login Timeout + + + 120 + + + + Password Expiration + + + 121 + + + + Changelist + + + 128 + + + + Unable to shelve files: + + + 301 + + + + Unable to unshelve the following files: + + + 356 + + + + &Show In Depot Tree + + + 443 + + + + + + Parent + + + 887 + + + + Stream + + + 889 + + + + Submit Access + + + 891 + + + + + + Review Id + + + 691 + + + + Review State + + + 694 + + + + Get These Revisions + + + 781 + + + + + + Review Id + + + 923 + + + + Review State + + + 926 + + + + + + + + + + P4Admin + + + 82 + + + + Perforce P4Admin + + + 82 + + + + + + + 115 + + + + + + + + + + %1 is still running server commands. Do you want to stop those commands? +Stopping may result in partial completion of commands, but will not corrupt your data. + + + 288 + + + + Yes + + + 293 + + + + No + + + 292 + + + + Switch + + + 302 + + + + Don't Switch + + + 301 + + + + %1 is still running server commands. Do you want to stop those commands? +Switching workspaces may result in partial completion of commands, but will not corrupt your data. + + + 297 + + + + This workspace cannot be used on this computer either because the host field does not match your computer name or the workspace root cannot be used on this computer. + + + 401 + + + + This workspace cannot be used because it has been unloaded. It must be reloaded to be used (p4 reload -c). + + + 405 + + + + The stream associated with workspace "%1" has been unloaded. Stream must be reloaded in order to use this workspace. + + + 408 + + + + This workspace no longer exists on the server. + + + 411 + + + + Switching to workspace "%1" failed. + + + 416 + + + + Workspace switch error + + + 421 + + + + %1 + + + 488 + + + all window captions [add C,P,U] + + + + - Perforce P4V + + + 700 + + + + + + + + + + Checked Out + + + 234 + + + + Shelved* + + + 170 + + + + &Revert checked out files before unshelving + + + 316 + + + + &Overwrite workspace files even if they are writeable + + + 319 + + + + &Delete shelved files after they are unshelved + + + 539 + + + + Map unshelved files + + + 254 + + + + using %1 + + + 266 + + + + Browse... + + + 279 + + + 281 + + + + &Preview + + + 304 + + + + &Don't shelve unchanged files + + + 323 + + + + &Make shelf globally accessible + + + 326 + + + + File Name + + + 351 + + + + Open Review in Swarm + + + 383 + + + + &Unshelve + + + 492 + + + + &Shelve + + + 497 + + + + Update Files in Review + + + 501 + + + + Update Files + + + 502 + + + + Request New Swarm Review + + + 506 + + + + Request Review + + + 507 + + + + Update a Swarm Review + + + 511 + + + + Update Review + + + 512 + + + + Add to a Swarm Review + + + 516 + + + + Add to Review + + + 517 + + + + &Ok + + + 521 + + + + Cancel + + + 527 + + + + The following files will be shelved in pending changelist '%1' to update in review: + + + 646 + + + + The following files will be shelved for review: + + + 650 + + + + The following shelved files will be part of the review: + + + 654 + + + 676 + + + + The following files will be sent for review: + + + 658 + + + + Update Review: + + + 689 + + + + View Review Description + + + 692 + + + + Review Description: + + + 704 + + + 720 + + + + Reviewers: + + + 741 + + + + unshelve + + + 966 + + + + shelve + + + 966 + + + + You must select files to %1. + + + 969 + + + + %1 (not mapped to branch mapping source) + + + 1079 + + + 2166 + + + + Unable to unshelve the following file(s): + + + 1083 + + + 1559 + + + 2170 + + + + Some of the files you are trying to unshelve are checked out. + + + 1219 + + + + Select the "Resolve Files" button if you want to merge content from the shelved files into the checked out files. Otherwise, revert the checked out files before unshelving. + + + 1220 + + + + &Resolve Files + + + 1227 + + + + Unshelved from pending changelist '%1': + + + 1242 + + + + Unable to shelve files: + + + 1466 + + + + %1 unshelved, needs resolve + + + 1589 + + + + %1 unshelved + + + 1594 + + + + Preview: + + + 1597 + + + + You do not have permission to move the following files: + + + 1626 + + + + Please specify a stream root. + + + 1831 + + + + Please specify a branch mapping. + + + 1844 + + + + Using Stream + + + 1904 + + + + Please specify a valid branch mapping. + + + 2182 + + + + Required + + + 2255 + + + + Us&er: + + + 2298 + + + + A&dd + + + 2305 + + + + Br&owse... + + + 2316 + + + + Remo&ve + + + 2323 + + + + Select Users + + + 2346 + + + + User '%1' does not exist. + + + + 2404 + + + + Changelist could not be added to review. + + + + 2549 + + + + Changelist '%1' added to review '%2' + + + 2561 + + + + Review could not be created. + + + + 2627 + + + + Review '%1' created + + + 2638 + + + + Review ID not found + + + 2681 + + + + Swarm connection closed. + + + 2704 + + + + &Add to pending changelist: + + + 563 + + + + Options: + + + 572 + + + 760 + + + + &Revert checked out files after they are shelved + + + 632 + + + + Select files in the default pending changelist to shelve: + + + 640 + + + + *A shelved version of the file already exists in the changelist and will be replaced if selected + + + 672 + + + + Some of the files selected are checked out and cannot be unshelved unless they are reverted first. + + + 1204 + + + + Unshelve + + + 491 + + + + Select shelved files in pending changelist '%1' to unshelve: + + + 556 + + + + Select files in pending changelist '%1' to shelve: + + + 642 + + + + Shelve + + + 496 + + + + + + + + + + Cre&ate/Update %1 from '%2'... + + + 134 + + + + + + + + + + Stream '%1' has been created but an error occurred while trying to create a temporary workspace to branch the files from the parent stream. + + + 514 + + + + Stream '%1' has been created but an error occurred while trying to create the workspace. + + + 519 + + + + You will need to create a workspace for the stream. + + + 522 + + + + If you wish to branch files from the parent stream, use the branch dialog to branch those files. + + + 523 + + + + Populating files from parent stream... + + + 710 + + + + Branching files from parent stream... + + + 715 + + + + Stop + + + 716 + + + + Initial branch of files from %1 (%2) to %3 (%4) + + + 754 + + + 1031 + + + %1=parentName, %2=parent stream root, %3=new stream name, %4=new stream root + + + + Add Files Wizard + + + 855 + + + + Add Files Assistant + + + 857 + + + + Stream '%1' has been created but files have not been branched to it because an error occurred while trying to branch the files to the stream. + + + 957 + + + + Submitting files to stream... + + + 1006 + + + + Stream '%1' has been created but files have not been branched to it because an error occurred while trying to submit the files to the stream. You may use the workspace '%2' to try to submit the files in the changelist specified below. + + + 1067 + + + + Stream '%1' has been created. Files have been branched and submitted from the parent. An error occurred while trying to delete the temporary workspace '%2' used to branch and submit the files. You will need to delete the workspace manually. + + + 1150 + + + + Reverting files... + + + 1302 + + + + Stream '%1' has been created but files have not been branched to it because you stopped the Branch operation. An error occurred while trying to revert the branched files. + + + 1352 + + + + + + + + + + <a href="%1">Merge</a> changes + + + 982 + + + + Nothing to merge + + + 985 + + + + <i>Not fetched</i> + + + 988 + + + 1041 + + + + Copies pending, merge prior to copying + + + 1029 + + + + <a href="%1">Copy</a> changes + + + 1033 + + + + Nothing to copy + + + 1037 + + + + + + + + + + Could not parse the URI '%1' + + + 497 + + + + Cannot select objects on different servers + + + 499 + + + + p4v-js parse error + + + 1212 + + + + Missing quote somewhere + + + 1212 + + + 1213 + + + + P4JsApi global args error + + + 1266 + + + 1271 + + + 1276 + + + 1297 + + + 1302 + + + 1309 + + + 1325 + + + 1332 + + + 1341 + + + 1363 + + + 1370 + + + 1377 + + + 1382 + + + + -G not supported in P4JsApi + + + 1266 + + + 1267 + + + + -s not supported in P4JsApi + + + 1271 + + + 1272 + + + + -V not supported in P4JsApi + + + 1276 + + + 1277 + + + + PerforceJsApi global args error + + + 1290 + + + + -c not enough arguments + + + 1290 + + + 1291 + + + + -d not supported in P4JsApi + + + 1297 + + + 1298 + + + + Command not allowed by P4JsApi. + + + 1397 + + + 1398 + + + + P4JsApi broker error + + + 1409 + + + 1419 + + + + 'broker' must be followed by a command + + + 1409 + + + 1410 + + + + known commands are not allowed + + + 1419 + + + 1420 + + + + -H not supported in P4JsApi + + + 1302 + + + 1303 + + + + -p not enough arguments + + + 1309 + + + 1310 + + + 1332 + + + 1333 + + + + -P not supported in P4JsApi + + + 1325 + + + 1326 + + + + -x not enough arguments + + + 1341 + + + 1342 + + + + P4JsApi -x error + + + 1347 + + + + File not found. Requires absolute path + + + 1347 + + + 1348 + + + + -C not enough arguments + + + 1363 + + + 1364 + + + + -Q not supported in P4JsApi + + + 1370 + + + 1371 + + + + -L not supported in P4JsApi + + + 1377 + + + 1378 + + + + Invalid global option. + + + 1382 + + + 1383 + + + + P4JsApi error + + + 1397 + + + + p4 command error + + + 1480 + + + 1513 + + + 1521 + + + 1529 + + + + p4 command error: empty form + + + 1480 + + + 1481 + + + 1513 + + + 1514 + + + + p4 command error: unrecognized datatype in form + + + 1521 + + + 1522 + + + + p4 command error: last argument found was a '-i' + + + 1529 + + + 1530 + + + + + + + + + + No opened files found. + + + 258 + + + + You cannot submit an existing submitted changelist + + + 293 + + + + You do not have permission to submit this changelist + + + 298 + + + + Submit Changelist: %1 (%2) + + + 338 + + + 360 + + + + Changelist %1 contains shelved files and cannot be submitted. + + + 380 + + + + There was a problem deleting the shelve, please be sure you have permission to delete the shelve. + + + 396 + + + + + + There are no shelved files. + + + 497 + + + + Shelved change must be owned by user submitting the change. + + + 505 + + + + Submitting shelved files from a task stream is not supported. + + + 514 + + + + Changelist %1 contains non-shelved files and therefore the shelved files cannot be submitted. + + + 522 + + + + Move to Another Changelist... + + + 539 + + + + Revert Files + + + 540 + + + + Create changelist failed + + + 596 + + + + You do not have permission to move the following files: + + + 610 + + + + Files not reverted + + + 631 + + + + + + + + + + Write a changelist description: + + + 272 + + + + Choose files to submit: + + + 273 + + + + Choose additional options: + + + 274 + + + + Link jobs to changelist (optional): + + + 275 + + + + Out of date: Get latest revision to set up a resolve (this will not overwrite your copy of the file). + + + 319 + + + + Locked: File must be unlocked or submitted by user who locked it before you can submit. + + + 333 + + + + Moved: Edits to the file cannot be submitted until you resolve the moved file. + + + 340 + + + + Deleted: Edits to the file cannot be submitted without re-adding the file. + + + 347 + + + + Files to submit: + + + 457 + + + + Do not s&ubmit if resolved files have been modified + + + 561 + + + + On su&bmit: + + + 568 + + + + Submit all selected files + + + 573 + + + + Submit all files + + + 574 + + + + Don't submit unchanged files + + + 576 + + + + Revert unchanged files + + + 578 + + + + &Check out submitted files after submit + + + 580 + + + + There are %1 other files in this changelist that will not be submitted. + + + 1842 + + + + Unresolved: Resolve file(s) before submitting. + + + 326 + + + + form + + + 356 + + + + R&estrict access to changelist + + + 584 + + + + <a href>(more info)</a> + + + 586 + + + + Sa&ve + + + 416 + + + + J&ob: + + + 694 + + + + &Add + + + 703 + + + + B&rowse... + + + 712 + + + + &Job status upon submit: + + + 734 + + + + %1 does not exist. + + + 1406 + + + + This pending change has updated. Do you want to reset the file list? + + + 1784 + + + + + + + + + + <%1> + + + 1324 + + + + Unmapped (%1) + + + 1332 + + + + No unmapped files (%1) + + + 1334 + + + + Excluded files (%1) + + + 1338 + + + + + + + + + + Folder Diff Filter Error + + + 1453 + + + 1469 + + + + Skipping filter #%1 is not using a valid regular expression. + + + 1454 + + + 1470 + + + + + + + + + + Copying files to workspace + + + 51 + + + + &Stop + + + 52 + + + + files + + + 90 + + + + file + + + 92 + + + + (%1 of %2 selected) + + + 94 + + + + Copying %1 %2 to workspace %3 + + + 97 + + + + Get Revision Progress + + + 42 + + + + + + + + + + updating... + + + 85 + + + + + + + + + + Filter Options + + + 31 + + + + Apply the following settings to the file filter tree + + + 47 + + + + Show files that are specified in the view below: + + + 49 + + + + &Clear View + + + 65 + + + + &View of Current Graph + + + 68 + + + + Hide &deleted files + + + 79 + + + + Hide files with no &edits + + + 82 + + + + Hide files not mapped in &workspace view + + + 85 + + + + Hide files not &modified + + + 92 + + + + since date + + + 100 + + + + since changelist + + + 101 + + + + in the last week + + + 102 + + + + in the last month + + + 103 + + + + in the last six months + + + 104 + + + + in the last year + + + 105 + + + + &Set As Default + + + 153 + + + + &Filter + + + 158 + + + + Cancel + + + 159 + + + + Select Date + + + 206 + + + + Do you want the current settings to be +remembered as the default filter? + +The default filter will be applied to +Revision Graph on startup. + + + 229 + + + + + + + + + + Building filter tree... + + + 368 + + + + Cancel + + + 369 + + + + + + + + + + File Filter Tree + + + 46 + + + + Filter Options... + + + 69 + + + + + + + + + + Find File + + + 38 + + + + All or part of the &file name: + + + 19 + + + + &Previous + + + 41 + + + + &Next + + + 43 + + + + Highlight Files by Name + + + 50 + + + + &OK + + + 52 + + + + &Cancel + + + 53 + + + + + + + + + + Could not load OpenGL libraries. +Double check that OpenGL libraries are installed on your +system and that all appropriate drivers are updated. +See your system administrator if you require assistance. + + + + 887 + + + + Could not create an OpenGL rendering context. +Double check that your windowing system supports OpenGL +graphics and that all appropriate options are enabled. +See your system administrator if you require assistance. + + + + 894 + + + + This build does not support OpenGL. + + + + 901 + + + + + + + + + + Generating graph... + + + 68 + + + + Cancel + + + 69 + + + + You are in a twisty maze of passages, all alike. + + + 674 + + + + Too much junk! + + + 721 + + + + More than 32 edits selected. Remaining lines omitted. + + + 722 + + + + Remote depot + + + 1014 + + + + Spec depot + + + 1017 + + + + Unsubmitted changelist + + + 1020 + + + + Changelist %1 + + + 1023 + + + + remote + + + 1246 + + + + spec + + + 1250 + + + + pending + + + 1254 + + + + Times + + + 1317 + + + font for revision graph ellipsis + + + + No revisions to graph. + + + 1381 + + + + Save Graph As + + + 1557 + + + + Portable Document Format (*.pdf);;PostScript (*.ps) + + + 1558 + + + + ;;PNG Image (*.png);;JPEG Image (*.jpg) + + + 1559 + + + + ;;SVG Image (*.svg) + + + 1561 + + + + Unable to save file. + + + 1610 + + + 1632 + + + 1649 + + + + The graph is too large to save as a raster image. + + + 1625 + + + + Unknown file format. + + + 1655 + + + + %1 Error + + + 1661 + + + + + + + + + + Find changes made by &user: + + + 1304 + + + + &Search only in differences + + + 1305 + + + + + + Loading Time-lapse View data... + + + 61 + + + + Mode: + + + 272 + + + + Content range: + + + 273 + + + + Scale: + + + 274 + + + + Refresh + + + 282 + + + + Whitespace option + + + 319 + + + + Single revision + + + 325 + + + + Incremental diffs + + + 326 + + + + Multiple revisions + + + 327 + + + + About + + + 416 + + + + About Time-lapse View + + + 420 + + + + Help + + + 408 + + + + Original + + + 95 + + + + Current + + + 96 + + + + Unable to refresh Time-lapse View. + + + 518 + + + + Unable to open Time-lapse View. + + + 519 + + + + Slider revision: + + + 982 + + + + Starting revision: + + + 983 + + + + Ending revision: + + + 984 + + + + Selection added: + + + 985 + + + + Selection deleted: + + + 986 + + + + + + + + + + to + + + 22 + + + + + + + + + + Least recent + + + 566 + + + + Most recent + + + 567 + + + + 0 to 0 + + + 639 + + + + %1 to %2 + + + 641 + + + + + + + + + + Add + + + 44 + + + + Edit + + + 58 + + + + Delete + + + 72 + + + + Branch + + + 100 + + + + Add (branch w/ edit) + + + 128 + + + + Copy + + + 156 + + + + Merge + + + 193 + + + + Merge w/ Edit + + + 228 + + + + Ignore + + + 263 + + + + + + + + + + The first revision of an independently added (not branched) file. + + + 63 + + + + The first revision of a file that was directly branched from another file. + + + 65 + + + + A resolve in which the source completely overwrote the target. + + + 67 + + + + The last revision of a deleted file. + + + 69 + + + + A branch whose contents were edited before submit. + + + 71 + + + + An edit to an existing file. + + + 73 + + + + A resolve in which no changes were propagated from source to target. + + + 75 + + + + A resolve that required manual intervention, or whose results were edited before submit. + + + 77 + + + + A resolve completed via a purely automatic merge. + + + 79 + + + + + + + + + + Navigator + + + 223 + + + + Legend + + + 228 + + + + Clear filters (show all files) + + + 340 + + + + Diff selected revisions + + + 312 + + + + Diff Against Previous Revision + + + 423 + + + + Toolbar + + + 446 + + + + Sort &By + + + 606 + + + + &File Filter Tree + + + 627 + + + + Sho&w/Hide + + + 632 + + + + &Toolbar + + + 638 + + + + Loading workspace view... + + + 958 + + + + Cancel + + + 959 + + + + + + + + + + Select an argument to use with the application: + + + 20 + + + + Uppercase '%' specifiers send values to the application as a group +Lowercase '%' specifiers run the application once for each value + + + 30 + + + + OK + + + 32 + + + + Cancel + + + 33 + + + + Select Argument + + + 46 + + + + Argument + + + 53 + + + + Description + + + 54 + + + + %a + + + 57 + + + + Selected label + + + 58 + + + + %b + + + 61 + + + + Selected branch + + + 62 + + + + %C + + + 65 + + + + Selected changelists + + + 66 + + + 70 + + + + %c + + + 69 + + + + %D + + + 73 + + + + Selected files or folders (depot syntax used for depot files) + + + 74 + + + 78 + + + + %d + + + 77 + + + + %F + + + 81 + + + + Selected files (workspace syntax used for depot files) + + + 82 + + + 86 + + + + %f + + + 85 + + + + %i + + + 89 + + + + Selected workspace + + + 90 + + + + %J + + + 93 + + + + Selected jobs + + + 94 + + + 98 + + + + %j + + + 97 + + + + %O + + + + 101 + + + + Selected checked out files (when left pane has focus) +All checked out files (when right pane has focus) + + + 102 + + + 106 + + + + %o + + + + 105 + + + + %P + + + 109 + + + + Selected pending changelists + + + 110 + + + 114 + + + + %p + + + 113 + + + + %s + + + 117 + + + + Selected submitted changelists + + + 118 + + + 122 + + + + %S + + + 121 + + + + %t + + + 125 + + + + Selected stream + + + 126 + + + + %u + + + 129 + + + + Selected user + + + 130 + + + + $c + + + 133 + + + + Current workspace + + + 134 + + + + $p + + + 137 + + + + Current port + + + 138 + + + + $r + + + 141 + + + + Current workspace root + + + 142 + + + + $u + + + 145 + + + + Current user + + + 146 + + + + $D + + + 149 + + + + Arguments from prompt dialog + + + 150 + + + + $% + + + 153 + + + + Literal '%' character + + + 154 + + + + $$ + + + 157 + + + + Literal '$' character + + + 158 + + + + + + + + + + ToolDetail + + + 32 + + + + Associated application: + + + 61 + + + + Include folder in context menus + + + 170 + + + + Include folder in conte&xt menus + + + 171 + + + + Add to applicable context menus + + + 174 + + + + Add to applicable conte&xt menus + + + 175 + + + + Application: + + + 180 + + + + &Application: + + + 180 + + + + &Browse... + + + 182 + + + + Arguments: + + + 185 + + + + Ar&guments: + + + 185 + + + + Selec&t... + + + 187 + + + + Start In: + + + 190 + + + + &Start In: + + + 190 + + + + Bro&wse... + + + 192 + + + + Run tool in terminal window + + + 195 + + + + Run t&ool in terminal window + + + 195 + + + + Close window upon completion + + + 198 + + + + Close window &upon completion + + + 199 + + + + Prompt user for arguments + + + 202 + + + + &Prompt user for arguments + + + 203 + + + + Add file browser to prompt dialog + + + 206 + + + + Add &file browser to prompt dialog + + + 207 + + + + Refresh %1 upon completion + + + 210 + + + + &Refresh %1 upon completion + + + 211 + + + + &Ignore P4CONFIG files + + + 213 + + + + Description: + + + 216 + + + + &Description: + + + 216 + + + + Executable files (*.exe *.com *.bat);;All Files (*.*) + + + 421 + + + + All Files (*) + + + 423 + + + + Choose Application + + + 427 + + + + Select Start location + + + 456 + + + + + + + + + + %1 Console + + + 21 + + + + &Reply + + + 63 + + + + + + + + + + Import Preview + + + 30 + + + + Export Preview + + + 32 + + + + &Select the tools to import: + + + 39 + + + + &Select the tools to export: + + + 41 + + + + Select &All + + + 65 + + + + Select &None + + + 73 + + + + &Import + + + 82 + + + + &Export... + + + 84 + + + + Cancel + + + 87 + + + + Application: + + + 147 + + + + Arguments: + + + 156 + + + + + + + + + + Arguments: + + + 20 + + + + &Browse + + + 42 + + + + OK + + + 46 + + + + Cancel + + + 48 + + + + All files (*.*) + + + 104 + + + + All files (*) + + + 106 + + + + + + + + + + Invalid file path: %1 + + + 177 + + + 187 + + + + Invalid file revision path: %1 + + + 217 + + + 224 + + + + Invalid folder path: %1 + + + 255 + + + + Files in selected &folder + + + 264 + + + + Files in selected folder and its &subfolders (recursive) + + + 265 + + + + Choose which files to display in the graph: + + + 270 + + + + + + + + + + About %1 + + + 24 + + + + <h2>The %1</h2>Copyright &#169; 2002-%2 <A href>Perforce Software</A>.<br>All rights reserved.<hr width=10000>%3 <br> + + + 33 + + + + Rev. %1 + + + 57 + + + + + + + + + + Add Files to Workspace + + + 30 + + + + Do you want to copy files from the server to your workspace? + + + 39 + + + + &Yes, I would like to copy files to my workspace + + + 51 + + + + N&o, I will copy files to my workspace later + + + 52 + + + + Yes, I would like to copy files to my workspace + + + 56 + + + + No, I will copy files to my workspace later + + + 57 + + + + Check the folders and files to copy: + + + 60 + + + + Number of files to copy: + + + 80 + + + + + + + + + + Add Files Wizard + + + 77 + + + + Add Files Assistant + + + 81 + + + + Are you sure you want to cancel the %1? + + + 397 + + + + assistant + + + 398 + + + + wizard + + + 398 + + + + + + + + + + The path %1 + could not be found. + + + 180 + + + + + + + + + + Depot: + + + 112 + + + + &Owner: + + + 131 + + + + &Description: + + + 140 + + + + Depot type: + + + 151 + + + 159 + + + + Date modified: + + + 121 + + + + &local (a depot that resides on the current Perforce server) + + + 163 + + + + s&tream (a local depot that stores stream files) + + + 173 + + + + &spec (a local depot that stores changes to specifications) + + + 179 + + + + &remote (read-only access to files on a different Perforce server) + + + 183 + + + + archi&ve (a depot used to store obsolete revisions) + + + 188 + + + + &unload (where database records can be unloaded) + + + 196 + + + + (host:port) + + + 221 + + + + R&emote Server: + + + 223 + + + + Map: + + + 237 + + + + S&pecification mapping: + + + 251 + + + + File e&xtension: + + + 266 + + + + (appended to versioned specifications) + + + 268 + + + + <b>Note:</b> Remote depots are best used for integrating code drops from a remote depot to a local depot. For best performance, do not use remote depots for routine, daily work. + + + 282 + + + + <b>Note:</b> By default, the storage location is relative to the server root. Use an absolute path to store files in a location that is not under the server root. + + + 294 + + + + Storage lo&cation for versioned files: + + + 423 + + + + local (a depot that resides on the current Perforce server) + + + 439 + + + + stream (a local depot that stores stream files) + + + 444 + + + + archive (a depot used to store obsolete revisions) + + + 449 + + + + spec (a local depot that stores changes to specifications) + + + 454 + + + + Storage lo&cation for versioned specifications: + + + 456 + + + + remote (read-only access to files on a different Perforce server) + + + 468 + + + + &Mapped portion of remote server: + + + 470 + + + + unload (where database records can be unloaded) + + + 481 + + + + You must manually copy all versioned files to the new location on the server machine prior to changing the location in the depot specification. + +Proceed with changing the depot location? + + + + 590 + + + + The changes were not saved. Check for invalid form values. + + + + 686 + + + + Do you want to populate the spec depot? + +The current versions of all branch, depot, group, job, label, user and workspace specifications will be copied to the depot. + + + 709 + + + + + + + + + + Member + + + 379 + + + + Gr&oups: + + + 402 + + + 417 + + + + General + + + 481 + + + 809 + + + + Owner + + + 381 + + + + Type: + + + 173 + + + + &standard + + + 185 + + + + opera&tor (restricted to server management) + + + 192 + + + + servi&ce (used only for server-to-server process authentication) + + + 197 + + + + Note: Owners can edit the group's settings + + + 422 + + + + Form + + + 446 + + + + Reviews + + + 458 + + + + form + + + 566 + + + + &Group: + + + 647 + + + + A&dd + + + 654 + + + + &Browse... + + + 662 + + + + &Remove + + + 669 + + + + You do not have permission to add users to group '%1'. + + + 856 + + + + The group '%1' does not exist. + +Do you want to create a group with this name? + + + + 879 + + + + Select Groups + + + 915 + + + + You do not have permission to remove users from group '%1'. + + + 977 + + + + User '%1' already exists. + + + + 1079 + + + + Your password entries do not match. +Please re-enter your password. + + + 1100 + + + + Password changed: + + + 1530 + + + + &User: + + + 119 + + + 1507 + + + + &Full name: + + + 145 + + + 1512 + + + + &Job view: + + + 213 + + + 1516 + + + + &Password: + + + 156 + + + 1518 + + + + &Email: + + + 132 + + + 1524 + + + + Re&views: + + + 225 + + + 1526 + + + + &Type: + + + 1532 + + + + Last accessed: + + + 1510 + + + + Date modified: + + + 1514 + + + + (repeat password to confirm) + + + 168 + + + 1528 + + + + + + + + + + Select User + + + 40 + + + + User '%1' does not have permission to browse users. + + + 152 + + + + + + + + + + the Explorer + + + 305 + + + + any open file browser window + + + 307 + + + + the Finder + + + 309 + + + + Add Files to Server + + + 327 + + + + The Perforce server you selected is empty. + + + 335 + + + + Click the "Browse" button to browse the local file system for folders you want to add. +You can also drag files from %1. + + + 336 + + + + Bro&wse + + + 337 + + + + &Remove + + + 338 + + + + Ren&ame + + + 339 + + + + N&ew Folder + + + 340 + + + + Number of files to copy: + + + 341 + + + + Size of files to copy:: + + + 342 + + + + Folder %1 already contains a folder called %2. + Would you like to replace the existing folder? + + + 426 + + + + Folder %1 already contains a file called %2. + Would you like to replace the existing file? + + + 431 + + + + Choose Folder to Add + + + 558 + + + + New Folder + + + 595 + + + + mainline "%1" + + + 664 + + + + Choose Files to Add + + + 665 + + + + server + + + 682 + + + + The Perforce %1 you selected is empty. + + + 689 + + + + + + + + + + Choose Stream + + + 120 + + + + Do you want to use a stream for your workspace? + + + 128 + + + + No, I do not want to use a stream + + + 129 + + + + Yes, I know which stream I want to use + + + 130 + + + + Select the stream to link to your workspace: + + + 131 + + + + Check the stream folders and files to copy to your workspace: + + + 132 + + + + Number of files to copy: + + + 133 + + + + A stream is a predefined collection of files on a Perforce server. <a href="moreInfo">More Info</a> + + + 139 + + + + Streams are groups of related files, like branches and codelines, with additional properties. Most notably, streams are defined hierarchically using the mainline model, and Perforce generates the views for workspaces that are associated with a stream. Among the advantages of this approach are (1) change is propagated in a controlled way through the hierarchy that you define, and (2) you can compose the contents of a stream by defining its view, thereby providing all users who work in the stream with a consistent view of its contents. + + + 237 + + + + For further information, see the + + + 247 + + + + %1 online help + + + 249 + + + + OK + + + 259 + + + + + Number of files to copy: %n + Number of files to copy: %n + + + Number of files to copy: %n + Number of files to copy: %n + + + 330 + + + + + + + + + + Select folder to view + + + 60 + + + + + + All Depots + + + 304 + + + + Entire Workspace + + + 310 + + + + Select folder to search in + + + 314 + + + + + + + + + + Enter revision number + + + 467 + + + + &Browse... + + + 58 + + + + Changelist + + + 520 + + + + Workspace + + + 563 + + + + Date/Time + + + 594 + + + + Label + + + 636 + + + + Have + + + 653 + + + + Shelved + + + 709 + + + + + + + + + + Open Connection + + + 144 + + + + Type in connection settings or choose a recent or favorite connection: + + + 158 + + + + &Server: + + + 161 + + + + server:port + + + 164 + + + + &Browse... + + + 167 + + + + Locate DVCS root folder to use. + + + 168 + + + + &Connections + + + 170 + + + + Type in connection settings: + + + 190 + + + + No favorite connections + + + 195 + + + + &User: + + + 199 + + + + Br&owse... + + + 204 + + + + &New... + + + 206 + + + + Create a new user + + + 207 + + + + &Workspace: + + + 210 + + + + (optional) + + + 214 + + + + B&rowse... + + + 217 + + + + N&ew... + + + 219 + + + + Create a new workspace + + + 220 + + + + Select DVCS root folder + + + 801 + + + + Show &dialog at startup + + + 225 + + + + &Help + + + 237 + + + + Version: %1 + + + 249 + + + + Trying to connect to the server on '%1'... + + + 451 + + + + '%1' is a reserved %2 workspace name, and considered invalid by %3. + + + 480 + + + + '%1' is a service user and cannot be used to log into %2. + + + 556 + + + + Unable to connect to the server %1 as user '%2' + +Would you like to run %3 without a connection? + + + 652 + + + + Charset must be selected for Unicode server. + + + 607 + + + + %1 - no such user. + + + 610 + + + + A password is required for this user. +Missing or invalid password. + + + 613 + + + + The security level of this server requires the password to be reset. + + + 630 + + + + %1 - no such client. + + + 636 + + + + There isn't a valid DVCS repo at the location specified. + + + 671 + + + + A character encoding must be selected to connect to this server. + + + 788 + + + + + + + + + + Are you sure you want to cancel the assistant? + + + 126 + + + + Are you sure you want to cancel the wizard? + + + 127 + + + + + + + + + + System Info + + + 29 + + + + Jobs column + + + 107 + + + + Disabled features: + + + 110 + + + + Server refresh interval: %1 minute(s) %2 + + + 114 + + + + Maximum files displayed per changelists: %1 %2 + + + 118 + + + + Maximum file preview size: %1 k-byte(s) %2 + + + 121 + + + + Entries fetched at a time: %1 %2 + + + 125 + + + + Connection: + + + 143 + + + + (pushed from the server) + + + 165 + + + + Application: + + + 144 + + + + Perforce Applet Configuration: + + + 145 + + + + Swarm Configuration: + + + 146 + + + + Computer: + + + 147 + + + + Integration + + + 172 + + + + Labeling + + + 174 + + + + Revision Graph + + + 180 + + + + Time-lapse View + + + 182 + + + + Custom Tools + + + 184 + + + + Administration + + + 186 + + + + Connection Wizard + + + 188 + + + + Dashboard + + + 190 + + + + + + + + + + Diff + + + 192 + + + + 1st Path: + + + 203 + + + + 1 + + + 204 + + + + &Browse... + + + 205 + + + + &Workspace version on local disk + + + 206 + + + + &Latest revision + + + 207 + + + + &Have revision + + + 208 + + + + &Specify revision + + + 209 + + + + Br&owse... + + + 210 + + + + 2nd Path: + + + 212 + + + + 2 + + + 213 + + + + B&rowse... + + + 214 + + + + Wor&kspace version on local disk + + + 215 + + + + La&test revision + + + 216 + + + + Ha&ve revision + + + 217 + + + + Spe&cify revision + + + 218 + + + + Brows&e... + + + 219 + + + + &Diff + + + 243 + + + + Cancel + + + 244 + + + + + + %1 (last revision retrieved from depot) + + + 904 + + + 1086 + + + 1172 + + + 1303 + + + 1443 + + + + %1 (#%2 - last revision retrieved from depot) + + + 1098 + + + + %1 deleted at (#%2) + + + 1105 + + + 1147 + + + + %1 (#%2) + + + 1111 + + + 1155 + + + 1310 + + + + Have revision (last revision retrieved from depot) + + + 1139 + + + + + + + + + + Select a Folder or File + + + 147 + + + + &Depot + + + 162 + + + + &Workspace + + + 178 + + + + Cancel + + + 182 + + + + OK + + + 186 + + + + + + Browse for Folder + + + 282 + + + + Browse for Files/Folder + + + 284 + + + + Cancel + + + 291 + + + + OK + + + 295 + + + + + + + + + + No Files Found + + + 168 + + + + Find Files - %1 (%2) + + + 281 + + + + Save Search... + + + 505 + + + + Manage Searches... + + + 508 + + + + and + + + 592 + + + + or + + + 595 + + + 637 + + + 639 + + + + and + + + 617 + + + 619 + + + 649 + + + 651 + + + + "%1" + + + 708 + + + + Changelist + + + 816 + + + + Date + + + 821 + + + + &Clear Search + + + 992 + + + + &Save Search... + + + 998 + + + + &Manage Searches... + + + 1004 + + + + &Refresh Search + + + 1010 + + + + + + + + + + Close + + + 36 + + + + File Information + + + 69 + + + + + + + + + + Change Filetype + + + 67 + + + + C&hange base filetype and/or file attributes + + + 114 + + + + &Add additional file attributes + + + 115 + + + + Workspace related attributes: + + + 117 + + + + +&m Preserve local modification times + + + 118 + + + + +&w Always writable in workspace + + + 119 + + + + +&x Exec bit set in workspace + + + 120 + + + + +&k RCS keyword expansion + + + 121 + + + + Expansion of Id, Header, Author, Date, DateTime, Change, File, Revision + + + 122 + + + + +ko &RCS keyword expansion of $Id$, $Header$ only + + + 123 + + + + +C Server stores compressed file per re&vision (binary default) + + + 127 + + + + +&l Exclusive checkout (don't allow concurrent checkouts) + + + 124 + + + + Server storage method: + + + 126 + + + + +&D Server stores deltas in RCS format (text default) + + + 128 + + + + +&F Server stores full file per revision + + + 129 + + + + +&S Server limits the number of revisions stored to: + + + 131 + + + + &Base Filetype: + + + 154 + + + + Add files to &pending changelist: + + + 224 + + + + S&erver stores revisions using default method for base file type + + + 387 + + + + Do not chang&e how server stores file contents + + + 394 + + + + Change filetype + + + 407 + + + + + + + + + + Show files in a list or as thumbnails + + + 487 + + + + Tear off this folder view + + + 512 + + + + Folder: + + + 550 + + + + %1 (%2) + + + 606 + + + + %1 - %2 (%3) + + + 614 + + + + Sort By + + + 1261 + + + + Show Files As + + + 1266 + + + + + + + + + + %1 will now copy any server files you chose to your workspace and bring up a connection window with the following settings: + + + 30 + + + + %1 will now bring up a connection window with the following settings: + + + 36 + + + + + + + + + + Dock window + + + 31 + + + + + + + + + + form + + + 122 + + + + + + + + + + Cancel + + + 90 + + + 272 + + + + OK + + + 91 + + + 273 + + + + Go To Spec + + + 218 + + + + Changelist + + + 223 + + + 429 + + + + Enter Number + + + 268 + + + + Enter Name + + + 269 + + + + Exact Match + + + 270 + + + + Enter Stream Name + + + 271 + + + + %1 cannot be accessed because it has been unloaded. + + + 455 + + + + %1 does not exist. + + + 433 + + + + %1: '%2' + + + 434 + + + + + + + + + + &Group: + + + 185 + + + + &Subgroups: + + + 224 + + + + &Users: + + + 195 + + + + &Maximum number of results returned per server request: + + + 268 + + + + Ma&ximum number of database rows scanned per server request: + + + 288 + + + + Maximum loc&k time of database tables per server request (msec): + + + 308 + + + + form + + + 497 + + + + days + + + 518 + + + 538 + + + + Su&bgroup: + + + 641 + + + + &Add + + + 648 + + + + Bro&wse... + + + 658 + + + + &Remove + + + 665 + + + + Member + + + 702 + + + + Note: Owners can edit the group's settings + + + 205 + + + + Duration be&fore login session times out: + + + 322 + + + + Duration before &password expires: + + + 363 + + + + Appl&y + + + 410 + + + + Owner + + + 704 + + + + Us&er: + + + 759 + + + + A&dd + + + 766 + + + + Br&owse... + + + 776 + + + + Remo&ve + + + 783 + + + + Duration be&fore login session times out (days - hh:mm:ss): + + + 879 + + + + Duration before &password expires (days - hh:mm:ss): + + + 886 + + + + The group '%1' cannot be added as a subgroup +because it would cause a recursive relationship. + + + + 1056 + + + + 1 day, + + + 928 + + + 944 + + + + %1 days, + + + 928 + + + 944 + + + + hrs + + + 929 + + + 945 + + + + The changes were not saved. Check for invalid form values. + + + + 981 + + + + Select Subgroups + + + 1086 + + + + Select Users + + + 1142 + + + + The group must contain at least one user +or one subgroup. + + + 1398 + + + + Group '%1' already exists. + + + + 1411 + + + + Invalid ID + + + 1441 + + + + A valid Group name is required to save the form. + + + 1442 + + + + Error in group specification. Invalid MaxResults: +'%1' + + + + 1518 + + + + Error in group specification. Invalid MaxScanRows: +'%1' + + + + 1534 + + + + Error in group specification. Invalid MaxLockTime: +'%1' + + + + 1550 + + + + + + + + + + &Changelist: + + + 314 + + + + &Add + + + 320 + + + + B&rowse... + + + 328 + + + + form + + + 350 + + + + + + + + + + UILabelSyncPreviewWindow + + + 102 + + + + Files + + + 103 + + + + Cancel + + + 104 + + + 133 + + + + Label Checked Files + + + 105 + + + + Label: + + + 130 + + + + No Files. + + + 145 + + + + Label in sync + + + 202 + + + + + + + + + + Label '%1' is an automatic label. +Files cannot be associated with this type of label. + + + 57 + + + + Label Files + + + 83 + + + 323 + + + + View: + + + 146 + + + + Cancel + + + 149 + + + + Lab&el: + + + 92 + + + + Br&owse... + + + 97 + + + + A&t latest revision + + + 122 + + + + Action: + + + 142 + + + + Appl&y label to file revisions + + + 143 + + + + Re&move label from file revisions + + + 144 + + + + E&xclude deleted revisions + + + 145 + + + + Files/Folders: + + + 147 + + + + &Preview + + + 148 + + + + &Label + + + 150 + + + + This label doesn't exist. Create it? + + + 263 + + + + Invalid Label + + + 410 + + + + You must be the owner of the label to use it. + + + 411 + + + + <No view defined for this label.> + + + 512 + + + + + + + + + + %1: + + + 64 + + + + %1 does not exist. + + + 179 + + + + + + + + + + Log + + + 45 + + + + + + + + + + New Workspace + + + 20 + + + + &Save + + + 34 + + + + &Help + + + 35 + + + + + + + + + + Move + + + 35 + + + + Files/Folders: + + + 45 + + + + Move to: + + + 55 + + + + Location: + + + 59 + + + + N&ew location: + + + 70 + + + + &Browse... + + + 74 + + + + &Add files to pending changelist: + + + 83 + + + + Sub&mit... + + + 98 + + + + Cancel + + + 101 + + + + &Save to Changelist + + + 103 + + + + The following files are deleted from the depot: + + + 179 + + + + + + + + + + A password is not required. + + + 28 + + + + This server does not permit new users to create their own accounts. + + + 34 + + + + User '%1' already exists. + + + 40 + + + + &User name: + + + 74 + + + + Name used for log in (no spaces allowed) + + + 78 + + + + &Full name: + + + 87 + + + + First and last name + + + 90 + + + + &Password*: + + + + 105 + + + + Enter password twice + + + 108 + + + + &Email: + + + 122 + + + + &Save + + + 132 + + + + &Help + + + 139 + + + + New User (%1) + + + 203 + + + + + + + + + + R&estrict access to changelist + + + 162 + + + + <a href>(more info)</a> + + + 164 + + + + Access type: + + + 134 + + + + &Description: + + + 188 + + + + &Files + + + 197 + + + + &Shelved Files + + + 214 + + + + &Unshelve + + + 219 + + + + Changelist: + + + 237 + + + + Date: + + + 238 + + + + Workspace: + + + 241 + + + + User: + + + 242 + + + + A&pply + + + 331 + + + + &Jobs + + + 616 + + + + Jo&b: + + + 696 + + + + &Add + + + 703 + + + + B&rowse... + + + 712 + + + + %1 does not exist. + + + 923 + + + + restricted + + + 1249 + + + + public + + + 1249 + + + + + + + + + + &Apply + + + 83 + + + + &Help + + + 85 + + + + Preferences + + + 46 + + + + + + + + + + Welcome to the Connection Setup Wizard + + + 34 + + + + Welcome to the Connection Setup Assistant + + + 36 + + + + This wizard helps you set up a connection with a Perforce Server. + + + 51 + + + + This assistant helps you set up a connection with a Perforce Server. + + + 56 + + + + Type in the server connection settings: + + + 60 + + + 84 + + + + &Type in the server connection settings: + + + 77 + + + + &Host: + + + 78 + + + + &Port number: + + + 79 + + + + Host: + + + 85 + + + + Port number: + + + 86 + + + + The server stores files managed by Perforce. + + + 107 + + + + More Info + + + 109 + + + + A Perforce server stores files so that multiple users can access them. You can copy files from the server to a workspace on your computer. You can also send files from your workspace to the server, where they can be retreived by other users. + + + + 218 + + + + For further information, see the + + + 223 + + + + %1 online help + + + 225 + + + + OK + + + 235 + + + + + + + + + + None + + + 180 + + + 198 + + + + (in use) + + + 182 + + + 202 + + + + The shortcut is already in use for the %1 "%2". + +Reassign %3 shortcut key? + + + 217 + + + + + + + + + + Showing only workspaces available for use on this computer. + + + 208 + + + + Choose a stream from the list below to merge to the target stream: + + + 231 + + + + <a href>Display all streams</a> if you need to bypass the configured flow of change and merge from another stream. + + + 235 + + + + &New %1... + + + 621 + + + + Switch + + + 663 + + + + Merge + + + 665 + + + + Copy + + + 667 + + + + Your current workspace is not associated with the following stream. Switch to another workspace to complete the %1. + + + 670 + + + + %1 to stream: + + + 672 + + + + + + + + + + Cancel + + + 205 + + + + OK + + + 206 + + + + New Workspace + + + 226 + + + + Automatically get the latest revisions after switching your workspace + + + 221 + + + + Select Submitted Changelist + + + 461 + + + + Select Pending Changelist + + + 463 + + + + Select User + + + 465 + + + + Select Workspace + + + 467 + + + + Select Stream + + + 469 + + + + Select Mapping + + + 471 + + + + Select Label + + + 473 + + + + Select Jobs + + + 475 + + + + Select Group + + + 477 + + + + Select Depot + + + 479 + + + + Select Pending Shelved Changelist + + + 481 + + + + Generic Spec Picker + + + 483 + + + + + + + + + + &Print... + + + 213 + + + + &Back Out + + + 219 + + + + &Edit + + + 228 + + + + Close + + + 252 + + + + &Apply + + + 321 + + + + Save as &Numbered Changelist + + + 329 + + + + The + + + 642 + + + + , ' + + + 644 + + + + ' has been unloaded. + + + 646 + + + + %1 is unable to perform the requested operation without a server connection. + + + 731 + + + + Do you want to save changes to %1? + + + 851 + + + + &Submit + + + 312 + + + + &Save + + + 862 + + + + You are about to edit a locked %1 owned by another user. +Are you sure you want to continue? + + + 80 + + + + New + + + 835 + + + + Do you want to save your new change as a numbered change? + + + 846 + + + + Do you want to save your default change as a numbered change? + + + 848 + + + + &Don't Save + + + 863 + + + + + + + + + + Tear off this view + + + 280 + + + + Show/hide filtering + + + 120 + + + + Show/hide navigator + + + 128 + + + + Unloaded... + + + 147 + + + + Show streams in a graph, list or tree + + + 255 + + + + none + + + 361 + + + + Depot + + + 362 + + + + is + + + 362 + + + + %1 (%2) + + + 494 + + + + %1 - %2 (%3) + + + 502 + + + + + + + + + + Options: + + + 94 + + + + Get Revision + + + 71 + + + + Get or replace the following files/folders: + + + 111 + + + + &Force Operation (replace file even if you already have the revision specified) + + + 120 + + + + Only get revisions for files listed in c&hangelist + + + 122 + + + + Re&move files from workspace if they are not in label + + + 123 + + + + Get &latest revision + + + 99 + + + + Preview: + + + 115 + + + + Safe &Update: Don't overwrite files that were changed without being checked out. + + + 121 + + + + &Preview + + + 210 + + + + &Hide Preview + + + 211 + + + + &Get Revision + + + 212 + + + + Cancel + + + 213 + + + + Please enter a revision specifier. + + + 314 + + + + The revision specified does not exist + + + 329 + + + + %1#%2 - deleted as %3 + + + 393 + + + %1#%2=file#revision_number, %3=file + + + + %1#%2 - added as %3 + + + 402 + + + %1#%2=file#revision_number, %3=file + + + + %1#%2 + + + 411 + + + %1#%2=file#revision_number + + + + %1 - not updated (marked for delete) + + + 426 + + + + %1 - not updated (checked out) + + + 429 + + + + %1 - not updated (safe sync used and modified) + + + 432 + + + + Files up-to-date + + + 450 + + + + + + + + + + Pending + + + 29 + + + + Submitted + + + 36 + + + + Shelved + + + 41 + + + + Branches + + + 45 + + + + History + + + 55 + + + + Depot + + + 59 + + + + Files + + + 63 + + + + Files in Folder + + + 65 + + + + Find File + + + 68 + + + + Perforce Applet + + + 70 + + + + Log + + + 72 + + + + Dashboard + + + 74 + + + + %1 (%2) + + + 292 + + + + + + + + + + Change the sort order of files + + + 62 + + + + Depot + + + 95 + + + + Filter depot + + + 99 + + + + Filter workspace + + + 104 + + + + + + + + + + No Items Found + + + 292 + + + + Tasks: + + + 335 + + + + No tasks available + + + 361 + + + + Files in my workspace are in pending changelists + + + 492 + + + + Files in my workspace need to be resolved + + + 500 + + + + Jobs on the server are in my Job View + + + 507 + + + + In order to use this feature, you must be connected to a 2009.2 or later server. + + + 640 + + + + + More than <a href="jobs">%n job(s)</a> are in your <a href="jobview">job view</a> + + + + More than <a href="jobs">%n job(s)</a> are in your <a href="jobview">job view</a> + + + + 1004 + + + + + <a href="jobs">%n job(s)</a> are in your <a href="jobview">job view</a> + + + + <a href="jobs">%n job(s)</a> are in your <a href="jobview">job view</a> + + + + 1006 + + + + is having trouble translating the location "%1" to a valid workspace location. To filter using this location, drag the folder from the workspace tree to the workspace folder field or include the depot location in your workspace view. + + + 1377 + + + + Edit settings + + + 1961 + + + + + Choose a folder with fewer files in order to see current tasks and status. The selected Workspace folder has more than %n depot files. + Choose a folder with fewer files in order to see current tasks and status. The selected Workspace folder has more than %n depot files. + + + Choose a folder with fewer files in order to see current tasks and status. The selected Workspace folder has more than %n depot files. + Choose a folder with fewer files in order to see current tasks and status. The selected Workspace folder has more than %n depot files. + + + 1617 + + + + + <a href="test">Get latest revisions (%n file(s))</a> + <a href="test">Get latest revisions (%n files)</a> + + + <a href="test">Get latest revisions (%n file(s))</a> + <a href="test">Get latest revisions (%n files)</a> + + + 989 + + + + + Merge from %1 (%n change(s)) + Merge from %1 (%n changes) + + + Merge from %1 (%n change(s)) + Merge from %1 (%n changes) + + + 1031 + + + + + Merge to %1 (%n change(s)) + Merge to %1 (%n changes) + + + Merge to %1 (%n change(s)) + Merge to %1 (%n changes) + + + 1033 + + + + + Copy to %1 (%n change(s)) + Copy to %1 (%n changes) + + + Copy to %1 (%n change(s)) + Copy to %1 (%n changes) + + + 1064 + + + + + Copy from %1 (%n change(s)) + Copy from %1 (%n changes) + + + Copy from %1 (%n change(s)) + Copy from %1 (%n changes) + + + 1066 + + + + <br><a href="merge">merge</a> required prior to copy + + + 1074 + + + + + <a href>View %n pending changelist(s) + <a href>View %n pending changelists + + + <a href>View %n pending changelist(s) + <a href>View %n pending changelists + + + 1147 + + + + + (%n file(s))</a> + (%n files)</a> + + + (%n file(s))</a> + (%n files)</a> + + + 1148 + + + + <a href>Select workspace</a> + + + 456 + + + + Workspace folder: + + + 260 + + + + Dashboard last updated: + + + 320 + + + + <a href>Update now</a> + + + 326 + + + + Folder status: + + + 379 + + + 586 + + + + Display a task when: + + + 417 + + + + Check for updates: + + + 419 + + + + Manual user refresh + + + 421 + + + + Every + + + 422 + + + + minutes + + + 433 + + + + No workspace is selected + + + 455 + + + + My stream or its parent may receive merges + + + 463 + + + + My stream or its parent may be copied + + + 471 + + + + Files in my workspace are not at the latest revisions + + + 484 + + + + Show changelists when the folder is out of date + + + 588 + + + + Display the number of files with potential conflicts + + + 591 + + + + + <a href>Resolve conflicts (%n file(s))</a> + <a href>Resolve conflicts (%n files)</a> + + + <a href>Resolve conflicts (%n file(s))</a> + <a href>Resolve conflicts (%n files)</a> + + + 1187 + + + + Changelists from remote depots cannot be shown + + + 1468 + + + + All files are at the latest revision. + + + 1474 + + + + Not updated with the following changelists (%1) + + + 1478 + + + + + + + + + + OK + + + 22 + + + + UITextWindow + + + 30 + + + + + + + + + + Ready to Add Files + + + 21 + + + + &Start + + + 109 + + + + Files will be added to the root of the depot named depot + + + 116 + + + + Files will be added to the depot: + + + 127 + + + + Files will be in the stream: + + + 129 + + + + To begin copying files to the workspace and add them to the Perforce Server, click "Start". + + + 133 + + + + Number of files to copy: + + + 134 + + + + Size of files to copy: + + + 135 + + + + + + + + + + &Pause + + + 108 + + + + &Continue + + + 109 + + + + Updating Workspace and Server + + + 170 + + + + Copying files to workspace... + + + 258 + + + 392 + + + + Ready to upload files to server... + + + 333 + + + + Error copying "%1" from "%2": (%3) + + + 365 + + + + Copying files paused... + + + 399 + + + + Uploading files to server... + + + 449 + + + + Error adding file to server: File is locked + + + 479 + + + + Populated server with files + + + 538 + + + + Connection Setup + + + 599 + + + + Add Files + + + 601 + + + + Wizard + + + 604 + + + + Assistant + + + 606 + + + + Completing the %1 %2 + + + 608 + + + + N&o + + + 819 + + + + + + + + 625 + + + 631 + + + + Error adding files to server: + + + 627 + + + + + + + + + 631 + + + + Are you sure you want to stop copying files to the workspace? + + + 805 + + + + &Yes + + + 818 + + + + + + + + + + Connect to the server with an existing or new user account. + + + 60 + + + + Name used to log in (no spaces allowed) + + + 76 + + + + First and last name + + + 83 + + + + Enter password twice + + + 90 + + + + &Create a new user account: + + + 102 + + + 112 + + + + B&rowse... + + + 104 + + + + &Full name: + + + 106 + + + + &Password*: + + + 107 + + + + &Email: + + + 108 + + + + Browse... + + + 114 + + + + &Log in to server "%1" with an existing user account: + + + 253 + + + + Full name: + + + 80 + + + 116 + + + + Log In + + + 39 + + + + &User: + + + 62 + + + 103 + + + + User: + + + 72 + + + 113 + + + 115 + + + + Password: + + + 84 + + + + Email: + + + 91 + + + + U&ser: + + + 105 + + + + Password*: + + + 117 + + + + Email address: + + + 118 + + + + User name not found + + + 352 + + + + User '%1' not found. + + + 353 + + + + + + + + + + &Back + + + 67 + + + + &Next + + + 68 + + + + Connection Setup Wizard + + + 69 + + + + Connection Setup Assistant + + + 76 + + + + To close this wizard, click Finish. + + + 172 + + + + + + + + + + Choose Workspace + + + 96 + + + 390 + + + + Choose an existing workspace or create a new one. + + + 118 + + + 391 + + + + No spaces allowed in name + + + 123 + + + + &Select a workspace that has already been set up for your computer: + + + 136 + + + + &Create a new workspace: + + + 137 + + + + &Workspace name: + + + 138 + + + + &Location: + + + 139 + + + + B&rowse... + + + 140 + + + + Select a workspace that has already been set up for your computer: + + + 144 + + + + Create a new workspace: + + + 145 + + + + Workspace name: + + + 146 + + + + Location: + + + 147 + + + + Browse... + + + 148 + + + + The workspace is where you keep local copies of the files managed by Perforce. + + + 155 + + + + More Info + + + 157 + + + + Location + + + 326 + + + + Create a New Workspace + + + 378 + + + + A folder named "%1" already exists at the location "%2". + +Do you want to choose a different name or location for the workspace? + + + 583 + + + + For further information, see the + + + 992 + + + + %1 online help + + + 994 + + + + Could not create a directory + + + 550 + + + + Stream + + + 331 + + + + Cannot create a directory named '%1' + + + 599 + + + + Select the location for the top-level workspace folder: + + + 940 + + + + Select Workspace Location + + + 941 + + + + A workspace is a folder on your computer for keeping files you're working on. You can copy files from the Perforce server to your workspace for editing or viewing. When you're done working on a file, you can copy your version of the file back to the server, thereby making it available to other Perforce users. + +If you connect to more than one Perforce server, create a different workspace for each server. + + + + + 983 + + + + OK + + + 1004 + + + + + + + + + + (no workspace selected) + + + 58 + + + + Switch to Workspace... + + + 63 + + + + New Workspace... + + + 68 + + + + + + + + + + Workspace + + + 156 + + + + Depot + + + 157 + + + + + + + + + + Update Spec Depot + + + 20 + + + + Select the specifications that you want to add to the depot: + + + 28 + + + + &Branch Mapping + + + 36 + + + + &Depot + + + 38 + + + + &Group + + + 40 + + + + &Job + + + 42 + + + + &Label + + + 44 + + + + U&ser + + + 46 + + + + &Workspace + + + 48 + + + + &Update + + + 55 + + + + + + + + + + Advanced Settings + + + 25 + + + + &Save cookies from applets + + + 32 + + + + &Remove Cookies + + + 34 + + + + All&ow applets to store data locally + + + 43 + + + + &Manually configure web proxy used by applets for internet access: + + + 47 + + + + A&ddress: + + + 55 + + + + &Port: + + + 60 + + + + Are you sure you want to remove all cookies? + + + 159 + + + + &Remove + + + 162 + + + + + + + + + + Perforce Applet + + + 37 + + + + + + + + + + Browse... + + + 78 + + + + %1(%2): %3 + + + 166 + + + 383 + + + + Select HTML File + + + 330 + + + + %1(%2): %3<br> + + + 382 + + + + Save File + + + 395 + + + + + + + + + + What's New + + + 43 + + + + <html><body><h1>Failed to load the What's New page</h1><br><i>If your machine is behind a firewall, you might need to adjust your proxy settings in Preferences/Applets/Configure Proxy</i></body></html> + + + 103 + + + + http://www.perforce.com/applications/p4v/latestP4V_en.html + + + 112 + + + + + + + + + + Open in preferred application + + + 8 + + + + Get latest revision + + + 9 + + + + Diff against have revision + + + 10 + + + + Diff against previous revision + + + 11 + + + + Diff against source revision + + + 12 + + + + Diff against latest revision + + + 13 + + + + Diff against workspace file + + + 14 + + + + Revert file + + + 15 + + + + Revert file if unchanged + + + 16 + + + + Submit file + + + 17 + + + + Submit pending changelist + + + 18 + + + + Shelve file + + + 19 + + + + Unshelve file + + + 20 + + + + Check out file + + + 21 + + + + Check out and open file + + + 22 + + + + Get this revision + + + 23 + + + + Unshelve files + + + 24 + + + + Delete shelved files + + + 25 + + + + Show file history + + + 26 + + + + Merge/Integrate using the revision + + + 27 + + + + View pending changelist + + + 28 + + + + Edit pending changelist + + + 29 + + + + View submitted changelist + + + 30 + + + + Edit submitted changelist + + + 31 + + + + Get revisions at submitted changelist + + + 32 + + + + Merge/Integrate using the changelist + + + 33 + + + + View branch mapping + + + 34 + + + + Edit branch mapping + + + 35 + + + + Merge/Integrate using the branch mapping + + + 36 + + + + View label + + + 37 + + + + Edit label + + + 38 + + + + Get revisions at label + + + 39 + + + + Label files + + + 40 + + + + View workspace + + + 41 + + + + Edit workspace + + + 42 + + + + Switch to workspace + + + 43 + + + + View job + + + 44 + + + + Edit job + + + 45 + + + + View stream + + + 46 + + + + Edit stream + + + 47 + + + + Merge/Integrate to stream + + + 48 + + + + Copy to stream + + + 49 + + + + Expand/Collapse folder + + + 50 + + + + Check out folder + + + 51 + + + + Show folder history + + + 52 + + + + + + + + + + updating... + + + 228 + + + + list + + + 863 + + + + + + + + + + The following files were not marked for add, since they are 'ignored': + + + 37 + + + + Do not warn me about ignored files. + + + 51 + + + + + + + + + + workspace + + + 36 + + + + + + + + + + Submit Changelist Progress + + + 21 + + + + &Stop + + + 33 + + + + File: %1 %2% complete + + + 107 + + + + Submitting file %1 of %2 to depot + + + 111 + + + + + + + + + + Reverting will undo the following file changes: + + + 109 + + + + File + + + 170 + + + + Do you want to proceed with the revert of the selected files? + + + 217 + + + + Don't &warn me about file changes + + + 225 + + + + &Revert + + + 236 + + + + &Don't Revert + + + 240 + + + + Reverting will undo the following changed files: + + + 253 + + + + Do you want to proceed with the revert of these files? + + + 254 + + + + + + + + + + The following file revisions in your workspace have been purged from the depot: + + + 43 + + + + If you revert these files, the workspace file will be replaced with the latest revision + + + 69 + + + + &Revert + + + 84 + + + + &Don't Revert + + + 87 + + + + + + + + + + === Date: %1 + + + 175 + + + + === Config: %1 + + + 200 + + + + + + + + + + &Dates: + + + 23 + + + + OS format + + + 32 + + + + Perforce standard (yyyy/mm/dd hh:mm:ss) + + + 33 + + + + + + + + + + &No default, prompt for selection + + + 32 + + + + &Don't prompt again. Remember my choice as the default. + + + 38 + + + + &Last + + + 41 + + + + All re&visions + + + 42 + + + + You must select at least 2 revisions + + + 46 + + + + S&pecify revision using: + + + 51 + + + + revisions + + + 105 + + + + &From + + + 115 + + + + &To + + + 116 + + + + + + Time-lapse Image Range + + + 252 + + + + How many revisions of the file would you like Time-lapse to retrieve? + + + 255 + + + + <i>Your upper bound must be greater or equal to the lower bound.</i> + + + 310 + + + + + + + + + + Job Query Builder + + + 76 + + + + Query preview: + + + 77 + + + + + + + + + + Construct a search query + + + 34 + + + + + + + + + + Browse... + + + 299 + + + + + + + + + + Construct a file path + + + 41 + + + + + + + + + + Current User + + + 188 + + + + Current workspace + + + 197 + + + 241 + + + 245 + + + + Current depot or workspace tree selection + + + 205 + + + + + + + + + + Name + + + 19 + + + + Stream Root + + + 20 + + + + Stream w&idth: + + + 22 + + + + Display stream: + + + 30 + + + + + + + + + + Show date and ti&me as: + + + 23 + + + + Server time + + + 32 + + + + Local time + + + 33 + + + + + + + + + + The %1 cannot be accessed because it has been unloaded. + +To use it, it must first be reloaded. + + + 332 + + + + Unloaded %1s (%2) + + + 418 + + + + + + + + + + Submitted + + + 141 + + + 368 + + + + is + + + 328 + + + 330 + + + + and + + + 334 + + + + Changelist + + + 372 + + + + + + is between + + + 157 + + + + + + + + + + File name + + + 141 + + + + Folder path + + + 144 + + + + + + + + + + &Browse... + + + 72 + + + + and + + + 371 + + + 470 + + + + Changelist + + + 390 + + + + Date + + + 484 + + + + + + + + + + Filter: + + + 78 + + + + Clear filter + + + 106 + + + + Apply saved filter + + + 124 + + + + Unloaded... + + + 138 + + + + Refresh filter + + + 152 + + + + Save Filter... + + + 306 + + + + Save as a named filter + + + 307 + + + + Manage Filters... + + + 310 + + + + Manage named filters + + + 311 + + + + no matches + + + 417 + + + zero rows retrieved + + + + + %n match(es) + %n matches + + + %n match(es) + %n matches + + + 420 + + + All rows retrieved + + + + + %n+ match(es) + %n+ matches + + + %n+ match(es) + %n+ matches + + + 421 + + + More rows to be retrieved + + + + + + Keywords or search query: + + + 632 + + + + Files match any of the following file paths: + + + 636 + + + + + + Search in: + + + 685 + + + + Find + + + 689 + + + + Apply saved search + + + 695 + + + + Clear search + + + 706 + + + + Path + + + 728 + + + + Raw Query + + + 729 + + + + Name matches any of the following: + + + 737 + + + + Submission date or changelist: + + + 749 + + + + Include deleted depot files + + + 755 + + + + Filter + + + 762 + + + + + + + + + + Configure Monitoring + + + 21 + + + + Monitoring &disabled + + + 25 + + + + Monitor &active processes only + + + 29 + + + + Monitor &both active and inactive processes + + + 33 + + + + + + + + + + Password Security Level + + + 22 + + + + Select security level for all passwords: + + + 26 + + + + &3 (ticket-based authentication required) + + + 30 + + + + &2 (strong passwords required) + + + 34 + + + + &1 (passwords required) + + + 38 + + + + &0 (passwords not required) + + + 42 + + + + + + + + + + Setup is now complete. + + + 104 + + + + Server: + + + 105 + + + + User: + + + 106 + + + + Workspace: + + + 107 + + + + Stream: + + + 108 + + + + + + + + + + Choose a Depot Type + + + 49 + + + + Use a streams depot + + + 63 + + + + Use a classic depot + + + 65 + + + + Streams depots enable you to build change propagation rules and workspace definitions into codelines. <a href="moreInfo">More Info</a> + + + 77 + + + + Depot: + + + 84 + + + + Files will be added to the mainline stream. + + + 95 + + + + Mainline name: + + + 100 + + + + Files will be added to the depot named depot. + + + 116 + + + + Streams provide a robust branching model layered on Perforce's core behaviors. If your organization is new to Perforce, streams can provide you with an effective branching strategy without having to design it from scratch. Streams give you straightforward tools for generating codelines and guiding users in the most effective management of their work. If your organization already uses Perforce, you may already have a branching model that works for you, with the tools and processes in place to support it. In that case, "classic" (non-streams) depots may be preferable. Whether you start with stream depots or classic depots, you can always add depots of either type at any time; they can be used side-by-side. + + + 257 + + + + For further information, see + + + 272 + + + + <a href> About Streams</a>. + + + 276 + + + + OK + + + 289 + + + + + + + + + + File Searches + + + 58 + + + 109 + + + 152 + + + + Folder Diff + + + 60 + + + 145 + + + + Reconcile + + + 62 + + + 147 + + + + Stream Graph + + + 64 + + + 149 + + + + %1 Filters + + + 67 + + + 111 + + + 154 + + + + Add File Searches + + + 85 + + + + Add Filter Files + + + 87 + + + + Add Reconcile Filter + + + 89 + + + + Add Stream Graph Filter + + + 91 + + + + Add %1 Filter + + + 93 + + + + Edit Filter + + + 141 + + + + Menu Item: + + + 164 + + + + &Name: + + + 166 + + + + Place&ment: + + + 173 + + + + N&ew Folder... + + + 178 + + + + Filter: + + + 182 + + + + Show only workspaces available for use on this computer + + + 334 + + + + Match case when filtering by name + + + 353 + + + + Changelist + + + 643 + + + + Date + + + 648 + + + + + + [Current User] + + + 576 + + + 289 + + + + [Current Workspace] + + + 577 + + + 290 + + + + [Current Host] + + + 578 + + + 291 + + + + [Current P4Port] + + + 579 + + + 292 + + + + [Current Charset] + + + 580 + + + 293 + + + + + + + + + + Filter... + + + 50 + + + + Manage Filters + + + 58 + + + + File Searches + + + 132 + + + 160 + + + + Folder Diff Filters + + + 134 + + + + Reconcile Filters + + + 136 + + + + Stream Graph Filters + + + 138 + + + + %1 Filters + + + 140 + + + 169 + + + + Folder Diff + + + 162 + + + + Reconcile + + + 164 + + + + Stream Graph + + + 166 + + + + Add File Searches + + + 211 + + + + Add Filter Files + + + 213 + + + + Add Reconcile Filter + + + 215 + + + + Add Stream Graph Filter + + + 217 + + + + Add %1 Filter + + + 219 + + + + Are you sure you want to delete the query '%1'? + + + 319 + + + + Do you want to delete all of the queries? + + + 331 + + + + Do you want to delete the folder '%1' and all of the queries it contains? + + + 333 + + + + + + + + + + Add Filter + + + 76 + + + + %1 Filters + + + 79 + + + + Named Filter: + + + 90 + + + + &Name: + + + 92 + + + + Place&ment: + + + 99 + + + + N&ew Folder... + + + 104 + + + + + + + + + + Reconcile + + + 120 + + + + Folder Diff + + + 122 + + + + Stream Graph + + + 124 + + + + %1 Filters + + + 125 + + + + + + + + + + Merge + + + 37 + + + 121 + + + + Cancel + + + 38 + + + 98 + + + 122 + + + 146 + + + 168 + + + 189 + + + + Copy + + + 39 + + + 99 + + + + Your stream is not configured to receive merges in this direction. + +Are you sure you want to merge instead of copy? + + + 83 + + + + Force the merge, I am working outside the established stream configuration + + + 88 + + + 158 + + + + Continue with Merge + + + 90 + + + 160 + + + + Force the preview, I am working outside the established stream configuration + + + 94 + + + 164 + + + 184 + + + + Continue with Preview + + + 96 + + + 118 + + + 143 + + + 166 + + + 186 + + + + Your stream is not configured to receive copies in this direction. + +Are you sure you want to copy instead of merge? + + + 104 + + + + Force the copy, I understand that I am working outside the established stream configuration + + + 109 + + + + Continue with Copy + + + 111 + + + 137 + + + 179 + + + + Force the preview, I understand that I am working outside the established stream configuration + + + 116 + + + + There are outstanding changes in the target which will be overwritten if you proceed with copying. It is recommended that you perform a merge prior to copying. + + + + 129 + + + + Force the copy, I understand that the changes will be overwritten. + + + 135 + + + + Force the preview, I understand that the changes will be overwritten. + + + 141 + + + + Merge Outstanding Changes + + + 145 + + + + Your stream is not configured to receive merges. + + + 154 + + + + Your stream is not configured to receive copies. + + + 173 + + + + Force the copy, I am working outside the established stream configuration + + + 177 + + + + + + + + + + &Add files to pending changelist + + + 39 + + + + Automat&ically resolve files after merging + + + 57 + + + + Resol&ve option: + + + 60 + + + + P&ending changelist: + + + 62 + + + + Chan&gelist description: + + + 84 + + + + Add previously linked &job(s) to the new changelist + + + 87 + + + + Jobs cannot be linked to the "default" changelist + + + 92 + + + + Automatica&lly submit after resolving + + + 140 + + + + Automatica&lly submit copied files + + + 148 + + + + Automatica&lly submit after branching files + + + 152 + + + + + + + + + + Created by + + + 139 + + + + + + + + + + Connections + + + 48 + + + + Streams + + + 55 + + + + Server Data + + + 62 + + + + Behavior + + + 69 + + + + Integrate Flags + + + 83 + + + + Double Click + + + 104 + + + + Shortcuts + + + 111 + + + + Logging + + + 118 + + + + Display + + + 125 + + + + Files and History + + + 132 + + + + Application Font + + + 139 + + + + Features + + + 146 + + + + Tools + + + 153 + + + + Image Timelapse + + + 160 + + + + File Editors + + + 167 + + + + Diff + + + 174 + + + + Merge + + + 181 + + + + Applets + + + 188 + + + + Merge-Integrate + + + 217 + + + + Copy + + + 223 + + + + Branch + + + 229 + + + + + + + + + + Allo&w Perforce applets to run in %1 + + + 39 + + + + Always accept Perforce applets from the following servers: + + + 43 + + + + &Server: + + + 45 + + + + (server name or host:port) + + + 49 + + + 89 + + + + A&dd + + + 52 + + + 92 + + + + &Remove + + + 57 + + + + Never accept Perforce applets from the following servers: + + + 82 + + + + S&erver: + + + 85 + + + + Re&move + + + 98 + + + + Note: To run Perforce applets after adding a server to the "always accept" list, close and reopen any connections to that server. + + + 121 + + + + Ad&vanced... + + + 127 + + + + + + + + + + Prompts: + + + 30 + + + + &Warn before reverting files + + + 34 + + + + Warn when &ignored files are not marked for add + + + 36 + + + + Prompt for changelist when checking out, adding, or deleting &files + + + 38 + + + + Prompt to &get latest revision when checking out files that are out of date + + + 40 + + + + When dropping file(s): + + + 63 + + + + on &a file, do a diff comparison + + + 67 + + + + anywhere within a changelist, &move open files to new changelist + + + 72 + + + + on a file, do &nothing + + + 77 + + + + &Disable refresh all for the reconcile offline work dialog. + + + 84 + + + + Drag and drop: + + + 51 + + + + &Enable integration on directory-to-directory drag and drop + + + 56 + + + + + + + + + + When the application launches: + + + 34 + + + + &Show the Perforce Connection dialog + + + 43 + + + + &Restore all previously opened connections + + + 45 + + + 82 + + + + &Open the connection specified by your Perforce environment settings + + + 49 + + + + &Change Settings + + + 64 + + + + Opening and closing connections: + + + 91 + + + + Use IP-specific tickets when lo&gging in + + + 95 + + + + Au&tomatically log off when closing a connection + + + 98 + + + + &Don't expand Workspace and Depot trees to their previous state when opening connections + + + 103 + + + + A&utomatically check for P4V updates. + + + 112 + + + + Contri&bute your anonymous usage data to help us improve our products. + + + 116 + + + + + + + + + + Default diff application: + + + 30 + + + + &P4Merge + + + 32 + + + + &Other application + + + 34 + + + + &Location: + + + 42 + + + + &Browse... + + + 48 + + + + Arg&uments: + + + 52 + + + + Specify diff application by extension (overrides default): + + + 57 + + + + Please specify the location of the application. + + + 102 + + + + Choose an application + + + 178 + + + + + + + + + + Application: + + + 28 + + + + Show item count on tab bar of detai&ls pane + + + 34 + + + + Show the &What's New in P4V page after upgrading + + + 38 + + + + (requires refresh to see changes) + + + 54 + + + + Localization: + + + 58 + + + + Set &encoding for all connections to: + + + 68 + + + + (will not affect connections for which the encoding was previously set) + + + 75 + + + + Use the s&ystem character encoding in P4Merge when connected to non-unicode servers + + + 88 + + + + + + + + + + Object + + + 166 + + + + Double Click Behavior + + + 166 + + + + &Restore Defaults + + + 174 + + + + File in Depot tree + + + 183 + + + + File in Workspace tree + + + 186 + + + + Folder in Depot tree + + + 189 + + + + Folder in Workspace tree + + + 192 + + + + File in Pending changelist + + + 195 + + + + Shelved File + + + 198 + + + + Revision + + + 201 + + + + Pending Changelist + + + 204 + + + + Shelved Files Folder + + + 207 + + + + Submitted Changelist + + + 210 + + + + Branch Mapping + + + 213 + + + + Label + + + 216 + + + + Workspace + + + 219 + + + + Job + + + 222 + + + + Stream + + + 225 + + + + + + + + + + User specified editor associations: + + + 50 + + + + Extension + + + 58 + + + + Default + + + 58 + + + + Application + + + 58 + + + + A&dd... + + + 64 + + + + &Edit... + + + 66 + + + + &Remove + + + 68 + + + + Application Association + + + 155 + + + + true + + + 167 + + + 248 + + + + false + + + 248 + + + + + + + + + + Enable these features: + + + 38 + + + + &Labels + + + 46 + + + + &Jobs + + + 50 + + + + &Streams + + + 54 + + + + &Unload/Reload + + + 58 + + + + Enable these menu options: + + + 62 + + + + &Merge, Copy and Branch Dialogs + + + 66 + + + + Set Up Connection Assistant + + + 71 + + + + Set Up Connection Wi&zard + + + 73 + + + + &Revision Graph + + + 78 + + + + C&ustom Tools + + + 82 + + + + &Time-lapse + + + 86 + + + + A&dministration Tool + + + 90 + + + + Changes will take effect the next time you start P4V. + + + 97 + + + + Warning: Your administrator may turn off some features, overriding your preference selections made here. + + + 100 + + + + + + + + + + Default revisions to retrieve for Image Time-lapse View: + + + 21 + + + + Image Time-lapse View Filetype Associations: + + + 28 + + + + Do not open files as images in Time-lapse View + + + 32 + + + + Open selected filetypes as images in Time-lapse View + + + 34 + + + + + + + + + + Specify source and target files + + + 18 + + + + Branch mapping + + + 19 + + + + Remember my last choice + + + 20 + + + + Resolve and Submit + + + 21 + + + + Submit + + + 22 + + + + Filter + + + 23 + + + + Advanced + + + 24 + + + + + + Restore Defaults + + + 92 + + + + When the + + + 93 + + + + dialog opens: + + + 93 + + + + Use this default + + + 94 + + + + method: + + + 94 + + + + From a &non-stream file or folder: + + + 95 + + + + From a submitted &changelist: + + + 98 + + + + When filtering a branch mapping by files, by default use the files as the + + + 101 + + + + : + + + 101 + + + + &Source + + + 102 + + + + &Target + + + 103 + + + + &Remember my last choice + + + 104 + + + + Default &options tab: + + + 105 + + + + &Add files to pending changelist + + + 108 + + + + Automatically resolve f&iles after merging + + + 110 + + + + Resol&ve method: + + + 111 + + + + P&ending changelist: + + + 114 + + + + Add previously linked &job(s) to the new changelist + + + 117 + + + + Check for opened files and &warn prior to merging (might affect server performance) + + + 118 + + + + &Do not copy newly branched files to workspace (-v) + + + 119 + + + + Specify source and target files + + + 130 + + + + Branch mapping + + + 131 + + + + Remember my last choice + + + 132 + + + + Resolve and Submit + + + 143 + + + + Submit + + + 145 + + + + Filter + + + 146 + + + + Advanced + + + 147 + + + + Automatica&lly submit after resolving + + + 164 + + + + Automatica&lly submit copied files + + + 168 + + + + Automatically submit branched files + + + 175 + + + + + + The integrate flags are applied when the integrate command is run by the<br>Merge/Integrate and Branch dialogs. <a href>More Info</a> + + + 437 + + + + &Do not copy newly branched target files to workspace (-v) + + + 441 + + + + &Try to integrate changes when source deleted and re-added (-Di) + + + 443 + + + + &Force integration on all revisions, disregarding integration history (-f) + + + 445 + + + + Do not get latest re&vision of selected files (-h) + + + 447 + + + + Dis&regard indirect integration history (-1) + + + 449 + + + + Propagate source filet&ypes to target files (-t) + + + 451 + + + + S&chedule 'branch resolves' instead of branching new target files (-Rb) + + + 453 + + + + Sc&hedule 'delete resolves' instead of deleting target files (-Rd) + + + 455 + + + + S&kip previously merged cherry-picked revisions to improve merge results (-Rs) + + + 457 + + + + + + + + + + Log pane options: + + + 30 + + + + Show p4 reporting &commands (dirs, filelog, fstat, etc.) + + + 36 + + + + Show &p4 command output for file operations + + + 43 + + + + Logging to a file: + + + 49 + + + + Enable l&ogging to file + + + 55 + + + + &Name: + + + 62 + + + + Selec&t... + + + 64 + + + + &Size: + + + 77 + + + + KB + + + 78 + + + + A restart will be requred to start or stop logging to a file + + + 86 + + + + Enter a log file name: + + + 178 + + + + Text (*.txt) + + + 180 + + + + + + + + + + Default merge application: + + + 29 + + + + &P4Merge + + + 31 + + + + &Other application + + + 33 + + + + &Location: + + + 42 + + + + &Browse... + + + 46 + + + + Arg&uments: + + + 52 + + + + Specify merge application by extension (overrides default): + + + 57 + + + + The arguments field must contain the +%1, %2 and %r arguments. + + + + 102 + + + + Please specify the location of the application. + + + 104 + + + + Choose an application + + + 180 + + + + + + + + + + Server data: + + + 26 + + + + Check server for &updates every: + + + 38 + + + + minutes + + + 40 + + + + Ma&ximum number of files displayed per changelist*: + + + 50 + + + + &Maximum size of files to preview (excludes audio and video files): + + + 66 + + + + KB + + + 67 + + + + &Number of changelists, jobs, branch mappings or labels to fetch at a time: + + + 77 + + + + (0 = all) + + + 84 + + + + Parallel commands: + + + 97 + + + + &Enable parallel sync + + + 101 + + + + Enable &parallel submit** + + + 105 + + + + * After limit is reached, changelist and resolve dialogs show plain text list of files + + + 116 + + + + ** The progress indicator is unavailable with parallel submit + + + 119 + + + + + + + + + + &Shortcuts for: + + + 53 + + + + &Restore Defaults + + + 78 + + + + Click the "Shortcut" column for a command, and then press the keyboard shortcut to assign. Shortcuts must include either the command key, the control key or a function key. + + + 89 + + + + Click the "Shortcut" column for a command, and then press the keyboard shortcut to assign. Shortcuts must include either the control key or a function key. + + + 90 + + + + &Undo + + + 103 + + + + &Clear + + + 112 + + + + Use &Default + + + 121 + + + + Command + + + 138 + + + + Menu + + + 138 + + + + Shortcut + + + 138 + + + + This key combination is reserved for %1 shortcuts + + + 194 + + + + bookmark + + + 221 + + + + favorite connection + + + 223 + + + + pre-defined + + + 241 + + + + The shortcut is already in use for the %1 command "%2" + +Reassign the %3 shortcut key? + + + 258 + + + + Are you sure you want to reset the key assignments? +All custom shortcuts assigned to commands will be removed. + + + 392 + + + + + + + + + + When performing stream operations: + + + 22 + + + + Stream workspaces: + + + 26 + + + + Stream graph: + + + 30 + + + + When clicking 'Work in this Stream'*: + + + 34 + + + + use different workspace + + + 37 + + + 45 + + + + reuse current workspace + + + 38 + + + 46 + + + + When dragging workspace icon to a new stream*: + + + 42 + + + + Don't &warn me when switching workspaces + + + 50 + + + + A&utomatically update the workspace with all stream files when switching between streams + + + 54 + + + + When &branching streams, include file deletion actions (Server 12.1 or later) + + + 56 + + + + Don't allow streams to be re&parented with drag and drop in the stream graph. + + + 58 + + + + Sh&ow pending stream-to-stream merge and copy hints* + + + 62 + + + + * When disabled, pending stream-to-parent merge and copy hints can be displayed on individual stream refresh + + + 100 + + + + Do not warn when chec&king out, adding or deleting imported files, always + + + 65 + + + + act on imported files + + + 68 + + + + ignore action for imported files + + + 69 + + + + + + + + + + Revision Graph: + + + 26 + + + + Limit Revision &Graph to ancestors and descendants + + + 60 + + + + Time-lapse View: + + + 64 + + + + By default Time-lapse View should show + + + 67 + + + + Direct history + + + 68 + + + + Branch history + + + 69 + + + + Originating changelist history + + + 70 + + + + + + + + + + content + + + 91 + + + + + + + + + + Task stream + + + 129 + + + + %1 '%2' could not be reloaded because you are not its owner. + + + 132 + + + + Unloaded + + + 160 + + + + Task Streams + + + 175 + + + + Labels + + + 178 + + + + Workspaces + + + 181 + + + + + + + + + + Task stream + + + 156 + + + + %1 '%2' could not be unloaded because you are not its owner. + + + 159 + + + + + + + + + + New + + + 105 + + + 2423 + + + + Switch to workspace &immediately + + + 127 + + + + A&utomatically get all revisions + + + 130 + + + + &Basic + + + 136 + + + + &Advanced + + + 137 + + + + &Workspace name: + + + 303 + + + + Wor&kspace root: + + + 309 + + + + &Stream: + + + 314 + + + + Str&eam at change: + + + 319 + + + + B&rowse... + + + 323 + + + + Brows&e... + + + 325 + + + + Workspace &Mappings: + + + 333 + + + + To edit these mappings, you must edit the stream's path configuration + + + 341 + + + + Include selected in workspace mapping + + + 392 + + + + Exclude selected from workspace mapping + + + 396 + + + + Clear selected from workspace mapping + + + 400 + + + + View workspace mapping as tree + + + 405 + + + + View workspace mapping as text + + + 410 + + + + Ow&ner: + + + 491 + + + + Loc&ked: only the owner can edit workspace settings + + + 495 + + + + &Description: + + + 499 + + + + Ho&st: + + + 504 + + + + Al&t roots: + + + 509 + + + + C&hange View: + + + 522 + + + + Restrict to Ser&ver ID: + + + 531 + + + + File Options: + + + 537 + + + + All&write: leave all workspace files writeable when getting revisions + + + 545 + + + + C&lobber: overwrite writeable workspace files when getting revisions + + + 546 + + + + Com&press: speed up slow connections by compressing files when submitting or getting revisions + + + 547 + + + + Modtim&e: set file modification times to what they were in the submitter's workspace + + + 548 + + + + &Rmdir: delete workspace directories when empty + + + 549 + + + + Line ending characters for te&xt files: + + + 562 + + + + Local: defaults to the current operating system + + + 567 + + + + Unix: UNIX style linefeed + + + 568 + + + + Mac: Macintosh style carriage return + + + 569 + + + + Win: Windows style carriage return linefeed + + + 570 + + + + Shared: writes UNIX style and reads local style + + + 571 + + + + On s&ubmit: + + + 579 + + + + Check out submitted &files after submit + + + 584 + + + + Submit all selected files + + + 587 + + + + Don't submit unchanged files + + + 588 + + + + Revert unchanged files + + + 589 + + + + Additional Fields: + + + 684 + + + + Created by %1. + + + 864 + + + + Switch to new workspace &immediately + + + 894 + + + + has been unloaded. Reload to get workspace view. + + + 1608 + + + + Select Root Folder + + + 1690 + + + + You can't have a recursive path for a workspace root. + + + 1812 + + + + A folder named "%1" already exists. +Do you want to choose a different location for the workspace? + + + 1822 + + + %1=workspace location + + + + '%1' is a reserved Perforce workspace name, and considered invalid by Perforce. + + + 1843 + + + + Do you want to save changes to %1? + + + 2425 + + + + &Save + + + 2438 + + + + &Don't Save + + + 2439 + + + + Unable to create a workspace named "%1", because"%2" is already in use as a name for a %3. + + + 2470 + + + + Unable to create a workspace named '%1'. + + + + 2476 + + + + Workspace '%1' already exists. + + + 2481 + + + + %1/Perforce + + + 2499 + + + %1=home + + + + Error at line %1 of field 'View' in client specification. +Wrong number of words for field 'View'. + + + 2556 + + + + This user does not exist: + + + + 2604 + + + + + +Continue with saving workspace? + + + 2606 + + + + &Save Workspace + + + 2621 + + + + &Cancel + + + 2622 + + + + + + Select Stream + + + 2669 + + + + + + + + + + Browse... + + + 82 + + + + Graph View Options + + + 53 + + + + Depot: + + + 77 + + + + Apply saved filter + + + 103 + + + + Select Streams + + + 114 + + + + None + + + 117 + + + + All + + + 118 + + + + Current stream + + + 119 + + + + Streams with my workspaces + + + 120 + + + + More stable than main + + + 121 + + + + Less stable than main + + + 122 + + + + Apply + + + 155 + + + + Save Filter... + + + 766 + + + + Save as a named filter + + + 767 + + + + Manage Filters... + + + 770 + + + + Manage named filters + + + 771 + + + + + + + + + + Graph Navigator + + + 114 + + + + No streams selected. + + + 320 + + + + Refresh Streams + + + 400 + + + + Currently un-doing a stream reparenting, wait until the previous one is finished before un-doing another. + + + 590 + + + + Reparent Stream + + + 1039 + + + + + + + + + + You are about to switch your workspace to a stream not associated with your current mainline. + +Are you sure you want to continue? + + + 112 + + + + The following file is checked out and cannot be submitted until you switch back. + + + 182 + + + + In order to get revisions of this file after switching, you must first shelve or submit this file before switching. + + + + + 183 + + + + The following files are checked out and cannot be submitted until you switch back. + + + 188 + + + + In order to get revisions of these files after switching, you must first shelve or submit these files before switching. + + + + + 189 + + + + Do you want to continue with switching your workspace? + + + 193 + + + + Switch + + + 208 + + + + Do Not Switch + + + 210 + + + + + + + + + + The stream this client is attached to has been unloaded. +Reload the stream to be able to use the workspace. + + + 114 + + + + + + + + + + The stream "%1" is empty. + + + 56 + + + %1=stream name + + + + Choose one of the options below to populate the stream: + + + 75 + + + + Would you like to populate the stream? + + + 77 + + + + &Copy files from the local file system + + + 90 + + + + &Branch files from the server's depots + + + 91 + + + + &Populate + + + 105 + + + + Cancel + + + 106 + + + + + + + + + + &Show In Depot Tree + + + 110 + + + + + + + + + + Time-lapse View Tools + + + 24 + + + + Revisions + + + 34 + + + + Changelists + + + 35 + + + + Dates + + + 36 + + + + + + + + + + Retrieving file revision list + + + 230 + + + + Retrieving image content: 0% + + + 430 + + + + Ready + + + 514 + + + + Retrieving image content: %1% + + + 550 + + + + Current selection: + + + 619 + + + + Scale: + + + 620 + + + + Refresh + + + 654 + + + + Retrieved range: + + + 669 + + + + Autoplay: + + + 676 + + + + rotate images every + + + 678 + + + + seconds + + + 680 + + + + Help + + + 690 + + + + About + + + 698 + + + + About Time-lapse View + + + 702 + + + + + + + + + + Work in this Stream... + + + 135 + + + + + + + + + + OK + + + 28 + + + + + + + + + + Add These Files with Wildcards + + + 48 + + + + The following files contain wildcards in their names. + + + 52 + + + + Select the files you want to add: + + + 56 + + + + C&ontinue + + + 112 + + + + Unchec&k all + + + 115 + + + 219 + + + + Chec&k all + + + 214 + + + + + + + + + + Contribute your anonymous usage data to help us improve our products? + + + 436 + + + + By choosing to send Perforce usage data, you help us improve all our products. This data measures things like which product versions are in use and which features are most popular. All data collection is anonymous and no ip address information is logged or stored. It is examined only on an aggregate basis, and maintained in accordance with Perforce's privacy policies. + + + 439 + + + + &Yes + + + 446 + + + + &No + + + 447 + + + + &More + + + 451 + + + + + + + + + + SSL Error # '%1' + + + 309 + + + + Error count: '%1' + + + 315 + + + + + + Certificate Error + + + 646 + + + + &Trust + + + 671 + + + + Alt+T + + + 672 + + + + &Don't Trust + + + 673 + + + + Alt+D + + + 674 + + + + OK + + + 676 + + + + The root certificate used for the Swarm integration is self-signed and untrusted. + + + 778 + + + + The root certificate used for the Swarm integration has been rejected. All Swarm functionality will be unavailable + + + 780 + + + + Trust this certificate? + + + 810 + + + + Please contact your system administrator. + + + 812 + + + + The root certificate used for the Swarm integration has been rejected. All Swarm functionality will be unavailable. Please contact your system administrator. + + + 824 + + + + + + + + + + Connection timed out + + + 680 + + + + + + + + + + There was a problem reaching the update service. + + + 87 + + + 127 + + + + There was a problem parsing the response from the update server. +Please contact Perforce support at support@perforce.com. + + + 94 + + + + There was a problem reading the current P4V version, please contact support. + + + 142 + + + + + + + + + + Unable to connect to the server %1 using workspace '%2' + + + + 106 + + + + No such workspace. + + + 107 + + + + Unable to connect to the server %1 with user '%2' + + + + 112 + + + + No such user. + + + 113 + + + + + + + + + + Files: + + + 35 + + + + Use a distinct file icon for &modified files + + + 40 + + + + Show Perforce f&iletype for files in the Workspace and Depot tree + + + 45 + + + + Show Perforce f&iletype for files in the Depot tree + + + 47 + + + + Show re&vision information for files in the Workspace and Depot tree + + + 52 + + + + Show re&vision information for files in the Depot tree + + + 54 + + + + Render th&umbnails for Maya files + + + 61 + + + + File Revision History: + + + 69 + + + + Hide files/revisions from 'task' streams (when following branch, copy actions) + + + 72 + + + + + + + + + + Files: + + + 39 + + + + Application &font: + + + 43 + + + + F&ont style: + + + 45 + + + + &Size: + + + 46 + + + + &Restore Defaults + + + 47 + + + + Sample: + + + 85 + + + + abcdefghijklmnopqrstuvwxyz +ABCDEFGHIJKLMNOPQRSTUVWXYZ + + + 91 + + + + Show fi&xed sized fonts only + + + 95 + + + + + + + + + + This revision has been deleted and/or contains no data. + + + 357 + + + + + + + + + + OK + + + 48 + + + + Cancel + + + 49 + + + + + + + + + + Auto-generated repro script for this graph + + + 16 + + + + + + + + + + There is a new version available for P4V + + + 35 + + + + + + diff --git a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/aw30_101x_Rev_1_ui_configurations_ja_JP.xml b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/aw30_101x_Rev_1_ui_configurations_ja_JP.xml index 6fffbd01..75aa1542 100644 --- a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/aw30_101x_Rev_1_ui_configurations_ja_JP.xml +++ b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/aw30_101x_Rev_1_ui_configurations_ja_JP.xml @@ -1,137 +1,137 @@ - - - - コンãƒãƒ¼ãƒãƒ³ãƒˆã‚’追加 - ç½²åを追加 - å‚加者を追加 - プロファイルピクãƒãƒ£ã‚’追加 - レビューアを追加 - 代ç†ã‚’追加 - ãŠæ°—ã«å…¥ã‚Šã«è¿½åŠ  - 情報 - ã™ã¹ã¦ã‚ªãƒ• - ã™ã¹ã¦ã‚ªãƒ³ - éžè¡¨ç¤º - 下ã¸ç§»å‹• - 上ã¸ç§»å‹• - アレンジ - 表示 - ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆã®ã‚­ãƒ£ãƒ³ã‚»ãƒ« - 編集をキャンセル - 所有者ã®å¤‰æ›´ - テーマã®å¤‰æ›´ - ãƒã‚§ãƒƒã‚¯ã‚¤ãƒ³ - ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ - ワークフロータスクã®è¦æ±‚ - 分類 - 色フィルタを表示 - アレンジ - 詳細 - コピー - ホームフォルダ - 切å–ã‚Š - 削除 - 削除 - 削除 - æ–­é¢ã‚’削除 - é¸æŠžã‚’クリア - 検索をä¿å­˜ - 全画é¢ã‚’終了 - フィット - å…¨ç”»é¢ - フルテキスト検索 - å¹¾ä½•è§£æž - Home - キャプãƒãƒ£ - レãƒãƒ¼ãƒˆ - フィルタ - é–‹ã - マークアップ - 表示トグルをリスト - NXリレーションブラウザã§é–‹ã - パン - 回転 - ズーム - ワークフローã«é€ä¿¡ - 情報 - NXã§é–‹ã - Creoã§é–‹ã - Officeクライアントã§é–‹ã - リッãƒã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã§é–‹ã - Solid Edgeã§é–‹ã - Solid Edgeã§é–‹ã - SolidWorks ã§é–‹ã - AutoCADã§é–‹ã - Inventorã§é–‹ã - CATIAã§é–‹ã - SampleCADã§é–‹ã - å—信トレイを開ã - NXã§é–‹ã - NXè¦ä»¶ãƒªãƒ³ã‚¯ã§é–‹ã - Visã§é–‹ã - 戻る - ä¸‹é¢ - å‰ - 等角投影 - å·¦å´é¢ - å³å´é¢ - ä¸Šé¢ - ä¸ç­‰è§’投影 - タスクを転é€(ä¸åœ¨) - タスクを実行 - フィーãƒãƒ£ã‚’ピック - パートをピック - ピン - ホームã¸ã®ãƒ”ン - 検索をピン留゠- コマンドãƒãƒ³ãƒ‰ãƒ©ãŒã‚ã‚Šã¾ã›ã‚“ - PMI - å°åˆ· - プロモートタスク - クイック測定 - タスクをå†å‰²å½“㦠- リリース - ãŠæ°—ã«å…¥ã‚Šã‹ã‚‰é™¤åŽ» - å‚加者を除去 - 代ç†ã‚’除去 - ファイルã®ç½®æ› - å‚åŠ è€…ã‚’ç½®æ› - 編集をä¿å­˜ - 検索をä¿å­˜ - 検索 - 検索フィルタ - ã™ã¹ã¦ã‚’é¸æŠž - é¸æŠžã‚ªãƒ• - é¸æŠžã®ã¿ - NXデフォルトフォルダを設定 - 形状検索 - 形状検索 - 追加 - ç®¡ç† - アラート - ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’ä½œæˆ - ä½œæˆ - ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’ä½œæˆ - My Homeフォルダ - リスト - é–‹ã - é–‹ã - ファイルをダウンロード - レンダ - åå‰ã‚’変ãˆã¦ä¿å­˜ã¾ãŸã¯ãƒªãƒ“ジョンアップ - サマリ付ãリスト - サマリ付ãテーブル - テーブル - Visualization - サインアウト - スタンドイン - 編集を開始 - 固定解除 - ホームã‹ã‚‰å›ºå®šè§£é™¤ - 検索を固定解除 - ファイルをダウンロード - 表示 - ç½²åを無効化 - - + + + + コンãƒãƒ¼ãƒãƒ³ãƒˆã‚’追加 + ç½²åを追加 + å‚加者を追加 + プロファイルピクãƒãƒ£ã‚’追加 + レビューアを追加 + 代ç†ã‚’追加 + ãŠæ°—ã«å…¥ã‚Šã«è¿½åŠ  + 情報 + ã™ã¹ã¦ã‚ªãƒ• + ã™ã¹ã¦ã‚ªãƒ³ + éžè¡¨ç¤º + 下ã¸ç§»å‹• + 上ã¸ç§»å‹• + アレンジ + 表示 + ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆã®ã‚­ãƒ£ãƒ³ã‚»ãƒ« + 編集をキャンセル + 所有者ã®å¤‰æ›´ + テーマã®å¤‰æ›´ + ãƒã‚§ãƒƒã‚¯ã‚¤ãƒ³ + ãƒã‚§ãƒƒã‚¯ã‚¢ã‚¦ãƒˆ + ワークフロータスクã®è¦æ±‚ + 分類 + 色フィルタを表示 + アレンジ + 詳細 + コピー + ホームフォルダ + 切å–ã‚Š + 削除 + 削除 + 削除 + æ–­é¢ã‚’削除 + é¸æŠžã‚’クリア + 検索をä¿å­˜ + 全画é¢ã‚’終了 + フィット + å…¨ç”»é¢ + フルテキスト検索 + å¹¾ä½•è§£æž + Home + キャプãƒãƒ£ + レãƒãƒ¼ãƒˆ + フィルタ + é–‹ã + マークアップ + 表示トグルをリスト + NXリレーションブラウザã§é–‹ã + パン + 回転 + ズーム + ワークフローã«é€ä¿¡ + 情報 + NXã§é–‹ã + Creoã§é–‹ã + Officeクライアントã§é–‹ã + リッãƒã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã§é–‹ã + Solid Edgeã§é–‹ã + Solid Edgeã§é–‹ã + SolidWorks ã§é–‹ã + AutoCADã§é–‹ã + Inventorã§é–‹ã + CATIAã§é–‹ã + SampleCADã§é–‹ã + å—信トレイを開ã + NXã§é–‹ã + NXè¦ä»¶ãƒªãƒ³ã‚¯ã§é–‹ã + Visã§é–‹ã + 戻る + ä¸‹é¢ + å‰ + 等角投影 + å·¦å´é¢ + å³å´é¢ + ä¸Šé¢ + ä¸ç­‰è§’投影 + タスクを転é€(ä¸åœ¨) + タスクを実行 + フィーãƒãƒ£ã‚’ピック + パートをピック + ピン + ホームã¸ã®ãƒ”ン + 検索をピン留゠+ コマンドãƒãƒ³ãƒ‰ãƒ©ãŒã‚ã‚Šã¾ã›ã‚“ + PMI + å°åˆ· + プロモートタスク + クイック測定 + タスクをå†å‰²å½“㦠+ リリース + ãŠæ°—ã«å…¥ã‚Šã‹ã‚‰é™¤åŽ» + å‚加者を除去 + 代ç†ã‚’除去 + ファイルã®ç½®æ› + å‚åŠ è€…ã‚’ç½®æ› + 編集をä¿å­˜ + 検索をä¿å­˜ + 検索 + 検索フィルタ + ã™ã¹ã¦ã‚’é¸æŠž + é¸æŠžã‚ªãƒ• + é¸æŠžã®ã¿ + NXデフォルトフォルダを設定 + 形状検索 + 形状検索 + 追加 + ç®¡ç† + アラート + ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’ä½œæˆ + ä½œæˆ + ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’ä½œæˆ + My Homeフォルダ + リスト + é–‹ã + é–‹ã + ファイルをダウンロード + レンダ + åå‰ã‚’変ãˆã¦ä¿å­˜ã¾ãŸã¯ãƒªãƒ“ジョンアップ + サマリ付ãリスト + サマリ付ãテーブル + テーブル + Visualization + サインアウト + スタンドイン + 編集を開始 + 固定解除 + ホームã‹ã‚‰å›ºå®šè§£é™¤ + 検索を固定解除 + ファイルをダウンロード + 表示 + ç½²åを無効化 + + diff --git a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/euc-jp.txt b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/euc-jp.txt index 4bd0c168..c7ccae55 100644 --- a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/euc-jp.txt +++ b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/euc-jp.txt @@ -1,10 +1,10 @@ -Extended Unix Code(EUC)¤Ï¡¢UNIX¾å¤Ç¤è¤¯»È¤ï¤ì¤ëʸ»ú¥³¡¼¥É¤ÎÉä¹æ²½Êý¼°¤Ç¤¢¤ë¡£ - - ÆüËܸìEUC - JIS X 0208¥Ù¡¼¥¹ (EUC-JP) - JIS X 0213¥Ù¡¼¥¹ (EUC-JIS-2004) - ´Ú¹ñ¸ìEUC (EUC-KR) - ´ÊÂλúÃæ¹ñ¸ìEUC (EUC-CN) - ÈËÂλúÃæ¹ñ¸ìEUC (EUC-TW) - -¤Ê¤É¤¬¤¢¤ë¡£ +Extended Unix Code(EUC)¤Ï¡¢UNIX¾å¤Ç¤è¤¯»È¤ï¤ì¤ëʸ»ú¥³¡¼¥É¤ÎÉä¹æ²½Êý¼°¤Ç¤¢¤ë¡£ + + ÆüËܸìEUC + JIS X 0208¥Ù¡¼¥¹ (EUC-JP) + JIS X 0213¥Ù¡¼¥¹ (EUC-JIS-2004) + ´Ú¹ñ¸ìEUC (EUC-KR) + ´ÊÂλúÃæ¹ñ¸ìEUC (EUC-CN) + ÈËÂλúÃæ¹ñ¸ìEUC (EUC-TW) + +¤Ê¤É¤¬¤¢¤ë¡£ diff --git a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/gb18030.txt b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/gb18030.txt index ea3e4548..a083f0bc 100644 --- a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/gb18030.txt +++ b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/gb18030.txt @@ -1,2 +1,2 @@ -¼òÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎÄ +¼òÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎļòÌåÖÐÎÄ ¡¾¹þ¹þ¡¿£¬Ö»ÊDzâÊÔ£¡- \ No newline at end of file diff --git a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/shift_jis.txt b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/shift_jis.txt index a580281d..4174d1a5 100644 --- a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/shift_jis.txt +++ b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/shift_jis.txt @@ -1 +1 @@ -“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê +“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê“ú–{Œê diff --git a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf8_with_bom_unix_lineending.txt b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf8_with_bom_unix_lineending.txt index 668bab23..29857843 100644 --- a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf8_with_bom_unix_lineending.txt +++ b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf8_with_bom_unix_lineending.txt @@ -1,2 +1,2 @@ -a +a b \ No newline at end of file diff --git a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf_8-jp_with_bom.txt b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf_8-jp_with_bom.txt index 72ef1f80..76706262 100644 --- a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf_8-jp_with_bom.txt +++ b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf_8-jp_with_bom.txt @@ -1,9 +1,9 @@ -UTF-8(ユーティーエフã¯ã¡ã€ãƒ¦ãƒ¼ãƒ†ã‚£ãƒ¼ã‚¨ãƒ•ã‚¨ã‚¤ãƒˆï¼‰ã¯ISO/IEC 10646 (UCS) ã¨Unicodeã§ä½¿ãˆã‚‹8ビット符å·å˜ä½ã®æ–‡å­—符å·åŒ–å½¢å¼åŠã³æ–‡å­—符å·åŒ–スキーム。 - -æ­£å¼å称ã¯ã€ISO/IEC 10646ã§ã¯ “UCS Transformation Format 8â€ã€Unicodeã§ã¯ “Unicode Transformation Format-8†ã¨ã„ã†ã€‚両者ã¯ISO/IEC 10646ã¨Unicodeã®ã‚³ãƒ¼ãƒ‰é‡è¤‡ç¯„囲ã§äº’æ›æ€§ãŒã‚る。RFCã«ã‚‚仕様ãŒã‚ã‚‹[1]。 - -2ãƒã‚¤ãƒˆç›®ä»¥é™ã«ã€Œ/ã€ãªã©ã®ASCII文字ãŒç¾ã‚Œãªã„よã†ã«å·¥å¤«ã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‹ã‚‰ã€UTF-FSS (File System Safe) ã¨ã‚‚ã„ã‚れる。旧å称ã¯UTF-2。 - -データ交æ›æ–¹å¼ã€ãƒ•ã‚¡ã‚¤ãƒ«å½¢å¼ã¨ã—ã¦ã€ä¸€èˆ¬çš„ã«UTF-8ã¯ä½¿ã‚れる傾å‘ã«ã‚る。 - -当åˆã¯ã€ãƒ™ãƒ«ç ”究所ã«ãŠã„ã¦Plan 9ã§ç”¨ã„るエンコードã¨ã—ã¦ã€ãƒ­ãƒ–・パイクã«ã‚ˆã‚‹è¨­è¨ˆæŒ‡é‡ã®ã‚‚ã¨ã€ã‚±ãƒ³ãƒ»ãƒˆãƒ³ãƒ—ソンã«ã‚ˆã£ã¦è€ƒæ¡ˆã•ã‚ŒãŸ +UTF-8(ユーティーエフã¯ã¡ã€ãƒ¦ãƒ¼ãƒ†ã‚£ãƒ¼ã‚¨ãƒ•ã‚¨ã‚¤ãƒˆï¼‰ã¯ISO/IEC 10646 (UCS) ã¨Unicodeã§ä½¿ãˆã‚‹8ビット符å·å˜ä½ã®æ–‡å­—符å·åŒ–å½¢å¼åŠã³æ–‡å­—符å·åŒ–スキーム。 + +æ­£å¼å称ã¯ã€ISO/IEC 10646ã§ã¯ “UCS Transformation Format 8â€ã€Unicodeã§ã¯ “Unicode Transformation Format-8†ã¨ã„ã†ã€‚両者ã¯ISO/IEC 10646ã¨Unicodeã®ã‚³ãƒ¼ãƒ‰é‡è¤‡ç¯„囲ã§äº’æ›æ€§ãŒã‚る。RFCã«ã‚‚仕様ãŒã‚ã‚‹[1]。 + +2ãƒã‚¤ãƒˆç›®ä»¥é™ã«ã€Œ/ã€ãªã©ã®ASCII文字ãŒç¾ã‚Œãªã„よã†ã«å·¥å¤«ã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‹ã‚‰ã€UTF-FSS (File System Safe) ã¨ã‚‚ã„ã‚れる。旧å称ã¯UTF-2。 + +データ交æ›æ–¹å¼ã€ãƒ•ã‚¡ã‚¤ãƒ«å½¢å¼ã¨ã—ã¦ã€ä¸€èˆ¬çš„ã«UTF-8ã¯ä½¿ã‚れる傾å‘ã«ã‚る。 + +当åˆã¯ã€ãƒ™ãƒ«ç ”究所ã«ãŠã„ã¦Plan 9ã§ç”¨ã„るエンコードã¨ã—ã¦ã€ãƒ­ãƒ–・パイクã«ã‚ˆã‚‹è¨­è¨ˆæŒ‡é‡ã®ã‚‚ã¨ã€ã‚±ãƒ³ãƒ»ãƒˆãƒ³ãƒ—ソンã«ã‚ˆã£ã¦è€ƒæ¡ˆã•ã‚ŒãŸ diff --git a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf_8-jp_without_bom.txt b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf_8-jp_without_bom.txt index cd4b3123..9e2dfcda 100644 --- a/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf_8-jp_without_bom.txt +++ b/p4java/r19-1/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf_8-jp_without_bom.txt @@ -1,9 +1,9 @@ -UTF-8(ユーティーエフã¯ã¡ã€ãƒ¦ãƒ¼ãƒ†ã‚£ãƒ¼ã‚¨ãƒ•ã‚¨ã‚¤ãƒˆï¼‰ã¯ISO/IEC 10646 (UCS) ã¨Unicodeã§ä½¿ãˆã‚‹8ビット符å·å˜ä½ã®æ–‡å­—符å·åŒ–å½¢å¼åŠã³æ–‡å­—符å·åŒ–スキーム。 - -æ­£å¼å称ã¯ã€ISO/IEC 10646ã§ã¯ “UCS Transformation Format 8â€ã€Unicodeã§ã¯ “Unicode Transformation Format-8†ã¨ã„ã†ã€‚両者ã¯ISO/IEC 10646ã¨Unicodeã®ã‚³ãƒ¼ãƒ‰é‡è¤‡ç¯„囲ã§äº’æ›æ€§ãŒã‚る。RFCã«ã‚‚仕様ãŒã‚ã‚‹[1]。 - -2ãƒã‚¤ãƒˆç›®ä»¥é™ã«ã€Œ/ã€ãªã©ã®ASCII文字ãŒç¾ã‚Œãªã„よã†ã«å·¥å¤«ã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‹ã‚‰ã€UTF-FSS (File System Safe) ã¨ã‚‚ã„ã‚れる。旧å称ã¯UTF-2。 - -データ交æ›æ–¹å¼ã€ãƒ•ã‚¡ã‚¤ãƒ«å½¢å¼ã¨ã—ã¦ã€ä¸€èˆ¬çš„ã«UTF-8ã¯ä½¿ã‚れる傾å‘ã«ã‚る。 - -当åˆã¯ã€ãƒ™ãƒ«ç ”究所ã«ãŠã„ã¦Plan 9ã§ç”¨ã„るエンコードã¨ã—ã¦ã€ãƒ­ãƒ–・パイクã«ã‚ˆã‚‹è¨­è¨ˆæŒ‡é‡ã®ã‚‚ã¨ã€ã‚±ãƒ³ãƒ»ãƒˆãƒ³ãƒ—ソンã«ã‚ˆã£ã¦è€ƒæ¡ˆã•ã‚ŒãŸ +UTF-8(ユーティーエフã¯ã¡ã€ãƒ¦ãƒ¼ãƒ†ã‚£ãƒ¼ã‚¨ãƒ•ã‚¨ã‚¤ãƒˆï¼‰ã¯ISO/IEC 10646 (UCS) ã¨Unicodeã§ä½¿ãˆã‚‹8ビット符å·å˜ä½ã®æ–‡å­—符å·åŒ–å½¢å¼åŠã³æ–‡å­—符å·åŒ–スキーム。 + +æ­£å¼å称ã¯ã€ISO/IEC 10646ã§ã¯ “UCS Transformation Format 8â€ã€Unicodeã§ã¯ “Unicode Transformation Format-8†ã¨ã„ã†ã€‚両者ã¯ISO/IEC 10646ã¨Unicodeã®ã‚³ãƒ¼ãƒ‰é‡è¤‡ç¯„囲ã§äº’æ›æ€§ãŒã‚る。RFCã«ã‚‚仕様ãŒã‚ã‚‹[1]。 + +2ãƒã‚¤ãƒˆç›®ä»¥é™ã«ã€Œ/ã€ãªã©ã®ASCII文字ãŒç¾ã‚Œãªã„よã†ã«å·¥å¤«ã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‹ã‚‰ã€UTF-FSS (File System Safe) ã¨ã‚‚ã„ã‚れる。旧å称ã¯UTF-2。 + +データ交æ›æ–¹å¼ã€ãƒ•ã‚¡ã‚¤ãƒ«å½¢å¼ã¨ã—ã¦ã€ä¸€èˆ¬çš„ã«UTF-8ã¯ä½¿ã‚れる傾å‘ã«ã‚る。 + +当åˆã¯ã€ãƒ™ãƒ«ç ”究所ã«ãŠã„ã¦Plan 9ã§ç”¨ã„るエンコードã¨ã—ã¦ã€ãƒ­ãƒ–・パイクã«ã‚ˆã‚‹è¨­è¨ˆæŒ‡é‡ã®ã‚‚ã¨ã€ã‚±ãƒ³ãƒ»ãƒˆãƒ³ãƒ—ソンã«ã‚ˆã£ã¦è€ƒæ¡ˆã•ã‚ŒãŸ diff --git a/p4java/r19-1/src/test/resources/dev/test.txt b/p4java/r19-1/src/test/resources/dev/test.txt index 5e499d9c..75b299cd 100644 --- a/p4java/r19-1/src/test/resources/dev/test.txt +++ b/p4java/r19-1/src/test/resources/dev/test.txt @@ -1 +1 @@ -#test resources +#test resources diff --git a/p4java/src/main/java/com/perforce/p4java/impl/mapbased/rpc/msg/ServerMessage.java b/p4java/src/main/java/com/perforce/p4java/impl/mapbased/rpc/msg/ServerMessage.java index f91bed81..23562fb1 100644 --- a/p4java/src/main/java/com/perforce/p4java/impl/mapbased/rpc/msg/ServerMessage.java +++ b/p4java/src/main/java/com/perforce/p4java/impl/mapbased/rpc/msg/ServerMessage.java @@ -1,390 +1,390 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.perforce.p4java.impl.mapbased.rpc.msg; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.MessageGenericCode; -import com.perforce.p4java.exception.MessageSeverityCode; -import com.perforce.p4java.exception.MessageSubsystemCode; -import com.perforce.p4java.impl.generic.core.file.FilePath; -import com.perforce.p4java.impl.mapbased.rpc.CommandEnv; -import com.perforce.p4java.server.IServerMessage; -import com.perforce.p4java.server.ISingleServerMessage; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; - -import static com.perforce.p4java.common.base.ObjectUtils.isNull; -import static com.perforce.p4java.common.base.ObjectUtils.nonNull; -import static com.perforce.p4java.exception.MessageSeverityCode.E_FAILED; -import static com.perforce.p4java.exception.MessageSeverityCode.E_INFO; -import static com.perforce.p4java.exception.MessageSeverityCode.E_WARN; -import static org.apache.commons.lang3.StringUtils.EMPTY; - -public class ServerMessage implements IServerMessage { - private final ISingleServerMessage[] messages; - private final ISingleServerMessage highestSeverity; - private final String str; - - - public ServerMessage(final @Nonnull Iterable messages) { - this(asList(messages)); - } - - public ServerMessage(final @Nonnull List messages) { - if (messages.isEmpty()) { - throw new IllegalArgumentException("messages must not be empty"); - } - this.messages = messages.toArray(new ISingleServerMessage[0]); - - ISingleServerMessage highest = this.messages[0]; - StringBuilder sb = new StringBuilder(); - for (ISingleServerMessage message : this.messages) { - sb.append(message.getLocalizedMessage()).append('\n'); - if (highest.getSeverity() < message.getSeverity()) { - highest = message; - } - } - str = sb.toString().trim(); - highestSeverity = highest; - } - - @Nonnull - @Override - public Iterable getAllMessages() { - return Arrays.asList(messages); - } - - @Nonnull - @Override - public Iterable getForSeverity(final int minimum) { - return getForSeverity(minimum, false); - } - - @Nonnull - @Override - public Iterable getForExactSeverity(final int minimum) { - return getForSeverity(minimum, true); - } - - private Iterable getForSeverity(final int minimum, final boolean isExact) { - return () -> new Iterator() { - int next = -1; - @Override - public boolean hasNext() { - if (next < 0) { - advance(); - } - return next < messages.length; - } - - @Override - public ISingleServerMessage next() { - if (next < 0) { - advance(); - } - if (next >= messages.length) { - throw new NoSuchElementException(); - } - int pos = next; - advance(); - return messages[pos]; - } - - void advance() { - while (++next < messages.length) { - final int severity = messages[next].getSeverity(); - if ((isExact && severity == minimum) || (!isExact && severity >= minimum)) { - break; - } - } - } - }; - } - - @Override - public boolean hasSeverity(int minimum) { - return highestSeverity != null && highestSeverity.getSeverity() >= minimum; - } - - public boolean isExactSeverity(int value) { - return highestSeverity != null && highestSeverity.getSeverity() == value; - } - - @Override - public boolean isInfoOrError() { - return hasSeverity(E_INFO); - } - - @Override - public boolean isInfo() { - return isExactSeverity(E_INFO); - } - - @Override - public boolean isWarning() { - return isExactSeverity(E_WARN); - } - - @Override - public boolean isError() { - return hasSeverity(E_FAILED); - } - - @Nonnull - @Override - public String getFirstInfoString() { - for (ISingleServerMessage message : messages) { - if (message.getSeverity() == E_INFO) { - return message.getLocalizedMessage(); - } - } - return EMPTY; - } - - @Nonnull - @Override - public String getAllInfoStrings() { - return getAllInfoStrings("\n"); - } - - @Nonnull - @Override - public String getAllInfoStrings(@Nonnull String separator) { - StringBuilder ret = new StringBuilder(100); - for (ISingleServerMessage message : messages) { - if (message.getSeverity() == E_INFO) { - if (ret.length() > 0) { - ret.append(separator); - } - ret.append(message.getLocalizedMessage()); - } - } - return ret.toString(); - } - - @Override - public byte[] getBytes(final String charsetName) throws UnsupportedEncodingException { - String msg = toString() + CommandEnv.LINE_SEPARATOR; - return msg.getBytes(charsetName); - } - - @Nullable - @Override - public String getErrorOrInfoStr() { - return str; - } - - @Override - public String getLocalizedMessage() { - return highestSeverity.getLocalizedMessage(); - } - - @Override - public String getMessageFormat() { - return highestSeverity.getMessageFormat(); - } - - @Override - public int getSeverity() { - return highestSeverity.getSeverity(); - } - - @Override - public int getSubSystem() { - return highestSeverity.getSubSystem(); - } - - @Override - public int getGeneric() { - return highestSeverity.getGeneric(); - } - - @Override - public int getUniqueCode() { - return highestSeverity.getUniqueCode(); - } - - @Override - public int getSubCode() { - return highestSeverity.getSubCode(); - } - - @Override - public int getRawCode() { - return highestSeverity.getRawCode(); - } - - @Override - public String getCode() { - return highestSeverity.getCode(); - } - - /** - * - * @param fragment string to check against the messages - * @return true if the fragment is in one of the messages - * @deprecated - */ - @Override - public boolean hasMessageFragment(final String fragment) { - for (ISingleServerMessage msg: messages) { - if (msg.hasMessageFragment(fragment)) { - return true; - } - } - return false; - } - - @Override - public String toString() { - return str; - } - - - public static class SingleServerMessage implements ISingleServerMessage { - private final String localized; - private final String format; - private final int severity; - private final int subSystem; - private final int generic; - private final int rawCode; - private final int uniqueCode; - private final int subCode; - - public SingleServerMessage(String code, int index, Map map) { - int rawNum; - int tSeverity; - int tGeneric; - int tSubSystem; - int tSubCode; - try { - // See RequestException.setCodes for the decoding - // RpcMessage includes details on the argument count. - - rawNum = new Integer(code); // Really need an unsigned here... - tSeverity = ((rawNum >> 28) & 0x0f); - tGeneric = ((rawNum >> 16) & 0x0FF); - tSubSystem = ((rawNum >> 10) & 0x3F); - tSubCode = ((rawNum >> 0) & 0x3ff); - } catch (Exception exc) { - // If there's a conversion error, just let it return below - Log.exception(exc); - rawNum = 0; - tSeverity = MessageSeverityCode.E_EMPTY; - tGeneric = MessageGenericCode.EV_NONE; - tSubSystem = MessageSubsystemCode.ES_CLIENT; - tSubCode = 0; - } - rawCode = rawNum; - uniqueCode = rawNum & 0xffff; - severity = tSeverity; - subSystem = tSubSystem; - generic = tGeneric; - subCode = tSubCode; - - format = (String) map.get(RpcMessage.FMT + index); - localized = RpcMessage.interpolateArgs(format, map); - - - } - - /** @deprecated only useful in one place */ - public SingleServerMessage(String message) { - localized = format = message; - severity = MessageSeverityCode.E_INFO; - generic = MessageGenericCode.EV_NONE; - subSystem = MessageSubsystemCode.ES_CLIENT; - subCode = 0; - uniqueCode = rawCode = (severity << 28) | (generic << 16) | (subSystem << 10); - } - - @Override - public String getLocalizedMessage() { - return localized; - } - - @Override - public String getMessageFormat() { - return format; - } - - @Override - public int getSeverity() { - return severity; - } - - @Override - public int getSubSystem() { - return subSystem; - } - - @Override - public int getGeneric() { - return generic; - } - - @Override - public int getUniqueCode() { - return uniqueCode; - } - - @Override - public int getSubCode() { - return subCode; - } - - @Override - public int getRawCode() { - return rawCode; - } - - @Override - public String getCode() { - return getGeneric() + ":" + - getSubSystem() + ":" + - getSubCode() + " (" + getUniqueCode() + ")"; - } - - @Override - public boolean hasMessageFragment(final String fragment) { - if (fragment == null) { - return false; - } - return (nonNull(format) && format.toLowerCase().contains(fragment.toLowerCase())) - || (nonNull(localized) && localized.toLowerCase().contains(fragment.toLowerCase())); - } - - @Override - public String toString() { - return getLocalizedMessage(); - } - } - - private static List asList(Iterable iter) { - List ret = new ArrayList<>(); - for (T t : iter) { - ret.add(t); - } - return ret; - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.perforce.p4java.impl.mapbased.rpc.msg; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.MessageGenericCode; +import com.perforce.p4java.exception.MessageSeverityCode; +import com.perforce.p4java.exception.MessageSubsystemCode; +import com.perforce.p4java.impl.generic.core.file.FilePath; +import com.perforce.p4java.impl.mapbased.rpc.CommandEnv; +import com.perforce.p4java.server.IServerMessage; +import com.perforce.p4java.server.ISingleServerMessage; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import static com.perforce.p4java.common.base.ObjectUtils.isNull; +import static com.perforce.p4java.common.base.ObjectUtils.nonNull; +import static com.perforce.p4java.exception.MessageSeverityCode.E_FAILED; +import static com.perforce.p4java.exception.MessageSeverityCode.E_INFO; +import static com.perforce.p4java.exception.MessageSeverityCode.E_WARN; +import static org.apache.commons.lang3.StringUtils.EMPTY; + +public class ServerMessage implements IServerMessage { + private final ISingleServerMessage[] messages; + private final ISingleServerMessage highestSeverity; + private final String str; + + + public ServerMessage(final @Nonnull Iterable messages) { + this(asList(messages)); + } + + public ServerMessage(final @Nonnull List messages) { + if (messages.isEmpty()) { + throw new IllegalArgumentException("messages must not be empty"); + } + this.messages = messages.toArray(new ISingleServerMessage[0]); + + ISingleServerMessage highest = this.messages[0]; + StringBuilder sb = new StringBuilder(); + for (ISingleServerMessage message : this.messages) { + sb.append(message.getLocalizedMessage()).append('\n'); + if (highest.getSeverity() < message.getSeverity()) { + highest = message; + } + } + str = sb.toString().trim(); + highestSeverity = highest; + } + + @Nonnull + @Override + public Iterable getAllMessages() { + return Arrays.asList(messages); + } + + @Nonnull + @Override + public Iterable getForSeverity(final int minimum) { + return getForSeverity(minimum, false); + } + + @Nonnull + @Override + public Iterable getForExactSeverity(final int minimum) { + return getForSeverity(minimum, true); + } + + private Iterable getForSeverity(final int minimum, final boolean isExact) { + return () -> new Iterator() { + int next = -1; + @Override + public boolean hasNext() { + if (next < 0) { + advance(); + } + return next < messages.length; + } + + @Override + public ISingleServerMessage next() { + if (next < 0) { + advance(); + } + if (next >= messages.length) { + throw new NoSuchElementException(); + } + int pos = next; + advance(); + return messages[pos]; + } + + void advance() { + while (++next < messages.length) { + final int severity = messages[next].getSeverity(); + if ((isExact && severity == minimum) || (!isExact && severity >= minimum)) { + break; + } + } + } + }; + } + + @Override + public boolean hasSeverity(int minimum) { + return highestSeverity != null && highestSeverity.getSeverity() >= minimum; + } + + public boolean isExactSeverity(int value) { + return highestSeverity != null && highestSeverity.getSeverity() == value; + } + + @Override + public boolean isInfoOrError() { + return hasSeverity(E_INFO); + } + + @Override + public boolean isInfo() { + return isExactSeverity(E_INFO); + } + + @Override + public boolean isWarning() { + return isExactSeverity(E_WARN); + } + + @Override + public boolean isError() { + return hasSeverity(E_FAILED); + } + + @Nonnull + @Override + public String getFirstInfoString() { + for (ISingleServerMessage message : messages) { + if (message.getSeverity() == E_INFO) { + return message.getLocalizedMessage(); + } + } + return EMPTY; + } + + @Nonnull + @Override + public String getAllInfoStrings() { + return getAllInfoStrings("\n"); + } + + @Nonnull + @Override + public String getAllInfoStrings(@Nonnull String separator) { + StringBuilder ret = new StringBuilder(100); + for (ISingleServerMessage message : messages) { + if (message.getSeverity() == E_INFO) { + if (ret.length() > 0) { + ret.append(separator); + } + ret.append(message.getLocalizedMessage()); + } + } + return ret.toString(); + } + + @Override + public byte[] getBytes(final String charsetName) throws UnsupportedEncodingException { + String msg = toString() + CommandEnv.LINE_SEPARATOR; + return msg.getBytes(charsetName); + } + + @Nullable + @Override + public String getErrorOrInfoStr() { + return str; + } + + @Override + public String getLocalizedMessage() { + return highestSeverity.getLocalizedMessage(); + } + + @Override + public String getMessageFormat() { + return highestSeverity.getMessageFormat(); + } + + @Override + public int getSeverity() { + return highestSeverity.getSeverity(); + } + + @Override + public int getSubSystem() { + return highestSeverity.getSubSystem(); + } + + @Override + public int getGeneric() { + return highestSeverity.getGeneric(); + } + + @Override + public int getUniqueCode() { + return highestSeverity.getUniqueCode(); + } + + @Override + public int getSubCode() { + return highestSeverity.getSubCode(); + } + + @Override + public int getRawCode() { + return highestSeverity.getRawCode(); + } + + @Override + public String getCode() { + return highestSeverity.getCode(); + } + + /** + * + * @param fragment string to check against the messages + * @return true if the fragment is in one of the messages + * @deprecated + */ + @Override + public boolean hasMessageFragment(final String fragment) { + for (ISingleServerMessage msg: messages) { + if (msg.hasMessageFragment(fragment)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return str; + } + + + public static class SingleServerMessage implements ISingleServerMessage { + private final String localized; + private final String format; + private final int severity; + private final int subSystem; + private final int generic; + private final int rawCode; + private final int uniqueCode; + private final int subCode; + + public SingleServerMessage(String code, int index, Map map) { + int rawNum; + int tSeverity; + int tGeneric; + int tSubSystem; + int tSubCode; + try { + // See RequestException.setCodes for the decoding + // RpcMessage includes details on the argument count. + + rawNum = new Integer(code); // Really need an unsigned here... + tSeverity = ((rawNum >> 28) & 0x0f); + tGeneric = ((rawNum >> 16) & 0x0FF); + tSubSystem = ((rawNum >> 10) & 0x3F); + tSubCode = ((rawNum >> 0) & 0x3ff); + } catch (Exception exc) { + // If there's a conversion error, just let it return below + Log.exception(exc); + rawNum = 0; + tSeverity = MessageSeverityCode.E_EMPTY; + tGeneric = MessageGenericCode.EV_NONE; + tSubSystem = MessageSubsystemCode.ES_CLIENT; + tSubCode = 0; + } + rawCode = rawNum; + uniqueCode = rawNum & 0xffff; + severity = tSeverity; + subSystem = tSubSystem; + generic = tGeneric; + subCode = tSubCode; + + format = (String) map.get(RpcMessage.FMT + index); + localized = RpcMessage.interpolateArgs(format, map); + + + } + + /** @deprecated only useful in one place */ + public SingleServerMessage(String message) { + localized = format = message; + severity = MessageSeverityCode.E_INFO; + generic = MessageGenericCode.EV_NONE; + subSystem = MessageSubsystemCode.ES_CLIENT; + subCode = 0; + uniqueCode = rawCode = (severity << 28) | (generic << 16) | (subSystem << 10); + } + + @Override + public String getLocalizedMessage() { + return localized; + } + + @Override + public String getMessageFormat() { + return format; + } + + @Override + public int getSeverity() { + return severity; + } + + @Override + public int getSubSystem() { + return subSystem; + } + + @Override + public int getGeneric() { + return generic; + } + + @Override + public int getUniqueCode() { + return uniqueCode; + } + + @Override + public int getSubCode() { + return subCode; + } + + @Override + public int getRawCode() { + return rawCode; + } + + @Override + public String getCode() { + return getGeneric() + ":" + + getSubSystem() + ":" + + getSubCode() + " (" + getUniqueCode() + ")"; + } + + @Override + public boolean hasMessageFragment(final String fragment) { + if (fragment == null) { + return false; + } + return (nonNull(format) && format.toLowerCase().contains(fragment.toLowerCase())) + || (nonNull(localized) && localized.toLowerCase().contains(fragment.toLowerCase())); + } + + @Override + public String toString() { + return getLocalizedMessage(); + } + } + + private static List asList(Iterable iter) { + List ret = new ArrayList<>(); + for (T t : iter) { + ret.add(t); + } + return ret; + } +} diff --git a/p4java/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java b/p4java/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java index 955735f4..75e9be6e 100644 --- a/p4java/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java +++ b/p4java/src/main/java/com/perforce/p4java/impl/mapbased/rpc/sys/helper/AppleFileHelper.java @@ -1,118 +1,118 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.impl.mapbased.rpc.sys.helper; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; -import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFile; -import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFileType; -import com.perforce.p4java.io.apple.AppleFileData; -import com.perforce.p4java.io.apple.AppleFileDecoder; - -/** - * Helper class for handling Apple files. - */ -public class AppleFileHelper { - - /** - * Extract the data fork and the resource fork from the Apple file. - * - * @param file the Apple file - */ - public static void extractFile(RpcPerforceFile file) { - if (file.getFileType() == RpcPerforceFileType.FST_APPLEFILE) { - FileOutputStream fosData = null; - FileOutputStream fosResource = null; - try { - byte[] data = AppleFileHelper.getBytesFromFile(file); - AppleFileData fileData = new AppleFileData(data); - AppleFileDecoder appleFile = new AppleFileDecoder(fileData); - appleFile.extract(); - fosData = new FileOutputStream(file); - AppleFileData forkData = appleFile.getDataFork(); - if (forkData != AppleFileData.EMPTY_FILE_DATA) { - fosData.write(forkData.getBytes()); - } - String resourceFilePath = file.getParent() + File.separator + "%" + file.getName(); - RpcPerforceFile targetResourceFile = new RpcPerforceFile(resourceFilePath, file.getFileType()); - fosResource = new FileOutputStream(targetResourceFile); - AppleFileData forkResource = appleFile.getResourceFork(); - if (forkResource != AppleFileData.EMPTY_FILE_DATA) { - fosResource.write(forkResource.getBytes()); - } - } catch (IOException e) { - Log.error("Problem handling the Apple file: " + file.getName()); - } catch (FileDecoderException e) { - Log.error("Problem decoding the Apple file: " + file.getName()); - } finally { - if (fosData != null) { - try { - fosData.close(); - } catch (Exception e) { - // Do nothing - } - } - if (fosResource != null) { - try { - fosResource.close(); - } catch (Exception e) { - // Do nothing - } - } - } - } - } - - /** - * Gets the bytes from file. - * - * @param file - * the file - * @return the bytes from file - * @throws IOException - * Signals that an I/O exception has occurred. - */ - public static byte[] getBytesFromFile(File file) throws IOException { - InputStream is = new FileInputStream(file); - - long length = file.length(); - if (length > Integer.MAX_VALUE) { - // File is too large - throw new IOException("Apple file too large for decoding."); - } - - byte[] bytes = new byte[(int) length]; - int offset = 0; - int numRead = 0; - - try { - while (offset < bytes.length - && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { - offset += numRead; - } - // Ensure all the bytes have been read in - if (offset < bytes.length) { - throw new IOException( - "Could not completely read the Apple file " - + file.getName()); - } - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - // Do nothing - } - } - } - - return bytes; - } -} +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.impl.mapbased.rpc.sys.helper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; +import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFile; +import com.perforce.p4java.impl.mapbased.rpc.sys.RpcPerforceFileType; +import com.perforce.p4java.io.apple.AppleFileData; +import com.perforce.p4java.io.apple.AppleFileDecoder; + +/** + * Helper class for handling Apple files. + */ +public class AppleFileHelper { + + /** + * Extract the data fork and the resource fork from the Apple file. + * + * @param file the Apple file + */ + public static void extractFile(RpcPerforceFile file) { + if (file.getFileType() == RpcPerforceFileType.FST_APPLEFILE) { + FileOutputStream fosData = null; + FileOutputStream fosResource = null; + try { + byte[] data = AppleFileHelper.getBytesFromFile(file); + AppleFileData fileData = new AppleFileData(data); + AppleFileDecoder appleFile = new AppleFileDecoder(fileData); + appleFile.extract(); + fosData = new FileOutputStream(file); + AppleFileData forkData = appleFile.getDataFork(); + if (forkData != AppleFileData.EMPTY_FILE_DATA) { + fosData.write(forkData.getBytes()); + } + String resourceFilePath = file.getParent() + File.separator + "%" + file.getName(); + RpcPerforceFile targetResourceFile = new RpcPerforceFile(resourceFilePath, file.getFileType()); + fosResource = new FileOutputStream(targetResourceFile); + AppleFileData forkResource = appleFile.getResourceFork(); + if (forkResource != AppleFileData.EMPTY_FILE_DATA) { + fosResource.write(forkResource.getBytes()); + } + } catch (IOException e) { + Log.error("Problem handling the Apple file: " + file.getName()); + } catch (FileDecoderException e) { + Log.error("Problem decoding the Apple file: " + file.getName()); + } finally { + if (fosData != null) { + try { + fosData.close(); + } catch (Exception e) { + // Do nothing + } + } + if (fosResource != null) { + try { + fosResource.close(); + } catch (Exception e) { + // Do nothing + } + } + } + } + } + + /** + * Gets the bytes from file. + * + * @param file + * the file + * @return the bytes from file + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public static byte[] getBytesFromFile(File file) throws IOException { + InputStream is = new FileInputStream(file); + + long length = file.length(); + if (length > Integer.MAX_VALUE) { + // File is too large + throw new IOException("Apple file too large for decoding."); + } + + byte[] bytes = new byte[(int) length]; + int offset = 0; + int numRead = 0; + + try { + while (offset < bytes.length + && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { + offset += numRead; + } + // Ensure all the bytes have been read in + if (offset < bytes.length) { + throw new IOException( + "Could not completely read the Apple file " + + file.getName()); + } + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + // Do nothing + } + } + } + + return bytes; + } +} diff --git a/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFile.java b/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFile.java index 5b89f33a..615bfcc9 100644 --- a/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFile.java +++ b/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFile.java @@ -1,735 +1,735 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; - -/** - * This abstract class handles AppleSingle/Double files. It contains a common - * method to verify the Apple file, and figure out if it is an AppleSingle or - * AppleDouble formatted file. - *

- * - * The AppleSingle format is a representation of Macintosh files as one - * consecutive stream of bytes. AppleSingle combines the data fork, resource - * fork and the related Finder meta-file information into a single file. - *

- * - * The AppleDouble format stores the data fork, resource fork as two separate - * files. AppleDouble leaves the data fork in its original format, and the - * resource fork and Finder information were combined into a second file. - *

- * - * Apple defined the magic number for the AppleSingle format as 0x00051600, and - * the magic number for the AppleDouble format as 0x00051607. - * - *

- * AppleSingle file header: 
- * 
- * Field Length
- * ----- ------
- * Magic number ------- 4 bytes
- * Version number ------ 4 bytes
- * Filler ------------- 16 bytes
- * Number of entries ----- 2 bytes
- * 
- * Entry descriptor for each entry:
- * Entry ID ------ 4 bytes
- * Offset -------- 4 bytes
- * Length -------- 4 bytes
- * 
- * Apple reserved entry IDs:
- * 
- * Data Fork -------- 1 Data fork
- * Resource Fork ----- 2 Resource fork
- * Real Name -------- 3 File's name as created on home file system
- * Comment --------- 4 Standard Macintosh comment
- * Icon, B&W -------- 5 Standard Macintosh black and white icon
- * Icon, Color -------- 6 Macintosh color icon
- * File Dates Info ------8 File creation date, modification date, and so on
- * Finder Info -------- 9 Standard Macintosh Finder information
- * Macintosh File Info ---10 Macintosh file information, attributes, and so on
- * ProDOS File Info -----11 ProDOS file information, attributes, and so on
- * MS-DOS File Info ----12 MS-DOS file information, attributes, and so on
- * Short Name --------13 AFP short name
- * AFP File Info ------- 14 AFP file information, attributes, and so on
- * Directory ID --------15 AFP directory ID
- * 
- * - * See RFC 1740 for reference: http://tools.ietf.org/html/rfc1740 - */ -public abstract class AppleFile { - - /** The Apple file format: AppleSingle, AppleDouble, default to unknown. */ - protected FileFormat format = FileFormat.UNKNOWN; - - /** The raw Apple file. */ - protected AppleFileData fileData = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 1: Data fork. */ - protected AppleFileData dataFork = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 2: Resource fork. */ - protected AppleFileData resourceFork = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 3: File's name as created on home file system. */ - protected AppleFileData realName = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 4: Standard Macintosh comment. */ - protected AppleFileData comment = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 5: Standard Macintosh black and white icon. */ - protected AppleFileData iconBW = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 6: Macintosh color icon. */ - protected AppleFileData iconColor = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 8: File creation date, modification date, and so on. */ - protected AppleFileData fileDatesInfo = AppleFileData.EMPTY_FILE_DATA; - - /** The file dates info entry. */ - protected FileDatesInfoEntry fileDatesInfoEntry = null; - - /** Entry 9: Standard Macintosh Finder information. */ - protected AppleFileData finderInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 10: Macintosh file information, attributes, and so on. */ - protected AppleFileData macintoshInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 11: ProDOS file information, attributes, and so on. */ - protected AppleFileData proDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 12: MS-DOS file information, attributes, and so on. */ - protected AppleFileData msDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 13: AFP short name. */ - protected AppleFileData shortName = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 14: AFP file information, attributes, and so on. */ - protected AppleFileData afpFileInfo = AppleFileData.EMPTY_FILE_DATA; - - /** Entry 15: AFP directory ID. */ - protected AppleFileData directoryID = AppleFileData.EMPTY_FILE_DATA; - - /** The num entries. */ - protected int numEntries = 0; - - /** - * The Apple file format. - */ - public enum FileFormat { - - APPLE_SINGLE, - APPLE_DOUBLE, - UNKNOWN; - - /** - * Return a suitable Apple file format as inferred from the passed-in - * string. Otherwise return the UNKNOWN file format. - * - * @param fileFormat - * the file format - * @return the FileFormat - */ - public static FileFormat fromString(String fileFormat) { - if (fileFormat == null) { - return null; - } - - try { - return FileFormat.valueOf(fileFormat.toUpperCase()); - } catch (IllegalArgumentException iae) { - Log.error("Bad conversion attempt in FileFormat.fromString; string: " - + fileFormat + "; message: " + iae.getMessage()); - Log.exception(iae); - return UNKNOWN; - } - } - }; - - /** - * This class represents the file dates. - */ - public class FileDatesInfoEntry { - - /** The create time. */ - private int createTime = Integer.MIN_VALUE; - - /** The modify time. */ - private int modifyTime = Integer.MIN_VALUE; - - /** The backup time. */ - private int backupTime = Integer.MIN_VALUE; - - /** The access time. */ - private int accessTime = Integer.MIN_VALUE; - - /** - * Instantiates a new file dates info entry. - */ - public FileDatesInfoEntry() { - - } - - /** - * Gets the creates the time. - * - * @return the creates the time - */ - public int getCreateTime() { - return createTime; - } - - /** - * Sets the creates the time. - * - * @param createTime - * the new creates the time - */ - public void setCreateTime(int createTime) { - this.createTime = createTime; - } - - /** - * Gets the modify time. - * - * @return the modify time - */ - public int getModifyTime() { - return modifyTime; - } - - /** - * Sets the modify time. - * - * @param modifyTime - * the new modify time - */ - public void setModifyTime(int modifyTime) { - this.modifyTime = modifyTime; - } - - /** - * Gets the backup time. - * - * @return the backup time - */ - public int getBackupTime() { - return backupTime; - } - - /** - * Sets the backup time. - * - * @param backupTime - * the new backup time - */ - public void setBackupTime(int backupTime) { - this.backupTime = backupTime; - } - - /** - * Gets the access time. - * - * @return the access time - */ - public int getAccessTime() { - return accessTime; - } - - /** - * Sets the access time. - * - * @param accessTime - * the new access time - */ - public void setAccessTime(int accessTime) { - this.accessTime = accessTime; - } - } - - /** - * Sets the num entries. - * - * @param numEntries - * the new num entries - */ - public void setNumEntries(int numEntries) { - this.numEntries = numEntries; - } - - /** - * Verify the validity of the Apple file. - * - * @throws FileDecoderException - * the file decoder exception - */ - @SuppressWarnings("unused") - protected void verify() throws FileDecoderException { - byte[] data = this.fileData.getData(); - int offset = this.fileData.getOffset(); - int length = this.fileData.getLength(); - int position = offset; - if (length < 26) { - throw new FileDecoderException("File is too short"); - } - - /* Magic number */ - int magic = 0; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - magic <<= 8; - magic |= data[(position++)] & 0xFF; - - /* Check Apple file format: AppleSingle or AppleDobule */ - if (magic == 0x00051600) { - this.format = FileFormat.APPLE_SINGLE; - } else if (magic == 0x00051607) { - this.format = FileFormat.APPLE_DOUBLE; - } else { - throw new FileDecoderException("Invalid Apple file magic number."); - } - - /* Version number */ - int version = 0; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - version <<= 8; - version |= data[(position++)] & 0xFF; - if (version != 0x00020000) { - throw new FileDecoderException("Unknown Apple file version"); - } - - /* Filler */ - position += 16; - - /* Number of entries */ - this.numEntries = 0; - this.numEntries |= data[(position++)] & 0xFF; - this.numEntries <<= 8; - this.numEntries |= data[(position++)] & 0xFF; - if (length < 26 + 12 * this.numEntries) { - throw new FileDecoderException("Corrupt Apple file data."); - } - - /* Check entries */ - int entryId = 0; - int entryOffset = 0; - int entryLength = 0; - int contentPosition = 26 + 12 * this.numEntries; - for (int i = 0; i < this.numEntries; i++) { - position = 26 + i * 12; - /* Entry ID */ - entryId = 0; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - entryId <<= 8; - entryId |= data[(position++)] & 0xFF; - - /* Entry offset */ - entryOffset = 0; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(position++)] & 0xFF; - entryOffset &= 0x7FFFFFFF; - - /* Entry length */ - entryLength = 0; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(position++)] & 0xFF; - entryLength &= 0x7FFFFFFF; - if ((entryOffset < contentPosition) - || (length < entryOffset + entryLength)) { - throw new FileDecoderException("Corrupt Apple file data."); - } - } - } - - /** - * Extract file dates. - * - * @param data the data - * @param offset the offset - * @param length the length - */ - protected void extractFileDates(byte[] data, int offset, int length) { - if ((0 > offset) || (offset > data.length)) - throw new IndexOutOfBoundsException(); - if ((0 > length) || (length > data.length - offset)) - throw new IndexOutOfBoundsException(); - - int position = offset; - - int createTime = 0; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - createTime <<= 8; - createTime |= data[(position++)] & 0xFF; - int modifyTime = 0; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - modifyTime <<= 8; - modifyTime |= data[(position++)] & 0xFF; - int backupTime = 0; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - backupTime <<= 8; - backupTime |= data[(position++)] & 0xFF; - int accessTime = 0; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - accessTime <<= 8; - accessTime |= data[(position++)] & 0xFF; - - this.fileDatesInfoEntry = new FileDatesInfoEntry(); - fileDatesInfoEntry.setCreateTime(createTime); - fileDatesInfoEntry.setModifyTime(modifyTime); - fileDatesInfoEntry.setBackupTime(backupTime); - fileDatesInfoEntry.setAccessTime(accessTime); - } - - /** - * Gets the format. - * - * @return the format - */ - public FileFormat getFormat() { - return format; - } - - /** - * Sets the format. - * - * @param format - * the new format - */ - public void setFormat(FileFormat format) { - this.format = format; - } - - /** - * Gets the file data. - * - * @return the file data - */ - public AppleFileData getFileData() { - return fileData; - } - - /** - * Sets the file data. - * - * @param fileData - * the new file data - */ - public void setFileData(AppleFileData fileData) { - this.fileData = fileData; - } - - /** - * Gets the data fork. - * - * @return the data fork - */ - public AppleFileData getDataFork() { - return dataFork; - } - - /** - * Sets the data fork. - * - * @param dataFork - * the new data fork - */ - public void setDataFork(AppleFileData dataFork) { - this.dataFork = dataFork; - } - - /** - * Gets the resource fork. - * - * @return the resource fork - */ - public AppleFileData getResourceFork() { - return resourceFork; - } - - /** - * Sets the resource fork. - * - * @param resourceFork - * the new resource fork - */ - public void setResourceFork(AppleFileData resourceFork) { - this.resourceFork = resourceFork; - } - - /** - * Gets the real name. - * - * @return the real name - */ - public AppleFileData getRealName() { - return realName; - } - - /** - * Sets the real name. - * - * @param realName - * the new real name - */ - public void setRealName(AppleFileData realName) { - this.realName = realName; - } - - /** - * Gets the comment. - * - * @return the comment - */ - public AppleFileData getComment() { - return comment; - } - - /** - * Sets the comment. - * - * @param comment - * the new comment - */ - public void setComment(AppleFileData comment) { - this.comment = comment; - } - - /** - * Gets the icon bw. - * - * @return the icon bw - */ - public AppleFileData getIconBW() { - return iconBW; - } - - /** - * Sets the icon bw. - * - * @param iconBW - * the new icon bw - */ - public void setIconBW(AppleFileData iconBW) { - this.iconBW = iconBW; - } - - /** - * Gets the icon color. - * - * @return the icon color - */ - public AppleFileData getIconColor() { - return iconColor; - } - - /** - * Sets the icon color. - * - * @param iconColor - * the new icon color - */ - public void setIconColor(AppleFileData iconColor) { - this.iconColor = iconColor; - } - - /** - * Gets the file dates info. - * - * @return the file dates info - */ - public AppleFileData getFileDatesInfo() { - return fileDatesInfo; - } - - /** - * Sets the file dates info. - * - * @param fileDatesInfo - * the new file dates info - */ - public void setFileDatesInfo(AppleFileData fileDatesInfo) { - this.fileDatesInfo = fileDatesInfo; - } - - /** - * Gets the finder info. - * - * @return the finder info - */ - public AppleFileData getFinderInfo() { - return finderInfo; - } - - /** - * Sets the finder info. - * - * @param finderInfo - * the new finder info - */ - public void setFinderInfo(AppleFileData finderInfo) { - this.finderInfo = finderInfo; - } - - /** - * Gets the macintosh info. - * - * @return the macintosh info - */ - public AppleFileData getMacintoshInfo() { - return macintoshInfo; - } - - /** - * Sets the macintosh info. - * - * @param macintoshInfo - * the new macintosh info - */ - public void setMacintoshInfo(AppleFileData macintoshInfo) { - this.macintoshInfo = macintoshInfo; - } - - /** - * Gets the pro dos file info. - * - * @return the pro dos file info - */ - public AppleFileData getProDOSFileInfo() { - return proDOSFileInfo; - } - - /** - * Sets the pro dos file info. - * - * @param proDOSFileInfo - * the new pro dos file info - */ - public void setProDOSFileInfo(AppleFileData proDOSFileInfo) { - this.proDOSFileInfo = proDOSFileInfo; - } - - /** - * Gets the ms dos file info. - * - * @return the ms dos file info - */ - public AppleFileData getMsDOSFileInfo() { - return msDOSFileInfo; - } - - /** - * Sets the ms dos file info. - * - * @param msDOSFileInfo - * the new ms dos file info - */ - public void setMsDOSFileInfo(AppleFileData msDOSFileInfo) { - this.msDOSFileInfo = msDOSFileInfo; - } - - /** - * Gets the short name. - * - * @return the short name - */ - public AppleFileData getShortName() { - return shortName; - } - - /** - * Sets the short name. - * - * @param shortName - * the new short name - */ - public void setShortName(AppleFileData shortName) { - this.shortName = shortName; - } - - /** - * Gets the afp file info. - * - * @return the afp file info - */ - public AppleFileData getAfpFileInfo() { - return afpFileInfo; - } - - /** - * Sets the afp file info. - * - * @param afpFileInfo - * the new afp file info - */ - public void setAfpFileInfo(AppleFileData afpFileInfo) { - this.afpFileInfo = afpFileInfo; - } - - /** - * Gets the directory id. - * - * @return the directory id - */ - public AppleFileData getDirectoryID() { - return directoryID; - } - - /** - * Sets the directory id. - * - * @param directoryID - * the new directory id - */ - public void setDirectoryID(AppleFileData directoryID) { - this.directoryID = directoryID; - } - - /** - * Gets the num entries. - * - * @return the num entries - */ - public int getNumEntries() { - return numEntries; - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; + +/** + * This abstract class handles AppleSingle/Double files. It contains a common + * method to verify the Apple file, and figure out if it is an AppleSingle or + * AppleDouble formatted file. + *

+ * + * The AppleSingle format is a representation of Macintosh files as one + * consecutive stream of bytes. AppleSingle combines the data fork, resource + * fork and the related Finder meta-file information into a single file. + *

+ * + * The AppleDouble format stores the data fork, resource fork as two separate + * files. AppleDouble leaves the data fork in its original format, and the + * resource fork and Finder information were combined into a second file. + *

+ * + * Apple defined the magic number for the AppleSingle format as 0x00051600, and + * the magic number for the AppleDouble format as 0x00051607. + * + *

+ * AppleSingle file header: 
+ * 
+ * Field Length
+ * ----- ------
+ * Magic number ------- 4 bytes
+ * Version number ------ 4 bytes
+ * Filler ------------- 16 bytes
+ * Number of entries ----- 2 bytes
+ * 
+ * Entry descriptor for each entry:
+ * Entry ID ------ 4 bytes
+ * Offset -------- 4 bytes
+ * Length -------- 4 bytes
+ * 
+ * Apple reserved entry IDs:
+ * 
+ * Data Fork -------- 1 Data fork
+ * Resource Fork ----- 2 Resource fork
+ * Real Name -------- 3 File's name as created on home file system
+ * Comment --------- 4 Standard Macintosh comment
+ * Icon, B&W -------- 5 Standard Macintosh black and white icon
+ * Icon, Color -------- 6 Macintosh color icon
+ * File Dates Info ------8 File creation date, modification date, and so on
+ * Finder Info -------- 9 Standard Macintosh Finder information
+ * Macintosh File Info ---10 Macintosh file information, attributes, and so on
+ * ProDOS File Info -----11 ProDOS file information, attributes, and so on
+ * MS-DOS File Info ----12 MS-DOS file information, attributes, and so on
+ * Short Name --------13 AFP short name
+ * AFP File Info ------- 14 AFP file information, attributes, and so on
+ * Directory ID --------15 AFP directory ID
+ * 
+ * + * See RFC 1740 for reference: http://tools.ietf.org/html/rfc1740 + */ +public abstract class AppleFile { + + /** The Apple file format: AppleSingle, AppleDouble, default to unknown. */ + protected FileFormat format = FileFormat.UNKNOWN; + + /** The raw Apple file. */ + protected AppleFileData fileData = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 1: Data fork. */ + protected AppleFileData dataFork = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 2: Resource fork. */ + protected AppleFileData resourceFork = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 3: File's name as created on home file system. */ + protected AppleFileData realName = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 4: Standard Macintosh comment. */ + protected AppleFileData comment = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 5: Standard Macintosh black and white icon. */ + protected AppleFileData iconBW = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 6: Macintosh color icon. */ + protected AppleFileData iconColor = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 8: File creation date, modification date, and so on. */ + protected AppleFileData fileDatesInfo = AppleFileData.EMPTY_FILE_DATA; + + /** The file dates info entry. */ + protected FileDatesInfoEntry fileDatesInfoEntry = null; + + /** Entry 9: Standard Macintosh Finder information. */ + protected AppleFileData finderInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 10: Macintosh file information, attributes, and so on. */ + protected AppleFileData macintoshInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 11: ProDOS file information, attributes, and so on. */ + protected AppleFileData proDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 12: MS-DOS file information, attributes, and so on. */ + protected AppleFileData msDOSFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 13: AFP short name. */ + protected AppleFileData shortName = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 14: AFP file information, attributes, and so on. */ + protected AppleFileData afpFileInfo = AppleFileData.EMPTY_FILE_DATA; + + /** Entry 15: AFP directory ID. */ + protected AppleFileData directoryID = AppleFileData.EMPTY_FILE_DATA; + + /** The num entries. */ + protected int numEntries = 0; + + /** + * The Apple file format. + */ + public enum FileFormat { + + APPLE_SINGLE, + APPLE_DOUBLE, + UNKNOWN; + + /** + * Return a suitable Apple file format as inferred from the passed-in + * string. Otherwise return the UNKNOWN file format. + * + * @param fileFormat + * the file format + * @return the FileFormat + */ + public static FileFormat fromString(String fileFormat) { + if (fileFormat == null) { + return null; + } + + try { + return FileFormat.valueOf(fileFormat.toUpperCase()); + } catch (IllegalArgumentException iae) { + Log.error("Bad conversion attempt in FileFormat.fromString; string: " + + fileFormat + "; message: " + iae.getMessage()); + Log.exception(iae); + return UNKNOWN; + } + } + }; + + /** + * This class represents the file dates. + */ + public class FileDatesInfoEntry { + + /** The create time. */ + private int createTime = Integer.MIN_VALUE; + + /** The modify time. */ + private int modifyTime = Integer.MIN_VALUE; + + /** The backup time. */ + private int backupTime = Integer.MIN_VALUE; + + /** The access time. */ + private int accessTime = Integer.MIN_VALUE; + + /** + * Instantiates a new file dates info entry. + */ + public FileDatesInfoEntry() { + + } + + /** + * Gets the creates the time. + * + * @return the creates the time + */ + public int getCreateTime() { + return createTime; + } + + /** + * Sets the creates the time. + * + * @param createTime + * the new creates the time + */ + public void setCreateTime(int createTime) { + this.createTime = createTime; + } + + /** + * Gets the modify time. + * + * @return the modify time + */ + public int getModifyTime() { + return modifyTime; + } + + /** + * Sets the modify time. + * + * @param modifyTime + * the new modify time + */ + public void setModifyTime(int modifyTime) { + this.modifyTime = modifyTime; + } + + /** + * Gets the backup time. + * + * @return the backup time + */ + public int getBackupTime() { + return backupTime; + } + + /** + * Sets the backup time. + * + * @param backupTime + * the new backup time + */ + public void setBackupTime(int backupTime) { + this.backupTime = backupTime; + } + + /** + * Gets the access time. + * + * @return the access time + */ + public int getAccessTime() { + return accessTime; + } + + /** + * Sets the access time. + * + * @param accessTime + * the new access time + */ + public void setAccessTime(int accessTime) { + this.accessTime = accessTime; + } + } + + /** + * Sets the num entries. + * + * @param numEntries + * the new num entries + */ + public void setNumEntries(int numEntries) { + this.numEntries = numEntries; + } + + /** + * Verify the validity of the Apple file. + * + * @throws FileDecoderException + * the file decoder exception + */ + @SuppressWarnings("unused") + protected void verify() throws FileDecoderException { + byte[] data = this.fileData.getData(); + int offset = this.fileData.getOffset(); + int length = this.fileData.getLength(); + int position = offset; + if (length < 26) { + throw new FileDecoderException("File is too short"); + } + + /* Magic number */ + int magic = 0; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + magic <<= 8; + magic |= data[(position++)] & 0xFF; + + /* Check Apple file format: AppleSingle or AppleDobule */ + if (magic == 0x00051600) { + this.format = FileFormat.APPLE_SINGLE; + } else if (magic == 0x00051607) { + this.format = FileFormat.APPLE_DOUBLE; + } else { + throw new FileDecoderException("Invalid Apple file magic number."); + } + + /* Version number */ + int version = 0; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + version <<= 8; + version |= data[(position++)] & 0xFF; + if (version != 0x00020000) { + throw new FileDecoderException("Unknown Apple file version"); + } + + /* Filler */ + position += 16; + + /* Number of entries */ + this.numEntries = 0; + this.numEntries |= data[(position++)] & 0xFF; + this.numEntries <<= 8; + this.numEntries |= data[(position++)] & 0xFF; + if (length < 26 + 12 * this.numEntries) { + throw new FileDecoderException("Corrupt Apple file data."); + } + + /* Check entries */ + int entryId = 0; + int entryOffset = 0; + int entryLength = 0; + int contentPosition = 26 + 12 * this.numEntries; + for (int i = 0; i < this.numEntries; i++) { + position = 26 + i * 12; + /* Entry ID */ + entryId = 0; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + entryId <<= 8; + entryId |= data[(position++)] & 0xFF; + + /* Entry offset */ + entryOffset = 0; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(position++)] & 0xFF; + entryOffset &= 0x7FFFFFFF; + + /* Entry length */ + entryLength = 0; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(position++)] & 0xFF; + entryLength &= 0x7FFFFFFF; + if ((entryOffset < contentPosition) + || (length < entryOffset + entryLength)) { + throw new FileDecoderException("Corrupt Apple file data."); + } + } + } + + /** + * Extract file dates. + * + * @param data the data + * @param offset the offset + * @param length the length + */ + protected void extractFileDates(byte[] data, int offset, int length) { + if ((0 > offset) || (offset > data.length)) + throw new IndexOutOfBoundsException(); + if ((0 > length) || (length > data.length - offset)) + throw new IndexOutOfBoundsException(); + + int position = offset; + + int createTime = 0; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + createTime <<= 8; + createTime |= data[(position++)] & 0xFF; + int modifyTime = 0; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + modifyTime <<= 8; + modifyTime |= data[(position++)] & 0xFF; + int backupTime = 0; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + backupTime <<= 8; + backupTime |= data[(position++)] & 0xFF; + int accessTime = 0; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + accessTime <<= 8; + accessTime |= data[(position++)] & 0xFF; + + this.fileDatesInfoEntry = new FileDatesInfoEntry(); + fileDatesInfoEntry.setCreateTime(createTime); + fileDatesInfoEntry.setModifyTime(modifyTime); + fileDatesInfoEntry.setBackupTime(backupTime); + fileDatesInfoEntry.setAccessTime(accessTime); + } + + /** + * Gets the format. + * + * @return the format + */ + public FileFormat getFormat() { + return format; + } + + /** + * Sets the format. + * + * @param format + * the new format + */ + public void setFormat(FileFormat format) { + this.format = format; + } + + /** + * Gets the file data. + * + * @return the file data + */ + public AppleFileData getFileData() { + return fileData; + } + + /** + * Sets the file data. + * + * @param fileData + * the new file data + */ + public void setFileData(AppleFileData fileData) { + this.fileData = fileData; + } + + /** + * Gets the data fork. + * + * @return the data fork + */ + public AppleFileData getDataFork() { + return dataFork; + } + + /** + * Sets the data fork. + * + * @param dataFork + * the new data fork + */ + public void setDataFork(AppleFileData dataFork) { + this.dataFork = dataFork; + } + + /** + * Gets the resource fork. + * + * @return the resource fork + */ + public AppleFileData getResourceFork() { + return resourceFork; + } + + /** + * Sets the resource fork. + * + * @param resourceFork + * the new resource fork + */ + public void setResourceFork(AppleFileData resourceFork) { + this.resourceFork = resourceFork; + } + + /** + * Gets the real name. + * + * @return the real name + */ + public AppleFileData getRealName() { + return realName; + } + + /** + * Sets the real name. + * + * @param realName + * the new real name + */ + public void setRealName(AppleFileData realName) { + this.realName = realName; + } + + /** + * Gets the comment. + * + * @return the comment + */ + public AppleFileData getComment() { + return comment; + } + + /** + * Sets the comment. + * + * @param comment + * the new comment + */ + public void setComment(AppleFileData comment) { + this.comment = comment; + } + + /** + * Gets the icon bw. + * + * @return the icon bw + */ + public AppleFileData getIconBW() { + return iconBW; + } + + /** + * Sets the icon bw. + * + * @param iconBW + * the new icon bw + */ + public void setIconBW(AppleFileData iconBW) { + this.iconBW = iconBW; + } + + /** + * Gets the icon color. + * + * @return the icon color + */ + public AppleFileData getIconColor() { + return iconColor; + } + + /** + * Sets the icon color. + * + * @param iconColor + * the new icon color + */ + public void setIconColor(AppleFileData iconColor) { + this.iconColor = iconColor; + } + + /** + * Gets the file dates info. + * + * @return the file dates info + */ + public AppleFileData getFileDatesInfo() { + return fileDatesInfo; + } + + /** + * Sets the file dates info. + * + * @param fileDatesInfo + * the new file dates info + */ + public void setFileDatesInfo(AppleFileData fileDatesInfo) { + this.fileDatesInfo = fileDatesInfo; + } + + /** + * Gets the finder info. + * + * @return the finder info + */ + public AppleFileData getFinderInfo() { + return finderInfo; + } + + /** + * Sets the finder info. + * + * @param finderInfo + * the new finder info + */ + public void setFinderInfo(AppleFileData finderInfo) { + this.finderInfo = finderInfo; + } + + /** + * Gets the macintosh info. + * + * @return the macintosh info + */ + public AppleFileData getMacintoshInfo() { + return macintoshInfo; + } + + /** + * Sets the macintosh info. + * + * @param macintoshInfo + * the new macintosh info + */ + public void setMacintoshInfo(AppleFileData macintoshInfo) { + this.macintoshInfo = macintoshInfo; + } + + /** + * Gets the pro dos file info. + * + * @return the pro dos file info + */ + public AppleFileData getProDOSFileInfo() { + return proDOSFileInfo; + } + + /** + * Sets the pro dos file info. + * + * @param proDOSFileInfo + * the new pro dos file info + */ + public void setProDOSFileInfo(AppleFileData proDOSFileInfo) { + this.proDOSFileInfo = proDOSFileInfo; + } + + /** + * Gets the ms dos file info. + * + * @return the ms dos file info + */ + public AppleFileData getMsDOSFileInfo() { + return msDOSFileInfo; + } + + /** + * Sets the ms dos file info. + * + * @param msDOSFileInfo + * the new ms dos file info + */ + public void setMsDOSFileInfo(AppleFileData msDOSFileInfo) { + this.msDOSFileInfo = msDOSFileInfo; + } + + /** + * Gets the short name. + * + * @return the short name + */ + public AppleFileData getShortName() { + return shortName; + } + + /** + * Sets the short name. + * + * @param shortName + * the new short name + */ + public void setShortName(AppleFileData shortName) { + this.shortName = shortName; + } + + /** + * Gets the afp file info. + * + * @return the afp file info + */ + public AppleFileData getAfpFileInfo() { + return afpFileInfo; + } + + /** + * Sets the afp file info. + * + * @param afpFileInfo + * the new afp file info + */ + public void setAfpFileInfo(AppleFileData afpFileInfo) { + this.afpFileInfo = afpFileInfo; + } + + /** + * Gets the directory id. + * + * @return the directory id + */ + public AppleFileData getDirectoryID() { + return directoryID; + } + + /** + * Sets the directory id. + * + * @param directoryID + * the new directory id + */ + public void setDirectoryID(AppleFileData directoryID) { + this.directoryID = directoryID; + } + + /** + * Gets the num entries. + * + * @return the num entries + */ + public int getNumEntries() { + return numEntries; + } } \ No newline at end of file diff --git a/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java b/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java index f28c979a..d89471e2 100644 --- a/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java +++ b/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFileData.java @@ -1,91 +1,91 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -/** - * This class is for representing the AppleSingle/Double file or its file forks - * (data fork and resource fork) and the related Finder meta-file information. - */ -public final class AppleFileData { - - public static final AppleFileData EMPTY_FILE_DATA = new AppleFileData(); - private byte[] data; - private int offset; - private int length; - - /** - * Instantiates a new apple file data. - */ - public AppleFileData() { - this.data = new byte[0]; - this.offset = 0; - this.length = 0; - } - - /** - * Instantiates a new apple file data. - * - * @param data the data - */ - public AppleFileData(byte[] data) { - this.data = data; - this.offset = 0; - this.length = data.length; - } - - /** - * Instantiates a new apple file data. - * - * @param data the data - * @param offset the offset - * @param length the length - */ - public AppleFileData(byte[] data, int offset, int length) { - if ((0 > offset) || (offset > data.length)) - throw new IndexOutOfBoundsException(); - if ((0 > length) || (length > data.length - offset)) - throw new IndexOutOfBoundsException(); - this.data = data; - this.offset = offset; - this.length = length; - } - - /** - * Gets the bytes. - * - * @return the bytes - */ - public byte[] getBytes() { - byte[] data = new byte[this.length]; - System.arraycopy(this.data, this.offset, data, 0, this.length); - return data; - } - - /** - * Gets the data. - * - * @return the data - */ - public byte[] getData() { - return this.data; - } - - /** - * Gets the offset. - * - * @return the offset - */ - public int getOffset() { - return this.offset; - } - - /** - * Gets the length. - * - * @return the length - */ - public int getLength() { - return this.length; - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +/** + * This class is for representing the AppleSingle/Double file or its file forks + * (data fork and resource fork) and the related Finder meta-file information. + */ +public final class AppleFileData { + + public static final AppleFileData EMPTY_FILE_DATA = new AppleFileData(); + private byte[] data; + private int offset; + private int length; + + /** + * Instantiates a new apple file data. + */ + public AppleFileData() { + this.data = new byte[0]; + this.offset = 0; + this.length = 0; + } + + /** + * Instantiates a new apple file data. + * + * @param data the data + */ + public AppleFileData(byte[] data) { + this.data = data; + this.offset = 0; + this.length = data.length; + } + + /** + * Instantiates a new apple file data. + * + * @param data the data + * @param offset the offset + * @param length the length + */ + public AppleFileData(byte[] data, int offset, int length) { + if ((0 > offset) || (offset > data.length)) + throw new IndexOutOfBoundsException(); + if ((0 > length) || (length > data.length - offset)) + throw new IndexOutOfBoundsException(); + this.data = data; + this.offset = offset; + this.length = length; + } + + /** + * Gets the bytes. + * + * @return the bytes + */ + public byte[] getBytes() { + byte[] data = new byte[this.length]; + System.arraycopy(this.data, this.offset, data, 0, this.length); + return data; + } + + /** + * Gets the data. + * + * @return the data + */ + public byte[] getData() { + return this.data; + } + + /** + * Gets the offset. + * + * @return the offset + */ + public int getOffset() { + return this.offset; + } + + /** + * Gets the length. + * + * @return the length + */ + public int getLength() { + return this.length; + } } \ No newline at end of file diff --git a/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java b/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java index 3248f85d..aa7b0363 100644 --- a/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java +++ b/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFileDecoder.java @@ -1,143 +1,143 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.Log; -import com.perforce.p4java.exception.FileDecoderException; - -/** - * This class handles the extraction of the data fork, resource fork and other - * entries from an AppleSingle/Double file. The Perforce 'apple' file type is a - * compressed AppleSingle (Mac resource + data) file. The Perforce 'resource' - * file type is a compressed AppleDouble (Mac resource fork) file. - */ -public class AppleFileDecoder extends AppleFile { - - /** - * Instantiates a new apple file decoder. - * - * @param fileData - * the file data - */ - public AppleFileDecoder(AppleFileData fileData) { - if (fileData != null) { - this.fileData = fileData; - } - } - - /** - * Extract the data fork, resource fork and other entries from the Apple - * file. - * - * @throws FileDecoderException - * the file decoder exception - */ - @SuppressWarnings("unused") - public void extract() throws FileDecoderException { - // Verify the validity of the Apple file - verify(); - - byte[] data = this.fileData.getData(); - int offset = this.fileData.getOffset(); - int length = this.fileData.getLength(); - int contentPosition = 0; - int entryId = 0; - int entryOffset = 0; - int entryLength = 0; - for (int i = 0; i < this.numEntries; i++) { - contentPosition = offset + 26 + i * 12; - /* Entry ID */ - entryId = 0; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - entryId <<= 8; - entryId |= data[(contentPosition++)] & 0xFF; - - /* Entry offset */ - entryOffset = 0; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset <<= 8; - entryOffset |= data[(contentPosition++)] & 0xFF; - entryOffset &= 0x7FFFFFFF; - - /* Entry length */ - entryLength = 0; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength <<= 8; - entryLength |= data[(contentPosition++)] & 0xFF; - entryLength &= 0x7FFFFFFF; - - switch (entryId) { - case 1: - this.dataFork = new AppleFileData(data, offset + entryOffset, - entryLength); - break; - case 2: - this.resourceFork = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 3: - this.realName = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 4: - this.comment = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 5: - this.iconBW = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 6: - this.iconColor = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 8: - this.fileDatesInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - extractFileDates(data, offset + entryOffset, entryLength); - break; - case 9: - this.finderInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 10: - this.macintoshInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 11: - this.proDOSFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 12: - this.msDOSFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - case 13: - this.shortName = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 14: - this.afpFileInfo = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - case 15: - this.directoryID = new AppleFileData(data, offset - + entryOffset, entryLength); - break; - default: - Log.warn("Apple file entry ID: " + entryId + " is not handled."); - - } - } - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.Log; +import com.perforce.p4java.exception.FileDecoderException; + +/** + * This class handles the extraction of the data fork, resource fork and other + * entries from an AppleSingle/Double file. The Perforce 'apple' file type is a + * compressed AppleSingle (Mac resource + data) file. The Perforce 'resource' + * file type is a compressed AppleDouble (Mac resource fork) file. + */ +public class AppleFileDecoder extends AppleFile { + + /** + * Instantiates a new apple file decoder. + * + * @param fileData + * the file data + */ + public AppleFileDecoder(AppleFileData fileData) { + if (fileData != null) { + this.fileData = fileData; + } + } + + /** + * Extract the data fork, resource fork and other entries from the Apple + * file. + * + * @throws FileDecoderException + * the file decoder exception + */ + @SuppressWarnings("unused") + public void extract() throws FileDecoderException { + // Verify the validity of the Apple file + verify(); + + byte[] data = this.fileData.getData(); + int offset = this.fileData.getOffset(); + int length = this.fileData.getLength(); + int contentPosition = 0; + int entryId = 0; + int entryOffset = 0; + int entryLength = 0; + for (int i = 0; i < this.numEntries; i++) { + contentPosition = offset + 26 + i * 12; + /* Entry ID */ + entryId = 0; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + entryId <<= 8; + entryId |= data[(contentPosition++)] & 0xFF; + + /* Entry offset */ + entryOffset = 0; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset <<= 8; + entryOffset |= data[(contentPosition++)] & 0xFF; + entryOffset &= 0x7FFFFFFF; + + /* Entry length */ + entryLength = 0; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength <<= 8; + entryLength |= data[(contentPosition++)] & 0xFF; + entryLength &= 0x7FFFFFFF; + + switch (entryId) { + case 1: + this.dataFork = new AppleFileData(data, offset + entryOffset, + entryLength); + break; + case 2: + this.resourceFork = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 3: + this.realName = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 4: + this.comment = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 5: + this.iconBW = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 6: + this.iconColor = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 8: + this.fileDatesInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + extractFileDates(data, offset + entryOffset, entryLength); + break; + case 9: + this.finderInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 10: + this.macintoshInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 11: + this.proDOSFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 12: + this.msDOSFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + case 13: + this.shortName = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 14: + this.afpFileInfo = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + case 15: + this.directoryID = new AppleFileData(data, offset + + entryOffset, entryLength); + break; + default: + Log.warn("Apple file entry ID: " + entryId + " is not handled."); + + } + } + } } \ No newline at end of file diff --git a/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java b/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java index 7c5d0a04..abdfed59 100644 --- a/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java +++ b/p4java/src/main/java/com/perforce/p4java/io/apple/AppleFileEncoder.java @@ -1,300 +1,300 @@ -/** - * Copyright 2012 Perforce Software Inc., All Rights Reserved. - */ -package com.perforce.p4java.io.apple; - -import com.perforce.p4java.exception.FileEncoderException; - -/** - * This class handles the combination of the data fork, resource fork and other - * entries into an AppleSingle/Double file. - *

- * - * Note that if it is an AppleDouble, the data fork is a separate file external - * to this file. - */ -public class AppleFileEncoder extends AppleFile { - - /** - * Instantiates a new apple file decoder. - * - * @throws FileEncoderException - */ - public AppleFileEncoder(FileFormat fileFormat) throws FileEncoderException { - if (fileFormat == null) { - throw new FileEncoderException("Null file format passed to the AppleFileEncoder constructor."); - } - if (fileFormat == FileFormat.UNKNOWN) { - throw new FileEncoderException("Unknown file format passed to the AppleFileEncoder constructor."); - } - } - - /** - * Combine the data fork, resource fork and other entries into an - * AppleSingle/Double file. - * - * @throws FileEncoderException - * the file encoder exception - */ - @SuppressWarnings("unused") - public void combine() throws FileEncoderException { - - boolean isAppleSingle = (this.format == FileFormat.APPLE_SINGLE); - boolean isAppleDouble = (this.format == FileFormat.APPLE_DOUBLE); - - boolean hasDataFork = (this.dataFork != AppleFileData.EMPTY_FILE_DATA); - boolean hasResourceFork = (this.resourceFork != AppleFileData.EMPTY_FILE_DATA); - boolean hasRealName = (this.realName != AppleFileData.EMPTY_FILE_DATA); - boolean hasComment = (this.comment != AppleFileData.EMPTY_FILE_DATA); - boolean hasIconBW = (this.iconBW != AppleFileData.EMPTY_FILE_DATA); - boolean hasIconColor = (this.iconColor != AppleFileData.EMPTY_FILE_DATA); - boolean hasFileDatesInfo = (this.fileDatesInfoEntry != null); - boolean hasFinderInfo = (this.finderInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasMacintoshInfo = (this.macintoshInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasProDOSFileInfo = (this.proDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasMsDOSFileInfo = (this.msDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasShortName = (this.shortName != AppleFileData.EMPTY_FILE_DATA); - boolean hasAfpFileInfo = (this.afpFileInfo != AppleFileData.EMPTY_FILE_DATA); - boolean hasDirectoryID = (this.directoryID != AppleFileData.EMPTY_FILE_DATA); - - this.fileData = AppleFileData.EMPTY_FILE_DATA; - - int length = 90 + this.realName.getLength() - + this.resourceFork.getLength(); - - /* AppleSingle includes the data fork */ - if (isAppleSingle) { - length += this.dataFork.getLength(); - } - - byte[] data = new byte[length]; - int position = 0; - - /* Magic number for AppleSingle or AppleDouble */ - if (isAppleDouble) { - data[(position++)] = 0; - data[(position++)] = 5; - data[(position++)] = 22; - data[(position++)] = 0; - } else { - data[(position++)] = 0; - data[(position++)] = 5; - data[(position++)] = 22; - data[(position++)] = 7; - } - - /* Version number */ - data[(position++)] = 0; - data[(position++)] = 2; - data[(position++)] = 0; - data[(position++)] = 0; - - /* Filler */ - - for (int k = 0; k < 16; k++) { - data[(position++)] = 0; - } - - /* Number of entries */ - this.numEntries = 0; - if (hasRealName) { - this.numEntries += 1; - } - if (hasFileDatesInfo) { - this.numEntries += 1; - } - if (hasResourceFork) { - this.numEntries += 1; - } - if ((hasDataFork) && (isAppleSingle)) { - this.numEntries += 1; - } - data[(position++)] = ((byte) (this.numEntries >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.numEntries & 0xFF)); - - /* Header information for the entries */ - - /* Real name entry header */ - int realNamePosition = 0; - if (hasRealName) { - int realNameEntryId = 3; - int realNameEntryOffset = 0; - int realNameEntryLength = this.realName.getLength(); - data[(position++)] = ((byte) (realNameEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryId >> 0 & 0xFF)); - realNamePosition = position; - data[(position++)] = ((byte) (realNameEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNameEntryLength >> 0 & 0xFF)); - } - - /* File dates info entry header */ - int fileDatesInfoPosition = 0; - if (hasFileDatesInfo) { - int fileDatesInfoEntryId = 8; - int fileDatesInfoEntryOffset = 0; - int fileDatesInfoEntryLength = 16; - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryId >> 0 & 0xFF)); - fileDatesInfoPosition = position; - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 0 & 0xFF)); - } - - /* Resource fork entry header */ - int resourceForkPosition = 0; - if (hasResourceFork) { - int resourceForkEntryId = 2; - int resourceForkEntryOffset = 0; - int resourceFokrEntryLength = this.resourceFork.getLength(); - data[(position++)] = ((byte) (resourceForkEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryId >> 0 & 0xFF)); - resourceForkPosition = position; - data[(position++)] = ((byte) (resourceForkEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceFokrEntryLength >> 0 & 0xFF)); - } - - /* Data fork entry header */ - int dataForkPosition = 0; - if ((hasDataFork) && (isAppleSingle)) { - int dataForkEntryId = 1; - int dataForkEntryOffset = 0; - int dataForkEntryLength = this.dataFork.getLength(); - data[(position++)] = ((byte) (dataForkEntryId >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryId >> 0 & 0xFF)); - dataForkPosition = position; - data[(position++)] = ((byte) (dataForkEntryOffset >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryOffset >> 0 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkEntryLength >> 0 & 0xFF)); - } - - /* Content for the entries */ - - /* Real name content */ - if (hasRealName) { - int realNamePositionCurrent = position; - position = realNamePosition; - data[(position++)] = ((byte) (realNamePositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (realNamePositionCurrent >> 0 & 0xFF)); - position = realNamePositionCurrent; - byte[] realNameData = this.realName.getData(); - int realNameOffset = this.realName.getOffset(); - int realNameLength = this.realName.getLength(); - System.arraycopy(realNameData, realNameOffset, data, position, - realNameLength); - position += realNameLength; - } - - /* File dates info content */ - if (hasFileDatesInfo) { - int fileDatesInfoPositionCurrent = position; - position = fileDatesInfoPosition; - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 0 & 0xFF)); - position = fileDatesInfoPositionCurrent; - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getCreateTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getModifyTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getBackupTime() >> 0 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 24 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 16 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 8 & 0xFF)); - data[(position++)] = ((byte) (this.fileDatesInfoEntry - .getAccessTime() >> 0 & 0xFF)); - } - - /* Resource fork content */ - if (hasResourceFork) { - int resourceForkPositionCurrent = position; - position = resourceForkPosition; - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 24 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 16 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 8 & 0xFF)); - data[(position++)] = ((byte) (resourceForkPositionCurrent >> 0 & 0xFF)); - position = resourceForkPositionCurrent; - byte[] resourceForkData = this.resourceFork.getData(); - int resourceForkOffset = this.resourceFork.getOffset(); - int resourceForkLength = this.resourceFork.getLength(); - System.arraycopy(resourceForkData, resourceForkOffset, data, - position, resourceForkLength); - position += resourceForkLength; - } - - /* Data fork content */ - if ((hasDataFork) && (isAppleSingle)) { - int dataForkPosition2 = position; - position = dataForkPosition; - data[(position++)] = ((byte) (dataForkPosition2 >> 24 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 16 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 8 & 0xFF)); - data[(position++)] = ((byte) (dataForkPosition2 >> 0 & 0xFF)); - position = dataForkPosition2; - byte[] dataForkData = this.dataFork.getData(); - int dataForkOffset = this.dataFork.getOffset(); - int dataForkLength = this.dataFork.getLength(); - System.arraycopy(dataForkData, dataForkOffset, data, position, - dataForkLength); - position += dataForkLength; - } - - /* Create the Apple file data */ - this.fileData = new AppleFileData(data, 0, position); - } +/** + * Copyright 2012 Perforce Software Inc., All Rights Reserved. + */ +package com.perforce.p4java.io.apple; + +import com.perforce.p4java.exception.FileEncoderException; + +/** + * This class handles the combination of the data fork, resource fork and other + * entries into an AppleSingle/Double file. + *

+ * + * Note that if it is an AppleDouble, the data fork is a separate file external + * to this file. + */ +public class AppleFileEncoder extends AppleFile { + + /** + * Instantiates a new apple file decoder. + * + * @throws FileEncoderException + */ + public AppleFileEncoder(FileFormat fileFormat) throws FileEncoderException { + if (fileFormat == null) { + throw new FileEncoderException("Null file format passed to the AppleFileEncoder constructor."); + } + if (fileFormat == FileFormat.UNKNOWN) { + throw new FileEncoderException("Unknown file format passed to the AppleFileEncoder constructor."); + } + } + + /** + * Combine the data fork, resource fork and other entries into an + * AppleSingle/Double file. + * + * @throws FileEncoderException + * the file encoder exception + */ + @SuppressWarnings("unused") + public void combine() throws FileEncoderException { + + boolean isAppleSingle = (this.format == FileFormat.APPLE_SINGLE); + boolean isAppleDouble = (this.format == FileFormat.APPLE_DOUBLE); + + boolean hasDataFork = (this.dataFork != AppleFileData.EMPTY_FILE_DATA); + boolean hasResourceFork = (this.resourceFork != AppleFileData.EMPTY_FILE_DATA); + boolean hasRealName = (this.realName != AppleFileData.EMPTY_FILE_DATA); + boolean hasComment = (this.comment != AppleFileData.EMPTY_FILE_DATA); + boolean hasIconBW = (this.iconBW != AppleFileData.EMPTY_FILE_DATA); + boolean hasIconColor = (this.iconColor != AppleFileData.EMPTY_FILE_DATA); + boolean hasFileDatesInfo = (this.fileDatesInfoEntry != null); + boolean hasFinderInfo = (this.finderInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasMacintoshInfo = (this.macintoshInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasProDOSFileInfo = (this.proDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasMsDOSFileInfo = (this.msDOSFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasShortName = (this.shortName != AppleFileData.EMPTY_FILE_DATA); + boolean hasAfpFileInfo = (this.afpFileInfo != AppleFileData.EMPTY_FILE_DATA); + boolean hasDirectoryID = (this.directoryID != AppleFileData.EMPTY_FILE_DATA); + + this.fileData = AppleFileData.EMPTY_FILE_DATA; + + int length = 90 + this.realName.getLength() + + this.resourceFork.getLength(); + + /* AppleSingle includes the data fork */ + if (isAppleSingle) { + length += this.dataFork.getLength(); + } + + byte[] data = new byte[length]; + int position = 0; + + /* Magic number for AppleSingle or AppleDouble */ + if (isAppleDouble) { + data[(position++)] = 0; + data[(position++)] = 5; + data[(position++)] = 22; + data[(position++)] = 0; + } else { + data[(position++)] = 0; + data[(position++)] = 5; + data[(position++)] = 22; + data[(position++)] = 7; + } + + /* Version number */ + data[(position++)] = 0; + data[(position++)] = 2; + data[(position++)] = 0; + data[(position++)] = 0; + + /* Filler */ + + for (int k = 0; k < 16; k++) { + data[(position++)] = 0; + } + + /* Number of entries */ + this.numEntries = 0; + if (hasRealName) { + this.numEntries += 1; + } + if (hasFileDatesInfo) { + this.numEntries += 1; + } + if (hasResourceFork) { + this.numEntries += 1; + } + if ((hasDataFork) && (isAppleSingle)) { + this.numEntries += 1; + } + data[(position++)] = ((byte) (this.numEntries >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.numEntries & 0xFF)); + + /* Header information for the entries */ + + /* Real name entry header */ + int realNamePosition = 0; + if (hasRealName) { + int realNameEntryId = 3; + int realNameEntryOffset = 0; + int realNameEntryLength = this.realName.getLength(); + data[(position++)] = ((byte) (realNameEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryId >> 0 & 0xFF)); + realNamePosition = position; + data[(position++)] = ((byte) (realNameEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNameEntryLength >> 0 & 0xFF)); + } + + /* File dates info entry header */ + int fileDatesInfoPosition = 0; + if (hasFileDatesInfo) { + int fileDatesInfoEntryId = 8; + int fileDatesInfoEntryOffset = 0; + int fileDatesInfoEntryLength = 16; + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryId >> 0 & 0xFF)); + fileDatesInfoPosition = position; + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoEntryLength >> 0 & 0xFF)); + } + + /* Resource fork entry header */ + int resourceForkPosition = 0; + if (hasResourceFork) { + int resourceForkEntryId = 2; + int resourceForkEntryOffset = 0; + int resourceFokrEntryLength = this.resourceFork.getLength(); + data[(position++)] = ((byte) (resourceForkEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryId >> 0 & 0xFF)); + resourceForkPosition = position; + data[(position++)] = ((byte) (resourceForkEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceFokrEntryLength >> 0 & 0xFF)); + } + + /* Data fork entry header */ + int dataForkPosition = 0; + if ((hasDataFork) && (isAppleSingle)) { + int dataForkEntryId = 1; + int dataForkEntryOffset = 0; + int dataForkEntryLength = this.dataFork.getLength(); + data[(position++)] = ((byte) (dataForkEntryId >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryId >> 0 & 0xFF)); + dataForkPosition = position; + data[(position++)] = ((byte) (dataForkEntryOffset >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryOffset >> 0 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkEntryLength >> 0 & 0xFF)); + } + + /* Content for the entries */ + + /* Real name content */ + if (hasRealName) { + int realNamePositionCurrent = position; + position = realNamePosition; + data[(position++)] = ((byte) (realNamePositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (realNamePositionCurrent >> 0 & 0xFF)); + position = realNamePositionCurrent; + byte[] realNameData = this.realName.getData(); + int realNameOffset = this.realName.getOffset(); + int realNameLength = this.realName.getLength(); + System.arraycopy(realNameData, realNameOffset, data, position, + realNameLength); + position += realNameLength; + } + + /* File dates info content */ + if (hasFileDatesInfo) { + int fileDatesInfoPositionCurrent = position; + position = fileDatesInfoPosition; + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (fileDatesInfoPositionCurrent >> 0 & 0xFF)); + position = fileDatesInfoPositionCurrent; + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getCreateTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getModifyTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getBackupTime() >> 0 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 24 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 16 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 8 & 0xFF)); + data[(position++)] = ((byte) (this.fileDatesInfoEntry + .getAccessTime() >> 0 & 0xFF)); + } + + /* Resource fork content */ + if (hasResourceFork) { + int resourceForkPositionCurrent = position; + position = resourceForkPosition; + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 24 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 16 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 8 & 0xFF)); + data[(position++)] = ((byte) (resourceForkPositionCurrent >> 0 & 0xFF)); + position = resourceForkPositionCurrent; + byte[] resourceForkData = this.resourceFork.getData(); + int resourceForkOffset = this.resourceFork.getOffset(); + int resourceForkLength = this.resourceFork.getLength(); + System.arraycopy(resourceForkData, resourceForkOffset, data, + position, resourceForkLength); + position += resourceForkLength; + } + + /* Data fork content */ + if ((hasDataFork) && (isAppleSingle)) { + int dataForkPosition2 = position; + position = dataForkPosition; + data[(position++)] = ((byte) (dataForkPosition2 >> 24 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 16 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 8 & 0xFF)); + data[(position++)] = ((byte) (dataForkPosition2 >> 0 & 0xFF)); + position = dataForkPosition2; + byte[] dataForkData = this.dataFork.getData(); + int dataForkOffset = this.dataFork.getOffset(); + int dataForkLength = this.dataFork.getLength(); + System.arraycopy(dataForkData, dataForkOffset, data, position, + dataForkLength); + position += dataForkLength; + } + + /* Create the Apple file data */ + this.fileData = new AppleFileData(data, 0, position); + } } \ No newline at end of file diff --git a/p4java/src/main/java/com/perforce/p4java/server/IServerMessage.java b/p4java/src/main/java/com/perforce/p4java/server/IServerMessage.java index 8ee5d3a1..89913cb0 100644 --- a/p4java/src/main/java/com/perforce/p4java/server/IServerMessage.java +++ b/p4java/src/main/java/com/perforce/p4java/server/IServerMessage.java @@ -1,79 +1,79 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.perforce.p4java.server; - -import com.perforce.p4java.impl.generic.core.file.FilePath; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.UnsupportedEncodingException; -import java.util.Map; - -/** - * Represents a message from the server, along with the localized version of it. - * The message can contain multiple problems. The top level message represents - * the highest severity message. - *

- * To create this, see {@link com.perforce.p4java.impl.mapbased.server.cmd.ResultMapParser#toServerMessage(Map)} - */ -public interface IServerMessage extends ISingleServerMessage { - @Nonnull - Iterable getAllMessages(); - - /** - * @return the list of messages that had at least the minimum severity. - */ - @Nonnull - Iterable getForSeverity(int minimum); - - /** - * @return the list of messages that had at least the minimum severity. - */ - @Nonnull - Iterable getForExactSeverity(int minimum); - - boolean hasSeverity(int minimum); - - /** - * @return true if the message is an informative message (info, error, fatal, etc.) - */ - boolean isInfoOrError(); - - boolean isInfo(); - - boolean isWarning(); - - boolean isError(); - - @Nonnull - String getFirstInfoString(); - - @Nonnull - String getAllInfoStrings(); - - @Nonnull - String getAllInfoStrings(@Nonnull String separator); - - byte[] getBytes(String charsetName) throws UnsupportedEncodingException; - - /** - * Returns the raw string that the server call to getErrorOrInfoStr originally returned. - * This is for writing data back to the RPC output stream. - * - * @return raw info string - */ - @Nullable - String getErrorOrInfoStr(); -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.perforce.p4java.server; + +import com.perforce.p4java.impl.generic.core.file.FilePath; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.UnsupportedEncodingException; +import java.util.Map; + +/** + * Represents a message from the server, along with the localized version of it. + * The message can contain multiple problems. The top level message represents + * the highest severity message. + *

+ * To create this, see {@link com.perforce.p4java.impl.mapbased.server.cmd.ResultMapParser#toServerMessage(Map)} + */ +public interface IServerMessage extends ISingleServerMessage { + @Nonnull + Iterable getAllMessages(); + + /** + * @return the list of messages that had at least the minimum severity. + */ + @Nonnull + Iterable getForSeverity(int minimum); + + /** + * @return the list of messages that had at least the minimum severity. + */ + @Nonnull + Iterable getForExactSeverity(int minimum); + + boolean hasSeverity(int minimum); + + /** + * @return true if the message is an informative message (info, error, fatal, etc.) + */ + boolean isInfoOrError(); + + boolean isInfo(); + + boolean isWarning(); + + boolean isError(); + + @Nonnull + String getFirstInfoString(); + + @Nonnull + String getAllInfoStrings(); + + @Nonnull + String getAllInfoStrings(@Nonnull String separator); + + byte[] getBytes(String charsetName) throws UnsupportedEncodingException; + + /** + * Returns the raw string that the server call to getErrorOrInfoStr originally returned. + * This is for writing data back to the RPC output stream. + * + * @return raw info string + */ + @Nullable + String getErrorOrInfoStr(); +} diff --git a/p4java/src/main/java/com/perforce/p4java/server/ISingleServerMessage.java b/p4java/src/main/java/com/perforce/p4java/server/ISingleServerMessage.java index 50320fc3..c16ab123 100644 --- a/p4java/src/main/java/com/perforce/p4java/server/ISingleServerMessage.java +++ b/p4java/src/main/java/com/perforce/p4java/server/ISingleServerMessage.java @@ -1,91 +1,91 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.perforce.p4java.server; - -/** - * Represents a message from the server, along with the localized version of it. - */ -public interface ISingleServerMessage { - /** - * Localized message with argument replacements. - * - * @return localized message - */ - String getLocalizedMessage(); - - /** - * The localized message format text, without argument replacements. - * - * @return localized format for the message text. - */ - String getMessageFormat(); - - /** - * - * @return the documented severity level of the message. - * @see com.perforce.p4java.exception.MessageSeverityCode - */ - int getSeverity(); - - /** - * - * @return subsystem message code - * @see com.perforce.p4java.exception.MessageSubsystemCode - */ - int getSubSystem(); - - /** - * - * @return the documented generic code for the message. - * @see com.perforce.p4java.exception.MessageGenericCode - */ - int getGeneric(); - - /** - * The unique code for the message. Nearly the same as the raw code. - * - * @return the unique code - */ - int getUniqueCode(); - - /** - * - * @return the sub-code - * @see IServerMessageCode - */ - int getSubCode(); - - /** - * - * @return the raw, unparsed code. - */ - int getRawCode(); - - /** - * Checks if the given string fragment is in the message. - * - * @param fragment - * @return true if the fragment is in the message. - * @deprecated here until the string matching is eliminated - */ - boolean hasMessageFragment(String fragment); - - /** - * - * @return string version of the status code - */ - String getCode(); - -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.perforce.p4java.server; + +/** + * Represents a message from the server, along with the localized version of it. + */ +public interface ISingleServerMessage { + /** + * Localized message with argument replacements. + * + * @return localized message + */ + String getLocalizedMessage(); + + /** + * The localized message format text, without argument replacements. + * + * @return localized format for the message text. + */ + String getMessageFormat(); + + /** + * + * @return the documented severity level of the message. + * @see com.perforce.p4java.exception.MessageSeverityCode + */ + int getSeverity(); + + /** + * + * @return subsystem message code + * @see com.perforce.p4java.exception.MessageSubsystemCode + */ + int getSubSystem(); + + /** + * + * @return the documented generic code for the message. + * @see com.perforce.p4java.exception.MessageGenericCode + */ + int getGeneric(); + + /** + * The unique code for the message. Nearly the same as the raw code. + * + * @return the unique code + */ + int getUniqueCode(); + + /** + * + * @return the sub-code + * @see IServerMessageCode + */ + int getSubCode(); + + /** + * + * @return the raw, unparsed code. + */ + int getRawCode(); + + /** + * Checks if the given string fragment is in the message. + * + * @param fragment + * @return true if the fragment is in the message. + * @deprecated here until the string matching is eliminated + */ + boolean hasMessageFragment(String fragment); + + /** + * + * @return string version of the status code + */ + String getCode(); + +} diff --git a/p4java/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java b/p4java/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java index ce3a3b73..68456034 100644 --- a/p4java/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java +++ b/p4java/src/test/java/com/perforce/p4java/tests/dev/unit/features123/SymbolicLinkHelperTest.java @@ -1,95 +1,95 @@ -/** - * Copyright (c) 2012 Perforce Software. All rights reserved. - */ -package com.perforce.p4java.tests.dev.unit.features123; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.File; - -import com.perforce.p4java.tests.dev.UnitTestDevServerManager; -import com.perforce.p4java.tests.ignoreRule.ConditionallyIgnoreClassRule; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Test; - -import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SymbolicLinkHelper; -import com.perforce.p4java.tests.dev.unit.P4JavaTestCase; - -/** - * Test symbolic link helper (JDK 7 or above) - */ -public class SymbolicLinkHelperTest extends P4JavaTestCase { - - @ClassRule - public static ConditionallyIgnoreClassRule ignoreWindows = ConditionallyIgnoreClassRule.ifWindows( - "Creates paths that can't be used on Windows"); - - /** - * @BeforeClass annotation to a method to be run before all the tests in a - * class. - */ - @BeforeClass - public static void oneTimeSetUp() { - // one-time initialization code (before all the tests). - // p4ic4idea: use local server - UnitTestDevServerManager.INSTANCE.startTestClass("unicode"); - } - - /** - * @AfterClass annotation to a method to be run after all the tests in a - * class. - */ - @AfterClass - public static void oneTimeTearDown() { - // one-time cleanup code (after all the tests). - // p4ic4idea: use local server - UnitTestDevServerManager.INSTANCE.endTestClass("unicode"); - } - - /** - * @Before annotation to a method to be run before each test in a class. - */ - @Before - public void setUp() { - // initialization code (before each test). - } - - /** - * @After annotation to a method to be run after each test in a class. - */ - @After - public void tearDown() { - // cleanup code (after each test). - } - - /** - * Test symbolic link - */ - @Test - public void tesSymbolicLink() { - // Check if symlink capable - if (SymbolicLinkHelper.isSymbolicLinkCapable()) { - - String target = "/usr/bin"; - String link = "/tmp/user-bin-" + getRandomInt(); - - // Create symbolic link - String path = SymbolicLinkHelper.createSymbolicLink(link, target); - assertNotNull(path); - - boolean isSymlink = SymbolicLinkHelper.isSymbolicLink(path); - assertTrue(isSymlink); - - File file = new File(path); - if (file.exists()) { - assertTrue(file.delete()); - } - } - } - -} +/** + * Copyright (c) 2012 Perforce Software. All rights reserved. + */ +package com.perforce.p4java.tests.dev.unit.features123; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import com.perforce.p4java.tests.dev.UnitTestDevServerManager; +import com.perforce.p4java.tests.ignoreRule.ConditionallyIgnoreClassRule; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import com.perforce.p4java.impl.mapbased.rpc.sys.helper.SymbolicLinkHelper; +import com.perforce.p4java.tests.dev.unit.P4JavaTestCase; + +/** + * Test symbolic link helper (JDK 7 or above) + */ +public class SymbolicLinkHelperTest extends P4JavaTestCase { + + @ClassRule + public static ConditionallyIgnoreClassRule ignoreWindows = ConditionallyIgnoreClassRule.ifWindows( + "Creates paths that can't be used on Windows"); + + /** + * @BeforeClass annotation to a method to be run before all the tests in a + * class. + */ + @BeforeClass + public static void oneTimeSetUp() { + // one-time initialization code (before all the tests). + // p4ic4idea: use local server + UnitTestDevServerManager.INSTANCE.startTestClass("unicode"); + } + + /** + * @AfterClass annotation to a method to be run after all the tests in a + * class. + */ + @AfterClass + public static void oneTimeTearDown() { + // one-time cleanup code (after all the tests). + // p4ic4idea: use local server + UnitTestDevServerManager.INSTANCE.endTestClass("unicode"); + } + + /** + * @Before annotation to a method to be run before each test in a class. + */ + @Before + public void setUp() { + // initialization code (before each test). + } + + /** + * @After annotation to a method to be run after each test in a class. + */ + @After + public void tearDown() { + // cleanup code (after each test). + } + + /** + * Test symbolic link + */ + @Test + public void tesSymbolicLink() { + // Check if symlink capable + if (SymbolicLinkHelper.isSymbolicLinkCapable()) { + + String target = "/usr/bin"; + String link = "/tmp/user-bin-" + getRandomInt(); + + // Create symbolic link + String path = SymbolicLinkHelper.createSymbolicLink(link, target); + assertNotNull(path); + + boolean isSymlink = SymbolicLinkHelper.isSymbolicLink(path); + assertTrue(isSymlink); + + File file = new File(path); + if (file.exists()) { + assertTrue(file.delete()); + } + } + } + +} diff --git a/p4java/src/test/java/com/perforce/p4java/tests/dev/unit/features132/ClientCompressSyncTest.java b/p4java/src/test/java/com/perforce/p4java/tests/dev/unit/features132/ClientCompressSyncTest.java index 1e538a28..3210b4c8 100644 --- a/p4java/src/test/java/com/perforce/p4java/tests/dev/unit/features132/ClientCompressSyncTest.java +++ b/p4java/src/test/java/com/perforce/p4java/tests/dev/unit/features132/ClientCompressSyncTest.java @@ -1,131 +1,131 @@ -/** - * Copyright (c) 2013 Perforce Software. All rights reserved. - */ -package com.perforce.p4java.tests.dev.unit.features132; - -import com.perforce.p4java.exception.AccessException; -import com.perforce.p4java.exception.ConnectionException; -import com.perforce.p4java.exception.P4JavaException; -import com.perforce.p4java.exception.RequestException; -import com.perforce.p4java.server.CmdSpec; -import com.perforce.p4java.tests.UnicodeServerRule; -import com.perforce.p4java.tests.dev.annotations.Jobs; -import com.perforce.p4java.tests.dev.annotations.TestId; -import com.perforce.p4java.tests.dev.unit.P4JavaRshTestCase; -import org.junit.After; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Ignore; -import org.junit.Test; - -import java.io.IOException; -import java.nio.file.Paths; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -/** - * Test P4Java sync using JZlib with client compression mode. - */ -@Jobs({ "job066779" }) -@TestId("Dev132_ClientCompressSyncTest") -public class ClientCompressSyncTest extends P4JavaRshTestCase { - - @ClassRule - public static UnicodeServerRule p4d = new UnicodeServerRule("r16.1", ClientCompressSyncTest.class.getSimpleName()); - - String serverMessage = null; - long completedTime = 0; - - /** - * @Before annotation to a method to be run before each test in a class. - */ - @Before - public void setUp() { - try { - final String depotName = this.getRandomName(false, "p4TestUserWSCompress-depot"); - final String clientName = "p4TestUserWSCompress"; - final String clientDescription = "temp stream client for test"; - final String clientRoot = Paths.get("").toAbsolutePath().toString(); - final String[] clientViews = {"//" + depotName + "/... //" + clientName + "/..."}; - - Properties props = new Properties(); - props.put("sockPerfPrefs", "3, 2, 1"); - props.put("sockSoTimeout", 0); - - setupServer(p4d.getRSHURL(), userName, password,true, props); - setupUtf8(server); - createLocalDepot(depotName, server); - - client = createClient(server, clientName, clientDescription, clientRoot, clientViews); - } catch (Exception e) { - fail("Unexpected exception: " + e.getLocalizedMessage()); - } - } - - /** - * @After annotation to a method to be run after each test in a class. - */ - @After - public void tearDown() { - // cleanup code (after each test). - if (server != null) { - endServerSession(server); - } - } - - /** - * Test "p4 sync" using Process.exec("p4 sync") - */ - @Test - @Ignore("Seems pointless to run this in shell immediately before the same in java") - public void testCmdSync() { - - try { - String[] command = new String[] {"/bin/sh", "-c", "/opt/perforce/p4/p4 -p" - + server.getServerInfo().getServerAddress() + " -u" - + userName + " -P" + server.getAuthTicket() + " -c" - + client.getName() + " sync -f //depot/..."}; - - ProcessBuilder builder = new ProcessBuilder(command); - - final Process process = builder.start(); - process.waitFor(); - System.out.println("Program terminated!"); - - } catch (IOException e) { - fail("Unexpected exception: " + e.getLocalizedMessage()); - } catch (ConnectionException e) { - fail("Unexpected exception: " + e.getLocalizedMessage()); - } catch (RequestException e) { - fail("Unexpected exception: " + e.getLocalizedMessage()); - } catch (AccessException e) { - fail("Unexpected exception: " + e.getLocalizedMessage()); - } catch (InterruptedException e) { - fail("Unexpected exception: " + e.getLocalizedMessage()); - } - } - - /** - * Test "p4 sync" using execMapCmd - */ - @Test - public void testExecMapCmdSync() { - - try { - /** Limited to /depot/basic/... to reduce the memory load. */ - List> result = server.execMapCmdList( - CmdSpec.SYNC.toString(), new String[] { "-f", - "//depot/basic/..." }, null); - assertNotNull(result); - assertTrue(result.size() > 0); - - } catch (P4JavaException e) { - fail("Unexpected exception: " + e.getLocalizedMessage()); - } - } -} +/** + * Copyright (c) 2013 Perforce Software. All rights reserved. + */ +package com.perforce.p4java.tests.dev.unit.features132; + +import com.perforce.p4java.exception.AccessException; +import com.perforce.p4java.exception.ConnectionException; +import com.perforce.p4java.exception.P4JavaException; +import com.perforce.p4java.exception.RequestException; +import com.perforce.p4java.server.CmdSpec; +import com.perforce.p4java.tests.UnicodeServerRule; +import com.perforce.p4java.tests.dev.annotations.Jobs; +import com.perforce.p4java.tests.dev.annotations.TestId; +import com.perforce.p4java.tests.dev.unit.P4JavaRshTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Test P4Java sync using JZlib with client compression mode. + */ +@Jobs({ "job066779" }) +@TestId("Dev132_ClientCompressSyncTest") +public class ClientCompressSyncTest extends P4JavaRshTestCase { + + @ClassRule + public static UnicodeServerRule p4d = new UnicodeServerRule("r16.1", ClientCompressSyncTest.class.getSimpleName()); + + String serverMessage = null; + long completedTime = 0; + + /** + * @Before annotation to a method to be run before each test in a class. + */ + @Before + public void setUp() { + try { + final String depotName = this.getRandomName(false, "p4TestUserWSCompress-depot"); + final String clientName = "p4TestUserWSCompress"; + final String clientDescription = "temp stream client for test"; + final String clientRoot = Paths.get("").toAbsolutePath().toString(); + final String[] clientViews = {"//" + depotName + "/... //" + clientName + "/..."}; + + Properties props = new Properties(); + props.put("sockPerfPrefs", "3, 2, 1"); + props.put("sockSoTimeout", 0); + + setupServer(p4d.getRSHURL(), userName, password,true, props); + setupUtf8(server); + createLocalDepot(depotName, server); + + client = createClient(server, clientName, clientDescription, clientRoot, clientViews); + } catch (Exception e) { + fail("Unexpected exception: " + e.getLocalizedMessage()); + } + } + + /** + * @After annotation to a method to be run after each test in a class. + */ + @After + public void tearDown() { + // cleanup code (after each test). + if (server != null) { + endServerSession(server); + } + } + + /** + * Test "p4 sync" using Process.exec("p4 sync") + */ + @Test + @Ignore("Seems pointless to run this in shell immediately before the same in java") + public void testCmdSync() { + + try { + String[] command = new String[] {"/bin/sh", "-c", "/opt/perforce/p4/p4 -p" + + server.getServerInfo().getServerAddress() + " -u" + + userName + " -P" + server.getAuthTicket() + " -c" + + client.getName() + " sync -f //depot/..."}; + + ProcessBuilder builder = new ProcessBuilder(command); + + final Process process = builder.start(); + process.waitFor(); + System.out.println("Program terminated!"); + + } catch (IOException e) { + fail("Unexpected exception: " + e.getLocalizedMessage()); + } catch (ConnectionException e) { + fail("Unexpected exception: " + e.getLocalizedMessage()); + } catch (RequestException e) { + fail("Unexpected exception: " + e.getLocalizedMessage()); + } catch (AccessException e) { + fail("Unexpected exception: " + e.getLocalizedMessage()); + } catch (InterruptedException e) { + fail("Unexpected exception: " + e.getLocalizedMessage()); + } + } + + /** + * Test "p4 sync" using execMapCmd + */ + @Test + public void testExecMapCmdSync() { + + try { + /** Limited to /depot/basic/... to reduce the memory load. */ + List> result = server.execMapCmdList( + CmdSpec.SYNC.toString(), new String[] { "-f", + "//depot/basic/..." }, null); + assertNotNull(result); + assertTrue(result.size() > 0); + + } catch (P4JavaException e) { + fail("Unexpected exception: " + e.getLocalizedMessage()); + } + } +} diff --git a/p4java/src/test/java/com/perforce/p4java/tests/dev/unit/features132/GetUserGroupTest.java b/p4java/src/test/java/com/perforce/p4java/tests/dev/unit/features132/GetUserGroupTest.java index 027ddb90..178dec64 100644 --- a/p4java/src/test/java/com/perforce/p4java/tests/dev/unit/features132/GetUserGroupTest.java +++ b/p4java/src/test/java/com/perforce/p4java/tests/dev/unit/features132/GetUserGroupTest.java @@ -1,85 +1,85 @@ -package com.perforce.p4java.tests.dev.unit.features132; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsNull.notNullValue; -import static org.junit.Assert.fail; - -import java.util.List; - -import com.perforce.p4java.impl.generic.core.UserGroup; -import com.perforce.p4java.tests.UnicodeServerRule; -import com.perforce.p4java.tests.dev.unit.P4JavaRshTestCase; -import org.junit.After; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; - -import com.perforce.p4java.client.IClient; -import com.perforce.p4java.core.IUserGroup; -import com.perforce.p4java.option.server.GetUserGroupsOptions; -import com.perforce.p4java.server.IOptionsServer; -import com.perforce.p4java.tests.dev.annotations.Jobs; -import com.perforce.p4java.tests.dev.annotations.TestId; - -/** - * Test getting user groups with '-g | -u | -o name' options. - */ - -@Jobs({"job059617"}) -@TestId("Dev132_GetUserGroupTest") -public class GetUserGroupTest extends P4JavaRshTestCase { - - @ClassRule - public static UnicodeServerRule p4d = new UnicodeServerRule("r16.1", GetUserGroupTest.class.getSimpleName()); - - private IOptionsServer superserver = null; - private String superUserGroupName = "p4jtestsuper"; - private UserGroup userGroup; - private String superUser = "testsuperuser"; - private String user = "testuser"; - - @Before - public void setUp() { - try { - setupServer(p4d.getRSHURL(), superUserName, superUserPassword, true, null); - client = getClient(server); - userGroup = createSuperUserGroup(server, superUserGroupName, superUser, user); - superserver = getSuperConnection(p4d.getRSHURL()); - IClient superClient = getDefaultClient(superserver); - assertThat(superClient, notNullValue()); - superserver.setCurrentClient(superClient); - } catch (Exception e) { - fail("Unexpected exception: " + e.getLocalizedMessage()); - } - } - - @After - public void tearDown() { - afterEach(server, superserver); - } - - /* - * Test getting user groups with '-g | -u | -o name' options. - */ - @Test - public void testGetUserGroups() throws Exception { - - // p4 groups -o p4jtestsuper - List userGroups = server.getUserGroups(superUser, new GetUserGroupsOptions().setOwnerName(true)); - assertThat("null user group list", userGroups, notNullValue()); - assertThat("too few user groups in list", userGroups.size() >= 1, is(true)); - - // p4 groups -u p4jtestuser - userGroups = server.getUserGroups(user, new GetUserGroupsOptions().setUserName(true)); - assertThat("null user group list", userGroups, notNullValue()); - assertThat("too few user groups in list", userGroups.size() >= 1); - - // p4 groups -g p4users - userGroups = server.getUserGroups(superUserGroupName, new GetUserGroupsOptions().setGroupName(true)); - assertThat("null user group list", userGroups, notNullValue()); - assertThat(userGroups.size() == 0, is(true)); - - } - -} +package com.perforce.p4java.tests.dev.unit.features132; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; +import static org.junit.Assert.fail; + +import java.util.List; + +import com.perforce.p4java.impl.generic.core.UserGroup; +import com.perforce.p4java.tests.UnicodeServerRule; +import com.perforce.p4java.tests.dev.unit.P4JavaRshTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; + +import com.perforce.p4java.client.IClient; +import com.perforce.p4java.core.IUserGroup; +import com.perforce.p4java.option.server.GetUserGroupsOptions; +import com.perforce.p4java.server.IOptionsServer; +import com.perforce.p4java.tests.dev.annotations.Jobs; +import com.perforce.p4java.tests.dev.annotations.TestId; + +/** + * Test getting user groups with '-g | -u | -o name' options. + */ + +@Jobs({"job059617"}) +@TestId("Dev132_GetUserGroupTest") +public class GetUserGroupTest extends P4JavaRshTestCase { + + @ClassRule + public static UnicodeServerRule p4d = new UnicodeServerRule("r16.1", GetUserGroupTest.class.getSimpleName()); + + private IOptionsServer superserver = null; + private String superUserGroupName = "p4jtestsuper"; + private UserGroup userGroup; + private String superUser = "testsuperuser"; + private String user = "testuser"; + + @Before + public void setUp() { + try { + setupServer(p4d.getRSHURL(), superUserName, superUserPassword, true, null); + client = getClient(server); + userGroup = createSuperUserGroup(server, superUserGroupName, superUser, user); + superserver = getSuperConnection(p4d.getRSHURL()); + IClient superClient = getDefaultClient(superserver); + assertThat(superClient, notNullValue()); + superserver.setCurrentClient(superClient); + } catch (Exception e) { + fail("Unexpected exception: " + e.getLocalizedMessage()); + } + } + + @After + public void tearDown() { + afterEach(server, superserver); + } + + /* + * Test getting user groups with '-g | -u | -o name' options. + */ + @Test + public void testGetUserGroups() throws Exception { + + // p4 groups -o p4jtestsuper + List userGroups = server.getUserGroups(superUser, new GetUserGroupsOptions().setOwnerName(true)); + assertThat("null user group list", userGroups, notNullValue()); + assertThat("too few user groups in list", userGroups.size() >= 1, is(true)); + + // p4 groups -u p4jtestuser + userGroups = server.getUserGroups(user, new GetUserGroupsOptions().setUserName(true)); + assertThat("null user group list", userGroups, notNullValue()); + assertThat("too few user groups in list", userGroups.size() >= 1); + + // p4 groups -g p4users + userGroups = server.getUserGroups(superUserGroupName, new GetUserGroupsOptions().setGroupName(true)); + assertThat("null user group list", userGroups, notNullValue()); + assertThat(userGroups.size() == 0, is(true)); + + } + +} diff --git a/p4java/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf_8-jp_with_bom.txt b/p4java/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf_8-jp_with_bom.txt index 72ef1f80..76706262 100644 --- a/p4java/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf_8-jp_with_bom.txt +++ b/p4java/src/test/resources/com/perforce/p4java/impl/mapbased/rpc/sys/utf_8-jp_with_bom.txt @@ -1,9 +1,9 @@ -UTF-8(ユーティーエフã¯ã¡ã€ãƒ¦ãƒ¼ãƒ†ã‚£ãƒ¼ã‚¨ãƒ•ã‚¨ã‚¤ãƒˆï¼‰ã¯ISO/IEC 10646 (UCS) ã¨Unicodeã§ä½¿ãˆã‚‹8ビット符å·å˜ä½ã®æ–‡å­—符å·åŒ–å½¢å¼åŠã³æ–‡å­—符å·åŒ–スキーム。 - -æ­£å¼å称ã¯ã€ISO/IEC 10646ã§ã¯ “UCS Transformation Format 8â€ã€Unicodeã§ã¯ “Unicode Transformation Format-8†ã¨ã„ã†ã€‚両者ã¯ISO/IEC 10646ã¨Unicodeã®ã‚³ãƒ¼ãƒ‰é‡è¤‡ç¯„囲ã§äº’æ›æ€§ãŒã‚る。RFCã«ã‚‚仕様ãŒã‚ã‚‹[1]。 - -2ãƒã‚¤ãƒˆç›®ä»¥é™ã«ã€Œ/ã€ãªã©ã®ASCII文字ãŒç¾ã‚Œãªã„よã†ã«å·¥å¤«ã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‹ã‚‰ã€UTF-FSS (File System Safe) ã¨ã‚‚ã„ã‚れる。旧å称ã¯UTF-2。 - -データ交æ›æ–¹å¼ã€ãƒ•ã‚¡ã‚¤ãƒ«å½¢å¼ã¨ã—ã¦ã€ä¸€èˆ¬çš„ã«UTF-8ã¯ä½¿ã‚れる傾å‘ã«ã‚る。 - -当åˆã¯ã€ãƒ™ãƒ«ç ”究所ã«ãŠã„ã¦Plan 9ã§ç”¨ã„るエンコードã¨ã—ã¦ã€ãƒ­ãƒ–・パイクã«ã‚ˆã‚‹è¨­è¨ˆæŒ‡é‡ã®ã‚‚ã¨ã€ã‚±ãƒ³ãƒ»ãƒˆãƒ³ãƒ—ソンã«ã‚ˆã£ã¦è€ƒæ¡ˆã•ã‚ŒãŸ +UTF-8(ユーティーエフã¯ã¡ã€ãƒ¦ãƒ¼ãƒ†ã‚£ãƒ¼ã‚¨ãƒ•ã‚¨ã‚¤ãƒˆï¼‰ã¯ISO/IEC 10646 (UCS) ã¨Unicodeã§ä½¿ãˆã‚‹8ビット符å·å˜ä½ã®æ–‡å­—符å·åŒ–å½¢å¼åŠã³æ–‡å­—符å·åŒ–スキーム。 + +æ­£å¼å称ã¯ã€ISO/IEC 10646ã§ã¯ “UCS Transformation Format 8â€ã€Unicodeã§ã¯ “Unicode Transformation Format-8†ã¨ã„ã†ã€‚両者ã¯ISO/IEC 10646ã¨Unicodeã®ã‚³ãƒ¼ãƒ‰é‡è¤‡ç¯„囲ã§äº’æ›æ€§ãŒã‚る。RFCã«ã‚‚仕様ãŒã‚ã‚‹[1]。 + +2ãƒã‚¤ãƒˆç›®ä»¥é™ã«ã€Œ/ã€ãªã©ã®ASCII文字ãŒç¾ã‚Œãªã„よã†ã«å·¥å¤«ã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‹ã‚‰ã€UTF-FSS (File System Safe) ã¨ã‚‚ã„ã‚れる。旧å称ã¯UTF-2。 + +データ交æ›æ–¹å¼ã€ãƒ•ã‚¡ã‚¤ãƒ«å½¢å¼ã¨ã—ã¦ã€ä¸€èˆ¬çš„ã«UTF-8ã¯ä½¿ã‚れる傾å‘ã«ã‚る。 + +当åˆã¯ã€ãƒ™ãƒ«ç ”究所ã«ãŠã„ã¦Plan 9ã§ç”¨ã„るエンコードã¨ã—ã¦ã€ãƒ­ãƒ–・パイクã«ã‚ˆã‚‹è¨­è¨ˆæŒ‡é‡ã®ã‚‚ã¨ã€ã‚±ãƒ³ãƒ»ãƒˆãƒ³ãƒ—ソンã«ã‚ˆã£ã¦è€ƒæ¡ˆã•ã‚ŒãŸ diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/.gitignore b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/.gitignore index 6c5d5bbf..f6fecb94 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/.gitignore +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/.gitignore @@ -1 +1 @@ -tmp-depot/ +tmp-depot/ diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/README.md b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/README.md index 79d09ae4..e9e9205f 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/README.md +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/README.md @@ -1,68 +1,68 @@ -# About - -A locally hosted depot version of what the integration tests run against. These are intended to run against the Perforce servers internal to the Perforce company, but as we don't have access to those servers, this is the placeholder. - -The depot is constructed through the `construction` directory. - -## Double Check - -When you work on creating this, follow this checklist: - -* `Host:` line in each client is set to an empty value. -* `Root:` line is a non-descriptive directory (say, `/tmp/client`) -* Each user has a password set, which is equal to the user name. - - -## Construction - -If this depot needs updates, follow these steps. - -First, create a docker server (or run locally, you know, if you want). For tests, we need an audit log and a log. - -```bash -docker run -it --rm --name p4d -p 11666:1666 -v /tmp/p4d-depot:/opt/p4d ubuntu -(now inside the docker container) -apt-get update && apt-get -y upgrade && apt-get -y install wget -cd /tmp/p4d-depot -wget http://ftp.perforce.com/perforce/r17.1/bin.linux26x86_64/p4d -chmod +x p4d -./p4d -A audit.log -L p4d.log -``` - -Then, in another shell, setup your environment for manipulation. - -```bash -export P4ENVIRO= -export P4PORT=localhost:11666 -export P4CLIENT=p4TestUserWS (or whatever is required by the test) -export P4USER=p4jtestuser -export P4PASSWD=p4jtestuser -``` - -(Super user is `p4jtestsuper`, normal user is `p4jtestuser`) - -or for Windows: - -```cmd -set P4ENVIRO= -set P4PORT=localhost:11666 -set P4CLIENT=p4TestUserWSNT -set P4USER=p4jtestuser -set P4PASSWD=p4jtestuser -``` - -To package this up, run this in another shell: - -```bash -cd (this data resource directory) -_here=$( pwd ) - -rm -f /tmp/depot/checkpoint.* ; \ -docker exec -it p4d /bin/sh -c "cd /opt/p4d/depot && ./p4d -jc" && \ -rm -f /tmp/depot/checkpoint.*.md5 ; \ -mv /tmp/depot/checkpoint.* checkpoint ; \ -rm depot.tar.gz ; \ -( cd /tmp/depot/ && tar zcf "$_here"/depot.tar.gz depot p4java_stream ) ; \ -rm -f checkpoint.gz ; \ -gzip -9 checkpoint -``` +# About + +A locally hosted depot version of what the integration tests run against. These are intended to run against the Perforce servers internal to the Perforce company, but as we don't have access to those servers, this is the placeholder. + +The depot is constructed through the `construction` directory. + +## Double Check + +When you work on creating this, follow this checklist: + +* `Host:` line in each client is set to an empty value. +* `Root:` line is a non-descriptive directory (say, `/tmp/client`) +* Each user has a password set, which is equal to the user name. + + +## Construction + +If this depot needs updates, follow these steps. + +First, create a docker server (or run locally, you know, if you want). For tests, we need an audit log and a log. + +```bash +docker run -it --rm --name p4d -p 11666:1666 -v /tmp/p4d-depot:/opt/p4d ubuntu +(now inside the docker container) +apt-get update && apt-get -y upgrade && apt-get -y install wget +cd /tmp/p4d-depot +wget http://ftp.perforce.com/perforce/r17.1/bin.linux26x86_64/p4d +chmod +x p4d +./p4d -A audit.log -L p4d.log +``` + +Then, in another shell, setup your environment for manipulation. + +```bash +export P4ENVIRO= +export P4PORT=localhost:11666 +export P4CLIENT=p4TestUserWS (or whatever is required by the test) +export P4USER=p4jtestuser +export P4PASSWD=p4jtestuser +``` + +(Super user is `p4jtestsuper`, normal user is `p4jtestuser`) + +or for Windows: + +```cmd +set P4ENVIRO= +set P4PORT=localhost:11666 +set P4CLIENT=p4TestUserWSNT +set P4USER=p4jtestuser +set P4PASSWD=p4jtestuser +``` + +To package this up, run this in another shell: + +```bash +cd (this data resource directory) +_here=$( pwd ) + +rm -f /tmp/depot/checkpoint.* ; \ +docker exec -it p4d /bin/sh -c "cd /opt/p4d/depot && ./p4d -jc" && \ +rm -f /tmp/depot/checkpoint.*.md5 ; \ +mv /tmp/depot/checkpoint.* checkpoint ; \ +rm depot.tar.gz ; \ +( cd /tmp/depot/ && tar zcf "$_here"/depot.tar.gz depot p4java_stream ) ; \ +rm -f checkpoint.gz ; \ +gzip -9 checkpoint +``` diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/build-depot-files.sh b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/build-depot-files.sh index 28490e11..67ae701c 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/build-depot-files.sh +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/build-depot-files.sh @@ -1,70 +1,70 @@ -#!/bin/sh - -if [ "$1" = "-h" ] || [ "$1" = "--help" ] ; then - echo "Example for creating the checkpoint.gz and depot.tar.gz files." - echo "YMMV" - exit 0 -fi - -current_uid=$( cat /etc/passwd | grep "${USER}" | cut -f 3 -d : ) - -output_dir="/tmp/p4d.$$.$RANDOM" - -basen="d-$$" -clean_up() { - docker kill "x-p4d-${basen}" - docker rm "x-p4d-${basen}" - docker rmi "p4d-${basen}" - docker rm "x-p4client-${basen}" - docker rmi "p4client-${basen}" - docker network rm "p4-net-${basen}" - rm -rf "${output_dir}" - exit "$1" -} -trap clean_up HUP INT TERM - -mkdir -p "$output_dir" || clean_up 1 - -cd $( dirname "$0" ) || clean_up 1 - -# prod... -checkpoint_dir=$( pwd ) -# testing... -# checkpoint_dir=$( pwd )/tmp-depot -# mkdir -p "${checkpoint_dir}" - -cd construction || clean_up 1 - -( cd p4d-docker && docker build -t "p4d-${basen}" . ) || clean_up 1 -( cd p4-client-docker && docker build -t "p4client-${basen}" . ) || clean_up 1 - -docker network create "p4-net-${basen}" || clean_up 1 - -# Start the Perforce server as a daemon process ("-d"). This will stay running in the background. -docker run --rm \ - --name "x-p4d-${basen}" \ - --network "p4-net-${basen}" \ - --network-alias "perforce.local" \ - -u "${current_uid}" \ - -v "${output_dir}:/opt/p4d-base" \ - -d \ - "p4d-${basen}" || clean_up 1 - -# Execute the Perforce construction tool. -docker run --rm \ - --name "x-p4clinet-${basen}" \ - --network "p4-net-${basen}" \ - "p4client-${basen}" || clean_up 1 - -# ExportListTest - leave checkpoint file in depot directory. -test -f "${output_dir}/checkpoint.1" || clean_up 1 -cp "${output_dir}/checkpoint.1" "${checkpoint_dir}/checkpoint" || clean_up 1 -test -f "${checkpoint_dir}/checkpoint.gz" && rm -f "${checkpoint_dir}/checkpoint.gz" -( cd "${checkpoint_dir}" && gzip -9 checkpoint) - -test -d "${output_dir}/depot" || clean_up 1 -test -d "${output_dir}/p4java_stream" || clean_up 1 -test -f "${checkpoint_dir}/depot.tar.gz" && rm -f "${checkpoint_dir}/depot.tar.gz" -( cd "${output_dir}" && tar zcf "${checkpoint_dir}/depot.tar.gz" depot p4java_stream checkpoint.1 ) - -chmod +w "${checkpoint_dir}/checkpoint.gz" "${checkpoint_dir}/depot.tar.gz" +#!/bin/sh + +if [ "$1" = "-h" ] || [ "$1" = "--help" ] ; then + echo "Example for creating the checkpoint.gz and depot.tar.gz files." + echo "YMMV" + exit 0 +fi + +current_uid=$( cat /etc/passwd | grep "${USER}" | cut -f 3 -d : ) + +output_dir="/tmp/p4d.$$.$RANDOM" + +basen="d-$$" +clean_up() { + docker kill "x-p4d-${basen}" + docker rm "x-p4d-${basen}" + docker rmi "p4d-${basen}" + docker rm "x-p4client-${basen}" + docker rmi "p4client-${basen}" + docker network rm "p4-net-${basen}" + rm -rf "${output_dir}" + exit "$1" +} +trap clean_up HUP INT TERM + +mkdir -p "$output_dir" || clean_up 1 + +cd $( dirname "$0" ) || clean_up 1 + +# prod... +checkpoint_dir=$( pwd ) +# testing... +# checkpoint_dir=$( pwd )/tmp-depot +# mkdir -p "${checkpoint_dir}" + +cd construction || clean_up 1 + +( cd p4d-docker && docker build -t "p4d-${basen}" . ) || clean_up 1 +( cd p4-client-docker && docker build -t "p4client-${basen}" . ) || clean_up 1 + +docker network create "p4-net-${basen}" || clean_up 1 + +# Start the Perforce server as a daemon process ("-d"). This will stay running in the background. +docker run --rm \ + --name "x-p4d-${basen}" \ + --network "p4-net-${basen}" \ + --network-alias "perforce.local" \ + -u "${current_uid}" \ + -v "${output_dir}:/opt/p4d-base" \ + -d \ + "p4d-${basen}" || clean_up 1 + +# Execute the Perforce construction tool. +docker run --rm \ + --name "x-p4clinet-${basen}" \ + --network "p4-net-${basen}" \ + "p4client-${basen}" || clean_up 1 + +# ExportListTest - leave checkpoint file in depot directory. +test -f "${output_dir}/checkpoint.1" || clean_up 1 +cp "${output_dir}/checkpoint.1" "${checkpoint_dir}/checkpoint" || clean_up 1 +test -f "${checkpoint_dir}/checkpoint.gz" && rm -f "${checkpoint_dir}/checkpoint.gz" +( cd "${checkpoint_dir}" && gzip -9 checkpoint) + +test -d "${output_dir}/depot" || clean_up 1 +test -d "${output_dir}/p4java_stream" || clean_up 1 +test -f "${checkpoint_dir}/depot.tar.gz" && rm -f "${checkpoint_dir}/depot.tar.gz" +( cd "${output_dir}" && tar zcf "${checkpoint_dir}/depot.tar.gz" depot p4java_stream checkpoint.1 ) + +chmod +w "${checkpoint_dir}/checkpoint.gz" "${checkpoint_dir}/depot.tar.gz" diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/.dockerignore b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/.dockerignore index 6e19512a..fd2f2d2e 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/.dockerignore +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/.dockerignore @@ -1,2 +1,2 @@ -.dockerignore -Dockerfile +.dockerignore +Dockerfile diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/Dockerfile b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/Dockerfile index 21e0c5a9..883ac135 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/Dockerfile +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/Dockerfile @@ -1,34 +1,34 @@ -FROM ubuntu:latest - -RUN echo "Start install" \ - && apt-get update \ - && apt-get -y upgrade \ - && apt-get -y install wget zip \ - && mkdir -p /opt/p4d-base \ - && cd /tmp \ - && wget http://ftp.perforce.com/perforce/r17.1/bin.linux26x86_64/p4 \ - && mv /tmp/p4 /usr/local/bin/. \ - && chmod +x /usr/local/bin/p4 \ - && echo "Completed install" - -COPY . /opt/client/ -COPY docker-entrypoint.sh / - -RUN echo "Setting up files" \ - && chmod +x /opt/client/*.sh \ - && mkdir -p /opt/client/bin \ - && cd /opt/client/bin \ - && cp /opt/client/text/text00.txt /opt/client/text/text01.txt /opt/client/text/text02.txt . \ - && gzip text00.txt \ - && mv text00.txt.gz bin00.bin \ - && gzip text01.txt \ - && mv text01.txt.gz bin01.bin \ - && gzip text02.txt \ - && mv text02.txt.gz bin02.bin \ - && echo "Completed setup" - -ENV P4PORT perforce.local:1666 - -WORKDIR /opt/client - -CMD [ "/bin/bash", "/docker-entrypoint.sh" ] +FROM ubuntu:latest + +RUN echo "Start install" \ + && apt-get update \ + && apt-get -y upgrade \ + && apt-get -y install wget zip \ + && mkdir -p /opt/p4d-base \ + && cd /tmp \ + && wget http://ftp.perforce.com/perforce/r17.1/bin.linux26x86_64/p4 \ + && mv /tmp/p4 /usr/local/bin/. \ + && chmod +x /usr/local/bin/p4 \ + && echo "Completed install" + +COPY . /opt/client/ +COPY docker-entrypoint.sh / + +RUN echo "Setting up files" \ + && chmod +x /opt/client/*.sh \ + && mkdir -p /opt/client/bin \ + && cd /opt/client/bin \ + && cp /opt/client/text/text00.txt /opt/client/text/text01.txt /opt/client/text/text02.txt . \ + && gzip text00.txt \ + && mv text00.txt.gz bin00.bin \ + && gzip text01.txt \ + && mv text01.txt.gz bin01.bin \ + && gzip text02.txt \ + && mv text02.txt.gz bin02.bin \ + && echo "Completed setup" + +ENV P4PORT perforce.local:1666 + +WORKDIR /opt/client + +CMD [ "/bin/bash", "/docker-entrypoint.sh" ] diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/branch-specs/p4jtest-job043500-branch.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/branch-specs/p4jtest-job043500-branch.spec index 7e2db9a2..23ac7a4c 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/branch-specs/p4jtest-job043500-branch.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/branch-specs/p4jtest-job043500-branch.spec @@ -1,11 +1,11 @@ -Branch: p4jtest-job043500-branch - -Owner: p4jtestuser - -Description: - Job043500Test - -Options: unlocked - -View: - //depot/111bugs/Bugs111_Job043500Test/src/... //depot/111bugs/Bugs111_Job043500Test/tgt/... +Branch: p4jtest-job043500-branch + +Owner: p4jtestuser + +Description: + Job043500Test + +Options: unlocked + +View: + //depot/111bugs/Bugs111_Job043500Test/src/... //depot/111bugs/Bugs111_Job043500Test/tgt/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/Job060376Client.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/Job060376Client.spec index 88d72504..d362a7b7 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/Job060376Client.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/Job060376Client.spec @@ -1,11 +1,11 @@ -Client: Job060376Client -Owner: p4testuser -Description: - Created by p4testuser. Uses explicit 1-1 mapping for files. -Root: /tmp/client/Job060376Client -Options: noallwrite noclobber nocompress unlocked nomodtime normdir -SubmitOptions: submitunchanged -LineEnd: local -View: - //depot/112Dev/testreplacing1/testfile1.txt //Job060376Client/112Dev/testreplacing1/testfile1.txt - //depot/112Dev/testreplacing2/testfile1.txt //Job060376Client/112Dev/testreplacing2/testfile1.txt +Client: Job060376Client +Owner: p4testuser +Description: + Created by p4testuser. Uses explicit 1-1 mapping for files. +Root: /tmp/client/Job060376Client +Options: noallwrite noclobber nocompress unlocked nomodtime normdir +SubmitOptions: submitunchanged +LineEnd: local +View: + //depot/112Dev/testreplacing1/testfile1.txt //Job060376Client/112Dev/testreplacing1/testfile1.txt + //depot/112Dev/testreplacing2/testfile1.txt //Job060376Client/112Dev/testreplacing2/testfile1.txt diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/Job072688Client.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/Job072688Client.spec index 6766d722..5c6a655c 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/Job072688Client.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/Job072688Client.spec @@ -1,10 +1,10 @@ -Client: p4TestUserWS -Owner: p4testuser -Description: - Created by p4testuser. -Root: /tmp/client/p4TestUserWS -Options: noallwrite noclobber nocompress unlocked nomodtime normdir -SubmitOptions: submitunchanged -LineEnd: local -View: - //depot/... //p4TestUserWS/... +Client: p4TestUserWS +Owner: p4testuser +Description: + Created by p4testuser. +Root: /tmp/client/p4TestUserWS +Options: noallwrite noclobber nocompress unlocked nomodtime normdir +SubmitOptions: submitunchanged +LineEnd: local +View: + //depot/... //p4TestUserWS/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/Job072688NT.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/Job072688NT.spec index 0370f430..c9abb2d4 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/Job072688NT.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/Job072688NT.spec @@ -1,10 +1,10 @@ -Client: p4TestUserWSNT -Owner: p4testuser -Description: - Created by p4testuser. -Root: c:\Windows\temp\client\p4TestUserWSNT -Options: noallwrite noclobber nocompress unlocked nomodtime normdir -SubmitOptions: submitunchanged -LineEnd: local -View: - //depot/... //p4TestUserWSNT/... +Client: p4TestUserWSNT +Owner: p4testuser +Description: + Created by p4testuser. +Root: c:\Windows\temp\client\p4TestUserWSNT +Options: noallwrite noclobber nocompress unlocked nomodtime normdir +SubmitOptions: submitunchanged +LineEnd: local +View: + //depot/... //p4TestUserWSNT/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS-MacLinux.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS-MacLinux.spec index d0b32e0b..a6627fa6 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS-MacLinux.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS-MacLinux.spec @@ -1,10 +1,10 @@ -Client: p4TestUserWS-MacLinux -Owner: p4testuser -Description: - Created by p4testuser. -Root: /tmp/client/p4TestUserWS-MacLinux -Options: noallwrite noclobber nocompress unlocked nomodtime normdir -SubmitOptions: submitunchanged -LineEnd: local -View: - //depot/... //p4TestUserWS-MacLinux/... +Client: p4TestUserWS-MacLinux +Owner: p4testuser +Description: + Created by p4testuser. +Root: /tmp/client/p4TestUserWS-MacLinux +Options: noallwrite noclobber nocompress unlocked nomodtime normdir +SubmitOptions: submitunchanged +LineEnd: local +View: + //depot/... //p4TestUserWS-MacLinux/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS-Windows.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS-Windows.spec index 7a219a6e..62b0aea0 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS-Windows.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS-Windows.spec @@ -1,10 +1,10 @@ -Client: p4TestUserWS-Windows -Owner: p4testuser -Description: - Created by p4testuser. -Root: c:\Windows\temp\client\p4TestUserWS-Windows -Options: noallwrite noclobber nocompress unlocked nomodtime normdir -SubmitOptions: submitunchanged -LineEnd: local -View: - //depot/... //p4TestUserWS-Windows/... +Client: p4TestUserWS-Windows +Owner: p4testuser +Description: + Created by p4testuser. +Root: c:\Windows\temp\client\p4TestUserWS-Windows +Options: noallwrite noclobber nocompress unlocked nomodtime normdir +SubmitOptions: submitunchanged +LineEnd: local +View: + //depot/... //p4TestUserWS-Windows/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS.spec index 6766d722..5c6a655c 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS.spec @@ -1,10 +1,10 @@ -Client: p4TestUserWS -Owner: p4testuser -Description: - Created by p4testuser. -Root: /tmp/client/p4TestUserWS -Options: noallwrite noclobber nocompress unlocked nomodtime normdir -SubmitOptions: submitunchanged -LineEnd: local -View: - //depot/... //p4TestUserWS/... +Client: p4TestUserWS +Owner: p4testuser +Description: + Created by p4testuser. +Root: /tmp/client/p4TestUserWS +Options: noallwrite noclobber nocompress unlocked nomodtime normdir +SubmitOptions: submitunchanged +LineEnd: local +View: + //depot/... //p4TestUserWS/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS20112.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS20112.spec index 2c7faa21..e1f87e26 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS20112.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS20112.spec @@ -1,10 +1,10 @@ -Client: p4TestUserWS20112 -Owner: p4testuser -Description: - Created by p4testuser. ReconcileWorkspaceFilesTest needs "rmdir" -Root: /tmp/client/p4TestUserWS20112 -Options: noallwrite noclobber nocompress unlocked nomodtime rmdir -SubmitOptions: submitunchanged -LineEnd: local -View: - //depot/... //p4TestUserWS20112/... +Client: p4TestUserWS20112 +Owner: p4testuser +Description: + Created by p4testuser. ReconcileWorkspaceFilesTest needs "rmdir" +Root: /tmp/client/p4TestUserWS20112 +Options: noallwrite noclobber nocompress unlocked nomodtime rmdir +SubmitOptions: submitunchanged +LineEnd: local +View: + //depot/... //p4TestUserWS20112/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS20112NT.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS20112NT.spec index 52b6c358..e595f0fc 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS20112NT.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWS20112NT.spec @@ -1,10 +1,10 @@ -Client: p4TestUserWS20112NT -Owner: p4testuser -Description: - Created by p4testuser. ReconcileWorkspaceFilesTest needs "rmdir" -Root: c:\Windows\temp\client\p4TestUserWS20112NT -Options: noallwrite noclobber nocompress unlocked nomodtime rmdir -SubmitOptions: submitunchanged -LineEnd: local -View: - //depot/... //p4TestUserWS20112NT/... +Client: p4TestUserWS20112NT +Owner: p4testuser +Description: + Created by p4testuser. ReconcileWorkspaceFilesTest needs "rmdir" +Root: c:\Windows\temp\client\p4TestUserWS20112NT +Options: noallwrite noclobber nocompress unlocked nomodtime rmdir +SubmitOptions: submitunchanged +LineEnd: local +View: + //depot/... //p4TestUserWS20112NT/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWSNT.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWSNT.spec index 0370f430..c9abb2d4 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWSNT.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4TestUserWSNT.spec @@ -1,10 +1,10 @@ -Client: p4TestUserWSNT -Owner: p4testuser -Description: - Created by p4testuser. -Root: c:\Windows\temp\client\p4TestUserWSNT -Options: noallwrite noclobber nocompress unlocked nomodtime normdir -SubmitOptions: submitunchanged -LineEnd: local -View: - //depot/... //p4TestUserWSNT/... +Client: p4TestUserWSNT +Owner: p4testuser +Description: + Created by p4testuser. +Root: c:\Windows\temp\client\p4TestUserWSNT +Options: noallwrite noclobber nocompress unlocked nomodtime normdir +SubmitOptions: submitunchanged +LineEnd: local +View: + //depot/... //p4TestUserWSNT/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_dev.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_dev.spec index ca86f67d..3cf2f0e4 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_dev.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_dev.spec @@ -1,11 +1,11 @@ -Client: p4java_stream_dev -Owner: p4testuser -Description: - Created by p4testuser. -Root: /tmp/client/p4java_stream_dev -Options: noallwrite noclobber nocompress unlocked nomodtime normdir -SubmitOptions: submitunchanged -LineEnd: local -Stream: //p4java_stream/dev -View: - //p4java_stream/dev/... //p4java_stream_dev/... +Client: p4java_stream_dev +Owner: p4testuser +Description: + Created by p4testuser. +Root: /tmp/client/p4java_stream_dev +Options: noallwrite noclobber nocompress unlocked nomodtime normdir +SubmitOptions: submitunchanged +LineEnd: local +Stream: //p4java_stream/dev +View: + //p4java_stream/dev/... //p4java_stream_dev/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_devNT.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_devNT.spec index 8abbfe4e..0102652c 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_devNT.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_devNT.spec @@ -1,11 +1,11 @@ -Client: p4java_stream_devNT -Owner: p4testuser -Description: - Created by p4testuser. -Root: c:\Windows\temp\client\p4java_stream_devNT -Options: noallwrite noclobber nocompress unlocked nomodtime normdir -SubmitOptions: submitunchanged -LineEnd: local -Stream: //p4java_stream/dev -View: - //p4java_stream/dev/... //p4java_stream_devNT/... +Client: p4java_stream_devNT +Owner: p4testuser +Description: + Created by p4testuser. +Root: c:\Windows\temp\client\p4java_stream_devNT +Options: noallwrite noclobber nocompress unlocked nomodtime normdir +SubmitOptions: submitunchanged +LineEnd: local +Stream: //p4java_stream/dev +View: + //p4java_stream/dev/... //p4java_stream_devNT/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_main.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_main.spec index 7f1e6d75..22f9319d 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_main.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_main.spec @@ -1,11 +1,11 @@ -Client: p4java_stream_main -Owner: p4testuser -Description: - Created by p4testuser. -Root: /tmp/client/p4java_stream_main -Options: noallwrite noclobber nocompress unlocked nomodtime normdir -SubmitOptions: submitunchanged -LineEnd: local -Stream: //p4java_stream/main -View: - //p4java_stream/main/... //p4java_stream_main/... +Client: p4java_stream_main +Owner: p4testuser +Description: + Created by p4testuser. +Root: /tmp/client/p4java_stream_main +Options: noallwrite noclobber nocompress unlocked nomodtime normdir +SubmitOptions: submitunchanged +LineEnd: local +Stream: //p4java_stream/main +View: + //p4java_stream/main/... //p4java_stream_main/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_mainNT.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_mainNT.spec index d7fcc2dc..3a17fd3a 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_mainNT.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/p4java_stream_mainNT.spec @@ -1,11 +1,11 @@ -Client: p4java_stream_mainNT -Owner: p4testuser -Description: - Created by p4testuser. -Root: c:\Windows\temp\client\p4java_stream_mainNT -Options: noallwrite noclobber nocompress unlocked nomodtime normdir -SubmitOptions: submitunchanged -LineEnd: local -Stream: //p4java_stream/main -View: - //p4java_stream/main/... //p4java_stream_mainNT/... +Client: p4java_stream_mainNT +Owner: p4testuser +Description: + Created by p4testuser. +Root: c:\Windows\temp\client\p4java_stream_mainNT +Options: noallwrite noclobber nocompress unlocked nomodtime normdir +SubmitOptions: submitunchanged +LineEnd: local +Stream: //p4java_stream/main +View: + //p4java_stream/main/... //p4java_stream_mainNT/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/superWS.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/superWS.spec index 3f908e6e..b42b1af2 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/superWS.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/client-specs/superWS.spec @@ -1,10 +1,10 @@ -Client: superWS -Owner: p4testsuper -Description: - Created by p4testsuper. -Root: /tmp/client/p4testsuper -Options: noallwrite noclobber nocompress unlocked nomodtime rmdir -SubmitOptions: submitunchanged -LineEnd: local -View: - //depot/... //superWS/... +Client: superWS +Owner: p4testsuper +Description: + Created by p4testsuper. +Root: /tmp/client/p4testsuper +Options: noallwrite noclobber nocompress unlocked nomodtime rmdir +SubmitOptions: submitunchanged +LineEnd: local +View: + //depot/... //superWS/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/depot-specs/p4java_stream.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/depot-specs/p4java_stream.spec index 91880713..6af30b7f 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/depot-specs/p4java_stream.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/depot-specs/p4java_stream.spec @@ -1,12 +1,12 @@ -Depot: p4java_stream - -Owner: p4jtestsuper - -Description: - stream depot - -Type: stream - -StreamDepth: //p4java_stream/1 - -Map: p4java_stream/... +Depot: p4java_stream + +Owner: p4jtestsuper + +Description: + stream depot + +Type: stream + +StreamDepth: //p4java_stream/1 + +Map: p4java_stream/... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/docker-entrypoint.sh b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/docker-entrypoint.sh index f0c3e88e..6926c733 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/docker-entrypoint.sh +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/docker-entrypoint.sh @@ -1,28 +1,28 @@ -#!/bin/bash - -# Wait up to 120 seconds for the Perforce server to be available. - -echo "Using Perforce server ${P4PORT}" -ec=0 -p4d_up=no -while [ ${ec} -lt 12 ] && [ ${p4d_up} = no ] ; do - p4 info >/dev/null 2>&1 - if [ $? = 0 ] ; then - p4d_up=yes - else - echo "Waiting 10 seconds for server to come up..." - ec=$(( ec + 1 )) - sleep 10 - fi -done -if [ ${p4d_up} = no ] ; then - echo "Could not find Perforce server; halting." - exit 1 -fi - -export SOURCE_ROOT=/opt/client -for i in /opt/client/setup-*.sh ; do - "$i" || exit 1 -done - - +#!/bin/bash + +# Wait up to 120 seconds for the Perforce server to be available. + +echo "Using Perforce server ${P4PORT}" +ec=0 +p4d_up=no +while [ ${ec} -lt 12 ] && [ ${p4d_up} = no ] ; do + p4 info >/dev/null 2>&1 + if [ $? = 0 ] ; then + p4d_up=yes + else + echo "Waiting 10 seconds for server to come up..." + ec=$(( ec + 1 )) + sleep 10 + fi +done +if [ ${p4d_up} = no ] ; then + echo "Could not find Perforce server; halting." + exit 1 +fi + +export SOURCE_ROOT=/opt/client +for i in /opt/client/setup-*.sh ; do + "$i" || exit 1 +done + + diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/group-specs/p4jtestgroup.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/group-specs/p4jtestgroup.spec index f17101d6..b28a8866 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/group-specs/p4jtestgroup.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/group-specs/p4jtestgroup.spec @@ -1,13 +1,13 @@ -Group: p4jtestgroup -MaxResults: 500000 -MaxScanRows: 5000000 -MaxLockTime: unset -MaxOpenFiles: unset -Timeout: unset -PasswordTimeout: unlimited -Subgroups: - -Owners: - -Users: - p4jtestuser +Group: p4jtestgroup +MaxResults: 500000 +MaxScanRows: 5000000 +MaxLockTime: unset +MaxOpenFiles: unset +Timeout: unset +PasswordTimeout: unlimited +Subgroups: + +Owners: + +Users: + p4jtestuser diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/group-specs/p4jtestgroup01.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/group-specs/p4jtestgroup01.spec index 56465e96..ad1d74ae 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/group-specs/p4jtestgroup01.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/group-specs/p4jtestgroup01.spec @@ -1,13 +1,13 @@ -Group: p4jtestgroup01 -MaxResults: 500000 -MaxScanRows: 5000000 -MaxLockTime: unset -MaxOpenFiles: unset -Timeout: unset -PasswordTimeout: 43002 -Subgroups: - -Owners: - -Users: - p4jtestuser +Group: p4jtestgroup01 +MaxResults: 500000 +MaxScanRows: 5000000 +MaxLockTime: unset +MaxOpenFiles: unset +Timeout: unset +PasswordTimeout: 43002 +Subgroups: + +Owners: + +Users: + p4jtestuser diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/group-specs/p4users.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/group-specs/p4users.spec index 501208a3..5838a0ac 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/group-specs/p4users.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/group-specs/p4users.spec @@ -1,13 +1,13 @@ -Group: p4users -MaxResults: 500000 -MaxScanRows: 5000000 -MaxLockTime: unset -MaxOpenFiles: unset -Timeout: unset -PasswordTimeout: unset -Subgroups: - -Owners: - -Users: - p4jtestuser +Group: p4users +MaxResults: 500000 +MaxScanRows: 5000000 +MaxLockTime: unset +MaxOpenFiles: unset +Timeout: unset +PasswordTimeout: unset +Subgroups: + +Owners: + +Users: + p4jtestuser diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/job-specs/job000001.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/job-specs/job000001.spec index e014e3b8..7ed0cd68 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/job-specs/job000001.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/job-specs/job000001.spec @@ -1,5 +1,5 @@ -Job: job000001 -Status: open -User: p4jtestuser -Description: - A job +Job: job000001 +Status: open +User: p4jtestuser +Description: + A job diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/job-specs/job000002.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/job-specs/job000002.spec index 3f36d9ce..e5611cbe 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/job-specs/job000002.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/job-specs/job000002.spec @@ -1,5 +1,5 @@ -Job: job000002 -Status: open -User: p4jtestuser -Description: - A job +Job: job000002 +Status: open +User: p4jtestuser +Description: + A job diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/job-specs/job000802.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/job-specs/job000802.spec index a548277e..efaffc66 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/job-specs/job000802.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/job-specs/job000802.spec @@ -1,5 +1,5 @@ -Job: job000802 -Status: open -User: p4jtestuser -Description: - job for Job040877Test +Job: job000802 +Status: open +User: p4jtestuser +Description: + job for Job040877Test diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/label-specs/LabelSyncTestLabel.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/label-specs/LabelSyncTestLabel.spec index 20b76028..4b0adf7e 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/label-specs/LabelSyncTestLabel.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/label-specs/LabelSyncTestLabel.spec @@ -1,9 +1,9 @@ -Label: LabelSyncTestLabel -Owner: p4jtestuser -Description: - LabelSynTest label. -Options: unlocked noautoreload -View: - //depot/basic/readonly/labelsync/... -#Revision: -# @1234 +Label: LabelSyncTestLabel +Owner: p4jtestuser +Description: + LabelSynTest label. +Options: unlocked noautoreload +View: + //depot/basic/readonly/labelsync/... +#Revision: +# @1234 diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/protect-specs/protect.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/protect-specs/protect.spec index eba332d8..375ec339 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/protect-specs/protect.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/protect-specs/protect.spec @@ -1,6 +1,6 @@ - -protections: - write user * //... - write group p4users //... - operator user p4jtestoperator //... - super user p4jtestsuper //... + +protections: + write user * //... + write group p4users //... + operator user p4jtestoperator //... + super user p4jtestsuper //... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-00.sh b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-00.sh index 999baa41..16407fa1 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-00.sh +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-00.sh @@ -1,54 +1,54 @@ -#!/bin/bash -x - -set -e - -# Setup users, protections, and clients. - -export P4USER=p4jtestsuper -export P4PASSWD=p4jtestsuper - -p4 protect -i protect-specs/protect.spec - -for i in user-specs/*.spec ; do - username=$( basename "${i}" .spec ) - p4 user -f -i < "${i}" - # the root user may have been added before this. Explicitly - # remove it inside the user loop, in case we go over the free server limit. - p4 user -f -d root || true - - # CreateDeleteUser: dummy user does not set its password - if [ "${username}" != "p4jtestdummy" ] ; then - p4 passwd -P "${username}" "${username}" - fi -done - -for i in depot-specs/*.spec ; do - p4 depot -i < "${i}" -done - -for i in stream-specs/*.spec ; do - p4 stream -i < "${i}" -done - -for i in client-specs/*.spec ; do - p4 client -i < "${i}" -done - -for i in job-specs/*.spec ; do - p4 job -i < "${i}" -done - -for i in branch-specs/*.spec ; do - p4 branch -i < "${i}" -done - -for i in group-specs/*.spec ; do - p4 group -i < "${i}" -done - -for i in label-specs/*.spec ; do - p4 label -i < "${i}" -done - -# explicit counters at initialization time. -p4 counter -f job 100 +#!/bin/bash -x + +set -e + +# Setup users, protections, and clients. + +export P4USER=p4jtestsuper +export P4PASSWD=p4jtestsuper + +p4 protect -i protect-specs/protect.spec + +for i in user-specs/*.spec ; do + username=$( basename "${i}" .spec ) + p4 user -f -i < "${i}" + # the root user may have been added before this. Explicitly + # remove it inside the user loop, in case we go over the free server limit. + p4 user -f -d root || true + + # CreateDeleteUser: dummy user does not set its password + if [ "${username}" != "p4jtestdummy" ] ; then + p4 passwd -P "${username}" "${username}" + fi +done + +for i in depot-specs/*.spec ; do + p4 depot -i < "${i}" +done + +for i in stream-specs/*.spec ; do + p4 stream -i < "${i}" +done + +for i in client-specs/*.spec ; do + p4 client -i < "${i}" +done + +for i in job-specs/*.spec ; do + p4 job -i < "${i}" +done + +for i in branch-specs/*.spec ; do + p4 branch -i < "${i}" +done + +for i in group-specs/*.spec ; do + p4 group -i < "${i}" +done + +for i in label-specs/*.spec ; do + p4 label -i < "${i}" +done + +# explicit counters at initialization time. +p4 counter -f job 100 diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-01.sh b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-01.sh index 9a9446cc..50f51291 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-01.sh +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-01.sh @@ -1,367 +1,367 @@ -#!/bin/sh -x - -set -e - -# Hard-coded changelist numbers... -export P4USER=p4jtestsuper -export P4PASSWD=p4jtestsuper -export P4CLIENT=superWS - -# ChangelistTypeFieldTest -# create a restricted changelist; it must have an exact changelist number -echo 'Change: new -Client: '$P4CLIENT' -User: '$P4USER' -Status: new -Type: restricted -Description: - test ChangelistTypeFieldTest -' > /tmp/ch3.spec -ChangelistTypeFieldTest_change=$( p4 change -i < /tmp/ch3.spec | cut -f 2 -d ' ' ) -test "${ChangelistTypeFieldTest_change}" = "1" - - -# Basic files. - -export P4USER=p4jtestuser -export P4PASSWD=p4jtestuser -export P4CLIENT=p4TestUserWS - -# hard-coded root at /tmp/client/p4TestUserWS - -mkdir -p /tmp/client/p4TestUserWS -cd /tmp/client/p4TestUserWS -p4 sync ... - - -# General Tests - -mkdir -p SandboxTest/Attributes -cp "$SOURCE_ROOT/text/text00.txt" SandboxTest/Attributes/test01.txt -p4 add SandboxTest/Attributes/test01.txt -p4 submit -d "sandbox test files 1" -p4 edit SandboxTest/Attributes/test01.txt -cp "$SOURCE_ROOT/text/text01.txt" SandboxTest/Attributes/test01.txt -p4 submit -d "sandbox test files 2" - -# TODO this requires more work -mkdir -p client/ResolveFileStreamTest -cp "$SOURCE_ROOT/text/text01.txt" client/ResolveFileStreamTest/test01.txt -cp "$SOURCE_ROOT/text/text02.txt" client/ResolveFileStreamTest/test02.txt -p4 add client/ResolveFileStreamTest/test01.txt client/ResolveFileStreamTest/test02.txt -p4 submit -d "ResolveFileStreamTest 1" - - -# features 112 tests - -# GetOpenedFilesTest -mkdir -p 112Dev/GetOpenedFilesTest -echo "00" > 112Dev/GetOpenedFilesTest/00.txt -echo "01" > 112Dev/GetOpenedFilesTest/01.txt -echo "02" > 112Dev/GetOpenedFilesTest/02.txt -echo "03" > 112Dev/GetOpenedFilesTest/03.txt -echo "04" > 112Dev/GetOpenedFilesTest/04.txt -echo "05" > 112Dev/GetOpenedFilesTest/05.txt -echo "06" > 112Dev/GetOpenedFilesTest/06.txt -echo "07" > 112Dev/GetOpenedFilesTest/07.txt -echo "08" > 112Dev/GetOpenedFilesTest/08.txt -echo "09" > 112Dev/GetOpenedFilesTest/09.txt -p4 add 112Dev/GetOpenedFilesTest/* -p4 submit -d "feature 112 GetOpenedFilesTest 1" - -mkdir -p 112Dev/GetOpenedFilesTest/bin/gnu/getopt -mkdir -p 112Dev/GetOpenedFilesTest/src/gnu/getopt -cp "$SOURCE_ROOT/text/text00.txt" 112Dev/GetOpenedFilesTest/bin/gnu/getopt/MessagesBundle_es.properties -# MergeFilesTest uses the src files. -cp "$SOURCE_ROOT/text/text00.txt" 112Dev/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_es.properties -cp "$SOURCE_ROOT/text/text01.txt" 112Dev/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_it.properties -# MessagesBundle_ro.properties uses the ro file -cp "$SOURCE_ROOT/text/text02.txt" 112Dev/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ro.properties -p4 add \ - 112Dev/GetOpenedFilesTest/bin/gnu/getopt/MessagesBundle_es.properties \ - 112Dev/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_es.properties \ - 112Dev/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_it.properties \ - 112Dev/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ro.properties -p4 submit -d "feature 112 GetOpenedFilesTest 2" - -p4 integrate 112Dev/GetOpenedFilesTest/bin/gnu/getopt/MessagesBundle_es.properties#1 112Dev/GetOpenedFilesTest/bin/gnu/getopt/main303385669/MessagesBundle_es.properties -p4 submit -d "feature 112 GetOpenedFilesTest 3a" -p4 edit 112Dev/GetOpenedFilesTest/bin/gnu/getopt/main303385669/MessagesBundle_es.properties -cp "$SOURCE_ROOT/text/text01.txt" 112Dev/GetOpenedFilesTest/bin/gnu/getopt/main303385669/MessagesBundle_es.properties -p4 submit -d "feature 112 GetOpenedFilesTest 3b" -p4 delete 112Dev/GetOpenedFilesTest/bin/gnu/getopt/main303385669/MessagesBundle_es.properties -p4 submit -d "feature 112 GetOpenedFilesTest 3c" - -p4 integrate 112Dev/GetOpenedFilesTest/bin/gnu/getopt/main303385669/MessagesBundle_es.properties#1 112Dev/GetOpenedFilesTest/bin/gnu/getopt/release303385669/MessagesBundle_es.properties -p4 submit -d "feature 112 GetOpenedFilesTest 5a" -p4 delete 112Dev/GetOpenedFilesTest/bin/gnu/getopt/release303385669/MessagesBundle_es.properties -p4 submit -d "feature 112 GetOpenedFilesTest 5b" - -mkdir -p 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd -cp "$SOURCE_ROOT/text/text00.txt" 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java -p4 add 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java -p4 submit -d "feature 112 GetOpenedFilesTest 6a" -p4 edit 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java -cp "$SOURCE_ROOT/text/text01.txt" 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java -p4 submit -d "feature 112 GetOpenedFilesTest 6b" -p4 edit 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java -cp "$SOURCE_ROOT/text/text02.txt" 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java -p4 submit -d "feature 112 GetOpenedFilesTest 6c" - -p4 integrate 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java#1 112Dev/GetOpenedFilesTest/src/com/perforce/branch534212163/P4CmdDispatcher.java -p4 submit -d "feature 112 GetOpenedFilesTest 7a" -p4 delete 112Dev/GetOpenedFilesTest/src/com/perforce/branch534212163/P4CmdDispatcher.java -p4 submit -d "feature 112 GetOpenedFilesTest 7b" - -cp "$SOURCE_ROOT/text/text00.txt" 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdLogListener.java -p4 add 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdLogListener.java -p4 submit -d "feature 112 GetOpenedFilesTest 8a" -p4 edit 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdLogListener.java -cp "$SOURCE_ROOT/text/text01.txt" 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdLogListener.java -p4 submit -d "feature 112 GetOpenedFilesTest 8b" -p4 edit 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdLogListener.java -cp "$SOURCE_ROOT/text/text02.txt" 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdLogListener.java -p4 submit -d "feature 112 GetOpenedFilesTest 8c" - -mkdir -p 112Dev/xbin -cp "$SOURCE_ROOT/bin/bin00.bin" 112Dev/xbin/eclipse.exe -p4 add -t xbinary 112Dev/xbin/eclipse.exe -p4 submit -d "feature 112 binary type 1" -cp "$SOURCE_ROOT/bin/bin00.bin" 112Dev/xbin/eclipse1175795839.exe -p4 add -t xbinary 112Dev/xbin/eclipse1175795839.exe -p4 submit -d "feature 112 binary type 2" -p4 delete 112Dev/xbin/eclipse1175795839.exe -p4 submit -d "feature 112 binary type 3" - - - -# Job035290Test: -# requires file at revision 4. -mkdir -p 92bugs/Job035290Test - -cp "$SOURCE_ROOT/text/text00.txt" 92bugs/Job035290Test/Job035290TestNew.txt -p4 add 92bugs/Job035290Test/Job035290TestNew.txt -p4 submit -d 'Job035290Test: revision 1' -p4 edit 92bugs/Job035290Test/Job035290TestNew.txt -cp "$SOURCE_ROOT/text/text01.txt" 92bugs/Job035290Test/Job035290TestNew.txt -p4 submit -d 'Job035290Test: revision 2' -p4 edit 92bugs/Job035290Test/Job035290TestNew.txt -cp "$SOURCE_ROOT/text/text00.txt" 92bugs/Job035290Test/Job035290TestNew.txt -p4 submit -d 'Job035290Test: revision 3' -p4 edit 92bugs/Job035290Test/Job035290TestNew.txt -cp "$SOURCE_ROOT/text/text01.txt" 92bugs/Job035290Test/Job035290TestNew.txt -p4 submit -d 'Job035290Test: revision 4' - -echo "Testing Job035290Test compatibility..." -p4 -Ztag files //depot/92bugs/Job035290Test/Job035290TestNew.txt#4 - - - -# Job036949Test -# requires annotation output with last line of "(no newline)" -# can't figure out how to get it to do that. Note that explicitly -# adding that line still has the diff terminate with a newline. -mkdir -p 92bugs/Job036949Test -echo -n 'line 1 -line 2 -line 3 -' > 92bugs/Job036949Test/annotatetest.txt -p4 add 92bugs/Job036949Test/annotatetest.txt -p4 submit -d 'Job036949Test: added file' -p4 edit 92bugs/Job036949Test/annotatetest.txt -echo -n 'line 1 -line 2 -line 99 -(no newline)' > 92bugs/Job036949Test/annotatetest.txt -p4 submit -d 'Job036949Test: altered file' - -echo "Testing Job036949Test compatibility..." -p4 annotate //depot/92bugs/Job036949Test/annotatetest.txt - - -# Job043500Test -# performs a copy + submit, so the target must exist. -mkdir -p 111bugs/Bugs111_Job043500Test/src -cp "$SOURCE_ROOT/text/text01.txt" 111bugs/Bugs111_Job043500Test/src/test01.txt -mkdir -p 111bugs/Bugs111_Job043500Test/tgt -cp "$SOURCE_ROOT/text/text01.txt" 111bugs/Bugs111_Job043500Test/tgt/test01.txt -p4 add 111bugs/Bugs111_Job043500Test/src/test01.txt 111bugs/Bugs111_Job043500Test/tgt/test01.txt -p4 submit -d "Job043500Test: new files" - - -# Job040703Test - needs at least changes with jobs -echo 'Change: new -Client: '$P4CLIENT' -User: '$P4USER' -Status: new -Jobs: - job000001 -Description: - test Job040703Test jobs; Job040649: required files -' > /tmp/ch1.spec -job040703_change1=$( p4 change -i < /tmp/ch1.spec | cut -f 2 -d ' ' ) - - -# Job040649 -mkdir -p 101Bugs/Bugs101_Job040649Test -cp "$SOURCE_ROOT/text/text01.txt" 101Bugs/Bugs101_Job040649Test/test01.txt -# test02 is the destination, so it should not be added. -p4 add -c "${job040703_change1}" 101Bugs/Bugs101_Job040649Test/test01.txt -p4 submit -c "${job040703_change1}" - - - -# Job040703Test - needs at least changes with jobs -echo 'Change: new -Client: '$P4CLIENT' -User: '$P4USER' -Status: new -Jobs: - job000002 -Description: - test Job040703Test jobs; Job040877Test: required files -' > /tmp/ch2.spec -job040703_change2=$( p4 change -i < /tmp/ch2.spec | cut -f 2 -d ' ' ) - - -# Job040877Test -mkdir -p 101Bugs/Bugs101_Job040877Test -cp "$SOURCE_ROOT/text/text01.txt" 101Bugs/Bugs101_Job040877Test/test01.txt -p4 add -c "${job040703_change2}" 101Bugs/Bugs101_Job040877Test/test01.txt -p4 submit -c "${job040703_change2}" - - -# ReconcileWorkspaceFilesTest -mkdir -p reconcile -( cd "${SOURCE_ROOT}/text" && zip -9r /tmp/client/p4TestUserWS/reconcile/TestFramework.zip * ) -p4 add reconcile/TestFramework.zip -p4 submit -d "ReconcileWorkspaceFilesTest: add zip file for tests" - - -# Job040762Test - requires an exact changelist for the edit. Original number is 6421. -mkdir -p 101Bugs/Bugs101_Job040762Test -cp "$SOURCE_ROOT/text/text01.txt" 101Bugs/Bugs101_Job040762Test/test01.txt -p4 add 101Bugs/Bugs101_Job040762Test/test01.txt -p4 submit -d "Job040762Test: add file" -p4 edit 101Bugs/Bugs101_Job040762Test/test01.txt -cp "$SOURCE_ROOT/text/text02.txt" 101Bugs/Bugs101_Job040762Test/test01.txt -res=$( p4 submit -d "Job040762Test: add file at exact changelist 33" ) -res_cl=$( echo "${res}" | sed -E '{ N; s/Change ([0-9]+) submitted\./\1/ ; D }' ) -test "${res_cl}" = "34" - - -# FileActionReplacedTest -mkdir -p 112Dev/testreplacing1 -cp "$SOURCE_ROOT/text/text00.txt" 112Dev/testreplacing1/testfile1.txt -mkdir -p 112Dev/testreplacing2 -cp "$SOURCE_ROOT/text/text01.txt" 112Dev/testreplacing2/testfile1.txt -p4 add 112Dev/testreplacing1/testfile1.txt 112Dev/testreplacing2/testfile1.txt -p4 submit -d "FileActionReplacedTest: add files" - - -# Job040346Test -mkdir -p 101Bugs/Bugs101_Job040346Test -cp "$SOURCE_ROOT/text/text00.txt" 101Bugs/Bugs101_Job040346Test/test01.txt -#cp "$SOURCE_ROOT/text/text01.txt" 101Bugs/Bugs101_Job040346Test/test02.txt -#p4 add 101Bugs/Bugs101_Job040346Test/test01.txt 101Bugs/Bugs101_Job040346Test/test02.txt -p4 add 101Bugs/Bugs101_Job040346Test/test01.txt -p4 submit -d "Job040346Test: add file" -p4 integrate 101Bugs/Bugs101_Job040346Test/test01.txt 101Bugs/Bugs101_Job040346Test/test02.txt -p4 submit -d "Job040346Test: integrate file" -p4 edit 101Bugs/Bugs101_Job040346Test/test01.txt -cp "$SOURCE_ROOT/text/text01.txt" 101Bugs/Bugs101_Job040346Test/test01.txt -p4 submit -d "Job040346Test: revise file" - - -# LabelSyncTest -mkdir -p basic/readonly/labelsync -# Need at least 11 files, each with a different revision... -i=1 -while [ $i -le 11 ] ; do - cp "$SOURCE_ROOT/text/text00.txt" basic/readonly/labelsync/test-${i}.txt - p4 add basic/readonly/labelsync/test-${i}.txt - p4 submit -d "LabelSyncTest: label file ${i} add" - p4 edit basic/readonly/labelsync/test-${i}.txt - cp "$SOURCE_ROOT/text/text01.txt" basic/readonly/labelsync/test-${i}.txt - p4 submit -d "LabelSyncTest: label file ${i} edit" - i=$(( $i + 1 )) -done -p4 labelsync -a -l LabelSyncTestLabel //depot/basic/readonly/labelsync/... - - -# SimpleSyncTest -mkdir -p basic/readonly/sync -cp "$SOURCE_ROOT/text/text00.txt" basic/readonly/sync/test00.txt -p4 add basic/readonly/sync/test00.txt -p4 submit -d "SimpleSyncTest: add file" - - -# GetMatchingLinesTest -# Requires 8 lines that match, -mkdir -p basic/readonly/grep -cp "$SOURCE_ROOT/text/text00.txt" basic/readonly/grep/test00.txt -cp "$SOURCE_ROOT/text/grep-text-P4Java.txt" basic/readonly/grep/test-match-01.txt -cp "$SOURCE_ROOT/text/grep-text-P4Java-x2.txt" basic/readonly/grep/test-match-02.txt -cp "$SOURCE_ROOT/text/grep-text-P4Java.txt" basic/readonly/grep/test-match-03.txt -cp "$SOURCE_ROOT/text/grep-text-P4Java-x2.txt" basic/readonly/grep/test-match-04.txt -cp "$SOURCE_ROOT/text/grep-text-P4Java-x2.txt" basic/readonly/grep/test-match-05.txt -cp "$SOURCE_ROOT/text/grep-text-P4Java-lower.txt" basic/readonly/grep/test-match-06.txt -p4 add basic/readonly/grep/* -p4 submit -d "GetMatchingLinesTest: add files" - - -# SyncIntegrityCheckTest -mkdir -p 102Dev/SyncIntegrityCheckTest -cp "$SOURCE_ROOT/text/text00.txt" 102Dev/SyncIntegrityCheckTest/test01.txt -p4 add 102Dev/SyncIntegrityCheckTest/test01.txt -cp "$SOURCE_ROOT/bin/bin00.bin" 102Dev/SyncIntegrityCheckTest/test02.jpg -# called "UBinary" in the source ... what type is required? -p4 add -t binary+SC 102Dev/SyncIntegrityCheckTest/test02.jpg -cp "$SOURCE_ROOT/bin/bin01.bin" 102Dev/SyncIntegrityCheckTest/test03.bin -p4 add -t binary 102Dev/SyncIntegrityCheckTest/test03.bin -p4 submit -d "SyncIntegrityCheckTest: add files" - - -# ClientIntegrationE2ETest -mkdir -p Dev/rteam/HBOP/admin -cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HBOP/admin/displayTool.pl -mkdir -p Dev/rteam/HBOP/src -cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HBOP/src/hbop1.html -cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HBOP/src/hbop4.java -cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HBOP/src/hbop5.txt -cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HBOP/src/hbop6.txt -cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HBOP/src/hbop7.txt -p4 add Dev/rteam/HBOP/src/* Dev/rteam/HBOP/admin/* -p4 submit -d "ClientIntegrationE2ETest: add hbop files" - -p4 integrate Dev/rteam/HBOP/... Dev/rteam/TLCP/... -cp "$SOURCE_ROOT/bin/bin00.bin" Dev/rteam/TLCP/src/p4merge_help.png -cp "$SOURCE_ROOT/bin/bin00.bin" Dev/rteam/TLCP/src/bindetmi2.dll -# Do not add the binary files as explicitly binary; this will mess up the test's integration resolution results. -# The resolve results will include an extra set of records for file type (along with content) integration. -p4 add Dev/rteam/TLCP/src/bindetmi2.dll Dev/rteam/TLCP/src/p4merge_help.png -p4 submit -d "ClientIntegrationE2ETest: add tlcp files" - -mkdir -p Dev/rteam/HOLD -cp "$SOURCE_ROOT/bin/bin00.bin" Dev/rteam/HOLD/old_p4java.jar -p4 add Dev/rteam/HOLD/old_p4java.jar -p4 submit -d "ClientIntegrationE2ETest: add hold files" - -p4 integrate Dev/rteam/TLCP/... Dev/rteam/SHOW/... -p4 submit -d "ClientIntegrationE2ETest: create show branch" - -mkdir -p Dev/rteam/HGTV/src -cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HGTV/src/homePlan.txt -cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HGTV/src/homeWorks.html -cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HGTV/src/hProj.java -cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HGTV/src/lProj.java -cp "$SOURCE_ROOT/bin/bin00.bin" Dev/rteam/HGTV/src/proj1.dll -p4 add Dev/rteam/HGTV/src/* -p4 integrate Dev/rteam/TLCP/admin/... Dev/rteam/HGTV/admin/... -p4 submit -d "ClientIntegrationE2ETest: create hgtv branch" - -p4 integrate Dev/rteam/HGTV/src/lProj.java Dev/rteam/HOLD/src/lProj.java -p4 submit -d "ClientIntegrationE2ETest: make another branch" -p4 edit Dev/rteam/HOLD/src/lProj.java -cp "$SOURCE_ROOT/text/text01.txt" Dev/rteam/HOLD/src/lProj.java -p4 submit -d "ClientIntegrationE2ETest: make a change in the branch" +#!/bin/sh -x + +set -e + +# Hard-coded changelist numbers... +export P4USER=p4jtestsuper +export P4PASSWD=p4jtestsuper +export P4CLIENT=superWS + +# ChangelistTypeFieldTest +# create a restricted changelist; it must have an exact changelist number +echo 'Change: new +Client: '$P4CLIENT' +User: '$P4USER' +Status: new +Type: restricted +Description: + test ChangelistTypeFieldTest +' > /tmp/ch3.spec +ChangelistTypeFieldTest_change=$( p4 change -i < /tmp/ch3.spec | cut -f 2 -d ' ' ) +test "${ChangelistTypeFieldTest_change}" = "1" + + +# Basic files. + +export P4USER=p4jtestuser +export P4PASSWD=p4jtestuser +export P4CLIENT=p4TestUserWS + +# hard-coded root at /tmp/client/p4TestUserWS + +mkdir -p /tmp/client/p4TestUserWS +cd /tmp/client/p4TestUserWS +p4 sync ... + + +# General Tests + +mkdir -p SandboxTest/Attributes +cp "$SOURCE_ROOT/text/text00.txt" SandboxTest/Attributes/test01.txt +p4 add SandboxTest/Attributes/test01.txt +p4 submit -d "sandbox test files 1" +p4 edit SandboxTest/Attributes/test01.txt +cp "$SOURCE_ROOT/text/text01.txt" SandboxTest/Attributes/test01.txt +p4 submit -d "sandbox test files 2" + +# TODO this requires more work +mkdir -p client/ResolveFileStreamTest +cp "$SOURCE_ROOT/text/text01.txt" client/ResolveFileStreamTest/test01.txt +cp "$SOURCE_ROOT/text/text02.txt" client/ResolveFileStreamTest/test02.txt +p4 add client/ResolveFileStreamTest/test01.txt client/ResolveFileStreamTest/test02.txt +p4 submit -d "ResolveFileStreamTest 1" + + +# features 112 tests + +# GetOpenedFilesTest +mkdir -p 112Dev/GetOpenedFilesTest +echo "00" > 112Dev/GetOpenedFilesTest/00.txt +echo "01" > 112Dev/GetOpenedFilesTest/01.txt +echo "02" > 112Dev/GetOpenedFilesTest/02.txt +echo "03" > 112Dev/GetOpenedFilesTest/03.txt +echo "04" > 112Dev/GetOpenedFilesTest/04.txt +echo "05" > 112Dev/GetOpenedFilesTest/05.txt +echo "06" > 112Dev/GetOpenedFilesTest/06.txt +echo "07" > 112Dev/GetOpenedFilesTest/07.txt +echo "08" > 112Dev/GetOpenedFilesTest/08.txt +echo "09" > 112Dev/GetOpenedFilesTest/09.txt +p4 add 112Dev/GetOpenedFilesTest/* +p4 submit -d "feature 112 GetOpenedFilesTest 1" + +mkdir -p 112Dev/GetOpenedFilesTest/bin/gnu/getopt +mkdir -p 112Dev/GetOpenedFilesTest/src/gnu/getopt +cp "$SOURCE_ROOT/text/text00.txt" 112Dev/GetOpenedFilesTest/bin/gnu/getopt/MessagesBundle_es.properties +# MergeFilesTest uses the src files. +cp "$SOURCE_ROOT/text/text00.txt" 112Dev/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_es.properties +cp "$SOURCE_ROOT/text/text01.txt" 112Dev/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_it.properties +# MessagesBundle_ro.properties uses the ro file +cp "$SOURCE_ROOT/text/text02.txt" 112Dev/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ro.properties +p4 add \ + 112Dev/GetOpenedFilesTest/bin/gnu/getopt/MessagesBundle_es.properties \ + 112Dev/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_es.properties \ + 112Dev/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_it.properties \ + 112Dev/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ro.properties +p4 submit -d "feature 112 GetOpenedFilesTest 2" + +p4 integrate 112Dev/GetOpenedFilesTest/bin/gnu/getopt/MessagesBundle_es.properties#1 112Dev/GetOpenedFilesTest/bin/gnu/getopt/main303385669/MessagesBundle_es.properties +p4 submit -d "feature 112 GetOpenedFilesTest 3a" +p4 edit 112Dev/GetOpenedFilesTest/bin/gnu/getopt/main303385669/MessagesBundle_es.properties +cp "$SOURCE_ROOT/text/text01.txt" 112Dev/GetOpenedFilesTest/bin/gnu/getopt/main303385669/MessagesBundle_es.properties +p4 submit -d "feature 112 GetOpenedFilesTest 3b" +p4 delete 112Dev/GetOpenedFilesTest/bin/gnu/getopt/main303385669/MessagesBundle_es.properties +p4 submit -d "feature 112 GetOpenedFilesTest 3c" + +p4 integrate 112Dev/GetOpenedFilesTest/bin/gnu/getopt/main303385669/MessagesBundle_es.properties#1 112Dev/GetOpenedFilesTest/bin/gnu/getopt/release303385669/MessagesBundle_es.properties +p4 submit -d "feature 112 GetOpenedFilesTest 5a" +p4 delete 112Dev/GetOpenedFilesTest/bin/gnu/getopt/release303385669/MessagesBundle_es.properties +p4 submit -d "feature 112 GetOpenedFilesTest 5b" + +mkdir -p 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd +cp "$SOURCE_ROOT/text/text00.txt" 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java +p4 add 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java +p4 submit -d "feature 112 GetOpenedFilesTest 6a" +p4 edit 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java +cp "$SOURCE_ROOT/text/text01.txt" 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java +p4 submit -d "feature 112 GetOpenedFilesTest 6b" +p4 edit 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java +cp "$SOURCE_ROOT/text/text02.txt" 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java +p4 submit -d "feature 112 GetOpenedFilesTest 6c" + +p4 integrate 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdDispatcher.java#1 112Dev/GetOpenedFilesTest/src/com/perforce/branch534212163/P4CmdDispatcher.java +p4 submit -d "feature 112 GetOpenedFilesTest 7a" +p4 delete 112Dev/GetOpenedFilesTest/src/com/perforce/branch534212163/P4CmdDispatcher.java +p4 submit -d "feature 112 GetOpenedFilesTest 7b" + +cp "$SOURCE_ROOT/text/text00.txt" 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdLogListener.java +p4 add 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdLogListener.java +p4 submit -d "feature 112 GetOpenedFilesTest 8a" +p4 edit 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdLogListener.java +cp "$SOURCE_ROOT/text/text01.txt" 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdLogListener.java +p4 submit -d "feature 112 GetOpenedFilesTest 8b" +p4 edit 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdLogListener.java +cp "$SOURCE_ROOT/text/text02.txt" 112Dev/GetOpenedFilesTest/src/com/perforce/p4cmd/P4CmdLogListener.java +p4 submit -d "feature 112 GetOpenedFilesTest 8c" + +mkdir -p 112Dev/xbin +cp "$SOURCE_ROOT/bin/bin00.bin" 112Dev/xbin/eclipse.exe +p4 add -t xbinary 112Dev/xbin/eclipse.exe +p4 submit -d "feature 112 binary type 1" +cp "$SOURCE_ROOT/bin/bin00.bin" 112Dev/xbin/eclipse1175795839.exe +p4 add -t xbinary 112Dev/xbin/eclipse1175795839.exe +p4 submit -d "feature 112 binary type 2" +p4 delete 112Dev/xbin/eclipse1175795839.exe +p4 submit -d "feature 112 binary type 3" + + + +# Job035290Test: +# requires file at revision 4. +mkdir -p 92bugs/Job035290Test + +cp "$SOURCE_ROOT/text/text00.txt" 92bugs/Job035290Test/Job035290TestNew.txt +p4 add 92bugs/Job035290Test/Job035290TestNew.txt +p4 submit -d 'Job035290Test: revision 1' +p4 edit 92bugs/Job035290Test/Job035290TestNew.txt +cp "$SOURCE_ROOT/text/text01.txt" 92bugs/Job035290Test/Job035290TestNew.txt +p4 submit -d 'Job035290Test: revision 2' +p4 edit 92bugs/Job035290Test/Job035290TestNew.txt +cp "$SOURCE_ROOT/text/text00.txt" 92bugs/Job035290Test/Job035290TestNew.txt +p4 submit -d 'Job035290Test: revision 3' +p4 edit 92bugs/Job035290Test/Job035290TestNew.txt +cp "$SOURCE_ROOT/text/text01.txt" 92bugs/Job035290Test/Job035290TestNew.txt +p4 submit -d 'Job035290Test: revision 4' + +echo "Testing Job035290Test compatibility..." +p4 -Ztag files //depot/92bugs/Job035290Test/Job035290TestNew.txt#4 + + + +# Job036949Test +# requires annotation output with last line of "(no newline)" +# can't figure out how to get it to do that. Note that explicitly +# adding that line still has the diff terminate with a newline. +mkdir -p 92bugs/Job036949Test +echo -n 'line 1 +line 2 +line 3 +' > 92bugs/Job036949Test/annotatetest.txt +p4 add 92bugs/Job036949Test/annotatetest.txt +p4 submit -d 'Job036949Test: added file' +p4 edit 92bugs/Job036949Test/annotatetest.txt +echo -n 'line 1 +line 2 +line 99 +(no newline)' > 92bugs/Job036949Test/annotatetest.txt +p4 submit -d 'Job036949Test: altered file' + +echo "Testing Job036949Test compatibility..." +p4 annotate //depot/92bugs/Job036949Test/annotatetest.txt + + +# Job043500Test +# performs a copy + submit, so the target must exist. +mkdir -p 111bugs/Bugs111_Job043500Test/src +cp "$SOURCE_ROOT/text/text01.txt" 111bugs/Bugs111_Job043500Test/src/test01.txt +mkdir -p 111bugs/Bugs111_Job043500Test/tgt +cp "$SOURCE_ROOT/text/text01.txt" 111bugs/Bugs111_Job043500Test/tgt/test01.txt +p4 add 111bugs/Bugs111_Job043500Test/src/test01.txt 111bugs/Bugs111_Job043500Test/tgt/test01.txt +p4 submit -d "Job043500Test: new files" + + +# Job040703Test - needs at least changes with jobs +echo 'Change: new +Client: '$P4CLIENT' +User: '$P4USER' +Status: new +Jobs: + job000001 +Description: + test Job040703Test jobs; Job040649: required files +' > /tmp/ch1.spec +job040703_change1=$( p4 change -i < /tmp/ch1.spec | cut -f 2 -d ' ' ) + + +# Job040649 +mkdir -p 101Bugs/Bugs101_Job040649Test +cp "$SOURCE_ROOT/text/text01.txt" 101Bugs/Bugs101_Job040649Test/test01.txt +# test02 is the destination, so it should not be added. +p4 add -c "${job040703_change1}" 101Bugs/Bugs101_Job040649Test/test01.txt +p4 submit -c "${job040703_change1}" + + + +# Job040703Test - needs at least changes with jobs +echo 'Change: new +Client: '$P4CLIENT' +User: '$P4USER' +Status: new +Jobs: + job000002 +Description: + test Job040703Test jobs; Job040877Test: required files +' > /tmp/ch2.spec +job040703_change2=$( p4 change -i < /tmp/ch2.spec | cut -f 2 -d ' ' ) + + +# Job040877Test +mkdir -p 101Bugs/Bugs101_Job040877Test +cp "$SOURCE_ROOT/text/text01.txt" 101Bugs/Bugs101_Job040877Test/test01.txt +p4 add -c "${job040703_change2}" 101Bugs/Bugs101_Job040877Test/test01.txt +p4 submit -c "${job040703_change2}" + + +# ReconcileWorkspaceFilesTest +mkdir -p reconcile +( cd "${SOURCE_ROOT}/text" && zip -9r /tmp/client/p4TestUserWS/reconcile/TestFramework.zip * ) +p4 add reconcile/TestFramework.zip +p4 submit -d "ReconcileWorkspaceFilesTest: add zip file for tests" + + +# Job040762Test - requires an exact changelist for the edit. Original number is 6421. +mkdir -p 101Bugs/Bugs101_Job040762Test +cp "$SOURCE_ROOT/text/text01.txt" 101Bugs/Bugs101_Job040762Test/test01.txt +p4 add 101Bugs/Bugs101_Job040762Test/test01.txt +p4 submit -d "Job040762Test: add file" +p4 edit 101Bugs/Bugs101_Job040762Test/test01.txt +cp "$SOURCE_ROOT/text/text02.txt" 101Bugs/Bugs101_Job040762Test/test01.txt +res=$( p4 submit -d "Job040762Test: add file at exact changelist 33" ) +res_cl=$( echo "${res}" | sed -E '{ N; s/Change ([0-9]+) submitted\./\1/ ; D }' ) +test "${res_cl}" = "34" + + +# FileActionReplacedTest +mkdir -p 112Dev/testreplacing1 +cp "$SOURCE_ROOT/text/text00.txt" 112Dev/testreplacing1/testfile1.txt +mkdir -p 112Dev/testreplacing2 +cp "$SOURCE_ROOT/text/text01.txt" 112Dev/testreplacing2/testfile1.txt +p4 add 112Dev/testreplacing1/testfile1.txt 112Dev/testreplacing2/testfile1.txt +p4 submit -d "FileActionReplacedTest: add files" + + +# Job040346Test +mkdir -p 101Bugs/Bugs101_Job040346Test +cp "$SOURCE_ROOT/text/text00.txt" 101Bugs/Bugs101_Job040346Test/test01.txt +#cp "$SOURCE_ROOT/text/text01.txt" 101Bugs/Bugs101_Job040346Test/test02.txt +#p4 add 101Bugs/Bugs101_Job040346Test/test01.txt 101Bugs/Bugs101_Job040346Test/test02.txt +p4 add 101Bugs/Bugs101_Job040346Test/test01.txt +p4 submit -d "Job040346Test: add file" +p4 integrate 101Bugs/Bugs101_Job040346Test/test01.txt 101Bugs/Bugs101_Job040346Test/test02.txt +p4 submit -d "Job040346Test: integrate file" +p4 edit 101Bugs/Bugs101_Job040346Test/test01.txt +cp "$SOURCE_ROOT/text/text01.txt" 101Bugs/Bugs101_Job040346Test/test01.txt +p4 submit -d "Job040346Test: revise file" + + +# LabelSyncTest +mkdir -p basic/readonly/labelsync +# Need at least 11 files, each with a different revision... +i=1 +while [ $i -le 11 ] ; do + cp "$SOURCE_ROOT/text/text00.txt" basic/readonly/labelsync/test-${i}.txt + p4 add basic/readonly/labelsync/test-${i}.txt + p4 submit -d "LabelSyncTest: label file ${i} add" + p4 edit basic/readonly/labelsync/test-${i}.txt + cp "$SOURCE_ROOT/text/text01.txt" basic/readonly/labelsync/test-${i}.txt + p4 submit -d "LabelSyncTest: label file ${i} edit" + i=$(( $i + 1 )) +done +p4 labelsync -a -l LabelSyncTestLabel //depot/basic/readonly/labelsync/... + + +# SimpleSyncTest +mkdir -p basic/readonly/sync +cp "$SOURCE_ROOT/text/text00.txt" basic/readonly/sync/test00.txt +p4 add basic/readonly/sync/test00.txt +p4 submit -d "SimpleSyncTest: add file" + + +# GetMatchingLinesTest +# Requires 8 lines that match, +mkdir -p basic/readonly/grep +cp "$SOURCE_ROOT/text/text00.txt" basic/readonly/grep/test00.txt +cp "$SOURCE_ROOT/text/grep-text-P4Java.txt" basic/readonly/grep/test-match-01.txt +cp "$SOURCE_ROOT/text/grep-text-P4Java-x2.txt" basic/readonly/grep/test-match-02.txt +cp "$SOURCE_ROOT/text/grep-text-P4Java.txt" basic/readonly/grep/test-match-03.txt +cp "$SOURCE_ROOT/text/grep-text-P4Java-x2.txt" basic/readonly/grep/test-match-04.txt +cp "$SOURCE_ROOT/text/grep-text-P4Java-x2.txt" basic/readonly/grep/test-match-05.txt +cp "$SOURCE_ROOT/text/grep-text-P4Java-lower.txt" basic/readonly/grep/test-match-06.txt +p4 add basic/readonly/grep/* +p4 submit -d "GetMatchingLinesTest: add files" + + +# SyncIntegrityCheckTest +mkdir -p 102Dev/SyncIntegrityCheckTest +cp "$SOURCE_ROOT/text/text00.txt" 102Dev/SyncIntegrityCheckTest/test01.txt +p4 add 102Dev/SyncIntegrityCheckTest/test01.txt +cp "$SOURCE_ROOT/bin/bin00.bin" 102Dev/SyncIntegrityCheckTest/test02.jpg +# called "UBinary" in the source ... what type is required? +p4 add -t binary+SC 102Dev/SyncIntegrityCheckTest/test02.jpg +cp "$SOURCE_ROOT/bin/bin01.bin" 102Dev/SyncIntegrityCheckTest/test03.bin +p4 add -t binary 102Dev/SyncIntegrityCheckTest/test03.bin +p4 submit -d "SyncIntegrityCheckTest: add files" + + +# ClientIntegrationE2ETest +mkdir -p Dev/rteam/HBOP/admin +cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HBOP/admin/displayTool.pl +mkdir -p Dev/rteam/HBOP/src +cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HBOP/src/hbop1.html +cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HBOP/src/hbop4.java +cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HBOP/src/hbop5.txt +cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HBOP/src/hbop6.txt +cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HBOP/src/hbop7.txt +p4 add Dev/rteam/HBOP/src/* Dev/rteam/HBOP/admin/* +p4 submit -d "ClientIntegrationE2ETest: add hbop files" + +p4 integrate Dev/rteam/HBOP/... Dev/rteam/TLCP/... +cp "$SOURCE_ROOT/bin/bin00.bin" Dev/rteam/TLCP/src/p4merge_help.png +cp "$SOURCE_ROOT/bin/bin00.bin" Dev/rteam/TLCP/src/bindetmi2.dll +# Do not add the binary files as explicitly binary; this will mess up the test's integration resolution results. +# The resolve results will include an extra set of records for file type (along with content) integration. +p4 add Dev/rteam/TLCP/src/bindetmi2.dll Dev/rteam/TLCP/src/p4merge_help.png +p4 submit -d "ClientIntegrationE2ETest: add tlcp files" + +mkdir -p Dev/rteam/HOLD +cp "$SOURCE_ROOT/bin/bin00.bin" Dev/rteam/HOLD/old_p4java.jar +p4 add Dev/rteam/HOLD/old_p4java.jar +p4 submit -d "ClientIntegrationE2ETest: add hold files" + +p4 integrate Dev/rteam/TLCP/... Dev/rteam/SHOW/... +p4 submit -d "ClientIntegrationE2ETest: create show branch" + +mkdir -p Dev/rteam/HGTV/src +cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HGTV/src/homePlan.txt +cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HGTV/src/homeWorks.html +cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HGTV/src/hProj.java +cp "$SOURCE_ROOT/text/text00.txt" Dev/rteam/HGTV/src/lProj.java +cp "$SOURCE_ROOT/bin/bin00.bin" Dev/rteam/HGTV/src/proj1.dll +p4 add Dev/rteam/HGTV/src/* +p4 integrate Dev/rteam/TLCP/admin/... Dev/rteam/HGTV/admin/... +p4 submit -d "ClientIntegrationE2ETest: create hgtv branch" + +p4 integrate Dev/rteam/HGTV/src/lProj.java Dev/rteam/HOLD/src/lProj.java +p4 submit -d "ClientIntegrationE2ETest: make another branch" +p4 edit Dev/rteam/HOLD/src/lProj.java +cp "$SOURCE_ROOT/text/text01.txt" Dev/rteam/HOLD/src/lProj.java +p4 submit -d "ClientIntegrationE2ETest: make a change in the branch" diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-02.sh b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-02.sh index 3fa0356d..c00ff274 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-02.sh +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-02.sh @@ -1,42 +1,42 @@ -#!/bin/sh - -set -e - -# Stream depot files. - -export P4USER=p4jtestuser -export P4PASSWD=p4jtestuser - -export P4CLIENT=p4java_stream_dev - -# hard-coded root at /tmp/client/p4java_stream_dev - -mkdir -p /tmp/client/p4java_stream_dev -cd /tmp/client/p4java_stream_dev -p4 sync ... - -cp "$SOURCE_ROOT/text/text00.txt" ./text00.txt -mkdir -p core/GetOpenedFilesTest/src/gnu/getopt -cp "$SOURCE_ROOT/text/text00.txt" core/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ja.properties -p4 add text00.txt core/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ja.properties -p4 submit -d 'Job035290Test: revision 1' - - -# Now merge the files into main. -export P4CLIENT=p4java_stream_main -mkdir -p /tmp/client/p4java_stream_main -cd /tmp/client/p4java_stream_main -p4 copy //p4java_stream/dev/... ... -p4 submit -d 'initial population of main' - - - -# GetStreamFileDiffsTest: Now have multiple revisions in dev -export P4CLIENT=p4java_stream_dev -cd /tmp/client/p4java_stream_dev -p4 edit core/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ja.properties -cp "$SOURCE_ROOT/text/text01.txt" core/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ja.properties -p4 submit -d 'GetStreamFileDiffsTest: rev 2' -p4 edit core/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ja.properties -cp "$SOURCE_ROOT/text/text02.txt" core/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ja.properties -p4 submit -d 'GetStreamFileDiffsTest: rev 3' +#!/bin/sh + +set -e + +# Stream depot files. + +export P4USER=p4jtestuser +export P4PASSWD=p4jtestuser + +export P4CLIENT=p4java_stream_dev + +# hard-coded root at /tmp/client/p4java_stream_dev + +mkdir -p /tmp/client/p4java_stream_dev +cd /tmp/client/p4java_stream_dev +p4 sync ... + +cp "$SOURCE_ROOT/text/text00.txt" ./text00.txt +mkdir -p core/GetOpenedFilesTest/src/gnu/getopt +cp "$SOURCE_ROOT/text/text00.txt" core/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ja.properties +p4 add text00.txt core/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ja.properties +p4 submit -d 'Job035290Test: revision 1' + + +# Now merge the files into main. +export P4CLIENT=p4java_stream_main +mkdir -p /tmp/client/p4java_stream_main +cd /tmp/client/p4java_stream_main +p4 copy //p4java_stream/dev/... ... +p4 submit -d 'initial population of main' + + + +# GetStreamFileDiffsTest: Now have multiple revisions in dev +export P4CLIENT=p4java_stream_dev +cd /tmp/client/p4java_stream_dev +p4 edit core/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ja.properties +cp "$SOURCE_ROOT/text/text01.txt" core/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ja.properties +p4 submit -d 'GetStreamFileDiffsTest: rev 2' +p4 edit core/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ja.properties +cp "$SOURCE_ROOT/text/text02.txt" core/GetOpenedFilesTest/src/gnu/getopt/MessagesBundle_ja.properties +p4 submit -d 'GetStreamFileDiffsTest: rev 3' diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-03.sh b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-03.sh index fc933187..cb4d81ce 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-03.sh +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-03.sh @@ -1,27 +1,27 @@ -#!/bin/bash - -set -e - -export P4USER=p4jtestsuper -export P4PASSWD=p4jtestsuper -export P4CLIENT=p4TestUserWS - -# PasswordTest ... -echo 'User: testuser-job059485 -Email: testuser-job059485@nowhere -FullName: testuser-job059485 -' | p4 user -i -f - -# Password with 2 tabs at the start and 2 spaces at the end -p4 passwd -P $'\t\tabc123 ' testuser-job059485 - -# Test out the password using an alternate approach -export P4USER=testuser-job059485 -P4PASSWD=$( echo -e '\t\tabc123 ' ) -export P4PASSWD -p4 users > /dev/null - - - -# CountersTest -p4 counter p4jtestCounter 10 +#!/bin/bash + +set -e + +export P4USER=p4jtestsuper +export P4PASSWD=p4jtestsuper +export P4CLIENT=p4TestUserWS + +# PasswordTest ... +echo 'User: testuser-job059485 +Email: testuser-job059485@nowhere +FullName: testuser-job059485 +' | p4 user -i -f + +# Password with 2 tabs at the start and 2 spaces at the end +p4 passwd -P $'\t\tabc123 ' testuser-job059485 + +# Test out the password using an alternate approach +export P4USER=testuser-job059485 +P4PASSWD=$( echo -e '\t\tabc123 ' ) +export P4PASSWD +p4 users > /dev/null + + + +# CountersTest +p4 counter p4jtestCounter 10 diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-98.sh b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-98.sh index 90559976..bcffdbe2 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-98.sh +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-98.sh @@ -1,12 +1,12 @@ -#!/bin/bash - -set -e - -# Changelist # specific stuff. At the end to not upset the other stuff. - -export P4USER=p4jtestsuper -export P4PASSWD=p4jtestsuper - -# Job040762Test -# Needs changelist 6421 diffed against head for //depot/101Bugs/Bugs101_Job040762Test -# looks for file //depot/101Bugs/Bugs101_Job040762Test/test01.txt +#!/bin/bash + +set -e + +# Changelist # specific stuff. At the end to not upset the other stuff. + +export P4USER=p4jtestsuper +export P4PASSWD=p4jtestsuper + +# Job040762Test +# Needs changelist 6421 diffed against head for //depot/101Bugs/Bugs101_Job040762Test +# looks for file //depot/101Bugs/Bugs101_Job040762Test/test01.txt diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-99.sh b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-99.sh index 62a7d252..cde9f941 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-99.sh +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/setup-99.sh @@ -1,6 +1,6 @@ -#!/bin/sh - -set -e - -p4 admin checkpoint -p4 admin stop +#!/bin/sh + +set -e + +p4 admin checkpoint +p4 admin stop diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/00-p4java_stream_main.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/00-p4java_stream_main.spec index 1b5538bb..f707ad7b 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/00-p4java_stream_main.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/00-p4java_stream_main.spec @@ -1,10 +1,10 @@ -Stream: //p4java_stream/main -Owner: p4jtestsuper -Name: main -Parent: none -Type: mainline -Description: - p4java_stream main -Options: allsubmit unlocked notoparent nofromparent mergedown -Paths: - share ... +Stream: //p4java_stream/main +Owner: p4jtestsuper +Name: main +Parent: none +Type: mainline +Description: + p4java_stream main +Options: allsubmit unlocked notoparent nofromparent mergedown +Paths: + share ... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/01-p4java_stream_dev.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/01-p4java_stream_dev.spec index 0753d2ef..6c5a24df 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/01-p4java_stream_dev.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/01-p4java_stream_dev.spec @@ -1,10 +1,10 @@ -Stream: //p4java_stream/dev -Owner: p4jtestsuper -Name: dev -Parent: //p4java_stream/main -Type: development -Description: - p4java_stream dev -Options: allsubmit unlocked toparent fromparent mergedown -Paths: - share ... +Stream: //p4java_stream/dev +Owner: p4jtestsuper +Name: dev +Parent: //p4java_stream/main +Type: development +Description: + p4java_stream dev +Options: allsubmit unlocked toparent fromparent mergedown +Paths: + share ... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/02-p4java_stream_test2.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/02-p4java_stream_test2.spec index 01d411bb..c6c9ae23 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/02-p4java_stream_test2.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/02-p4java_stream_test2.spec @@ -1,10 +1,10 @@ -Stream: //p4java_stream/test2 -Owner: p4jtestsuper -Name: dev -Parent: //p4java_stream/main -Type: development -Description: - p4java_stream test2 for GetStreamsBaseParentFilterTest (needs 5 total streams with main as parent) -Options: allsubmit unlocked toparent fromparent mergedown -Paths: - share ... +Stream: //p4java_stream/test2 +Owner: p4jtestsuper +Name: dev +Parent: //p4java_stream/main +Type: development +Description: + p4java_stream test2 for GetStreamsBaseParentFilterTest (needs 5 total streams with main as parent) +Options: allsubmit unlocked toparent fromparent mergedown +Paths: + share ... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/03-p4java_stream_test3.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/03-p4java_stream_test3.spec index e59f315a..9ea2ff88 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/03-p4java_stream_test3.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/03-p4java_stream_test3.spec @@ -1,10 +1,10 @@ -Stream: //p4java_stream/test3 -Owner: p4jtestsuper -Name: dev -Parent: //p4java_stream/main -Type: development -Description: - p4java_stream test3 for GetStreamsBaseParentFilterTest (needs 5 total streams with main as parent) -Options: allsubmit unlocked toparent fromparent mergedown -Paths: - share ... +Stream: //p4java_stream/test3 +Owner: p4jtestsuper +Name: dev +Parent: //p4java_stream/main +Type: development +Description: + p4java_stream test3 for GetStreamsBaseParentFilterTest (needs 5 total streams with main as parent) +Options: allsubmit unlocked toparent fromparent mergedown +Paths: + share ... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/04-p4java_stream_test4.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/04-p4java_stream_test4.spec index 8906b477..728ac114 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/04-p4java_stream_test4.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/04-p4java_stream_test4.spec @@ -1,10 +1,10 @@ -Stream: //p4java_stream/test4 -Owner: p4jtestsuper -Name: dev -Parent: //p4java_stream/main -Type: development -Description: - p4java_stream test4 for GetStreamsBaseParentFilterTest (needs 5 total streams with main as parent) -Options: allsubmit unlocked toparent fromparent mergedown -Paths: - share ... +Stream: //p4java_stream/test4 +Owner: p4jtestsuper +Name: dev +Parent: //p4java_stream/main +Type: development +Description: + p4java_stream test4 for GetStreamsBaseParentFilterTest (needs 5 total streams with main as parent) +Options: allsubmit unlocked toparent fromparent mergedown +Paths: + share ... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/05-p4java_stream_test5.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/05-p4java_stream_test5.spec index fb3f38a1..c7fcd48e 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/05-p4java_stream_test5.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/stream-specs/05-p4java_stream_test5.spec @@ -1,10 +1,10 @@ -Stream: //p4java_stream/test5 -Owner: p4jtestsuper -Name: dev -Parent: //p4java_stream/main -Type: development -Description: - p4java_stream test5 for GetStreamsBaseParentFilterTest (needs 5 total streams with main as parent) -Options: allsubmit unlocked toparent fromparent mergedown -Paths: - share ... +Stream: //p4java_stream/test5 +Owner: p4jtestsuper +Name: dev +Parent: //p4java_stream/main +Type: development +Description: + p4java_stream test5 for GetStreamsBaseParentFilterTest (needs 5 total streams with main as parent) +Options: allsubmit unlocked toparent fromparent mergedown +Paths: + share ... diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/grep-text-P4Java-lower.txt b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/grep-text-P4Java-lower.txt index 998fbf42..cd214f47 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/grep-text-P4Java-lower.txt +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/grep-text-P4Java-lower.txt @@ -1,6 +1,6 @@ -Text file for tests that require explicit text. -start text: - -line: -- p4java -- - +Text file for tests that require explicit text. +start text: + +line: -- p4java -- + :end text \ No newline at end of file diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/grep-text-P4Java-x2.txt b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/grep-text-P4Java-x2.txt index 0650825d..68d601dc 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/grep-text-P4Java-x2.txt +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/grep-text-P4Java-x2.txt @@ -1,7 +1,7 @@ -Text file for tests that require explicit text. -start text: - -line 1: -- P4Java -- -line 2: -- P4Java -- - +Text file for tests that require explicit text. +start text: + +line 1: -- P4Java -- +line 2: -- P4Java -- + :end text \ No newline at end of file diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/grep-text-P4Java.txt b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/grep-text-P4Java.txt index a58fcf60..76188007 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/grep-text-P4Java.txt +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/grep-text-P4Java.txt @@ -1,6 +1,6 @@ -Text file for tests that require explicit text. -start text: - -line: -- P4Java -- - +Text file for tests that require explicit text. +start text: + +line: -- P4Java -- + :end text \ No newline at end of file diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/text00.txt b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/text00.txt index 4672e520..f7def679 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/text00.txt +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/text00.txt @@ -1,2 +1,2 @@ -Content -Sample Text document, 00. +Content +Sample Text document, 00. diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/text01.txt b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/text01.txt index 7749ee88..7ccf6503 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/text01.txt +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/text01.txt @@ -1,2 +1,2 @@ -Content -Sample Text document, 01. +Content +Sample Text document, 01. diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/text02.txt b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/text02.txt index 47bf9140..4204c386 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/text02.txt +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/text/text02.txt @@ -1,2 +1,2 @@ -Content -Sample Text document, 02. +Content +Sample Text document, 02. diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestdummy.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestdummy.spec index 576bfb23..36614c7a 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestdummy.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestdummy.spec @@ -1,4 +1,4 @@ -User: p4jtestdummy -Email: p4jtestdummy@nowhere -FullName: dummy user -#Type: service +User: p4jtestdummy +Email: p4jtestdummy@nowhere +FullName: dummy user +#Type: service diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestoperator.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestoperator.spec index cc51d0a6..41660bdd 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestoperator.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestoperator.spec @@ -1,4 +1,4 @@ -User: p4jtestoperator -Email: p4jtestoperator@nowhere -FullName: operator user -Type: operator +User: p4jtestoperator +Email: p4jtestoperator@nowhere +FullName: operator user +Type: operator diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestserviceuser.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestserviceuser.spec index fdf0e2cb..8155f7bb 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestserviceuser.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestserviceuser.spec @@ -1,4 +1,4 @@ -User: p4jtestserviceuser -Email: p4jtestserviceuser@nowhere -FullName: service user -Type: service +User: p4jtestserviceuser +Email: p4jtestserviceuser@nowhere +FullName: service user +Type: service diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestsuper.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestsuper.spec index 848dccb9..751bc8d6 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestsuper.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestsuper.spec @@ -1,3 +1,3 @@ -User: p4jtestsuper -Email: p4jtestsuper@nowhere -FullName: super user +User: p4jtestsuper +Email: p4jtestsuper@nowhere +FullName: super user diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestuser.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestuser.spec index 27d078b4..e3b633e4 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestuser.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestuser.spec @@ -1,3 +1,3 @@ -User: p4jtestuser -Email: p4jtestuser@nowhere -FullName: ordinary user +User: p4jtestuser +Email: p4jtestuser@nowhere +FullName: ordinary user diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestuser2.spec b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestuser2.spec index 72a0f0a2..44cc6231 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestuser2.spec +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4-client-docker/user-specs/p4jtestuser2.spec @@ -1,3 +1,3 @@ -User: p4jtestuser2 -Email: p4jtestuser2@nowhere -FullName: ordinary user2 +User: p4jtestuser2 +Email: p4jtestuser2@nowhere +FullName: ordinary user2 diff --git a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4d-docker/Dockerfile b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4d-docker/Dockerfile index af5337f7..bbc95760 100644 --- a/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4d-docker/Dockerfile +++ b/p4java/src/test/resources/data/eng-p4java-vm.das.perforce.com/construction/p4d-docker/Dockerfile @@ -1,20 +1,20 @@ -FROM ubuntu:latest - -EXPOSE 1666 - -VOLUME /opt/p4d-base - -RUN echo "Start install" \ - && apt-get update \ - && apt-get -y upgrade \ - && apt-get -y install wget \ - && mkdir -p /opt/p4d-base \ - && cd /tmp \ - && wget http://ftp.perforce.com/perforce/r17.1/bin.linux26x86_64/p4d \ - && mv /tmp/p4d /usr/local/bin/. \ - && chmod +x /usr/local/bin/p4d \ - && echo "Completed install" - -WORKDIR /opt/p4d-base - -CMD [ "/usr/local/bin/p4d", "-A", "audit.log", "-L", "p4d.log" ] +FROM ubuntu:latest + +EXPOSE 1666 + +VOLUME /opt/p4d-base + +RUN echo "Start install" \ + && apt-get update \ + && apt-get -y upgrade \ + && apt-get -y install wget \ + && mkdir -p /opt/p4d-base \ + && cd /tmp \ + && wget http://ftp.perforce.com/perforce/r17.1/bin.linux26x86_64/p4d \ + && mv /tmp/p4d /usr/local/bin/. \ + && chmod +x /usr/local/bin/p4d \ + && echo "Completed install" + +WORKDIR /opt/p4d-base + +CMD [ "/usr/local/bin/p4d", "-A", "audit.log", "-L", "p4d.log" ] diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/actions/ChangelistDescriptionAction.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/actions/ChangelistDescriptionAction.java index 2271594b..caefba56 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/actions/ChangelistDescriptionAction.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/actions/ChangelistDescriptionAction.java @@ -1,205 +1,205 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4plugin.actions; - -import com.intellij.icons.AllIcons; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vcs.VcsDataKeys; -import com.intellij.openapi.vcs.changes.ChangeList; -import com.intellij.openapi.vcs.changes.LocalChangeList; -import com.intellij.openapi.vcs.history.VcsFileRevision; -import com.intellij.openapi.vfs.VirtualFile; -import net.groboclown.p4.server.api.ClientConfigRoot; -import net.groboclown.p4.server.api.ProjectConfigRegistry; -import net.groboclown.p4.server.api.commands.changelist.DescribeChangelistQuery; -import net.groboclown.p4.server.api.config.ClientConfig; -import net.groboclown.p4.server.api.config.OptionalClientServerConfig; -import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; -import net.groboclown.p4.server.api.messagebus.ErrorEvent; -import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; -import net.groboclown.p4.server.api.values.P4ChangelistId; -import net.groboclown.p4.server.api.values.P4CommittedChangelist; -import net.groboclown.p4.server.impl.repository.P4HistoryVcsFileRevision; -import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.components.CacheComponent; -import net.groboclown.p4plugin.components.P4ServerComponent; -import net.groboclown.p4plugin.ui.history.ChangelistDetails; - -import java.util.Arrays; -import java.util.Collection; - -public class ChangelistDescriptionAction extends DumbAwareAction { - private static final Logger LOG = Logger.getInstance(ChangelistDescriptionAction.class); - - public ChangelistDescriptionAction() { - super( - P4Bundle.getString("history.describe-change"), - P4Bundle.getString("history.describe-change.description"), - AllIcons.General.BalloonInformation); - } - - @Override - public void actionPerformed(AnActionEvent e) { - final Project project = getEventProject(e); - if (project == null || project.isDisposed()) { - LOG.info("Skipping because project is disposed"); - return; - } - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null || registry.isDisposed()) { - LOG.info("Skipping because no config registry known"); - return; - } - - Pair setup = getReferencedChangelistId(project, registry, e); - if (setup == null) { - LOG.info("Skipping because no changelist associated to context item could be found"); - return; - } - - P4ServerComponent - .query(project, setup.first, new DescribeChangelistQuery(setup.second)) - .whenCompleted((r) -> { - if (r.getRemoteChangelist() != null) { - ChangelistDetails.showDocked(project, r.getRemoteChangelist()); - } - }); - } - - - private Pair getReferencedChangelistId(Project project, - ProjectConfigRegistry registry, AnActionEvent e) { - Pair ret; - ret = findAttachedChangelist(project, registry, e); - if (ret != null) { - return ret; - } - ret = findAttachedFileRevision(e); - - return ret; - } - - - private Pair findAttachedChangelist(Project project, ProjectConfigRegistry registry, - AnActionEvent e) { - ChangeList[] changeLists = VcsDataKeys.CHANGE_LISTS.getData(e.getDataContext()); - if (LOG.isDebugEnabled()) { - LOG.debug("Found changelists on context item: " + Arrays.toString(changeLists)); - } - if (changeLists == null || changeLists.length <= 0) { - return null; - } - - // Check for easy changelists first - for (ChangeList changeList : changeLists) { - if (changeList instanceof P4CommittedChangelist) { - P4CommittedChangelist p4cl = (P4CommittedChangelist) changeList; - ClientConfig clientConfig = getConfigForChangelistId(registry, - p4cl.getSummary().getChangelistId()); - if (clientConfig != null) { - return Pair.create(new OptionalClientServerConfig(clientConfig), - p4cl.getSummary().getChangelistId()); - } else { - LOG.warn("Skipped " + p4cl + " / " + p4cl.getSummary().getChangelistId() + - " because no server registration known to exist for " + - p4cl.getSummary().getChangelistId().getClientServerRef()); - } - } - } - - // Check for harder ones second - for (ChangeList changeList : changeLists) { - if (changeList instanceof LocalChangeList) { - LocalChangeList ide = (LocalChangeList) changeList; - try { - Collection p4ChangeLists = CacheComponent.getInstance(project).getServerOpenedCache() - .first.getP4ChangesFor(ide); - if (p4ChangeLists.isEmpty()) { - if (LOG.isDebugEnabled()) { - LOG.debug("No cached P4 changelist known for IDE change list " + ide); - } - continue; - } - - VirtualFile file = e.getData(VcsDataKeys.VCS_VIRTUAL_FILE); - if (file != null) { - ClientConfigRoot root = registry.getClientFor(file); - if (root != null) { - for (P4ChangelistId p4ChangeList : p4ChangeLists) { - if (p4ChangeList.isIn(root.getServerConfig())) { - if (LOG.isDebugEnabled()) { - LOG.debug("Using changelist " + p4ChangeList); - } - return Pair.create(new OptionalClientServerConfig(root.getClientConfig()), p4ChangeList); - } - } - } - } - - // Pick one - P4ChangelistId first = p4ChangeLists.iterator().next(); - ClientConfig clientConfig = getConfigForChangelistId(registry, first); - if (clientConfig != null) { - if (LOG.isDebugEnabled()) { - LOG.debug("Using changelist " + first + " out of " + p4ChangeLists); - } - return Pair.create(new OptionalClientServerConfig(clientConfig), first); - } - - // Unknown... - LOG.warn("No known client associated with p4 changelist " + first + "; probably a caching issue?"); - } catch (InterruptedException ex) { - InternalErrorMessage.send(project).cacheLockTimeoutError( - new ErrorEvent<>(new VcsInterruptedException(ex))); - } - } - } - return null; - } - - private ClientConfig getConfigForChangelistId(ProjectConfigRegistry registry, P4ChangelistId p4cl) { - return registry.getRegisteredClientConfigState(p4cl.getClientServerRef()); - } - - - - private Pair findAttachedFileRevision(AnActionEvent e) { - final VirtualFile file; - { - final Boolean nonLocal = e.getData(VcsDataKeys.VCS_NON_LOCAL_HISTORY_SESSION); - if (Boolean.TRUE.equals(nonLocal)) { - LOG.info("non-local VCS history session; ignoring changelist description action"); - return null; - } - file = e.getData(VcsDataKeys.VCS_VIRTUAL_FILE); - if (file == null || file.isDirectory()) { - LOG.info("No VCS virtual file associated with changelist description action; ignoring request."); - return null; - } - } - - final VcsFileRevision revision = e.getData(VcsDataKeys.VCS_FILE_REVISION); - if (!(revision instanceof P4HistoryVcsFileRevision)) { - LOG.info("No file revision associated with file " + file); - return null; - } - P4HistoryVcsFileRevision history = (P4HistoryVcsFileRevision) revision; - return Pair.create(new OptionalClientServerConfig(history.getClientConfig()), history.getChangelistId()); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4plugin.actions; + +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.changes.ChangeList; +import com.intellij.openapi.vcs.changes.LocalChangeList; +import com.intellij.openapi.vcs.history.VcsFileRevision; +import com.intellij.openapi.vfs.VirtualFile; +import net.groboclown.p4.server.api.ClientConfigRoot; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import net.groboclown.p4.server.api.commands.changelist.DescribeChangelistQuery; +import net.groboclown.p4.server.api.config.ClientConfig; +import net.groboclown.p4.server.api.config.OptionalClientServerConfig; +import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; +import net.groboclown.p4.server.api.messagebus.ErrorEvent; +import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; +import net.groboclown.p4.server.api.values.P4ChangelistId; +import net.groboclown.p4.server.api.values.P4CommittedChangelist; +import net.groboclown.p4.server.impl.repository.P4HistoryVcsFileRevision; +import net.groboclown.p4plugin.P4Bundle; +import net.groboclown.p4plugin.components.CacheComponent; +import net.groboclown.p4plugin.components.P4ServerComponent; +import net.groboclown.p4plugin.ui.history.ChangelistDetails; + +import java.util.Arrays; +import java.util.Collection; + +public class ChangelistDescriptionAction extends DumbAwareAction { + private static final Logger LOG = Logger.getInstance(ChangelistDescriptionAction.class); + + public ChangelistDescriptionAction() { + super( + P4Bundle.getString("history.describe-change"), + P4Bundle.getString("history.describe-change.description"), + AllIcons.General.BalloonInformation); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final Project project = getEventProject(e); + if (project == null || project.isDisposed()) { + LOG.info("Skipping because project is disposed"); + return; + } + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null || registry.isDisposed()) { + LOG.info("Skipping because no config registry known"); + return; + } + + Pair setup = getReferencedChangelistId(project, registry, e); + if (setup == null) { + LOG.info("Skipping because no changelist associated to context item could be found"); + return; + } + + P4ServerComponent + .query(project, setup.first, new DescribeChangelistQuery(setup.second)) + .whenCompleted((r) -> { + if (r.getRemoteChangelist() != null) { + ChangelistDetails.showDocked(project, r.getRemoteChangelist()); + } + }); + } + + + private Pair getReferencedChangelistId(Project project, + ProjectConfigRegistry registry, AnActionEvent e) { + Pair ret; + ret = findAttachedChangelist(project, registry, e); + if (ret != null) { + return ret; + } + ret = findAttachedFileRevision(e); + + return ret; + } + + + private Pair findAttachedChangelist(Project project, ProjectConfigRegistry registry, + AnActionEvent e) { + ChangeList[] changeLists = VcsDataKeys.CHANGE_LISTS.getData(e.getDataContext()); + if (LOG.isDebugEnabled()) { + LOG.debug("Found changelists on context item: " + Arrays.toString(changeLists)); + } + if (changeLists == null || changeLists.length <= 0) { + return null; + } + + // Check for easy changelists first + for (ChangeList changeList : changeLists) { + if (changeList instanceof P4CommittedChangelist) { + P4CommittedChangelist p4cl = (P4CommittedChangelist) changeList; + ClientConfig clientConfig = getConfigForChangelistId(registry, + p4cl.getSummary().getChangelistId()); + if (clientConfig != null) { + return Pair.create(new OptionalClientServerConfig(clientConfig), + p4cl.getSummary().getChangelistId()); + } else { + LOG.warn("Skipped " + p4cl + " / " + p4cl.getSummary().getChangelistId() + + " because no server registration known to exist for " + + p4cl.getSummary().getChangelistId().getClientServerRef()); + } + } + } + + // Check for harder ones second + for (ChangeList changeList : changeLists) { + if (changeList instanceof LocalChangeList) { + LocalChangeList ide = (LocalChangeList) changeList; + try { + Collection p4ChangeLists = CacheComponent.getInstance(project).getServerOpenedCache() + .first.getP4ChangesFor(ide); + if (p4ChangeLists.isEmpty()) { + if (LOG.isDebugEnabled()) { + LOG.debug("No cached P4 changelist known for IDE change list " + ide); + } + continue; + } + + VirtualFile file = e.getData(VcsDataKeys.VCS_VIRTUAL_FILE); + if (file != null) { + ClientConfigRoot root = registry.getClientFor(file); + if (root != null) { + for (P4ChangelistId p4ChangeList : p4ChangeLists) { + if (p4ChangeList.isIn(root.getServerConfig())) { + if (LOG.isDebugEnabled()) { + LOG.debug("Using changelist " + p4ChangeList); + } + return Pair.create(new OptionalClientServerConfig(root.getClientConfig()), p4ChangeList); + } + } + } + } + + // Pick one + P4ChangelistId first = p4ChangeLists.iterator().next(); + ClientConfig clientConfig = getConfigForChangelistId(registry, first); + if (clientConfig != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Using changelist " + first + " out of " + p4ChangeLists); + } + return Pair.create(new OptionalClientServerConfig(clientConfig), first); + } + + // Unknown... + LOG.warn("No known client associated with p4 changelist " + first + "; probably a caching issue?"); + } catch (InterruptedException ex) { + InternalErrorMessage.send(project).cacheLockTimeoutError( + new ErrorEvent<>(new VcsInterruptedException(ex))); + } + } + } + return null; + } + + private ClientConfig getConfigForChangelistId(ProjectConfigRegistry registry, P4ChangelistId p4cl) { + return registry.getRegisteredClientConfigState(p4cl.getClientServerRef()); + } + + + + private Pair findAttachedFileRevision(AnActionEvent e) { + final VirtualFile file; + { + final Boolean nonLocal = e.getData(VcsDataKeys.VCS_NON_LOCAL_HISTORY_SESSION); + if (Boolean.TRUE.equals(nonLocal)) { + LOG.info("non-local VCS history session; ignoring changelist description action"); + return null; + } + file = e.getData(VcsDataKeys.VCS_VIRTUAL_FILE); + if (file == null || file.isDirectory()) { + LOG.info("No VCS virtual file associated with changelist description action; ignoring request."); + return null; + } + } + + final VcsFileRevision revision = e.getData(VcsDataKeys.VCS_FILE_REVISION); + if (!(revision instanceof P4HistoryVcsFileRevision)) { + LOG.info("No file revision associated with file " + file); + return null; + } + P4HistoryVcsFileRevision history = (P4HistoryVcsFileRevision) revision; + return Pair.create(new OptionalClientServerConfig(history.getClientConfig()), history.getChangelistId()); + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/actions/P4EditAction.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/actions/P4EditAction.java index 08938ffb..7ddfb728 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/actions/P4EditAction.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/actions/P4EditAction.java @@ -1,133 +1,133 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.actions; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsConnectionProblem; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.changes.ChangeListManager; -import com.intellij.openapi.vcs.changes.LocalChangeList; -import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.vcsUtil.VcsUtil; -import net.groboclown.p4.server.api.P4CommandRunner; -import net.groboclown.p4.server.api.commands.file.AddEditAction; -import net.groboclown.p4.server.api.config.ClientConfig; -import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; -import net.groboclown.p4.server.api.values.P4ChangelistId; -import net.groboclown.p4.server.impl.commands.DoneActionAnswer; -import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.components.CacheComponent; -import net.groboclown.p4plugin.components.P4ServerComponent; -import net.groboclown.p4plugin.extension.P4Vcs; -import org.jetbrains.annotations.NotNull; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Stream; - -public class P4EditAction - extends BasicAction { - private static final Logger LOG = Logger.getInstance(P4EditAction.class); - - public P4EditAction() { - super(P4Bundle.getString("files.edit.action-name")); - } - - - @Override - protected boolean isEnabled(@NotNull Project project, @NotNull P4Vcs vcs, @NotNull VirtualFile... vFiles) { - // If an input file does not map to a server, then we still report enabled. If all the files only map to - // non-P4 files, then disable. - - if (LOG.isDebugEnabled()) { - LOG.debug("Checking enabled state for files " + Arrays.asList(vFiles)); - } - - /* - - This seems to have trouble performing the correct calculations. - Allow it to be shown, which will also skip the possible check - and speed up the UI operation. If the user tries to edit the - file when it's not really possible, then the edit will handle the - error conditions at that point. - - try { - final Map> servers = vcs.mapVirtualFilesToOnlineP4Server(Arrays.asList(vFiles)); - - // all we care about for open for edit/add is whether - // the files map to servers or not. Online or offline - // modes don't matter. - return !(servers.isEmpty() || (servers.size() == 1 && servers.containsKey(null))); - } catch (InterruptedException e) { - LOG.info(e); - return true; - } - - */ - return true; - } - - - - @NotNull - @SuppressWarnings("unchecked") - @Override - protected P4CommandRunner.ActionAnswer perform(@NotNull final Project project, @NotNull final P4Vcs vcs, - @NotNull final List exceptions, @NotNull final List affectedFiles) { - if (affectedFiles.isEmpty()) { - return new DoneActionAnswer(null); - } - - // Note: the user is forcing the add action. Do not inspect the IgnoreFileSet status. - - LocalChangeList defaultChangeList = - ChangeListManager.getInstance(project).getDefaultChangeList(); - - return getFilesByConfiguration(project, affectedFiles) - .flatMap((Function>, Stream>) (entity) -> { - P4ChangelistId p4cl; - try { - p4cl = CacheComponent.getInstance(project).getServerOpenedCache().first.getP4ChangeFor( - entity.first.getClientServerRef(), defaultChangeList); - } catch (InterruptedException e) { - exceptions.add(new VcsInterruptedException(e)); - return Stream.empty(); - } - return entity.second.stream() - // Make sure we don't try to add a directory. - .filter(f -> !f.isDirectory()) - .map((file) -> { - FilePath path = VcsUtil.getFilePath(file); - return P4ServerComponent - .perform(project, entity.first, new AddEditAction(path, null, p4cl, path.getCharset(project))) - .whenCompleted((r) -> VcsDirtyScopeManager.getInstance(project).filesDirty(Collections.singleton(file), null)) - .whenServerError((err) -> exceptions.add(new VcsException(err))) - // TODO use message bundle - .whenOffline(() -> exceptions.add(new VcsConnectionProblem("went offline while editing " + file))) - ; - }); - }) - .reduce((P4CommandRunner.ActionAnswer) new DoneActionAnswer(null), - (base, next) -> base.mapActionAsync((x) -> next)) - .whenCompleted((x) -> - LOG.debug("added or edited files: " + affectedFiles)) - ; - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.actions; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsConnectionProblem; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.LocalChangeList; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.vcsUtil.VcsUtil; +import net.groboclown.p4.server.api.P4CommandRunner; +import net.groboclown.p4.server.api.commands.file.AddEditAction; +import net.groboclown.p4.server.api.config.ClientConfig; +import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; +import net.groboclown.p4.server.api.values.P4ChangelistId; +import net.groboclown.p4.server.impl.commands.DoneActionAnswer; +import net.groboclown.p4plugin.P4Bundle; +import net.groboclown.p4plugin.components.CacheComponent; +import net.groboclown.p4plugin.components.P4ServerComponent; +import net.groboclown.p4plugin.extension.P4Vcs; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +public class P4EditAction + extends BasicAction { + private static final Logger LOG = Logger.getInstance(P4EditAction.class); + + public P4EditAction() { + super(P4Bundle.getString("files.edit.action-name")); + } + + + @Override + protected boolean isEnabled(@NotNull Project project, @NotNull P4Vcs vcs, @NotNull VirtualFile... vFiles) { + // If an input file does not map to a server, then we still report enabled. If all the files only map to + // non-P4 files, then disable. + + if (LOG.isDebugEnabled()) { + LOG.debug("Checking enabled state for files " + Arrays.asList(vFiles)); + } + + /* + + This seems to have trouble performing the correct calculations. + Allow it to be shown, which will also skip the possible check + and speed up the UI operation. If the user tries to edit the + file when it's not really possible, then the edit will handle the + error conditions at that point. + + try { + final Map> servers = vcs.mapVirtualFilesToOnlineP4Server(Arrays.asList(vFiles)); + + // all we care about for open for edit/add is whether + // the files map to servers or not. Online or offline + // modes don't matter. + return !(servers.isEmpty() || (servers.size() == 1 && servers.containsKey(null))); + } catch (InterruptedException e) { + LOG.info(e); + return true; + } + + */ + return true; + } + + + + @NotNull + @SuppressWarnings("unchecked") + @Override + protected P4CommandRunner.ActionAnswer perform(@NotNull final Project project, @NotNull final P4Vcs vcs, + @NotNull final List exceptions, @NotNull final List affectedFiles) { + if (affectedFiles.isEmpty()) { + return new DoneActionAnswer(null); + } + + // Note: the user is forcing the add action. Do not inspect the IgnoreFileSet status. + + LocalChangeList defaultChangeList = + ChangeListManager.getInstance(project).getDefaultChangeList(); + + return getFilesByConfiguration(project, affectedFiles) + .flatMap((Function>, Stream>) (entity) -> { + P4ChangelistId p4cl; + try { + p4cl = CacheComponent.getInstance(project).getServerOpenedCache().first.getP4ChangeFor( + entity.first.getClientServerRef(), defaultChangeList); + } catch (InterruptedException e) { + exceptions.add(new VcsInterruptedException(e)); + return Stream.empty(); + } + return entity.second.stream() + // Make sure we don't try to add a directory. + .filter(f -> !f.isDirectory()) + .map((file) -> { + FilePath path = VcsUtil.getFilePath(file); + return P4ServerComponent + .perform(project, entity.first, new AddEditAction(path, null, p4cl, path.getCharset(project))) + .whenCompleted((r) -> VcsDirtyScopeManager.getInstance(project).filesDirty(Collections.singleton(file), null)) + .whenServerError((err) -> exceptions.add(new VcsException(err))) + // TODO use message bundle + .whenOffline(() -> exceptions.add(new VcsConnectionProblem("went offline while editing " + file))) + ; + }); + }) + .reduce((P4CommandRunner.ActionAnswer) new DoneActionAnswer(null), + (base, next) -> base.mapActionAsync((x) -> next)) + .whenCompleted((x) -> + LOG.debug("added or edited files: " + affectedFiles)) + ; + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/actions/P4Menu.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/actions/P4Menu.java index 86bd9e57..6e9deac2 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/actions/P4Menu.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/actions/P4Menu.java @@ -1,31 +1,31 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.actions; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.AbstractVcs; -import com.intellij.openapi.vcs.actions.StandardVcsGroup; -import net.groboclown.p4plugin.extension.P4Vcs; - -public class P4Menu extends StandardVcsGroup { - @Override - public AbstractVcs getVcs(Project project) { - return P4Vcs.getInstance(project); - } - - @Override - public String getVcsName(final Project project) { - return P4Vcs.VCS_NAME; - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.actions; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.actions.StandardVcsGroup; +import net.groboclown.p4plugin.extension.P4Vcs; + +public class P4Menu extends StandardVcsGroup { + @Override + public AbstractVcs getVcs(Project project) { + return P4Vcs.getInstance(project); + } + + @Override + public String getVcsName(final Project project) { + return P4Vcs.VCS_NAME; + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4AnnotationProvider.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4AnnotationProvider.java index 7bbb111b..6d2c86c3 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4AnnotationProvider.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4AnnotationProvider.java @@ -1,126 +1,126 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.extension; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.annotate.AnnotationProvider; -import com.intellij.openapi.vcs.annotate.FileAnnotation; -import com.intellij.openapi.vcs.history.VcsFileRevision; -import com.intellij.openapi.vcs.history.VcsRevisionNumber; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.vcsUtil.VcsUtil; -import com.perforce.p4java.core.file.IFileSpec; -import net.groboclown.p4.server.api.ClientConfigRoot; -import net.groboclown.p4.server.api.ProjectConfigRegistry; -import net.groboclown.p4.server.api.commands.file.AnnotateFileQuery; -import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; -import net.groboclown.p4.server.api.values.P4FileRevision; -import net.groboclown.p4.server.api.commands.HistoryContentLoader; -import net.groboclown.p4.server.api.commands.HistoryMessageFormatter; -import net.groboclown.p4plugin.components.P4ServerComponent; -import net.groboclown.p4plugin.components.UserProjectPreferences; -import net.groboclown.p4plugin.messages.HistoryMessageFormatterImpl; -import net.groboclown.p4plugin.revision.P4AnnotatedFileImpl; -import net.groboclown.p4plugin.util.HistoryContentLoaderImpl; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.TimeUnit; - -/** - * - */ -public class P4AnnotationProvider - implements AnnotationProvider { - private static final Logger LOG = Logger.getInstance(P4AnnotationProvider.class); - - private final Project project; - private final HistoryMessageFormatter messageFormatter = new HistoryMessageFormatterImpl(); - private final HistoryContentLoader contentLoader; - - P4AnnotationProvider(@NotNull P4Vcs vcs) { - this.project = vcs.getProject(); - this.contentLoader = new HistoryContentLoaderImpl(project); - } - - @NotNull - @Override - public FileAnnotation annotate(@NotNull VirtualFile file) throws VcsException { - return annotate(file, null); - } - - @NotNull - @Override - public FileAnnotation annotate(@NotNull VirtualFile file, VcsFileRevision revision) throws VcsException { - if (ApplicationManager.getApplication().isDispatchThread()) { - LOG.info("Fetching annotation from the EDT"); - // TODO bundle for error messages - throw new VcsException("Does not support fetching annotations from the EDT."); - } - // TODO use a better location for this constant. - int rev = IFileSpec.HEAD_REVISION; - if (revision != null) { - VcsRevisionNumber revNumber = revision.getRevisionNumber(); - if (revNumber instanceof VcsRevisionNumber.Int) { - rev = ((VcsRevisionNumber.Int) revNumber).getValue(); - } else { - LOG.warn("Unknown file revision " + revision + " for " + file + "; using head revision"); - } - } - FilePath fp = VcsUtil.getFilePath(file); - if (fp == null) { - // TODO bundle for error messages - throw new VcsException("No known Perforce server for " + file); - } - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null) { - // TODO bundle for error messages - throw new VcsException("Project not configured for showing annotations"); - } - ClientConfigRoot client = registry.getClientFor(file); - if (client == null) { - // TODO bundle for error messages - throw new VcsException("No known Perforce server for " + file); - } - String clientname = client.getClientConfig().getClientname(); - if (clientname == null) { - // TODO bundle for error messages - throw new VcsException("No workspace name set for Perforce connection for " + file); - } - try { - return new P4AnnotatedFileImpl(project, fp, - messageFormatter, contentLoader, - P4ServerComponent - .query(project, client.getClientConfig(), new AnnotateFileQuery(fp, rev)) - .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS)); - } catch (InterruptedException e) { - throw new VcsInterruptedException(e); - } - } - - /** - * Check whether the annotation retrieval is valid (or possible) for the - * particular file revision (or version in the repository). - * - * @param rev File revision to be checked. - * @return true if annotation it valid for the given revision. - */ - @Override - public boolean isAnnotationValid(@NotNull VcsFileRevision rev) { - return (rev instanceof P4FileRevision); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.extension; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.annotate.AnnotationProvider; +import com.intellij.openapi.vcs.annotate.FileAnnotation; +import com.intellij.openapi.vcs.history.VcsFileRevision; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.vcsUtil.VcsUtil; +import com.perforce.p4java.core.file.IFileSpec; +import net.groboclown.p4.server.api.ClientConfigRoot; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import net.groboclown.p4.server.api.commands.file.AnnotateFileQuery; +import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; +import net.groboclown.p4.server.api.values.P4FileRevision; +import net.groboclown.p4.server.api.commands.HistoryContentLoader; +import net.groboclown.p4.server.api.commands.HistoryMessageFormatter; +import net.groboclown.p4plugin.components.P4ServerComponent; +import net.groboclown.p4plugin.components.UserProjectPreferences; +import net.groboclown.p4plugin.messages.HistoryMessageFormatterImpl; +import net.groboclown.p4plugin.revision.P4AnnotatedFileImpl; +import net.groboclown.p4plugin.util.HistoryContentLoaderImpl; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.TimeUnit; + +/** + * + */ +public class P4AnnotationProvider + implements AnnotationProvider { + private static final Logger LOG = Logger.getInstance(P4AnnotationProvider.class); + + private final Project project; + private final HistoryMessageFormatter messageFormatter = new HistoryMessageFormatterImpl(); + private final HistoryContentLoader contentLoader; + + P4AnnotationProvider(@NotNull P4Vcs vcs) { + this.project = vcs.getProject(); + this.contentLoader = new HistoryContentLoaderImpl(project); + } + + @NotNull + @Override + public FileAnnotation annotate(@NotNull VirtualFile file) throws VcsException { + return annotate(file, null); + } + + @NotNull + @Override + public FileAnnotation annotate(@NotNull VirtualFile file, VcsFileRevision revision) throws VcsException { + if (ApplicationManager.getApplication().isDispatchThread()) { + LOG.info("Fetching annotation from the EDT"); + // TODO bundle for error messages + throw new VcsException("Does not support fetching annotations from the EDT."); + } + // TODO use a better location for this constant. + int rev = IFileSpec.HEAD_REVISION; + if (revision != null) { + VcsRevisionNumber revNumber = revision.getRevisionNumber(); + if (revNumber instanceof VcsRevisionNumber.Int) { + rev = ((VcsRevisionNumber.Int) revNumber).getValue(); + } else { + LOG.warn("Unknown file revision " + revision + " for " + file + "; using head revision"); + } + } + FilePath fp = VcsUtil.getFilePath(file); + if (fp == null) { + // TODO bundle for error messages + throw new VcsException("No known Perforce server for " + file); + } + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null) { + // TODO bundle for error messages + throw new VcsException("Project not configured for showing annotations"); + } + ClientConfigRoot client = registry.getClientFor(file); + if (client == null) { + // TODO bundle for error messages + throw new VcsException("No known Perforce server for " + file); + } + String clientname = client.getClientConfig().getClientname(); + if (clientname == null) { + // TODO bundle for error messages + throw new VcsException("No workspace name set for Perforce connection for " + file); + } + try { + return new P4AnnotatedFileImpl(project, fp, + messageFormatter, contentLoader, + P4ServerComponent + .query(project, client.getClientConfig(), new AnnotateFileQuery(fp, rev)) + .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + throw new VcsInterruptedException(e); + } + } + + /** + * Check whether the annotation retrieval is valid (or possible) for the + * particular file revision (or version in the repository). + * + * @param rev File revision to be checked. + * @return true if annotation it valid for the given revision. + */ + @Override + public boolean isAnnotationValid(@NotNull VcsFileRevision rev) { + return (rev instanceof P4FileRevision); + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4ChangeProvider.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4ChangeProvider.java index e7d80cec..8c9d91f2 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4ChangeProvider.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4ChangeProvider.java @@ -1,812 +1,812 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.extension; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.FileStatus; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.VcsRoot; -import com.intellij.openapi.vcs.changes.Change; -import com.intellij.openapi.vcs.changes.ChangeListManager; -import com.intellij.openapi.vcs.changes.ChangeListManagerGate; -import com.intellij.openapi.vcs.changes.ChangeProvider; -import com.intellij.openapi.vcs.changes.ChangelistBuilder; -import com.intellij.openapi.vcs.changes.ContentRevision; -import com.intellij.openapi.vcs.changes.CurrentContentRevision; -import com.intellij.openapi.vcs.changes.LocalChangeList; -import com.intellij.openapi.vcs.changes.VcsDirtyScope; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.vcsUtil.VcsUtil; -import net.groboclown.p4.server.api.ClientConfigRoot; -import net.groboclown.p4.server.api.P4CommandRunner; -import net.groboclown.p4.server.api.ProjectConfigRegistry; -import net.groboclown.p4.server.api.cache.IdeChangelistMap; -import net.groboclown.p4.server.api.cache.IdeFileMap; -import net.groboclown.p4.server.api.cache.messagebus.AbstractCacheMessage; -import net.groboclown.p4.server.api.cache.messagebus.ClientActionMessage; -import net.groboclown.p4.server.api.commands.HistoryContentLoader; -import net.groboclown.p4.server.api.commands.changelist.CreateChangelistAction; -import net.groboclown.p4.server.api.commands.changelist.CreateChangelistResult; -import net.groboclown.p4.server.api.commands.changelist.DeleteChangelistAction; -import net.groboclown.p4.server.api.config.ClientConfig; -import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; -import net.groboclown.p4.server.api.messagebus.ErrorEvent; -import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; -import net.groboclown.p4.server.api.messagebus.MessageBusClient; -import net.groboclown.p4.server.api.util.EqualUtil; -import net.groboclown.p4.server.api.values.P4ChangelistId; -import net.groboclown.p4.server.api.values.P4FileAction; -import net.groboclown.p4.server.api.values.P4LocalChangelist; -import net.groboclown.p4.server.api.values.P4LocalFile; -import net.groboclown.p4.server.api.values.P4RemoteFile; -import net.groboclown.p4.server.impl.commands.DoneActionAnswer; -import net.groboclown.p4.server.impl.values.P4ChangelistIdImpl; -import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.components.CacheComponent; -import net.groboclown.p4plugin.components.UserProjectPreferences; -import net.groboclown.p4plugin.revision.P4DeletedLocalFileRevision; -import net.groboclown.p4plugin.revision.P4LocalFileContentRevision; -import net.groboclown.p4plugin.revision.P4RemoteFileContentRevision; -import net.groboclown.p4plugin.util.ChangelistUtil; -import net.groboclown.p4plugin.util.HistoryContentLoaderImpl; -import net.groboclown.p4plugin.util.RemoteFileUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -/** - * Pushes changes FROM Perforce INTO idea. No Perforce jobs will be altered. - *

- * If there was an IDEA changelist that referenced a Perforce changelist that - * has since been submitted or deleted, the IDEA changelist will be removed, - * and any contents will be moved into the default changelist. - *

- * If there is a Perforce changelist that has no mapping to IDEA, an IDEA - * change list is created. - */ -public class P4ChangeProvider - implements ChangeProvider { - private static final Logger LOG = Logger.getInstance(P4ChangeProvider.class); - - private final Project project; - private final P4Vcs vcs; - private final HistoryContentLoader loader; - - private volatile boolean active = false; - private volatile Exception activeThread = null; - - P4ChangeProvider(@NotNull P4Vcs vcs, @NotNull Disposable parentDisposable) { - this.project = vcs.getProject(); - this.vcs = vcs; - this.loader = new HistoryContentLoaderImpl(project); - - final MessageBusClient.ApplicationClient mbClient = MessageBusClient.forApplication(parentDisposable); - final String cacheId = AbstractCacheMessage.createCacheId(project, P4ChangeProvider.class); - ClientActionMessage.addListener(mbClient, cacheId, event -> { - P4CommandRunner.ClientActionCmd cmd = (P4CommandRunner.ClientActionCmd) event.getAction().getCmd(); - try { - switch (cmd) { - case CREATE_CHANGELIST: - onChangelistCreated( - (CreateChangelistAction) event.getAction(), - (CreateChangelistResult) event.getResult(), - event.getState()); - break; - case DELETE_CHANGELIST: - if (event.getState() == ClientActionMessage.ActionState.PENDING) { - onChangelistDelete((DeleteChangelistAction) event.getAction()); - } - break; - } - } catch (InterruptedException e) { - InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>( - new VcsInterruptedException(e))); - } - }); - } - - @Override - public boolean isModifiedDocumentTrackingRequired() { - // editing a file requires opening the file for edit or add, and thus changing its dirty state. - return true; - } - - @Override - public void doCleanup(List files) { - // clean up the working copy. - LOG.info("Cleanup called for " + files); - // FIXME clean up cache for the files. - LOG.warn("FIXME clean up cache for the files."); - } - - @Override - public void getChanges(@NotNull VcsDirtyScope dirtyScope, - @NotNull ChangelistBuilder builder, - @NotNull ProgressIndicator progress, - @NotNull ChangeListManagerGate addGate) throws VcsException { - if (project.isDisposed()) { - return; - } - - // This check is kind of necessary. It's ensuring that IntelliJ is doing the right thing. - // Note the identity equals, not .equals(). - if (dirtyScope.getVcs() != vcs) { - throw new VcsException(P4Bundle.message("error.vcs.dirty-scope.wrong")); - } - - if (active) { - LOG.warn("Second call into already active getChanges. Ignoring call."); - LOG.warn("Original active thread", activeThread); - LOG.warn("Current thread", new Exception()); - return; - } - try { - active = true; - activeThread = new Exception(); - - double lastFraction = 0.0; - - progress.setFraction(lastFraction); - - // How this is called by IntelliJ: - // IntelliJ calls this method on updates to the files or changes; - // not for all the files or changes, but just the ones that are - // in the dirty scope. The method is expected to only categorize - // the dirty scoped files, nothing more. - - // The biggest issue that this method presents to us (the plugin makers) - // is that incorrect usage will cause infinite refresh of the change lists. - // If any dirty file is not handled, this will be called again. If any - // file is marked as dirty by calling this method, the method will be - // called again. - - // Unfortunately, there are circumstances where a follow-up call can't be - // avoided. An example of this is where a file is mis-marked to be in - // a different changelist. It's up to this method to correctly sort - // files into their IDEA changelists. - - // For the purposes of this implementation, we'll always attempt to - // refresh the cache from the server. Then we'll update the requested file - // status. - - Collection allClientRoots = getClientConfigRoots(); - - // This request is performed by the IDE in a background thread, so it can block. - - Pair cachedMaps = CacheComponent.getInstance(project) - .blockingRefreshServerOpenedCache( - allClientRoots, - UserProjectPreferences.getLockWaitTimeoutMillis(project), - TimeUnit.MILLISECONDS - ); - if (cachedMaps.first == null || cachedMaps.second == null) { - // This can happen if a non-P4Vcs project calls into here. - progress.setFraction(1.0); - return; - } - - lastFraction = 0.6; - progress.setFraction(lastFraction); - - - try { - // For now, just blocking get right here. - updateChangelists(cachedMaps.first, addGate) - .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>( - new VcsInterruptedException("Update Changelists interrupted", e))); - progress.setFraction(1.0); - return; - } - - if (dirtyScope.wasEveryThingDirty()) { - // Update all the files. - - double fractionRootIncr = (1.0 - lastFraction) / allClientRoots.size(); - for (ClientConfigRoot root : allClientRoots) { - LOG.info("Processing changes in " + root.getProjectVcsRootDir()); - - updateFileCache(root.getClientConfig(), - cachedMaps.first, cachedMaps.second, builder); - - lastFraction += fractionRootIncr; - progress.setFraction(lastFraction); - } - } else { - // Update just the dirty files. - final Set dirtyFiles = dirtyScope.getDirtyFiles(); - if (dirtyFiles == null || dirtyFiles.isEmpty()) { - LOG.info("No dirty files."); - progress.setFraction(1.0); - return; - } - - if (LOG.isDebugEnabled()) { - LOG.debug("Processing dirty files " + dirtyFiles); - } - Map> fileRoots = VcsUtil.groupByRoots(project, dirtyFiles, (f) -> f); - if (!fileRoots.isEmpty()) { - double fractionRootIncr = (1.0 - lastFraction) / fileRoots.size(); - for (Map.Entry> entry : fileRoots.entrySet()) { - if (entry.getKey().getVcs() != null && - P4Vcs.getKey().equals(entry.getKey().getVcs().getKeyInstanceMethod())) { - VirtualFile root = entry.getKey().getPath(); - ClientConfigRoot config = getClientFor(root); - Set matched = - getMatchedDirtyRootFiles(dirtyFiles, config, root, entry.getValue()); - if (!matched.isEmpty() && config != null) { - updateFileCache(config.getClientConfig(), - matched, cachedMaps.first, cachedMaps.second, builder); - } - } - - lastFraction += fractionRootIncr; - progress.setFraction(lastFraction); - } - } - - // All the remaining dirty files are not under our VCS, so mark them as ignored. - markIgnored(dirtyFiles, builder); - } - - progress.setFraction(1.0); - } finally { - active = false; - activeThread = null; - } - } - - private Set getMatchedDirtyRootFiles(Set dirtyFiles, - @Nullable ClientConfigRoot config, VirtualFile root, List changedFiles) { - LOG.info("Processing changes for " + changedFiles + " under " + root); - if (config == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("Skipping because not under a Perforce root: " + root + "; expected key " + P4Vcs.getKey()); - } - return Collections.emptySet(); - } - Set matched = new HashSet<>(); - for (FilePath filePath : changedFiles) { - if (dirtyFiles.remove(filePath)) { - if (LOG.isDebugEnabled()) { - LOG.debug("Matched " + filePath + " in dirty file list"); - } - matched.add(filePath); - } else if (LOG.isDebugEnabled()) { - LOG.debug("Not in dirty file list (" + filePath + "); already processed?"); - } - } - if (LOG.isDebugEnabled()) { - LOG.debug("Processing under " + root + ": matched dirty files " + - changedFiles + " to root: " + matched); - LOG.debug("Remaining dirty files: " + dirtyFiles); - } - return matched; - } - - private P4CommandRunner.ActionAnswer updateChangelists(IdeChangelistMap changelistMap, ChangeListManagerGate addGate) - throws InterruptedException { - List existingLocalChangeLists = addGate.getListsCopy(); - P4CommandRunner.ActionAnswer actions = new DoneActionAnswer<>(null); - - Collection roots = getClientConfigRoots(); - - // #177 - prevent old clients from keeping their changelist mappings around. - changelistMap.clearChangesNotIn( - roots.stream() - .map(ClientConfigRoot::getClientConfig) - .map(ClientConfig::getClientServerRef) - .collect(Collectors.toList()) - ); - - for (ClientConfigRoot clientConfigRoot : roots) { - if (LOG.isDebugEnabled()) { - LOG.debug("Updating changelists for " + clientConfigRoot); - } - Set unvisitedLocalChangeLists = new HashSet<>(existingLocalChangeLists); - for (P4LocalChangelist changelist : CacheComponent.getInstance(project).getCacheQuery() - .getCachedOpenedChangelists(clientConfigRoot.getClientConfig())) { - LocalChangeList ideChangeList = - changelistMap.getIdeChangeFor(changelist.getChangelistId()); - if (LOG.isDebugEnabled()) { - LOG.debug("P4 Changelist " + changelist.getChangelistId() + - " has cached map to IDE changelist " + ideChangeList); - } - if (ideChangeList == null) { - if (changelist.getChangelistId().isDefaultChangelist()) { - // Link to the IDE default changelist. - LOG.debug("Attaching default changelist to IDE default changelist"); - LocalChangeList defaultIdeCl = ChangeListManager.getInstance(project).getDefaultChangeList(); - unvisitedLocalChangeLists.remove(defaultIdeCl); - changelistMap.setMapping(changelist.getChangelistId(), defaultIdeCl); - } else { - // Create a new IDE changelist and link to that. - ideChangeList = addGate.addChangeList( - ChangelistUtil.createUniqueIdeChangeListName(changelist, null, existingLocalChangeLists, - UserProjectPreferences.getMaxChangelistNameLength(project)), - changelist.getComment()); - // Mark the just-created changelist as added, so that we don't attempt to use the name a - // second time. - existingLocalChangeLists.add(ideChangeList); - if (LOG.isDebugEnabled()) { - LOG.debug("Attaching " + changelist + " to IDE change " + ideChangeList); - } - changelistMap.setMapping(changelist.getChangelistId(), ideChangeList); - } - } else { - unvisitedLocalChangeLists.remove(ideChangeList); - if (LOG.isDebugEnabled()) { - LOG.debug("Already attached " + changelist + " to IDE change " + ideChangeList); - } - // Don't change the name of the IDE's default changelist. - if (!changelist.getChangelistId().isDefaultChangelist()) { - // Only change things up if the p4 changelist comment changed. - if (!EqualUtil.isEqual(changelist.getComment(), ideChangeList.getComment())) { - addGate.editComment(ideChangeList.getName(), changelist.getComment()); - String newName = ChangelistUtil.createUniqueIdeChangeListName(changelist, ideChangeList, - existingLocalChangeLists, - UserProjectPreferences.getMaxChangelistNameLength(project)); - if (!EqualUtil.isEqual(ideChangeList.getName(), newName)) { - addGate.editName(ideChangeList.getName(), newName); - } - } - } - } - } - - // If there are still links to changelists that are no longer pending, then the link MUST be removed. - // The other direction - local IDE change list removed which has a link to a P4 changelist - is handled - // by the P4ChangelistListener class. - for (LocalChangeList unvisited : unvisitedLocalChangeLists) { - P4ChangelistId attached = changelistMap.getP4ChangeFor( - clientConfigRoot.getClientConfig().getClientServerRef(), - unvisited); - if (attached != null) { - changelistMap.changelistDeleted(attached); - if (LOG.isDebugEnabled()) { - LOG.debug("Removing association of removed changelist " + attached + " to " + unvisited); - } - } - } - } - - // Files that are in IDE change lists but not in mapped Perforce changelists should not be fixed here. - // That operation should be handled by the P4ChangelistListener class. - - return actions; - } - - /** - * Everything is marked as dirty. Just look at the state of files on the server, - * and move files into the right changelist. - * - * @param config config for the root. - * @param changes changelist mapping - * @param files file mapping, which has already been loaded from the server. - * @param builder changelist builder - */ - private void updateFileCache(ClientConfig config, - IdeChangelistMap changes, IdeFileMap files, ChangelistBuilder builder) { - updateLocalFileCache(config, files.getLinkedFiles().collect(Collectors.toList()), changes, builder); - } - - /** - * Just the list of files are dirty. If additional files are checked out - * on the server, but they aren't marked as dirty, then make a call to mark - * them as dirty. - * @param config config - * @param files cached files - * @param builder builds changelists - */ - private void updateFileCache(ClientConfig config, Set dirty, - IdeChangelistMap changes, IdeFileMap files, ChangelistBuilder builder) { - List localFiles = new ArrayList<>(dirty.size()); - for (FilePath filePath : dirty) { - P4LocalFile local = files.forIdeFile(filePath); - if (local == null) { - if (filePath.getVirtualFile() == null || !filePath.getVirtualFile().exists()) { - if (LOG.isDebugEnabled()) { - LOG.debug("Marking " + filePath + " as locally deleted"); - } - builder.processLocallyDeletedFile(filePath); - } else if (UserProjectPreferences.getAutoCheckoutModifiedFiles(project)) { - if (LOG.isDebugEnabled()) { - LOG.debug("Scheduling " + filePath + " for edit and marking it as modified without checkout"); - } - vcs.getCheckinEnvironment().scheduleUnversionedFilesForAddition( - Collections.singletonList(filePath.getVirtualFile())); - builder.processModifiedWithoutCheckout(filePath.getVirtualFile()); - } else { - if (LOG.isDebugEnabled()) { - LOG.debug("Marking " + filePath + " as modified without checkout"); - } - builder.processModifiedWithoutCheckout(filePath.getVirtualFile()); - } - } else { - localFiles.add(local); - } - } - updateLocalFileCache(config, localFiles, changes, builder); - } - - - private void updateLocalFileCache(ClientConfig config, List files, - IdeChangelistMap changes, ChangelistBuilder builder) { - final Map moved = findMovedFiles(files); - final Set unprocessedDeletedMovedFiles = new HashSet<>(moved.values()); - - files.forEach((file) -> { - if (LOG.isDebugEnabled()) { - LOG.debug("Updating cache for " + file); - } - if (unprocessedDeletedMovedFiles.contains(file)) { - if (LOG.isDebugEnabled()) { - LOG.debug("Skipping delete file because it is part of a move", file); - } - } else { - final P4LocalFile movedFrom = moved.get(file); - if (processModifiedFileChange(config, changes, builder, file, movedFrom)) { - unprocessedDeletedMovedFiles.remove(movedFrom); - } - } - }); - - unprocessedDeletedMovedFiles.forEach((file) -> { - if (LOG.isDebugEnabled()) { - LOG.debug("Updating cache for " + file); - } - processModifiedFileChange(config, changes, builder, file, null); - }); - } - - private boolean processModifiedFileChange(ClientConfig config, IdeChangelistMap changes, ChangelistBuilder builder, - @NotNull P4LocalFile file, @Nullable P4LocalFile movedFrom) { - // The file can have a null changelist here if "open for edit" didn't assign one. - if (file.getChangelistId() != null) { - try { - LocalChangeList localChangeList = changes.getIdeChangeFor(file.getChangelistId()); - if (localChangeList == null) { - // Should have already been created. - LOG.warn("Encountered changelist " + file.getChangelistId() + - " that wasn't mapped to an IDE changelist."); - if (LOG.isDebugEnabled()) { - LOG.debug("Mapped changelists: " + changes.getLinkedIdeChanges()); - } - builder.processModifiedWithoutCheckout(file.getFilePath().getVirtualFile()); - return false; - } - // See #206 - // There's a weird situation where the getChangeList(id) succeeds, but - // the underlying call is to an equivalent of findChangeList(name), which - // fails. This might even be a race condition. From what I can tell, the - // ChangeListManagerImpl has a ChangeListWorker, which knows about this - // changelist, but the UpdatingChangeListBuilder has a worker which does not. - // - LocalChangeList ideChangelist = - ChangeListManager.getInstance(project).findChangeList(localChangeList.getName()); - if (ideChangelist == null) { - // This can happen after submit, and is a sign that the cache is out of date. - LOG.info("Encountered deleted changelist " + localChangeList + - "; cache is probably out of date and needs a refresh."); - builder.processModifiedWithoutCheckout(file.getFilePath().getVirtualFile()); - return false; - } else { - if (LOG.isDebugEnabled()) { - LOG.debug("Adding change " + file + " to IDE changelist " + localChangeList); - } - - Pair change = createChange(file, movedFrom, config); - associateChangeWithList(builder, change.first, localChangeList); - return change.second; - } - } catch (InterruptedException e) { - InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>( - new VcsInterruptedException("Lock timed out for " + file, e))); - builder.processModifiedWithoutCheckout(file.getFilePath().getVirtualFile()); - return false; - } - } else { - LOG.warn("Encountered file " + file + " with no changelist."); - builder.processModifiedWithoutCheckout(file.getFilePath().getVirtualFile()); - return false; - } - } - - private Map findMovedFiles(List files) { - Map movedFrom = new HashMap<>(); - Map deleted = new HashMap<>(); - - files.forEach((f) -> { - if (f.getIntegrateFrom() != null) { - movedFrom.put(f, f.getIntegrateFrom()); - } - if (f.getFileAction() == P4FileAction.DELETE || f.getFileAction() == P4FileAction.MOVE_DELETE) { - deleted.put(f.getDepotPath(), f); - } - }); - - Map ret = new HashMap<>(); - movedFrom.forEach((key, value) -> { - P4LocalFile f = deleted.get(value); - if (f != null) { - ret.put(key, f); - } - }); - - return ret; - } - - - private void markIgnored(Set dirtyFiles, ChangelistBuilder builder) { - for (FilePath dirtyFile : dirtyFiles) { - if (dirtyFile.getVirtualFile() == null || !dirtyFile.getVirtualFile().exists()) { - if (LOG.isDebugEnabled()) { - LOG.debug("Marking " + dirtyFile + " as locally deleted"); - } - builder.processLocallyDeletedFile(dirtyFile); - } else { - if (LOG.isDebugEnabled()) { - LOG.debug("Marking " + dirtyFile + " as not versioned"); - } - - // TODO include the IgnoreFileSet - - // TODO if these files aren't in P4, then they are locally edited but not - // marked so by the server. In that case, they are Unversioned. If they - // are in Perforce, then they are locally edited. - - // TODO unversioned files should be susceptible to the P4IGNORE settings. - - // This is deprecated in >v193, which introduced the new method - // that takes a FilePath. Earlier versions don't have that - // method. - builder.processUnversionedFile(dirtyFile.getVirtualFile()); - } - } - } - - - private void associateChangeWithList(ChangelistBuilder builder, Change change, LocalChangeList localChangeList) { - // The IDE reports a warning from ChangeListWorker when this change is just straight-up added. - // This happens when multiple change objects are added into the builder. - // The check for "equals" happens on the Change object, which checks the - // before and after FilePath objects, if both are equal. This warning - // indicates that this class should ensure that the FilePath combo is only in - // this one changelist: if it's already in the changelist, then skip it; if it's - // in another changelist, then move it; if it's not in a changelist, then add it. - - if (change.getBeforeRevision() != null) { - VirtualFile vf = change.getBeforeRevision().getFile().getVirtualFile(); - if (vf != null) { - LocalChangeList before = ChangeListManager.getInstance(project).getChangeList(vf); - // Only perform the "remove" if the change is different and exists - if (before != null && !before.equals(localChangeList)) { - if (LOG.isDebugEnabled()) { - LOG.debug("Moving file " + vf + " off of 'before' IDE change list " + before); - } - builder.removeRegisteredChangeFor(change.getBeforeRevision().getFile()); - } - } - } - if (change.getAfterRevision() != null) { - VirtualFile vf = change.getAfterRevision().getFile().getVirtualFile(); - if (vf != null) { - LocalChangeList after = ChangeListManager.getInstance(project).getChangeList(vf); - // Only perform the "remove" if the change is different and exists - if (after != null && !after.equals(localChangeList)) { - if (LOG.isDebugEnabled()) { - LOG.debug("Moving file " + vf + " off of 'after' IDE change list " + after); - } - builder.removeRegisteredChangeFor(change.getAfterRevision().getFile()); - } - } - } - if (LOG.isDebugEnabled()) { - LOG.debug("Adding change " + change + " to IDE change list " + localChangeList); - } - - // #206 can't figure out the reason why the ChangeListManagerImpl would know about the change, - // while the builder's worker wouldn't know it. - try { - builder.processChangeInList(change, localChangeList, P4Vcs.getKey()); - } catch (Throwable err) { - if (err.getClass().equals(Throwable.class)) { - // Chances are this is related to #206. - InternalErrorMessage.send(project).unexpectedError(new ErrorEvent<>( - err, - P4Bundle.message("error.changelist.bad-cache", localChangeList.getName()) - )); - // fallback... - builder.processChange(change, P4Vcs.getKey()); - } else { - throw err; - } - } - } - - - private Pair createChange(@NotNull P4LocalFile file, @Nullable P4LocalFile movedFrom, - ClientConfig config) { - boolean usedMovedFrom = false; - ContentRevision before = null; - ContentRevision after = null; - FileStatus status; - switch (file.getFileAction()) { - case ADD: - case ADD_EDIT: - if (movedFrom != null) { - before = new P4LocalFileContentRevision(config, movedFrom, loader); - usedMovedFrom = true; - } - after = new CurrentContentRevision(file.getFilePath()); - status = FileStatus.ADDED; - break; - case EDIT: - case REOPEN: - assert file.getDepotPath() != null; - // If we set the before to a different file location, then the IDE will - // think we set the wrong status, and set it to a "move" operation. - // before = new P4RemoteFileContentRevision(project, - // file.getDepotPath(), file.getHaveRevision(), config.getServerConfig()); - before = new P4LocalFileContentRevision(config, file, loader); - after = new CurrentContentRevision(file.getFilePath()); - status = FileStatus.MODIFIED; - break; - case INTEGRATE: - after = new CurrentContentRevision(file.getFilePath()); - if (file.getIntegrateFrom() != null) { - // TODO find the right charset - if (LOG.isDebugEnabled()) { - LOG.debug("INTEGRATE: Finding relative path from " + file.getFilePath() + " ; " + - file.getDepotPath() + " ; " + file.getClientDepotPath() + " -> " + - file.getIntegrateFrom()); - } - before = P4RemoteFileContentRevision.create(file.getIntegrateFrom(), - RemoteFileUtil.findRelativeRemotePath(file, file.getIntegrateFrom()), - config, loader, null); - } else if (file.getDepotPath() != null) { - before = P4RemoteFileContentRevision.create( - file.getDepotPath(), file.getFilePath(), file.getHaveRevision(), config, - loader, file.getCharset()); - } else { - before = new P4LocalFileContentRevision(config, file, loader); - } - status = FileStatus.MERGE; - break; - case DELETE: - before = new P4LocalFileContentRevision(config, file, loader); - status = FileStatus.DELETED; - break; - case REVERTED: - case NONE: - before = after = new P4LocalFileContentRevision(config, file, loader); - status = FileStatus.NOT_CHANGED; - break; - case EDIT_RESOLVED: - if (file.getDepotPath() == null) { - before = null; - if (movedFrom != null) { - before = new P4LocalFileContentRevision(config, movedFrom, loader); - usedMovedFrom = true; - } - } else { - before = P4RemoteFileContentRevision.create( - file.getDepotPath(), file.getFilePath(), - file.getHaveRevision(), config, loader, file.getCharset()); - } - after = new CurrentContentRevision(file.getFilePath()); - status = FileStatus.MERGE; - break; - case MOVE_DELETE: - if (file.getDepotPath() == null) { - // TODO needs to reference the source file. - before = new P4DeletedLocalFileRevision(file); - } else { - before = P4RemoteFileContentRevision.create( - file.getDepotPath(), file.getFilePath(), - file.getHaveRevision(), config, loader, file.getCharset()); - } - status = FileStatus.DELETED; - break; - case MOVE_ADD: - case MOVE_ADD_EDIT: - case MOVE_EDIT: { - if (movedFrom != null) { - before = new P4LocalFileContentRevision(config, movedFrom, loader); - usedMovedFrom = true; - } else if (file.getIntegrateFrom() != null) { - if (LOG.isDebugEnabled()) { - LOG.debug("MOVE_ADD: Finding relative path from " + file.getFilePath() + " ; " + - file.getDepotPath() + " ; " + file.getClientDepotPath() + " -> " + - file.getIntegrateFrom()); - } - FilePath relativeFrom = RemoteFileUtil.findRelativeRemotePath(file, file.getIntegrateFrom()); - before = P4RemoteFileContentRevision.create( - file.getIntegrateFrom(), relativeFrom, - file.getHaveRevision(), config, loader, file.getCharset()); - } - after = new CurrentContentRevision(file.getFilePath()); - // Even though the status is "ADD", the UI will notice the different before/after - // and show the files as moved. - status = FileStatus.ADDED; - break; - } - case UNKNOWN: - if (movedFrom != null) { - before = new P4LocalFileContentRevision(config, movedFrom, loader); - usedMovedFrom = true; - } else { - before = new P4LocalFileContentRevision(config, file, loader); - } - after = new CurrentContentRevision(file.getFilePath()); - status = FileStatus.UNKNOWN; - break; - default: - throw new IllegalArgumentException("Unknown file action " + file.getFileAction()); - } - return Pair.create(new Change(before, after, status), usedMovedFrom); - } - - private void onChangelistCreated(CreateChangelistAction action, CreateChangelistResult result, - ClientActionMessage.ActionState state) - throws InterruptedException { - IdeChangelistMap cache = CacheComponent.getInstance(project).getServerOpenedCache().first; - switch (state) { - // pending is ignored, because it's handled within this class later, and by IdeChangelistCacheStore. - - case COMPLETED: - cache.setMapping(new P4ChangelistIdImpl( - result.getChangelistId(), result.getClientConfig().getClientServerRef()), action); - break; - - case FAILED: - cache.actionFailed(action); - break; - } - } - - private void onChangelistDelete(DeleteChangelistAction action) - throws InterruptedException { - // Only care about pending changelists that haven't been created yet. - IdeChangelistMap cache = CacheComponent.getInstance(project).getServerOpenedCache().first; - cache.changelistDeleted(action.getChangelistId()); - } - - private ClientConfigRoot getClientFor(VirtualFile file) { - ProjectConfigRegistry reg = ProjectConfigRegistry.getInstance(project); - return reg == null ? null : reg.getClientFor(file); - } - - @NotNull - private Collection getClientConfigRoots() { - ProjectConfigRegistry reg = ProjectConfigRegistry.getInstance(project); - return reg == null ? Collections.emptyList() : reg.getClientConfigRoots(); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.extension; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.VcsRoot; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.ChangeListManagerGate; +import com.intellij.openapi.vcs.changes.ChangeProvider; +import com.intellij.openapi.vcs.changes.ChangelistBuilder; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.changes.CurrentContentRevision; +import com.intellij.openapi.vcs.changes.LocalChangeList; +import com.intellij.openapi.vcs.changes.VcsDirtyScope; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.vcsUtil.VcsUtil; +import net.groboclown.p4.server.api.ClientConfigRoot; +import net.groboclown.p4.server.api.P4CommandRunner; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import net.groboclown.p4.server.api.cache.IdeChangelistMap; +import net.groboclown.p4.server.api.cache.IdeFileMap; +import net.groboclown.p4.server.api.cache.messagebus.AbstractCacheMessage; +import net.groboclown.p4.server.api.cache.messagebus.ClientActionMessage; +import net.groboclown.p4.server.api.commands.HistoryContentLoader; +import net.groboclown.p4.server.api.commands.changelist.CreateChangelistAction; +import net.groboclown.p4.server.api.commands.changelist.CreateChangelistResult; +import net.groboclown.p4.server.api.commands.changelist.DeleteChangelistAction; +import net.groboclown.p4.server.api.config.ClientConfig; +import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; +import net.groboclown.p4.server.api.messagebus.ErrorEvent; +import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; +import net.groboclown.p4.server.api.messagebus.MessageBusClient; +import net.groboclown.p4.server.api.util.EqualUtil; +import net.groboclown.p4.server.api.values.P4ChangelistId; +import net.groboclown.p4.server.api.values.P4FileAction; +import net.groboclown.p4.server.api.values.P4LocalChangelist; +import net.groboclown.p4.server.api.values.P4LocalFile; +import net.groboclown.p4.server.api.values.P4RemoteFile; +import net.groboclown.p4.server.impl.commands.DoneActionAnswer; +import net.groboclown.p4.server.impl.values.P4ChangelistIdImpl; +import net.groboclown.p4plugin.P4Bundle; +import net.groboclown.p4plugin.components.CacheComponent; +import net.groboclown.p4plugin.components.UserProjectPreferences; +import net.groboclown.p4plugin.revision.P4DeletedLocalFileRevision; +import net.groboclown.p4plugin.revision.P4LocalFileContentRevision; +import net.groboclown.p4plugin.revision.P4RemoteFileContentRevision; +import net.groboclown.p4plugin.util.ChangelistUtil; +import net.groboclown.p4plugin.util.HistoryContentLoaderImpl; +import net.groboclown.p4plugin.util.RemoteFileUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * Pushes changes FROM Perforce INTO idea. No Perforce jobs will be altered. + *

+ * If there was an IDEA changelist that referenced a Perforce changelist that + * has since been submitted or deleted, the IDEA changelist will be removed, + * and any contents will be moved into the default changelist. + *

+ * If there is a Perforce changelist that has no mapping to IDEA, an IDEA + * change list is created. + */ +public class P4ChangeProvider + implements ChangeProvider { + private static final Logger LOG = Logger.getInstance(P4ChangeProvider.class); + + private final Project project; + private final P4Vcs vcs; + private final HistoryContentLoader loader; + + private volatile boolean active = false; + private volatile Exception activeThread = null; + + P4ChangeProvider(@NotNull P4Vcs vcs, @NotNull Disposable parentDisposable) { + this.project = vcs.getProject(); + this.vcs = vcs; + this.loader = new HistoryContentLoaderImpl(project); + + final MessageBusClient.ApplicationClient mbClient = MessageBusClient.forApplication(parentDisposable); + final String cacheId = AbstractCacheMessage.createCacheId(project, P4ChangeProvider.class); + ClientActionMessage.addListener(mbClient, cacheId, event -> { + P4CommandRunner.ClientActionCmd cmd = (P4CommandRunner.ClientActionCmd) event.getAction().getCmd(); + try { + switch (cmd) { + case CREATE_CHANGELIST: + onChangelistCreated( + (CreateChangelistAction) event.getAction(), + (CreateChangelistResult) event.getResult(), + event.getState()); + break; + case DELETE_CHANGELIST: + if (event.getState() == ClientActionMessage.ActionState.PENDING) { + onChangelistDelete((DeleteChangelistAction) event.getAction()); + } + break; + } + } catch (InterruptedException e) { + InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>( + new VcsInterruptedException(e))); + } + }); + } + + @Override + public boolean isModifiedDocumentTrackingRequired() { + // editing a file requires opening the file for edit or add, and thus changing its dirty state. + return true; + } + + @Override + public void doCleanup(List files) { + // clean up the working copy. + LOG.info("Cleanup called for " + files); + // FIXME clean up cache for the files. + LOG.warn("FIXME clean up cache for the files."); + } + + @Override + public void getChanges(@NotNull VcsDirtyScope dirtyScope, + @NotNull ChangelistBuilder builder, + @NotNull ProgressIndicator progress, + @NotNull ChangeListManagerGate addGate) throws VcsException { + if (project.isDisposed()) { + return; + } + + // This check is kind of necessary. It's ensuring that IntelliJ is doing the right thing. + // Note the identity equals, not .equals(). + if (dirtyScope.getVcs() != vcs) { + throw new VcsException(P4Bundle.message("error.vcs.dirty-scope.wrong")); + } + + if (active) { + LOG.warn("Second call into already active getChanges. Ignoring call."); + LOG.warn("Original active thread", activeThread); + LOG.warn("Current thread", new Exception()); + return; + } + try { + active = true; + activeThread = new Exception(); + + double lastFraction = 0.0; + + progress.setFraction(lastFraction); + + // How this is called by IntelliJ: + // IntelliJ calls this method on updates to the files or changes; + // not for all the files or changes, but just the ones that are + // in the dirty scope. The method is expected to only categorize + // the dirty scoped files, nothing more. + + // The biggest issue that this method presents to us (the plugin makers) + // is that incorrect usage will cause infinite refresh of the change lists. + // If any dirty file is not handled, this will be called again. If any + // file is marked as dirty by calling this method, the method will be + // called again. + + // Unfortunately, there are circumstances where a follow-up call can't be + // avoided. An example of this is where a file is mis-marked to be in + // a different changelist. It's up to this method to correctly sort + // files into their IDEA changelists. + + // For the purposes of this implementation, we'll always attempt to + // refresh the cache from the server. Then we'll update the requested file + // status. + + Collection allClientRoots = getClientConfigRoots(); + + // This request is performed by the IDE in a background thread, so it can block. + + Pair cachedMaps = CacheComponent.getInstance(project) + .blockingRefreshServerOpenedCache( + allClientRoots, + UserProjectPreferences.getLockWaitTimeoutMillis(project), + TimeUnit.MILLISECONDS + ); + if (cachedMaps.first == null || cachedMaps.second == null) { + // This can happen if a non-P4Vcs project calls into here. + progress.setFraction(1.0); + return; + } + + lastFraction = 0.6; + progress.setFraction(lastFraction); + + + try { + // For now, just blocking get right here. + updateChangelists(cachedMaps.first, addGate) + .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>( + new VcsInterruptedException("Update Changelists interrupted", e))); + progress.setFraction(1.0); + return; + } + + if (dirtyScope.wasEveryThingDirty()) { + // Update all the files. + + double fractionRootIncr = (1.0 - lastFraction) / allClientRoots.size(); + for (ClientConfigRoot root : allClientRoots) { + LOG.info("Processing changes in " + root.getProjectVcsRootDir()); + + updateFileCache(root.getClientConfig(), + cachedMaps.first, cachedMaps.second, builder); + + lastFraction += fractionRootIncr; + progress.setFraction(lastFraction); + } + } else { + // Update just the dirty files. + final Set dirtyFiles = dirtyScope.getDirtyFiles(); + if (dirtyFiles == null || dirtyFiles.isEmpty()) { + LOG.info("No dirty files."); + progress.setFraction(1.0); + return; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Processing dirty files " + dirtyFiles); + } + Map> fileRoots = VcsUtil.groupByRoots(project, dirtyFiles, (f) -> f); + if (!fileRoots.isEmpty()) { + double fractionRootIncr = (1.0 - lastFraction) / fileRoots.size(); + for (Map.Entry> entry : fileRoots.entrySet()) { + if (entry.getKey().getVcs() != null && + P4Vcs.getKey().equals(entry.getKey().getVcs().getKeyInstanceMethod())) { + VirtualFile root = entry.getKey().getPath(); + ClientConfigRoot config = getClientFor(root); + Set matched = + getMatchedDirtyRootFiles(dirtyFiles, config, root, entry.getValue()); + if (!matched.isEmpty() && config != null) { + updateFileCache(config.getClientConfig(), + matched, cachedMaps.first, cachedMaps.second, builder); + } + } + + lastFraction += fractionRootIncr; + progress.setFraction(lastFraction); + } + } + + // All the remaining dirty files are not under our VCS, so mark them as ignored. + markIgnored(dirtyFiles, builder); + } + + progress.setFraction(1.0); + } finally { + active = false; + activeThread = null; + } + } + + private Set getMatchedDirtyRootFiles(Set dirtyFiles, + @Nullable ClientConfigRoot config, VirtualFile root, List changedFiles) { + LOG.info("Processing changes for " + changedFiles + " under " + root); + if (config == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Skipping because not under a Perforce root: " + root + "; expected key " + P4Vcs.getKey()); + } + return Collections.emptySet(); + } + Set matched = new HashSet<>(); + for (FilePath filePath : changedFiles) { + if (dirtyFiles.remove(filePath)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Matched " + filePath + " in dirty file list"); + } + matched.add(filePath); + } else if (LOG.isDebugEnabled()) { + LOG.debug("Not in dirty file list (" + filePath + "); already processed?"); + } + } + if (LOG.isDebugEnabled()) { + LOG.debug("Processing under " + root + ": matched dirty files " + + changedFiles + " to root: " + matched); + LOG.debug("Remaining dirty files: " + dirtyFiles); + } + return matched; + } + + private P4CommandRunner.ActionAnswer updateChangelists(IdeChangelistMap changelistMap, ChangeListManagerGate addGate) + throws InterruptedException { + List existingLocalChangeLists = addGate.getListsCopy(); + P4CommandRunner.ActionAnswer actions = new DoneActionAnswer<>(null); + + Collection roots = getClientConfigRoots(); + + // #177 - prevent old clients from keeping their changelist mappings around. + changelistMap.clearChangesNotIn( + roots.stream() + .map(ClientConfigRoot::getClientConfig) + .map(ClientConfig::getClientServerRef) + .collect(Collectors.toList()) + ); + + for (ClientConfigRoot clientConfigRoot : roots) { + if (LOG.isDebugEnabled()) { + LOG.debug("Updating changelists for " + clientConfigRoot); + } + Set unvisitedLocalChangeLists = new HashSet<>(existingLocalChangeLists); + for (P4LocalChangelist changelist : CacheComponent.getInstance(project).getCacheQuery() + .getCachedOpenedChangelists(clientConfigRoot.getClientConfig())) { + LocalChangeList ideChangeList = + changelistMap.getIdeChangeFor(changelist.getChangelistId()); + if (LOG.isDebugEnabled()) { + LOG.debug("P4 Changelist " + changelist.getChangelistId() + + " has cached map to IDE changelist " + ideChangeList); + } + if (ideChangeList == null) { + if (changelist.getChangelistId().isDefaultChangelist()) { + // Link to the IDE default changelist. + LOG.debug("Attaching default changelist to IDE default changelist"); + LocalChangeList defaultIdeCl = ChangeListManager.getInstance(project).getDefaultChangeList(); + unvisitedLocalChangeLists.remove(defaultIdeCl); + changelistMap.setMapping(changelist.getChangelistId(), defaultIdeCl); + } else { + // Create a new IDE changelist and link to that. + ideChangeList = addGate.addChangeList( + ChangelistUtil.createUniqueIdeChangeListName(changelist, null, existingLocalChangeLists, + UserProjectPreferences.getMaxChangelistNameLength(project)), + changelist.getComment()); + // Mark the just-created changelist as added, so that we don't attempt to use the name a + // second time. + existingLocalChangeLists.add(ideChangeList); + if (LOG.isDebugEnabled()) { + LOG.debug("Attaching " + changelist + " to IDE change " + ideChangeList); + } + changelistMap.setMapping(changelist.getChangelistId(), ideChangeList); + } + } else { + unvisitedLocalChangeLists.remove(ideChangeList); + if (LOG.isDebugEnabled()) { + LOG.debug("Already attached " + changelist + " to IDE change " + ideChangeList); + } + // Don't change the name of the IDE's default changelist. + if (!changelist.getChangelistId().isDefaultChangelist()) { + // Only change things up if the p4 changelist comment changed. + if (!EqualUtil.isEqual(changelist.getComment(), ideChangeList.getComment())) { + addGate.editComment(ideChangeList.getName(), changelist.getComment()); + String newName = ChangelistUtil.createUniqueIdeChangeListName(changelist, ideChangeList, + existingLocalChangeLists, + UserProjectPreferences.getMaxChangelistNameLength(project)); + if (!EqualUtil.isEqual(ideChangeList.getName(), newName)) { + addGate.editName(ideChangeList.getName(), newName); + } + } + } + } + } + + // If there are still links to changelists that are no longer pending, then the link MUST be removed. + // The other direction - local IDE change list removed which has a link to a P4 changelist - is handled + // by the P4ChangelistListener class. + for (LocalChangeList unvisited : unvisitedLocalChangeLists) { + P4ChangelistId attached = changelistMap.getP4ChangeFor( + clientConfigRoot.getClientConfig().getClientServerRef(), + unvisited); + if (attached != null) { + changelistMap.changelistDeleted(attached); + if (LOG.isDebugEnabled()) { + LOG.debug("Removing association of removed changelist " + attached + " to " + unvisited); + } + } + } + } + + // Files that are in IDE change lists but not in mapped Perforce changelists should not be fixed here. + // That operation should be handled by the P4ChangelistListener class. + + return actions; + } + + /** + * Everything is marked as dirty. Just look at the state of files on the server, + * and move files into the right changelist. + * + * @param config config for the root. + * @param changes changelist mapping + * @param files file mapping, which has already been loaded from the server. + * @param builder changelist builder + */ + private void updateFileCache(ClientConfig config, + IdeChangelistMap changes, IdeFileMap files, ChangelistBuilder builder) { + updateLocalFileCache(config, files.getLinkedFiles().collect(Collectors.toList()), changes, builder); + } + + /** + * Just the list of files are dirty. If additional files are checked out + * on the server, but they aren't marked as dirty, then make a call to mark + * them as dirty. + * @param config config + * @param files cached files + * @param builder builds changelists + */ + private void updateFileCache(ClientConfig config, Set dirty, + IdeChangelistMap changes, IdeFileMap files, ChangelistBuilder builder) { + List localFiles = new ArrayList<>(dirty.size()); + for (FilePath filePath : dirty) { + P4LocalFile local = files.forIdeFile(filePath); + if (local == null) { + if (filePath.getVirtualFile() == null || !filePath.getVirtualFile().exists()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Marking " + filePath + " as locally deleted"); + } + builder.processLocallyDeletedFile(filePath); + } else if (UserProjectPreferences.getAutoCheckoutModifiedFiles(project)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Scheduling " + filePath + " for edit and marking it as modified without checkout"); + } + vcs.getCheckinEnvironment().scheduleUnversionedFilesForAddition( + Collections.singletonList(filePath.getVirtualFile())); + builder.processModifiedWithoutCheckout(filePath.getVirtualFile()); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("Marking " + filePath + " as modified without checkout"); + } + builder.processModifiedWithoutCheckout(filePath.getVirtualFile()); + } + } else { + localFiles.add(local); + } + } + updateLocalFileCache(config, localFiles, changes, builder); + } + + + private void updateLocalFileCache(ClientConfig config, List files, + IdeChangelistMap changes, ChangelistBuilder builder) { + final Map moved = findMovedFiles(files); + final Set unprocessedDeletedMovedFiles = new HashSet<>(moved.values()); + + files.forEach((file) -> { + if (LOG.isDebugEnabled()) { + LOG.debug("Updating cache for " + file); + } + if (unprocessedDeletedMovedFiles.contains(file)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Skipping delete file because it is part of a move", file); + } + } else { + final P4LocalFile movedFrom = moved.get(file); + if (processModifiedFileChange(config, changes, builder, file, movedFrom)) { + unprocessedDeletedMovedFiles.remove(movedFrom); + } + } + }); + + unprocessedDeletedMovedFiles.forEach((file) -> { + if (LOG.isDebugEnabled()) { + LOG.debug("Updating cache for " + file); + } + processModifiedFileChange(config, changes, builder, file, null); + }); + } + + private boolean processModifiedFileChange(ClientConfig config, IdeChangelistMap changes, ChangelistBuilder builder, + @NotNull P4LocalFile file, @Nullable P4LocalFile movedFrom) { + // The file can have a null changelist here if "open for edit" didn't assign one. + if (file.getChangelistId() != null) { + try { + LocalChangeList localChangeList = changes.getIdeChangeFor(file.getChangelistId()); + if (localChangeList == null) { + // Should have already been created. + LOG.warn("Encountered changelist " + file.getChangelistId() + + " that wasn't mapped to an IDE changelist."); + if (LOG.isDebugEnabled()) { + LOG.debug("Mapped changelists: " + changes.getLinkedIdeChanges()); + } + builder.processModifiedWithoutCheckout(file.getFilePath().getVirtualFile()); + return false; + } + // See #206 + // There's a weird situation where the getChangeList(id) succeeds, but + // the underlying call is to an equivalent of findChangeList(name), which + // fails. This might even be a race condition. From what I can tell, the + // ChangeListManagerImpl has a ChangeListWorker, which knows about this + // changelist, but the UpdatingChangeListBuilder has a worker which does not. + // + LocalChangeList ideChangelist = + ChangeListManager.getInstance(project).findChangeList(localChangeList.getName()); + if (ideChangelist == null) { + // This can happen after submit, and is a sign that the cache is out of date. + LOG.info("Encountered deleted changelist " + localChangeList + + "; cache is probably out of date and needs a refresh."); + builder.processModifiedWithoutCheckout(file.getFilePath().getVirtualFile()); + return false; + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("Adding change " + file + " to IDE changelist " + localChangeList); + } + + Pair change = createChange(file, movedFrom, config); + associateChangeWithList(builder, change.first, localChangeList); + return change.second; + } + } catch (InterruptedException e) { + InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>( + new VcsInterruptedException("Lock timed out for " + file, e))); + builder.processModifiedWithoutCheckout(file.getFilePath().getVirtualFile()); + return false; + } + } else { + LOG.warn("Encountered file " + file + " with no changelist."); + builder.processModifiedWithoutCheckout(file.getFilePath().getVirtualFile()); + return false; + } + } + + private Map findMovedFiles(List files) { + Map movedFrom = new HashMap<>(); + Map deleted = new HashMap<>(); + + files.forEach((f) -> { + if (f.getIntegrateFrom() != null) { + movedFrom.put(f, f.getIntegrateFrom()); + } + if (f.getFileAction() == P4FileAction.DELETE || f.getFileAction() == P4FileAction.MOVE_DELETE) { + deleted.put(f.getDepotPath(), f); + } + }); + + Map ret = new HashMap<>(); + movedFrom.forEach((key, value) -> { + P4LocalFile f = deleted.get(value); + if (f != null) { + ret.put(key, f); + } + }); + + return ret; + } + + + private void markIgnored(Set dirtyFiles, ChangelistBuilder builder) { + for (FilePath dirtyFile : dirtyFiles) { + if (dirtyFile.getVirtualFile() == null || !dirtyFile.getVirtualFile().exists()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Marking " + dirtyFile + " as locally deleted"); + } + builder.processLocallyDeletedFile(dirtyFile); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("Marking " + dirtyFile + " as not versioned"); + } + + // TODO include the IgnoreFileSet + + // TODO if these files aren't in P4, then they are locally edited but not + // marked so by the server. In that case, they are Unversioned. If they + // are in Perforce, then they are locally edited. + + // TODO unversioned files should be susceptible to the P4IGNORE settings. + + // This is deprecated in >v193, which introduced the new method + // that takes a FilePath. Earlier versions don't have that + // method. + builder.processUnversionedFile(dirtyFile.getVirtualFile()); + } + } + } + + + private void associateChangeWithList(ChangelistBuilder builder, Change change, LocalChangeList localChangeList) { + // The IDE reports a warning from ChangeListWorker when this change is just straight-up added. + // This happens when multiple change objects are added into the builder. + // The check for "equals" happens on the Change object, which checks the + // before and after FilePath objects, if both are equal. This warning + // indicates that this class should ensure that the FilePath combo is only in + // this one changelist: if it's already in the changelist, then skip it; if it's + // in another changelist, then move it; if it's not in a changelist, then add it. + + if (change.getBeforeRevision() != null) { + VirtualFile vf = change.getBeforeRevision().getFile().getVirtualFile(); + if (vf != null) { + LocalChangeList before = ChangeListManager.getInstance(project).getChangeList(vf); + // Only perform the "remove" if the change is different and exists + if (before != null && !before.equals(localChangeList)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Moving file " + vf + " off of 'before' IDE change list " + before); + } + builder.removeRegisteredChangeFor(change.getBeforeRevision().getFile()); + } + } + } + if (change.getAfterRevision() != null) { + VirtualFile vf = change.getAfterRevision().getFile().getVirtualFile(); + if (vf != null) { + LocalChangeList after = ChangeListManager.getInstance(project).getChangeList(vf); + // Only perform the "remove" if the change is different and exists + if (after != null && !after.equals(localChangeList)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Moving file " + vf + " off of 'after' IDE change list " + after); + } + builder.removeRegisteredChangeFor(change.getAfterRevision().getFile()); + } + } + } + if (LOG.isDebugEnabled()) { + LOG.debug("Adding change " + change + " to IDE change list " + localChangeList); + } + + // #206 can't figure out the reason why the ChangeListManagerImpl would know about the change, + // while the builder's worker wouldn't know it. + try { + builder.processChangeInList(change, localChangeList, P4Vcs.getKey()); + } catch (Throwable err) { + if (err.getClass().equals(Throwable.class)) { + // Chances are this is related to #206. + InternalErrorMessage.send(project).unexpectedError(new ErrorEvent<>( + err, + P4Bundle.message("error.changelist.bad-cache", localChangeList.getName()) + )); + // fallback... + builder.processChange(change, P4Vcs.getKey()); + } else { + throw err; + } + } + } + + + private Pair createChange(@NotNull P4LocalFile file, @Nullable P4LocalFile movedFrom, + ClientConfig config) { + boolean usedMovedFrom = false; + ContentRevision before = null; + ContentRevision after = null; + FileStatus status; + switch (file.getFileAction()) { + case ADD: + case ADD_EDIT: + if (movedFrom != null) { + before = new P4LocalFileContentRevision(config, movedFrom, loader); + usedMovedFrom = true; + } + after = new CurrentContentRevision(file.getFilePath()); + status = FileStatus.ADDED; + break; + case EDIT: + case REOPEN: + assert file.getDepotPath() != null; + // If we set the before to a different file location, then the IDE will + // think we set the wrong status, and set it to a "move" operation. + // before = new P4RemoteFileContentRevision(project, + // file.getDepotPath(), file.getHaveRevision(), config.getServerConfig()); + before = new P4LocalFileContentRevision(config, file, loader); + after = new CurrentContentRevision(file.getFilePath()); + status = FileStatus.MODIFIED; + break; + case INTEGRATE: + after = new CurrentContentRevision(file.getFilePath()); + if (file.getIntegrateFrom() != null) { + // TODO find the right charset + if (LOG.isDebugEnabled()) { + LOG.debug("INTEGRATE: Finding relative path from " + file.getFilePath() + " ; " + + file.getDepotPath() + " ; " + file.getClientDepotPath() + " -> " + + file.getIntegrateFrom()); + } + before = P4RemoteFileContentRevision.create(file.getIntegrateFrom(), + RemoteFileUtil.findRelativeRemotePath(file, file.getIntegrateFrom()), + config, loader, null); + } else if (file.getDepotPath() != null) { + before = P4RemoteFileContentRevision.create( + file.getDepotPath(), file.getFilePath(), file.getHaveRevision(), config, + loader, file.getCharset()); + } else { + before = new P4LocalFileContentRevision(config, file, loader); + } + status = FileStatus.MERGE; + break; + case DELETE: + before = new P4LocalFileContentRevision(config, file, loader); + status = FileStatus.DELETED; + break; + case REVERTED: + case NONE: + before = after = new P4LocalFileContentRevision(config, file, loader); + status = FileStatus.NOT_CHANGED; + break; + case EDIT_RESOLVED: + if (file.getDepotPath() == null) { + before = null; + if (movedFrom != null) { + before = new P4LocalFileContentRevision(config, movedFrom, loader); + usedMovedFrom = true; + } + } else { + before = P4RemoteFileContentRevision.create( + file.getDepotPath(), file.getFilePath(), + file.getHaveRevision(), config, loader, file.getCharset()); + } + after = new CurrentContentRevision(file.getFilePath()); + status = FileStatus.MERGE; + break; + case MOVE_DELETE: + if (file.getDepotPath() == null) { + // TODO needs to reference the source file. + before = new P4DeletedLocalFileRevision(file); + } else { + before = P4RemoteFileContentRevision.create( + file.getDepotPath(), file.getFilePath(), + file.getHaveRevision(), config, loader, file.getCharset()); + } + status = FileStatus.DELETED; + break; + case MOVE_ADD: + case MOVE_ADD_EDIT: + case MOVE_EDIT: { + if (movedFrom != null) { + before = new P4LocalFileContentRevision(config, movedFrom, loader); + usedMovedFrom = true; + } else if (file.getIntegrateFrom() != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("MOVE_ADD: Finding relative path from " + file.getFilePath() + " ; " + + file.getDepotPath() + " ; " + file.getClientDepotPath() + " -> " + + file.getIntegrateFrom()); + } + FilePath relativeFrom = RemoteFileUtil.findRelativeRemotePath(file, file.getIntegrateFrom()); + before = P4RemoteFileContentRevision.create( + file.getIntegrateFrom(), relativeFrom, + file.getHaveRevision(), config, loader, file.getCharset()); + } + after = new CurrentContentRevision(file.getFilePath()); + // Even though the status is "ADD", the UI will notice the different before/after + // and show the files as moved. + status = FileStatus.ADDED; + break; + } + case UNKNOWN: + if (movedFrom != null) { + before = new P4LocalFileContentRevision(config, movedFrom, loader); + usedMovedFrom = true; + } else { + before = new P4LocalFileContentRevision(config, file, loader); + } + after = new CurrentContentRevision(file.getFilePath()); + status = FileStatus.UNKNOWN; + break; + default: + throw new IllegalArgumentException("Unknown file action " + file.getFileAction()); + } + return Pair.create(new Change(before, after, status), usedMovedFrom); + } + + private void onChangelistCreated(CreateChangelistAction action, CreateChangelistResult result, + ClientActionMessage.ActionState state) + throws InterruptedException { + IdeChangelistMap cache = CacheComponent.getInstance(project).getServerOpenedCache().first; + switch (state) { + // pending is ignored, because it's handled within this class later, and by IdeChangelistCacheStore. + + case COMPLETED: + cache.setMapping(new P4ChangelistIdImpl( + result.getChangelistId(), result.getClientConfig().getClientServerRef()), action); + break; + + case FAILED: + cache.actionFailed(action); + break; + } + } + + private void onChangelistDelete(DeleteChangelistAction action) + throws InterruptedException { + // Only care about pending changelists that haven't been created yet. + IdeChangelistMap cache = CacheComponent.getInstance(project).getServerOpenedCache().first; + cache.changelistDeleted(action.getChangelistId()); + } + + private ClientConfigRoot getClientFor(VirtualFile file) { + ProjectConfigRegistry reg = ProjectConfigRegistry.getInstance(project); + return reg == null ? null : reg.getClientFor(file); + } + + @NotNull + private Collection getClientConfigRoots() { + ProjectConfigRegistry reg = ProjectConfigRegistry.getInstance(project); + return reg == null ? Collections.emptyList() : reg.getClientConfigRoots(); + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4ChangelistListener.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4ChangelistListener.java index afc50d95..02b3f017 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4ChangelistListener.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4ChangelistListener.java @@ -1,396 +1,396 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.extension; - -import com.intellij.notification.NotificationType; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vcs.AbstractVcs; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.ProjectLevelVcsManager; -import com.intellij.openapi.vcs.changes.Change; -import com.intellij.openapi.vcs.changes.ChangeList; -import com.intellij.openapi.vcs.changes.ChangeListListener; -import com.intellij.openapi.vcs.changes.ContentRevision; -import com.intellij.openapi.vcs.changes.LocalChangeList; -import net.groboclown.p4.server.api.ClientConfigRoot; -import net.groboclown.p4.server.api.ProjectConfigRegistry; -import net.groboclown.p4.server.api.async.Answer; -import net.groboclown.p4.server.api.async.AnswerSink; -import net.groboclown.p4.server.api.cache.IdeChangelistMap; -import net.groboclown.p4.server.api.cache.IdeFileMap; -import net.groboclown.p4.server.api.commands.changelist.CreateChangelistAction; -import net.groboclown.p4.server.api.commands.changelist.DeleteChangelistAction; -import net.groboclown.p4.server.api.commands.changelist.EditChangelistAction; -import net.groboclown.p4.server.api.commands.changelist.MoveFilesToChangelistAction; -import net.groboclown.p4.server.api.commands.changelist.MoveFilesToChangelistResult; -import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; -import net.groboclown.p4.server.api.messagebus.ErrorEvent; -import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; -import net.groboclown.p4.server.api.util.FileTreeUtil; -import net.groboclown.p4.server.api.values.P4ChangelistId; -import net.groboclown.p4.server.api.values.P4LocalChangelist; -import net.groboclown.p4.server.api.values.P4LocalFile; -import net.groboclown.p4.server.impl.commands.AnswerUtil; -import net.groboclown.p4.server.impl.values.P4ChangelistIdImpl; -import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.components.CacheComponent; -import net.groboclown.p4plugin.components.P4ServerComponent; -import net.groboclown.p4plugin.components.UserProjectPreferences; -import net.groboclown.p4plugin.messages.UserMessage; -import net.groboclown.p4plugin.util.ChangelistUtil; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; - -public class P4ChangelistListener - implements ChangeListListener { - private final static Logger LOG = Logger.getInstance(P4ChangelistListener.class); - - private final Project myProject; - - P4ChangelistListener(@NotNull final Project project) { - myProject = project; - } - - @Override - public void changeListAdded(@NotNull final ChangeList list) { - // Adding a changelist does not automatically create a corresponding - // Perforce changelist. It must have files added to it that are - // Perforce-backed in order for it to become one. - LOG.debug("changeListAdded: " + list.getName() + "; [" + list.getComment() + "]; " + - list.getClass().getSimpleName()); - } - - @Override - public void changeListRemoved(@NotNull final ChangeList list) { - LOG.debug("changeListRemoved: " + list.getName() + "; [" + list.getComment() + "]; " + list.getClass() - .getSimpleName()); - if (!(list instanceof LocalChangeList) || !UserProjectPreferences.getRemoveP4Changelist(myProject)) { - return; - } - LocalChangeList local = (LocalChangeList) list; - - IdeChangelistMap changelistMap = - CacheComponent.getInstance(myProject).getServerOpenedCache().first; - try { - final List changelists = new ArrayList<>(changelistMap.getP4ChangesFor(local)); - for (ClientConfigRoot clientConfigRoot : getClientConfigRoots()) { - final Iterator iter = changelists.iterator(); - while (iter.hasNext()) { - final P4ChangelistId p4change = iter.next(); - if (p4change.isIn(clientConfigRoot.getServerConfig())) { - iter.remove(); - P4ServerComponent.perform(myProject, clientConfigRoot.getClientConfig(), - new DeleteChangelistAction(p4change)) - .whenCompleted((r) -> { - UserMessage.showNotification(myProject, UserMessage.INFO, - P4Bundle.message("changelist.removed.text", p4change.getClientname(), p4change), - P4Bundle.message("changelist.removed.title", p4change.getChangelistId()), - NotificationType.INFORMATION); - }) - .whenServerError((err) -> { - UserMessage.showNotification(myProject, UserMessage.ERROR, - err.getLocalizedMessage(), - P4Bundle.message("error.remove-changelist.title", p4change.getChangelistId()), - NotificationType.ERROR); - }); - } - } - } - } catch (InterruptedException e) { - InternalErrorMessage.send(myProject).cacheLockTimeoutError(new ErrorEvent<>( - new VcsInterruptedException("Interrupted while performing changelist removal", e))); - } - } - - @Override - public void changesRemoved(@NotNull final Collection changes, @NotNull final ChangeList fromList) { - if (LOG.isDebugEnabled()) { - LOG.debug("changesRemoved: changes " + changes); - LOG.debug("changesRemoved: changelist " + fromList.getName() + "; [" + fromList.getComment() + "]; " - + fromList.getClass().getSimpleName()); - } - - // This is called when a file change is removed from a changelist, not when a changelist is deleted. - // A revert will move the file it out of the changelist. - // Note that if a change is removed, it is usually added or - // moved, so we can ignore this call. - } - - @Override - public void changesAdded(@NotNull final Collection changes, @NotNull final ChangeList toList) { - if (LOG.isDebugEnabled()) { - LOG.debug("changesAdded: changes " + changes); - LOG.debug("changesAdded: changelist " + toList.getName() + "; [" + toList.getComment() + "]"); - } - - if (! (toList instanceof LocalChangeList)) { - return; - } - - // TODO if a file in a "move" operation is included, but not the - // other side, then ensure the other side is also moved along with this one. - // That can be easily solved if the move operation is contained in a single Change object. - - - LocalChangeList local = (LocalChangeList) toList; - - for (ClientConfigRoot clientConfigRoot : getClientConfigRoots()) { - // First, see if there are any changes for this client config that require an - // underlying P4 changelist move. - - Collection affectedFiles = getAffectedFiles(clientConfigRoot, changes); - if (affectedFiles.isEmpty()) { - continue; - } - - try { - // The file may already be associated with the correct change; this can happen on - // the first invocation from the changelist view refresh. - final Pair cache = - CacheComponent.getInstance(myProject).getServerOpenedCache(); - final P4ChangelistId p4changeSrc = cache.first.getP4ChangeFor( - clientConfigRoot.getClientConfig().getClientServerRef(), - local); - if (p4changeSrc != null) { - final Iterator iter = affectedFiles.iterator(); - while (iter.hasNext()) { - final P4LocalFile p4file = cache.second.forIdeFile(iter.next()); - if (p4file != null && p4file.getChangelistId() != null && - p4changeSrc.getChangelistId() == p4file.getChangelistId().getChangelistId()) { - if (LOG.isDebugEnabled()) { - LOG.debug("Skipping " + p4file + " from changelist move; it's already in changelist " + - p4changeSrc); - } - iter.remove(); - } - } - } - if (affectedFiles.isEmpty()) { - continue; - } - - Answer.resolve(p4changeSrc) - .futureMap((BiConsumer>) (p4change, sink) -> { - if (p4change != null) { - sink.resolve(p4change); - return; - } - if (LOG.isDebugEnabled()) { - LOG.debug("Forcing the creation of a changelist due to moving p4 file into IDE change"); - } - // No server changelist associated with this ide change. Create one. - CreateChangelistAction action = - new CreateChangelistAction(clientConfigRoot.getClientConfig().getClientServerRef(), - toDescription(local), local.getId()); - P4ServerComponent.perform(myProject, clientConfigRoot.getClientConfig(), action) - .whenCompleted((res) -> { - sink.resolve(new P4ChangelistIdImpl( - res.getChangelistId(), - clientConfigRoot.getClientConfig().getClientServerRef())); - }) - .whenServerError(sink::reject) - .whenOffline(() -> { - try { - CacheComponent.getInstance(myProject).getServerOpenedCache().first - .setMapping(action, local); - P4LocalChangelist v = CacheComponent.getInstance(myProject).getServerOpenedCache() - .first.getMappedChangelist(action); - sink.resolve(v == null ? null : v.getChangelistId()); - } catch (InterruptedException e) { - sink.reject(AnswerUtil.createFor(e)); - } - }); - }) - .futureMap((cl, sink) -> { - if (cl == null) { - UserMessage.showNotification(myProject, UserMessage.ERROR, - P4Bundle.message("error.create-changelist", local.getName()), - P4Bundle.message("error.create-changelist.title"), - NotificationType.ERROR); - sink.resolve(null); - return; - } - UserMessage.showNotification(myProject, UserMessage.VERBOSE, - P4Bundle.message("changelist.created", cl.getChangelistId()), - P4Bundle.message("changelist.created.title"), - NotificationType.INFORMATION); - P4ServerComponent - .perform(myProject, clientConfigRoot.getClientConfig(), - new MoveFilesToChangelistAction(cl, affectedFiles)) - .whenCompleted(sink::resolve) - .whenServerError(sink::reject) - - // Offline is not an error for this particular request. - .whenOffline(() -> sink.resolve(null)); - }) - .whenCompleted((r) -> { - if (r != null) { - MoveFilesToChangelistResult res = (MoveFilesToChangelistResult) r; - UserMessage.showNotification(myProject, UserMessage.VERBOSE, - P4Bundle.message("changelist.file.moved", res.getFiles().size(), res.getMessage()), - P4Bundle.message("changelist.file.moved.title", - res.getChangelistId().getChangelistId()), - NotificationType.INFORMATION); - } - }) - .whenFailed(err -> { - UserMessage.showNotification(myProject, UserMessage.ERROR, - P4Bundle.message("error.changelist-file-move", err.getLocalizedMessage()), - P4Bundle.message("error.changelist-file-move.title"), - NotificationType.ERROR); - }) - .blockingWait(UserProjectPreferences.getLockWaitTimeoutMillis(myProject), TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - InternalErrorMessage.send(myProject).cacheLockTimeoutError(new ErrorEvent<>( - new VcsInterruptedException(e))); - } - } - } - - @Override - public void changeListChanged(final ChangeList list) { - LOG.debug("changeListChanged: " + list); - } - - @Override - public void changeListRenamed(final ChangeList list, final String oldName) { - LOG.info("changeListRenamed: from " + oldName + " to " + list); - - if (! (list instanceof LocalChangeList)) { - // ignore - return; - } - - // Don't check name equality, due to the reuse from - // changeListCommentChanged - - LocalChangeList local = (LocalChangeList) list; - - for (ClientConfigRoot clientConfigRoot : getClientConfigRoots()) { - try { - P4ChangelistId change = - CacheComponent.getInstance(myProject).getServerOpenedCache().first.getP4ChangeFor( - clientConfigRoot.getClientConfig().getClientServerRef(), - local); - if (change != null) { - P4ServerComponent - .perform(myProject, clientConfigRoot.getClientConfig(), - new EditChangelistAction(change, toDescription(local))); - } - } catch (InterruptedException e) { - InternalErrorMessage.send(myProject).cacheLockTimeoutError(new ErrorEvent<>( - new VcsInterruptedException(e))); - } - } - } - - @Override - public void changeListCommentChanged(final ChangeList list, final String oldComment) { - LOG.debug("changeListCommentChanged: " + list); - - // This is the same logic as with the name change. - changeListRenamed(list, list.getName()); - } - - @Override - public void changesMoved(final Collection changes, final ChangeList fromList, final ChangeList toList) { - LOG.debug("changesMoved: " + fromList + " to " + toList); - - // This is just like a "changes added" command, - // in the sense that the old list doesn't matter too much. - changesAdded(changes, toList); - } - - @Override - public void defaultListChanged(final ChangeList oldDefaultList, final ChangeList newDefaultList) { - LOG.debug("defaultListChanged: " + oldDefaultList + " to " + newDefaultList); - - // Don't change the internal default changelist id mapping to the IDE change list. - } - - @Override - public void unchangedFileStatusChanged() { - LOG.debug("unchangedFileStatusChanged"); - } - - @Override - public void changeListUpdateDone() { - LOG.debug("changeListUpdateDone"); - } - - private boolean isUnderVcs(final FilePath path) { - // Only files can be under VCS control. - if (path.isDirectory()) { - return false; - } - final AbstractVcs vcs = ProjectLevelVcsManager.getInstance(myProject).getVcsFor(path); - return ((vcs != null) && (P4Vcs.VCS_NAME.equals(vcs.getName()))); - } - - private List getPathsFromChanges(final Collection changes) { - final List paths = new ArrayList<>(); - for (Change change : changes) { - if ((change.getBeforeRevision() != null) && (isUnderVcs(change.getBeforeRevision().getFile()))) { - FilePath path = change.getBeforeRevision().getFile(); - if (!paths.contains(path)) { - paths.add(path); - } - } - if ((change.getAfterRevision() != null) && (isUnderVcs(change.getAfterRevision().getFile()))) { - final FilePath path = change.getAfterRevision().getFile(); - if (!paths.contains(path)) { - paths.add(path); - } - } - } - return paths; - } - - private String toDescription(@NotNull ChangeList changeList) { - return ChangelistUtil.createP4ChangelistDescription(myProject, changeList); - } - - private Collection getAffectedFiles(ClientConfigRoot clientConfigRoot, Collection changes) { - Set ret = new HashSet<>(); - for (Change change : changes) { - for (ContentRevision cr : Arrays.asList(change.getBeforeRevision(), change.getAfterRevision())) { - if (cr != null) { - FilePath file = cr.getFile(); - if (!ret.contains(file) && FileTreeUtil.isSameOrUnder(clientConfigRoot.getClientRootDir(), file)) { - ret.add(cr.getFile()); - } - } - } - } - return ret; - } - - @NotNull - private Collection getClientConfigRoots() { - ProjectConfigRegistry reg = ProjectConfigRegistry.getInstance(myProject); - return reg == null ? Collections.emptyList() : reg.getClientConfigRoots(); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.extension; + +import com.intellij.notification.NotificationType; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeList; +import com.intellij.openapi.vcs.changes.ChangeListListener; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.changes.LocalChangeList; +import net.groboclown.p4.server.api.ClientConfigRoot; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import net.groboclown.p4.server.api.async.Answer; +import net.groboclown.p4.server.api.async.AnswerSink; +import net.groboclown.p4.server.api.cache.IdeChangelistMap; +import net.groboclown.p4.server.api.cache.IdeFileMap; +import net.groboclown.p4.server.api.commands.changelist.CreateChangelistAction; +import net.groboclown.p4.server.api.commands.changelist.DeleteChangelistAction; +import net.groboclown.p4.server.api.commands.changelist.EditChangelistAction; +import net.groboclown.p4.server.api.commands.changelist.MoveFilesToChangelistAction; +import net.groboclown.p4.server.api.commands.changelist.MoveFilesToChangelistResult; +import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; +import net.groboclown.p4.server.api.messagebus.ErrorEvent; +import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; +import net.groboclown.p4.server.api.util.FileTreeUtil; +import net.groboclown.p4.server.api.values.P4ChangelistId; +import net.groboclown.p4.server.api.values.P4LocalChangelist; +import net.groboclown.p4.server.api.values.P4LocalFile; +import net.groboclown.p4.server.impl.commands.AnswerUtil; +import net.groboclown.p4.server.impl.values.P4ChangelistIdImpl; +import net.groboclown.p4plugin.P4Bundle; +import net.groboclown.p4plugin.components.CacheComponent; +import net.groboclown.p4plugin.components.P4ServerComponent; +import net.groboclown.p4plugin.components.UserProjectPreferences; +import net.groboclown.p4plugin.messages.UserMessage; +import net.groboclown.p4plugin.util.ChangelistUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; + +public class P4ChangelistListener + implements ChangeListListener { + private final static Logger LOG = Logger.getInstance(P4ChangelistListener.class); + + private final Project myProject; + + P4ChangelistListener(@NotNull final Project project) { + myProject = project; + } + + @Override + public void changeListAdded(@NotNull final ChangeList list) { + // Adding a changelist does not automatically create a corresponding + // Perforce changelist. It must have files added to it that are + // Perforce-backed in order for it to become one. + LOG.debug("changeListAdded: " + list.getName() + "; [" + list.getComment() + "]; " + + list.getClass().getSimpleName()); + } + + @Override + public void changeListRemoved(@NotNull final ChangeList list) { + LOG.debug("changeListRemoved: " + list.getName() + "; [" + list.getComment() + "]; " + list.getClass() + .getSimpleName()); + if (!(list instanceof LocalChangeList) || !UserProjectPreferences.getRemoveP4Changelist(myProject)) { + return; + } + LocalChangeList local = (LocalChangeList) list; + + IdeChangelistMap changelistMap = + CacheComponent.getInstance(myProject).getServerOpenedCache().first; + try { + final List changelists = new ArrayList<>(changelistMap.getP4ChangesFor(local)); + for (ClientConfigRoot clientConfigRoot : getClientConfigRoots()) { + final Iterator iter = changelists.iterator(); + while (iter.hasNext()) { + final P4ChangelistId p4change = iter.next(); + if (p4change.isIn(clientConfigRoot.getServerConfig())) { + iter.remove(); + P4ServerComponent.perform(myProject, clientConfigRoot.getClientConfig(), + new DeleteChangelistAction(p4change)) + .whenCompleted((r) -> { + UserMessage.showNotification(myProject, UserMessage.INFO, + P4Bundle.message("changelist.removed.text", p4change.getClientname(), p4change), + P4Bundle.message("changelist.removed.title", p4change.getChangelistId()), + NotificationType.INFORMATION); + }) + .whenServerError((err) -> { + UserMessage.showNotification(myProject, UserMessage.ERROR, + err.getLocalizedMessage(), + P4Bundle.message("error.remove-changelist.title", p4change.getChangelistId()), + NotificationType.ERROR); + }); + } + } + } + } catch (InterruptedException e) { + InternalErrorMessage.send(myProject).cacheLockTimeoutError(new ErrorEvent<>( + new VcsInterruptedException("Interrupted while performing changelist removal", e))); + } + } + + @Override + public void changesRemoved(@NotNull final Collection changes, @NotNull final ChangeList fromList) { + if (LOG.isDebugEnabled()) { + LOG.debug("changesRemoved: changes " + changes); + LOG.debug("changesRemoved: changelist " + fromList.getName() + "; [" + fromList.getComment() + "]; " + + fromList.getClass().getSimpleName()); + } + + // This is called when a file change is removed from a changelist, not when a changelist is deleted. + // A revert will move the file it out of the changelist. + // Note that if a change is removed, it is usually added or + // moved, so we can ignore this call. + } + + @Override + public void changesAdded(@NotNull final Collection changes, @NotNull final ChangeList toList) { + if (LOG.isDebugEnabled()) { + LOG.debug("changesAdded: changes " + changes); + LOG.debug("changesAdded: changelist " + toList.getName() + "; [" + toList.getComment() + "]"); + } + + if (! (toList instanceof LocalChangeList)) { + return; + } + + // TODO if a file in a "move" operation is included, but not the + // other side, then ensure the other side is also moved along with this one. + // That can be easily solved if the move operation is contained in a single Change object. + + + LocalChangeList local = (LocalChangeList) toList; + + for (ClientConfigRoot clientConfigRoot : getClientConfigRoots()) { + // First, see if there are any changes for this client config that require an + // underlying P4 changelist move. + + Collection affectedFiles = getAffectedFiles(clientConfigRoot, changes); + if (affectedFiles.isEmpty()) { + continue; + } + + try { + // The file may already be associated with the correct change; this can happen on + // the first invocation from the changelist view refresh. + final Pair cache = + CacheComponent.getInstance(myProject).getServerOpenedCache(); + final P4ChangelistId p4changeSrc = cache.first.getP4ChangeFor( + clientConfigRoot.getClientConfig().getClientServerRef(), + local); + if (p4changeSrc != null) { + final Iterator iter = affectedFiles.iterator(); + while (iter.hasNext()) { + final P4LocalFile p4file = cache.second.forIdeFile(iter.next()); + if (p4file != null && p4file.getChangelistId() != null && + p4changeSrc.getChangelistId() == p4file.getChangelistId().getChangelistId()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Skipping " + p4file + " from changelist move; it's already in changelist " + + p4changeSrc); + } + iter.remove(); + } + } + } + if (affectedFiles.isEmpty()) { + continue; + } + + Answer.resolve(p4changeSrc) + .futureMap((BiConsumer>) (p4change, sink) -> { + if (p4change != null) { + sink.resolve(p4change); + return; + } + if (LOG.isDebugEnabled()) { + LOG.debug("Forcing the creation of a changelist due to moving p4 file into IDE change"); + } + // No server changelist associated with this ide change. Create one. + CreateChangelistAction action = + new CreateChangelistAction(clientConfigRoot.getClientConfig().getClientServerRef(), + toDescription(local), local.getId()); + P4ServerComponent.perform(myProject, clientConfigRoot.getClientConfig(), action) + .whenCompleted((res) -> { + sink.resolve(new P4ChangelistIdImpl( + res.getChangelistId(), + clientConfigRoot.getClientConfig().getClientServerRef())); + }) + .whenServerError(sink::reject) + .whenOffline(() -> { + try { + CacheComponent.getInstance(myProject).getServerOpenedCache().first + .setMapping(action, local); + P4LocalChangelist v = CacheComponent.getInstance(myProject).getServerOpenedCache() + .first.getMappedChangelist(action); + sink.resolve(v == null ? null : v.getChangelistId()); + } catch (InterruptedException e) { + sink.reject(AnswerUtil.createFor(e)); + } + }); + }) + .futureMap((cl, sink) -> { + if (cl == null) { + UserMessage.showNotification(myProject, UserMessage.ERROR, + P4Bundle.message("error.create-changelist", local.getName()), + P4Bundle.message("error.create-changelist.title"), + NotificationType.ERROR); + sink.resolve(null); + return; + } + UserMessage.showNotification(myProject, UserMessage.VERBOSE, + P4Bundle.message("changelist.created", cl.getChangelistId()), + P4Bundle.message("changelist.created.title"), + NotificationType.INFORMATION); + P4ServerComponent + .perform(myProject, clientConfigRoot.getClientConfig(), + new MoveFilesToChangelistAction(cl, affectedFiles)) + .whenCompleted(sink::resolve) + .whenServerError(sink::reject) + + // Offline is not an error for this particular request. + .whenOffline(() -> sink.resolve(null)); + }) + .whenCompleted((r) -> { + if (r != null) { + MoveFilesToChangelistResult res = (MoveFilesToChangelistResult) r; + UserMessage.showNotification(myProject, UserMessage.VERBOSE, + P4Bundle.message("changelist.file.moved", res.getFiles().size(), res.getMessage()), + P4Bundle.message("changelist.file.moved.title", + res.getChangelistId().getChangelistId()), + NotificationType.INFORMATION); + } + }) + .whenFailed(err -> { + UserMessage.showNotification(myProject, UserMessage.ERROR, + P4Bundle.message("error.changelist-file-move", err.getLocalizedMessage()), + P4Bundle.message("error.changelist-file-move.title"), + NotificationType.ERROR); + }) + .blockingWait(UserProjectPreferences.getLockWaitTimeoutMillis(myProject), TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + InternalErrorMessage.send(myProject).cacheLockTimeoutError(new ErrorEvent<>( + new VcsInterruptedException(e))); + } + } + } + + @Override + public void changeListChanged(final ChangeList list) { + LOG.debug("changeListChanged: " + list); + } + + @Override + public void changeListRenamed(final ChangeList list, final String oldName) { + LOG.info("changeListRenamed: from " + oldName + " to " + list); + + if (! (list instanceof LocalChangeList)) { + // ignore + return; + } + + // Don't check name equality, due to the reuse from + // changeListCommentChanged + + LocalChangeList local = (LocalChangeList) list; + + for (ClientConfigRoot clientConfigRoot : getClientConfigRoots()) { + try { + P4ChangelistId change = + CacheComponent.getInstance(myProject).getServerOpenedCache().first.getP4ChangeFor( + clientConfigRoot.getClientConfig().getClientServerRef(), + local); + if (change != null) { + P4ServerComponent + .perform(myProject, clientConfigRoot.getClientConfig(), + new EditChangelistAction(change, toDescription(local))); + } + } catch (InterruptedException e) { + InternalErrorMessage.send(myProject).cacheLockTimeoutError(new ErrorEvent<>( + new VcsInterruptedException(e))); + } + } + } + + @Override + public void changeListCommentChanged(final ChangeList list, final String oldComment) { + LOG.debug("changeListCommentChanged: " + list); + + // This is the same logic as with the name change. + changeListRenamed(list, list.getName()); + } + + @Override + public void changesMoved(final Collection changes, final ChangeList fromList, final ChangeList toList) { + LOG.debug("changesMoved: " + fromList + " to " + toList); + + // This is just like a "changes added" command, + // in the sense that the old list doesn't matter too much. + changesAdded(changes, toList); + } + + @Override + public void defaultListChanged(final ChangeList oldDefaultList, final ChangeList newDefaultList) { + LOG.debug("defaultListChanged: " + oldDefaultList + " to " + newDefaultList); + + // Don't change the internal default changelist id mapping to the IDE change list. + } + + @Override + public void unchangedFileStatusChanged() { + LOG.debug("unchangedFileStatusChanged"); + } + + @Override + public void changeListUpdateDone() { + LOG.debug("changeListUpdateDone"); + } + + private boolean isUnderVcs(final FilePath path) { + // Only files can be under VCS control. + if (path.isDirectory()) { + return false; + } + final AbstractVcs vcs = ProjectLevelVcsManager.getInstance(myProject).getVcsFor(path); + return ((vcs != null) && (P4Vcs.VCS_NAME.equals(vcs.getName()))); + } + + private List getPathsFromChanges(final Collection changes) { + final List paths = new ArrayList<>(); + for (Change change : changes) { + if ((change.getBeforeRevision() != null) && (isUnderVcs(change.getBeforeRevision().getFile()))) { + FilePath path = change.getBeforeRevision().getFile(); + if (!paths.contains(path)) { + paths.add(path); + } + } + if ((change.getAfterRevision() != null) && (isUnderVcs(change.getAfterRevision().getFile()))) { + final FilePath path = change.getAfterRevision().getFile(); + if (!paths.contains(path)) { + paths.add(path); + } + } + } + return paths; + } + + private String toDescription(@NotNull ChangeList changeList) { + return ChangelistUtil.createP4ChangelistDescription(myProject, changeList); + } + + private Collection getAffectedFiles(ClientConfigRoot clientConfigRoot, Collection changes) { + Set ret = new HashSet<>(); + for (Change change : changes) { + for (ContentRevision cr : Arrays.asList(change.getBeforeRevision(), change.getAfterRevision())) { + if (cr != null) { + FilePath file = cr.getFile(); + if (!ret.contains(file) && FileTreeUtil.isSameOrUnder(clientConfigRoot.getClientRootDir(), file)) { + ret.add(cr.getFile()); + } + } + } + } + return ret; + } + + @NotNull + private Collection getClientConfigRoots() { + ProjectConfigRegistry reg = ProjectConfigRegistry.getInstance(myProject); + return reg == null ? Collections.emptyList() : reg.getClientConfigRoots(); + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4ChangelistNumber.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4ChangelistNumber.java index 31d344ce..89daf6a3 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4ChangelistNumber.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4ChangelistNumber.java @@ -1,73 +1,73 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4plugin.extension; - -import com.intellij.openapi.vcs.history.VcsRevisionNumber; -import com.perforce.p4java.core.IChangelist; -import com.perforce.p4java.core.IChangelistSummary; -import org.jetbrains.annotations.NotNull; - -/** - * An extension to the VcsRevisionNumber that allows for the revisions to - * spread into the past history of the file across branches, rather than - * limited to the current file depot location. - */ -public class P4ChangelistNumber implements VcsRevisionNumber { - private final int changelist; - - public P4ChangelistNumber(@NotNull final IChangelist changelist) { - this.changelist = changelist.getId(); - } - - public P4ChangelistNumber(@NotNull IChangelistSummary change) { - this.changelist = change.getId(); - } - - public int getChangelist() { - return changelist; - } - - - @Override - public String asString() { - return '@' + Integer.toString(changelist); - } - - @Override - public int compareTo(final VcsRevisionNumber o) { - if (o != null && o instanceof P4ChangelistNumber) { - P4ChangelistNumber that = (P4ChangelistNumber) o; - return this.changelist - that.changelist; - } else { - return -1; - } - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj == null || ! (obj instanceof VcsRevisionNumber)) { - return false; - } - return compareTo((VcsRevisionNumber) obj) == 0; - } - - @Override - public int hashCode() { - return changelist; - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4plugin.extension; + +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.perforce.p4java.core.IChangelist; +import com.perforce.p4java.core.IChangelistSummary; +import org.jetbrains.annotations.NotNull; + +/** + * An extension to the VcsRevisionNumber that allows for the revisions to + * spread into the past history of the file across branches, rather than + * limited to the current file depot location. + */ +public class P4ChangelistNumber implements VcsRevisionNumber { + private final int changelist; + + public P4ChangelistNumber(@NotNull final IChangelist changelist) { + this.changelist = changelist.getId(); + } + + public P4ChangelistNumber(@NotNull IChangelistSummary change) { + this.changelist = change.getId(); + } + + public int getChangelist() { + return changelist; + } + + + @Override + public String asString() { + return '@' + Integer.toString(changelist); + } + + @Override + public int compareTo(final VcsRevisionNumber o) { + if (o != null && o instanceof P4ChangelistNumber) { + P4ChangelistNumber that = (P4ChangelistNumber) o; + return this.changelist - that.changelist; + } else { + return -1; + } + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || ! (obj instanceof VcsRevisionNumber)) { + return false; + } + return compareTo((VcsRevisionNumber) obj) == 0; + } + + @Override + public int hashCode() { + return changelist; + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4CheckinEnvironment.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4CheckinEnvironment.java index 8ca9ff96..df5adab0 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4CheckinEnvironment.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4CheckinEnvironment.java @@ -1,378 +1,378 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.extension; - -import com.intellij.notification.NotificationType; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.CheckinProjectPanel; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.changes.Change; -import com.intellij.openapi.vcs.changes.ChangeList; -import com.intellij.openapi.vcs.changes.ChangeListManager; -import com.intellij.openapi.vcs.changes.CommitExecutor; -import com.intellij.openapi.vcs.changes.CommitSession; -import com.intellij.openapi.vcs.changes.LocalChangeList; -import com.intellij.openapi.vcs.checkin.CheckinChangeListSpecificComponent; -import com.intellij.openapi.vcs.checkin.CheckinEnvironment; -import com.intellij.openapi.vcs.ui.RefreshableOnComponent; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.NullableFunction; -import com.intellij.util.PairConsumer; -import com.intellij.vcsUtil.VcsUtil; -import net.groboclown.p4.server.api.ClientConfigRoot; -import net.groboclown.p4.server.api.ClientServerRef; -import net.groboclown.p4.server.api.P4CommandRunner; -import net.groboclown.p4.server.api.ProjectConfigRegistry; -import net.groboclown.p4.server.api.commands.file.AddEditAction; -import net.groboclown.p4.server.api.commands.file.AddEditResult; -import net.groboclown.p4.server.api.commands.file.DeleteFileAction; -import net.groboclown.p4.server.api.commands.file.DeleteFileResult; -import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; -import net.groboclown.p4.server.api.values.P4ChangelistId; -import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.components.P4ServerComponent; -import net.groboclown.p4plugin.components.UserProjectPreferences; -import net.groboclown.p4plugin.messages.UserMessage; -import net.groboclown.p4plugin.ui.WrapperPanel; -import net.groboclown.p4plugin.ui.submit.SubmitModel; -import net.groboclown.p4plugin.ui.submit.SubmitPanel; -import net.groboclown.p4plugin.util.ChangelistUtil; -import net.groboclown.p4plugin.util.CommitUtil; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import static net.groboclown.p4.server.api.values.P4FileType.getFileType; - -public class P4CheckinEnvironment implements CheckinEnvironment, CommitExecutor { - private static final Logger LOG = Logger.getInstance(P4CheckinEnvironment.class); - private static final String HELP_ID = null; - - private final Project project; - - P4CheckinEnvironment(@NotNull P4Vcs vcs) { - this.project = vcs.getProject(); - } - - @Nullable - //@Override - @Deprecated - public RefreshableOnComponent createAdditionalOptionsPanel(CheckinProjectPanel panel, PairConsumer additionalDataConsumer) { - // #52 - we could be able to monitor panel.getCommitMessage(); to ensure - // that there's a message, and when there isn't, disable the submit - // button. - // We can monitor the message (with a thread, disposing of it is - // a bit of a bother, but it's doable). However, disabling the button - // currently requires: - // 1. Grabbing the owning dialog object of the panel. - // 2. Using reflection to make the protected "disableOKButton" method - // accessible and callable. - // All of this means that this isn't a very good idea. - // The panel has a "setWarning" method, but that doesn't do anything. - - return new P4OnCheckinPanel(project, panel, additionalDataConsumer); - } - - @Nullable - @Override - public String getDefaultMessageFor(FilePath[] filesToCheckin) { - return null; - } - - @Nullable - @Override - public String getHelpId() { - return HELP_ID; - } - - @Override - public String getCheckinOperationName() { - return P4Bundle.message("commit.operation.name"); - } - - @Nullable - //@Override - @Deprecated - public List commit(List changes, String preparedComment) { - return commit(changes, preparedComment, (p) -> null, new HashSet<>()); - } - - @Nullable - @Override - public List commit(List changes, final String preparedComment, - @NotNull NullableFunction parametersHolder, Set feedback) { - return CommitUtil.commit(project, changes, preparedComment, SubmitModel.getJobs(parametersHolder), - SubmitModel.getSubmitStatus(parametersHolder)); - } - - @Nullable - @Override - public List scheduleMissingFileForDeletion(List files) { - if (LOG.isDebugEnabled()) { - LOG.debug("scheduleMissingFileForDeletion: " + files); - } - List ret = new ArrayList<>(); - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null) { - ret.add(new VcsException("Project not configured")); - return ret; - } - Map activeChangelistIds = getActiveChangelistIds(); - for (FilePath file : files) { - ClientConfigRoot root = registry.getClientFor(file); - if (root == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("Skipped adding file not in VCS root: " + file); - } - } else { - P4ChangelistId id = getActiveChangelistFor(root, activeChangelistIds); - if (LOG.isDebugEnabled()) { - LOG.debug("Opening for add/edit: " + file + " (@" + id + ")"); - } - P4CommandRunner.ActionAnswer answer = - P4ServerComponent - .perform(project, root.getClientConfig(), new DeleteFileAction(file, id)) - .whenCompleted((res) -> ChangeListManager.getInstance(project).scheduleUpdate()); - if (ApplicationManager.getApplication().isDispatchThread()) { - LOG.info("Running delete file command in EDT; will not wait for server errors."); - } else { - try { - answer.blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), - TimeUnit.MILLISECONDS); - } catch (P4CommandRunner.ServerResultException e) { - ret.add(e); - } catch (InterruptedException e) { - ret.add(new VcsInterruptedException(e)); - } - } - } - } - return ret; - } - - @Nullable - @Override - public List scheduleUnversionedFilesForAddition(List files) { - if (LOG.isDebugEnabled()) { - LOG.info("scheduleUnversionedFilesForAddition: " + files); - } - List ret = new ArrayList<>(); - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null) { - ret.add(new VcsException("Project not configured")); - return ret; - } - Map activeChangelistIds = getActiveChangelistIds(); - for (VirtualFile file : files) { - ClientConfigRoot root = registry.getClientFor(file); - if (root == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("Skipped adding file not in VCS root: " + file); - } - } else { - FilePath fp = VcsUtil.getFilePath(file); - P4ChangelistId id = getActiveChangelistFor(root, activeChangelistIds); - if (LOG.isDebugEnabled()) { - LOG.debug("Opening for add/edit: " + fp + " (@" + id + ")"); - } - P4CommandRunner.ActionAnswer answer = - P4ServerComponent - .perform(project, root.getClientConfig(), new AddEditAction(fp, getFileType(fp), id, (String) null)) - .whenCompleted((res) -> ChangeListManager.getInstance(project).scheduleUpdate()) - ; - if (ApplicationManager.getApplication().isDispatchThread()) { - LOG.info("Running add/edit command in EDT; will not wait for server errors."); - } else { - try { - answer.blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), - TimeUnit.MILLISECONDS); - } catch (P4CommandRunner.ServerResultException e) { - // TODO better exception wrapper? - ret.add(e); - } catch (InterruptedException e) { - ret.add(new VcsInterruptedException(e)); - } - } - } - } - return ret; - } - - @Deprecated - //@Override - public boolean keepChangeListAfterCommit(ChangeList changeList) { - return false; - } - - @Override - public boolean isRefreshAfterCommitNeeded() { - // File status (read-only state) may have changed, or CVS-style substitution - // may have happened. - return true; - } - - private Map getActiveChangelistIds() { - return ChangelistUtil.getActiveChangelistIds(project); - } - - @NotNull - private P4ChangelistId getActiveChangelistFor(ClientConfigRoot root, Map ids) { - return ChangelistUtil.getActiveChangelistFor(root, ids); - } - - @Nls - @Override - public String getActionText() { - return P4Bundle.getString("commit.operation.name"); - } - - @NotNull - //@Override - @Deprecated - public CommitSession createCommitSession() { - final SubmitModel model = new SubmitModel(project); - return new CommitSession() { - // Only here for API compatibility - @Deprecated - @Nullable - //@Override - public JComponent getAdditionalConfigurationUI() { - return getAdditionalConfigurationUI(Collections.emptyList(), ""); - } - - @Nullable - @Override - public JComponent getAdditionalConfigurationUI(Collection changes, String commitMessage) { - model.setSelectedCurrentChanges(changes); - return new SubmitPanel(model).getRoot(); - } - - @Override - public boolean canExecute(Collection changes, String commitMessage) { - // Set the ok action as enabled/disabled depending upon - // whether the comment is empty or not (bug #52). Probably - // should just set a warning, though, but that doesn't do anything. - // We will also need a notification for when the comment is changed, - // which will require a poll thread, which isn't ideal. - - // Unfortunately, this class is never used. - - return !changes.isEmpty() && commitMessage != null && !commitMessage.isEmpty(); - } - - @Override - public void execute(Collection changes, String commitMessage) { - List changeList; - if (changes instanceof List) { - changeList = (List) changes; - } else { - changeList = new ArrayList<>(changes); - } - List problems = CommitUtil.commit(project, - changeList, commitMessage, model.getJobs(), model.getJobStatus()); - if (!problems.isEmpty()) { - StringBuilder exMessages = new StringBuilder(); - boolean first = true; - for (VcsException problem : problems) { - LOG.warn(problem); - if (first) { - first = false; - } else { - // TODO use bundle for value - exMessages.append("\n"); - } - exMessages.append(String.join(", ", problem.getMessages())); - } - UserMessage.showNotification(project, UserMessage.ERROR, - exMessages.toString(), - P4Bundle.message("error.submit"), - NotificationType.ERROR); - } - } - - @Override - public void executionCanceled() { - UserMessage.showNotification(project, UserMessage.WARNING, - P4Bundle.message("submit.cancelled"), - P4Bundle.message("submit.cancelled.title"), - NotificationType.INFORMATION); - } - - @Override - public String getHelpId() { - return HELP_ID; - } - }; - } - - - - private static class P4OnCheckinPanel implements CheckinChangeListSpecificComponent { - private final CheckinProjectPanel parentPanel; - private final PairConsumer dataConsumer; - private final SubmitModel model; - private final SubmitPanel panel; - private final JComponent root; - - - P4OnCheckinPanel(@NotNull Project project, @NotNull CheckinProjectPanel panel, - final PairConsumer additionalDataConsumer) { - this.parentPanel = panel; - this.dataConsumer = additionalDataConsumer; - this.model = new SubmitModel(project); - this.panel = new SubmitPanel(model); - this.root = new WrapperPanel(this.panel.getRoot(), false, false); - model.setSelectedCurrentChanges(panel.getSelectedChanges()); - } - - @Override - public void onChangeListSelected(LocalChangeList list) { - model.setSelectedCurrentChanges(list.getChanges()); - } - - @Override - public JComponent getComponent() { - return root; - } - - @Override - public void refresh() { - restoreState(); - } - - @Override - public void saveState() { - // load from context into the data consumer - model.saveState(dataConsumer); - } - - @Override - public void restoreState() { - model.resetState(); - } - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.extension; + +import com.intellij.notification.NotificationType; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.CheckinProjectPanel; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeList; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.CommitExecutor; +import com.intellij.openapi.vcs.changes.CommitSession; +import com.intellij.openapi.vcs.changes.LocalChangeList; +import com.intellij.openapi.vcs.checkin.CheckinChangeListSpecificComponent; +import com.intellij.openapi.vcs.checkin.CheckinEnvironment; +import com.intellij.openapi.vcs.ui.RefreshableOnComponent; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.NullableFunction; +import com.intellij.util.PairConsumer; +import com.intellij.vcsUtil.VcsUtil; +import net.groboclown.p4.server.api.ClientConfigRoot; +import net.groboclown.p4.server.api.ClientServerRef; +import net.groboclown.p4.server.api.P4CommandRunner; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import net.groboclown.p4.server.api.commands.file.AddEditAction; +import net.groboclown.p4.server.api.commands.file.AddEditResult; +import net.groboclown.p4.server.api.commands.file.DeleteFileAction; +import net.groboclown.p4.server.api.commands.file.DeleteFileResult; +import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; +import net.groboclown.p4.server.api.values.P4ChangelistId; +import net.groboclown.p4plugin.P4Bundle; +import net.groboclown.p4plugin.components.P4ServerComponent; +import net.groboclown.p4plugin.components.UserProjectPreferences; +import net.groboclown.p4plugin.messages.UserMessage; +import net.groboclown.p4plugin.ui.WrapperPanel; +import net.groboclown.p4plugin.ui.submit.SubmitModel; +import net.groboclown.p4plugin.ui.submit.SubmitPanel; +import net.groboclown.p4plugin.util.ChangelistUtil; +import net.groboclown.p4plugin.util.CommitUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static net.groboclown.p4.server.api.values.P4FileType.getFileType; + +public class P4CheckinEnvironment implements CheckinEnvironment, CommitExecutor { + private static final Logger LOG = Logger.getInstance(P4CheckinEnvironment.class); + private static final String HELP_ID = null; + + private final Project project; + + P4CheckinEnvironment(@NotNull P4Vcs vcs) { + this.project = vcs.getProject(); + } + + @Nullable + //@Override + @Deprecated + public RefreshableOnComponent createAdditionalOptionsPanel(CheckinProjectPanel panel, PairConsumer additionalDataConsumer) { + // #52 - we could be able to monitor panel.getCommitMessage(); to ensure + // that there's a message, and when there isn't, disable the submit + // button. + // We can monitor the message (with a thread, disposing of it is + // a bit of a bother, but it's doable). However, disabling the button + // currently requires: + // 1. Grabbing the owning dialog object of the panel. + // 2. Using reflection to make the protected "disableOKButton" method + // accessible and callable. + // All of this means that this isn't a very good idea. + // The panel has a "setWarning" method, but that doesn't do anything. + + return new P4OnCheckinPanel(project, panel, additionalDataConsumer); + } + + @Nullable + @Override + public String getDefaultMessageFor(FilePath[] filesToCheckin) { + return null; + } + + @Nullable + @Override + public String getHelpId() { + return HELP_ID; + } + + @Override + public String getCheckinOperationName() { + return P4Bundle.message("commit.operation.name"); + } + + @Nullable + //@Override + @Deprecated + public List commit(List changes, String preparedComment) { + return commit(changes, preparedComment, (p) -> null, new HashSet<>()); + } + + @Nullable + @Override + public List commit(List changes, final String preparedComment, + @NotNull NullableFunction parametersHolder, Set feedback) { + return CommitUtil.commit(project, changes, preparedComment, SubmitModel.getJobs(parametersHolder), + SubmitModel.getSubmitStatus(parametersHolder)); + } + + @Nullable + @Override + public List scheduleMissingFileForDeletion(List files) { + if (LOG.isDebugEnabled()) { + LOG.debug("scheduleMissingFileForDeletion: " + files); + } + List ret = new ArrayList<>(); + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null) { + ret.add(new VcsException("Project not configured")); + return ret; + } + Map activeChangelistIds = getActiveChangelistIds(); + for (FilePath file : files) { + ClientConfigRoot root = registry.getClientFor(file); + if (root == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Skipped adding file not in VCS root: " + file); + } + } else { + P4ChangelistId id = getActiveChangelistFor(root, activeChangelistIds); + if (LOG.isDebugEnabled()) { + LOG.debug("Opening for add/edit: " + file + " (@" + id + ")"); + } + P4CommandRunner.ActionAnswer answer = + P4ServerComponent + .perform(project, root.getClientConfig(), new DeleteFileAction(file, id)) + .whenCompleted((res) -> ChangeListManager.getInstance(project).scheduleUpdate()); + if (ApplicationManager.getApplication().isDispatchThread()) { + LOG.info("Running delete file command in EDT; will not wait for server errors."); + } else { + try { + answer.blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), + TimeUnit.MILLISECONDS); + } catch (P4CommandRunner.ServerResultException e) { + ret.add(e); + } catch (InterruptedException e) { + ret.add(new VcsInterruptedException(e)); + } + } + } + } + return ret; + } + + @Nullable + @Override + public List scheduleUnversionedFilesForAddition(List files) { + if (LOG.isDebugEnabled()) { + LOG.info("scheduleUnversionedFilesForAddition: " + files); + } + List ret = new ArrayList<>(); + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null) { + ret.add(new VcsException("Project not configured")); + return ret; + } + Map activeChangelistIds = getActiveChangelistIds(); + for (VirtualFile file : files) { + ClientConfigRoot root = registry.getClientFor(file); + if (root == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Skipped adding file not in VCS root: " + file); + } + } else { + FilePath fp = VcsUtil.getFilePath(file); + P4ChangelistId id = getActiveChangelistFor(root, activeChangelistIds); + if (LOG.isDebugEnabled()) { + LOG.debug("Opening for add/edit: " + fp + " (@" + id + ")"); + } + P4CommandRunner.ActionAnswer answer = + P4ServerComponent + .perform(project, root.getClientConfig(), new AddEditAction(fp, getFileType(fp), id, (String) null)) + .whenCompleted((res) -> ChangeListManager.getInstance(project).scheduleUpdate()) + ; + if (ApplicationManager.getApplication().isDispatchThread()) { + LOG.info("Running add/edit command in EDT; will not wait for server errors."); + } else { + try { + answer.blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), + TimeUnit.MILLISECONDS); + } catch (P4CommandRunner.ServerResultException e) { + // TODO better exception wrapper? + ret.add(e); + } catch (InterruptedException e) { + ret.add(new VcsInterruptedException(e)); + } + } + } + } + return ret; + } + + @Deprecated + //@Override + public boolean keepChangeListAfterCommit(ChangeList changeList) { + return false; + } + + @Override + public boolean isRefreshAfterCommitNeeded() { + // File status (read-only state) may have changed, or CVS-style substitution + // may have happened. + return true; + } + + private Map getActiveChangelistIds() { + return ChangelistUtil.getActiveChangelistIds(project); + } + + @NotNull + private P4ChangelistId getActiveChangelistFor(ClientConfigRoot root, Map ids) { + return ChangelistUtil.getActiveChangelistFor(root, ids); + } + + @Nls + @Override + public String getActionText() { + return P4Bundle.getString("commit.operation.name"); + } + + @NotNull + //@Override + @Deprecated + public CommitSession createCommitSession() { + final SubmitModel model = new SubmitModel(project); + return new CommitSession() { + // Only here for API compatibility + @Deprecated + @Nullable + //@Override + public JComponent getAdditionalConfigurationUI() { + return getAdditionalConfigurationUI(Collections.emptyList(), ""); + } + + @Nullable + @Override + public JComponent getAdditionalConfigurationUI(Collection changes, String commitMessage) { + model.setSelectedCurrentChanges(changes); + return new SubmitPanel(model).getRoot(); + } + + @Override + public boolean canExecute(Collection changes, String commitMessage) { + // Set the ok action as enabled/disabled depending upon + // whether the comment is empty or not (bug #52). Probably + // should just set a warning, though, but that doesn't do anything. + // We will also need a notification for when the comment is changed, + // which will require a poll thread, which isn't ideal. + + // Unfortunately, this class is never used. + + return !changes.isEmpty() && commitMessage != null && !commitMessage.isEmpty(); + } + + @Override + public void execute(Collection changes, String commitMessage) { + List changeList; + if (changes instanceof List) { + changeList = (List) changes; + } else { + changeList = new ArrayList<>(changes); + } + List problems = CommitUtil.commit(project, + changeList, commitMessage, model.getJobs(), model.getJobStatus()); + if (!problems.isEmpty()) { + StringBuilder exMessages = new StringBuilder(); + boolean first = true; + for (VcsException problem : problems) { + LOG.warn(problem); + if (first) { + first = false; + } else { + // TODO use bundle for value + exMessages.append("\n"); + } + exMessages.append(String.join(", ", problem.getMessages())); + } + UserMessage.showNotification(project, UserMessage.ERROR, + exMessages.toString(), + P4Bundle.message("error.submit"), + NotificationType.ERROR); + } + } + + @Override + public void executionCanceled() { + UserMessage.showNotification(project, UserMessage.WARNING, + P4Bundle.message("submit.cancelled"), + P4Bundle.message("submit.cancelled.title"), + NotificationType.INFORMATION); + } + + @Override + public String getHelpId() { + return HELP_ID; + } + }; + } + + + + private static class P4OnCheckinPanel implements CheckinChangeListSpecificComponent { + private final CheckinProjectPanel parentPanel; + private final PairConsumer dataConsumer; + private final SubmitModel model; + private final SubmitPanel panel; + private final JComponent root; + + + P4OnCheckinPanel(@NotNull Project project, @NotNull CheckinProjectPanel panel, + final PairConsumer additionalDataConsumer) { + this.parentPanel = panel; + this.dataConsumer = additionalDataConsumer; + this.model = new SubmitModel(project); + this.panel = new SubmitPanel(model); + this.root = new WrapperPanel(this.panel.getRoot(), false, false); + model.setSelectedCurrentChanges(panel.getSelectedChanges()); + } + + @Override + public void onChangeListSelected(LocalChangeList list) { + model.setSelectedCurrentChanges(list.getChanges()); + } + + @Override + public JComponent getComponent() { + return root; + } + + @Override + public void refresh() { + restoreState(); + } + + @Override + public void saveState() { + // load from context into the data consumer + model.saveState(dataConsumer); + } + + @Override + public void restoreState() { + model.resetState(); + } + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4CommittedChangesProvider.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4CommittedChangesProvider.java index 41ea3ec8..56ab03a9 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4CommittedChangesProvider.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4CommittedChangesProvider.java @@ -1,728 +1,728 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.extension; - -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vcs.CachingCommittedChangesProvider; -import com.intellij.openapi.vcs.ChangeListColumn; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.RepositoryLocation; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.changes.Change; -import com.intellij.openapi.vcs.changes.ContentRevision; -import com.intellij.openapi.vcs.changes.committed.DecoratorManager; -import com.intellij.openapi.vcs.changes.committed.RepositoryLocationGroup; -import com.intellij.openapi.vcs.changes.committed.VcsCommittedListsZipper; -import com.intellij.openapi.vcs.changes.committed.VcsCommittedListsZipperAdapter; -import com.intellij.openapi.vcs.changes.committed.VcsCommittedViewAuxiliary; -import com.intellij.openapi.vcs.history.VcsRevisionNumber; -import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; -import com.intellij.openapi.vcs.versionBrowser.ChangesBrowserSettingsEditor; -import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; -import com.intellij.openapi.vcs.versionBrowser.StandardVersionFilterComponent; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.AsynchConsumer; -import com.intellij.vcsUtil.VcsUtil; -import net.groboclown.p4.server.api.ClientConfigRoot; -import net.groboclown.p4.server.api.ClientServerRef; -import net.groboclown.p4.server.api.P4CommandRunner; -import net.groboclown.p4.server.api.P4ServerName; -import net.groboclown.p4.server.api.ProjectConfigRegistry; -import net.groboclown.p4.server.api.commands.HistoryContentLoader; -import net.groboclown.p4.server.api.commands.changelist.ListSubmittedChangelistsQuery; -import net.groboclown.p4.server.api.commands.file.ListFilesDetailsQuery; -import net.groboclown.p4.server.api.commands.file.ListFilesDetailsResult; -import net.groboclown.p4.server.api.commands.sync.SyncListFilesDetailsQuery; -import net.groboclown.p4.server.api.config.ClientConfig; -import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; -import net.groboclown.p4.server.api.messagebus.ErrorEvent; -import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; -import net.groboclown.p4.server.api.repository.P4RepositoryLocation; -import net.groboclown.p4.server.api.values.P4ChangelistId; -import net.groboclown.p4.server.api.values.P4ChangelistSummary; -import net.groboclown.p4.server.api.values.P4CommittedChangelist; -import net.groboclown.p4.server.api.values.P4RemoteFile; -import net.groboclown.p4.server.impl.commands.DoneQueryAnswer; -import net.groboclown.p4.server.impl.repository.RepositoryLocationFactory; -import net.groboclown.p4.server.impl.values.P4ChangelistIdImpl; -import net.groboclown.p4.server.impl.values.P4ChangelistSummaryImpl; -import net.groboclown.p4.server.impl.values.P4CommittedChangelistImpl; -import net.groboclown.p4.server.impl.values.P4RemoteFileImpl; -import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.actions.ChangelistDescriptionAction; -import net.groboclown.p4plugin.components.P4ServerComponent; -import net.groboclown.p4plugin.components.UserProjectPreferences; -import net.groboclown.p4plugin.extension.P4CommittedChangesProvider.P4ChangeBrowserSettings; -import net.groboclown.p4plugin.revision.P4RemoteFileContentRevision; -import net.groboclown.p4plugin.util.HistoryContentLoaderImpl; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.concurrent.TimeUnit; - -// CachingCommittedChangesProvider is required to use the Repository built-in tab, and that built-in tab -// always appears when the VCS is marked as centralized (as opposed to distributed). -public class P4CommittedChangesProvider implements - CachingCommittedChangesProvider { - private static final Logger LOG = Logger.getInstance(P4CommittedChangesProvider.class); - - private final Project project; - private final HistoryContentLoader loader; - - P4CommittedChangesProvider(@NotNull final P4Vcs vcs) { - this.project = vcs.getProject(); - this.loader = new HistoryContentLoaderImpl(vcs.getProject()); - } - - - @NotNull - @Override - public P4ChangeBrowserSettings createDefaultSettings() { - LOG.debug("Creating default settings"); - return new P4ChangeBrowserSettings(); - } - - @Override - public ChangesBrowserSettingsEditor createFilterUI(boolean showDateFilter) { - LOG.debug("Creating filter UI"); - return new StandardVersionFilterComponent() { - @Override - public JComponent getComponent() { - return (JComponent) getStandardPanel(); - } - }; - } - - @Nullable - @Override - public RepositoryLocation getLocationFor(@Nullable FilePath root) { - if (LOG.isDebugEnabled()) { - LOG.debug("Finding location for " + root); - } - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null || root == null) { - LOG.debug("No registry, or root is null"); - return null; - } - ClientConfigRoot client = registry.getClientFor(root); - if (client == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("No client for root " + root); - } - return null; - } - ListFilesDetailsResult details; - if (ApplicationManager.getApplication().isDispatchThread()) { - // Use the cache - LOG.debug("Loading file location from the cache due to execution within the dispatch thread"); - details = P4ServerComponent - .syncCachedQuery(project, client.getClientConfig(), - new SyncListFilesDetailsQuery(root)); - } else { - try { - if (LOG.isDebugEnabled()) { - LOG.debug("Loading file location from the server for " + root); - } - details = P4ServerComponent - .query(project, client.getClientConfig(), - new ListFilesDetailsQuery( - Collections.singletonList(root), ListFilesDetailsQuery.RevState.HAVE, 1)) - .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>(new VcsInterruptedException( - "Encountered error looking for files under " + root, e))); - return null; - } catch (P4CommandRunner.ServerResultException e) { - LOG.warn("Encountered error looking for files under " + root, e); - return null; - } - } - return RepositoryLocationFactory.getLocationFor(root, client, details); - } - - @Deprecated - @Nullable - //@Override - public RepositoryLocation getLocationFor(FilePath root, String repositoryPath) { - // TODO should this use repository path? - if (LOG.isDebugEnabled()) { - LOG.debug("Finding location for root [" + root + "], path [" + repositoryPath + "]"); - } - return getLocationFor(root); - } - - @Nullable - @Override - public VcsCommittedListsZipper getZipper() { - LOG.debug("Creating the zipper"); - return new VcsCommittedListsZipperAdapter(new VcsCommittedListsZipperAdapter.GroupCreator() { - @Override - public Object createKey(RepositoryLocation repositoryLocation) { - return repositoryLocation; - } - - @Override - public RepositoryLocationGroup createGroup(Object o, Collection collection) { - return new RepositoryLocationGroup(o.toString()); - } - }){}; - } - - /** - * Called by IDE in a Worker Thread. - * - * @param settings settings - * @param location location - * @param maxCount count - * @return list of changes, which MUST be modifiable. - * @throws VcsException if there was a problem on the server. - */ - @Override - public List getCommittedChanges(P4ChangeBrowserSettings settings, - RepositoryLocation location, int maxCount) throws VcsException { - if (LOG.isDebugEnabled()) { - LOG.debug("Finding committed changes for location " + location); - } - try { - List changes = asyncLoadCommittedChanges(settings, location, maxCount) - .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS); - // MUST return a modifiable list - return new ArrayList<>(changes); - } catch (InterruptedException e) { - throw new VcsInterruptedException(e); - } catch (CancellationException e) { - throw new VcsInterruptedException(e); - } - } - - // v171 - v183 has ... AsynchConsumer - // while v191+ has ... AsynchConsumer - // The "fix" is to strip off the typing conflict. - @Override - public void loadCommittedChanges(P4ChangeBrowserSettings settings, RepositoryLocation location, int maxCount, - AsynchConsumer consumer) throws VcsException { - if (LOG.isDebugEnabled()) { - LOG.debug("Loading committed changes for location " + location); - } - if (consumer == null) { - LOG.debug("Attempted to load committed changes with null consumer"); - return; - } - asyncLoadCommittedChanges(settings, location, maxCount) - .whenCompleted((c) -> c.forEach(consumer::consume)) - .whenAnyState(consumer::finished) - .whenServerError((e) -> LOG.warn("Problem loading committed changes", e)); - } - - @NotNull - private P4CommandRunner.QueryAnswer> asyncLoadCommittedChanges( - P4ChangeBrowserSettings settings, RepositoryLocation location, int maxCount) { - if (LOG.isDebugEnabled()) { - LOG.debug("Loading committed changes for " + location); - } - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (location == null || registry == null) { - return new DoneQueryAnswer<>(Collections.emptyList()); - } - if (location instanceof P4RepositoryLocation) { - P4RepositoryLocation repo = (P4RepositoryLocation) location; - ClientConfig clientConfig = registry.getRegisteredClientConfigState(repo.getClientServerRef()); - if (clientConfig == null) { - LOG.warn("Could not find configuration for " + repo.getClientServerRef()); - return new DoneQueryAnswer<>(Collections.emptyList()); - } - - P4Vcs vcs = P4Vcs.getInstance(project); - return P4ServerComponent - .query(project, clientConfig, - new ListSubmittedChangelistsQuery(repo, settings.getQueryFilter(), maxCount)) - .mapQuery((c) -> c.getChangesForVcs(vcs)); - } - LOG.warn("Cannot load changes for non-perforce repository location " + location); - return new DoneQueryAnswer<>(Collections.emptyList()); - } - - @Override - public ChangeListColumn[] getColumns() { - LOG.debug("Getting columns"); - return new ChangeListColumn[] { - // Need to support revision or changelist view... See #180 - // ChangeListColumn.NUMBER, - new RevisionColumn(project), - - ChangeListColumn.NAME, - ChangeListColumn.DESCRIPTION, - ChangeListColumn.DATE, - HAS_SHELVED, - }; - } - - @Nullable - @Override - public VcsCommittedViewAuxiliary createActions(DecoratorManager manager, RepositoryLocation location) { - LOG.debug("Creating actions"); - List allActions = - Collections.singletonList(new ChangelistDescriptionAction()); - return new VcsCommittedViewAuxiliary( - allActions, - () -> { - // on dispose - do nothing - LOG.debug("Disposing view"); - }, - allActions - ); - } - - /** - * since may be different for different VCSs - */ - @Override - public int getUnlimitedCountValue() { - return 0; - } - - /** - * Called by IDE in a Worker Thread. - * - * @param file file - * @param number revision - * @return required list and path of the target file in that revision (changes when move/rename) - */ - @Nullable - @Override - public Pair getOneList(VirtualFile file, VcsRevisionNumber number) { - if (LOG.isDebugEnabled()) { - LOG.debug("Getting one list for " + file + " " + number); - } - FilePath fp = VcsUtil.getFilePath(file); - if (fp == null) { - return null; - } - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null) { - return Pair.create(null, fp); - } - ClientConfigRoot clientConfig = registry.getClientFor(file); - if (clientConfig == null) { - return Pair.create(null, fp); - } - - String revision; - if (number != null) { - revision = number.asString(); - - if (revision == null || revision.isEmpty()) { - revision = "#head"; - } else if (!(revision.charAt(0) == '@' || revision.charAt(0) == '#')) { - revision = '#' + revision; - } - } else { - revision = "#head"; - } - - if (LOG.isDebugEnabled()) { - LOG.debug("Getting changelist for " + file + " " + revision); - } - - // FIXME pull the revision from the server. This is in a worker thread, so do a blocking wait. - LOG.warn("FIXME pull the revision from the server. This is in a worker thread, so do a blocking wait."); - return Pair.create(null, fp); - } - - @Override - public RepositoryLocation getForNonLocal(VirtualFile file) { - return getLocationFor(VcsUtil.getFilePath(file)); - } - - /** - * Return true if this committed changes provider can be used to show the incoming changes. - * If false is returned, the "Incoming" tab won't be shown in the Changes toolwindow. - */ - @Override - public boolean supportsIncomingChanges() { - return false; - } - - // -------------------------------------------------------------------------------------- - // Caching support API - // The plugin has its own caching mechanism, but this is explicitly for the server-side - // commits, which the plugin shouldn't maintain by itself. - - /** - * Returns the current version of the binary data format that is read/written by the caching provider. - * If the format version loaded from the cache stream does not match the format version returned by - * the provider, the cache stream is discarded and changes are reloaded from server. - * - * @return binary format version. - */ - @Override - public int getFormatVersion() { - return 0; - } - - @Override - public void writeChangeList(DataOutput dataOutput, P4CommittedChangelist p4CommittedChangelist) - throws IOException { - // 1. Commit Date - long - Date commitDate = p4CommittedChangelist.getCommitDate(); - dataOutput.writeLong(commitDate.getTime()); - - // 2. Change Collection size - int - Collection changes = p4CommittedChangelist.getChanges(); - dataOutput.writeInt(changes.size()); - for (Change change : changes) { - // 3.a. before revision - writeContentRevision(change.getBeforeRevision(), dataOutput); - - // 3.b. after revision - writeContentRevision(change.getAfterRevision(), dataOutput); - } - - // 4. Summary - P4ChangelistSummary summary = p4CommittedChangelist.getSummary(); - // 4.a. id - P4ChangelistId changeId = summary.getChangelistId(); - // 4.a.i. client server ref - ClientServerRef ref = changeId.getClientServerRef(); - // 4.a.i.A. server name full port - utf - dataOutput.writeUTF(ref.getServerName().getFullPort()); - // 4.a.i.B. client name - if (ref.getClientName() == null) { - // 4.a.i.B.i has client name - int - dataOutput.writeInt(0); - } else { - // 4.a.i.B.i has client name - int - dataOutput.writeInt(1); - // 4.a.i.B.ii client name - utf - dataOutput.writeUTF(ref.getClientName()); - } - // 4.a.ii. changelist ID - int - dataOutput.writeInt(changeId.getChangelistId()); - - // 4.b. Comment - utf - dataOutput.writeUTF(summary.getComment()); - // 4.c. username - utf - dataOutput.writeUTF(summary.getUsername()); - // 4.d. submitted - int - dataOutput.writeInt(summary.isSubmitted() ? 1 : 0); - // 4.e. has shelved - int - dataOutput.writeInt(summary.hasShelvedFiles() ? 1 : 0); - } - - @Override - public P4CommittedChangelist readChangeList(RepositoryLocation repositoryLocation, DataInput dataInput) - throws IOException { - // 1. Commit Date - long - Date commitDate = new Date(dataInput.readLong()); - - // 2. Change Collection size - int - int changeCount = dataInput.readInt(); - List changes = new ArrayList<>(changeCount); - for (int i = 0; i < changeCount; i++) { - // 3.a. before revision - P4RemoteFileContentRevision beforeRevision = readContentRevision(dataInput); - - // 3.b. after revision - P4RemoteFileContentRevision afterRevision = readContentRevision(dataInput); - - changes.add(new Change(beforeRevision, afterRevision)); - } - - // 4. Summary - - // 4. Summary - // 4.a. id - // 4.a.i. client server ref - // 4.a.i.A. server name full port - utf - P4ServerName serverName = P4ServerName.forPort(dataInput.readUTF()); - assert serverName != null; - // 4.a.i.B. client name - String clientName; - // 4.a.i.B.i has client name - int - int hasClientName = dataInput.readInt(); - if (hasClientName == 0) { - clientName = null; - } else { - // 4.a.i.B.ii client name - utf - clientName = dataInput.readUTF(); - } - ClientServerRef ref = new ClientServerRef(serverName, clientName); - // 4.a.ii. changelist ID - int - int changelistId = dataInput.readInt(); - P4ChangelistId changeId = new P4ChangelistIdImpl(changelistId, ref); - - // 4.b. Comment - utf - String comment = dataInput.readUTF(); - // 4.c. username - utf - String username = dataInput.readUTF(); - // 4.d. submitted - int - int hasSubmitted = dataInput.readInt(); - // 4.e. has shelved - int - int hasShelvedFiles = dataInput.readInt(); - P4ChangelistSummaryImpl summary = new P4ChangelistSummaryImpl( - changeId, comment, username, hasSubmitted != 0, hasShelvedFiles != 0 - ); - - return new P4CommittedChangelistImpl(summary, changes, commitDate); - } - - private void writeContentRevision(ContentRevision revision, DataOutput dataOutput) - throws IOException { - if (revision instanceof P4RemoteFileContentRevision) { - P4RemoteFileContentRevision rev = (P4RemoteFileContentRevision) revision; - - // 1. Is "revision" set - int - dataOutput.writeInt(1); - - // 2. depot file - // 2.a. path - utf - dataOutput.writeUTF(rev.getDepotPath().getDepotPath()); - // 2.b. display name - utf - dataOutput.writeUTF(rev.getDepotPath().getDisplayName()); - String depotLocalPath = rev.getDepotPath().getLocalPath().orElse(null); - if (depotLocalPath == null) { - // 2.c. is local path non-null - int - dataOutput.writeInt(0); - } else { - // 2.c. is local path non-null - int - dataOutput.writeInt(1); - // 2.c.i. local path - utf - dataOutput.writeUTF(depotLocalPath); - } - - // 3. file path - FilePath filePath = rev.getFile(); - if (filePath.isNonLocal()) { - // 3.a. is file path used - int - dataOutput.writeInt(0); - } else { - // 3.a. is file path used - int - dataOutput.writeInt(1); - // 3.a.i. file path - utf - dataOutput.writeUTF(filePath.getPath()); - } - - // 4. Revision - int - dataOutput.writeInt(rev.getIntRevisionNumber().getValue()); - - // 5. Charset - utf - dataOutput.writeUTF(rev.getCharset().name()); - } else { - // 1. Is "revision" set - int - dataOutput.writeInt(0); - } - } - - private P4RemoteFileContentRevision readContentRevision(DataInput dataInput) - throws IOException { - // 1. Is "revision" set - int - int isSet = dataInput.readInt(); - if (isSet == 1) { - // 2. depot file - // 2.a. path - utf - String depotPath = dataInput.readUTF(); - // 2.b. display name - utf - String depotDisplayName = dataInput.readUTF(); - // 2.c. is local path non-null - int - String localPath; - int hasLocalPath = dataInput.readInt(); - if (hasLocalPath == 0) { - localPath = null; - } else { - // 2.c.i. local path - utf - localPath = dataInput.readUTF(); - } - P4RemoteFile file = new P4RemoteFileImpl(depotPath, depotDisplayName, localPath); - - // 3. file path - FilePath path; - // 3.a. is file path used - int - int hasFilePath = dataInput.readInt(); - if (hasFilePath == 0) { - path = null; - } else { - // 3.a.i. file path - utf - path = VcsUtil.getFilePath(dataInput.readUTF()); - } - - // 4. Revision - int - VcsRevisionNumber.Int rev = new VcsRevisionNumber.Int(dataInput.readInt()); - - // 5. Charset - utf - Charset charset = Charset.forName(dataInput.readUTF()); - - return P4RemoteFileContentRevision.delayCreation(project, file, path, rev, loader, charset); - } else { - return null; - } - } - - @Override - public boolean isMaxCountSupported() { - return true; - } - - @Nullable - @Override - public Collection getIncomingFiles(RepositoryLocation repositoryLocation) - throws VcsException { - // No real concept of incoming files. - return null; - } - - @Override - public boolean refreshCacheByNumber() { - // use numbers, not dates - return true; - } - - /** - * Returns the name of the "changelist" concept in the specified VCS (changelist, revision etc.) - * - * @return the name of the concept, or null if the VCS (like CVS) does not use changelist numbering. - */ - @Nls - @Nullable - @Override - public String getChangelistTitle() { - return P4Bundle.getString("changelist.title"); - } - - @Override - public boolean isChangeLocallyAvailable(FilePath filePath, @Nullable VcsRevisionNumber localRevision, - VcsRevisionNumber changeRevision, P4CommittedChangelist changelist) { - // All committed revisions are remote. - return false; - } - - /** - * Returns true if a timer-based refresh of committed changes should be followed by refresh of incoming changes, so that, - * for example, changes from the wrong branch would be automatically removed from view. - * - * @return true if auto-refresh includes incoming changes refresh, false otherwise - */ - @Override - public boolean refreshIncomingWithCommitted() { - return false; - } - - - public static class P4ChangeBrowserSettings extends ChangeBrowserSettings { - String SHOW_ONLY_SHELVED_FILTER = "false"; - - // TODO add shelved filter option to filter editor - public void setShowOnlyShelvedFilter(@Nullable String showFilter) { - SHOW_ONLY_SHELVED_FILTER = showFilter == null ? "false" : - Boolean.valueOf(Boolean.parseBoolean(showFilter)).toString(); - } - - boolean isShowOnlyShelvedFilter() { - return Boolean.parseBoolean(SHOW_ONLY_SHELVED_FILTER); - } - - ListSubmittedChangelistsQuery.Filter getQueryFilter() { - ListSubmittedChangelistsQuery.Filter ret = new ListSubmittedChangelistsQuery.Filter(); - ret.setOnlyChangesWithShelvedFilesFilter(isShowOnlyShelvedFilter()); - ret.setChangesAfterChangelistFilter(getChangeAfterFilter()); - ret.setChangesBeforeChangelistFilter(getChangeBeforeFilter()); - ret.setChangesAfterDateFilter(getDateAfterFilter()); - ret.setChangesBeforeDateFilter(getDateBeforeFilter()); - return ret; - } - - @NotNull - @Override - protected List createFilters() { - final List ret = super.createFilters(); - - if (isShowOnlyShelvedFilter()) { - // FIXME - throw new IllegalStateException("not implemented"); - /* - ret.add(new Filter() { - @Override - public boolean accepts(final CommittedChangeList change) { - if (change != null && change instanceof P4CommittedChangeList) { - P4CommittedChangeList p4cl = (P4CommittedChangeList) change; - return p4cl.hasShelved(); - } - return true; - } - }); - */ - } - - return ret; - } - } - - private static class RevisionColumn extends ChangeListColumn { - private final Project project; - - private RevisionColumn(Project project) { - this.project = project; - } - - @Override - public String getTitle() { - return NUMBER.getTitle(); - } - - @Override - public Object getValue(P4CommittedChangelist p4CommittedChangelist) { - if (UserProjectPreferences.getPreferRevisionsForFiles(project)) { - return p4CommittedChangelist.getNumber(); - } - return p4CommittedChangelist.getSummary().getChangelistId().getChangelistId(); - } - - @NotNull - public Comparator getComparator() { - return Comparator.comparing(CommittedChangeList::getNumber); - } - } - - private static final ChangeListColumn HAS_SHELVED = - new ChangeListColumn() { - @Override - public String getTitle() { - return P4Bundle.message("changelist.shelved"); - } - - @Override - public Object getValue(final P4CommittedChangelist changeList) { - // FIXME implement correctly - // committed changelists can't have shelved files... so this is probably the wrong object. - LOG.warn("FIXME implement HAS_SHELVED getValue correctly"); - return false; - } - }; -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.extension; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.CachingCommittedChangesProvider; +import com.intellij.openapi.vcs.ChangeListColumn; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.RepositoryLocation; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.changes.committed.DecoratorManager; +import com.intellij.openapi.vcs.changes.committed.RepositoryLocationGroup; +import com.intellij.openapi.vcs.changes.committed.VcsCommittedListsZipper; +import com.intellij.openapi.vcs.changes.committed.VcsCommittedListsZipperAdapter; +import com.intellij.openapi.vcs.changes.committed.VcsCommittedViewAuxiliary; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; +import com.intellij.openapi.vcs.versionBrowser.ChangesBrowserSettingsEditor; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vcs.versionBrowser.StandardVersionFilterComponent; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.AsynchConsumer; +import com.intellij.vcsUtil.VcsUtil; +import net.groboclown.p4.server.api.ClientConfigRoot; +import net.groboclown.p4.server.api.ClientServerRef; +import net.groboclown.p4.server.api.P4CommandRunner; +import net.groboclown.p4.server.api.P4ServerName; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import net.groboclown.p4.server.api.commands.HistoryContentLoader; +import net.groboclown.p4.server.api.commands.changelist.ListSubmittedChangelistsQuery; +import net.groboclown.p4.server.api.commands.file.ListFilesDetailsQuery; +import net.groboclown.p4.server.api.commands.file.ListFilesDetailsResult; +import net.groboclown.p4.server.api.commands.sync.SyncListFilesDetailsQuery; +import net.groboclown.p4.server.api.config.ClientConfig; +import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; +import net.groboclown.p4.server.api.messagebus.ErrorEvent; +import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; +import net.groboclown.p4.server.api.repository.P4RepositoryLocation; +import net.groboclown.p4.server.api.values.P4ChangelistId; +import net.groboclown.p4.server.api.values.P4ChangelistSummary; +import net.groboclown.p4.server.api.values.P4CommittedChangelist; +import net.groboclown.p4.server.api.values.P4RemoteFile; +import net.groboclown.p4.server.impl.commands.DoneQueryAnswer; +import net.groboclown.p4.server.impl.repository.RepositoryLocationFactory; +import net.groboclown.p4.server.impl.values.P4ChangelistIdImpl; +import net.groboclown.p4.server.impl.values.P4ChangelistSummaryImpl; +import net.groboclown.p4.server.impl.values.P4CommittedChangelistImpl; +import net.groboclown.p4.server.impl.values.P4RemoteFileImpl; +import net.groboclown.p4plugin.P4Bundle; +import net.groboclown.p4plugin.actions.ChangelistDescriptionAction; +import net.groboclown.p4plugin.components.P4ServerComponent; +import net.groboclown.p4plugin.components.UserProjectPreferences; +import net.groboclown.p4plugin.extension.P4CommittedChangesProvider.P4ChangeBrowserSettings; +import net.groboclown.p4plugin.revision.P4RemoteFileContentRevision; +import net.groboclown.p4plugin.util.HistoryContentLoaderImpl; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.TimeUnit; + +// CachingCommittedChangesProvider is required to use the Repository built-in tab, and that built-in tab +// always appears when the VCS is marked as centralized (as opposed to distributed). +public class P4CommittedChangesProvider implements + CachingCommittedChangesProvider { + private static final Logger LOG = Logger.getInstance(P4CommittedChangesProvider.class); + + private final Project project; + private final HistoryContentLoader loader; + + P4CommittedChangesProvider(@NotNull final P4Vcs vcs) { + this.project = vcs.getProject(); + this.loader = new HistoryContentLoaderImpl(vcs.getProject()); + } + + + @NotNull + @Override + public P4ChangeBrowserSettings createDefaultSettings() { + LOG.debug("Creating default settings"); + return new P4ChangeBrowserSettings(); + } + + @Override + public ChangesBrowserSettingsEditor createFilterUI(boolean showDateFilter) { + LOG.debug("Creating filter UI"); + return new StandardVersionFilterComponent() { + @Override + public JComponent getComponent() { + return (JComponent) getStandardPanel(); + } + }; + } + + @Nullable + @Override + public RepositoryLocation getLocationFor(@Nullable FilePath root) { + if (LOG.isDebugEnabled()) { + LOG.debug("Finding location for " + root); + } + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null || root == null) { + LOG.debug("No registry, or root is null"); + return null; + } + ClientConfigRoot client = registry.getClientFor(root); + if (client == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("No client for root " + root); + } + return null; + } + ListFilesDetailsResult details; + if (ApplicationManager.getApplication().isDispatchThread()) { + // Use the cache + LOG.debug("Loading file location from the cache due to execution within the dispatch thread"); + details = P4ServerComponent + .syncCachedQuery(project, client.getClientConfig(), + new SyncListFilesDetailsQuery(root)); + } else { + try { + if (LOG.isDebugEnabled()) { + LOG.debug("Loading file location from the server for " + root); + } + details = P4ServerComponent + .query(project, client.getClientConfig(), + new ListFilesDetailsQuery( + Collections.singletonList(root), ListFilesDetailsQuery.RevState.HAVE, 1)) + .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>(new VcsInterruptedException( + "Encountered error looking for files under " + root, e))); + return null; + } catch (P4CommandRunner.ServerResultException e) { + LOG.warn("Encountered error looking for files under " + root, e); + return null; + } + } + return RepositoryLocationFactory.getLocationFor(root, client, details); + } + + @Deprecated + @Nullable + //@Override + public RepositoryLocation getLocationFor(FilePath root, String repositoryPath) { + // TODO should this use repository path? + if (LOG.isDebugEnabled()) { + LOG.debug("Finding location for root [" + root + "], path [" + repositoryPath + "]"); + } + return getLocationFor(root); + } + + @Nullable + @Override + public VcsCommittedListsZipper getZipper() { + LOG.debug("Creating the zipper"); + return new VcsCommittedListsZipperAdapter(new VcsCommittedListsZipperAdapter.GroupCreator() { + @Override + public Object createKey(RepositoryLocation repositoryLocation) { + return repositoryLocation; + } + + @Override + public RepositoryLocationGroup createGroup(Object o, Collection collection) { + return new RepositoryLocationGroup(o.toString()); + } + }){}; + } + + /** + * Called by IDE in a Worker Thread. + * + * @param settings settings + * @param location location + * @param maxCount count + * @return list of changes, which MUST be modifiable. + * @throws VcsException if there was a problem on the server. + */ + @Override + public List getCommittedChanges(P4ChangeBrowserSettings settings, + RepositoryLocation location, int maxCount) throws VcsException { + if (LOG.isDebugEnabled()) { + LOG.debug("Finding committed changes for location " + location); + } + try { + List changes = asyncLoadCommittedChanges(settings, location, maxCount) + .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS); + // MUST return a modifiable list + return new ArrayList<>(changes); + } catch (InterruptedException e) { + throw new VcsInterruptedException(e); + } catch (CancellationException e) { + throw new VcsInterruptedException(e); + } + } + + // v171 - v183 has ... AsynchConsumer + // while v191+ has ... AsynchConsumer + // The "fix" is to strip off the typing conflict. + @Override + public void loadCommittedChanges(P4ChangeBrowserSettings settings, RepositoryLocation location, int maxCount, + AsynchConsumer consumer) throws VcsException { + if (LOG.isDebugEnabled()) { + LOG.debug("Loading committed changes for location " + location); + } + if (consumer == null) { + LOG.debug("Attempted to load committed changes with null consumer"); + return; + } + asyncLoadCommittedChanges(settings, location, maxCount) + .whenCompleted((c) -> c.forEach(consumer::consume)) + .whenAnyState(consumer::finished) + .whenServerError((e) -> LOG.warn("Problem loading committed changes", e)); + } + + @NotNull + private P4CommandRunner.QueryAnswer> asyncLoadCommittedChanges( + P4ChangeBrowserSettings settings, RepositoryLocation location, int maxCount) { + if (LOG.isDebugEnabled()) { + LOG.debug("Loading committed changes for " + location); + } + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (location == null || registry == null) { + return new DoneQueryAnswer<>(Collections.emptyList()); + } + if (location instanceof P4RepositoryLocation) { + P4RepositoryLocation repo = (P4RepositoryLocation) location; + ClientConfig clientConfig = registry.getRegisteredClientConfigState(repo.getClientServerRef()); + if (clientConfig == null) { + LOG.warn("Could not find configuration for " + repo.getClientServerRef()); + return new DoneQueryAnswer<>(Collections.emptyList()); + } + + P4Vcs vcs = P4Vcs.getInstance(project); + return P4ServerComponent + .query(project, clientConfig, + new ListSubmittedChangelistsQuery(repo, settings.getQueryFilter(), maxCount)) + .mapQuery((c) -> c.getChangesForVcs(vcs)); + } + LOG.warn("Cannot load changes for non-perforce repository location " + location); + return new DoneQueryAnswer<>(Collections.emptyList()); + } + + @Override + public ChangeListColumn[] getColumns() { + LOG.debug("Getting columns"); + return new ChangeListColumn[] { + // Need to support revision or changelist view... See #180 + // ChangeListColumn.NUMBER, + new RevisionColumn(project), + + ChangeListColumn.NAME, + ChangeListColumn.DESCRIPTION, + ChangeListColumn.DATE, + HAS_SHELVED, + }; + } + + @Nullable + @Override + public VcsCommittedViewAuxiliary createActions(DecoratorManager manager, RepositoryLocation location) { + LOG.debug("Creating actions"); + List allActions = + Collections.singletonList(new ChangelistDescriptionAction()); + return new VcsCommittedViewAuxiliary( + allActions, + () -> { + // on dispose - do nothing + LOG.debug("Disposing view"); + }, + allActions + ); + } + + /** + * since may be different for different VCSs + */ + @Override + public int getUnlimitedCountValue() { + return 0; + } + + /** + * Called by IDE in a Worker Thread. + * + * @param file file + * @param number revision + * @return required list and path of the target file in that revision (changes when move/rename) + */ + @Nullable + @Override + public Pair getOneList(VirtualFile file, VcsRevisionNumber number) { + if (LOG.isDebugEnabled()) { + LOG.debug("Getting one list for " + file + " " + number); + } + FilePath fp = VcsUtil.getFilePath(file); + if (fp == null) { + return null; + } + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null) { + return Pair.create(null, fp); + } + ClientConfigRoot clientConfig = registry.getClientFor(file); + if (clientConfig == null) { + return Pair.create(null, fp); + } + + String revision; + if (number != null) { + revision = number.asString(); + + if (revision == null || revision.isEmpty()) { + revision = "#head"; + } else if (!(revision.charAt(0) == '@' || revision.charAt(0) == '#')) { + revision = '#' + revision; + } + } else { + revision = "#head"; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Getting changelist for " + file + " " + revision); + } + + // FIXME pull the revision from the server. This is in a worker thread, so do a blocking wait. + LOG.warn("FIXME pull the revision from the server. This is in a worker thread, so do a blocking wait."); + return Pair.create(null, fp); + } + + @Override + public RepositoryLocation getForNonLocal(VirtualFile file) { + return getLocationFor(VcsUtil.getFilePath(file)); + } + + /** + * Return true if this committed changes provider can be used to show the incoming changes. + * If false is returned, the "Incoming" tab won't be shown in the Changes toolwindow. + */ + @Override + public boolean supportsIncomingChanges() { + return false; + } + + // -------------------------------------------------------------------------------------- + // Caching support API + // The plugin has its own caching mechanism, but this is explicitly for the server-side + // commits, which the plugin shouldn't maintain by itself. + + /** + * Returns the current version of the binary data format that is read/written by the caching provider. + * If the format version loaded from the cache stream does not match the format version returned by + * the provider, the cache stream is discarded and changes are reloaded from server. + * + * @return binary format version. + */ + @Override + public int getFormatVersion() { + return 0; + } + + @Override + public void writeChangeList(DataOutput dataOutput, P4CommittedChangelist p4CommittedChangelist) + throws IOException { + // 1. Commit Date - long + Date commitDate = p4CommittedChangelist.getCommitDate(); + dataOutput.writeLong(commitDate.getTime()); + + // 2. Change Collection size - int + Collection changes = p4CommittedChangelist.getChanges(); + dataOutput.writeInt(changes.size()); + for (Change change : changes) { + // 3.a. before revision + writeContentRevision(change.getBeforeRevision(), dataOutput); + + // 3.b. after revision + writeContentRevision(change.getAfterRevision(), dataOutput); + } + + // 4. Summary + P4ChangelistSummary summary = p4CommittedChangelist.getSummary(); + // 4.a. id + P4ChangelistId changeId = summary.getChangelistId(); + // 4.a.i. client server ref + ClientServerRef ref = changeId.getClientServerRef(); + // 4.a.i.A. server name full port - utf + dataOutput.writeUTF(ref.getServerName().getFullPort()); + // 4.a.i.B. client name + if (ref.getClientName() == null) { + // 4.a.i.B.i has client name - int + dataOutput.writeInt(0); + } else { + // 4.a.i.B.i has client name - int + dataOutput.writeInt(1); + // 4.a.i.B.ii client name - utf + dataOutput.writeUTF(ref.getClientName()); + } + // 4.a.ii. changelist ID - int + dataOutput.writeInt(changeId.getChangelistId()); + + // 4.b. Comment - utf + dataOutput.writeUTF(summary.getComment()); + // 4.c. username - utf + dataOutput.writeUTF(summary.getUsername()); + // 4.d. submitted - int + dataOutput.writeInt(summary.isSubmitted() ? 1 : 0); + // 4.e. has shelved - int + dataOutput.writeInt(summary.hasShelvedFiles() ? 1 : 0); + } + + @Override + public P4CommittedChangelist readChangeList(RepositoryLocation repositoryLocation, DataInput dataInput) + throws IOException { + // 1. Commit Date - long + Date commitDate = new Date(dataInput.readLong()); + + // 2. Change Collection size - int + int changeCount = dataInput.readInt(); + List changes = new ArrayList<>(changeCount); + for (int i = 0; i < changeCount; i++) { + // 3.a. before revision + P4RemoteFileContentRevision beforeRevision = readContentRevision(dataInput); + + // 3.b. after revision + P4RemoteFileContentRevision afterRevision = readContentRevision(dataInput); + + changes.add(new Change(beforeRevision, afterRevision)); + } + + // 4. Summary + + // 4. Summary + // 4.a. id + // 4.a.i. client server ref + // 4.a.i.A. server name full port - utf + P4ServerName serverName = P4ServerName.forPort(dataInput.readUTF()); + assert serverName != null; + // 4.a.i.B. client name + String clientName; + // 4.a.i.B.i has client name - int + int hasClientName = dataInput.readInt(); + if (hasClientName == 0) { + clientName = null; + } else { + // 4.a.i.B.ii client name - utf + clientName = dataInput.readUTF(); + } + ClientServerRef ref = new ClientServerRef(serverName, clientName); + // 4.a.ii. changelist ID - int + int changelistId = dataInput.readInt(); + P4ChangelistId changeId = new P4ChangelistIdImpl(changelistId, ref); + + // 4.b. Comment - utf + String comment = dataInput.readUTF(); + // 4.c. username - utf + String username = dataInput.readUTF(); + // 4.d. submitted - int + int hasSubmitted = dataInput.readInt(); + // 4.e. has shelved - int + int hasShelvedFiles = dataInput.readInt(); + P4ChangelistSummaryImpl summary = new P4ChangelistSummaryImpl( + changeId, comment, username, hasSubmitted != 0, hasShelvedFiles != 0 + ); + + return new P4CommittedChangelistImpl(summary, changes, commitDate); + } + + private void writeContentRevision(ContentRevision revision, DataOutput dataOutput) + throws IOException { + if (revision instanceof P4RemoteFileContentRevision) { + P4RemoteFileContentRevision rev = (P4RemoteFileContentRevision) revision; + + // 1. Is "revision" set - int + dataOutput.writeInt(1); + + // 2. depot file + // 2.a. path - utf + dataOutput.writeUTF(rev.getDepotPath().getDepotPath()); + // 2.b. display name - utf + dataOutput.writeUTF(rev.getDepotPath().getDisplayName()); + String depotLocalPath = rev.getDepotPath().getLocalPath().orElse(null); + if (depotLocalPath == null) { + // 2.c. is local path non-null - int + dataOutput.writeInt(0); + } else { + // 2.c. is local path non-null - int + dataOutput.writeInt(1); + // 2.c.i. local path - utf + dataOutput.writeUTF(depotLocalPath); + } + + // 3. file path + FilePath filePath = rev.getFile(); + if (filePath.isNonLocal()) { + // 3.a. is file path used - int + dataOutput.writeInt(0); + } else { + // 3.a. is file path used - int + dataOutput.writeInt(1); + // 3.a.i. file path - utf + dataOutput.writeUTF(filePath.getPath()); + } + + // 4. Revision - int + dataOutput.writeInt(rev.getIntRevisionNumber().getValue()); + + // 5. Charset - utf + dataOutput.writeUTF(rev.getCharset().name()); + } else { + // 1. Is "revision" set - int + dataOutput.writeInt(0); + } + } + + private P4RemoteFileContentRevision readContentRevision(DataInput dataInput) + throws IOException { + // 1. Is "revision" set - int + int isSet = dataInput.readInt(); + if (isSet == 1) { + // 2. depot file + // 2.a. path - utf + String depotPath = dataInput.readUTF(); + // 2.b. display name - utf + String depotDisplayName = dataInput.readUTF(); + // 2.c. is local path non-null - int + String localPath; + int hasLocalPath = dataInput.readInt(); + if (hasLocalPath == 0) { + localPath = null; + } else { + // 2.c.i. local path - utf + localPath = dataInput.readUTF(); + } + P4RemoteFile file = new P4RemoteFileImpl(depotPath, depotDisplayName, localPath); + + // 3. file path + FilePath path; + // 3.a. is file path used - int + int hasFilePath = dataInput.readInt(); + if (hasFilePath == 0) { + path = null; + } else { + // 3.a.i. file path - utf + path = VcsUtil.getFilePath(dataInput.readUTF()); + } + + // 4. Revision - int + VcsRevisionNumber.Int rev = new VcsRevisionNumber.Int(dataInput.readInt()); + + // 5. Charset - utf + Charset charset = Charset.forName(dataInput.readUTF()); + + return P4RemoteFileContentRevision.delayCreation(project, file, path, rev, loader, charset); + } else { + return null; + } + } + + @Override + public boolean isMaxCountSupported() { + return true; + } + + @Nullable + @Override + public Collection getIncomingFiles(RepositoryLocation repositoryLocation) + throws VcsException { + // No real concept of incoming files. + return null; + } + + @Override + public boolean refreshCacheByNumber() { + // use numbers, not dates + return true; + } + + /** + * Returns the name of the "changelist" concept in the specified VCS (changelist, revision etc.) + * + * @return the name of the concept, or null if the VCS (like CVS) does not use changelist numbering. + */ + @Nls + @Nullable + @Override + public String getChangelistTitle() { + return P4Bundle.getString("changelist.title"); + } + + @Override + public boolean isChangeLocallyAvailable(FilePath filePath, @Nullable VcsRevisionNumber localRevision, + VcsRevisionNumber changeRevision, P4CommittedChangelist changelist) { + // All committed revisions are remote. + return false; + } + + /** + * Returns true if a timer-based refresh of committed changes should be followed by refresh of incoming changes, so that, + * for example, changes from the wrong branch would be automatically removed from view. + * + * @return true if auto-refresh includes incoming changes refresh, false otherwise + */ + @Override + public boolean refreshIncomingWithCommitted() { + return false; + } + + + public static class P4ChangeBrowserSettings extends ChangeBrowserSettings { + String SHOW_ONLY_SHELVED_FILTER = "false"; + + // TODO add shelved filter option to filter editor + public void setShowOnlyShelvedFilter(@Nullable String showFilter) { + SHOW_ONLY_SHELVED_FILTER = showFilter == null ? "false" : + Boolean.valueOf(Boolean.parseBoolean(showFilter)).toString(); + } + + boolean isShowOnlyShelvedFilter() { + return Boolean.parseBoolean(SHOW_ONLY_SHELVED_FILTER); + } + + ListSubmittedChangelistsQuery.Filter getQueryFilter() { + ListSubmittedChangelistsQuery.Filter ret = new ListSubmittedChangelistsQuery.Filter(); + ret.setOnlyChangesWithShelvedFilesFilter(isShowOnlyShelvedFilter()); + ret.setChangesAfterChangelistFilter(getChangeAfterFilter()); + ret.setChangesBeforeChangelistFilter(getChangeBeforeFilter()); + ret.setChangesAfterDateFilter(getDateAfterFilter()); + ret.setChangesBeforeDateFilter(getDateBeforeFilter()); + return ret; + } + + @NotNull + @Override + protected List createFilters() { + final List ret = super.createFilters(); + + if (isShowOnlyShelvedFilter()) { + // FIXME + throw new IllegalStateException("not implemented"); + /* + ret.add(new Filter() { + @Override + public boolean accepts(final CommittedChangeList change) { + if (change != null && change instanceof P4CommittedChangeList) { + P4CommittedChangeList p4cl = (P4CommittedChangeList) change; + return p4cl.hasShelved(); + } + return true; + } + }); + */ + } + + return ret; + } + } + + private static class RevisionColumn extends ChangeListColumn { + private final Project project; + + private RevisionColumn(Project project) { + this.project = project; + } + + @Override + public String getTitle() { + return NUMBER.getTitle(); + } + + @Override + public Object getValue(P4CommittedChangelist p4CommittedChangelist) { + if (UserProjectPreferences.getPreferRevisionsForFiles(project)) { + return p4CommittedChangelist.getNumber(); + } + return p4CommittedChangelist.getSummary().getChangelistId().getChangelistId(); + } + + @NotNull + public Comparator getComparator() { + return Comparator.comparing(CommittedChangeList::getNumber); + } + } + + private static final ChangeListColumn HAS_SHELVED = + new ChangeListColumn() { + @Override + public String getTitle() { + return P4Bundle.message("changelist.shelved"); + } + + @Override + public Object getValue(final P4CommittedChangelist changeList) { + // FIXME implement correctly + // committed changelists can't have shelved files... so this is probably the wrong object. + LOG.warn("FIXME implement HAS_SHELVED getValue correctly"); + return false; + } + }; +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4DiffProvider.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4DiffProvider.java index 31f471eb..7894e1ae 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4DiffProvider.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4DiffProvider.java @@ -1,303 +1,303 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.extension; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.changes.ContentRevision; -import com.intellij.openapi.vcs.diff.DiffMixin; -import com.intellij.openapi.vcs.diff.DiffProviderEx; -import com.intellij.openapi.vcs.diff.ItemLatestState; -import com.intellij.openapi.vcs.history.VcsFileRevision; -import com.intellij.openapi.vcs.history.VcsRevisionDescription; -import com.intellij.openapi.vcs.history.VcsRevisionNumber; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.vcsUtil.VcsUtil; -import com.perforce.p4java.core.file.IFileSpec; -import net.groboclown.p4.server.api.ClientConfigRoot; -import net.groboclown.p4.server.api.P4CommandRunner; -import net.groboclown.p4.server.api.ProjectConfigRegistry; -import net.groboclown.p4.server.api.commands.HistoryContentLoader; -import net.groboclown.p4.server.api.commands.HistoryMessageFormatter; -import net.groboclown.p4.server.api.commands.file.ListFileHistoryQuery; -import net.groboclown.p4.server.api.commands.file.ListFilesDetailsQuery; -import net.groboclown.p4.server.api.commands.file.ListFilesDetailsResult; -import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; -import net.groboclown.p4.server.api.messagebus.ErrorEvent; -import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; -import net.groboclown.p4.server.api.values.P4FileAction; -import net.groboclown.p4.server.api.values.P4FileRevision; -import net.groboclown.p4plugin.components.P4ServerComponent; -import net.groboclown.p4plugin.components.UserProjectPreferences; -import net.groboclown.p4plugin.messages.HistoryMessageFormatterImpl; -import net.groboclown.p4plugin.util.HistoryContentLoaderImpl; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.TimeUnit; - -public class P4DiffProvider extends DiffProviderEx - implements DiffMixin { - private static final Logger LOG = Logger.getInstance(P4DiffProvider.class); - - private final Project project; - private final HistoryMessageFormatter formatter; - private final HistoryContentLoader loader; - - P4DiffProvider(Project project) { - this.project = project; - this.loader = new HistoryContentLoaderImpl(project); - this.formatter = new HistoryMessageFormatterImpl(); - } - - @Nullable - @Override - public VcsRevisionNumber getCurrentRevision(VirtualFile file) { - if (file.isDirectory()) { - return null; - } - // TODO this needs to return the "have", not "head" revision - ClientConfigRoot root = getRootFor(file); - if (root == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("Not under perforce root: " + file); - } - return null; - } - FilePath fp = VcsUtil.getFilePath(file); - if (fp == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("Not a known FilePath: " + file); - } - return null; - } - try { - ListFilesDetailsResult result = - P4ServerComponent - .query(project, root.getClientConfig(), - new ListFilesDetailsQuery( - Collections.singletonList(fp), ListFilesDetailsQuery.RevState.HAVE, 1)) - .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS); - if (result.getFiles().isEmpty()) { - LOG.info("No P4 file details found for " + file); - return null; - } - if (LOG.isDebugEnabled()) { - LOG.debug("Current revision for " + file + ": " + result.getFiles().get(0).getRevision()); - } - return result.getFiles().get(0).getRevision(); - } catch (InterruptedException e) { - InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>(new VcsInterruptedException(e))); - return null; - } catch (P4CommandRunner.ServerResultException e) { - // Already reported elsewhere. - LOG.debug(e); - return null; - } - } - - @Nullable - @Override - public ItemLatestState getLastRevision(VirtualFile virtualFile) { - return getLastRevision(VcsUtil.getFilePath(virtualFile)); - } - - /** - * Get the current version of the file as it exists in the depot - * - * @param filePath file to fetch a revision - * @return state of the file - */ - @Nullable - @Override - public ItemLatestState getLastRevision(FilePath filePath) { - if (filePath.isDirectory()) { - return null; - } - ClientConfigRoot root = getRootFor(filePath); - if (root == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("Not under perforce root: " + filePath); - } - return null; - } - try { - ListFilesDetailsResult result = P4ServerComponent - .query(project, root.getClientConfig(), - new ListFilesDetailsQuery( - Collections.singletonList(filePath), ListFilesDetailsQuery.RevState.HEAD, 1)) - .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS); - if (result.getFiles().isEmpty()) { - LOG.info("No P4 file details found for " + filePath); - return null; - } - P4FileRevision res = result.getFiles().get(0); - if (LOG.isDebugEnabled()) { - LOG.debug("Last revision for " + filePath +": " + res); - } - return new ItemLatestState(res.getRevisionNumber(), - res.getFileAction() != P4FileAction.DELETE && res.getFileAction() != P4FileAction.MOVE_DELETE, - true); - } catch (InterruptedException e) { - InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>(new VcsInterruptedException(e))); - return null; - } catch (P4CommandRunner.ServerResultException e) { - // Alreday reported elsewhere. - LOG.debug(e); - return null; - } - } - - @Nullable - @Override - public ContentRevision createFileContent(final VcsRevisionNumber revisionNumber, VirtualFile selectedFile) { - FilePath local = VcsUtil.getFilePath(selectedFile); - final ClientConfigRoot root = getRootFor(local); - if (root == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("Not under perforce root: " + selectedFile); - } - return null; - } - String clientName = root.getClientConfig().getClientname(); - if (clientName == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("No client for perforce file: " + selectedFile); - } - return null; - } - final VcsRevisionNumber.Int iRev; - if (revisionNumber instanceof VcsRevisionNumber.Int) { - iRev = (VcsRevisionNumber.Int) revisionNumber; - } else { - iRev = new VcsRevisionNumber.Int(IFileSpec.HEAD_REVISION); - } - if (LOG.isDebugEnabled()) { - LOG.debug("Generating file content for " + selectedFile + " rev " + iRev); - } - return new ContentRevision() { - @Nullable - @Override - public String getContent() - throws VcsException { - try { - if (LOG.isDebugEnabled()) { - LOG.debug("Loading file content for " + local + " rev " + iRev); - } - return loader.loadStringContentForLocal( - root.getClientConfig(), - local, - iRev.getValue()); - } catch (IOException e) { - LOG.info("Problem loading file content for " + local + " rev " + iRev, e); - throw new VcsException(e); - } - } - - @NotNull - @Override - public FilePath getFile() { - return local; - } - - @NotNull - @Override - public VcsRevisionNumber getRevisionNumber() { - return revisionNumber; - } - }; - } - - @Nullable - @Override - public VcsRevisionNumber getLatestCommittedRevision(VirtualFile vcsRoot) { - // Doesn't really mean anything in Perforce. It stores changelists, not revisions. - if (LOG.isDebugEnabled()) { - LOG.debug("Call to getLatestCommittedRevision not valid for perforce (file " + vcsRoot + ")"); - } - return null; - } - - @Nullable - @Override - public VcsRevisionDescription getCurrentRevisionDescription(VirtualFile file) { - FilePath local = VcsUtil.getFilePath(file); - final ClientConfigRoot root = getRootFor(local); - if (root == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("Not under perforce root: " + file); - } - return null; - } - String clientName = root.getClientConfig().getClientname(); - if (clientName == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("No client for perforce file: " + file); - } - return null; - } - try { - List revisions = P4ServerComponent - .query(project, root.getClientConfig(), - new ListFileHistoryQuery(local, 1)) - .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS) - .getRevisions(formatter, loader); - if (revisions.isEmpty()) { - LOG.info("No revisions found for " + file); - return null; - } - if (LOG.isDebugEnabled()) { - LOG.debug("Current revision description for " + file + ": " + revisions.get(0)); - } - return revisions.get(0); - } catch (InterruptedException e) { - InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>(new VcsInterruptedException(e))); - return null; - } catch (P4CommandRunner.ServerResultException e) { - // Already reported elsewhere. - LOG.debug(e); - return null; - } - } - - - @Nullable - private ClientConfigRoot getRootFor(VirtualFile f) { - if (f == null) { - return null; - } - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null) { - return null; - } - return registry.getClientFor(f); - } - - @Nullable - private ClientConfigRoot getRootFor(FilePath f) { - if (f == null) { - return null; - } - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null) { - return null; - } - return registry.getClientFor(f); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.extension; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.diff.DiffMixin; +import com.intellij.openapi.vcs.diff.DiffProviderEx; +import com.intellij.openapi.vcs.diff.ItemLatestState; +import com.intellij.openapi.vcs.history.VcsFileRevision; +import com.intellij.openapi.vcs.history.VcsRevisionDescription; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.vcsUtil.VcsUtil; +import com.perforce.p4java.core.file.IFileSpec; +import net.groboclown.p4.server.api.ClientConfigRoot; +import net.groboclown.p4.server.api.P4CommandRunner; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import net.groboclown.p4.server.api.commands.HistoryContentLoader; +import net.groboclown.p4.server.api.commands.HistoryMessageFormatter; +import net.groboclown.p4.server.api.commands.file.ListFileHistoryQuery; +import net.groboclown.p4.server.api.commands.file.ListFilesDetailsQuery; +import net.groboclown.p4.server.api.commands.file.ListFilesDetailsResult; +import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; +import net.groboclown.p4.server.api.messagebus.ErrorEvent; +import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; +import net.groboclown.p4.server.api.values.P4FileAction; +import net.groboclown.p4.server.api.values.P4FileRevision; +import net.groboclown.p4plugin.components.P4ServerComponent; +import net.groboclown.p4plugin.components.UserProjectPreferences; +import net.groboclown.p4plugin.messages.HistoryMessageFormatterImpl; +import net.groboclown.p4plugin.util.HistoryContentLoaderImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class P4DiffProvider extends DiffProviderEx + implements DiffMixin { + private static final Logger LOG = Logger.getInstance(P4DiffProvider.class); + + private final Project project; + private final HistoryMessageFormatter formatter; + private final HistoryContentLoader loader; + + P4DiffProvider(Project project) { + this.project = project; + this.loader = new HistoryContentLoaderImpl(project); + this.formatter = new HistoryMessageFormatterImpl(); + } + + @Nullable + @Override + public VcsRevisionNumber getCurrentRevision(VirtualFile file) { + if (file.isDirectory()) { + return null; + } + // TODO this needs to return the "have", not "head" revision + ClientConfigRoot root = getRootFor(file); + if (root == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Not under perforce root: " + file); + } + return null; + } + FilePath fp = VcsUtil.getFilePath(file); + if (fp == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Not a known FilePath: " + file); + } + return null; + } + try { + ListFilesDetailsResult result = + P4ServerComponent + .query(project, root.getClientConfig(), + new ListFilesDetailsQuery( + Collections.singletonList(fp), ListFilesDetailsQuery.RevState.HAVE, 1)) + .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS); + if (result.getFiles().isEmpty()) { + LOG.info("No P4 file details found for " + file); + return null; + } + if (LOG.isDebugEnabled()) { + LOG.debug("Current revision for " + file + ": " + result.getFiles().get(0).getRevision()); + } + return result.getFiles().get(0).getRevision(); + } catch (InterruptedException e) { + InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>(new VcsInterruptedException(e))); + return null; + } catch (P4CommandRunner.ServerResultException e) { + // Already reported elsewhere. + LOG.debug(e); + return null; + } + } + + @Nullable + @Override + public ItemLatestState getLastRevision(VirtualFile virtualFile) { + return getLastRevision(VcsUtil.getFilePath(virtualFile)); + } + + /** + * Get the current version of the file as it exists in the depot + * + * @param filePath file to fetch a revision + * @return state of the file + */ + @Nullable + @Override + public ItemLatestState getLastRevision(FilePath filePath) { + if (filePath.isDirectory()) { + return null; + } + ClientConfigRoot root = getRootFor(filePath); + if (root == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Not under perforce root: " + filePath); + } + return null; + } + try { + ListFilesDetailsResult result = P4ServerComponent + .query(project, root.getClientConfig(), + new ListFilesDetailsQuery( + Collections.singletonList(filePath), ListFilesDetailsQuery.RevState.HEAD, 1)) + .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS); + if (result.getFiles().isEmpty()) { + LOG.info("No P4 file details found for " + filePath); + return null; + } + P4FileRevision res = result.getFiles().get(0); + if (LOG.isDebugEnabled()) { + LOG.debug("Last revision for " + filePath +": " + res); + } + return new ItemLatestState(res.getRevisionNumber(), + res.getFileAction() != P4FileAction.DELETE && res.getFileAction() != P4FileAction.MOVE_DELETE, + true); + } catch (InterruptedException e) { + InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>(new VcsInterruptedException(e))); + return null; + } catch (P4CommandRunner.ServerResultException e) { + // Alreday reported elsewhere. + LOG.debug(e); + return null; + } + } + + @Nullable + @Override + public ContentRevision createFileContent(final VcsRevisionNumber revisionNumber, VirtualFile selectedFile) { + FilePath local = VcsUtil.getFilePath(selectedFile); + final ClientConfigRoot root = getRootFor(local); + if (root == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Not under perforce root: " + selectedFile); + } + return null; + } + String clientName = root.getClientConfig().getClientname(); + if (clientName == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("No client for perforce file: " + selectedFile); + } + return null; + } + final VcsRevisionNumber.Int iRev; + if (revisionNumber instanceof VcsRevisionNumber.Int) { + iRev = (VcsRevisionNumber.Int) revisionNumber; + } else { + iRev = new VcsRevisionNumber.Int(IFileSpec.HEAD_REVISION); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Generating file content for " + selectedFile + " rev " + iRev); + } + return new ContentRevision() { + @Nullable + @Override + public String getContent() + throws VcsException { + try { + if (LOG.isDebugEnabled()) { + LOG.debug("Loading file content for " + local + " rev " + iRev); + } + return loader.loadStringContentForLocal( + root.getClientConfig(), + local, + iRev.getValue()); + } catch (IOException e) { + LOG.info("Problem loading file content for " + local + " rev " + iRev, e); + throw new VcsException(e); + } + } + + @NotNull + @Override + public FilePath getFile() { + return local; + } + + @NotNull + @Override + public VcsRevisionNumber getRevisionNumber() { + return revisionNumber; + } + }; + } + + @Nullable + @Override + public VcsRevisionNumber getLatestCommittedRevision(VirtualFile vcsRoot) { + // Doesn't really mean anything in Perforce. It stores changelists, not revisions. + if (LOG.isDebugEnabled()) { + LOG.debug("Call to getLatestCommittedRevision not valid for perforce (file " + vcsRoot + ")"); + } + return null; + } + + @Nullable + @Override + public VcsRevisionDescription getCurrentRevisionDescription(VirtualFile file) { + FilePath local = VcsUtil.getFilePath(file); + final ClientConfigRoot root = getRootFor(local); + if (root == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Not under perforce root: " + file); + } + return null; + } + String clientName = root.getClientConfig().getClientname(); + if (clientName == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("No client for perforce file: " + file); + } + return null; + } + try { + List revisions = P4ServerComponent + .query(project, root.getClientConfig(), + new ListFileHistoryQuery(local, 1)) + .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS) + .getRevisions(formatter, loader); + if (revisions.isEmpty()) { + LOG.info("No revisions found for " + file); + return null; + } + if (LOG.isDebugEnabled()) { + LOG.debug("Current revision description for " + file + ": " + revisions.get(0)); + } + return revisions.get(0); + } catch (InterruptedException e) { + InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>(new VcsInterruptedException(e))); + return null; + } catch (P4CommandRunner.ServerResultException e) { + // Already reported elsewhere. + LOG.debug(e); + return null; + } + } + + + @Nullable + private ClientConfigRoot getRootFor(VirtualFile f) { + if (f == null) { + return null; + } + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null) { + return null; + } + return registry.getClientFor(f); + } + + @Nullable + private ClientConfigRoot getRootFor(FilePath f) { + if (f == null) { + return null; + } + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null) { + return null; + } + return registry.getClientFor(f); + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4EditFileProvider.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4EditFileProvider.java index 813afa3e..53cc0ba0 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4EditFileProvider.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4EditFileProvider.java @@ -1,166 +1,166 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.extension; - -import com.intellij.notification.NotificationType; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.EditFileProvider; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.vcsUtil.VcsUtil; -import net.groboclown.p4.server.api.ClientConfigRoot; -import net.groboclown.p4.server.api.ClientServerRef; -import net.groboclown.p4.server.api.ProjectConfigRegistry; -import net.groboclown.p4.server.api.commands.file.AddEditAction; -import net.groboclown.p4.server.api.values.P4ChangelistId; -import net.groboclown.p4.server.api.values.P4FileType; -import net.groboclown.p4.server.impl.util.DispatchActions; -import net.groboclown.p4.server.impl.values.P4ChangelistIdImpl; -import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.components.P4ServerComponent; -import net.groboclown.p4plugin.messages.UserMessage; -import net.groboclown.p4plugin.util.ChangelistUtil; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Map; - -/** - * This is only called when the file is changed from - * read-only to writable. - */ -public class P4EditFileProvider implements EditFileProvider { - private static final Logger LOG = Logger.getInstance(P4EditFileProvider.class); - - private final Project project; - - P4EditFileProvider(@NotNull P4Vcs vcs) { - this.project = vcs.getProject(); - } - - - // This method was called with nearly every keystroke, so it must be very, very - // performant. That seems to be the case with older IDE verseions; now, it's called - // only on changes from read-only to writable. - @Override - public void editFiles(final VirtualFile[] allFiles) throws VcsException { - if (LOG.isDebugEnabled()) { - LOG.debug("Calling `editFiles` with " + Arrays.asList(allFiles)); - } - if (allFiles == null || allFiles.length <= 0) { - return; - } - - // TODO inspect the IgnoreFileSet ignore state. - - // In order to speed up the operation of this call, we will not care who - // has this open for edit or not. Make the file writable, then pass on - // the actual server edit checks to a background thread. - - // TODO This is ignoring the user property to edit in another thread. - - makeWritable(allFiles); - ApplicationManager.getApplication().executeOnPooledThread(() -> { - Map activeChangelistIds = getActiveChangelistIds(); - for (VirtualFile file : allFiles) { - FilePath fp = VcsUtil.getFilePath(file); - if (fp == null || !file.isInLocalFileSystem()) { - continue; - } - ClientConfigRoot root = getClientFor(file); - if (root != null) { - P4ChangelistId id = getActiveChangelistFor(root, activeChangelistIds); - if (LOG.isDebugEnabled()) { - LOG.debug("Opening for add/edit: " + fp + " (@" + id + ")"); - } - P4ServerComponent - .perform(project, root.getClientConfig(), - new AddEditAction(fp, getFileType(fp), id, (String) null)) - .whenCompleted((res) -> { - LOG.info("Opened for add/edit: " + fp + ": add? " + res.isAdd() + "; cl: " + res.getChangelistId()); - }) - .whenOffline(() -> { - LOG.warn("Queued add/edit action for " + fp + "; server offline"); - }) - .whenServerError((e) -> { - LOG.warn("Could not open for add", e); - }); - } else { - LOG.info("Not under Perforce VCS root: " + file); - } - } - }); - } - - @Override - public String getRequestText() { - // TODO verify where this is used, and whether the text makes sense in the context. - return P4Bundle.getString("file.open-for-edit"); - } - - private void makeWritable(@NotNull final VirtualFile[] allFiles) { - DispatchActions.writeAction(() -> { - for (VirtualFile file : allFiles) { - if (file.isInLocalFileSystem()) { - try { - file.setWritable(true); - } catch (IOException e) { - handleError(file, e); - } - } - } - }); - } - - private Map getActiveChangelistIds() { - return ChangelistUtil.getActiveChangelistIds(project); - } - - @NotNull - private P4ChangelistId getActiveChangelistFor(ClientConfigRoot root, Map ids) { - ClientServerRef ref = root.getClientConfig().getClientServerRef(); - P4ChangelistId ret = ids.get(ref); - if (ret == null) { - ret = P4ChangelistIdImpl.createDefaultChangelistId(ref); - ids.put(ref, ret); - } - return ret; - } - - private P4FileType getFileType(FilePath fp) { - FileType ft = fp.getFileType(); - if (ft.isBinary()) { - return P4FileType.convert("binary"); - } - return P4FileType.convert("text"); - } - - private void handleError(VirtualFile file, IOException e) { - UserMessage.showNotification(project, UserMessage.ERROR, - e.getLocalizedMessage(), - file.getName(), - NotificationType.ERROR); - } - - - private ClientConfigRoot getClientFor(VirtualFile file) { - ProjectConfigRegistry reg = ProjectConfigRegistry.getInstance(project); - return reg == null ? null : reg.getClientFor(file); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.extension; + +import com.intellij.notification.NotificationType; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.EditFileProvider; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.vcsUtil.VcsUtil; +import net.groboclown.p4.server.api.ClientConfigRoot; +import net.groboclown.p4.server.api.ClientServerRef; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import net.groboclown.p4.server.api.commands.file.AddEditAction; +import net.groboclown.p4.server.api.values.P4ChangelistId; +import net.groboclown.p4.server.api.values.P4FileType; +import net.groboclown.p4.server.impl.util.DispatchActions; +import net.groboclown.p4.server.impl.values.P4ChangelistIdImpl; +import net.groboclown.p4plugin.P4Bundle; +import net.groboclown.p4plugin.components.P4ServerComponent; +import net.groboclown.p4plugin.messages.UserMessage; +import net.groboclown.p4plugin.util.ChangelistUtil; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; + +/** + * This is only called when the file is changed from + * read-only to writable. + */ +public class P4EditFileProvider implements EditFileProvider { + private static final Logger LOG = Logger.getInstance(P4EditFileProvider.class); + + private final Project project; + + P4EditFileProvider(@NotNull P4Vcs vcs) { + this.project = vcs.getProject(); + } + + + // This method was called with nearly every keystroke, so it must be very, very + // performant. That seems to be the case with older IDE verseions; now, it's called + // only on changes from read-only to writable. + @Override + public void editFiles(final VirtualFile[] allFiles) throws VcsException { + if (LOG.isDebugEnabled()) { + LOG.debug("Calling `editFiles` with " + Arrays.asList(allFiles)); + } + if (allFiles == null || allFiles.length <= 0) { + return; + } + + // TODO inspect the IgnoreFileSet ignore state. + + // In order to speed up the operation of this call, we will not care who + // has this open for edit or not. Make the file writable, then pass on + // the actual server edit checks to a background thread. + + // TODO This is ignoring the user property to edit in another thread. + + makeWritable(allFiles); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + Map activeChangelistIds = getActiveChangelistIds(); + for (VirtualFile file : allFiles) { + FilePath fp = VcsUtil.getFilePath(file); + if (fp == null || !file.isInLocalFileSystem()) { + continue; + } + ClientConfigRoot root = getClientFor(file); + if (root != null) { + P4ChangelistId id = getActiveChangelistFor(root, activeChangelistIds); + if (LOG.isDebugEnabled()) { + LOG.debug("Opening for add/edit: " + fp + " (@" + id + ")"); + } + P4ServerComponent + .perform(project, root.getClientConfig(), + new AddEditAction(fp, getFileType(fp), id, (String) null)) + .whenCompleted((res) -> { + LOG.info("Opened for add/edit: " + fp + ": add? " + res.isAdd() + "; cl: " + res.getChangelistId()); + }) + .whenOffline(() -> { + LOG.warn("Queued add/edit action for " + fp + "; server offline"); + }) + .whenServerError((e) -> { + LOG.warn("Could not open for add", e); + }); + } else { + LOG.info("Not under Perforce VCS root: " + file); + } + } + }); + } + + @Override + public String getRequestText() { + // TODO verify where this is used, and whether the text makes sense in the context. + return P4Bundle.getString("file.open-for-edit"); + } + + private void makeWritable(@NotNull final VirtualFile[] allFiles) { + DispatchActions.writeAction(() -> { + for (VirtualFile file : allFiles) { + if (file.isInLocalFileSystem()) { + try { + file.setWritable(true); + } catch (IOException e) { + handleError(file, e); + } + } + } + }); + } + + private Map getActiveChangelistIds() { + return ChangelistUtil.getActiveChangelistIds(project); + } + + @NotNull + private P4ChangelistId getActiveChangelistFor(ClientConfigRoot root, Map ids) { + ClientServerRef ref = root.getClientConfig().getClientServerRef(); + P4ChangelistId ret = ids.get(ref); + if (ret == null) { + ret = P4ChangelistIdImpl.createDefaultChangelistId(ref); + ids.put(ref, ret); + } + return ret; + } + + private P4FileType getFileType(FilePath fp) { + FileType ft = fp.getFileType(); + if (ft.isBinary()) { + return P4FileType.convert("binary"); + } + return P4FileType.convert("text"); + } + + private void handleError(VirtualFile file, IOException e) { + UserMessage.showNotification(project, UserMessage.ERROR, + e.getLocalizedMessage(), + file.getName(), + NotificationType.ERROR); + } + + + private ClientConfigRoot getClientFor(VirtualFile file) { + ProjectConfigRegistry reg = ProjectConfigRegistry.getInstance(project); + return reg == null ? null : reg.getClientFor(file); + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4HistoryProvider.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4HistoryProvider.java index 205d88f7..f67c9693 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4HistoryProvider.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4HistoryProvider.java @@ -1,391 +1,391 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.extension; - -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.history.DiffFromHistoryHandler; -import com.intellij.openapi.vcs.history.HistoryAsTreeProvider; -import com.intellij.openapi.vcs.history.VcsAbstractHistorySession; -import com.intellij.openapi.vcs.history.VcsAppendableHistorySessionPartner; -import com.intellij.openapi.vcs.history.VcsDependentHistoryComponents; -import com.intellij.openapi.vcs.history.VcsFileRevision; -import com.intellij.openapi.vcs.history.VcsHistorySession; -import com.intellij.openapi.vcs.history.VcsHistoryUtil; -import com.intellij.openapi.vcs.history.VcsRevisionNumber; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.ColoredTableCellRenderer; -import com.intellij.ui.dualView.DualViewColumnInfo; -import com.intellij.ui.speedSearch.SpeedSearchUtil; -import com.intellij.util.ui.ColumnInfo; -import com.intellij.vcs.history.VcsHistoryProviderEx; -import com.intellij.vcsUtil.VcsUtil; -import net.groboclown.p4.server.api.ClientConfigRoot; -import net.groboclown.p4.server.api.P4CommandRunner; -import net.groboclown.p4.server.api.ProjectConfigRegistry; -import net.groboclown.p4.server.api.commands.HistoryContentLoader; -import net.groboclown.p4.server.api.commands.HistoryMessageFormatter; -import net.groboclown.p4.server.api.commands.file.ListFileHistoryQuery; -import net.groboclown.p4.server.api.commands.file.ListFileHistoryResult; -import net.groboclown.p4.server.api.commands.file.ListFilesDetailsQuery; -import net.groboclown.p4.server.api.commands.file.ListFilesDetailsResult; -import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; -import net.groboclown.p4.server.api.messagebus.ErrorEvent; -import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; -import net.groboclown.p4.server.api.values.P4FileRevision; -import net.groboclown.p4.server.impl.repository.P4HistoryVcsFileRevision; -import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.actions.ChangelistDescriptionAction; -import net.groboclown.p4plugin.components.P4ServerComponent; -import net.groboclown.p4plugin.components.UserProjectPreferences; -import net.groboclown.p4plugin.messages.HistoryMessageFormatterImpl; -import net.groboclown.p4plugin.util.HistoryContentLoaderImpl; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.table.TableCellRenderer; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.TimeUnit; - -public class P4HistoryProvider - implements VcsHistoryProviderEx { - private static final Logger LOG = Logger.getInstance(P4HistoryProvider.class); - - private final Project project; - private final DiffFromHistoryHandler diffHandler = new P4DiffFromHistoryHandler(); - private final HistoryMessageFormatter formatter = new HistoryMessageFormatterImpl(); - private final HistoryContentLoader loader; - - P4HistoryProvider(@NotNull Project project) { - this.project = project; - this.loader = new HistoryContentLoaderImpl(project); - } - - @Override - public VcsDependentHistoryComponents getUICustomization(VcsHistorySession session, JComponent forShortcutRegistration) { - return VcsDependentHistoryComponents.createOnlyColumns(ADDITIONAL_HISTORY_COLUMNS); - } - - @Override - public AnAction[] getAdditionalActions(Runnable refresher) { - return new AnAction[] { - new ChangelistDescriptionAction() - }; - } - - @Override - public boolean isDateOmittable() { - // Show the date column. - return false; - } - - @Nullable - @Override - public String getHelpId() { - // TODO add help - return null; - } - - @Override - public boolean supportsHistoryForDirectories() { - return false; - } - - @Nullable - @Override - public DiffFromHistoryHandler getHistoryDiffHandler() { - return diffHandler; - } - - @Override - public boolean canShowHistoryFor(@NotNull VirtualFile file) { - if (file.isDirectory() || !file.isValid()) { - return false; - } - FilePath fp = VcsUtil.getFilePath(file); - return fp != null && getRootFor(fp) != null; - } - - @Nullable - @Override - public VcsHistorySession createSessionFor(FilePath filePath) throws VcsException { - ClientConfigRoot root = getRootFor(filePath); - if (root == null) { - LOG.info("Not in P4 project: " + filePath); - return null; - } - - try { - List revisions = P4ServerComponent - .query(project, root.getClientConfig(), - new ListFileHistoryQuery(filePath, -1)) - .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS) - .getRevisions(formatter, loader); - return createAppendableSession(filePath, revisions, null); - } catch (InterruptedException e) { - throw new VcsInterruptedException(e); - } - } - - @Override - public void reportAppendableHistory(FilePath path, VcsAppendableHistorySessionPartner partner) { - partner.reportCreatedEmptySession(createAppendableSession( - path, Collections.emptyList(), null)); - ClientConfigRoot root = getRootFor(path); - if (root == null) { - LOG.info("File not under VCS: " + path); - // TODO bundle message - partner.reportException(new VcsException("File not under VCS: " + path)); - - // Deprecated in 191. - partner.finished(); - return; - } - - // Async operation. - getHistory(root, path, -1) - .whenCompleted((r) -> { - r.getRevisions(formatter, loader).forEach(partner::acceptRevision); - - // Deprecated in 191. - partner.finished(); - }) - .whenServerError((e) -> { - LOG.warn(e); - partner.reportException(e); - - // Deprecated in 191. - partner.finished(); - }); - } - - @Override - @Nullable - public VcsFileRevision getLastRevision(FilePath filePath) - throws VcsException { - ClientConfigRoot root = getRootFor(filePath); - if (root == null) { - LOG.info("File not under vcs: " + filePath); - return null; - } - - try { - List revisions = getHistory(root, filePath, 1) - .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS) - .getRevisions(formatter, loader); - if (revisions.isEmpty()) { - if (LOG.isDebugEnabled()) { - LOG.debug("no revisions found for " + filePath); - } - return null; - } - return revisions.get(0); - } catch (InterruptedException e) { - InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>(new VcsInterruptedException(e))); - return null; - } - } - - @Override - public void reportAppendableHistory(@NotNull FilePath path, @Nullable VcsRevisionNumber startingRevision, - @NotNull VcsAppendableHistorySessionPartner partner) { - partner.reportCreatedEmptySession(createAppendableSession( - path, Collections.emptyList(), null)); - ClientConfigRoot root = getRootFor(path); - if (root == null) { - LOG.warn("File not under vcs: " + path); - // TODO bundle message - partner.reportException(new VcsException("File not under VCS: " + path)); - partner.finished(); - return; - } - final int startingRev; - if (startingRevision instanceof VcsRevisionNumber.Int) { - startingRev = ((VcsRevisionNumber.Int) startingRevision).getValue(); - } else { - startingRev = 0; - if (startingRevision != null) { - LOG.warn("Requested reportAppendableHistory with unexpected type " + startingRevision + - " (" + startingRevision.getClass() + ")"); - } - } - - // Async operation - getHistory(root, path, -1) - .whenCompleted((r) -> - r.getRevisions(formatter, loader).forEach((rev) -> { - VcsRevisionNumber rn = rev.getRevisionNumber(); - if (rn instanceof VcsRevisionNumber.Int) { - VcsRevisionNumber.Int rni = (VcsRevisionNumber.Int) rn; - if (rni.getValue() >= startingRev) { - partner.acceptRevision(rev); - } - } else { - LOG.warn("VcsFileRevision returned unexpected revision number " + rn + - " (" + rn.getClass() + ")"); - } - }) - ) - .whenServerError(partner::reportException) - .whenAnyState(partner::finished); - } - - @Nullable - private ClientConfigRoot getRootFor(FilePath fp) { - if (fp == null || project.isDisposed()) { - return null; - } - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null || registry.isDisposed()) { - return null; - } - return registry.getClientFor(fp); - } - - private VcsAbstractHistorySession createAppendableSession(final FilePath path, List revisions, @Nullable final VcsRevisionNumber number) { - return new VcsAbstractHistorySession(revisions, number) { - /** - * This method should return actual value for current revision (it can be changed after submit for example) - * - * @return current file revision, null if file does not exist anymore - */ - @Nullable - protected VcsRevisionNumber calcCurrentRevisionNumber() { - ClientConfigRoot root = getRootFor(path); - if (root == null) { - return null; - } - try { - ListFilesDetailsResult result = P4ServerComponent - .query(project, root.getClientConfig(), new ListFilesDetailsQuery( - Collections.singletonList(path), - ListFilesDetailsQuery.RevState.HEAD, 1)) - .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), - TimeUnit.MILLISECONDS); - if (result.getFiles().isEmpty()) { - if (LOG.isDebugEnabled()) { - LOG.debug("No file details found for " + path); - } - return null; - } - return result.getFiles().get(0).getRevisionNumber(); - } catch (InterruptedException e) { - InternalErrorMessage.send(project).cacheLockTimeoutError( - new ErrorEvent<>(new VcsInterruptedException(e))); - return null; - } catch (P4CommandRunner.ServerResultException e) { - // Already reported elsewhere - LOG.debug(e); - return null; - } - } - - public HistoryAsTreeProvider getHistoryAsTreeProvider() { - // All providers seem to just return null here - return null; - } - - @Override - public VcsHistorySession copy() { - return createAppendableSession(path, getRevisionList(), getCurrentRevisionNumber()); - } - }; - } - - private P4CommandRunner.QueryAnswer getHistory( - @NotNull ClientConfigRoot root, FilePath file, int revisionCount) { - return P4ServerComponent - .query(project, root.getClientConfig(), new ListFileHistoryQuery(file, revisionCount)); - } - - private static class P4DiffFromHistoryHandler implements DiffFromHistoryHandler { - @Override - public void showDiffForOne(@NotNull AnActionEvent e, @NotNull Project project, @NotNull FilePath filePath, - @NotNull VcsFileRevision previousRevision, @NotNull VcsFileRevision revision) { - VcsHistoryUtil.showDifferencesInBackground(project, filePath, previousRevision, revision); - } - - @Override - public void showDiffForTwo(@NotNull Project project, - @NotNull FilePath filePath, - @NotNull VcsFileRevision revision1, - @NotNull VcsFileRevision revision2) { - VcsHistoryUtil.showDifferencesInBackground(project, filePath, revision1, revision2); - } - } - - - private static class ChangelistColumnInfo extends DualViewColumnInfo - implements Comparator { - @NotNull - private final ColoredTableCellRenderer myRenderer = new ColoredTableCellRenderer() { - protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { - this.setOpaque(selected); - this.append(value == null ? "?" : value.toString()); - SpeedSearchUtil.applySpeedSearchHighlighting(table, this, false, selected); - } - }; - - public ChangelistColumnInfo() { - super(P4Bundle.getString("history.columns.changelist")); - } - - Integer getDataOf(VcsFileRevision rev) { - if (rev instanceof P4FileRevision) { - return ((P4FileRevision) rev).getChangelistId().getChangelistId(); - } - if (rev instanceof P4HistoryVcsFileRevision) { - return ((P4HistoryVcsFileRevision) rev).getChangelistId().getChangelistId(); - } - return null; - } - - public Comparator getComparator() { - return this; - } - - public String valueOf(VcsFileRevision object) { - Integer result = this.getDataOf(object); - return result == null ? "" : result.toString(); - } - - public int compare(VcsFileRevision o1, VcsFileRevision o2) { - return Comparing.compare(this.getDataOf(o1), this.getDataOf(o2)); - } - - public boolean shouldBeShownIsTheTree() { - return true; - } - - public boolean shouldBeShownIsTheTable() { - return true; - } - - @Nullable - public TableCellRenderer getRenderer(VcsFileRevision revision) { - return this.myRenderer; - } - } - - private static final ChangelistColumnInfo CHANGELIST_COLUMN_INFO = new ChangelistColumnInfo(); - private static final ColumnInfo[] ADDITIONAL_HISTORY_COLUMNS = new ColumnInfo[] { CHANGELIST_COLUMN_INFO }; -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.extension; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.history.DiffFromHistoryHandler; +import com.intellij.openapi.vcs.history.HistoryAsTreeProvider; +import com.intellij.openapi.vcs.history.VcsAbstractHistorySession; +import com.intellij.openapi.vcs.history.VcsAppendableHistorySessionPartner; +import com.intellij.openapi.vcs.history.VcsDependentHistoryComponents; +import com.intellij.openapi.vcs.history.VcsFileRevision; +import com.intellij.openapi.vcs.history.VcsHistorySession; +import com.intellij.openapi.vcs.history.VcsHistoryUtil; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.ColoredTableCellRenderer; +import com.intellij.ui.dualView.DualViewColumnInfo; +import com.intellij.ui.speedSearch.SpeedSearchUtil; +import com.intellij.util.ui.ColumnInfo; +import com.intellij.vcs.history.VcsHistoryProviderEx; +import com.intellij.vcsUtil.VcsUtil; +import net.groboclown.p4.server.api.ClientConfigRoot; +import net.groboclown.p4.server.api.P4CommandRunner; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import net.groboclown.p4.server.api.commands.HistoryContentLoader; +import net.groboclown.p4.server.api.commands.HistoryMessageFormatter; +import net.groboclown.p4.server.api.commands.file.ListFileHistoryQuery; +import net.groboclown.p4.server.api.commands.file.ListFileHistoryResult; +import net.groboclown.p4.server.api.commands.file.ListFilesDetailsQuery; +import net.groboclown.p4.server.api.commands.file.ListFilesDetailsResult; +import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; +import net.groboclown.p4.server.api.messagebus.ErrorEvent; +import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; +import net.groboclown.p4.server.api.values.P4FileRevision; +import net.groboclown.p4.server.impl.repository.P4HistoryVcsFileRevision; +import net.groboclown.p4plugin.P4Bundle; +import net.groboclown.p4plugin.actions.ChangelistDescriptionAction; +import net.groboclown.p4plugin.components.P4ServerComponent; +import net.groboclown.p4plugin.components.UserProjectPreferences; +import net.groboclown.p4plugin.messages.HistoryMessageFormatterImpl; +import net.groboclown.p4plugin.util.HistoryContentLoaderImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.table.TableCellRenderer; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class P4HistoryProvider + implements VcsHistoryProviderEx { + private static final Logger LOG = Logger.getInstance(P4HistoryProvider.class); + + private final Project project; + private final DiffFromHistoryHandler diffHandler = new P4DiffFromHistoryHandler(); + private final HistoryMessageFormatter formatter = new HistoryMessageFormatterImpl(); + private final HistoryContentLoader loader; + + P4HistoryProvider(@NotNull Project project) { + this.project = project; + this.loader = new HistoryContentLoaderImpl(project); + } + + @Override + public VcsDependentHistoryComponents getUICustomization(VcsHistorySession session, JComponent forShortcutRegistration) { + return VcsDependentHistoryComponents.createOnlyColumns(ADDITIONAL_HISTORY_COLUMNS); + } + + @Override + public AnAction[] getAdditionalActions(Runnable refresher) { + return new AnAction[] { + new ChangelistDescriptionAction() + }; + } + + @Override + public boolean isDateOmittable() { + // Show the date column. + return false; + } + + @Nullable + @Override + public String getHelpId() { + // TODO add help + return null; + } + + @Override + public boolean supportsHistoryForDirectories() { + return false; + } + + @Nullable + @Override + public DiffFromHistoryHandler getHistoryDiffHandler() { + return diffHandler; + } + + @Override + public boolean canShowHistoryFor(@NotNull VirtualFile file) { + if (file.isDirectory() || !file.isValid()) { + return false; + } + FilePath fp = VcsUtil.getFilePath(file); + return fp != null && getRootFor(fp) != null; + } + + @Nullable + @Override + public VcsHistorySession createSessionFor(FilePath filePath) throws VcsException { + ClientConfigRoot root = getRootFor(filePath); + if (root == null) { + LOG.info("Not in P4 project: " + filePath); + return null; + } + + try { + List revisions = P4ServerComponent + .query(project, root.getClientConfig(), + new ListFileHistoryQuery(filePath, -1)) + .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS) + .getRevisions(formatter, loader); + return createAppendableSession(filePath, revisions, null); + } catch (InterruptedException e) { + throw new VcsInterruptedException(e); + } + } + + @Override + public void reportAppendableHistory(FilePath path, VcsAppendableHistorySessionPartner partner) { + partner.reportCreatedEmptySession(createAppendableSession( + path, Collections.emptyList(), null)); + ClientConfigRoot root = getRootFor(path); + if (root == null) { + LOG.info("File not under VCS: " + path); + // TODO bundle message + partner.reportException(new VcsException("File not under VCS: " + path)); + + // Deprecated in 191. + partner.finished(); + return; + } + + // Async operation. + getHistory(root, path, -1) + .whenCompleted((r) -> { + r.getRevisions(formatter, loader).forEach(partner::acceptRevision); + + // Deprecated in 191. + partner.finished(); + }) + .whenServerError((e) -> { + LOG.warn(e); + partner.reportException(e); + + // Deprecated in 191. + partner.finished(); + }); + } + + @Override + @Nullable + public VcsFileRevision getLastRevision(FilePath filePath) + throws VcsException { + ClientConfigRoot root = getRootFor(filePath); + if (root == null) { + LOG.info("File not under vcs: " + filePath); + return null; + } + + try { + List revisions = getHistory(root, filePath, 1) + .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS) + .getRevisions(formatter, loader); + if (revisions.isEmpty()) { + if (LOG.isDebugEnabled()) { + LOG.debug("no revisions found for " + filePath); + } + return null; + } + return revisions.get(0); + } catch (InterruptedException e) { + InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>(new VcsInterruptedException(e))); + return null; + } + } + + @Override + public void reportAppendableHistory(@NotNull FilePath path, @Nullable VcsRevisionNumber startingRevision, + @NotNull VcsAppendableHistorySessionPartner partner) { + partner.reportCreatedEmptySession(createAppendableSession( + path, Collections.emptyList(), null)); + ClientConfigRoot root = getRootFor(path); + if (root == null) { + LOG.warn("File not under vcs: " + path); + // TODO bundle message + partner.reportException(new VcsException("File not under VCS: " + path)); + partner.finished(); + return; + } + final int startingRev; + if (startingRevision instanceof VcsRevisionNumber.Int) { + startingRev = ((VcsRevisionNumber.Int) startingRevision).getValue(); + } else { + startingRev = 0; + if (startingRevision != null) { + LOG.warn("Requested reportAppendableHistory with unexpected type " + startingRevision + + " (" + startingRevision.getClass() + ")"); + } + } + + // Async operation + getHistory(root, path, -1) + .whenCompleted((r) -> + r.getRevisions(formatter, loader).forEach((rev) -> { + VcsRevisionNumber rn = rev.getRevisionNumber(); + if (rn instanceof VcsRevisionNumber.Int) { + VcsRevisionNumber.Int rni = (VcsRevisionNumber.Int) rn; + if (rni.getValue() >= startingRev) { + partner.acceptRevision(rev); + } + } else { + LOG.warn("VcsFileRevision returned unexpected revision number " + rn + + " (" + rn.getClass() + ")"); + } + }) + ) + .whenServerError(partner::reportException) + .whenAnyState(partner::finished); + } + + @Nullable + private ClientConfigRoot getRootFor(FilePath fp) { + if (fp == null || project.isDisposed()) { + return null; + } + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null || registry.isDisposed()) { + return null; + } + return registry.getClientFor(fp); + } + + private VcsAbstractHistorySession createAppendableSession(final FilePath path, List revisions, @Nullable final VcsRevisionNumber number) { + return new VcsAbstractHistorySession(revisions, number) { + /** + * This method should return actual value for current revision (it can be changed after submit for example) + * + * @return current file revision, null if file does not exist anymore + */ + @Nullable + protected VcsRevisionNumber calcCurrentRevisionNumber() { + ClientConfigRoot root = getRootFor(path); + if (root == null) { + return null; + } + try { + ListFilesDetailsResult result = P4ServerComponent + .query(project, root.getClientConfig(), new ListFilesDetailsQuery( + Collections.singletonList(path), + ListFilesDetailsQuery.RevState.HEAD, 1)) + .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), + TimeUnit.MILLISECONDS); + if (result.getFiles().isEmpty()) { + if (LOG.isDebugEnabled()) { + LOG.debug("No file details found for " + path); + } + return null; + } + return result.getFiles().get(0).getRevisionNumber(); + } catch (InterruptedException e) { + InternalErrorMessage.send(project).cacheLockTimeoutError( + new ErrorEvent<>(new VcsInterruptedException(e))); + return null; + } catch (P4CommandRunner.ServerResultException e) { + // Already reported elsewhere + LOG.debug(e); + return null; + } + } + + public HistoryAsTreeProvider getHistoryAsTreeProvider() { + // All providers seem to just return null here + return null; + } + + @Override + public VcsHistorySession copy() { + return createAppendableSession(path, getRevisionList(), getCurrentRevisionNumber()); + } + }; + } + + private P4CommandRunner.QueryAnswer getHistory( + @NotNull ClientConfigRoot root, FilePath file, int revisionCount) { + return P4ServerComponent + .query(project, root.getClientConfig(), new ListFileHistoryQuery(file, revisionCount)); + } + + private static class P4DiffFromHistoryHandler implements DiffFromHistoryHandler { + @Override + public void showDiffForOne(@NotNull AnActionEvent e, @NotNull Project project, @NotNull FilePath filePath, + @NotNull VcsFileRevision previousRevision, @NotNull VcsFileRevision revision) { + VcsHistoryUtil.showDifferencesInBackground(project, filePath, previousRevision, revision); + } + + @Override + public void showDiffForTwo(@NotNull Project project, + @NotNull FilePath filePath, + @NotNull VcsFileRevision revision1, + @NotNull VcsFileRevision revision2) { + VcsHistoryUtil.showDifferencesInBackground(project, filePath, revision1, revision2); + } + } + + + private static class ChangelistColumnInfo extends DualViewColumnInfo + implements Comparator { + @NotNull + private final ColoredTableCellRenderer myRenderer = new ColoredTableCellRenderer() { + protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { + this.setOpaque(selected); + this.append(value == null ? "?" : value.toString()); + SpeedSearchUtil.applySpeedSearchHighlighting(table, this, false, selected); + } + }; + + public ChangelistColumnInfo() { + super(P4Bundle.getString("history.columns.changelist")); + } + + Integer getDataOf(VcsFileRevision rev) { + if (rev instanceof P4FileRevision) { + return ((P4FileRevision) rev).getChangelistId().getChangelistId(); + } + if (rev instanceof P4HistoryVcsFileRevision) { + return ((P4HistoryVcsFileRevision) rev).getChangelistId().getChangelistId(); + } + return null; + } + + public Comparator getComparator() { + return this; + } + + public String valueOf(VcsFileRevision object) { + Integer result = this.getDataOf(object); + return result == null ? "" : result.toString(); + } + + public int compare(VcsFileRevision o1, VcsFileRevision o2) { + return Comparing.compare(this.getDataOf(o1), this.getDataOf(o2)); + } + + public boolean shouldBeShownIsTheTree() { + return true; + } + + public boolean shouldBeShownIsTheTable() { + return true; + } + + @Nullable + public TableCellRenderer getRenderer(VcsFileRevision revision) { + return this.myRenderer; + } + } + + private static final ChangelistColumnInfo CHANGELIST_COLUMN_INFO = new ChangelistColumnInfo(); + private static final ColumnInfo[] ADDITIONAL_HISTORY_COLUMNS = new ColumnInfo[] { CHANGELIST_COLUMN_INFO }; +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4QuickListContentProvider.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4QuickListContentProvider.java index 7f7cafee..ca18cf37 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4QuickListContentProvider.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4QuickListContentProvider.java @@ -1,90 +1,90 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.extension; - -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.Separator; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.AbstractVcs; -import com.intellij.openapi.vcs.actions.VcsQuickListContentProvider; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -public class P4QuickListContentProvider implements VcsQuickListContentProvider { - @Nullable - @Override - public List getVcsActions(Project project, AbstractVcs activeVcs, DataContext dataContext) { - if (activeVcs == null || !P4Vcs.VCS_NAME.equals(activeVcs.getName())) { - return null; - } - - final ActionManager manager = ActionManager.getInstance(); - final List actions = new ArrayList(); - - actions.add(new Separator(activeVcs.getDisplayName())); - add("CheckinProject", manager, actions); - add("CheckinFiles", manager, actions); - add("ChangesView.Revert", manager, actions); - add("UpdateFiles", manager, actions); - add("ChangesView.AddUnversioned", manager, actions); - - addSeparator(actions); - add("Vcs.ShowTabbedFileHistory", manager, actions); - add("Annotate", manager, actions); - add("Compare.SameVersion", manager, actions); - add("Compare.Selected", manager, actions); - - /* FIXME add shelve/unshelve capability. - addSeparator(actions); - add("P4.Shelve", manager, actions); - add("P4.Unshelve", manager, actions); - */ - - /* - add("P4.ResolveConflicts", manager, actions); - */ - - return actions; - } - - @Nullable - @Override - public List getNotInVcsActions(@Nullable Project project, @Nullable DataContext dataContext) { - // Don't do anything if it's not in source control - return null; - } - - @Override - public boolean replaceVcsActionsFor(@NotNull AbstractVcs activeVcs, @Nullable DataContext dataContext) { - if (!P4Vcs.VCS_NAME.equals(activeVcs.getName())) { - return false; - } - return true; - } - - private static void addSeparator(@NotNull final List actions) { - actions.add(new Separator()); - } - - private static void add(String actionName, ActionManager manager, List actions) { - final AnAction action = manager.getAction(actionName); - assert action != null; - actions.add(action); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.extension; + +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.Separator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.actions.VcsQuickListContentProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class P4QuickListContentProvider implements VcsQuickListContentProvider { + @Nullable + @Override + public List getVcsActions(Project project, AbstractVcs activeVcs, DataContext dataContext) { + if (activeVcs == null || !P4Vcs.VCS_NAME.equals(activeVcs.getName())) { + return null; + } + + final ActionManager manager = ActionManager.getInstance(); + final List actions = new ArrayList(); + + actions.add(new Separator(activeVcs.getDisplayName())); + add("CheckinProject", manager, actions); + add("CheckinFiles", manager, actions); + add("ChangesView.Revert", manager, actions); + add("UpdateFiles", manager, actions); + add("ChangesView.AddUnversioned", manager, actions); + + addSeparator(actions); + add("Vcs.ShowTabbedFileHistory", manager, actions); + add("Annotate", manager, actions); + add("Compare.SameVersion", manager, actions); + add("Compare.Selected", manager, actions); + + /* FIXME add shelve/unshelve capability. + addSeparator(actions); + add("P4.Shelve", manager, actions); + add("P4.Unshelve", manager, actions); + */ + + /* + add("P4.ResolveConflicts", manager, actions); + */ + + return actions; + } + + @Nullable + @Override + public List getNotInVcsActions(@Nullable Project project, @Nullable DataContext dataContext) { + // Don't do anything if it's not in source control + return null; + } + + @Override + public boolean replaceVcsActionsFor(@NotNull AbstractVcs activeVcs, @Nullable DataContext dataContext) { + if (!P4Vcs.VCS_NAME.equals(activeVcs.getName())) { + return false; + } + return true; + } + + private static void addSeparator(@NotNull final List actions) { + actions.add(new Separator()); + } + + private static void add(String actionName, ActionManager manager, List actions) { + final AnAction action = manager.getAction(actionName); + assert action != null; + actions.add(action); + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4RevisionSelector.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4RevisionSelector.java index 51a2c12d..4f34fa31 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4RevisionSelector.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4RevisionSelector.java @@ -1,47 +1,47 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4plugin.extension; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.vcs.diff.RevisionSelector; -import com.intellij.openapi.vcs.history.VcsRevisionNumber; -import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.annotations.Nullable; - -public class P4RevisionSelector implements RevisionSelector { - private static final Logger LOG = Logger.getInstance(P4RevisionSelector.class); - private final P4Vcs vcs; - - public P4RevisionSelector(final P4Vcs vcs) { - this.vcs = vcs; - } - - @Nullable - @Override - public VcsRevisionNumber selectNumber(final VirtualFile file) { - if (LOG.isDebugEnabled()) { - LOG.debug("Selecting version for file " + file); - } - /* - final P4FileRevision rev = RevisionDialog.requestRevision(vcs, file); - if (rev == null) { - return null; - } - return rev.getRevisionNumber(); - */ - throw new IllegalStateException("not implemented"); - } - -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4plugin.extension; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.vcs.diff.RevisionSelector; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; + +public class P4RevisionSelector implements RevisionSelector { + private static final Logger LOG = Logger.getInstance(P4RevisionSelector.class); + private final P4Vcs vcs; + + public P4RevisionSelector(final P4Vcs vcs) { + this.vcs = vcs; + } + + @Nullable + @Override + public VcsRevisionNumber selectNumber(final VirtualFile file) { + if (LOG.isDebugEnabled()) { + LOG.debug("Selecting version for file " + file); + } + /* + final P4FileRevision rev = RevisionDialog.requestRevision(vcs, file); + if (rev == null) { + return null; + } + return rev.getRevisionNumber(); + */ + throw new IllegalStateException("not implemented"); + } + +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4RollbackEnvironment.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4RollbackEnvironment.java index 6feb59fb..dc419495 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4RollbackEnvironment.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4RollbackEnvironment.java @@ -1,223 +1,223 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.extension; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.changes.Change; -import com.intellij.openapi.vcs.changes.ChangeListManager; -import com.intellij.openapi.vcs.rollback.RollbackEnvironment; -import com.intellij.openapi.vcs.rollback.RollbackProgressListener; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.vcsUtil.VcsUtil; -import net.groboclown.p4.server.api.ClientConfigRoot; -import net.groboclown.p4.server.api.P4CommandRunner; -import net.groboclown.p4.server.api.ProjectConfigRegistry; -import net.groboclown.p4.server.api.async.BlockingAnswer; -import net.groboclown.p4.server.api.commands.file.FetchFilesAction; -import net.groboclown.p4.server.api.commands.file.RevertFileAction; -import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; -import net.groboclown.p4.server.impl.util.ErrorCollectors; -import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.components.P4ServerComponent; -import net.groboclown.p4plugin.components.UserProjectPreferences; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -public class P4RollbackEnvironment implements RollbackEnvironment { - private static final Logger LOG = Logger.getInstance(P4RollbackEnvironment.class); - - private final Project project; - - P4RollbackEnvironment(@NotNull P4Vcs vcs) { - this.project = vcs.getProject(); - } - - @Override - public String getRollbackOperationName() { - return P4Bundle.message("rollback.action.name"); - } - - @Override - public void rollbackChanges(List changes, List vcsExceptions, @NotNull RollbackProgressListener listener) { - if (changes == null || changes.isEmpty() || project.isDisposed()) { - return; - } - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null || registry.isDisposed()) { - return; - } - if (ApplicationManager.getApplication().isDispatchThread()) { - vcsExceptions.add(new VcsException("Can only be run in a background thread")); - return; - } - - LOG.info("Rollback environment requested revert to " + changes); - - Set paths = new HashSet<>(); - for (Change change: changes) { - if (change != null) { - if (change.getAfterRevision() != null) { - paths.add(change.getAfterRevision().getFile()); - } - if (change.getBeforeRevision() != null) { - paths.add(change.getBeforeRevision().getFile()); - } - } - } - if (paths.isEmpty()) { - LOG.info("No affected files found for rollback. Not doing anything"); - return; - } - - List needsRefresh = new ArrayList<>(); - try { - // Having an issue with revert files happening randomly. See #181 - LOG.warn("Performing unconditional revert on " + paths); - LOG.debug("Unconditional Revert origin stack trace", new Throwable()); - - BlockingAnswer.createBlockFor(paths.stream() - .map((f) -> Pair.create(registry.getClientFor(f), f)) - .filter((p) -> p.first != null) - .map((p) -> P4ServerComponent - .perform(project, p.first.getClientConfig(), new RevertFileAction(p.second, false)) - .whenCompleted((r) -> { - LOG.info("Reverted " + p.second); - listener.accept(p.second); - VirtualFile vf = p.second.getVirtualFile(); - if (vf != null) { - needsRefresh.add(vf); - } - }) - .whenServerError((ex) -> listener.accept(p.second)) - .whenOffline(() -> listener.accept(p.second)) - ) - .collect(ErrorCollectors.collectActionErrors(vcsExceptions))) - .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - vcsExceptions.add(new VcsInterruptedException(e)); - } catch (P4CommandRunner.ServerResultException e) { - // This should never be reached, because of the collector. - vcsExceptions.add(e); - } - - LocalFileSystem lfs = LocalFileSystem.getInstance(); - lfs.refreshFiles(needsRefresh); - - // A refresh of the changes is sometimes needed. - ChangeListManager.getInstance(project).scheduleUpdate(); - } - - @Override - public void rollbackMissingFileDeletion(List files, List exceptions, RollbackProgressListener listener) { - forceSync(files, exceptions, listener); - } - - @Override - public void rollbackModifiedWithoutCheckout(List files, List exceptions, RollbackProgressListener listener) { - List paths = new ArrayList<>(files.size()); - for (VirtualFile vf: files) { - paths.add(VcsUtil.getFilePath(vf)); - } - forceSync(paths, exceptions, listener); - } - - - @Override - public void rollbackIfUnchanged(VirtualFile file) { - if (file == null || project.isDisposed()) { - return; - } - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null || registry.isDisposed()) { - LOG.info("Skipping revert for " + file + ": plugin not in a valid state"); - return; - } - FilePath fp = VcsUtil.getFilePath(file); - if (fp == null) { - LOG.info("Skipping revert for " + file + ": no FilePath found"); - return; - } - ClientConfigRoot root = registry.getClientFor(file); - if (root == null) { - LOG.info("SKipping revert for " + file + ": no P4 root found"); - return; - } - - LOG.info("Reverting if the file is unchanged: " + file, new Throwable()); - LOG.debug("Unchanged Rollback origin stack trace", new Throwable()); - - P4ServerComponent - .perform(project, root.getClientConfig(), new RevertFileAction(fp, true)) - .whenCompleted((r) -> ChangeListManager.getInstance(project).scheduleUpdate()); - } - - - /** - * Force a sync from server operation to overwrite local changes. - * @param files files to sync - * @param exceptions exceptions encountered - * @param listener listener on progress - */ - private void forceSync(List files, List exceptions, RollbackProgressListener listener) { - if (files.isEmpty() || project.isDisposed()) { - return; - } - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null || registry.isDisposed()) { - return; - } - - groupFilesByClient(registry, files) - .stream() - .map(e -> P4ServerComponent - .perform(project, e.getKey().getClientConfig(), new FetchFilesAction(e.getValue(), "", true)) - .whenCompleted((c) -> listener.accept(e.getValue())) - .whenServerError((ex) -> listener.accept(e.getValue())) - .whenOffline(() -> listener.accept(e.getValue()))) - .collect(ErrorCollectors.collectActionErrors(exceptions)) - .whenCompleted((c) -> LOG.info("Completed sync of files")) - .whenFailed((c) -> LOG.info("Failed to sync files")); - } - - private Set>> groupFilesByClient(@NotNull final ProjectConfigRegistry registry, - @NotNull final List files) { - // Note: can't use files.stream().collect(Collectors.groupingBy(registry::getClientFor)) - // because the root might be null for a file, and the groupingBy doesn't allow for null keys. - // See #195. - // This fix also moves the null root check into this method, out of the stream processing. - Map> ret = new HashMap<>(); - files.forEach((f) -> { - ClientConfigRoot root = registry.getClientFor(f); - if (root != null) { - ret.computeIfAbsent(root, (x) -> new ArrayList<>()).add(f); - } else { - LOG.info("Skipping revert for " + f + ": not in a Perforce root"); - } - }); - return ret.entrySet(); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.extension; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.rollback.RollbackEnvironment; +import com.intellij.openapi.vcs.rollback.RollbackProgressListener; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.vcsUtil.VcsUtil; +import net.groboclown.p4.server.api.ClientConfigRoot; +import net.groboclown.p4.server.api.P4CommandRunner; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import net.groboclown.p4.server.api.async.BlockingAnswer; +import net.groboclown.p4.server.api.commands.file.FetchFilesAction; +import net.groboclown.p4.server.api.commands.file.RevertFileAction; +import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; +import net.groboclown.p4.server.impl.util.ErrorCollectors; +import net.groboclown.p4plugin.P4Bundle; +import net.groboclown.p4plugin.components.P4ServerComponent; +import net.groboclown.p4plugin.components.UserProjectPreferences; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class P4RollbackEnvironment implements RollbackEnvironment { + private static final Logger LOG = Logger.getInstance(P4RollbackEnvironment.class); + + private final Project project; + + P4RollbackEnvironment(@NotNull P4Vcs vcs) { + this.project = vcs.getProject(); + } + + @Override + public String getRollbackOperationName() { + return P4Bundle.message("rollback.action.name"); + } + + @Override + public void rollbackChanges(List changes, List vcsExceptions, @NotNull RollbackProgressListener listener) { + if (changes == null || changes.isEmpty() || project.isDisposed()) { + return; + } + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null || registry.isDisposed()) { + return; + } + if (ApplicationManager.getApplication().isDispatchThread()) { + vcsExceptions.add(new VcsException("Can only be run in a background thread")); + return; + } + + LOG.info("Rollback environment requested revert to " + changes); + + Set paths = new HashSet<>(); + for (Change change: changes) { + if (change != null) { + if (change.getAfterRevision() != null) { + paths.add(change.getAfterRevision().getFile()); + } + if (change.getBeforeRevision() != null) { + paths.add(change.getBeforeRevision().getFile()); + } + } + } + if (paths.isEmpty()) { + LOG.info("No affected files found for rollback. Not doing anything"); + return; + } + + List needsRefresh = new ArrayList<>(); + try { + // Having an issue with revert files happening randomly. See #181 + LOG.warn("Performing unconditional revert on " + paths); + LOG.debug("Unconditional Revert origin stack trace", new Throwable()); + + BlockingAnswer.createBlockFor(paths.stream() + .map((f) -> Pair.create(registry.getClientFor(f), f)) + .filter((p) -> p.first != null) + .map((p) -> P4ServerComponent + .perform(project, p.first.getClientConfig(), new RevertFileAction(p.second, false)) + .whenCompleted((r) -> { + LOG.info("Reverted " + p.second); + listener.accept(p.second); + VirtualFile vf = p.second.getVirtualFile(); + if (vf != null) { + needsRefresh.add(vf); + } + }) + .whenServerError((ex) -> listener.accept(p.second)) + .whenOffline(() -> listener.accept(p.second)) + ) + .collect(ErrorCollectors.collectActionErrors(vcsExceptions))) + .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + vcsExceptions.add(new VcsInterruptedException(e)); + } catch (P4CommandRunner.ServerResultException e) { + // This should never be reached, because of the collector. + vcsExceptions.add(e); + } + + LocalFileSystem lfs = LocalFileSystem.getInstance(); + lfs.refreshFiles(needsRefresh); + + // A refresh of the changes is sometimes needed. + ChangeListManager.getInstance(project).scheduleUpdate(); + } + + @Override + public void rollbackMissingFileDeletion(List files, List exceptions, RollbackProgressListener listener) { + forceSync(files, exceptions, listener); + } + + @Override + public void rollbackModifiedWithoutCheckout(List files, List exceptions, RollbackProgressListener listener) { + List paths = new ArrayList<>(files.size()); + for (VirtualFile vf: files) { + paths.add(VcsUtil.getFilePath(vf)); + } + forceSync(paths, exceptions, listener); + } + + + @Override + public void rollbackIfUnchanged(VirtualFile file) { + if (file == null || project.isDisposed()) { + return; + } + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null || registry.isDisposed()) { + LOG.info("Skipping revert for " + file + ": plugin not in a valid state"); + return; + } + FilePath fp = VcsUtil.getFilePath(file); + if (fp == null) { + LOG.info("Skipping revert for " + file + ": no FilePath found"); + return; + } + ClientConfigRoot root = registry.getClientFor(file); + if (root == null) { + LOG.info("SKipping revert for " + file + ": no P4 root found"); + return; + } + + LOG.info("Reverting if the file is unchanged: " + file, new Throwable()); + LOG.debug("Unchanged Rollback origin stack trace", new Throwable()); + + P4ServerComponent + .perform(project, root.getClientConfig(), new RevertFileAction(fp, true)) + .whenCompleted((r) -> ChangeListManager.getInstance(project).scheduleUpdate()); + } + + + /** + * Force a sync from server operation to overwrite local changes. + * @param files files to sync + * @param exceptions exceptions encountered + * @param listener listener on progress + */ + private void forceSync(List files, List exceptions, RollbackProgressListener listener) { + if (files.isEmpty() || project.isDisposed()) { + return; + } + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null || registry.isDisposed()) { + return; + } + + groupFilesByClient(registry, files) + .stream() + .map(e -> P4ServerComponent + .perform(project, e.getKey().getClientConfig(), new FetchFilesAction(e.getValue(), "", true)) + .whenCompleted((c) -> listener.accept(e.getValue())) + .whenServerError((ex) -> listener.accept(e.getValue())) + .whenOffline(() -> listener.accept(e.getValue()))) + .collect(ErrorCollectors.collectActionErrors(exceptions)) + .whenCompleted((c) -> LOG.info("Completed sync of files")) + .whenFailed((c) -> LOG.info("Failed to sync files")); + } + + private Set>> groupFilesByClient(@NotNull final ProjectConfigRegistry registry, + @NotNull final List files) { + // Note: can't use files.stream().collect(Collectors.groupingBy(registry::getClientFor)) + // because the root might be null for a file, and the groupingBy doesn't allow for null keys. + // See #195. + // This fix also moves the null root check into this method, out of the stream processing. + Map> ret = new HashMap<>(); + files.forEach((f) -> { + ClientConfigRoot root = registry.getClientFor(f); + if (root != null) { + ret.computeIfAbsent(root, (x) -> new ArrayList<>()).add(f); + } else { + LOG.info("Skipping revert for " + f + ": not in a Perforce root"); + } + }); + return ret.entrySet(); + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4StatusUpdateEnvironment.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4StatusUpdateEnvironment.java index 6890b46f..1d7a652f 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4StatusUpdateEnvironment.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4StatusUpdateEnvironment.java @@ -1,96 +1,96 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.extension; - -import com.intellij.openapi.options.Configurable; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.update.FileGroup; -import com.intellij.openapi.vcs.update.SequentialUpdatesContext; -import com.intellij.openapi.vcs.update.UpdateEnvironment; -import com.intellij.openapi.vcs.update.UpdateSession; -import com.intellij.openapi.vcs.update.UpdatedFiles; -import com.perforce.p4java.core.file.FileAction; -import com.perforce.p4java.core.file.IExtendedFileSpec; -import net.groboclown.p4.server.api.values.P4FileAction; -import net.groboclown.p4plugin.P4Bundle; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * Checks the latest status of the files on the server - */ -public class P4StatusUpdateEnvironment - implements UpdateEnvironment { - private final Project project; - - public static final String OFFLINE_GROUP_ID = "p4.offline"; - - public P4StatusUpdateEnvironment(@NotNull Project project) { - this.project = project; - } - - - @Override - public void fillGroups(UpdatedFiles updatedFiles) { - updatedFiles.registerGroup(new FileGroup( - P4Bundle.message("update.status.offline"), - P4Bundle.message("update.status.offline"), - false, - OFFLINE_GROUP_ID, - false)); - } - - /** - * Performs the update/integrate/status operation. - * - * @param contentRoots the content roots for which update/integrate/status was requested by the user. - * @param updatedFiles the holder for the results of the update/integrate/status operation. - * @param progressIndicator the indicator that can be used to report the progress of the operation. - * @param context in-out parameter: a link between several sequential update operations (that can be triggered by one update action) - * @return the update session instance, which can be used to get information about errors that have occurred - * during the operation and to perform additional post-update processing. - * @throws ProcessCanceledException if the update operation has been cancelled by the user. Alternatively, - * cancellation can be reported by returning true from - * {@link UpdateSession#isCanceled}. - */ - @NotNull - @Override - public UpdateSession updateDirectories(@NotNull FilePath[] contentRoots, UpdatedFiles updatedFiles, - ProgressIndicator progressIndicator, @NotNull Ref context) - throws ProcessCanceledException { - throw new IllegalStateException("not implemented"); - } - - @Nullable - @Override - public Configurable createConfigurable(Collection files) { - // No UI for checking status - return null; - } - - @Override - public boolean validateOptions(Collection roots) { - return false; - } - -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.extension; + +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.update.FileGroup; +import com.intellij.openapi.vcs.update.SequentialUpdatesContext; +import com.intellij.openapi.vcs.update.UpdateEnvironment; +import com.intellij.openapi.vcs.update.UpdateSession; +import com.intellij.openapi.vcs.update.UpdatedFiles; +import com.perforce.p4java.core.file.FileAction; +import com.perforce.p4java.core.file.IExtendedFileSpec; +import net.groboclown.p4.server.api.values.P4FileAction; +import net.groboclown.p4plugin.P4Bundle; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Checks the latest status of the files on the server + */ +public class P4StatusUpdateEnvironment + implements UpdateEnvironment { + private final Project project; + + public static final String OFFLINE_GROUP_ID = "p4.offline"; + + public P4StatusUpdateEnvironment(@NotNull Project project) { + this.project = project; + } + + + @Override + public void fillGroups(UpdatedFiles updatedFiles) { + updatedFiles.registerGroup(new FileGroup( + P4Bundle.message("update.status.offline"), + P4Bundle.message("update.status.offline"), + false, + OFFLINE_GROUP_ID, + false)); + } + + /** + * Performs the update/integrate/status operation. + * + * @param contentRoots the content roots for which update/integrate/status was requested by the user. + * @param updatedFiles the holder for the results of the update/integrate/status operation. + * @param progressIndicator the indicator that can be used to report the progress of the operation. + * @param context in-out parameter: a link between several sequential update operations (that can be triggered by one update action) + * @return the update session instance, which can be used to get information about errors that have occurred + * during the operation and to perform additional post-update processing. + * @throws ProcessCanceledException if the update operation has been cancelled by the user. Alternatively, + * cancellation can be reported by returning true from + * {@link UpdateSession#isCanceled}. + */ + @NotNull + @Override + public UpdateSession updateDirectories(@NotNull FilePath[] contentRoots, UpdatedFiles updatedFiles, + ProgressIndicator progressIndicator, @NotNull Ref context) + throws ProcessCanceledException { + throw new IllegalStateException("not implemented"); + } + + @Nullable + @Override + public Configurable createConfigurable(Collection files) { + // No UI for checking status + return null; + } + + @Override + public boolean validateOptions(Collection roots) { + return false; + } + +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4SyncUpdateEnvironment.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4SyncUpdateEnvironment.java index b4ae5988..6272215e 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4SyncUpdateEnvironment.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4SyncUpdateEnvironment.java @@ -1,239 +1,239 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4plugin.extension; - -import com.intellij.openapi.options.Configurable; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.update.FileGroup; -import com.intellij.openapi.vcs.update.SequentialUpdatesContext; -import com.intellij.openapi.vcs.update.UpdateEnvironment; -import com.intellij.openapi.vcs.update.UpdateSession; -import com.intellij.openapi.vcs.update.UpdatedFiles; -import net.groboclown.p4.server.api.ClientConfigRoot; -import net.groboclown.p4.server.api.P4CommandRunner; -import net.groboclown.p4.server.api.P4ServerName; -import net.groboclown.p4.server.api.ProjectConfigRegistry; -import net.groboclown.p4.server.api.commands.file.FetchFilesAction; -import net.groboclown.p4.server.api.commands.file.FetchFilesResult; -import net.groboclown.p4.server.api.config.ClientConfig; -import net.groboclown.p4.server.api.config.ServerConfig; -import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; -import net.groboclown.p4.server.api.values.P4LocalFile; -import net.groboclown.p4plugin.components.P4ServerComponent; -import net.groboclown.p4plugin.components.UserProjectPreferences; -import net.groboclown.p4plugin.ui.DummyProgressIndicator; -import net.groboclown.p4plugin.ui.sync.SyncOptionConfigurable; -import net.groboclown.p4plugin.ui.sync.SyncOptions; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -public class P4SyncUpdateEnvironment - implements UpdateEnvironment { - private final Project project; - private SyncOptions options = SyncOptions.createDefaultSyncOptions(); - - P4SyncUpdateEnvironment(Project project) { - this.project = project; - } - - @Override - public void fillGroups(UpdatedFiles updatedFiles) { - // The plugin doesn't have any non-standard update file groups, so this call does nothing. - } - - @NotNull - @Override - public UpdateSession updateDirectories(@NotNull FilePath[] filePaths, UpdatedFiles updatedFiles, - ProgressIndicator pi, @NotNull Ref ref) - throws ProcessCanceledException { - ProgressIndicator progressIndicator = DummyProgressIndicator.nullSafe(pi); - progressIndicator.setFraction(0.0); - final SyncUpdateSession ret = new SyncUpdateSession(); - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null || registry.isDisposed()) { - ret.exceptions.add(new VcsException("Plugin disposed")); - return ret; - } - - final Map groups = collateByFileGroupId(updatedFiles.getTopLevelGroups(), null); - final Map> filesByRoot = new HashMap<>(); - for (FilePath filePath : filePaths) { - filesByRoot.computeIfAbsent(registry.getClientFor(filePath), k -> new ArrayList<>()).add(filePath); - } - final double inc = filesByRoot.isEmpty() ? 1.0 : (1.0 / filesByRoot.size()); - final double[] pos = { 0.0 }; - - filesByRoot.forEach((key, value) -> { - try { - FetchFilesResult res = - P4ServerComponent - .perform(project, key.getClientConfig(), - new FetchFilesAction(value, options.getSpecAnnotation(), - options.isForce())) - .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), - TimeUnit.MILLISECONDS); - updateForResult(res, groups); - pos[0] += inc; - progressIndicator.setFraction(pos[0]); - } catch (P4CommandRunner.ServerResultException e) { - ret.exceptions.add(e); - } catch (InterruptedException e) { - ret.exceptions.add(new VcsInterruptedException(e)); - } - }); - return ret; - } - - private void updateForResult(@Nullable FetchFilesResult res, Map groups) { - if (res == null) { - return; - } - for (P4LocalFile file : res.getFiles()) { - - // hardRefresh and refresh are deprecated now. We don't need to do that anymore. - // path.hardRefresh(); - - String groupId = getGroupIdFor(file); - FileGroup group = groups.get(groupId); - if (group != null) { - group.add(file.getFilePath().getIOFile().getAbsolutePath(), - P4Vcs.getKey(), file.getHaveRevision()); - } - - } - } - - private String getGroupIdFor(@NotNull final P4LocalFile file) { - switch (file.getFileAction()) { - case ADD: - case ADD_EDIT: - return FileGroup.LOCALLY_ADDED_ID; - - case REOPEN: - case EDIT: - return FileGroup.MODIFIED_ID; - - case MOVE_ADD_EDIT: - case MOVE_ADD: - return FileGroup.LOCALLY_ADDED_ID; - - case EDIT_RESOLVED: - return FileGroup.MERGED_ID; - - case INTEGRATE: - return FileGroup.MERGED_ID; - - case DELETE: - case MOVE_DELETE: - return FileGroup.LOCALLY_REMOVED_ID; - - case REVERTED: - return FileGroup.RESTORED_ID; - - case MOVE_EDIT: - return FileGroup.MERGED_ID; - - case NONE: - return FileGroup.UPDATED_ID; - - case UNKNOWN: - default: - return FileGroup.UNKNOWN_ID; - } - } - - - @Nullable - @Override - public Configurable createConfigurable(Collection collection) { - // Note the way this is currently implemented: synchronize options will always be reset. - options = SyncOptions.createDefaultSyncOptions(); - - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null || registry.isDisposed()) { - return new SyncOptionConfigurable(project, options, Collections.emptyList()); - } - Map configMap = new HashMap<>(); - collection.forEach((fp) -> { - ClientConfigRoot clientRoot = registry.getClientFor(fp); - if (clientRoot != null) { - configMap.put(clientRoot.getServerConfig().getServerName(), clientRoot.getClientConfig()); - } - }); - return new SyncOptionConfigurable(project, options, configMap.values()); - } - - @Override - public boolean validateOptions(Collection collection) { - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - if (registry == null || registry.isDisposed()) { - return false; - } - for (FilePath filePath : collection) { - ClientConfigRoot root = registry.getClientFor(filePath); - if (root != null) { - return true; - } - } - return false; - } - - private Map collateByFileGroupId(final List groups, Map sorted) { - if (sorted == null) { - sorted = new HashMap<>(); - } - - for (FileGroup group : groups) { - sorted.put(group.getId(), group); - sorted = collateByFileGroupId(group.getChildren(), sorted); - } - - return sorted; - } - - static class SyncUpdateSession implements UpdateSession { - private boolean cancelled = false; - private List exceptions = new ArrayList<>(); - - @NotNull - @Override - public List getExceptions() { - return exceptions; - } - - @Override - public void onRefreshFilesCompleted() { - // TODO if any cache needs update, call it from here. - } - - @Override - public boolean isCanceled() { - return cancelled; - } - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4plugin.extension; + +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.update.FileGroup; +import com.intellij.openapi.vcs.update.SequentialUpdatesContext; +import com.intellij.openapi.vcs.update.UpdateEnvironment; +import com.intellij.openapi.vcs.update.UpdateSession; +import com.intellij.openapi.vcs.update.UpdatedFiles; +import net.groboclown.p4.server.api.ClientConfigRoot; +import net.groboclown.p4.server.api.P4CommandRunner; +import net.groboclown.p4.server.api.P4ServerName; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import net.groboclown.p4.server.api.commands.file.FetchFilesAction; +import net.groboclown.p4.server.api.commands.file.FetchFilesResult; +import net.groboclown.p4.server.api.config.ClientConfig; +import net.groboclown.p4.server.api.config.ServerConfig; +import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; +import net.groboclown.p4.server.api.values.P4LocalFile; +import net.groboclown.p4plugin.components.P4ServerComponent; +import net.groboclown.p4plugin.components.UserProjectPreferences; +import net.groboclown.p4plugin.ui.DummyProgressIndicator; +import net.groboclown.p4plugin.ui.sync.SyncOptionConfigurable; +import net.groboclown.p4plugin.ui.sync.SyncOptions; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class P4SyncUpdateEnvironment + implements UpdateEnvironment { + private final Project project; + private SyncOptions options = SyncOptions.createDefaultSyncOptions(); + + P4SyncUpdateEnvironment(Project project) { + this.project = project; + } + + @Override + public void fillGroups(UpdatedFiles updatedFiles) { + // The plugin doesn't have any non-standard update file groups, so this call does nothing. + } + + @NotNull + @Override + public UpdateSession updateDirectories(@NotNull FilePath[] filePaths, UpdatedFiles updatedFiles, + ProgressIndicator pi, @NotNull Ref ref) + throws ProcessCanceledException { + ProgressIndicator progressIndicator = DummyProgressIndicator.nullSafe(pi); + progressIndicator.setFraction(0.0); + final SyncUpdateSession ret = new SyncUpdateSession(); + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null || registry.isDisposed()) { + ret.exceptions.add(new VcsException("Plugin disposed")); + return ret; + } + + final Map groups = collateByFileGroupId(updatedFiles.getTopLevelGroups(), null); + final Map> filesByRoot = new HashMap<>(); + for (FilePath filePath : filePaths) { + filesByRoot.computeIfAbsent(registry.getClientFor(filePath), k -> new ArrayList<>()).add(filePath); + } + final double inc = filesByRoot.isEmpty() ? 1.0 : (1.0 / filesByRoot.size()); + final double[] pos = { 0.0 }; + + filesByRoot.forEach((key, value) -> { + try { + FetchFilesResult res = + P4ServerComponent + .perform(project, key.getClientConfig(), + new FetchFilesAction(value, options.getSpecAnnotation(), + options.isForce())) + .blockingGet(UserProjectPreferences.getLockWaitTimeoutMillis(project), + TimeUnit.MILLISECONDS); + updateForResult(res, groups); + pos[0] += inc; + progressIndicator.setFraction(pos[0]); + } catch (P4CommandRunner.ServerResultException e) { + ret.exceptions.add(e); + } catch (InterruptedException e) { + ret.exceptions.add(new VcsInterruptedException(e)); + } + }); + return ret; + } + + private void updateForResult(@Nullable FetchFilesResult res, Map groups) { + if (res == null) { + return; + } + for (P4LocalFile file : res.getFiles()) { + + // hardRefresh and refresh are deprecated now. We don't need to do that anymore. + // path.hardRefresh(); + + String groupId = getGroupIdFor(file); + FileGroup group = groups.get(groupId); + if (group != null) { + group.add(file.getFilePath().getIOFile().getAbsolutePath(), + P4Vcs.getKey(), file.getHaveRevision()); + } + + } + } + + private String getGroupIdFor(@NotNull final P4LocalFile file) { + switch (file.getFileAction()) { + case ADD: + case ADD_EDIT: + return FileGroup.LOCALLY_ADDED_ID; + + case REOPEN: + case EDIT: + return FileGroup.MODIFIED_ID; + + case MOVE_ADD_EDIT: + case MOVE_ADD: + return FileGroup.LOCALLY_ADDED_ID; + + case EDIT_RESOLVED: + return FileGroup.MERGED_ID; + + case INTEGRATE: + return FileGroup.MERGED_ID; + + case DELETE: + case MOVE_DELETE: + return FileGroup.LOCALLY_REMOVED_ID; + + case REVERTED: + return FileGroup.RESTORED_ID; + + case MOVE_EDIT: + return FileGroup.MERGED_ID; + + case NONE: + return FileGroup.UPDATED_ID; + + case UNKNOWN: + default: + return FileGroup.UNKNOWN_ID; + } + } + + + @Nullable + @Override + public Configurable createConfigurable(Collection collection) { + // Note the way this is currently implemented: synchronize options will always be reset. + options = SyncOptions.createDefaultSyncOptions(); + + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null || registry.isDisposed()) { + return new SyncOptionConfigurable(project, options, Collections.emptyList()); + } + Map configMap = new HashMap<>(); + collection.forEach((fp) -> { + ClientConfigRoot clientRoot = registry.getClientFor(fp); + if (clientRoot != null) { + configMap.put(clientRoot.getServerConfig().getServerName(), clientRoot.getClientConfig()); + } + }); + return new SyncOptionConfigurable(project, options, configMap.values()); + } + + @Override + public boolean validateOptions(Collection collection) { + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + if (registry == null || registry.isDisposed()) { + return false; + } + for (FilePath filePath : collection) { + ClientConfigRoot root = registry.getClientFor(filePath); + if (root != null) { + return true; + } + } + return false; + } + + private Map collateByFileGroupId(final List groups, Map sorted) { + if (sorted == null) { + sorted = new HashMap<>(); + } + + for (FileGroup group : groups) { + sorted.put(group.getId(), group); + sorted = collateByFileGroupId(group.getChildren(), sorted); + } + + return sorted; + } + + static class SyncUpdateSession implements UpdateSession { + private boolean cancelled = false; + private List exceptions = new ArrayList<>(); + + @NotNull + @Override + public List getExceptions() { + return exceptions; + } + + @Override + public void onRefreshFilesCompleted() { + // TODO if any cache needs update, call it from here. + } + + @Override + public boolean isCanceled() { + return cancelled; + } + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4VFSListener.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4VFSListener.java index c13db12c..65b5f8c6 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4VFSListener.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/extension/P4VFSListener.java @@ -1,283 +1,283 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.extension; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsVFSListener; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.vcsUtil.VcsFileUtil; -import com.intellij.vcsUtil.VcsUtil; -import net.groboclown.p4.server.api.ClientConfigRoot; -import net.groboclown.p4.server.api.ClientServerRef; -import net.groboclown.p4.server.api.P4CommandRunner; -import net.groboclown.p4.server.api.ProjectConfigRegistry; -import net.groboclown.p4.server.api.commands.file.AddEditAction; -import net.groboclown.p4.server.api.commands.file.DeleteFileAction; -import net.groboclown.p4.server.api.commands.file.MoveFileAction; -import net.groboclown.p4.server.api.values.P4ChangelistId; -import net.groboclown.p4.server.impl.commands.DoneActionAnswer; -import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.components.P4ServerComponent; -import net.groboclown.p4plugin.util.ChangelistUtil; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import static net.groboclown.p4.server.api.values.P4FileType.getFileType; - -/** - * Handles the file change requests: add, edit, delete, move. - */ -public class P4VFSListener extends VcsVFSListener { - private static final Logger LOG = Logger.getInstance(VcsVFSListener.class); - - P4VFSListener(@NotNull Project project, - @NotNull P4Vcs vcs) { - super(project, vcs); - } - - @Override - protected void performAdding( - @NotNull final Collection addedFiles, - @NotNull final Map copyFromMap) { - - // TODO all add requests must go through the IgnoreFileSet. - - // Copies are handled as add commands, so we don't need to worry about - // performing integrations - this is the common use case desired by the - // user - they want to use another file as a template for work. - // This could eventually be supported as a feature flag if the user really - // wants it. If they are implemented, then the "add" files should first be - // pruned of the "integrate" files. - - // Bug #102: The keys in the "copyFromMap" will also be in the "addedFiles" - // list. If copy rather than integrate is supported, this loop will need to be - // changed. - Map activeChangelistIds = getActiveChangelistIds(); - for (VirtualFile file : addedFiles) { - if (file.isDirectory()) { - LOG.warn("Attempted to add a directory " + file); - continue; - } - ClientConfigRoot root = getClientFor(file); - if (root != null) { - FilePath fp = VcsUtil.getFilePath(file); - P4ChangelistId id = getActiveChangelistFor(root, activeChangelistIds); - if (LOG.isDebugEnabled()) { - LOG.debug("Opening for add/edit: " + fp + " (@" + id + ")"); - } - P4ServerComponent - .perform(myProject, root.getClientConfig(), - new AddEditAction(fp, getFileType(fp), id, (String) null)) - .whenAnyState(() -> { - if (LOG.isDebugEnabled()) { - LOG.debug("Completed call to add " + addedFiles + "; copy " + copyFromMap); - } - }); - } else { - LOG.info("Skipped adding " + file + "; not under known P4 client"); - } - } - } - - @Override - protected void performDeletion(List filesToDelete) { - VcsFileUtil.markFilesDirty(myProject, filesToDelete); - final List affectedFiles = filesToDelete.stream().map(FilePath::getVirtualFile) - .collect(Collectors.toList()); - - Map activeChangelistIds = getActiveChangelistIds(); - for (FilePath filePath : filesToDelete) { - ClientConfigRoot root = getClientFor(filePath); - if (root != null) { - P4ChangelistId id = getActiveChangelistFor(root, activeChangelistIds); - if (LOG.isDebugEnabled()) { - LOG.debug("Opening for delete: " + filePath + " (@" + id + ")"); - } - P4ServerComponent - .perform(myProject, root.getClientConfig(), - new DeleteFileAction(filePath, id)) - .whenAnyState(() -> { - if (LOG.isDebugEnabled()) { - LOG.debug("Completed call to delete file " + filesToDelete); - } - }); - } else { - LOG.info("Skipped deleting " + filePath + "; not under known P4 client"); - } - } - } - - @Override - protected void performMoveRename(List movedFiles) { - Set allFiles = new HashSet<>(movedFiles.size()); - Map activeChangelistIds = getActiveChangelistIds(); - - // All the move operations need to complete before we can request an update to the changelist view - List> pendingAnswers = new ArrayList<>(); - - for (MovedFileInfo movedFile : movedFiles) { - LOG.info("Moving file `" + movedFile.myOldPath + "` to `" + movedFile.myNewPath + "`"); - final FilePath src = VcsUtil.getFilePath(movedFile.myOldPath); - if (src.isDirectory()) { - LOG.warn("Attempted to move directory " + src + "; refusing move."); - continue; - } - - // For a single move request, all requests must be run serially. - P4CommandRunner.ActionAnswer pending = new DoneActionAnswer<>(null); - - final FilePath tgt; - if (VcsUtil.getFilePath(movedFile.myNewPath).isDirectory()) { - LOG.info("Moving file into directory " + movedFile.myNewPath); - tgt = VcsUtil.getFilePath(new File(VcsUtil.getFilePath(movedFile.myNewPath).getIOFile(), src.getName())); - } else { - tgt = VcsUtil.getFilePath(movedFile.myNewPath); - } - allFiles.add(src); - allFiles.add(tgt); - ClientConfigRoot srcRoot = getClientFor(src); - ClientConfigRoot tgtRoot = getClientFor(tgt); - if (srcRoot != null && tgtRoot != null && - srcRoot.getClientConfig().getClientServerRef().equals(tgtRoot.getClientConfig().getClientServerRef())) { - // A real P4 move operation. - P4ChangelistId id = getActiveChangelistFor(srcRoot, activeChangelistIds); - if (LOG.isDebugEnabled()) { - LOG.debug("Opening for move: " + src + " -> " + tgt + " (@" + id + ")"); - } - pending = pending.mapActionAsync((x) -> - P4ServerComponent.perform(myProject, srcRoot.getClientConfig(), - new MoveFileAction(src, tgt, id))); - } else { - // Not a move operation, because they aren't in the same perforce client. - - if (srcRoot != null) { - // Source is in P4, so delete it. - P4ChangelistId id = getActiveChangelistFor(srcRoot, activeChangelistIds); - if (LOG.isDebugEnabled()) { - LOG.debug("Opening for move/delete: " + src + " (@" + id + ")"); - } - pending = pending.mapActionAsync((x) -> - P4ServerComponent.perform(myProject, srcRoot.getClientConfig(), - new DeleteFileAction(src, id))); - } - - if (tgtRoot != null) { - if (srcRoot != null && - srcRoot.getServerConfig().getServerName().equals(tgtRoot.getServerConfig().getServerName())) { - // Source and target are on the same server. We can perform an integrate here. - // TODO perform an integrate - } - // Just a regular Perforce add - P4ChangelistId id = getActiveChangelistFor(tgtRoot, activeChangelistIds); - if (LOG.isDebugEnabled()) { - LOG.debug("Opening for move/add-edit: " + tgt + " (@" + id + ")"); - } - pending = pending.mapActionAsync((x) -> - P4ServerComponent.perform(myProject, tgtRoot.getClientConfig(), - new AddEditAction(tgt, null, id, (String) null))); - } - } - - pending.whenAnyState(() -> { - if (LOG.isDebugEnabled()) { - LOG.debug("Completed move request for " + src + " -> " + tgt); - } - }); - - pendingAnswers.add(pending); - } - - /* - // This method is called from the EDT, so DO NOT WAIT IN THIS THREAD. - // Wait for the pending requests to complete before marking files as dirty. - for (P4CommandRunner.ActionAnswer pendingAnswer : pendingAnswers) { - try { - pendingAnswer.waitForCompletion(UserProjectPreferences.getLockWaitTimeoutMillis(myProject), - TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - LOG.warn("Interruption waiting on move commands to complete.", e); - } - } - - // Make sure any potential null isn't in the set. - allFiles.remove(null); - VcsFileUtil.markFilesDirty(myProject, new ArrayList<>(allFiles)); - */ - } - - @Override - protected String getAddTitle() { - return P4Bundle.message("vfs.add.files"); - } - - @Override - protected String getSingleFileAddTitle() { - return P4Bundle.message("vfs.add.file"); - } - - @Override - protected String getSingleFileAddPromptTemplate() { - return P4Bundle.getString("vfs.add.single.prompt"); - } - - @Override - protected String getDeleteTitle() { - return P4Bundle.message("vfs.delete.files"); - } - - @Override - protected String getSingleFileDeleteTitle() { - return P4Bundle.message("vfs.delete.file"); - } - - @Override - protected String getSingleFileDeletePromptTemplate() { - return P4Bundle.getString("vfs.delete.single.prompt"); - } - - @Override - protected boolean isDirectoryVersioningSupported() { - return false; - } - - - private Map getActiveChangelistIds() { - return ChangelistUtil.getActiveChangelistIds(myProject); - } - - @NotNull - private P4ChangelistId getActiveChangelistFor(ClientConfigRoot root, Map ids) { - return ChangelistUtil.getActiveChangelistFor(root, ids); - } - - private ClientConfigRoot getClientFor(FilePath file) { - ProjectConfigRegistry reg = ProjectConfigRegistry.getInstance(myProject); - return reg == null ? null : reg.getClientFor(file); - } - - private ClientConfigRoot getClientFor(VirtualFile file) { - ProjectConfigRegistry reg = ProjectConfigRegistry.getInstance(myProject); - return reg == null ? null : reg.getClientFor(file); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.extension; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsVFSListener; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.vcsUtil.VcsFileUtil; +import com.intellij.vcsUtil.VcsUtil; +import net.groboclown.p4.server.api.ClientConfigRoot; +import net.groboclown.p4.server.api.ClientServerRef; +import net.groboclown.p4.server.api.P4CommandRunner; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import net.groboclown.p4.server.api.commands.file.AddEditAction; +import net.groboclown.p4.server.api.commands.file.DeleteFileAction; +import net.groboclown.p4.server.api.commands.file.MoveFileAction; +import net.groboclown.p4.server.api.values.P4ChangelistId; +import net.groboclown.p4.server.impl.commands.DoneActionAnswer; +import net.groboclown.p4plugin.P4Bundle; +import net.groboclown.p4plugin.components.P4ServerComponent; +import net.groboclown.p4plugin.util.ChangelistUtil; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static net.groboclown.p4.server.api.values.P4FileType.getFileType; + +/** + * Handles the file change requests: add, edit, delete, move. + */ +public class P4VFSListener extends VcsVFSListener { + private static final Logger LOG = Logger.getInstance(VcsVFSListener.class); + + P4VFSListener(@NotNull Project project, + @NotNull P4Vcs vcs) { + super(project, vcs); + } + + @Override + protected void performAdding( + @NotNull final Collection addedFiles, + @NotNull final Map copyFromMap) { + + // TODO all add requests must go through the IgnoreFileSet. + + // Copies are handled as add commands, so we don't need to worry about + // performing integrations - this is the common use case desired by the + // user - they want to use another file as a template for work. + // This could eventually be supported as a feature flag if the user really + // wants it. If they are implemented, then the "add" files should first be + // pruned of the "integrate" files. + + // Bug #102: The keys in the "copyFromMap" will also be in the "addedFiles" + // list. If copy rather than integrate is supported, this loop will need to be + // changed. + Map activeChangelistIds = getActiveChangelistIds(); + for (VirtualFile file : addedFiles) { + if (file.isDirectory()) { + LOG.warn("Attempted to add a directory " + file); + continue; + } + ClientConfigRoot root = getClientFor(file); + if (root != null) { + FilePath fp = VcsUtil.getFilePath(file); + P4ChangelistId id = getActiveChangelistFor(root, activeChangelistIds); + if (LOG.isDebugEnabled()) { + LOG.debug("Opening for add/edit: " + fp + " (@" + id + ")"); + } + P4ServerComponent + .perform(myProject, root.getClientConfig(), + new AddEditAction(fp, getFileType(fp), id, (String) null)) + .whenAnyState(() -> { + if (LOG.isDebugEnabled()) { + LOG.debug("Completed call to add " + addedFiles + "; copy " + copyFromMap); + } + }); + } else { + LOG.info("Skipped adding " + file + "; not under known P4 client"); + } + } + } + + @Override + protected void performDeletion(List filesToDelete) { + VcsFileUtil.markFilesDirty(myProject, filesToDelete); + final List affectedFiles = filesToDelete.stream().map(FilePath::getVirtualFile) + .collect(Collectors.toList()); + + Map activeChangelistIds = getActiveChangelistIds(); + for (FilePath filePath : filesToDelete) { + ClientConfigRoot root = getClientFor(filePath); + if (root != null) { + P4ChangelistId id = getActiveChangelistFor(root, activeChangelistIds); + if (LOG.isDebugEnabled()) { + LOG.debug("Opening for delete: " + filePath + " (@" + id + ")"); + } + P4ServerComponent + .perform(myProject, root.getClientConfig(), + new DeleteFileAction(filePath, id)) + .whenAnyState(() -> { + if (LOG.isDebugEnabled()) { + LOG.debug("Completed call to delete file " + filesToDelete); + } + }); + } else { + LOG.info("Skipped deleting " + filePath + "; not under known P4 client"); + } + } + } + + @Override + protected void performMoveRename(List movedFiles) { + Set allFiles = new HashSet<>(movedFiles.size()); + Map activeChangelistIds = getActiveChangelistIds(); + + // All the move operations need to complete before we can request an update to the changelist view + List> pendingAnswers = new ArrayList<>(); + + for (MovedFileInfo movedFile : movedFiles) { + LOG.info("Moving file `" + movedFile.myOldPath + "` to `" + movedFile.myNewPath + "`"); + final FilePath src = VcsUtil.getFilePath(movedFile.myOldPath); + if (src.isDirectory()) { + LOG.warn("Attempted to move directory " + src + "; refusing move."); + continue; + } + + // For a single move request, all requests must be run serially. + P4CommandRunner.ActionAnswer pending = new DoneActionAnswer<>(null); + + final FilePath tgt; + if (VcsUtil.getFilePath(movedFile.myNewPath).isDirectory()) { + LOG.info("Moving file into directory " + movedFile.myNewPath); + tgt = VcsUtil.getFilePath(new File(VcsUtil.getFilePath(movedFile.myNewPath).getIOFile(), src.getName())); + } else { + tgt = VcsUtil.getFilePath(movedFile.myNewPath); + } + allFiles.add(src); + allFiles.add(tgt); + ClientConfigRoot srcRoot = getClientFor(src); + ClientConfigRoot tgtRoot = getClientFor(tgt); + if (srcRoot != null && tgtRoot != null && + srcRoot.getClientConfig().getClientServerRef().equals(tgtRoot.getClientConfig().getClientServerRef())) { + // A real P4 move operation. + P4ChangelistId id = getActiveChangelistFor(srcRoot, activeChangelistIds); + if (LOG.isDebugEnabled()) { + LOG.debug("Opening for move: " + src + " -> " + tgt + " (@" + id + ")"); + } + pending = pending.mapActionAsync((x) -> + P4ServerComponent.perform(myProject, srcRoot.getClientConfig(), + new MoveFileAction(src, tgt, id))); + } else { + // Not a move operation, because they aren't in the same perforce client. + + if (srcRoot != null) { + // Source is in P4, so delete it. + P4ChangelistId id = getActiveChangelistFor(srcRoot, activeChangelistIds); + if (LOG.isDebugEnabled()) { + LOG.debug("Opening for move/delete: " + src + " (@" + id + ")"); + } + pending = pending.mapActionAsync((x) -> + P4ServerComponent.perform(myProject, srcRoot.getClientConfig(), + new DeleteFileAction(src, id))); + } + + if (tgtRoot != null) { + if (srcRoot != null && + srcRoot.getServerConfig().getServerName().equals(tgtRoot.getServerConfig().getServerName())) { + // Source and target are on the same server. We can perform an integrate here. + // TODO perform an integrate + } + // Just a regular Perforce add + P4ChangelistId id = getActiveChangelistFor(tgtRoot, activeChangelistIds); + if (LOG.isDebugEnabled()) { + LOG.debug("Opening for move/add-edit: " + tgt + " (@" + id + ")"); + } + pending = pending.mapActionAsync((x) -> + P4ServerComponent.perform(myProject, tgtRoot.getClientConfig(), + new AddEditAction(tgt, null, id, (String) null))); + } + } + + pending.whenAnyState(() -> { + if (LOG.isDebugEnabled()) { + LOG.debug("Completed move request for " + src + " -> " + tgt); + } + }); + + pendingAnswers.add(pending); + } + + /* + // This method is called from the EDT, so DO NOT WAIT IN THIS THREAD. + // Wait for the pending requests to complete before marking files as dirty. + for (P4CommandRunner.ActionAnswer pendingAnswer : pendingAnswers) { + try { + pendingAnswer.waitForCompletion(UserProjectPreferences.getLockWaitTimeoutMillis(myProject), + TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + LOG.warn("Interruption waiting on move commands to complete.", e); + } + } + + // Make sure any potential null isn't in the set. + allFiles.remove(null); + VcsFileUtil.markFilesDirty(myProject, new ArrayList<>(allFiles)); + */ + } + + @Override + protected String getAddTitle() { + return P4Bundle.message("vfs.add.files"); + } + + @Override + protected String getSingleFileAddTitle() { + return P4Bundle.message("vfs.add.file"); + } + + @Override + protected String getSingleFileAddPromptTemplate() { + return P4Bundle.getString("vfs.add.single.prompt"); + } + + @Override + protected String getDeleteTitle() { + return P4Bundle.message("vfs.delete.files"); + } + + @Override + protected String getSingleFileDeleteTitle() { + return P4Bundle.message("vfs.delete.file"); + } + + @Override + protected String getSingleFileDeletePromptTemplate() { + return P4Bundle.getString("vfs.delete.single.prompt"); + } + + @Override + protected boolean isDirectoryVersioningSupported() { + return false; + } + + + private Map getActiveChangelistIds() { + return ChangelistUtil.getActiveChangelistIds(myProject); + } + + @NotNull + private P4ChangelistId getActiveChangelistFor(ClientConfigRoot root, Map ids) { + return ChangelistUtil.getActiveChangelistFor(root, ids); + } + + private ClientConfigRoot getClientFor(FilePath file) { + ProjectConfigRegistry reg = ProjectConfigRegistry.getInstance(myProject); + return reg == null ? null : reg.getClientFor(file); + } + + private ClientConfigRoot getClientFor(VirtualFile file) { + ProjectConfigRegistry reg = ProjectConfigRegistry.getInstance(myProject); + return reg == null ? null : reg.getClientFor(file); + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/P4ChangeListDecorator.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/P4ChangeListDecorator.java index 39675f7b..c0a8073b 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/P4ChangeListDecorator.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/P4ChangeListDecorator.java @@ -1,266 +1,266 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.ui; - -import com.intellij.openapi.components.ProjectComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vcs.changes.ChangeListDecorator; -import com.intellij.openapi.vcs.changes.LocalChangeList; -import com.intellij.ui.ColoredTreeCellRenderer; -import com.intellij.ui.SimpleTextAttributes; -import net.groboclown.p4.server.api.ClientServerRef; -import net.groboclown.p4.server.api.ProjectConfigRegistry; -import net.groboclown.p4.server.api.cache.IdeChangelistMap; -import net.groboclown.p4.server.api.cache.IdeFileMap; -import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; -import net.groboclown.p4.server.api.messagebus.ErrorEvent; -import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; -import net.groboclown.p4.server.api.values.P4ChangelistId; -import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.components.CacheComponent; -import net.groboclown.p4plugin.extension.P4Vcs; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -public class P4ChangeListDecorator implements ChangeListDecorator, ProjectComponent { - private static final Logger LOG = Logger.getInstance(P4ChangeListDecorator.class); - - private final Project project; - - public static class ChangelistConnectionInfo { - private final List validIds = new ArrayList<>(); - private final List defaults = new ArrayList<>(); - private final List unsynced = new ArrayList<>(); - private final List unknowns = new ArrayList<>(); - private final List offline = new ArrayList<>(); - private final boolean hasOneServer; - private final int serverCount; - - ChangelistConnectionInfo(int serverCount) { - this.hasOneServer = serverCount == 1; - this.serverCount = serverCount; - } - - void addOffline(@NotNull P4ChangelistId p4cl) { - offline.add(p4cl.getClientServerRef()); - } - - void addOnline(@NotNull P4ChangelistId p4cl) { - switch (p4cl.getState()) { - case NUMBERED: - validIds.add(p4cl); - break; - case PENDING_CREATION: - unsynced.add(p4cl.getClientServerRef()); - break; - case DEFAULT: - defaults.add(p4cl.getClientServerRef()); - break; - default: - unknowns.add(p4cl.getClientServerRef()); - } - } - } - - @SuppressWarnings("WeakerAccess") - public P4ChangeListDecorator(@NotNull Project project) { - this.project = project; - } - - @Override - public void decorateChangeList(LocalChangeList changeList, ColoredTreeCellRenderer cellRenderer, boolean selected, boolean expanded, boolean hasFocus) { - if (isProjectInvalid()) { - return; - } - CacheComponent cache = CacheComponent.getInstance(project); - ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); - Pair - openedCache = cache.getServerOpenedCache(); - try { - Collection p4Changes = openedCache.first.getP4ChangesFor(changeList); - if (LOG.isDebugEnabled()) { - LOG.debug("Change " + changeList + " has p4 changes " + p4Changes); - } - if (p4Changes.isEmpty()) { - // Early exit - return; - } - ChangelistConnectionInfo info = new ChangelistConnectionInfo(p4Changes.size()); - for (P4ChangelistId p4Change: p4Changes) { - if (registry != null && registry.isOnline(p4Change.getClientServerRef())) { - info.addOnline(p4Change); - } else { - info.addOffline(p4Change); - } - } - decorateInfo(info, cellRenderer); - } catch (InterruptedException e) { - InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>(new VcsInterruptedException(e))); - } - } - - private static void decorateInfo(@NotNull ChangelistConnectionInfo info, - @NotNull ColoredTreeCellRenderer cellRenderer) { - if (info.serverCount <= 0) { - return; - } - - boolean hasOne = false; - - if (info.hasOneServer && info.validIds.size() == 1) { - hasOne = true; - // Cannot do all servers here, because each connection's corresponding changelist - // is most probably different. - cellRenderer.append(P4Bundle.message("changelist.render", info.validIds.get(0).getChangelistId()), - SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); - } else if (! info.validIds.isEmpty()) { - hasOne = true; - Iterator iter = info.validIds.iterator(); - P4ChangelistId next = iter.next(); - StringBuilder sb = new StringBuilder(P4Bundle.message("changelist.render-many.first", - next.getClientname(), next.getChangelistId())); - while (iter.hasNext()) { - next = iter.next(); - sb.append(P4Bundle.message("changelist.render-many.after", - next.getClientname(), next.getChangelistId())); - } - cellRenderer.append(sb.toString(), SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); - } - - if (info.defaults.size() == info.serverCount) { - String msg = P4Bundle.message("changelist.decorator.default"); - cellRenderer.append(msg, SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); - hasOne = true; - } else if (! info.defaults.isEmpty()) { - StringBuilder sb = new StringBuilder(); - Iterator iter = info.defaults.iterator(); - ClientServerRef next = iter.next(); - if (hasOne) { - sb.append(P4Bundle.message("changelist.decorator.default.second.first", next.getClientName())); - } else { - sb.append(P4Bundle.message("changelist.decorator.default.first.first", next.getClientName())); - } - while (iter.hasNext()) { - next = iter.next(); - sb.append(P4Bundle.message("changelist.decorator.default.middle", next.getClientName())); - } - sb.append(P4Bundle.message("changelist.decorator.default.end")); - cellRenderer.append(sb.toString(), SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); - hasOne = true; - } - - if (info.unknowns.size() == info.serverCount) { - String msg = P4Bundle.message("changelist.decorator.unknowns"); - cellRenderer.append(msg, SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); - hasOne = true; - } else if (! info.unknowns.isEmpty()) { - Iterator iter = info.unknowns.iterator(); - ClientServerRef next = iter.next(); - StringBuilder sb = new StringBuilder(); - if (hasOne) { - sb.append(P4Bundle.message("changelist.decorator.unknowns.second.first", next.getClientName())); - } else { - sb.append(P4Bundle.message("changelist.decorator.unknowns.first.first", next.getClientName())); - } - while (iter.hasNext()) { - next = iter.next(); - sb.append(P4Bundle.message("changelist.decorator.unknowns.middle", next.getClientName())); - } - sb.append(P4Bundle.message("changelist.decorator.unknowns.end")); - cellRenderer.append(sb.toString(), SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); - hasOne = true; - } - - if (info.unsynced.size() == info.serverCount) { - String msg = P4Bundle.message("changelist.decorator.unsynced"); - cellRenderer.append(msg, SimpleTextAttributes.REGULAR_ITALIC_ATTRIBUTES); - } else if (! info.unsynced.isEmpty()) { - Iterator iter = info.unsynced.iterator(); - ClientServerRef next = iter.next(); - StringBuilder sb = new StringBuilder(); - if (hasOne) { - sb.append(P4Bundle.message("changelist.decorator.unsynced.second.first", next.getClientName())); - } else { - sb.append(P4Bundle.message("changelist.decorator.unsynced.first.first", next.getClientName())); - } - while (iter.hasNext()) { - next = iter.next(); - sb.append(P4Bundle.message("changelist.decorator.unsynced.middle", next.getClientName())); - } - sb.append(P4Bundle.message("changelist.decorator.unsynced.end")); - cellRenderer.append(sb.toString(), SimpleTextAttributes.REGULAR_ITALIC_ATTRIBUTES); - hasOne = true; - } - - if (info.serverCount == info.offline.size()) { - String msg = P4Bundle.message("changelist.decorator.offline"); - cellRenderer.append(msg, SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); - } else if (!info.offline.isEmpty()) { - Iterator iter = info.offline.iterator(); - ClientServerRef next = iter.next(); - StringBuilder sb = new StringBuilder(); - if (hasOne) { - sb.append(P4Bundle.message("changelist.decorator.offline.second.first", next.getClientName())); - } else { - sb.append(P4Bundle.message("changelist.decorator.offline.first.first", next.getClientName())); - } - while (iter.hasNext()) { - next = iter.next(); - sb.append(P4Bundle.message("changelist.decorator.offline.middle", next.getClientName())); - } - if (sb.length() > 0) { - sb.append(' '); - } - sb.append(P4Bundle.message("changelist.decorator.offline.end")); - cellRenderer.append(sb.toString(), SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); - } - } - - @Override - public void projectOpened() { - // ignore - } - - @Override - public void projectClosed() { - // ignore - } - - @Override - public void initComponent() { - // ignore - } - - @Override - public void disposeComponent() { - // ignore - } - - @NotNull - @Override - public String getComponentName() { - return "PerforceChangeListDecorator"; - } - - - private boolean isProjectInvalid() { - return ! P4Vcs.isProjectValid(project); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.ui; + +import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.changes.ChangeListDecorator; +import com.intellij.openapi.vcs.changes.LocalChangeList; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.SimpleTextAttributes; +import net.groboclown.p4.server.api.ClientServerRef; +import net.groboclown.p4.server.api.ProjectConfigRegistry; +import net.groboclown.p4.server.api.cache.IdeChangelistMap; +import net.groboclown.p4.server.api.cache.IdeFileMap; +import net.groboclown.p4.server.api.exceptions.VcsInterruptedException; +import net.groboclown.p4.server.api.messagebus.ErrorEvent; +import net.groboclown.p4.server.api.messagebus.InternalErrorMessage; +import net.groboclown.p4.server.api.values.P4ChangelistId; +import net.groboclown.p4plugin.P4Bundle; +import net.groboclown.p4plugin.components.CacheComponent; +import net.groboclown.p4plugin.extension.P4Vcs; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class P4ChangeListDecorator implements ChangeListDecorator, ProjectComponent { + private static final Logger LOG = Logger.getInstance(P4ChangeListDecorator.class); + + private final Project project; + + public static class ChangelistConnectionInfo { + private final List validIds = new ArrayList<>(); + private final List defaults = new ArrayList<>(); + private final List unsynced = new ArrayList<>(); + private final List unknowns = new ArrayList<>(); + private final List offline = new ArrayList<>(); + private final boolean hasOneServer; + private final int serverCount; + + ChangelistConnectionInfo(int serverCount) { + this.hasOneServer = serverCount == 1; + this.serverCount = serverCount; + } + + void addOffline(@NotNull P4ChangelistId p4cl) { + offline.add(p4cl.getClientServerRef()); + } + + void addOnline(@NotNull P4ChangelistId p4cl) { + switch (p4cl.getState()) { + case NUMBERED: + validIds.add(p4cl); + break; + case PENDING_CREATION: + unsynced.add(p4cl.getClientServerRef()); + break; + case DEFAULT: + defaults.add(p4cl.getClientServerRef()); + break; + default: + unknowns.add(p4cl.getClientServerRef()); + } + } + } + + @SuppressWarnings("WeakerAccess") + public P4ChangeListDecorator(@NotNull Project project) { + this.project = project; + } + + @Override + public void decorateChangeList(LocalChangeList changeList, ColoredTreeCellRenderer cellRenderer, boolean selected, boolean expanded, boolean hasFocus) { + if (isProjectInvalid()) { + return; + } + CacheComponent cache = CacheComponent.getInstance(project); + ProjectConfigRegistry registry = ProjectConfigRegistry.getInstance(project); + Pair + openedCache = cache.getServerOpenedCache(); + try { + Collection p4Changes = openedCache.first.getP4ChangesFor(changeList); + if (LOG.isDebugEnabled()) { + LOG.debug("Change " + changeList + " has p4 changes " + p4Changes); + } + if (p4Changes.isEmpty()) { + // Early exit + return; + } + ChangelistConnectionInfo info = new ChangelistConnectionInfo(p4Changes.size()); + for (P4ChangelistId p4Change: p4Changes) { + if (registry != null && registry.isOnline(p4Change.getClientServerRef())) { + info.addOnline(p4Change); + } else { + info.addOffline(p4Change); + } + } + decorateInfo(info, cellRenderer); + } catch (InterruptedException e) { + InternalErrorMessage.send(project).cacheLockTimeoutError(new ErrorEvent<>(new VcsInterruptedException(e))); + } + } + + private static void decorateInfo(@NotNull ChangelistConnectionInfo info, + @NotNull ColoredTreeCellRenderer cellRenderer) { + if (info.serverCount <= 0) { + return; + } + + boolean hasOne = false; + + if (info.hasOneServer && info.validIds.size() == 1) { + hasOne = true; + // Cannot do all servers here, because each connection's corresponding changelist + // is most probably different. + cellRenderer.append(P4Bundle.message("changelist.render", info.validIds.get(0).getChangelistId()), + SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); + } else if (! info.validIds.isEmpty()) { + hasOne = true; + Iterator iter = info.validIds.iterator(); + P4ChangelistId next = iter.next(); + StringBuilder sb = new StringBuilder(P4Bundle.message("changelist.render-many.first", + next.getClientname(), next.getChangelistId())); + while (iter.hasNext()) { + next = iter.next(); + sb.append(P4Bundle.message("changelist.render-many.after", + next.getClientname(), next.getChangelistId())); + } + cellRenderer.append(sb.toString(), SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); + } + + if (info.defaults.size() == info.serverCount) { + String msg = P4Bundle.message("changelist.decorator.default"); + cellRenderer.append(msg, SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); + hasOne = true; + } else if (! info.defaults.isEmpty()) { + StringBuilder sb = new StringBuilder(); + Iterator iter = info.defaults.iterator(); + ClientServerRef next = iter.next(); + if (hasOne) { + sb.append(P4Bundle.message("changelist.decorator.default.second.first", next.getClientName())); + } else { + sb.append(P4Bundle.message("changelist.decorator.default.first.first", next.getClientName())); + } + while (iter.hasNext()) { + next = iter.next(); + sb.append(P4Bundle.message("changelist.decorator.default.middle", next.getClientName())); + } + sb.append(P4Bundle.message("changelist.decorator.default.end")); + cellRenderer.append(sb.toString(), SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); + hasOne = true; + } + + if (info.unknowns.size() == info.serverCount) { + String msg = P4Bundle.message("changelist.decorator.unknowns"); + cellRenderer.append(msg, SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); + hasOne = true; + } else if (! info.unknowns.isEmpty()) { + Iterator iter = info.unknowns.iterator(); + ClientServerRef next = iter.next(); + StringBuilder sb = new StringBuilder(); + if (hasOne) { + sb.append(P4Bundle.message("changelist.decorator.unknowns.second.first", next.getClientName())); + } else { + sb.append(P4Bundle.message("changelist.decorator.unknowns.first.first", next.getClientName())); + } + while (iter.hasNext()) { + next = iter.next(); + sb.append(P4Bundle.message("changelist.decorator.unknowns.middle", next.getClientName())); + } + sb.append(P4Bundle.message("changelist.decorator.unknowns.end")); + cellRenderer.append(sb.toString(), SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); + hasOne = true; + } + + if (info.unsynced.size() == info.serverCount) { + String msg = P4Bundle.message("changelist.decorator.unsynced"); + cellRenderer.append(msg, SimpleTextAttributes.REGULAR_ITALIC_ATTRIBUTES); + } else if (! info.unsynced.isEmpty()) { + Iterator iter = info.unsynced.iterator(); + ClientServerRef next = iter.next(); + StringBuilder sb = new StringBuilder(); + if (hasOne) { + sb.append(P4Bundle.message("changelist.decorator.unsynced.second.first", next.getClientName())); + } else { + sb.append(P4Bundle.message("changelist.decorator.unsynced.first.first", next.getClientName())); + } + while (iter.hasNext()) { + next = iter.next(); + sb.append(P4Bundle.message("changelist.decorator.unsynced.middle", next.getClientName())); + } + sb.append(P4Bundle.message("changelist.decorator.unsynced.end")); + cellRenderer.append(sb.toString(), SimpleTextAttributes.REGULAR_ITALIC_ATTRIBUTES); + hasOne = true; + } + + if (info.serverCount == info.offline.size()) { + String msg = P4Bundle.message("changelist.decorator.offline"); + cellRenderer.append(msg, SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); + } else if (!info.offline.isEmpty()) { + Iterator iter = info.offline.iterator(); + ClientServerRef next = iter.next(); + StringBuilder sb = new StringBuilder(); + if (hasOne) { + sb.append(P4Bundle.message("changelist.decorator.offline.second.first", next.getClientName())); + } else { + sb.append(P4Bundle.message("changelist.decorator.offline.first.first", next.getClientName())); + } + while (iter.hasNext()) { + next = iter.next(); + sb.append(P4Bundle.message("changelist.decorator.offline.middle", next.getClientName())); + } + if (sb.length() > 0) { + sb.append(' '); + } + sb.append(P4Bundle.message("changelist.decorator.offline.end")); + cellRenderer.append(sb.toString(), SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); + } + } + + @Override + public void projectOpened() { + // ignore + } + + @Override + public void projectClosed() { + // ignore + } + + @Override + public void initComponent() { + // ignore + } + + @Override + public void disposeComponent() { + // ignore + } + + @NotNull + @Override + public String getComponentName() { + return "PerforceChangeListDecorator"; + } + + + private boolean isProjectInvalid() { + return ! P4Vcs.isProjectValid(project); + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/P4Icons.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/P4Icons.java index 3826f0cc..128288eb 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/P4Icons.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/P4Icons.java @@ -1,28 +1,28 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.ui; - -import com.intellij.openapi.util.IconLoader; - -import javax.swing.*; - -public interface P4Icons { - Icon CONNECTED = IconLoader.getIcon("/icons/p4connected.png"); - Icon DISCONNECTED = IconLoader.getIcon("/icons/p4disconnected.png"); - Icon SWARM = IconLoader.getIcon("/icons/swarm.png"); - - // TODO make this its own icon - Icon MIXED = CONNECTED; - Icon UNKNOWN = DISCONNECTED; -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.ui; + +import com.intellij.openapi.util.IconLoader; + +import javax.swing.*; + +public interface P4Icons { + Icon CONNECTED = IconLoader.getIcon("/icons/p4connected.png"); + Icon DISCONNECTED = IconLoader.getIcon("/icons/p4disconnected.png"); + Icon SWARM = IconLoader.getIcon("/icons/swarm.png"); + + // TODO make this its own icon + Icon MIXED = CONNECTED; + Icon UNKNOWN = DISCONNECTED; +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/SubProgressIndicator.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/SubProgressIndicator.java index 2dfb54b0..eebc675d 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/SubProgressIndicator.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/SubProgressIndicator.java @@ -1,41 +1,41 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package net.groboclown.p4plugin.ui; - -import com.intellij.ide.util.DelegatingProgressIndicator; -import com.intellij.openapi.progress.ProgressIndicator; -import org.jetbrains.annotations.NotNull; - -public class SubProgressIndicator extends DelegatingProgressIndicator { - private final double min; - private final double range; - - public SubProgressIndicator(@NotNull ProgressIndicator indicator, - double startFraction, double endFraction) { - super(indicator); - assert startFraction >= 0.0; - assert startFraction < endFraction; - assert endFraction <= 1.0; - this.min = startFraction; - this.range = endFraction - min; - } - - - @Override - public void setFraction(final double fraction) { - assert fraction >= 0.0; - assert fraction <= 1.0; - super.setFraction((fraction * range) + min); - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.groboclown.p4plugin.ui; + +import com.intellij.ide.util.DelegatingProgressIndicator; +import com.intellij.openapi.progress.ProgressIndicator; +import org.jetbrains.annotations.NotNull; + +public class SubProgressIndicator extends DelegatingProgressIndicator { + private final double min; + private final double range; + + public SubProgressIndicator(@NotNull ProgressIndicator indicator, + double startFraction, double endFraction) { + super(indicator); + assert startFraction >= 0.0; + assert startFraction < endFraction; + assert endFraction <= 1.0; + this.min = startFraction; + this.range = endFraction - min; + } + + + @Override + public void setFraction(final double fraction) { + assert fraction >= 0.0; + assert fraction <= 1.0; + super.setFraction((fraction * range) + min); + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/config/UserPreferencesPanel.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/config/UserPreferencesPanel.java index b6a2d361..abdb8880 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/config/UserPreferencesPanel.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/config/UserPreferencesPanel.java @@ -1,693 +1,693 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4plugin.ui.config; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.uiDesigner.core.GridConstraints; -import com.intellij.uiDesigner.core.GridLayoutManager; -import com.intellij.uiDesigner.core.Spacer; -import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.components.UserProjectPreferences; -import net.groboclown.p4plugin.ui.DoubleMinMaxSpinnerModel; -import net.groboclown.p4plugin.ui.IntMinMaxSpinnerModel; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import javax.swing.border.TitledBorder; -import java.awt.*; -import java.util.ResourceBundle; - -// eighth -// eighteenth - -/** - * Global properties. - */ -public class UserPreferencesPanel { - private static final Logger LOG = Logger.getInstance(UserPreferencesPanel.class); - - private enum UserMessageLevel { - VERBOSE(UserProjectPreferences.USER_MESSAGE_LEVEL_VERBOSE, "verbose"), - INFO(UserProjectPreferences.USER_MESSAGE_LEVEL_INFO, "info"), - WARNING(UserProjectPreferences.USER_MESSAGE_LEVEL_WARNING, "warn"), - ERROR(UserProjectPreferences.USER_MESSAGE_LEVEL_ERROR, "error") - - // "ALWAYS" is not an option. The least you can set is error. - ; - - private final String displayName; - final int value; - - UserMessageLevel(int value, String name) { - this.value = value; - this.displayName = P4Bundle.getString("user.prefs.user_message." + name); - } - - @Override - public String toString() { - return displayName; - } - } - - private JPanel myRootPanel; - private JSpinner myMaxTimeoutSecondsSpinner; - private JRadioButton myPreferRevisionNumber; - private JRadioButton myPreferChangelist; - private JCheckBox myConcatenateChangelistNameCommentCheckBox; - private JSpinner mySocketSoTimeoutSecondsSpinner; - private JCheckBox myAutoCheckoutCheckBox; - private JSpinner myMaxConnectionsSpinner; - private JSpinner myMaxClientRetrieveSpinner; - private JSpinner myMaxChangelistRetrieveSpinner; - private JSpinner myMaxFileRetrieveSpinner; - private JCheckBox myRemoveP4ChangelistCheckBox; - private JComboBox myUserMessageLevelComboBox; - private JSpinner myMaxChangelistNameSpinner; - private JSpinner myRetryActionCountSpinner; - private JCheckBox myNotifyOnReverts; - private ButtonGroup myPreferRevisionGroup; - - - UserPreferencesPanel() { - LOG.debug("UI constructed, now setting up models"); - setupSpinner(myMaxTimeoutSecondsSpinner, new DoubleMinMaxSpinnerModel( - millisToSeconds(UserProjectPreferences.MIN_LOCK_WAIT_TIMEOUT_MILLIS), - millisToSeconds(UserProjectPreferences.MAX_LOCK_WAIT_TIMEOUT_MILLIS), - 0.5, - millisToSeconds(UserProjectPreferences.DEFAULT_LOCK_WAIT_TIMEOUT_MILLIS))); - setupSpinner(mySocketSoTimeoutSecondsSpinner, new DoubleMinMaxSpinnerModel( - millisToSeconds(UserProjectPreferences.MIN_SOCKET_SO_TIMEOUT_MILLIS), - millisToSeconds(UserProjectPreferences.MAX_SOCKET_SO_TIMEOUT_MILLIS), - .1, - millisToSeconds(UserProjectPreferences.DEFAULT_SOCKET_SO_TIMEOUT_MILLIS))); - setupSpinner(myMaxConnectionsSpinner, new IntMinMaxSpinnerModel( - UserProjectPreferences.MIN_SERVER_CONNECTIONS, - UserProjectPreferences.MAX_SERVER_CONNECTIONS, - 1, - UserProjectPreferences.DEFAULT_SERVER_CONNECTIONS)); - setupSpinner(myMaxChangelistNameSpinner, new IntMinMaxSpinnerModel( - UserProjectPreferences.MIN_CHANGELIST_NAME_LENGTH, - UserProjectPreferences.MAX_CHANGELIST_NAME_LENGTH, - 1, - UserProjectPreferences.DEFAULT_MAX_CHANGELIST_NAME_LENGTH)); - setupSpinner(myMaxClientRetrieveSpinner, new IntMinMaxSpinnerModel( - UserProjectPreferences.MIN_CLIENT_RETRIEVE_COUNT, - UserProjectPreferences.MAX_CLIENT_RETRIEVE_COUNT, - 5, - UserProjectPreferences.DEFAULT_MAX_CLIENT_RETRIEVE_COUNT)); - setupSpinner(myMaxChangelistRetrieveSpinner, new IntMinMaxSpinnerModel( - UserProjectPreferences.MIN_CHANGELIST_RETRIEVE_COUNT, - UserProjectPreferences.MAX_CHANGELIST_RETRIEVE_COUNT, - 5, - UserProjectPreferences.DEFAULT_MAX_CHANGELIST_RETRIEVE_COUNT)); - setupSpinner(myMaxFileRetrieveSpinner, new IntMinMaxSpinnerModel( - UserProjectPreferences.MIN_FILE_RETRIEVE_COUNT, - UserProjectPreferences.MAX_FILE_RETRIEVE_COUNT, - 50, - UserProjectPreferences.DEFAULT_MAX_FILE_RETRIEVE_COUNT)); - setupSpinner(myRetryActionCountSpinner, new IntMinMaxSpinnerModel( - UserProjectPreferences.MIN_RETRY_ACTION_COUNT, - UserProjectPreferences.MAX_RETRY_ACTION_COUNT, - 1, - UserProjectPreferences.DEFAULT_RETRY_ACTION_COUNT)); - myMaxFileRetrieveSpinner.getEditor().setEnabled(true); - for (UserMessageLevel value : UserMessageLevel.values()) { - myUserMessageLevelComboBox.addItem(value); - } - // Future settings here - - myPreferRevisionGroup = new ButtonGroup(); - myPreferRevisionGroup.add(myPreferChangelist); - myPreferRevisionGroup.add(myPreferRevisionNumber); - LOG.debug("Completed panel setup"); - } - - - void loadSettingsIntoGUI(@NotNull UserProjectPreferences userPrefs) { - LOG.debug("Loading settings into UI"); - myMaxTimeoutSecondsSpinner.setValue(millisToSeconds(userPrefs.getLockWaitTimeoutMillis())); - mySocketSoTimeoutSecondsSpinner.setValue(millisToSeconds(userPrefs.getSocketSoTimeoutMillis())); - myMaxConnectionsSpinner.setValue(userPrefs.getMaxServerConnections()); - myMaxChangelistNameSpinner.setValue(userPrefs.getMaxChangelistNameLength()); - myPreferRevisionGroup.setSelected( - userPrefs.getPreferRevisionsForFiles() - ? myPreferRevisionNumber.getModel() - : myPreferChangelist.getModel() - , true); - myConcatenateChangelistNameCommentCheckBox.setSelected(userPrefs.getConcatenateChangelistNameComment()); - myAutoCheckoutCheckBox.setSelected(userPrefs.getAutoCheckoutModifiedFiles()); - myMaxClientRetrieveSpinner.setValue(userPrefs.getMaxClientRetrieveCount()); - myMaxChangelistRetrieveSpinner.setValue(userPrefs.getMaxChangelistRetrieveCount()); - myMaxFileRetrieveSpinner.setValue(userPrefs.getMaxFileRetrieveCount()); - myRemoveP4ChangelistCheckBox.setSelected(userPrefs.getRemoveP4Changelist()); - myUserMessageLevelComboBox.setSelectedItem(messageLevelFromInt(userPrefs.getUserMessageLevel())); - myRetryActionCountSpinner.setValue(userPrefs.getRetryActionCount()); - myNotifyOnReverts.setSelected(userPrefs.getNotifyOnRevert()); - // Future settings here - LOG.debug("Finished loading settings into the UI"); - } - - - void saveSettingsToConfig(@NotNull UserProjectPreferences userPrefs) { - userPrefs.setLockWaitTimeoutMillis(getMaxTimeoutMillis()); - userPrefs.setSocketSoTimeoutMillis(getSocketSoTimeoutMillis()); - userPrefs.setMaxServerConnections(getMaxServerConnections()); - userPrefs.setMaxChangelistNameLength(getMaxChangelistNameLength()); - userPrefs.setPreferRevisionsForFiles(getPreferRevisionsForFiles()); - userPrefs.setConcatenateChangelistNameComment(getConcatenateChangelistNameComment()); - userPrefs.setAutoCheckoutModifiedFiles(getAutoCheckout()); - userPrefs.setMaxClientRetrieveCount(getMaxClientRetrieveCount()); - userPrefs.setMaxChangelistRetrieveCount(getMaxChangelistRetrieveCount()); - userPrefs.setMaxFileRetrieveCount(getMaxFileRetrieveCount()); - userPrefs.setRemoveP4Changelist(getRemoveP4Changelist()); - userPrefs.setUserMessageLevel(getUserMessageLevel()); - userPrefs.setRetryActionCount(getRetryActionCount()); - userPrefs.setNotifyOnRevert(getNotifyOnRevert()); - // Future settings here - } - - - boolean isModified(@NotNull final UserProjectPreferences preferences) { - return - getMaxTimeoutMillis() != preferences.getLockWaitTimeoutMillis() || - getSocketSoTimeoutMillis() != preferences.getSocketSoTimeoutMillis() || - getMaxServerConnections() != preferences.getMaxServerConnections() || - getMaxChangelistNameLength() != preferences.getMaxChangelistNameLength() || - getPreferRevisionsForFiles() != preferences.getPreferRevisionsForFiles() || - getConcatenateChangelistNameComment() != preferences.getConcatenateChangelistNameComment() || - getAutoCheckout() != preferences.getAutoCheckoutModifiedFiles() || - getMaxClientRetrieveCount() != preferences.getMaxClientRetrieveCount() || - getMaxChangelistRetrieveCount() != preferences.getMaxChangelistRetrieveCount() || - getMaxFileRetrieveCount() != preferences.getMaxFileRetrieveCount() || - getRemoveP4Changelist() != preferences.getRemoveP4Changelist() || - getUserMessageLevel() != preferences.getUserMessageLevel() || - getRetryActionCount() != preferences.getRetryActionCount() || - getNotifyOnRevert() != preferences.getNotifyOnRevert(); - // Future settings here - } - - - private int getMaxTimeoutMillis() { - return secondsToMillis(myMaxTimeoutSecondsSpinner); - } - - private boolean getPreferRevisionsForFiles() { - return myPreferRevisionGroup.getSelection() == null || - myPreferRevisionGroup.getSelection() == myPreferRevisionNumber.getModel(); - } - - private int getSocketSoTimeoutMillis() { - return secondsToMillis(mySocketSoTimeoutSecondsSpinner); - } - - private int getMaxServerConnections() { - return toInt(myMaxConnectionsSpinner); - } - - private int getMaxChangelistNameLength() { - return toInt(myMaxChangelistNameSpinner); - } - - private int getMaxClientRetrieveCount() { - return toInt(myMaxClientRetrieveSpinner); - } - - private boolean getConcatenateChangelistNameComment() { - return myConcatenateChangelistNameCommentCheckBox.isSelected(); - } - - private boolean getAutoCheckout() { - return myAutoCheckoutCheckBox.isSelected(); - } - - private int getMaxChangelistRetrieveCount() { - return toInt(myMaxChangelistRetrieveSpinner); - } - - private int getMaxFileRetrieveCount() { - return toInt(myMaxFileRetrieveSpinner); - } - - private boolean getRemoveP4Changelist() { - return myRemoveP4ChangelistCheckBox.isSelected(); - } - - private int getUserMessageLevel() { - Object level = myUserMessageLevelComboBox.getSelectedItem(); - if (level instanceof UserMessageLevel) { - return ((UserMessageLevel) level).value; - } - return UserProjectPreferences.DEFAULT_USER_MESSAGE_LEVEL; - } - - private int getRetryActionCount() { - return toInt(myRetryActionCountSpinner); - } - - private boolean getNotifyOnRevert() { - return myNotifyOnReverts.isSelected(); - } - - - private void createUIComponents() { - // place custom component creation code here - } - - JPanel getRootPanel() { - return myRootPanel; - } - - - private static double millisToSeconds(long millis) { - return ((double) millis) / 1000.; - } - - private static int secondsToMillis(JSpinner secondsSpinner) { - double seconds = ((Number) secondsSpinner.getModel().getValue()).doubleValue(); - return (int) Math.floor(seconds * 1000.); - } - - private static int toInt(JSpinner spinner) { - return ((Number) spinner.getModel().getValue()).intValue(); - } - - private static void setupSpinner(JSpinner spinner, SpinnerModel model) { - spinner.setModel(model); - JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) spinner.getEditor(); - editor.setEnabled(true); - editor.getTextField().setEditable(true); - } - - private static UserMessageLevel messageLevelFromInt(int value) { - for (UserMessageLevel userMessageLevel : UserMessageLevel.values()) { - if (userMessageLevel.value == value) { - return userMessageLevel; - } - } - for (UserMessageLevel userMessageLevel : UserMessageLevel.values()) { - if (userMessageLevel.value == UserProjectPreferences.DEFAULT_USER_MESSAGE_LEVEL) { - return userMessageLevel; - } - } - LOG.warn("UserProjectPreferences.DEFAULT_USER_MESSAGE_LEVEL is an invalid value"); - return UserMessageLevel.INFO; - } - - /** - * @noinspection ALL - */ - private Font getFont1494608681498(String fontName, int style, int size, Font currentFont) { - if (currentFont == null) { - return null; - } - String resultName; - if (fontName == null) { - resultName = currentFont.getName(); - } else { - Font testFont = new Font(fontName, Font.PLAIN, 10); - if (testFont.canDisplay('a') && testFont.canDisplay('1')) { - resultName = fontName; - } else { - resultName = currentFont.getName(); - } - } - return new Font(resultName, style >= 0 ? style : currentFont.getStyle(), - size >= 0 ? size : currentFont.getSize()); - } - - /** - * @noinspection ALL - */ - private Font getFont1495815520855(String fontName, int style, int size, Font currentFont) { - if (currentFont == null) { - return null; - } - String resultName; - if (fontName == null) { - resultName = currentFont.getName(); - } else { - Font testFont = new Font(fontName, Font.PLAIN, 10); - if (testFont.canDisplay('a') && testFont.canDisplay('1')) { - resultName = fontName; - } else { - resultName = currentFont.getName(); - } - } - return new Font(resultName, style >= 0 ? style : currentFont.getStyle(), - size >= 0 ? size : currentFont.getSize()); - } - - /** - * @noinspection ALL - */ - private Font getFont1495815749256(String fontName, int style, int size, Font currentFont) { - if (currentFont == null) { - return null; - } - String resultName; - if (fontName == null) { - resultName = currentFont.getName(); - } else { - Font testFont = new Font(fontName, Font.PLAIN, 10); - if (testFont.canDisplay('a') && testFont.canDisplay('1')) { - resultName = fontName; - } else { - resultName = currentFont.getName(); - } - } - return new Font(resultName, style >= 0 ? style : currentFont.getStyle(), - size >= 0 ? size : currentFont.getSize()); - } - - { -// GUI initializer generated by IntelliJ IDEA GUI Designer -// >>> IMPORTANT!! <<< -// DO NOT EDIT OR ADD ANY CODE HERE! - $$$setupUI$$$(); - } - - /** - * Method generated by IntelliJ IDEA GUI Designer - * >>> IMPORTANT!! <<< - * DO NOT edit this method OR call it in your code! - * - * @noinspection ALL - */ - private void $$$setupUI$$$() { - myRootPanel = new JPanel(); - myRootPanel.setLayout(new GridLayoutManager(12, 2, new Insets(0, 0, 0, 0), -1, -1)); - final JLabel label1 = new JLabel(); - this.$$$loadLabelText$$$(label1, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.max_timeout")); - label1.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.max_timeout.tooltip")); - myRootPanel.add(label1, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - final JPanel panel1 = new JPanel(); - panel1.setLayout(new GridLayoutManager(4, 1, new Insets(0, 0, 0, 0), -1, -1)); - myRootPanel.add(panel1, - new GridConstraints(0, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, - 0, false)); - myConcatenateChangelistNameCommentCheckBox = new JCheckBox(); - this.$$$loadButtonText$$$(myConcatenateChangelistNameCommentCheckBox, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.concatenate-changelist")); - myConcatenateChangelistNameCommentCheckBox.setToolTipText( - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.concatenate-changelist.tooltip")); - panel1.add(myConcatenateChangelistNameCommentCheckBox, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 1, false)); - myAutoCheckoutCheckBox = new JCheckBox(); - this.$$$loadButtonText$$$(myAutoCheckoutCheckBox, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.auto-checkout")); - myAutoCheckoutCheckBox.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.auto-checkout.tooltip")); - panel1.add(myAutoCheckoutCheckBox, - new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 1, false)); - myRemoveP4ChangelistCheckBox = new JCheckBox(); - this.$$$loadButtonText$$$(myRemoveP4ChangelistCheckBox, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.remove_p4_changelist")); - myRemoveP4ChangelistCheckBox.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.remove_p4_changelist.tooltip")); - panel1.add(myRemoveP4ChangelistCheckBox, - new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 1, false)); - myNotifyOnReverts = new JCheckBox(); - this.$$$loadButtonText$$$(myNotifyOnReverts, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.notify_on_revert")); - myNotifyOnReverts.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.notify_on_revert.tooltip")); - panel1.add(myNotifyOnReverts, - new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 1, false)); - final JPanel panel2 = new JPanel(); - panel2.setLayout(new GridLayoutManager(1, 3, new Insets(0, 0, 0, 0), -1, -1)); - myRootPanel.add(panel2, - new GridConstraints(10, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, - 0, false)); - panel2.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black), - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.rev_display"), - TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, - this.$$$getFont$$$(null, -1, -1, panel2.getFont()))); - myPreferRevisionNumber = new JRadioButton(); - this.$$$loadButtonText$$$(myPreferRevisionNumber, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.revision")); - panel2.add(myPreferRevisionNumber, - new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - final Spacer spacer1 = new Spacer(); - panel2.add(spacer1, - new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, - GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); - myPreferChangelist = new JRadioButton(); - this.$$$loadButtonText$$$(myPreferChangelist, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.prefer_changelist")); - panel2.add(myPreferChangelist, - new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - final Spacer spacer2 = new Spacer(); - myRootPanel.add(spacer2, - new GridConstraints(11, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, - GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); - final JLabel label2 = new JLabel(); - this.$$$loadLabelText$$$(label2, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.socket-so-timeout")); - label2.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.socket-so-timeout.tooltip")); - myRootPanel.add(label2, new GridConstraints(8, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - final JLabel label3 = new JLabel(); - this.$$$loadLabelText$$$(label3, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.max_connections")); - myRootPanel.add(label3, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - myMaxConnectionsSpinner = new JSpinner(); - myRootPanel.add(myMaxConnectionsSpinner, - new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); - final JPanel panel3 = new JPanel(); - panel3.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); - myRootPanel.add(panel3, - new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, - 0, false)); - myMaxTimeoutSecondsSpinner = new JSpinner(); - myMaxTimeoutSecondsSpinner.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.max_timeout.tooltip")); - panel3.add(myMaxTimeoutSecondsSpinner); - final JLabel label4 = new JLabel(); - this.$$$loadLabelText$$$(label4, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.max_timeout.unit")); - panel3.add(label4); - final JPanel panel4 = new JPanel(); - panel4.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); - myRootPanel.add(panel4, - new GridConstraints(8, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, - 0, false)); - mySocketSoTimeoutSecondsSpinner = new JSpinner(); - mySocketSoTimeoutSecondsSpinner.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.socket-so-timeout.tooltip")); - panel4.add(mySocketSoTimeoutSecondsSpinner); - final JLabel label5 = new JLabel(); - this.$$$loadLabelText$$$(label5, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.socket-so-timeout.unit")); - panel4.add(label5); - final JLabel label6 = new JLabel(); - this.$$$loadLabelText$$$(label6, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.max_client_retrieve")); - label6.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.max_client_retrieve.tooltip")); - myRootPanel.add(label6, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - myMaxClientRetrieveSpinner = new JSpinner(); - myRootPanel.add(myMaxClientRetrieveSpinner, - new GridConstraints(6, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); - myMaxChangelistRetrieveSpinner = new JSpinner(); - myRootPanel.add(myMaxChangelistRetrieveSpinner, - new GridConstraints(4, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); - final JLabel label7 = new JLabel(); - this.$$$loadLabelText$$$(label7, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.max_changelist_retrieve")); - label7.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.max_changelist_retrieve.tooltip")); - myRootPanel.add(label7, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - myMaxFileRetrieveSpinner = new JSpinner(); - myRootPanel.add(myMaxFileRetrieveSpinner, - new GridConstraints(5, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); - final JLabel label8 = new JLabel(); - this.$$$loadLabelText$$$(label8, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.max_file_retrieve_count")); - label8.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.max_file_retrieve_count.tooltip")); - myRootPanel.add(label8, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - myUserMessageLevelComboBox = new JComboBox(); - myRootPanel.add(myUserMessageLevelComboBox, - new GridConstraints(7, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); - final JLabel label9 = new JLabel(); - this.$$$loadLabelText$$$(label9, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.user_message_level")); - label9.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.user_message_level.tooltip")); - myRootPanel.add(label9, new GridConstraints(7, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - myMaxChangelistNameSpinner = new JSpinner(); - myRootPanel.add(myMaxChangelistNameSpinner, - new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); - final JLabel label10 = new JLabel(); - this.$$$loadLabelText$$$(label10, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.max_changelist_name")); - label10.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.max_changelist_name.tooltip")); - myRootPanel.add(label10, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - myRetryActionCountSpinner = new JSpinner(); - myRootPanel.add(myRetryActionCountSpinner, - new GridConstraints(9, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, - false)); - final JLabel label11 = new JLabel(); - this.$$$loadLabelText$$$(label11, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.retry-action-count")); - label11.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("user.prefs.retry-action-count.tooltip")); - myRootPanel.add(label11, new GridConstraints(9, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - label1.setLabelFor(myMaxTimeoutSecondsSpinner); - label3.setLabelFor(myMaxConnectionsSpinner); - label6.setLabelFor(myMaxClientRetrieveSpinner); - label7.setLabelFor(myMaxClientRetrieveSpinner); - label8.setLabelFor(myMaxFileRetrieveSpinner); - label9.setLabelFor(myUserMessageLevelComboBox); - label10.setLabelFor(myMaxChangelistNameSpinner); - label11.setLabelFor(myRetryActionCountSpinner); - } - - /** - * @noinspection ALL - */ - private Font $$$getFont$$$(String fontName, int style, int size, Font currentFont) { - if (currentFont == null) { - return null; - } - String resultName; - if (fontName == null) { - resultName = currentFont.getName(); - } else { - Font testFont = new Font(fontName, Font.PLAIN, 10); - if (testFont.canDisplay('a') && testFont.canDisplay('1')) { - resultName = fontName; - } else { - resultName = currentFont.getName(); - } - } - return new Font(resultName, style >= 0 ? style : currentFont.getStyle(), - size >= 0 ? size : currentFont.getSize()); - } - - /** - * @noinspection ALL - */ - private void $$$loadLabelText$$$(JLabel component, String text) { - StringBuffer result = new StringBuffer(); - boolean haveMnemonic = false; - char mnemonic = '\0'; - int mnemonicIndex = -1; - for (int i = 0; i < text.length(); i++) { - if (text.charAt(i) == '&') { - i++; - if (i == text.length()) { - break; - } - if (!haveMnemonic && text.charAt(i) != '&') { - haveMnemonic = true; - mnemonic = text.charAt(i); - mnemonicIndex = result.length(); - } - } - result.append(text.charAt(i)); - } - component.setText(result.toString()); - if (haveMnemonic) { - component.setDisplayedMnemonic(mnemonic); - component.setDisplayedMnemonicIndex(mnemonicIndex); - } - } - - /** - * @noinspection ALL - */ - private void $$$loadButtonText$$$(AbstractButton component, String text) { - StringBuffer result = new StringBuffer(); - boolean haveMnemonic = false; - char mnemonic = '\0'; - int mnemonicIndex = -1; - for (int i = 0; i < text.length(); i++) { - if (text.charAt(i) == '&') { - i++; - if (i == text.length()) { - break; - } - if (!haveMnemonic && text.charAt(i) != '&') { - haveMnemonic = true; - mnemonic = text.charAt(i); - mnemonicIndex = result.length(); - } - } - result.append(text.charAt(i)); - } - component.setText(result.toString()); - if (haveMnemonic) { - component.setMnemonic(mnemonic); - component.setDisplayedMnemonicIndex(mnemonicIndex); - } - } - - /** - * @noinspection ALL - */ - public JComponent $$$getRootComponent$$$() { - return myRootPanel; - } - -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4plugin.ui.config; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.uiDesigner.core.GridConstraints; +import com.intellij.uiDesigner.core.GridLayoutManager; +import com.intellij.uiDesigner.core.Spacer; +import net.groboclown.p4plugin.P4Bundle; +import net.groboclown.p4plugin.components.UserProjectPreferences; +import net.groboclown.p4plugin.ui.DoubleMinMaxSpinnerModel; +import net.groboclown.p4plugin.ui.IntMinMaxSpinnerModel; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.border.TitledBorder; +import java.awt.*; +import java.util.ResourceBundle; + +// eighth +// eighteenth + +/** + * Global properties. + */ +public class UserPreferencesPanel { + private static final Logger LOG = Logger.getInstance(UserPreferencesPanel.class); + + private enum UserMessageLevel { + VERBOSE(UserProjectPreferences.USER_MESSAGE_LEVEL_VERBOSE, "verbose"), + INFO(UserProjectPreferences.USER_MESSAGE_LEVEL_INFO, "info"), + WARNING(UserProjectPreferences.USER_MESSAGE_LEVEL_WARNING, "warn"), + ERROR(UserProjectPreferences.USER_MESSAGE_LEVEL_ERROR, "error") + + // "ALWAYS" is not an option. The least you can set is error. + ; + + private final String displayName; + final int value; + + UserMessageLevel(int value, String name) { + this.value = value; + this.displayName = P4Bundle.getString("user.prefs.user_message." + name); + } + + @Override + public String toString() { + return displayName; + } + } + + private JPanel myRootPanel; + private JSpinner myMaxTimeoutSecondsSpinner; + private JRadioButton myPreferRevisionNumber; + private JRadioButton myPreferChangelist; + private JCheckBox myConcatenateChangelistNameCommentCheckBox; + private JSpinner mySocketSoTimeoutSecondsSpinner; + private JCheckBox myAutoCheckoutCheckBox; + private JSpinner myMaxConnectionsSpinner; + private JSpinner myMaxClientRetrieveSpinner; + private JSpinner myMaxChangelistRetrieveSpinner; + private JSpinner myMaxFileRetrieveSpinner; + private JCheckBox myRemoveP4ChangelistCheckBox; + private JComboBox myUserMessageLevelComboBox; + private JSpinner myMaxChangelistNameSpinner; + private JSpinner myRetryActionCountSpinner; + private JCheckBox myNotifyOnReverts; + private ButtonGroup myPreferRevisionGroup; + + + UserPreferencesPanel() { + LOG.debug("UI constructed, now setting up models"); + setupSpinner(myMaxTimeoutSecondsSpinner, new DoubleMinMaxSpinnerModel( + millisToSeconds(UserProjectPreferences.MIN_LOCK_WAIT_TIMEOUT_MILLIS), + millisToSeconds(UserProjectPreferences.MAX_LOCK_WAIT_TIMEOUT_MILLIS), + 0.5, + millisToSeconds(UserProjectPreferences.DEFAULT_LOCK_WAIT_TIMEOUT_MILLIS))); + setupSpinner(mySocketSoTimeoutSecondsSpinner, new DoubleMinMaxSpinnerModel( + millisToSeconds(UserProjectPreferences.MIN_SOCKET_SO_TIMEOUT_MILLIS), + millisToSeconds(UserProjectPreferences.MAX_SOCKET_SO_TIMEOUT_MILLIS), + .1, + millisToSeconds(UserProjectPreferences.DEFAULT_SOCKET_SO_TIMEOUT_MILLIS))); + setupSpinner(myMaxConnectionsSpinner, new IntMinMaxSpinnerModel( + UserProjectPreferences.MIN_SERVER_CONNECTIONS, + UserProjectPreferences.MAX_SERVER_CONNECTIONS, + 1, + UserProjectPreferences.DEFAULT_SERVER_CONNECTIONS)); + setupSpinner(myMaxChangelistNameSpinner, new IntMinMaxSpinnerModel( + UserProjectPreferences.MIN_CHANGELIST_NAME_LENGTH, + UserProjectPreferences.MAX_CHANGELIST_NAME_LENGTH, + 1, + UserProjectPreferences.DEFAULT_MAX_CHANGELIST_NAME_LENGTH)); + setupSpinner(myMaxClientRetrieveSpinner, new IntMinMaxSpinnerModel( + UserProjectPreferences.MIN_CLIENT_RETRIEVE_COUNT, + UserProjectPreferences.MAX_CLIENT_RETRIEVE_COUNT, + 5, + UserProjectPreferences.DEFAULT_MAX_CLIENT_RETRIEVE_COUNT)); + setupSpinner(myMaxChangelistRetrieveSpinner, new IntMinMaxSpinnerModel( + UserProjectPreferences.MIN_CHANGELIST_RETRIEVE_COUNT, + UserProjectPreferences.MAX_CHANGELIST_RETRIEVE_COUNT, + 5, + UserProjectPreferences.DEFAULT_MAX_CHANGELIST_RETRIEVE_COUNT)); + setupSpinner(myMaxFileRetrieveSpinner, new IntMinMaxSpinnerModel( + UserProjectPreferences.MIN_FILE_RETRIEVE_COUNT, + UserProjectPreferences.MAX_FILE_RETRIEVE_COUNT, + 50, + UserProjectPreferences.DEFAULT_MAX_FILE_RETRIEVE_COUNT)); + setupSpinner(myRetryActionCountSpinner, new IntMinMaxSpinnerModel( + UserProjectPreferences.MIN_RETRY_ACTION_COUNT, + UserProjectPreferences.MAX_RETRY_ACTION_COUNT, + 1, + UserProjectPreferences.DEFAULT_RETRY_ACTION_COUNT)); + myMaxFileRetrieveSpinner.getEditor().setEnabled(true); + for (UserMessageLevel value : UserMessageLevel.values()) { + myUserMessageLevelComboBox.addItem(value); + } + // Future settings here + + myPreferRevisionGroup = new ButtonGroup(); + myPreferRevisionGroup.add(myPreferChangelist); + myPreferRevisionGroup.add(myPreferRevisionNumber); + LOG.debug("Completed panel setup"); + } + + + void loadSettingsIntoGUI(@NotNull UserProjectPreferences userPrefs) { + LOG.debug("Loading settings into UI"); + myMaxTimeoutSecondsSpinner.setValue(millisToSeconds(userPrefs.getLockWaitTimeoutMillis())); + mySocketSoTimeoutSecondsSpinner.setValue(millisToSeconds(userPrefs.getSocketSoTimeoutMillis())); + myMaxConnectionsSpinner.setValue(userPrefs.getMaxServerConnections()); + myMaxChangelistNameSpinner.setValue(userPrefs.getMaxChangelistNameLength()); + myPreferRevisionGroup.setSelected( + userPrefs.getPreferRevisionsForFiles() + ? myPreferRevisionNumber.getModel() + : myPreferChangelist.getModel() + , true); + myConcatenateChangelistNameCommentCheckBox.setSelected(userPrefs.getConcatenateChangelistNameComment()); + myAutoCheckoutCheckBox.setSelected(userPrefs.getAutoCheckoutModifiedFiles()); + myMaxClientRetrieveSpinner.setValue(userPrefs.getMaxClientRetrieveCount()); + myMaxChangelistRetrieveSpinner.setValue(userPrefs.getMaxChangelistRetrieveCount()); + myMaxFileRetrieveSpinner.setValue(userPrefs.getMaxFileRetrieveCount()); + myRemoveP4ChangelistCheckBox.setSelected(userPrefs.getRemoveP4Changelist()); + myUserMessageLevelComboBox.setSelectedItem(messageLevelFromInt(userPrefs.getUserMessageLevel())); + myRetryActionCountSpinner.setValue(userPrefs.getRetryActionCount()); + myNotifyOnReverts.setSelected(userPrefs.getNotifyOnRevert()); + // Future settings here + LOG.debug("Finished loading settings into the UI"); + } + + + void saveSettingsToConfig(@NotNull UserProjectPreferences userPrefs) { + userPrefs.setLockWaitTimeoutMillis(getMaxTimeoutMillis()); + userPrefs.setSocketSoTimeoutMillis(getSocketSoTimeoutMillis()); + userPrefs.setMaxServerConnections(getMaxServerConnections()); + userPrefs.setMaxChangelistNameLength(getMaxChangelistNameLength()); + userPrefs.setPreferRevisionsForFiles(getPreferRevisionsForFiles()); + userPrefs.setConcatenateChangelistNameComment(getConcatenateChangelistNameComment()); + userPrefs.setAutoCheckoutModifiedFiles(getAutoCheckout()); + userPrefs.setMaxClientRetrieveCount(getMaxClientRetrieveCount()); + userPrefs.setMaxChangelistRetrieveCount(getMaxChangelistRetrieveCount()); + userPrefs.setMaxFileRetrieveCount(getMaxFileRetrieveCount()); + userPrefs.setRemoveP4Changelist(getRemoveP4Changelist()); + userPrefs.setUserMessageLevel(getUserMessageLevel()); + userPrefs.setRetryActionCount(getRetryActionCount()); + userPrefs.setNotifyOnRevert(getNotifyOnRevert()); + // Future settings here + } + + + boolean isModified(@NotNull final UserProjectPreferences preferences) { + return + getMaxTimeoutMillis() != preferences.getLockWaitTimeoutMillis() || + getSocketSoTimeoutMillis() != preferences.getSocketSoTimeoutMillis() || + getMaxServerConnections() != preferences.getMaxServerConnections() || + getMaxChangelistNameLength() != preferences.getMaxChangelistNameLength() || + getPreferRevisionsForFiles() != preferences.getPreferRevisionsForFiles() || + getConcatenateChangelistNameComment() != preferences.getConcatenateChangelistNameComment() || + getAutoCheckout() != preferences.getAutoCheckoutModifiedFiles() || + getMaxClientRetrieveCount() != preferences.getMaxClientRetrieveCount() || + getMaxChangelistRetrieveCount() != preferences.getMaxChangelistRetrieveCount() || + getMaxFileRetrieveCount() != preferences.getMaxFileRetrieveCount() || + getRemoveP4Changelist() != preferences.getRemoveP4Changelist() || + getUserMessageLevel() != preferences.getUserMessageLevel() || + getRetryActionCount() != preferences.getRetryActionCount() || + getNotifyOnRevert() != preferences.getNotifyOnRevert(); + // Future settings here + } + + + private int getMaxTimeoutMillis() { + return secondsToMillis(myMaxTimeoutSecondsSpinner); + } + + private boolean getPreferRevisionsForFiles() { + return myPreferRevisionGroup.getSelection() == null || + myPreferRevisionGroup.getSelection() == myPreferRevisionNumber.getModel(); + } + + private int getSocketSoTimeoutMillis() { + return secondsToMillis(mySocketSoTimeoutSecondsSpinner); + } + + private int getMaxServerConnections() { + return toInt(myMaxConnectionsSpinner); + } + + private int getMaxChangelistNameLength() { + return toInt(myMaxChangelistNameSpinner); + } + + private int getMaxClientRetrieveCount() { + return toInt(myMaxClientRetrieveSpinner); + } + + private boolean getConcatenateChangelistNameComment() { + return myConcatenateChangelistNameCommentCheckBox.isSelected(); + } + + private boolean getAutoCheckout() { + return myAutoCheckoutCheckBox.isSelected(); + } + + private int getMaxChangelistRetrieveCount() { + return toInt(myMaxChangelistRetrieveSpinner); + } + + private int getMaxFileRetrieveCount() { + return toInt(myMaxFileRetrieveSpinner); + } + + private boolean getRemoveP4Changelist() { + return myRemoveP4ChangelistCheckBox.isSelected(); + } + + private int getUserMessageLevel() { + Object level = myUserMessageLevelComboBox.getSelectedItem(); + if (level instanceof UserMessageLevel) { + return ((UserMessageLevel) level).value; + } + return UserProjectPreferences.DEFAULT_USER_MESSAGE_LEVEL; + } + + private int getRetryActionCount() { + return toInt(myRetryActionCountSpinner); + } + + private boolean getNotifyOnRevert() { + return myNotifyOnReverts.isSelected(); + } + + + private void createUIComponents() { + // place custom component creation code here + } + + JPanel getRootPanel() { + return myRootPanel; + } + + + private static double millisToSeconds(long millis) { + return ((double) millis) / 1000.; + } + + private static int secondsToMillis(JSpinner secondsSpinner) { + double seconds = ((Number) secondsSpinner.getModel().getValue()).doubleValue(); + return (int) Math.floor(seconds * 1000.); + } + + private static int toInt(JSpinner spinner) { + return ((Number) spinner.getModel().getValue()).intValue(); + } + + private static void setupSpinner(JSpinner spinner, SpinnerModel model) { + spinner.setModel(model); + JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) spinner.getEditor(); + editor.setEnabled(true); + editor.getTextField().setEditable(true); + } + + private static UserMessageLevel messageLevelFromInt(int value) { + for (UserMessageLevel userMessageLevel : UserMessageLevel.values()) { + if (userMessageLevel.value == value) { + return userMessageLevel; + } + } + for (UserMessageLevel userMessageLevel : UserMessageLevel.values()) { + if (userMessageLevel.value == UserProjectPreferences.DEFAULT_USER_MESSAGE_LEVEL) { + return userMessageLevel; + } + } + LOG.warn("UserProjectPreferences.DEFAULT_USER_MESSAGE_LEVEL is an invalid value"); + return UserMessageLevel.INFO; + } + + /** + * @noinspection ALL + */ + private Font getFont1494608681498(String fontName, int style, int size, Font currentFont) { + if (currentFont == null) { + return null; + } + String resultName; + if (fontName == null) { + resultName = currentFont.getName(); + } else { + Font testFont = new Font(fontName, Font.PLAIN, 10); + if (testFont.canDisplay('a') && testFont.canDisplay('1')) { + resultName = fontName; + } else { + resultName = currentFont.getName(); + } + } + return new Font(resultName, style >= 0 ? style : currentFont.getStyle(), + size >= 0 ? size : currentFont.getSize()); + } + + /** + * @noinspection ALL + */ + private Font getFont1495815520855(String fontName, int style, int size, Font currentFont) { + if (currentFont == null) { + return null; + } + String resultName; + if (fontName == null) { + resultName = currentFont.getName(); + } else { + Font testFont = new Font(fontName, Font.PLAIN, 10); + if (testFont.canDisplay('a') && testFont.canDisplay('1')) { + resultName = fontName; + } else { + resultName = currentFont.getName(); + } + } + return new Font(resultName, style >= 0 ? style : currentFont.getStyle(), + size >= 0 ? size : currentFont.getSize()); + } + + /** + * @noinspection ALL + */ + private Font getFont1495815749256(String fontName, int style, int size, Font currentFont) { + if (currentFont == null) { + return null; + } + String resultName; + if (fontName == null) { + resultName = currentFont.getName(); + } else { + Font testFont = new Font(fontName, Font.PLAIN, 10); + if (testFont.canDisplay('a') && testFont.canDisplay('1')) { + resultName = fontName; + } else { + resultName = currentFont.getName(); + } + } + return new Font(resultName, style >= 0 ? style : currentFont.getStyle(), + size >= 0 ? size : currentFont.getSize()); + } + + { +// GUI initializer generated by IntelliJ IDEA GUI Designer +// >>> IMPORTANT!! <<< +// DO NOT EDIT OR ADD ANY CODE HERE! + $$$setupUI$$$(); + } + + /** + * Method generated by IntelliJ IDEA GUI Designer + * >>> IMPORTANT!! <<< + * DO NOT edit this method OR call it in your code! + * + * @noinspection ALL + */ + private void $$$setupUI$$$() { + myRootPanel = new JPanel(); + myRootPanel.setLayout(new GridLayoutManager(12, 2, new Insets(0, 0, 0, 0), -1, -1)); + final JLabel label1 = new JLabel(); + this.$$$loadLabelText$$$(label1, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.max_timeout")); + label1.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.max_timeout.tooltip")); + myRootPanel.add(label1, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final JPanel panel1 = new JPanel(); + panel1.setLayout(new GridLayoutManager(4, 1, new Insets(0, 0, 0, 0), -1, -1)); + myRootPanel.add(panel1, + new GridConstraints(0, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, + 0, false)); + myConcatenateChangelistNameCommentCheckBox = new JCheckBox(); + this.$$$loadButtonText$$$(myConcatenateChangelistNameCommentCheckBox, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.concatenate-changelist")); + myConcatenateChangelistNameCommentCheckBox.setToolTipText( + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.concatenate-changelist.tooltip")); + panel1.add(myConcatenateChangelistNameCommentCheckBox, + new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 1, false)); + myAutoCheckoutCheckBox = new JCheckBox(); + this.$$$loadButtonText$$$(myAutoCheckoutCheckBox, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.auto-checkout")); + myAutoCheckoutCheckBox.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.auto-checkout.tooltip")); + panel1.add(myAutoCheckoutCheckBox, + new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 1, false)); + myRemoveP4ChangelistCheckBox = new JCheckBox(); + this.$$$loadButtonText$$$(myRemoveP4ChangelistCheckBox, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.remove_p4_changelist")); + myRemoveP4ChangelistCheckBox.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.remove_p4_changelist.tooltip")); + panel1.add(myRemoveP4ChangelistCheckBox, + new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 1, false)); + myNotifyOnReverts = new JCheckBox(); + this.$$$loadButtonText$$$(myNotifyOnReverts, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.notify_on_revert")); + myNotifyOnReverts.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.notify_on_revert.tooltip")); + panel1.add(myNotifyOnReverts, + new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 1, false)); + final JPanel panel2 = new JPanel(); + panel2.setLayout(new GridLayoutManager(1, 3, new Insets(0, 0, 0, 0), -1, -1)); + myRootPanel.add(panel2, + new GridConstraints(10, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, + 0, false)); + panel2.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black), + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.rev_display"), + TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, + this.$$$getFont$$$(null, -1, -1, panel2.getFont()))); + myPreferRevisionNumber = new JRadioButton(); + this.$$$loadButtonText$$$(myPreferRevisionNumber, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.revision")); + panel2.add(myPreferRevisionNumber, + new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final Spacer spacer1 = new Spacer(); + panel2.add(spacer1, + new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, + GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); + myPreferChangelist = new JRadioButton(); + this.$$$loadButtonText$$$(myPreferChangelist, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.prefer_changelist")); + panel2.add(myPreferChangelist, + new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final Spacer spacer2 = new Spacer(); + myRootPanel.add(spacer2, + new GridConstraints(11, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, + GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); + final JLabel label2 = new JLabel(); + this.$$$loadLabelText$$$(label2, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.socket-so-timeout")); + label2.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.socket-so-timeout.tooltip")); + myRootPanel.add(label2, new GridConstraints(8, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final JLabel label3 = new JLabel(); + this.$$$loadLabelText$$$(label3, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.max_connections")); + myRootPanel.add(label3, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + myMaxConnectionsSpinner = new JSpinner(); + myRootPanel.add(myMaxConnectionsSpinner, + new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); + final JPanel panel3 = new JPanel(); + panel3.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + myRootPanel.add(panel3, + new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, + 0, false)); + myMaxTimeoutSecondsSpinner = new JSpinner(); + myMaxTimeoutSecondsSpinner.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.max_timeout.tooltip")); + panel3.add(myMaxTimeoutSecondsSpinner); + final JLabel label4 = new JLabel(); + this.$$$loadLabelText$$$(label4, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("user.prefs.max_timeout.unit")); + panel3.add(label4); + final JPanel panel4 = new JPanel(); + panel4.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + myRootPanel.add(panel4, + new GridConstraints(8, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, + 0, false)); + mySocketSoTimeoutSecondsSpinner = new JSpinner(); + mySocketSoTimeoutSecondsSpinner.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.socket-so-timeout.tooltip")); + panel4.add(mySocketSoTimeoutSecondsSpinner); + final JLabel label5 = new JLabel(); + this.$$$loadLabelText$$$(label5, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.socket-so-timeout.unit")); + panel4.add(label5); + final JLabel label6 = new JLabel(); + this.$$$loadLabelText$$$(label6, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.max_client_retrieve")); + label6.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.max_client_retrieve.tooltip")); + myRootPanel.add(label6, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + myMaxClientRetrieveSpinner = new JSpinner(); + myRootPanel.add(myMaxClientRetrieveSpinner, + new GridConstraints(6, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); + myMaxChangelistRetrieveSpinner = new JSpinner(); + myRootPanel.add(myMaxChangelistRetrieveSpinner, + new GridConstraints(4, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); + final JLabel label7 = new JLabel(); + this.$$$loadLabelText$$$(label7, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.max_changelist_retrieve")); + label7.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.max_changelist_retrieve.tooltip")); + myRootPanel.add(label7, new GridConstraints(4, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + myMaxFileRetrieveSpinner = new JSpinner(); + myRootPanel.add(myMaxFileRetrieveSpinner, + new GridConstraints(5, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); + final JLabel label8 = new JLabel(); + this.$$$loadLabelText$$$(label8, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.max_file_retrieve_count")); + label8.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.max_file_retrieve_count.tooltip")); + myRootPanel.add(label8, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + myUserMessageLevelComboBox = new JComboBox(); + myRootPanel.add(myUserMessageLevelComboBox, + new GridConstraints(7, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); + final JLabel label9 = new JLabel(); + this.$$$loadLabelText$$$(label9, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.user_message_level")); + label9.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.user_message_level.tooltip")); + myRootPanel.add(label9, new GridConstraints(7, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + myMaxChangelistNameSpinner = new JSpinner(); + myRootPanel.add(myMaxChangelistNameSpinner, + new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); + final JLabel label10 = new JLabel(); + this.$$$loadLabelText$$$(label10, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.max_changelist_name")); + label10.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.max_changelist_name.tooltip")); + myRootPanel.add(label10, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + myRetryActionCountSpinner = new JSpinner(); + myRootPanel.add(myRetryActionCountSpinner, + new GridConstraints(9, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, + false)); + final JLabel label11 = new JLabel(); + this.$$$loadLabelText$$$(label11, ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.retry-action-count")); + label11.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("user.prefs.retry-action-count.tooltip")); + myRootPanel.add(label11, new GridConstraints(9, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + label1.setLabelFor(myMaxTimeoutSecondsSpinner); + label3.setLabelFor(myMaxConnectionsSpinner); + label6.setLabelFor(myMaxClientRetrieveSpinner); + label7.setLabelFor(myMaxClientRetrieveSpinner); + label8.setLabelFor(myMaxFileRetrieveSpinner); + label9.setLabelFor(myUserMessageLevelComboBox); + label10.setLabelFor(myMaxChangelistNameSpinner); + label11.setLabelFor(myRetryActionCountSpinner); + } + + /** + * @noinspection ALL + */ + private Font $$$getFont$$$(String fontName, int style, int size, Font currentFont) { + if (currentFont == null) { + return null; + } + String resultName; + if (fontName == null) { + resultName = currentFont.getName(); + } else { + Font testFont = new Font(fontName, Font.PLAIN, 10); + if (testFont.canDisplay('a') && testFont.canDisplay('1')) { + resultName = fontName; + } else { + resultName = currentFont.getName(); + } + } + return new Font(resultName, style >= 0 ? style : currentFont.getStyle(), + size >= 0 ? size : currentFont.getSize()); + } + + /** + * @noinspection ALL + */ + private void $$$loadLabelText$$$(JLabel component, String text) { + StringBuffer result = new StringBuffer(); + boolean haveMnemonic = false; + char mnemonic = '\0'; + int mnemonicIndex = -1; + for (int i = 0; i < text.length(); i++) { + if (text.charAt(i) == '&') { + i++; + if (i == text.length()) { + break; + } + if (!haveMnemonic && text.charAt(i) != '&') { + haveMnemonic = true; + mnemonic = text.charAt(i); + mnemonicIndex = result.length(); + } + } + result.append(text.charAt(i)); + } + component.setText(result.toString()); + if (haveMnemonic) { + component.setDisplayedMnemonic(mnemonic); + component.setDisplayedMnemonicIndex(mnemonicIndex); + } + } + + /** + * @noinspection ALL + */ + private void $$$loadButtonText$$$(AbstractButton component, String text) { + StringBuffer result = new StringBuffer(); + boolean haveMnemonic = false; + char mnemonic = '\0'; + int mnemonicIndex = -1; + for (int i = 0; i < text.length(); i++) { + if (text.charAt(i) == '&') { + i++; + if (i == text.length()) { + break; + } + if (!haveMnemonic && text.charAt(i) != '&') { + haveMnemonic = true; + mnemonic = text.charAt(i); + mnemonicIndex = result.length(); + } + } + result.append(text.charAt(i)); + } + component.setText(result.toString()); + if (haveMnemonic) { + component.setMnemonic(mnemonic); + component.setDisplayedMnemonicIndex(mnemonicIndex); + } + } + + /** + * @noinspection ALL + */ + public JComponent $$$getRootComponent$$$() { + return myRootPanel; + } + +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/sync/SyncOptionConfigurable.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/sync/SyncOptionConfigurable.java index 188529be..e105c306 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/sync/SyncOptionConfigurable.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/sync/SyncOptionConfigurable.java @@ -1,100 +1,100 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4plugin.ui.sync; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.options.Configurable; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.project.Project; -import net.groboclown.p4.server.api.config.ClientConfig; -import net.groboclown.p4.server.api.config.ServerConfig; -import net.groboclown.p4plugin.P4Bundle; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -public final class SyncOptionConfigurable implements Configurable { - private static final Logger LOG = Logger.getInstance(SyncOptionConfigurable.class); - - private final Project project; - private final SyncOptions baseOptions; - private final SyncOptions pendingOptions; - private final List configs; - - // TODO allow for changelist browsing. - // For changelist browsing, we can limit the number of changes returned, and have a paging - // mechanism - "p4 changes -m 10 ...@<(last changelist number)" - // This, however, requires access to all the source ServerConfig instances. - - public SyncOptionConfigurable(@NotNull Project project, @NotNull SyncOptions baseOptions, - @NotNull Collection configs) { - this.project = project; - this.baseOptions = baseOptions; - this.pendingOptions = new SyncOptions(baseOptions); - this.configs = new ArrayList<>(configs); - } - - - - @Nls - @Override - public String getDisplayName() { - return P4Bundle.getString("sync.options.title"); - } - - @Nullable - @Override - public String getHelpTopic() { - return null; - } - - @Nullable - @Override - public JComponent createComponent() { - reset(); - return new SyncPanel(project, pendingOptions, configs).getPanel(); - } - - @Override - public boolean isModified() { - return pendingOptions.equals(baseOptions); - } - - @Override - public void apply() throws ConfigurationException { - baseOptions.copyFrom(pendingOptions); - if (LOG.isDebugEnabled()) { - LOG.debug("SyncOptions set to " + pendingOptions); - } - } - - @Override - public void reset() { - pendingOptions.copyFrom(baseOptions); - } - - @Override - public void disposeUIResources() { - // should dispose of the panel - // however, we don't keep references to it, so it is - // automatically cleaned up. - // TODO is this assumption correct? - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4plugin.ui.sync; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.project.Project; +import net.groboclown.p4.server.api.config.ClientConfig; +import net.groboclown.p4.server.api.config.ServerConfig; +import net.groboclown.p4plugin.P4Bundle; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public final class SyncOptionConfigurable implements Configurable { + private static final Logger LOG = Logger.getInstance(SyncOptionConfigurable.class); + + private final Project project; + private final SyncOptions baseOptions; + private final SyncOptions pendingOptions; + private final List configs; + + // TODO allow for changelist browsing. + // For changelist browsing, we can limit the number of changes returned, and have a paging + // mechanism - "p4 changes -m 10 ...@<(last changelist number)" + // This, however, requires access to all the source ServerConfig instances. + + public SyncOptionConfigurable(@NotNull Project project, @NotNull SyncOptions baseOptions, + @NotNull Collection configs) { + this.project = project; + this.baseOptions = baseOptions; + this.pendingOptions = new SyncOptions(baseOptions); + this.configs = new ArrayList<>(configs); + } + + + + @Nls + @Override + public String getDisplayName() { + return P4Bundle.getString("sync.options.title"); + } + + @Nullable + @Override + public String getHelpTopic() { + return null; + } + + @Nullable + @Override + public JComponent createComponent() { + reset(); + return new SyncPanel(project, pendingOptions, configs).getPanel(); + } + + @Override + public boolean isModified() { + return pendingOptions.equals(baseOptions); + } + + @Override + public void apply() throws ConfigurationException { + baseOptions.copyFrom(pendingOptions); + if (LOG.isDebugEnabled()) { + LOG.debug("SyncOptions set to " + pendingOptions); + } + } + + @Override + public void reset() { + pendingOptions.copyFrom(baseOptions); + } + + @Override + public void disposeUIResources() { + // should dispose of the panel + // however, we don't keep references to it, so it is + // automatically cleaned up. + // TODO is this assumption correct? + } +} diff --git a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/sync/SyncPanel.java b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/sync/SyncPanel.java index 106246e2..569378bf 100644 --- a/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/sync/SyncPanel.java +++ b/plugin-v3/src/main/java/net/groboclown/p4plugin/ui/sync/SyncPanel.java @@ -1,453 +1,453 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.groboclown.p4plugin.ui.sync; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.uiDesigner.core.GridConstraints; -import com.intellij.uiDesigner.core.GridLayoutManager; -import com.intellij.uiDesigner.core.Spacer; -import com.jgoodies.forms.layout.CellConstraints; -import com.jgoodies.forms.layout.FormLayout; -import com.perforce.p4java.core.file.IFileSpec; -import net.groboclown.p4.server.api.config.ClientConfig; -import net.groboclown.p4.server.api.config.ServerConfig; -import net.groboclown.p4plugin.P4Bundle; -import net.groboclown.p4plugin.ui.TextFieldListener; -import net.groboclown.p4plugin.ui.TextFieldUtil; -import net.groboclown.p4plugin.ui.history.ChooseLabelDialog; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import java.awt.*; -import java.beans.PropertyChangeEvent; -import java.text.NumberFormat; -import java.util.List; -import java.util.ResourceBundle; - - -/** - * Synchronize revision panel. - *

- * TODO allow for browsing changelists and labels. - */ -public class SyncPanel { - private static final Logger LOG = Logger.getInstance(SyncPanel.class); - - - private JRadioButton mySyncHead; - private JRadioButton mySyncRev; - private JTextField myRevision; - private JRadioButton mySyncChangelist; - private JLabel myRevLabel; - private JLabel myOtherLabel; - private JTextField myOther; - private JPanel myRootPane; - private JCheckBox myForce; - private JButton myFindLabelButton; - private final ButtonGroup syncTypeGroup; - - - SyncPanel(@NotNull Project project, @NotNull final SyncOptions parent, - @NotNull List configs) { - syncTypeGroup = new ButtonGroup(); - $$$setupUI$$$(); - syncTypeGroup.add(mySyncHead); - mySyncHead.addActionListener(e -> { - setPairState(false, myRevLabel, myRevision); - setPairState(false, myOtherLabel, myOther); - myFindLabelButton.setEnabled(false); - updateValues(parent); - }); - syncTypeGroup.add(mySyncRev); - mySyncRev.addActionListener(e -> { - setPairState(true, myRevLabel, myRevision); - setPairState(false, myOtherLabel, myOther); - myFindLabelButton.setEnabled(false); - updateValues(parent); - }); - syncTypeGroup.add(mySyncChangelist); - mySyncChangelist.addActionListener(e -> { - setPairState(false, myRevLabel, myRevision); - setPairState(true, myOtherLabel, myOther); - myFindLabelButton.setEnabled(!configs.isEmpty()); - updateValues(parent); - }); - myFindLabelButton.addActionListener(e -> { - ChooseLabelDialog.show(project, configs, (label) -> { - if (label == null) { - myOther.setText(""); - } else { - myOther.setText(label.getName()); - } - // These should trigger the other actions, but just in case... - updateValues(parent); - }); - }); - TextFieldUtil.addTo(myRevision, new TextFieldListener() { - @Override - public void textUpdated(@NotNull DocumentEvent e, @Nullable String text) { - if (getSelectedSyncType() == SyncOptions.SyncType.REV && getRevValue() < 0) { - Messages.showMessageDialog( - P4Bundle.message("sync.options.rev.error"), - P4Bundle.message("sync.options.rev.error.title"), - Messages.getErrorIcon()); - } else { - updateValues(parent); - } - } - - @Override - public void enabledStateChanged(@NotNull PropertyChangeEvent evt) { - updateValues(parent); - } - }); - TextFieldUtil.addTo(myOther, new TextFieldListener() { - @Override - public void textUpdated(@NotNull DocumentEvent e, @Nullable String text) { - updateValues(parent); - } - - @Override - public void enabledStateChanged(@NotNull PropertyChangeEvent evt) { - updateValues(parent); - } - }); - switch (parent.getSyncType()) { - case HEAD: - syncTypeGroup.setSelected(mySyncHead.getModel(), true); - setPairState(false, myRevLabel, myRevision); - setPairState(false, myOtherLabel, myOther); - myFindLabelButton.setEnabled(false); - break; - case REV: - syncTypeGroup.setSelected(mySyncRev.getModel(), true); - myRevision.setText(Integer.toString(parent.getRev())); - setPairState(true, myRevLabel, myRevision); - setPairState(false, myOtherLabel, myOther); - myFindLabelButton.setEnabled(false); - break; - case OTHER: - syncTypeGroup.setSelected(mySyncChangelist.getModel(), true); - myOther.setText(parent.getOther()); - setPairState(false, myRevLabel, myRevision); - setPairState(true, myOtherLabel, myOther); - myFindLabelButton.setEnabled(!configs.isEmpty()); - break; - default: - throw new IllegalStateException("unknown sync type " + parent.getSyncType()); - } - } - - - JPanel getPanel() { - return myRootPane; - } - - - @NotNull - SyncOptions getState() { - return new SyncOptions( - getSelectedSyncType(), - getRevValue(), - getOtherValue(), - myForce.isSelected() - ); - } - - private void updateValues(@NotNull final SyncOptions options) { - int rev = getRevValue(); - String other = getOtherValue(); - switch (getSelectedSyncType()) { - case HEAD: - options.setHead(); - break; - case REV: - if (rev >= 0) { - options.setRevision(getRevValue()); - } else { - options.setHead(); - } - break; - case OTHER: - if (other != null) { - options.setOther(other); - } else { - options.setHead(); - } - break; - } - } - - - @NotNull - private SyncOptions.SyncType getSelectedSyncType() { - final ButtonModel selected = syncTypeGroup.getSelection(); - if (selected == null) { - LOG.warn("No synchronization option button selected"); - return SyncOptions.SyncType.HEAD; - } - if (selected.equals(mySyncHead.getModel())) { - return SyncOptions.SyncType.HEAD; - } - if (selected.equals(mySyncRev.getModel())) { - return SyncOptions.SyncType.REV; - } - if (selected.equals(mySyncChangelist.getModel())) { - return SyncOptions.SyncType.OTHER; - } - LOG.warn("Unknown synch option button selection"); - return SyncOptions.SyncType.HEAD; - } - - @Nullable - private String getOtherValue() { - if (myOther.isEnabled()) { - LOG.info("Read 'other' value as [" + myOther.getText() + "]"); - return myOther.getText(); - } - LOG.info("Read 'other' value as null"); - return null; - } - - private int getRevValue() { - if (myRevision.isEnabled()) { - final String text = myRevision.getText(); - if (text != null && text.length() > 0) { - try { - final int value = Integer.parseInt(text); - if (value < 0) { - return -1; - } - return value; - } catch (NumberFormatException e) { - return IFileSpec.NO_FILE_REVISION; - } - } - // allow an empty value; it's the same as "head" - return IFileSpec.HEAD_REVISION; - } - return IFileSpec.NO_FILE_REVISION; - } - - - private static void setPairState(boolean state, @NotNull final JLabel label, @NotNull final JTextField field) { - label.setEnabled(state); - field.setEnabled(state); - field.setEditable(state); - } - - private void createUIComponents() { - // place custom component creation code here - - NumberFormat intFormat = NumberFormat.getNumberInstance(); - intFormat.setMinimumFractionDigits(0); - intFormat.setMaximumFractionDigits(0); - intFormat.setGroupingUsed(false); - - // TODO by using a formatted text field, we lose the nice UI skinning. - myRevision = new JFormattedTextField(intFormat); - } - - /** - * Method generated by IntelliJ IDEA GUI Designer - * >>> IMPORTANT!! <<< - * DO NOT edit this method OR call it in your code! - * - * @noinspection ALL - */ - private void $$$setupUI$$$() { - createUIComponents(); - myRootPane = new JPanel(); - myRootPane.setLayout(new BorderLayout(0, 0)); - final JPanel panel1 = new JPanel(); - panel1.setLayout(new GridLayoutManager(4, 2, new Insets(4, 4, 4, 4), -1, -1)); - myRootPane.add(panel1, BorderLayout.NORTH); - panel1.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black), - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.to.title"))); - mySyncHead = new JRadioButton(); - mySyncHead.setSelected(true); - this.$$$loadButtonText$$$(mySyncHead, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.head")); - mySyncHead.setToolTipText( - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.head.tooltip")); - panel1.add(mySyncHead, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - mySyncRev = new JRadioButton(); - this.$$$loadButtonText$$$(mySyncRev, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.rev")); - mySyncRev.setToolTipText( - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.rev.tooltip")); - panel1.add(mySyncRev, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - mySyncChangelist = new JRadioButton(); - this.$$$loadButtonText$$$(mySyncChangelist, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.change")); - mySyncChangelist.setToolTipText( - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.other.tooltip")); - panel1.add(mySyncChangelist, - new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - final Spacer spacer1 = new Spacer(); - panel1.add(spacer1, - new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, - GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); - final JPanel panel2 = new JPanel(); - panel2.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1)); - panel1.add(panel2, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, - false)); - myRevision.setColumns(4); - myRevision.setEnabled(false); - myRevision.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("sync.options.rev.value.tooltip")); - panel2.add(myRevision, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, - false)); - myRevLabel = new JLabel(); - myRevLabel.setEnabled(false); - this.$$$loadLabelText$$$(myRevLabel, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.rev.value")); - panel2.add(myRevLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - final JPanel panel3 = new JPanel(); - panel3.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1)); - panel1.add(panel3, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, - false)); - myOther = new JTextField(); - myOther.setColumns(10); - myOther.setEditable(true); - myOther.setEnabled(false); - myOther.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") - .getString("sync.options.other.value.tooltip")); - panel3.add(myOther, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, - false)); - myOtherLabel = new JLabel(); - myOtherLabel.setEnabled(false); - this.$$$loadLabelText$$$(myOtherLabel, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.other.field")); - panel3.add(myOtherLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - final JPanel panel4 = new JPanel(); - panel4.setLayout(new FormLayout("fill:d:noGrow,left:4dlu:noGrow,fill:d:grow", "center:d:grow")); - panel1.add(panel4, new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, - false)); - myFindLabelButton = new JButton(); - myFindLabelButton.setEnabled(false); - this.$$$loadButtonText$$$(myFindLabelButton, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.find-label")); - CellConstraints cc = new CellConstraints(); - panel4.add(myFindLabelButton, cc.xy(1, 1, CellConstraints.LEFT, CellConstraints.DEFAULT)); - final Spacer spacer2 = new Spacer(); - panel4.add(spacer2, cc.xy(3, 1, CellConstraints.FILL, CellConstraints.DEFAULT)); - final JPanel panel5 = new JPanel(); - panel5.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); - myRootPane.add(panel5, BorderLayout.CENTER); - panel5.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4), null)); - myForce = new JCheckBox(); - this.$$$loadButtonText$$$(myForce, - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.force")); - myForce.setToolTipText( - ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.force.tooltip")); - panel5.add(myForce, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, - GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, - GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - final Spacer spacer3 = new Spacer(); - panel5.add(spacer3, - new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, - GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); - myRevLabel.setLabelFor(myRevision); - myOtherLabel.setLabelFor(myOther); - } - - /** - * @noinspection ALL - */ - private void $$$loadLabelText$$$(JLabel component, String text) { - StringBuffer result = new StringBuffer(); - boolean haveMnemonic = false; - char mnemonic = '\0'; - int mnemonicIndex = -1; - for (int i = 0; i < text.length(); i++) { - if (text.charAt(i) == '&') { - i++; - if (i == text.length()) { - break; - } - if (!haveMnemonic && text.charAt(i) != '&') { - haveMnemonic = true; - mnemonic = text.charAt(i); - mnemonicIndex = result.length(); - } - } - result.append(text.charAt(i)); - } - component.setText(result.toString()); - if (haveMnemonic) { - component.setDisplayedMnemonic(mnemonic); - component.setDisplayedMnemonicIndex(mnemonicIndex); - } - } - - /** - * @noinspection ALL - */ - private void $$$loadButtonText$$$(AbstractButton component, String text) { - StringBuffer result = new StringBuffer(); - boolean haveMnemonic = false; - char mnemonic = '\0'; - int mnemonicIndex = -1; - for (int i = 0; i < text.length(); i++) { - if (text.charAt(i) == '&') { - i++; - if (i == text.length()) { - break; - } - if (!haveMnemonic && text.charAt(i) != '&') { - haveMnemonic = true; - mnemonic = text.charAt(i); - mnemonicIndex = result.length(); - } - } - result.append(text.charAt(i)); - } - component.setText(result.toString()); - if (haveMnemonic) { - component.setMnemonic(mnemonic); - component.setDisplayedMnemonicIndex(mnemonicIndex); - } - } - - /** - * @noinspection ALL - */ - public JComponent $$$getRootComponent$$$() { - return myRootPane; - } -} +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.groboclown.p4plugin.ui.sync; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.uiDesigner.core.GridConstraints; +import com.intellij.uiDesigner.core.GridLayoutManager; +import com.intellij.uiDesigner.core.Spacer; +import com.jgoodies.forms.layout.CellConstraints; +import com.jgoodies.forms.layout.FormLayout; +import com.perforce.p4java.core.file.IFileSpec; +import net.groboclown.p4.server.api.config.ClientConfig; +import net.groboclown.p4.server.api.config.ServerConfig; +import net.groboclown.p4plugin.P4Bundle; +import net.groboclown.p4plugin.ui.TextFieldListener; +import net.groboclown.p4plugin.ui.TextFieldUtil; +import net.groboclown.p4plugin.ui.history.ChooseLabelDialog; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import java.awt.*; +import java.beans.PropertyChangeEvent; +import java.text.NumberFormat; +import java.util.List; +import java.util.ResourceBundle; + + +/** + * Synchronize revision panel. + *

+ * TODO allow for browsing changelists and labels. + */ +public class SyncPanel { + private static final Logger LOG = Logger.getInstance(SyncPanel.class); + + + private JRadioButton mySyncHead; + private JRadioButton mySyncRev; + private JTextField myRevision; + private JRadioButton mySyncChangelist; + private JLabel myRevLabel; + private JLabel myOtherLabel; + private JTextField myOther; + private JPanel myRootPane; + private JCheckBox myForce; + private JButton myFindLabelButton; + private final ButtonGroup syncTypeGroup; + + + SyncPanel(@NotNull Project project, @NotNull final SyncOptions parent, + @NotNull List configs) { + syncTypeGroup = new ButtonGroup(); + $$$setupUI$$$(); + syncTypeGroup.add(mySyncHead); + mySyncHead.addActionListener(e -> { + setPairState(false, myRevLabel, myRevision); + setPairState(false, myOtherLabel, myOther); + myFindLabelButton.setEnabled(false); + updateValues(parent); + }); + syncTypeGroup.add(mySyncRev); + mySyncRev.addActionListener(e -> { + setPairState(true, myRevLabel, myRevision); + setPairState(false, myOtherLabel, myOther); + myFindLabelButton.setEnabled(false); + updateValues(parent); + }); + syncTypeGroup.add(mySyncChangelist); + mySyncChangelist.addActionListener(e -> { + setPairState(false, myRevLabel, myRevision); + setPairState(true, myOtherLabel, myOther); + myFindLabelButton.setEnabled(!configs.isEmpty()); + updateValues(parent); + }); + myFindLabelButton.addActionListener(e -> { + ChooseLabelDialog.show(project, configs, (label) -> { + if (label == null) { + myOther.setText(""); + } else { + myOther.setText(label.getName()); + } + // These should trigger the other actions, but just in case... + updateValues(parent); + }); + }); + TextFieldUtil.addTo(myRevision, new TextFieldListener() { + @Override + public void textUpdated(@NotNull DocumentEvent e, @Nullable String text) { + if (getSelectedSyncType() == SyncOptions.SyncType.REV && getRevValue() < 0) { + Messages.showMessageDialog( + P4Bundle.message("sync.options.rev.error"), + P4Bundle.message("sync.options.rev.error.title"), + Messages.getErrorIcon()); + } else { + updateValues(parent); + } + } + + @Override + public void enabledStateChanged(@NotNull PropertyChangeEvent evt) { + updateValues(parent); + } + }); + TextFieldUtil.addTo(myOther, new TextFieldListener() { + @Override + public void textUpdated(@NotNull DocumentEvent e, @Nullable String text) { + updateValues(parent); + } + + @Override + public void enabledStateChanged(@NotNull PropertyChangeEvent evt) { + updateValues(parent); + } + }); + switch (parent.getSyncType()) { + case HEAD: + syncTypeGroup.setSelected(mySyncHead.getModel(), true); + setPairState(false, myRevLabel, myRevision); + setPairState(false, myOtherLabel, myOther); + myFindLabelButton.setEnabled(false); + break; + case REV: + syncTypeGroup.setSelected(mySyncRev.getModel(), true); + myRevision.setText(Integer.toString(parent.getRev())); + setPairState(true, myRevLabel, myRevision); + setPairState(false, myOtherLabel, myOther); + myFindLabelButton.setEnabled(false); + break; + case OTHER: + syncTypeGroup.setSelected(mySyncChangelist.getModel(), true); + myOther.setText(parent.getOther()); + setPairState(false, myRevLabel, myRevision); + setPairState(true, myOtherLabel, myOther); + myFindLabelButton.setEnabled(!configs.isEmpty()); + break; + default: + throw new IllegalStateException("unknown sync type " + parent.getSyncType()); + } + } + + + JPanel getPanel() { + return myRootPane; + } + + + @NotNull + SyncOptions getState() { + return new SyncOptions( + getSelectedSyncType(), + getRevValue(), + getOtherValue(), + myForce.isSelected() + ); + } + + private void updateValues(@NotNull final SyncOptions options) { + int rev = getRevValue(); + String other = getOtherValue(); + switch (getSelectedSyncType()) { + case HEAD: + options.setHead(); + break; + case REV: + if (rev >= 0) { + options.setRevision(getRevValue()); + } else { + options.setHead(); + } + break; + case OTHER: + if (other != null) { + options.setOther(other); + } else { + options.setHead(); + } + break; + } + } + + + @NotNull + private SyncOptions.SyncType getSelectedSyncType() { + final ButtonModel selected = syncTypeGroup.getSelection(); + if (selected == null) { + LOG.warn("No synchronization option button selected"); + return SyncOptions.SyncType.HEAD; + } + if (selected.equals(mySyncHead.getModel())) { + return SyncOptions.SyncType.HEAD; + } + if (selected.equals(mySyncRev.getModel())) { + return SyncOptions.SyncType.REV; + } + if (selected.equals(mySyncChangelist.getModel())) { + return SyncOptions.SyncType.OTHER; + } + LOG.warn("Unknown synch option button selection"); + return SyncOptions.SyncType.HEAD; + } + + @Nullable + private String getOtherValue() { + if (myOther.isEnabled()) { + LOG.info("Read 'other' value as [" + myOther.getText() + "]"); + return myOther.getText(); + } + LOG.info("Read 'other' value as null"); + return null; + } + + private int getRevValue() { + if (myRevision.isEnabled()) { + final String text = myRevision.getText(); + if (text != null && text.length() > 0) { + try { + final int value = Integer.parseInt(text); + if (value < 0) { + return -1; + } + return value; + } catch (NumberFormatException e) { + return IFileSpec.NO_FILE_REVISION; + } + } + // allow an empty value; it's the same as "head" + return IFileSpec.HEAD_REVISION; + } + return IFileSpec.NO_FILE_REVISION; + } + + + private static void setPairState(boolean state, @NotNull final JLabel label, @NotNull final JTextField field) { + label.setEnabled(state); + field.setEnabled(state); + field.setEditable(state); + } + + private void createUIComponents() { + // place custom component creation code here + + NumberFormat intFormat = NumberFormat.getNumberInstance(); + intFormat.setMinimumFractionDigits(0); + intFormat.setMaximumFractionDigits(0); + intFormat.setGroupingUsed(false); + + // TODO by using a formatted text field, we lose the nice UI skinning. + myRevision = new JFormattedTextField(intFormat); + } + + /** + * Method generated by IntelliJ IDEA GUI Designer + * >>> IMPORTANT!! <<< + * DO NOT edit this method OR call it in your code! + * + * @noinspection ALL + */ + private void $$$setupUI$$$() { + createUIComponents(); + myRootPane = new JPanel(); + myRootPane.setLayout(new BorderLayout(0, 0)); + final JPanel panel1 = new JPanel(); + panel1.setLayout(new GridLayoutManager(4, 2, new Insets(4, 4, 4, 4), -1, -1)); + myRootPane.add(panel1, BorderLayout.NORTH); + panel1.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black), + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.to.title"))); + mySyncHead = new JRadioButton(); + mySyncHead.setSelected(true); + this.$$$loadButtonText$$$(mySyncHead, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.head")); + mySyncHead.setToolTipText( + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.head.tooltip")); + panel1.add(mySyncHead, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + mySyncRev = new JRadioButton(); + this.$$$loadButtonText$$$(mySyncRev, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.rev")); + mySyncRev.setToolTipText( + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.rev.tooltip")); + panel1.add(mySyncRev, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + mySyncChangelist = new JRadioButton(); + this.$$$loadButtonText$$$(mySyncChangelist, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.change")); + mySyncChangelist.setToolTipText( + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.other.tooltip")); + panel1.add(mySyncChangelist, + new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final Spacer spacer1 = new Spacer(); + panel1.add(spacer1, + new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, + GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); + final JPanel panel2 = new JPanel(); + panel2.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1)); + panel1.add(panel2, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, + false)); + myRevision.setColumns(4); + myRevision.setEnabled(false); + myRevision.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("sync.options.rev.value.tooltip")); + panel2.add(myRevision, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, + false)); + myRevLabel = new JLabel(); + myRevLabel.setEnabled(false); + this.$$$loadLabelText$$$(myRevLabel, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.rev.value")); + panel2.add(myRevLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final JPanel panel3 = new JPanel(); + panel3.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1)); + panel1.add(panel3, new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, + false)); + myOther = new JTextField(); + myOther.setColumns(10); + myOther.setEditable(true); + myOther.setEnabled(false); + myOther.setToolTipText(ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle") + .getString("sync.options.other.value.tooltip")); + panel3.add(myOther, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, + false)); + myOtherLabel = new JLabel(); + myOtherLabel.setEnabled(false); + this.$$$loadLabelText$$$(myOtherLabel, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.other.field")); + panel3.add(myOtherLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final JPanel panel4 = new JPanel(); + panel4.setLayout(new FormLayout("fill:d:noGrow,left:4dlu:noGrow,fill:d:grow", "center:d:grow")); + panel1.add(panel4, new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, + false)); + myFindLabelButton = new JButton(); + myFindLabelButton.setEnabled(false); + this.$$$loadButtonText$$$(myFindLabelButton, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.find-label")); + CellConstraints cc = new CellConstraints(); + panel4.add(myFindLabelButton, cc.xy(1, 1, CellConstraints.LEFT, CellConstraints.DEFAULT)); + final Spacer spacer2 = new Spacer(); + panel4.add(spacer2, cc.xy(3, 1, CellConstraints.FILL, CellConstraints.DEFAULT)); + final JPanel panel5 = new JPanel(); + panel5.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); + myRootPane.add(panel5, BorderLayout.CENTER); + panel5.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4), null)); + myForce = new JCheckBox(); + this.$$$loadButtonText$$$(myForce, + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.force")); + myForce.setToolTipText( + ResourceBundle.getBundle("net/groboclown/p4plugin/P4Bundle").getString("sync.options.force.tooltip")); + panel5.add(myForce, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); + final Spacer spacer3 = new Spacer(); + panel5.add(spacer3, + new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, + GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); + myRevLabel.setLabelFor(myRevision); + myOtherLabel.setLabelFor(myOther); + } + + /** + * @noinspection ALL + */ + private void $$$loadLabelText$$$(JLabel component, String text) { + StringBuffer result = new StringBuffer(); + boolean haveMnemonic = false; + char mnemonic = '\0'; + int mnemonicIndex = -1; + for (int i = 0; i < text.length(); i++) { + if (text.charAt(i) == '&') { + i++; + if (i == text.length()) { + break; + } + if (!haveMnemonic && text.charAt(i) != '&') { + haveMnemonic = true; + mnemonic = text.charAt(i); + mnemonicIndex = result.length(); + } + } + result.append(text.charAt(i)); + } + component.setText(result.toString()); + if (haveMnemonic) { + component.setDisplayedMnemonic(mnemonic); + component.setDisplayedMnemonicIndex(mnemonicIndex); + } + } + + /** + * @noinspection ALL + */ + private void $$$loadButtonText$$$(AbstractButton component, String text) { + StringBuffer result = new StringBuffer(); + boolean haveMnemonic = false; + char mnemonic = '\0'; + int mnemonicIndex = -1; + for (int i = 0; i < text.length(); i++) { + if (text.charAt(i) == '&') { + i++; + if (i == text.length()) { + break; + } + if (!haveMnemonic && text.charAt(i) != '&') { + haveMnemonic = true; + mnemonic = text.charAt(i); + mnemonicIndex = result.length(); + } + } + result.append(text.charAt(i)); + } + component.setText(result.toString()); + if (haveMnemonic) { + component.setMnemonic(mnemonic); + component.setDisplayedMnemonicIndex(mnemonicIndex); + } + } + + /** + * @noinspection ALL + */ + public JComponent $$$getRootComponent$$$() { + return myRootPane; + } +}