Coverage Report

Created: 2026-11-06 11:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sc/source/ui/docshell/datastream.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 */
9
10
#include <datastream.hxx>
11
#include <datastreamgettime.hxx>
12
13
#include <com/sun/star/frame/XLayoutManager.hpp>
14
#include <osl/conditn.hxx>
15
#include <osl/time.h>
16
#include <salhelper/thread.hxx>
17
#include <sfx2/viewfrm.hxx>
18
#include <tools/stream.hxx>
19
#include <vcl/svapp.hxx>
20
#include <docsh.hxx>
21
#include <tabvwsh.hxx>
22
#include <viewdata.hxx>
23
#include <stringutil.hxx>
24
#include <documentlinkmgr.hxx>
25
#include <o3tl/enumarray.hxx>
26
27
#include <officecfg/Office/Calc.hxx>
28
29
#include <orcus/csv_parser.hpp>
30
31
#include <atomic>
32
#include <queue>
33
34
namespace com::sun::star::ui { class XUIElement; }
35
36
namespace sc {
37
38
static o3tl::enumarray<DebugTime, double> fTimes { 0.0, 0.0, 0.0 };
39
40
double datastream_get_time(DebugTime nIdx)
41
0
{
42
0
    return fTimes[ nIdx ];
43
0
}
44
45
namespace {
46
47
double getNow()
48
0
{
49
0
    TimeValue now;
50
0
    osl_getSystemTime(&now);
51
0
    return static_cast<double>(now.Seconds) + static_cast<double>(now.Nanosec) / 1000000000.0;
52
0
}
53
54
class CSVHandler
55
{
56
    DataStream::Line& mrLine;
57
    size_t mnColCount;
58
    size_t mnCols;
59
    const char* mpLineHead;
60
61
public:
62
    CSVHandler( DataStream::Line& rLine, size_t nColCount ) :
63
0
        mrLine(rLine), mnColCount(nColCount), mnCols(0), mpLineHead(rLine.maLine.getStr()) {}
64
65
0
    static void begin_parse() {}
66
0
    static void end_parse() {}
67
0
    static void begin_row() {}
68
0
    static void end_row() {}
69
70
    void cell(std::string_view s, bool /*transient*/)
71
0
    {
72
0
        if (mnCols >= mnColCount)
73
0
            return;
74
75
0
        DataStream::Cell aCell;
76
0
        if (ScStringUtil::parseSimpleNumber(s.data(), s.size(), '.', ',', aCell.mfValue))
77
0
        {
78
0
            aCell.mbValue = true;
79
0
        }
80
0
        else
81
0
        {
82
0
            aCell.mbValue = false;
83
0
            aCell.maStr.Pos = std::distance(mpLineHead, s.data());
84
0
            aCell.maStr.Size = s.size();
85
0
        }
86
0
        mrLine.maCells.push_back(aCell);
87
88
0
        ++mnCols;
89
0
    }
90
};
91
92
}
93
94
namespace datastreams {
95
96
class ReaderThread : public salhelper::Thread
97
{
98
    std::unique_ptr<SvStream> mpStream;
99
    size_t mnColCount;
100
    std::atomic<bool> mbTerminate;
101
102
    std::queue<DataStream::LinesType> maPendingLines;
103
    std::queue<DataStream::LinesType> maUsedLines;
104
    std::mutex maMtxLines;
105
106
    osl::Condition maCondReadStream;
107
    osl::Condition maCondConsume;
108
109
    orcus::csv::parser_config maConfig;
110
111
public:
112
113
    ReaderThread(std::unique_ptr<SvStream> pData, size_t nColCount):
114
0
        Thread("ReaderThread"),
115
0
        mpStream(std::move(pData)),
116
0
        mnColCount(nColCount),
117
0
        mbTerminate(false)
118
0
    {
119
0
        maConfig.delimiters.push_back(',');
120
0
        maConfig.text_qualifier = '"';
121
0
    }
122
123
    bool isTerminateRequested()
124
0
    {
125
0
        return mbTerminate.load();
126
0
    }
127
128
    void requestTerminate()
129
0
    {
130
0
        mbTerminate.store(true);
131
0
    }
132
133
    void endThread()
134
0
    {
135
0
        requestTerminate();
136
0
        maCondReadStream.set();
137
0
    }
138
139
    void waitForNewLines()
140
0
    {
141
0
        maCondConsume.wait();
142
0
        maCondConsume.reset();
143
0
    }
144
145
    DataStream::LinesType popNewLines()
146
0
    {
147
0
        auto pLines = std::move(maPendingLines.front());
148
0
        maPendingLines.pop();
149
0
        return pLines;
150
0
    }
151
152
    void resumeReadStream()
153
0
    {
154
0
        if (maPendingLines.size() <= 4)
155
0
            maCondReadStream.set(); // start producer again
156
0
    }
157
158
    bool hasNewLines() const
159
0
    {
160
0
        return !maPendingLines.empty();
161
0
    }
162
163
    void pushUsedLines( DataStream::LinesType pLines )
164
0
    {
165
0
        maUsedLines.push(std::move(pLines));
166
0
    }
167
168
    std::mutex& getLinesMutex()
169
0
    {
170
0
        return maMtxLines;
171
0
    }
172
173
private:
174
    virtual void execute() override
175
0
    {
176
0
        while (!isTerminateRequested())
177
0
        {
178
0
            std::optional<DataStream::LinesType> oLines;
179
0
            std::unique_lock aGuard(getLinesMutex());
180
181
0
            if (!maUsedLines.empty())
182
0
            {
183
                // Re-use lines from previous runs.
184
0
                oLines = std::move(maUsedLines.front());
185
0
                maUsedLines.pop();
186
0
                aGuard.unlock(); // unlock
187
0
            }
188
0
            else
189
0
            {
190
0
                aGuard.unlock(); // unlock
191
0
                oLines.emplace(10);
192
0
            }
193
194
            // Read & store new lines from stream.
195
0
            OStringBuffer aBuffer;
196
0
            for (DataStream::Line & rLine : *oLines)
197
0
            {
198
0
                rLine.maCells.clear();
199
0
                mpStream->ReadLine(aBuffer);
200
0
                rLine.maLine = aBuffer.toString(); // produces minimal string, and keeps buffer
201
0
                CSVHandler aHdl(rLine, mnColCount);
202
0
                orcus::csv_parser<CSVHandler> parser(rLine.maLine, aHdl, maConfig);
203
0
                parser.parse();
204
0
            }
205
206
0
            aGuard.lock(); // lock
207
0
            while (!isTerminateRequested() && maPendingLines.size() >= 8)
208
0
            {
209
                // pause reading for a bit
210
0
                aGuard.unlock(); // unlock
211
0
                maCondReadStream.wait();
212
0
                maCondReadStream.reset();
213
0
                aGuard.lock(); // lock
214
0
            }
215
0
            maPendingLines.push(std::move(*oLines));
216
0
            maCondConsume.set();
217
0
            if (!mpStream->good())
218
0
                requestTerminate();
219
0
        }
220
0
    }
221
};
222
223
}
224
225
0
DataStream::Cell::Cell() : mfValue(0.0), mbValue(true) {}
226
227
0
DataStream::Cell::Cell( const Cell& r ) : mbValue(r.mbValue)
228
0
{
229
0
    if (r.mbValue)
230
0
        mfValue = r.mfValue;
231
0
    else
232
0
    {
233
0
        maStr.Pos = r.maStr.Pos;
234
0
        maStr.Size = r.maStr.Size;
235
0
    }
236
0
}
237
238
void DataStream::MakeToolbarVisible()
239
0
{
240
0
    ScViewData* pViewData = ScDocShell::GetViewData();
241
0
    if (!pViewData)
242
0
        return;
243
244
0
    css::uno::Reference< css::frame::XFrame > xFrame =
245
0
        pViewData->GetViewShell()->GetViewFrame().GetFrame().GetFrameInterface();
246
0
    if (!xFrame.is())
247
0
        return;
248
249
0
    css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY);
250
0
    if (!xPropSet.is())
251
0
        return;
252
253
0
    css::uno::Reference< css::frame::XLayoutManager > xLayoutManager;
254
0
    xPropSet->getPropertyValue(u"LayoutManager"_ustr) >>= xLayoutManager;
255
0
    if (!xLayoutManager.is())
256
0
        return;
257
258
0
    static constexpr OUString sResourceURL( u"private:resource/toolbar/datastreams"_ustr );
259
0
    css::uno::Reference< css::ui::XUIElement > xUIElement = xLayoutManager->getElement(sResourceURL);
260
0
    if (!xUIElement.is())
261
0
    {
262
0
        xLayoutManager->createElement( sResourceURL );
263
0
        xLayoutManager->showElement( sResourceURL );
264
0
    }
265
0
}
266
267
DataStream* DataStream::Set(
268
    ScDocShell *pShell, const OUString& rURL, const ScRange& rRange,
269
    sal_Int32 nLimit, MoveType eMove)
270
0
{
271
0
    DataStream* pLink = new DataStream(pShell, rURL, rRange, nLimit, eMove);
272
0
    sc::DocumentLinkManager& rMgr = pShell->GetDocument().GetDocLinkManager();
273
0
    rMgr.setDataStream(pLink);
274
0
    return pLink;
275
0
}
276
277
DataStream::DataStream(ScDocShell *pShell, const OUString& rURL, const ScRange& rRange,
278
        sal_Int32 nLimit, MoveType eMove) :
279
0
    mpDocShell(pShell),
280
0
    maDocAccess(mpDocShell->GetDocument()),
281
0
    meOrigMove(NO_MOVE),
282
0
    meMove(NO_MOVE),
283
0
    mbRunning(false),
284
0
    mbValuesInLine(false),
285
0
    mbRefreshOnEmptyLine(false),
286
0
    mnLinesCount(0),
287
0
    mnLinesSinceRefresh(0),
288
0
    mfLastRefreshTime(0.0),
289
0
    mnCurRow(0),
290
0
    maImportTimer("sc DataStream maImportTimer"),
291
0
    mbIsFirst(true),
292
0
    mbIsUpdate(false)
293
0
{
294
0
    maImportTimer.SetTimeout(0);
295
0
    maImportTimer.SetInvokeHandler( LINK(this, DataStream, ImportTimerHdl) );
296
297
0
    Decode(rURL, rRange, nLimit, eMove);
298
0
}
299
300
DataStream::~DataStream()
301
0
{
302
0
    if (mbRunning)
303
0
        StopImport();
304
305
0
    if (mxReaderThread.is())
306
0
    {
307
0
        mxReaderThread->endThread();
308
0
        mxReaderThread->join();
309
0
    }
310
0
    moLines.reset();
311
0
}
312
313
DataStream::Line DataStream::ConsumeLine()
314
0
{
315
0
    if (!moLines || mnLinesCount >= moLines->size())
316
0
    {
317
0
        mnLinesCount = 0;
318
0
        if (mxReaderThread->isTerminateRequested())
319
0
            return Line();
320
321
0
        std::unique_lock aGuard(mxReaderThread->getLinesMutex());
322
0
        if (moLines)
323
0
        {
324
0
            mxReaderThread->pushUsedLines(std::move(*moLines));
325
0
            moLines.reset();
326
0
        }
327
328
0
        while (!mxReaderThread->hasNewLines())
329
0
        {
330
0
            aGuard.unlock(); // unlock
331
0
            mxReaderThread->waitForNewLines();
332
0
            aGuard.lock(); // lock
333
0
        }
334
335
0
        moLines = mxReaderThread->popNewLines();
336
0
        mxReaderThread->resumeReadStream();
337
0
    }
338
0
    return moLines->at(mnLinesCount++);
339
0
}
340
341
ScRange DataStream::GetRange() const
342
0
{
343
0
    ScRange aRange = maStartRange;
344
0
    aRange.aEnd = maEndRange.aEnd;
345
0
    return aRange;
346
0
}
347
348
void DataStream::Decode(const OUString& rURL, const ScRange& rRange,
349
        sal_Int32 nLimit, MoveType eMove)
350
0
{
351
0
    msURL = rURL;
352
0
    meMove = eMove;
353
0
    meOrigMove = eMove;
354
355
0
    mbValuesInLine = true; // always true.
356
357
0
    mnCurRow = rRange.aStart.Row();
358
359
0
    ScRange aRange = rRange;
360
0
    if (aRange.aStart.Row() != aRange.aEnd.Row())
361
        // We only allow this range to be one row tall.
362
0
        aRange.aEnd.SetRow(aRange.aStart.Row());
363
364
0
    maStartRange = aRange;
365
0
    maEndRange = aRange;
366
0
    const auto & rDoc = mpDocShell->GetDocument();
367
0
    if (nLimit == 0)
368
0
    {
369
        // Unlimited
370
0
        maEndRange.aStart.SetRow(rDoc.MaxRow());
371
0
    }
372
0
    else if (nLimit > 0)
373
0
    {
374
        // Limited.
375
0
        maEndRange.aStart.IncRow(nLimit-1);
376
0
        if (maEndRange.aStart.Row() > rDoc.MaxRow())
377
0
            maEndRange.aStart.SetRow(rDoc.MaxRow());
378
0
    }
379
380
0
    maEndRange.aEnd.SetRow(maEndRange.aStart.Row());
381
0
}
382
383
void DataStream::StartImport()
384
0
{
385
0
    if (mbRunning)
386
0
        return;
387
388
0
    if (!mxReaderThread.is())
389
0
    {
390
0
        std::unique_ptr<SvStream> pStream(new SvFileStream(msURL, StreamMode::READ));
391
0
        mxReaderThread = new datastreams::ReaderThread(std::move(pStream), maStartRange.aEnd.Col() - maStartRange.aStart.Col() + 1);
392
0
        mxReaderThread->launch();
393
0
    }
394
0
    mbRunning = true;
395
0
    maDocAccess.reset();
396
397
0
    maImportTimer.Start();
398
0
}
399
400
void DataStream::StopImport()
401
0
{
402
0
    if (!mbRunning)
403
0
        return;
404
405
0
    mbRunning = false;
406
0
    Refresh();
407
0
    maImportTimer.Stop();
408
0
}
409
410
void DataStream::SetRefreshOnEmptyLine( bool bVal )
411
0
{
412
0
    mbRefreshOnEmptyLine = bVal;
413
0
}
414
415
void DataStream::Refresh()
416
0
{
417
0
    Application::Yield();
418
419
0
    double fStart = getNow();
420
421
    // Hard recalc will repaint the grid area.
422
0
    mpDocShell->DoHardRecalc();
423
0
    mpDocShell->SetDocumentModified();
424
425
0
    fTimes[ DebugTime::Recalc ] = getNow() - fStart;
426
427
0
    mfLastRefreshTime = getNow();
428
0
    mnLinesSinceRefresh = 0;
429
0
}
430
431
void DataStream::MoveData()
432
0
{
433
0
    switch (meMove)
434
0
    {
435
0
        case RANGE_DOWN:
436
0
        {
437
0
            if (mnCurRow == maEndRange.aStart.Row())
438
0
                meMove = MOVE_UP;
439
0
        }
440
0
        break;
441
0
        case MOVE_UP:
442
0
        {
443
0
            mbIsUpdate = true;
444
            // Remove the top row and shift the remaining rows upward. Then
445
            // insert a new row at the end row position.
446
0
            ScRange aRange = maStartRange;
447
0
            aRange.aEnd = maEndRange.aEnd;
448
0
            maDocAccess.shiftRangeUp(aRange);
449
0
        }
450
0
        break;
451
0
        case MOVE_DOWN:
452
0
        {
453
0
            mbIsUpdate = true;
454
            // Remove the end row and shift the remaining rows downward by
455
            // inserting a new row at the top row.
456
0
            ScRange aRange = maStartRange;
457
0
            aRange.aEnd = maEndRange.aEnd;
458
0
            maDocAccess.shiftRangeDown(aRange);
459
0
        }
460
0
        break;
461
0
        case NO_MOVE:
462
0
        default:
463
0
            ;
464
0
    }
465
0
    if(mbIsFirst && mbIsUpdate)
466
0
    {
467
0
        sal_Int32 nStreamTimeout = officecfg::Office::Calc::DataStream::UpdateTimeout::get();
468
0
        maImportTimer.SetTimeout(nStreamTimeout);
469
0
        mbIsFirst = false;
470
0
    }
471
0
}
472
473
void DataStream::Text2Doc()
474
0
{
475
0
    Line aLine = ConsumeLine();
476
0
    if (aLine.maCells.empty() && mbRefreshOnEmptyLine)
477
0
    {
478
        // Empty line detected.  Trigger refresh and discard it.
479
0
        Refresh();
480
0
        return;
481
0
    }
482
483
0
    double fStart = getNow();
484
485
0
    MoveData();
486
0
    {
487
0
        SCCOL nCol = maStartRange.aStart.Col();
488
0
        const char* pLineHead = aLine.maLine.getStr();
489
0
        for (const Cell& rCell : aLine.maCells)
490
0
        {
491
0
            if (rCell.mbValue)
492
0
            {
493
0
                maDocAccess.setNumericCell(
494
0
                    ScAddress(nCol, mnCurRow, maStartRange.aStart.Tab()), rCell.mfValue);
495
0
            }
496
0
            else
497
0
            {
498
0
                maDocAccess.setStringCell(
499
0
                    ScAddress(nCol, mnCurRow, maStartRange.aStart.Tab()),
500
0
                    OUString(pLineHead+rCell.maStr.Pos, rCell.maStr.Size, RTL_TEXTENCODING_UTF8));
501
0
            }
502
0
            ++nCol;
503
0
        }
504
0
    }
505
506
0
    fTimes[ DebugTime::Import ] = getNow() - fStart;
507
508
0
    if (meMove == NO_MOVE)
509
0
        return;
510
511
0
    if (meMove == RANGE_DOWN)
512
0
    {
513
0
        ++mnCurRow;
514
//      mpDocShell->GetViewData().GetView()->AlignToCursor(
515
//              maStartRange.aStart.Col(), mnCurRow, SC_FOLLOW_JUMP);
516
0
    }
517
518
0
    if (getNow() - mfLastRefreshTime > 0.1 && mnLinesSinceRefresh > 200)
519
        // Refresh no more frequently than every 0.1 second, and wait until at
520
        // least we have processed 200 lines.
521
0
        Refresh();
522
523
0
    ++mnLinesSinceRefresh;
524
0
}
525
526
bool DataStream::ImportData()
527
0
{
528
0
    if (!mbValuesInLine)
529
        // We no longer support this mode. To be deleted later.
530
0
        return false;
531
532
0
    ScViewData* pViewData = ScDocShell::GetViewData();
533
0
    if (!pViewData)
534
0
        return false;
535
536
0
    if (pViewData->GetViewShell()->NeedsRepaint())
537
0
        return mbRunning;
538
539
0
    Text2Doc();
540
0
    return mbRunning;
541
0
}
542
543
IMPL_LINK_NOARG(DataStream, ImportTimerHdl, Timer *, void)
544
0
{
545
0
    if (ImportData())
546
0
        maImportTimer.Start();
547
0
}
548
549
} // namespace sc
550
551
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */