
Introduce the Yobi Update Notification feature.
Site Administrator can see Update Available notification in Software Update page. Yobi fetches the latest version from https://github.com/naver/yobi. You can configure the url by modifying application.update.repositoryUrl. The url should be to a git repository of Yobi. The default link to a release of Yobi is defined by application.update.releaseUrlFormat and the default value is https://github.com/naver/yobi/releases/tag/v{version}.
@39d5ea222dff3c4bbb542c6d4205489e41378a60
--- app/controllers/SiteApp.java
+++ app/controllers/SiteApp.java
... | ... | @@ -31,6 +31,7 @@ |
31 | 31 |
import org.apache.commons.mail.EmailException; |
32 | 32 |
import org.apache.commons.mail.SimpleEmail; |
33 | 33 |
import org.codehaus.jackson.node.ObjectNode; |
34 |
+import org.eclipse.jgit.api.errors.GitAPIException; |
|
34 | 35 |
import play.Configuration; |
35 | 36 |
import play.Logger; |
36 | 37 |
import play.db.ebean.Transactional; |
... | ... | @@ -48,6 +49,8 @@ |
48 | 49 |
import java.util.*; |
49 | 50 |
|
50 | 51 |
import static play.libs.Json.toJson; |
52 |
+ |
|
53 |
+import utils.Config; |
|
51 | 54 |
|
52 | 55 |
/** |
53 | 56 |
* The Class SiteApp. |
... | ... | @@ -247,4 +250,23 @@ |
247 | 250 |
|
248 | 251 |
return ok(toJson(emails)); |
249 | 252 |
} |
253 |
+ |
|
254 |
+ /** |
|
255 |
+ * Show the page to update Yobi. |
|
256 |
+ */ |
|
257 |
+ public static Result update() throws GitAPIException { |
|
258 |
+ String currentVersion = null; |
|
259 |
+ String versionToUpdate = null; |
|
260 |
+ Exception exception = null; |
|
261 |
+ |
|
262 |
+ try { |
|
263 |
+ currentVersion = Config.getCurrentVersion(); |
|
264 |
+ versionToUpdate = YobiUpdate.fetchVersionToUpdate(); |
|
265 |
+ } catch (Exception e) { |
|
266 |
+ exception = e; |
|
267 |
+ } |
|
268 |
+ |
|
269 |
+ return ok(update.render( |
|
270 |
+ "title.siteSetting", currentVersion, versionToUpdate, exception)); |
|
271 |
+ } |
|
250 | 272 |
} |
+++ app/models/YobiUpdate.java
... | ... | @@ -0,0 +1,94 @@ |
1 | +/** | |
2 | + * Yobi, Project Hosting SW | |
3 | + * | |
4 | + * Copyright 2014 NAVER Corp. | |
5 | + * http://yobi.io | |
6 | + * | |
7 | + * @Author Yi EungJun | |
8 | + * | |
9 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
10 | + * you may not use this file except in compliance with the License. | |
11 | + * You may obtain a copy of the License at | |
12 | + * | |
13 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
14 | + * | |
15 | + * Unless required by applicable law or agreed to in writing, software | |
16 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
17 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
18 | + * See the License for the specific language governing permissions and | |
19 | + * limitations under the License. | |
20 | + */ | |
21 | + | |
22 | +package models; | |
23 | + | |
24 | +import com.github.zafarkhaja.semver.Version; | |
25 | +import com.github.zafarkhaja.semver.util.UnexpectedElementTypeException; | |
26 | +import com.typesafe.config.*; | |
27 | +import org.eclipse.jgit.api.Git; | |
28 | +import org.eclipse.jgit.api.errors.GitAPIException; | |
29 | +import org.eclipse.jgit.api.errors.InvalidRemoteException; | |
30 | +import org.eclipse.jgit.lib.Ref; | |
31 | +import play.Configuration; | |
32 | +import play.Logger; | |
33 | +import play.libs.Akka; | |
34 | +import scala.concurrent.duration.Duration; | |
35 | +import utils.Config; | |
36 | + | |
37 | +import java.util.*; | |
38 | +import java.util.concurrent.TimeUnit; | |
39 | + | |
40 | +public class YobiUpdate { | |
41 | + private static final String UPDATE_REPOSITORY_URL = Configuration.root() | |
42 | + .getString("application.update.repositoryUrl", "http://demo.yobi.io/naver/Yobi"); | |
43 | + private static final String RELEASE_URL_FORMAT = Configuration.root() | |
44 | + .getString("application.update.releaseUrlFormat", | |
45 | + "https://github.com/naver/yobi/releases/tag/v%s"); | |
46 | + | |
47 | + public static String getReleaseUrl(String version) throws GitAPIException { | |
48 | + return String.format(RELEASE_URL_FORMAT, version); | |
49 | + } | |
50 | + | |
51 | + /** | |
52 | + * Fetch the latest version to update. | |
53 | + * | |
54 | + * @Return a string to represent the version to update; null if there is | |
55 | + * no version to update | |
56 | + */ | |
57 | + public static String fetchVersionToUpdate() throws GitAPIException { | |
58 | + Version current = Version.valueOf(Config.semverize(Config.getCurrentVersion())); | |
59 | + Version latest = current; | |
60 | + boolean isUpdateRequired = false; | |
61 | + | |
62 | + Collection<Ref> refs; | |
63 | + | |
64 | + refs = Git.lsRemoteRepository() | |
65 | + .setRemote(UPDATE_REPOSITORY_URL) | |
66 | + .setHeads(false) | |
67 | + .setTags(true) | |
68 | + .call(); | |
69 | + | |
70 | + for(Ref ref : refs) { | |
71 | + String tag = ref.getName().replaceFirst("^refs/tags/", ""); | |
72 | + if (tag.charAt(0) == 'v') { | |
73 | + String versionString = Config.semverize(tag); | |
74 | + | |
75 | + try { | |
76 | + Version ver = Version.valueOf(versionString); | |
77 | + if (ver.greaterThan(latest)) { | |
78 | + isUpdateRequired = true; | |
79 | + latest = ver; | |
80 | + } | |
81 | + } catch (UnexpectedElementTypeException e) { | |
82 | + play.Logger.warn("Failed to parse a version: " + | |
83 | + versionString); | |
84 | + } | |
85 | + } | |
86 | + } | |
87 | + | |
88 | + if (isUpdateRequired) { | |
89 | + return latest.toString(); | |
90 | + } else { | |
91 | + return null; | |
92 | + } | |
93 | + } | |
94 | +} |
--- app/utils/Config.java
+++ app/utils/Config.java
... | ... | @@ -20,11 +20,14 @@ |
20 | 20 |
*/ |
21 | 21 |
package utils; |
22 | 22 |
|
23 |
+import com.typesafe.config.*; |
|
23 | 24 |
import org.apache.commons.lang3.ObjectUtils; |
24 | 25 |
import play.Configuration; |
25 | 26 |
import play.mvc.Http; |
26 | 27 |
|
28 |
+import java.io.File; |
|
27 | 29 |
import java.net.*; |
30 |
+import java.nio.file.Paths; |
|
28 | 31 |
import java.util.Enumeration; |
29 | 32 |
|
30 | 33 |
public class Config { |
... | ... | @@ -208,4 +211,41 @@ |
208 | 211 |
public static URI createFullURI(String uri) throws URISyntaxException { |
209 | 212 |
return createFullURI(new URI(uri)); |
210 | 213 |
} |
214 |
+ |
|
215 |
+ /** |
|
216 |
+ * Convert the given version to be compatible with |
|
217 |
+ * <a href="http://semver.org/">semver</a>. |
|
218 |
+ * |
|
219 |
+ * Examples: |
|
220 |
+ * v0.5.7 -> 0.5.7 |
|
221 |
+ * 0.5 -> 0.5.0 |
|
222 |
+ * 0.4.0.pre -> 0.4.0-pre |
|
223 |
+ * |
|
224 |
+ * @param ver a version string to be semverized |
|
225 |
+ * @return the semverized string |
|
226 |
+ */ |
|
227 |
+ public static String semverize(String ver) { |
|
228 |
+ // v0.5.7 -> 0.5.7 |
|
229 |
+ ver = ver.replaceFirst("^v", ""); |
|
230 |
+ |
|
231 |
+ // 0.4.0.pre -> 0.4.0-pre |
|
232 |
+ ver = ver.replaceAll("\\.([^\\d]+)","-$1"); |
|
233 |
+ |
|
234 |
+ // 0.5 -> 0.5.0 |
|
235 |
+ // 0.5-alpha -> 0.5.0-alpha |
|
236 |
+ ver = ver.replaceFirst("^(\\d+\\.\\d+)($|-)", "$1.0$2"); |
|
237 |
+ |
|
238 |
+ return ver; |
|
239 |
+ } |
|
240 |
+ |
|
241 |
+ /** |
|
242 |
+ * Return the version of Yobi installed currently. |
|
243 |
+ * |
|
244 |
+ * @return the current version |
|
245 |
+ */ |
|
246 |
+ public static String getCurrentVersion() { |
|
247 |
+ File versionFile = Paths.get("conf", "version.conf").toFile(); |
|
248 |
+ |
|
249 |
+ return ConfigFactory.parseFile(versionFile).resolve().getString("app.version"); |
|
250 |
+ } |
|
211 | 251 |
} |
--- app/views/site/siteMngLayout.scala.html
+++ app/views/site/siteMngLayout.scala.html
... | ... | @@ -59,6 +59,10 @@ |
59 | 59 |
<li class="@isActiveMenu(routes.SiteApp.massMail())"> |
60 | 60 |
<a href="@routes.SiteApp.massMail()">@Messages("site.sidebar.massMail")</a> |
61 | 61 |
</li> |
62 |
+ <li class="@isActiveMenu(routes.SiteApp.update())"> |
|
63 |
+ <a href="@routes.SiteApp.update()">@Messages("site.sidebar.update") |
|
64 |
+ </a> |
|
65 |
+ </li> |
|
62 | 66 |
</ul> |
63 | 67 |
</div> |
64 | 68 |
<div class="span10"> |
+++ app/views/site/update.scala.html
... | ... | @@ -0,0 +1,48 @@ |
1 | +@** | |
2 | +* Yobi, Project Hosting SW | |
3 | +* | |
4 | +* Copyright 2014 NAVER Corp. | |
5 | +* http://yobi.io | |
6 | +* | |
7 | +* @Author Yi EungJun | |
8 | +* | |
9 | +* Licensed under the Apache License, Version 2.0 (the "License"); | |
10 | +* you may not use this file except in compliance with the License. | |
11 | +* You may obtain a copy of the License at | |
12 | +* | |
13 | +* http://www.apache.org/licenses/LICENSE-2.0 | |
14 | +* | |
15 | +* Unless required by applicable law or agreed to in writing, software | |
16 | +* distributed under the License is distributed on an "AS IS" BASIS, | |
17 | +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
18 | +* See the License for the specific language governing permissions and | |
19 | +* limitations under the License. | |
20 | +**@ | |
21 | +@(message: String, currentVersion: String, versionToUpdate: String, exception: Exception = null) | |
22 | + | |
23 | +@import org.apache.commons.lang.exception.ExceptionUtils | |
24 | +@import utils.TemplateHelper._ | |
25 | + | |
26 | +@siteMngLayout(message) { | |
27 | + <div class="title_area"> | |
28 | + <h2 class="pull-left">@Messages("site.sidebar.update")</h2> | |
29 | + </div> | |
30 | + | |
31 | + @if(versionToUpdate != null){ | |
32 | + <p><strong>@Messages("site.update.isAvailable", versionToUpdate)</strong> | |
33 | + <a href="@YobiUpdate.getReleaseUrl(versionToUpdate)" class="ybtn ybtn-success">@Messages("site.update.download")</a></p> | |
34 | + } | |
35 | + | |
36 | + @if(currentVersion != null){ | |
37 | + <p>@Messages("site.update.currentVersion", currentVersion)</p> | |
38 | + } | |
39 | + | |
40 | + @if(versionToUpdate == null && exception == null) { | |
41 | + <p>@Messages("site.update.isNotNecessary", currentVersion)</p> | |
42 | + } | |
43 | + | |
44 | + @if(exception != null){ | |
45 | + <p>@Messages("site.update.error")</p> | |
46 | + <pre>@ExceptionUtils.getStackTrace(exception)</pre> | |
47 | + } | |
48 | +} |
--- conf/application.conf.default
+++ conf/application.conf.default
... | ... | @@ -127,6 +127,14 @@ |
127 | 127 |
# If this value is undefined or not positive number, notifications will remain forever. |
128 | 128 |
# application.notification.keep-time = 60 |
129 | 129 |
|
130 |
+# Software Update |
|
131 |
+# ~~~~~~~~~~~~~~~ |
|
132 |
+# A url to the git repository for Yobi releases. |
|
133 |
+application.update.repositoryUrl = "https://github.com/naver/yobi" |
|
134 |
+# A format to construct the url to latest Yobi release. "%s" is a format |
|
135 |
+# specifier for Yobi version to download like "0.5.7". |
|
136 |
+application.update.releaesUrlFormat = "https://github.com/naver/yobi/releases/tag/v%s" |
|
137 |
+ |
|
130 | 138 |
#customize play default thread pool size |
131 | 139 |
play { |
132 | 140 |
akka { |
--- conf/messages
+++ conf/messages
... | ... | @@ -672,6 +672,12 @@ |
672 | 672 |
site.sidebar.postList = Posts |
673 | 673 |
site.sidebar.projectList = Projects |
674 | 674 |
site.sidebar.userList = Users |
675 |
+site.sidebar.update = Software Update |
|
676 |
+site.update.currentVersion = Current version is Yobi {0} |
|
677 |
+site.update.download = Download |
|
678 |
+site.update.isAvailable = Yobi {0} is available |
|
679 |
+site.update.isNotNecessary = You are using the latest version |
|
680 |
+site.update.error = Failed to check for updates because of the following error: |
|
675 | 681 |
site.user.delete = Delete user |
676 | 682 |
site.user.deleteConfirm = Are you sure you want this user to leave? |
677 | 683 |
site.userList.deleteAlert = This user cannot be deleted because this user is the only manager of the project. |
--- conf/messages.ko
+++ conf/messages.ko
... | ... | @@ -672,6 +672,12 @@ |
672 | 672 |
site.sidebar.postList = 게시물 |
673 | 673 |
site.sidebar.projectList = 프로젝트 |
674 | 674 |
site.sidebar.userList = 사용자 |
675 |
+site.sidebar.update = 업데이트 |
|
676 |
+site.update.currentVersion = 현재 버전은 {0} 입니다 |
|
677 |
+site.update.download = 다운로드 |
|
678 |
+site.update.error = 다음과 같이 에러가 발생하여 업데이트 할 버전을 확인하지 못했습니다. |
|
679 |
+site.update.isAvailable = Yobi {0} 버전으로 업데이트 할 수 있습니다 |
|
680 |
+site.update.isNotNecessary = 현재 최신 버전을 사용중입니다 |
|
675 | 681 |
site.user.delete = 사용자 삭제 |
676 | 682 |
site.user.deleteConfirm = 정말로 해당 사용자를 사이트에서 탈퇴시키겠습니까? |
677 | 683 |
site.userList.deleteAlert = 프로젝트의 유일한 관리자이므로 사이트에서 삭제할 수 없습니다. |
--- conf/routes
+++ conf/routes
... | ... | @@ -70,6 +70,7 @@ |
70 | 70 |
GET /sites/projectList controllers.SiteApp.projectList(filter:String ?= "", pageNum: Int ?=0) |
71 | 71 |
DELETE /sites/project/delete/:projectId controllers.SiteApp.deleteProject(projectId:Long) |
72 | 72 |
POST /sites/toggleAccountLock controllers.SiteApp.toggleAccountLock(loginId: String, state: String ?= null, query: String ?= null) |
73 |
+GET /sites/update controllers.SiteApp.update() |
|
73 | 74 |
GET /lostPassword controllers.PasswordResetApp.lostPassword |
74 | 75 |
POST /lostPassword controllers.PasswordResetApp.requestResetPasswordEmail() |
75 | 76 |
GET /resetPassword controllers.PasswordResetApp.resetPasswordForm(s:String) |
--- project/Build.scala
+++ project/Build.scala
... | ... | @@ -49,7 +49,8 @@ |
49 | 49 |
"commons-collections" % "commons-collections" % "3.2.1", |
50 | 50 |
"org.jsoup" % "jsoup" % "1.7.2", |
51 | 51 |
"com.googlecode.juniversalchardet" % "juniversalchardet" % "1.0.3", |
52 |
- "org.mockito" % "mockito-all" % "1.9.0" % "test" |
|
52 |
+ "org.mockito" % "mockito-all" % "1.9.0" % "test", |
|
53 |
+ "com.github.zafarkhaja" % "java-semver" % "0.7.2" |
|
53 | 54 |
) |
54 | 55 |
|
55 | 56 |
val projectSettings = Seq( |
--- test/utils/ConfigTest.java
+++ test/utils/ConfigTest.java
... | ... | @@ -100,4 +100,13 @@ |
100 | 100 |
assertThat(Config.getSiteName()).isEqualTo("Yobi"); |
101 | 101 |
Helpers.stop(app); |
102 | 102 |
} |
103 |
+ |
|
104 |
+ @Test |
|
105 |
+ public void semverize() { |
|
106 |
+ assertThat(Config.semverize("0.5.7")).isEqualTo("0.5.7"); |
|
107 |
+ assertThat(Config.semverize("v0.5.7")).isEqualTo("0.5.7"); |
|
108 |
+ assertThat(Config.semverize("0.5")).isEqualTo("0.5.0"); |
|
109 |
+ assertThat(Config.semverize("0.4.0.pre")).isEqualTo("0.4.0-pre"); |
|
110 |
+ assertThat(Config.semverize("0.5-alpha")).isEqualTo("0.5.0-alpha"); |
|
111 |
+ } |
|
103 | 112 |
} |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?