/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: */ |