folder reorganization
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from client import Client
|
||||
from server import Server
|
||||
__all__ = ['Client', 'Server']
|
||||
@@ -0,0 +1,200 @@
|
||||
import base64
|
||||
import json
|
||||
import random
|
||||
import struct
|
||||
import time
|
||||
import urllib
|
||||
from core import httptools
|
||||
from threading import Thread
|
||||
|
||||
from file import File
|
||||
from handler import Handler
|
||||
from platformcode import logger
|
||||
from server import Server
|
||||
|
||||
|
||||
class Client(object):
|
||||
VIDEO_EXTS = {'.avi': 'video/x-msvideo', '.mp4': 'video/mp4', '.mkv': 'video/x-matroska',
|
||||
'.m4v': 'video/mp4', '.mov': 'video/quicktime', '.mpg': 'video/mpeg','.ogv': 'video/ogg',
|
||||
'.ogg': 'video/ogg', '.webm': 'video/webm', '.ts': 'video/mp2t', '.3gp': 'video/3gpp'}
|
||||
|
||||
def __init__(self, url, port=None, ip=None, auto_shutdown=True, wait_time=20, timeout=5, is_playing_fnc=None):
|
||||
|
||||
self.port = port if port else random.randint(8000,8099)
|
||||
self.ip = ip if ip else "127.0.0.1"
|
||||
self.connected = False
|
||||
self.start_time = None
|
||||
self.last_connect = None
|
||||
self.is_playing_fnc = is_playing_fnc
|
||||
self.auto_shutdown = auto_shutdown
|
||||
self.wait_time = wait_time
|
||||
self.timeout = timeout
|
||||
self.running = False
|
||||
self.file = None
|
||||
self.files = []
|
||||
|
||||
self._server = Server((self.ip, self.port), Handler, client=self)
|
||||
self.add_url(url)
|
||||
self.start()
|
||||
|
||||
def start(self):
|
||||
self.start_time = time.time()
|
||||
self.running = True
|
||||
self._server.run()
|
||||
t= Thread(target=self._auto_shutdown)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
logger.info("MEGA Server Started")
|
||||
|
||||
def _auto_shutdown(self):
|
||||
while self.running:
|
||||
time.sleep(1)
|
||||
if self.file and self.file.cursor:
|
||||
self.last_connect = time.time()
|
||||
|
||||
if self.is_playing_fnc and self.is_playing_fnc():
|
||||
self.last_connect = time.time()
|
||||
|
||||
if self.auto_shutdown:
|
||||
#shudown por haber cerrado el reproductor
|
||||
if self.connected and self.last_connect and self.is_playing_fnc and not self.is_playing_fnc():
|
||||
if time.time() - self.last_connect - 1 > self.timeout:
|
||||
self.stop()
|
||||
|
||||
#shutdown por no realizar ninguna conexion
|
||||
if (not self.file or not self.file.cursor) and self.start_time and self.wait_time and not self.connected:
|
||||
if time.time() - self.start_time - 1 > self.wait_time:
|
||||
self.stop()
|
||||
|
||||
#shutdown tras la ultima conexion
|
||||
if (not self.file or not self.file.cursor) and self.timeout and self.connected and self.last_connect and not self.is_playing_fnc:
|
||||
if time.time() - self.last_connect - 1 > self.timeout:
|
||||
self.stop()
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
self._server.stop()
|
||||
logger.info("MEGA Server Stopped")
|
||||
|
||||
def get_play_list(self):
|
||||
if len(self.files) > 1:
|
||||
return "http://" + self.ip + ":" + str(self.port) + "/playlist.pls"
|
||||
else:
|
||||
return "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(self.files[0].name.encode("utf8"))
|
||||
|
||||
def get_files(self):
|
||||
files = []
|
||||
if self.files:
|
||||
for file in self.files:
|
||||
n = file.name.encode("utf8")
|
||||
u = "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(n)
|
||||
s = file.size
|
||||
file_id = file.file_id
|
||||
files.append({"name":n,"url":u,"size":s, "id": file_id})
|
||||
return files
|
||||
|
||||
def add_url(self, url):
|
||||
url = url.split("/#")[1]
|
||||
id_video = None
|
||||
if "|" in url:
|
||||
url, id_video = url.split("|")
|
||||
if url.startswith("F!"):
|
||||
if len(url.split("!")) ==3:
|
||||
folder_id = url.split("!")[1]
|
||||
folder_key = url.split("!")[2]
|
||||
master_key = self.base64_to_a32(folder_key)
|
||||
files = self.api_req({"a":"f","c":1,"r":1},"&n="+folder_id)
|
||||
for file in files["f"]:
|
||||
if file["t"] == 0:
|
||||
if id_video and id_video != file["h"]:
|
||||
continue
|
||||
key = file['k'][file['k'].index(':') + 1:]
|
||||
key = self.decrypt_key(self.base64_to_a32(key), master_key)
|
||||
k = (key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6], key[3] ^ key[7])
|
||||
attributes = self.base64urldecode(file['a'])
|
||||
attributes = self.dec_attr(attributes, k)
|
||||
self.files.append(File(info=attributes, file_id=file["h"], key=key, folder_id=folder_id, file= file, client = self ))
|
||||
else:
|
||||
raise Exception("Enlace no valido")
|
||||
|
||||
elif url.startswith("!") or url.startswith("N!"):
|
||||
if len(url.split("!")) ==3:
|
||||
file_id = url.split("!")[1]
|
||||
file_key = url.split("!")[2]
|
||||
file = self.api_req({'a': 'g', 'g': 1, 'p': file_id})
|
||||
key = self.base64_to_a32(file_key)
|
||||
k = (key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6], key[3] ^ key[7])
|
||||
attributes = self.base64urldecode(file['at'])
|
||||
attributes = self.dec_attr(attributes, k)
|
||||
self.files.append(File(info=attributes, file_id=file_id, key=key, file= file, client = self))
|
||||
else:
|
||||
raise Exception("Enlace no valido")
|
||||
else:
|
||||
raise Exception("Enlace no valido")
|
||||
|
||||
def api_req(self, req, get=""):
|
||||
seqno = random.randint(0, 0xFFFFFFFF)
|
||||
url = 'https://g.api.mega.co.nz/cs?id=%d%s' % (seqno, get)
|
||||
return json.loads(self.post(url, json.dumps([req])))[0]
|
||||
|
||||
def base64urldecode(self,data):
|
||||
data += '=='[(2 - len(data) * 3) % 4:]
|
||||
for search, replace in (('-', '+'), ('_', '/'), (',', '')):
|
||||
data = data.replace(search, replace)
|
||||
return base64.b64decode(data)
|
||||
|
||||
def base64urlencode(self,data):
|
||||
data = base64.b64encode(data)
|
||||
for search, replace in (('+', '-'), ('/', '_'), ('=', '')):
|
||||
data = data.replace(search, replace)
|
||||
return data
|
||||
|
||||
def a32_to_str(self,a):
|
||||
return struct.pack('>%dI' % len(a), *a)
|
||||
|
||||
def str_to_a32(self,b):
|
||||
if len(b) % 4: # Add padding, we need a string with a length multiple of 4
|
||||
b += '\0' * (4 - len(b) % 4)
|
||||
return struct.unpack('>%dI' % (len(b) / 4), b)
|
||||
|
||||
def base64_to_a32(self,s):
|
||||
return self.str_to_a32(self.base64urldecode(s))
|
||||
|
||||
def a32_to_base64(self,a):
|
||||
return self.base64urlencode(self.a32_to_str(a))
|
||||
|
||||
def aes_cbc_decrypt(self, data, key):
|
||||
try:
|
||||
from Crypto.Cipher import AES
|
||||
decryptor = AES.new(key, AES.MODE_CBC, '\0' * 16)
|
||||
#decryptor = aes.AESModeOfOperationCBC(key, iv='\0' * 16)
|
||||
except:
|
||||
import jscrypto
|
||||
decryptor = jscrypto.new(key, jscrypto.MODE_CBC, '\0' * 16)
|
||||
return decryptor.decrypt(data)
|
||||
|
||||
def aes_cbc_decrypt_a32(self,data, key):
|
||||
return self.str_to_a32(self.aes_cbc_decrypt(self.a32_to_str(data), self.a32_to_str(key)))
|
||||
|
||||
def decrypt_key(self,a, key):
|
||||
return sum((self.aes_cbc_decrypt_a32(a[i:i+4], key) for i in xrange(0, len(a), 4)), ())
|
||||
|
||||
def post(self, url, data):
|
||||
return httptools.downloadpage(url, data).data
|
||||
import ssl
|
||||
from functools import wraps
|
||||
def sslwrap(func):
|
||||
@wraps(func)
|
||||
def bar(*args, **kw):
|
||||
kw['ssl_version'] = ssl.PROTOCOL_TLSv1
|
||||
return func(*args, **kw)
|
||||
return bar
|
||||
|
||||
ssl.wrap_socket = sslwrap(ssl.wrap_socket)
|
||||
return urllib.urlopen(url, data).read()
|
||||
|
||||
def dec_attr(self, attr, key):
|
||||
attr = self.aes_cbc_decrypt(attr, self.a32_to_str(key)).rstrip('\0')
|
||||
if not attr.endswith("}"):
|
||||
attr = attr.rsplit("}", 1)[0] + "}"
|
||||
return json.loads(attr[4:]) if attr[:6] == 'MEGA{"' else False
|
||||
@@ -0,0 +1,72 @@
|
||||
import urllib2
|
||||
|
||||
class Cursor(object):
|
||||
def __init__(self, file):
|
||||
self._file=file
|
||||
self.pos=0
|
||||
self.conn =None
|
||||
self.initial_value = file.initial_value
|
||||
self.k = file.k
|
||||
|
||||
def mega_request(self,offset, retry=False):
|
||||
if not self._file.url or retry:
|
||||
if self._file.folder_id :
|
||||
file = self._file._client.api_req({"a":"g","g":1,"n":self._file.file_id},"&n="+self._file.folder_id)
|
||||
self._file.url= file["g"]
|
||||
else:
|
||||
file = self._file._client.api_req({'a': 'g', 'g': 1, 'p': self._file.file_id})
|
||||
self._file.url= file["g"]
|
||||
|
||||
req = urllib2.Request(self._file.url)
|
||||
req.headers['Range'] = 'bytes=%s-' % (offset)
|
||||
try:
|
||||
self.conn = urllib2.urlopen(req)
|
||||
self.prepare_decoder(offset)
|
||||
except:
|
||||
#La url del archivo expira transcurrido un tiempo, si da error 403, reintenta volviendo a solicitar la url mediante la API
|
||||
self.mega_request(offset, True)
|
||||
|
||||
def read(self,n=None):
|
||||
if not self.conn:
|
||||
return
|
||||
res=self.conn.read(n)
|
||||
if res:
|
||||
res = self.decode(res)
|
||||
self.pos+=len(res)
|
||||
return res
|
||||
|
||||
|
||||
def seek(self,n):
|
||||
if n>self._file.size:
|
||||
n=self._file.size
|
||||
elif n<0:
|
||||
raise ValueError('Seeking negative')
|
||||
self.mega_request(n)
|
||||
self.pos=n
|
||||
|
||||
def tell(self):
|
||||
return self.pos
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self,exc_type, exc_val, exc_tb):
|
||||
self._file.cursors.remove(self)
|
||||
if len(self._file.cursors) == 0: self._file.cursor = False
|
||||
|
||||
def decode(self, data):
|
||||
return self.decryptor.decrypt(data)
|
||||
|
||||
def prepare_decoder(self,offset):
|
||||
initial_value = self.initial_value + int(offset/16)
|
||||
try:
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util import Counter
|
||||
self.decryptor = AES.new(self._file._client.a32_to_str(self.k), AES.MODE_CTR, counter = Counter.new(128, initial_value = initial_value))
|
||||
except:
|
||||
from pyaes import aes
|
||||
self.decryptor = aes.AESModeOfOperationCTR(f=self,key=self._client.a32_to_str(self.k),counter=aes.Counter(initial_value=initial_value))
|
||||
|
||||
rest = offset - int(offset/16)*16
|
||||
if rest:
|
||||
self.decode(str(0)*rest)
|
||||
@@ -0,0 +1,35 @@
|
||||
from cursor import Cursor
|
||||
|
||||
|
||||
class File(object):
|
||||
def __init__(self, info, file_id, key, file ,client, folder_id=None):
|
||||
self._client = client
|
||||
self.folder_id = folder_id
|
||||
self.file_id = file_id
|
||||
self.cursor = False
|
||||
self.cursors = []
|
||||
self.key = key
|
||||
self.file = file
|
||||
self.info= info
|
||||
self.name = info["n"]
|
||||
self.size = file["s"]
|
||||
self.request=None
|
||||
self.k = self.key[0] ^ self.key[4] , self.key[1] ^ self.key[5] , self.key[2] ^ self.key[6], self.key[3] ^ self.key[7]
|
||||
self.iv = self.key[4:6] + (0, 0)
|
||||
self.initial_value = (((self.iv[0] << 32) + self.iv[1]) << 64)
|
||||
if not self.folder_id:
|
||||
self.url= self.file["g"]
|
||||
else:
|
||||
self.url = None
|
||||
|
||||
def create_cursor(self,offset):
|
||||
c = Cursor(self)
|
||||
c.seek(offset)
|
||||
self.cursor = True
|
||||
self.cursors.append(c)
|
||||
return c
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import BaseHTTPServer
|
||||
import urlparse
|
||||
import time
|
||||
import urllib
|
||||
import types
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
protocol_version = 'HTTP/1.1'
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
def parse_range(self, range):
|
||||
if range:
|
||||
m=re.compile(r'bytes=(\d+)-(\d+)?').match(range)
|
||||
if m:
|
||||
return m.group(1), m.group(2)
|
||||
return None, None
|
||||
|
||||
def do_GET(self):
|
||||
self.server._client.connected = True
|
||||
|
||||
if self.do_HEAD():
|
||||
with self.server._client.file.create_cursor(self.offset) as f:
|
||||
sended = 0
|
||||
while sended < self.size:
|
||||
buf= f.read(1024*16)
|
||||
if buf:
|
||||
if sended + len(buf) > self.size: buf=buf[:self.size-sended]
|
||||
self.wfile.write(buf)
|
||||
sended +=len(buf)
|
||||
else:
|
||||
break
|
||||
|
||||
def send_pls(self, files):
|
||||
playlist = "[playlist]\n\n"
|
||||
for x,f in enumerate(files):
|
||||
playlist += "File"+str(x+1)+"=http://" + self.server._client.ip + ":" + str(self.server._client.port) + "/" + urllib.quote(f.name)+"\n"
|
||||
playlist += "Title"+str(x+1)+"=" +f.name+"\n"
|
||||
|
||||
playlist +="NumberOfEntries=" + str(len(files))
|
||||
playlist +="Version=2"
|
||||
self.send_response(200, 'OK')
|
||||
self.send_header("Content-Length", str(len(playlist)))
|
||||
self.finish_header()
|
||||
self.wfile.write(playlist)
|
||||
|
||||
def do_HEAD(self):
|
||||
url=urlparse.urlparse(self.path).path
|
||||
|
||||
while not self.server._client.files:
|
||||
time.sleep(1)
|
||||
|
||||
if url=="/playlist.pls":
|
||||
self.send_pls(self.server._client.files)
|
||||
return False
|
||||
|
||||
|
||||
if not self.server._client.file or urllib.unquote(url)[1:] != self.server._client.file.name:
|
||||
for f in self.server._client.files:
|
||||
if f.name == urllib.unquote(url)[1:].decode("utf-8"):
|
||||
self.server._client.file = f
|
||||
break
|
||||
|
||||
|
||||
if self.server._client.file and urllib.unquote(url)[1:].decode("utf-8") == self.server._client.file.name:
|
||||
range = False
|
||||
self.offset=0
|
||||
size, mime = self._file_info()
|
||||
start, end = self.parse_range(self.headers.get('Range', ""))
|
||||
self.size = size
|
||||
|
||||
if start <> None:
|
||||
if end == None: end = size - 1
|
||||
self.offset=int(start)
|
||||
self.size=int(end) - int(start) + 1
|
||||
range=(int(start), int(end), int(size))
|
||||
else:
|
||||
range = None
|
||||
|
||||
self.send_resp_header(mime, size, range)
|
||||
return True
|
||||
|
||||
else:
|
||||
self.send_error(404, 'Not Found')
|
||||
|
||||
|
||||
def _file_info(self):
|
||||
size=self.server._client.file.size
|
||||
ext=os.path.splitext(self.server._client.file.name)[1]
|
||||
mime=self.server._client.VIDEO_EXTS.get(ext)
|
||||
if not mime:
|
||||
mime='application/octet-stream'
|
||||
return size,mime
|
||||
|
||||
|
||||
def send_resp_header(self, cont_type, size, range=False):
|
||||
|
||||
if range:
|
||||
self.send_response(206, 'Partial Content')
|
||||
else:
|
||||
self.send_response(200, 'OK')
|
||||
|
||||
self.send_header('Content-Type', cont_type)
|
||||
self.send_header('Accept-Ranges', 'bytes')
|
||||
|
||||
if range:
|
||||
if isinstance(range, (types.TupleType, types.ListType)) and len(range)==3:
|
||||
self.send_header('Content-Range', 'bytes %d-%d/%d' % range)
|
||||
self.send_header('Content-Length', range[1]-range[0]+1)
|
||||
else:
|
||||
raise ValueError('Invalid range value')
|
||||
else:
|
||||
self.send_header('Content-Length', size)
|
||||
|
||||
self.send_header('Connection', 'close')
|
||||
self.end_headers()
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import traceback
|
||||
import BaseHTTPServer
|
||||
from SocketServer import ThreadingMixIn
|
||||
from threading import Thread
|
||||
|
||||
|
||||
class Server(ThreadingMixIn, BaseHTTPServer.HTTPServer):
|
||||
daemon_threads = True
|
||||
timeout = 1
|
||||
def __init__(self, address, handler, client):
|
||||
BaseHTTPServer.HTTPServer.__init__(self,address,handler)
|
||||
self._client = client
|
||||
self.running=True
|
||||
self.request = None
|
||||
|
||||
def stop(self):
|
||||
self.running=False
|
||||
|
||||
def serve(self):
|
||||
while self.running:
|
||||
try:
|
||||
self.handle_request()
|
||||
except:
|
||||
print traceback.format_exc()
|
||||
|
||||
def run(self):
|
||||
t=Thread(target=self.serve, name='HTTP Server')
|
||||
t.daemon=self.daemon_threads
|
||||
t.start()
|
||||
|
||||
def handle_error(self, request, client_address):
|
||||
if not "socket.py" in traceback.format_exc():
|
||||
print traceback.format_exc()
|
||||
Reference in New Issue
Block a user