feat(SublimeText2.EditorPackages): cache packages
This commit is contained in:
@@ -0,0 +1,436 @@
|
||||
import sublime,sublime_plugin,threading,json,os,threading,subprocess,socket,shutil
|
||||
from base64 import b64encode, b64decode
|
||||
# python 2.6 differences
|
||||
try:
|
||||
from hashlib import md5, sha1
|
||||
except: from md5 import md5; from sha import sha as sha1
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
from struct import pack, unpack_from
|
||||
import array, struct, os
|
||||
import urllib2
|
||||
s2a = lambda s: [ord(c) for c in s]
|
||||
settings = sublime.load_settings('LiveReload.sublime-settings')
|
||||
port = settings.get('port')
|
||||
version = settings.get('version')
|
||||
##LOAD latest livereload.js from github (for v2 of protocol) or if this fails local version
|
||||
try:
|
||||
req = urllib2.urlopen(urllib2.Request("http://raw.github.com/livereload/livereload-js/master/dist/livereload.js"))
|
||||
source_livereload_js = req.read()
|
||||
if not "http://livereload.com/protocols/official-6" in source_livereload_js:
|
||||
raise Exception("Something wrong with download!")
|
||||
except Exception, u:
|
||||
print u
|
||||
try:
|
||||
path = os.path.join(sublime.packages_path(), "LiveReload")
|
||||
local = open(os.path.join(path, "livereload.js"), "rU")
|
||||
source_livereload_js = local.read()
|
||||
except IOError, e:
|
||||
print e
|
||||
sublime.error_message("livereload.js is missing from LiveReload package install")
|
||||
|
||||
|
||||
class LiveReload(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
global LivereloadFactory
|
||||
threading.Thread.__init__(self)
|
||||
LivereloadFactory = WebSocketServer(port,version)
|
||||
LivereloadFactory.start()
|
||||
|
||||
class LiveReloadChange(sublime_plugin.EventListener):
|
||||
def __init__ (self):
|
||||
LiveReload().start()
|
||||
|
||||
def __del__(self):
|
||||
global LivereloadFactory
|
||||
LivereloadFactory.stop()
|
||||
|
||||
def on_post_save(self, view):
|
||||
global LivereloadFactory
|
||||
settings = sublime.load_settings('LiveReload.sublime-settings')
|
||||
filename = view.file_name()
|
||||
if view.file_name().find('.scss') > 0 or view.file_name().find('.sass') > 0:
|
||||
compiler = CompassThread(filename,LivereloadFactory)
|
||||
compiler.start()
|
||||
else:
|
||||
filename = os.path.normcase(filename)
|
||||
filename = os.path.split(filename)[1]
|
||||
filename = filename.replace('.scss','.css').replace('.styl','.css').replace('.less','.css')
|
||||
filename = filename.replace('.coffee','.js')
|
||||
|
||||
data = json.dumps(["refresh", {
|
||||
"path": filename,
|
||||
"apply_js_live": settings.get('apply_js_live'),
|
||||
"apply_css_live": settings.get('apply_css_live'),
|
||||
"apply_images_live": settings.get('apply_images_live')
|
||||
}])
|
||||
sublime.set_timeout(lambda: LivereloadFactory.send_all(data), int(settings.get('delay_ms')))
|
||||
sublime.set_timeout(lambda: sublime.status_message("Sent LiveReload command for file: "+filename), int(settings.get('delay_ms')))
|
||||
|
||||
|
||||
class CompassThread(threading.Thread):
|
||||
|
||||
def __init__(self, filename,LivereloadFactory):
|
||||
self.dirname = os.path.dirname(filename)
|
||||
self.filename = filename.replace('.scss','.css').replace('.sass','.css')
|
||||
self.LivereloadFactory = LivereloadFactory
|
||||
self.stdout = None
|
||||
self.stderr = None
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
global LivereloadFactory
|
||||
print 'compass compile ' + self.dirname
|
||||
|
||||
# autocreate config.rb for compass
|
||||
if not os.path.exists(os.path.join(self.dirname, "config.rb")):
|
||||
print "Generating config.rb"
|
||||
shutil.copy(os.path.join(sublime.packages_path(), "LiveReload","assets","config.rb"), self.dirname)
|
||||
|
||||
# compass compile
|
||||
p = subprocess.Popen(['compass compile ' + self.dirname.replace('\\','/')],shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
|
||||
if p.stdout.read() :
|
||||
self.LivereloadFactory.send_all(json.dumps(["refresh", {
|
||||
"path": self.filename.replace('\\','/'),
|
||||
"apply_js_live": True,
|
||||
"apply_css_live": True,
|
||||
"apply_images_live": True
|
||||
}]))
|
||||
|
||||
|
||||
class WebSocketServer:
|
||||
"""
|
||||
Handle the Server, bind and accept new connections, open and close
|
||||
clients connections.
|
||||
"""
|
||||
|
||||
def __init__(self, port, version):
|
||||
self.clients = []
|
||||
self.port = port
|
||||
self.version = version
|
||||
self.s = None
|
||||
|
||||
def stop(self):
|
||||
[client.close() for client in self.clients]
|
||||
l = threading.Lock()
|
||||
l.acquire()
|
||||
l.release()
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start the server.
|
||||
"""
|
||||
try:
|
||||
self.s = socket.socket()
|
||||
self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.s.bind(('', self.port))
|
||||
self.s.listen(1)
|
||||
except Exception, e:
|
||||
self.stop()
|
||||
|
||||
try:
|
||||
while 1:
|
||||
conn, addr = self.s.accept()
|
||||
newClient = WebSocketClient(conn, addr, self)
|
||||
self.clients.append(newClient)
|
||||
newClient.start()
|
||||
except Exception, e:
|
||||
self.stop()
|
||||
|
||||
def send_all(self, data):
|
||||
"""
|
||||
Send a message to all the currenly connected clients.
|
||||
"""
|
||||
|
||||
[client.send(data) for client in self.clients]
|
||||
|
||||
def remove(self, client):
|
||||
"""
|
||||
Remove a client from the connected list.
|
||||
"""
|
||||
try:
|
||||
l = threading.Lock()
|
||||
l.acquire()
|
||||
self.clients.remove(client)
|
||||
l.release()
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
|
||||
class WebSocketClient(threading.Thread):
|
||||
"""
|
||||
A single connection (client) of the program
|
||||
"""
|
||||
|
||||
# Handshaking, create the WebSocket connection
|
||||
server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r
|
||||
Upgrade: websocket\r
|
||||
Connection: Upgrade\r
|
||||
Sec-WebSocket-Accept: %s\r
|
||||
"""
|
||||
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
def __init__(self, sock, addr, server):
|
||||
threading.Thread.__init__(self)
|
||||
self.s = sock
|
||||
self.addr = addr
|
||||
self.server = server
|
||||
|
||||
def run(self):
|
||||
|
||||
wsh = WSRequestHandler(self.s, self.addr, False)
|
||||
h = self.headers = wsh.headers
|
||||
ver = h.get('Sec-WebSocket-Version')
|
||||
if ver:
|
||||
# HyBi/IETF version of the protocol
|
||||
|
||||
# HyBi-07 report version 7
|
||||
# HyBi-08 - HyBi-12 report version 8
|
||||
# HyBi-13 reports version 13
|
||||
if ver in ['7', '8', '13']:
|
||||
self.version = "hybi-%02d" % int(ver)
|
||||
else:
|
||||
raise Exception('Unsupported protocol version %s' % ver)
|
||||
|
||||
key = h['Sec-WebSocket-Key']
|
||||
print key
|
||||
# Generate the hash value for the accept header
|
||||
accept = b64encode(sha1(key + self.GUID).digest())
|
||||
|
||||
response = self.server_handshake_hybi % accept
|
||||
response += "\r\n"
|
||||
print response
|
||||
self.s.send(response.encode())
|
||||
self.new_client()
|
||||
|
||||
# Receive and handle data
|
||||
while 1:
|
||||
try:
|
||||
data = self.s.recv(1024)
|
||||
except Exception, e:
|
||||
break
|
||||
if not data: break
|
||||
dec = WebSocketClient.decode_hybi(data)
|
||||
if dec["opcode"] == 8:
|
||||
self.close()
|
||||
else:
|
||||
self.onreceive(dec)
|
||||
|
||||
# Close the client connection
|
||||
self.close()
|
||||
|
||||
@staticmethod
|
||||
def unmask(buf, f):
|
||||
pstart = f['hlen'] + 4
|
||||
pend = pstart + f['length']
|
||||
# Slower fallback
|
||||
data = array.array('B')
|
||||
mask = s2a(f['mask'])
|
||||
data.fromstring(buf[pstart:pend])
|
||||
for i in range(len(data)):
|
||||
data[i] ^= mask[i % 4]
|
||||
return data.tostring()
|
||||
|
||||
@staticmethod
|
||||
def encode_hybi(buf, opcode, base64=False):
|
||||
""" Encode a HyBi style WebSocket frame.
|
||||
Optional opcode:
|
||||
0x0 - continuation
|
||||
0x1 - text frame (base64 encode buf)
|
||||
0x2 - binary frame (use raw buf)
|
||||
0x8 - connection close
|
||||
0x9 - ping
|
||||
0xA - pong
|
||||
"""
|
||||
if base64:
|
||||
buf = b64encode(buf)
|
||||
|
||||
b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
|
||||
payload_len = len(buf)
|
||||
if payload_len <= 125:
|
||||
header = pack('>BB', b1, payload_len)
|
||||
elif payload_len > 125 and payload_len < 65536:
|
||||
header = pack('>BBH', b1, 126, payload_len)
|
||||
elif payload_len >= 65536:
|
||||
header = pack('>BBQ', b1, 127, payload_len)
|
||||
|
||||
print("Encoded: %s" % repr(header + buf))
|
||||
|
||||
return header + buf, len(header), 0
|
||||
|
||||
@staticmethod
|
||||
def decode_hybi(buf, base64=False):
|
||||
""" Decode HyBi style WebSocket packets.
|
||||
Returns:
|
||||
{'fin' : 0_or_1,
|
||||
'opcode' : number,
|
||||
'mask' : 32_bit_number,
|
||||
'hlen' : header_bytes_number,
|
||||
'length' : payload_bytes_number,
|
||||
'payload' : decoded_buffer,
|
||||
'left' : bytes_left_number,
|
||||
'close_code' : number,
|
||||
'close_reason' : string}
|
||||
"""
|
||||
|
||||
f = {'fin' : 0,
|
||||
'opcode' : 0,
|
||||
'mask' : 0,
|
||||
'hlen' : 2,
|
||||
'length' : 0,
|
||||
'payload' : None,
|
||||
'left' : 0,
|
||||
'close_code' : None,
|
||||
'close_reason' : None}
|
||||
|
||||
blen = len(buf)
|
||||
f['left'] = blen
|
||||
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
|
||||
b1, b2 = unpack_from(">BB", buf)
|
||||
f['opcode'] = b1 & 0x0f
|
||||
f['fin'] = (b1 & 0x80) >> 7
|
||||
has_mask = (b2 & 0x80) >> 7
|
||||
|
||||
f['length'] = b2 & 0x7f
|
||||
|
||||
if f['length'] == 126:
|
||||
f['hlen'] = 4
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
(f['length'],) = unpack_from('>xxH', buf)
|
||||
elif f['length'] == 127:
|
||||
f['hlen'] = 10
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
(f['length'],) = unpack_from('>xxQ', buf)
|
||||
|
||||
full_len = f['hlen'] + has_mask * 4 + f['length']
|
||||
|
||||
if blen < full_len: # Incomplete frame
|
||||
return f # Incomplete frame header
|
||||
|
||||
# Number of bytes that are part of the next frame(s)
|
||||
f['left'] = blen - full_len
|
||||
|
||||
# Process 1 frame
|
||||
if has_mask:
|
||||
# unmask payload
|
||||
f['mask'] = buf[f['hlen']:f['hlen']+4]
|
||||
f['payload'] = WebSocketClient.unmask(buf, f)
|
||||
else:
|
||||
print("Unmasked frame: %s" % repr(buf))
|
||||
f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len]
|
||||
|
||||
if base64 and f['opcode'] in [1, 2]:
|
||||
try:
|
||||
f['payload'] = b64decode(f['payload'])
|
||||
except:
|
||||
print("Exception while b64decoding buffer: %s" %
|
||||
repr(buf))
|
||||
raise
|
||||
|
||||
if f['opcode'] == 0x08:
|
||||
if f['length'] >= 2:
|
||||
f['close_code'] = unpack_from(">H", f['payload'])
|
||||
if f['length'] > 3:
|
||||
f['close_reason'] = f['payload'][2:]
|
||||
|
||||
return f
|
||||
def close(self):
|
||||
"""
|
||||
Close this connection
|
||||
"""
|
||||
self.server.remove(self)
|
||||
self.s.close()
|
||||
|
||||
def send(self, msg):
|
||||
"""
|
||||
Send a message to this client
|
||||
"""
|
||||
msg = WebSocketClient.encode_hybi(msg, 0x1, False)
|
||||
self.s.send(msg[0])
|
||||
|
||||
def onreceive(self, data):
|
||||
"""
|
||||
Event called when a message is received from this client
|
||||
"""
|
||||
try:
|
||||
print data
|
||||
if "payload" in data:
|
||||
print "payload true"
|
||||
if "hello" in data.get("payload"):
|
||||
sublime.set_timeout(lambda: sublime.status_message("New LiveReload v2 client connected"), 100)
|
||||
self.send('{"command":"hello","protocols":["http://livereload.com/protocols/connection-check-1","http://livereload.com/protocols/official-6"]}')
|
||||
else:
|
||||
sublime.set_timeout(lambda: sublime.status_message("New LiveReload v1 client connected"), 100)
|
||||
self.send("!!ver:" + str(self.server.version))
|
||||
except Exception, e:
|
||||
print e
|
||||
|
||||
|
||||
def new_client(self):
|
||||
"""
|
||||
Event called when handshake is compleated
|
||||
"""
|
||||
#self.send("!!ver:"+str(self.server.version))
|
||||
|
||||
def _clean(self, msg):
|
||||
"""
|
||||
Remove special chars used for the transmission
|
||||
"""
|
||||
msg = msg.replace(b'\x00', b'', 1)
|
||||
msg = msg.replace(b'\xff', b'', 1)
|
||||
return msg
|
||||
|
||||
|
||||
# HTTP handler with WebSocket upgrade support
|
||||
class WSRequestHandler(SimpleHTTPRequestHandler):
|
||||
def __init__(self, req, addr, only_upgrade=True):
|
||||
self.only_upgrade = only_upgrade # only allow upgrades
|
||||
SimpleHTTPRequestHandler.__init__(self, req, addr, object())
|
||||
|
||||
def do_GET(self):
|
||||
if (self.headers.get('upgrade') and
|
||||
self.headers.get('upgrade').lower() == 'websocket'):
|
||||
|
||||
if (self.headers.get('sec-websocket-key1') or
|
||||
self.headers.get('websocket-key1')):
|
||||
# For Hixie-76 read out the key hash
|
||||
self.headers.__setitem__('key3', self.rfile.read(8))
|
||||
|
||||
# Just indicate that an WebSocket upgrade is needed
|
||||
self.last_code = 101
|
||||
self.last_message = "101 Switching Protocols"
|
||||
elif self.only_upgrade:
|
||||
# Normal web request responses are disabled
|
||||
self.last_code = 405
|
||||
self.last_message = "405 Method Not Allowed"
|
||||
else:
|
||||
#Self injecting plugin
|
||||
if "livereload.js" in self.path:
|
||||
self.send_response(200, 'OK')
|
||||
self.send_header('Content-type', 'text/javascript')
|
||||
self.send_header("Content-Length", len(source_livereload_js))
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes(source_livereload_js))
|
||||
return
|
||||
else:
|
||||
#Disable other requests
|
||||
self.send_response(405, 'Method Not Allowed')
|
||||
self.send_header('Content-type', 'text/plain')
|
||||
self.send_header("Content-Length", len("Method Not Allowed"))
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes("Method Not Allowed"))
|
||||
return
|
||||
|
||||
def send_response(self, code, message=None):
|
||||
# Save the status code
|
||||
self.last_code = code
|
||||
SimpleHTTPRequestHandler.send_response(self, code, message)
|
||||
|
||||
def log_message(self, f, *args):
|
||||
# Save instead of printing
|
||||
self.last_message = f % args
|
Reference in New Issue
Block a user