storage: split station meta data and previous positions

To improve the performance and DB footprint, no full log of every
stations' meta data is stored. Instead, only the latest meta data
is to be found in the "stations" table, and the movement track is
in the "positions" table.
service_trace
Georg Lukas 2011-07-15 00:27:12 +02:00
rodzic 077cf8881b
commit 748fcdb91f
5 zmienionych plików z 77 dodań i 55 usunięć

Wyświetl plik

@ -12,7 +12,7 @@ class HubActivity extends MainListActivity("hub", R.id.hub) {
val TAG = "APRSdroid.Hub"
lazy val mycall = prefs.getCallSsid()
lazy val pla = new PositionListAdapter(this, prefs, mycall, mycall, PositionListAdapter.NEIGHBORS)
lazy val pla = new StationListAdapter(this, prefs, mycall, mycall, StationListAdapter.NEIGHBORS)
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
@ -36,7 +36,7 @@ class HubActivity extends MainListActivity("hub", R.id.hub) {
override def onListItemClick(l : ListView, v : View, position : Int, id : Long) {
//super.onListItemClick(l, v, position, id)
val c = getListView().getItemAtPosition(position).asInstanceOf[Cursor]
val call = c.getString(StorageDatabase.Position.COLUMN_CALL)
val call = c.getString(StorageDatabase.Station.COLUMN_CALL)
openDetails(call)
}

Wyświetl plik

@ -90,11 +90,11 @@ class MapAct extends MapActivity with UIHelper {
def animateToCall() {
if (targetcall != "") {
val cursor = db.getStaPositions(targetcall, "1")
val cursor = db.getStaPosition(targetcall)
if (cursor.getCount() > 0) {
cursor.moveToFirst()
val lat = cursor.getInt(StorageDatabase.Position.COLUMN_LAT)
val lon = cursor.getInt(StorageDatabase.Position.COLUMN_LON)
val lat = cursor.getInt(StorageDatabase.Station.COLUMN_LAT)
val lon = cursor.getInt(StorageDatabase.Station.COLUMN_LON)
mapview.getController().animateTo(new GeoPoint(lat, lon))
}
cursor.close()
@ -253,19 +253,19 @@ class StationOverlay(icons : Drawable, context : MapAct, db : StorageDatabase) e
val s = new ArrayList[Station]()
val age_ts = (System.currentTimeMillis - context.prefs.getShowAge()).toString
val filter = if (context.showObjects) "TS > ? OR CALL=?" else "(ORIGIN IS NULL AND TS > ?) OR CALL=?"
val c = db.getPositions(filter, Array(age_ts, context.targetcall), null)
val c = db.getStations(filter, Array(age_ts, context.targetcall), null)
c.moveToFirst()
var m = new ArrayBuffer[GeoPoint]()
while (!c.isAfterLast()) {
val call = c.getString(StorageDatabase.Position.COLUMN_MAP_CALL)
val lat = c.getInt(StorageDatabase.Position.COLUMN_MAP_LAT)
val lon = c.getInt(StorageDatabase.Position.COLUMN_MAP_LON)
val symbol = c.getString(StorageDatabase.Position.COLUMN_MAP_SYMBOL)
val call = c.getString(StorageDatabase.Station.COLUMN_MAP_CALL)
val lat = c.getInt(StorageDatabase.Station.COLUMN_MAP_LAT)
val lon = c.getInt(StorageDatabase.Station.COLUMN_MAP_LON)
val symbol = c.getString(StorageDatabase.Station.COLUMN_MAP_SYMBOL)
val p = new GeoPoint(lat, lon)
m.add(p)
// peek at the next row
c.moveToNext()
val next_call = if (!c.isAfterLast()) c.getString(StorageDatabase.Position.COLUMN_MAP_CALL) else null
val next_call = if (!c.isAfterLast()) c.getString(StorageDatabase.Station.COLUMN_MAP_CALL) else null
c.moveToPrevious()
if (next_call != call) {
//Log.d(TAG, "end of call: " + call + " " + next_call + " " + m.size())

Wyświetl plik

@ -18,7 +18,7 @@ class StationActivity extends LoadingListActivity
lazy val postlist = findViewById(R.id.postlist).asInstanceOf[ListView]
lazy val mycall = prefs.getCallSsid()
lazy val pla = new PositionListAdapter(this, prefs, mycall, targetcall, PositionListAdapter.SSIDS)
lazy val pla = new StationListAdapter(this, prefs, mycall, targetcall, StationListAdapter.SSIDS)
lazy val la = new PostListAdapter(this)
lazy val locReceiver = new LocationReceiver2[Cursor](load_cursor, replace_cursor, cancel_cursor)
@ -56,7 +56,7 @@ class StationActivity extends LoadingListActivity
override def onListItemClick(l : ListView, v : View, position : Int, id : Long) {
//super.onListItemClick(l, v, position, id)
val c = getListView().getItemAtPosition(position).asInstanceOf[Cursor]
val call = c.getString(StorageDatabase.Position.COLUMN_CALL)
val call = c.getString(StorageDatabase.Station.COLUMN_CALL)
Log.d("StationActivity", "onListItemClick: %s".format(call))
if (targetcall == call) {
@ -80,7 +80,7 @@ class StationActivity extends LoadingListActivity
}
def replace_cursor(c : Cursor) {
la.changeCursor(c)
// do not call onStopLoading, PositionListAdapter takes much longer
// do not call onStopLoading, StationListAdapter takes much longer
//onStopLoading()
}
def cancel_cursor(c : Cursor) {

Wyświetl plik

@ -10,8 +10,8 @@ import _root_.android.view.View
import _root_.android.widget.{SimpleCursorAdapter, TextView}
import _root_.android.widget.FilterQueryProvider
object PositionListAdapter {
import StorageDatabase.Position._
object StationListAdapter {
import StorageDatabase.Station._
val LIST_FROM = Array(CALL, COMMENT, QRG)
val LIST_TO = Array(R.id.station_call, R.id.listmessage, R.id.station_qrg)
@ -20,16 +20,16 @@ object PositionListAdapter {
val SSIDS = 2
}
class PositionListAdapter(context : Context, prefs : PrefsWrapper,
class StationListAdapter(context : Context, prefs : PrefsWrapper,
mycall : String, targetcall : String, mode : Int)
extends SimpleCursorAdapter(context, R.layout.stationview, null, PositionListAdapter.LIST_FROM, PositionListAdapter.LIST_TO) {
extends SimpleCursorAdapter(context, R.layout.stationview, null, StationListAdapter.LIST_FROM, StationListAdapter.LIST_TO) {
var my_lat = 0
var my_lon = 0
var reload_pending = 0
lazy val storage = StorageDatabase.open(context)
if (mode == PositionListAdapter.NEIGHBORS)
if (mode == StationListAdapter.NEIGHBORS)
setFilterQueryProvider(getNeighborFilter())
reload()
@ -54,7 +54,7 @@ class PositionListAdapter(context : Context, prefs : PrefsWrapper,
def getBearing(b : Double) = LETTERS(((b.toInt + 22 + 720) % 360) / 45)
override def bindView(view : View, context : Context, cursor : Cursor) {
import StorageDatabase.Position._
import StorageDatabase.Station._
// TODO: multidimensional mapping
val distage = view.findViewById(R.id.station_distage).asInstanceOf[TextView]
@ -97,12 +97,12 @@ class PositionListAdapter(context : Context, prefs : PrefsWrapper,
}
def load_cursor(i : Intent) = {
import PositionListAdapter._
import StationListAdapter._
val cursor = storage.getStaPosition(mycall)
if (cursor.getCount() > 0) {
cursor.moveToFirst()
my_lat = cursor.getInt(StorageDatabase.Position.COLUMN_LAT)
my_lon = cursor.getInt(StorageDatabase.Position.COLUMN_LON)
my_lat = cursor.getInt(StorageDatabase.Station.COLUMN_LAT)
my_lon = cursor.getInt(StorageDatabase.Station.COLUMN_LON)
}
cursor.close()
val c = mode match {

Wyświetl plik

@ -14,7 +14,7 @@ import _root_.scala.math.{cos, Pi}
object StorageDatabase {
val TAG = "APRSdroid.Storage"
val DB_VERSION = 2
val DB_VERSION = 3
val DB_NAME = "storage.db"
val TSS_COL = "DATETIME(TS/1000, 'unixepoch', 'localtime') as TSS"
@ -40,8 +40,8 @@ object StorageDatabase {
var trimCounter = 0
}
object Position {
val TABLE = "position"
object Station {
val TABLE = "stations"
val _ID = "_id"
val TS = "ts"
val CALL = "call"
@ -55,7 +55,7 @@ object StorageDatabase {
val ORIGIN = "origin" // originator call for object/item
val QRG = "qrg" // voice frequency
lazy val TABLE_CREATE = """CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s LONG,
%s TEXT, %s INTEGER, %s INTEGER,
%s TEXT UNIQUE, %s INTEGER, %s INTEGER,
%s INTEGER, %s INTEGER, %s INTEGER,
%s TEXT, %s TEXT, %s TEXT, %s TEXT)"""
.format(TABLE, _ID, TS,
@ -84,11 +84,23 @@ object StorageDatabase {
val COLUMN_MAP_LON = 3
val COLUMN_MAP_SYMBOL = 4
lazy val TABLE_INDEX = "CREATE INDEX idx_position_%s ON position (%s)"
lazy val TABLE_INDEX = "CREATE INDEX idx_stations_%s ON stations (%s)"
}
object Position {
val TABLE = "positions"
val _ID = "_id"
val TS = "ts"
val CALL = "call"
val LAT = "lat"
val LON = "lon"
lazy val TABLE_CREATE = """CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s LONG,
%s TEXT, %s INTEGER, %s INTEGER)"""
.format(TABLE, _ID, TS,
CALL, LAT, LON)
}
object Message {
val TABLE = "message"
val TABLE = "messages"
val _ID = "_id"
val TS = "ts" // timestamp of RX or first TX
val RETRYCNT = "retrycnt" // attemp number for sending msg
@ -131,7 +143,7 @@ object StorageDatabase {
def cursor2call(c : Cursor) : String = {
val msgidx = c.getColumnIndex(Post.MESSAGE)
val callidx = c.getColumnIndex(Position.CALL)
val callidx = c.getColumnIndex(Station.CALL)
if (msgidx != -1 && callidx == -1) { // Post table
val t = c.getInt(Post.COLUMN_TYPE)
if (t == Post.TYPE_POST || t == Post.TYPE_INCMG)
@ -151,14 +163,22 @@ class StorageDatabase(context : Context) extends
override def onCreate(db: SQLiteDatabase) {
Log.d(TAG, "onCreate(): creating new database " + DB_NAME);
db.execSQL(Post.TABLE_CREATE);
db.execSQL(Station.TABLE_CREATE)
// index on call is implicit due to UNIQUE
Array("lat", "lon").map(col => db.execSQL(Station.TABLE_INDEX.format(col, col)))
db.execSQL(Position.TABLE_CREATE)
Array("call", "lat", "lon").map(col => db.execSQL(Position.TABLE_INDEX.format(col, col)))
db.execSQL(Message.TABLE_CREATE)
}
override def onUpgrade(db: SQLiteDatabase, from : Int, to : Int) {
if (from <= 1 && to <= 2) {
db.execSQL(Message.TABLE_CREATE)
if (from <= 1 && to <= 3) {
db.execSQL("CREATE TABLE message") // use old name here!
}
if (from <= 2 && to <= 3) {
db.execSQL("ALTER TABLE message RENAME TO messages") // make names consistent
db.execSQL("DROP TABLE position") // old name
db.execSQL(Station.TABLE_CREATE)
db.execSQL(Position.TABLE_CREATE)
}
}
@ -174,7 +194,7 @@ class StorageDatabase(context : Context) extends
def trimPosts() : Unit = trimPosts(System.currentTimeMillis - 2L * 24 * 3600 * 1000)
def addPosition(ts : Long, ap : APRSPacket, pos : Position, objectname : String) {
import Position._
import Station._
val cv = new ContentValues()
val call = ap.getSourceCall()
val lat = (pos.getLatitude()*1000000).asInstanceOf[Int]
@ -183,18 +203,20 @@ class StorageDatabase(context : Context) extends
val comment = ap.getAprsInformation().getComment()
val qrg = AprsPacket.parseQrg(comment)
cv.put(TS, ts.asInstanceOf[java.lang.Long])
if (objectname != null) {
cv.put(CALL, objectname)
cv.put(ORIGIN, call)
} else
cv.put(CALL, call)
cv.put(CALL, if (objectname != null) objectname else call)
cv.put(LAT, lat.asInstanceOf[java.lang.Integer])
cv.put(LON, lon.asInstanceOf[java.lang.Integer])
// add the position into positions table
getWritableDatabase().insertOrThrow(Position.TABLE, CALL, cv)
if (objectname != null)
cv.put(ORIGIN, call)
cv.put(SYMBOL, sym)
cv.put(COMMENT, comment)
cv.put(QRG, qrg)
Log.d(TAG, "got %s(%d, %d)%s -> %s".format(call, lat, lon, sym, comment))
getWritableDatabase().insertOrThrow(TABLE, CALL, cv)
// replace the full station info in stations table
getWritableDatabase().replaceOrThrow(TABLE, CALL, cv)
}
def isMessageDuplicate(call : String, msgid : String, text : String) : Boolean = {
@ -224,31 +246,31 @@ class StorageDatabase(context : Context) extends
addMessage(cv)
}
def getPositions(sel : String, selArgs : Array[String], limit : String) : Cursor = {
getReadableDatabase().query(Position.TABLE, Position.COLUMNS_MAP,
def getStations(sel : String, selArgs : Array[String], limit : String) : Cursor = {
getReadableDatabase().query(Station.TABLE, Station.COLUMNS_MAP,
sel, selArgs,
null, null, "CALL, _ID", limit)
null, null, "CALL", limit)
}
def getRectPositions(lat1 : Int, lon1 : Int, lat2 : Int, lon2 : Int, limit : String) : Cursor = {
Log.d(TAG, "StorageDatabase.getRectPositions: %d,%d - %d,%d".format(lat1, lon1, lat2, lon2))
getPositions("LAT >= ? AND LAT <= ? AND LON >= ? AND LON <= ?",
def getRectStations(lat1 : Int, lon1 : Int, lat2 : Int, lon2 : Int, limit : String) : Cursor = {
Log.d(TAG, "StorageDatabase.getRectStations: %d,%d - %d,%d".format(lat1, lon1, lat2, lon2))
getStations("LAT >= ? AND LAT <= ? AND LON >= ? AND LON <= ?",
Array(lat1, lat2, lon1, lon2).map(_.toString), limit)
}
def getStaPosition(call : String) : Cursor = {
getReadableDatabase().query(Position.TABLE, Position.COLUMNS,
getReadableDatabase().query(Station.TABLE, Station.COLUMNS,
"call LIKE ?", Array(call),
null, null, "_ID DESC", "1")
}
def getStaPositions(call : String, limit : String) : Cursor = {
getReadableDatabase().query(Position.TABLE, Position.COLUMNS,
getReadableDatabase().query(Station.TABLE, Station.COLUMNS,
"call LIKE ? AND TS > ?", Array(call, limit),
null, null, "_ID DESC", null)
}
def getAllSsids(call : String) : Cursor = {
val querycall = call.split("[- _]+")(0) + "%"
getReadableDatabase().query(Position.TABLE, Position.COLUMNS,
getReadableDatabase().query(Station.TABLE, Station.COLUMNS,
"call LIKE ? or origin LIKE ?", Array(querycall, querycall),
"call", null, null, null)
}
@ -257,10 +279,10 @@ class StorageDatabase(context : Context) extends
val corr = (cos(Pi*lat/180000000.)*cos(Pi*lat/180000000.)*100).toInt
Log.d(TAG, "getNeighbors: correcting by %d".format(corr))
// add a distance column to the query
val newcols = Position.COLUMNS :+ Position.COL_DIST.format(lat, lat, lon, lon, corr)
getReadableDatabase().query(Position.TABLE, newcols,
val newcols = Station.COLUMNS :+ Station.COL_DIST.format(lat, lat, lon, lon, corr)
getReadableDatabase().query(Station.TABLE, newcols,
"ts > ? or call = ?", Array(ts.toString, mycall),
"call", null, "dist", limit)
null, null, "dist", limit)
}
def getNeighborsLike(call : String, lat : Int, lon : Int, ts : Long, limit : String) : Cursor = {
@ -268,10 +290,10 @@ class StorageDatabase(context : Context) extends
val corr = (cos(Pi*lat/180000000.)*cos(Pi*lat/180000000.)*100).toInt
Log.d(TAG, "getNeighborsLike: correcting by %d".format(corr))
// add a distance column to the query
val newcols = Position.COLUMNS :+ Position.COL_DIST.format(lat, lat, lon, lon, corr)
getReadableDatabase().query(Position.TABLE, newcols,
val newcols = Station.COLUMNS :+ Station.COL_DIST.format(lat, lat, lon, lon, corr)
getReadableDatabase().query(Station.TABLE, newcols,
"call like ?", Array(call),
"call", null, "dist", limit)
null, null, "dist", limit)
}
def addPost(ts : Long, posttype : Int, status : String, message : String) {