@ -12,6 +12,7 @@ lunch_src = files(
|
|||||||
'source/lunch/http.d',
|
'source/lunch/http.d',
|
||||||
'source/lunch/launch.d',
|
'source/lunch/launch.d',
|
||||||
'source/lunch/logger.d',
|
'source/lunch/logger.d',
|
||||||
|
'source/lunch/term.d',
|
||||||
'source/lunch/ui.d',
|
'source/lunch/ui.d',
|
||||||
'source/lunch/update.d',
|
'source/lunch/update.d',
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,6 +1,92 @@
|
|||||||
module lunch.color;
|
module lunch.color;
|
||||||
|
|
||||||
|
|
||||||
|
string black()
|
||||||
|
{
|
||||||
|
return "\x1B[30m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string red()
|
||||||
|
{
|
||||||
|
return "\x1B[31m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string green()
|
||||||
|
{
|
||||||
|
return "\x1B[32m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string yellow()
|
||||||
|
{
|
||||||
|
return "\x1B[33m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string blue()
|
||||||
|
{
|
||||||
|
return "\x1B[34m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string magenta()
|
||||||
|
{
|
||||||
|
return "\x1B[35m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string cyan()
|
||||||
|
{
|
||||||
|
return "\x1B[36m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string white()
|
||||||
|
{
|
||||||
|
return "\x1B[37m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string brightBlack()
|
||||||
|
{
|
||||||
|
return "\x1B[90m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string brightRed()
|
||||||
|
{
|
||||||
|
return "\x1B[91m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string brightGreen()
|
||||||
|
{
|
||||||
|
return "\x1B[92m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string brightYellow()
|
||||||
|
{
|
||||||
|
return "\x1B[93m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string brightBlue()
|
||||||
|
{
|
||||||
|
return "\x1B[94m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string brightMagenta()
|
||||||
|
{
|
||||||
|
return "\x1B[95m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string brightCyan()
|
||||||
|
{
|
||||||
|
return "\x1B[96m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string brightWhite()
|
||||||
|
{
|
||||||
|
return "\x1B[97m";
|
||||||
|
}
|
||||||
|
|
||||||
|
string reset()
|
||||||
|
{
|
||||||
|
return "\x1B[0m";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const(char)[] black(const(char)[] str)
|
const(char)[] black(const(char)[] str)
|
||||||
{
|
{
|
||||||
return "\x1B[30m" ~ str ~ "\x1B[0m";
|
return "\x1B[30m" ~ str ~ "\x1B[0m";
|
||||||
|
|||||||
@ -82,7 +82,7 @@ T[] put(Conn = AutoProtocol, T = char, PutUnit)(const(char)[] url, const(PutUnit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _download(const(char)[] url, string file)
|
private void _download(const(char)[] url, string file, void delegate(size_t size) progress)
|
||||||
{
|
{
|
||||||
scope (failure)
|
scope (failure)
|
||||||
if (exists(file))
|
if (exists(file))
|
||||||
@ -94,16 +94,18 @@ private void _download(const(char)[] url, string file)
|
|||||||
http.onReceive = (ubyte[] data)
|
http.onReceive = (ubyte[] data)
|
||||||
{
|
{
|
||||||
handle.rawWrite(data);
|
handle.rawWrite(data);
|
||||||
|
if (progress)
|
||||||
|
progress(data.length);
|
||||||
return data.length;
|
return data.length;
|
||||||
};
|
};
|
||||||
http.perform();
|
http.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
void download(const(char)[] url, string file)
|
void download(const(char)[] url, string file, void delegate(size_t size) progress = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_retry!_download(url, file);
|
_retry!_download(url, file, progress);
|
||||||
infof("DL %s", url);
|
infof("DL %s", url);
|
||||||
}
|
}
|
||||||
catch (CurlException ex)
|
catch (CurlException ex)
|
||||||
|
|||||||
39
source/lunch/term.d
Normal file
39
source/lunch/term.d
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
module lunch.term;
|
||||||
|
|
||||||
|
version (Windows)
|
||||||
|
{
|
||||||
|
import core.sys.windows.windows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct TermSize
|
||||||
|
{
|
||||||
|
int width, height;
|
||||||
|
}
|
||||||
|
|
||||||
|
TermSize termSize()
|
||||||
|
{
|
||||||
|
version (Windows)
|
||||||
|
{
|
||||||
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
||||||
|
int columns, rows;
|
||||||
|
|
||||||
|
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
|
||||||
|
return TermSize(
|
||||||
|
csbi.srWindow.Right - csbi.srWindow.Left + 1,
|
||||||
|
csbi.srWindow.Bottom - csbi.srWindow.Top + 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TermSize(80, 25); // Default for windows cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
int termWidth()
|
||||||
|
{
|
||||||
|
return termSize().width;
|
||||||
|
}
|
||||||
|
|
||||||
|
int termHeight()
|
||||||
|
{
|
||||||
|
return termSize().height;
|
||||||
|
}
|
||||||
@ -1,24 +1,27 @@
|
|||||||
module lunch.update;
|
module lunch.update;
|
||||||
|
|
||||||
import std.datetime;
|
import std.datetime;
|
||||||
import std.stdio : writefln, File;
|
import std.stdio;
|
||||||
import std.traits;
|
import std.traits;
|
||||||
import std.json;
|
import std.json;
|
||||||
import std.conv;
|
import std.conv;
|
||||||
import std.file;
|
import std.file : dirEntries;
|
||||||
import std.path;
|
import std.path;
|
||||||
import std.file;
|
import std.file;
|
||||||
import std.math;
|
import std.math;
|
||||||
import std.string;
|
import std.string;
|
||||||
import std.digest.sha;
|
import std.digest.sha;
|
||||||
import std.array;
|
import std.array;
|
||||||
|
import std.format;
|
||||||
import std.algorithm;
|
import std.algorithm;
|
||||||
import std.parallelism;
|
import std.parallelism;
|
||||||
|
import std.datetime.stopwatch;
|
||||||
import lunch.conf;
|
import lunch.conf;
|
||||||
|
import lunch.term;
|
||||||
|
import lunch.color;
|
||||||
import lunch.http;
|
import lunch.http;
|
||||||
import lunch.logger;
|
import lunch.logger;
|
||||||
|
|
||||||
|
|
||||||
private struct RemoteFile
|
private struct RemoteFile
|
||||||
{
|
{
|
||||||
string file;
|
string file;
|
||||||
@ -87,6 +90,7 @@ private struct Action
|
|||||||
private struct Actions
|
private struct Actions
|
||||||
{
|
{
|
||||||
Action[] actions;
|
Action[] actions;
|
||||||
|
ulong total_download_size;
|
||||||
|
|
||||||
alias this = actions;
|
alias this = actions;
|
||||||
}
|
}
|
||||||
@ -100,7 +104,6 @@ private immutable(Local) _local;
|
|||||||
private bool _actions_set = false;
|
private bool _actions_set = false;
|
||||||
private immutable(Actions) _actions;
|
private immutable(Actions) _actions;
|
||||||
|
|
||||||
|
|
||||||
private JSONValue[string] safe_object(JSONValue value) @trusted
|
private JSONValue[string] safe_object(JSONValue value) @trusted
|
||||||
{
|
{
|
||||||
if (value.type != JSONType.object)
|
if (value.type != JSONType.object)
|
||||||
@ -221,13 +224,11 @@ void wrapJSON(T)(ref T wrapper, JSONValue[string] json, bool ignore_missing = fa
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private string workdir(string path)
|
private string workdir(string path)
|
||||||
{
|
{
|
||||||
return chainPath(config.updater.workdir, path).array;
|
return chainPath(config.updater.workdir, path).array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void touch(string path, long mtime = Clock.currTime().toUnixTime)
|
private void touch(string path, long mtime = Clock.currTime().toUnixTime)
|
||||||
{
|
{
|
||||||
infof("Touching %s", path);
|
infof("Touching %s", path);
|
||||||
@ -236,7 +237,6 @@ private void touch(string path, long mtime = Clock.currTime().toUnixTime)
|
|||||||
setTimes(path, old_atime, SysTime.fromUnixTime(mtime));
|
setTimes(path, old_atime, SysTime.fromUnixTime(mtime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private immutable(Remote) remote()
|
private immutable(Remote) remote()
|
||||||
{
|
{
|
||||||
if (_remote_set)
|
if (_remote_set)
|
||||||
@ -263,7 +263,6 @@ private immutable(Remote) remote()
|
|||||||
return _remote;
|
return _remote;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private immutable(Local) local()
|
private immutable(Local) local()
|
||||||
{
|
{
|
||||||
if (_local_set)
|
if (_local_set)
|
||||||
@ -302,7 +301,6 @@ private immutable(Local) local()
|
|||||||
return _local;
|
return _local;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private immutable(Actions) actions()
|
private immutable(Actions) actions()
|
||||||
{
|
{
|
||||||
if (_actions_set)
|
if (_actions_set)
|
||||||
@ -346,6 +344,7 @@ private immutable(Actions) actions()
|
|||||||
if (!this_loc)
|
if (!this_loc)
|
||||||
{
|
{
|
||||||
act ~= this_act;
|
act ~= this_act;
|
||||||
|
act.total_download_size += this_rem.size;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,6 +355,7 @@ private immutable(Actions) actions()
|
|||||||
if (this_loc.hash != this_rem.hash || this_loc.size != this_rem.size)
|
if (this_loc.hash != this_rem.hash || this_loc.size != this_rem.size)
|
||||||
{
|
{
|
||||||
act ~= this_act;
|
act ~= this_act;
|
||||||
|
act.total_download_size += this_rem.size;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -378,7 +378,6 @@ private immutable(Actions) actions()
|
|||||||
return _actions;
|
return _actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public bool updateAvailable()
|
public bool updateAvailable()
|
||||||
{
|
{
|
||||||
if (actions.length != 0)
|
if (actions.length != 0)
|
||||||
@ -391,16 +390,77 @@ public void update()
|
|||||||
{
|
{
|
||||||
info("Updating");
|
info("Updating");
|
||||||
|
|
||||||
scope (exit) cast() _actions = [];
|
scope (exit) cast() _actions = Actions();
|
||||||
|
|
||||||
|
auto writeSW = StopWatch(AutoStart.yes);
|
||||||
|
auto speedSW = StopWatch(AutoStart.yes);
|
||||||
const dl_total = actions.actions.count!"a.what == b"(Action.download);
|
const dl_total = actions.actions.count!"a.what == b"(Action.download);
|
||||||
const dl_pad = cast(int)log10(cast(float)dl_total) + 1;
|
const dl_pad = cast(int)log10(cast(float)dl_total) + 1;
|
||||||
size_t dl_count = 0;
|
uint dl_count = 0;
|
||||||
|
ulong dl_size = 0;
|
||||||
|
const dl_spad = cast(int)log10(cast(float)actions.total_download_size) + 1;
|
||||||
|
ulong dl_speed = 0;
|
||||||
|
ulong dl_speed_old_size = 0;
|
||||||
|
|
||||||
const old_defaultpool = defaultPoolThreads;
|
const old_defaultpool = defaultPoolThreads;
|
||||||
scope (exit) defaultPoolThreads = old_defaultpool;
|
scope (exit) defaultPoolThreads = old_defaultpool;
|
||||||
defaultPoolThreads = config.updater.parallel_downloads.to!uint - 1;
|
defaultPoolThreads = config.updater.parallel_downloads.to!uint - 1;
|
||||||
|
|
||||||
|
auto writeProgress = delegate()
|
||||||
|
{
|
||||||
|
string numbers = format(
|
||||||
|
"[%s%*d%s|%s%d%s]",
|
||||||
|
magenta, dl_spad, dl_size, reset,
|
||||||
|
magenta, actions.total_download_size, reset,
|
||||||
|
);
|
||||||
|
|
||||||
|
int barSpace = termWidth - dl_spad*2 - 3 - 2 - 10 - 11;
|
||||||
|
float progress = dl_size.to!float / actions.total_download_size;
|
||||||
|
long filledSpace = lround(progress * barSpace);
|
||||||
|
long emptySpace = lround((1.0 - progress) * barSpace);
|
||||||
|
|
||||||
|
string progress_bar = "["~yellow;
|
||||||
|
for (int n = 0; n < filledSpace; n++)
|
||||||
|
progress_bar ~= '=';
|
||||||
|
for (int n = 0; n < emptySpace; n++)
|
||||||
|
progress_bar ~= ' ';
|
||||||
|
progress_bar ~= reset~"]";
|
||||||
|
|
||||||
|
string percent = format(
|
||||||
|
"[%7.2f%%]", progress * 100
|
||||||
|
);
|
||||||
|
|
||||||
|
int speed_rank = 0;
|
||||||
|
float speed = dl_speed.to!float;
|
||||||
|
|
||||||
|
for (speed_rank = 0; speed >= 1000; speed_rank += 1)
|
||||||
|
speed /= 1000;
|
||||||
|
|
||||||
|
string unit = [
|
||||||
|
" B",
|
||||||
|
"KB",
|
||||||
|
"MB",
|
||||||
|
"GB",
|
||||||
|
"TB",
|
||||||
|
"PB",
|
||||||
|
"EB",
|
||||||
|
"ZB",
|
||||||
|
"YB",
|
||||||
|
"RB",
|
||||||
|
"QB",
|
||||||
|
][speed_rank];
|
||||||
|
|
||||||
|
string download_speed = (speed < 100)
|
||||||
|
? format(
|
||||||
|
"[%s%4.1f %s%s/s]", cyan, speed, reset, unit
|
||||||
|
)
|
||||||
|
: format(
|
||||||
|
"[%s%5.1f%s%s/s]", cyan, speed, reset, unit
|
||||||
|
);
|
||||||
|
|
||||||
|
writef("\r%s%s%s%s\b", numbers, download_speed, progress_bar, percent);
|
||||||
|
};
|
||||||
|
|
||||||
foreach (action; parallel(actions.actions, 1))
|
foreach (action; parallel(actions.actions, 1))
|
||||||
final switch(action.what)
|
final switch(action.what)
|
||||||
{
|
{
|
||||||
@ -409,13 +469,36 @@ public void update()
|
|||||||
remove(action.file);
|
remove(action.file);
|
||||||
break;
|
break;
|
||||||
case Action.download:
|
case Action.download:
|
||||||
|
info("Update %s", action.file);
|
||||||
mkdirRecurse(dirName(action.file));
|
mkdirRecurse(dirName(action.file));
|
||||||
|
|
||||||
|
download(action.url, action.file, (size_t amount){
|
||||||
synchronized
|
synchronized
|
||||||
{
|
{
|
||||||
dl_count += 1;
|
dl_size += amount;
|
||||||
writefln("[%*d|%*d] %s", dl_pad, dl_count, dl_pad, dl_total, action.file);
|
if (speedSW.peek.total!"msecs" >= 1000)
|
||||||
|
{
|
||||||
|
long time = speedSW.peek.total!"msecs";
|
||||||
|
dl_speed = lround((dl_size - dl_speed_old_size) * time.to!float / 1000);
|
||||||
|
dl_speed_old_size = dl_size;
|
||||||
|
speedSW.reset();
|
||||||
}
|
}
|
||||||
download(action.url, action.file);
|
if (writeSW.peek.total!"msecs" < 100)
|
||||||
|
return;
|
||||||
|
writeSW.reset();
|
||||||
|
}
|
||||||
|
writeProgress();
|
||||||
|
});
|
||||||
|
|
||||||
|
synchronized dl_count += 1;
|
||||||
|
writefln(
|
||||||
|
"\33[2K\r[%s%*d%s|%s%d%s] %s",
|
||||||
|
cyan, dl_pad, dl_count, reset,
|
||||||
|
cyan, dl_total, reset,
|
||||||
|
action.file.replace("/", dirSeparator).green,
|
||||||
|
);
|
||||||
|
writeProgress();
|
||||||
|
|
||||||
touch(action.file, action.mtime);
|
touch(action.file, action.mtime);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user