diff --git a/Mac/Controller.m b/Mac/Controller.m index 9612ed0..a863202 100644 --- a/Mac/Controller.m +++ b/Mac/Controller.m @@ -92,6 +92,9 @@ NSString *index_string = [NSString stringWithContentsOfFile:[NSString stringWithFormat:@"%@index.html", path] encoding:NSUTF8StringEncoding error:nil]; oauthView = [[WebView alloc] init]; + WebPreferences* prefs = [oauthView preferences]; + [prefs _setLocalStorageDatabasePath:@"~/Library/Application Support/Tentia"]; + [prefs setLocalStorageEnabled:YES]; viewDelegate.oauthView = oauthView; [[oauthView mainFrame] loadHTMLString:index_string baseURL:url]; [oauthView setFrameLoadDelegate:viewDelegate]; @@ -110,6 +113,8 @@ if (YES) //viewDelegate.timelineView != timelineView) { + NSString *localStoragePath = @"~/Library/Application Support/Tentia"; + NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/Webkit/"]; NSURL *url = [NSURL fileURLWithPath:path]; NSString *index_string = [NSString stringWithContentsOfFile:[NSString stringWithFormat:@"%@index.html", path] encoding:NSUTF8StringEncoding error:nil]; @@ -120,6 +125,9 @@ [timelineView setPolicyDelegate:viewDelegate]; [timelineView setUIDelegate:viewDelegate]; [[timelineView windowScriptObject] setValue:self forKey:@"controller"]; + WebPreferences* prefs = [timelineView preferences]; + [prefs _setLocalStorageDatabasePath:localStoragePath]; + [prefs setLocalStorageEnabled:YES]; viewDelegate.mentionsView = mentionsView; [[mentionsView mainFrame] loadHTMLString:index_string baseURL:url]; @@ -127,7 +135,9 @@ [mentionsView setPolicyDelegate:viewDelegate]; [mentionsView setUIDelegate:viewDelegate]; [[mentionsView windowScriptObject] setValue:self forKey:@"controller"]; - + prefs = [mentionsView preferences]; + [prefs _setLocalStorageDatabasePath:localStoragePath]; + [prefs setLocalStorageEnabled:YES]; viewDelegate.conversationView = conversationView; [[conversationView mainFrame] loadHTMLString:index_string baseURL:url]; @@ -135,6 +145,9 @@ [conversationView setPolicyDelegate:viewDelegate]; [conversationView setUIDelegate:viewDelegate]; [[conversationView windowScriptObject] setValue:self forKey:@"controller"]; + prefs = [conversationView preferences]; + [prefs _setLocalStorageDatabasePath:localStoragePath]; + [prefs setLocalStorageEnabled:YES]; viewDelegate.profileView = profileView; [[profileView mainFrame] loadHTMLString:index_string baseURL:url]; @@ -142,6 +155,10 @@ [profileView setPolicyDelegate:viewDelegate]; [profileView setUIDelegate:viewDelegate]; [[profileView windowScriptObject] setValue:self forKey:@"controller"]; + prefs = [profileView preferences]; + [prefs _setLocalStorageDatabasePath:localStoragePath]; + [prefs setLocalStorageEnabled:YES]; + } else { diff --git a/WebKit/scripts/controller/Conversation.js b/WebKit/scripts/controller/Conversation.js index bb99b0e..ca6a040 100644 --- a/WebKit/scripts/controller/Conversation.js +++ b/WebKit/scripts/controller/Conversation.js @@ -2,10 +2,11 @@ define([ "helper/HostApp", "helper/Core", "helper/Paths", - "lib/URI" + "lib/URI", + "helper/Cache" ], -function(HostApp, Core, Paths, URI) { +function(HostApp, Core, Paths, URI, Cache) { function Conversation() { @@ -66,15 +67,16 @@ function(HostApp, Core, Paths, URI) { Paths.getURL(URI(server + "/posts/" + id).toString(), "GET", callback, null, false); } + var profile = JSON.parse(Cache.profiles.getItem(entity)); if (entity == HostApp.stringForKey("entity")) { var url = URI(Paths.mkApiRootPath("/posts/" + id)); Paths.getURL(url.toString(), "GET", callback, null); - } else if(this.cache.followings[entity]) { + } else if(profile) { - getRemoteStatus(this.cache.followings[entity].profile); + getRemoteStatus(profile); } else { @@ -82,11 +84,20 @@ function(HostApp, Core, Paths, URI) { if (profile_url) { - Paths.getURL(profile_url, "GET", function(resp) { + var profile = JSON.parse(Cache.profiles.getItem(entity)); + if (profile) { - getRemoteStatus(JSON.parse(resp.responseText)); + getRemoteStatus(profile); - }, null, false); // do not send auth-headers + } else { + + Paths.getURL(profile_url, "GET", function(resp) { + + Cache.profiles.setItem(entity, resp.responseText); + getRemoteStatus(JSON.parse(resp.responseText)); + + }, null, false); // do not send auth-headers + } } }); } diff --git a/WebKit/scripts/controller/Mentions.js b/WebKit/scripts/controller/Mentions.js index 20ca384..ad7066f 100644 --- a/WebKit/scripts/controller/Mentions.js +++ b/WebKit/scripts/controller/Mentions.js @@ -1,9 +1,10 @@ define([ "helper/HostApp", - "controller/Timeline" + "controller/Timeline", + "helper/Cache" ], -function(HostApp, Timeline) { +function(HostApp, Timeline, Cache) { function Mentions() { @@ -33,8 +34,9 @@ function(HostApp, Timeline) { var status = statuses[i]; var name; - if(this.cache.followings[status.entity]) { - name = this.cache.followings[status.entity].profile["https://tent.io/types/info/basic/v0.1.0"].name; + var profile = JSON.parse(Cache.profiles.getItem(status.entity)); + if(profile) { + name = profile["https://tent.io/types/info/basic/v0.1.0"].name; } HostApp.notificateUserAboutMention(status.content.text, name || status.entity, status.id, status.entity); diff --git a/WebKit/scripts/controller/Profile.js b/WebKit/scripts/controller/Profile.js index 74591bb..cbb0df6 100644 --- a/WebKit/scripts/controller/Profile.js +++ b/WebKit/scripts/controller/Profile.js @@ -2,10 +2,11 @@ define([ "helper/HostApp", "helper/Core", "helper/Paths", - "lib/URI" + "lib/URI", + "helper/Cache" ], -function(HostApp, Core, Paths, URI) { +function(HostApp, Core, Paths, URI, Cache) { function Profile() { @@ -15,6 +16,8 @@ function(HostApp, Core, Paths, URI) { this.action = "profile"; this.initProfileTemplate(); + + //setTimeout(Cache.getFollowings, 1000 * 60 * 5); } Profile.prototype = Object.create(Core.prototype); @@ -26,7 +29,7 @@ function(HostApp, Core, Paths, URI) { this.profile_template.entity.innerHTML = this.entity; this.profile_template.entity.href = this.entity; - this.setFollowingButton(!!this.cache.followings[this.entity]); + this.setFollowingButton(!!Cache.followings.getItem(this.entity)); this.getProfile(); } @@ -153,17 +156,25 @@ function(HostApp, Core, Paths, URI) { this.profile_template.following_button.style.display = "none"; } - Paths.findProfileURL(this.entity, function(profile_url) { + var profile = JSON.parse(Cache.profiles.getItem(this.entity)); + if (profile && profile != "null") { - if (profile_url) { + this.showProfile(profile); - Paths.getURL(profile_url, "GET", function(resp) { + } else { + Paths.findProfileURL(this.entity, function(profile_url) { - _this.showProfile(JSON.parse(resp.responseText)); + if (profile_url) { - }, null, false); // do not send auth-headers - } - }); + Paths.getURL(profile_url, "GET", function(resp) { + + _this.showProfile(JSON.parse(resp.responseText)); + + }, null, false); // do not send auth-headers + } + }); + + } } Profile.prototype.showProfile = function(profile) { @@ -313,21 +324,26 @@ function(HostApp, Core, Paths, URI) { Profile.prototype.toggleFollow = function() { var _this = this; - var callback = function(resp) { _this.cache.getAllFollowings(); debug(resp.responseText) }; - if (this.cache.followings[this.entity]) { - - var url = URI(Paths.mkApiRootPath("/followings/" + this.cache.followings[this.entity].id)); - Paths.getURL(url.toString(), "DELETE", callback); - this.setFollowingButton(false); - delete this.cache.followings[this.entity]; + var following = Cache.followings.getItem(this.entity); + if (following) { + var url = URI(Paths.mkApiRootPath("/followings/" + following.id)); + Paths.getURL(url.toString(), "DELETE", function(resp) { + if (resp.status >= 200 && resp.status < 300) { + Cache.followings.removeItem(_this.entity); + _this.setFollowingButton(false); + } + }); } else { var url = URI(Paths.mkApiRootPath("/followings")); var data = JSON.stringify({"entity": this.entity }); - Paths.getURL(url.toString(), "POST", callback, data); - this.setFollowingButton(true); + Paths.getURL(url.toString(), "POST", function(resp) { + debug(resp.responseText) + Cache.followings.setItem(_this.entity, resp.responseText); + _this.setFollowingButton(true); + }, data); } } diff --git a/WebKit/scripts/helper/Cache.js b/WebKit/scripts/helper/Cache.js index e63dae4..f83c872 100644 --- a/WebKit/scripts/helper/Cache.js +++ b/WebKit/scripts/helper/Cache.js @@ -1,28 +1,23 @@ define([ "helper/Paths", - "lib/URI" + "lib/URI", + "helper/CacheStorage" ], -function(Paths, URI) { +function(Paths, URI, CacheStorage) { - function Cache() { + var Cache = {}; - this.timeout = 2 * 60 * 1000; - this.before_id = null; + var timeout = 2 * 60 * 1000; + var followings_before_id = null; - this.followings = {}; - - var _this = this; - this.intervall = setInterval(function() { _this.getAllFollowings(); }, this.timeout); + Cache.followings = new CacheStorage("followings"); + Cache.profiles = new CacheStorage("profiles"); + Cache.profile_urls = new CacheStorage("profile_urls"); - this.getAllFollowings(); - } - Cache.prototype.getAllFollowings = function() { - - var _this = this; - - var callback = function(resp) { + Cache.getFollowings = function() { + function callback(resp) { var fs = JSON.parse(resp.responseText) @@ -31,18 +26,24 @@ function(Paths, URI) { for (var i = 0; i < fs.length; i++) { var following = fs[i]; - _this.before_id = following.id; - _this.followings[following.entity] = following; + followings_before_id = following.id; + + Cache.followings.setItem(following.entity, following) + Cache.profiles.setItem(following.entity, following); } } - var url = URI(Paths.mkApiRootPath("/followings")); - if (this.before_id) { - url.addSearch("before_id", this.before_id); + var u = Paths.mkApiRootPath("/followings"); + + var url = URI(u); + if (followings_before_id) { + url.addSearch("before_id", followings_before_id); } Paths.getURL(url, "GET", callback); } + + // setTimeout(function(){ Cache.getAllFollowings() }, timeout); return Cache; diff --git a/WebKit/scripts/helper/CacheStorage.js b/WebKit/scripts/helper/CacheStorage.js new file mode 100644 index 0000000..76f2dae --- /dev/null +++ b/WebKit/scripts/helper/CacheStorage.js @@ -0,0 +1,64 @@ +define([ +], + +function() { + + function CacheStorage(name) { + this.name = name; + } + + CacheStorage.prototype.mkPath = function(key) { + return this.mkInternalPath("") + "-" + key; + } + + CacheStorage.prototype.mkInternalPath = function(key) { + return "tentia-cache-" + this.name + key; + }; + + CacheStorage.prototype.getItem = function(key) { + return localStorage.getItem(this.mkPath(key)); + } + + CacheStorage.prototype.setItem = function(key, value) { + var item = this.getItem(key); + + localStorage.setItem(this.mkPath(key), value); + + if (!item) { + var length_path = this.mkInternalPath("_length"); + var length = parseInt(localStorage.getItem(length_path), 10) + 1; + localStorage.setItem(length_path, length); + } + } + + CacheStorage.prototype.removeItem = function(key) { + + var item = this.getItem(key); + + localStorage.removeItem(this.mkPath(key)); + + if (item) { + var length_path = this.mkInternalPath("_length"); + var length = parseInt(localStorage.getItem(length_path), 10) - 1; + localStorage.setItem(length_path, length); + } + }; + + CacheStorage.prototype.clear = function() { + for (var i = 0; i < localStorage.length; i++) { + var key = localStorage.key(i); + if (key.startsWith(this.mkPath(""))) { + localStorage.removeItem(key); + } + } + + localStorage.setItem(this.mkInternalPath("_length"), 0); + } + + CacheStorage.prototype.length = function() { + return parseInt(localStorage.getItem(this.mkInternalPath("_length")), 10); + } + + return CacheStorage; + +}); \ No newline at end of file diff --git a/WebKit/scripts/helper/Core.js b/WebKit/scripts/helper/Core.js index d55e24e..1c9f8e9 100644 --- a/WebKit/scripts/helper/Core.js +++ b/WebKit/scripts/helper/Core.js @@ -11,8 +11,6 @@ function(jQuery, Paths, URI, HostApp, Cache) { function Core() { - this.cache = new Cache(); - } Core.prototype.getTemplate = function() { @@ -184,17 +182,17 @@ function(jQuery, Paths, URI, HostApp, Cache) { } template.username.innerText = status.entity; - template.username.href = status.entity; // FIXME open profile + template.username.href = status.entity; template.username.onclick = function() { HostApp.showProfileForEntity(status.entity); return false; } - var profile = function(profile) { + function profile(p) { - var basic = profile["https://tent.io/types/info/basic/v0.1.0"]; + var basic = p["https://tent.io/types/info/basic/v0.1.0"]; - if (profile && basic) { + if (p && basic) { if(basic.name) { template.username.title = template.username.innerText; template.username.innerText = basic.name; @@ -207,17 +205,25 @@ function(jQuery, Paths, URI, HostApp, Cache) { } - if (this.cache.followings[status.entity]) { + var p = JSON.parse(Cache.profiles.getItem(status.entity)); - profile(this.cache.followings[status.entity].profile); + if (p && p != "null") { + profile(p); } else { Paths.findProfileURL(status.entity, function(profile_url) { + if (profile_url) { + Paths.getURL(profile_url, "GET", function(resp) { + var p = JSON.parse(resp.responseText); - profile(p) + if (p && p != "null") { + Cache.profiles.setItem(status.entity, resp.responseText); + profile(p); + } + }, null, false); // do not send auth-headers } }); @@ -234,15 +240,20 @@ function(jQuery, Paths, URI, HostApp, Cache) { return false; } - if (this.cache.followings[status.__repost.entity]) { + var profile = JSON.parse(Cache.profiles.getItem(status.__repost.entity)) + if (profile) { - var basic = this.cache.followings[status.__repost.entity].profile["https://tent.io/types/info/basic/v0.1.0"]; + var basic = profile["https://tent.io/types/info/basic/v0.1.0"]; template.reposted_by.innerText = basic.name; + } else { Paths.findProfileURL(status.__repost.entity, function(profile_url) { if (profile_url) { Paths.getURL(profile_url, "GET", function(resp) { + + Cache.profiles.setItem(status.__repost.entity, resp.responseText); + var p = JSON.parse(resp.responseText); var profile = p["https://tent.io/types/info/basic/v0.1.0"]; if (profile && profile.name) { @@ -350,24 +361,32 @@ function(jQuery, Paths, URI, HostApp, Cache) { var _this = this; var callback = function(resp) { - var status = JSON.parse(resp.responseText); - status.__repost = repost; - var li = _this.getStatusDOMElement(status); - before_node.parentNode.insertBefore(li, before_node); + if (resp.status >= 200 && resp.status < 300) { + var status = JSON.parse(resp.responseText); + status.__repost = repost; + var li = _this.getStatusDOMElement(status); + before_node.parentNode.insertBefore(li, before_node); + } } - Paths.findProfileURL(repost.content.entity, function(profile_url) { - if (profile_url) { + var profile = JSON.parse(Cache.profiles.getItem(repost.content.entity)); + if (profile && profile != "null") { + var server = profile["https://tent.io/types/info/core/v0.1.0"].servers[0]; + Paths.getURL(URI(server + "/posts/" + repost.content.id).toString(), "GET", callback, null, false); + } else { + Paths.findProfileURL(repost.content.entity, function(profile_url) { + if (profile_url) { - Paths.getURL(profile_url, "GET", function(resp) { + Paths.getURL(profile_url, "GET", function(resp) { - var profile = JSON.parse(resp.responseText); - var server = profile["https://tent.io/types/info/core/v0.1.0"].servers[0]; - Paths.getURL(URI(server + "/posts/" + repost.content.id).toString(), "GET", callback, null, false); + var profile = JSON.parse(resp.responseText); + var server = profile["https://tent.io/types/info/core/v0.1.0"].servers[0]; + Paths.getURL(URI(server + "/posts/" + repost.content.id).toString(), "GET", callback, null, false); - }, null, false); // do not send auth-headers + }, null, false); // do not send auth-headers } - }) + }) + } } Core.prototype.sendNewMessage = function(content, in_reply_to_status_id, in_reply_to_entity, location, image_file_path, callback) { @@ -583,15 +602,19 @@ function(jQuery, Paths, URI, HostApp, Cache) { } } - if (_this.cache.followings[mention.entity]) { + var p = JSON.parse(Cache.profiles.getItem(mention.entity)); + if (p) { - profile(_this.cache.followings[mention.entity].profile) + profile(p); } else { Paths.findProfileURL(mention.entity, function(profile_url) { if (profile_url) { Paths.getURL(profile_url, "GET", function(resp) { + + Cache.profiles.setItem(mention.entity, resp.responseText); + var p = JSON.parse(resp.responseText); profile(p) }, null, false); // do not send auth-headers diff --git a/WebKit/scripts/helper/Paths.js b/WebKit/scripts/helper/Paths.js index 83d4824..8b337f8 100644 --- a/WebKit/scripts/helper/Paths.js +++ b/WebKit/scripts/helper/Paths.js @@ -1,10 +1,11 @@ define([ "jquery", "helper/HostApp", - "helper/Hmac" + "helper/Hmac", + "helper/Cache" ], -function(jQuery, HostApp, Hmac) { +function(jQuery, HostApp, Hmac, Cache) { var Paths = {}; Paths.getUrlVars = function(url) { @@ -102,39 +103,51 @@ function(jQuery, HostApp, Hmac) { } Paths.findProfileURL = function(entity, callback, errorCallback) { - - jQuery.ajax({ - url: entity, - type: "HEAD", - complete: function(resp) { - if(resp) { - var headers = resp.getAllResponseHeaders(); - var regex = /Link: <([^>]*)>; rel="https:\/\/tent.io\/rels\/profile"/; // FIXME: parse it! - var ret = headers.match(regex); - var profile_url = null; - if(ret && ret.length > 1) { - var profile_url = ret[1]; - if (profile_url == "/profile") { - profile_url = entity + "/profile"; + + var profile_url = Cache.profile_urls.getItem(entity); + + if (profile_url && profile_url != "null") { + + callback(profile_url); + + } else { + + jQuery.ajax({ + url: entity, + type: "HEAD", + complete: function(resp) { + if(resp) { + var headers = resp.getAllResponseHeaders(); + + var profile_urls = Paths.parseHeaderForProfiles(headers); + var profile_url = null; + if(profile_urls.length > 0) { + var profile_url = profile_urls[0]; + if (!profile_url.startsWith("http")) { + profile_url = entity + "/profile"; + } + } + + if (profile_url) { + Cache.profile_urls.setItem(entity, profile_url); + callback(profile_url); + } else { + if(errorCallback) errorCallback(entity + " has no profile URL"); } } - - if (profile_url) { - callback(profile_url); - } else { - if(errorCallback) errorCallback(entity + " has no profile URL"); - } + }, + error: function(xhr, ajaxOptions, thrownError) { + console.error("findProfileURL " + xhr.statusText + " (" + entity + "): " + xhr.responseText); + if (errorCallback) errorCallback(xhr.statusText + " - " + xhr.responseText) } - }, - error: function(xhr, ajaxOptions, thrownError) { - console.error("findProfileURL " + xhr.statusText + " (" + entity + "): " + xhr.responseText); - if (errorCallback) errorCallback(xhr.statusText + " - " + xhr.responseText) - } - }); + }); + } } Paths.mkApiRootPath = function(path) { + var api_root = HostApp.stringForKey("api_root"); + if((api_root.substring(api_root.length - 1, api_root.length) != "/") && (path.substring(0, 1) != "/")) { api_root += "/"; } else if((api_root.substring(api_root.length - 1, api_root.length) == "/") && (path.substring(0, 1) == "/")) { @@ -143,5 +156,33 @@ function(jQuery, HostApp, Hmac) { return api_root + path; } + Paths.parseHeaderForProfiles = function(header_string) { + var headers = header_string.split(/\n/); + var links = []; + for (var i = 0; i < headers.length; i++) { + var header = headers[i]; + if (header.match(/^Link:(.*)/i)) { + links.push(header.replace(/\r/, "").substr(5).trim()); + } + } + + var items = []; + for (var i = 0; i < links.length; i++) { + items = items.concat(links[i].split(",")); + } + var profiles = []; + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (item.match(/https:\/\/tent.io\/rels\/profile/i)) { + var n = item.match(/<([^>]*)>/); + if (n) { + profiles.push(n[1]); + } + } + } + + return profiles; + } + return Paths; }); \ No newline at end of file