package org.vaadin.mideaas.model; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import name.fraser.neil.plaintext.diff_match_patch.Diff; import name.fraser.neil.plaintext.diff_match_patch.Operation; import name.fraser.neil.plaintext.diff_match_patch.Patch; import org.apache.commons.io.FileUtils; import org.vaadin.aceeditor.ServerSideDocDiff; public class ProjectLog { enum Type { CREATED, REMOVED, LOADED_FROM_DISK, EDIT, CHAT, OPEN_FILE, CLOSE_FILE } public static abstract class LogItem { public final Date timestamp; private LogItem() { timestamp = new Date(); } public String logString() { return timestamp.getTime() + " " + getType() + " " + logContentString(); } abstract Type getType(); abstract String logContentString(); } public static class SimpleLogItem extends LogItem { private final Type type; private final String str; private SimpleLogItem(Type type, String str) { this.type = type; this.str = str; } public SimpleLogItem(Type type) { this(type, ""); } @Override Type getType() { return type; } @Override String logContentString() { return str; } } public static class UserEditLogItem extends LogItem { public final String filename; public final String anonCode; public final String userName; public final Operation op; public final int editLen; public final int editPos; public final int textLen; public UserEditLogItem(String filename, String anonCode, String userName, Operation op, int editLen, int editPos, int textLen) { this.filename = filename; this.anonCode = anonCode; this.userName = userName; this.op = op; this.editLen = editLen; this.editPos = editPos; this.textLen = textLen; } @Override public String logContentString() { return filename+";"+anonCode+";"+userName+";"+ op+";"+editLen+";"+editPos+";"+textLen; } @Override public Type getType() { return Type.EDIT; } } public static class ChatLogItem extends LogItem { public final String userId; public final String message; private ChatLogItem(String userId, String message) { this.userId = userId; this.message = message; } @Override public String logContentString() { return userId+";"+message; } @Override public Type getType() { return Type.CHAT; } } public static class FileOpenLogItem extends LogItem { public final ProjectFile file; public final long collaboratorId; public final User user; private FileOpenLogItem(ProjectFile file, long collaboratorId, User user) { this.file = file; this.collaboratorId = collaboratorId; this.user = user; } @Override Type getType() { return Type.OPEN_FILE; } @Override String logContentString() { return file.getName()+";"+collaboratorId+";"+user.getUserId()+";"+user.getName(); } } public static class FileCloseLogItem extends LogItem { public final ProjectFile file; public final long collaboratorId; public final User user; private FileCloseLogItem(ProjectFile file, long collaboratorId, User user) { this.file = file; this.collaboratorId = collaboratorId; this.user = user; } @Override Type getType() { return Type.CLOSE_FILE; } @Override String logContentString() { return file.getName()+";"+collaboratorId+";"+user.getUserId()+";"+user.getName(); } } private static File logDir; private final ArrayList<LogItem> log = new ArrayList<LogItem>(); private final File file; public ProjectLog(String projectName) { file = isLogDirSet() ? logFileForProject(projectName) : null; } public static synchronized void setLogDir(File dir) { logDir = dir; } public static synchronized boolean isLogDirSet() { return logDir!=null; } private static synchronized File logFileForProject(String projectName) { return new File(logDir, projectName+".log"); } public void logCreated() { log(new SimpleLogItem(Type.CREATED)); } public void logLoadedFromDisk(File f) { log(new SimpleLogItem(Type.LOADED_FROM_DISK, f.getAbsolutePath())); } public void logRemoved() { log(new SimpleLogItem(Type.REMOVED)); } public void logChat(String userId, String message) { log(new ChatLogItem(userId, message)); } public void logUserEdit(String filename, String anonCode, String userName, Operation op, int editLen, int editPos, int fileLen) { log(new UserEditLogItem(filename, anonCode, userName, op, editLen, editPos, fileLen)); } public void logUserEdit(String filename, User user, ServerSideDocDiff diff, int docLen) { List<Patch> patches = diff.getPatches(); String anonCode = user instanceof ExperimentUser ? ((ExperimentUser)user).getAnonymizerCode() : ""; String userName = user==null ? "" : user.getName(); for (Patch p : patches) { for (Diff d : p.diffs) { if (d.operation.equals(Operation.EQUAL)) { continue; } int ol = d.text==null ? 0 : d.text.length(); logUserEdit(filename, anonCode, userName, d.operation, ol, p.start1, docLen); } } } public void logOpenFile(ProjectFile f, long collabId, User user) { log(new FileOpenLogItem(f, collabId, user)); } public void logCloseFile(ProjectFile f, long collabId, User user) { log(new FileCloseLogItem(f, collabId, user)); } synchronized private void log(LogItem item) { log.add(item); if (file!=null) { logToFile(item); } } private void logToFile(LogItem item) { try { FileUtils.writeLines(file, Collections.singletonList(item.logString()), true); } catch (IOException e) { System.err.println("WARNING: could not write log to "+file); } } }