Multiuser Alpha 1.0
This is the historic moment When the multiuser was implemented.
This commit is contained in:
parent
7f6ba90906
commit
ba370b7cb1
7 changed files with 1371 additions and 12 deletions
153
network/insure.py
Normal file
153
network/insure.py
Normal file
|
@ -0,0 +1,153 @@
|
|||
# THIS FILE IS A PART OF VCStudio
|
||||
# PYTHON 3
|
||||
|
||||
###############################################################################
|
||||
|
||||
# With the Multiuser system. It became clear early on that simply using the UDP
|
||||
# protocol would not be cool. Because it's just not checking enough things.
|
||||
# In my point of view. I want to send to the end network all the data directly
|
||||
# and not care too much about what format it is in and stuff.
|
||||
|
||||
# So this file will insure that the data will be transferred what ever it is
|
||||
# just as is. The hard part will be here.
|
||||
|
||||
###############################################################################
|
||||
|
||||
import json
|
||||
|
||||
def send(c, message):
|
||||
|
||||
# This function will do the sending of the message. I will treat every
|
||||
# message as if it's a file. And will write everything into a json format
|
||||
# for transferring. Unless it's already in binarry. In which case I will
|
||||
# keep it that way.
|
||||
|
||||
# The mode will be either B ( bytes ) or J ( json ). All the rest will
|
||||
# handalled by the json madule hopefully.
|
||||
|
||||
T = b"B"
|
||||
if type(message) != bytes:
|
||||
T = b"J"
|
||||
message = bytes(json.dumps(message), 'utf-8')
|
||||
|
||||
# So now it's bytes anyway and it means we can send it over the network.
|
||||
# This will be done in 3 stages. TYPE ( T ), AMOUNT ( len(message) )
|
||||
# and the message it self. Always.
|
||||
|
||||
# Now we we going to wait for a send message from the other side.
|
||||
m = c.recv(4)
|
||||
m = m.decode('utf8')
|
||||
while m != "SEND":
|
||||
print("DE-SYNCED! '"+m+"' NOT 'SEND'")
|
||||
c.sendall(b"N")
|
||||
m = c.recv(4)
|
||||
m = m.decode('utf8')
|
||||
|
||||
|
||||
c.sendall(T)
|
||||
c.recv(2)
|
||||
c.sendall(bytes(str(len(message)), 'utf-8'))
|
||||
c.recv(2)
|
||||
c.sendall(message)
|
||||
c.recv(2)
|
||||
|
||||
def recv(c):
|
||||
|
||||
# This function will do the recieving of the message.
|
||||
|
||||
# I guess in order to fix most of the problems with this recv function
|
||||
# which is a little unsynsing here and there. I will make a fail switch
|
||||
# so it wil alight it self properly even it network if hidby, bibdy or
|
||||
# any of my server or client code is terribly unrelible.
|
||||
|
||||
c.sendall(b"SEND")
|
||||
|
||||
# It might fail or it might work
|
||||
|
||||
T = c.recv(1)
|
||||
T = T.decode('utf8')
|
||||
tr = 0
|
||||
while T not in ["B", "J"]:
|
||||
print("DE-SYNCED! '"+T+"' NOT 'J' OR 'B'")
|
||||
c.sendall(b"SEND")
|
||||
T = c.recv(1)
|
||||
T = T.decode('utf8')
|
||||
tr = tr + 1
|
||||
if tr == 8:
|
||||
exit()
|
||||
|
||||
c.sendall(b"OK")
|
||||
|
||||
# So here we just recieved the T , Type of our message
|
||||
|
||||
SIZE = c.recv(1024)
|
||||
SIZE = int(SIZE.decode('utf8'))
|
||||
|
||||
# Now we recieved our amount. Next is to recieve the message
|
||||
c.sendall(b"OK")
|
||||
|
||||
message = b""
|
||||
cs = 0
|
||||
|
||||
while SIZE > cs:
|
||||
l = c.recv(SIZE)
|
||||
cs = cs + len(l)
|
||||
message = message + l
|
||||
|
||||
c.sendall(b"OK")
|
||||
|
||||
message[SIZE:]
|
||||
|
||||
# Now let's ge the data back from JSON id it's a JSON.
|
||||
if T == "J":
|
||||
message = json.loads(message.decode('utf8'))
|
||||
|
||||
return message
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# P for Pain
|
||||
|
||||
# Can't afford to feel the joy
|
||||
# Can't find strength to feel the pain
|
||||
# I'll shrink and go and feel no more
|
||||
# Is it fine to be lame?
|
||||
|
||||
# I don't want death. Since death
|
||||
# Has an opposite effect
|
||||
# From suffering from being lame
|
||||
# Death is opposite of pain
|
||||
|
||||
# An importance of the pain
|
||||
# Is that joy is just a drug
|
||||
# Such as suffering and pain
|
||||
# It's a head confusing bug
|
||||
|
||||
# Joyful, satisfying shit
|
||||
# make you feel comfortable
|
||||
# But discomfort and the pain
|
||||
# It's what is affordable
|
||||
|
||||
# Lameness feel pathetic shit
|
||||
# Me described using four words
|
||||
# No more joy, only regrets
|
||||
# Five more words of discomfort
|
||||
|
||||
# Shrink from big and fall from tall
|
||||
# Give up everything you have
|
||||
# Go homeless, starve, and suffer. Feel.
|
||||
# The pain inducing hollow and venomous discomfort,
|
||||
# swallowing all and every thought and only
|
||||
# pain. Pain. And nothing more exists.
|
||||
# And nothing more is fair.
|
||||
|
||||
# Can't afford to feel the joy
|
||||
# But find strength to feel the pain
|
||||
# I'll shrink and go and feel no more
|
||||
# It is fine to be lame.
|
||||
|
||||
# https://open.lbry.com/@blenderdumbass:f/P-for-Pain:f?r=GRS5mK5GDEGa77vRotG4LwMqPCyhpF2k
|
521
network/multiuser_server.py
Normal file
521
network/multiuser_server.py
Normal file
|
@ -0,0 +1,521 @@
|
|||
# THIS FILE IS A PART OF VCStudio
|
||||
# PYTHON 3
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
# This is the SERVER program for the Multiuser system in the VCStudio.
|
||||
|
||||
# During production of "I'm Not Even Human" back in 2016 - 2018 we had multiple
|
||||
# machines to work on the project. And back then some way of moving parts of the
|
||||
# project between the machines was needed. Originally we were using a simple
|
||||
# USB thumb drive. But quickly it became inpractical.
|
||||
|
||||
# Then I developed a little program called J.Y.Exchange. ( Python 2 ) Which
|
||||
# was very cool, general purpose moving thing. And after this I introduced
|
||||
# compatibility layer to J.Y.Exchange using Organizer.
|
||||
|
||||
# Later with the development of Blender-Organizer ( It's when I stopped using
|
||||
# Gtk and wrote the first Organizer with custom UI ) I wanted to make a better
|
||||
# sync function. And the history recording was a step into that direction.
|
||||
|
||||
# Tho unfortunatly. The sync function was never finished.
|
||||
|
||||
# This is an attempt so make a sync function with some extended functionality
|
||||
# so in theory when a large studio wants to use VCStudio they would have a
|
||||
# way to work with multiple users in the same time.
|
||||
|
||||
# CHALLENGES
|
||||
|
||||
# The main challenge of this system would be the sizes of the projects. See every
|
||||
# asset, scene, shot, has a folder. In which you have hundreds of files. Take
|
||||
# shots for example. Each has 4 render directories. Where the renderer puts
|
||||
# single frames. 24 frames per second on a large project and we have 2 hundred
|
||||
# thousand frames for a 2 hour movie. 8 hundred thousand frames if all 4 folders
|
||||
# are filled up. And it's not counting all the blend files with all the revisions.
|
||||
# Assets with textures and references. And other various stuff.
|
||||
|
||||
# This is not going to be wise to just send it over the network on every frame.
|
||||
# We need a system of version contoll. Something that is a little
|
||||
# more dynamic. And doesn't require scanning the entire project.
|
||||
|
||||
# HISTORY
|
||||
|
||||
# The idea behind history is to record what stuff are being changed. So we are
|
||||
# sending only the moderatly small analytics file over the network. And if a user
|
||||
# sees that other user has changed something. They can click a button to update
|
||||
# this thing on their machine too.
|
||||
|
||||
# CONCEPT
|
||||
|
||||
# I think every item in the VCStudio. Meaning asset or shot. Every thing that we
|
||||
# can access using the win.cur variable. Should be concidered as their own things
|
||||
# but with some smartness added to it. For example. Since there are linked assets
|
||||
# in blend files for the shots. When updating the shot. You should also update
|
||||
# the assets.
|
||||
|
||||
# This server program is going to be the main allocator of recourses. The all
|
||||
# knowing wizzard to which all the VCStudios will talk in order to get up to
|
||||
# date information of who does what.
|
||||
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import insure
|
||||
import socket
|
||||
import random
|
||||
import datetime
|
||||
import threading
|
||||
import subprocess
|
||||
|
||||
# So the first thing we want to do is to make sure that we are talking to the
|
||||
# right project. For this we are going to use the name of the project. As
|
||||
# specified in the analytics data. Not by the folder name. Since multiple
|
||||
# computers might have different folder names.
|
||||
|
||||
project_name = "VCStudio_Multiuser_Project_Name_Failed"
|
||||
try:
|
||||
project_name = sys.argv[1]
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# Since it's a terminal application. That I'm totally suggest you run from the
|
||||
# stand alone computer. Rather then from the UI of the Multiuser. We are going
|
||||
# to treat it as a terminal program and print a bunch stuff to the terminal.
|
||||
|
||||
print("\n") # For when running using python3 run.py -ms
|
||||
|
||||
# Not at this moment I want to know what is my IP address. In the local network
|
||||
# space there is.
|
||||
ipget = subprocess.Popen(["hostname", "-I"],stdout=subprocess.PIPE, universal_newlines=True)
|
||||
ipget.wait()
|
||||
thisIP = ipget.stdout.read()[:-2]
|
||||
|
||||
|
||||
# Please tell me if you see an easier methon of gathering the current IP.
|
||||
|
||||
# Before we go any fursther I think it's a good idea to actually initilize the
|
||||
# server.
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind(("", 64646))
|
||||
sock.listen(0)
|
||||
|
||||
# Now since we are using threading. I need a way to manage them somehow. I love
|
||||
# when 1 function can talk freely to another. So let's use a global dictionary
|
||||
# of threads.
|
||||
|
||||
threads = {} # All of the connections will be each in it's own thread.
|
||||
users = {} # This is the list of users and metadata about those users.
|
||||
messages = [["Multiuser Server", "Server Started"]] # This is a list of messages sent by users.
|
||||
assets = {} # This is a list of assets there is in the project.
|
||||
story = ["0.0.0.0:0000", {}, "0000/00/00-00:00:00"] # The current story
|
||||
# story | last modification time
|
||||
analytics = [{}, "0000/00/00-00:00:00"] # The current analytics
|
||||
# analytics | last modification time
|
||||
|
||||
def output(string):
|
||||
|
||||
# This is a fancy Print() function.
|
||||
|
||||
cs0 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
cs0.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
cs0.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
cs0.sendto(bytes("VCStudio MULTIUSER SERVER TERMINAL | "+string, 'utf-8'), ("255.255.255.255", 54545))
|
||||
cs0.close()
|
||||
|
||||
seconds_format = "%H:%M:%S"
|
||||
time = datetime.datetime.strftime(datetime.datetime.now(), seconds_format)
|
||||
|
||||
print(time+" | "+string)
|
||||
|
||||
output("multiuser server | started | "+thisIP+" | "+project_name)
|
||||
|
||||
# Okay let's define our MAIN. Because It seems like important to do here.
|
||||
|
||||
def main():
|
||||
|
||||
# This function is going to run in a loop. Unless user specifies to close it
|
||||
# from the UI or by closing the terminal.
|
||||
while True:
|
||||
|
||||
# Let's listen for connections.
|
||||
|
||||
client, ipport = sock.accept()
|
||||
ip, port = ipport
|
||||
|
||||
userid = str(ip)+":"+str(port)
|
||||
|
||||
# Now I'm going to add the user into the main users dictionary
|
||||
users[userid] = {
|
||||
"client" :client,
|
||||
"ip" :ip ,
|
||||
"port" :port ,
|
||||
"username":"" ,
|
||||
"request" :[] , # The current request to this or all users
|
||||
"asnswer" :[] ,
|
||||
"camera" :[0,0] , # The camera in the story world
|
||||
"pause" :False # I need this when a different thread is talking
|
||||
# to the user
|
||||
}
|
||||
|
||||
# And now let's call a thread for this user
|
||||
threads[userid] = threading.Thread(target=user, args=(userid, ))
|
||||
threads[userid].setDaemon(True) # So I could close the program
|
||||
threads[userid].start()
|
||||
|
||||
|
||||
|
||||
def user(userid):
|
||||
|
||||
# Let's get the info about this user
|
||||
client = users[userid]["client"]
|
||||
ip = users[userid]["ip"]
|
||||
port = users[userid]["port"]
|
||||
username = users[userid]["username"]
|
||||
|
||||
|
||||
# This function will run for every single connected user in it's own thread.
|
||||
|
||||
# Then we want to know client's Username.
|
||||
|
||||
insure.send(client, "username?")
|
||||
|
||||
data = insure.recv(client)
|
||||
users[userid]["username"] = data
|
||||
username = users[userid]["username"]
|
||||
|
||||
# Now let's check that this person is from our project.
|
||||
insure.send(client, "project?")
|
||||
data = insure.recv(client)
|
||||
|
||||
# If this is the wrong project. The client will be kicked out.
|
||||
|
||||
if data != project_name:
|
||||
insure.send(client, "wrong_project")
|
||||
client.close()
|
||||
return
|
||||
|
||||
output(username+" | connected | "+userid)
|
||||
|
||||
# So now that the user is here. We can do some stuff with the user.
|
||||
# And since the user is the user. And server is a server. Like in
|
||||
# litteral sense of those 2 words. From now own user will be able
|
||||
# to request a bunch of stuff. So...
|
||||
|
||||
def get_story(client):
|
||||
global story
|
||||
insure.send(client, "story")
|
||||
clients_story = insure.recv(client)
|
||||
|
||||
if clients_story[1] > story[2]:
|
||||
story = [userid, clients_story[0], clients_story[1]]
|
||||
users[userid]["camera"] = clients_story[0]["camera"].copy()
|
||||
|
||||
insure.send(client, [story[0], story[1]])
|
||||
insure.recv(client)
|
||||
|
||||
def get_assets(client):
|
||||
insure.send(client, "assets")
|
||||
clients_assets = insure.recv(client)
|
||||
|
||||
# "/chr/Moria" : [userid, timestamp]
|
||||
|
||||
for asset in clients_assets:
|
||||
if asset not in assets or assets[asset][1] < clients_assets[asset][1]:
|
||||
assets[asset] = clients_assets[asset]
|
||||
|
||||
|
||||
insure.send(client, assets)
|
||||
insure.recv(client)
|
||||
|
||||
|
||||
def get_analytics(client):
|
||||
global analytics
|
||||
insure.send(client, "analytics")
|
||||
clients_analytics = insure.recv(client)
|
||||
if clients_analytics[1] > analytics[1]:
|
||||
analytics = clients_analytics
|
||||
|
||||
insure.send(client, analytics[0])
|
||||
insure.recv(client)
|
||||
|
||||
get_story(client)
|
||||
get_assets(client)
|
||||
get_analytics(client)
|
||||
|
||||
|
||||
insure.send(client, "yours")
|
||||
|
||||
# Here when the connection is established. I would like to tell all the
|
||||
# already connected users that the connection is established. It's going
|
||||
# to be a 2 way process.
|
||||
|
||||
# 1. We send a notice to all the threads saying to send the corrisponding
|
||||
# users the data about the new user.
|
||||
request_all([0,"users"])
|
||||
# 2. We execute this request. Which is not going to happen in this thread
|
||||
# but in the instances of this thread for all the other users. So the
|
||||
# code is in this function.
|
||||
|
||||
while True:
|
||||
|
||||
try:
|
||||
|
||||
|
||||
|
||||
# Recieving users request
|
||||
request = insure.recv(client)
|
||||
|
||||
|
||||
if request == "yours":
|
||||
|
||||
###############################################################
|
||||
# REQUESTS FROM ONE USER TO ANOTHER #
|
||||
###############################################################
|
||||
|
||||
if len(users[userid]["request"]) > 1:
|
||||
|
||||
if users[userid]["request"][1] == "story":
|
||||
get_story(client)
|
||||
insure.send(client, "yours")
|
||||
|
||||
elif users[userid]["request"][1] == "analytics":
|
||||
get_analytics(client)
|
||||
insure.send(client, "yours")
|
||||
|
||||
elif users[userid]["request"][1] == "assets":
|
||||
get_assets(client)
|
||||
insure.send(client, "yours")
|
||||
|
||||
elif users[userid]["request"][1] == "users":
|
||||
insure.send(client, "users")
|
||||
|
||||
elif users[userid]["request"][1] == "at":
|
||||
insure.send(client, users[userid]["request"])
|
||||
|
||||
elif users[userid]["request"][1] == "message":
|
||||
insure.send(client, ["messages",messages])
|
||||
|
||||
elif users[userid]["request"][0] == "serve":
|
||||
|
||||
serve(
|
||||
users[userid]["request"][1],
|
||||
userid,
|
||||
users[userid]["request"][2]
|
||||
)
|
||||
|
||||
# Clearing the request.
|
||||
users[userid]["request"] = []
|
||||
|
||||
# If there is nothing we want to tell the user that it's
|
||||
# their turn to request.
|
||||
else:
|
||||
insure.send(client, "yours")
|
||||
|
||||
###############################################################
|
||||
# REQUESTS FROM USER #
|
||||
###############################################################
|
||||
|
||||
else:
|
||||
|
||||
|
||||
|
||||
if request == "story":
|
||||
get_story(client)
|
||||
request_all([userid, "story"])
|
||||
|
||||
elif request == "analytics":
|
||||
get_analytics(client)
|
||||
request_all([userid, "analytics"])
|
||||
|
||||
elif request[0] == "at":
|
||||
output(userid+" | at | "+request[1]+" | time | "+request[2])
|
||||
request_all([userid, "at", request[1], request[2]])
|
||||
|
||||
elif request == "users":
|
||||
insure.send(client, users_list())
|
||||
|
||||
elif request[0] == "message":
|
||||
messages.append([username, request[1]])
|
||||
request_all([0, "message"])
|
||||
|
||||
elif request[0] == "get":
|
||||
# If a user sends GET. It means that the user wants a
|
||||
# folder from another user. This means that we need to
|
||||
# communicate with 2 clients at ones. Which is kind a
|
||||
# tricky.
|
||||
try:
|
||||
|
||||
# There is a problem to just simply requesting a user
|
||||
# directly. Because he might have something requested.
|
||||
while users[request[1]]["request"]:
|
||||
time.sleep(0.001)
|
||||
|
||||
users[request[1]]["request"] = ["serve", request[2], userid]
|
||||
|
||||
# The problem is that this thead will probably continue
|
||||
# asking stuff from the server. Which is not good. We want
|
||||
# to stop it here and now.
|
||||
|
||||
# Pause
|
||||
users[userid]["pause"] = True
|
||||
while users[userid]["pause"]:
|
||||
time.sleep(0.001) # Funny but thread needs to do something or
|
||||
# it pauses all of the threads. LOL.
|
||||
|
||||
# The other thread will unpause this thread when the
|
||||
# folder is downloaded.
|
||||
except:
|
||||
# Sometimes there is no user to get it from.
|
||||
print("USER GET ERROR")
|
||||
|
||||
# Finishing the request and giving the user it's turn
|
||||
insure.send(client, "yours")
|
||||
|
||||
except Exception as e:
|
||||
|
||||
# If the connection is lost. We want to delete the user.
|
||||
request_all([0,"users"])
|
||||
output(username+" | "+userid+" | "+str(e)+" | line: "+str(sys.exc_info()[-1].tb_lineno))
|
||||
try:
|
||||
del users[userid]
|
||||
except Exception as e:
|
||||
output("deleting user error | "+str(e))
|
||||
|
||||
# We want to clear the data of the assets if this happens
|
||||
|
||||
globals()["assets"] = {}
|
||||
request_all([0,"assets"])
|
||||
|
||||
return
|
||||
|
||||
def users_list():
|
||||
|
||||
# This function will make users
|
||||
|
||||
U = {}
|
||||
|
||||
for user in list(users.keys()):
|
||||
U[user] = {
|
||||
"username":users[user]["username"],
|
||||
"camera" :users[user]["camera"]
|
||||
}
|
||||
|
||||
return U
|
||||
|
||||
def request_all(request):
|
||||
|
||||
for user in users:
|
||||
if user != request[0]:
|
||||
while users[user]["request"]:
|
||||
time.sleep(0.001)
|
||||
users[user]["request"] = request
|
||||
|
||||
|
||||
def broadcast():
|
||||
|
||||
# This function will broadcast the IP address of the server to all VCStudio
|
||||
# users. So the user experience would be straight forward. As clicking a button
|
||||
# and not complex as knowing the IP and stuff. I mean yes. Good for you if you
|
||||
# want to do everything manually. But most people are dumb.
|
||||
|
||||
cs1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
cs1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
cs1.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
message = "VCStudio MULTIUSER SERVER | "+thisIP+" | "+project_name
|
||||
cs1.sendto(bytes(message, 'utf-8'), ("255.255.255.255", 54545))
|
||||
cs1.close()
|
||||
|
||||
|
||||
def listen():
|
||||
|
||||
# This function will listen to all commenications for any kind of abbort or
|
||||
# extreme messages.
|
||||
|
||||
message = ""
|
||||
|
||||
# Let's try receiving messages from the outside.
|
||||
|
||||
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))
|
||||
sock.settimeout(0.05)
|
||||
|
||||
data, addr = sock.recvfrom(1024)
|
||||
data = data.decode('utf8')
|
||||
|
||||
sock.close()
|
||||
|
||||
message = data
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
# Now let's close the serer if the message is VCStudio ABORT MULTIUSER
|
||||
if message == "VCStudio ABORT MULTIUSER":
|
||||
output("recieved abort message | closing")
|
||||
exit()
|
||||
|
||||
|
||||
def serve(folder, fromid, toid):
|
||||
|
||||
output("serving | "+folder+" | from | "+fromid+" | to | "+toid)
|
||||
|
||||
# Let's first of all get all the data that we need before starting the
|
||||
# operation.
|
||||
|
||||
to = users[ toid ]["client"]
|
||||
fr = users[fromid]["client"]
|
||||
|
||||
# Now let's tell our "fr" that we need the "folder" from him.
|
||||
|
||||
insure.send(fr, ["give", folder])
|
||||
|
||||
# The "fr" client should respond with a list of files / folder and their
|
||||
# hashes. The is no need for us ho have this data. So let's pipe it
|
||||
# directly to the "to" user.
|
||||
|
||||
insure.send(to, insure.recv(fr))
|
||||
|
||||
# Now we are going to retvieve the list of files that the user needs.
|
||||
# We are going to save this list since we will be doing the connection of
|
||||
# them transferring the files. And we need to know the length of it.
|
||||
|
||||
getlist = insure.recv(to)
|
||||
insure.send(fr, getlist)
|
||||
|
||||
# Now let's serve the files.
|
||||
for f in getlist:
|
||||
print("serving file:",f)
|
||||
insure.send(to, insure.recv(fr))
|
||||
|
||||
insure.send(fr, insure.recv(to))
|
||||
|
||||
|
||||
# And finally let's release our "to".
|
||||
users[toid]["pause"] = False
|
||||
|
||||
# Before we start the main loop I want to have the server loop too.
|
||||
threads["server_listen"] = threading.Thread(target=main, args=())
|
||||
threads["server_listen"].setDaemon(True) # So I could close the program
|
||||
threads["server_listen"].start()
|
||||
|
||||
while True:
|
||||
|
||||
# This next stuff will be running in the main thread in the loop.
|
||||
|
||||
# First we are going to broadcast ourselves.
|
||||
broadcast()
|
||||
|
||||
# Then we are going to listen for input from the Users. Because some kind of
|
||||
# abbort operation could be called using UDP protocol. And we want to be
|
||||
# able to hear it.
|
||||
|
||||
listen()
|
||||
|
||||
|
95
network/multiuser_terminal.py
Normal file
95
network/multiuser_terminal.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
# THIS FILE IS A PART OF VCStudio
|
||||
# PYTHON 3
|
||||
|
||||
###############################################################################
|
||||
|
||||
# Multiuser is a complex system. It's so complex that my brain is melting at
|
||||
# the moment. Anyways. Users need a way to talk to the server. And I'm going
|
||||
# to provide it to the user using a multiuser Layer. See:
|
||||
# studio/studio_multiuserLayer.py.
|
||||
|
||||
# Tho there is one problem. The user should be able to read and write messages
|
||||
# to the server. And you think. Right. There is a network/network_multiuser.py
|
||||
# for this. Well. for the most stuff yes.
|
||||
|
||||
# There are 2 types of protocols multiuser system uses. The TCP and the UDP.
|
||||
|
||||
# TCP protocol is used to tranfer sensetive stuff. Like files, scene text. Stuff
|
||||
# that has to be at full. And where loosing packages is unexceptable.
|
||||
|
||||
# UDP will be used for the other stuff. Like log of the server. And messages to
|
||||
# the server. Such as an Abort message.
|
||||
|
||||
# This file handle the UDP side of things.
|
||||
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import socket
|
||||
import datetime
|
||||
|
||||
def listen(win):
|
||||
|
||||
# Listen function will run in it's own thread and get the data from the
|
||||
# server. It will be teminal like messages that we show in the multiuser
|
||||
# window.
|
||||
|
||||
UDP_IP = "255.255.255.255"
|
||||
UDP_PORT = 54545
|
||||
|
||||
while True:
|
||||
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind((UDP_IP, UDP_PORT))
|
||||
#sock.settimeout(0.05)
|
||||
|
||||
data, addr = sock.recvfrom(1024)
|
||||
data = data.decode('utf8')
|
||||
|
||||
seconds_format = "%H:%M:%S"
|
||||
time = datetime.datetime.strftime(datetime.datetime.now(), seconds_format)
|
||||
|
||||
if "VCStudio MULTIUSER SERVER TERMINAL" in data:
|
||||
#print(time+data.replace("VCStudio MULTIUSER SERVER TERMINAL", ""))
|
||||
win.multiuser["terminal"].append(time+data.replace("VCStudio MULTIUSER SERVER TERMINAL", ""))
|
||||
|
||||
# Now i want to use a limit. Because hell I don't want 20000000
|
||||
# bazillion messages stored in the memory at all times.
|
||||
|
||||
win.multiuser["terminal"] = win.multiuser["terminal"][-300:]
|
||||
|
||||
# And we want to scroll down on each message so
|
||||
|
||||
win.scroll["multiuser_terminal"] = 0 - len(win.multiuser["terminal"])*50
|
||||
|
||||
elif "VCStudio ABORT MULTIUSER" in data:
|
||||
win.multiuser["users"] = {}
|
||||
|
||||
sock.close()
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
def message(message):
|
||||
|
||||
# This is a function that will be called by multiple buttons. That want to
|
||||
# send the server any kind of message. But actually it's gonna send a message
|
||||
# to 255.255.255.255 port 54545 that the server is listening. At the moment
|
||||
# of writing this comment the server is programed only for 1 type of command
|
||||
# like this. It's the "VCStudio ABORT MULTIUSER". That makes the server stop
|
||||
# working. This command is a bit dangerous at the moment. Because it's running
|
||||
# on it's own thread in the server. Making it close even in the middle of a
|
||||
# request. Like it can corrupt a file that's being transferred.
|
||||
|
||||
UDP_IP = "255.255.255.255"
|
||||
UDP_PORT = 54545
|
||||
|
||||
cs1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
cs1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
cs1.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
cs1.sendto(bytes(message, 'utf-8'), (UDP_IP, UDP_PORT))
|
||||
cs1.close()
|
||||
|
||||
|
504
network/network_multiuser.py
Normal file
504
network/network_multiuser.py
Normal file
|
@ -0,0 +1,504 @@
|
|||
# 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"
|
||||
else:
|
||||
t = "/rnd"
|
||||
|
||||
# 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"]:
|
||||
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)
|
||||
|
|
@ -1,22 +1,40 @@
|
|||
# GNU General Public License
|
||||
|
||||
# This script is for developers of VCStudio rendering. This will read any
|
||||
# message on 127.0.0.1 port 54545
|
||||
# This script is for developers of VCStudio. This one is for reading messages.
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
# Fisrt we need to know what mode are we testing.
|
||||
|
||||
print(" VCStudio Read Messages.")
|
||||
print(" Modes: r [Render], m [Multiuser]")
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
mode = sys.argv[2]
|
||||
print("Mode: "+mode)
|
||||
|
||||
else:
|
||||
mode = input("Mode: ")
|
||||
|
||||
import socket
|
||||
|
||||
if mode == "r":
|
||||
UDP_IP = "127.0.0.1"
|
||||
UDP_PORT = 54545
|
||||
|
||||
elif mode == "m":
|
||||
UDP_IP = "255.255.255.255"
|
||||
UDP_PORT = 54545
|
||||
|
||||
else:
|
||||
print("There is no mode: "+mode)
|
||||
exit()
|
||||
|
||||
prev = ""
|
||||
|
||||
while True:
|
||||
|
||||
|
||||
try:
|
||||
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind((UDP_IP, UDP_PORT))
|
||||
|
@ -25,8 +43,11 @@ while True:
|
|||
data, addr = sock.recvfrom(1024)
|
||||
data = data.decode('utf8')
|
||||
|
||||
seconds_format = "%H:%M:%S"
|
||||
time = datetime.datetime.strftime(datetime.datetime.now(), seconds_format)
|
||||
|
||||
if prev != str(data):
|
||||
print("<<< "+str(data))
|
||||
print(time+" | "+str(data))
|
||||
|
||||
prev = str(data)
|
||||
|
||||
|
|
|
@ -1,10 +1,37 @@
|
|||
# GNU General Public License
|
||||
|
||||
# This script is for developers of VCStudio rendering. This will send any
|
||||
# message you type in the terminal to 127.0.0.1 port 54545
|
||||
# This script is for developers of VCStudio. This one is sending test messages.
|
||||
|
||||
import socket
|
||||
import time
|
||||
import sys
|
||||
|
||||
# Fisrt we need to know what mode are we testing.
|
||||
|
||||
print(" VCStudio Send Test Messages.")
|
||||
print(" Modes: r [Render], m [Multiuser]")
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
mode = sys.argv[2]
|
||||
print("Mode: "+mode)
|
||||
|
||||
else:
|
||||
mode = input("Mode: ")
|
||||
|
||||
import socket
|
||||
|
||||
if mode == "r":
|
||||
UDP_IP = "127.0.0.1"
|
||||
UDP_PORT = 54545
|
||||
|
||||
elif mode == "m":
|
||||
UDP_IP = "255.255.255.255"
|
||||
UDP_PORT = 54545
|
||||
|
||||
else:
|
||||
print("There is no mode: "+mode)
|
||||
exit()
|
||||
|
||||
|
||||
prev = ""
|
||||
|
||||
|
@ -20,5 +47,5 @@ while True:
|
|||
cs1.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
|
||||
#message = "["+str(i)+"]TEST VCStudio rendering"
|
||||
cs1.sendto(bytes(message, 'utf-8'), ('127.0.0.1', 54545))
|
||||
cs1.sendto(bytes(message, 'utf-8'), (UDP_IP, UDP_PORT))
|
||||
#print(message)
|
||||
|
|
38
network/test_client.py
Normal file
38
network/test_client.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
# GNU General Public License
|
||||
|
||||
# This script is for developers of VCStudio. This one is going to simulate the
|
||||
# VCStudio client. But in a way where you have more control.
|
||||
|
||||
# This is a dumb script so deal with it.
|
||||
import socket
|
||||
|
||||
print("What is te IP of the server?")
|
||||
|
||||
ip = input("IP: ")
|
||||
port = 64646
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect((ip, port))
|
||||
|
||||
|
||||
# Now let's talk to the server
|
||||
|
||||
while True:
|
||||
|
||||
# Simple cycle of receive / write
|
||||
|
||||
# RECIEVE
|
||||
|
||||
data = sock.recv(1024)
|
||||
data = data.decode('utf8')
|
||||
print(data)
|
||||
|
||||
# WRITE
|
||||
|
||||
message = input(">>> ")
|
||||
if message == "exit":
|
||||
exit()
|
||||
message = bytes(message, 'utf-8')
|
||||
sock.sendall(message)
|
||||
|
||||
|
Loading…
Reference in a new issue