Popular Posts
javax.net.ssl.SSLHandshakeException: Connection closed by peer in Android 5.0 Lollipop Recently, there is a error occurs when access website via ssl connection like below although it worked fine several days ago. // Enable SSL... Enable SSL connection for Jsoup import org.jsoup.Connection; import org.jsoup.Jsoup; import javax.net.ssl.*; import java.io.IOException; import java.security.KeyManagement... set/remove cookie using applet jdk/jre 1.4 later, the library is included in plugin.jar file. import java.applet.Applet; import java.util.ArrayList; import java.util.Date;...
Blog Archive
Stats
jQuery Validation Engine default options
{
// Name of the event triggering field validation
validationEventTrigger: "blur",
// Automatically scroll viewport to the first error
scroll: true,
// Focus on the first input
focusFirstField: true,
// Show prompts, set to false to disable prompts
showPrompts: true,
// Should we attempt to validate non-visible input fields contained in the form? (Useful in cases of tabbed containers, e.g. jQuery-UI tabs)
validateNonVisibleFields: false,
// ignore the validation for fields with this specific class (Useful in cases of tabbed containers AND hidden fields we don't want to validate)
ignoreFieldsWithClass: 'ignoreMe',
// Opening box position, possible locations are: topLeft,
// topRight, bottomLeft, centerRight, bottomRight, inline
// inline gets inserted after the validated field or into an element specified in data-prompt-target
promptPosition: "topRight",
bindMethod: "bind",
// internal, automatically set to true when it parse a _ajax rule
inlineAjax: false,
// if set to true, the form data is sent asynchronously via ajax to the form.action url (get)
ajaxFormValidation: false,
// The url to send the submit ajax validation (default to action)
ajaxFormValidationURL: false,
// HTTP method used for ajax validation
ajaxFormValidationMethod: 'get',
// Ajax form validation callback method: boolean onComplete(form, status, errors, options)
// retuns false if the form.submit event needs to be canceled.
onAjaxFormComplete: $.noop,
// called right before the ajax call, may return false to cancel
onBeforeAjaxFormValidation: $.noop,
// Stops form from submitting and execute function assiciated with it
onValidationComplete: false,
// Used when you have a form fields too close and the errors messages are on top of other disturbing viewing messages
doNotShowAllErrosOnSubmit: false,
// Object where you store custom messages to override the default error messages
custom_error_messages: {},
// true if you want to validate the input fields on blur event
binded: true,
// set to true if you want to validate the input fields on blur only if the field it's not empty
notEmpty: false,
// set to true, when the prompt arrow needs to be displayed
showArrow: true,
// set to false, determines if the prompt arrow should be displayed when validating
// checkboxes and radio buttons
showArrowOnRadioAndCheckbox: false,
// did one of the validation fail ? kept global to stop further ajax validations
isError: false,
// Limit how many displayed errors a field can have
maxErrorsPerField: false,
// Caches field validation status, typically only bad status are created.
// the array is used during ajax form validation to detect issues early and prevent an expensive submit
ajaxValidCache: {},
// Auto update prompt position after window resize
autoPositionUpdate: false,
InvalidFields: [],
onFieldSuccess: false,
onFieldFailure: false,
onSuccess: false,
onFailure: false,
validateAttribute: "class",
addSuccessCssClassToField: "",
addFailureCssClassToField: "",
// Auto-hide prompt
autoHidePrompt: false,
// Delay before auto-hide
autoHideDelay: 10000,
// Fade out duration while hiding the validations
fadeDuration: 300,
// Use Prettify select library
prettySelect: false,
// Add css class on prompt
addPromptClass: "",
// Custom ID uses prefix
usePrefix: "",
// Custom ID uses suffix
useSuffix: "",
// Only show one message per error prompt
showOneMessage: false
}
view raw option.js hosted with ❤ by GitHub
Difference source code programmably from svn
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.*;
import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
import java.io.ByteArrayOutputStream;
import java.io.File;
/**
* Diff & export
* Created by nanashi07 on 2016/6/29.
*/
public class DiffWithSvnKit {
public static void main(String[] args) throws SVNException {
final String fromUrl = "https://svn.your_server.com/path_to_compare_from";
final long fromRevision = 1000;
final String targetUrl = "https://svn.your_server.com/path_to_compare_to";
final long targetRevision = 2000;
final String outputFolder = "/folder_to_export";
final String account = "account";
final String password = "password";
DAVRepositoryFactory.setup();
SVNRepositoryFactoryImpl.setup();
FSRepositoryFactory.setup();
SVNURL fromSvnUrl = SVNURL.parseURIEncoded(fromUrl);
SVNRevision fromSvnRevision = SVNRevision.create(fromRevision);
SVNURL targetSvnUrl = SVNURL.parseURIEncoded(targetUrl);
SVNRevision targetSvnRevision = SVNRevision.create(targetRevision);
final File baseFolder = new File(outputFolder);
final SvnOperationFactory svnOperationFactory = new SvnOperationFactory();
final SVNRepository repository = SVNRepositoryFactory.create(targetSvnUrl);
final StringBuilder builder = new StringBuilder();
ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(account, password.toCharArray());
svnOperationFactory.setAuthenticationManager(authManager);
repository.setAuthenticationManager(authManager);
SVNDiffClient diffClient = new SVNDiffClient(authManager, null);
try {
diffClient.doDiffStatus(fromSvnUrl, fromSvnRevision, targetSvnUrl, targetSvnRevision, org.tmatesoft.svn.core.SVNDepth.UNKNOWN, true, new ISVNDiffStatusHandler() {
@Override
public void handleDiffStatus(SVNDiffStatus diffStatus) throws SVNException {
builder.append(String.format("%s, %s%n", diffStatus.getModificationType(), diffStatus.getPath()));
if (diffStatus.getKind() == SVNNodeKind.FILE) {
SVNProperties p = new SVNProperties();
ByteArrayOutputStream output = new ByteArrayOutputStream();
repository.getFile(diffStatus.getPath(), targetRevision, p, output);
byte[] data = output.toByteArray();
File copy = new File(baseFolder, diffStatus.getPath());
if (!copy.getParentFile().exists()) copy.getParentFile().mkdirs();
Files.write(copy, data);
}
}
});
Files.write(new File(baseFolder, "diff.txt"), builder);
} catch (Throwable ex) {
ex.printStackTrace();
}
}
}
Invoke Gson#toJson() on anonymous class

Create a anonymous object and try to serialize instance via gson. The code is below

public class SerializeJson {
static class ResultMessage {
int code;
String content;
public ResultMessage() {
}
public ResultMessage(int code, String content) {
this.code = code;
this.content = content;
}
}
public static void main(String[] args) {
ResultMessage m1 = new ResultMessage(111, "hello");
ResultMessage m2 = new ResultMessage() {{
code = 2323;
content = "hello";
}};
System.out.printf("m1.code : %s%n", m1.code);
System.out.printf("m1.content : %s%n", m1.content);
System.out.printf("m2.code : %s%n", m2.code);
System.out.printf("m2.content : %s%n", m2.content);
System.out.println(m1.getClass());
System.out.println(m2.getClass());
System.out.println("m1: " + new Gson().toJson(m1));
System.out.println("m2: " + new Gson().toJson(m2));
}
}

Result of execution. null result when serialize a anonymous instance

m1.code : 111
m1.content : hello
m2.code : 2323
m2.content : hello
class com.prhythm.test.generic.http.SerializeJson$ResultMessage
class com.prhythm.test.generic.http.SerializeJson$1
m1: {"code":111,"content":"hello"}
m2: null
view raw output hosted with ❤ by GitHub
Commit file via SVNkit

SVNKit version:

<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit</artifactId>
<version>1.8.12</version>
</dependency>
view raw pom.xml hosted with ❤ by GitHub

SvnCommitter.java

package com.prhythm.batch.source.control;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator;
import org.tmatesoft.svn.core.wc.SVNWCUtil;
import java.io.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Created by nanashi07 on 2016/6/22.
*/
@SuppressWarnings("Duplicates")
public class SvnCommitter implements ICommitable {
public static void main(String[] args) throws Throwable {
String url1 = "https://svn.your_server.com";
String userName = "account";
String userPassword = "password";
DAVRepositoryFactory.setup();
SVNRepositoryFactoryImpl.setup();
FSRepositoryFactory.setup();
new SvnCommitter().commit(
url1,
userName,
userPassword,
"Commit updates",
"trunk/other",
new File("xom/src")
);
}
/**
* 最新版本
*/
private int revision = -1;
@Override
public void commit(String repositoryUrl, String account, String password, String commitMessage, String pathToCommit, File... fileOrDirectory) throws Throwable {
// 解析網址
SVNURL url = SVNURL.parseURIEncoded(repositoryUrl);
// 取得資源庫
SVNRepository repository = SVNRepositoryFactory.create(url);
// 身份驗證
ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(account, password.toCharArray());
repository.setAuthenticationManager(authManager);
// 檢查路徑是否存在
if (pathToCommit == null) pathToCommit = "";
SVNNodeKind nodeKind = repository.checkPath(pathToCommit, revision);
if (nodeKind == SVNNodeKind.NONE) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "No entry at URL ''{0}''", url);
throw new SVNException(err);
} else if (nodeKind == SVNNodeKind.FILE) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Entry at URL ''{0}'' is a file while directory was expected", url);
throw new SVNException(err);
}
Map<String, SVNNodeKind> kinds = new HashMap<String, SVNNodeKind>();
for (File file : fileOrDirectory) {
kinds.putAll(collect(repository, pathToCommit, file));
}
ISVNEditor editor = repository.getCommitEditor(commitMessage, null);
try {
editor.openRoot(revision);
SVNDeltaGenerator svnDeltaGenerator = new SVNDeltaGenerator();
for (String segment : (pathToCommit.charAt(0) == '/' ? pathToCommit.substring(1) : pathToCommit).split("[/]")) {
System.out.printf("開啟資料夾 %s%n", segment);
editor.openDir(segment, revision);
}
boolean result = false;
for (File file : fileOrDirectory) {
result = sendDelta(kinds, editor, svnDeltaGenerator, pathToCommit, file);
}
for (String segment : (pathToCommit.charAt(0) == '/' ? pathToCommit.substring(1) : pathToCommit).split("[/]")) {
System.out.printf("關閉資料夾 %s%n", segment);
editor.closeDir();
}
if (!result) {
editor.abortEdit();
System.out.println("無變更");
} else {
SVNCommitInfo svnCommitInfo = editor.closeEdit();
System.out.println(svnCommitInfo);
}
} catch (Throwable e) {
editor.abortEdit();
e.printStackTrace();
}
}
/**
* 收集版本資訊
*
* @param repository 版本庫
* @param path 路徑
* @param fileOrDir 檔案
* @return
* @throws SVNException
*/
private Map<String, SVNNodeKind> collect(final SVNRepository repository, String path, File fileOrDir) throws SVNException, FileNotFoundException {
Map<String, SVNNodeKind> values = new HashMap<String, SVNNodeKind>();
final String targetPath = path.endsWith("/") ? path + fileOrDir.getName() : path + "/" + fileOrDir.getName();
final SVNNodeKind kind = repository.checkPath(targetPath, revision);
System.out.println(String.format("%s: %s", targetPath, kind));
// 取得相關資料
boolean ignore = false;
if (kind == SVNNodeKind.FILE) {
SVNProperties p = new SVNProperties();
ByteArrayOutputStream output = new ByteArrayOutputStream();
repository.getFile(targetPath, revision, p, output);
byte[] data = output.toByteArray();
// 檔案未變更
ignore = Arrays.equals(data, toByteArray(new FileInputStream(fileOrDir)));
}
// 比對版本,檔案相同時不處理
if (!ignore) values.put(targetPath, kind);
if (fileOrDir.isDirectory()) {
File[] files = fileOrDir.listFiles();
if (files != null) {
for (File file : files) {
values.putAll(collect(repository, targetPath, file));
}
}
}
return values;
}
/**
* 處理提交的內容。
*
* @param kindMap 版本資訊
* @param editor
* @param deltaGenerator
* @param path 處理路徑
* @param fileOrDir 檔案
* @return
* @throws SVNException
* @throws FileNotFoundException
*/
private boolean sendDelta(Map<String, SVNNodeKind> kindMap, ISVNEditor editor, SVNDeltaGenerator deltaGenerator, String path, File fileOrDir) throws SVNException, FileNotFoundException {
boolean result = false;
String targetPath = path.endsWith("/") ? path + fileOrDir.getName() : path + "/" + fileOrDir.getName();
SVNNodeKind info = kindMap.get(targetPath);
if (info == SVNNodeKind.NONE) {
// 新增
if (fileOrDir.isDirectory()) {
System.out.printf("加入資料夾 %s%n", fileOrDir.getName());
editor.addDir(fileOrDir.getName(), null, revision);
File[] subFiles = fileOrDir.listFiles();
if (subFiles != null) {
for (File subFile : subFiles) {
result = result || sendDelta(kindMap, editor, deltaGenerator, targetPath, subFile);
}
}
editor.closeDir();
result = true;
} else if (fileOrDir.isFile()) {
System.out.printf("加入檔案 %s%n", fileOrDir.getName());
editor.addFile(fileOrDir.getName(), null, revision);
editor.applyTextDelta(fileOrDir.getName(), null);
String checksum = deltaGenerator.sendDelta(fileOrDir.getName(), new FileInputStream(fileOrDir), editor, true);
editor.closeFile(fileOrDir.getName(), checksum);
result = true;
}
} else if (info == SVNNodeKind.DIR || info == SVNNodeKind.FILE) {
// 修改
if (fileOrDir.isDirectory()) {
if (info != SVNNodeKind.DIR)
throw new RuntimeException(String.format("無法提交資料夾 %s 至檔案格式", fileOrDir));
System.out.printf("開啟資料夾 %s%n", fileOrDir);
editor.openDir(fileOrDir.getName(), revision);
File[] subFiles = fileOrDir.listFiles();
if (subFiles != null) {
for (File subFile : subFiles) {
result = result || sendDelta(kindMap, editor, deltaGenerator, targetPath, subFile);
}
}
editor.closeDir();
} else if (fileOrDir.isFile()) {
if (info != SVNNodeKind.FILE)
throw new RuntimeException(String.format("無法提交檔案 %s 至資料夾格式", fileOrDir));
System.out.printf("變更檔案 %s%n", fileOrDir.getName());
editor.openFile(fileOrDir.getName(), revision);
editor.applyTextDelta(fileOrDir.getName(), null);
String checksum = deltaGenerator.sendDelta(fileOrDir.getName(), new FileInputStream(fileOrDir), editor, true);
editor.closeFile(fileOrDir.getName(), checksum);
result = true;
}
}
return result;
}
private byte[] toByteArray(InputStream input) {
if (input == null) throw new IllegalArgumentException("Input can't be null");
ByteArrayOutputStream output = null;
try {
output = new ByteArrayOutputStream();
byte[] buffer = new byte[128];
int len;
while ((len = input.read(buffer)) > 0) {
output.write(buffer, 0, len);
}
return output.toByteArray();
} catch (Throwable e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
if (output != null) try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
List changed files between commits
List all changed files between commits of git.
git diff --name-only SHA1 SHA2
or
git diff --name-only HEAD~10 HEAD~5
Problem on result of DexFile.entries()

Recently I upgrade my Android Studio to version 2.0, and try to build and run some app on devices. Unfortunately something goes wrong when app executing. When app invokes DexFile.entries() for the class names, the result between android 4 and 5+ is different.

Android version


Okay, let's do some simple test, here is a brand new project I've currently created. The build config and code is below:
App only invokes DexFile.entries() and show the result on a TextView.
On the result, there is a large difference between android 4.2.2 and android 5.0.0
On version 4.2.2, the listed class count is 1555, but on version 5.0.0, there are only 31 entries return.
build.gradle
  1. apply plugin: 'com.android.application'
  2.  
  3. android {
  4.     compileSdkVersion 23
  5.     buildToolsVersion "23.0.3"
  6.  
  7.     defaultConfig {
  8.         applicationId "com.prhythm.myapplication"
  9.         minSdkVersion 14
  10.         targetSdkVersion 23
  11.         versionCode 1
  12.         versionName "1.0"
  13.     }
  14.     buildTypes {
  15.         release {
  16.             minifyEnabled false
  17.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  18.         }
  19.     }
  20. }
  21.  
  22. dependencies {
  23.     compile fileTree(dir: 'libs', include: ['*.jar'])
  24.     testCompile 'junit:junit:4.12'
  25.     compile 'com.android.support:appcompat-v7:23.3.0'
  26. }
MainActivity.java
  1. package com.prhythm.myapplication;
  2.  
  3. import android.os.Bundle;
  4. import android.support.v7.app.AppCompatActivity;
  5. import android.text.TextUtils;
  6. import android.widget.TextView;
  7.  
  8. import java.io.IOException;
  9. import java.util.ArrayList;
  10. import java.util.Enumeration;
  11. import java.util.List;
  12.  
  13. import dalvik.system.DexFile;
  14.  
  15. public class MainActivity extends AppCompatActivity {
  16.  
  17.     @Override
  18.     protected void onCreate(Bundle savedInstanceState) {
  19.         super.onCreate(savedInstanceState);
  20.         setContentView(R.layout.activity_main);
  21.  
  22.         TextView text = (TextView) findViewById(R.id.content);
  23.  
  24.         try {
  25.             List<String> paths = new ArrayList<>();
  26.             DexFile dex = new DexFile(getApplicationInfo().sourceDir);
  27.             for (Enumeration<String> entries = dex.entries(); entries.hasMoreElements(); ) {
  28.                 paths.add(entries.nextElement());
  29.             }
  30.             text.append(String.format("Total entry count: %d%n", paths.size()));
  31.             text.append("* " + TextUtils.join("\n* ", paths));
  32.         } catch (IOException e) {
  33.             e.printStackTrace();
  34.         }
  35.     }
  36. }
Run Result on android 4.2.2 & 5.0.0
Solution:

Looks like this issue is related to the new InstantRun feature in the Android Plugin for Gradle 2.0.0. getPackageCodePath() gets a String pointing towards the base.apk file in the Android file system. If we unzip that apk we can find one or several .dex files inside its root folder. The entries obtained from the method df.entries() iterates over the .dex files found in that root folder in order to obtain all of its compiled classes. However, if we are using the new Android Plugin for Gradle, we will only find the .dex related to the android runtime and instant run (packages com.tools.android.fd.runtime, com.tools.android.fd.common and com.tools.android.tools.ir.api). Every other class will be compiled in several .dex files, zipped into a file called instant-run.zip and placed into the root folder of the apk. That's why the code posted in the question is not able to list all the classes within the app. Still, this will only affect Debug builds since the Release ones don't feature InstantRun.

  1. List<String> getClasses() throws IOException {
  2.     List<String> paths = new ArrayList<>();
  3.     File instantRunDir = new File(getBaseContext().getFilesDir(), "instant-run/dex");
  4.     if (instantRunDir.exists()) {
  5.         for (File dexPath : instantRunDir.listFiles()) {
  6.             DexFile dex = new DexFile(dexPath);
  7.             for (Enumeration<String> entries = dex.entries(); entries.hasMoreElements(); ) {
  8.                 paths.add(entries.nextElement());
  9.             }
  10.         }
  11.     }
  12.  
  13.     DexFile dex = new DexFile(getApplicationInfo().sourceDir);
  14.     for (Enumeration<String> entries = dex.entries(); entries.hasMoreElements(); ) {
  15.         paths.add(entries.nextElement());
  16.     }
  17.  
  18.     return paths;
  19. }
Operate form in WebView and get content
  1. class ContentLoader {
  2.     @JavascriptInterface
  3.     public void load(String body) {
  4.         // do something
  5.     }
  6. }
  7.  
  8. @Override
  9. protected void onCreate(Bundle savedInstanceState) {
  10.     super.onCreate(savedInstanceState);
  11.     setContentView(R.layout.activity_main);
  12.  
  13.     WebView webView = createWebView();
  14.  
  15.     webView.loadUrl("https://www.somedoamin.com/product.jsp");
  16.     // enable javascript
  17.     webView.getSettings().setJavaScriptEnabled(true);
  18.     // add interface
  19.     webView.addJavascriptInterface(new ContentLoader(), "ContentLoader");
  20.     webView.setWebViewClient(new WebViewClient() {
  21.  
  22.         int action;
  23.  
  24.         @Override
  25.         public void onPageFinished(WebView view, String url) {
  26.             switch (action) {
  27.                 case 0:
  28.                     // fill field and submit
  29.                     view.loadUrl("javascript:{\n" +
  30.                             "\tdocument.querySelector('input[name=cgi_tel_no]').value='12345678';\n" +
  31.                             "\tdocument.querySelector('input[name=cgi_id_no]').value='abcdef';\n" +
  32.                             "\tsubmitMyForm();\n" +
  33.                             "};");
  34.                 case 1:
  35.                     // get content from 'ContentLoader' interface
  36.                     view.loadUrl("javascript:{\n" +
  37.                             "\twindow.ContentLoader.load('<body>'+document.body.innerHTML+'</body>');\t\n" +
  38.                             "};");
  39.                     break;
  40.             }
  41.  
  42.             action++;
  43.         }
  44.     });
  45. }
  46.  
  47. WebView createWebView() {
  48.     WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
  49.     WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
  50.     params.gravity = Gravity.TOP | Gravity.LEFT;
  51.     params.= 0;
  52.     params.= 0;
  53.     params.width = 0;
  54.     params.height = 0;
  55.  
  56.     LinearLayout layout = new LinearLayout(this);
  57.     layout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
  58.  
  59.     WebView view = new WebView(this);
  60.     view.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
  61.     layout.addView(view);
  62.  
  63.     windowManager.addView(layout, params);
  64.  
  65.     return view;
  66. }
Gitlab

安裝

線上安裝,支援的 OS 有 Ubuntu、Debian、CentOS、Raspberry PI 2 on Raspbian。直接依照說明即可完成。

建議安裝時也同時下載目前安裝的版本,避免之後有移轉問題需要還原。各版本下載位址為

備份

  1. # 執行備份
  2. gitlab-rake gitlab:backup:create

還原

  1. # 複製備份檔至備份路徑
  2. sudo cp 1493107454_2017_04_25_9.1.0_gitlab_backup.tar /var/opt/gitlab/backups/
  3.  
  4. # 停止服務
  5. sudo gitlab-ctl stop unicorn
  6. sudo gitlab-ctl stop sidekiq
  7.  
  8. # 開始還原
  9. sudo gitlab-rake gitlab:backup:restore BACKUP=1493107454_2017_04_25_9.1.0
  10.  
  11. # 啟動系統
  12. sudo gitlab-ctl start
  13.  
  14. # 檢查還原結果
  15. sudo gitlab-rake gitlab:check SANITIZE=true

由 SVN 移轉

建立 svn 與 git 作者的 mapping 檔(author.txt)

  1. nanashi07 = Bruce <nanashi07@haha.com>

由 svn 簽出為 git 專案

  1. # clone from svn
  2. # with standard layout(trunk/branches/tags), "-s" argument is required
  3. # "author.txt" contains a list that svn accounts mapping to git authors
  4. git svn clone https://10.10.10.162/svn/caml/Caml.Maker --authors-file=author.txt --no-metadata -s Caml.Maker
  5. cd Caml.Maker
  6. # convert tags
  7. git for-each-ref refs/remotes/tags | cut -d / -f 4- | grep -v @ | while read tagname; do git tag "$tagname" "tags/$tagname"; git branch -r -d "tags/$tagname"; done
  8. # convert branches
  9. git for-each-ref refs/remotes | cut -d / -f 3- | grep -v @ | while read branchname; do git branch "$branchname" "refs/remotes/$branchname"; git branch -r -d "$branchname"; done
  10. # add remote repository
  11. git remote add origin http://nanashi07:password@10.10.10.222/caml/Caml.Maker.git
  12. # push to remote
  13. git push origin --all
  14. cd ..

升級

  1. # 停止服務
  2. sudo gitlab-ctl stop unicorn
  3. sudo gitlab-ctl stop sidekiq
  4. sudo gitlab-ctl stop nginx
  5.  
  6. # 備份
  7. sudo gitlab-rake gitlab:backup:create
  8.  
  9. # 安裝下載的檔案
  10. # Ubuntu/Debian:
  11. sudo dpkg -i gitlab_x.x.x-omnibus.xxx.deb
  12.  
  13. # CentOS:
  14. sudo rpm -Uvh gitlab-x.x.x_xxx.rpm
  15.  
  16. # 重新設定與啟動
  17. sudo gitlab-ctl reconfigure
  18. sudo gitlab-ctl restart
Output a file like url via routing
Prepare a binary output action for file output
  1. [RoutePrefix("images")]
  2. public class ImageController : Controller
  3. {
  4.     // GET: ~/images/profile.png
  5.     // GET: ~/images/1873429732.jpg
  6.     [HttpGet]
  7.     [Route("{name}.{ext}")]
  8.     public ActionResult ReadImage(string name, string ext)
  9.     {
  10.         if (System.IO.File.Exists(Server.MapPath(string.Format("~/uploads/{0}.{1}", name, ext))))
  11.         {
  12.             byte[] data = System.IO.File.ReadAllBytes(Server.MapPath(string.Format("~/uploads/{0}.{1}", name, ext)));
  13.             string contentType = MimeMapping.GetMimeMapping(System.IO.Path.GetFileName(string.Format("{0}.{1}", name, ext)));
  14.             return File(data, contentType);
  15.         }
  16.         else
  17.         {
  18.             return new HttpStatusCodeResult(404);
  19.         }
  20.     }
  21. }

Then try test it, but respone 404 not found.




Add runAllManagedModulesForAllRequests="true" on modules section.


  1. <?xml version="1.0" encoding="utf-8"?>
  2. <configuration>
  3.   ...
  4.   <system.webServer>
  5.     ...
  6.     <modules runAllManagedModulesForAllRequests="true">
  7.       <remove name="ApplicationInsightsWebTracking"/>
  8.       <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"
  9.         preCondition="managedHandler"/>
  10.     </modules>
  11.   </system.webServer>
  12.   ...
  13. </configuration>

Then try it again, image rendered success.


Install subversion with apache server on linux (ubuntu)
# 更新 apt package
  1. sudo apt-get update
# 安裝 package
  1. sudo apt-get install subversion apache2 libapache2-svn apache2-utils
# 建立 svn 資料夾
  1. sudo mkdir -p /svn/repos/
# 建立 svn 使用者 (apache)
  1. sudo mkdir -p /svn/users/
# 建立使用者(第一次時有參數-c)
  1. sudo htpasswd -cm /svn/users/svnpasswd nanashi07
# 刪除使用者後再重建(重設密碼)
  1. #sudo htpasswd -D /svn/users/svnpasswd nanashi07
  2. #sudo htpasswd -m /svn/users/svnpasswd nanashi07
# 修改 apache 設定
  1. sudo vi /etc/apache2/mods-enabled/dav_svn.conf
  1. <Location /svn>
  2. DAV svn
  3. SVNParentPath /svn/repos/
  4. AuthType Basic
  5. AuthName "SVN Repository"
  6. AuthUserFile /svn/users/svnpasswd
  7. Require valid-user
  8. </Location>
# 修改 apache 監聽埠
  1. sudo vi /etc/apache2/ports.conf
# 重新啟動 apache
  1. sudo service apache2 restart
# 建立資源庫
  1. sudo svnadmin create /svn/repos/myproject
# 設定資源庫權限
  1. sudo chown -R www-data:www-data /svn/repos/myproject
# 設定資源庫權限
  1. sudo chmod -R g+rws /svn/repos/myproject
Add file to google drive
  1. using Google.Apis.Auth.OAuth2;
  2. using Google.Apis.Drive.v2;
  3. using Google.Apis.Drive.v2.Data;
  4. using Google.Apis.Services;
  5. using Google.Apis.Util.Store;
  6. using Newtonsoft.Json;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Text;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13.  
  14. namespace SyncDump
  15. {
  16.     class Program
  17.     {
  18.         /// <summary>
  19.         /// 存取權限
  20.         /// </summary>
  21.         static string[] Scopes = { DriveService.Scope.Drive, DriveService.Scope.DriveFile };
  22.         static string ApplicationName = "Drive Uploader";
  23.  
  24.         static void Main(string[] args)
  25.         {
  26.             // 檢查輸入參數
  27.             if (args.Length < 1)
  28.             {
  29.                 Console.WriteLine("No target file argument!");
  30.                 return;
  31.             }
  32.  
  33.             // 要上傳的檔案
  34.             string dumpPath = args[0];
  35.             string dumpName = System.IO.Path.GetFileName(dumpPath);
  36.  
  37.             // 載入驗證資訊
  38.             GoogleCredential credential;
  39.             using (var stream = new System.IO.FileStream("client_secret.json", System.IO.FileMode.Open, System.IO.FileAccess.Read))
  40.             {
  41.                 credential = GoogleCredential.FromStream(stream);
  42.             }
  43.  
  44.             // 建立 service
  45.             DriveService service = new DriveService(new BaseClientService.Initializer()
  46.             {
  47.                 HttpClientInitializer = credential.CreateScoped(Scopes),
  48.                 ApplicationName = ApplicationName
  49.             });
  50.             
  51.             // 進行上傳
  52.             insertFile(
  53.                 service,
  54.                 dumpName,
  55.                 dumpName,
  56.                 System.Configuration.ConfigurationManager.AppSettings["drive.folder.id"],
  57.                 "application/oct-stream",
  58.                 dumpPath
  59.             );
  60.         }
  61.  
  62.         /// <summary>
  63.         /// 新增檔案
  64.         /// </summary>
  65.         /// <param name="service">Drive API service instance.</param>
  66.         /// <param name="title">Title of the file to insert, including the extension.</param>
  67.         /// <param name="description">Description of the file to insert.</param>
  68.         /// <param name="parentId">Parent folder's ID.</param>
  69.         /// <param name="mimeType">MIME type of the file to insert.</param>
  70.         /// <param name="filename">Filename of the file to insert.</param><br>  /// <returns>Inserted file metadata, null is returned if an API error occurred.</returns>
  71.         private static File insertFile(DriveService service, string title, string description, string parentId, string mimeType, string filename)
  72.         {
  73.             // 檔案資訊
  74.             File body = new File();
  75.             body.Title = title;
  76.             body.Description = description;
  77.             body.MimeType = mimeType;
  78.  
  79.             // 設定上傳位置
  80.             if (!string.IsNullOrEmpty(parentId))
  81.             {
  82.                 body.Parents = new List<ParentReference>() { new ParentReference() { Id = parentId } };
  83.             }
  84.  
  85.             // 檔案內容
  86.             byte[] byteArray = System.IO.File.ReadAllBytes(filename);
  87.             System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray);
  88.             try
  89.             {
  90.                 FilesResource.InsertMediaUpload request = service.Files.Insert(body, stream, mimeType);
  91.                 request.Upload();
  92.  
  93.                 File file = request.ResponseBody;
  94.  
  95.                 Console.WriteLine("{0:yyyy/MM/dd HH:mm:ss} File {1} is uploaded to drive", DateTime.Now, file.Title);
  96.  
  97.                 return file;
  98.             }
  99.             catch (Exception e)
  100.             {
  101.                 Console.WriteLine("An error occurred: " + e.Message);
  102.                 return null;
  103.             }
  104.         }
  105.     }
  106. }