From: Jonathan Parr Date: Sat, 19 Mar 2011 19:07:26 +0000 (+0000) Subject: Added initial revision of stock widget to repo. X-Git-Url: http://git.maemo.org/git/?p=stockwidget;a=commitdiff_plain;h=debf1fe34b36af95996fb9d5a88eeb5f5ddf59da;ds=sidebyside Added initial revision of stock widget to repo. TODO: Transparent background for widget TODO: Debian package generation. --- diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..709015a --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +# +# Makefile for stock widget +# TODO: Generate debian package for easy installation. +# Jon Parr +# + +all: stock-home-widget + +SOURCES=lib-stock-home-widget.c lib-stock-settings.c stockgetter.c +TARGET =lib-stock-home-widget.so +DESKTOP=stock-home-widget.desktop + +lib-stock-home-widget.c : lib-stock-home-widget.h +stockgetter.c : stockgetter.h + +stock-home-widget: $(SOURCES) + @gcc -Wall -ansi -pedantic -shared `pkg-config --libs --cflags hildon-1 libhildondesktop-1` $(SOURCES) -o $(TARGET) -lcurl + +clean: + @rm -rf *.o *~ + +clobber: + @rm -rf $(TARGET) *.o *~ + +install: + @cp -f $(TARGET) `pkg-config libhildondesktop-1 --variable=hildondesktoplibdir`/. + @cp -f $(DESKTOP) `pkg-config libhildondesktop-1 --variable=hildonhomedesktopentrydir`/. diff --git a/lib-stock-home-widget.c b/lib-stock-home-widget.c new file mode 100644 index 0000000..9f9e054 --- /dev/null +++ b/lib-stock-home-widget.c @@ -0,0 +1,390 @@ +/* + * Simple stock widget + * Jon Parr + */ + +#include +#include +#include +#include +#include "lib-stock-home-widget.h" +#include "lib-stock-settings.h" + +HD_DEFINE_PLUGIN_MODULE (StockPlugin, stock_plugin, HD_TYPE_HOME_PLUGIN_ITEM) + +static void +OnClickRefresh(GtkWidget *widget, StockPluginContext *psContext) +{ + DebugOut(("OnClickRefresh")); + + /* Disable button while getting latest stocks */ + gtk_widget_set_sensitive(GTK_WIDGET(psContext->psButton),FALSE); + { + float fPrice; + float fChange; + char szBuffer[1024]; + + if(RetrieveStockPrice(psContext->hSG,psContext->psSettings->ppszTickers[0],&fPrice,NULL,&fChange) == SG_OK) + { + char szTime[80]; + time_t rawtime; + struct tm *timeinfo; + + time(&rawtime); + timeinfo = localtime(&rawtime); + strftime(szTime,80,"(%I:%M%p)",timeinfo); + + /* Update to latest price */ + sprintf(szBuffer,"%s %.2f %.2f%% %s",psContext->psSettings->ppszTickers[0],fPrice,fChange,szTime); + + hildon_button_set_title(HILDON_BUTTON(psContext->psButton),szBuffer); + gtk_widget_queue_draw(GTK_WIDGET(psContext->psBox)); + } + /* + If it fails, leave the old price as the user can see the timestamp + and know it's not up-to-date. So just notify them of the failure. + */ + else + { + hildon_banner_show_information(GTK_WIDGET(widget),NULL,"Failed to retrieve stock information, check ticker symbol and try again."); + } + } + gtk_widget_set_sensitive(GTK_WIDGET(psContext->psButton),TRUE); +} + +static gboolean +UpdateStockOnTimeout(gpointer data) +{ + StockPluginContext *psContext = (StockPluginContext*)data; + + DebugOut(("UpdateStockOnTimeout")); + + if(!psContext) + return FALSE; + + /* If no longer a timeout, don't do anything and stop */ + if(!psContext->psSettings->uiUpdateTime) + return FALSE; + + /* Update stock price */ + OnClickRefresh(GTK_WIDGET(psContext->psButton),psContext); + return TRUE; +} + +static void +stock_show_settings_dialog(GtkWidget* widget, gpointer data) +{ + StockPluginContext *psContext = (StockPluginContext*)data; + GtkWidget *dialog; + GtkWidget *content; + GtkSizeGroup *group; + GtkWidget *addselector; + GtkWidget *addbutton; + GtkWidget *timeselector; + GtkWidget *timebutton; + + DebugOut(("stock_show_settings_dialog")); + + /* Stop timer while settings are one */ + if(psContext->nTimerID != -1) + { + g_source_remove(psContext->nTimerID); + psContext->nTimerID = -1; + } + + dialog = gtk_dialog_new_with_buttons("Settings",NULL,0,"Save",GTK_RESPONSE_ACCEPT,NULL); + content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + group = GTK_SIZE_GROUP(gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL)); + + /* Add new stock symbol */ + { + int i; + + addbutton = hildon_picker_button_new (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT, + HILDON_BUTTON_ARRANGEMENT_HORIZONTAL); + + addselector = hildon_touch_selector_entry_new_text(); + + for(i=0; i < psContext->psSettings->uiNumTickers; i++) + { + hildon_touch_selector_append_text(HILDON_TOUCH_SELECTOR(addselector),psContext->psSettings->ppszTickers[i]); + } + + /* First ticker in ppszTickers is the chosen one */ + hildon_touch_selector_set_active(HILDON_TOUCH_SELECTOR(addselector), 0, 0); + + hildon_picker_button_set_selector (HILDON_PICKER_BUTTON (addbutton), HILDON_TOUCH_SELECTOR (addselector)); + + hildon_button_set_title(HILDON_BUTTON(addbutton), "Select Ticker"); + } + + /* Set auto update time */ + { + int i; + int sel = 0; + + char *aszUpdateTimes[] = + { + "No update", + "1 minute", + "15 minutes", + "30 minutes", + "1 hour" + }; + + timebutton = hildon_picker_button_new (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT, + HILDON_BUTTON_ARRANGEMENT_HORIZONTAL); + + timeselector = hildon_touch_selector_new_text(); + + for(i=0; i < sizeof(aszUpdateTimes)/sizeof(aszUpdateTimes[0]); i++) + { + hildon_touch_selector_append_text(HILDON_TOUCH_SELECTOR(timeselector),aszUpdateTimes[i]); + } + + switch(psContext->psSettings->uiUpdateTime) + { + case 0: sel = 0; break; + case 1: sel = 1; break; + case 15: sel = 2; break; + case 30: sel = 3; break; + case 60: sel = 4; break; + } + + hildon_touch_selector_set_active(HILDON_TOUCH_SELECTOR(timeselector), 0, sel); + hildon_picker_button_set_selector (HILDON_PICKER_BUTTON (timebutton), HILDON_TOUCH_SELECTOR (timeselector)); + hildon_button_set_title(HILDON_BUTTON(timebutton), "Automatic Update Period"); + } + + gtk_container_add(GTK_CONTAINER(content),addbutton); + gtk_container_add(GTK_CONTAINER(content),timebutton); + gtk_widget_show_all(dialog); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) + { + const char *pszNewTicker; + char aszOldTicker[255]={0}; + guint uiUpdateSelection = 0; + + /* Only perform ops if they actually changed the ticker */ + if(strcmp(aszOldTicker,psContext->psSettings->ppszTickers[0])) + { + /* Save off old ticker */ + strcpy(aszOldTicker,psContext->psSettings->ppszTickers[0]); + + /* Get new ticker */ + pszNewTicker = hildon_button_get_value(HILDON_BUTTON(addbutton)); + + /* Check if ticker already exists */ + { + int i; + gboolean bFound = FALSE; + + /* Check it doesn't exist already */ + for(i=0; i < psContext->psSettings->uiNumTickers; i++) + { + if(!strcmp(pszNewTicker,psContext->psSettings->ppszTickers[i])) + { + bFound = TRUE; + break; + } + } + + /* If they just chose another one, just swap the pointers so new one is at the top: + */ + if(bFound) + { + /* Swapped ith and 0th element */ + char *psTmp = psContext->psSettings->ppszTickers[i]; + psContext->psSettings->ppszTickers[i] = psContext->psSettings->ppszTickers[0]; + psContext->psSettings->ppszTickers[0] = psTmp; + + DebugOut(("Swapped ticker %d (%s) with 0 (%s)", + i, + psContext->psSettings->ppszTickers[i], + psContext->psSettings->ppszTickers[0])); + } + /* If it doesn't add it to the list, placing chosen one at the front */ + else + { + gchar **ppszTickers = g_malloc0(sizeof(char*) * (psContext->psSettings->uiNumTickers+2)); + + /* Place the selected one first */ + ppszTickers[0] = g_strdup(pszNewTicker); + + /* Copy across old tickers */ + for(i=0; i < psContext->psSettings->uiNumTickers; i++) + { + /* offset by one as the selected one is first */ + ppszTickers[i+1] = g_strdup(psContext->psSettings->ppszTickers[i]); + } + + /* Free current array */ + g_strfreev(psContext->psSettings->ppszTickers); + + /* Assign new array */ + psContext->psSettings->ppszTickers = ppszTickers; + + /* Update count */ + psContext->psSettings->uiNumTickers++; + } + } + + /* Force a refresh as the ticker changed */ + OnClickRefresh(GTK_WIDGET(psContext->psButton),psContext); + } + + uiUpdateSelection = hildon_touch_selector_get_active(HILDON_TOUCH_SELECTOR(timeselector),0); + + switch(uiUpdateSelection) + { + /* No update */ + case 0: psContext->psSettings->uiUpdateTime = 0; break; + case 1: psContext->psSettings->uiUpdateTime = 1; break; + case 2: psContext->psSettings->uiUpdateTime = 15; break; + case 3: psContext->psSettings->uiUpdateTime = 30; break; + case 4: psContext->psSettings->uiUpdateTime = 60; break; + } + + if(psContext->psSettings->uiUpdateTime != 0) + { + /* Remove previous timeout if there was one */ + if(psContext->nTimerID != -1) + { + g_source_remove(psContext->nTimerID); + } + + /* Add timer (in seconds) */ + psContext->nTimerID = g_timeout_add_seconds(psContext->psSettings->uiUpdateTime * 60, + UpdateStockOnTimeout, + psContext); + } + + /* Save Settings */ + stock_save_settings(psContext->psSettings); + } + + gtk_widget_destroy (dialog); +} + +static GtkWidget *stock_create_gui(gchar *pszStockLabel, + StockPluginContext *psContext) +{ + HildonButton *psButton = NULL; + + DebugOut(("stock_create_gui")); + + psButton = (HildonButton*)hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT, + HILDON_BUTTON_ARRANGEMENT_VERTICAL, + pszStockLabel, + NULL); + + psContext->psButton = psButton; + + /* Setup button click signal for refreshing stocks */ + gtk_signal_connect (GTK_OBJECT(psButton),"clicked",GTK_SIGNAL_FUNC(OnClickRefresh),(gpointer)psContext); + + /* Display */ + gtk_widget_show_all(GTK_WIDGET(psButton)); + + return GTK_WIDGET(psButton); +} + +static void +stock_plugin_finalize (GObject *object) +{ + StockPluginContext *psContext = ((StockPlugin*)object)->context; + + DebugOut(("stock_plugin_finalize")); + + if(psContext) + { + if(psContext->psSettings) + { + if(psContext->psSettings) + g_free(psContext->psSettings->iD); + + stock_free_settings(psContext->psSettings); + g_free(psContext->psSettings); + } + + if(psContext->nTimerID != -1) + { + g_source_remove(psContext->nTimerID); + psContext->nTimerID = -1; + } + + if(psContext->hSG) + FreeStockGetter(psContext->hSG); + + g_free(psContext); + } +} + +static void +stock_plugin_init (StockPlugin *desktop_plugin) +{ + StockPluginContext *psContext; + SGHandle hSG; + + DebugOut(("stock_plugin_init")); + + hSG = InitStockGetter(); + + if(!hSG) + { + return; + } + + /* Create plugin context */ + psContext = g_new0(StockPluginContext,1); + + /* Always create a settings */ + psContext->psSettings = g_new0(StockPluginSettings,1); + psContext->psSettings->iD = g_strdup(STOCK_PLUGIN_APPLET_ID); + + /* Attempt to load settings (defaulted if not present) */ + stock_read_settings(psContext->psSettings); + + /* Create GUI widget interface (and sets links in context) */ + psContext->psBox = stock_create_gui("Click to Update",psContext); + + gtk_container_add (GTK_CONTAINER (desktop_plugin), psContext->psBox); + + psContext->hSG = hSG; + psContext->nTimerID = -1; + + /* Assign private data to plugin */ + desktop_plugin->context = psContext; + + /* Remove previous timeout if there was one */ + if(psContext->nTimerID != -1) + { + g_source_remove(psContext->nTimerID); + } + + /* Setup new one if required */ + if(psContext->psSettings->uiUpdateTime > 0) + { + psContext->nTimerID = g_timeout_add_seconds(psContext->psSettings->uiUpdateTime * 60, + UpdateStockOnTimeout, + psContext); + } + + /* Show a settings button */ + hd_home_plugin_item_set_settings (HD_HOME_PLUGIN_ITEM (desktop_plugin), TRUE); + + /* Connect to the settings button */ + g_signal_connect(desktop_plugin, "show-settings", G_CALLBACK(stock_show_settings_dialog), psContext); +} + +static void +stock_plugin_class_init (StockPluginClass *klass) +{ + G_OBJECT_CLASS(klass)->finalize = stock_plugin_finalize; +} + +static void +stock_plugin_class_finalize (StockPluginClass *klass) +{ +} diff --git a/lib-stock-home-widget.h b/lib-stock-home-widget.h new file mode 100644 index 0000000..ef8f87b --- /dev/null +++ b/lib-stock-home-widget.h @@ -0,0 +1,80 @@ +/* + * Simple stock widget + * Jon Parr + */ + +#ifndef STOCK_PLUGIN_H +#define STOCK_PLUGIN_H + +/* Standard includes */ +#include +#include + +/* Custom includes */ +#include "stockgetter.h" +#include "lib-stock-settings.h" + +G_BEGIN_DECLS + +typedef struct _StockPlugin StockPlugin; +typedef struct _StockPluginClass StockPluginClass; + +#define STOCK_TYPE_HOME_PLUGIN (stock_home_plugin_get_type ()) + +#define STOCK_HOME_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + STOCK_TYPE_HOME_PLUGIN, StockPlugin)) + +#define STOCK_HOME_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \ + STOCK_TYPE_HOME_PLUGIN, StockPluginClass)) + +#define STOCK_IS_HOME_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + STOCK_TYPE_HOME_PLUGIN)) + +#define STOCK_IS_HOME_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + STOCK_TYPE_HOME_PLUGIN)) + +#define STOCK_HOME_PLUGIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + STOCK_TYPE_HOME_PLUGIN, StockPluginClass)) + +typedef struct _StockPluginContext +{ + /* Used for updating when stock price changes */ + HildonButton *psButton; + GtkWidget *psLabel; + GtkWidget *psBox; + + /* Stock Getter Handle */ + SGHandle hSG; + + /* Timer func id */ + guint nTimerID; + + /* Settings for the plugin (saved and loaded into config file) */ + StockPluginSettings *psSettings; + +} StockPluginContext; + +struct _StockPlugin +{ + HDHomePluginItem hitem; + StockPluginContext *context; +}; + +struct _StockPluginClass +{ + HDHomePluginItemClass parent_class; +}; + +GType stock_home_plugin_get_type(void); +G_END_DECLS + +#define DEBUG + +/* Debug print only */ +#if defined(DEBUG) +#define DebugOut(X) {g_print("stockwidget: "); g_print X; g_print("\n");} +#else +#define DebugOut(X) +#endif/*defined(DEBUG)*/ + +#endif diff --git a/lib-stock-settings.c b/lib-stock-settings.c new file mode 100644 index 0000000..c220645 --- /dev/null +++ b/lib-stock-settings.c @@ -0,0 +1,126 @@ +/* + * Stock Widget: Resource settings + * Jon Parr + */ +#include "lib-stock-settings.h" + +void +stock_free_settings(StockPluginSettings *psSettings) +{ + if(psSettings->ppszTickers) + { + g_strfreev(psSettings->ppszTickers); + psSettings->ppszTickers = NULL; + } + + psSettings->uiNumTickers = 0; + psSettings->uiUpdateTime = 0; +} + +void +stock_set_default_settings(StockPluginSettings *psSettings) +{ + /* Default to a single ticker ;) */ + psSettings->uiNumTickers = 1; + psSettings->ppszTickers = g_malloc0(sizeof(char*)*2); + psSettings->ppszTickers[0] = g_strdup(STOCK_PLUGIN_DEFAULT_TICKER); + + /* Default to no automatic update */ + psSettings->uiUpdateTime = 0; +} + +void +stock_read_settings(StockPluginSettings *psSettings) +{ + GKeyFile *psKeyFile; + gchar *pszFileName; + gboolean bExists; + gint nItems = 0; + + psKeyFile = g_key_file_new(); + pszFileName = g_strconcat(g_get_home_dir(), STOCK_PLUGIN_SETTINGS_FILE, NULL); + bExists = g_key_file_load_from_file(psKeyFile, pszFileName, G_KEY_FILE_KEEP_COMMENTS, NULL); + + if(bExists) + { + /*TODO: Check case if config file exists but no data, simply use defaults */ + + if(g_key_file_has_key(psKeyFile,psSettings->iD,STOCK_PLUGIN_TICKERS,NULL)) + { + psSettings->ppszTickers = + g_key_file_get_string_list(psKeyFile, + psSettings->iD, + STOCK_PLUGIN_TICKERS, + &psSettings->uiNumTickers, + NULL); + } + else + { + bExists = FALSE; + } + + if(g_key_file_has_key(psKeyFile,psSettings->iD,STOCK_PLUGIN_UPDATE_TIME,NULL)) + { + psSettings->uiUpdateTime = + g_key_file_get_integer(psKeyFile, + psSettings->iD, + STOCK_PLUGIN_UPDATE_TIME, + NULL); + nItems++; + } + else + { + if(psSettings->ppszTickers) + { + g_strfreev(psSettings->ppszTickers); + psSettings->ppszTickers = NULL; + } + + bExists = FALSE; + } + } + + /* If it doesn't exists or there's an error reading data, just set defaults */ + if(!bExists) + stock_set_default_settings(psSettings); + + g_key_file_free(psKeyFile); + g_free(pszFileName); +} + + +void +stock_save_settings(StockPluginSettings *psSettings) +{ + GKeyFile *psKeyFile; + gchar *pszFileName; + gchar *pszFileData; + gsize nSize; + + /* Create key file */ + psKeyFile = g_key_file_new(); + pszFileName = g_strconcat(g_get_home_dir(), STOCK_PLUGIN_SETTINGS_FILE, NULL); + g_key_file_load_from_file(psKeyFile, pszFileName, G_KEY_FILE_KEEP_COMMENTS, NULL); + + /* Set tickers */ + g_key_file_set_string_list(psKeyFile, + psSettings->iD, + STOCK_PLUGIN_TICKERS, + (const gchar **)psSettings->ppszTickers, + psSettings->uiNumTickers); + + /* Set update time */ + g_key_file_set_integer(psKeyFile, + psSettings->iD, + STOCK_PLUGIN_UPDATE_TIME, + psSettings->uiUpdateTime); + + /* Save to disk */ + pszFileData = g_key_file_to_data(psKeyFile,&nSize,NULL); + g_file_set_contents(pszFileName,pszFileData,nSize,NULL); + + /* Cleanup */ + g_key_file_free(psKeyFile); + g_free(pszFileName); + g_free(pszFileData); +} diff --git a/lib-stock-settings.h b/lib-stock-settings.h new file mode 100644 index 0000000..02683e9 --- /dev/null +++ b/lib-stock-settings.h @@ -0,0 +1,51 @@ +/* + * Stock Widget: Resource settings + * Jon Parr + */ +#ifndef STOCK_SETTINGS_H +#define STOCK_SETTINGS_H + +#include + +/* Configuration file entries */ +#define STOCK_PLUGIN_APPLET_ID "stockwidget" +#define STOCK_PLUGIN_TICKERS "tickers" +#define STOCK_PLUGIN_UPDATE_TIME "updatetime" +#define STOCK_PLUGIN_SETTINGS_FILE "/stockwidget.config" +#define STOCK_PLUGIN_DEFAULT_TICKER "IMG.L" + +/* + * Struct used for holding settings stored out and loaded + * in from configuration file. + */ +typedef struct _StockPluginSettings +{ + /* Current number of stock tickers selectable in the settings dialog */ + unsigned int uiNumTickers; + + /* Current stock ticker symbols selectable in the settings dialog. + The chosen one is always the first to make loading/saving chosen ticker easy as. + */ + char **ppszTickers; + + /* Number of minutes between an automatic update, if 0 no automatic updating */ + unsigned int uiUpdateTime; + + /* Applet ID used as key for config file */ + gchar *iD; + +} StockPluginSettings; + +void +stock_free_settings(StockPluginSettings *psSettings); + +void +stock_set_default_settings(StockPluginSettings *psSettings); + +void +stock_read_settings(StockPluginSettings *psSettings); + +void +stock_save_settings(StockPluginSettings *psSettings); + +#endif/*STOCK_SETTINGS_H*/ diff --git a/stock-home-widget.desktop b/stock-home-widget.desktop new file mode 100644 index 0000000..fc99468 --- /dev/null +++ b/stock-home-widget.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Name=Stock Widget +Comment=Track your stocks +Type=default +X-Path=lib-stock-home-widget.so diff --git a/stockgetter.c b/stockgetter.c new file mode 100644 index 0000000..de13724 --- /dev/null +++ b/stockgetter.c @@ -0,0 +1,145 @@ +/* + * Simple wrapper for curl and Yahoo finance queries + * to enable grabbing stock data. + * Jon Parr + */ +#include "stockgetter.h" + +#include +#include +#include + +/* Current maximum query return size */ +#define SG_MAX_BUFFER 512 + +/* Current query string, note the %s for the stock ticker name */ +#define SG_QUERY_STRING "http://finance.yahoo.com/d/quotes.csv?s=%s&f=l1c" + +static size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp) +{ + StockBuffer *psContext = (StockBuffer*)userp; + int nBytes = size * nmemb; + + /*TODO: Write extendable buffer */ + if(nBytes > psContext->nCount - psContext->nCurr) + { + fprintf(stderr,"Ran out of buffer space! (Bytes %d, %d/%d)\n",nBytes,psContext->nCurr,psContext->nCount); + return 0; + } + + memcpy((void*)(psContext->pszBuffer + psContext->nCurr),buffer,nBytes); + psContext->nCurr += nBytes; + + return nmemb; +} + +SGHandle +InitStockGetter(void) +{ + SGHandle hSG; + CURL *curl_handle; + + hSG = (SGHandle)malloc(sizeof(struct _SGHandle)); + + if(!hSG) + return NULL; + + memset(hSG,0,sizeof(struct _SGHandle)); + + if(curl_global_init(CURL_GLOBAL_ALL) != 0) + goto err_cleanup; + + curl_handle = curl_easy_init(); + + if(!curl_handle) + goto err_cleanup; + + if(curl_easy_setopt(curl_handle,CURLOPT_WRITEFUNCTION,write_data) != 0) + goto err_cleanup; + + if(curl_easy_setopt (curl_handle, CURLOPT_FOLLOWLOCATION, 1) != 0) + goto err_cleanup; + + if(curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA,&hSG->sData) != 0) + goto err_cleanup; + + hSG->hCurlHandle = curl_handle; + + return hSG; + +err_cleanup: + + if(hSG) + free(hSG); + + return NULL; +} + +int +RetrieveStockPrice(SGHandle hSG, + char *szTicker, + float *pfValue, + float *pfChangeReal, + float *pfChangePercent) +{ + const char *pszQueryString = SG_QUERY_STRING; + char aszQuery[255] = {0}; + float fValue = 0.0f; + float fChangeReal = 0.0f; + float fChangePer = 0.0f; + + if(!hSG || !pfValue || !szTicker || strlen(szTicker) > 10) + return SG_INVALID_PARAMS; + + /* On first lookup setup output buffer */ + if(!hSG->sData.pszBuffer) + { + hSG->sData.pszBuffer = (char*)malloc(sizeof(char)*SG_MAX_BUFFER); + + if(!hSG->sData.pszBuffer) + return SG_BAD_ALLOC; + + memset(hSG->sData.pszBuffer,0,SG_MAX_BUFFER); + + hSG->sData.nCount = SG_MAX_BUFFER; + } + + sprintf(aszQuery,pszQueryString,szTicker); + + if(curl_easy_setopt(hSG->hCurlHandle, CURLOPT_URL,aszQuery) != 0) + return SG_LOOKUP_FAILURE; + + if(curl_easy_perform(hSG->hCurlHandle) != 0) + return SG_LOOKUP_FAILURE; + + hSG->sData.nCurr = 0; + + if(sscanf(hSG->sData.pszBuffer,"%f,\"%f - %f\"\n",&fValue,&fChangeReal,&fChangePer) != 3) + return SG_LOOKUP_FAILURE; + + if(pfValue) + *pfValue = fValue; + + if(pfChangeReal) + *pfChangeReal = fChangeReal; + + if(pfChangePercent) + *pfChangePercent = fChangePer; + + return SG_OK; +} + +int +FreeStockGetter(SGHandle hSG) +{ + if(!hSG) + return SG_INVALID_PARAMS; + + curl_easy_cleanup(hSG->hCurlHandle); + curl_global_cleanup(); + + free(hSG); + + return SG_OK; +} + diff --git a/stockgetter.h b/stockgetter.h new file mode 100644 index 0000000..aacecc9 --- /dev/null +++ b/stockgetter.h @@ -0,0 +1,39 @@ +#ifndef _STOCKGETTER_H_ +#define _STOCKGETTER_H_ + +#include + +#define SG_OK +1 +#define SG_INVALID_PARAMS -1 +#define SG_LOOKUP_FAILURE -2 +#define SG_BAD_ALLOC -3 + +typedef struct _StockBuffer +{ + int nCurr; + int nCount; + char *pszBuffer; +} StockBuffer; + +typedef struct _SGHandle +{ + CURL *hCurlHandle; + StockBuffer sData; +} *SGHandle; + +/* API */ + +SGHandle +InitStockGetter(void); + +int +RetrieveStockPrice(SGHandle hSG, + char *szTicker, + float *pfValue, + float *pfChangeReal, + float *pfChangePercent); + +int +FreeStockGetter(SGHandle hSG); + +#endif/*_STOCKGETTER_H_*/ diff --git a/welcome b/welcome deleted file mode 100644 index e69de29..0000000