Skip to content

Commit

Permalink
[feature/ISSUE-28] Cache initial HTML page retrieval
Browse files Browse the repository at this point in the history
  • Loading branch information
azihassan committed Oct 9, 2023
1 parent 8b955fa commit 658cde3
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 1 deletion.
77 changes: 77 additions & 0 deletions source/cache.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import std.base64 : Base64URL;
import std.conv : to;
import std.datetime : SysTime, Clock;
import std.file : exists, getcwd, readText, tempDir, write;
import std.net.curl : get;
import std.path : buildPath;
import std.string : indexOf;

import helpers : StdoutLogger, parseID, parseQueryString;
import parsers : makeParser, YoutubeVideoURLExtractor;

struct Cache
{
private StdoutLogger logger;
private string delegate(string url) downloadAsString;
string cacheDirectory;

this(StdoutLogger logger)
{
this.logger = logger;
downloadAsString = (string url) => url.get().idup;
cacheDirectory = tempDir();
}

this(StdoutLogger logger, string delegate(string url) downloadAsString)
{
this(logger);
this.downloadAsString = downloadAsString;
}

string getHTML(string url, int itag)
{
string cacheKey = url.parseID();
if(cacheKey == "")
{
cacheKey = Base64URL.encode(cast(ubyte[]) url);
}

string cachePath = buildPath(cacheDirectory, cacheKey) ~ ".html";
updateCache(url, cachePath, itag);
return cachePath.readText();
}

private void updateCache(string url, string cachePath, int itag)
{
string cachedHTML = cachePath.readText();
if(!cachePath.exists() || isStale(cachedHTML, itag))
{
string html = this.downloadAsString(url);
cachePath.write(html);
}
}

private bool isStale(string html, int itag)
{
YoutubeVideoURLExtractor parser = makeParser(html, logger);
string videoURL = parser.getURL(itag);
int expire = videoURL.parseQueryString()["expire"].to!int;
return Clock.currTime() < SysTime.fromUnixTime(expire);
}
}

unittest
{
import std.stdio : writeln;
writeln("When cache is stale, should redownload HTML");
bool downloadAttempted;
auto downloadAsString = delegate string(string url) {
downloadAttempted = true;
return "";
};
auto cache = Cache(new StdoutLogger(), downloadAsString);
cache.cacheDirectory = getcwd();

string html = cache.getHTML("https://youtu.be/dQw4w9WgXcQ", 18);
assert(downloadAttempted);
}
34 changes: 33 additions & 1 deletion source/helpers.d
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import std.regex : ctRegex, matchFirst, escaper, regex, Captures;
import std.algorithm : filter;
import std.conv : to;
import std.net.curl : HTTP;
import std.string : split;
import std.string : split, indexOf, startsWith;
import std.format : formattedRead;

ulong getContentLength(string url)
{
Expand Down Expand Up @@ -158,3 +159,34 @@ string formatTitle(string input)
{
return "\033[1m" ~ input ~ "\033[0m";
}

string parseID(string url)
{
string id;
if(!url.startsWith("https://"))
{
url = "https://" ~ url;
}

if(url.indexOf("?v=") != -1)
{
return url[url.indexOf("?") + 1 .. $].parseQueryString()["v"];
}
if(url.indexOf("youtu.be") != -1)
{
url.formattedRead!"https://youtu.be/%s"(id);
}
if(url.indexOf("shorts") != -1)
{
url.formattedRead!"https://www.youtube.com/shorts/%s"(id);
}
return id;
}

unittest
{
assert("https://www.youtube.com/watch?v=-H-Fno9xbE4".parseID() == "-H-Fno9xbE4");
assert("https://youtu.be/-H-Fno9xbE4".parseID() == "-H-Fno9xbE4");
assert("https://www.youtube.com/shorts/_tT2ldpZHek".parseID() == "_tT2ldpZHek");
assert("qlsdkqsldkj".parseID() == "");
}

0 comments on commit 658cde3

Please sign in to comment.