/* * Copyright (c) 2010-2017, b3log.org & hacpai.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.b3log.solo.service; import org.b3log.latke.Keys; import org.b3log.latke.Latkes; import org.b3log.latke.ioc.inject.Inject; import org.b3log.latke.logging.Level; import org.b3log.latke.logging.Logger; import org.b3log.latke.mail.MailService; import org.b3log.latke.mail.MailServiceFactory; import org.b3log.latke.model.User; import org.b3log.latke.repository.Query; import org.b3log.latke.repository.Transaction; import org.b3log.latke.repository.jdbc.util.Connections; import org.b3log.latke.service.LangPropsService; import org.b3log.latke.service.ServiceException; import org.b3log.latke.service.annotation.Service; import org.b3log.solo.SoloServletListener; import org.b3log.solo.model.Article; import org.b3log.solo.model.Option; import org.b3log.solo.model.UserExt; import org.b3log.solo.repository.ArticleRepository; import org.b3log.solo.repository.CommentRepository; import org.b3log.solo.repository.OptionRepository; import org.b3log.solo.repository.UserRepository; import org.b3log.solo.util.Thumbnails; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.sql.Connection; import java.sql.Statement; /** * Upgrade service. * * @author <a href="http://88250.b3log.org">Liang Ding</a> * @author <a href="mailto:dongxu.wang@acm.org">Dongxu Wang</a> * @version 1.2.0.12, May 2, 2017 * @since 1.2.0 */ @Service public class UpgradeService { /** * Logger. */ private static final Logger LOGGER = Logger.getLogger(UpgradeService.class); /** * Step for article updating. */ private static final int STEP = 50; /** * Mail Service. */ private static final MailService MAIL_SVC = MailServiceFactory.getMailService(); /** * Old version. */ private static final String FROM_VER = "1.9.0"; /** * New version. */ private static final String TO_VER = SoloServletListener.VERSION; /** * Whether the email has been sent. */ private static boolean sent = false; /** * Article repository. */ @Inject private ArticleRepository articleRepository; /** * Comment repository. */ @Inject private CommentRepository commentRepository; /** * User repository. */ @Inject private UserRepository userRepository; /** * Option repository. */ @Inject private OptionRepository optionRepository; /** * Preference Query Service. */ @Inject private PreferenceQueryService preferenceQueryService; /** * Language service. */ @Inject private LangPropsService langPropsService; /** * Upgrades if need. */ public void upgrade() { try { final JSONObject preference = preferenceQueryService.getPreference(); if (null == preference) { return; } final String currentVer = preference.getString(Option.ID_C_VERSION); if (SoloServletListener.VERSION.equals(currentVer)) { return; } if (FROM_VER.equals(currentVer)) { perform(); return; } LOGGER.log(Level.WARN, "Attempt to skip more than one version to upgrade. Expected: {0}; Actually: {1}", FROM_VER, currentVer); if (!sent) { notifyUserByEmail(); sent = true; } } catch (final Exception e) { LOGGER.log(Level.ERROR, e.getMessage(), e); LOGGER.log(Level.ERROR, "Upgrade failed [" + e.getMessage() + "], please contact the Solo developers or reports this " + "issue directly (<a href='https://github.com/b3log/solo/issues/new'>" + "https://github.com/b3log/solo/issues/new</a>) "); } } /** * Performs upgrade. * * @throws Exception upgrade fails */ private void perform() throws Exception { LOGGER.log(Level.INFO, "Upgrading from version [{0}] to version [{1}]....", FROM_VER, TO_VER); Transaction transaction = null; try { final Connection connection = Connections.getConnection(); final Statement statement = connection.createStatement(); final String tablePrefix = Latkes.getLocalProperty("jdbc.tablePrefix") + "_"; statement.execute("CREATE TABLE `" + tablePrefix + "category` (\n" + " `oId` varchar(19) NOT NULL,\n" + " `categoryTitle` varchar(64) NOT NULL,\n" + " `categoryURI` varchar(32) NOT NULL,\n" + " `categoryDescription` text NOT NULL,\n" + " `categoryOrder` int(11) NOT NULL,\n" + " `categoryTagCnt` int(11) NOT NULL,\n" + " PRIMARY KEY (`oId`)\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;"); statement.execute("CREATE TABLE `" + tablePrefix + "category_tag` (\n" + " `oId` varchar(19) NOT NULL,\n" + " `category_oId` varchar(19) NOT NULL,\n" + " `tag_oId` varchar(19) NOT NULL,\n" + " PRIMARY KEY (`oId`)\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;"); statement.close(); connection.commit(); connection.close(); transaction = optionRepository.beginTransaction(); final JSONObject versionOpt = optionRepository.get(Option.ID_C_VERSION); versionOpt.put(Option.OPTION_VALUE, TO_VER); optionRepository.update(Option.ID_C_VERSION, versionOpt); // https://github.com/b3log/solo/issues/12285 // final JSONObject editorTypeOpt = optionRepository.get(Option.ID_C_EDITOR_TYPE); // editorTypeOpt.put(Option.OPTION_VALUE, Option.DefaultPreference.DEFAULT_EDITOR_TYPE); // optionRepository.update(Option.ID_C_EDITOR_TYPE, editorTypeOpt); transaction.commit(); LOGGER.log(Level.INFO, "Updated preference"); } catch (final Exception e) { if (null != transaction && transaction.isActive()) { transaction.rollback(); } LOGGER.log(Level.ERROR, "Upgrade failed!", e); throw new Exception("Upgrade failed from version [" + FROM_VER + "] to version [" + TO_VER + ']'); } LOGGER.log(Level.INFO, "Upgraded from version [{0}] to version [{1}] successfully :-)", FROM_VER, TO_VER); } /** * Upgrades users. * <p> * Password hashing. * </p> * * @throws Exception exception */ private void upgradeUsers() throws Exception { final JSONArray users = userRepository.get(new Query()).getJSONArray(Keys.RESULTS); for (int i = 0; i < users.length(); i++) { final JSONObject user = users.getJSONObject(i); final String email = user.optString(User.USER_EMAIL); user.put(UserExt.USER_AVATAR, Thumbnails.getGravatarURL(email, "128")); userRepository.update(user.optString(Keys.OBJECT_ID), user); LOGGER.log(Level.INFO, "Updated user[email={0}]", email); } } /** * Upgrades articles. * * @throws Exception exception */ private void upgradeArticles() throws Exception { LOGGER.log(Level.INFO, "Adds a property [articleEditorType] to each of articles"); final JSONArray articles = articleRepository.get(new Query()).getJSONArray(Keys.RESULTS); if (articles.length() <= 0) { LOGGER.log(Level.TRACE, "No articles"); return; } Transaction transaction = null; try { for (int i = 0; i < articles.length(); i++) { if (0 == i % STEP || !transaction.isActive()) { transaction = userRepository.beginTransaction(); } final JSONObject article = articles.getJSONObject(i); final String articleId = article.optString(Keys.OBJECT_ID); LOGGER.log(Level.INFO, "Found an article[id={0}]", articleId); article.put(Article.ARTICLE_EDITOR_TYPE, "tinyMCE"); articleRepository.update(article.getString(Keys.OBJECT_ID), article); if (0 == i % STEP) { transaction.commit(); LOGGER.log(Level.TRACE, "Updated some articles"); } } if (transaction.isActive()) { transaction.commit(); } LOGGER.log(Level.TRACE, "Updated all articles"); } catch (final Exception e) { if (transaction.isActive()) { transaction.rollback(); } throw e; } } /** * Send an email to the user who upgrades Solo with a discontinuous version. * * @throws ServiceException ServiceException * @throws JSONException JSONException * @throws IOException IOException */ private void notifyUserByEmail() throws ServiceException, JSONException, IOException { final String adminEmail = preferenceQueryService.getPreference().getString(Option.ID_C_ADMIN_EMAIL); final MailService.Message message = new MailService.Message(); message.setFrom(adminEmail); message.addRecipient(adminEmail); message.setSubject(langPropsService.get("skipVersionMailSubject")); message.setHtmlBody(langPropsService.get("skipVersionMailBody")); MAIL_SVC.send(message); LOGGER.info("Send an email to the user who upgrades Solo with a discontinuous version."); } }