Browse Source

feat: 完成书签从服务器恢复功能

master
Alvis Zhao 5 years ago
parent
commit
de67660b0e
  1. 5
      extension/popup.html
  2. 303
      extension/popup.js
  3. 55
      server/db.js
  4. 9
      server/index.js

5
extension/popup.html

@ -7,8 +7,9 @@
<title>Document</title> <title>Document</title>
</head> </head>
<body style="width: 400px"> <body style="width: 400px">
<button id='download' type="button" class="btn btn-default">从服务器同步书签</button> <button id='download' type="button" class="btn btn-default">恢复</button>
<button id='upload' type="button" class="btn btn-default" id='upload'>同步书签至服务器</button> <button id='upload' type="button" class="btn btn-default">备份</button>
<button id='test' type="button" class="btn btn-default">测试</button>
<script src='./popup.js'></script> <script src='./popup.js'></script>
</body> </body>
</html> </html>

303
extension/popup.js

@ -1,3 +1,97 @@
let api = {};
// chrome bookmark api wrapper
(function(api) {
/**
* 获得整个书签树
*/
const getTree = () => {
return new Promise(resolve => {
chrome.bookmarks.getTree(resolve);
});
};
const getTreeAsync = async () => {
return await getTree();
};
/**
* 获得特定父书签组下的所有子书签和子书签组
* 返回的书签数组中是不包含children字段的即不包含子节点以下的节点
* @param {String} id 父书签组id
*/
const getChildren = id => {
return new Promise(resolve => {
chrome.bookmarks.getChildren(id, resolve);
});
};
const getChildrenAsync = async id => {
return await getChildren(id);
};
/**
* 获得特定书签组下的所有书签
* 返回的书签数组中包含children字段即包含子节点以下的节点
* @param {String} id 父书签组id
*/
const getSubTree = id => {
return new Promise(resolve => {
chrome.bookmarks.getSubTree(id, resolve);
});
};
const getSubTreeAsync = async id => {
return await getSubTree(id);
};
/**
* 删除指定id的书签
* @param {String} id 需要删除的书签的id
*/
const remove = id => {
return new Promise(resolve => {
chrome.bookmarks.remove(id, resolve);
});
};
const removeAsync = async id => {
return await remove(id);
};
/**
* 删除指定id的空书签组如果书签组下有子书签或子书签组删除将失败
* @param {String} id 需要删除的书签文件夹id
*/
const removeTree = id => {
return new Promise(resolve => {
chrome.bookmarks.removeTree(id, resolve);
});
};
const removeTreeAsync = async id => {
await removeTree(id);
};
/**
* 创建一个书签
* @param {Object} bookmark
* string (optional) parentId 父书签组如果不填则默认在**其他书签**一栏中
* integer (optional) index
* string (optional) title
* string (optional) url 如果为NULL或者不填则代表一个书签组文件夹
*/
const create = bookmark => {
return new Promise(resolve => {
chrome.bookmarks.create(bookmark, resolve);
});
};
const createAsync = async bookmark => {
return await create(bookmark);
};
api.getTreeAsync = getTreeAsync;
api.getSubTreeAsync = getSubTreeAsync;
api.getChildrenAsync = getChildrenAsync;
api.removeAsync = removeAsync;
api.removeTreeAsync = removeTreeAsync;
api.createAsync = createAsync;
})(api);
function Node() { function Node() {
/** /**
* id ( string ) * id ( string )
@ -38,63 +132,201 @@ function Node() {
* 是否为根节点 * 是否为根节点
*/ */
this.root = false; this.root = false;
/**
* 子节点数组
*/
this.children = undefined;
} }
let BookmarkTreeNodeList = []; let BookmarkTreeNodeList = [];
const BOOKMARK = 0; const BOOKMARK = 0;
const BOOKEMAR_FOLDER = 1; const BOOKEMARK_FOLDER = 1;
const PROTO = 'http' const PROTO = "http";
const SERVER_ADDRESS = '127.0.0.1' const SERVER_ADDRESS = "127.0.0.1";
const SERVER_PORT = '3000' const SERVER_PORT = "3000";
const SERVER_URL = `${PROTO}://${SERVER_ADDRESS}:${SERVER_PORT}` const SERVER_URL = `${PROTO}://${SERVER_ADDRESS}:${SERVER_PORT}`;
/**
* 获得浏览器书签的map表
* 表的key值表示书签深度根目录深度为0
*/
async function getBookmarkMap() {
let localTree = await api.getTreeAsync();
let localMap = {};
async function addToMap(localTree, depth) {
for (let i = 0; i < localTree.length; i++) {
let node = localTree[i];
function addToList(node) {
let bookmarkNode = new Node(); let bookmarkNode = new Node();
bookmarkNode.id = node.id; bookmarkNode.id = node.id;
bookmarkNode.parentId = node.parentId; bookmarkNode.parentId = node.parentId;
bookmarkNode.dateAdded = node.dateAdded; // bookmarkNode.dateAdded = node.dateAdded;
bookmarkNode.dateGroupModified = node.dateGroupModified; // bookmarkNode.dateGroupModified = node.dateGroupModified;
bookmarkNode.index = node.index; bookmarkNode.index = node.index;
bookmarkNode.title = node.title; bookmarkNode.title = node.title;
bookmarkNode.url = node.url; bookmarkNode.url = node.url;
bookmarkNode.type = bookmarkNode.type =
typeof node.dateGroupNodified === "undefined" ? BOOKMARK : BOOKEMAR_FOLDER; typeof node.dateGroupModified === "undefined"
? BOOKMARK
: BOOKEMARK_FOLDER;
bookmarkNode.root = typeof node.parentId === "undefined"; bookmarkNode.root = typeof node.parentId === "undefined";
BookmarkTreeNodeList.push(bookmarkNode); // 根节点没有 dateGroupModified 属性,但是应该是个书签组类型
if (bookmarkNode.root) {
bookmarkNode.type = BOOKEMARK_FOLDER;
}
if (!Array.isArray(localMap[depth])) {
localMap[depth] = [];
}
localMap[depth].push(bookmarkNode);
if (Array.isArray(node.children)) { if (
bookmarkNode.type === BOOKEMARK_FOLDER &&
Array.isArray(node.children) &&
node.children.length > 0
) {
for (let j = 0; j < node.children.length; j++) { for (let j = 0; j < node.children.length; j++) {
addToList(node.children[j]); let childNode = node.children[j];
let childTree = await api.getSubTreeAsync(childNode.id);
await addToMap(childTree, depth + 1);
}
}
}
}
await addToMap(localTree, 0);
return localMap;
}
/**
* 获得浏览器书签的数组
*/
async function getBookmarkList() {
let localTree = await api.getTreeAsync();
let localList = [];
async function addToList(localTree) {
for (let i = 0; i < localTree.length; i++) {
let node = localTree[i];
let bookmarkNode = new Node();
bookmarkNode.id = node.id;
bookmarkNode.parentId = node.parentId;
// bookmarkNode.dateAdded = node.dateAdded;
// bookmarkNode.dateGroupModified = node.dateGroupModified;
bookmarkNode.index = node.index;
bookmarkNode.title = node.title;
bookmarkNode.url = node.url;
bookmarkNode.type =
typeof node.dateGroupModified === "undefined"
? BOOKMARK
: BOOKEMARK_FOLDER;
bookmarkNode.root = typeof node.parentId === "undefined";
// 根节点没有 dateGroupModified 属性,但是应该是个书签组类型
if (bookmarkNode.root) {
bookmarkNode.type = BOOKEMARK_FOLDER;
}
localList.push(bookmarkNode);
if (Array.isArray(node.children) && node.children.length > 0) {
await addToList(node.children)
}
}
}
await addToList(localTree);
return localList;
}
/**
* 数组转map表
* @param {Object} bookmarkList
*/
function listToMap(bookmarkList) {
let map = {}
for(let i = 0; i < bookmarkList.length; i++) {
let bookmark = bookmarkList[i]
map[bookmark.id] = bookmark
}
function getDepth(bookmark, depth) {
if (parseInt(bookmark.id) === 0) {
return depth
} else {
return getDepth(map[bookmark.parentId], depth + 1)
}
} }
let finalMap = {}
for(let i = 0; i < bookmarkList.length; i++) {
let bookmark = bookmarkList[i]
let finalDepth = getDepth(bookmark, 0)
if (!Array.isArray(finalMap[finalDepth])) {
finalMap[finalDepth] = []
} }
finalMap[finalDepth].push(bookmark)
}
return finalMap
}
async function removeAllBookmarks() {
let localTree = await api.getTreeAsync();
} }
function getBookmarkList(callback) { async function restore(remoteBookmarkArray) {
BookmarkTreeNodeList = [] function getNewbookmarkId(array, parentId) {
chrome.bookmarks.getTree(function(tree) { for (let i = 0; i < array.length; i++) {
for (let i = 0; i < tree.length; i++) { if (array[i].id === parentId) {
let node = tree[i]; return array[i].newId;
addToList(node); }
}
} }
callback(null, BookmarkTreeNodeList);
let array = listToMap(remoteBookmarkArray);
for (let depth in array) {
const bookmarks = array[depth];
for (let i = 0; i < bookmarks.length; i++) {
const bookmark = bookmarks[i];
if (
bookmark.root ||
bookmark.id === "0" ||
bookmark.id === "1" ||
bookmark.id === "2"
) {
bookmark.newId = bookmark.id;
continue;
}
try {
const newBookmark = await api.createAsync({
parentId: getNewbookmarkId(array[depth - 1], bookmark.parentId),
index: bookmark.index,
title: bookmark.title,
url: bookmark.url
}); });
bookmark.newId = newBookmark.id;
console.log(
`restroe ${bookmark.title}:${bookmark.id} to ${newBookmark.title}:${newBookmark.id}`
);
} catch (error) {
console.error(error);
}
}
}
} }
$("#upload").on("click", function() { $("#upload").on("click", async () => {
getBookmarkList(function(error, bookmarkArray) { let bookmarkArray = await getBookmarkList();
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: `${SERVER_URL}/bookmarks`, url: `${SERVER_URL}/bookmarks`,
contentType: 'application/json;charset=utf-8', contentType: "application/json;charset=utf-8",
dataType: 'json', dataType: "json",
data: JSON.stringify(bookmarkArray), data: JSON.stringify(bookmarkArray),
success: function() { success: function() {},
console.log('上传了 ' + bookmarkArray.length + ' 条书签') error: function() {}
},
error: function() {
console.error('上传失败')
}
});
}); });
}); });
@ -102,8 +334,15 @@ $("#download").on("click", function() {
$.ajax({ $.ajax({
type: "GET", type: "GET",
url: `${SERVER_URL}/bookmarks`, url: `${SERVER_URL}/bookmarks`,
success: function(bookmarkArray) { success: function(result) {
console.log('下载了 ' + bookmarkArray.length + ' 条书签') if (result.code === 0) {
restore(result.data);
}
} }
}); });
}); });
$("#test").on("click", async () => {
let array = await getBookmarkList();
console.log(array);
});

55
server/db.js

@ -1,7 +1,8 @@
// const MongoClient = require('mongodb').MongoClient // const MongoClient = require('mongodb').MongoClient
const assert = require('assert') const assert = require("assert");
const fs = require('fs') const fs = require("fs");
const path = require('path') const path = require("path");
const crypto = require('crypto');
// const DATABASE_NAME = 'bookmarksdb' // const DATABASE_NAME = 'bookmarksdb'
// const DATABASE_SERVER = '192.168.31.201' // const DATABASE_SERVER = '192.168.31.201'
@ -37,32 +38,42 @@ exports.connect = () => {
// client.close() // client.close()
// }) // })
// }) // })
} };
exports.save = (bookmarkArray, callback) => { exports.save = (bookmarkArray, callback) => {
console.log('save data') const bookmarkStr = JSON.stringify(bookmarkArray);
const bookmarkStr = JSON.stringify(bookmarkArray) // 计算hash
fs.writeFile(path.join('database', Date.now() + '.json'), bookmarkStr, err => { const hash = crypto
callback(err) .createHash("sha256")
}); .update(JSON.stringify(bookmarkArray))
.digest("base64");
console.log(hash);
fs.writeFile(
path.join("database", Date.now() + ".json"),
bookmarkStr,
err => {
callback(err);
} }
);
};
exports.get = callback => { exports.get = callback => {
console.log('get latest bookmarks') fs.readdir(path.join("database"), (err, files) => {
fs.readdir(path.join('database'), (err, files ) => { if (err) throw err;
if (err) throw err let latestFile = 0;
console.log(files)
let latestFile = 0
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
let filename = files[i] let filename = files[i];
const time = filename.substr(0, filename.length - 5) const time = filename.substr(0, filename.length - 5);
console.log(time)
if (time > latestFile) { if (time > latestFile) {
latestFile = time latestFile = time;
} }
} }
fs.readFile(path.join('database', latestFile + '.json'), (error, data) => { fs.readFile(
callback(error, JSON.parse(data.toString())) path.join("database", latestFile + ".json"),
}) "utf8",
}) (error, data) => {
callback(error, JSON.parse(data));
} }
);
});
};

9
server/index.js

@ -1,8 +1,6 @@
const express = require('express'); const express = require('express');
const cors = require('cors'); const cors = require('cors');
const app = express(); const app = express();
const path = require('path');
const fs = require('fs');
const port = 3000; const port = 3000;
const SUCCESS_CODE = 0 const SUCCESS_CODE = 0
@ -25,6 +23,7 @@ app.get('/', (req, res) => {
}) })
app.get('/bookmarks', function (req, res) { app.get('/bookmarks', function (req, res) {
console.log('donwload bookmarks')
db.get((error, bookmarkArray) => { db.get((error, bookmarkArray) => {
if (error) throw error if (error) throw error
res.json({ res.json({
@ -35,13 +34,11 @@ app.get('/bookmarks', function (req, res) {
}) })
app.post('/bookmarks', function (req, res) { app.post('/bookmarks', function (req, res) {
const bookmarkArray = req.body db.save(req.body, function(err) {
db.save(bookmarkArray, err => {
if (err) throw err
})
res.json({ res.json({
code: SUCCESS_CODE code: SUCCESS_CODE
}) })
}) })
})
app.listen(port, () => { console.log("server is running at port " + port) }) app.listen(port, () => { console.log("server is running at port " + port) })

Loading…
Cancel
Save