mimeTypeList = Lists.newArrayList();
32 |
33 | static {
34 | mimeTypeList.add(new MediaType("application", "json", DEFAULT_CHARSET));
35 | mimeTypeList.add(new MediaType("application", "*+json", DEFAULT_CHARSET));
36 | }
37 |
38 | private Gson gson = new Gson();
39 |
40 |
41 | public GsonMessageConverter() {
42 | super(mimeTypeList);
43 | }
44 |
45 |
46 | /**
47 | * Set the {@code Gson} instance to use.
48 | * If not set, a default {@link Gson#Gson() Gson} instance is used.
49 | * Setting a custom-configured {@code Gson} is one way to take further
50 | * control of the JSON serialization process.
51 | */
52 | public void setGson(Gson gson) {
53 | Assert.notNull(gson, "'gson' is required");
54 | this.gson = gson;
55 | }
56 |
57 | /**
58 | * Return the configured {@code Gson} instance for this converter.
59 | */
60 | public Gson getGson() {
61 | return this.gson;
62 | }
63 |
64 |
65 | @Override
66 | protected boolean supports(Class> clazz) {
67 | return true;
68 | }
69 |
70 | @Override
71 | public Object convertFromInternal(Message> message, Class> targetClass) {
72 |
73 | TypeToken> token = getTypeToken(targetClass);
74 |
75 | Object payload = message.getPayload();
76 |
77 | Charset charset = getCharset(getMimeType(message.getHeaders()));
78 |
79 | Reader reader;
80 |
81 | if (payload instanceof byte[]) {
82 | reader = new InputStreamReader(new ByteArrayInputStream((byte[]) payload), charset);
83 | } else {
84 | reader = new StringReader((String) payload);
85 | }
86 |
87 | try {
88 |
89 | return this.gson.fromJson(reader, token.getType());
90 |
91 | } catch (JsonParseException ex) {
92 | throw new MessageConversionException(message, "Could not read JSON: " + ex.getMessage(), ex);
93 | }
94 | }
95 |
96 |
97 | @Override
98 | public Object convertToInternal(Object payload, MessageHeaders headers) {
99 |
100 | Charset charset = getCharset(getMimeType(headers));
101 | try {
102 | if (byte[].class.equals(getSerializedPayloadClass())) {
103 | ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
104 | OutputStreamWriter writer = new OutputStreamWriter(out, charset);
105 | this.gson.toJson(payload, writer);
106 | writer.close();
107 | payload = out.toByteArray();
108 | } else {
109 | Writer writer = new StringWriter();
110 | this.gson.toJson(payload, writer);
111 | payload = writer.toString();
112 | }
113 | } catch (IOException ex) {
114 | throw new MessageConversionException("Could not write JSON: " + ex.getMessage(), ex);
115 | }
116 |
117 | return payload;
118 | }
119 |
120 | protected TypeToken> getTypeToken(Type type) {
121 | return TypeToken.get(type);
122 | }
123 |
124 | private Charset getCharset(MimeType contentType) {
125 |
126 | if ((contentType != null) && (contentType.getCharSet() != null)) {
127 | return contentType.getCharSet();
128 | }
129 |
130 | return DEFAULT_CHARSET;
131 |
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/src/main/java/net/coding/ide/web/message/EventExchange.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2016 CODING.
3 | */
4 |
5 | package net.coding.ide.web.message;
6 |
7 | import com.fatboyindustrial.gsonjodatime.Converters;
8 | import com.google.gson.Gson;
9 | import com.google.gson.GsonBuilder;
10 | import com.google.gson.JsonObject;
11 | import lombok.extern.slf4j.Slf4j;
12 | import net.coding.ide.event.*;
13 | import net.coding.ide.model.FileInfo;
14 | import net.coding.ide.model.Workspace;
15 | import org.springframework.beans.BeansException;
16 | import org.springframework.beans.factory.annotation.Autowired;
17 | import org.springframework.context.*;
18 | import org.springframework.context.event.EventListener;
19 | import org.springframework.messaging.Message;
20 | import org.springframework.messaging.simp.SimpMessagingTemplate;
21 | import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
22 | import org.springframework.stereotype.Component;
23 | import org.springframework.web.socket.messaging.AbstractSubProtocolEvent;
24 | import org.springframework.web.socket.messaging.SessionConnectEvent;
25 | import org.springframework.web.socket.messaging.SessionDisconnectEvent;
26 |
27 | /**
28 | * Created by vangie on 15/2/4.
29 | */
30 | @Slf4j
31 | @Component
32 | public class EventExchange {
33 |
34 | @Autowired
35 | private WebSocketSessionStore webSocketSessionStore;
36 |
37 | @Autowired
38 | private OnlineWorkspaceStore onlineWorkspaceStore;
39 |
40 | @Autowired
41 | private SimpMessagingTemplate simpMessagingTemplate;
42 |
43 | @Autowired
44 | private ApplicationEventPublisher eventPublisher;
45 |
46 | private Gson gson = Converters.registerDateTime(new GsonBuilder()).create();
47 |
48 | @EventListener
49 | public void onSessionConnected(SessionConnectEvent event) {
50 | Message msg = event.getMessage();
51 | StompHeaderAccessor accessor = StompHeaderAccessor.wrap(msg);
52 | String sessionId = accessor.getSessionId();
53 |
54 | String spaceKey = (String) webSocketSessionStore.getAttribute(sessionId, "spaceKey");
55 |
56 | log.debug("Session connect: spaceKey => {}, sessionId => {} ", spaceKey, sessionId);
57 |
58 | if (spaceKey == null) {
59 | return;
60 | }
61 |
62 | boolean isToBeOnline = onlineWorkspaceStore.isEmpty(spaceKey);
63 | onlineWorkspaceStore.addSession(spaceKey, sessionId);
64 |
65 | if (isToBeOnline) {
66 | eventPublisher.publishEvent(new WorkspaceOnlineEvent(event, spaceKey));
67 | }
68 | }
69 |
70 | @EventListener
71 | public void onSessionDisConnected(SessionDisconnectEvent event) {
72 | Message msg = event.getMessage();
73 | StompHeaderAccessor accessor = StompHeaderAccessor.wrap(msg);
74 | String sessionId = accessor.getSessionId();
75 |
76 | String spaceKey = (String) webSocketSessionStore.getAttribute(sessionId, "spaceKey");
77 |
78 | log.debug("Session disconnect: spaceKey => {}, sessionId => {} ", spaceKey, sessionId);
79 |
80 | if (spaceKey == null) {
81 | return;
82 | }
83 |
84 | onlineWorkspaceStore.removeSession(spaceKey, sessionId);
85 | boolean isToBeOffline = onlineWorkspaceStore.isEmpty(spaceKey);
86 |
87 | if (isToBeOffline) {
88 | eventPublisher.publishEvent(new WorkspaceOfflineEvent(event, spaceKey));
89 | }
90 |
91 | }
92 |
93 | @EventListener
94 | public void onFileChanged(FileChangeEvent event) {
95 | String spaceKey = event.getSpaceKey();
96 | FileInfo fileInfo = event.getFileInfo();
97 |
98 | JsonObject jsonObj = new JsonObject();
99 | jsonObj.add("fileInfo", gson.toJsonTree(fileInfo));
100 |
101 | if (event instanceof FileCreateEvent) {
102 | jsonObj.addProperty("changeType", "create");
103 | } else if (event instanceof FileModifyEvent) {
104 | jsonObj.addProperty("changeType", "modify");
105 | } else if (event instanceof FileDeleteEvent) {
106 | jsonObj.addProperty("changeType", "delete");
107 | }
108 |
109 | if (fileInfo.getLastModified() != null) {
110 | jsonObj.addProperty("lastModified", fileInfo.getLastModified().getMillis());
111 | }
112 |
113 | log.debug("send file change event: {}", event);
114 | simpMessagingTemplate.convertAndSend("/topic/ws/" + spaceKey + "/change", jsonObj);
115 | }
116 |
117 | @EventListener
118 | public void onGitCheckout(GitCheckoutEvent event) {
119 | Workspace ws = event.getWorkspace();
120 | String spaceKey = ws.getSpaceKey();
121 |
122 | String branch = event.getBranch();
123 |
124 | JsonObject jsonObj = new JsonObject();
125 | jsonObj.addProperty("branch", branch);
126 |
127 | simpMessagingTemplate.convertAndSend("/topic/git/" + spaceKey + "/checkout", jsonObj);
128 | }
129 |
130 | @EventListener
131 | public void onWorkspaceDeleted(WorkspaceDeleteEvent event) {
132 | String spaceKey = event.getSpaceKey();
133 |
134 | simpMessagingTemplate.convertAndSend("/topic/ws/" + spaceKey + "/delete", (Object) null);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/main/java/net/coding/ide/service/KeyManagerImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2016 CODING.
3 | */
4 |
5 | package net.coding.ide.service;
6 |
7 | import com.google.common.io.Files;
8 | import lombok.extern.slf4j.Slf4j;
9 | import net.coding.ide.model.Key;
10 | import net.coding.ide.model.Workspace;
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 | import org.springframework.beans.factory.annotation.Autowired;
14 | import org.springframework.beans.factory.annotation.Value;
15 | import org.springframework.stereotype.Service;
16 | import org.springframework.transaction.annotation.Transactional;
17 |
18 | import java.io.File;
19 | import java.io.IOException;
20 | import java.security.KeyPair;
21 | import java.security.KeyPairGenerator;
22 | import java.security.NoSuchAlgorithmException;
23 | import java.security.interfaces.RSAPrivateKey;
24 | import java.security.interfaces.RSAPublicKey;
25 |
26 | import static com.google.common.collect.Sets.newHashSet;
27 | import static java.lang.String.format;
28 | import static java.nio.charset.Charset.defaultCharset;
29 | import static java.nio.file.Files.setPosixFilePermissions;
30 | import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
31 | import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
32 | import static net.coding.ide.utils.KeyUtils.fingerprint;
33 | import static net.coding.ide.utils.KeyUtils.keyToString;
34 | import static org.apache.commons.lang.SystemUtils.IS_OS_WINDOWS;
35 |
36 | /**
37 | * Created by vangie on 14/12/24.
38 | */
39 | @Slf4j
40 | @Service
41 | public class KeyManagerImpl extends BaseService implements KeyManager {
42 | private final static String CODING_PUB_KEY = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHOWdwLpkos2CLli6DFvQ36yQE6Pe" +
43 | "/PtFp3XwyirfZCIoGWnedaWI8zkJWVCs0wgOB9/urFepTDfV2wN49KGy1sl2/CCDEH2K/zeoEAZlTcBrhU17bwg1yMHCyJ7IM+zdLzIt" +
44 | "DEKYjgoWqVdUGK1dXQQlwt7GP4W7HqffelQQoVxOMoZ5N50MzD+nvV4y8iq0KwDQNy62iU4hui9ajCSVUDLu/06ucd5IojSI9keRIYAX" +
45 | "vQf52TJ5EbvoBggp9RhjuWNEG8IhnPP6rzPS11Ocmwg/HsP8xOKL28AeDBAh6B6MEBDtlyp5Yfu9cwZJ9CFtU/x5fHFPtANmgIphAfwN1";
46 |
47 | private final static String PUBLICKEY = "publicKey";
48 |
49 | private final static String PRIVATEKEY = "privateKey";
50 |
51 | private final static String FINGERPRINT = "fingerPrint";
52 |
53 | @Value("${USERNAME}")
54 | private String username;
55 |
56 | @Autowired
57 | private ConfigService configService;
58 |
59 | public boolean isKeyExist() {
60 | return configService.getByKey(PUBLICKEY) != null;
61 | }
62 |
63 | @Override
64 | public boolean isKeyExist(Workspace ws) {
65 | return getPrivateKeyFile(ws).exists() && getPublicKeyFile(ws).exists();
66 | }
67 |
68 | public Key generateKey() throws IOException {
69 | KeyPairGenerator generator = null;
70 | try {
71 | generator = KeyPairGenerator.getInstance("RSA");
72 | // or: generator = KeyPairGenerator.getInstance("DSA");
73 | } catch (NoSuchAlgorithmException e) {
74 | e.printStackTrace();
75 | }
76 |
77 | generator.initialize(2048);
78 | KeyPair keyPair = generator.genKeyPair();
79 | final String publicKey = keyToString((RSAPublicKey) keyPair.getPublic(), username);
80 | final String privateKey = keyToString((RSAPrivateKey) keyPair.getPrivate());
81 | final String fingerprint = fingerprint(publicKey);
82 |
83 | Key key = new Key(privateKey, publicKey, fingerprint);
84 |
85 | saveOrUpdateKey(key);
86 |
87 | return key;
88 | }
89 |
90 | @Transactional
91 | private void saveOrUpdateKey(Key key) {
92 | configService.setCfg(PUBLICKEY, PUBLICKEY, key.getPublicKey());
93 | configService.setCfg(PRIVATEKEY, PRIVATEKEY, key.getPrivateKey());
94 | configService.setCfg(FINGERPRINT, FINGERPRINT, key.getFingerprint());
95 | }
96 |
97 | public Key getKey() {
98 | Key key = new Key();
99 |
100 | key.setPrivateKey(configService.getValue(PRIVATEKEY));
101 | key.setPublicKey(configService.getValue(PUBLICKEY));
102 | key.setFingerprint(configService.getValue(FINGERPRINT));
103 |
104 | return key;
105 | }
106 |
107 | @Override
108 | public void copyToWorkspace(Workspace ws) {
109 | try {
110 | if (!isKeyExist()) generateKey();
111 |
112 | Key key = getKey();
113 |
114 | Files.write(key.getPublicKey(), getPublicKeyFile(ws), defaultCharset());
115 | Files.write(key.getPrivateKey(), getPrivateKeyFile(ws), defaultCharset());
116 | Files.write(format("coding.net %s\n", CODING_PUB_KEY), getKnownHostsFile(ws), defaultCharset());
117 |
118 | //ignore on windows.
119 | if (!IS_OS_WINDOWS) {
120 | //using PosixFilePermission to set file permissions 600
121 | setPosixFilePermissions(getPrivateKeyFile(ws).toPath(), newHashSet(OWNER_READ, OWNER_WRITE));
122 | }
123 | } catch (IOException e) {
124 | log.error("copy key to workspace failed", e);
125 | }
126 | }
127 | @Override
128 | public File getPrivateKeyFile(Workspace ws) {
129 | return new File(ws.getKeyDir(), "id_rsa");
130 | }
131 |
132 | @Override
133 | public File getPublicKeyFile(Workspace ws) {
134 | return new File(ws.getKeyDir(), "id_rsa.pub");
135 | }
136 |
137 | @Override
138 | public File getKnownHostsFile(Workspace ws) {
139 | return new File(ws.getKeyDir(), "known_hosts");
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/main/java/com/pty4j/util/PtyUtil.java:
--------------------------------------------------------------------------------
1 | package com.pty4j.util;
2 |
3 | import com.google.common.base.Function;
4 | import com.google.common.collect.Lists;
5 | import com.pty4j.windows.WinPty;
6 | import com.sun.jna.Platform;
7 |
8 | import java.io.File;
9 | import java.net.URI;
10 | import java.security.CodeSource;
11 | import java.util.List;
12 | import java.util.Map;
13 |
14 | /**
15 | * @author traff
16 | */
17 | public class PtyUtil {
18 | public static final String OS_VERSION = System.getProperty("os.version").toLowerCase();
19 |
20 | private final static String PTY_LIB_FOLDER = System.getenv("PTY_LIB_FOLDER") != null ? System.getenv("PTY_LIB_FOLDER") : System.getProperty("PTY_LIB_FOLDER");
21 |
22 | public static String[] toStringArray(Map environment) {
23 | if (environment == null) return new String[0];
24 | List list = Lists.transform(Lists.newArrayList(environment.entrySet()), new Function, String>() {
25 | public String apply(Map.Entry entry) {
26 | return entry.getKey() + "=" + entry.getValue();
27 | }
28 | });
29 | return list.toArray(new String[list.size()]);
30 | }
31 |
32 | /**
33 | * Returns the folder that contains a jar that contains the class
34 | *
35 | * @param aclass a class to find a jar
36 | * @return
37 | */
38 | public static String getJarContainingFolderPath(Class aclass) throws Exception {
39 | CodeSource codeSource = aclass.getProtectionDomain().getCodeSource();
40 |
41 | File jarFile;
42 |
43 | if (codeSource.getLocation() != null) {
44 | jarFile = new File(codeSource.getLocation().toURI());
45 | } else {
46 | String path = aclass.getResource(aclass.getSimpleName() + ".class").getPath();
47 |
48 | int startIndex = path.indexOf(":") + 1;
49 | int endIndex = path.indexOf("!");
50 | if (startIndex == -1 || endIndex == -1) {
51 | throw new IllegalStateException("Class " + aclass.getSimpleName() + " is located not within a jar: " + path);
52 | }
53 | String jarFilePath = path.substring(startIndex, endIndex);
54 | jarFilePath = new URI(jarFilePath).getPath();
55 | jarFile = new File(jarFilePath);
56 | }
57 | return jarFile.getParentFile().getAbsolutePath();
58 | }
59 |
60 | public static String getPtyLibFolderPath() throws Exception {
61 | if (PTY_LIB_FOLDER != null) {
62 | return PTY_LIB_FOLDER;
63 | }
64 | //Class aclass = WinPty.class.getClassLoader().loadClass("com.jediterm.pty.PtyMain");
65 | Class aclass = WinPty.class;
66 |
67 | return getJarContainingFolderPath(aclass);
68 | }
69 |
70 | public static File resolveNativeLibrary() throws Exception {
71 | String libFolderPath = getPtyLibFolderPath();
72 |
73 | if (libFolderPath != null) {
74 |
75 | File libFolder = new File(libFolderPath);
76 | File lib = resolveNativeLibrary(libFolder);
77 |
78 | lib = lib.exists() ? lib : resolveNativeLibrary(new File(libFolder, "libpty"));
79 |
80 | if (!lib.exists()) {
81 | throw new IllegalStateException(String.format("Couldn't find %s, jar folder %s", lib.getName(),
82 | libFolder.getAbsolutePath()));
83 | }
84 |
85 | return lib;
86 | } else {
87 | throw new IllegalStateException("Couldn't detect lib folder");
88 | }
89 | }
90 |
91 | public static File resolveNativeLibrary(File parent) {
92 | return resolveNativeFile(parent, getNativeLibraryName());
93 | }
94 |
95 | public static File resolveNativeFile(String fileName) throws Exception {
96 | File libFolder = new File(getPtyLibFolderPath());
97 | File file = resolveNativeFile(libFolder, fileName);
98 | return file.exists() ? file : resolveNativeFile(new File(libFolder, "libpty"), fileName);
99 | }
100 |
101 | public static File resolveNativeFile(File parent, String fileName) {
102 | final File path = new File(parent, getPlatformFolder());
103 |
104 | String arch = Platform.is64Bit() ? "x86_64" : "x86";
105 | String prefix = isWinXp() ? "xp" : arch;
106 |
107 | if (new File(parent, prefix).exists()) {
108 | return new File(new File(parent, prefix), fileName);
109 | } else {
110 | return new File(new File(path, prefix), fileName);
111 | }
112 | }
113 |
114 | private static String getPlatformFolder() {
115 | String result;
116 |
117 | if (Platform.isMac()) {
118 | result = "macosx";
119 | } else if (Platform.isWindows()) {
120 | result = "win";
121 | } else if (Platform.isLinux()) {
122 | result = "linux";
123 | } else if (Platform.isFreeBSD()) {
124 | result = "freebsd";
125 | } else if (Platform.isOpenBSD()) {
126 | result = "openbsd";
127 | } else {
128 | throw new IllegalStateException("Platform " + Platform.getOSType() + " is not supported");
129 | }
130 |
131 | return result;
132 | }
133 |
134 | private static String getNativeLibraryName() {
135 | String result;
136 |
137 | if (Platform.isMac()) {
138 | result = "libpty.dylib";
139 | } else if (Platform.isWindows()) {
140 | result = "winpty.dll";
141 | } else if (Platform.isLinux() || Platform.isFreeBSD() || Platform.isOpenBSD()) {
142 | result = "libpty.so";
143 | } else {
144 | throw new IllegalStateException("Platform " + Platform.getOSType() + " is not supported");
145 | }
146 |
147 | return result;
148 | }
149 |
150 | public static boolean isWinXp() {
151 | return Platform.isWindows() && (OS_VERSION.equals("5.1") || OS_VERSION.equals("5.2"));
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/main/java/net/coding/ide/service/GitManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2016 CODING.
3 | */
4 |
5 | package net.coding.ide.service;
6 |
7 | import net.coding.ide.model.*;
8 | import net.coding.ide.model.exception.GitOperationException;
9 | import org.eclipse.jgit.api.errors.GitAPIException;
10 | import org.eclipse.jgit.revwalk.filter.RevFilter;
11 | import org.springframework.data.domain.Pageable;
12 |
13 | import java.io.IOException;
14 | import java.io.InputStream;
15 | import java.nio.file.AccessDeniedException;
16 | import java.nio.file.Path;
17 | import java.util.List;
18 | import java.util.Map;
19 |
20 | /**
21 | * Created by vangie on 14/12/29.
22 | */
23 | public interface GitManager {
24 |
25 | GitStatus status(Workspace ws, Path path) throws Exception;
26 |
27 | boolean clone(Workspace ws) throws IOException, GitAPIException;
28 |
29 | void config(Workspace ws) throws IOException;
30 |
31 | CommitStatus getStatus(Workspace ws) throws GitAPIException;
32 |
33 | MergeResponse merge(Workspace ws, String branch) throws GitAPIException, IOException;
34 |
35 | void createStash(Workspace ws, boolean includeUntracked, String message) throws GitAPIException, GitOperationException;
36 |
37 | void applyStash(Workspace ws, String stashRef, boolean applyIndex, boolean pop) throws GitAPIException;
38 |
39 | CheckoutResponse checkoutStash(Workspace ws, String stashRef, String branch) throws IOException, GitAPIException, GitOperationException;
40 |
41 | ListStashResponse listStash(Workspace ws) throws GitAPIException;
42 |
43 | void dropStash(Workspace ws, String stashRef) throws GitAPIException;
44 |
45 | void dropAllStash(Workspace ws) throws GitAPIException;
46 |
47 | ConflictFile queryConflictFile(Workspace ws, String path, boolean base64) throws Exception;
48 |
49 | void deleteConflictFile(Workspace ws, String path) throws Exception;
50 |
51 | void resolveConflictFile(Workspace ws, String path, String content, boolean base64) throws Exception;
52 |
53 | List commit(Workspace ws, List files, String message) throws GitAPIException, IOException;
54 |
55 | List commitAll(Workspace ws, String message) throws GitAPIException, IOException;
56 |
57 | List refs(Workspace ws) throws IOException, GitAPIException;
58 |
59 | List log(Workspace ws, String[] ref, String[] path, String[] authors, Long since, Long until, Pageable pageable) throws GitAPIException, IOException;
60 |
61 | List blame(Workspace ws, String path) throws AccessDeniedException, GitAPIException;
62 |
63 | String diff(Workspace ws, String path, String oldRef, String newRef) throws IOException, GitAPIException;
64 |
65 | void sync(Workspace ws) throws GitAPIException;
66 |
67 | PushCommits getPushCommits(Workspace ws) throws IOException, GitAPIException, GitOperationException;
68 |
69 | PushCommits getPushCommits(Workspace ws, String branch) throws IOException, GitAPIException, GitOperationException;
70 |
71 | PushResponse push(Workspace ws) throws GitAPIException, IOException, GitOperationException;
72 |
73 | PushResponse push(Workspace ws, String ref) throws GitAPIException, IOException, GitOperationException;
74 |
75 | PushResponse pushAll(Workspace ws) throws GitAPIException, IOException, GitOperationException;
76 |
77 | boolean pull(Workspace ws) throws GitAPIException, IOException;
78 |
79 | CheckoutResponse checkout(Workspace ws, String name, String startPoint) throws GitAPIException, IOException, GitOperationException;
80 |
81 | void fetch(Workspace ws) throws GitAPIException;
82 |
83 | void fetch(Workspace ws, boolean prune) throws GitAPIException;
84 |
85 | /**
86 | * Retrieve the current branch that HEAD points to.
87 | *
88 | * @return branch name
89 | */
90 | String getBranch(Workspace ws) throws IOException;
91 |
92 | List getLocalBranches(Workspace ws) throws GitAPIException;
93 |
94 | List getRemoteBranches(Workspace ws) throws GitAPIException;
95 |
96 | Branches getBranches(Workspace ws) throws GitAPIException, IOException;
97 |
98 | void createBranch(Workspace ws, String branchName) throws GitAPIException;
99 |
100 | void deleteBranch(Workspace ws, String branchName) throws GitAPIException, IOException, GitOperationException;
101 |
102 | boolean hasBranch(Workspace ws, String branch) throws GitAPIException;
103 |
104 | List getTags(Workspace ws) throws GitAPIException;
105 |
106 | void createTag(Workspace ws, String tagName, String ref, String message, boolean force) throws GitAPIException, IOException;
107 |
108 | void deleteTag(Workspace ws, String tagName) throws GitAPIException;
109 |
110 | boolean checkIgnore(InputStream in, String path, boolean isDir);
111 |
112 | List getDiffEntryForCommit(Workspace ws, String commitId) throws IOException, GitAPIException;
113 |
114 | RebaseResponse rebase(Workspace ws, String upstream, boolean interactive, boolean preserve) throws GitAPIException, IOException, GitOperationException;
115 |
116 | RebaseResponse rebase(Workspace ws, String branch, String upstream, boolean interactive, boolean preserve) throws GitAPIException, IOException, GitOperationException;
117 |
118 | RebaseResponse updateRebaseTodo(Workspace ws, List lines) throws IOException, GitOperationException, GitAPIException;
119 |
120 | RebaseResponse operateRebase(Workspace ws, RebaseOperation operation) throws GitAPIException, IOException;
121 |
122 | RebaseResponse operateRebase(Workspace ws, RebaseOperation operation, String message) throws GitAPIException, IOException;
123 |
124 | RepositoryState state(Workspace ws);
125 |
126 | String readFileFromRef(Workspace ws, String ref, String path, boolean base64) throws IOException;
127 |
128 | String readFileFromRef(Workspace ws, String ref, String path, String encoding, boolean base64) throws IOException;
129 |
130 | void reset(Workspace ws, String ref, ResetType resetType) throws GitAPIException;
131 | }
132 |
--------------------------------------------------------------------------------
/src/main/java/net/coding/ide/tty/SocketIOHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2016 CODING.
3 | */
4 |
5 | package net.coding.ide.tty;
6 |
7 | import com.google.common.collect.HashMultimap;
8 | import com.google.common.collect.Maps;
9 | import com.google.common.collect.Multimap;
10 | import com.google.gson.Gson;
11 | import lombok.extern.slf4j.Slf4j;
12 | import net.coding.ide.model.Message;
13 | import org.atmosphere.cache.UUIDBroadcasterCache;
14 | import org.atmosphere.config.service.AtmosphereHandlerService;
15 | import org.atmosphere.cpr.AtmosphereResource;
16 | import org.atmosphere.socketio.SocketIOSessionOutbound;
17 | import org.atmosphere.socketio.cache.SocketIOBroadcasterCache;
18 | import org.atmosphere.socketio.cpr.SocketIOAtmosphereHandler;
19 | import org.atmosphere.socketio.cpr.SocketIOAtmosphereInterceptor;
20 | import org.atmosphere.socketio.transport.DisconnectReason;
21 | import org.slf4j.Logger;
22 | import org.slf4j.LoggerFactory;
23 |
24 | import java.io.File;
25 | import java.io.IOException;
26 | import java.util.Collection;
27 | import java.util.Map;
28 | import java.util.concurrent.ConcurrentHashMap;
29 | import java.util.concurrent.ConcurrentMap;
30 |
31 | import static java.lang.String.format;
32 |
33 | /**
34 | * Simple SocketIOAtmosphereHandler that implements the logic to build a
35 | * SocketIO Chat application.
36 | */
37 | @Slf4j
38 | @AtmosphereHandlerService(
39 | path = "/*",
40 | supportSession = true,
41 | broadcasterCache = UUIDBroadcasterCache.class,
42 | interceptors = SocketIOAtmosphereInterceptor.class)
43 | public class SocketIOHandler extends SocketIOAtmosphereHandler {
44 | private final Gson gson = new Gson();
45 | private ConcurrentMap connectors = new ConcurrentHashMap<>();
46 | private Multimap sessions = HashMultimap.create();
47 |
48 | public void onConnect(AtmosphereResource r, SocketIOSessionOutbound outbound) throws IOException {
49 | log.debug("onConnect");
50 | outbound.sendMessage("0{\"sid\":\"" + outbound.getSessionId() + "\",\"upgrades\":[],\"pingInterval\":25000,\"pingTimeout\":60000}");
51 | }
52 |
53 | public void onMessage(AtmosphereResource r, SocketIOSessionOutbound outbound, String message) {
54 | if (outbound == null || message == null || message.length() == 0) {
55 | return;
56 | }
57 |
58 | try {
59 | Message msg = gson.fromJson(message, Message.class);
60 |
61 | String name = msg.getName();
62 |
63 | String spaceHome = r.getAtmosphereConfig().getInitParameter("SPACE_HOME", null);
64 |
65 | if (spaceHome == null) {
66 | log.error("SocketIOHandler on message error: SPACE_HOME must be setted in env");
67 | return;
68 | }
69 |
70 | Message.Arg arg = msg.getArgs().get(0);
71 |
72 | switch (name) {
73 | case "term.open":
74 | itermOpen(outbound, spaceHome, arg);
75 | break;
76 | case "term.input":
77 | itermInput(outbound, arg);
78 | break;
79 | case "term.resize":
80 | itermResize(outbound, arg);
81 | break;
82 | case "term.close":
83 | itermClose(outbound, arg);
84 | break;
85 | default:
86 | System.out.println("command " + name + " not found.");
87 | break;
88 | }
89 | } catch (Exception e) {
90 | log.error("SocketIOHandler onMessage error, message => {}, error => {}", message, e.getMessage());
91 | }
92 | }
93 |
94 | private void itermClose(SocketIOSessionOutbound outbound, Message.Arg arg) {
95 | String key = makeConnectorKey(outbound.getSessionId(), arg.getId());
96 |
97 | TerminalEmulator emulator = connectors.get(key);
98 | if (emulator != null) {
99 | emulator.close();
100 | }
101 |
102 | connectors.remove(key);
103 | sessions.remove(outbound.getSessionId(), arg.getId());
104 | }
105 |
106 | private void itermResize(SocketIOSessionOutbound outbound, Message.Arg arg) {
107 | String key = makeConnectorKey(outbound.getSessionId(), arg.getId());
108 | TerminalEmulator emulator = connectors.get(key);
109 |
110 | if (emulator != null) {
111 | emulator.resize(arg.getCols(), arg.getRows());
112 | }
113 | }
114 |
115 | private String getWorkdingDir(String spaceHome, String spaceKey) {
116 | return format("%s/%s/%s", spaceHome, spaceKey, "/working-dir");
117 | }
118 |
119 | private String makeConnectorKey(String session, String termId) {
120 | return session + "-" + termId;
121 | }
122 |
123 | public void itermOpen(SocketIOSessionOutbound outbound, String spaceHome, Message.Arg arg) {
124 | try {
125 | Map envs = Maps.newHashMap(System.getenv());
126 |
127 |
128 | String shell = envs.get("SHELL");
129 |
130 | if (shell == null) {
131 | shell = "/bin/bash";
132 | }
133 |
134 | String[] command = new String[]{shell, "--login"};
135 | envs.put("TERM", "xterm");
136 |
137 | TerminalEmulator emulator = new TerminalEmulator(arg.getId(),
138 | outbound,
139 | command,
140 | envs,
141 | getWorkdingDir(spaceHome, arg.getSpaceKey()));
142 |
143 | String key = makeConnectorKey(outbound.getSessionId(), arg.getId());
144 |
145 | connectors.put(key, emulator);
146 | sessions.put(outbound.getSessionId(), arg.getId());
147 |
148 | emulator.start();
149 | } catch (Exception e) {
150 | e.printStackTrace();
151 | }
152 | }
153 |
154 | public void itermInput(SocketIOSessionOutbound outbound, Message.Arg arg) throws IOException {
155 | String key = makeConnectorKey(outbound.getSessionId(), arg.getId());
156 | TerminalEmulator emulator = connectors.get(key);
157 |
158 | if (emulator != null) {
159 | connectors.get(key).write(arg.getInput());
160 | }
161 | }
162 |
163 | public void onDisconnect(AtmosphereResource r, SocketIOSessionOutbound outbound, DisconnectReason reason) {
164 | log.debug("ondisconnect, reason is :" + reason);
165 |
166 | String sessionId = outbound.getSessionId();
167 |
168 | Collection termIds = sessions.get(sessionId);
169 |
170 | for (String termId : termIds) {
171 | String key = makeConnectorKey(sessionId, termId);
172 |
173 | TerminalEmulator emulator = connectors.get(key);
174 | if (emulator != null) {
175 | emulator.close();
176 | connectors.remove(key);
177 | }
178 | }
179 |
180 | sessions.removeAll(sessionId);
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/main/java/net/coding/ide/service/WorkspaceWatcher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2014-2016 CODING.
3 | */
4 |
5 | package net.coding.ide.service;
6 |
7 | import com.google.common.collect.Lists;
8 | import lombok.extern.slf4j.Slf4j;
9 | import net.coding.ide.event.FileChangeEvent;
10 | import net.coding.ide.event.FileCreateEvent;
11 | import net.coding.ide.event.FileDeleteEvent;
12 | import net.coding.ide.event.FileModifyEvent;
13 | import net.coding.ide.model.FileInfo;
14 | import net.coding.ide.model.Workspace;
15 | import net.coding.ide.utils.Debouncer;
16 | import org.springframework.context.ApplicationEventPublisher;
17 |
18 | import java.io.IOException;
19 | import java.nio.file.*;
20 | import java.nio.file.attribute.BasicFileAttributes;
21 | import java.util.HashMap;
22 | import java.util.List;
23 | import java.util.Map;
24 |
25 | import static java.nio.file.Files.isDirectory;
26 | import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
27 | import static java.nio.file.StandardWatchEventKinds.*;
28 |
29 | /**
30 | * Created by phy on 2015/1/30.
31 | */
32 | @Slf4j
33 | public class WorkspaceWatcher extends Thread {
34 | private volatile boolean stopWatching = false;
35 | private Workspace ws;
36 | private WatchService watcher;
37 | private Path workingDir;
38 | private Map keys = new HashMap<>();
39 |
40 | private Debouncer debouncer;
41 |
42 | private static String[] ignoreDirs = new String[]{"/.git/objects/"};
43 |
44 | private WatchedPathStore watchedPathStore;
45 |
46 | private WorkspaceManager wsMgr;
47 |
48 | private List ignorePaths = Lists.newArrayList();
49 |
50 | {
51 | try {
52 | this.watcher = FileSystems.getDefault().newWatchService();
53 | } catch (IOException e) {
54 | e.printStackTrace();
55 | }
56 | }
57 |
58 | public WorkspaceWatcher(WorkspaceManager wsMgr, Workspace ws, WatchedPathStore watchedPathStore, final ApplicationEventPublisher publisher) {
59 | this.ws = ws;
60 | this.wsMgr = wsMgr;
61 | this.workingDir = ws.getWorkingDir().toPath();
62 | this.watchedPathStore = watchedPathStore;
63 |
64 | this.debouncer = new Debouncer<>(event -> {
65 | log.info("publish file change event: {}", event);
66 | publisher.publishEvent(event);
67 | }, 50);
68 |
69 | for (String dir : ignoreDirs) {
70 | try {
71 | ignorePaths.add(ws.getPath(dir));
72 | watchedPathStore.add(ws.getSpaceKey(), ws.getPath(dir).toString());
73 | } catch (AccessDeniedException e) {
74 | e.printStackTrace();
75 | }
76 | }
77 |
78 | watchedPathStore.add(ws.getSpaceKey(), "/");
79 | watchedPathStore.add(ws.getSpaceKey(), "/.git/"); // for .git/HEAD
80 | }
81 |
82 | public void stopWatching() {
83 | this.stopWatching = true;
84 | try {
85 | watcher.close();
86 | } catch (IOException e) {
87 | e.printStackTrace();
88 | }
89 | }
90 |
91 |
92 | private void register(Path dir) throws IOException {
93 | WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
94 | keys.put(key, dir);
95 | }
96 |
97 | private void registerAll(final Path start) {
98 | // register directory and sub-directories
99 | try {
100 | Files.walkFileTree(start, new SimpleFileVisitor() {
101 | @Override
102 | public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
103 | throws IOException {
104 | if (ignorePaths.contains(dir)) {
105 | return FileVisitResult.SKIP_SUBTREE;
106 | } else {
107 | register(dir);
108 | return FileVisitResult.CONTINUE;
109 | }
110 | }
111 | });
112 | } catch (IOException e) {
113 | // ignore to keep sample readable
114 | }
115 | }
116 |
117 | public void run() {
118 |
119 | registerAll(workingDir);
120 |
121 | while ( ! stopWatching && ! isInterrupted() ) {
122 |
123 | try {
124 | WatchKey watchKey = watcher.take();
125 |
126 | Path dir = keys.get(watchKey);
127 |
128 | if (dir == null) {
129 | continue;
130 | }
131 |
132 | for (WatchEvent> event : watchKey.pollEvents()) {
133 |
134 | WatchEvent ev = (WatchEvent) event;
135 | Path fileName = ev.context();
136 | Path filePath = dir.resolve(fileName);
137 |
138 | Path relativePath = workingDir.relativize(filePath);
139 | String path = ws.getNormalizePath(relativePath.toString()).toString();
140 |
141 | if ( ! path.startsWith("/.git/refs/heads/")
142 | && ! watchedPathStore.hasWatched(ws.getSpaceKey(), path) ) {
143 | log.debug("not watched {} on workspace {}", path, ws.getSpaceKey());
144 | continue;
145 | }
146 |
147 | FileChangeEvent fileChangeEvent = null;
148 |
149 | if (event.kind() == ENTRY_CREATE) {
150 |
151 |
152 | if (isDirectory(filePath, NOFOLLOW_LINKS)) {
153 | registerAll(filePath);
154 | }
155 |
156 | try {
157 | FileInfo fileInfo = wsMgr.getFileInfo(ws, relativePath.toString());
158 | fileChangeEvent = new FileCreateEvent(ws.getSpaceKey(), fileInfo);
159 |
160 | } catch (NoSuchFileException e) {
161 | log.debug("file " + fileName + " not found.", e);
162 | }
163 |
164 |
165 | } else if (event.kind() == ENTRY_MODIFY) {
166 |
167 | try {
168 | FileInfo fileInfo = wsMgr.getFileInfo(ws, relativePath.toString());
169 | fileChangeEvent = new FileModifyEvent(ws.getSpaceKey(), fileInfo);
170 | } catch (NoSuchFileException e) {
171 | log.debug("file " + fileName + " not found.", e);
172 | }
173 |
174 |
175 | } else if (event.kind() == ENTRY_DELETE) {
176 |
177 | FileInfo fileInfo = new FileInfo();
178 | fileInfo.setName(fileName.toString());
179 | fileInfo.setPath(path);
180 | fileChangeEvent = new FileDeleteEvent(ws.getSpaceKey(), fileInfo);
181 |
182 | watchedPathStore.remove(ws.getSpaceKey(), path);
183 |
184 | }
185 |
186 | if (fileChangeEvent != null) {
187 | synchronized (debouncer) {
188 | debouncer.call(fileChangeEvent);
189 | }
190 | }
191 |
192 |
193 | }
194 | // reset key and remove from set if directory no longer accessible
195 | boolean valid = watchKey.reset();
196 | if (!valid) {
197 | keys.remove(watchKey);
198 | // all directories are inaccessible
199 | if (keys.isEmpty()) {
200 | // 此处有问题
201 | break;
202 | }
203 | }
204 |
205 | } catch (ClosedWatchServiceException e) {
206 | // ignore
207 | } catch (Exception e) {
208 | e.printStackTrace();
209 | }
210 | }
211 | this.debouncer.terminate();
212 |
213 | }
214 | }
215 |
--------------------------------------------------------------------------------