1 /*
2  *  OpenSCAD (www.openscad.org)
3  *  Copyright (C) 2009-2019 Clifford Wolf <clifford@clifford.at> and
4  *                          Marius Kintel <marius@kintel.net>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  As a special exception, you have permission to link this program
12  *  with the CGAL library and distribute executables, as long as you
13  *  follow the requirements of the GNU GPL in regard to all of the
14  *  software in the executable aside from CGAL.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24  *
25  */
26 
27 #include "printutils.h"
28 #include "boost-utils.h"
29 #include "PrintService.h"
30 
31 std::mutex PrintService::printServiceMutex;
32 
PrintService()33 PrintService::PrintService()
34 {
35 	enabled = false;
36 	try {
37 		init();
38 	} catch (const NetworkException& e) {
39 		LOG(message_group::Error,Location::NONE,"","%1$s",e.getErrorMessage());
40 	}
41 	if (enabled) {
42 		LOG(message_group::None,Location::NONE,"","External print service available: %1$s (upload limit = %2$d MB)",displayName.toStdString(),fileSizeLimitMB);
43 	}
44 }
45 
~PrintService()46 PrintService::~PrintService()
47 {
48 }
49 
inst()50 PrintService * PrintService::inst()
51 {
52 	static PrintService *instance = nullptr;
53 
54 	std::lock_guard<std::mutex> lock(printServiceMutex);
55 
56 	if (instance == nullptr) {
57 		instance = new PrintService();
58 	}
59 
60 	return instance;
61 }
62 
init()63 void PrintService::init()
64 {
65 	auto networkRequest = NetworkRequest<void>{QUrl{"https://files.openscad.org/print-service.json"}, { 200 }, 30};
66 	return networkRequest.execute(
67 			[](QNetworkRequest& request) {
68 				request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
69 			},
70 			[](QNetworkAccessManager& nam, QNetworkRequest& request) {
71 				return nam.get(request);
72 			},
73 			[&](QNetworkReply *reply) {
74 				const auto doc = QJsonDocument::fromJson(reply->readAll());
75 				PRINTDB("Response: %s", QString{doc.toJson()}.toStdString());
76 				const QStringList services = doc.object().keys();
77 				if (services.length() >= 1) {
78 					service = services.at(0);
79 					if (!service.isEmpty()) {
80 						initService(doc.object().value(service).toObject());
81 					}
82 				}
83 			}
84 	);
85 }
86 
initService(const QJsonObject & serviceObject)87 void PrintService::initService(const QJsonObject& serviceObject)
88 {
89 	displayName = serviceObject.value("displayName").toString();
90 	apiUrl = serviceObject.value("apiUrl").toString();
91 	fileSizeLimitMB = serviceObject.value("fileSizeLimitMB").toInt();
92 	infoHtml = serviceObject.value("infoHtml").toString();
93 	infoUrl = serviceObject.value("infoUrl").toString();
94 	enabled = !displayName.isEmpty() && !apiUrl.isEmpty() && !infoHtml.isEmpty() && !infoUrl.isEmpty() && fileSizeLimitMB != 0;
95 }
96 
97 /**
98  * This function uploads a design to the 3D printing API endpoint and
99  * returns an URL that, when accessed, will show the design as a part
100  * that can be configured and added to the shopping cart. If it's not
101  * successful, it throws an exception with a message.
102  *
103  * Inputs:
104  *   fileName - Then name we should give the file when it is uploaded
105  *              for the order process.
106  * Outputs:
107  *    The resulting url to go to next to continue the order process.
108  */
upload(const QString & fileName,const QString & contentBase64,network_progress_func_t progress_func)109 const QString PrintService::upload(const QString& fileName, const QString& contentBase64, network_progress_func_t progress_func)
110 {
111 	QJsonObject jsonInput;
112 	jsonInput.insert("fileName", fileName);
113 	jsonInput.insert("file", contentBase64);
114 
115     // Safe guard against QJson silently dropping the file content if it's
116 	// too big. This seems to be configured at MaxSize = (1<<27) - 1 in Qt
117 	// via qtbase/src/corelib/json/qjson_p.h
118 	// Due to the base64 encoding having 33% overhead, that should allow for
119 	// about 96MB data.
120     if (jsonInput.value("file") == QJsonValue::Undefined) {
121 		const QString msg = "Could not encode STL into JSON. Perhaps it is too large of a file? Maybe try reducing the model resolution.";
122         throw NetworkException(QNetworkReply::ProtocolFailure, msg);
123 	}
124 
125 	auto networkRequest = NetworkRequest<const QString>{QUrl{apiUrl}, { 200, 201 }, 180};
126 	networkRequest.set_progress_func(progress_func);
127 	return networkRequest.execute(
128 			[](QNetworkRequest& request) {
129 				request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
130 			},
131 			[&](QNetworkAccessManager& nam, QNetworkRequest& request) {
132 				return nam.post(request, QJsonDocument(jsonInput).toJson());
133 			},
134 			[](QNetworkReply *reply) {
135 				const auto doc = QJsonDocument::fromJson(reply->readAll());
136 				PRINTDB("Response: %s", QString{doc.toJson()}.toStdString());
137 
138 				// Extract cartUrl which gives the page to open in a webbrowser to view uploaded part
139 				const auto cartUrlValue = doc.object().value("data").toObject().value("cartUrl");
140 				const auto cartUrl = cartUrlValue.toString();
141 				if ((cartUrlValue == QJsonValue::Undefined) || (cartUrl.isEmpty())) {
142 					const QString msg = "Could not get data.cartUrl field from response.";
143 					throw NetworkException(QNetworkReply::ProtocolFailure, msg);
144 				}
145 				LOG(message_group::None,Location::NONE,"","Upload finished, opening URL %1$s.",cartUrl.toStdString());
146 				return cartUrl;
147 			}
148 	);
149 }
150