From de67660b0e6cec8e5dc57251bca570a47850b876 Mon Sep 17 00:00:00 2001 From: Alvis Date: Tue, 3 Mar 2020 16:09:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E4=B9=A6=E7=AD=BE?= =?UTF-8?q?=E4=BB=8E=E6=9C=8D=E5=8A=A1=E5=99=A8=E6=81=A2=E5=A4=8D=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extension/popup.html | 5 +- extension/popup.js | 333 +++++++++++++++++++++++++++++++++++++------ server/db.js | 85 ++++++----- server/index.js | 13 +- 4 files changed, 342 insertions(+), 94 deletions(-) diff --git a/extension/popup.html b/extension/popup.html index 82363e9..5b63458 100644 --- a/extension/popup.html +++ b/extension/popup.html @@ -7,8 +7,9 @@ Document - - + + + \ No newline at end of file diff --git a/extension/popup.js b/extension/popup.js index d2f4666..970ed87 100644 --- a/extension/popup.js +++ b/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() { /** * id ( string ) @@ -38,63 +132,201 @@ function Node() { * 是否为根节点 */ this.root = false; + + /** + * 子节点数组 + */ + this.children = undefined; } let BookmarkTreeNodeList = []; const BOOKMARK = 0; -const BOOKEMAR_FOLDER = 1; -const PROTO = 'http' -const SERVER_ADDRESS = '127.0.0.1' -const SERVER_PORT = '3000' -const SERVER_URL = `${PROTO}://${SERVER_ADDRESS}:${SERVER_PORT}` - -function addToList(node) { - 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.dateGroupNodified === "undefined" ? BOOKMARK : BOOKEMAR_FOLDER; - bookmarkNode.root = typeof node.parentId === "undefined"; - BookmarkTreeNodeList.push(bookmarkNode); - - if (Array.isArray(node.children)) { - for (let j = 0; j < node.children.length; j++) { - addToList(node.children[j]); +const BOOKEMARK_FOLDER = 1; +const PROTO = "http"; +const SERVER_ADDRESS = "127.0.0.1"; +const SERVER_PORT = "3000"; +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]; + + 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; + } + + if (!Array.isArray(localMap[depth])) { + localMap[depth] = []; + } + localMap[depth].push(bookmarkNode); + + if ( + bookmarkNode.type === BOOKEMARK_FOLDER && + Array.isArray(node.children) && + node.children.length > 0 + ) { + for (let j = 0; j < node.children.length; j++) { + let childNode = node.children[j]; + let childTree = await api.getSubTreeAsync(childNode.id); + await addToMap(childTree, depth + 1); + } + } } } + + await addToMap(localTree, 0); + return localMap; } -function getBookmarkList(callback) { - BookmarkTreeNodeList = [] - chrome.bookmarks.getTree(function(tree) { - for (let i = 0; i < tree.length; i++) { - let node = tree[i]; - addToList(node); +/** + * 获得浏览器书签的数组 + */ +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) + } } - callback(null, BookmarkTreeNodeList); - }); + } + + await addToList(localTree); + return localList; } -$("#upload").on("click", function() { - getBookmarkList(function(error, bookmarkArray) { - $.ajax({ - type: "POST", - url: `${SERVER_URL}/bookmarks`, - contentType: 'application/json;charset=utf-8', - dataType: 'json', - data: JSON.stringify(bookmarkArray), - success: function() { - console.log('上传了 ' + bookmarkArray.length + ' 条书签') - }, - error: function() { - console.error('上传失败') +/** + * 数组转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(); +} + +async function restore(remoteBookmarkArray) { + function getNewbookmarkId(array, parentId) { + for (let i = 0; i < array.length; i++) { + if (array[i].id === parentId) { + return array[i].newId; + } + } + } + + 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", async () => { + let bookmarkArray = await getBookmarkList(); + $.ajax({ + type: "POST", + url: `${SERVER_URL}/bookmarks`, + contentType: "application/json;charset=utf-8", + dataType: "json", + data: JSON.stringify(bookmarkArray), + success: function() {}, + error: function() {} }); }); @@ -102,8 +334,15 @@ $("#download").on("click", function() { $.ajax({ type: "GET", url: `${SERVER_URL}/bookmarks`, - success: function(bookmarkArray) { - console.log('下载了 ' + bookmarkArray.length + ' 条书签') + success: function(result) { + if (result.code === 0) { + restore(result.data); + } } }); }); + +$("#test").on("click", async () => { + let array = await getBookmarkList(); + console.log(array); +}); diff --git a/server/db.js b/server/db.js index d943dcc..610b477 100644 --- a/server/db.js +++ b/server/db.js @@ -1,7 +1,8 @@ // const MongoClient = require('mongodb').MongoClient -const assert = require('assert') -const fs = require('fs') -const path = require('path') +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); +const crypto = require('crypto'); // const DATABASE_NAME = 'bookmarksdb' // const DATABASE_SERVER = '192.168.31.201' @@ -28,41 +29,51 @@ const path = require('path') // } exports.connect = () => { - // const client = new MongoClient(DATABASE_URL) - // client.connect((error) => { - // assert.equal(null, error) - // const db = client.db(DATABASE_NAME) - // console.log('connect ' + DATABASE_URL + ' successfully') - // inertData(db, result => { - // client.close() - // }) - // }) -} + // const client = new MongoClient(DATABASE_URL) + // client.connect((error) => { + // assert.equal(null, error) + // const db = client.db(DATABASE_NAME) + // console.log('connect ' + DATABASE_URL + ' successfully') + // inertData(db, result => { + // client.close() + // }) + // }) +}; exports.save = (bookmarkArray, callback) => { - console.log('save data') - const bookmarkStr = JSON.stringify(bookmarkArray) - fs.writeFile(path.join('database', Date.now() + '.json'), bookmarkStr, err => { - callback(err) - }); -} + const bookmarkStr = JSON.stringify(bookmarkArray); + // 计算hash + const hash = crypto + .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 => { - console.log('get latest bookmarks') - fs.readdir(path.join('database'), (err, files ) => { - if (err) throw err - console.log(files) - let latestFile = 0 - for (let i = 0; i < files.length; i++) { - let filename = files[i] - const time = filename.substr(0, filename.length - 5) - console.log(time) - if (time > latestFile) { - latestFile = time - } - } - fs.readFile(path.join('database', latestFile + '.json'), (error, data) => { - callback(error, JSON.parse(data.toString())) - }) - }) -} \ No newline at end of file + fs.readdir(path.join("database"), (err, files) => { + if (err) throw err; + let latestFile = 0; + for (let i = 0; i < files.length; i++) { + let filename = files[i]; + const time = filename.substr(0, filename.length - 5); + if (time > latestFile) { + latestFile = time; + } + } + fs.readFile( + path.join("database", latestFile + ".json"), + "utf8", + (error, data) => { + callback(error, JSON.parse(data)); + } + ); + }); +}; diff --git a/server/index.js b/server/index.js index bf0c108..acf2862 100644 --- a/server/index.js +++ b/server/index.js @@ -1,8 +1,6 @@ const express = require('express'); const cors = require('cors'); const app = express(); -const path = require('path'); -const fs = require('fs'); const port = 3000; const SUCCESS_CODE = 0 @@ -25,6 +23,7 @@ app.get('/', (req, res) => { }) app.get('/bookmarks', function (req, res) { + console.log('donwload bookmarks') db.get((error, bookmarkArray) => { if (error) throw error res.json({ @@ -35,12 +34,10 @@ app.get('/bookmarks', function (req, res) { }) app.post('/bookmarks', function (req, res) { - const bookmarkArray = req.body - db.save(bookmarkArray, err => { - if (err) throw err - }) - res.json({ - code: SUCCESS_CODE + db.save(req.body, function(err) { + res.json({ + code: SUCCESS_CODE + }) }) })