From 430617045da709023a9f2cc9f4bffdecd3c3728a Mon Sep 17 00:00:00 2001 From: Jack Zielke Date: Tue, 26 May 2009 05:16:33 +0000 Subject: version 5 - no journal, can not cancel messages --- diff --git a/NEWS b/NEWS index c64a9df..21c5b6f 100644 --- a/NEWS +++ b/NEWS @@ -3,3 +3,4 @@ September 20 2008 - version 1 - no journal, no messaging September 22 2008 - version 2 - no journal, can not send messages, gui update September 25 2008 - version 3 - no journal, can not send messages, gui update September 28 2008 - version 4 - no journal, no message queue, gui update +October 5 2008 - version 5 - no journal, can not cancel messages diff --git a/TODO b/TODO index a23850b..ca8ab62 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,5 @@ save in journal -send messages multi language cleanup timers on disconnect diff --git a/activity/activity.info b/activity/activity.info index 7c99a2c..34c38f0 100644 --- a/activity/activity.info +++ b/activity/activity.info @@ -1,6 +1,6 @@ [Activity] name = APRS-XO -activity_version= 4 +activity_version= 5 service_name = org.laptop.APRSXO icon = activity-aprs-xo exec = sugar-activity aprs.APRSActivity diff --git a/aprs.py b/aprs.py index f60ad04..b037c5b 100755 --- a/aprs.py +++ b/aprs.py @@ -40,6 +40,8 @@ RECV_BUFFER = 4096 MAXLINES = 50 MAXRETRIES = 15 +MAX_MSG_QUEUE = 10 +MAX_PER_CALL_QUEUE = 2 bundle = ActivityBundle(activity.get_bundle_path()) VERSION = bundle.get_activity_version() @@ -61,6 +63,10 @@ class APRSActivity(activity.Activity): self.help = True self.messagebox = False self.sequence = random.randrange(0, 7000) + self.queue_list = {} + self.message_list = {} + self.seen_bulletins = {} + self.message_marks = {} titlefont = pango.FontDescription('Sans bold 8') mediumfont = pango.FontDescription('Sans 6.5') @@ -168,7 +174,7 @@ class APRSActivity(activity.Activity): bottomidentbox = gtk.HBox(False, 10) calllabel1 = gtk.Label("Callsign: ") - calllabel1.set_alignment(0, 0.5) + calllabel1.set_alignment(1, 0.5) bottomidentbox.pack_start(calllabel1, False, False, 0) calllabel1.show() @@ -180,7 +186,7 @@ class APRSActivity(activity.Activity): self.calltext.show() passlabel1 = gtk.Label("Password: ") - passlabel1.set_alignment(0, 0.5) + passlabel1.set_alignment(1, 0.5) bottomidentbox.pack_start(passlabel1, False, False, 0) passlabel1.show() @@ -268,7 +274,6 @@ class APRSActivity(activity.Activity): self.latDDtext.set_max_length(2) self.latDDtext.set_width_chars(4) self.latDDtext.set_text("DD") - self.latDDtext.select_region(1,2) self.latDDtext.connect("changed", self.disable_beacon) latpositbox.pack_start(self.latDDtext, False, False, 0) self.latDDtext.show() @@ -277,7 +282,6 @@ class APRSActivity(activity.Activity): self.latMMtext.set_max_length(2) self.latMMtext.set_width_chars(3) self.latMMtext.set_text("MM") - self.latMMtext.select_region(1,2) self.latMMtext.connect("changed", self.disable_beacon) latpositbox.pack_start(self.latMMtext, False, False, 0) self.latMMtext.show() @@ -291,7 +295,6 @@ class APRSActivity(activity.Activity): self.latmmtext.set_max_length(2) self.latmmtext.set_width_chars(3) self.latmmtext.set_text("mm") - self.latmmtext.select_region(1,2) self.latmmtext.connect("changed", self.disable_beacon) latpositbox.pack_start(self.latmmtext, False, False, 0) self.latmmtext.show() @@ -316,7 +319,6 @@ class APRSActivity(activity.Activity): self.lonDDDtext.set_max_length(3) self.lonDDDtext.set_width_chars(4) self.lonDDDtext.set_text("DDD") - self.lonDDDtext.select_region(1,2) self.lonDDDtext.connect("changed", self.disable_beacon) lonpositbox.pack_start(self.lonDDDtext, False, False, 0) self.lonDDDtext.show() @@ -325,7 +327,6 @@ class APRSActivity(activity.Activity): self.lonMMtext.set_max_length(2) self.lonMMtext.set_width_chars(3) self.lonMMtext.set_text("MM") - self.lonMMtext.select_region(1,2) self.lonMMtext.connect("changed", self.disable_beacon) lonpositbox.pack_start(self.lonMMtext, False, False, 0) self.lonMMtext.show() @@ -339,7 +340,6 @@ class APRSActivity(activity.Activity): self.lonmmtext.set_max_length(2) self.lonmmtext.set_width_chars(3) self.lonmmtext.set_text("mm") - self.lonmmtext.select_region(1,2) self.lonmmtext.connect("changed", self.disable_beacon) lonpositbox.pack_start(self.lonmmtext, False, False, 0) self.lonmmtext.show() @@ -433,7 +433,7 @@ class APRSActivity(activity.Activity): self.messageview.set_justification(gtk.JUSTIFY_LEFT) self.messageview.modify_font(smallfont) - self.messagebuffer.set_text("Welcome to APRS-XO.\n\nThis program sends your position information to a server that will\nthen display your location on a webpage. This program requires an\nactive Internet connection to work.\n\nSelect an APRS Site\nSelecting a button will copy the URI to the clipboard.\n\nIndentifiers\nEnter your callsign and optionally an aprsd password.\n\nStation Comment\nData in the Station Comment field will appear after your location\ninformation on the website.") + self.messagebuffer.set_text("Welcome to APRS-XO.\n\nThis program sends your position information to a server that will\ndisplay your location on a webpage. This program requires an\nactive Internet connection to work.\n\nSelect an APRS Site\nSelecting a button will copy the URI to the clipboard.\n\nIndentifiers\nEnter your callsign and optionally an aprsd password.\n\nStation Comment\nData in the Station Comment field will appear after your location\ninformation on the website.") self.messagewindow = gtk.ScrolledWindow() self.messagewindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) @@ -458,7 +458,6 @@ class APRSActivity(activity.Activity): self.messagecombo.show() self.messagetext = gtk.Entry() - # TODO should this be lower with acks? self.messagetext.set_max_length(67) self.messagetext.set_width_chars(32) self.messagetext.modify_font(smallfont) @@ -512,22 +511,6 @@ class APRSActivity(activity.Activity): win.pack_start(rightwin, False, False, 0) rightwin.show() - # for quick testing - if (TESTING): - self.calltext.set_text("kg4gjy") - self.passtext.set_text("18107") - self.latDDtext.set_text("35") - self.latMMtext.set_text("7") - self.latmmtext.set_text("42") - self.latcombo.set_active(0) - self.lonDDDtext.set_text("85") - self.lonMMtext.set_text("6") - self.lonmmtext.set_text("93") - self.loncombo.set_active(0) -# self.stationtext.set_text("") - self.ziptext.set_text("") - self.beaconbutton.set_active(True) - win.show() self.calltext.grab_focus() @@ -630,31 +613,32 @@ class APRSActivity(activity.Activity): recv_data = sock.recv(RECV_BUFFER) except: self.status_write("Server closed connection.\n") + self.sock = None return False if not recv_data: self.status_write("Server closed connection.\n") + self.sock = None return False else: - # find uri's http or else www - webaddy = recv_data.lower().find("http") - if (webaddy != -1): - recv_data = ("%s\n%s" % (recv_data[:webaddy], recv_data[webaddy:])) - else: - webaddy = recv_data.lower().find("www") + # sometimes more than one packet comes in + for packet in recv_data.split("\n"): + if (len(packet) < 2): + break + # find uri's http or else www + webaddy = packet.lower().find("http") if (webaddy != -1): - recv_data = ("%s\n%s" % (recv_data[:webaddy], recv_data[webaddy:])) - if (recv_data[0] == "#"): - self.status_write("%s\n\n" % recv_data) - else: - cuthere = recv_data.find(":") - self.status_write("%s\n" % recv_data[:cuthere+1]) - - # some incoming lines end in \n and some do not - if (recv_data[-1:] == "\n"): - self.status_write("%s\n" % recv_data[cuthere+1:]) + packet = ("%s\n%s" % (packet[:webaddy], packet[webaddy:])) + else: + webaddy = packet.lower().find("www") + if (webaddy != -1): + packet = ("%s\n%s" % (packet[:webaddy], packet[webaddy:])) + if (packet[0] == "#"): + self.status_write("%s\n\n" % packet) else: - self.status_write("%s\n\n" % recv_data[cuthere+1:]) - self.msg_check(recv_data) + cuthere = packet.find(":") + self.status_write("%s\n" % packet[:cuthere+1]) + self.status_write("%s\n\n" % packet[cuthere+1:]) + self.msg_check(packet) return True def send_beacon(self): @@ -678,8 +662,14 @@ class APRSActivity(activity.Activity): self.status_write("%s\n\n" % msg) return True - def send_ack(self, msg): - self.send_data(msg) + def send_ack(self, tocall, sequence): + # should check for duplicate messages in the next 30 seconds and drop them + # aprsd does this for us - but should do it here as well + if (tocall in self.sent_acks): + currentack = self.sent_acks[tocall] + if (currentack == sequence): + ackmessage = ":%s:ack%s" % (tocall, sequence) + self.send_data(ackmessage) return False def status_write(self, text): @@ -905,92 +895,63 @@ class APRSActivity(activity.Activity): fromcall = data[:data.find(">")].upper() isbulletin = self.bulletin_check(strippedtocall) if (isbulletin): - message = data[firstcheck+12:-1] - # TODO add BLN, etc to tocall dropdown list? -# self.add_callsign(fromcall, False) - # for now, display them every time - self.message_write("%s %s.%s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, strippedtocall, message)) + message = data[firstcheck+12:] + self.add_callsign(fromcall, False) + bln_id = "%s-%s" % (fromcall, strippedtocall) + if (not bln_id in self.seen_bulletins): + self.seen_bulletins[bln_id] = 1 + self.message_write("%s %s:%s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, strippedtocall, message)) else: if (strippedtocall == self.calltext.get_text()): self.add_callsign(fromcall, False) if (thirdcheck != -1): sequence_end = data.find("}") if (sequence_end == -1): - sequence = data[thirdcheck+1:-1] + sequence = data[thirdcheck + 1:] else: - sequence = data[thirdcheck+1:sequence_end] - replyack = data[sequence_end+1:-1] - if (replyack[0:3] == "ack"): - self.recv_acks["%s-%s" % (fromcall, replyack[3:])] = 1 + sequence = data[thirdcheck + 1:sequence_end] + replyack = data[sequence_end + 1:] + if (len(replyack) > 1): + ackid = "%s-%s" % (fromcall, replyack) + if (ackid in self.recv_acks): + if (self.recv_acks[ackid] == 0): + count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid]) + count_end = count_start.forward_search(">", gtk.TEXT_SEARCH_TEXT_ONLY) + self.messagebuffer.delete(count_start, count_end[0]) + self.send_msg_queue(fromcall) + self.recv_acks[ackid] = 1 message = data[firstcheck+12:thirdcheck] - ackmessage = ":%s:ack%s" % (fromcall, sequence) +# ackmessage = ":%s:ack%s" % (fromcall, sequence) + ackmessage = ":%s:ack%s" % (fromcall, data[thirdcheck + 1:]) self.send_data(ackmessage) id = "%s-%s" % (fromcall, sequence) if (id in self.sent_acks): - self.timers.append(gobject.timeout_add(30 * 1000, self.send_ack, ackmessage)) - self.timers.append(gobject.timeout_add(60 * 1000, self.send_ack, ackmessage)) - self.timers.append(gobject.timeout_add(120 * 1000, self.send_ack, ackmessage)) + self.timers.append(gobject.timeout_add(30 * 1000, self.send_ack, tocall, sequence)) + self.timers.append(gobject.timeout_add(60 * 1000, self.send_ack, tocall, sequence)) + self.timers.append(gobject.timeout_add(120 * 1000, self.send_ack, tocall, sequence)) else: + # TODO beep? self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message)) self.sent_acks[id] = time.time() # to help a cleanup thread later self.sent_acks[fromcall] = sequence # to help with reply acks later else: - message = data[firstcheck+12:-1] - if (message[0:3] == "ack"): - self.recv_acks["%s-%s" % (fromcall, message[3:])] = 1 - else: - self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message)) - - def msg_check_old(self, data): - # a quick "does it look like a message?" check - # this check will only find messages that require acks - firstcheck = data.find("::") - secondcheck = data.find("{") - # FIX this will ignore BLN, ALL, QST, CQ, etc with { in message - # FIXME actually a BLN with a { freezes the status window.. - if (firstcheck != -1 and secondcheck != -1): - # great, it looks kinda like a message, is it to me? - tocall = data[firstcheck+2:firstcheck+11].upper() - strippedtocall = tocall.strip() - if (strippedtocall == self.calltext.get_text()): - sequence_end = data.find("}") - if (sequence_end == -1): - sequence = data[secondcheck+1:-1] - else: - sequence = data[secondcheck+1:sequence_end] - fromcall = data[:data.find(">")].upper() - message = data[firstcheck+12:secondcheck] - self.add_callsign(fromcall, False) - ackmessage = ":%s:ack%s" % (fromcall, sequence) - self.send_data(ackmessage) - id = "%s-%s" % (fromcall, sequence) - if (id in self.sent_acks): - self.timers.append(gobject.timeout_add(30 * 1000, self.send_ack, ackmessage)) - self.timers.append(gobject.timeout_add(60 * 1000, self.send_ack, ackmessage)) - self.timers.append(gobject.timeout_add(120 * 1000, self.send_ack, ackmessage)) - else: - self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message)) - - self.sent_acks[id] = time.time() # to help a cleanup thread later - self.sent_acks[fromcall] = sequence # to help with reply acks later - else: - # is it a message that does not get acked? - if (firstcheck != -1): - tocall = data[firstcheck+2:firstcheck+11].upper() - strippedtocall = tocall.strip() - fromcall = data[:data.find(">")].upper() - message = data[firstcheck+12:-1] - if (strippedtocall == "ALL" or strippedtocall == "QST" or strippedtocall == "CQ" or strippedtocall[:3] == "BLN" or strippedtocall[:3] == "NWS"): - # for now, display them every time - self.message_write("%s %s:%s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, strippedtocall, message)) - else: - # is it an ack (to me)? - if (strippedtocall == self.calltext.get_text()): + message = data[firstcheck+12:] if (message[0:3] == "ack"): - # record the message as acked to stop repeating it - self.recv_acks["%s-%s" % (fromcall, message[3:])] = 1 + sequence_end = message.find("}") + if (sequence_end == -1): + sequence = message[3:] + else: + sequence = message[3:sequence_end] + ackid = "%s-%s" % (fromcall, sequence) + if (ackid in self.recv_acks): + if (self.recv_acks[ackid] == 0): + count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid]) + count_end = count_start.forward_search(">", gtk.TEXT_SEARCH_TEXT_ONLY) + self.messagebuffer.delete(count_start, count_end[0]) + self.send_msg_queue(fromcall) + self.recv_acks[ackid] = 1 else: - # no ack and addressed to me. + # TODO beep? self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message)) def disable_beacon(self, widget, data=None): @@ -1014,9 +975,14 @@ class APRSActivity(activity.Activity): return True def send_message(self, widget, data=None): + sendnow = False + tocall = self.messagedest.get_text().upper() message = self.messagetext.get_text() + if (message == ""): + return False + isbulletin = self.bulletin_check(tocall) # check for illegal characters before clearing message @@ -1029,8 +995,6 @@ class APRSActivity(activity.Activity): self.message_write("Messages can not contain the following characters: | ~ {") return False - self.messagetext.set_text("") - # add callsign to list if just entered if (self.messagecombo.get_active() == -1): self.add_callsign(tocall, True) @@ -1043,27 +1007,23 @@ class APRSActivity(activity.Activity): sequence = "%s" % self.b90() # TODO - # check to see if there is an unack-ed message to same callsign - # too many to the same call? give an error instead - # too many in general? give an error instead - # print message + QUEUED add to the queue... # cancel message option - menu popup - # TODO save location for repeat/ack counter, queued and cancelled locations too - # put the message in the message window -# self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), self.calltext.get_text(), message)) - self.message_write("%s To:%s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), tocall, message)) + # is there a queue for this callsign? + if (tocall not in self.queue_list): + sendnow = True - # send the message - if (isbulletin): - self.send_data(":%s:%s" % (tocall.ljust(9), message)) - # TODO what are the correct timings for bulletins? - gobject.timeout_add(600 * 1000, self.msg_timer, tocall, message, "", 2, 600) - else: - replyack = self.replyack(tocall) - self.send_data(":%s:%s{%s}%s" % (tocall.ljust(9), message, sequence, replyack)) - self.recv_acks["%s-%s" % (tocall, sequence)] = 0 - gobject.timeout_add(7 * 1000, self.msg_timer, tocall, message, sequence, 2, 7) + # add the message to the queue + if (not self.msg_queue(tocall, sequence, message)): + if (self.sequence > 0): + self.sequence -= 1 + return False + + # clear the message text box + self.messagetext.set_text("") + + if (sendnow): + self.send_msg_queue(tocall) def b90(self): if (self.sequence > 8099): @@ -1075,15 +1035,35 @@ class APRSActivity(activity.Activity): return output def msg_timer(self, tocall, message, sequence, count, delay): - if (self.recv_acks["%s-%s" % (tocall, sequence)] == 1): + id = "%s-%s" % (tocall, sequence) + if (self.recv_acks[id] == 1): return False else: replyack = self.replyack(tocall) self.send_data(":%s:%s{%s}%s" % (tocall.ljust(9), message, sequence, replyack)) + count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) + count_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) + count_end.forward_chars(len(str(count - 1)) + 1) + self.messagebuffer.delete(count_start, count_end) + iter = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) + self.messagebuffer.insert(iter, " %i" % count) + # start the next timer count += 1 if (count > MAXRETRIES): + self.recv_acks[id] = 1 + + count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) + count_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) + count_end.forward_chars(len(str(count - 1)) + len(str(MAXRETRIES)) + 2) + self.messagebuffer.delete(count_start, count_end) + + line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) + line_end.forward_to_line_end() + self.messagebuffer.insert(line_end, " * TIMEOUT *") + + self.send_msg_queue(tocall) return False delay *= 2 if (delay > 600): @@ -1122,7 +1102,7 @@ class APRSActivity(activity.Activity): def bulletin_check(self, callsign): for currentcall in self.bulletins: length = len(currentcall) - if (currentcall == callsign[0:length]): + if (currentcall == callsign[:length]): return True return False @@ -1132,5 +1112,60 @@ class APRSActivity(activity.Activity): id = "%s-%s" % (tocall, self.sent_acks[tocall]) if (time.time() - self.sent_acks[id] < 5400): # less than 90 minutes ago - replyack = "ack%s" % self.sent_acks[tocall] + replyack = self.sent_acks[tocall] return replyack + + def msg_queue(self, call, sequence, message): + if (len(self.message_list) >= MAX_MSG_QUEUE): + self.message_write("Too many messages in queue.\n") + return False + id = "%s-%s" % (call, sequence) + if (call in self.queue_list): + if (len(self.queue_list[call]) >= MAX_PER_CALL_QUEUE): + self.message_write("Too many messages to %s in queue.\n" % call) + return False + self.queue_list[call].append(sequence) + self.message_list[id] = message + else: + self.queue_list[call] = [] + self.queue_list[call].append(sequence) + self.message_list[id] = message + self.message_write("%s To:%s> %s * QUEUED *\n" % (time.strftime("%m/%d %H:%M", time.localtime()), call, message)) + # if a message is received before the next lines are run there will be problems... + iter = self.messagebuffer.get_iter_at_line(self.messagebuffer.get_line_count() - 2) + iter.forward_chars(15 + len(call)) + self.message_marks[id] = self.messagebuffer.create_mark(None, iter, True) +# self.message_marks[id].set_visible(True) + return True + + def send_msg_queue(self, call): + if (call in self.queue_list): + if (self.queue_list[call] == []): + del self.queue_list[call] + return False + sequence = self.queue_list[call].pop(0) + id = "%s-%s" % (call, sequence) + message = self.message_list[id] + del self.message_list[id] + + # send message + isbulletin = self.bulletin_check(call) + if (isbulletin): + self.send_data(":%s:%s" % (call.ljust(9), message)) + # TODO what are the correct timings for bulletins? + gobject.timeout_add(600 * 1000, self.msg_timer, call, message, "", 2, 600) + else: + replyack = self.replyack(call) + self.send_data(":%s:%s{%s}%s" % (call.ljust(9), message, sequence, replyack)) + self.recv_acks["%s-%s" % (call, sequence)] = 0 + gobject.timeout_add(7 * 1000, self.msg_timer, call, message, sequence, 2, 7) + + # clear "QUEUED" and add the counter + line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) + queue_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) + line_end.forward_to_line_end() + queue_start.forward_to_line_end() + queue_start.backward_chars(11) + self.messagebuffer.delete(queue_start, line_end) + iter = self.messagebuffer.get_iter_at_mark(self.message_marks[id]) + self.messagebuffer.insert(iter, " 1/%s" % MAXRETRIES) -- cgit v0.9.1