Первый коммит.
This commit is contained in:
commit
f8b8e8c02b
6 changed files with 961 additions and 0 deletions
34
404.html
Normal file
34
404.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
<!--
|
||||
Copyright (c) 2023 Nikita Osokin.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>:(</h1>
|
||||
<p>Страница не найдена.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
20
COPYRIGHT
Normal file
20
COPYRIGHT
Normal file
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2023 Nikita Osokin.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
247
editpage.html
Normal file
247
editpage.html
Normal file
|
@ -0,0 +1,247 @@
|
|||
<!--
|
||||
Copyright (c) 2023 Nikita Osokin.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<input type="date" id="date" />
|
||||
<p><p/>
|
||||
|
||||
<table id="schedule" border=1>
|
||||
<tbody>
|
||||
<tr class="schedRow">
|
||||
<td>
|
||||
1.
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" checked />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="schedRow">
|
||||
<td>
|
||||
2.
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" checked />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="schedRow">
|
||||
<td>
|
||||
3.
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" checked />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="schedRow">
|
||||
<td>
|
||||
4.
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" checked />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="schedRow">
|
||||
<td>
|
||||
5.
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" checked />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="schedRow">
|
||||
<td>
|
||||
6.
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" checked />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="schedRow">
|
||||
<td>
|
||||
7.
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" checked />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p></p>
|
||||
<input id="password" type="password" placeholder="Пароль" />
|
||||
<input id="send" type="button" onclick="sendEdits()" value="Отправить изменения" />
|
||||
|
||||
<script>
|
||||
function numToUint8Array(num, arr, off) {
|
||||
for(var i = 0; i < 8; i++) {
|
||||
arr[off + 7 - i] = num % 0x100;
|
||||
num >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
function displayStatus(response) {
|
||||
let sendButton = document.getElementById("send");
|
||||
let statusText;
|
||||
|
||||
if(response.status == 200) {
|
||||
statusText = document.createTextNode(" Расписание успешно изменено.");
|
||||
} else if(response.status == 400) {
|
||||
statusText = document.createTextNode(" Ошибка при изменении расписания: неправильный формат данных.");
|
||||
} else if(response.status == 401) {
|
||||
statusText = document.createTextNode(" Неверный пароль.");
|
||||
} else if(response.status == 500) {
|
||||
statusText = document.createTextNode(" Ошибка при изменении расписания: внутренняя ошибка сервера. Попробуйте отправить изменения ещё раз прямо сейчас или чуть позже.");
|
||||
}
|
||||
|
||||
sendButton.after(statusText);
|
||||
}
|
||||
|
||||
function displayNetError(e) {
|
||||
let sendButton = document.getElementById("send");
|
||||
let statusText = document.createTextNode(" Сетевая ошибка при отправке изменений. Возможно, вам стоит проверить своё интернет-соединение или подождать, а затем отправить изменения снова.");
|
||||
sendButton.after(statusText);
|
||||
}
|
||||
|
||||
function sendEdits() {
|
||||
const schedule = document.getElementById("schedule");
|
||||
let lessonsCount = 0;
|
||||
let indices = [];
|
||||
let names = [];
|
||||
let namesLens = [];
|
||||
let descs = [];
|
||||
let descsLens = [];
|
||||
let index = 0;
|
||||
let textEncoder = new TextEncoder();
|
||||
let password = textEncoder.encode(document.getElementById("password").value);
|
||||
let bufferSize = 24 + password.length;
|
||||
for(const row of schedule.children[0].children) {
|
||||
if(row.children[1].children[0].checked) {
|
||||
lessonsCount++;
|
||||
indices.push(index);
|
||||
let name = textEncoder.encode(row.children[2].children[0].value);
|
||||
names.push(name);
|
||||
namesLens.push(name.length);
|
||||
let desc = textEncoder.encode(row.children[3].children[0].value);
|
||||
descs.push(desc);
|
||||
descsLens.push(desc.length);
|
||||
bufferSize += 24 + name.length + desc.length;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
let data = new Uint8Array(new ArrayBuffer(bufferSize));
|
||||
|
||||
numToUint8Array(password.length, data, 0);
|
||||
for(let i = 0; i < password.length; i++) {
|
||||
data[8 + i] = password[i];
|
||||
}
|
||||
|
||||
let currentOff = 16 + password.length;
|
||||
numToUint8Array(lessonsCount, data, currentOff);
|
||||
currentOff += 8;
|
||||
for(let i = 0; i < lessonsCount; i++) {
|
||||
numToUint8Array(indices[i], data, currentOff);
|
||||
currentOff += 8;
|
||||
|
||||
numToUint8Array(namesLens[i], data, currentOff);
|
||||
currentOff += 8;
|
||||
for(let j = 0; j < namesLens[i]; j++) {
|
||||
data[currentOff + j] = names[i][j];
|
||||
}
|
||||
currentOff += namesLens[i];
|
||||
|
||||
numToUint8Array(descsLens[i], data, currentOff);
|
||||
currentOff += 8;
|
||||
for(let j = 0; j < descsLens[i]; j++) {
|
||||
data[currentOff + j] = descs[i][j];
|
||||
}
|
||||
currentOff += descsLens[i];
|
||||
}
|
||||
|
||||
let params = "?c=" + (new URLSearchParams(document.location.search)).get("c")
|
||||
let day = document.getElementById("date").valueAsNumber;
|
||||
if(day != NaN) {
|
||||
params += "&d=" + (day / 86400000).toString();
|
||||
}
|
||||
|
||||
fetch(
|
||||
params,
|
||||
{
|
||||
method: "POST",
|
||||
body: data
|
||||
}
|
||||
).then(
|
||||
displayStatus,
|
||||
displayNetError
|
||||
);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
123
index.html
Normal file
123
index.html
Normal file
|
@ -0,0 +1,123 @@
|
|||
<!--
|
||||
Copyright (c) 2023 Nikita Osokin.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Добро пожаловать!</h1>
|
||||
<p>Здравствуй, клиент!</p>
|
||||
|
||||
<p>Список классов:</p>
|
||||
|
||||
<table id="classes" border=1>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
const classes = document.getElementById("classes");
|
||||
|
||||
function uint8ArrayToNum(arr, off) {
|
||||
let result = 0;
|
||||
for(var i = 0; i < 8; i++) {
|
||||
result <<= 8;
|
||||
result |= arr[off + i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function displayGetError(e) {
|
||||
let p = document.createElement("p");
|
||||
p.appendChild(document.createTextNode("Не удалось получить список классов от сервера."));
|
||||
classes.before(p);
|
||||
}
|
||||
|
||||
function getClasses() {
|
||||
fetch(
|
||||
"../getClassesList",
|
||||
{
|
||||
method: "GET"
|
||||
}
|
||||
).then(
|
||||
getData,
|
||||
displayGetError
|
||||
);
|
||||
}
|
||||
|
||||
function getData(response) {
|
||||
if(response.status != 200) {
|
||||
displayGetError();
|
||||
return;
|
||||
}
|
||||
|
||||
response.arrayBuffer().then(
|
||||
displayData,
|
||||
displayGetError
|
||||
);
|
||||
}
|
||||
|
||||
function displayData(dataBuffer) {
|
||||
let data = new Uint8Array(dataBuffer);
|
||||
|
||||
let classesCount = uint8ArrayToNum(data, 0);
|
||||
let textDecoder = new TextDecoder();
|
||||
let currentOff = 8;
|
||||
for(let i = 0; i < classesCount; i++) {
|
||||
let nRow = document.createElement("tr");
|
||||
|
||||
let len = uint8ArrayToNum(data, currentOff);
|
||||
currentOff += 8;
|
||||
let className = textDecoder.decode(data.slice(currentOff, currentOff + len));
|
||||
let nCell1 = document.createElement("th");
|
||||
nCell1.appendChild(document.createTextNode(className));
|
||||
currentOff += len;
|
||||
nRow.appendChild(nCell1);
|
||||
|
||||
|
||||
let nCell2 = document.createElement("th");
|
||||
let nA1 = document.createElement("a");
|
||||
nA1.href = "../schedule?c=" + className;
|
||||
nA1.appendChild(document.createTextNode("Расписание"));
|
||||
nCell2.appendChild(nA1);
|
||||
nRow.appendChild(nCell2);
|
||||
|
||||
let nCell3 = document.createElement("th");
|
||||
let nA2 = document.createElement("a");
|
||||
nA2.href = "../editpage?c=" + className;
|
||||
nA2.appendChild(document.createTextNode("Изменить"));
|
||||
nCell3.appendChild(nA2);
|
||||
nRow.appendChild(nCell3);
|
||||
|
||||
classes.children[0].appendChild(nRow);
|
||||
}
|
||||
}
|
||||
|
||||
getClasses();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
411
main.go
Normal file
411
main.go
Normal file
|
@ -0,0 +1,411 @@
|
|||
// citrusNikOs' journal -- минималистичный сервер для веб-журнала с расписанием.
|
||||
//
|
||||
// Copyright (c) 2023 Nikita Osokin.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
// provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
// conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"net/http"
|
||||
"time"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var schedFileRWMutex sync.RWMutex
|
||||
var classesRWMutexes map[string]*sync.RWMutex
|
||||
var classesNames []string
|
||||
|
||||
func isValidClass(class string) bool {
|
||||
for i := 0; i < len(classesNames); i++ {
|
||||
if classesNames[i] == class {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getCurrentDay() uint64 {
|
||||
return uint64(time.Now().Unix()) / 86400
|
||||
}
|
||||
|
||||
func isValidDay(day string) bool {
|
||||
if len(day) == 0 || (day[0] == '0' && len(day) != 1) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < len(day); i++ {
|
||||
if day[i] < '0' || day[i] > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func read8BytesBE(reader io.Reader, file *os.File) (uint64, error) {
|
||||
var result uint64
|
||||
var err error
|
||||
var count int
|
||||
buffer := make([]byte, 8)
|
||||
|
||||
count, err = reader.Read(buffer)
|
||||
if count != 8 {
|
||||
return 0, err
|
||||
}
|
||||
for i := 0; i < 8; i++ {
|
||||
result <<= 8
|
||||
result |= uint64(buffer[i])
|
||||
}
|
||||
|
||||
_, err = (*file).Write(buffer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func read8BytesBENoCopy(reader io.Reader) (uint64, error) {
|
||||
var result uint64
|
||||
var err error
|
||||
var count int
|
||||
buffer := make([]byte, 8)
|
||||
|
||||
count, err = reader.Read(buffer)
|
||||
if count != 8 {
|
||||
return 0, err
|
||||
}
|
||||
for i := 0; i < 8; i++ {
|
||||
result <<= 8
|
||||
result |= uint64(buffer[i])
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func writeUint64BE(writer io.Writer, num uint64) error {
|
||||
buffer := make([]byte, 8)
|
||||
bufferI := 0
|
||||
for i := 56; i >= 0; i -= 8 {
|
||||
buffer[bufferI] = uint8((num >> i) & 0xFF)
|
||||
bufferI++
|
||||
}
|
||||
_, err := writer.Write(buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func respondWithFile(writer http.ResponseWriter, request *http.Request, filename string) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println(" !!! Файл", filename, "не найден!")
|
||||
return
|
||||
}
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
fmt.Println(" !!! Не удалось получить информацию о файле", filename + "!")
|
||||
return
|
||||
}
|
||||
buffer := make([]byte, stat.Size())
|
||||
|
||||
count, err := file.Read(buffer);
|
||||
if err != nil {
|
||||
fmt.Println(" !!! Не удалось прочитать содержимое файла", filename + "!")
|
||||
return
|
||||
}
|
||||
if int64(count) != stat.Size() {
|
||||
fmt.Println(" *** Файл", filename, "был прочитан не полностью.")
|
||||
}
|
||||
|
||||
writer.Write(buffer)
|
||||
}
|
||||
|
||||
func handleRootAnd404(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.URL.Path != "/" {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
respondWithFile(writer, request, "404.html")
|
||||
} else {
|
||||
respondWithFile(writer, request, "index.html")
|
||||
}
|
||||
}
|
||||
|
||||
func handleGetClassesList(writer http.ResponseWriter, request *http.Request) {
|
||||
err := writeUint64BE(writer, uint64(len(classesNames)))
|
||||
if err != nil {
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
for i := 0; i < len(classesNames); i++ {
|
||||
err = writeUint64BE(writer, uint64(len(classesNames[i])))
|
||||
if err != nil {
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
_, err = writer.Write([]uint8(classesNames[i]))
|
||||
if err != nil {
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleEditPage(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.Method == http.MethodPost {
|
||||
queryValues := request.URL.Query()
|
||||
|
||||
if len(queryValues["c"]) == 0 || !isValidClass(queryValues["c"][0]) {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var day string
|
||||
if len(queryValues["d"]) == 0 || !isValidDay(queryValues["d"][0]) {
|
||||
day = strconv.FormatUint(getCurrentDay(), 10)
|
||||
} else {
|
||||
day = queryValues["d"][0]
|
||||
}
|
||||
|
||||
passwordLen, err := read8BytesBENoCopy(request.Body);
|
||||
if err != nil {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
passwordFile, err := os.Open("classes/" + queryValues["c"][0] + "/password")
|
||||
if err != nil {
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
passwordFileInfo, err := passwordFile.Stat()
|
||||
if err != nil {
|
||||
passwordFile.Close()
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if uint64(passwordFileInfo.Size() - 1) != passwordLen {
|
||||
passwordFile.Close()
|
||||
writer.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
userPassword := make([]byte, passwordLen)
|
||||
passwordCount, err := request.Body.Read(userPassword)
|
||||
if uint64(passwordCount) != passwordLen || err != nil {
|
||||
passwordFile.Close()
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
password := make([]byte, passwordLen)
|
||||
passwordCount, err = passwordFile.Read(password)
|
||||
if uint64(passwordCount) != passwordLen || err != nil {
|
||||
passwordFile.Close()
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
for i := uint64(0); i < passwordLen; i++ {
|
||||
if password[i] != userPassword[i] {
|
||||
passwordFile.Close()
|
||||
writer.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tmpSchedFile, err := os.CreateTemp("", "sched")
|
||||
if err != nil {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
layoutID, err := read8BytesBE(request.Body, tmpSchedFile)
|
||||
if err != nil || layoutID != 0 {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
os.Remove(tmpSchedFile.Name())
|
||||
return
|
||||
}
|
||||
|
||||
lessonsCount, err := read8BytesBE(request.Body, tmpSchedFile)
|
||||
if err != nil || lessonsCount > 7 {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
os.Remove((*tmpSchedFile).Name())
|
||||
return
|
||||
}
|
||||
|
||||
buffer := make([]byte, 1)
|
||||
lastNumber := uint64(0)
|
||||
isFirst := true
|
||||
for i := uint64(0); i < lessonsCount; i++ {
|
||||
number, err := read8BytesBE(request.Body, tmpSchedFile)
|
||||
if err != nil {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
os.Remove((*tmpSchedFile).Name())
|
||||
return
|
||||
}
|
||||
if !isFirst && number <= lastNumber {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
os.Remove((*tmpSchedFile).Name())
|
||||
return
|
||||
}
|
||||
isFirst = false
|
||||
lastNumber = number
|
||||
|
||||
nameLen, err := read8BytesBE(request.Body, tmpSchedFile)
|
||||
if err != nil {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
os.Remove((*tmpSchedFile).Name())
|
||||
return
|
||||
}
|
||||
for j := uint64(0); j < nameLen; j++ {
|
||||
beenRead, _ := request.Body.Read(buffer)
|
||||
_, err := (*tmpSchedFile).Write(buffer)
|
||||
|
||||
if beenRead == 0 || err != nil {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
os.Remove((*tmpSchedFile).Name())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
descrLen, err := read8BytesBE(request.Body, tmpSchedFile)
|
||||
if err != nil {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
os.Remove((*tmpSchedFile).Name())
|
||||
return
|
||||
}
|
||||
for j := uint64(0); j < descrLen; j++ {
|
||||
beenRead, _ := request.Body.Read(buffer)
|
||||
_, err := (*tmpSchedFile).Write(buffer)
|
||||
|
||||
if beenRead == 0 || err != nil {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
os.Remove((*tmpSchedFile).Name())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beenRead, err := request.Body.Read(buffer)
|
||||
if beenRead != 0 {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
os.Remove((*tmpSchedFile).Name())
|
||||
return
|
||||
}
|
||||
|
||||
classesRWMutexes[queryValues["c"][0]].Lock()
|
||||
schedFileName := "classes/" + queryValues["c"][0] + "/" + day
|
||||
|
||||
os.Remove(schedFileName)
|
||||
os.Rename((*tmpSchedFile).Name(), schedFileName)
|
||||
|
||||
classesRWMutexes[queryValues["c"][0]].Unlock()
|
||||
} else {
|
||||
respondWithFile(writer, request, "editpage.html")
|
||||
}
|
||||
}
|
||||
/* TODO: в эту и похожие функции нужно добавть возможность отправить статус 500 */
|
||||
func handleSchedule(writer http.ResponseWriter, request *http.Request) {
|
||||
respondWithFile(writer, request, "schedule.html")
|
||||
}
|
||||
|
||||
/* TODO: возможно стоит отправлять что-то кроме просто ошибки 500 */
|
||||
func handleScheduleGet(writer http.ResponseWriter, request *http.Request) {
|
||||
queryValues := request.URL.Query()
|
||||
|
||||
if len(queryValues["c"]) == 0 || !isValidClass(queryValues["c"][0]) {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var day string
|
||||
if len(queryValues["d"]) == 0 || !isValidDay(queryValues["d"][0]) {
|
||||
day = strconv.FormatUint(getCurrentDay(), 10)
|
||||
} else {
|
||||
day = queryValues["d"][0]
|
||||
}
|
||||
|
||||
classesRWMutexes[queryValues["c"][0]].RLock()
|
||||
defer classesRWMutexes[queryValues["c"][0]].RUnlock()
|
||||
|
||||
i := 0
|
||||
var schedFile *os.File
|
||||
var err error
|
||||
schedFileName := "classes/" + queryValues["c"][0] + "/" + day
|
||||
for schedFile, err = os.Open(schedFileName); err != nil; schedFile, err = os.Open(schedFileName) {
|
||||
if i == 1000 {
|
||||
return
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
_, err = io.Copy(writer, schedFile)
|
||||
if err != nil {
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
schedFile.Close()
|
||||
}
|
||||
|
||||
func main() {
|
||||
classesRWMutexes = make(map[string]*sync.RWMutex)
|
||||
|
||||
var classesDir *os.File
|
||||
var classes []os.DirEntry
|
||||
var err error
|
||||
|
||||
classesDir, err = os.Open("classes")
|
||||
if err != nil {
|
||||
fmt.Printf("[!!!]Не удалось открыть директорию classes.\n")
|
||||
return
|
||||
}
|
||||
|
||||
classes, err = classesDir.ReadDir(0)
|
||||
if err != nil {
|
||||
fmt.Printf("[!!!]Не удалось прочитать содержимое директории classes.\n")
|
||||
return
|
||||
}
|
||||
classesNames = make([]string, len(classes))
|
||||
|
||||
for i := 0; i < len(classes); i++ {
|
||||
if !classes[i].IsDir() {
|
||||
fmt.Printf("[!!!]Директория classes должна содержать только директории.\n")
|
||||
return
|
||||
}
|
||||
|
||||
classesNames[i] = classes[i].Name()
|
||||
classesRWMutexes[classes[i].Name()] = new(sync.RWMutex)
|
||||
}
|
||||
|
||||
http.HandleFunc("/", handleRootAnd404)
|
||||
http.HandleFunc("/getClassesList", handleGetClassesList)
|
||||
http.HandleFunc("/schedule", handleSchedule)
|
||||
http.HandleFunc("/scheduleGet", handleScheduleGet)
|
||||
http.HandleFunc("/editpage", handleEditPage)
|
||||
http.ListenAndServe(":62314", nil)
|
||||
}
|
||||
|
126
schedule.html
Normal file
126
schedule.html
Normal file
|
@ -0,0 +1,126 @@
|
|||
<!--
|
||||
Copyright (c) 2023 Nikita Osokin.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<input type=date id="date" />
|
||||
<input type=button onclick="changeDay()" value="Получить расписание" />
|
||||
<p></p>
|
||||
|
||||
<table id="schedule" border=1>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
const schedule = document.getElementById("schedule");
|
||||
|
||||
function changeDay() {
|
||||
document.location.assign("?c=" + (new URLSearchParams(document.location.search)).get("c") + "&d=" + (document.getElementById("date").valueAsNumber / 86400000).toString());
|
||||
}
|
||||
|
||||
function uint8ArrayToNum(arr, off) {
|
||||
let result = 0;
|
||||
for(var i = 0; i < 8; i++) {
|
||||
result <<= 8;
|
||||
result |= arr[off + i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function displayGetError(e) {
|
||||
let p = document.createElement("p");
|
||||
p.appendChild(document.createTextNode("Не удалось получить расписание от сервера."));
|
||||
schedule.before(p);
|
||||
}
|
||||
|
||||
function getSchedule() {
|
||||
fetch(
|
||||
"../scheduleGet" + document.location.search,
|
||||
{
|
||||
method: "GET"
|
||||
}
|
||||
).then(
|
||||
getData,
|
||||
displayGetError
|
||||
);
|
||||
}
|
||||
|
||||
function getData(response) {
|
||||
if(response.status != 200) {
|
||||
displayGetError();
|
||||
return;
|
||||
}
|
||||
|
||||
response.arrayBuffer().then(
|
||||
displayData,
|
||||
displayGetError
|
||||
);
|
||||
}
|
||||
|
||||
function displayData(dataBuffer) {
|
||||
let data = new Uint8Array(dataBuffer);
|
||||
|
||||
let lessonsCount = uint8ArrayToNum(data, 8);
|
||||
let textDecoder = new TextDecoder();
|
||||
let currentOff = 16;
|
||||
for(let i = 0; i < lessonsCount; i++) {
|
||||
let nRow = document.createElement("tr");
|
||||
|
||||
let nCell1 = document.createElement("th");
|
||||
nCell1.appendChild(document.createTextNode(uint8ArrayToNum(data, currentOff) + 1));
|
||||
currentOff += 8;
|
||||
nRow.appendChild(nCell1);
|
||||
|
||||
let len = uint8ArrayToNum(data, currentOff);
|
||||
currentOff += 8;
|
||||
let nCell2 = document.createElement("th");
|
||||
nCell2.appendChild(document.createTextNode(textDecoder.decode(data.slice(currentOff, currentOff + len))));
|
||||
currentOff += len;
|
||||
nRow.appendChild(nCell2);
|
||||
|
||||
len = uint8ArrayToNum(data, currentOff);
|
||||
currentOff += 8;
|
||||
let nCell3 = document.createElement("th");
|
||||
nCell3.appendChild(document.createTextNode(textDecoder.decode(data.slice(currentOff, currentOff + len))));
|
||||
currentOff += len;
|
||||
nRow.appendChild(nCell3);
|
||||
|
||||
schedule.children[0].appendChild(nRow);
|
||||
}
|
||||
}
|
||||
|
||||
let day = (new URLSearchParams(document.location.search)).get("d");
|
||||
if(day != null) {
|
||||
document.getElementById("date").valueAsNumber = day * 86400000;
|
||||
}
|
||||
|
||||
getSchedule();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in a new issue