Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Obsolete] Introduce Commenting Feature and Simultaneous Coding Environment. #551

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5a77d20
allows reorganizing of files and folders
parvmor Jun 27, 2017
c1155bd
reorganizing for funblocks
parvmor Jun 27, 2017
26f2990
Merge remote-tracking branch 'upstream/master'
parvmor Jul 1, 2017
282accf
constraints user to create new files before starting to code
parvmor Jul 6, 2017
5559b03
added some handlers for comments
parvmor Jul 8, 2017
42b87ff
Added a basic frontend for feedback
parvmor Jul 14, 2017
16d4cb4
Merge remote-tracking branch 'upstream/master'
parvmor Jul 14, 2017
cc4c713
Rearranges the code to make it more readable. Fixes the warnings of G…
parvmor Jul 15, 2017
32f1244
modifies Folder.hs according to requirements of Comment.hs
parvmor Jul 15, 2017
a364e48
Changes the comment handlers to support versioning of code. frontend …
parvmor Jul 21, 2017
220c182
complete copy dir handler
parvmor Jul 22, 2017
f6dc724
Fixes type mismatches
parvmor Jul 24, 2017
3583656
improves copyHandler and implements moveHandler
parvmor Jul 25, 2017
ad50f2e
completes save handler
parvmor Aug 14, 2017
26c608d
adds a copy feature to frontend
parvmor Aug 15, 2017
01b433c
updates askForFeedback() according to latest backend
parvmor Aug 16, 2017
c26cd61
update frontend to accomodate versions
parvmor Aug 21, 2017
0a07520
remove save as functionality
parvmor Aug 25, 2017
9c27f2c
Make frontend completely compatile to backend
parvmor Aug 27, 2017
f28626b
remove lazy input output
parvmor Aug 28, 2017
1bc2c93
add collaborate file structure
parvmor Sep 2, 2017
923067f
improve linting and correct collaborate file structure
parvmor Sep 4, 2017
0e9d33e
add frontend for simultaneous coding
parvmor Sep 4, 2017
595b6b8
add collaboration handlers
parvmor Sep 5, 2017
2c9cb97
Merge remote-tracking branch 'upstream/master'
parvmor Sep 7, 2017
fd7206e
make a seperate library for funblocks-server due to conflicting handl…
parvmor Sep 7, 2017
b722062
clean the hiding functions hack, improve pattern matching
parvmor Sep 11, 2017
139d75a
replace frequent param extraction type with a data type instead of int
parvmor Sep 11, 2017
bf12126
update operational-transformation install to be dynamic
parvmor Sep 11, 2017
03fd99f
add a separate server for operational transformation
parvmor Sep 12, 2017
c504e04
add cross-origin-resource-sharing in collabServer
parvmor Sep 12, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ run codeworld-api cabal haddock --hoogle

# Build codeworld-server from this project.

run . cabal_install ./codeworld-server \
run . cabal_install ./third_party/ot.hs \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this even do? You're passing an individual Haskell source file to cabal_install?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait which file? That is a directory. It contains the source code for operational transformations.

./funblocks-server \
./codeworld-error-sanitizer \
./codeworld-compiler \
./codeworld-server \
./codeworld-game-api \
./codeworld-prediction \
./codeworld-api \
Expand Down
12 changes: 11 additions & 1 deletion codeworld-server/codeworld-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,30 @@ Executable codeworld-server
cryptonite,
data-default,
directory,
engine-io,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the multi-player gaming API, we decided to separate the game server from the web server, because it was less critical and more susceptible to denial-of-service or other failures. I believe that the same thing applies here, as well. Relaying real-time messages to other clients is dangerous, and if collaborative editing fails for a bit, it's better than the web site being down.

Instead of adding yet another server, can you rename codeworld-game-server, and add this functionality there, instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure would make the required changes!

engine-io-snap,
filepath,
filesystem-trees,
funblocks-server,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me why this is a separate library. What's up here?

Copy link
Contributor Author

@parvmor parvmor Sep 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file structure for funblocks and codeworld are different. So, the handlers for handling files in both have different logic. I thought it would be best to have a separate library for the same. Any other suggestions over how to accommodate this?

hashable,
hindent >= 5 && < 5.2.3,
http-conduit,
memory,
mtl,
ot,
process,
regex-compat,
regex-tdfa,
snap-core,
snap-server,
socket-io,
stm,
temporary,
text,
unix
time,
transformers,
unix,
unordered-containers

Ghc-options: -threaded -Wall -funbox-strict-fields -O2
-fno-warn-unused-do-bind
19 changes: 19 additions & 0 deletions codeworld-server/src/Collaboration.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{-
Copyright 2017 The CodeWorld Authors. All rights reserved.

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.
-}

module Collaboration (module Collaboration__) where

import Collaboration_ as Collaboration__ hiding (getFrequentParams)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh? Can you explain what you're doing here? Why not just put the code in Collaboration instead of aliasing a second (and hideously named) module?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was used as a way to get around the fact that haskell requires you to explicitly export functions you want to export. This allows to hide the functions you do not want to export. But now I can see that looks kind of a hack and ugly. So, I will change that.

204 changes: 204 additions & 0 deletions codeworld-server/src/CollaborationUtil.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-
Copyright 2017 The CodeWorld Authors. All rights reserved.

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.
-}

module CollaborationUtil where

import qualified Control.Concurrent.STM as STM
import Control.Monad
import Control.OperationalTransformation.Selection (Selection)
import Control.OperationalTransformation.Server (ServerState)
import Control.OperationalTransformation.Text (TextOperation)
import Data.Aeson
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy as LB
import Data.Hashable (Hashable)
import qualified Data.HashMap.Strict as HM
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time.Clock
import GHC.Generics (Generic)
import System.Directory
import System.FilePath

import CommentUtil
import DataUtil
import Model

data CollabServerState = CollabServerState
{ collabProjects :: STM.TVar CollabProjects
, started :: UTCTime
}

type CollabProjects = HM.HashMap CollabId (STM.TVar CollabProject)

data CollabProject = CollabProject
{ totalUsers :: !Int
, collabKey :: CollabId
, collabState :: ServerState Text TextOperation
, users :: [CollabUserState]
}

data CollabUserState = CollabUserState
{ suserId :: !Text
, suserIdent :: !Text
, userSelection :: !Selection
}

instance ToJSON CollabUserState where
toJSON (CollabUserState _ userIdent' sel) =
object $ [ "name" .= userIdent' ] ++ (if sel == mempty then [] else [ "selection" .= sel ])

newtype CollabId = CollabId { unCollabId :: Text } deriving (Eq, Generic)

instance Hashable CollabId

collabHashRootDir :: BuildMode -> FilePath
collabHashRootDir (BuildMode m) = "data" </> m </> "projectContents"

nameToCollabHash :: FilePath -> CollabId
nameToCollabHash = CollabId . hashToId "H" . BC.pack

ensureCollabHashDir :: BuildMode -> CollabId -> IO ()
ensureCollabHashDir mode (CollabId c) = createDirectoryIfMissing True dir
where dir = collabHashRootDir mode </> take 3 (T.unpack c)

collabHashLink :: CollabId -> FilePath
collabHashLink (CollabId c) = let s = T.unpack c in take 3 s </> s

newCollaboratedProject :: BuildMode -> Text -> Text -> ByteString -> FilePath -> Project -> IO (Either String ())
newCollaboratedProject mode userId' userIdent' name projectFilePath project = do
let collabHash = nameToCollabHash projectFilePath
collabHashPath = collabHashRootDir mode </> collabHashLink collabHash <.> "cw"
userDump = UserDump userId' userIdent' (T.pack projectFilePath) "owner"
identAllowed = foldl (\acc l -> if l `elem` (T.unpack userIdent')
then False else acc) True ['/', '.', '+']
case identAllowed of
False -> return $ Left "User Identifier Has Unallowed Characters(/+.)"
True -> do
ensureCollabHashDir mode collabHash
B.writeFile collabHashPath $ LB.toStrict . encode $ project
B.writeFile (collabHashPath <.> "users") $
LB.toStrict . encode $ userDump : []
B.writeFile projectFilePath $ BC.pack collabHashPath
B.writeFile (projectFilePath <.> "info") name
addCommentFunc mode userDump project $ collabHashPath <.> "comments"
return $ Right ()

addForCollaboration :: BuildMode -> Text -> Text -> ByteString -> FilePath -> FilePath -> IO (Either String ())
addForCollaboration mode userId' userIdent' name projectFilePath collabFilePath = do
let userDump = UserDump userId' userIdent' (T.pack projectFilePath) "owner"
identAllowed = foldl (\acc l -> if l `elem` (T.unpack userIdent')
then False else acc) True ['/', '.', '+']
case identAllowed of
False -> return $ Left "User Identifier Has Unallowed Characters(/+.)"
True -> do
Just (currentUsers :: [UserDump]) <- decodeStrict <$>
B.readFile (collabFilePath <.> "users")
let currentIdents = map uuserIdent currentUsers
currentIds = map uuserId currentUsers
case (userId' `elem` currentIds, userIdent' `elem` currentIdents) of
(True, _) -> return $ Left "User already exists maybe with a different identifier"
(False, True) -> return $ Left "User Identifier already exists"
(False, False) -> do
res <- addNewOwner mode userDump $ collabFilePath <.> "comments"
case res of
Left err -> return $ Left err
Right _ -> do
B.writeFile (collabFilePath <.> "users") $
LB.toStrict . encode $ userDump : currentUsers
createDirectoryIfMissing False (takeDirectory projectFilePath)
B.writeFile projectFilePath $ BC.pack collabFilePath
B.writeFile (projectFilePath <.> "info") name
return $ Right ()

removeProjectIfExists :: BuildMode -> Text -> FilePath -> IO ()
removeProjectIfExists mode userId' userPath = do
projectContentPath <- BC.unpack <$> B.readFile userPath
_ <- removeUserFromCollaboration mode userId' projectContentPath
removeFileIfExists userPath
removeFileIfExists $ userPath <.> "info"
cleanBaseDirectory userPath

removeUserFromCollaboration :: BuildMode -> Text -> FilePath -> IO (Either String ())
removeUserFromCollaboration mode userId' projectContentPath = do
Just (currentUsers :: [UserDump]) <- decodeStrict <$>
B.readFile (projectContentPath <.> "users")
case userId' `elem` (map uuserId currentUsers) of
False -> do
return $ Left "User does not exists in the project which is being tried to be deleted"
True -> do
let newUsers = filter (\x -> uuserId x /= userId') currentUsers
case length newUsers of
0 -> do
removeCollaboratedProject projectContentPath
removeCommentUtils $ projectContentPath <.> "comments"
cleanBaseDirectory projectContentPath
cleanCommentHashPath mode userId' $ projectContentPath <.> "comments"
_ -> do
-- update hash path to one of existing users path since this users filepath may contain different project
B.writeFile (projectContentPath <.> "users") $
LB.toStrict . encode $ newUsers
removeOwnerPathInComments mode userId' $ projectContentPath <.> "comments"
modifyCollabPath mode projectContentPath
return $ Right ()

modifyCollabPath :: BuildMode -> FilePath -> IO ()
modifyCollabPath mode projectContentPath = do
Just (currentUsers :: [UserDump]) <- decodeStrict <$>
B.readFile (projectContentPath <.> "users")
let newCollabHash = nameToCollabHash . T.unpack . upath $ currentUsers !! 0
newCollabHashPath = collabHashRootDir mode </> collabHashLink newCollabHash <.> "cw"
forM_ currentUsers $ \u -> do
B.writeFile (T.unpack $ upath u) $ BC.pack newCollabHashPath
createDirectoryIfMissing False $ takeDirectory newCollabHashPath
mapM_ (\x -> renameDirectory (projectContentPath <.> x) $ newCollabHashPath <.> x)
["comments", "comments" <.> "users", "comments" <.> "versions"]
mapM_ (\x -> renameFile (projectContentPath <.> x) $ newCollabHashPath <.> x)
["", "users"]
cleanBaseDirectory projectContentPath
updateSharedCommentPath mode (projectContentPath <.> "comments") $ newCollabHashPath <.> "comments"

modifyCollabPathIfReq :: BuildMode -> Text -> FilePath -> FilePath -> IO ()
modifyCollabPathIfReq mode userId' fromFile toFile = do
let collabHash = nameToCollabHash fromFile
collabHashPath = collabHashRootDir mode </> collabHashLink collabHash <.> "cw"
projectContentPath <- BC.unpack <$> B.readFile toFile
Just (currentUsers :: [UserDump]) <- decodeStrict <$>
B.readFile (projectContentPath <.> "users")
B.writeFile (projectContentPath <.> "users") $
LB.toStrict . encode $ map (\x -> if userId' == uuserId x
then x { upath = T.pack toFile }
else x) currentUsers
correctOwnerPathInComments mode userId' toFile $ projectContentPath <.> "comments"
case projectContentPath == collabHashPath of
True -> modifyCollabPath mode projectContentPath
False -> return ()

removeCommentUtils :: FilePath -> IO ()
removeCommentUtils commentFolder = do
mapM_ (\x -> removeDirectoryIfExists $ commentFolder <.> x) ["", "users", "versions"]

removeCollaboratedProject :: FilePath -> IO ()
removeCollaboratedProject projectContentPath = do
removeFileIfExists projectContentPath
removeFileIfExists $ projectContentPath <.> "users"
cleanBaseDirectory projectContentPath
Loading