Skip to content

Commit ec1660c

Browse files
committed
Date, build and log
1 parent 641ef80 commit ec1660c

File tree

3 files changed

+125
-111
lines changed

3 files changed

+125
-111
lines changed

build/jsroot.js

Lines changed: 123 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const version_id = 'dev',
1414

1515
/** @summary version date
1616
* @desc Release date in format day/month/year like '14/04/2022' */
17-
version_date = '12/11/2025',
17+
version_date = '24/11/2025',
1818

1919
/** @summary version id and date
2020
* @desc Produced by concatenation of {@link version_id} and {@link version_date}
@@ -80315,32 +80315,42 @@ class StandaloneMenu extends JSRootMenu {
8031580315
select(`#${dlg_id}`).remove();
8031680316
select(`#${dlg_id}_block`).remove();
8031780317

80318-
const w = Math.min(args.width || 450, Math.round(0.9 * browser.screenWidth));
80319-
modal.block = select('body').append('div')
80320-
.attr('id', `${dlg_id}_block`)
80321-
.attr('class', 'jsroot_dialog_block')
80322-
.attr('style', 'z-index: 100000; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; opacity: 0.2; background-color: white');
80323-
modal.element = select('body')
80324-
.append('div')
80325-
.attr('id', dlg_id)
80326-
.attr('class', 'jsroot_dialog')
80327-
.style('position', 'absolute')
80328-
.style('width', `${w}px`)
80329-
.style('left', '50%')
80330-
.style('top', '50%')
80331-
.style('z-index', 100001)
80332-
.attr('tabindex', '0');
80318+
const w = Math.min(args.width || 450, Math.round(0.9 * browser.screenWidth)),
80319+
b = select('body');
80320+
modal.block = b.append('div')
80321+
.attr('id', `${dlg_id}_block`)
80322+
.attr('class', 'jsroot_dialog_block')
80323+
.attr('style', 'z-index: 100000; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; opacity: 0.2; background-color: white');
80324+
modal.element = b.append('div')
80325+
.attr('id', dlg_id)
80326+
.attr('class', 'jsroot_dialog')
80327+
.style('position', 'absolute')
80328+
.style('width', `${w}px`)
80329+
.style('left', '50%')
80330+
.style('top', '50%')
80331+
.style('z-index', 100001)
80332+
.attr('tabindex', '0');
8033380333

8033480334
modal.element.html(
8033580335
'<div style=\'position: relative; left: -50%; top: -50%; border: solid green 3px; padding: 5px; display: flex; flex-flow: column; background-color: white\'>' +
80336-
`<div style='flex: 0 1 auto; padding: 5px'>${title}</div>` +
80336+
`<div style='flex: 0 1 auto; padding: 5px; cursor: pointer;' class='jsroot_dialog_title'>${title}</div>` +
8033780337
`<div class='jsroot_dialog_content' style='flex: 1 1 auto; padding: 5px'>${main_content}</div>` +
8033880338
'<div class=\'jsroot_dialog_footer\' style=\'flex: 0 1 auto; padding: 5px\'>' +
8033980339
`<button class='jsroot_dialog_button' style='float: right; width: fit-content; margin-right: 1em'>${args.Ok}</button>` +
8034080340
(args.btns ? '<button class=\'jsroot_dialog_button\' style=\'float: right; width: fit-content; margin-right: 1em\'>Cancel</button>' : '') +
8034180341
'</div></div>'
8034280342
);
8034380343

80344+
const drag_move = drag().on('start', () => { modal.y0 = 0; }).on('drag', evnt => {
80345+
if (!modal.y0)
80346+
modal.y0 = pointer(evnt, modal.element.node())[1];
80347+
let p0 = Math.max(0, pointer(evnt, b.node())[1] - modal.y0);
80348+
if (b.node().clientHeight)
80349+
p0 = Math.min(p0, 0.8 * b.node().clientHeight);
80350+
modal.element.style('top', `${p0}px`);
80351+
});
80352+
modal.element.select('.jsroot_dialog_title').call(drag_move);
80353+
8034480354
modal.done = function(res) {
8034580355
if (this._done)
8034680356
return;
@@ -121180,7 +121190,10 @@ const clTStreamerElement = 'TStreamerElement', clTStreamerObject = 'TStreamerObj
121180121190
StlNames = ['', 'vector', 'list', 'deque', 'map', 'multimap', 'set', 'multiset', 'bitset'],
121181121191

121182121192
// TObject bits
121183-
kIsReferenced = BIT(4), kHasUUID = BIT(5);
121193+
kIsReferenced = BIT(4), kHasUUID = BIT(5),
121194+
121195+
// gap in http which can be merged into single http request
121196+
kMinimalHttpGap = 128;
121184121197

121185121198

121186121199
/** @summary Custom streamers for root classes
@@ -124173,17 +124186,77 @@ class TFile {
124173124186
* @private */
124174124187
async _open() { return this.readKeys(); }
124175124188

124189+
/** @summary check if requested segments can be reordered or merged
124190+
* @private */
124191+
#checkNeedReorder(place) {
124192+
let res = false, resort = false;
124193+
for (let n = 0; n < place.length - 2; n += 2) {
124194+
if (place[n] > place[n + 2])
124195+
res = resort = true;
124196+
if (place[n] + place[n + 1] > place[n + 2] - kMinimalHttpGap)
124197+
res = true;
124198+
}
124199+
if (!res) {
124200+
return {
124201+
place,
124202+
blobs: [],
124203+
expectedSize(indx) { return this.place[indx + 1]; },
124204+
addBuffer(indx, buf, o) {
124205+
this.blobs[indx / 2] = new DataView(buf, o, this.place[indx + 1]);
124206+
}
124207+
};
124208+
}
124209+
124210+
res = { place, reorder: [], place_new: [], blobs: [] };
124211+
124212+
for (let n = 0; n < place.length; n += 2)
124213+
res.reorder.push({ pos: place[n], len: place[n + 1], indx: [n] });
124214+
124215+
if (resort)
124216+
res.reorder.sort((a, b) => { return a.pos - b.pos; });
124217+
124218+
for (let n = 0; n < res.reorder.length - 1; n++) {
124219+
const curr = res.reorder[n],
124220+
next = res.reorder[n + 1];
124221+
if (curr.pos + curr.len + kMinimalHttpGap > next.pos) {
124222+
curr.indx.push(...next.indx);
124223+
curr.len = next.pos + next.len - curr.pos;
124224+
res.reorder.splice(n + 1, 1); // remove segment
124225+
n--;
124226+
}
124227+
}
124228+
124229+
res.reorder.forEach(elem => res.place_new.push(elem.pos, elem.len));
124230+
124231+
res.expectedSize = function(indx) {
124232+
return this.reorder[indx / 2].len;
124233+
};
124234+
124235+
res.addBuffer = function(indx, buf, o) {
124236+
const elem = this.reorder[indx / 2],
124237+
pos0 = elem.pos;
124238+
elem.indx.forEach(indx0 => {
124239+
this.blobs[indx0 / 2] = new DataView(buf, o + this.place[indx0] - pos0, this.place[indx0 + 1]);
124240+
});
124241+
};
124242+
124243+
return res;
124244+
}
124245+
124176124246
/** @summary read buffer(s) from the file
124177124247
* @return {Promise} with read buffers
124178124248
* @private */
124179124249
async readBuffer(place, filename, progress_callback) {
124180124250
if ((this.fFileContent !== null) && !filename && (!this.fAcceptRanges || this.fFileContent.canExtract(place)))
124181124251
return this.fFileContent.extract(place);
124182124252

124253+
const reorder = this.#checkNeedReorder(place);
124254+
if (reorder?.place_new)
124255+
place = reorder?.place_new;
124256+
124183124257
let resolveFunc, rejectFunc;
124184124258

124185124259
const file = this, first_block = (place[0] === 0) && (place.length === 2),
124186-
blobs = [], // array of requested segments
124187124260
promise = new Promise((resolve, reject) => {
124188124261
resolveFunc = resolve;
124189124262
rejectFunc = reject;
@@ -124209,12 +124282,15 @@ class TFile {
124209124282
}
124210124283
}
124211124284

124212-
function send_new_request(increment) {
124213-
if (increment) {
124285+
function send_new_request(arg) {
124286+
if (arg === 'noranges') {
124287+
file.fMaxRanges = 1;
124288+
last = Math.min(last, first + file.fMaxRanges * 2);
124289+
} else if (arg) {
124214124290
first = last;
124215124291
last = Math.min(first + file.fMaxRanges * 2, place.length);
124216124292
if (first >= place.length)
124217-
return resolveFunc(blobs);
124293+
return resolveFunc(reorder.blobs.length === 1 ? reorder.blobs[0] : reorder.blobs);
124218124294
}
124219124295

124220124296
let fullurl = fileurl, ranges = 'bytes', totalsz = 0;
@@ -124231,7 +124307,7 @@ class TFile {
124231124307

124232124308
// when read first block, allow to read more - maybe ranges are not supported and full file content will be returned
124233124309
if (file.fAcceptRanges && first_block)
124234-
totalsz = Math.max(totalsz, 1e7);
124310+
totalsz = Math.max(totalsz, 1e5);
124235124311

124236124312
return createHttpRequest(fullurl, 'buf', read_callback, undefined, true).then(xhr => {
124237124313
if (file.fAcceptRanges) {
@@ -124349,70 +124425,34 @@ class TFile {
124349124425

124350124426
// if only single segment requested, return result as is
124351124427
if (last - first === 2) {
124352-
const b = new DataView(res);
124353-
if (place.length === 2)
124354-
return resolveFunc(b);
124355-
blobs.push(b);
124428+
reorder.addBuffer(first, res, 0);
124356124429
return send_new_request(true);
124357124430
}
124358124431

124359124432
// object to access response data
124360-
const hdr = this.getResponseHeader('Content-Type'),
124361-
ismulti = isStr(hdr) && (hdr.indexOf('multipart') >= 0),
124362-
view = new DataView(res);
124363-
124364-
if (!ismulti) {
124365-
// server may returns simple buffer, which combines all segments together
124366-
124367-
const hdr_range = this.getResponseHeader('Content-Range');
124368-
let segm_start = 0, segm_last = -1;
124369-
124370-
if (isStr(hdr_range) && hdr_range.indexOf('bytes') >= 0) {
124371-
const parts = hdr_range.slice(hdr_range.indexOf('bytes') + 6).split(/[\s-/]+/);
124372-
if (parts.length === 3) {
124373-
segm_start = Number.parseInt(parts[0]);
124374-
segm_last = Number.parseInt(parts[1]);
124375-
if (!Number.isInteger(segm_start) || !Number.isInteger(segm_last) || (segm_start > segm_last)) {
124376-
segm_start = 0;
124377-
segm_last = -1;
124378-
}
124379-
}
124380-
}
124381-
124382-
let canbe_single_segment = (segm_start <= segm_last);
124383-
for (let n = first; n < last; n += 2) {
124384-
if ((place[n] < segm_start) || (place[n] + place[n + 1] - 1 > segm_last))
124385-
canbe_single_segment = false;
124386-
}
124387-
124388-
if (canbe_single_segment) {
124389-
for (let n = first; n < last; n += 2)
124390-
blobs.push(new DataView(res, place[n] - segm_start, place[n + 1]));
124391-
return send_new_request(true);
124392-
}
124393-
124394-
if ((file.fMaxRanges === 1) || !first)
124395-
return rejectFunc(Error('Server returns normal response when multipart was requested, disable multirange support'));
124396-
124397-
file.fMaxRanges = 1;
124398-
last = Math.min(last, file.fMaxRanges * 2);
124433+
const hdr = this.getResponseHeader('Content-Type');
124399124434

124400-
return send_new_request();
124435+
if (!isStr(hdr) || (hdr.indexOf('multipart') < 0)) {
124436+
console.error('Did not found multipart in content-type - fallback to single range request');
124437+
return send_new_request('noranges');
124401124438
}
124402124439

124403124440
// multipart messages requires special handling
124404124441

124405124442
const indx = hdr.indexOf('boundary=');
124406-
let boundary = '', n = first, o = 0, normal_order = true;
124407-
if (indx > 0) {
124408-
boundary = hdr.slice(indx + 9);
124409-
if ((boundary[0] === '"') && (boundary.at(-1) === '"'))
124410-
boundary = boundary.slice(1, boundary.length - 1);
124411-
boundary = '--' + boundary;
124412-
} else
124413-
console.error('Did not found boundary id in the response header');
124443+
if (indx <= 0) {
124444+
console.error('Did not found boundary id in the response header - fallback to single range request');
124445+
return send_new_request('noranges');
124446+
}
124447+
124448+
let boundary = hdr.slice(indx + 9);
124449+
if ((boundary[0] === '"') && (boundary.at(-1) === '"'))
124450+
boundary = boundary.slice(1, boundary.length - 1);
124451+
boundary = '--' + boundary;
124414124452

124415-
while (n < last) {
124453+
const view = new DataView(res);
124454+
124455+
for (let n = first, o = 0; n < last; n += 2) {
124416124456
let code1, code2 = view.getUint8(o), nline = 0, line = '',
124417124457
finish_header = false, segm_start = 0, segm_last = -1;
124418124458

@@ -124431,6 +124471,7 @@ class TFile {
124431124471
if (parts.length === 3) {
124432124472
segm_start = Number.parseInt(parts[0]);
124433124473
segm_last = Number.parseInt(parts[1]);
124474+
// TODO: check for consistency
124434124475
if (!Number.isInteger(segm_start) || !Number.isInteger(segm_last) || (segm_start > segm_last)) {
124435124476
segm_start = 0;
124436124477
segm_last = -1;
@@ -124453,44 +124494,16 @@ class TFile {
124453124494
o++;
124454124495
}
124455124496

124456-
if (!finish_header)
124457-
return rejectFunc(Error('Cannot decode header in multipart message'));
124458-
124459-
if (segm_start > segm_last) {
124460-
// fall-back solution, believe that segments same as requested
124461-
blobs.push(new DataView(res, o, place[n + 1]));
124462-
o += place[n + 1];
124463-
n += 2;
124464-
} else if (normal_order) {
124465-
const n0 = n;
124466-
while ((n < last) && (place[n] >= segm_start) && (place[n] + place[n + 1] - 1 <= segm_last)) {
124467-
blobs.push(new DataView(res, o + place[n] - segm_start, place[n + 1]));
124468-
n += 2;
124469-
}
124497+
const segm_size = segm_last - segm_start + 1;
124470124498

124471-
if (n > n0)
124472-
o += (segm_last - segm_start + 1);
124473-
else
124474-
normal_order = false;
124499+
if (!finish_header || (segm_size <= 0) || (reorder.expectedSize(n) !== segm_size)) {
124500+
console.error('Failure decoding multirange header - fallback to single range request');
124501+
return send_new_request('noranges');
124475124502
}
124476124503

124477-
if (!normal_order) {
124478-
// special situation when server reorder segments in the reply
124479-
let isany = false;
124480-
for (let n1 = n; n1 < last; n1 += 2) {
124481-
if ((place[n1] >= segm_start) && (place[n1] + place[n1 + 1] - 1 <= segm_last)) {
124482-
blobs[n1 / 2] = new DataView(res, o + place[n1] - segm_start, place[n1 + 1]);
124483-
isany = true;
124484-
}
124485-
}
124486-
if (!isany)
124487-
return rejectFunc(Error(`Provided fragment ${segm_start} - ${segm_last} out of requested multi-range request`));
124488-
124489-
while (blobs[n / 2])
124490-
n += 2;
124504+
reorder.addBuffer(n, res, o);
124491124505

124492-
o += (segm_last - segm_start + 1);
124493-
}
124506+
o += segm_size;
124494124507
}
124495124508

124496124509
send_new_request(true);

changes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33

44
## Changes in dev
5+
1. Resort order of ranges in http request, fixing several problems #374
56
1. Implement for `TPie` 3d, text, title drawing including interactivity
67
1. Remove support for deprectaed TH1K class
78
1. Fix - proper paint axis labels on both sides when pad.fTickx/y = 2

modules/core.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const version_id = 'dev',
66

77
/** @summary version date
88
* @desc Release date in format day/month/year like '14/04/2022' */
9-
version_date = '12/11/2025',
9+
version_date = '24/11/2025',
1010

1111
/** @summary version id and date
1212
* @desc Produced by concatenation of {@link version_id} and {@link version_date}

0 commit comments

Comments
 (0)