1d7ae458b937a95e86857ca33ffbb84e36e52e9c
[debian/pino.git] / src / rest_api_abstract.vala
1 /* rest_api_abstract.vala
2  *
3  * Copyright (C) 2009-2010  troorl
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author:
19  *      troorl <troorl@gmail.com>
20  */
21
22 using Gee;
23 using Soup;
24 using Auth;
25 using TimeUtils;
26
27 namespace RestAPI {
28
29 public class Status : Object {
30         public string id;
31         public string text;
32         public Time created_at = Time();
33         public string user_name;
34         public string user_screen_name;
35         public string user_avatar;
36         
37         public string re_text = "";
38         public string re_user_name;
39         public string re_user_screen_name;
40         public string re_user_avatar;
41         
42         public string to_user = "";
43         public string to_status_id = "";
44         
45         public bool is_retweet = false;
46 }
47
48 public struct AuthData {
49         public string login = "";
50         public string password = "";
51         public string service = "";
52
53
54 errordomain RestError {
55         CODE
56 }
57
58 errordomain ParseError {
59         CODE
60 }
61
62 public static enum TimelineType {
63         HOME,
64         MENTIONS
65 }
66
67 public abstract class RestAPIAbstract : Object {
68         
69         protected IRestUrls urls;
70         public Account? account;
71         
72         public RestAPIAbstract(Account? _account) {
73                 set_auth(_account);
74         }
75         
76         private IRestUrls select_urls(string service) {
77                 switch(service) {
78                         case "twitter.com":
79                                 return new TwitterUrls();
80                         
81                         case "identi.ca":
82                                 return new IdenticaUrls();
83                         
84                         default:
85                                 return new TwitterUrls();
86                 }
87         }
88         
89         public void set_auth(Account? _account) {
90                 account = _account;
91                 
92                 if(account != null)
93                         urls = select_urls(account.service);
94         }
95         
96         public signal void request(string req);
97         
98         public virtual ArrayList<Status>? get_timeline(int count = 0,
99                 string since_id = "", string max_id = "") throws RestError, ParseError {
100                 
101                 return null;
102         }
103         
104         public virtual Status get_status(string id) throws RestError, ParseError {
105                 return new Status();
106         }
107         
108         public virtual void destroy_status(string id) throws RestError {}
109         
110         protected void reply_tracking(int status_code) throws RestError {
111                 switch(status_code) {
112                         case 2:
113                                 throw new RestError.CODE("Connection problems: can't connect to the server.");
114                         
115                         case 401:
116                                 throw new RestError.CODE("%d Unauthorized: the request requires user authentication.".printf(status_code));
117                         
118                         case 403:
119                                 throw new RestError.CODE("%d Forbidden: the server understood the request, but is refusing to fulfill it.".printf(status_code));
120                         
121                         case 404:
122                                 throw new RestError.CODE("%d Not Found: The server has not found anything matching the Request-URI.".printf(status_code));
123                         
124                         case 407:
125                                 throw new RestError.CODE("%d Proxy Authentication Required: the request requires user authentication.".printf(status_code));
126                         
127                         default:
128                                 throw new RestError.CODE("%d Unknown Error".printf(status_code));
129                 }
130         }
131         
132         protected void no_account() throws RestError {
133                 throw new RestError.CODE("Account is not found");
134         }
135         
136         public string make_request(owned string req_url, string method,
137                 HashTable<string, string> params = new HashTable<string, string>(null, null),
138                 bool async = true, int retry = 3) throws RestError {
139                 
140                 if(account == null)
141                         no_account();
142                 
143                 if(method == "GET") { //set get-parameters
144                         string query = "";
145                 
146                         if(params.size() > 0) {
147                                 query = "?";
148                                 
149                                 //Very dirty. HashTable.loockup() doesn't work. Bug?
150                                 int tmp_iter = 0;
151                                 foreach(string key in params.get_keys()) {
152                                         int tmp_iter2 = 0;
153                                         foreach(string val in params.get_values()) {
154                                                 if(tmp_iter2 == tmp_iter) {
155                                                         query += Soup.form_encode(key, val);
156                                                         if(tmp_iter < params.size() - 1)
157                                                                 query += "&";
158                                                         break;
159                                                 }
160                                                 tmp_iter2++;
161                                         }
162                                         tmp_iter++;
163                                 }
164                         }
165                         req_url += query;
166                 }
167                 
168                 //send signal about all requests
169         request("%s: %s".printf(method, req_url));
170         
171         Session session;
172         
173                 if(async)
174                         session = new SessionAsync();
175                 else
176                         session = new SessionSync();
177                 
178         Message message = new Message(method, req_url);
179         message.set_http_version (HTTPVersion.1_1);
180         
181         MessageHeaders headers = new MessageHeaders(MessageHeadersType.MULTIPART);
182         headers.append("User-Agent", "%s/%s".printf(Config.APPNAME, Config.APP_VERSION));
183         
184         message.request_headers = headers;
185         
186         if(method != "GET") { //set post/delete-parameters
187                 string body = form_encode_hash(params);
188                         message.set_request("application/x-www-form-urlencoded",
189                                 MemoryUse.COPY, body, (int)body.size());
190                 }
191                 
192                 //Basic HTTP authorization
193         session.authenticate += (sess, msg, auth, retrying) => {
194                         if (retrying) return;
195                         auth.authenticate(account.login, account.password);
196                 };
197                 
198                 int status_code = 0;
199                 for(int i = 0; i < retry; i++) {
200                         status_code = (int)session.send_message(message);
201                         if(status_code == 200 || status_code == 401)
202                                 break;
203                 }
204                 
205                 if(status_code != 200)
206                         reply_tracking(status_code);
207                 
208                 return (string)message.response_body.flatten().data;
209         }
210 }
211
212 }