123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507 |
- # THIS FILE IS A PART OF VCStudio
- # PYTHON 3
- ###############################################################################
- # In order for Multiuser to function. VCStudio should have a process on the
- # background can access to all the data, that will talk to the Multiuser
- # server.
- ###############################################################################
- ##################### IMPLEMENTED FEATURES LIST ###############################
- # [V] List of users | Important to know the usernames of all users
- # [V] Assets | Important to have up to date assets everywhere
- # [ ] Shots | Important to have up to date shots / and their assets
- # [ ] Rendering | Ability to render on a separate machine.
- # [ ] Story | Real time Sync of the story edito and the script writer
- # [ ] Analytics | Real time Sync of History and Schedules.
- # [ ] Messages | A little messaging system in the Multiuser window.
- ###############################################################################
- import os
- import sys
- import time
- import json
- import socket
- import random
- import hashlib
- import datetime
- import threading
- import subprocess
- from UI import UI_elements
- from settings import talk
- from network import insure
- time_format = "%Y/%m/%d-%H:%M:%S"
- def client(win):
-
- ###########################################################################
-
- # This function is the thing that we need. It's going to run in it's own
- # thread. Separatelly from the rest of the program.
-
- ###########################################################################
-
- while True:
- try:
- # So the first thing that we want to do is to listen for a server
- # broadcasting himself. And when found connect to that server.
- connect(win)
-
- # Then it's going to ask us that project are we in and our username.
-
- server = win.multiuser["server"]
-
- # Then as soon as we are connected. We want to start the main loop. I know
- # that some stuff should be done early. But I guess it's better to do them
- # in the main loop. So basically the server will request a bunch of stuff.
- # And when it sends no valid request. Or something like KEEP ALIVE then it's
- # a turn for as to send requests to server. Or nothing. The communication
- # happens all the time.
-
- while win.multiuser["server"]:
-
- request = insure.recv(server)
-
- if request == "yours":
-
- ############################################################
- # WE REQUEST FROM SERVER #
- ############################################################
-
- if win.multiuser["curs"]:
-
- # The get asset function
-
- get(win, list(win.multiuser["curs"].keys())[0],
- win.multiuser["curs"][list(win.multiuser["curs"].keys())[0]])
-
- try:
- del win.multiuser["curs"][list(win.multiuser["curs"].keys())[0]]
- except:
- pass
-
- elif win.multiuser["request"]:
- insure.send(server, win.multiuser["request"])
- win.multiuser["request"] = []
-
-
- elif not win.multiuser["users"]:
- insure.send(server, "users")
- win.multiuser["users"] = insure.recv(server)
-
- elif win.cur and win.cur != win.multiuser["last_request"]:
-
- win.multiuser["last_request"] = win.cur
-
- # If we are currently at some asset or shot. We want to
- # send to the server the current state of the folder
- # if such exists. So if we have the latest one. Every
- # body else could send us a give request. But if somebody
- # has the latest and it's not us. We could send them
- # the get request.
-
- # First step will be to check whitch cur are we in.
- if win.url == "assets":
- t = "/dev"
- elif win.url == "script":
- t = "/rnd"
- else:
- t = ""
-
- # Then we need to see if there is a folder to begin
- # with. Some scenes and some shots have no folder.
-
- if not os.path.exists(win.project+t+win.cur):
- insure.send(server, "yours")
- else:
-
- # If there is a folder let's get the timestamp.
- timestamp = getfoldertime(win.project+t+win.cur)
-
- insure.send(server, ["at", t+win.cur, timestamp])
-
- else:
-
- insure.send(server, "yours")
-
- if win.url not in ["assets", "script", "analytics"]:
- win.multiuser["last_request"] = ""
- win.cur = ""
- else:
-
- ############################################################
- # SERVER REQUESTS FROM US #
- ############################################################
-
-
- if request == "story":
-
- if not win.multiuser["story_check"]:
- storytime = "1997/07/30-00:00:00"
- win.multiuser["story_check"] = True
- else:
- storytime = gettime(win.project+"/pln/story.vcss")
-
- selectedtmp = win.story["selected"]
- tmppointers = win.story["pointers"]
- tmpcamera = win.story["camera"]
-
- insure.send(server, [win.story, storytime])
-
- story = insure.recv(server)
-
- win.story = story[1].copy()
- win.story["selected"] = selectedtmp
- win.story["camera"] = tmpcamera
- win.story["pointers"] = tmppointers
-
- if story[0] in win.multiuser["users"]:
- win.multiuser["users"][story[0]]["camera"] = story[1]["camera"]
-
-
- elif request == "users":
- win.multiuser["users"] = {}
-
-
- elif request == "analytics":
- if not win.multiuser["analytics_check"]:
- analyticstime = "1997/07/30-00:00:00"
- win.multiuser["analytics_check"] = True
- else:
- analyticstime = datetime.datetime.strftime(datetime.datetime.now(), time_format)
- insure.send(server, [win.analytics, analyticstime])
-
- win.analytics = insure.recv(server)
- elif request == "assets":
-
- assets = list_all_assets(win)
- insure.send(server, assets)
- new_assets = insure.recv(server)
-
-
- for asset in new_assets:
-
- if asset not in assets or assets[asset][1] < new_assets[asset][1]:
-
- # If a given asset is not on our system or
- # being updated on another system. We want to
- # call for get() function. To get the new
- # and up to date version of the asset. And if
- # doesn't exist get the asset.
-
- # But it's better to do one by one.
- try:
- win.multiuser["curs"]["/dev"+asset] = new_assets[asset][0]
- except:
- pass
-
-
- elif request[0] == "give":
- give(win, request[1], server)
-
- elif request[1] == "at":
- print(request)
- try:
- # If we need an update let's update
- if getfoldertime(win.project+request[2]) < request[3]:
- win.multiuser["curs"][request[2]] = request[0]
- # Else tell the world that we are the newest ones
- elif getfoldertime(win.project+request[2]) > request[3]:
- insure.send(server, ["at", request[2], getfoldertime(win.project+request[2])])
- insure.recv(server)
- except:
- pass
-
- elif request[0] == "messages":
- win.multiuser["messages"] = request[1]
- win.multiuser["unread"] += 1
- win.scroll["multiuser_messages"] = 0-500*len(request[1])
-
-
- insure.send(server, "yours")
-
- except Exception as e:
- #raise()
- win.multiuser["server"] = False
- print("Connection Multiuser Error | "+str(e)+" | Line: "+str(sys.exc_info()[-1].tb_lineno))
- def getfoldertime(path):
-
- # I noticed a problem with getting a last modification time for a directory.
- # It is not nearly the same time as the contents inside that directory. And
- # when for example I have /dev/chr/Moria folder. And I want to check is this
- # asset is newer here or somebody has a more up to date version. It checks
- # for only the time of the folder /dev/chr/Moria and not the contents of
- # that folder. Which is not cool. I want to get the newest time from all the
- # files and folders inside it. Including in the case of assets the
- # /ast/chr/Moria.blend files. So this is why you see this function here.
-
-
-
- if os.path.isdir(path):
-
- # So basically we are doing it only if it's actually a directory. In
- # case there is an error. I didn't check. But who knows.
-
- # We need to get the full list of subdirectories and files.
-
- allstuff = []
-
- if "/dev/" in path and os.path.exists(path[:path.find("/dev")]+"/ast"+path[path.find("/dev")+4:]+".blend"):
- allstuff.append(gettime(path[:path.find("/dev")]+"/ast"+path[path.find("/dev")+4:]+".blend"))
-
- for i in os.walk(path):
- if i[-1]:
- for b in i[-1]:
- allstuff.append(gettime(i[0]+"/"+b))
- else:
- allstuff.append(gettime(i[0]))
-
- # Now let's find the biggest one
- biggest = allstuff[0]
- for i in allstuff:
- if i > biggest:
- biggest = i
-
- return biggest
-
- else:
- return gettime(path)
- def gettime(path):
-
- # This function will get a pretty time for a given path. The last change
- # to the file or the folder recorded by the os.
- time_format = "%Y/%m/%d-%H:%M:%S"
- timestamp = os.path.getmtime(path)
- timestamp = datetime.datetime.fromtimestamp(timestamp)
- timestamp = datetime.datetime.strftime(timestamp, time_format)
- return timestamp
-
- def connect(win):
-
- # This is going to be a function that connects to the server and makes a
- # handshake
-
- while not win.multiuser["server"]:
-
- # So the first step will be to listen for multiuser.
-
- data = ""
-
- try:
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- sock.bind(("255.255.255.255", 54545))
-
-
- data, addr = sock.recvfrom(1024)
- data = data.decode('utf8')
- sock.close()
-
- except:
- pass
-
- # If any data revieved. It's not nessesarily the server. So let's read it
-
- if data.startswith("VCStudio MULTIUSER SERVER"):
- try:
- data, ip, project = data.split(" | ")
- except:
- continue
-
- # So now we know the ip of the server. And the name of a project
- # that it's hosting. We need to check that our name is the same
- # and if yes. Connect to the server.
-
- if win.analytics["name"] == project:
-
- server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- server.connect((ip, 64646))
- win.multiuser["server"] = server
- win.multiuser["userid"] = str(server.getsockname()[0])+":"+str(server.getsockname()[1])
-
- insure.recv(server)
- insure.send(server, win.settings["Username"])
- insure.recv(server)
- insure.send(server, win.analytics["name"])
-
- print("Connected to Multiuser as: "+win.multiuser["userid"])
-
- # Adiing myself into the list of all users
- win.multiuser["users"][win.multiuser["userid"]] = {"username":win.settings["Username"], "camera":[0,0]}
- def hash_file(f):
- try:
- BLOCKSIZE = 65536
- hasher = hashlib.md5()
- with open(f, 'rb') as afile:
- buf = afile.read(BLOCKSIZE)
- while len(buf) > 0:
- hasher.update(buf)
- buf = afile.read(BLOCKSIZE)
- return str(hasher.hexdigest())
- except:
- return "FOLDER"
-
- def list_all_assets(win):
-
- # This function is listing all the asset CURs in the project. Not the shots
- # only the assets. Since we want to have what assets are being created. For
- # shots. It's done using the story editor file. So we don't really need it.
-
- allcurs = {}
-
- for c in ["chr", "veh", "loc", "obj"]:
-
- for i in os.listdir(win.project+"/dev/"+c):
- allcurs["/"+c+"/"+i] = [
- win.multiuser["userid"],
- getfoldertime(win.project+"/dev/"+c+"/"+i)
- ]
-
- return allcurs
- def get_give_folder_list(project, folder):
-
- # This function will prepare our folder list for get and giv functions.
-
- path = folder
- astblend = path[:path.find("/dev")]+"/ast"+path[path.find("/dev")+4:]+".blend"
-
- fs = []
-
- if os.path.exists(project+astblend):
- fs.append([astblend, hash_file(project+astblend)])
-
- # There might not even be any folder as far as this function concerned
- # there is nothing in it if there is no folder.
-
- try:
- for f in os.walk(project+folder):
-
- # If files in the folder
- if f[2]:
- for i in f[2]:
- fs.append([f[0].replace(project, "")+"/"+i, hash_file(f[0]+"/"+i)])
- # Else just put the folder in
- else:
- fs.append([f[0].replace(project, ""), "FOLDER"])
-
- except Exception as e:
- print("get_give_folder_list(): "+str(e))
-
- return fs
- def get(win, folder, userid):
-
- # This function will get any folder from any other user using the connection
- # to the server.
-
- print("Trying to get: [", folder, "] From: [", userid, "]")
-
- server = win.multiuser["server"]
-
- insure.send(server, ["get", userid, folder])
-
- # Next we will recieve the list of file / folders in the directory
- # we are looking for. With the MD5 hash of each. We do not want to
- # download files that are identical between both machines.
-
- available = insure.recv(server)
- current = get_give_folder_list(win.project, folder)
- getlist = []
-
- # Now we need to compare between them to get a list of files we want.
- # Also at this stange we can already make the folders
-
- for f in available:
- if f not in current:
- if f[1] == "FOLDER":
-
- # If it's a folder there is nothing I need to download, we
- # already have the name. So let's just make it.
- try:
- os.makedirs(win.project+f[0])
- except:
- pass
-
- else:
-
- # If it's not a folder. And it does not exist. Let's actually
- # get it.
-
- getlist.append(f[0])
-
- # Now we want to send to that other user the "getlist" so he would know.
- # which files to send back to us. This is a bit harder communication then
- # just sending a big object contaning all of the files in it. Because I'm
- # using Json for the complex data structures. It's just not going to be cool
- # because at best it will convert the bytes of the files into strings of
- # text. Which are like 4 to 8 times larger in sizes. And at worst it will
- # fail complitelly. So what I will do is ask the files one by one. The
- # insure script knows how to deal with bytes objects so we are fine.
-
- insure.send(server, getlist)
-
- # Now let's just recieve the files and save them.
- for f in getlist:
-
- # We also want the folder to be make just in case.
- try:
- os.makedirs(win.project+f[:f.rfind("/")])
- except:
- pass
-
- data = open(win.project+f, "wb")
- data.write(insure.recv(server))
- data.close()
-
- insure.send(server, "saved")
-
- # Refrashing peviews for the images and stuff
- win.checklists = {}
- UI_elements.reload_images(win)
-
- def give(win, folder, server):
-
- # This function will send to the server any folder that other users might
- # request.
-
- print("Someone wants: [", folder, "]")
-
- # We are going to send the list of files and folder and their hash values
- # to the other client. So it could choose what files does it wants. Not
- # all files will be changed. So there is no need to copy the entire folder.
-
- insure.send(server, get_give_folder_list(win.project, folder))
-
-
- # The other user will select the files that he needs. Based on what's
- # changed. And will send us the short version of the same list.
-
- getlist = insure.recv(server)
-
- # Now let's send the actuall binaries of the files.
-
- for f in getlist:
- print("sending file:", f)
- data = open(win.project+f, "rb")
- data = data.read()
- insure.send(server, data)
-
- insure.recv(server)
-
|