History

Moved ModelUploader to interface + New error messages

Atlante45 committed 12 years ago

Commit 8ad7c3b1 parent 8e097be2 files · +48 −36Browse files

Showing the changes to interface/src/ModelUploader.cpp (1 of 2 files). Show all

Unified · Split

interface/src/ModelUploader.cpp+342−0
@@ -0,0 +1,342 @@
1+//
2+// ModelUploader.cpp
3+// hifi
4+//
5+// Created by Clément Brisset on 3/4/14.
6+// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
7+//
8+//
9+
10+#include <QDebug>
11+#include <QFile>
12+#include <QFileDialog>
13+#include <QGridLayout>
14+#include <QHttpMultiPart>
15+#include <QMessageBox>
16+#include <QProgressBar>
17+#include <QStandardPaths>
18+#include <QTextStream>
19+#include <QVariant>
20+
21+#include <AccountManager.h>
22+
23+#include "renderer/FBXReader.h"
24+#include "ModelUploader.h"
25+
26+
27+static const QString NAME_FIELD = "name";
28+static const QString FILENAME_FIELD = "filename";
29+static const QString TEXDIR_FIELD = "texdir";
30+static const QString LOD_FIELD = "lod";
31+
32+static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
33+static const QString MODEL_URL = "/api/v1/models";
34+
35+static const int MAX_SIZE = 10 * 1024 * 1024; // 10 MB
36+static const int TIMEOUT = 1000;
37+static const int MAX_CHECK = 30;
38+
39+static const int QCOMPRESS_HEADER_POSITION = 0;
40+static const int QCOMPRESS_HEADER_SIZE = 4;
41+
42+ModelUploader::ModelUploader(bool isHead) :
43+ _lodCount(-1),
44+ _texturesCount(-1),
45+ _totalSize(0),
46+ _isHead(isHead),
47+ _readyToSend(false),
48+ _dataMultiPart(new QHttpMultiPart(QHttpMultiPart::FormDataType)),
49+ _numberOfChecks(MAX_CHECK)
50+{
51+ connect(&_timer, SIGNAL(timeout()), SLOT(checkS3()));
52+}
53+
54+ModelUploader::~ModelUploader() {
55+ delete _dataMultiPart;
56+}
57+
58+bool ModelUploader::zip() {
59+ // File Dialog
60+ QString filename = QFileDialog::getOpenFileName(NULL,
61+ "Select your .fst file ...",
62+ QStandardPaths::writableLocation(QStandardPaths::HomeLocation),
63+ "*.fst");
64+ if (filename == "") {
65+ // If the user canceled we return.
66+ return false;
67+ }
68+ QString texDir;
69+ QString fbxFile;
70+
71+
72+ // First we check the FST file
73+ QFile fst(filename);
74+ if (!fst.open(QFile::ReadOnly | QFile::Text)) {
75+ QMessageBox::warning(NULL,
76+ QString("ModelUploader::zip()"),
77+ QString("Could not open FST file."),
78+ QMessageBox::Ok);
79+ qDebug() << "[Warning] " << QString("Could not open FST file.");
80+ return false;
81+ }
82+ qDebug() << "Reading FST file : " << QFileInfo(fst).filePath();
83+
84+ // Compress and copy the fst
85+ if (!addPart(QFileInfo(fst).filePath(), QString("fst"))) {
86+ return false;
87+ }
88+
89+ // Let's read through the FST file
90+ QTextStream stream(&fst);
91+ QList<QString> line;
92+ while (!stream.atEnd()) {
93+ line = stream.readLine().split(QRegExp("[ =]"), QString::SkipEmptyParts);
94+ if (line.isEmpty()) {
95+ continue;
96+ }
97+
98+ // according to what is read, we modify the command
99+ if (line[0] == NAME_FIELD) {
100+ QHttpPart textPart;
101+ textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"
102+ " name=\"model_name\"");
103+ textPart.setBody(line[1].toUtf8());
104+ _dataMultiPart->append(textPart);
105+ _url = S3_URL + ((_isHead)? "/models/heads/" : "/models/skeletons/") + line[1].toUtf8() + ".fst";
106+ } else if (line[0] == FILENAME_FIELD) {
107+ fbxFile = QFileInfo(fst).path() + "/" + line[1];
108+ QFileInfo fbxInfo(fbxFile);
109+ if (!fbxInfo.exists() || !fbxInfo.isFile()) { // Check existence
110+ QMessageBox::warning(NULL,
111+ QString("ModelUploader::zip()"),
112+ QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()),
113+ QMessageBox::Ok);
114+ qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(fbxInfo.fileName());
115+ return false;
116+ }
117+ // Compress and copy
118+ if (!addPart(fbxInfo.filePath(), "fbx")) {
119+ return false;
120+ }
121+ } else if (line[0] == TEXDIR_FIELD) { // Check existence
122+ texDir = QFileInfo(fst).path() + "/" + line[1];
123+ QFileInfo texInfo(texDir);
124+ if (!texInfo.exists() || !texInfo.isDir()) {
125+ QMessageBox::warning(NULL,
126+ QString("ModelUploader::zip()"),
127+ QString("Texture directory could not be found."),
128+ QMessageBox::Ok);
129+ qDebug() << "[Warning] " << QString("Texture directory could not be found.");
130+ return false;
131+ }
132+ } else if (line[0] == LOD_FIELD) {
133+ QFileInfo lod(QFileInfo(fst).path() + "/" + line[1]);
134+ if (!lod.exists() || !lod.isFile()) { // Check existence
135+ QMessageBox::warning(NULL,
136+ QString("ModelUploader::zip()"),
137+ QString("LOD file %1 could not be found.").arg(lod.fileName()),
138+ QMessageBox::Ok);
139+ qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(lod.fileName());
140+ }
141+ // Compress and copy
142+ if (!addPart(lod.filePath(), QString("lod%1").arg(++_lodCount))) {
143+ return false;
144+ }
145+ }
146+ }
147+
148+ if (!addTextures(texDir, fbxFile)) {
149+ return false;
150+ }
151+
152+ QHttpPart textPart;
153+ textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"
154+ " name=\"model_category\"");
155+ if (_isHead) {
156+ textPart.setBody("heads");
157+ } else {
158+ textPart.setBody("skeletons");
159+ }
160+ _dataMultiPart->append(textPart);
161+
162+ _readyToSend = true;
163+ return true;
164+}
165+
166+void ModelUploader::send() {
167+ if (!zip()) {
168+ deleteLater();
169+ return;
170+ }
171+
172+ JSONCallbackParameters callbackParams;
173+ callbackParams.jsonCallbackReceiver = this;
174+ callbackParams.jsonCallbackMethod = "uploadSuccess";
175+ callbackParams.errorCallbackReceiver = this;
176+ callbackParams.errorCallbackMethod = "uploadFailed";
177+ callbackParams.updateReciever = this;
178+ callbackParams.updateSlot = SLOT(uploadUpdate(qint64, qint64));
179+
180+ AccountManager::getInstance().authenticatedRequest(MODEL_URL, QNetworkAccessManager::PostOperation, callbackParams, QByteArray(), _dataMultiPart);
181+ _dataMultiPart = NULL;
182+ qDebug() << "Sending model...";
183+ _progressDialog = new QDialog();
184+ _progressBar = new QProgressBar(_progressDialog);
185+ _progressBar->setRange(0, 100);
186+ _progressBar->setValue(0);
187+
188+ _progressDialog->setWindowTitle("Uploading model...");
189+ _progressDialog->setLayout(new QGridLayout(_progressDialog));
190+ _progressDialog->layout()->addWidget(_progressBar);
191+
192+ _progressDialog->exec();
193+
194+ delete _progressDialog;
195+ _progressDialog = NULL;
196+ _progressBar = NULL;
197+}
198+
199+void ModelUploader::uploadUpdate(qint64 bytesSent, qint64 bytesTotal) {
200+ if (_progressDialog) {
201+ _progressBar->setRange(0, bytesTotal);
202+ _progressBar->setValue(bytesSent);
203+ }
204+}
205+
206+void ModelUploader::uploadSuccess(const QJsonObject& jsonResponse) {
207+ if (_progressDialog) {
208+ _progressDialog->accept();
209+ }
210+ QMessageBox::information(NULL,
211+ QString("ModelUploader::uploadSuccess()"),
212+ QString("We are reading your model information."),
213+ QMessageBox::Ok);
214+ qDebug() << "Model sent with success";
215+ checkS3();
216+}
217+
218+void ModelUploader::uploadFailed(QNetworkReply::NetworkError errorCode, const QString& errorString) {
219+ if (_progressDialog) {
220+ _progressDialog->reject();
221+ }
222+ QMessageBox::warning(NULL,
223+ QString("ModelUploader::uploadFailed()"),
224+ QString("There was a problem with your upload, please try again later."),
225+ QMessageBox::Ok);
226+ qDebug() << "Model upload failed (" << errorCode << "): " << errorString;
227+ deleteLater();
228+}
229+
230+void ModelUploader::checkS3() {
231+ qDebug() << "Checking S3 for " << _url;
232+ QNetworkRequest request(_url);
233+ QNetworkReply* reply = _networkAccessManager.head(request);
234+ connect(reply, SIGNAL(finished()), SLOT(processCheck()));
235+}
236+
237+void ModelUploader::processCheck() {
238+ QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
239+ _timer.stop();
240+
241+ switch (reply->error()) {
242+ case QNetworkReply::NoError:
243+ QMessageBox::information(NULL,
244+ QString("ModelUploader::processCheck()"),
245+ QString("Your model is now available in the browser."),
246+ QMessageBox::Ok);
247+ deleteLater();
248+ break;
249+ case QNetworkReply::ContentNotFoundError:
250+ if (--_numberOfChecks) {
251+ _timer.start(TIMEOUT);
252+ break;
253+ }
254+ default:
255+ QMessageBox::warning(NULL,
256+ QString("ModelUploader::processCheck()"),
257+ QString("We could not verify that your model was sent sucessfully\n"
258+ "but it may have. If you do not see it in the model browser, try to upload again."),
259+ QMessageBox::Ok);
260+ deleteLater();
261+ break;
262+ }
263+
264+ delete reply;
265+}
266+
267+bool ModelUploader::addTextures(const QString& texdir, const QString fbxFile) {
268+ QFile fbx(fbxFile);
269+ if (!fbx.open(QIODevice::ReadOnly)) {
270+ return false;
271+ }
272+
273+ QByteArray buffer = fbx.readAll();
274+ QVariantHash variantHash = readMapping(buffer);
275+ FBXGeometry geometry = readFBX(buffer, variantHash);
276+
277+ foreach (FBXMesh mesh, geometry.meshes) {
278+ foreach (FBXMeshPart part, mesh.parts) {
279+ if (!part.diffuseFilename.isEmpty()) {
280+ if (!addPart(QFileInfo(fbxFile).path() + "/" + part.diffuseFilename,
281+ QString("texture%1").arg(++_texturesCount))) {
282+ return false;
283+ }
284+ }
285+ if (!part.normalFilename.isEmpty()) {
286+ if (!addPart(QFileInfo(fbxFile).path() + "/" + part.normalFilename,
287+ QString("texture%1").arg(++_texturesCount))) {
288+ return false;
289+ }
290+ }
291+ }
292+ }
293+
294+ return true;
295+}
296+
297+bool ModelUploader::addPart(const QString &path, const QString& name) {
298+ qDebug() << path;
299+ QFile file(path);
300+ if (!file.open(QIODevice::ReadOnly)) {
301+ QMessageBox::warning(NULL,
302+ QString("ModelUploader::addPart()"),
303+ QString("Could not open %1").arg(path),
304+ QMessageBox::Ok);
305+ qDebug() << "[Warning] " << QString("Could not open %1").arg(path);
306+ return false;
307+ }
308+ QByteArray buffer = qCompress(file.readAll());
309+
310+ // Qt's qCompress() default compression level (-1) is the standard zLib compression.
311+ // Here remove Qt's custom header that prevent the data server from uncompressing the files with zLib.
312+ buffer.remove(QCOMPRESS_HEADER_POSITION, QCOMPRESS_HEADER_SIZE);
313+
314+ QHttpPart part;
315+ part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data;"
316+ " name=\"" + name.toUtf8() + "\";"
317+ " filename=\"" + QFileInfo(file).fileName().toUtf8() + "\""));
318+ part.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
319+ part.setBody(buffer);
320+ _dataMultiPart->append(part);
321+
322+
323+ qDebug() << "File " << QFileInfo(file).fileName() << " added to model.";
324+ _totalSize += file.size();
325+ if (_totalSize > MAX_SIZE) {
326+ QMessageBox::warning(NULL,
327+ QString("ModelUploader::zip()"),
328+ QString("Model too big, over %1 Bytes.").arg(MAX_SIZE),
329+ QMessageBox::Ok);
330+ qDebug() << "[Warning] " << QString("Model too big, over %1 Bytes.").arg(MAX_SIZE);
331+ return false;
332+ }
333+ qDebug() << "Current model size: " << _totalSize;
334+
335+ return true;
336+}
337+
338+
339+
340+
341+
342+

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t