Skip to content

Commit 6115f51

Browse files
committed
Merge branch 'main' into issue-1307
2 parents 9068fa8 + 6ce17a6 commit 6115f51

File tree

16 files changed

+683
-176
lines changed

16 files changed

+683
-176
lines changed

app/controllers/redirect.php

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use Opencast\Models\VideosShares;
55
use Opencast\Models\LTI\LtiHelper;
66
use Opencast\Models\REST\ApiEventsClient;
7+
use Opencast\Errors\Error;
78

89
class RedirectController extends Opencast\Controller
910
{
@@ -87,15 +88,46 @@ public function download_action($token, $type, $index)
8788

8889
$publication = $video->publication? json_decode($video->publication, true) : null;
8990
if (!empty($publication) && isset($publication['downloads'][$type][$index]['url'])) {
90-
$url = $publication['downloads'][$type][$index]['url'];
9191

92-
$api_events = ApiEventsClient::getInstance($video->config_id);
93-
$response = $api_events->fileRequest($url);
92+
// Make sure the server configs are overwritten, in order to allow large file downloads.
93+
ignore_user_abort(true);
94+
set_time_limit(0);
95+
ini_set('memory_limit', '512M');
9496

95-
header('Content-Type: '. $response['mimetype']);
96-
97-
echo $response['body'];
98-
die;
97+
// Clean all output buffers.
98+
while (ob_get_level()) {
99+
ob_end_clean();
100+
}
101+
try {
102+
$url = $publication['downloads'][$type][$index]['url'];
103+
104+
$api_events = ApiEventsClient::getInstance($video->config_id);
105+
// Since we are using stream, we need to perform the get directly here! Doing it in another file and pass the body as a parameter won't work!
106+
$response = $api_events->ocRestClient->get($url, $api_events->getStreamDownloadConfig());
107+
$stream = $response->getBody();
108+
109+
// Set headers properly.
110+
header('Content-Type: ' . $response->getHeaderLine('Content-Type') ?: 'application/octet-stream');
111+
header('Content-Length: ' . $response->getHeaderLine('Content-Length'));
112+
header('Content-Disposition: attachment; filename*=UTF-8\'\'' . basename($url));
113+
header('Cache-Control: no-cache');
114+
header('Pragma: no-cache');
115+
116+
// Stream in chunks
117+
while (!$stream->eof()) {
118+
// A double check to make sure the connection is still open.
119+
if(connection_status() != CONNECTION_NORMAL){
120+
// If not leave the loop!
121+
break;
122+
}
123+
// 1MB per chunk.
124+
echo $stream->read(1024 * 1024);
125+
flush();
126+
}
127+
die;
128+
} catch (\Throwable $th) {
129+
throw new Error(_('Fehler beim Herunterladen der Datei').': ' . $th->getMessage(), 500);
130+
}
99131
}
100132
}
101133

assets/css/opencast.scss

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,13 @@ h2.oc--loadingbar, .oc--loadingbar-title {
723723
}
724724
}
725725

726+
&.oc--minimal-progress {
727+
height: 3px !important;
728+
.oc--progress-bar {
729+
height: 3px !important;
730+
}
731+
}
732+
726733
margin: 5px 0;
727734
}
728735

@@ -911,6 +918,134 @@ div.oc--dialog-possibilities {
911918
display: block;
912919
}
913920

921+
/* * * * * * * * * * * */
922+
/* DOWNLOAD VIDEOS */
923+
/* * * * * * * * * * * */
924+
925+
.oc--download-list-container {
926+
display: flex;
927+
flex-direction: column;
928+
height: 100%;
929+
.oc--download-list {
930+
flex-grow: 1;
931+
}
932+
.oc--download-messages {
933+
flex-shrink: 0;
934+
}
935+
936+
.oc--download-item {
937+
position: relative;
938+
display: flex;
939+
flex-direction: column;
940+
justify-content: start;
941+
align-items: stretch;
942+
padding: 5px 0px;
943+
944+
button.button {
945+
margin: 0px !important;
946+
}
947+
948+
.oc--download-item-control-row {
949+
display: flex;
950+
flex-direction: row;
951+
justify-content: space-between;
952+
align-items: center;
953+
width: 100%;
954+
gap: 5px;
955+
956+
.oc--tooltip--copy {
957+
width: 40px;
958+
flex-shrink: 0;
959+
display: flex;
960+
justify-content: center;
961+
align-items: center;
962+
963+
.oc--tooltip--copy-success {
964+
top: 0;
965+
}
966+
}
967+
}
968+
969+
.oc--download-btn-container {
970+
position: relative;
971+
display: flex;
972+
flex-direction: row;
973+
justify-content: space-between;
974+
align-items: center;
975+
width: 100%;
976+
977+
.oc--download-btn {
978+
flex-grow: 1;
979+
position: relative;
980+
}
981+
982+
.oc--download-cancel {
983+
margin-left: 5px;
984+
flex-shrink: 0;
985+
display: flex;
986+
justify-content: center;
987+
align-items: center;
988+
989+
&.active {
990+
cursor: pointer;
991+
}
992+
993+
&.inactive {
994+
cursor: default;
995+
}
996+
}
997+
}
998+
999+
.oc--download-info-container {
1000+
display: flex;
1001+
flex-direction: row;
1002+
justify-content: start;
1003+
align-items: center;
1004+
.oc--download-info-status {
1005+
flex: 1;
1006+
margin-left: 5px;
1007+
font-size: 0.8em;
1008+
}
1009+
}
1010+
1011+
.oc--download-spinner-container {
1012+
z-index: 999;
1013+
position: absolute;
1014+
top: 50%;
1015+
translate: 0 -50%;
1016+
left: 2px;
1017+
margin-right: 5px;
1018+
flex-shrink: 0;
1019+
display: flex;
1020+
justify-content: center;
1021+
align-items: center;
1022+
width: 24px;
1023+
height: 24px;
1024+
1025+
.oc--spinner {
1026+
transform: scale(0.5);
1027+
}
1028+
.oc--spinner:after {
1029+
content: " ";
1030+
display: block;
1031+
width: 24px;
1032+
height: 24px;
1033+
border-radius: 50%;
1034+
border: 6px solid;
1035+
border-color: #28497c transparent #28497c transparent;
1036+
animation: oc-spinner 1.2s linear infinite;
1037+
}
1038+
@keyframes oc-spinner {
1039+
0% {
1040+
transform: rotate(0deg);
1041+
}
1042+
100% {
1043+
transform: rotate(360deg);
1044+
}
1045+
}
1046+
}
1047+
}
1048+
}
9141049

9151050
/* * * * * * * * * * * * * * * * * * */
9161051
/* U P L O A D E L E M E N T S */
@@ -1322,3 +1457,12 @@ label.oc--file-upload {
13221457
position: sticky;
13231458
bottom: 0px;
13241459
}
1460+
1461+
/* * * * * * * * * * * * * * * * * */
1462+
/* E M B E D D I N G C O D E */
1463+
/* * * * * * * * * * * * * * * * * */
1464+
1465+
.oc--embedding-code-text {
1466+
width: 100%;
1467+
box-sizing: border-box;
1468+
}

bootstrap_migrations.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
<?php
2-
ini_set('display_errors', 1);
3-
42
StudipAutoloader::addAutoloadPath(__DIR__, 'ElanEv');
53
StudipAutoloader::addAutoloadPath(__DIR__ . '/lib', 'Opencast');
64

lib/Models/REST/RestClient.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,20 @@ public function __construct($config)
8282
$this->ocRestClient = new OcRestClient($oc_config);
8383
}
8484

85+
/**
86+
* Get the Guzzle client options suitable for stream downloading files.
87+
*
88+
* @return array
89+
*/
90+
public function getStreamDownloadConfig() {
91+
return [
92+
'auth' => [$this->username, $this->password],
93+
'timeout' => 0,
94+
'connect_timeout' => 0,
95+
'stream' => true,
96+
];
97+
}
98+
8599
public function fileRequest($file_url)
86100
{
87101
$response = $this->ocRestClient->get($file_url, [

lib/Routes/Config/SimpleConfigList.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public function __invoke(Request $request, Response $response, $args)
3737
'id' => $conf->id,
3838
'name' => $conf->service_url,
3939
'version' => $conf->service_version,
40+
'play' => reset(Endpoints::findBySql("config_id = ? AND service_type = 'play'", [$conf->id]))->service_url,
4041
'ingest' => reset(Endpoints::findBySql("config_id = ? AND service_type = 'ingest'", [$conf->id]))->service_url,
4142
'apievents' => reset(Endpoints::findBySql("config_id = ? AND service_type = 'apievents'", [$conf->id]))->service_url,
4243
'apiplaylists' => reset(Endpoints::findBySql("config_id = ? AND service_type = 'apiplaylists'", [$conf->id]))->service_url,

migrations/107_migrate_previews.php

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,26 @@ public function up()
1616

1717

1818
while ($data = $result->fetch()) {
19-
$new_preview = null;
19+
$new_preview_url = null;
2020
$previews = json_decode($data['preview'], true);
2121

2222
if (!empty($previews)) {
2323
if (!empty($previews['player'])) {
24-
$new_preview = $previews['player'];
24+
$new_preview_url = $previews['player'];
2525
} else if (!empty($previews['search'])) {
26-
$new_preview = $previews['search'];
26+
$new_preview_url = $previews['search'];
2727
}
2828
}
2929

30-
if (!empty($new_preview)) {
31-
$stmt->execute([
32-
':preview' => $new_preview,
33-
':id' => $data['id']
34-
]);
35-
}
30+
// Since we are migrating previews to preview URL, we update the preview regardless of the value of the new preview url, whether it is empty or not!
31+
$stmt->execute([
32+
':preview' => $new_preview_url,
33+
':id' => $data['id']
34+
]);
3635
}
3736
}
3837

3938
public function down()
4039
{
4140
}
42-
}
41+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
class FixOrphanedPreviews extends Migration
4+
{
5+
public function description()
6+
{
7+
return 'Fix for previews that could not get migrated in 107.';
8+
}
9+
10+
public function up()
11+
{
12+
$db = DBManager::get();
13+
14+
$stmt = $db->prepare('UPDATE oc_video SET preview = :preview WHERE id = :id');
15+
$result = $db->query('SELECT id, preview FROM oc_video WHERE preview IS NOT NULL');
16+
17+
18+
while ($data = $result->fetch()) {
19+
$new_preview_url = null;
20+
$previews = json_decode($data['preview'], true);
21+
22+
// We want to handle the case where the preview is still a json object.
23+
if (!empty($previews)) {
24+
// Last chance to present the preview URL.
25+
if (!empty($previews['player'])) {
26+
$new_preview_url = $previews['player'];
27+
} else if (!empty($previews['search'])) {
28+
$new_preview_url = $previews['search'];
29+
}
30+
31+
$stmt->execute([
32+
':preview' => $new_preview_url,
33+
':id' => $data['id']
34+
]);
35+
}
36+
}
37+
}
38+
39+
public function down()
40+
{
41+
}
42+
}

vueapp/app.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ window.addEventListener("DOMContentLoaded", function() {
4545
}, function (error) {
4646
store.dispatch('axiosStop');
4747

48+
// This makes it possible to cancel requests, but we at the moment we don't want to handle it here.
49+
if (axios.isCancel(error)) {
50+
return Promise.reject(error);
51+
}
52+
4853
if (error.response.data !== undefined) {
4954
store.dispatch('addMessage', error.response);
5055
}

vueapp/common/upload.service.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -372,13 +372,13 @@ class UploadService {
372372
uploadDone(episode_id, terms, workflowId);
373373
}
374374
} catch (ex) {
375-
console.log(ex);
375+
console.error(ex);
376+
onError();
376377
/* Catch XML parse error. On Error Resume Next ;-) */
377378
}
378379
}).catch(function (error) {
379-
if (error.code === 'ERR_NETWORK') {
380-
onError();
381-
}
380+
console.error(error);
381+
onError();
382382
});
383383
}
384384

vueapp/components/ProgressBar.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
2-
<div class="oc--progress">
2+
<div class="oc--progress" :class="{'oc--minimal-progress': minimal === true}">
33
<div class="oc--progress-bar" :style="`width: ` + progress + `%`">
4-
<span>
4+
<span v-if="minimal !== true">
55
{{ progress }}%
66
</span>
77
</div>
@@ -12,6 +12,6 @@
1212
export default {
1313
name: 'ProgressBar',
1414
15-
props: ['progress']
15+
props: ['progress', 'minimal']
1616
}
1717
</script>

0 commit comments

Comments
 (0)