Skip to content

Commit

Permalink
Alhpa release. It works.
Browse files Browse the repository at this point in the history
  • Loading branch information
krishnangovindraj committed Jun 18, 2014
1 parent 48cc230 commit 6f8dbef
Show file tree
Hide file tree
Showing 18 changed files with 766 additions and 238 deletions.
68 changes: 68 additions & 0 deletions CallbackDBI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import MySQLdb
from WWMException import WWMException
from Config import Config,Constants

import time, Queue

class CallbackDBI:
def __init__(self):
self.dbi = None
self.cursor = None
self.connectedAt = 0
self.connect() #Hack
self.waitTimeout = Config.CallbackDBI_queryTimeout
self.queryQueue = Queue.Queue(50)

def connect(self):
try:
print "CallbackDBI CONNECTING TO DB!"
self.dbi = MySQLdb.connect(Config.mysql_host,Config.mysql_user,Config.mysql_password,Config.mysql_database)
self.cursor = self.dbi.cursor(MySQLdb.cursors.DictCursor)
#All hail autocommit!
self.dbi.autocommit(True)

self.connectedAt = time.time()
self.inUse =0

except MySQLdb.Error, e:
self.close()
raise WWMException("MySQL could not connect: %s" %e)

def addToQueue(self, queryTup):
self.queryQueue.put(queryTup)

def processQueue(self):
while self.queryQueue.qsize()>0:
q = self.queryQueue.get()
queryStr, args = q
self.executeQuery(queryStr,args)

def execute(self,queryStr, args):
waitingFor = 0
while self.inUse:
if waitingFor>=self.waitTimeout:
self.addToQueue((queryStr,args)) #the
return

time.sleep(1)
waitingFor+= 1

self.inUse = 1

This comment has been minimized.

Copy link
@akshayah3

akshayah3 Jun 19, 2014

I think this should be declared at the beginning of the method as the while loop fails for the first entry to the queue as inUse is set to 0

This comment has been minimized.

Copy link
@krishnangovindraj

krishnangovindraj Jun 19, 2014

Author Owner

I'm pretty sure this is in place.
The reason i'm doing that while loop is to avoid 2 threads using the DBConnection at the same time. The while loop is to wait till the connection is free. If the wait times out ( ie, Another insert is taking too long ), We'll add it to the process queue to process when the connection is free. ( Which happens two lines later, just after the current query is done excecuting. )

Line 41-48: Wait till the connection is free, Add to queue if we timeout

Line
50: Cursor is free, We set the flag to show we're using it.
51: execute the query
52: Execute any queries that may have queued up while the current query was executing.

So it's definitely in the right place.

I should probably explain what a callback DBI is. Will do that once i get down to documenting the code.

This comment has been minimized.

Copy link
@akshayah3

akshayah3 Jun 19, 2014

Oh! I just had a glance at your code, will take a deeper look. Before you document i think you should follow pep 8 conventions for your code as it will be more readable. Should I send a PR with the necessary changes?

This comment has been minimized.

Copy link
@krishnangovindraj

krishnangovindraj Jun 20, 2014

Author Owner

I honestly have no idea how python is written. I've done a weird mix of coding styles and any notion of convention went down the drain -_-
Your changes are to adapt to the convention? You have to be careful with the flags though, If you changed the order, The whole app will crash ( Been there, That's why i wrote a blocking cursor )

Aand let's stick to tabs please? Spaces are retarded.

This comment has been minimized.

Copy link
@akshayah3

akshayah3 via email Jun 20, 2014

self.executeQuery(queryStr,args)
self.processQueue() #Whichever query was added

self.inUse = 0

def executeQuery(self,queryStr,args): #Yes, It needs to be this complicated.
self.cursor.execute(queryStr,args)


def close(self):
print "DBI.close WAS CALLED!"
#Closing/Querying a closed connection crashes python. So be careful
if self.dbi!= None and self.dbi.open!=0:
print "CLOSING CONN TO DB!"
self.dbi.close()
self.dbi = None
self.dbiCursor = None
self.connectedAt = 0
35 changes: 21 additions & 14 deletions Config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,33 @@
class Config:
keepAlive = True
sendReceipts = False
waitForReceipt = False
#waitForReceipt = False

session_opts = {'session.type': 'file', 'session.cookie_expires': True,'session.file_dir': 'beaker\\file_dir','session.data_dir': 'beaker\\data_dir'}

#CONNECTION
conRetry_interval= 10 #Retry every 10 seconds
conRetry_maxRetry = 12 #Retry 12 times

#CallbackDBI
CallbackDBI_queryTimeout = 3

#Sender
#Sender_sendtimeout= 90 #1.5 minutes
Sender_resendInterval = 5#150

#outbox
outbox_retryInterval = 5#90 #1.5 minutes
#DBI
DBI_getCursorTimeout = 20 #20 seconds is a looot
#mysql connection
mysql_host= "localhost"
mysql_user= "root"
mysql_password= ""
mysql_database= "webwhatsapp"



'''
def __init__():
#Nothing
'''
class Constants:
#DBI
DBI_FETCHALL = 1
Expand All @@ -28,9 +43,7 @@ class Constants:
OUTBOX_PENDING = 1
OUTBOX_SENDING = 2
OUTBOX_SENT = 3

OUTBOX_TIMEOUT = 90 #1.5 minutes


#AUTH
AUTHSTATUS_IDLE = 1
AUTHSTATUS_TRYING = 2
Expand All @@ -41,9 +54,3 @@ class Constants:
INSTANCESTATUS_INITED = 0
INSTANCESTATUS_RUNNING = 1
INSTANCESTATUS_WRAPPEDUP = 2


'''
def __init():
#Nothing
'''
27 changes: 15 additions & 12 deletions Core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from Session import Session
from Sender import Sender
from Listener import Listener
from DBInterface import DBI
from DBInterface import DBI, BlockingDBICursor
from CallbackDBI import CallbackDBI

import MySQLdb,time, os, hashlib
class WWMCore:
Expand All @@ -27,7 +28,9 @@ def __init__(self):
self.httpRequestHandler = None
self.instanceId = None
self.status = Constants.INSTANCESTATUS_INITED
self.dbi = DBI() #Make an instance since it's a core part of the webapp. It'll control itself

self.dbi = DBI() #Use this for HTTP requests only. For every other case, Go ahead and create a different connection for now
self.callbackDBI = CallbackDBI() #Extra care for preventing interference
#self.dbiCursor = None
self.yowsupStarted = 0

Expand Down Expand Up @@ -70,14 +73,15 @@ def getDBICursor(self): #returns dbi and cursor
#This method is called from session
def initSession(self, phone, AESKey):
print "Core.initSession called"
if self.yowsupStarted==0 and (self.session == None or self.session.authStatus == Constants.AUTHSTATUS_IDLE):
if self.session == None or self.session.authStatus == Constants.AUTHSTATUS_IDLE: #self.yowsupStarted==0 and
self.yowsupStarted = 1
if self.session == None:
if self.session == None:

This comment has been minimized.

Copy link
@akshayah3

akshayah3 Jun 19, 2014

I think there is no need for this check?

This comment has been minimized.

Copy link
@krishnangovindraj

krishnangovindraj Jun 19, 2014

Author Owner

No, But there's no need to execute the code within it twice.
This code is specifically for a connection lost midway, You don't need to get the data a second time. And you especially don't want to register the same handler multiple times.

So i think this condition is needed

(I'll admit the yowsupStarted flag is very badly used )

self.session = Session(self, phone, AESKey)
self.session.getAuthData()
self.signalsInterface.registerListener("disconnected", self.onDisconnected)
self.session.login()

else:
print "\nPretty sure yowsup is already started."

def authCallback(self,isSuccess): #Called manually from Session
if isSuccess:
Expand All @@ -92,15 +96,16 @@ def authCallback(self,isSuccess): #Called manually from Session
self.yowsupStarted= 0

def onDisconnected(self, reason):

print "Core.onDisconnected called"

if self.status==Constants.INSTANCESTATUS_WRAPPEDUP:
print "Core.onDisconnected: Disconnected and wrapping up"
return #And die


self.yowsupStarted = 0
self.session.updateAuthStatus(Constants.AUTHSTATUS_IDLE)
self.session.authStatus = Constants.AUTHSTATUS_IDLE
#self.session.updateAuthStatus(Constants.AUTHSTATUS_IDLE)


if self.yowsupRanOnce==0:
Expand Down Expand Up @@ -133,7 +138,7 @@ def getStatus(self):
#Create a dictionary and return it
status = {}

status["Core"] = [("yowsupStarted", self.yowsupStarted)]
status["Core"] = [("yowsupStarted", self.yowsupStarted), ("instanceStatus", self.status)]

if self.session==None:
status["Session"]=[("inited","No")]
Expand All @@ -150,10 +155,8 @@ def wrapUp(self, reason):
#Write some code to wrap up
self.status = Constants.INSTANCESTATUS_WRAPPEDUP

dbiCursor = self.dbi.getCursor()
dbiCursor.execute("UPDATE pythonInstances set status=%s WHERE instanceId=%s ", (self.status, self.instanceId))
self.dbi.commit()
self.dbi.done()
with BlockingDBICursor(self.dbi) as dbiCursor:
dbiCursor.execute("UPDATE pythonInstances set status=%s WHERE instanceId=%s ", (self.status, self.instanceId))

self.connectionManager.disconnect(reason)
self.yowsupStarted = 0
Expand Down
8 changes: 2 additions & 6 deletions Listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,10 @@ def onMessageReceived(self, messageId, jid, messageContent, timestamp, wantsRece
sender = re.match('([0-9]*)@s\.whatsapp\.net',jid).group(1)

try:
dbiCursor = self.core.dbi.getCursor()#self.listenerDBI.getCursor()
#self.core.dbiCursor.execute("INSERT INTO inbox (messageId, recipient, sender, message, tstamp, seen ) VALUES( %s, %s, %s, %s, %s, %s )", ( messageId, self.core.session.phone, sender, messageContent, timestamp, 0))
dbiCursor.execute("REPLACE INTO inbox (messageId, recipient, sender, message, tstamp, seen ) VALUES( %s, %s, %s, %s, %s, %s )", ( messageId, self.core.session.phone, sender, messageContent, timestamp, 0))
self.core.dbi.commit()#self.listenerDBI.commit()
#oh the satisfaction of writing one line to query!
self.core.callbackDBI.execute("REPLACE INTO inbox (messageId, recipient, sender, message, tstamp, seen ) VALUES( %s, %s, %s, %s, %s, %s )", ( messageId, self.core.session.phone, sender, messageContent, timestamp, 0))
except MySQLdb.Error, e:
print "Exception onMessageReceived: %s"%e
finally:
self.core.dbi.done()#self.listenerDBI.done()

if wantsReceipt and Config.sendReceipts:
self.methodsInterface.call("message_ack", (jid, messageId))
Expand Down
18 changes: 17 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
README:
Aim: Make a web client for whatsapp. Do whatever you want as long as that's achieved. And rewrite this readme when you're done

NOTE: You need Yowsup copied to the same directory. ie .\Yowsup has to contain all the Yowsup library files

NOTE:
You need Yowsup Library copied to the same directory. ie .\Yowsup has to contain all the Yowsup library files

OTHER DEPENDENCIES:
MySQLdb ( If you can rewrite it to have a server independent solution, Please do :) )

Usage:
Import the webwhatsapp.sql file into a database name webwhatasapp ( or reconfigure your config.py mysql_ fields accordingly )
adduser by running >py adduser.py through the commandline ( Web interface adding users is an easy to do )
Launch an instance using >py wsgi.py
visit http://host:portnumber
Login using the password you specified.
That should be it

Repo: https://github.com/krishnangovindraj/webwhatsapp
Contact: [email protected]
Loading

0 comments on commit 6f8dbef

Please sign in to comment.